一、编程模型
1.1 操作模式和状态
cortex-m3和cortex-m4处理器有两种操作状态和两个模式。另外,处理器还可以区分特权和非特权访问等级,如下图所示。特权访问等级可以访问处理器中的所有资源,而非特权访问等级则意味着有些存储器区域是不能访问的,有些操作也是无法使用的。
操作状态:
调试状态:当处理器被暂停后(例如,通过调试器或触发断点后),就会进入调试状态,并停止指令执行。
Thumb状态:若处理器在执行程序代码(Thumb指令),它就会处于Thumb状态。cortex-m不支持ARM指令集,因此不存在ARM状态。
操作模式:
处理模式:执行中断服务程序等异常操作。在处理模式下,处理器总是具有特权访问等级。
线程模式:在执行普通的应用程序代码时,处理器可以处于特权访问等级,也可以处于非特权访问等级。实际的访问等级由特殊寄存器CONTROL控制。
软件可以将处理器从特权线程模式切换到非特权线程模式,但无法将自身从非特权切换到特权模式,非要进行这种切换的话,处理器必须得借助异常机制才行。除了存储器访问权限和几个指令的差异,特权访问等级和非特权访问等级的编程模型基本上是一样的。需要注意的是,几乎所有的NVIC寄存器都只支持特权访问。同样地,线程模式和处理模式的编程模型也是类似。不过,线程模式可以切换使用独立的影子栈指针(SP)。这种设计使得应用任务的栈空间可以和OS内核的相互独立,因此提高了系统的可靠性。
cortex-m处理器在启动后默认处于特权线程模式以及Thumb状态。对于许多简单的应用,非特权线程模式和影子SP根本用不上。cortex-m0处理器不支持非特权线程模式,m0+上是可选的。
调试状态仅用于调试操作,可以通过两种方式进入调试状态:调试器发起的暂停请求,或处理器中的调试部件产生的调试事件。在此状态下,调试器可以访问或修改处理器寄存器的数值。无论在Thumb状态还是调试状态,调试器都可以访问处理器内外的外设等系统存储器。
1.2寄存器
cortex-m3和cortex-m4 处理器的寄存器组中有16个寄存器,其中13个为32位通用目的寄存器,其他3个则有特殊用途,如下图所示:
R0-R12:为通用目的寄存器,前8个被称为低寄存器。由于指令中可用的空间有限,许多16位指令只能访问低寄存器。高寄存器则可以用于32位指令和几个16位指令,如MOV(move)。R0~R12的初始值是未定义的。
R13:栈指针(SP),可通过PUSH和POP操作实现栈存储的访问。物理上存在两个栈指针,主栈指针(MSP或SP_main)为默认的栈指针,在复位后或处理器处于处理模式时,其会被处理器选择使用。另外一个栈指针名为进程栈指针(PSP或SP_process),其只能用于线程模式。栈指针的选择由特殊寄存器CONTROL决定,对于一般的程序,这两个寄存器只会有一个可见。
MSP和PSP都是32位的,不过栈指针最低两位总是为0,对这两位的写操作不起作用,对于cortex-m处理器,PUSH和POP总是32位的,栈操作的地址也必须对齐到32位的字边界上。PSP的初始值未定义,MSP的初始值则需要在复位流程中从存储器的第一个字中取出。
R14:链接寄存器(LR),用于函数或子程序调用时返回地址的保存。在函数或字程序结束时,程序控制可以通过将LR的数值加载程序计数器(PC)中返回调用程序处并继续执行。当执行了函数或子程序调用后,LR的数值会自动更新。若某函数需要调用另外一个函数或子程序,则需要首先将LR的数值保存在栈中,否则,当执行了函数调用后,LR的当前值会丢失。在异常处理期间,LR也会被自动更新为特殊的EXC_RETURN(异常返回)数值,之后该数值会在异常处理结束时触发异常返回。尽管cortex-m处理器中的返回地址数值总是偶数(由于指令会对齐到半字地址上,因此第0位为0),LR的第0位为可读可写的,有些跳转/调用操作需要将LR的第0位置1以表示Thumb状态。
R15:程序计数器(PC),可读可写,读操作返回当前指令地址+4(由于设计的流水线特性及同ARM7TDMI处理器兼容的需要).写PC会引起跳转操作。由于指令必须要对齐到半字或字地址,PC的最低位(LSB)为0。不过,在使用一些跳转/读存储器指令更新PC时,需要将新PC值得LSB置1以表示Thumb状态,否则就会由于试图使用不支持ARM指令(如ARM7TDMI中的32位ARM指令)而触发错误异常。对于高级编程语言,编译器会自动将跳转目标的LSB置位。
1.3特殊寄存器
除了寄存器组中的寄存器外,处理器中还存在多个特殊寄存器。特殊寄存器未经过存储器映射,可以使用MSR和MRS等特殊寄存器访问指令来进行访问。CMSIS-CORE也提供了几个用于访问特殊寄存器的C函数。
MRS , 将特殊寄存器读入寄存器
MSR , 写入特殊寄存器
程序状态寄存器
程序状态寄存器包含以下3个状态寄存器.这三个寄存器可以通过一个组合寄存器访问即XPSR。
应用PSR(APSR)
执行PSR(EPSR)
中断PSR(IPSR)
组合xpr内容如下:
N:负标志
Z:零标志
C:进位(或者非借位)标志
V:溢出标志
Q:饱和标志
GE[3:0]:大于或等于标志,对应每个字节通路
ICI/IT:中断继续指令(ICI)位,IF-THEN指令状态位用于条件执行。
T:Thumb状态,总是1,清除此位会引起错误异常
异常编号:表示处理器正在处理的异常。
PRIMASK、FAULTMASK 和 BASEPRI寄存器
PRIMASK、FAULTMASK和BASEPRI寄存器都用于异常或中断屏蔽,每个异常(包括中断)都具有一个优先等级,数值小的优先级高。这些特殊寄存器可基于优先等级屏蔽异常,只有在特权访问等级才可以对它们进行操作(非特权状态下写操作会被忽略,读返回0)。它们默认全部为0,也就是屏蔽不起作用。
PRIMASK寄存器为1位宽的中断屏蔽寄存器。在置位时,它会阻止NMI和HardFault异常之外的所有异常。实际上,它是将当前异常优先级提升为0,这也是可编程异常/中断的最高优先级。
FAULTMASK和PRIMASK类似,不过它还能屏蔽HardFault异常,实际上将异常优先级提升到了-1.
BASEPRI 会根据优先等级屏蔽异常或中断。
CMSIS-CORE提供了多个C函数用于访问这些特殊寄存器(只能在特权等级下访问)。
x= _get_BASEPRI() //读BASEPRI寄存器x= _get_PRIMASK() //读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寄存器读入R0MRS r0 PRIMASK //将PRIMASK寄存器读入R0MRS r0 FAULTMSK //将FAULTMSK寄存器读入R0MSR BASEPRI RO //将R0值写入BASEPRI寄存器MSR PRIMASK RO //将R0值写入PRIMASK寄存器MSR FAULTMSK RO //将R0值写入FAULTMSK寄存器
另外,利用修改处理器状态CPS指令,可以很方便的设置或清除PRIMASK和FAULTMASK的数值
CPSIE i //使能中断,清除PRIMASKCPSID i //禁止中断,设置PRIMASKCPSIE f //使能中断,清除FAULTMASKCPSID f //禁止中断,设置FAULTMASK
CONTROL寄存器
control寄存器定义如下图所示
nPRIV:定义线程模式中的特权等级,为0时(默认),处理器会处于线程模式中的特权等级,而当其为1时,则处于线程模式中的非特权等级。该位在cortex-M0中不存在,cortex-M0+中是可选的。
SPSEL:定义栈指针的选择,为0时(默认),线程模式使用主栈指针MSP, 当为1时,线程模式使用进程栈指针;而在处理模式,该位始终为0且对其写操作会被忽略。
FPCA:浮点上下文活跃,只存在于具有浮点单元的CORTEX-M4中。异常处理机制使用该位确定异常产生时浮点单元中的寄存器是否需要保存。当为1时,当前的上下文使用浮点指令,因此需要保存浮点寄存器。FPCA位会在执行浮点指令时自动置位,在异常入口处被硬件清除。
复位后,CONTROL寄存器默认为0,这也意味着处理器此时处于线程模式、具有特权访问权限以及使用主栈指针。通过写CONTROL寄存器,特权线程模式的程序可以切换栈指针的选择或者进入非特权访问等级。不过nPRIV置位后,运行在线程模式的程序就不能访问CONTROL寄存器了。
运行在非特权等级的程序无法再切换回特权访问等级,这样就提供了一个基本的安全模型。例如,嵌入式系统中可能会具有运行在非特权访问等级的不受信任的应用,这些应用的访问权限就需要受到限制,以免不可靠的程序引起整个系统的崩溃。若有必要将处理器在线程模式切换回特权访问等级,则需要使用异常机制。在异常处理期间,处理程序可以清除nPRIV位,如下图所示,在返回线程模式后,处理器就会进入特权访问等级。
若使用嵌入式OS,每次上下文切换时都可以重新编程CONTROL寄存器,以满足应用间不同的特权访问等级需要。
nPRIV和SPSEL的设置共有4种组合方式,其中3种在实际应用中较为常见。
nPRIV | SPSEL | 应用场景 |
---|---|---|
0 | 0 | 简单应用,整个应用运行在特权访问等级,主程序和中断处理只会使用一个栈,即主栈 |
0 | 1 | 具有嵌入式OS的应用,当前执行的任务运行在特权线程模式,当前任务选择使用进程栈指针(PSP),OS内核以及异常处理使用MSP。 |
1 | 1 | 具有嵌入式OS的应用,当前执行的任务运行在非特权线程模式,当前任务选择使用进程栈指针(PSP),OS内核以及异常处理使用MSP。 |
1 | 0 | 线程模式运行在非特权访问等级,且使用MSP,处理模式中可见,而用户任务则一般无法使用,这是因为在多数嵌入式IS中,应用任务的栈和OS内核以及异常处理使用的栈相互独立。 |
control寄存器访问方法:
x = _get_CONTROL(); //读取CONTROL寄存器的当前值_set_CONTROL(X); //设置CONTROL寄存器的数值xMRS r0,CONTROL //将CONTROL寄存器读入R0MSR CONTROL, r0 //将r0写入CONTROL寄存器
修改CONTROL寄存器需要修改以下两点:
①对于具有浮点单元(FPU)的M4处理器,或具有FPU的ARMv7-M处理器,由于浮点单元的存在,FPCA位会自动置位。若程序中包含浮点运行但FPCA位被意外清除,而且接下来产生一个中断,那么浮点单元寄存器中的数据将不能再异常入口流程保存,且可能会被中断处理覆盖。在这种情况下,继续执行被中断的任务时,程序可能无法继续正确地处理。
②在修改了CONTROL寄存器后,从架构来看,应该使用指令同步屏障(ISB)指令,以确保本次修改对接下来的代码能起作用,由于M3 M4 M0 M0+ M1流水线非常简单,不使用ISB也不会引起什么问题。
二、应用程序状态寄存器
2.1 整数状态标志
整数状态标志和其他许多处理器架构中的ALU状态标志类似,它们受普通数据处理指令的影响,在控制条件跳转和条件执行时非常有用。cortex-m处理器中共存在4个整数标志,如下所示:
标志 | 描述 |
---|---|
N | 设置为所执行指令得到结果的bit[31],为1时,结果为负值;为0时,结果为正或0。 |
Z | 若所执行指令的结果为0则设置为0,若比较指令执行后发现两个数值相等,该位也会置1。 |
C | 结果的进位标志,对于无符号加法,若产生无符号溢出则该位被置为1;对于无符号减法,该位同借位输出状态相反,移位和循环移位也会影响该位 |
V | 结果溢出。对于有符号加法或减法,若产生有符号溢出则该位会被置1 |
对于ARMv7-M和ARMv7E-M架构,多数16位指令会影响这4个ALU标志。对于多数32位指令,指令编码中的一个位定义了是否应该更新APSR标志。注意,部分指令不会更新V和C标志,例如MULS指令只会修改N和Z标志。
2.2 Q状态标志
饱和运算标志
2.3 GE位
三、存储器系统
3.1存储器系统特性
cortex-m3和cortex-m4处理器具有以下存储系统特性:
①4GB线性地址空间。通过32位寻址,ARM处理器可以访问多达4GB的存储器空间
②架构定义的存储器映射。4GB的存储器空间被划分为多个区域,用于预定义的存储器和外设,以优化处理器设计的性能。
③支持小端和大端的存储器系统。
④位段访问(可选)当包含位段特性时,存储器映射的两个1MB区域可以通过两个位段区域进行寻址,这样可以实现对SRAM或外设地址空间中单独位的原子操作。
⑤写缓冲。若对可缓冲存储器区域的写传输需要花费几个周期,M3 M4处理器内的写缓冲可能会将本次传输缓存起来,处理器可以继续执行下一条指令,如果可能的话,这样可以提高程序的执行速度。
⑥存储器保护单元(MPU).mpu定义了各存储器区域的访问权限,且为可编程的。
⑦非对齐传输支持。ARMv7-M架构的所有处理器都支持费对齐传输。
3.2存储器映射
cortex-m处理器的4GB地址空间被分为了多个存储器区域,如下图所示,区域根据各自典型用法进行划分,它们主要用于:
程序代码访问(如CODE区域)
数据访问(如SRAM区域)
外设(如外设区域)
处理器的内部控制和调试部件(如私有外设总线)
3.3栈存储
同几乎所有的处理器架构一样,cortex-M处理器在运行时需要栈存储和栈指针(R13)。在栈这种存储器使用机制中,存储器的一部分可被用作后进先出的数据存储缓冲。ARM处理器将系统主存储器用于栈空间操作,且使用PUSH指令往栈中存储数据以及POP指令从栈中提取数据。每次PUSH 和POP操作后,当前使用的栈指针都会自动调整。
栈可用于:
①当正在执行的函数需要使用寄存器(寄存器组中)进行数据处理时,临时存储数据的初始值。这些数据在函数结束时可以被恢复出来,以免调用函数的程序丢失数据。
②往函数或子程序中的信息传递。
③用于存储局部变量。
④在中断等异常产生时保持处理器状态和寄存器数值。
cortex-m处理器使用的栈模型被称作为满递减。处理器启动后,SP被设置为栈存储空间最后的位置。对于每次PUSH操作,处理器首先减小SP的值,然后将数据存储在SP指向的存储器位置。在操作期间,SP指向上一次数据被存储在栈中的位置。对于POP操作,SP指向的存储器位置的数据被读出,然后SP的数值会自动增大。
应用场景1示例:
应用场景2示例:
应用场景3示例:
四、异常和中断
异常是会改变程序流的事件,当其产生时,处理器会暂停当前正在执行的任务,转而执行一段被称作异常处理的程序。在异常处理执行完后,处理器会继续正常地程序执行。对于ARM架构,中断是异常的一种,它一般由外设或外部输入产生,有时也可以由软件触发。中断的异常处理也被称作中断服务程序。
cortex-m处理器有多个异常源。
①NVIC处理异常。NVIC可以处理多个中断请求(IRQ)和一个不可屏蔽中断(NMI)请求,IRQ一般由片上外设或外部中断输入通过I/O端口产生,NMI可用于看门狗定时器或掉电检测。处理器内部也有systick定时器。
②处理器自身也是一个异常事件源,其中包括表示系统错误状态的错误事件以及软件产生、支持嵌入式OS操作的异常,这些异常如下表所示:
异常编号 | CMSIS中断编号 | 异常类型 | 优先级 | 功能 |
---|---|---|---|---|
1 | — | 复位 | -3(最高) | 复位 |
2 | -14 | NMI | -2 | 不可屏蔽中断 |
3 | -13 | 硬件错误 | -1 | 对于所有等级的错误,若相应的错误处理由于被禁止或被异常屏蔽阻止而未被激活,则会触发该异常 |
4 | -12 | MemManage错误 | 可设置 | 存储器管理错误,由MPU冲突或非法访问引起(如从不可执行区域取指) |
5 | -11 | 总线错误 | 可设置 | 从总线系统收到的错误响应,由指令预取终止和数据访问错误引发 |
6 | -10 | 使用错误 | 可设置 | 使用错误,典型原因为非法指令或非法的状态转换尝试(如在M3中试图切换至ARM状态) |
7~10 | — | — | — | 保留 |
11 | -5 | SVC | 可设置 | 通过SVC实现的请求管理调用 |
12 | -4 | 调试监控 | 可设置 | 调试监控,用于基于软件的调试 |
13 | — | — | — | 保留 |
14 | -2 | PendSV | 可设置 | 可挂起的系统服务请求 |
15 | -1 | SYSTICK | 可设置 | 系统节拍定时器 |
16~255 | 0~239 | IRQ | 可设置 | IRQ |
NVIC为cortex-m处理器的一部分,它是可编程的,且寄存器位于存储器映射的系统控制空间。NVIC处理异常和中断配置、优先级以及中断屏蔽,其具有以下特性:
灵活的异常和中断管理
支持嵌套异常/中断
向量化的异常/中断入口
中断屏蔽
当异常事件产生且被处理器内核接受后,相应的异常处理就会执行。要确定异常处理的起始地址,处理器利用了一种向量表机制。向量表为系统存储器内的字数据数组,每个元素都代表一个异常类型的起始地址。向量表是可以重定位的,重定位由NVIC中名为向量表偏移寄存器VTOR的可编程寄存器控制。复位后,VTOR默认为0,向量表则位于地址0处。
五、复位和复位流程
对于典型地CORTEX-M微控制器,复位类型共有三种
①上电复位:复位微控制器中的所有部分,其中包括处理器、调试支持部件和外设等
②系统复位:只会复位处理器和外设,不包括处理器的调试支持部件
③处理器复位:只复位处理器
在复位后以及处理器开始执行程序前,cortex-m处理器会从存储器中读出头两个字,如下图所示。向量表位于存储器的开头部分,它的头两个字为主栈指针MSP的初始值,以及代表复位处理起始地址的复位向量。处理器读出这两个字后,就会将这些数值赋给MSP和程序计数器PC.
MSP的设置是非常必要的,这是因为在复位的很短时间内有产生NMI或HardFault的可能,在异常处理前将处理器状态压栈时需要栈存储和MSP。
对于多数C开发环境,C启动代码会在进入主程序main之前更新MSP的值。通过这两次对栈的设置,具有外部存储器的微控制器可以将外部存储器用作栈。
由于M3 M4的栈操作基于满递减的栈(SP在存储前减小),SP的初始值应该被设置为栈区域顶部的第一个位置。例如,若存储器区域为0x20007C00 ~ 0X20007FFF,则初始的栈指针应该为0x20008000。
备注:参考ARM Cortex-M3与Cortex-M4权威指南