Cortex-M4内核知识点总结
余 明
目录
Cortex-M4内核知识点总结 ................................................................................. 1 1 ARM处理器简介 ................................................................................................ 4 2 架构 ................................................................................................................... 5
2.1架构简介 ................................................................................................. 5 2.2编程模型 ................................................................................................. 5 2.3存储器系统 ............................................................................................. 8 2.4复位和复位流程 ................................................................................... 12 3 指令集 ............................................................................................................. 14
3.1 CM4指令集特点 ................................................................................... 14 3.2 Cortex-M处理器间的指令集比较 ....................................................... 14 3.3 汇编指令简要介绍 .............................................................................. 14
3.3.1 处理器内传送数据 ................................................................... 14 3.3.2 存储器访问指令 ....................................................................... 15 3.3.3 算数运算 ................................................................................... 16 3.3.4 逻辑运算 ................................................................................... 17 3.3.5 移位 ........................................................................................... 17 3.3.6 异常相关指令 ........................................................................... 17
4 存储器系统 ..................................................................................................... 18
4.1 存储器外设 .......................................................................................... 18 4.2 Bootloader ............................................................................................. 18 4.3位段操作 ............................................................................................... 19 4.4 存储器大小端 ...................................................................................... 19 5 异常和中断 ..................................................................................................... 21
5.1 中断简介 .............................................................................................. 21 5.2异常类型 ............................................................................................... 21 5.3 中断管理 .............................................................................................. 22 5.4 异常或中断屏蔽寄存器 ...................................................................... 23
5.4.1 PRIMASK ...................................................................................... 23
5.4.2 FAULMASK (M0中无) .......................................................... 23 5.4.3 BASEPRI(M0中无) ................................................................. 23 5.5 中断状态及中断行为 .......................................................................... 23
5.5.1 中断状态 ................................................................................... 23 5.5.2 中断行为 ................................................................................... 24 5.6 各Cortex-M处理器NVIC差异 ........................................................... 26 6 异常处理 ......................................................................................................... 28
6.1 C实现的异常处理 ................................................................................ 28 6.2 栈帧 ...................................................................................................... 28 6.3 EXC_RETURN .......................................................................................... 29 6.4异常流程 ............................................................................................... 30
6.4.1 异常进入和压栈 ....................................................................... 30 6.4.2 异常返回和出栈 ....................................................................... 31
7 低功耗和系统控制特性 ................................................................................. 32
7.1 低功耗模式 .......................................................................................... 32 7.1 SysTick定时器 ....................................................................................... 32 8 OS支持特性 ..................................................................................................... 34
8.1 OS支持特性简介 .................................................................................. 34 8.2 SVC和PendSV ....................................................................................... 34 8.3 实际的上下文切换 .............................................................................. 35
1 ARM处理器简介
ARM处理器的种类很多,从手机上的高端处理器芯片到面向微控制器的芯片,都有ARM的身影。2011年基于ARM处理器的芯片的出货量已经到达79亿。这一章首先对ARM处理器有个简单的了解。
在早期的时候,ARM处理器使用后缀表明特性。例如ARM7TDMI,T表示支持Thumb指令,D表示JTAG,M表示快速乘法器,I则表示嵌入式ICE模块。 近几年,ARM改变处理器的命名方式,统一使用了Cortex处理器的名称。Cortex处理器下分为三类:
· Cortex-A系列:需要处理高端嵌入式系统等复杂应用的应用处理器 · Cortex-R系列:实时、高性能的处理器,面向较高端的实时市场 · Cortex-M系列:面向微控制器和混合信号设计等小型应用,注重低成本、
低功耗。
不同系列的处理器使用不同版本的架构
Cortex-A系列 Cortex-R系列 Cortex-M系列 ARMv7-A架构 ARMv7-R架构 ARMv7-M、ARMv6-M 在Cortex-M系列中,进一步都处理器进行了划分 处理器 Cortex-M0、Cortex-M0+ Cortex-M1 Cortex-M3 Cortex-M4 功能 低功耗 FPGA 微控制器 增加DSP 架构 ARMv6-M ARMv6-M ARMv7-M ARMv7E-M
2架构
2.1架构简介
Cortex-M3和Cortex-M4处理器都是基于ARMv7-M架构。最初ARMv-7M架构是随着Cortex-M3处理器一同引进的,而在Cortex-M4发布时,架构中又额外增加了新的指令和特性,改进后的架构有时也被称为ARMv7E-M。
2.2编程模型
2.2.1操作模式和状态
Cortex-M4处理器包括两种操作状态和模式,还有两种访问等级。 1. 操作状态
· 调试状态:处理器被暂停后,就会进入调试状态,比如利用调试器触发断点,单步执行等。 ·Thumb状态:处理器执行程序代码,它就是处在Thumb状态,因为Cortex-M4用的是Thumb指令,所以称为Thumb状态,并且在Cortex-M处理器中已经不支持ARM指令,也就不存在ARM状态。 2. 操作模式
· 处理模式:执行中断服务程序等异常处理。在处理模式下,处理器总是具有特权访问等级。 · 线程模式:执行普通的程序代码。 3. 访问等级
· 特权访问等级:可以访问处理器中的所有资源。
· 非特权访问等级:有些存储器区域无法访问,有些操作也无法使用。
Thumb状态处理模式异常请求启动线程模式(特权等级)处理模式执行异常处理执行异常处理异常请求线程模式(非特权等级)调试事件调试状态(处理器停止指令执行)解除调试软件转换
访问等级有特殊寄存器CONTROL控制。软件可将处理器从特权访问等级转换至非特权访问等级,但反之无法直接转换,需要借助异常机制。
处理器的操作模式和状态可由图1.1来表示,在上电后,默认处于特权线程模式下的Thumb状态。
2.2.2 寄存器
对于ARM架构来讲,处理存储器中的数据时,需将其从存储器加载到寄存器中,处理完毕后,若有必要,还可以再写回存储器。这种方式被称作“加载-存储架构”(LOAD-STORE)。
Cortex-M4处理器的寄存器组中有16个寄存器,其中包括13个通用寄存器和3个有特殊用途的寄存器。
1 通用寄存器R0-R12
R0-R7被称作低寄存器,许多16位指令只能访问低寄存器。R8-R12称作高寄存器,可用32位指令和几个16位指令访问。R0-R12初始值未定义。
2 栈指针R13
R13为栈指针,可通过PUSH和POP操作实现栈存储的访问。栈指针包括两个:主栈指针MSP和进程栈指针PSP。MSP为默认指针,复位后或处理模式时只能是MSP,而PSP只能在线程模式使用。栈指针的选择有CONTROL寄存器控制。
MSP和PSP的最低两位必须是0,也就是栈指针的地址操作必须4字节对齐。
R0R1R2R3R4R5R5R6R7R8R9R10R11R12R13(SP)R14R15通用寄存器栈指针链接寄存器LR程序计数器PC
3 链接寄存器(LR)R14
用于函数或子程序调用时返回地址的保存,在异常中则用来保存进异常前状态信息,包括系统模式、栈指针模式等。异常返回时参考LR中的信息返回到相应状态。
4 程序计数器(PC)R15
R15为程序计数器,读操作返回当前地址加4,写操作引起跳转。
2.2.3 特殊寄存器
特殊寄存器有三类:程序状态寄存器、中断/异常屏蔽寄存器、处理器控制寄存器。
1 程序状态寄存器:应用PSR(APSR)、执行PSR(EPSR)、中断PSR(IPSR)。三个寄存器可以单独访问,也可以组合到一个寄存器中访问。
31N Z C V Q26ICI/IT T2320GE[0-3]16ICI/IT108ISR NUM0
在APSR中包含N(负标志)、Z(零标志)、C(进位标志)、V(溢出标志)、Q(饱和标志)和GE(大于或等于标志,只在M4中有)。
IPSR中是中断号,只读。
EPSR中,T为表示Thumb状态,由于M4支持Thumb状态,不支持ARM状态,T位始终为1。ICI是中断继续指令位,保存的是中断被打断时的信息。IT指令时IF-THEN指令,用于条件执行。
2 PRIMASK、FAULTMASK、和BASEPRI寄存器:这三个寄存器只能在特权模式下使用。PRIMASK可屏蔽除NMI和HardFault之外的所有异常。FAULTMASK还可屏蔽HardFault。BASEPRI可以根据设置屏蔽低优先级的中断,可控制8个或16个中断,相应的改寄存器的宽度为3位或4位。
3 CONTROL寄存器
CONTROL寄存器主要有以下几项作用: · 线程模式下的访问等级 · 指针的选择
· 当前代码是否使用了浮点单元 分别对应了寄存器的低三位
位 功能 nPRIV(第0位) 0对应特权等级,1为非特权等级 SPSEL(第1位) 0对应主栈指针,1为进程栈指针,处理模式下始终为0。
FPCA(第2位) 0未使用浮点,1使用了浮点 2.2.4 浮点寄存器
1 S0-S31和D0-D15
S0-S31都为32位寄存器,也可以D0-D15的方式成对访问,但M4不支持双精度浮点运算,只是可以传输双精度数据。
2 浮点状态和控制寄存器(FPSCR) FPSCR两个功能
· 提供浮点运算结果的状态信息,如负标志、进位标志等。 · 定义一些浮点运算动作,如何舍入等 3 经过存储器映射的浮点单元控制寄存器(CPACR)
该寄存器经过了映射,也就是说需要通过通用寄存器加载进行设置,寄存器的功能是可以设置浮点单元的访问权限,拒绝访问、特权访问,全访问。
2.3存储器系统
2.3.1 存储器系统特性
· 4GB线性地址空间
· 架构定义的存储器映射。4GB的存储器空间被划分为多个区域,用于预定义的存储器和外设。 · 支持大端和小端的存储器系统。 · 位段访问。 · 写缓冲
· 存储器保护单元MPU · 非对齐传输支持
2.3.2 存储器映射
CORTEX-M处理器的4GB地址空间被分为了多个存储器区域,如图所示。区域根据各自典型用法进行划分,他们主要用于:
· 程序代码访问(如CODE区域)
· 数据访问(如SRAM区域) · 外设(如外设区域)
系统 0.5GB外部设备 1GB外部RAM 1GB外设 0.5GBSRAM 0.5GBCODE 0.5GB
某款芯片的存储器映射分配
PWM registerGPIO registerAnalog control(ADC/TP/Analog Reg/1k sram)0x50C0_0000IO control0xFFFF_FFFFReserved0xF000_00000xE000_0000Private Peripheral Bus7816 register0x5090_0000SPI3 registerSPI2 registerSPI1 registerSPI0 registerReservedTimer_2 registerTimer_1 registerTimer_0 registerAPBBRG device0x5000_0000WDT registerRTC registerCRC register0x4010_00000x4000_0000FLASH CTRLDMAReserved0x3000_00000x2000_00000x60_0000SRAMReserved8KBytes32KBytes0x50F0_00000x50D0_00000x50B0_00000x5073_00000x5072_00000x5071_00000x5070_00000x5062_00000x5061_00000x5060_00000x5050_00000x5040_00000x5030_00000x5023_0000UART3 registerUART2 registerUART1 registerUART0 registerI2C_1 registerI2C_0 register0x5010_0000SCU(clockResetPmucalendar)0x5000_00000x5022_00000x5021_00000x5020_00000x5011_00000x0000_0000FLASH MEMORY128K+4K FLASH 一般Code放在Flash当中,数据放在RAM中。数据在RAM存放有一定的顺序,可以分为数据段,BSS段、堆和栈区域。
· 数据段,存储在内存的底部,包含初始化的全局变量和静态变量。
· BSS段,未初始化的数据。
· 堆,C函数自动分配存储器区域,例如alloc()和malloc()。 · 栈,用于临时数据存储,局部变量,函数调用
存储器栈(向下增长)堆(向上增堆(向上增长)长)BSS段数据段0x20000000
2.3.3 栈存储
同几乎所有的处理器架构一样,Cortex-M处理器在运行时需要栈存储和栈指
针R13。ARM处理器将系统主存储器用于栈空间操作,使用PUSH指令往栈中存储数据以及POP指令从栈中提取数据。
处理器使用的是满递减的模型,栈指针是向下增长的。处理器启动后,SP被设置为栈存储空间的最后的位置,也就是最低位置,PUSH时,SP指针首先减小,然后将数据压入栈中。POP的时候相反,先将当前SP所指的数据出栈,然后再修改SP,SP此时增大。可用下面两幅图加以理解,
PUSH操作栈空间寄存器0x123456780x999999990x12345678R13(SP)R13(SP)
POP操作栈空间0x99999999R13(SP)R13(SP)寄存器0x12345678
栈中主要用于:
· 存储局部变量
· 异常产生时保存处理器状态(LR、xPSR)和寄存器数值 · 函数调用时
2.4复位和复位流程
对于典型的Cortex-M处理器,复位类型有三种:
· 上电复位。复位微控制器中所有部分。
· 系统复位。只会复位处理器和外设,不包括处理的调试支持部件 · 处理器复位。只复位处理器。
在复位后以及处理器开始执行程序前,处理器会从存储器中读出头两个字节。第一个字表示主栈指针的初始值。第二个字代表复位处理起始地址的复位向量。处理器读出这两个自己后,就会将这些数值赋给MSP和PC。
读地址0x00000000取MSP初始值读地址0x00000004取复位向量读取复位向量表示的地址复位取第一条指令时间 之前在栈存储时讲到过,Cortex-M处理器的栈操作时基于满递减的,所以SP的初始值应该设置在栈顶的位置。例如,若存储器区域为0x20007C000~0x20007FFF(1KB),初始的栈指针就应该为0x20008000。 另外,对于Cortex-M处理器,向量表中向量地址的最低位应该为1,以代表他们为Thumb状态。对于下图中的例子,复位向量为0x101,而启动代码是从0
x100开始的。在取出复位向量后。Cortex-M处理器就可以从复位向量地址处执行程序,并开始正常操作。
其他存储器0X200080000X20007FFC0X20007FF8SP初始值第一个压栈顶第二个压栈顶0X20000000SRAM起始地址0X00000100启动代码其他异常向量0X000000040X000000000X000001010X20008000复位向量
3指令集
CORTEX-M4使用的是Thumb-2指令集,不支持ARM指令集,Thumb指令集是ARM指令集的子集,但是Thumb-2技术已经不再支持ARM状态。
CORTEX-M处理器间的一个区别就是指令集特性。为了将回路面积降到最低,CORTEXM0、CORTEXM0+、CORTEXM1处理器只支持多数16位指令和部分32位指令,CORTEX-M3支持的32位指令更多。CORTEX处理器支持剩下的SIMD(单指令多数据)等DSP提升指令集可选的浮点指令。
3.1 CM4指令集特点
CM4处理器使用ARMv7-M架构,指令集为Thumb指令集中的Thumb-2技术,具有如下特点
· 16位与32位混合指令
· 加载/存储指令集,不能直接操作存储器。
· 指令长度可变,使用16/32位由功能决定,优先使用16位。 · DSP指令,CM4中为单精度,CM7中可以双精度
3.2 Cortex-M处理器间的指令集比较
Cortex-M处理器的架构有三类,ARMv6-M,ARMv7-M,ARMv7E-M。
ARMv6-M ARMv7-M ARMv7E-M 内核 M0/M0+/M1 M3 M4 性能 一般数据处理,IO控制人物 高级数据处理、硬件除法 SIMD、快速MAC饱和运算 3.3 汇编指令简要介绍 3.3.1 处理器内传送数据
MOV
源寄存器处可以是立即数,立即数为8位以下,9-16位用MOVW,32位的需要使用LDR伪指令。
使用浮点单元时可以使用VMOV指令。
3.3.2 存储器访问指令
访问可分为读和写指令,另外根据读写的大小还有其他的延伸。 数据类型 8位无符号 8位有符号 16位无符号 16位有符号 32位 多个32位 64位 栈操作 介绍几个较为重要的 1 LDR/STR
LDR Rd,[Rn,#offset] 从存储器Rn+offset处读取字,读取到Rd中 STR Rd,[Rn,#offset] 向存储器Rn+offset处存储字,数据来自Rd。 LDR R0,[R1,#0X08] 从存储器R1+0x08处读取字,放到R0中 支持写回功能,加!即可,上面可以写成
LDR R0,[R1,#0X08]!这样表示存储器位置的R1被更新为R1+0x08 2 LDM/STM 读/写多个字
上述命令是为了从存储器中读写多个字。一般会加后缀配合使用 LDMIA Rn,
Rn是存储器位置,reg list是寄存器列表,从Rn所指的存储器位置读取数据,放入寄存器中,每次读取完成后,地址就会自动加4。作用相当于POP。另外要注意的是,先读取的数据放置在低寄存器中,后读取的数据放置到高寄存器。
加载(读) LDRB LDRSB LDRH LDRSH LDR LDM LDRD PUSH 存储(写) STRB STRB STRH STRH STR STM STRD POP
LDMIA操作栈空间0x888888880x99999999寄存器R1R0R13(SP)R13(SP)R13(SP) STMIA Rn,
Rn是存储器位置,reg list是寄存器列表,向Rn所指的存储器位置存储数据,每次存储前,地址自动减4。相当于PUSH操作。另要注意的是,先存储高寄存器的数据,后存储低寄存器的数据。
STMDB操作寄存器R1R00x999999990x12345678未存R1时SPR13(SP)R13(SP)栈空间 同样,这两个指令都可以通过!表示写回操作,更新寄存器所指的存储器位置
3 压栈与出栈 PUSH/POP
PUSH和POP和上面的LDMIA和STMDB是相同的。
3.3.3 算数运算
加:ADC 减:SUB 乘:MUL 除:DIV
对此不做详细介绍
3.3.4 逻辑运算
与:AND 或:ORR 位清除:BIC 按位异或:EOR 按位或非:ORN
3.3.5 移位
算数右移:ASR 逻辑左移:LSL 逻辑右移:LSR 循环右移:ROR
3.3.6 异常相关指令
之前说过,M4可以有特权模式和非特权模式,并且非特权模式不能直接转换到特权模式,只能在异常中修改CONTROL寄存器。这里就可以通过SVC指令来进入异常。
SVC #
这样就可以进入SVC中断中,然后修改CONTROL寄存器。要注意的是,调用SVC指令后,需尽快进入中断中,如果有其他高优先级的中断打断了SVC,就会引起HardFault。
CPS指令使用时需要带上后缀:IE(中断使能),ID(中断禁止),还需指定要设置的中断屏蔽寄存器,如之前讲到的PRIMASK和FAULTMASK
指令 CPSIE I CPSID I CPSIE F CPSID F 操作 使能中断(清除PRIMASK) 禁止中断(设置PRIMASK),除NMI和HardFault 使能中断(清除FAULTMASK) 禁止中断(设置FAULTMASK),除NMI
4存储器系统
4.1 存储器外设
哈佛结构,程序存储器和数据存储器分开,也就是指令和数据可以同时访问。 1、在第一章中的存储器映射图中,0-0.5G为代码段,主要用于程序代码,改区域一般也允许数据访问。一般此处为Flash。
在keil中,代码编译后,整个代码分为几部分:Code(代码),RO-data(只读数据),RW-data,(初始化的可读写变量大小),ZI-data(Zero Initialize)未初始化的可读写变量大小,它会被自动初始化为0。ZI-data不会被算到代码里,因为它不会被初始化。
简单来说呢就是在烧写的时候FLASH中的被占用的空间为:Code+RO-data+RW-data。
程序运行的时候,芯片内部RAM使用的空间为:RW-data+ZI-data
2、0.5G-1G范围内是SRAM,主要用于连接SRAM,其大都为片上SRAM,不过对存储器的类型没有什么限制。若支持可选的位段特性,则SRAM区域的第一个1MB可位寻址,还可以在这块区域中执行程序代码。
3、1G-1.5G是外设区域,多用于片上外设,和SRAM区域类似,也可以放置程序代码,若支持可选的位段特性,则外设区域的第一个1MB是可选的。
4、1.5G-2.5G空间为外部RAM空间
5、2G-3G空间为设备空间,用于片外外设。 6、3G-4G空间为系统空间。
4.2Bootloader
芯片设计人员将Bootloader放入系统中的原因是多方面的。例如:
· 提供Flash编程功能,这样就可以利用一个简单的UART接口来编程Flash,或者当程序运行时,在自己的应用程序中编程Flash存储器的某些部分。 · 提供通信协议栈等额外的固件,可被软件开发人员通过API调用。 · 提供芯片内置的自检功能(BIST)
比如在1601中,提供一个4K的info区,和128K的main区,4K的info区就是一个bootloader,提供SPI下载功能,利用拨码开关可以设置从哪里启动,mode=0时从info区启动,mode=1时从main区启动,并且main区分两部分,软件可设置从低64K启动还是从高64K启动。这里设计存储器重映射的问题,系
统启动时是从0X00开始的,不管是Boot loader还是用户flash,都得从0x00开始,然后0x04放的reset_handler的地址,mode=0,那infor区就被映射到了0x00,mode=1,main区就被映射到了0x00。
4.3位段操作
对存储器中某一位操作是如何实现的呢?先来看看普通模式下, 写某一位:
LDR R0,=0X200000000 ;设置地址 LDR R1,[R0] ;读数据 ORR.W R1,#0X04 ;修改第2位 STR R1,[R0] ;写回 读某一位:
LDR R0,=0X200000000 ;设置地址 LDR R1,[R0] ;读数据 UBFX.W R1, R1,#2,#1;提取第2位
这种操作无法保证原子性,比如输出端口的第0位被主程序使用,而第一位被中断使用,这样有可能出现数据冲突。位段操作模式下,这种现象可以避免,因为位段操作是在硬件等级下修改的。
位段操作只在两个区域支持,SRAM的第1MB,外设区域的第1MB。每1MB会对应一个32M的区域,只需操作这32M的某个字,就能对应那1MB区域的某一位。例如写0x22000008为1,就设置了0x20000000第3位为1。在指令上也会更加简化。
位段写操作
LDR R0, =0X2200000 ;设置add MOV R1,#1 ;要写的数据 STR R1 ,[R0] ;写
设置了位段操作模式,对应的32MB区域将不能再使用。
4.4 存储器大小端
大小端指的是数据存储时的顺序问题。大端指的是高字节的数据放在低地址中,低字节放在高地址中,这种方式符合人类思维。小端则是低字节放在低地址中,高字节放在高地址中,这种方式更符合计算机思维。
例如将0x12345678放到存储器0x2000-0x2003地址处 大端方式 地址 数据 小端方式 地址 数据 0x2004 0x12 0x2003 0x34 0x2002 0x56 0x2001 0x78 0x2003 0x78 0x2002 0x56 0x2001 0x34 0x2000 0x12 CM4处理器同时支持小端和大端的存储器系统。CM的微控制器大多是小端的。
5异常和中断
5.1 中断简介
所有的CORTEX-M处理器都会提供一个用于中断处理的嵌套向量中断控制器,也就是NVIC。中断也属于异常的一种,其他异常包括如错误异常和其他用于OS支持的系统异常。
M4的NVIC支持最多240个IRQ(中断请求),1个不可屏蔽中断(NMI),1个SysTick(系统节拍)定时中断及多个系统异常。 异常编号 1 2 3 4 5 6 7-10 11 12 13 14 15 16 17 … 255 异常类型 复位 NMI 硬件错误 优先级 -3 -2 -1 描述 复位 不可屏蔽中断 硬件错误 存储器管理错误 总线错误 程序错误 请求管理调用 调试监控 一般用于上下文切换 系统节拍定时器 MemManage错误 可编程 总线错误 使用错误 保留 SVC 调试监控 保留 PendSV SYSTICK 外部中断#0 外部中断#1 … 外部中断#239 可编程 可编程 可编程 可编程 可编程 可编程 可编程 可编程 … 可编程 片上外设或外部中断源产生 除了前3个异常的优先级是固定的,其余异常都可以修改优先级。 5.2异常类型
编号1-15为系统异常,16及以上的则为中断输入。
5.3 中断管理
为了简化中断和异常管理,CMSIS-Core提供了多个访问函数。
函数 Void NVIC_EnableIRQ(IRQn_Type IRQn) Void NVIC_DisableIRQ(IRQn_Type IRQn) Void NVIC_SetPriority(IRQn_Type IRQn,uint32_t priority) Void _enable_irq(void) Void _disable_irq(void) Void NVIC_SetPriorityGrouping(uint32_t priorityGroup) 用法 使能外部中断 禁止外部中断 设置中断的优先级 清除PRIMASK使能中断 设置PRIMASK禁止所有中断 设置优先级分组 复位后,所有中断都处于禁止状态,且默认的优先级为0。在使用任何一个中断之前需要
· 设置所需中断的优先级(可选)
· 使能外设中的可以触发中断的中断产生控制 · 使能NVIC中的中断
在M4中,中断优先级共8位宽,但芯片厂商可进行设置,范围是3-8位。中断优先级分为两个部分,分组优先级(也叫抢占优先级)和子优先级。处理器首先判断的是分组优先级,分组优先级高的会被首先处理,若分组优先级相同,再比较子优先级。8为宽的优先级如何分配抢占优先级和分组优先级,可由寄存器设置。如下表
优先级分组 0(默认) 1 2 3 4 5 6 7
抢占优先级域 Bit[7:1] Bit[7:2] Bit[7:3] Bit[7:4] Bit[7:5] Bit[7:6] Bit[7] 无 分组优先级域 Bit[0] Bit[1:0] Bit[2:0] Bit[3:0] Bit[4:0] Bit[5:0] Bit[6:0] Bit[7:0]
这里要注意的是,异常编号和优先级并不是一个意思,异常编号仅仅是一个编号,就像是枚举一样,而优先级则是需要手动的进行设置的。只有在分组优先级和子优先级完全一致时,异常编号才起作用,编号越小优先级越高。 另外,在M4内核中,还提供了中断向量重定位功能。向量表重定位功能提供了一个名为向量表偏移寄存器(VTOR)的可编程寄存器。前面提到的Bootloder就可以使用此项功能来完成。
5.4 异常或中断屏蔽寄存器 5.4.1 PRIMASK
PRIMASK用于禁止除NMI和HardFault外的所有异常,只能在特权状态访问如:
CPSIE I ;清除PRIMASK(使能中断) CPSID I;设置PRIMASK (禁止中断)
5.4.2 FAULMASK (M0中无)
FAULMASK用于禁止除NMI外的所有异常,只能在特权访问,如: CPSIE F ;清除FAULMASK COSID F;设置FAULMASK
5.4.3 BASEPRI(M0中无)
BASEPRI可禁止优先级低于某特定等级的中断,只能在特权状态访问,如: _set_BASEPRI(0X60); //禁止优先级在0x60-0xFF间的中断 _set_BASEPRI(0X0); //取消BASEPRI屏蔽
5.5 中断状态及中断行为 5.5.1 中断状态
中断状态:inactive, pending, active,active and pending 中断状态之间的转换:
InactivePendingActiveandPendingActive Inactive(非活跃):没有挂起或激活的状态
Pending(挂起):异常已经被触发但是处理还未处理。比如当前中断被触发,但是有高优先级或同等优先级的中断正在执行,或是中断使能被禁止。
Active(活跃):中断正在被处理
Active and pending(活跃并挂起)异常被处理时,同一个异常再次被触发。 上述异常对应的是每一个异常状态,不是整体的异常。
5.5.2 中断行为
每个中断都有多个属性:
· 每个中断都可以被禁止或使能 · 每个中断都可以被挂起或解除挂起 · 每个中断都可以处于活跃或非活跃状态
这样中断就会产生多种行为,先来看一下最简单的中断下中断状态的转换
中断请求处理器开始处理中断请求后,中断请求被清除中断挂起状态处理器模式进入中断处理中断活跃状态进入中断处理后活跃状态被置位
中断的挂起状态是可以手动设置的,有一个寄存器是中断挂起状态寄存器,例如上下文切换的PendSV异常,就是手动设置PendSV挂起,然后进入了PendSV异常中。同样,挂起状态是可以清除的。若中断请求产生时,处理器正在处理更高优先级的请求,然后在处理器对该中断请求作出相应之前,挂起状态被清除掉
了,那么该中断请求也就不会被处理了。
中断请求中断挂起状态挂起状态被软件清除处理器模式
但是,如果外设保持某中断为请求状态,那样即使软件清除掉挂起状态,挂起状态仍会再次置位。
中断请求中断挂起状态处理器模式
中断处理完成后,如果中断请求仍在继续保持,中断就再次进入挂起状态,再次得到处理器的服务
中断请求保持活跃中断请求中断挂起状态中断活跃状态再次进入中断处理器模式
对于脉冲中断请求,若在处理器开始处理前,中断请求产生了多次,它只会被当做一次请求
中断请求中断挂起状态中断活跃状态处理器模式
中断的挂起状态可以在其正在被处理时再次置位,也就是之前所说的active and pend状态。中断处理完成后会再次处理该中断。
再次产生中断请求中断请求中断再次挂起中断挂起状态中断活跃状态再次进入中断处理器模式
另外有一种情况需要注意,即使禁止了某中断,中断的挂起状态仍然可以使用,只是无法转化为活跃状态,并且,如果中断被使能后,若没有高优先级中断在执行,挂起状态就会被转换为活跃状态,进而进入中断处理,如果不希望此类状况发生,那应该在中断使能之前清除掉中断挂起状态。
5.6 各Cortex-M处理器NVIC差异
Cortex-M处理器的中断管理有一定区别 中断数量 NMI 优先级宽度 寄存器访问 PRIMASK CM0 1-32 Y 2 字 Y CM0+ 1-32 Y 2 字 Y CM1 1/8/16/32 Y 2 字 Y CM3 1-240 Y 3-8 字、半字、字节 Y CM4 1-240 Y 3-8 字、半字、字节 Y
FAULTMASK BASEPRI 向量表偏移寄存器 动态修改优先级 中断活跃状态 错误处理 调试监控异常 N N N N N N N Y(可选) N N N N N N N Y Y Y Y Y Y Y Y Y Y 硬件错误 硬件错误 硬件错误 硬件错误+3个硬件错误+3个其他错误异常 其他错误异常 N N N Y Y
6异常处理
6.1 C实现的异常处理
C函数在ARM架构上是如何工作的,用于ARM架构的C编译器遵循ARM的一个名为AAPCS的规范。根据这份标准,C函数可以修改R0-R3、R12、R14(LR)以及PSR。若C函数需要使用R4-R11,就应该将这些寄存器保存到栈空间中,并且在函数结束前将他们恢复。
R0-R3、R12、R14、PSR被称作“调用者保存寄存器”。 R4-R11为“被调用者保存寄存器”。
一般来说,函数调用将R0-R3作为输入参数,R0则用作返回结果。若返回值为64位,R1也会用于返回结果。
异常机制需要在异常入口处自动保存R0-R3、R12、R14、PSR,异常退出时将其恢复,这些都要由处理器硬件控制。另外,与普通的C函数调用不同,返回地址PC并没有存储在LR中,进入异常时LR存储的是EXC_RETURN,异常返回时将会用到。因此,异常流程也需要将返回地址保存,所以,不算浮点单元,异常处理期间保存的寄存器为8个。对于带浮点单元的Cortex-M4处理器,异常机制还会保存S0-S15以及FPSCR。
C函数第一个参数第二个参数第三个参数第四个参数R0R1R2R3R4R5R5R6R7R8R9R10R11R12R13(SP)R14R15PSR浮点单元被调用者保存寄存器S0-S15S16-S31FPSCRR0R1R2R3R4R5R5R6R7R8R9R10R11R12R13(SP)R14R15PSRS0-S15S16-S31FPSCR返回结果被调用者保存寄存器在函数调用后会被修改的寄存器栈指针链接寄存器LR程序计数器PC
6.2 栈帧
在异常入口处被压入栈空间的数据块为栈帧。不具有浮点单元的M4处理器,栈帧是8个字大小。带浮点的栈帧可能是8个或26个字大小。与上一个小节中所说的异常处理期间保存的寄存器为8个是一个意思。
AAPCS要求栈指针的数值在函数入口和出口处应是双字对齐的,双字栈对齐特性可编程,该特性可以关闭。M0中不可设置。压栈的xPSR的第9位表示栈指针的数值是否调整过,0未调整,1调整过,也就是会自动插入一个字的空间。所以,栈帧最大为9或27个字。
6.3 EXC_RETURN
6.1小节中说到过,处理器进入异常时,LR存储的是EXC_RETURN,这里说明一下,EXC_RETURN的具体细节。
EXC_RETURN是一个字大小,位域表示如下
位 31:28 27:5 4 描述 EXC_RETURN 指示 保留全为1 栈帧类型 (M0中无) 3 返回模式 数值 0xF 0Xefffff 1:8字 0:26字 1:返回线程模式 0:返回处理模式 2 返回栈 1:返回线程栈 0:返回主栈 1 0 EXC_RETURN的合法值
返回处理模式,总使用主栈 返回线程模式,返回后使用主栈 返回线程模式,返回后使用进程栈 __asm void vPortSVCHandler( void ) {
保留 保留 0 1 浮点未使用FPCA=1 浮点使用FPCA=0 0XFFFFFFE1 0XFFFFFFE9 0XFFFFFFED 0XFFFFFFF1 0XFFFFFFF1 0XFFFFFFFD PRESERVE8
ldr r3, =pxCurrentTCB
/* Restore the task stack pointer. */ /* Restore the context. */
ldr r1, [r3] }
ldr r0, [r1]
ldmia r0!, {r4-r11} msr psp, r0 isb mov r0, #0 msr basepri, r0
orr r14, #0xd /*或上0b1101,返回线程栈,线程模式*/ bx r14
6.4异常流程 6.4.1 异常进入和压栈
当异常产生且被处理器接受时,压栈流程会将寄存器压入栈中并组织栈帧。要注意的是,压栈期间的栈访问顺序和栈帧中的顺序不同,首先压栈的是PC和xPSR,这样在取向量时会尽快更新PC。
这里再复习一下主栈与进程栈的使用,在处理模式,必须使用主栈,在进程模式,由CONTROL寄存器控制使用主栈还是进程。
在处理器处于进程模式并且使用进程栈时,压栈操作使用进程栈,然后就进入了处理模式,直到异常返回之前,一直使用的是主栈,如异常嵌套的情况,在一个异常中又进入了另一个异常,仍然使用的是主栈进行压栈。
另外,我们在使用OS的时候会有两个栈空间,系统栈空间和任务栈空间,系统栈空间是在启动文件中进行了设置,如图6-1,这个栈空间是留给异常使用的,也就是处理模式下的MSP;而在创建任务的时候都会有对栈空间的设置,我们称为任务栈空间,也就是在线程模式下的PSP。
进入异常时,会将R0-R3,R12,LR和返回地址(带浮点单元时包括S0-S15和FPSCR)硬件自动压栈保存,其他的(R4-R11)需要软件保存。
6.4.2 异常返回和出栈
出栈时就是对EXC_RETURN数值的判断,也就是EXC_RETURN中位域所表示的那几项,用什么栈进行压栈的仍返回什么栈,操作模式和栈帧类型也类似。出栈操作结束时,还要检查xPSR的第9位,若置1则去除压栈时插入的额外空间。 对于栈空间中的数据,同入栈一样,R0-R3,R12,和LR(带浮点单元时包括S0-S15和FPSCR)是自动出栈的,其余的需要软件出栈。
7低功耗和系统控制特性
7.1 低功耗模式
CORTEX-M系列处理器提供两种休眠模式:休眠模式和深度休眠模式。由系统控制寄存器SCR控制。
处理器提供了两个用于进入休眠模式的指令:WFI,进入休眠模式,等待中断,可由中断、调试、复位唤醒。WFE:等待事件,条件进入休眠模式。内部事件寄存器为0,进入休眠,否则内部事件寄存器被清除,处理器继续执行,除了中断、调试、复位唤醒外,还能由事件唤醒。
WFE中的事件唤醒包括事件输入信号脉冲(RXEV)。该信号属于事件通信接口特性的一部分。处理器还存在一个名为TXEV(发送事件)的输出信号,当执行SEV(发送事件)指令时,TXEV会输出一个周期的脉冲信号。
事件通信接口的主要设计目标位,在一个特定事件发生前让处理器一直处于休眠模式。例如:
· 允许外设和处理器之间的通信 · 允许多个处理器间的通信
7.1 SysTick定时器
Cortex-M处理器内集成了一个小型的名为Systick的定时器,它属于NVIC的一部分,可以产生Systick异常。Systick为简单的向下计数的24位计数器。 Systick的主要作用是用于在OS中任务管理和上下文切换,处理器可以在不同时间片内处理不同任务。
设计这个定时器的目的是为了增加软件的可移植性。所有CORTEX-M系列的芯片都有相同的定时器。定时器的时钟可以是处理器时钟或者是外部参考时钟(STCLK)。
定时器的使用很简单,只有四个寄存器
地址 0XE000E010 0XE000E014 0XE000E018 0XE000E01C
寄存器 Systick控制和状态寄存器 Systick重装载值寄存器 Systick当前值寄存器 Systick校准值寄存器 作用 使能以及设置Systick Systick计时周期 Systick当前数值 校准设置
使用CMSIS-Core中的函数就能方便地使用Systick,主要作用就是产生定时中断,如下,产生1HZ的定时中断
配置正确的始终频率数,产生1khz的定时中断 Sys_Config(SystemCoreClock/1000); SystemCoreClock为对应芯片的时钟频率 SysTick定时器的寄存器只能在特权状态下访问。
8OS支持特性
8.1 OS支持特性简介
上下文切换是OS中很重要的一点,经过前面的学习,我们知道一个OS的运行就是在不同任务间频繁的切换,而在切换时,就需要对上一个任务的一些数据进行保存,然后进入下一个任务,并取出这个任务保存的数据。可以手动的实现一个系统的上下文切换。
处理器架构实现了多个特性,保证了OS设计的方便和高效:
· 影子栈指针。有两个栈指针,MSP用于OS内核以及中断处理,PSP用于应用任务。 · Systick定时器。处理器内部的简单的定时器。可实现任务的定时器切换 · SVC和PendSV异常。这两种异常对于嵌入式OS的操作非常重要,如上下文的切换的实现等等。 · 非特权等级执行,可以利用其实现一种基本安全模型
8.2 SVC和PendSV
SVC称作请求管理调用,它既是一条指令,也是一种异常。SVC指令就可以进入SVC异常中,之前说道过从非特权转换到特权模式就可以使用SVC异常的方法。并且SVC异常通常是不能被打断的,如果打断就会出现硬件错误。 PendSV异常是为完成OS的上下文切换,并且PendSV的异常等级要设置为最低。这是因为如果PendSV的异常等级比较高的话,当其他异常来临或正在执行时,上下文切换就会打断其他异常,上下文的切换较为复杂,就造成中断延迟。这在实时系统中是不可以的。所以要把PendSV异常等级设置为最低,等待所有中断执行完毕后,再进行上下文切换。下图是两种方式的对比。
任务切换一般有两种方式,一种可以通过systick中断触发PendSV异常,比如在时间片式的任务中;对于手动方式,可以通过SVC触发PendSV异常,完成上下文切换。这里要说明一下的是,下面两幅图中省略了触发PendSV的过程,Systick异常优先级是比较高的,它的作用简单,只是触发PendSV异常,它对IRQ的抢占影响很小。
优先级上下文切换PendSVOSOSIRQIRQIRQ线程任务A任务B时间优先级
IRQIRQ上下文切换PendSVOSOS线程任务A异常到来任务B时间
8.3 实际的上下文切换
上下文切换操作由PendSV异常执行处理,由于异常流程已经保存了寄存器的R0-R3、R12、LR、xPSR和返回地址,PendSV只需将R4-R11保存到栈中。
进入PendSV存储器任务A的栈任务A的异常栈帧(R0-R3、R12、LR、PC和xPSR)保存任务A的上下文存储器任务A的栈任务A的异常栈帧(R0-R3、R12、LR、PC和xPSR)任务A的R4-R11PSP存储器指向任务B的上下文任务B的栈任务B的异常栈帧(R0-R3、R12、LR、PC和xPSR)任务B的R4-R11任务B的上下文恢复存储器任务B的栈任务B的异常栈帧(R0-R3、R12、LR、PC和xPSR)PSPPSPPSP array[]STDMBLDMIA???任务B的SP任务A的SP加载下一个任务的PSP
具有四个任务的多任务系统实例: /*字访问的宏定义*/
#define HW32_REG(ADDRESS) (*(volatile unsigned long *)ADDRESS) /*以上宏定义相当于 Int *p;
p = (int*)0x60000000; *p = 0x01;
就是可以对ADDRESS地址处赋值,也就是访问这个字地址处的数据。*/ Void task0(void); Void task1(void); Void task2(void);
Void task3(void); //任务事件
Volatile uint32_t systick_count=0; //每个任务用的栈 8KB
long long
task0_stack[1024],task1_stack[1024],task2_stack[1024],task3_stack[1024];
//OS使用的数据
uint32_t curr_task = 0; //当前任务 uint32_t next_task = 0; //下一个任务 uint32_t PSP_array[4]; //任务的进程栈指针
//-------------------------------------------------------------------- // 主程序 int main(void) {
//任务初始化省略 //开始任务调度 //创建任务0栈帧
PSP_array[0] = ((unsigned int) task0_stack) + (sizeof task0_stack) -16*4; /*初始化函数地址,14作为PC的位置,左移2位相当于乘以4*/ HW32_REG(PSP_array[0] + (14<<2)) = (unsigned long) task0; /*初始化程序计数器,15作为PSR位置*/ HW32_REG(PSP_array[0] + (15<<2)) = 0x01000000; /*初始化其他任务*/ …….. ……. …….
Curr_task = 0; //切换到任务0 /*设置PSP为任务0的栈顶*/ _set_PSP((PSP_array[curr_task] + 16*4))
NVIC_SetPriority(PendSV_IRQn,0xFF); //设置PendSV为最低优先级 SysTick_Config(16800); _set_CONTROL(0X3); _ISB(); task0(); while(1);
{stop_cpu;//不应到此处}
}
Void task0(); { }
Void task1(); { }
Void task2(); { }
Void task3(); { }
_asm void PendSV_Handler() {
//1保存当前上下文
MRS R0,PSP //读取当前栈指针
STMDB R0!,{R4-R11} //将R4-R11保存到任务栈中 LDR R1, = _cpp(&curr_task)
LDR R2, [R1] //当前任务ID,也就是在PSP_array中的位置 LDR R3, = _cpp(&PSP_array) //PSP_array地址
STR R0,[R3,R2,LSL #2] //当前任务栈PSP的位置存放到PSP_array中,上
//文的保存完成
//2 加载下一个上下文 LDR R4 , =_cpp(&next_task)
LDR R4,[R4] //得到下一个任务的ID,也就是在PSP_array存放的
//位置 STR R4,[R1]
LDR R0,[R3,R4,LSL #2] //从PSP_array中取出这个任务的栈起始地址 LDMIA R0!,{R4-R11} //从任务栈中取出保存的R4-R11 MSR PSP, R0 //设置PSP为下一个任务 BX LR ALIGN 4 }
Void SysTick_Handler(void) {
Systick_count++; //简单的任务调度器 Switch(curr_task) { Case 0:
Next_task = 1;break; Case 1:
Next_task = 2;break; Case 2:
Next_task = 3;break; Case 3:
Next_task = 0;break; Default: next_task = 0;
Stop_cpu;
Break;//不应到此处 }
If(curr_task!=next_task) {
SCB->ICSR |=SCB_ICSR_PENDSVSET_Mask;
}//在这里设置了PENDSV挂起,在PENDSV_HANDLER中完成上下文切换 Return; }
对于带浮点单元的上下文切换,基本流程是一致的。这里要了解带浮点单元的一种压栈机制,惰性压栈。
带浮点的情况下,处理器进入异常时,若要将每个异常所需的浮点单元寄存器压栈(S0-S15,FPSCR),每次都需要额外执行17次存储器压栈操作,会增加中断等待时间。
为减少等待时间,M4处理器实现一种惰性压栈的特性。默认是使能的。若在浮点单元使能且使用的情况下产生了异常,则栈帧的长度会增加。不过,这些浮点寄存器的数值实际上是不会写入栈帧中的。惰性压栈机制只会为这些寄存器保留一定的栈空间,不过只有R0-R3、R12、LR、返回地址和xPSR被压栈。当出现惰性压栈时,一个名为LSPACT(惰性压栈保持活跃)的寄存器会被置位,浮点单元上下文地址寄存器(FPCAR)存放浮点寄存器预留栈空间的地址。 在栈初始化时,由于惰性压栈的缘故,并不需要单独考虑浮点单元的空间,但仍需要增加一个字的空间,这个用来保存LR(EXC_RETURN),它的第四位表示异常栈帧中是否包含浮点寄存器。上下文切换时通过检查它来确认是否需要压入或弹出浮点寄存器的相关内容。