Cheatsheet

Print this. Tape it to your monitor. Refer to it whenever you're working with a new peripheral.


The Pattern: Create → Configure → Control

1. CREATE   → Find the struct, call new() or use the builder
2. CONFIGURE → Find the Config struct, explore the enums
3. CONTROL  → Find the trait impls and inherent methods

docs.rs Navigation

I Need To...Go To...
Find a cratecrates.io → search
Read crate docsdocs.rs/CRATE_NAME
ESP32-C3 specific docsdocs.espressif.com/projects/rust/esp-hal/latest/esp32c3/esp_hal/
See examplesGitHub repo → examples/ directory
Find a driver cratecrates.io → search sensor name

On a Struct Page

  1. Top section — struct definition, what it is
  2. impl StructName — constructors (new()) and inherent methods
  3. impl Trait for StructName — portable methods (embedded-hal)
  4. Associated types — click through to Config structs, enums

GPIO Quick Reference

#![allow(unused)]
fn main() {
// Output
let mut led = Output::new(pin, Level::Low, OutputConfig::default());
led.set_high();
led.set_low();
led.toggle();

// Input
let button = Input::new(pin, InputConfig::default().with_pull(Pull::Up));
let pressed: bool = button.is_low();
let level: Level = button.get_level();
}

Configuration Options

TypeKey Variants
LevelHigh, Low
PullUp, Down, None
DriveStrength_5mA, _10mA, _20mA, _40mA
OutputConfig.with_drive_strength(), .with_open_drain()

I2C Quick Reference

#![allow(unused)]
fn main() {
// Setup
let i2c = I2c::new(peripherals.I2C0, Config::default())
    .with_sda(sda_pin)
    .with_scl(scl_pin);

// Bus scan
for addr in 0x01..=0x7F {
    if i2c.write(addr, &[]).is_ok() {
        println!("Found device at 0x{:02X}", addr);
    }
}

// Using a driver crate
let mut sensor = DriverCrate::new(i2c, address);
let reading = sensor.read_value().unwrap();
}

Configuration

#![allow(unused)]
fn main() {
let config = Config::default()
    .with_frequency(Rate::from_khz(400));  // Standard: 100, Fast: 400
}

Interrupts Quick Reference

#![allow(unused)]
fn main() {
use core::cell::RefCell;
use critical_section::Mutex;
use core::sync::atomic::{AtomicBool, Ordering};

// Shared state
static BUTTON: Mutex<RefCell<Option<Input>>> = Mutex::new(RefCell::new(None));
static FLAG: AtomicBool = AtomicBool::new(false);

// Setup: configure interrupt and move into shared state
button.listen(Event::FallingEdge);
critical_section::with(|cs| {
    BUTTON.borrow_ref_mut(cs).replace(button);
});

// Handler
#[handler]
fn gpio_handler() {
    critical_section::with(|cs| {
        if let Some(button) = BUTTON.borrow_ref_mut(cs).as_mut() {
            button.clear_interrupt();  // MUST clear!
        }
    });
    FLAG.store(true, Ordering::Relaxed);
}

// Main loop
loop {
    if FLAG.swap(false, Ordering::Relaxed) {
        led.toggle();
    }
}
}

Event Triggers

TriggerFires When
Event::FallingEdgeHIGH → LOW
Event::RisingEdgeLOW → HIGH
Event::AnyEdgeAny transition
- Add `unstable` feature to esp-hal in `Cargo.toml`
- Always call `clear_interrupt()` in the handler
- Keep handlers short — set a flag, handle in main loop

Abstraction Layers

BSP ──────────── board.led(), board.i2c()
Driver Crate ─── SensorDriver::new(impl I2c, addr)
embedded-hal ─── trait I2c, trait OutputPin
HAL (esp-hal) ── I2c::new(), Output::new()
PAC ──────────── Raw registers (auto-generated)

Common Commands

# Create a new project
esp-generate --chip esp32c3 project-name

# Build
cargo build --release

# Flash and monitor
espflash flash target/riscv32imc-unknown-none-elf/release/PROJECT --monitor

# Monitor only (already flashed)
espflash monitor

# Add a dependency
cargo add crate-name

ResourceURL
esp-hal docs (ESP32-C3)docs.espressif.com/projects/rust/esp-hal/latest/esp32c3/esp_hal/
crates.iocrates.io
docs.rsdocs.rs
esp-hal GitHubgithub.com/esp-rs/esp-hal
The Embedded Rustaceantheembeddedrustacean.com
Embassy (async)embassy.dev