Register access layer

The imxrt-ral crate provides a register access layer (RAL) for i.MX RT 10xx and 11xx microcontrollers. Use the RAL to

  • manage peripherals as low-level resources.
  • conveniently access registers and register fields.

imxrt-ral also provides a device-specific interrupt table. Once you link the runtime with the RAL,

  • enable imxrt-rt's "device" feature.
  • enable imxrt-ral's "rt" feature.

ℹ️ The RAL is very similar to the peripheral access crate (PAC) found in other embedded Rust ecosystems. The major difference is the API used to access registers.

This example improves on the previous walkthrough by using imxrt-ral to access registers. Note the imxrt-ral feature flag for the target MCU. Also note that the "device" feature for the runtime is enabled, and the "rt" feature for imxrt-ral is enabled; even though the example doesn't use interrupts, you should prefer the device-specific vector table when available.

# Cargo.toml

[dependencies]
imxrt-ral = { version = "0.5", features = ["imxrt1011", "rt"] }
imxrt-rt = { version = "0.1", features = ["device"] }

# As before...
imxrt1010evk-fcb = "0.1"
panic-halt = "0.2"

[build-dependencies]
imxrt-rt = { version = "0.1", features = ["device"] }
//! build.rs (unchanged)

use imxrt_rt::{Family, RuntimeBuilder};

fn main() {
    RuntimeBuilder::from_flexspi(Family::Imxrt1010, 16 * 1024 * 1024)
        .build()
        .unwrap();
}
//! main.rs

#![no_main]
#![no_std]

use imxrt_ral as ral;

use imxrt1010evk_fcb as _;
use imxrt_rt::entry;
use panic_halt as _;

const LED_OFFSET: u32 = 1 << 11;

#[entry]
fn main() -> ! {
    // Safety: we're the only code that "owns" the IOMUXC and GPIO1 peripherals.
    let iomuxc = unsafe { ral::iomuxc::IOMUXC::instance() };
    let gpio1 = unsafe { ral::gpio::GPIO1::instance() };

    // Configure the pad named "GPIO_11" as a GPIO pin
    // (as opposed to a UART TX pin, for example).
    ral::write_reg!(ral::iomuxc, iomuxc, SW_MUX_CTL_PAD_GPIO_11, 5);
    // Set the GPIO as an output with a RMW operation.
    ral::modify_reg!(ral::gpio, gpio1, GDIR, |gdir| gdir | LED_OFFSET);
    // Turn on the LED.
    ral::write_reg!(ral::gpio, gpio1, DR_SET, LED_OFFSET);

    loop {}
}

Acquiring peripheral instances is still unsafe. However, macros make it easier to read, write, and modify registers; and there's no need to maintain register addresses.

Consider using imxrt-ral when you want to implement higher-level peripheral drivers. These kinds of convenient, re-usable, and portable peripheral drivers are the topic of the next section: the hardware abstraction layer.