Skip to content

soc_esp32::reg

Read-modify-write on a memory-mapped register, spelled once.

Sixteen sites across six files open-coded

r.write_volatile(r.read_volatile() | bits);
r.write_volatile(r.read_volatile() & !bits);

Individually trivial. Together they are the pattern in this codebase most likely to be mistyped, and the typo is nasty: & bits where & !bits was meant clears every other bit in the register. On a config register that presents as a peripheral that stopped working, several layers away from the line that did it, and the compiler has no opinion.

[crate::dport] has its own [crate::dport::modify], and these must not be used in its place. A DPORT read needs the erratum workaround — an APB pre-read with interrupts masked — and the cross-core lock. Plain volatile access to that block is the bug #56 was about.

A trait would drag hal into pointer arithmetic it has no reason to know about, and every chip’s registers are its own. Free functions in the SoC crate keep this where the chip already is.

const fn at(base: u32, offset: u32) -> *mut u32

The address of offset within a peripheral’s register block.

unsafe fn clear(r: *mut u32, bits: u32)

Clear bits, leaving the rest alone.

As [set].

unsafe fn modify(r: *mut u32, mask: u32, value: u32)

Clear mask, then set value — a field update in one read-modify-write.

value is used as given, so it must already be shifted into the field. Clearing first is what makes this idempotent: | alone can only ever add bits, so a field would accumulate every value it had ever held.

As [set].

unsafe fn read(r: *mut u32) -> u32

Read a register.

r must be a valid, aligned, memory-mapped register.

unsafe fn set(r: *mut u32, bits: u32)

Set bits, leaving the rest alone.

r must be a valid, aligned, memory-mapped register.

unsafe fn write(r: *mut u32, val: u32)

Write a register.

As [read].