type
date
slug
category
icon
password
操作系统定义1:为用户及其应用程序管理计算机资源的软件层。
Definition: the layer of software that manages a computer's resources for its users and their applications
简单的嵌入式RTOS只完成任务调度器功能,其核心是按一定需求调度用户任务和系统任务,以获取处理器的计算资源和内存资源。而高效调度需要适配具体处理器,尤其对于CM3/CM4处理器,在设计之处就考虑到对嵌入式RTOS的高效支持,更需要了解其处理器架构和特性。对于这些特性学习(Systick 中断、栈指针、异常处理等),同样加深RTOS应用者对于操作系统移植配置的理解。
下面让我们从Cortex-M处理器架构谈起。
以下重点内容通过提示框标出,主要是与嵌入式RTOS设计相关概念和应用。
处理器一般信息
处理器类型
Cortex-M 处理器采用【哈佛总线架构】(不同于冯.诺依曼架构),该架构特点是将指令存储和数据存储分开,使用独立的储存器与总线系统。这种分离允许数据和指令同时访问,实现三级流水线(取指、译码和执行)从而提高了系统吞吐量和效率。
哈佛架构中:
- 指令存储器:专门用于存储程序指令(存储的二进制机械码,汇编和机械码转换可参考ARMv7-M架构参考手册)。
- 数据存储器:用于存储数据。
- 独立总线:分别用于指令和数据的传输。
Cortex-M处理器数据处理基于【加载-存储】架构,处理器使用一条指令将数据从SRAM中读出,放入到处理器寄存器中,然后使用第二条指令更改数据,最后使用第三条指令将数据保存回存储器中。处理器内部寄存器细节参考处理器编程模型。
处理器架构
不同的ARM处理器使用不同的ARM架构版本,例如 ARMv6-M、ARMv7-M、ARMv8-A 等,决定了处理器的指令集、性能和功能特性。Cortex-M3 和 Cortex-M4处理器都是基于ARMv7-M架构。对于ARM处理器,架构通常指两个方面:架构和微架构。架构指指令集架构、编程模型和调试方法,微架构指相关执行细节(接口信号、指令执行时序及流水线阶段等)。一个指令集架构可以包含多个微架构设计。
处理器型号 | 架构版本 | 典型应用领域 | 指令集 | 内核数量 | 缓存支持 | 浮点运算单元 (FPU) | 内存管理单元 (MMU) | 主要特点 |
Cortex-M0 | ARMv6-M | 微控制器、嵌入式 | Thumb (16位) | 单核 | 不支持 | 可选的简单 FPU | 不支持 | 超低功耗,简单指令集,适合低成本设备 |
Cortex-M3 | ARMv7-M | 微控制器、嵌入式 | Thumb-2 (16/32位) | 单核 | 不支持 | 可选的简单 FPU | 不支持 | 高能效,支持更复杂的嵌入式应用 |
Cortex-M4 | ARMv7E-M | 微控制器、嵌入式 | Thumb-2 (16/32位) | 单核 | 不支持 | 支持 FPU 和 DSP 指令 | 不支持 | 增强的 DSP 和浮点运算能力,适合数字信号处理 |
Cortex-M7 | ARMv7E-M | 高性能嵌入式 | Thumb-2 (16/32位) | 单核/多核 | 支持(I/D 缓存) | 支持 FPU (双精度) | 不支持 | 高性能,支持指令和数据缓存,适合复杂实时应用 |
Cortex-A53 | ARMv8-A | 移动设备、服务器 | AArch64/ARM (64位) | 多核 (1-8核) | 支持 L1/L2 缓存 | 支持 NEON 和 FPU | 支持 (完整 MMU) | 64位处理器,支持大容量内存,适合移动和服务器应用 |
Cortex-A72 | ARMv8-A | 高端移动、服务器 | AArch64/ARM (64位) | 多核 (1-8核) | 支持 L1/L2 缓存 | 支持 NEON 和 FPU | 支持 (完整 MMU) | 高性能处理器,适合高端智能手机和服务器 |
Cortex-A76 | ARMv8.2-A | 高端移动、服务器 | AArch64/ARM (64位) | 多核 (1-8核) | 支持 L1/L2/L3 缓存 | 支持 NEON 和 FPU | 支持 (完整 MMU) | 极高性能,适合旗舰级设备和高性能计算 |
Cortex-R5 | ARMv7-R | 实时嵌入式系统 | ARM/Thumb-2 (32位) | 单核/多核 | 支持 (I/D 缓存) | 支持 FPU 和 DSP 指令 | 支持 MPU(内存保护单元) | 实时处理器,适合汽车和工业控制 |
Cortex-R8 | ARMv7-R | 实时嵌入式系统 | ARM/Thumb-2 (32位) | 多核 (1-4核) | 支持 (I/D 缓存) | 支持 FPU 和 DSP 指令 | 支持 MPU(内存保护单元) | 多核实时处理器,适合高可靠性系统 |
- Cortex-M3 和Cortex-M4处理器都基于ARMv7-M架构,最初的ARMv7-M架构是随着Cortex-M处理器一同引入的,而在Cortex-M4发布时,架构中又增加了新的指令和特性,改进后的架构有事又被称为ARMv7E-M架构。
- ARMv7-M和ARMv7E-M架构设计细节可参考 ARMv7-M Architecture Reference Manual。
- 微架构信息参考Cortex-M处理器的技术参考手册(TRM)、产品手册。
指令集
Cortex-M 处理器使用的指令集名为Thumb(其中包括16位Thumb指令和更新的32位 Thumb指令),Cortex-M3和Cortex-M4处理器用到了Thumb-2技术,它允许16位和32位指令的混合使用,以获取更高的代码密度和效率。
这里经典 ARM 处理器,存在两种指令集,32位和16位。32位指令性能更强,16位更精简,很好的代码密度。为了混合这两种优势,经典处理器会在应用程序混合使用 ARM 和 Thumb 代码,这种方式一是会增加切换开销,二是增加软件编译过程复杂度。

通过引入Thumb-2技术,在编译阶段将Thumb指令解码位16位或32位方式,无序进行状态切换。事实上Cortex-M处理器根本不支持32位的ARM指令,甚至中断处理都可以完全在Thumb状态中处理。

模块框图
虽然M3/M4 由于内部数据通路设计存在较大差异(M4增加DSP扩展和硬件浮点单元(FPU),内部数据设计更加复杂),处理器一些部分还是存在相似性(流水线架构、嵌套向量中断控制器(NVIC)、SystTick定时器、外围接口、内存访问控制、调试接口)。

存储器系统
处理器本身并不包含存储器(没有程序存储器、SRAM或者缓存),需要微控制器厂家利用片上总线接口,将存储器系统添加到系统中。一般需要添加的存储器系统有程序存储器(一般使用FLASH)、数据存储器(一般选用SRAM)和外设。
处理器和微控制器关系:处理器只是微控制器芯片中的一部分。微控制器芯片还需要添加存储器系统、外设和各种接口。
Cortex-M处理器的总线接口为32位宽,且基于高级微控制器总线架构(AMBA)标准。AMBA包含多个总线协议,Cortex-M3和Cortex-M4主要使用AHB-Lite 和 APB两个总线协议。其中:
- AHB-Lite(Advanced High-performance Bus Lite)高性能总线,是AHB简化版本,单主设备的总线协议(处理器作为主设备,外设作为从设备),支持高带宽、高效的数据传输(全双工,支持突发传输,)。主要用于高带宽外设,比如SRAM(片上 RAM)、Flash(片上或外部闪存)、DMA 控制器(Direct Memory Access Controller)、系统控制寄存器(如 NVIC、SysTick 等)。
- APB 是 AMBA 总线体系中的一种低带宽、低功耗的总线协议,专门用于连接低速外设。用于那些不需要高吞吐量的外设,比如 UART、I2C、SPI、GPIO、定时器等。APB 通常作为 AHB-Lite 的从属总线,通过桥接器(APB Bridge)连接到 AHB-Lite 总线。

32 位的 multi-AHB 总线矩阵将所有主设备 (CPU、 DMA、以太网、 USB HS、 LCD-TFT、DMA2D)和从设备(Flash、RAM、FMC、AHB、APB 外设)互连,可以实现多个主设备(如处理器、DMA 控制器)并行访问不同的外设,从而提高系统的吞吐量。
中断和异常支持
Cortex-M3和Cortex-M4处理器通过嵌套向量中断控制器(NVIC)进行终,通过存储器映射,NVIC地址固定,中断优先级数量可编程。除了外设和其他外部输入的中断外,还支持多个系统异常。
⭐ 编程模型 - 操作模式和状态
操作模式和状态
Cortex-M3和Cortex-M4处理器有两种操作状态和操作模式,另外还区分特权和非特权访问(也可称作“用户”)等级。Cortex-M处理器启动后默认处于特权线程模式以及Thumb状态。裸机程序中非特权模式和影子栈用不上。

- 操作状态
- 调试状态:当处理器遇到调试事件或请求(断点、观察点、单步执行、异常事件等)后处理器被暂停,指令停止执行,就会进去调试状态。
- Thumb状态:当程序执行程序代码(Cortex-M处理器只支持Thumb指令),处理器就会处于Thumb状态。
- 操作模式
- 处理模式:执行中断服务程序(ISR)等异常处理,在该模式下总是具备特权访问等级。
- 线程模式:执行普通应用程序代码,该模式既可以处于特权访问等级也可以处于非特权访问等级。实际的访问等级由特殊寄存器CONTROL控制。注意特权访问等级和非特权访问等级切换单向,可能特权切到非特权,而无法由非特权切换到特权
嵌入式OS开发设计注意:
- 区分特权和非特权,限制关键区域访问,提供了基本安全模型。嵌入式OS设计,内核运行在特权访问等级,用户应用程序在非特权访问等级,使用MPU设置存储器访问权限。避免应用程序破坏OS内核和使用的存储器和外设。即使应用程序崩溃了,剩下的应用任务和OS内核还可以继续运行。
- 线程模式可以独立使用影子栈,使得应用任务栈空间和嵌入式OS内核相互独立,进一步提高系统可靠性。
⭐ 编程模型 - 寄存器
寄存器

在 arm 中有个 ATPCS 规则(ARM-THUMB procedure call standard(ARM-Thumb过程调用标准)。
约定R0-R15寄存器的用途:
- R0-R3 传参
调用者和被调用者之间传参数
- R0 返回值
- R4-R11
函数可能被使用,所以在函数的入口保存它们(寄存器保存进入栈),在函数的出口恢复它们(栈到寄存器)。
- R13-SP
- R14-LR
- R15-PC
对于如下介绍的特殊寄存器,平时应用开发不常用到,但嵌入式OS开发,需要高级中断屏蔽特性时就会用到。
特殊寄存器-程序状态寄存器
程序状态寄存器包括以下三个状态寄存器:
- 应用 PSR(APSR)
- 执行 PSR(EPSR)
- 中断 PSR(IPSR)
这三个寄存器可以通过一个组合寄存器访问,该寄存器在有些文献中也被称作xPSR。


*GE is available in ARMv7E-M processors such as the Cortex-M4. It is not available in the Cortex-M3 processor.
程序状态寄存器中位域(详细说明见JosephYiu.ARM Cortex-M3与Cortex-M4权威指南[M].清华大学出版社,2015. 4.3小节):
位 | 描述 |
N | 负标志 |
Z | 零标志 |
C | 进位标志 |
V | 溢出标志 |
Q | 饱和标志 |
GE[3:0] | 大于或等于标志,对应每个字节通路 |
ICI/IT | 中断继续指令(ICI)位,IF-THEN指令状态位用于条件执行 |
T | Thumb状态,总是1,清除此位会引起错误异常 |
Exception Number | 表示处理器正在处理的异常 |
对于ARM汇编器,在访问xPSR时,使用的是PSR。主要有如下指令:
由于不同架构下PSR位域定义不同,有些位域不存在,具体参见下表:

特殊寄存器-用于异常和中断屏蔽
如下三个寄存器用于异常和中断屏蔽,主要如下特性:
- 只有特权访问等下才可以对它们进行操作(非特权状态在写操作会被忽略,而读出则返回0)
- 默认为0,也就是屏蔽功能不起作用(即使能所有中断)
1. PRIMASK
PRIMASK最常见的用途为,在时间要求很严格的进程中禁止所有中断,在该进程完成后,需要将 PRIMASK 清除以重新使能中断。

把PRIMASK的bit0设置为1,就可以屏蔽所有优先级可配置的中断。 可以使用这些指令来设置它:
2. FAULTMASK
与PRIMASK类似,将异常优先级提升到-1,甚至能屏蔽HardFault, 只有NMI可以发生。与PRIMASK不同,FAULTMASK异常返回后会自动复位。可以使用这些指令来设置它:

3. BASEPRI
为使中断屏蔽更加灵活,ARMv7-M架构还支持BASEPRI,该寄存器会根据优先等级屏蔽异常和中断。BASEPRI 用来屏蔽这些中断:它们的优先级,其值大于或等于BASEPRI。

可以使用这些指令来设置它:
使用BASEPRI,降低中断延迟
中断延迟呢? 从中断触发到执行中断服务程序的第一条指令这段时间就是中断延迟时间。
FreeRTOS 内核源码中有多处开关全局中断的地方,这些开关全局中断会加大中断延迟时间。 比如在源码的某个地方关闭了全局中断,但是此时有外部中断触发,这个中断的服务程序就需要等到再次开启全局中断后才可以得到执行。开关中断之间的时间越长,中断延迟时间就越大,这样极其影响系统的实时性。如果这是一个紧急的中断事件, 得不到及时执行的话,后果是可想而知的。
针对这种情况, FreeRTOS 就专门做了一种新的开关中断实现机制。 关闭中断时仅关闭受 FreeRTOS管理的中断,不受 FreeRTOS 管理的中断不关闭, 这些不受管理的中断都是高优先级的中断,用户可以在这些中断里面加入需要实时响应的程序。 FreeRTOS 能够实现这种功能的奥秘就在于 FreeRTOS 开关中断使用的是寄存器 basepri,而像 uCOS 这种使用的是 primask。
特殊寄存器-CONTROL 寄存器
CONTROL寄存器定义了:
- 栈指针的选择(主栈指针/进程栈指针)。
- 线程模式的访问等级(特权/非特权)。
- 当前上下文(正在执行的代码)是否使用浮点单元(针对具有浮点单元的Cortex-M4处理器)。

CONTROL寄存器只能在特权访问等级进行修改操作,而读取操作则在特权和非特权访问等级都可以。CONTROL寄存器的具体位域定义可以参考下表的内容。
位 | 功能 |
nPRIV(第0位) | 定义线程模式中特权等级
0 默认- 处于线程模式特权等级
1- 处于线程模式非特权等级 |
SPSEL(第1位) | 定义栈指针的选择
0 默认- 线程模式使用主栈指针
1- 使用进程栈
处理模式下始终为0 |
FPCA(第2位) | 浮点上下文活跃,只存在于具有浮点单元的Cortex-M4处理器
0 - 未使用浮点指令
1 - 当前上下文使用浮点指令,异常产生时需要保存浮点单元寄存器
执行浮点指令时自动置位,异常入口处被硬件清除。 |
复位后,CONTROL 寄存器默认为0,处于线程模式、具有特权访问权限以及使用主栈指针。
1. 栈指针选择
通过修改 CONTROL 寄存器,可以切换栈指针的选择或进入非特权访问等级。但nPRIV置位后,运行在线程模式下程序就不能访问 CONTROL 寄存器了。

2. 特权模式和非特权模式切换
运行在非特权等级的程序无法再切换回特权访问等级,这样就提供了一个基本的安全模型。若有必要将处理器在线程模式切换回特权访问等级,则需要使用异常机制。
在异常处理期间,处理程序可以清除 nPRIV 位,返回线程模式后,处理器就会进入特权访问等级。

若使用嵌入式 OS,每次上下文切换时都可以重新编程 CONTROL 寄存器,以满足应用间不同的特权访问等级需要。
3. nPRIV 和 SPSEL 的选择
nPRIV和SPSEL的设置共有4种组合方式,其中3种在实际应用中较为常见,如下表所示:
nPRIV | SPSEL | 应 用 场 景 |
0 | 0 | 简单裸机应用,整个应用运行在特权访问等级,主程序和中断处理只会使用一个栈,也就是主栈(MSP) |
0 | 1 | 具有嵌入式OS的应用,当前执行的任务运行在特权线程模式,当前任务选择使用进程栈指针(PSP),而MSP则用于OS内核以及异常处理 |
1 | 1 | 具有嵌入式OS的应用,当前执行的任务运行在非特权线程模式,当前任务选择使用进程栈指针(PSP), OS 内核及异常处理使用MSP |
1 | 0 | 线程模式运行在非特权访问等级,且使用MSP,处理模式中可见,而用户任务则一般无法使用,这是因为在多数嵌入式OS中,应用任务的栈和OS内核以及异常处理使用的栈是相互独立的 |

注意1:在修改了CONTROL寄存器后,从架构来看,应该使用指令同步屏障(ISB)指令(或符合CMSIS的设备驱动库中的一ISB() 函数),以确保本次修改对接下来的代码能起到作用。由于Cortex-M3、Cortex-M4、Cortex-M0+、Cortex-M0以及Cortex-M1的流水线非常简单,不使用ISB指令也不会引起什么问题。
注意2:对于具有FPU单元的Cortex-M4处理器,FPCA会自动置位。但若程序含有浮点计算同时FPCA位被意外清除,同时来了一个中断,这种情况下,异常入口流程不会保存浮点单元寄存器,被中断处理覆盖,导致程序无法继续运行。
可以用如下指令操作Control寄存器。
可以判断CONTROL和IPSR,判断当前是否处于特权等级。
⭐ 编程模型 - 存储器系统
- 32位寻址,多达4GB存储器空间;
- 4GB存储器空间做好划分,用于预定义的存储器和外设。并通过总线矩阵并行访问不同地址,提高效率。
- 存储器系统支持大小端(实际设计只选用一种配置)
- 通过 实现SRAM和外设地址单独位原子操作
- 写缓冲:简单说就是先将数据写入缓冲区,由后台硬件单元负责将这些缓存中的数据异步地写入主内存。使用写缓存,可以缓解内存访问瓶颈,提高指令执行效率
- 内存屏障:
- 使用内存屏障指令(如 ARM 中的 DMB, DSB)来确保特定操作顺序,以防止因为写缓冲导致的数据不一致。
- 任务切换同步:
- 在任务切换时,确保关键数据已经刷新到内存。使用 RTOS 提供的 API 来保障临界区中的数据一致性。
- 中断处理与共享资源:
- 在访问共享资源时,通过禁用中断或启用互斥锁来保护该操作,使得写缓冲不会导致在中断上下文下的数据不一致问题。
在嵌入式RTOS中,需要特别注意以下几个方面:
- 支持存储器保护单元(MPU)
- 支持非对齐传输
Cortex-M处理器的总线接口为通用总线接口,可通过不同的存储器控制器被连接至不同类型和大小的存储器。微控制器存储器系统中的存储器一般为两种或更多:程序代码用的Flash存储器、数据用的静态RAM(SRAM),有时还会有电可擦除只读存储器(EEPROM)。
大多情况下,这些存储器位于芯片内部,实际的存储器接口细节对软件开发人员是不可见的。因此,软件开发人员只需了解程序存储器和SRAM的地址和大小即可。
存储器映射
Cortex-M处理器的4GB地址空间被分为了多个存储器区域,分为几个部分:
- 程序代码访问(如CODE区域)
- 数据访问(如SRAM区域)
- 外设(如外设区域)
- 处理器的内部控制和调试部件(如私有外设总线)
内存架构下统一寻址:ARMv7-M 使用统一的地址空间,CODE 区域和 SRAM 区域都只是地址中的特定区域。处理器并不区分这些区域是用于代码还是数据,只要地址是可访问的,就可以被读取、写入或执行。因此程序既可以在CODE区域执行,也可以在SRAM区域执行。

所有Cortex-M处理器的存储器映射处理都是一样的,例如PPB地址区域中存在嵌套向量中断控制器(NVIC)的寄存器、处理器配置寄存器以及调试部件的寄存器等,所有的Cortex-M设备都是这么设计的,这样可以提高不同Cortex-M设备间的软件可移植性和代码可重用性。这对开发工具供应商也非常有利,因为Cortex-M3和Cortex-M4设备调试控制的工作方式都是一样的。
栈存储
Cortex-M处理器在运行时需要栈存储和栈指针(R13)。
栈空间和机制
栈存储使用机制:先进后出(First Input Last Output,FILO)。ARM处理器将SRAM用于栈空间操作,且增加入栈PUSH指令和出栈POP指令。
对比堆存储结构,栈由编译器自动管理,使用空间相对较小,操作效率高(处理器分配寄存器存放栈地址,并提供压栈出栈专门指令)。而堆由使用者分配管理,容易产生内存泄露,空间向上增长,可分配空间大,堆的操作由C函数库提供,机制更加复杂。

出栈入栈靠栈指针维护,栈指针会自动调整。根据栈指针SP指向栈顶元素不同,分为满栈(SP指向栈顶元素)和空栈(指向可用空间 )根据栈生长方向分为递增栈(从低地址到高地址增长)和递减栈(从高地址到低地址增长)。Cortex-M处理器使用的栈模型被称为“满递减”,即栈地址从高地址向低地址生长,SP指向栈顶元素。

栈用途
- 当正在执行的函数需要使用寄存器(寄存器组中)进行数据处理时,临时存储数据的初始值。这些数据在函数结束时可以被恢复出来,以免调用函数的程序丢失数据。
- 往函数或子程序中的信息传递。
- 用于存储局部变量。
- 在中断等异常产生时保存处理器状态和寄存器数值。
在 中会对函数调用,中断异常两个场景下使用进行详细分析。
操作指令和应用
对于入栈PUSH指令和出栈POP指令操作过程,见下面示意图:

PUSH和POP指令最常见的用法为,在执行函数或子程序调用时保存寄存器组中的内容。
- 调用开始,PUSH指令保存寄存器内容到栈中;
- 调用结束,POP指令恢复初始值。

可以将函数返回和POP操作结合起来,首先,把LR(R14)的数值压到栈存储中,在子程序/函数结束时将其恢复到PC(R15),如下图所示。

两个栈指针
Cortex-M处理器在物理上存在两个栈指针。它们为:
- 主栈指针(MSP)。复位后默认使用的栈指针,用于所有的异常处理。
- 进程栈指针(PSP)。只能用于线程模式的栈指针,通常用于运行嵌入式OS的嵌入式系统中的应用任务。
栈指针选择可以通过两种方式,第一种在特权模式下由CONTROL寄存器(特殊寄存器-CONTROL 寄存器 ,点击这里跳转)SPSEL位的数值决定。另外从处理模式到线程模式的异常返回期间,栈指针的选择可由 EXC_RETURN(异常返回)数值决定,处理器硬件根据 EXC_RETURN 数值自动更新SPSEL数值。
对于简单裸机应用和嵌入式OS应用,栈指针的选择也不同。简单应用只用到MSP,而嵌入式OS,为了隔离应用任务和内核栈空间,PSP会被用到,异常入口和返回会发生SP的切换。


MSP和PSP栈可通过如下命令访问修改:
- 嵌入式OS中,读取PSP,可获得应用任务栈中压入的数据。OS上下文切换期间,会修改PSP数值。
- 上电后,处理器硬件在读取向量表后会自动初始化MSP。本书在节将会介绍向量表的细节内容。PSP则不会被自动初始化,需要在使用前由软件初始化。
CPU眼里的:函数调用 | 调用栈回溯 - 阿布的视频 - 知乎
https://www.zhihu.com/zvideo/1457018650724765697
存储器保护单元(MPU)
在需要高可靠的嵌入式系统中,MPU可以通过定义特权和非特权访问权限,来保护存储器区域。
嵌入式OS中,每个任务都被配置了存储器访问权限,以保证系统可靠性。
⭐ 编程模型 - 异常和中断
异常和中断关系
异常是改变程序流的事件,当异常发生,处理器暂停当前正在执行的任务,转而执行异常处理程序
对于ARM架构,中断是异常的一种,由外设或外部输入产生,中断的异常处理称作中断服务程序(ISR)。

各种异常源

cortex-M处理器具有多个异常源,主要分为系统异常和中断,其中系统异常包括:
- 复位(Reset)
- 硬故障(HardFault)
- 内存管理故障(MemManage Fault)
- 总线故障(BusFault)
- 使用故障(UsageFault)
还有不可屏蔽中断(NMI: Non-Maskable Interrupt),可用于看门狗定时器或掉电检测。
还有软件产生,支持嵌入式OS操作的异常SVCall (Supervisor Call) 异常、PendSV 异常 (Pendable Service Call)、系统计时器中断(SysTick)。
还有调试监控,用于基于软件的调试。
下表就是我们常见到的异常类型表,每个异常源都有一个异常编号,编号1~15被归为系统异常,16号及其之上的则用于中断。

注意,异常编号的定义和CMSIS的设备驱动库中的中断编号定义不同。在CMSIS的设备驱动库中,中断编号从0开始,系统异常编号则为负值。
中断控制用的NVIC寄存器
嵌套向量中断控制器NVIC是Cortex-M处理器一部分,可编程,寄存器位域存储器映射的系统控制空间(SCS)。

主要具有如下特性:
- 灵活的中断和异常管理(可使能或禁止,挂起状态可软件设置或清除,支持脉冲和电平触发中断请求)
- 支持嵌套异常/中断(中断优先级可编程,支持高优先级“抢占”)
- 向量化的异常/中断入口(异常发生通过向量表确定异常处理入口位置,降低从异常产生到处理的延时)
- 支持中断屏蔽(PRIMASK、FAULTMASK、BASEPRI可只支持不同程度的中断屏蔽,详细解释参考上文特殊寄存器-用于异常和中断屏蔽 ,点击这里跳转。)
NVIC中相关寄存器一览:
STM32 支持 5 种优先级分组,系统上电复位后, 默认使用的是优先级分组0,也就是没有抢占式优先级,只有子优先级。

中断优先级控制

- 具有高抢占式优先级的中断可以在具有低抢占式优先级的中断服务程序执行过程中被响应,即中断嵌套,或者说高抢占式优先级的中断可以抢占低抢占式优先级的中断的执行。
- 在抢占式优先级相同的情况下,有几个子优先级不同的中断同时到来,那么高子优先级的中断优先被响应。
- 在抢占式优先级相同的情况下,如果有低子优先级中断正在执行,高子优先级的中断要等待已被响应的低子优先级中断执行结束后才能得到响应, 即子优先级不支持中断嵌套。
- Reset、 NMI、 Hard Fault 优先级为负数, 高于普通中断优先级, 且优先级不可配置。
- 对于初学者还有一个比较纠结的问题就是系统中断(比如: PendSV, SVC, SysTick)是不是一定比外部中断(比如 SPI,USART)要高,答案:不是的,它们是在同一个 NVIC 下面设置的。
向量表
向量,在数学定义里是有方向的量,在程序里可以认为向量就是一个数组,里面有多个项。在ARM架构里,对于异常/中断,它们的处理入口会整齐地排放在一起。M3/M4的向量表中,存储放置的是具体异常/中断的处理函数的地址。
比如发生
Reset
异常时,CPU 就会从向量表里找到第1项,得到Reset_Handler
函数的地址,跳转去执行。
比如发生EXTI Line 0
中断时,CPU 就会从向量表里找到第22项,得到EXTI0_IRQHandler
函数的地址,跳转去执行。向量表可以重定位,即起始地址可通过NVIC中向量表偏移寄存器(VTOR)控制调整,复位后,VTOR默认位0,向量表则位于地址0x0处。
错误处理
Cortex-M3和Cortex-M4处理器中有几个异常为错误处理异常(硬故障(HardFault)、内存管理故障(MemManage Fault)、总线故障(BusFault)和使用故障(UsageFault))。处理器检测到错误时,就会触发错误异常,检测到的错误包括执行未定义的指令以及总线错误对存储器访问返回错误的响应等。错误异常机制使得错误可以被快速发现,软件因此也可以执行相应的修复措施。
总线错误、使用错误以及存储器管理错误默认都是禁止的,且所有的错误事件都会触发HardFault异常。不过,这些配置都是可编程的,可以单独使能这三个错误异常,以处理不同类型的错误。HardFault异常总是使能的。
错误异常也可在软件调试时使用。例如,在错误产生时,错误异常可以自动收集信息及通知用户或其他系统错误已产生,并能够提供调试信息。Cortex-M3和Cortex-M4处理器中有多个可用的错误状态寄存器(下文SCB控制块中),它们提供了错误源等信息。开发人员可以在软件开发过程中利用调试器检查这些错误状态寄存器。

系统控制块SCB
SCB为处理器的一部分,位于NVIC中。SCB包含寄存器,用于:
- 控制处理器配置(如低功耗模式)
- 提供错误状态信息(错误状态寄存器)
- 向量表重定位(VTOR)
⭐ 编程模型 - 复位和复位流程
对于典型的Cortex-M微控制器,复位类型共有三种:
- 上电复位。复位微控制器中的所有部分,其中包括处理器、调试支持部件和外设等。
- 系统复位。只会复位处理器和外设,不包括处理器的调试支持部件。
- 处理器复位。只复位处理器。
在系统调试或处理器复位操作过程中,Cortex-M3或Cortex-M4处理器中的调试部件不会复位,这样可以保持调试主机(如运行在计算机上的调试器软件)和微控制器间的连接。调试主机可以通过系统控制块(SCB)中的寄存器产生系统复位或处理器复位。


M3,M4内核
芯片上电复位后,处理器从存储器读出头两个字,分别位MSP和复位向量,其中0x0000 0000是栈顶地址,0x0000 0004存的是复位中断服务程序地址。- 获取MSP 位0x20008000,设置SP指针。
- 获取复位中断服务程序的入口地址(0x01)后,赋给PC寄存器,进入启动代码0x100处开始执行,即复位中断服务程序。


- 既然ARM规定了M3,M4内核要从地址0x0000 0000读取中断向量表,而STM32设置Flash地址到0x0800 0000怎么办?STM32支持了个内存重映射功能,将地址0x0800 0000开始的内容重映射到首地址0x0000 0000中,这样就解决了从0x0000 0000读取中断向量表的问题。
- 对于M3、M4内核,不能更改 IROM1 地址,将前面扇区当作参数存储,这样程序就会找不到复位中断函数的运行地址。
- 一旦程序开始运行后,我们就可以随意设置中断向量表的位置了,中断向量表存到内部SRAM,我们就可以操作寄存器
SCB->VTOR 重新安排
,然后将0x0800 0000
的内容复制到设置的地址内即可。
图示,以STM32F407IGT6为例,0x0000 0000和0x0800 0000开始的程序对比:

怎么保证0x08000 0000首地址存的就是中断向量表,不可以随意设置吗?
MDK对应的xxx.sct分散加载里面通过下面这句将这个RESET段放在了0x0800 0000优先存储。

既然设置到0x0800 0000这么麻烦,为什么不直接使用 0x0000 0000?
这是因为STM32不仅可以从内部Flash启动,还可以从系统存储器(可以实现串口ISP,USB DFU等程序下载方式,这个程序是ST固化好的程序代码)和从内部SRAM启动,
我们将内部Flash安排到0x0000 0000显然是不行的。这样会导致系统存储器或者内部SRAM无法重映射到0x0000 0000了。

了解了M3和M4,M7是怎么个执行情况呢?
M7内核芯片比较灵活了,改变了固定从0x0000 0000地址读取中断向量表的问题,以STM32H7为例,可以从 0x0000 0000 到 0x3FFF 0000 所有地址进行启动。
专门安排了个选项字节来配置。

H7里面没有重映射了,它的首地址0x0000 0000安排给ITCM RAM空间使用了。
参考文献
- JosephYiu.ARM Cortex-M3与Cortex-M4权威指南[M].清华大学出版社,2015.
- Yiu J .Definitive Guide to ARM(R) Cortex(R)-M3 and Cortex(R)-M4 Processors[J]. 2013.

- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/cortex_m_arch
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!