MCU 与体系结构
本文性质声明:本机没有开发板(用户环境的 Rockchip 交叉工具链在不可达的 Linux 开发机上),全部内容未实测,为手册级框架。站内对照:os-what-kernel-does(特权级与陷入的本机实测)、c-restricted-environment-and-volatile(受限环境的 C 写法)。
一、Cortex-M:寄存器与异常模型
Cortex-M(M0/M3/M4/M7/M33)的寄存器组小得可以背下来:
R0-R12 通用寄存器(R13=SP 栈指针,R14=LR 链接寄存器,R15=PC)
xPSR 程序状态寄存器(NZCV 标志 + 异常号 + IT 块)
两个 SP: MSP(主栈,异常/内核用)和 PSP(进程栈,线程模式可用)
异常模型是全架构的核心:中断和异常走同一张向量表(本质是函数指针数组),发生时硬件自动:保存 8 个寄存器到栈(R0-R3/R12/LR/PC/xPSR)→ 取向量表对应入口 → 切到 MSP → 跳转。这套”硬件保存上下文”的机制意味着:裸机中断处理函数就是个普通 C 函数(编译器按 ABI 保存其余寄存器),void USART1_IRQHandler(void) 就能跑——和需要手动保存的架构(如某些 DSP)完全不同。
三个必须知道的点:
- 优先级是”数值越小优先级越高”,且优先级位数由芯片实现决定(M3 通常 8 位可配 4 位——
NVIC_SetPriorityGrouping分抢占/子优先级)。同一抢占优先级不嵌套——后到的等前一个处理完,这决定了临界区的写法。 - 中断延迟是可算的:硬件保存(12 周期)+ 向量取指 + 流水线填充,典型 12–16 周期;尾部连锁(tail-chaining)让连续中断不重新保存。
- HardFault 是兜底异常:跑了未实现的中断、取了错地址、栈溢出——HardFault_Handler 里读
SCB->HFSR/CFSR是嵌入式调试的第一现场(本站 dbg-crash 讲 Windows 侧的终止码,这是 MCU 侧的对应物)。
二、存储映射:外设就是内存地址
Cortex-M 是统一地址空间——没有 x86 的 in/out 指令,外设寄存器就是普通内存地址:
0x00000000 代码区(Flash 别名或 SRAM,向量表在这)
0x20000000 SRAM(栈默认从顶端往下长)
0x40000000 外设区(APB 总线:GPIO/UART/I2C……)
0xE000E000 Cortex-M 系统级(NVIC 在 0xE000E100,SysTick 在 0xE000E010)
访问外设 = 往绝对地址读写:
#define GPIOC_ODR (*(volatile uint32_t*)0x40020814)
GPIOC_ODR |= (1 << 13); // 点灯
volatile 不是可选项:编译器不知道”这个地址后面连着硬件”——不加 volatile,GPIOC_ODR = 1; GPIOC_ODR = 2; 会被优化成只写第二次(本站 c-restricted-environment 专节讲过 volatile 与 MMIO)。厂商的 CMSIS 头文件把这些地址包成结构体(GPIOA->ODR),但底层就是上面这行。
位带(bit-band,M3/M4 有):给每个 bit 一个 alias 地址,写 alias 就是原子地置位/清位——GPIOC_ODR_13 = 1 背后是它,多中断共享的寄存器用它做原子改位。
三、RISC-V:同一 ISA,不同生态
RISC-V 的卖点是开放指令集:基 integer 指令集(RV32I/RV64I)+ 可选扩展(M 乘除、A 原子、C 压缩、F/D 浮点、向量)。对嵌入式:
- 中断模型不标准化:ARM 有 NVIC 这个事实标准,RISC-V 只有**平台级中断控制器(PLIC)**的规范和各家自己的 CLINT/本地中断——每家的中断代码不一样,没有”一份中断代码到处跑”。
- 寄存器模型不同:32 个通用寄存器(x0 恒零)、
mstatus/mie/mcause/mepc一组 CSR(控制状态寄存器)做特权状态——异常处理要手写汇编入口(没有 Cortex-M 那种硬件自动保存全套的便利,虽然可以配快速 trap)。 - 生态现实:ESP32-C 系列(乐鑫)、GD32V、沁恒 CH32V、平头哥——国产 MCU 的主力 ISA;工具链(gcc/riscv、OpenOCD)和 RTOS 移植都成熟了,但外设库每家一套,没有 CMSIS 那种统一 HAL。
选型口径:Cortex-M 是”生态换封闭”,RISC-V 是”开放换自建”——量产成本敏感、怕授权风险的看 RISC-V;要开发速度、要 HAL 成熟的看 Cortex-M。
四、读数据手册的四步方法论
数据手册几百上千页,别通读。四步:
- 先看订购信息和框图:型号后缀(温度/封装/Flash 大小)、内核主频、外设清单——确认这颗芯片能不能干你要的事。
- pin 定义和复用:每个引脚默认功能、可复用外设、模拟功能——原理图设计阶段反复看的一章。
- 电气特性:绝对最大值、IO 驱动能力(推挽几 mA)、ADC 的 INL/DNL、功耗表——原理图和电源设计的依据。
- 外设寄存器手册(Reference Manual):用到哪个外设查哪章——每个寄存器的位定义、复位值、访问限制(只读/只写/write-1-to-clear)。写驱动时这章和你的代码对照着看。
工程习惯:寄存器操作写成”基址 + 偏移”的宏或 CMSIS 结构体,别在业务代码里裸写地址——数据手册改版(勘误!)时只改一处。勘误表(errata)必看:某些芯片的 DMA+ADC 组合有 bug,手册后面挂着。
五、排障索引
| 症状 | 第一嫌疑 |
|---|---|
| 程序跑飞、随机复位 | 栈溢出(MSP/PSP 撞了)、向量表错、时钟没配对 |
| 写外设没反应 | 时钟没开(RCC 门控忘置位)、复用功能没配、volatile 漏了 |
| HardFault | CFSR 的 PRECISERR/IBUSERR/UNSTKERR——分别对应数据访问/取指/出栈错误 |
| 中断只进一次 | 标志位没清(write-1-to-clear 语义)、NVIC 没使能 |
| 功耗降不下来 | 外设时钟没关、IO 状态浮空、调试器还挂着( emb-lowpower) |
六、未实测清单
- 本文所有架构细节与寄存器行为——无开发板,未测。
- 具体型号的电气参数——以你芯片的数据手册为准。
- 中断延迟、栈帧布局的实测——未测。
- 用户 Linux 开发机上的 Rockchip RV1103 交叉工具链——该机不可达,未涉及。