补充:寄存器
🔑 一、寄存器到底是什么?
寄存器 = CPU/外设的「控制面板」
通过读写特定的内存地址,就能控制硬件的行为(比如点亮LED、配置串口、读取按键)
嵌入式系统 = CPU + 内存映射的寄存器 + 外设硬件
🎯 二、嵌入式中最常接触的3类寄存器
1️⃣ CPU内核寄存器(了解即可)
- R0~R15、CPSR 等(ARM架构)
- 用途:函数传参、保存状态、程序计数
- 初期不用深究,编译器会帮你处理好
2️⃣ 外设控制寄存器(⭐ 重点!天天用)
这是你操作硬件的核心,比如:
| 外设 | 典型寄存器 | 作用 |
|---|---|---|
| GPIO | GPXCON, GPXDAT, GPXUP | 配置引脚方向/输出值/上拉 |
| UART | ULCON, UCON, UTXH, URXH | 配置串口/收发数据 |
| Timer | TCFG0, TCON, TCNTB | 配置定时器/计数/中断 |
| Interrupt | SRCPND, INTMOD, INTMSK | 管理中断源/使能/屏蔽 |
3️⃣ 系统控制寄存器(进阶用)
- 时钟配置、电源管理、看门狗等
🛠️ 三、实操:如何操作寄存器?(3种主流方式)
方式1:直接指针操作(最底层,理解原理必学)
// 假设GPIOB的第5号引脚控制LED
#define GPIOB_CON (*(volatile unsigned int *)0x56000010)
#define GPIOB_DAT (*(volatile unsigned int *)0x56000014)
// 配置为输出模式
GPIOB_CON = (GPIOB_CON & ~(0xF << 20)) | (0x1 << 20); // 清0后设1
// 输出高电平点亮LED
GPIOB_DAT |= (1 << 5);
方式2:寄存器结构体映射(工程常用,可读性好)
// 定义GPIO寄存器组结构体(参考芯片手册)
typedef struct {
volatile unsigned int CON;
volatile unsigned int DAT;
volatile unsigned int UP;
} gpio_reg_t;
// 映射到物理地址
#define GPIOB_BASE ((gpio_reg_t *)0x56000010)
// 使用
GPIOB_BASE->CON = ...;
GPIOB_BASE->DAT |= (1 << 5);
方式3:调用厂商/内核提供的API(Linux驱动开发主流)
// Linux内核中(不直接操作寄存器!)
#include <linux/gpio.h>
gpio_request(LED_PIN, "led");
gpio_direction_output(LED_PIN, 1); // 输出高电平
// 或设备树 + pinctrl + gpio子系统(现代做法)
第九章 中断体系结构
9.1 ARM 体系 CPU 的 7 种工作模式
- 用户模式(usr):ARM 处理器正常的程序执行状态。
- 快速中断模式(fiq):用于高速数据传输或通道处理。
- 中断模式(irq):用于通用的中断处理。
- 管理模式(svc):操作系统使用的保护模式。
- 数据访问终止模式(abt):当数据或指令预取终止时进入该模式,可用于虚拟存储及 存储保护。
- 系统模式(sys):运行具有特权的操作系统任务。
- 未定义指令中止模式(und):当未定义的指令执行时进入该模式,可用于支持硬件协 处理器的软件仿真
可以通过软件来进行模式切换,或者发生各类中断、异常时 CPU 自动进入相应的模式。 除用户模式外,其他 6 种工作模式都属于特权模式。 大多数程序运行于用户模式,进入特权模式是为了处理中断、异常,或者访问被保护的系统资源。
精髓: 为什么你的 C 语言程序崩溃了(段错误),整个 Ubuntu 系统不会死机?因为你的程序跑在 User 模式,被关在笼子里;而操作系统跑在 SVC 模式,掌握着生杀大权。
9.2 中断控制器
9.2.1 为什么不用查询,要用中断?
CPU 运行过程中,如何知道各类外设发生了某些不预期的事件,比如串口接收到了新数 据、USB 接口中插入了设备、按下了某个按键等。主要有以下两个方法。
- 查询方式:程序循环地查询各设备的状态并作出相应反应。它实现简单,常用在功 能相对单一的系统中,比如在一个温控系统中可以使用查询方式不断检测温度的变化。缺点 是占用 CPU 资源过高,不适用于多任务系统。
- 中断方式:当某事件发生时,硬件会设置某个寄存器;CPU 在每执行完一个指令时, 通过硬件查看这个寄存器,如果发现所关注的事件发生了,则中断当前程序流程,跳转到一 个固定的地址处理这事件,最后返回继续执行被中断的程序。它的实现相对复杂,但是效率 很高,是常用的方法
工程师视角:中断的核心价值是 「事件驱动」。CPU 不再主动轮询,而是被动响应。
中断的本质是「异步事件通知机制」:
硬件负责可靠通知(寄存器状态 + 信号线),软件负责高效响应(最小化中断占用 + 合理拆分任务)。
9.2.2 通用中断处理链路(所有芯片通用):
外设 → 中断控制器 → CPU → 保存上下文 → 执行ISR → 恢复上下文
这是所有嵌入式中断体系的通用模型,无论ARM9还是Cortex-M33/RISC-V都遵循这个流程。
- 中断控制器汇集各类外设发出的中断信号,然后告诉 CPU。
- CPU 保存当前程序的运行环境(各个寄存器等),调用中断服务程序(ISR,Interrupt Service Routine)来处理这些中断。
- 在 ISR 中通过读取中断控制器、外设的相关寄存器来识别这是哪个中断,并进行相 应的处理。
- 清除中断:通过读写中断控制器和外设的相关寄存器来实现。
- 最后恢复被中断程序的运行环境(即上面保存的各个寄存器等),继续执行
中断处理流程(专业通用模型)
阶段一:中断信号产生与采集(外设 → 中断控制器)
外设事件(按键按下/串口收到数据/定时器溢出)
│
▼
┌─────────────────────────────────┐
│ 外设内部逻辑 │
│ • 状态寄存器置位 (Status Register) │
│ • 中断请求信号拉高 (IRQ Line ↑) │
└────────┬────────────────────────┘
│ 物理信号线 (IRQn)
▼
┌─────────────────────────────────┐
│ 中断控制器 (Interrupt Controller) │
│ 现代芯片多为 GIC (Generic Interrupt Controller) │
│ │
│ ① 中断采集 (Collect) │
│ • SPI: 外设中断 (Shared Peripheral Interrupt) │
│ • PPI: 私有外设中断 (Private Peripheral Interrupt) │
│ • SGI: 软件触发中断 (Software Generated Interrupt) │
│ │
│ ② 中断 pending (待处理) │
│ • 对应位在中断挂起寄存器置 1 │
│ • 例: GICD_ISPENDR[n] │
│ │
│ ③ 中断屏蔽 (Mask) │
│ • 检查中断使能寄存器 (GICD_ISENABLER) │
│ • 检查 CPU 全局中断开关 (CPSR.I/F) │
│ │
│ ④ 优先级仲裁 (Arbitration) │
│ • 比较 Priority Register │
│ • 支持抢占 (Preemption) │
│ │
│ ⑤ 路由 (Routing) │
│ • 指定目标 CPU 核 (Affinity) │
│ • 指定安全世界 (Secure/Non-secure) │
└────────┬────────────────────────┘
│ IRQ/FIQ 信号线
▼
🔹 阶段二:CPU 响应与上下文切换(硬件自动 + 软件协作)
CPU 检测到中断信号 (每指令周期采样)
│
▼
┌─────────────────────────────────┐
│ 硬件自动执行 (不可跳过) │
│ ① 完成当前指令 (Instruction Boundary) │
│ ② 保存关键上下文: │
│ • PC → LR_irq (返回地址) │
│ • CPSR → SPSR_irq (状态寄存器) │
│ ③ 切换处理器模式: │
│ • User → IRQ/FIQ Mode │
│ • 切换银行寄存器 (Banked Registers) │
│ ④ 禁用同级中断: │
│ • CPSR.I = 1 (屏蔽 IRQ) │
│ ⑤ 跳转到异常向量表: │
│ • IRQ Vector: 0x18 (或 VTOR 配置) │
└────────┬────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 软件执行 (异常入口汇编 + C 函数) │
│ ① 保存完整上下文 (通用寄存器 R0-R12) │
│ • stmdb sp!, {r0-r12, lr} │
│ ② 读取中断号: │
│ • GIC: GICC_IAR (Interrupt Acknowledge Register) │
│ • 提取 IRQ Number │
│ ③ 清除中断挂起 (Acknowledge) │
│ • 写 GICC_EOIR (End of Interrupt) │
│ • ⚠️ 顺序很重要: 先外设后控制器 │
│ ④ 调用注册的中断处理函数 (Handler) │
│ • Linux: request_irq() 注册的函数 │
│ • RTOS: ISR 回调函数 │
└────────┬────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 中断返回 (硬件 + 软件协作) │
│ ① 恢复通用寄存器: │
│ • ldmia sp!, {r0-r12, pc}^ │
│ • ^ 表示同时恢复 CPSR = SPSR │
│ ② 硬件自动: │
│ • LR_irq → PC (返回断点) │
│ • SPSR_irq → CPSR (恢复状态) │
│ • 重新使能中断 (CPSR.I = 0) │
└─────────────────────────────────┘
9.2.3 软件流程:中断服务程序 (ISR) 要做什么?
标准 ISR 执行四步曲:
-
保护现场 (Save Context)
- 为什么:中断可能发生在任何指令执行过程中,寄存器里的数据很重要。
- 怎么做:把
R0~R12,LR等压栈(文中汇编stmdb sp!就是在做这个)。 - 现代:内核入口代码自动完成。
-
识别中断源 (Identify)
- 为什么:可能多个设备共享中断。
- 怎么做:读中断控制器的状态寄存器(文中
INTPND或INTOFFSET)。
-
处理业务 (Handle)
- 怎么做:读串口数据、清零按键标志、翻转 LED 等。
- 原则:越快越好! 中断里不能睡大觉(不能延时、不能等待锁)。
-
清除中断 (Ack/Clear) ⭐ 最关键!
- 为什么:如果不告诉硬件「我处理完了」,硬件会认为事件还在,会无限重复进入中断,导致系统死机。
- 怎么做:文中提到的
INTPND = INTPND(写 1 清零)。 - 顺序:通常先清外设中断,再清中断控制器中断(具体看芯片手册)。
-
恢复现场 (Restore Context)
- 怎么做:出栈,恢复寄存器,返回断点继续执行(文中
ldmia sp!, {r0-r12, pc}^)
- 怎么做:出栈,恢复寄存器,返回断点继续执行(文中
第十章 系统时钟和定时器
10.1 系统时钟
通用时钟树模型
外部晶振 (12MHz/24MHz 唯一物理时钟源 )
│
▼
┌─────────────────┐
│ PLL (频率合成器) │ ← 核心!把低频晶振倍频到高频
│ • 输入: Fin │
│ • 输出: Fout = Fin × M / (P × 2^S) │
│ • 作用: 产生系统主频 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 时钟分频器(Divider) │ ← 按需分配不同频率
│ • FCLK: CPU 用 │
│ • HCLK: 总线用 │
│ • PCLK: 外设用 │
│ • 分频比可配置 │
└────────┬────────┘
│
▼
┌─────────────────┐
│ 时钟门控 (Gate) │ ← 省电关键!
│ • 不用的外设时钟关掉 │
│ • 动态功耗管理 │
└─────────────────┘
总线示意:
┌─────────┐ ┌─────────────────────────────┐ ┌─────────┐
│ CPU │◄────►│ AHB 高速总线 │◄────►│ 内存 │
│ (400MHz)│ │ (Advanced High-performance │ │(100MHz) │
└─────────┘ │ Bus) │ └─────────┘
│ • 32/64位宽(一次传更多数据) │
│ • 流水线传输 │
│ • 多主设备仲裁 │
└─────────────┬───────────────┘
│
┌─────────────┴───────────────┐
│ APB 桥接器 │ ← 速度转换 + 协议转换
│ (AHB-to-APB Bridge) │
└─────────────┬───────────────┘
│
┌─────────────┴───────────────┐
│ APB 低速总线 │
│ (Advanced Peripheral Bus) │
│ • 16/32位宽 │
│ • 简单协议,省电 │
│ • 适合慢速设备 │
└──────┬────────┬────────┬────┘
│ │ │
┌────┘ ┌────┘ ┌────┘
↓ ↓ ↓
┌──────┐ ┌──────┐ ┌──────┐
│ UART │ │ Timer│ │ GPIO│
│(50MHz)│ │(50MHz)│ │(50MHz)│
└──────┘ └──────┘ └──────┘
📌 一句话:时钟树就是「频率分配网络」,只需要知道「我要给某个外设多少频率」,具体路由由硬件/框架完成。