ARM Cortex-M3 核心知识指南

嵌入式系列(一):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
💡 提示:在 FreeRTOS 中,每个任务使用独立的 PSP,中断处理使用 MSP,实现任务堆栈隔离和保护。

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();      // 恢复中断
}
⚠️ 注意:FAULTMASK 只能在特权模式下修改,且从异常返回时自动清零。

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 异常处理流程

  1. 异常触发 → 硬件自动入栈(R0-R3, R12, LR, PC, xPSR)
  2. 取向量 → 从向量表读取异常处理函数地址
  3. 执行 ISR → 跳转到中断服务程序
  4. 异常返回 → 执行特殊返回指令(LR = 0xFFFFFFF9/D/1)
  5. 自动出栈 → 恢复寄存器,返回线程模式

EXC_RETURN 值:

  • 0xFFFFFFF1:返回 Handler 模式,使用 MSP
  • 0xFFFFFFF9:返回 Thread 模式,使用 MSP
  • 0xFFFFFFFD:返回 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 为什么重要?

  1. 节省 Flash 空间 → 降低芯片成本
  2. 提高取指效率 → 16 位指令一次取两条,减少总线占用
  3. 降低功耗 → 更少的内存访问 = 更低功耗
  4. 无需手动切换 → 编译器自动优化,开发者无感知

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. 启动流程

  1. 复位 → PC 指向复位向量(向量表偏移 0x04)
  2. 初始化 SP → 从向量表偏移 0x00 加载 MSP 初值
  3. 执行复位处理函数 → 通常为 Reset_Handler
  4. 系统初始化 → 时钟配置、外设初始化
  5. 跳转到 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. 性能优化技巧

  1. 使用位带操作 → 原子操作单个位,避免中断屏蔽
  2. 优化中断优先级 → 合理配置抢占优先级,减少中断延迟
  3. 使用 Thumb-2 指令 → 混合 16/32 位指令,提高代码密度
  4. 启用 MPU → 保护关键内存区域,提高系统稳定性
  5. 使用 WFI/WFE → 空闲时进入低功耗模式
  6. 对齐访问 → 避免非对齐访问导致的性能损失
  7. 利用尾链 → 连续中断时减少上下文切换开销

14. 常见故障处理

14.1 HardFault 调试

常见原因:

  • 访问非法地址(空指针、越界)
  • 非对齐访问(如 uint32_t 访问奇数地址)
  • 执行非法指令
  • 除零操作
  • 堆栈溢出

调试方法:

  1. 查看 CFSR(Configurable Fault Status Register)
  2. 查看 HFSR(HardFault Status Register)
  3. 查看 MMFAR(MemManage Fault Address Register)
  4. 查看 BFAR(BusFault Address Register)
  5. 在 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. 参考资料