Skip to content

Commit

Permalink
Document changes to LPC55 GPIO pin configuration in build/lpc55pins/R…
Browse files Browse the repository at this point in the history
…EADME.md

Feedback response: Fix idl/lpc55-pins.idol to use Option<PintSlot>.
Feedback response: Collapse the many PINT calls in lpc55-pins.idol to one `pint_op`
  • Loading branch information
lzrd committed Dec 13, 2024
1 parent ba8bb11 commit 77e17fd
Show file tree
Hide file tree
Showing 3 changed files with 223 additions and 50 deletions.
74 changes: 74 additions & 0 deletions build/lpc55pins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# LPC55 GPIO Pin configuration in `app.toml` files

Configuring LPC55 GPIO pins may require referencing the NXP UM11126 document and board schematics.

Some tasks, like `user_leds`, program the GPIO pins directly, but most
will use the API defined in `idl/lpc55-pins.idl`.

Tasks that use GPIO pins need to include a `[tasks.TASKNAME.config]` section.

For example, the `button` task in `app/lpc55xpresso/app-button.toml`
uses the red, green, and blue LEDs on the `LPCxpresso` board as well as the
user button. The LEDs are attached to GPIOs configured as outputs while
the "user" button is configured as an input with an interrupt.

The LPC55 GPIO Pin INTerrupts index is selected from the range `pint.irq0`
to `pint.irq7` and cannot be used more than once in the `app.toml`.
The task stanza's `interrupts` and the pin configuration information need to agree on the `PINT` index.

```toml
[tasks.button]
...
interrupts = { "pint.irq0" = "button-irq" }
...

[tasks.button.config]
pins = [
{ name = "BUTTON", pin = { port = 1, pin = 9}, alt = 0, pint = 0, direction = "input", opendrain = "normal" },
{ name = "RED_LED", pin = { port = 1, pin = 6}, alt = 0, direction = "output", value = true },
{ name = "GREEN_LED", pin = { port = 1, pin = 7}, alt = 0, direction = "output", value = true },
{ name = "BLUE_LED", pin = { port = 1, pin = 4}, alt = 0, direction = "output", value = true },
]
```

A notification bit corresponding to the above "button-irq" will be
generated and called `notification::BUTTON_IRQ_MASK`.

The task's `build.rs` generates GPIO support code:
```rust
let task_config = build_util::task_config::<TaskConfig>()?;
build_lpc55pins::codegen(task_config.pins)`
```

The `port`, `pin`, `alt`, `mode`, `slew`, `invert`, `digimode`, and
`opendrain` tags all represent values found in UM111126.

## Named pins

The `name` field is optional. Giving pins symbolic names in the `app.toml`
can make a task a bit more portable. Naming a pin will generate a
`pub const NAME: Pin` definition that can be used with the `lpc55-pins`
API instead referring to it as `Pin::PIOx_y`.

Naming a pin also generates a separate `setup_pinname` function that
is called by `setup_pins` unless `setup = false` is part of the pin
configuration.

Using `setup = false` and `name` allows there to be multiple
configurations for the same physical pin that can be used at different
times.

As an example, the action taken on detection of a change on an input
signal can be to change that pin to an output and drive the pin. When
the signal has been handled, the pin can be reverted to an input until
the next event.

```toml
{ name = "SP_RESET_IN", pin = { port = 0, pin = 13 }, alt = 0, direction = "input", pint = 0 },
{ name = "SP_RESET_OUT", pin = { port = 0, pin = 13 }, alt = 0, direction = "output", setup = false },
```

In the above case, `setup_pins()` will call `setup_reset_in()`
but not `setup_reset_out()`. The notification handler for
`notification::SP_RESET_IRQ_MASK` will call `setup_reset_out()` and then
`setup_reset_in()` before returning.
197 changes: 148 additions & 49 deletions build/lpc55pins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use anyhow::Result;
use proc_macro2::TokenStream;
use quote::{format_ident, ToTokens, TokenStreamExt};
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
use serde::Deserialize;
use std::io::{BufWriter, Write};

Expand All @@ -22,13 +22,19 @@ impl Pin {

(self.port, self.pin)
}

const MAX_PINS: usize = (1 << 5) + 31 + 1;

fn index(&self) -> usize {
(self.port << 5) + self.pin
}
}

impl ToTokens for Pin {
fn to_tokens(&self, tokens: &mut TokenStream) {
let (port, pin) = self.get_port_pin();
let final_pin = format_ident!("PIO{}_{}", port, pin);
tokens.append_all(quote::quote! {
tokens.append_all(quote! {
// Yes we want the trailing comma
Pin::#final_pin,
});
Expand All @@ -55,6 +61,7 @@ pub struct PinConfig {
value: Option<bool>,
name: Option<String>,
pint: Option<usize>,
setup: Option<bool>,
}

#[derive(Copy, Clone, Debug, Default, Deserialize)]
Expand Down Expand Up @@ -178,6 +185,10 @@ impl PinConfig {
None
}
}

fn call_in_setup(&self) -> bool {
self.setup.unwrap_or(true)
}
}

impl ToTokens for PinConfig {
Expand All @@ -191,7 +202,7 @@ impl ToTokens for PinConfig {
let digimode = format_ident!("{}", format!("{:?}", self.digimode));
let od = format_ident!("{}", format!("{:?}", self.opendrain));
tokens.append_all(final_pin);
tokens.append_all(quote::quote! {
tokens.append_all(quote! {
AltFn::#alt_num,
Mode::#mode,
Slew::#slew,
Expand All @@ -202,76 +213,153 @@ impl ToTokens for PinConfig {
}
}

fn pin_init(
buf: &mut BufWriter<Vec<u8>>,
p: &PinConfig,
) -> Result<(), Box<dyn std::error::Error>> {
// Output pins can specify their value, which is set before configuring
// their output mode (to avoid glitching).
let pin_tokens = p.pin.to_token_stream();
if let Some(v) = p.value {
assert!(
matches!(p.direction, Some(Direction::Output)),
"P{}_{}: can only set value for output pins",
p.pin.port,
p.pin.pin
);
writeln!(
buf,
"iocon.set_val({} {});",
pin_tokens,
if v { "Value::One" } else { "Value::Zero" }
)?;
}
if let Some(d) = p.direction {
writeln!(buf, "iocon.set_dir({} Direction::{:?});", pin_tokens, d)?;
}
Ok(())
}

pub fn codegen(pins: Vec<PinConfig>) -> Result<()> {
let out_dir = build_util::out_dir();
let dest_path = out_dir.join("pin_config.rs");
let mut file = std::fs::File::create(&dest_path)?;

let mut used_slots = 0u32;
let mut buf = BufWriter::new(Vec::new());
let mut top = BufWriter::new(Vec::new());
let mut middle = BufWriter::new(Vec::new());
let mut bottom = BufWriter::new(Vec::new());

if pins.iter().any(|p| p.name.is_some()) {
writeln!(&mut buf, "use drv_lpc55_gpio_api::Pin;")?;
}
if pins.iter().any(|p| p.pint.is_some()) {
writeln!(&mut buf, "use drv_lpc55_gpio_api::PintSlot;")?;
}
// Pins with interrupts need to be named and will have separate config functions
// that are called by the main setup function.
// The same pin must have different names if it is used with different
// configurations. All but one of the conflicting configs should have
// "setup = false" in their config specifications.
//
// The pin configuration source is organized in sections.
// - use statements
// - fn setup_pins()
// - fn setup_$named_pin()
// - constants for named pins

writeln!(&mut top, "use drv_lpc55_gpio_api::*;\n")?;
writeln!(
&mut file,
&mut top,
"fn setup_pins(task : TaskId) -> Result<(), ()> {{"
)?;
writeln!(&mut file, "use drv_lpc55_gpio_api::*;")?;
writeln!(&mut file, "let iocon = Pins::from(task);")?;
if pins.iter().any(|p| p.name.is_none() && p.call_in_setup()) {
// Some pins are initialized inline in setup_pins()
writeln!(&mut top, " let iocon = Pins::from(task);\n")?;
}

// If a task is defines alternate GPIO configurations. Ensure that not more
// than one of them is called by the setup function.
let mut conflict = [0usize; Pin::MAX_PINS];
let conflicts: Vec<String> = pins
.iter()
.filter_map(|p| {
if p.call_in_setup() {
// Just report the first conflict
let clash = conflict[p.pin.index()] == 1;
conflict[p.pin.index()] += 1;
if clash {
let (pin, port) = p.pin.get_port_pin();
Some(format!("P{}_{}", pin, port))
} else {
None
}
} else {
// Configurations not called from setup_pins() are ok.
None
}
})
.collect();
if !conflicts.is_empty() {
panic!(
"Conflicting pin configs: {:?}. Delete or use 'name=...' and setup=false'.",
conflicts);
}

for p in pins {
writeln!(&mut file, "iocon.iocon_configure(")?;
writeln!(&mut file, "{}", p.to_token_stream())?;
if let Some(slot) = p.get_pint_slot(&mut used_slots) {
writeln!(&mut file, "Some(PintSlot::Slot{}),", slot.index())?;
} else {
writeln!(&mut file, "None")?;
}
writeln!(&mut file, ");")?;

// Output pins can specify their value, which is set before configuring
// their output mode (to avoid glitching).
if let Some(v) = p.value {
assert!(
matches!(p.direction, Some(Direction::Output)),
"can only set value for output pins"
);
writeln!(&mut file, "iocon.set_val(")?;
writeln!(&mut file, "{}", p.pin.to_token_stream())?;
let pin_tokens = p.to_token_stream();
let pint_slot_config =
if let Some(slot) = p.get_pint_slot(&mut used_slots) {
let si = format_ident!("Slot{}", slot.index());
quote!(Some(PintSlot::#si))
} else {
quote!(None)
};

let setup_pin_fn = if let Some(name) = p.name.as_ref() {
let fn_name = format_ident!("setup_{}", name.to_lowercase());
writeln!(
&mut file,
"{});",
if v { "Value::One" } else { "Value::Zero" }
&mut middle,
r#"
fn {}(task: TaskId) {{
let iocon = Pins::from(task);
iocon.iocon_configure({} {});
"#,
fn_name, pin_tokens, pint_slot_config
)?;
}
match p.direction {
None => (),
Some(d) => {
writeln!(&mut file, "iocon.set_dir(")?;
writeln!(&mut file, "{}", p.pin.to_token_stream())?;
writeln!(&mut file, "Direction::{d:?}")?;
writeln!(&mut file, ");")?;
let _ = pin_init(&mut middle, &p);
writeln!(&mut middle, "}}")?;
Some(fn_name)
} else {
None
};

if p.call_in_setup() {
if let Some(fn_name) = setup_pin_fn {
writeln!(&mut top, "{}(task);", fn_name)?;
} else {
writeln!(
&mut top,
"{}",
quote!(
iocon.iocon_configure(#pin_tokens #pint_slot_config);
)
)?;
let _ = pin_init(&mut top, &p);
}
}

match p.name {
None => (),
Some(ref name) => {
let pin = p.pin.get_port_pin();
writeln!(&mut buf, "#[allow(unused)]")?;
writeln!(&mut bottom, "#[allow(unused)]")?;
writeln!(
&mut buf,
&mut bottom,
"const {}: Pin = Pin::PIO{}_{};",
&name, pin.0, pin.1
)?;

let mut ignore = 0u32;
if let Some(slot) = p.get_pint_slot(&mut ignore) {
writeln!(&mut buf, "#[allow(unused)]")?;
writeln!(&mut bottom, "#[allow(unused)]")?;
writeln!(
&mut buf,
&mut bottom,
"pub const {}_PINT_SLOT: PintSlot = PintSlot::Slot{};",
&name,
slot.index(),
Expand All @@ -281,9 +369,20 @@ pub fn codegen(pins: Vec<PinConfig>) -> Result<()> {
}
}

writeln!(&mut file, "Ok(())")?;
writeln!(&mut file, "}}")?;
write!(file, "{}", String::from_utf8(buf.into_inner()?).unwrap())?;
writeln!(&mut top, "Ok(())")?;
writeln!(&mut top, "}}")?;

writeln!(file, "{}", String::from_utf8(top.into_inner()?).unwrap())?;
writeln!(
file,
"\n{}",
String::from_utf8(middle.into_inner()?).unwrap()
)?;
writeln!(
file,
"\n{}",
String::from_utf8(bottom.into_inner()?).unwrap()
)?;
call_rustfmt::rustfmt(&dest_path)?;

Ok(())
Expand Down
2 changes: 1 addition & 1 deletion drv/lpc55-gpio-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ pub enum AltFn {
Alt9 = 9,
}

#[derive(Copy, Clone, Debug, FromPrimitive, AsBytes)]
#[derive(Copy, Clone, Debug, FromPrimitive, AsBytes, PartialEq)]
#[repr(u32)]
pub enum Direction {
Input = 0,
Expand Down

0 comments on commit 77e17fd

Please sign in to comment.