浅谈ARM Cortex-M系列架构——指令集
目录
前言
1.操作状态
2.操作模式
二、寄存器
1.通用目的寄存器
2.R13,栈指针(SP)
3.R14,链接寄存器(LR)
4.R15,程序计数器(PC)
三、特殊寄存器
1.PRIMASK寄存器
2.FAULTMASK寄存器
3.BASEPRI寄存器
4.CONTROL寄存器
四、存储器系统
1.存储器系统特性
2.存储器映射
3.栈存储
4.存储器保护单元(MPU)
五、异常和中断
六、系统控制块(SCB)
七、调试
八、复位和复位流程
总结
Cortex-M3和Cortex-M4 处理器都基于ARMv7-M架构。最初的ARMv7-M架构是随着Cortex-M3处理器一同引入的,而在Cortex-M4发布时,架构中又额外增加了新的指令和特性,改进后的架构有时也被称为 ARMv7E-M。要了解ARMv7-M和ARMv7E-M 的特性可以参考架构说明文献:ARMv7-M架构参考手册。ARMv7-M架构参考手册非常庞大,超过了1000页其中括从指令集存储器系统到调试支持的处理器行为的架构需求细节。尽管它对于处理器设计人员、C 编译器和开发工具设计人员非常有用,读起来可不是一件很容易的事情,对刚开始接触 ARM 架构的更是如此。
要在一般应用中使用 Cortex-M 微控制器,无须了解架构的详细内容。只需对一些方面有个基本了解就可以了,其中,包括编程模型、异常(如中断)如何处理、存储器映射、如何使用外设以及如何使用微控制器供应商提供的软件驱动库文件等。
首先,来看一下处理器的编程模型,它涉及操作模式、寄存器组,以及特殊寄存器。
一、操作模式和状态
Cortex-M3/M4有两种操作模式和状态。
图1.1
特殊寄存器CONTROL 控制特权模式和非特权模式
通过图1.1可以看出是软件可以将处理器从特权线程模式切换到非特权线程模式,但无法将自身从非特权线程模式切换到特权模式,如果非要进行这种切换的话,就要借用异常机制才能实现。
举个例子,这是一个简单的中断挂起和激活行为,我们可以看出,当处理器响应中断进入中断服务程序时,处理器模式从线程模式切换到了处理模式,结束之后重新变成线程模式。 这就实现了特权模式的切换。因为我们知道我们一般的程序运行都是处于非特权线程模式,当程序遇到中断(ARM中属于特殊的异常)就从线程模式切换到处理模式,上面也说到了,在处理模式下,处理器总是具有特权访问等级。所以这就是借用异常机制实现非特权切换到特权模式。
那么大家有没有想过为什么要推出特权和非特权模式呢?
我们可以这样想想,我们都知道在STM32中有一个叫NVIC的控制器,里面存放这中断向量表,如果我们的程序可以随意访问修改这些向量表,那么我们设立的这些规则不就没有用了吗?所以说,特权模式更像是一个保险库,保护着内核里面的寄存器不被修改具体地说就是MPU,NVIC,SCB和STK四个单元不能访问。
废话不多说直接上图
对于处理器来说,寄存器可以作为暂存器,存储临时结果,也可以作为输入数据,方便运算,也可以作为一种索引,去访问存储器,其作用各种各样。
其中R0-R7是低寄存器,因为16位的指令只能访问它们。
而R8-R12是高寄存器,可以被16/32位指令访问。
而且R0-R12这些寄存器的值都是未定义的,值是不确定的。
下图是一个简单的减法程序,访问通用目的寄存器,并将值保存在R3。
R13为栈指针,可通过PUSH和POP操作实现栈存储的访问。物理上存在两个栈指针:主栈指针(MSP,有些ARM文献也称其为SP_main)为默认的栈指针,在复位后或处理器处于处理模式时,其会被处理器选择使用。另外一个栈指针名为进程栈指针(PSP,有些ARM文献也称其为SP_process)其只能用于线程模式。栈指针的选择由特殊寄存器CONTROL决定。对于一般的程序,这两个寄存器只会有一个可见。MSP和PSP都是32位的,不过指针(MSP或PSP)的最低两位总是为0,对这两位的写操作不起作用。对于ARM Cortex-M 处理器PUSH和POP总是32位的操作的地址也必须对齐到32位的字边界上。
大多情况下,若应用不需要嵌入式OS,PSP 也没必要使用。许多简单的应用可以完全依赖于MSP,一般在用到嵌入式OS 时才会使用 PSP,此时OS 内核同应用任务的栈是相互独立的。PSP的初始值未定义,而MSP 的初始值则需要在复位流程中从存储器的第一个字中取出。
R14 也被称作链接寄存器(LR),用于函数或子程序调用时返回地址的保存。在函数或子程序结束时,程序控制可以通过将LR的数值加载程序计数器(PC)中返回调用程序处并继续执行。当执行了函数或子程序调用后,LR 的数值会自动更新。若某函数需要调用另外一个函数或子程序,则它需要首先将 LR 的数值保存在栈中,否则,当执行了函数调用后,LR 的当前值会丢失。
这是一段从keil中生成的反汇编代码
尽管 Cortex-M处理器中的返回地址数值总是偶数(由于指令会对齐到半字地址上,因此,第0位为0),LR的第0位为可读可写的,有些跳转/调用操作需要将LR(或正使用的任何寄存器)的第0位置1以表示Thumb状态。这就导致,当我们从LR寄存器中读取到的数值,想要通过改数值定位到函数入口时,需要将改数值的Bit0位置0,这样才能跳转到程序的入口地址。
R15 为程序计数器(PC),是可读可写的,读操作返回当前指令地址加 4(由于设计的流水线特性及同ARM7TDMI处理器兼容的需要)。写PC(例如使用数据传输/处理指)会引起跳转操作。
由于指令必须要对齐到半字或字地址,PC 的最低位(LSB)为0。不过,在使用一些跳转读存储器指令更新PC时,需要将新 PC值的LSB置1以表示Thumb状态否则就会由于试图使用不支持的ARM指令(如ARM7TDMI中的32位ARM指)而触发错误异常。对于高级编程语言(包括C和C++),编译器会自动将跳转目标的LSB置位。
多数情况下,跳转和调用由专门的指令实现,利用数据处理指令更新 PC 的情况较为少见。不过,在访问位于程序存储器中的字符数据时,PC 的数值非常有用,因此,会经常发现存储器读操作将PC作为基地址寄存器,而地址偏移则由指令中的立即数生成。
在上一篇文章中已经介绍过了xPSR寄存器这里将不再介绍,有需要看同学可以通过开头超链接进入查看,这里主要介绍中断/异常屏蔽寄存器和处理器控制寄存器。
PRIMASKFAULTMASK和BASEPRI寄存器都用于异常或中断屏蔽,每个异常(包括中断)都具有一个优先等级,数值小的优先级高,而数值大的则优先级低。这些特殊寄存器可基于优先等级屏蔽异常,只有在特权访问等级才可以对它们进行操作(非特权状态下的写操作会被忽略,而读出则会返回 0)。它们默认全部为 0,也就是屏蔽(禁止异常/中断)不起作用。
这些寄存器的编程模型如图所示。
由图可知PRIMASK寄存器为1位宽的中断屏蔽寄存器。在置位时,它会阻止不可屏蔽中断(NMI)和 HardFault 异常之外的所有异常(包括中断)。在许多应用中,可能都需要暂时禁止所有中断以执行一些时序关键的任务,此时可以使用PRIMASK寄存器。PRIMASK寄存器只能在特权状态访问。PRIMASK用于禁止除NMI和 HardFault外的所有异常它实际上是将当前优先级改为0(最高的可编程等级)。
FAULTMASK和PRIMASK 非常类似不过它还能屏蔽 HardFault 异常它实际上是将异常优先级提升到了-1。错误处理代码可以使用FAULTMASK 以免在错误处理期间引发其他的错误(只有几种)。例如,FAULTMASK 可用于路MPU 或屏蔽总线错误(这些都是可配置的),这样,错误处理代码执行修复措施也就更加容易了。与 PRIMASK 不同FAULTMASK 在异常返回时会被自动清除。
有些情况下,可能只想禁止优先级低于某特定等级的中断,此时,就可以使用 BASEPRI寄存器。要实现这个目的,只需简单地将所需的屏蔽优先级写入 BASEPRI寄存器。例如,若要屏蔽优先级小于等于0x60的所有异常,则可以将这个数值写入 BASEPRI。
这些寄存器的具体实现方式我会在异常和中断中详细讲解。
CONTROL寄存器定义了
另外,对于具有浮点单元的 Cortex-M4 处理器,CONTROL寄存器中有一位表示当前上下文(正在执行的代码)是否使用浮点单元。
注意 为了便于比较,ARMv6-M(如 Cortex-M0)的 CONTROL 寄存器可以参见本书插图对于ARMv6-M,nPRIV 和非特权等级的实现是和设计实现相关的,而且在最初的Cortex-M0和Cortex-M1产品上是不可用的,其在Cortex-M0+上则是可选的。
CONTROL寄存器只能在特权访问等级进行修改操作,而读取操作则在特权和非特权访问等级都可以。
各位域的作用
Cortex-M3/M4处理器的存储器特性:
Cortex-M处理器的总线接口为通用总线接口,可通过不同的存储器控制器被连接至不同类型和大小的存储器。微控制器存储器系统中的存储器一般为两种或更多:程序代码用的Flash 存储器数据用的静态 RAM(SRAM),有时还会有电可擦除只读存储器(EEPROM)大多情况下,这些存储器位于芯片内部,实际的存储器接口细节对软件开发人员是不可见的因此,软件开发人员只需了解程序存储器和 SRAM的地址和大小即可。
Cortex-M处理器的4GB地址空间被分为了多个存储器区域,如图所示。区域根据各自典型用法进行划分,它们主要用于:
同几乎所有的处理器架构一样,Cortex-M处理器在运行时需要栈存储和栈指针(R13)在栈这种存储器使用机制中,存储器的一部分可被用作后进先出的数据存储缓冲(LIFO)。ARM处理器将系统主存储器用于栈空间操作,且使用PUSH 指令往栈中存储数据以及 POP 指从栈中提取数据。每次 PUSH和POP操作后,当前使用的栈指针都会自动调整。
栈可用于:
Cortex-M处理器使用的栈模型被称作“满递减”。处理器启动后,SP 被设置为栈存储空间最后的位置。对于每次 PUSH 操作,处理器首先减小SP 的值,然后将数据存储在 SP 指向的存储器位置。在操作期间,SP 指向上一次数据被存储在栈中的位置,如图所示。
对于POP操作,SP指向的存储器位置的数据被读出,然后 SP的数值会自动减小PUSH和POP指令最常见的用法为,在执行函数或子程序调用时保存寄存器组中的内容。在函数调用开始时,有些寄存器的内容可以通过 PUSH 指保存在栈中,而后在函数调用结束时通过POP恢复为它们的初始值。
之前我们讲过Cortex-M在物理上有两个栈指针:主栈指针(MSP)和进程栈指针(PSP)
并且由CONTROL寄存器第二位SPSEL决定使用何种指针。
对于不具有OS的简单应用,线程模式和处理模式都可以只使用MSP如图所示。在异常事件产生后,处理器在进入中断服务程序(ISR)前会首先将多个寄存器压入栈中,这种寄存器状态保存操作被称作“压栈”而在ISR 结束时,这些寄存器又会被恢复到寄存器组中,这种操作则被称作“出栈”
我们可以看出SPSEL=0时,只使用了MSP
若嵌人式系统中包含嵌入式OS,它们通常会将应用任务和内核所用的栈空间分离开来因此,PSP就会被用到,而且在异常入口和异常退出时会发生 SP 切换,如图所示。注意,自动“压栈”和“出栈”阶段使用 PSP,利用这种分离的栈结构,栈或应用任务的错误不会影响OS使用的栈,同时也简化了OS设计,提高了上下文切换的速度。此时SPSEL=1。
MPU在Cortex-M3和Cortex-M4处理器中是可选的,因此并不是所有的 Cortex-M3或Cortex-M4微控制器都有 MPU特性。多数应用不会用到MPU,因此可以忽略。在需要高可靠性的嵌入式系中,MPU 可以通过定义特权和非特权访问权限,来保护存储器区域MPU是可编程的而且Cortex-M3和 Cortex-M4处理器中的MPU支持8个可编程区域。MPU可以有多种用法。有些情况下,MPU由嵌入式OS控制,每个任务都被配置了存储器访问权限:而对于其他情况,MPU被配置为只保护某一特定存储器区域,如将某存储器区域设置为只读。
并且MPU可以提高嵌入式系统的健壮性,可以使系统更加安全
异常是会改变程序流的事件,当其产生时,处理器会暂停当前正在执行的任务,转而执行段被称作异常处理的程序。在异常处理执行完后,处理器会继续正常地程序执行。对于ARM架构,中断就是异常的一种,它一般由硬件(如外设和外部输人引脚)产生,有时也可以由软件触发中断的异常处理也被称作中断服务程序(ISR)。
当外设或硬件需要处理器的服务时,一般会出现下面的流程:
由于异常和中断在Cortex-M架构中占有非常重要的地位,这里只简单介绍,到时会出专门来讲解。
SCB为处理器的一部分,位于NVIC中。SCB包含寄存器,用于:
Cortex-M处理器提供两个接口:调试和追踪
利用调试接口可以通过调试器连接Cortex-M处理器上控制调试特性和访问片上的存储器空间。常用的接口协议有JATG和SWD。其中SWD由ARM开发只需要两个引脚就可以实现需要利用四五个引脚的JATG协议同样的效果。
对于典型的Cortex-M微控制器,复位类型有三种:
在系统调试或处理器复位操作过程中,Cortex-M3或Cortex-M4处理器中的调试部件不会复位,这样可以保持调试主机(如运行在计算机上的调试器软件)和微控制器间的连接。调试主机可以通过系统控制块(SCB)中的寄存器产生系统复位或处理器复位。
复位流程
我们之前说过由于Cortex-M3和Cortex-M4中的栈操作基于满递减的栈(SP在存储前减小)SP的初始值应该被设置为栈区域顶部的第一个位置。
例如,若存储器区域0x20007C00~0x20007FFF(1KB),如图所示,初始的栈指针就应该为0x20008000。对于Cortex-M处理器,向量表中向量地址的最低位应该为 1,以表示它们为 Thumb代码。正是由于这个原因,对于图中的例子,复位向量为0x101,而启动代码从0x100处开始在取出复位向量后,Cortex-M处理器就可以从复位向量地址处执行程序,并开始正常操作。
能看到这也没啥好总结的了,架构本身就是枯燥无味的东西,能看完的就去休息吧。
要是觉得不错或者对文章有修改意见,欢迎留言和转发。