Memory-mapped registers¶
Level: 301 · deep dive
One line: On a microcontroller a peripheral is an address — writing a bit to a fixed location turns a pin on — so the low-level half of Rust without std is raw-pointer reads and writes the optimiser must not touch, wrapped as soon as possible in a crate generated from the chip's own register description.
Stub — an outline, not a lesson. There is no runnable example behind this page yet, so nothing on it has been through the check that backs every other claim in this library. The bullets below are the questions the finished page has to answer.
What it has to cover¶
- Why
*ptr = valueis not enough:ptr::write_volatile↗ andptr::read_volatile↗ (stable 1.9.0) are documented as externally observable events, guaranteed to not be elided or reordered by the compiler. What would the optimiser otherwise do to two writes of different values to the same address with no read between them? - What the volatile functions do not promise, from the same 1.98.0 docs: the access is not atomic and cannot be used for synchronisation, and a value wider than the target's natural load size may be split into several hardware loads in an unspecified way.
- Making the pointer: for memory outside any Rust allocation the docs say "the provenance of the pointer is irrelevant, and it can be created with
without_provenance" (ptr::without_provenance_mut↗). A plain0x4000_0000usize as *mut u32also compiles without a warning on 1.98.0 — what does the allow-by-defaultimplicit_provenance_castslint say about that cast? - A register block as a
#[repr(C)]struct laid over the base address, and the packed case: a&to a field of a#[repr(C, packed)]struct iserror[E0793]: reference to field of packed struct is unaligned, while&raw constand&raw mut↗ (stable 1.82.0) make a pointer without ever making the reference (both checked on 1.98.0). - Why even an aligned
&RegisterBlockis questionable: a shared reference asserts the memory does not change behind it, and a status register changes on its own. What do register crates hold instead? - The crate layers the Embedded Rust Book names: a micro-architecture crate (
cortex-m), a peripheral access crate (PAC) generated bysvd2rust↗ from the vendor's SVD file, a HAL crate, and a board crate. What does a PAC'smodify(|r, w| …)closure buy over hand-written bit masks? modifyis read, compute, write. What happens when an interrupt handler writes the same register between the read and the write — the race condition ↗ with no second thread in sight?- What a checkable example can show without a board: a
staticarray standing in for the peripheral region, compiled by barerustc— the API and the refusals, and nothing about the hardware.
The trap it exists for¶
Treating a register like a variable. A write that the program never reads back looks dead to an optimiser, a read of a status bit looks predictable, and a read-modify-write looks like one operation. Each of those assumptions is true for RAM and false for a peripheral, and none of them produces a compiler warning.
Where this sits¶
What unsafe turns off covers the dereference permission in general and What a union is covers layout tricks; this page covers only the addresses that are hardware. Hardware you cannot misuse is the safe API built on top of these pointers.
See also¶
- What
unsafeturns off — dereferencing a raw pointer is one of the five powers - What an address shows — addresses in ordinary memory, for contrast
- Meet the byte — the unit every register width is counted in
- Hardware you cannot misuse — the typed layer over these registers
- What the optimizer does — the pass that would delete a non-volatile write
- The Embedded Rust Book: Memory Mapped Registers ↗
- Byte order on the wire ↗ — the same bit-layout discipline, for bytes leaving the chip
If you are coming from another language¶
- C.
volatile uint32_t *reg = (uint32_t *)0x40000000;is the idiom, andvolatileis a property of the type that every access through it inherits. In Rust volatility belongs to the operation:read_volatileandwrite_volatileare explicit calls, so an ordinary*regthrough the same pointer is not volatile. The Rust docs also spell out what the volatile calls do not give you: atomicity, or a guarantee of one hardware load per call. - C++. The same
volatilequalifier, and since C++20 some uses of it are deprecated — among them++on a volatile lvalue (cppreference ↗), exactly the read-modify-write that hides two register accesses in one token. With Rust's volatile API there is no such token: the read and the write are two named calls.
Po polsku¶
Na mikrokontrolerze urządzenie peryferyjne to po prostu adres w pamięci — rejestr mapowany w pamięci (memory-mapped register). Zapis i odczyt robi się przez ptr::write_volatile i ptr::read_volatile, które kompilator obiecuje wykonać dokładnie tak, jak je napisano, bez usuwania „zbędnych” zapisów; nie są jednak atomowe. W praktyce adresów prawie nikt nie wpisuje ręcznie: crate typu PAC generuje svd2rust z pliku SVD producenta układu, a nad nim leżą HAL i crate płytki.
Szukaj po polsku: rejestry mapowane w pamięci · wskaźnik ulotny volatile · rust read_volatile write_volatile · svd2rust peripheral access crate · rust raw const packed field