ARM Cortex-M3 核心知识指南
- 嵌入式开发
- 2小时前
- 5热度
- 0评论
嵌入式系列(一):ARM Cortex-M3 核心知识指南
1. 概述
Cortex-M3 是 ARM 公司推出的 32 位 RISC 处理器内核,专为嵌入式应用设计,具有高性能、低功耗、低成本的特点。
主要特性:
- 32 位 ARMv7-M 架构
- 3 级流水线(取指、译码、执行)
- 哈佛总线架构(指令和数据总线分离)
- 支持 Thumb-2 指令集(16/32 位混合指令)
- 嵌套向量中断控制器(NVIC)
- 可选的内存保护单元(MPU)
2. 寄存器组
2.1 通用寄存器(R0-R12)
| 寄存器 | 说明 |
|---|---|
| R0-R7 | 低寄存器组,所有指令都可访问 |
| R8-R12 | 高寄存器组,部分指令可访问 |
2.2 特殊寄存器
| 寄存器 | 名称 | 说明 |
|---|---|---|
| R13 (SP) | 堆栈指针 | 分为 MSP(主堆栈指针)和 PSP(进程堆栈指针) |
| R14 (LR) | 链接寄存器 | 保存函数返回地址 |
| R15 (PC) | 程序计数器 | 指向当前执行指令地址 + 4 |
R13 (SP) - 堆栈指针详解
Cortex-M3 有 两个独立的堆栈指针,通过 CONTROL[1] 位选择:
MSP(Main Stack Pointer,主堆栈指针)
- 用途:系统启动、异常处理、特权模式默认使用
- 初始化:复位时从向量表偏移 0x00 加载初值
- 使用场景:
- 复位后默认使用 MSP
- 所有异常/中断处理程序使用 MSP
- 裸机程序通常只用 MSP
PSP(Process Stack Pointer,进程堆栈指针)
- 用途:用户任务/线程模式(RTOS 常用)
- 初始化:需要软件手动设置
- 使用场景:
- RTOS 中每个任务有独立的 PSP
- 任务切换时保存/恢复 PSP
- 实现任务堆栈隔离
切换示例:
// 切换到 PSP
__set_PSP(0x20002000); // 设置 PSP 地址
__set_CONTROL(__get_CONTROL() | 0x02); // CONTROL[1] = 1
// 切换回 MSP
__set_CONTROL(__get_CONTROL() & ~0x02); // CONTROL[1] = 0
R14 (LR) - 链接寄存器详解
LR 寄存器在 Cortex-M3 中有 双重用途:
1. 函数调用时保存返回地址
void func_a(void) {
func_b(); // BL func_b,LR = func_a 的返回地址
}
void func_b(void) {
// 执行代码
// BX LR 返回到 func_a
}
2. 异常返回时保存 EXC_RETURN 值
当异常发生时,LR 自动加载特殊的 EXC_RETURN 值:
| EXC_RETURN 值 | 说明 |
|---|---|
0xFFFFFFF1 |
返回 Handler 模式,使用 MSP |
0xFFFFFFF9 |
返回 Thread 模式,使用 MSP |
0xFFFFFFFD |
返回 Thread 模式,使用 PSP |
异常返回示例:
void USART1_IRQHandler(void) {
// 进入中断时,LR = 0xFFFFFFF9(返回 Thread 模式,使用 MSP)
// 处理中断
USART1->SR &= ~USART_SR_RXNE;
// 执行 BX LR,硬件自动:
// 1. 识别 LR 为 EXC_RETURN
// 2. 从堆栈恢复寄存器(R0-R3, R12, LR, PC, xPSR)
// 3. 返回到被中断的代码
}
嵌套函数调用问题:
void IRQ_Handler(void) {
// LR = 0xFFFFFFF9(EXC_RETURN)
func_a(); // BL func_a 会覆盖 LR!
// 此时 LR 已被破坏,无法正确返回
}
解决方案: 编译器自动在函数入口保存 LR 到堆栈
IRQ_Handler:
PUSH {LR} ; 保存 EXC_RETURN
BL func_a ; 调用函数
POP {LR} ; 恢复 EXC_RETURN
BX LR ; 异常返回
R15 (PC) - 程序计数器详解
PC 寄存器指向 当前执行指令地址 + 4(由于流水线)。
读取 PC:
uint32_t current_pc;
__asm volatile ("MOV %0, PC" : "=r" (current_pc));
// current_pc = 实际 PC + 4
PC 对齐要求:
- Thumb-2 指令:PC 必须是偶数(bit 0 = 0)
- 跳转到奇数地址会触发 HardFault
2.3 特殊功能寄存器
| 寄存器 | 全称 | 位宽 | 说明 |
|---|---|---|---|
| xPSR | Program Status Register | 32位 | 程序状态寄存器(APSR + IPSR + EPSR) |
| PRIMASK | Priority Mask Register | 1位 | 中断屏蔽寄存器(屏蔽所有可屏蔽中断) |
| FAULTMASK | Fault Mask Register | 1位 | 故障屏蔽寄存器(屏蔽所有中断,除 NMI) |
| BASEPRI | Base Priority Register | 8位 | 基础优先级寄存器(屏蔽低于指定优先级的中断) |
| CONTROL | Control Register | 3位 | 控制寄存器(选择堆栈指针、特权级别) |
xPSR 详解(32 位)
xPSR = APSR + IPSR + EPSR(三个寄存器的组合视图)
| 子寄存器 | 全称 | 位域 | 说明 |
|---|---|---|---|
| APSR | Application Program Status Register | [31:27] | 应用程序状态:N(负)、Z(零)、C(进位)、V(溢出)、Q(饱和) |
| IPSR | Interrupt Program Status Register | [8:0] | 中断状态:当前异常编号(0 = 线程模式,1-255 = 异常号) |
| EPSR | Execution Program Status Register | [26:24] | 执行状态:T 位(Thumb 状态,始终为 1) |
APSR 标志位:
- N (Negative) [31]:结果为负
- Z (Zero) [30]:结果为零
- C (Carry) [29]:进位/借位
- V (Overflow) [28]:有符号溢出
- Q (Saturation) [27]:饱和运算标志
PRIMASK 详解(1 位)
PRIMASK[0]:
0 = 允许所有可屏蔽中断
1 = 屏蔽所有可屏蔽中断(只有 NMI 和 HardFault 可响应)
操作指令:
CPSID I ; 设置 PRIMASK = 1(关闭中断)
CPSIE I ; 清除 PRIMASK = 0(开启中断)
C 语言示例:
// 方式 1:使用 CMSIS 库函数(推荐)
__disable_irq(); // 关闭中断(设置 PRIMASK = 1)
// 临界区代码
__enable_irq(); // 开启中断(清除 PRIMASK = 0)
// __disable_irq() 的实现(CMSIS core_cm3.h):
__STATIC_INLINE void __disable_irq(void) {
__asm volatile ("CPSID I" : : : "memory");
}
__STATIC_INLINE void __enable_irq(void) {
__asm volatile ("CPSIE I" : : : "memory");
}
// 方式 2:直接使用内联汇编
__asm volatile ("CPSID I"); // 关闭中断
// 临界区代码
__asm volatile ("CPSIE I"); // 开启中断
// 方式 3:保存和恢复中断状态(最安全)
uint32_t primask = __get_PRIMASK(); // 保存当前状态
__disable_irq(); // 关闭中断
// 临界区代码
__set_PRIMASK(primask); // 恢复原状态
// __get_PRIMASK() 的实现:
__STATIC_INLINE uint32_t __get_PRIMASK(void) {
uint32_t result;
__asm volatile ("MRS %0, PRIMASK" : "=r" (result));
return result;
}
// __set_PRIMASK() 的实现:
__STATIC_INLINE void __set_PRIMASK(uint32_t priMask) {
__asm volatile ("MSR PRIMASK, %0" : : "r" (priMask) : "memory");
}
// 实际应用:保护共享变量
volatile uint32_t shared_counter = 0;
void increment_counter(void) {
__disable_irq(); // 关中断
shared_counter++; // 原子操作
__enable_irq(); // 开中断
}
// 更安全的方式:支持嵌套调用
void safe_increment_counter(void) {
uint32_t primask = __get_PRIMASK(); // 保存状态
__disable_irq(); // 关中断
shared_counter++; // 原子操作
__set_PRIMASK(primask); // 恢复原状态(如果之前就是关闭的,保持关闭)
}
FAULTMASK 详解(1 位)
FAULTMASK[0]:
0 = 正常中断响应
1 = 屏蔽所有中断(除 NMI),包括 HardFault
操作指令:
CPSID F ; 设置 FAULTMASK = 1
CPSIE F ; 清除 FAULTMASK = 0
C 语言示例:
// 使用 CMSIS 库函数
__disable_fault_irq(); // 关闭所有中断(除 NMI)
// 极端临界区代码
__enable_fault_irq(); // 开启中断
// 实际应用:Flash 编程操作
void flash_program(uint32_t addr, uint32_t data) {
__disable_fault_irq(); // 关闭所有中断(包括 HardFault)
// Flash 编程操作(不能被打断)
FLASH->CR |= FLASH_CR_PG;
*(volatile uint32_t *)addr = data;
while (FLASH->SR & FLASH_SR_BSY);
__enable_fault_irq(); // 恢复中断
}
BASEPRI 详解(8 位)
BASEPRI[7:0]:
0x00 = 不屏蔽任何中断(默认值)
0x01-0xFF = 屏蔽优先级 ≥ BASEPRI 的中断(数值越大,优先级越低)
示例:
// 屏蔽优先级 ≥ 0x40 的中断
__set_BASEPRI(0x40);
// 取消屏蔽
__set_BASEPRI(0x00);
CONTROL 详解(3 位)
| 位 | 名称 | 说明 |
|---|---|---|
| [2] | FPCA | 浮点上下文激活(仅 Cortex-M4/M7 with FPU) |
| [1] | SPSEL | 堆栈指针选择(0=MSP,1=PSP) |
| [0] | nPRIV | 特权级别(0=特权模式,1=非特权模式) |
操作示例:
// 切换到 PSP
__set_CONTROL(__get_CONTROL() | 0x02);
// 切换到非特权模式
__set_CONTROL(__get_CONTROL() | 0x01);
3. 操作模式
3.1 特权级别
| 模式 | 说明 |
|---|---|
| 特权模式 | 可访问所有资源和寄存器,复位后默认模式 |
| 非特权模式 | 受限访问,无法访问某些系统资源(如 NVIC、SCB) |
切换方式:
- 特权 → 非特权:修改 CONTROL 寄存器
- 非特权 → 特权:通过异常返回或复位
3.2 堆栈指针选择
| 堆栈指针 | 使用场景 |
|---|---|
| MSP(主堆栈指针) | 复位后默认使用,异常处理时使用 |
| PSP(进程堆栈指针) | 用户任务/线程模式(RTOS 常用) |
切换: 通过 CONTROL[1] 位选择(0=MSP,1=PSP)
4. 异常与中断
4.1 异常类型
Cortex-M3 支持最多 256 个异常(16 个系统异常 + 240 个外部中断)。
| 异常编号 | 名称 | 优先级 | 说明 |
|---|---|---|---|
| 1 | Reset | -3(最高) | 复位 |
| 2 | NMI | -2 | 不可屏蔽中断 |
| 3 | HardFault | -1 | 硬件故障 |
| 4 | MemManage | 可配置 | 内存管理故障(需 MPU) |
| 5 | BusFault | 可配置 | 总线故障 |
| 6 | UsageFault | 可配置 | 用法故障(非法指令等) |
| 7-10 | 保留 | - | - |
| 11 | SVCall | 可配置 | 系统服务调用 |
| 12 | Debug Monitor | 可配置 | 调试监视器 |
| 13 | 保留 | - | - |
| 14 | PendSV | 可配置 | 可挂起的系统调用(RTOS 上下文切换) |
| 15 | SysTick | 可配置 | 系统滴答定时器 |
| 16+ | IRQ0-239 | 可配置 | 外部中断 |
4.2 NVIC(嵌套向量中断控制器)
核心特性:
- 支持中断嵌套(高优先级可打断低优先级)
- 硬件自动保存/恢复上下文(R0-R3, R12, LR, PC, xPSR)
- 尾链(Tail-chaining):连续中断无需恢复上下文
- 延迟到达(Late-arriving):更高优先级中断可抢占正在入栈的低优先级中断
优先级配置:
- 每个中断有 8 位优先级(实际实现位数由芯片厂商决定,如 STM32 为 4 位)
- 数值越小,优先级越高(0 = 最高优先级)
- 可分为抢占优先级和子优先级(通过 PRIGROUP 配置)
关键寄存器:
ISER/ICER:中断使能/清除ISPR/ICPR:中断挂起/清除挂起IPR:中断优先级配置AIRCR:应用中断和复位控制(优先级分组)
4.3 异常处理流程
- 异常触发 → 硬件自动入栈(R0-R3, R12, LR, PC, xPSR)
- 取向量 → 从向量表读取异常处理函数地址
- 执行 ISR → 跳转到中断服务程序
- 异常返回 → 执行特殊返回指令(LR = 0xFFFFFFF9/D/1)
- 自动出栈 → 恢复寄存器,返回线程模式
EXC_RETURN 值:
0xFFFFFFF1:返回 Handler 模式,使用 MSP0xFFFFFFF9:返回 Thread 模式,使用 MSP0xFFFFFFFD:返回 Thread 模式,使用 PSP
5. 内存映射
Cortex-M3 使用 4GB 统一地址空间(0x0000_0000 - 0xFFFF_FFFF)。
| 地址范围 | 区域 | 说明 |
|---|---|---|
| 0x0000_0000 - 0x1FFF_FFFF | Code | 程序代码(Flash、ROM) |
| 0x2000_0000 - 0x3FFF_FFFF | SRAM | 片上 SRAM |
| 0x4000_0000 - 0x5FFF_FFFF | Peripheral | 外设寄存器 |
| 0x6000_0000 - 0x9FFF_FFFF | External RAM | 外部 RAM |
| 0xA000_0000 - 0xDFFF_FFFF | External Device | 外部设备 |
| 0xE000_0000 - 0xE00F_FFFF | Private Peripheral Bus | 内核私有外设(NVIC、SysTick、MPU 等) |
| 0xE010_0000 - 0xFFFF_FFFF | Vendor-specific | 厂商自定义 |
向量表:
- 默认位置:
0x0000_0000 - 可通过 VTOR 寄存器重定位(必须 128 字节对齐)
- 前 16 个条目为系统异常,后续为外部中断
6. 总线架构
Cortex-M3 采用 哈佛架构,具有多条独立总线:
| 总线 | 说明 |
|---|---|
| ICode | 指令总线(访问 Code 区) |
| DCode | 数据总线(访问 Code 区的数据) |
| System | 系统总线(访问 SRAM、外设、外部设备) |
| PPB | 私有外设总线(访问 NVIC、SysTick 等) |
优势: 指令和数据可同时访问,提高性能。
7. 内存保护单元(MPU)
功能:
- 定义最多 8 个内存区域的访问权限
- 防止非法内存访问(提高系统安全性)
- 支持特权/非特权模式的不同访问权限
配置属性:
- 读/写/执行权限
- 缓存策略(Cacheable、Bufferable)
- 共享属性(Shareable)
8. 位带操作
Cortex-M3 支持 位带(Bit-banding) 功能,可将特定内存区域的每一位映射到一个 32 位字地址。
位带区域:
- SRAM 位带区:
0x2000_0000 - 0x200F_FFFF(1MB) - 外设位带区:
0x4000_0000 - 0x400F_FFFF(1MB)
位带别名区:
- SRAM 别名:
0x2200_0000 - 0x23FF_FFFF(32MB) - 外设别名:
0x4200_0000 - 0x43FF_FFFF(32MB)
地址转换公式:
别名地址 = 别名区基址 + (字节偏移 × 32) + (位序号 × 4)
优势: 原子操作单个位,无需读-改-写操作。
9. 调试支持
调试组件:
- SWD(Serial Wire Debug):2 线调试接口(SWDIO、SWCLK)
- JTAG:标准 5 线调试接口
- ITM(Instrumentation Trace Macrocell):指令跟踪
- DWT(Data Watchpoint and Trace):数据观察点
- FPB(Flash Patch and Breakpoint):断点和代码补丁
10. Thumb-2 指令集
10.1 什么是 Thumb-2?
Thumb-2 是 ARM 推出的混合指令集,结合了传统 ARM 指令(32 位)和 Thumb 指令(16 位)的优点。
历史演进:
- ARM 指令(32 位):功能强大,但代码体积大,占用更多 Flash
- Thumb 指令(16 位):代码密度高,但功能受限(如只能访问 R0-R7)
- Thumb-2(16/32 位混合):鱼和熊掌兼得
10.2 核心优势
| 特性 | 说明 |
|---|---|
| 代码密度 | 比纯 32 位 ARM 指令节省 26% 代码空间 |
| 性能 | 接近 32 位 ARM 指令的性能(98%) |
| 灵活性 | 编译器自动选择 16 位或 32 位指令 |
| 兼容性 | Cortex-M3 只支持 Thumb-2,不支持传统 ARM 指令 |
10.3 为什么重要?
- 节省 Flash 空间 → 降低芯片成本
- 提高取指效率 → 16 位指令一次取两条,减少总线占用
- 降低功耗 → 更少的内存访问 = 更低功耗
- 无需手动切换 → 编译器自动优化,开发者无感知
10.4 实际例子
// C 代码
int add(int a, int b) {
return a + b;
}
编译后(Thumb-2):
add:
ADD R0, R0, R1 ; 16 位指令(简单操作)
BX LR ; 16 位指令(返回)
如果是复杂操作:
int complex_calc(int a, int b, int c) {
return (a * b) + (c << 16);
}
编译后(Thumb-2 混合):
complex_calc:
MUL R0, R0, R1 ; 32 位指令(乘法需要更多编码空间)
ADD R0, R0, R2, LSL #16 ; 32 位指令(带移位的加法)
BX LR ; 16 位指令(返回)
编译器自动选择最优指令长度。
11. 常用指令示例
11.1 数据处理
MOV R0, R1 ; 数据传送
ADD R0, R1, R2 ; 加法
SUB R0, R1, #10 ; 减法(立即数)
MUL R0, R1, R2 ; 乘法
AND R0, R1, R2 ; 逻辑与
ORR R0, R1, R2 ; 逻辑或
LSL R0, R1, #2 ; 逻辑左移
11.2 内存访问
LDR R0, [R1] ; 加载字(32 位)
LDRH R0, [R1] ; 加载半字(16 位)
LDRB R0, [R1] ; 加载字节(8 位)
STR R0, [R1] ; 存储字
STRH R0, [R1] ; 存储半字
STRB R0, [R1] ; 存储字节
LDM R0, {R1-R4} ; 多寄存器加载
STM R0, {R1-R4} ; 多寄存器存储
11.3 分支与跳转
B label ; 无条件跳转
BL function ; 带链接跳转(调用函数)
BX R0 ; 跳转并切换状态(Thumb/ARM)
BLX R0 ; 带链接跳转并切换状态
BEQ label ; 相等则跳转
BNE label ; 不等则跳转
11.4 特殊指令
MRS R0, CONTROL ; 读特殊寄存器
MSR CONTROL, R0 ; 写特殊寄存器
CPSID I ; 关闭中断(设置 PRIMASK)
CPSIE I ; 开启中断(清除 PRIMASK)
WFI ; 等待中断(低功耗)
WFE ; 等待事件
SEV ; 发送事件
NOP ; 空操作
12. 启动流程
- 复位 → PC 指向复位向量(向量表偏移 0x04)
- 初始化 SP → 从向量表偏移 0x00 加载 MSP 初值
- 执行复位处理函数 → 通常为
Reset_Handler - 系统初始化 → 时钟配置、外设初始化
- 跳转到 main → 进入用户程序
向量表结构(前 16 项):
__attribute__((section(".isr_vector")))
const uint32_t vector_table[] = {
(uint32_t)&_estack, // 0x00: 初始 SP 值
(uint32_t)Reset_Handler, // 0x04: 复位
(uint32_t)NMI_Handler, // 0x08: NMI
(uint32_t)HardFault_Handler, // 0x0C: HardFault
// ... 其他异常向量
};
13. 性能优化技巧
- 使用位带操作 → 原子操作单个位,避免中断屏蔽
- 优化中断优先级 → 合理配置抢占优先级,减少中断延迟
- 使用 Thumb-2 指令 → 混合 16/32 位指令,提高代码密度
- 启用 MPU → 保护关键内存区域,提高系统稳定性
- 使用 WFI/WFE → 空闲时进入低功耗模式
- 对齐访问 → 避免非对齐访问导致的性能损失
- 利用尾链 → 连续中断时减少上下文切换开销
14. 常见故障处理
14.1 HardFault 调试
常见原因:
- 访问非法地址(空指针、越界)
- 非对齐访问(如
uint32_t访问奇数地址) - 执行非法指令
- 除零操作
- 堆栈溢出
调试方法:
- 查看 CFSR(Configurable Fault Status Register)
- 查看 HFSR(HardFault Status Register)
- 查看 MMFAR(MemManage Fault Address Register)
- 查看 BFAR(BusFault Address Register)
- 在 HardFault_Handler 中读取堆栈帧(R0-R3, R12, LR, PC, xPSR)
示例代码:
void HardFault_Handler(void) {
__asm volatile (
"TST LR, #4 \n" // 测试 bit 2
"ITE EQ \n"
"MRSEQ R0, MSP \n" // 使用 MSP
"MRSNE R0, PSP \n" // 使用 PSP
"B hard_fault_handler_c \n"
);
}
void hard_fault_handler_c(uint32_t *stack_frame) {
uint32_t r0 = stack_frame[0];
uint32_t r1 = stack_frame[1];
uint32_t r2 = stack_frame[2];
uint32_t r3 = stack_frame[3];
uint32_t r12 = stack_frame[4];
uint32_t lr = stack_frame[5];
uint32_t pc = stack_frame[6]; // 故障指令地址
uint32_t psr = stack_frame[7];
// 打印或记录寄存器值
while(1); // 停止执行
}
15. 参考资料
- ARM Cortex-M3 Technical Reference Manual
- ARMv7-M Architecture Reference Manual
- 《Cortex-M3 权威指南》(Joseph Yiu 著)