在前几讲中,我们构建了MCU的宏观视野,深入了指令执行流水线,理清了存储器布局,也掌握了时钟系统。现在,是时候真正走进CPU内部,去认识那些每天都在打交道、却又常常被忽略的“幕后英雄”——内核寄存器

如果说CPU是一座工厂,那么寄存器就是工人们手边的“工作台”。所有运算、判断、地址访问都离不开它们。理解寄存器,是理解C语言函数调用、RTOS任务切换、中断处理乃至底层调试的基石。

1. 什么是寄存器?

寄存器是CPU内部最高速的存储单元,位于处理器核心内部,与ALU(算术逻辑单元)直接相连。访问寄存器只需要一个时钟周期,而访问SRAM通常需要多个周期,访问Flash则更慢。

Cortex-M内核拥有16个通用寄存器(R0-R15)和若干个特殊功能寄存器。它们共享一个32位宽度(在Cortex-M中),且大部分操作都围绕这些寄存器展开。

注意:Cortex-M0/M0+只支持部分特殊功能寄存器,且有些功能(如BASEPRI)在M0上不可用。本讲以Cortex-M3/M4为主,兼顾差异。

2. 通用寄存器(R0-R12)——“临时工作台”

通用寄存器用于存放运算的操作数、中间结果、地址等。它们在功能上没有硬件规定的特殊用途(除了调用约定),程序员可以自由使用。

2.1 R0-R3:参数与返回值

  • R0-R3 在函数调用时用于传递前四个参数(整型或指针)。如果参数超过4个,多余的将通过栈传递。
  • R0 还作为函数返回值的存放位置(如果返回值是32位以内)。对于64位返回值,使用R0和R1。
  • 这些寄存器属于调用者保存(caller-saved),意味着在调用子函数前,如果调用者还需要使用这些寄存器中的值,必须将它们压栈保存;子函数可以直接修改它们而不需恢复。

2.2 R4-R11:局部变量与保存

  • R4-R11 用于存放局部变量或其他需要长久保留的值。
  • 它们属于被调用者保存(callee-saved)。如果一个函数使用了这些寄存器,它必须在函数开头将它们压栈,在返回前恢复原值。
  • R9 在某些操作系统中有特殊用途(如作为线程局部存储指针),但在裸机开发中可作为通用寄存器。
  • R11 在AAPCS(ARM架构过程调用标准)中常作为帧指针(FP),但在Cortex-M中,编译器通常不使用它,而是用栈指针(SP)来访问局部变量。

2.3 R12:内部过程调用暂存寄存器

  • R12 也称为IP(Intra-Procedure-call scratch register)。在函数调用时,它可用于临时存储,调用者无需保存其值。链接器有时用它进行代码段间的跳转。

3. 堆栈指针(R13,SP)——“工具箱”

堆栈指针是CPU中最重要的寄存器之一。它指向当前栈顶(在Cortex-M中栈是向下增长的,即栈顶地址逐渐减小)。

Cortex-M引入了双堆栈指针的概念,这是实现RTOS任务隔离的关键:

指针 全称 用途
MSP Main Stack Pointer 主堆栈指针。复位后默认使用,在特权模式下处理异常(中断)时也使用。
PSP Process Stack Pointer 进程堆栈指针。在线程模式下,如果选择了使用PSP,则用于用户任务的栈。

3.1 为什么需要两个堆栈指针?

在RTOS中,每个任务拥有独立的栈空间(通过PSP管理),而内核(中断服务函数)使用独立的MSP。这样:

  • 即使某个任务的栈溢出,也不会破坏内核的栈,提高了系统健壮性。
  • 中断响应更快,因为无需切换栈指针(MSP始终可用)。

在裸机系统中,通常只使用MSP,PSP被忽略。

3.2 CONTROL寄存器与栈指针选择

CONTROL 寄存器(特殊功能寄存器)的bit 1(SPSEL)控制当前使用的栈指针:

  • SPSEL = 0:使用MSP(无论在何种模式)
  • SPSEL = 1:在线程模式下使用PSP;在异常(中断)模式下强制使用MSP

4. 链接寄存器(R14,LR)——“回程票”

链接寄存器用于存储子函数返回地址。当执行BL(分支并链接)或BLX指令时,硬件会自动将下一条指令的地址存入LR。

BL func    ; 跳转到func,并将返回地址存入LR
...
func:
    ...
    BX LR    ; 返回到调用处

在异常(中断)处理中,LR的用途更为特殊。当进入中断服务函数时,LR会被硬件自动设置为一个特殊值(如0xFFFFFFF9),指示返回时使用MSP并进入线程模式;退出中断时,BX LR 会触发异常返回机制,自动恢复现场。

5. 程序计数器(R15,PC)——“路标”

程序计数器指向当前正在取指的指令地址。在Cortex-M中,由于流水线的存在,PC的值通常是当前执行指令地址 + 4(对于三级流水线)。

  • 直接修改PC可以实现跳转(如MOV PC, #addr),但通常使用BX指令。
  • 在调试时,PC值告诉程序运行到哪里。
  • PC的bit 0必须为0(因为Cortex-M只支持Thumb指令,所有地址都是半字对齐),但跳转指令中如果bit 0为1会切换到Thumb状态(实际上Cortex-M一直处于Thumb状态,所以总是为0)。

6. 特殊功能寄存器——“控制中心”

除了通用寄存器,Cortex-M还定义了一组特殊功能寄存器,它们位于系统控制空间(SCS,地址0xE000E000起),通过专用指令(如MRSMSR)访问。

6.1 xPSR(程序状态寄存器)

xPSR 实际上是三个PSR的组合:

  • APSR(应用程序状态寄存器):包含ALU标志位(N、Z、C、V、Q等)。
  • IPSR(中断状态寄存器):包含当前正在处理的异常编号(0表示线程模式,非0表示中断号)。
  • EPSR(执行状态寄存器):包含执行状态信息(如Thumb状态位,始终为1)。

通过MRS指令可以读取组合的xPSR,也可以分别读取APSR、IPSR、EPSR。

6.2 PRIMASK、FAULTMASK、BASEPRI——中断屏蔽三兄弟

这三个寄存器用于控制中断的响应,是实现临界区保护的关键。

寄存器 功能 清零方式
PRIMASK 置1时,屏蔽所有可屏蔽中断(除了NMI和HardFault) CPSIE I
FAULTMASK 置1时,屏蔽所有可屏蔽中断及HardFault(NMI除外) CPSIE F
BASEPRI 设置一个优先级阈值,只屏蔽优先级数值大于等于该阈值的异常 写0

使用场景

  • 在访问共享资源时,使用__disable_irq()(即设置PRIMASK)来关中断,实现临界区保护。
  • 在某些调试场景下,使用FAULTMASK来暂时屏蔽所有故障。
  • BASEPRI可以实现更细粒度的屏蔽:例如只允许优先级最高的几个中断响应,其他中断全部屏蔽。

6.3 CONTROL寄存器——模式与栈选择

功能
bit 0 0=特权模式,1=用户模式(线程模式下有效)
bit 1 0=使用MSP,1=线程模式下使用PSP
bit 2 0=无浮点上下文,1=浮点上下文激活(如果FPU存在)

用户模式下,无法访问某些系统寄存器,也无法执行CPS指令修改PRIMASK等,这为RTOS提供了任务隔离的基础。

7. 异常与中断中的寄存器自动压栈

当异常发生时,Cortex-M硬件会自动将8个寄存器压入当前栈(MSP或PSP),以保存现场:

压栈顺序(地址递增):
    R0
    R1
    R2
    R3
    R12
    LR
    PC (返回地址)
    xPSR

这个自动压栈的过程仅需12个时钟周期(在Cortex-M3上),大大缩短了中断延迟。中断返回时,硬件自动弹出这些寄存器,恢复现场。

如果使用了FPU,还会额外压入S0-S15、FPSCR等寄存器(共34字)。

8. 实例分析:C函数与寄存器

考虑以下简单函数:

int add(int a, int b) {
    return a + b;
}

编译后(ARM GCC,优化-O0)的汇编可能为:

add:
    push {r7, lr}       ; 保存帧指针和返回地址
    mov  r7, sp         ; 设置帧指针(可选)
    sub  sp, sp, #8     ; 为局部变量预留空间(此处无局部变量,仅为演示)
    str  r0, [r7, #-8]  ; 将参数a存入栈(r0是第一个参数)
    str  r1, [r7, #-12] ; 将参数b存入栈
    ldr  r2, [r7, #-8]  ; 从栈加载a到r2
    ldr  r3, [r7, #-12] ; 从栈加载b到r3
    add  r0, r2, r3     ; 相加,结果放入r0(返回值)
    add  sp, sp, #8     ; 回收局部变量空间
    pop  {r7, pc}       ; 恢复r7,并返回(pc从lr弹出)

在这个例子中,我们可以看到:

  • 参数通过R0和R1传入。
  • 返回值通过R0传出。
  • LR被保存到栈上,随后被弹出到PC完成返回。

如果启用优化(-O1),代码会简洁得多:

add:
    add  r0, r0, r1   ; 直接使用R0、R1
    bx   lr

9. 总结:寄存器是软硬件交互的“界面”

这一讲,我们详细拆解了Cortex-M内核的寄存器体系:

  1. 通用寄存器 R0-R12 是运算的主力,遵循AAPCS调用约定,决定了函数参数传递和局部变量的存储方式。
  2. 堆栈指针 SP(R13)管理函数调用和中断的现场保存,双堆栈机制为RTOS提供了任务隔离。
  3. 链接寄存器 LR(R14)保存返回地址,在异常处理中扮演关键角色。
  4. 程序计数器 PC(R15)指示指令流走向。
  5. 特殊功能寄存器(xPSR、PRIMASK、BASEPRI、CONTROL等)控制程序状态、中断屏蔽和模式切换,是实现临界区保护和特权级分离的基础。

理解这些寄存器,你就能看懂反汇编代码,理解函数调用开销,掌握中断响应机制,甚至能够手动编写启动文件和任务切换代码。

下一讲预告:我们将进入中断与事件的世界,深入NVIC(嵌套向量中断控制器),剖析中断优先级、中断嵌套、尾链技术以及HardFault的调试方法。


思考题:在RTOS中,当进行任务切换时,需要保存当前任务的哪些寄存器?为什么不需要保存R0-R3?这与AAPCS调用约定有什么关系?

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐