# Embedded Patterns ## Mental Model Embedded Development All Follows a Similar Pattern **Instantiate → Configure → Control** We will adopt this as a Mental Model to navigate the Rust Ecosystem --- ## Peripheral Singletons
**Each Block is a Peripheral Singleton**
```rust let peripherals = Peripherals::take().unwrap(); ```
--- ## Instantiation
PAC Singletons are Promoted (passed to) to HAL abstraction instances
--- ## GPIO can be Tricky
Some HALs (Ex. esp-hal) provide abstractions at the **pin level**, not the GPIO block level
--- ## GPIO Instantiation Examples How three different HALs instantiate a GPIO output pin:
**rp2040-hal** ```rust let pac = pac::Peripherals::take() .unwrap(); let sio = Sio::new(pac.SIO); let pins = Pins::new( pac.IO_BANK0, pac.PADS_BANK0, sio.gpio_bank0, &mut pac.RESETS, ); let led = pins.gpio25 .into_push_pull_output(); ```
**esp-hal** ```rust let peripherals = esp_hal::init( Config::default() ); let led = Output::new( peripherals.GPIO0, Level::High, OutputConfig::default(), ); ```
**stm32f4xx-hal** ```rust let pac = Peripherals::take() .unwrap(); let gpioa = pac.GPIOA.split(); let led = gpioa.pa5 .into_push_pull_output(); ```
--- ## Configure The instance gives us **configure** and **control** methods
**rp2040-hal** ```rust let button = pins.gpio15 .into_pull_up_input(); ```
**esp-hal** ```rust let button = Input::new( peripherals.GPIO9, InputConfig::default() .with_pull(Pull::Up), ); ```
**stm32f4xx-hal** ```rust let button = gpioa.pa0 .into_pull_up_input(); ```
--- ## Control
**rp2040-hal** ```rust if button.is_high().unwrap() { led.set_high().unwrap(); } ```
**esp-hal** ```rust if button.is_high() { led.set_high(); } ```
**stm32f4xx-hal** ```rust if button.is_high() { led.set_high(); } ```
--- ## Things to Note - HAL Peripheral instances are **drivers for microcontroller peripheral blocks** - Off-controller device drivers (e.g. an I2C sensor) are a similar concept. They are instances that use microcontroller peripheral drivers to provide abstraction. Same concept as how peripherals use the PAC. - Configuration is sometimes passed as an argument to the **instantiation** statement rather than as a separate step - HALs often implement **embedded-hal** traits to provide device drivers with a common interface across controllers --- ## embedded-hal in Practice
**esp-hal** ```rust use bme280::Bme280; use esp_hal::i2c::master::I2c; let i2c = I2c::new( peripherals.I2C0, I2cConfig::default(), ); let mut sensor = Bme280::new(i2c); ```
**stm32f4xx-hal** ```rust use bme280::Bme280; use stm32f4xx_hal::i2c::I2c; let i2c = I2c::new( pac.I2C1, (scl, sda), 100.kHz(), &clocks, ); let mut sensor = Bme280::new(i2c); ```
**rp2040-hal** ```rust use bme280::Bme280; use rp2040_hal::I2C; let i2c = I2C::i2c0( pac.I2C0, sda_pin, scl_pin, 100.kHz(), &mut pac.RESETS, &clocks, ); let mut sensor = Bme280::new(i2c); ```
The Embedded Rustacean · Rust Week 2026