imxrt-rs/imxrt-hal

Rethink how we accomplish pin muxing

mciantyre opened this issue · 1 comments

If you want to configure peripherals that require processor pads, you need to set the pad into the correct 'alternative,' or mode. Pads have multiple alternatives, depending on their usage. (This is also called pin muxing.) The current approach puts the responsibility on users to know what alternative is necessary for a given peripheral. Although this could be identified from the method signatures, it's more overhead, and the user's code isn't as clean. We should be able to simplify this API.

Consider the initialization of a UART peripheral, using Teensy pins 14 and 15. The snippet below describes how it happens today.

let mut uart = uarts
        .uart2
        .init(
            peripherals.pins.p14.alt2(),
            peripherals.pins.p15.alt2(),
            BAUD,
        )
        .unwrap();

See those calls to alt2()? Those set the pins into the correct alternative for UART. They're required because the signature if init() looks like

pub fn init<TX, RX>(
        self,
        mut tx: TX,
        mut rx: RX,
        baud: u32,
    ) -> Result<UART<M>, ccm::uart::TimingsError>
    where
        TX: uart::Pin<Direction = uart::TX, Module = M>,
        RX: uart::Pin<Direction = uart::RX, Module = M>,

and the uart::Pin implementations resemble

impl uart::Pin for GPIO_AD_B1_02<Alt2> {
  type Direction = uart::TX;
  type Module = uart::module::_2;
}

impl uart::Pin for GPIO_AD_B1_03<Alt2> {
  type Direction = uart::RX;
  type Module = uart::module::_2;
}

If we follow that through, we see that GPIO_AD_B1_02 can only be the TX pin for UART 2 when it's in the second alternative. Same applies for GPIO_AD_B1_03 as an RX pin. The only way to get a pad in that state is to call alt2().

I want to remove the requirement for users to set the alternative. Remove the calls to alt2() so that the snippet simplifies to

let mut uart = uarts
        .uart2
        .init(
            peripherals.pins.p14, // no more alt2() call
            peripherals.pins.p15, // no more alt2() call
            BAUD,
        )
        .unwrap();

A solution should not sacrifice compile-time checking for a convenient API. You cannot provide the wrong pin to the UART module; if you tried to pass in p13, rather than p14, for the TX pin, it fails to compile. You also cannot put the pin into the wrong alternative; a call of alt3(), rather than alt2(), fails to compile. We should keep these kinds of guarantees in a new approach.

This will probably require changes throughout the HAL, including in the IOMUXC modules, and all peripheral modules that depend on processor pins.

(Manually transferred from mciantyre/teensy4-rs#46)

perhaps something like...

impl Uart<N: UartInstance> {
   pub fn init<RX: Into<uart::Rx<N>>, TX: Into<uart::Tx<N>>>(rx: RX, tx: TX, config: UartConfig) {
   }
}

With a macro to implement Into for that uart instance and pin pair, along with the required alt. Perhaps a const defined PinConfig assuming my PR is liked for that stuff.

uart_tx_into!(instance, padx, alt6);
uart_rx_into!(instance, padx, alt7);

These macros could also configure the pad as needed (drive, speed, etc) which would be really quite nice. Its something that bit me in the stm32 hal using the i2c interface they have, you need to setup pin options and alt modes (I only did alt mode) before passing the pins in. It wasn't obvious and there's a bug for it.