欢迎光临
我们一直在努力

第 1 章:寄存器、触发器与 Memory‑Mapped I/O(硬件基础)

(本系列从硬件 → 架构 → 中断 → 上下文 → OS → 车规 OS → 编译器 → 上电流程 → 全栈图 升级推进)

目录

1. 寄存器是什么?(不是内存,是硬件触发器阵列)

2. Memory‑Mapped I/O:为什么“写一个地址”就能控制外设?

3. Memory‑Mapped I/O 内部结构图(假设示例)

4. 外设状态机(FSM)如何根据寄存器动作?

5. 为什么必须使用 volatile?

6. “假设寄存器”示例


1. 寄存器是什么?(不是内存,是硬件触发器阵列)

在 MCU 里,“寄存器”不是 C 语言里的变量,也不是 RAM,它是:

由多个 D 触发器构成的真实电路,直接驱动外设逻辑。

例如,一个 32-bit 寄存器就是 32 个 D 触发器组成:

┌───────────────────────────────────────────┐
│ DFF0 DFF1 DFF2 … DFF31 │
└───────────────────────────────────────────┘

这些触发器输出会连到:

  • 定时器启动逻辑
  • CAN 收发器状态机
  • UART 波特率分频器
  • ADC 采样控制
  • GPIO 输出驱动

寄存器是专门用于软件控制硬件的“电气开关 + 状态输入端”。

2. Memory‑Mapped I/O:为什么“写一个地址”就能控制外设?

MCU 把所有外设寄存器映射到某一段地址空间,这段区域不是 RAM,而是:

CPU 地址空间的一部分 → 实际连到外设模块

示例(假设映射,不对应任何实际芯片):

0x0000_0000 ~ 0x0000_FFFF RAM
0x0100_0000 ~ 0x01FF_FFFF Flash
0x0200_0000 ~ 0x02FF_FFFF 外设寄存器(Memory-Mapped I/O)

当你写:

*(volatile uint32_t*)0x02000000 = 1; // 假设地址

硬件内部实际做的是:

CPU → AHB/APB 总线 → 外设译码器
→ 对应外设寄存器触发器被写入
→ 外设状态机的输入发生变化
→ 硬件行为随之改变

CPU 并不知道“外设”的概念,它只知道:

写地址 = 写信号到总线 总线译码器负责找到“哪个外设负责这个地址”

3. Memory‑Mapped I/O 内部结构图(假设示例)

CPU Core (执行 STR 指令)


┌──────────────┐
│ AHB Bus │
└──────┬────────┘

├── RAM
├── Flash

Peripheral Bus

├── Timer0(寄存器:0x0200_0000~0x0200_00FF)
├── UART(寄存器:0x0201_0000~0x0201_00FF)
└── CAN (寄存器:0x0202_0000~0x0202_00FF)

关键点:

  • 总线译码器决定该把写访问送给哪个外设
  • CPU 并不会访问 RAM,然后 magically 控制外设

Memory‑Mapped I/O = “外设寄存器伪装成内存地址”。

4. 外设状态机(FSM)如何根据寄存器动作?

所有外设内部本质上都是 状态机(FSM)。

以“假设的定时器模块”为例(非真实):

IDLE → INIT → RUN → MATCH → INTERRUPT → RUN

假设寄存器 bit:

  • bit0 = ENABLE
  • bit1 = CLEAR
  • bit2 = MODE

当软件写:

TIMER_CTRL = 0x01; // ENABLE = 1

硬件内部行为:

  • 触发器锁存新电平
  • FSM 从 IDLE → RUN
  • 定时器开始计数
  • 到期触发 interrupt
  • 自动进入下一步逻辑
  • 你写寄存器,不是“执行函数”,是真正改变电路的走向与状态。

    5. 为什么必须使用 volatile?

    因为外设寄存器由硬件异步改变:

    • 定时器计数溢出会修改寄存器位
    • CAN 收帧后会硬件置位标志位
    • UART 发送完会更新状态位
    • DMA 会写新数据

    如果不使用 volatile:

    while (!(STATUS & 1)) {}

    编译器可能优化为:

    // 缓存 STATUS,并认为它永远不会变
    if (!(STATUS & 1)) {
    while (1) {}
    }

    程序将永远卡死。

    volatile 告诉编译器:

    每次都从真实地址读取,不要缓存,不要优化。(这个你注意看下代码寄存器地址前面都是会加volatile 就知道了)

    6. “假设寄存器”示例

    以下示例 纯粹用于概念讲解,不对应任何实际芯片:

    #define TIMER_CTRL (*(volatile uint32_t*)0x02000000u) // 假设地址
    #define TIMER_COUNT (*(volatile uint32_t*)0x02000004u) // 假设地址
    #define TIMER_STATUS (*(volatile uint32_t*)0x02000008u) // 假设地址

    void timer_start(void)
    {
    TIMER_CTRL = 0x1; // 启动(假设)
    }

    uint32_t timer_read(void)
    {
    return TIMER_COUNT; // 硬件递增(假设)
    }

    void timer_wait(void)
    {
    while (!(TIMER_STATUS & 0x1)) {
    // volatile 使 CPU 读取真实硬件
    }
    }

    这展示:

    • 寄存器像变量一样访问
    • 但其本质是访问“外设电路入口”
    • 这就是 RTOS/OS 操作硬件的基础能力
    赞(0)
    未经允许不得转载:171主机测评 » 第 1 章:寄存器、触发器与 Memory‑Mapped I/O(硬件基础)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址