Use the embedded-hal traits as your abstraction layer and rely on board-specific crates (like esp-hal or rpi-embedded-hal) to implement them for your hardware. You define your peripherals using the IntoPin, Spi, I2c, and Uart traits, then interact with them through blocking or asynchronous drivers provided by your specific board support package.
For a concrete example, here is how you would initialize and use these interfaces on an ESP32 using the esp-hal crate in a main function:
use esp_hal::peripherals::Peripherals;
use esp_hal::gpio::{Io, Input, Output, Level};
use esp_hal::spi::master::Spi;
use esp_hal::i2c::I2c;
use esp_hal::uart::{Uart, UartRx, UartTx};
use esp_hal::delay::Delay;
use esp_hal::prelude::*;
fn main() -> ! {
let peripherals = Peripherals::take();
let system = peripherals.SYSTEM;
let clocks = esp_hal::clock::Clocks::configure(system.clock_control).freeze();
// GPIO: Toggle a pin
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
let mut led = Output::new(io.pins.gpio2, Level::Low);
let delay = Delay::new(clocks);
// SPI: Initialize master with SCK, MOSI, MISO
let sck = io.pins.gpio18;
let mosi = io.pins.gpio23;
let miso = io.pins.gpio19;
let spi = Spi::new(peripherals.SPI2, sck, mosi, miso, 100.kHz(), &clocks);
// I2C: Initialize with SDA and SCL
let sda = io.pins.gpio21;
let scl = io.pins.gpio22;
let i2c = I2c::new(peripherals.I2C0, sda, scl, 100.kHz(), &clocks);
// UART: Initialize with TX and RX
let tx = io.pins.gpio1;
let rx = io.pins.gpio3;
let mut uart = Uart::new(peripherals.UART0, tx, rx, &clocks).unwrap();
loop {
// GPIO usage
led.toggle();
delay.delay_ms(500u32);
// SPI write (blocking)
let mut tx_buf = [0u8; 4];
let mut rx_buf = [0u8; 4];
spi.write(&mut tx_buf).ok();
// I2C write (blocking)
i2c.write(0x50, &[0x00, 0x01]).ok();
// UART write
uart.write_bytes(b"Hello\n").ok();
}
}
To get this running, add the specific board crate to your Cargo.toml (e.g., esp-hal for Espressif chips or rpi-embedded-hal for Raspberry Pi Pico). The key is that you never write raw register code; instead, you map your physical pins to the embedded-hal traits. For asynchronous operations, replace the blocking write calls with .await if your runtime supports it, ensuring you configure the interrupt controller in your main setup. Always check the specific board documentation for pin multiplexing constraints, as not every GPIO pin supports every peripheral function.