no_std Rust

Standard Rust programs depend on std, which assumes an OS providing heap allocation, threads, I/O, and networking. Embedded targets have no OS. Rust supports this through the #![no_std] attribute, which restricts you to the core library (the subset of Rust that needs no OS: types, traits, iterators, Option, Result, slices, closures) and optionally alloc (if you provide a heap allocator).

A no_std embedded binary also needs #![no_main] because there’s no OS to call fn main(). Instead, a framework (Embassy, RTIC, or cortex-m-rt) provides the entry point and vector table.

Embassy: Async Runtime + HAL

Embassy is an embedded framework for Rust that provides two things: an async executor (a cooperative task scheduler that replaces FreeRTOS) and hardware abstraction layers for multiple chip families. The async model maps naturally onto embedded work — “wait for this SPI transfer to complete” and “sleep for 4 hours” become .await points that yield the CPU to other tasks or put the chip into a low-power state.

embassy-stm32

The embassy-stm32 crate provides typed, safe access to STM32 peripherals including the H743. Peripheral instances are singleton resources — you get p.SPI1 exactly once from embassy_stm32::init(), and passing it to Spi::new() consumes it. This prevents two pieces of code from configuring the same peripheral simultaneously, which is a compile-time guarantee that C HALs cannot provide.

Both blocking and async APIs are available. The async SPI API uses DMA under the hood — when you .await a transfer, the executor puts the task to sleep and a DMA interrupt wakes it when the transfer completes. No busy-waiting.

embassy-time

Provides Timer::after_secs(), Timer::after_millis(), etc. On STM32, this is backed by a hardware timer peripheral. In low-power configurations, Embassy can enter Stop mode during long waits and wake on the timer interrupt.

probe-rs and defmt

probe-rs is a Rust-native tool that communicates with debug probes (ST-LINK, J-Link, CMSIS-DAP). It handles flashing firmware, resetting the chip, and hosting RTT (Real-Time Transfer) channels. Your cargo run invocation can be configured to flash the binary and immediately open an RTT console for log output.

defmt (deferred formatting) is a logging framework designed for embedded. Instead of formatting strings on the microcontroller (expensive: requires alloc or large stack buffers), defmt sends compact binary tokens over RTT to the host, where probe-rs decodes them into human-readable strings. A defmt::info!("temperature: {}", temp) call on the MCU costs only a few bytes of flash and nanoseconds of CPU time.

# Cargo.toml (key dependencies)
[dependencies]
embassy-executor = { version = "0.7", features = ["arch-cortex-m", "executor-thread"] }
embassy-stm32 = { version = "0.3", features = ["stm32h743zi", "time-driver-any"] }
embassy-time = "0.4"
defmt = "0.3"
defmt-rtt = "0.4"
epd-waveshare = "0.6"          # sync driver; or:
# epd-waveshare-async = "0.1"  # async driver for Embassy
 
[build-dependencies]
# probe-rs is installed as a CLI tool, not a build dependency

Toolchain Setup

# Install the Arm Cortex-M7 target (hard float)
rustup target add thumbv7em-none-eabihf
 
# Install probe-rs CLI tools
cargo install probe-rs-tools
 
# Create a new project
cargo init --name sentence-display
# Add .cargo/config.toml:
# .cargo/config.toml
[target.thumbv7em-none-eabihf]
runner = "probe-rs run --chip STM32H743ZITx"
 
[build]
target = "thumbv7em-none-eabihf"

With this configuration, cargo run --release compiles the firmware, flashes it to the Nucleo via the ST-LINK, and opens an RTT console for defmt output.

Skeleton: E-Paper Sentence Display on Nucleo-H743ZI2

#![no_std]
#![no_main]
 
use embassy_executor::Spawner;
use embassy_stm32::gpio::{Level, Output, Speed, Input, Pull};
use embassy_stm32::spi::{Config as SpiConfig, Spi};
use embassy_stm32::time::Hertz;
use embassy_time::Timer;
use defmt_rtt as _;        // links the RTT transport
use panic_probe as _;       // panics print via probe-rs
 
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    let p = embassy_stm32::init(Default::default());
 
    // SPI1: PA5 = SCK, PA7 = MOSI, PA6 = MISO (unused for e-paper)
    let mut spi_config = SpiConfig::default();
    spi_config.frequency = Hertz::mhz(4);
 
    let spi = Spi::new(
        p.SPI1,
        p.PA5,       // SCK
        p.PA7,       // MOSI
        p.PA6,       // MISO (not connected, but SPI requires it)
        p.DMA1_CH0,  // TX DMA
        p.DMA1_CH1,  // RX DMA
        spi_config,
    );
 
    // Display control pins (choose any convenient GPIO)
    let cs   = Output::new(p.PD14, Level::High, Speed::Low);
    let dc   = Output::new(p.PF12, Level::Low,  Speed::Low);
    let rst  = Output::new(p.PD15, Level::High, Speed::Low);
    let busy = Input::new(p.PF13, Pull::None);
 
    // Initialize e-paper driver (API depends on epd-waveshare version)
    // let mut display = Epd5in65f::new(spi, cs, dc, rst, busy, &mut delay);
    // display.init().await.unwrap();
 
    let sentences = [
        "Focus on what matters.",
        "Ship, then iterate.",
        "Be kind to future-you.",
        "Complexity is the enemy.",
    ];
 
    let mut idx = 0;
    loop {
        defmt::info!("Displaying sentence {}: {}", idx, sentences[idx]);
 
        // Render text into framebuffer and push to display
        // (use embedded-graphics crate for text rendering)
        // display.clear_frame().await;
        // draw_text(&mut display, sentences[idx]);
        // display.refresh().await;
 
        idx = (idx + 1) % sentences.len();
 
        // Sleep 4 hours — Embassy can enter low-power mode during this
        Timer::after_secs(4 * 60 * 60).await;
    }
}

Warning

On the STM32H743, DMA cannot access DTCM RAM. If your framebuffer or SPI buffer ends up in DTCM (the linker may place it there by default), DMA transfers will silently corrupt data. Either modify the linker script to place .bss and .data in AXI SRAM, or use #[link_section = ".axisram"] on your buffer. See STM32 and Nucleo Development for the SRAM topology.

E-Paper Driver Crates

The epd-waveshare crate (v0.6 as of early 2026) provides drivers for most Waveshare e-paper panels, including the 7-color ACeP models (5.65”, 7.3”). It implements the embedded-graphics DrawTarget trait, so you can use the embedded-graphics ecosystem for text rendering, shapes, and image display. The crate has both blocking and async variants — use epd-waveshare-async for integration with Embassy’s async SPI.

The embedded-graphics crate provides fonts (built-in bitmap fonts or load-your-own via eg-font), geometric primitives, and compositing — enough to render colored text and simple graphics into the framebuffer without pulling in a full GUI framework.