macro_rules! bsp_pins {
    (
        $(
            $( #[$id_cfg:meta] )*
            $Id:ident {
                $( #[$name_doc:meta] )*
                $( name: $name:ident $(,)? )?
                $(
                    aliases: {
                        $(
                            $( #[$alias_cfg:meta] )*
                            $Mode:ident: $Alias:ident $(,)?
                        )+
                    }
                )?
            } $(,)?
        )+
    ) => { ... };
}
Expand description

Helper macro to give meaningful names to GPIO pins

The atsamd_hal::gpio module generally refers to each Pin by its PinId. However, in the context of a BSP, pins can often be given more meaningful names. This macro gives BSP authors a convenient way to provide custom names for each pin.

Calling the macro

The bsp_pins! macro takes a series of PinId blocks. Each block starts with a PinId and is delimited by curly brackets. Within each block, there are two optional fields, name and aliases. The name field represents the principal name or function assigned to the pin and is given in snake_case. If the name field is absent, the pin name will default to its PinId (converted to snake_case). The aliases field represents any number of alternative names for the pin, where each name corresponds to the pin in a particular PinMode. Note that each alias is given in PascalCase.

The example below defines a name and two aliases for pin PA24. In PinMode AlternateC, the pin is used as an SPI MOSI pin. In PinMode AlternateD, it is used as a UART TX pin. In both cases, it is a serial output, so its name is serial_out.

atsamd_hal::bsp_pins!(
    PA24 {
        name: serial_out,
        aliases: {
            AlternateC: SpiMosi,
            AlternateD: UartTx,
        }
    }
);

Expanding the macro

When expanded, the bsp_pins! macro will define a number of structs, type aliases, constants and macros.

A new Pins struct

First, it will define a new, more-useful Pins struct. The Pins struct defined in the gpio module is intended for general use. It contains all the pins for a given chip, and each pin is named according to its PinId. The Pins struct defined by this macro, on the other hand, contains only the declared pins, and each pin is named appropriately.

The field name for each pin within the Pins struct is based on the macro name field. For example, the serial_out pin from the example above could be accessed like this:

let mut peripherals = pac::Peripherals::take().unwrap();
let pins = bsp::Pins::new(peripherals.PORT);
let out = pins.serial_out;

However, that is not the only way to access each pin. While the name field represents the principal name, each pin can also be accessed using its corresponding aliases.

In Rust, each struct field can only have one name. To provide access to the same struct field using several different names, the bsp_pins! macro defines another macro, pin_alias!. Based on the example above, we could use the pin_alias! macro to access pin PA24 without ever referring to the serial_out field.

let mut peripherals = pac::Peripherals::take().unwrap();
let pins = bsp::Pins::new(peripherals.PORT);
let mosi = pin_alias!(pins.spi_mosi);

Note that the SpiMosi alias was translated to snake_case when accessing the Pins field. The same is true for the UartTx alias.

let mut peripherals = pac::Peripherals::take().unwrap();
let pins = bsp::Pins::new(peripherals.PORT);
let tx = pin_alias!(pins.uart_tx);

Type aliases

Next, the macro defines several useful type aliases for each pin. It provides aliases for the corresponding PinId, PinMode and fully specified Pin type of each alternate name.

The example above would exand to

pub type SpiMosi = Pin<PA24, AlternateC>;
pub type SpiMosiId = PA24;
pub type SpiMosiMode = AlternateC;

pub type UartTx = Pin<PA24, AlternateD>;
pub type UartTxId = PA24;
pub type UartTxMode = AlternateD;

Each PascalCase alias provided in the macro is used for the Pin type, and the suffixes Id and Mode are appended to for the corresponding PinId and PinMode types.

DYN constants

Although the pin API is more common, there are use cases for the type-erased, dyn_pin API as well. The bsp_pins! macro also defines some useful constants for these cases. In particular, it defines DynPinId and DynPinMode constants for each alias.

The example above would effectively expand to

pub const SPI_MOSI_ID: DynPinId = DynPinId { group: DynGroup::A, num: 24 };
pub const SPI_MOSI_MODE: DynPinMode = DYN_ALTERNATE_C;

pub const UART_TX_ID: DynPinId = DynPinId { group: DynGroup::A, num: 24 };
pub const UART_TX_MODE: DynPinMode = DYN_ALTERNATE_D;

The PascalCase alias provided in the macro is converted to SCREAMING_CASE, and the suffixes _ID and _MODE are appended for the corresponding DynPinId and DynPinMode constants.

Attributes and documentation

BSP authors can also add attributes and documentation to various parts of the macro declaration. Attributes can be added to the entire PinId block. These attributes will be propagated to every use of the corresponding PinId. Attributes applied to each alias, on the other hand, will only be propagated to items specific to that alias, like the corresponding DYN constants. Finally, any documentation (or other attributes) provided for the name field will be propagated to the corresponding field of the bsp::Pins struct defined by this macro.

atsamd_hal::bsp_pins!(
    #[cfg(feature = "has_pin_PA24")]
    PA24 {
        /// Documentation that will appear on the corresponding field in the
        /// `bsp::Pins` struct
        name: serial_out,
        aliases: {
            #[cfg(feature = "uses_SPI")]
            AlternateC: SpiMosi,
            #[cfg(feature = "uses_UART")]
            AlternateD: UartTx,
        }
    }
);