Zig 中让我兴奋的众多特性之一,就是能够使用 packed struct 来进行 MMIO。
C 语言中常见的位移操作总是显得愚蠢且容易出错。

目前,我终于有了一个具体的用例,让我有充分的理由来尝试这些功能。因此我正在生成自己的寄存器映射,但现在我意识到自己不知怎么遗漏了 Zig 文档的这一部分:

pub const GpioRegister = packed struct(u8) {
    GPIO0: bool,
    GPIO1: bool,
    GPIO2: bool,
    GPIO3: bool,
    reserved: u4 = 0,
};

const gpio: *volatile GpioRegister = @ptrFromInt(0x0123);

pub fn writeToGpio(new_states: GpioRegister) void {
    // Example of what not to do:
    // BAD! gpio.GPIO0 = true; BAD!

    // Instead, do this:
    gpio.* = new_states;
}

我之前并没有意识到这一点。这似乎有些烦人。

我发现了这篇有用的帖子:MMIO access restriction using meta programming - #7 by lufe,但这让我有点……失望?

我喜欢 microzig 的方法,它看起来通用且健壮,但(除非我遗漏了什么重要的东西)这确实会破坏字段的自动补全。我需要处理一个庞大的寄存器映射,而直接的自动补全 / 使用文档注释来描述寄存器和位域,是绝对的核心特性。

有没有人了解更多关于为什么目前是这种状态的原因?我不明白为什么在上例中,volatile 指针的原子读写不是默认行为。

floooh July 22, 2026, 8:16am 2

Hmm… I would think that gpio.GPI0 = true; is identical with gpio |= GPI0 (assuming that GPI0 is a bit mask), or gpio |= (1<< GPI0) (assuming that GPI0 is the bit position).

If the memory-mapped register allows read-write that should be fine? But sometimes memory-mapped registers are write-only, or they trigger some action when read (like clearing the register), and in that case that pattern would break of course because these are read-modify-write operations. My only experience with MMIO is old-school home computers and emulators though, not modern embedded systems.

FWIW, for similar things in my emulator code I also tinkered with packed structs, but then went back to regular integer bit twiddling, just because the set of operators if ‘richer’ than what packed-structs allow (e.g. &, |, ^ on multiple bits, or <<, >> don’t work on packed structs without casting to the backing integer and back).