前言本文章相当于学习笔记摘录总结。推荐自行观看相关书籍权威指南不足之处请指点。4.1架构简介Cortex-M3和Cortex-M4都是基于ARMv7-M架构。Cortex-M4发布时架构中又额外增加了新的指令和特性改进后的架构有时也被称为ARMv7E-M.嵌入式开发只需要了解编程模型、异常如中断如何处理、存储器映射、如何使用外设以及如何使用微控制器供应商提供的软件驱动文件等。4.2编程模型4.2.1操作模式和状态Cortex-M3/M4处理器有两种操作状态和两个模式以及特权和非特权访问等级。1操作状态调试状态当处理器被暂停后例如通过调试器或触发断点后就会进入调试状态并停止指令执行。Thumb状态若处理器在执行程序代码Thumb指令它就会处于Thumb状态。Cortex-M处理器只有Thumb状态因为它没有ARM指令集。2操作模式处理模式执行中断服务程序ISR等异常处理。在处理模式下处理器具有特权访问等级。线程模式在执行普通的应用程序代码时处理器既可以处于特权访问等级也可以处于非特权访问等级。3访问等级特权访问等级可以访问处理器中的所有资源。NVIC寄存器只支持特权访问。非特权访问等级有些存储器区域不能访问有些操作也是无法使用的。访问等级由特殊寄存器CONTROL控制。4.2.2寄存器处理器中有多个用于执行数据处理和控制的寄存器可以以寄存器组的形式对其进行分组。每个数据处理指令都指定了所需的操作和源寄存器或目的寄存器。加载---存储架构若要处理存储器中的数据需要将数据从存储器中加载到寄存器里处理器处理完数据后如果有必要还要将寄存器里的数据写回存储器。1R0~R12R0~R7为低寄存器16位的指令只能访问低寄存器。R8~R12为高寄存器可以用于32位指令和几个16位指令。R0~R12的初始值是未定义的。2R13:栈指针SPR13为栈指针可以通过PUSH和POP操作实现栈存储的访问。在物理上存在两个栈指针主栈指针MSP为默认的栈指针在复位后或处理器处于处理模式时其会被处理器选择使用。进程栈指针PSP其只能用于线程模式。两个栈指针由特殊寄存器CONTROL决定对于一般的程序这两个寄存器只会有一个可见。两个栈指针都是32位的但是因为最低两位总是0所以栈操作的地址必须对齐到32位的字边界上。大多数情况下PSP没必要使用。一般用在OS中OS的内核与任务的栈相互独立。PSP的初始值未定义但是MSP的初始值则需要在复位流程中从存储器的第一个字中取出。3R14链接寄存器LR用于函数或子程序调用时返回地址的保存。在函数或子程序结束时通过将LR的数值加载至PC程序计数器中返回到调用函数或者子程序处继续执行。当调用函数或者子程序后LR的数值会自动更新。如果函数中又调用了另一个函数那么需要先将LR的数值保存在栈中否则返回地址将会被覆盖。如果发生了异常LR里的值会被自动更新为特殊的EXC_RETURN(异常返回)值之后在处理完异常后希望通过LR返回会发现这是一个特殊值则触发异常返回操作。PC地址永远是偶数的因为指令必须对齐到半字或者字地址。但是LR的第0位有一个解释在进行指令跳转时如果是经典的ARM处理器第0位等于1则表示跳转过去后切换到Thumb状态第0位等于0则跳转过去后切换到ARM状态。但是Cortex-M3/M4处理器只有Thumb状态所以第0位的作用变成了等于1表示正常跳转等于0表示错误的跳转地址触发异常。并且等于1的话加载到PC的地址会自动忽略第0位强制改为0。4R15程序计数器PC可读可写读操作返回当前指令地址4流水线特性。写PC会引起跳转操作。5程序中使用的寄存器名对于多数汇编工具寄存器有多种名称也可以大小写混用。4.2.3特殊寄存器除了寄存器组中的寄存器处理器中还有多个特殊寄存器。这些寄存器表示处理器状态、定义了操作状态和中断、异常屏蔽。一般来说用不到但是开发嵌入式OS或需要高级中断屏蔽特性时就需要访问它们。特殊寄存器未经过存储器映射需要通过MSR和MRS等特殊寄存器访问指令进行访问。CMSIS_Core也提供了几个用于访问特殊寄存器的C函数。MRS reg,special_reg;将特殊寄存器读入寄存器 MSR special_reg,reg;写入特殊寄存器1程序状态寄存器程序状态寄存器包括三个状态寄存器应用PSRAPSR、执行PSREPSR、中断PSRIPSR。这三个寄存器可以通过如下图所示的组合寄存器xPSR访问。MRS R0, PSR ;读组合程序状态字 MSR PSR, R0 ;写组合程序状态字 ;可以单独访问每个PSR MRS r0, APSR ;将标志状态读入R0 MRS R0, IPSR ;读取异常/中断状态 MSR APSR, R0 ;写标志状态 ;软件代码无法直接使用MRS读出为0或MSR直接访问EPSR ;IPSR为只读的可以从组合PSRxPSR中读出。每个架构的PSR不太一样。2PRIMASK、FAULTMASK、BASEPRI寄存器这三个寄存器都用于异常或中断屏蔽每个异常包括中断都有一个优先等级数值越小优先级越高。这些特殊寄存器可基于优先等级屏蔽异常只有在特权访问等级才可以对它们进行操作非特权状态下的写操作会被忽略而读出则会返回0)。它们默认全部为0也就是屏蔽(禁止异常/中断)不起作用也就是中断是打开的。PRIMASK寄存器为1位宽的中断屏蔽寄存器。在置位时它会阻止不可屏蔽中断(NMI)和HardFault异常之外的所有异常(包括中断)。实际上它是将当前异常优先级提升为0这也是可编程异常/中断的最高优先级。PRIMASK最常见的用途为在时间要求很严格的进程中禁止所有中断在该进程完成后,需要将PRIMASK清除以重新使能中断。FAULTMASK和 PRIMASK 非常类似,不过它还能屏蔽HardFault 异常,它实际上是将异常优先级提升到了-1。假设你进入了 HardFault 处理函数原因是访问了非法内存地址触发了 MemManage 错误。如果你在错误处理函数里想去“清理现场”或“保存肇事数据”而再次访问了那块非法地址系统会立即再次触发 HardFault。由于 CPU 还在 HardFault 里没出来新来的 HardFault 会直接导致Double Fault双倍错误系统直接锁死进入 Lockup 状态。FAULTMASK 的作用就是进入错误处理时先戴上“防爆头盔”开启 FAULTMASK此时即使你在修复代码中访问了错误地址、触发了总线错误或 MPU 保护CPU 也会选择“忽略”这些新错误不让他们打断当前的处理流程。这样你就能安心地执行修复措施比如强行读取危险地址的数据来备份或者重置外设。与PRIMASK不同FAULTMASK在异常返回时会被自动清除。为使中断屏蔽更加灵活ARMv7-M架构还支持BASEPRI,该寄存器会根据优先等级屏蔽异常或中断。BASEPRI的宽度取决于设计实际实现的优先级数量这是由微控制器供应商决定的。大多数Cortex-M3或Cortex-M4微控制器都有8个(3位宽)或16个可编程的异常优先级此时,BASEPRI的宽度就相应地为3位或4位。BASEPRI为0时就不会起作用当被设置为非0数值时它就会屏蔽具有相同或更低优先级的异常(包括中断),而更高优先级的则还可以被处理器接受。CMSIS-Core提供了多C 函数个 C 函数用于访问 PRIAMSK,FAULTMASK 及 BASEPRI 寄存器(注意这些寄存器只能在特权等级下访问)。X _get_BASEPRI(); //读BASEPRI寄存器 X _get_PRIMARK(); //读PRIMASK寄存器 X _get_FAULTMASK(); //读FAULTMASK 寄存器 _set_BASEPRI(x); //设置BASEPRI的新数值 _set_PRIMASK(x); //设置PRIMASK的新数值 _set_FAULTMASK(x) ; //设置FAULTMASK的新数值 _disable_irq(); //设置PRIMASK,禁止 IRQ _enable_irq(); //清除 PRIMASK,使能IRQ ;还可以使用汇编代码访问这些异常屏蔽寄存器: MRS r0, BASEPRI ;将BASEPRI寄存器读入RO MRS r0, PRIMASK ;将PRIMASK寄存器读入RO MRS r0, FAULTMASK ;将FAULTMASK寄存器读入RO MSR BASEPRI, r0 ;将RO写入BASEPRI寄存器 MSR PRIMASK, r0 ;将RO写人PRIMASK寄存器; MSR FAULTMASK, r0 ;将RO写人FAULTMASK寄存器 另外利用修改处理器状态(CPS)指令可以很方便地设置或清除PRIMASK和FAULTMASK 的数值。 CPSIE i ;使能中断(清除PRIMASK) CPSID i ;禁止中断(设置PRIMASK) CPSIE f ;使能中断(清除FAULTMASK) CPSID f ;禁止中断(设置FAULTMASK)(3)CONTROL寄存器栈指针的选择(主栈指针/进程栈指针)。线程模式的访问等级(特权/非特权)。对于具有浮点单元的Cortex-M4处理器CONTROL寄存器中有一位表示当前上下文(正在执行的代码)是否使用浮点单元。复位后CONTROL寄存器默认为0,这也就意味着处理器此时处于线程模式、具有特权访问权限以及使用主栈指针。通过写CONTROL寄存器特权线程模式的程序可以切换栈指针的选择或进人非特权访问等级。不过,nPRIV(CONTROL的第0位)置位后运行在线程模式的程序就不能访问CONTROL寄存器了。运行在非特权等级的程序无法再切换回特权访问等级要将处理器在线程模式切换回特权访问等级则需要使用异常机制。在异常处理期间处理程序可以清除nPRIV位。再返回到线程模式后处理器就会进入特权访问等级。若使用嵌入式OS每次上下文切换时都可以重新编程CONTROL寄存器以满足应用间不同的特权访问等级需要。nPRIV和 SPSEL的设置共有4种组合方式,其中3种在实际应用中较为常见。对于未使用嵌入式OS的多数简单应用无须修改CONTROL寄存器的数值。整个应用可以运行在特权访问等级并且只使用MSP。要利用C语言访问CONTROL寄存器可以使用符合CMSIS的设备驱动库提供的以下函数: x _get_CONTROL(); //读取CONTROL寄存器的当前值 _set_CONTROL(x); //设置CONTROL寄存器的数值为x在修改CONTROL寄存器的值时需要注意以下两点:1.对于具有浮点单元(FPU)的Cortex-M4处理器,或具有FPU的ARMv7-M处理器由于浮点单元的存在FPCA位会自动置位。若程序中包含浮点运算但FPCA位被意外清除而且接下来产生了一个中断那么浮点单元寄存器中的数据将不能在异常人口流程保存且可能会被中断处理覆盖。在这种情况下继续执行被中断的任务时程序能无法继续正确地处理。2.在修改了CONTROL寄存器后从架构来看应该使用指令同步屏障(ISB)指令(或符合CMSIS的设备驱动库中的----ISB()函数)以确保本次修改对接下来的代码能起到作用。 由于 Cortex-M3、Cortex-M4、Cortex-M0、Cortex-M0 以及 Cortex-M1 的流水线非常简单不使用ISB指令也不会引起什么问题。ISB指令同步屏障指令的作用是清空处理器流水线。它会确保在ISB执行完成前其之前的所有指令都已执行完毕而其后的所有指令都必须在ISB完成后重新从内存或缓存中获取。为什么架构要求必须使用ISB处理器为了提升效率会采用流水线技术在执行当前指令的同时预先从内存中读取预取后续的多条指令。问题就出在这里CONTROL寄存器的修改会影响后续指令的执行环境比如权限。但如果在修改CONTROL时流水线里已经预取了一些基于旧权限的指令那么这些指令就可能被错误地执行。ISB指令的作用就是强制清空流水线中这些“过时”的预取指令保证ISB之后的所有指令都使用更新后的CONTROL寄存器状态来执行。要用汇编访问CONTROL寄存器可以使用MRS和MSR指令: MRS r0, CONTROL ;将 CONTROL寄存器读人RO MSR CONTROL, r0 ;将RO写人CONTROL寄存器可以通过检查CONTROL和IPSR的数值来确定当前是否为特权等级。IPSR为正在处理中断编号。 int in_privileged(void) { if(_get_IPSR()!0)return 1;//Ture else if((_get_CONTROL()0X1)0)return 1;//Ture else return 0;//False }4.2.4浮点寄存器Cortex-M4具有可选的浮点单元其提供了浮点数据处理用的一些寄存器以及浮点状态和控制寄存器(FPSCR)。1S0~S31和 D0~D15S0~S31(S)都为32位寄存器,而且每个都可以通过浮点指令访问或者利用符号D0~D15(D代表双字/双精度)成对访问。例如,S0和S1成对组成D0,而S3和S2则成对组成D1。尽管Cortex-M4中的浮点单元不支持双精度浮点运算在传输双精度数据时仍可使用浮点指令。2浮点状态和控制寄存器(FPSCR)FPSCR中包含多个位域定义一些浮点运算动作提供浮点运算结果的状态信息。浮点控制默认被配置为符合IEEE754单精度运算。在普通应用中浮点运算控制的设置也无须修改。3经过存储器映射的浮点单元控制寄存器除了浮点单元寄存器组和FPSCR浮点单元还往系统中引人了一些经过存储器映射的寄存器如用于使能和禁止浮点单元的协处理器访问控制寄存器(CPACR)。浮点单元默认被禁止以降低功耗在使用任何浮点指令前都必须通过编程CPACR寄存器来使能浮点单元。在使用符合CMSIS的设备驱动库的C编程环境中: SCB - CPACR 0xFK20; //使能对FPU的全访问在汇编语言编程环境中可以使用下面的代码: LDR R0, 0xE000ED88 ;RO被设置为CPACR的地址 LDR R1, 0x00F00000 ; R1 0xF20 LDR R2 [R0] ;读取CPACR的当前值 ORRS R2, R2, R1 ;置位 STR R2,[R0] ;将修改后的数值写回CPACR4.3应用程序状态寄存器APSRAPSR中包含下面的几组状态标志:整数运算的状态标志(N-Z-C-V位)饱和运算的状态标志(Q位)SIMD运算的状态标志(GE位)4.3.1整数状态标志整数状态标志和其他许多处理器架构中的ALU状态标志类似它们受普通数据处理指令的影响在控制条件跳转和条件执行时非常有用。另外APSR标志之一也就是C(进位)位也可用于加法和减法运算中。对于ARMv7-M和ARMv7E-M架构,多数16位指令会影响这4个ALU标志。对于多数32位指令指令编码中的一个位定义了是否应该更新APSR标志。注意部分指令不会更新V标志和C标志。例如MULS(乘法)指令只会修改N标志和Z标志。除条件跳转或条件执行代码APSR的进位标志也可用于将加法和减法运算扩大为超过32位。例如在将两个64位整数相加时可以将低32位加法运算的进位标志作为高32位加法的一个输人://计算ZXY其中X、Y和Z都是64位的 Z[31:0] x[31:0] Y[31:0]; //低字相加更新进位标志 Z[63:32] X[63:32] Y[63:32] Carry; //高字相加ARM处理器都具有N-Z-C-V标志其中包括Cortex-M0处理器。4.3.2Q状态标志Q表示饱和算术运算或饱和调整运算过程中产生了饱和其存在于ARMv7-M(如Cortex-M3和 Cortex-M4 处理器),但在 ARMv6-M(如 Cortex-MO 处理器)中不可用。在该位被设置后以及软件写APSR清除Q位之前它会一直保持置位状态饱和算术/调整运算不会清除该位。作用它的作用就是记录“自从上一次被清除以来是否发生过饱和运算”。“饱和”Saturation是DSP数字信号处理运算中的一种特殊处理方式。1普通算术环绕/取模普通或-指令在溢出时结果会回绕例如0x7FFFFFFF 1 0x80000000。这在音频或控制算法中会引发剧烈失真。2饱和算术钳位专用的SSAT有符号饱和、USAT无符号饱和、QADD饱和加法等指令在溢出时会将结果钳位到最大值或最小值例如0x7FFFFFFF 1 的结果依然是 0x7FFFFFFF。应用场景自动增益控制AGC在音频或无线通信中输入信号过大导致饱和。虽然输出还能听但波形已被削平失真。主程序可以在中断或主循环中定期读取Q标志如果发现Q1说明信号过载了程序就会主动减小放大器的增益然后手动清除Q标志重新开始监控。Q标志的作用就是当上述这些“饱和指令”执行时如果发生了钳位即数据被截断在最大值/最小值硬件就会自动将Q标志置为1。4.3.3GE位在Cortex-M4中,“大于等于”(GE)位域在APSR中占用4 位,而 Cortex-M3处理器中则不存在。许多SIMD指令都会更新该标志其中多数情况下每个位表示SIMD运算的每个字节为正或溢出。对于具有16位数据的SIMD指令第0和1位由低半字的结果控制第2和3位则由高半字的结果控制。SIMD是Single Instruction, Multiple Data的缩写即“单指令多数据流”。它允许处理器用一条指令同时对多个数据执行相同的操作。核心思想数据打包Packing在32位的ARM处理器上一个寄存器可以存放4个8位的数据或2个16位的数据。SIMD指令就是把这些“打包”在一个寄存器里的多个数据拿出来同时计算。主要优势提升效率比如要计算4对8位数据的加法。普通指令需要4步而一条SIMD指令如SADD8就能一步完成。这在处理图像、音频等数据时能显著提升效率。GE位是Greater than or Equal的缩写是应用程序状态寄存器APSR中的4个位GE[3:0]。它的作用是记录SIMD指令的运算结果。如何记录当执行SADD8有符号8位并行加法这类SIMD指令时硬件会自动设置GE位。规则如下GE[0]对应字节0[7:0]的运算结果。GE[1]对应字节1[15:8]的运算结果。GE[2]对应字节2[23:16]的运算结果。GE[3]对应字节3[31:24]的运算结果。如果某个通道的运算结果大于或等于零其对应的GE位就会被置为1反之则被清零。GE位本身不直接参与运算它的价值在于配合SEL选择指令。SEL指令会根据GE[3:0]的值从两个源寄存器Rn和Rm中逐字节选择数据拼成最终结果。4.4存储器系统4.4.1存储器系统特性14GB线性地址空间。通过32位寻址ARM处理器可以访问多达4GB的存储器空间。尽管许多嵌入式系统需要的存储器都不超过1MB32位的寻址能力可以确保将来升级和扩展的可能。Cortex-M3和 Cortex-M4处理器用AHB LITE总线协议提供了32位总线利用合适的存储器接口控制器可以将32/16/8位处理器连接到总线上。2架构定义的存储器映射。4GB的存储器空间被划分为多个区域用于预定义的存储器和外设以优化处理器设计的性能。例如Cortex-M3和Cortex-M4处理器具有多个总线接口允许对程序代码用的CODE区域的访问和对SRAM或外设区域的数据操作同时进行。3支持小端和大端的存储器系统。Cortex-M3和Cortex-M4处理器可以使用小端或大端的存储器系统。实际上微控制器产品一般会被设计成只具有一种端配置。4位段访问(可选)。当包含位段特性时(由微控制器/片上系统供应商决定)存储器映射中的两个1MB区域可以通过两个位段区域进行位寻址这样可以实现对SRAM或外设地址空间中单独位的原子操作。5写缓冲。若对可缓冲存储器区域的写传输需要花费几个周期Cortex-M3和Cortex-M4处理器内的写缓冲可能会将本次传输缓存起来处理器可以继续执行下一条指令如果可能的话这样可以提高程序的执行速度。6存储器保护单元(MPU)。MPU定义了各存储器区域的访问权限且为可编程的。Cortex-M3和 Cortex-M4处理器中的 MPU支持8个可编程区域,可在嵌人式OS中提高系统的健壮性。7非对齐传输支持。ARMv7-M架构的所有处理器(包括Cortex-M3和Cortex-M4处理器)都支持非对齐传输。Cortex-M处理器的总线接口为通用总线接口可通过不同的存储器控制器被连接至不同类型和大小的存储器。微控制器存储器系统中的存储器一般为两种或更多:程序代码用的Flash存储器、数据用的静态RAM(SRAM),有时还会有电可擦除只读存储器(EEPROM)。4.4.2存储器影射Cortex-M处理器的4GB地址空间被分为了多个存储器区域。区域根据各自典型用法进行划分它们主要用于:1程序代码访问(如CODE区域)2数据访问(如SRAM区域)3外设(如外设区域)4处理器的内部控制和调试部件(如私有外设总线)程序既可以在CODE区域执行,也可以在SRAM区域执行,而且微控制器也可以在CODE区域加人SRAM块。不同的微控制器具有不同的存储器大小和外设地址这些信息一般会在微控制器供应商提供的数据手册中有所描述。所有Cortex-M处理器的存储器映射处理都是一样的。例如PPB地址区域中存在嵌套向量中断控制器(NVIC)的寄存器、处理器配置寄存器以及调试部件的寄存器等。4.4.3栈存储Cortex-M处理器在运行时需要栈存储和栈指针(R13)。在栈这种存储器使用机制中存储器的一部分可被用作后进先出的数据存储缓冲。ARM处理器将系统主存储器用于栈空间操作且使用PUSH指令往栈中存储数据以及POP指令从栈中提取数据。每次PUSH和POP操作后当前使用的栈指针都会自动调整。栈可用于:1当正在执行的函数需要使用寄存器(寄存器组中)进行数据处理时临时存储数据的初始值。这些数据在函数结束时可以被恢复出来以免调用函数的程序丢失数据。2往函数或子程序中的信息传递。3用于存储局部变量。4在中断等异常产生时保存处理器状态和寄存器数值。Cortex-M处理器使用的栈模型被称作“满递减”。处理器启动后SP被设置为栈存储空间最后的位置。对于每次PUSH操作处理器首先减小SP的值然后将数据存储在SP指向的存储器位置。在操作期间SP指向上一次数据被存储在栈中的位置。寄存器中的数值稍后还会被主程序用到因此它们就被PUSH保存到栈中且在functionl结束时被POP恢复。这样调用函数的程序代码不会丢失任何数据而且可以继续执行。注意对于每次PUSH操作(保存到存储器)都会有一个对应的POP(从存储器中读取),而且POP操作的地址应该和 PUSH的一致。每个PUSH和POP指令都可以往/从栈空间传输多个数据。由于寄存器组中的寄存器都是32位的每个由栈PUSH和POP生成的存储器传输都会访问至少一个字(4字节)的数据而且地址总是对齐到4字节边界上SP的最低两位也总是为0。可以将函数返回和POP操作结合起来首先把LR(R14)的数值压到栈存储中在子程序/函数结束时将其恢复到PC(R15)。Cortex-M处理器在物理上存在两个栈指针。它们为:主栈指针(MSP)。复位后默认使用的栈指针用于所有的异常处理。进程栈指针(PSP)。只能用于线程模式的栈指针通常用于运行嵌入式OS的嵌入式系统中的应用任务。MSP和PSP的选择由 CONTROL寄存器的第2位SPSEL的数值决定若该位为0,则线程模式在栈操作时使用MSP否则线程模式使用PSP。另外在从处理模式到线程模式的异常返回期间栈指针的选择可由EXC_RETURN(异常返回)数值决定这样处理器硬件会相应地自动更新SPSEL的数值。在异常返回时选择哪个栈指针MSP主栈指针 或 PSP进程栈指针不再是软件通过代码设置的而是由硬件根据EXC_RETURN的值自动决定的。硬件会直接修改CONTROL寄存器中的SPSEL栈指针选择位程序员不需要也不应该在此时手动写代码去改CONTROL寄存器。EXC_RETURN本质上是一个特殊的“魔法”数值在0xFFFFFFFx范围内它并不是一个真正的返回地址而是一个携带了返回上下文信息的状态值。当CPU执行异常返回指令如BX LR且LR中装载的是EXC_RETURN值时硬件会执行一个原子性的出栈Pop和恢复序列。在这个序列中它会执行以下操作1.读取EXC_RETURN判断Bit[2]的值。2.选择出栈用的SP如果Bit[2]1硬件就使用PSP来弹出恢复之前压入的寄存器如果Bit[2]0则使用MSP弹出。软件RTOS内核在切换任务时把当前要运行的任务的栈顶地址手动写进了PSP寄存器里。3.同步更新CONTROL.SPSEL在弹出PC程序计数器并跳转回线程模式之前硬件自动将CONTROL寄存器中的SPSEL位设置为与Bit[2]一致的值。4.切换模式硬件将处理器模式从处理模式切换回线程模式。对于不具有OS的简单应用线程模式和处理模式都可以只使用MSP。在异常事件产生后处理器在进入中断服务程序(ISR)前会首先将多个寄存器压人栈中这种寄存器状态保存操作被称作“压栈”,而在ISR结束时这些寄存器又会被恢复到寄存器组中这种操作则被称作“出栈”。若嵌人式系统中包含嵌入式OS它们通常会将应用任务和内核所用的栈空间分离开来因此,PSP就会被用到,而且在异常人口和异常退出时会发生SP切换。利用这种分离的栈结构栈或应用任务的错误不会影响OS使用的栈同时也简化了OS设计提高了上下文切换的速度。尽管同一时间内只有一个SP可见(当使用SP或R13访问时),也可以直接读/写MSP以及PSP无须考虑使用的是哪个SP/R13。假定当前处于特权等级则可以利用下面的CMSIS 函数问 MSP 和 PSP: x _get_MSP(); //读取MSP的数值 _set_MSP(x); //设置MSP的数值 x _get_PSP(); //读取PSP的数值 _set_PSP(x); //设置PSP的数值要在汇编代码中访问MSP和PSP可以使用MSR和 MRS指令: MRS R0, MSP ;将主栈指针读人R0 MSR MSP, R0 ;将R0写人主栈指针 MRS R0, PSP ;将进程栈指针读人R0 MSR PSP,R0 ;将R0写人进程栈指针MSP和PSP一般用于嵌入式 OS。例如通过MRS指令读取PSP的数值后OS可以从应用任务API调用的栈中读出压入的数据(如SVC执行前的寄存器内容)而且OS的上下文切换代码会在上下文切换期间更新PSP的数值。上电后处理器硬件在读取向量表后会自动初始化MSP。PSP则不会被自动初始化需要在使用前由软件初始化。4.4.4存储器保护单元MPUMPU在Cortex-M3和 Cortex-M4处理器中是可选的因此并不是所有的Cortex-M3或Cortex-M4微控制器都有MPU特性。多数应用不会用到MPU,因此可以忽略。在需要高可靠性的嵌入式系统中MPU可以通过定义特权和非特权访问权限来保护存储器区域。MPU是可编程的,而且 Cortex-M3和 Cortex-M4处理器中的MPU支持8 个可编程区域。MPU可以有多种用法。有些情况下MPU由嵌人式OS控制每个任务都被配置了存储器访问权限;而对于其他情况MPU被配置为只保护某一特定存储器区域如将某存储器区域设置为只读。4.5异常和中断4.5.1什么是异常打断运行的程序去执行异常处理函数执行完后返回被打断的地方继续运行。中断也是异常它可以由外设或者软件触发触发后去执行中断服务程序ISR。1NVIC处理异常。NVIC可以处理多个中断请求(IRQ)和一个不可屏蔽中断(NMI)请求IRQ一般由片上外设或外部中断输入通过I/O端口产生NMI可用于看门狗定时器或掉电检测(一种电压监视单元在电压低到一定程度时会给处理器产生警告)。处理器内部也有名为SysTick的定时器它可以产生周期性的定时中断请求可用于嵌人式OS计时或没有OS的应用中的简单定时控制。2处理器自身也是一个异常事件源其中包括表示系统错误状态的错误事件以及软件产生、支持嵌人式OS操作的异常。每个异常源都有一个异常编号编号1~15被归为系统异常16号及其之上的则用于中断。Cortex-M3和Cortex-M处理器在设计上支持最多240个中断输人实际一般在16~100之间这样可以减小硅片面积同时也会降低功耗。异常编号在多个寄存器中都有所体现其中包括用于确定异常向量地址的IPSR。异常向量存储在向量表中在异常入口流程中处理器会读取这个表格以确定异常处理的起始地址。注意异常编号的定义和CMSIS的设备驱动库中的中断编号定义不同。在CMSIS的设备驱动库中中断编号从0开始系统异常编号则为负值。与ARM7TDMI等经典ARM处理器相比,Cortex-M处理器中没有FIQ(快速中断)。不过Cortex-M3和Cortex-M4的中断等待非常小,只有12个周期因此这也不会引起什么问题。复位是一种特殊的异常当处理器从复位中退出时就会在线程模式(而不是其他异常时的处理模式)下执行复位处理。IPSR中的异常编号读出也是0。4.5.2嵌套向量中断控制器NVICNVIC是可编程的寄存器位于存储器映射的系统控制空间(SCS)。NVIC处理异常和中断配置、优先级以及中断屏蔽。1.灵活的中断和异常管理除了NMI其他都可以通过软件使能、禁止、挂起、清除。2.嵌套向量/中断支持有抢占优先级和响应优先级。抢占优先级高的可以打断抢占优先级低的。响应优先级高的可以优先响应但是不能抢占。如果优先级都一样则按照异常编号进行响应。3.向量化的异常/中断入口异常发生处理器会从存储器的中断向量表中自动定位异常处理入口。4.中断屏蔽提供了多个中断屏蔽寄存器,如 PRIMASK特殊寄存器。利用PRIMASK寄存器,可以禁止除HardFault 和NMI外的所有异常。这种屏蔽对不应被中断的操作非常有用如时序关键控制任务。使用BASEPRI寄存器来选择屏蔽低于特定优先级的异常或中断。4.5.3向量表向量表是系统存储器内的字数据数组每个元素都是一个异常的入口函数地址。向量表可以由NVIC中的向量表偏移寄存器VTOR进行重定位。复位后VTOR默认为0。例如复位异常编号为1那么地址为1*4每个字为4字节0x00000004。地址0x00000000存放的是MSP的初始值。4.5.4错误处理检测到未定义指令、总线错误、存储器访问返回错误等错误则会触发错误异常。定义指令、总线错误、存储器访问返回错误默认禁止可以单独使能进行处理当触发错误后都会触发HardFault异常HardFault异常总是使能。4.6系统控制块SCBSCB为处理器的一部分位于NVIC中。SCB包含寄存器用于:控制处理器配置(如低功耗模式)提供错误状态信息(错误状态寄存器)向量表重定位(VTOR)。SCB经过了存储器映射与NVIC寄存器类似SCB寄存器可以从系统控制空间(SCS)访问。4.7调试处理器中包含了多种经过精心设计的调试特性其中包括暂停和步进等程序执行控制、指令断点、数据监视点、寄存器和存储器访问、概况及跟踪等。Cortex-M处理器提供了两种接口:调试和跟踪。调试接口调试适配器可以连接到Cortex-M微控制器上以控制调试特性和访问片上的存储器空间。有JTAG和SWD两种协议。跟踪接口用于运行时收集处理器的信息如数据、事件、概况信息或者程序执行的完整细节。SWV和多引脚两种协议。实时跟踪需要嵌入式跟踪宏单元EMT该单元可选如果没有该单元的MCU则不具备程序/指令跟踪。处理器内还存在其他多个调试部件。例如利用指令跟踪宏单元(ITM)运行在微控制器上的程序代码可以产生通过跟踪接口输出的数据这些数据可以在调试器窗口中显示。4.8复位和复位流程上电复位、系统复位、处理器复位。SCB中的寄存器可以进行系统复位或处理器复位。复位后以及处理器开始执行程序前会读取栈地址和复位异常向量赋值给MSP和PC。
STM32F407学习记录(八)Cortex-M3/M4权威指南:第四章架构
前言本文章相当于学习笔记摘录总结。推荐自行观看相关书籍权威指南不足之处请指点。4.1架构简介Cortex-M3和Cortex-M4都是基于ARMv7-M架构。Cortex-M4发布时架构中又额外增加了新的指令和特性改进后的架构有时也被称为ARMv7E-M.嵌入式开发只需要了解编程模型、异常如中断如何处理、存储器映射、如何使用外设以及如何使用微控制器供应商提供的软件驱动文件等。4.2编程模型4.2.1操作模式和状态Cortex-M3/M4处理器有两种操作状态和两个模式以及特权和非特权访问等级。1操作状态调试状态当处理器被暂停后例如通过调试器或触发断点后就会进入调试状态并停止指令执行。Thumb状态若处理器在执行程序代码Thumb指令它就会处于Thumb状态。Cortex-M处理器只有Thumb状态因为它没有ARM指令集。2操作模式处理模式执行中断服务程序ISR等异常处理。在处理模式下处理器具有特权访问等级。线程模式在执行普通的应用程序代码时处理器既可以处于特权访问等级也可以处于非特权访问等级。3访问等级特权访问等级可以访问处理器中的所有资源。NVIC寄存器只支持特权访问。非特权访问等级有些存储器区域不能访问有些操作也是无法使用的。访问等级由特殊寄存器CONTROL控制。4.2.2寄存器处理器中有多个用于执行数据处理和控制的寄存器可以以寄存器组的形式对其进行分组。每个数据处理指令都指定了所需的操作和源寄存器或目的寄存器。加载---存储架构若要处理存储器中的数据需要将数据从存储器中加载到寄存器里处理器处理完数据后如果有必要还要将寄存器里的数据写回存储器。1R0~R12R0~R7为低寄存器16位的指令只能访问低寄存器。R8~R12为高寄存器可以用于32位指令和几个16位指令。R0~R12的初始值是未定义的。2R13:栈指针SPR13为栈指针可以通过PUSH和POP操作实现栈存储的访问。在物理上存在两个栈指针主栈指针MSP为默认的栈指针在复位后或处理器处于处理模式时其会被处理器选择使用。进程栈指针PSP其只能用于线程模式。两个栈指针由特殊寄存器CONTROL决定对于一般的程序这两个寄存器只会有一个可见。两个栈指针都是32位的但是因为最低两位总是0所以栈操作的地址必须对齐到32位的字边界上。大多数情况下PSP没必要使用。一般用在OS中OS的内核与任务的栈相互独立。PSP的初始值未定义但是MSP的初始值则需要在复位流程中从存储器的第一个字中取出。3R14链接寄存器LR用于函数或子程序调用时返回地址的保存。在函数或子程序结束时通过将LR的数值加载至PC程序计数器中返回到调用函数或者子程序处继续执行。当调用函数或者子程序后LR的数值会自动更新。如果函数中又调用了另一个函数那么需要先将LR的数值保存在栈中否则返回地址将会被覆盖。如果发生了异常LR里的值会被自动更新为特殊的EXC_RETURN(异常返回)值之后在处理完异常后希望通过LR返回会发现这是一个特殊值则触发异常返回操作。PC地址永远是偶数的因为指令必须对齐到半字或者字地址。但是LR的第0位有一个解释在进行指令跳转时如果是经典的ARM处理器第0位等于1则表示跳转过去后切换到Thumb状态第0位等于0则跳转过去后切换到ARM状态。但是Cortex-M3/M4处理器只有Thumb状态所以第0位的作用变成了等于1表示正常跳转等于0表示错误的跳转地址触发异常。并且等于1的话加载到PC的地址会自动忽略第0位强制改为0。4R15程序计数器PC可读可写读操作返回当前指令地址4流水线特性。写PC会引起跳转操作。5程序中使用的寄存器名对于多数汇编工具寄存器有多种名称也可以大小写混用。4.2.3特殊寄存器除了寄存器组中的寄存器处理器中还有多个特殊寄存器。这些寄存器表示处理器状态、定义了操作状态和中断、异常屏蔽。一般来说用不到但是开发嵌入式OS或需要高级中断屏蔽特性时就需要访问它们。特殊寄存器未经过存储器映射需要通过MSR和MRS等特殊寄存器访问指令进行访问。CMSIS_Core也提供了几个用于访问特殊寄存器的C函数。MRS reg,special_reg;将特殊寄存器读入寄存器 MSR special_reg,reg;写入特殊寄存器1程序状态寄存器程序状态寄存器包括三个状态寄存器应用PSRAPSR、执行PSREPSR、中断PSRIPSR。这三个寄存器可以通过如下图所示的组合寄存器xPSR访问。MRS R0, PSR ;读组合程序状态字 MSR PSR, R0 ;写组合程序状态字 ;可以单独访问每个PSR MRS r0, APSR ;将标志状态读入R0 MRS R0, IPSR ;读取异常/中断状态 MSR APSR, R0 ;写标志状态 ;软件代码无法直接使用MRS读出为0或MSR直接访问EPSR ;IPSR为只读的可以从组合PSRxPSR中读出。每个架构的PSR不太一样。2PRIMASK、FAULTMASK、BASEPRI寄存器这三个寄存器都用于异常或中断屏蔽每个异常包括中断都有一个优先等级数值越小优先级越高。这些特殊寄存器可基于优先等级屏蔽异常只有在特权访问等级才可以对它们进行操作非特权状态下的写操作会被忽略而读出则会返回0)。它们默认全部为0也就是屏蔽(禁止异常/中断)不起作用也就是中断是打开的。PRIMASK寄存器为1位宽的中断屏蔽寄存器。在置位时它会阻止不可屏蔽中断(NMI)和HardFault异常之外的所有异常(包括中断)。实际上它是将当前异常优先级提升为0这也是可编程异常/中断的最高优先级。PRIMASK最常见的用途为在时间要求很严格的进程中禁止所有中断在该进程完成后,需要将PRIMASK清除以重新使能中断。FAULTMASK和 PRIMASK 非常类似,不过它还能屏蔽HardFault 异常,它实际上是将异常优先级提升到了-1。假设你进入了 HardFault 处理函数原因是访问了非法内存地址触发了 MemManage 错误。如果你在错误处理函数里想去“清理现场”或“保存肇事数据”而再次访问了那块非法地址系统会立即再次触发 HardFault。由于 CPU 还在 HardFault 里没出来新来的 HardFault 会直接导致Double Fault双倍错误系统直接锁死进入 Lockup 状态。FAULTMASK 的作用就是进入错误处理时先戴上“防爆头盔”开启 FAULTMASK此时即使你在修复代码中访问了错误地址、触发了总线错误或 MPU 保护CPU 也会选择“忽略”这些新错误不让他们打断当前的处理流程。这样你就能安心地执行修复措施比如强行读取危险地址的数据来备份或者重置外设。与PRIMASK不同FAULTMASK在异常返回时会被自动清除。为使中断屏蔽更加灵活ARMv7-M架构还支持BASEPRI,该寄存器会根据优先等级屏蔽异常或中断。BASEPRI的宽度取决于设计实际实现的优先级数量这是由微控制器供应商决定的。大多数Cortex-M3或Cortex-M4微控制器都有8个(3位宽)或16个可编程的异常优先级此时,BASEPRI的宽度就相应地为3位或4位。BASEPRI为0时就不会起作用当被设置为非0数值时它就会屏蔽具有相同或更低优先级的异常(包括中断),而更高优先级的则还可以被处理器接受。CMSIS-Core提供了多C 函数个 C 函数用于访问 PRIAMSK,FAULTMASK 及 BASEPRI 寄存器(注意这些寄存器只能在特权等级下访问)。X _get_BASEPRI(); //读BASEPRI寄存器 X _get_PRIMARK(); //读PRIMASK寄存器 X _get_FAULTMASK(); //读FAULTMASK 寄存器 _set_BASEPRI(x); //设置BASEPRI的新数值 _set_PRIMASK(x); //设置PRIMASK的新数值 _set_FAULTMASK(x) ; //设置FAULTMASK的新数值 _disable_irq(); //设置PRIMASK,禁止 IRQ _enable_irq(); //清除 PRIMASK,使能IRQ ;还可以使用汇编代码访问这些异常屏蔽寄存器: MRS r0, BASEPRI ;将BASEPRI寄存器读入RO MRS r0, PRIMASK ;将PRIMASK寄存器读入RO MRS r0, FAULTMASK ;将FAULTMASK寄存器读入RO MSR BASEPRI, r0 ;将RO写入BASEPRI寄存器 MSR PRIMASK, r0 ;将RO写人PRIMASK寄存器; MSR FAULTMASK, r0 ;将RO写人FAULTMASK寄存器 另外利用修改处理器状态(CPS)指令可以很方便地设置或清除PRIMASK和FAULTMASK 的数值。 CPSIE i ;使能中断(清除PRIMASK) CPSID i ;禁止中断(设置PRIMASK) CPSIE f ;使能中断(清除FAULTMASK) CPSID f ;禁止中断(设置FAULTMASK)(3)CONTROL寄存器栈指针的选择(主栈指针/进程栈指针)。线程模式的访问等级(特权/非特权)。对于具有浮点单元的Cortex-M4处理器CONTROL寄存器中有一位表示当前上下文(正在执行的代码)是否使用浮点单元。复位后CONTROL寄存器默认为0,这也就意味着处理器此时处于线程模式、具有特权访问权限以及使用主栈指针。通过写CONTROL寄存器特权线程模式的程序可以切换栈指针的选择或进人非特权访问等级。不过,nPRIV(CONTROL的第0位)置位后运行在线程模式的程序就不能访问CONTROL寄存器了。运行在非特权等级的程序无法再切换回特权访问等级要将处理器在线程模式切换回特权访问等级则需要使用异常机制。在异常处理期间处理程序可以清除nPRIV位。再返回到线程模式后处理器就会进入特权访问等级。若使用嵌入式OS每次上下文切换时都可以重新编程CONTROL寄存器以满足应用间不同的特权访问等级需要。nPRIV和 SPSEL的设置共有4种组合方式,其中3种在实际应用中较为常见。对于未使用嵌入式OS的多数简单应用无须修改CONTROL寄存器的数值。整个应用可以运行在特权访问等级并且只使用MSP。要利用C语言访问CONTROL寄存器可以使用符合CMSIS的设备驱动库提供的以下函数: x _get_CONTROL(); //读取CONTROL寄存器的当前值 _set_CONTROL(x); //设置CONTROL寄存器的数值为x在修改CONTROL寄存器的值时需要注意以下两点:1.对于具有浮点单元(FPU)的Cortex-M4处理器,或具有FPU的ARMv7-M处理器由于浮点单元的存在FPCA位会自动置位。若程序中包含浮点运算但FPCA位被意外清除而且接下来产生了一个中断那么浮点单元寄存器中的数据将不能在异常人口流程保存且可能会被中断处理覆盖。在这种情况下继续执行被中断的任务时程序能无法继续正确地处理。2.在修改了CONTROL寄存器后从架构来看应该使用指令同步屏障(ISB)指令(或符合CMSIS的设备驱动库中的----ISB()函数)以确保本次修改对接下来的代码能起到作用。 由于 Cortex-M3、Cortex-M4、Cortex-M0、Cortex-M0 以及 Cortex-M1 的流水线非常简单不使用ISB指令也不会引起什么问题。ISB指令同步屏障指令的作用是清空处理器流水线。它会确保在ISB执行完成前其之前的所有指令都已执行完毕而其后的所有指令都必须在ISB完成后重新从内存或缓存中获取。为什么架构要求必须使用ISB处理器为了提升效率会采用流水线技术在执行当前指令的同时预先从内存中读取预取后续的多条指令。问题就出在这里CONTROL寄存器的修改会影响后续指令的执行环境比如权限。但如果在修改CONTROL时流水线里已经预取了一些基于旧权限的指令那么这些指令就可能被错误地执行。ISB指令的作用就是强制清空流水线中这些“过时”的预取指令保证ISB之后的所有指令都使用更新后的CONTROL寄存器状态来执行。要用汇编访问CONTROL寄存器可以使用MRS和MSR指令: MRS r0, CONTROL ;将 CONTROL寄存器读人RO MSR CONTROL, r0 ;将RO写人CONTROL寄存器可以通过检查CONTROL和IPSR的数值来确定当前是否为特权等级。IPSR为正在处理中断编号。 int in_privileged(void) { if(_get_IPSR()!0)return 1;//Ture else if((_get_CONTROL()0X1)0)return 1;//Ture else return 0;//False }4.2.4浮点寄存器Cortex-M4具有可选的浮点单元其提供了浮点数据处理用的一些寄存器以及浮点状态和控制寄存器(FPSCR)。1S0~S31和 D0~D15S0~S31(S)都为32位寄存器,而且每个都可以通过浮点指令访问或者利用符号D0~D15(D代表双字/双精度)成对访问。例如,S0和S1成对组成D0,而S3和S2则成对组成D1。尽管Cortex-M4中的浮点单元不支持双精度浮点运算在传输双精度数据时仍可使用浮点指令。2浮点状态和控制寄存器(FPSCR)FPSCR中包含多个位域定义一些浮点运算动作提供浮点运算结果的状态信息。浮点控制默认被配置为符合IEEE754单精度运算。在普通应用中浮点运算控制的设置也无须修改。3经过存储器映射的浮点单元控制寄存器除了浮点单元寄存器组和FPSCR浮点单元还往系统中引人了一些经过存储器映射的寄存器如用于使能和禁止浮点单元的协处理器访问控制寄存器(CPACR)。浮点单元默认被禁止以降低功耗在使用任何浮点指令前都必须通过编程CPACR寄存器来使能浮点单元。在使用符合CMSIS的设备驱动库的C编程环境中: SCB - CPACR 0xFK20; //使能对FPU的全访问在汇编语言编程环境中可以使用下面的代码: LDR R0, 0xE000ED88 ;RO被设置为CPACR的地址 LDR R1, 0x00F00000 ; R1 0xF20 LDR R2 [R0] ;读取CPACR的当前值 ORRS R2, R2, R1 ;置位 STR R2,[R0] ;将修改后的数值写回CPACR4.3应用程序状态寄存器APSRAPSR中包含下面的几组状态标志:整数运算的状态标志(N-Z-C-V位)饱和运算的状态标志(Q位)SIMD运算的状态标志(GE位)4.3.1整数状态标志整数状态标志和其他许多处理器架构中的ALU状态标志类似它们受普通数据处理指令的影响在控制条件跳转和条件执行时非常有用。另外APSR标志之一也就是C(进位)位也可用于加法和减法运算中。对于ARMv7-M和ARMv7E-M架构,多数16位指令会影响这4个ALU标志。对于多数32位指令指令编码中的一个位定义了是否应该更新APSR标志。注意部分指令不会更新V标志和C标志。例如MULS(乘法)指令只会修改N标志和Z标志。除条件跳转或条件执行代码APSR的进位标志也可用于将加法和减法运算扩大为超过32位。例如在将两个64位整数相加时可以将低32位加法运算的进位标志作为高32位加法的一个输人://计算ZXY其中X、Y和Z都是64位的 Z[31:0] x[31:0] Y[31:0]; //低字相加更新进位标志 Z[63:32] X[63:32] Y[63:32] Carry; //高字相加ARM处理器都具有N-Z-C-V标志其中包括Cortex-M0处理器。4.3.2Q状态标志Q表示饱和算术运算或饱和调整运算过程中产生了饱和其存在于ARMv7-M(如Cortex-M3和 Cortex-M4 处理器),但在 ARMv6-M(如 Cortex-MO 处理器)中不可用。在该位被设置后以及软件写APSR清除Q位之前它会一直保持置位状态饱和算术/调整运算不会清除该位。作用它的作用就是记录“自从上一次被清除以来是否发生过饱和运算”。“饱和”Saturation是DSP数字信号处理运算中的一种特殊处理方式。1普通算术环绕/取模普通或-指令在溢出时结果会回绕例如0x7FFFFFFF 1 0x80000000。这在音频或控制算法中会引发剧烈失真。2饱和算术钳位专用的SSAT有符号饱和、USAT无符号饱和、QADD饱和加法等指令在溢出时会将结果钳位到最大值或最小值例如0x7FFFFFFF 1 的结果依然是 0x7FFFFFFF。应用场景自动增益控制AGC在音频或无线通信中输入信号过大导致饱和。虽然输出还能听但波形已被削平失真。主程序可以在中断或主循环中定期读取Q标志如果发现Q1说明信号过载了程序就会主动减小放大器的增益然后手动清除Q标志重新开始监控。Q标志的作用就是当上述这些“饱和指令”执行时如果发生了钳位即数据被截断在最大值/最小值硬件就会自动将Q标志置为1。4.3.3GE位在Cortex-M4中,“大于等于”(GE)位域在APSR中占用4 位,而 Cortex-M3处理器中则不存在。许多SIMD指令都会更新该标志其中多数情况下每个位表示SIMD运算的每个字节为正或溢出。对于具有16位数据的SIMD指令第0和1位由低半字的结果控制第2和3位则由高半字的结果控制。SIMD是Single Instruction, Multiple Data的缩写即“单指令多数据流”。它允许处理器用一条指令同时对多个数据执行相同的操作。核心思想数据打包Packing在32位的ARM处理器上一个寄存器可以存放4个8位的数据或2个16位的数据。SIMD指令就是把这些“打包”在一个寄存器里的多个数据拿出来同时计算。主要优势提升效率比如要计算4对8位数据的加法。普通指令需要4步而一条SIMD指令如SADD8就能一步完成。这在处理图像、音频等数据时能显著提升效率。GE位是Greater than or Equal的缩写是应用程序状态寄存器APSR中的4个位GE[3:0]。它的作用是记录SIMD指令的运算结果。如何记录当执行SADD8有符号8位并行加法这类SIMD指令时硬件会自动设置GE位。规则如下GE[0]对应字节0[7:0]的运算结果。GE[1]对应字节1[15:8]的运算结果。GE[2]对应字节2[23:16]的运算结果。GE[3]对应字节3[31:24]的运算结果。如果某个通道的运算结果大于或等于零其对应的GE位就会被置为1反之则被清零。GE位本身不直接参与运算它的价值在于配合SEL选择指令。SEL指令会根据GE[3:0]的值从两个源寄存器Rn和Rm中逐字节选择数据拼成最终结果。4.4存储器系统4.4.1存储器系统特性14GB线性地址空间。通过32位寻址ARM处理器可以访问多达4GB的存储器空间。尽管许多嵌入式系统需要的存储器都不超过1MB32位的寻址能力可以确保将来升级和扩展的可能。Cortex-M3和 Cortex-M4处理器用AHB LITE总线协议提供了32位总线利用合适的存储器接口控制器可以将32/16/8位处理器连接到总线上。2架构定义的存储器映射。4GB的存储器空间被划分为多个区域用于预定义的存储器和外设以优化处理器设计的性能。例如Cortex-M3和Cortex-M4处理器具有多个总线接口允许对程序代码用的CODE区域的访问和对SRAM或外设区域的数据操作同时进行。3支持小端和大端的存储器系统。Cortex-M3和Cortex-M4处理器可以使用小端或大端的存储器系统。实际上微控制器产品一般会被设计成只具有一种端配置。4位段访问(可选)。当包含位段特性时(由微控制器/片上系统供应商决定)存储器映射中的两个1MB区域可以通过两个位段区域进行位寻址这样可以实现对SRAM或外设地址空间中单独位的原子操作。5写缓冲。若对可缓冲存储器区域的写传输需要花费几个周期Cortex-M3和Cortex-M4处理器内的写缓冲可能会将本次传输缓存起来处理器可以继续执行下一条指令如果可能的话这样可以提高程序的执行速度。6存储器保护单元(MPU)。MPU定义了各存储器区域的访问权限且为可编程的。Cortex-M3和 Cortex-M4处理器中的 MPU支持8个可编程区域,可在嵌人式OS中提高系统的健壮性。7非对齐传输支持。ARMv7-M架构的所有处理器(包括Cortex-M3和Cortex-M4处理器)都支持非对齐传输。Cortex-M处理器的总线接口为通用总线接口可通过不同的存储器控制器被连接至不同类型和大小的存储器。微控制器存储器系统中的存储器一般为两种或更多:程序代码用的Flash存储器、数据用的静态RAM(SRAM),有时还会有电可擦除只读存储器(EEPROM)。4.4.2存储器影射Cortex-M处理器的4GB地址空间被分为了多个存储器区域。区域根据各自典型用法进行划分它们主要用于:1程序代码访问(如CODE区域)2数据访问(如SRAM区域)3外设(如外设区域)4处理器的内部控制和调试部件(如私有外设总线)程序既可以在CODE区域执行,也可以在SRAM区域执行,而且微控制器也可以在CODE区域加人SRAM块。不同的微控制器具有不同的存储器大小和外设地址这些信息一般会在微控制器供应商提供的数据手册中有所描述。所有Cortex-M处理器的存储器映射处理都是一样的。例如PPB地址区域中存在嵌套向量中断控制器(NVIC)的寄存器、处理器配置寄存器以及调试部件的寄存器等。4.4.3栈存储Cortex-M处理器在运行时需要栈存储和栈指针(R13)。在栈这种存储器使用机制中存储器的一部分可被用作后进先出的数据存储缓冲。ARM处理器将系统主存储器用于栈空间操作且使用PUSH指令往栈中存储数据以及POP指令从栈中提取数据。每次PUSH和POP操作后当前使用的栈指针都会自动调整。栈可用于:1当正在执行的函数需要使用寄存器(寄存器组中)进行数据处理时临时存储数据的初始值。这些数据在函数结束时可以被恢复出来以免调用函数的程序丢失数据。2往函数或子程序中的信息传递。3用于存储局部变量。4在中断等异常产生时保存处理器状态和寄存器数值。Cortex-M处理器使用的栈模型被称作“满递减”。处理器启动后SP被设置为栈存储空间最后的位置。对于每次PUSH操作处理器首先减小SP的值然后将数据存储在SP指向的存储器位置。在操作期间SP指向上一次数据被存储在栈中的位置。寄存器中的数值稍后还会被主程序用到因此它们就被PUSH保存到栈中且在functionl结束时被POP恢复。这样调用函数的程序代码不会丢失任何数据而且可以继续执行。注意对于每次PUSH操作(保存到存储器)都会有一个对应的POP(从存储器中读取),而且POP操作的地址应该和 PUSH的一致。每个PUSH和POP指令都可以往/从栈空间传输多个数据。由于寄存器组中的寄存器都是32位的每个由栈PUSH和POP生成的存储器传输都会访问至少一个字(4字节)的数据而且地址总是对齐到4字节边界上SP的最低两位也总是为0。可以将函数返回和POP操作结合起来首先把LR(R14)的数值压到栈存储中在子程序/函数结束时将其恢复到PC(R15)。Cortex-M处理器在物理上存在两个栈指针。它们为:主栈指针(MSP)。复位后默认使用的栈指针用于所有的异常处理。进程栈指针(PSP)。只能用于线程模式的栈指针通常用于运行嵌入式OS的嵌入式系统中的应用任务。MSP和PSP的选择由 CONTROL寄存器的第2位SPSEL的数值决定若该位为0,则线程模式在栈操作时使用MSP否则线程模式使用PSP。另外在从处理模式到线程模式的异常返回期间栈指针的选择可由EXC_RETURN(异常返回)数值决定这样处理器硬件会相应地自动更新SPSEL的数值。在异常返回时选择哪个栈指针MSP主栈指针 或 PSP进程栈指针不再是软件通过代码设置的而是由硬件根据EXC_RETURN的值自动决定的。硬件会直接修改CONTROL寄存器中的SPSEL栈指针选择位程序员不需要也不应该在此时手动写代码去改CONTROL寄存器。EXC_RETURN本质上是一个特殊的“魔法”数值在0xFFFFFFFx范围内它并不是一个真正的返回地址而是一个携带了返回上下文信息的状态值。当CPU执行异常返回指令如BX LR且LR中装载的是EXC_RETURN值时硬件会执行一个原子性的出栈Pop和恢复序列。在这个序列中它会执行以下操作1.读取EXC_RETURN判断Bit[2]的值。2.选择出栈用的SP如果Bit[2]1硬件就使用PSP来弹出恢复之前压入的寄存器如果Bit[2]0则使用MSP弹出。软件RTOS内核在切换任务时把当前要运行的任务的栈顶地址手动写进了PSP寄存器里。3.同步更新CONTROL.SPSEL在弹出PC程序计数器并跳转回线程模式之前硬件自动将CONTROL寄存器中的SPSEL位设置为与Bit[2]一致的值。4.切换模式硬件将处理器模式从处理模式切换回线程模式。对于不具有OS的简单应用线程模式和处理模式都可以只使用MSP。在异常事件产生后处理器在进入中断服务程序(ISR)前会首先将多个寄存器压人栈中这种寄存器状态保存操作被称作“压栈”,而在ISR结束时这些寄存器又会被恢复到寄存器组中这种操作则被称作“出栈”。若嵌人式系统中包含嵌入式OS它们通常会将应用任务和内核所用的栈空间分离开来因此,PSP就会被用到,而且在异常人口和异常退出时会发生SP切换。利用这种分离的栈结构栈或应用任务的错误不会影响OS使用的栈同时也简化了OS设计提高了上下文切换的速度。尽管同一时间内只有一个SP可见(当使用SP或R13访问时),也可以直接读/写MSP以及PSP无须考虑使用的是哪个SP/R13。假定当前处于特权等级则可以利用下面的CMSIS 函数问 MSP 和 PSP: x _get_MSP(); //读取MSP的数值 _set_MSP(x); //设置MSP的数值 x _get_PSP(); //读取PSP的数值 _set_PSP(x); //设置PSP的数值要在汇编代码中访问MSP和PSP可以使用MSR和 MRS指令: MRS R0, MSP ;将主栈指针读人R0 MSR MSP, R0 ;将R0写人主栈指针 MRS R0, PSP ;将进程栈指针读人R0 MSR PSP,R0 ;将R0写人进程栈指针MSP和PSP一般用于嵌入式 OS。例如通过MRS指令读取PSP的数值后OS可以从应用任务API调用的栈中读出压入的数据(如SVC执行前的寄存器内容)而且OS的上下文切换代码会在上下文切换期间更新PSP的数值。上电后处理器硬件在读取向量表后会自动初始化MSP。PSP则不会被自动初始化需要在使用前由软件初始化。4.4.4存储器保护单元MPUMPU在Cortex-M3和 Cortex-M4处理器中是可选的因此并不是所有的Cortex-M3或Cortex-M4微控制器都有MPU特性。多数应用不会用到MPU,因此可以忽略。在需要高可靠性的嵌入式系统中MPU可以通过定义特权和非特权访问权限来保护存储器区域。MPU是可编程的,而且 Cortex-M3和 Cortex-M4处理器中的MPU支持8 个可编程区域。MPU可以有多种用法。有些情况下MPU由嵌人式OS控制每个任务都被配置了存储器访问权限;而对于其他情况MPU被配置为只保护某一特定存储器区域如将某存储器区域设置为只读。4.5异常和中断4.5.1什么是异常打断运行的程序去执行异常处理函数执行完后返回被打断的地方继续运行。中断也是异常它可以由外设或者软件触发触发后去执行中断服务程序ISR。1NVIC处理异常。NVIC可以处理多个中断请求(IRQ)和一个不可屏蔽中断(NMI)请求IRQ一般由片上外设或外部中断输入通过I/O端口产生NMI可用于看门狗定时器或掉电检测(一种电压监视单元在电压低到一定程度时会给处理器产生警告)。处理器内部也有名为SysTick的定时器它可以产生周期性的定时中断请求可用于嵌人式OS计时或没有OS的应用中的简单定时控制。2处理器自身也是一个异常事件源其中包括表示系统错误状态的错误事件以及软件产生、支持嵌人式OS操作的异常。每个异常源都有一个异常编号编号1~15被归为系统异常16号及其之上的则用于中断。Cortex-M3和Cortex-M处理器在设计上支持最多240个中断输人实际一般在16~100之间这样可以减小硅片面积同时也会降低功耗。异常编号在多个寄存器中都有所体现其中包括用于确定异常向量地址的IPSR。异常向量存储在向量表中在异常入口流程中处理器会读取这个表格以确定异常处理的起始地址。注意异常编号的定义和CMSIS的设备驱动库中的中断编号定义不同。在CMSIS的设备驱动库中中断编号从0开始系统异常编号则为负值。与ARM7TDMI等经典ARM处理器相比,Cortex-M处理器中没有FIQ(快速中断)。不过Cortex-M3和Cortex-M4的中断等待非常小,只有12个周期因此这也不会引起什么问题。复位是一种特殊的异常当处理器从复位中退出时就会在线程模式(而不是其他异常时的处理模式)下执行复位处理。IPSR中的异常编号读出也是0。4.5.2嵌套向量中断控制器NVICNVIC是可编程的寄存器位于存储器映射的系统控制空间(SCS)。NVIC处理异常和中断配置、优先级以及中断屏蔽。1.灵活的中断和异常管理除了NMI其他都可以通过软件使能、禁止、挂起、清除。2.嵌套向量/中断支持有抢占优先级和响应优先级。抢占优先级高的可以打断抢占优先级低的。响应优先级高的可以优先响应但是不能抢占。如果优先级都一样则按照异常编号进行响应。3.向量化的异常/中断入口异常发生处理器会从存储器的中断向量表中自动定位异常处理入口。4.中断屏蔽提供了多个中断屏蔽寄存器,如 PRIMASK特殊寄存器。利用PRIMASK寄存器,可以禁止除HardFault 和NMI外的所有异常。这种屏蔽对不应被中断的操作非常有用如时序关键控制任务。使用BASEPRI寄存器来选择屏蔽低于特定优先级的异常或中断。4.5.3向量表向量表是系统存储器内的字数据数组每个元素都是一个异常的入口函数地址。向量表可以由NVIC中的向量表偏移寄存器VTOR进行重定位。复位后VTOR默认为0。例如复位异常编号为1那么地址为1*4每个字为4字节0x00000004。地址0x00000000存放的是MSP的初始值。4.5.4错误处理检测到未定义指令、总线错误、存储器访问返回错误等错误则会触发错误异常。定义指令、总线错误、存储器访问返回错误默认禁止可以单独使能进行处理当触发错误后都会触发HardFault异常HardFault异常总是使能。4.6系统控制块SCBSCB为处理器的一部分位于NVIC中。SCB包含寄存器用于:控制处理器配置(如低功耗模式)提供错误状态信息(错误状态寄存器)向量表重定位(VTOR)。SCB经过了存储器映射与NVIC寄存器类似SCB寄存器可以从系统控制空间(SCS)访问。4.7调试处理器中包含了多种经过精心设计的调试特性其中包括暂停和步进等程序执行控制、指令断点、数据监视点、寄存器和存储器访问、概况及跟踪等。Cortex-M处理器提供了两种接口:调试和跟踪。调试接口调试适配器可以连接到Cortex-M微控制器上以控制调试特性和访问片上的存储器空间。有JTAG和SWD两种协议。跟踪接口用于运行时收集处理器的信息如数据、事件、概况信息或者程序执行的完整细节。SWV和多引脚两种协议。实时跟踪需要嵌入式跟踪宏单元EMT该单元可选如果没有该单元的MCU则不具备程序/指令跟踪。处理器内还存在其他多个调试部件。例如利用指令跟踪宏单元(ITM)运行在微控制器上的程序代码可以产生通过跟踪接口输出的数据这些数据可以在调试器窗口中显示。4.8复位和复位流程上电复位、系统复位、处理器复位。SCB中的寄存器可以进行系统复位或处理器复位。复位后以及处理器开始执行程序前会读取栈地址和复位异常向量赋值给MSP和PC。