diff --git a/build/lpc55pins/README.md b/build/lpc55pins/README.md new file mode 100644 index 000000000..7e283b428 --- /dev/null +++ b/build/lpc55pins/README.md @@ -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::()?; + 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. diff --git a/build/lpc55pins/src/lib.rs b/build/lpc55pins/src/lib.rs index 83bc3f3b2..bce963b3d 100644 --- a/build/lpc55pins/src/lib.rs +++ b/build/lpc55pins/src/lib.rs @@ -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}; @@ -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, }); @@ -55,6 +61,7 @@ pub struct PinConfig { value: Option, name: Option, pint: Option, + setup: Option, } #[derive(Copy, Clone, Debug, Default, Deserialize)] @@ -178,6 +185,10 @@ impl PinConfig { None } } + + fn call_in_setup(&self) -> bool { + self.setup.unwrap_or(true) + } } impl ToTokens for PinConfig { @@ -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, @@ -202,76 +213,153 @@ impl ToTokens for PinConfig { } } +fn pin_init( + buf: &mut BufWriter>, + p: &PinConfig, +) -> Result<(), Box> { + // 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) -> 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 = 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(), @@ -281,9 +369,20 @@ pub fn codegen(pins: Vec) -> 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(()) diff --git a/drv/lpc55-gpio-api/src/lib.rs b/drv/lpc55-gpio-api/src/lib.rs index 1b0aa5952..0cb12d6dc 100644 --- a/drv/lpc55-gpio-api/src/lib.rs +++ b/drv/lpc55-gpio-api/src/lib.rs @@ -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,