文章目录
- 三、Cortex-M3基础
- 3.1 寄存器
- 3.1.1 通用目的寄存器 R0~R7
- 3.1.2 通用目的寄存器 R8~R12
- 3.1.3 栈指针 R13
- 3.1.4 链接寄存器 R14
- 3.1.5 程序计数器 R15
- 3.2 特殊寄存器
- 3.2.1 程序状态寄存器
- 3.2.2 PRIMASK、FAULTMASK和BASEPRI寄存器
- 3.2.3 控制寄存器
- 3.3 操作模式
- 3.4 异常和中断
- 3.5 向量表
- 3.6 栈存储操作
- 3.6.1 栈的基本操作
- 3.6.2 Cortex-M3 栈的应用
- 3.6.3 Cortex-M3 的双栈模型
- 3.7 复位流程
三、Cortex-M3基础
3.1 寄存器
前面已经介绍过了,Cortex-M3处理器具有寄存器R0~R15以及多个特殊寄存器。 R0R12为通用目的寄存器,不过有些16位Thumb指令只能访问R0R7(低寄存器),而 32位Thumb-2指令则可以访问所有的寄存器,特殊寄存器具有预定义的功能,并且只能通过特殊寄存器访问指令操作。
3.1.1 通用目的寄存器 R0~R7
通用目的寄存器R0~R7也称作低寄存器,它们可以通过所有的16位Thumb指令和 32位Thumb-2指令访问。这些寄存器都是32位宽的,并且复位值为不可预测的。
3.1.2 通用目的寄存器 R8~R12
寄存器R8~R12也称作高寄存器,它们可以通过所有的Thumb-2指令访问,而有些 16位的Thumb指令则不可以。这些寄存器都是32位宽,并且复位值是不可预测的。
3.1.3 栈指针 R13
R13为栈指针(SP),Cortex-M3中存在两个SP,利用这种结构,我们可以设置两个独立的栈指针。在使用寄存器名为R13时,只能访问当前的SP,要访问另一个的话,只能通过特殊指令:
- 从通用目的寄存器到特殊寄存器 - MSR
- 从特殊寄存器到通用目的寄存器 - MRS
这两个栈指针为:
- 主栈指针(MSP):在ARM文献中也叫
SP_main
,这是默认的SP,用于操作系统内核、异常处理和所有需要特权访问的应用程序代码。 - 进程栈指针(PSP):在ARM文献中也叫
SP_process
,用于基本的应用程序代码(未运行异常处理时)。
使用两个SP是没有必要的,简单的应用程序可以完全依赖MSP。SP用于像PUSH和POP之类的栈存储处理。
对于Cortex-M3,访问栈空间的指令为PUSH和POP,汇编语法如下(每个分号[;]之后的文字为注释)。
PUSH {R0}
步骤1:R13 = R13 - 4
步骤2:存储器[R13] = R0
POP {R0}
步骤1:R0 = 存储器[R13]
步骤2:R13 = R13 + 4
Cortex-M3使用满递减的栈处理(本章的“栈存储操作”中有对这方面的详细介绍),因此,当新的数据存入栈后,SP减小。PUSH和POP通常用于在子例程开始时将寄存器内容保存至栈空间,之后会在子例程结束后将寄存器内容从栈中恢复。PUSH和POP可以在一条指令中操作多个寄存器:
subroutine_1
PUSH {R0 - R7, R12, R14}
...
POP {R0 - R7, R12, R14}
BX R14
在程序代码中,可以使用SP(栈指针)代替R13。这就意味着,在程序代码内部,MSP和PSP都可以被称作R13/SP。不过,你可以使用特殊寄存器访问指令(MRS/MSR)来操作特定的SP。
由于寄存器的PUSH和POP操作总是字对齐(0x4
,0x8
),故SP的第0位和第1位被硬连接为0,并且读出总是0。
3.1.4 链接寄存器 R14
R14为链接寄存器(LR),在汇编程序中,可以写作R14或LR。LR用于子例程或函数调用时保存返回地址,例如,在使用跳转链接(BL)指令时:
尽管PC的第0位总是为0(由于指令都是字对齐或者半字对齐的),LR的第0位是可读可写的。这是因为在Thumb指令集中,第0位通常用于指明ARM/Thumb状态,要将 Cortex-M3上实现的Thumb-2程序运行在其他支持Thumb-2技术的ARM处理器上,这个最低位(LSB)需要为可读可写的。
3.1.5 程序计数器 R15
R15就是PC,在汇编代码中可以使用R15或PC进行访问。由于Cortex-M3处理器的流水线特性,在读这个寄存器时,你会发现读出值和正在执行的指令位置不同,通常差4,例如:
对PC的写操作会引起跳转(但LR不会更新),由于指令地址必须是半字对齐的,PC读出值的LSB(最低位)总是0。不过,对于跳转,不管是通过写PC还是使用跳转指令,目标地址的LSB都应该置1,以指明当前处于Thumb状态;如果为0的话,处理器会试图切换至ARM状态,而Cortex-M3的这一操作会导致错误异常。
3.2 特殊寄存器
Cortex-M3处理器具有多个特殊寄存器:
- 程序状态寄存器(PSR);
- 中断屏蔽寄存器(PRIMASK、FAULTMASK、BASEPRI);
- 控制寄存器(CONTROL)。
特殊寄存器只能通过指令MSR
和MRS
访问,它们没有存储器地址:
MSR <special_reg> <reg>
:写入特殊寄存器。MRS <reg> <special_reg>
:读取特殊寄存器。
3.2.1 程序状态寄存器
PSR分为三个状态寄存器:
- 应用程序状态寄存器 -
APSR
- 中断程序状态寄存器 -
IPSR
(只读) - 执行程序状态寄存器 -
EPSR
(只读)
这三个PSR可以通过特殊寄存器访问指令MSR
和MRS
进行整体操作或者分开操作,整体操作时使用寄存器名xPSR
。
如果将这个寄存器同ARM7的当前程序状态寄存器(CPSR)相比,可能会发现ARM7中用的某些位域已经不存在了。由于Cortex-M3不具有ARM7定义的操作模式,模式(M)位就去掉了;Tumb(T)位变为了第24位;中断状态(I和F)位则被新的中断寄存器(PRIMASK)代替,这些寄存器同PSR是相互独立的。为了便于比较,传统ARM处理器的 CPSR寄存器如图3.5所示。
3.2.2 PRIMASK、FAULTMASK和BASEPRI寄存器
PRIMASK、FAULTMASK和BASEPRI寄存器用于禁止异常。
寄存器名 | 描述 |
---|---|
PRIMASK | 寄存器中仅有1位,在其置位时,允许不可屏蔽中断和硬件错误异常,其他所有中断和异常都会被屏蔽;默认为0,不屏蔽中断。 |
FAULTMASK | 寄存器中仅有1位,在其置位时,允许不可屏蔽中断NMI,其他所有中断和硬件错误处理异常都会被屏蔽;默认为0,不屏蔽中断。 |
BASEPRI | 寄存器中最多8位(取决于优先级的实际位宽),定义了屏蔽优先级。在其置位时,相同或者更低等级的所有中断都被禁止,更高优先级的中断仍可执行。如果设置为0,则屏蔽功能禁止(默认)。 |
在时间敏感的任务中需要暂时禁止中断时,可以使用PRIMASK和BASEPRI寄存器。当一个任务崩溃时,OS可以使用FAULTMASK暂时禁止错误处理。在这种情况下,任务崩溃时可能会产生多个不同错误。内核开始清理操作时,它也许不想被崩溃进程引起的其他错误打断,因此,利用FAULTMASK,OS内核就获得了处理错误状态的时间。
3.2.3 控制寄存器
控制寄存器用于定义特权等级和SP的选择,该寄存器为2位宽。
- CONTROL[1]
对于Cortex-M3,CONTROL[1]位在处理模式时总是0(使用MSP),不过在线程或者基本等级中,它可以是0或者1。
该位只有在内核处于特权线程下才可写,在其他模式下,该位是不允许进行写操作的。除了写这个寄存器外,也可以在异常返回时修改LR的第2位来修改这一位。
- CONTROL[0]
CONTROL[0]位只有在特权等级下可写,一旦进入用户等级,要想切换回特权状态,只能触发一次中断并且在异常处理中进行修改。
3.3 操作模式
当处理器运行在线程模式中时,它可以处在特权或用户等级,不过处理模式只能位于特权等级。当处理器退出复位时,它处于线程模式并且具有特权访问权限。
在用户访问等级(线程模式),对系统控制空间(SCS,存储器空间的一部分,用于配置寄存器和调试部件)的访问是不允许的。另外,访问特殊寄存器的指令(如MSR,访问APSR时除外)也不可以使用。如果用户访问等级的程序要访问SCS或特殊寄存器,错误异常就会产生。
处在特权访问等级的软件可以通过控制寄存器将程序切换至用户访问等级。异常发生时,处理器总会切换至特权状态,并且在退出异常处理时返回到之前的状态。用户程序无法直接通过写控制寄存器切回特权状态,在返回线程模式时,它必须通过设置控制寄存器的异常处理才可以切换至特权访问等级(见图3.7)。
对特权和用户访问等级的支持提供了一个更加安全和健壮的架构,例如,当用户程序错误时,它不会破坏嵌套向量中断控制器(NVIC)中的控制寄存器。另外,如果存储器保护单元(MPU)存在的话,用户程序对特权进程使用的存储器区域的访问可能会被禁止。
在简单的应用中,无须区分特权和用户访问等级。在这种情况下,也无须使用用户访问等级以及设置控制寄存器。
你可以将用户应用程序的栈同内核栈相分离,这样可以避免用户程序栈的错误操作引起的系统崩溃。根据这种设计,用户程序(运行在线程模式)使用PSP,而异常处理则使用 MSP。SP在进入或离开异常处理时自动切换(见3.6.3节),第8章有这个方面的详细介绍。
控制寄存器定义了处理器的模式和访问等级,当控制寄存器的第0位为0时,异常发生会引起处理器模式的改变(见图3.8和图3.9)。
当控制寄存器的第0位为1时(运行用户应用程序的线程),异常发生会引起处理器模式和访问等级的改变(见图3.10)。
只有在特权等级中,控制寄存器的第0位才是可编程的(见图2.5)。用户等级的程序要想切换至特权状态,它必须发起一个中断(例如,请求管理调用[SVC])并在中断处理中修改CONTROL[0]。
3.4 异常和中断
Cortex-M3支持多个异常,其中包括固定数量的系统异常和通常被称作IRQ的多个中断。根据个体设计的不同,Cortex-M3微控制器中断输人的数量也有所不同。外设产生的中断,除了系统节拍定时器,也被连接至中断输入信号上。中断输入的数量一般为16或 32,不过,有些微控制器的中断输入可能会更多(或更少)。
除了中断输入之外,处理器还有一个不可屏械中断(NMI)输入信号,NMI的实际使用情况取决于微控制器或片上系统(SoC)的设计。多数情况下,NMI可以连接至看门狗时钟,或者电压监控模块,这样在电压降到一定程度时处理器会收到警告。NMI异常随时可以激活,甚至是在内核刚刚退出复位后。
Cortex-M3支持的异常如表3.4所示。其中多个系统异常为错误处理异常,它们可由许多错误条件触发。NVIC也提供了多个错误状态寄存器,这样异常处理可以根据这些寄存器确定异常的原因。
如果要了解Cortex-M.3异常处理的细节,可以参考第7~9章的内容。
3.5 向量表
当Cortex-M3的异常发生并被处理器接受时,对应的异常处理就会执行。为了确定异常处理的起始地址,处理器使用了一种向量表机制。向量表是系统存储器中的字数据数组,每个元素代表了一种异常类型的起始地址。向量表的位置是可以重置的,该位置由NVIC中的重定位寄存器决定(见表3.5)。复位后,该重定位寄存器被置为0,因此,向量表在复位后位于地址0x0处。
例如,若复位的异常类型为1,那么复位向量的地址为1×4(每个字为4字节),也就是 0x00000004,NM1向量(类型2)位于2×4=0x00000008位置。地址0x00000000用于存放 MSP的初始值。
每个异常类型的LSB表示异常是否允许在Thumb状态中执行,由于Cortex-M3只支持Thumb指令,所有的异常向量的LSB都应该置1。
3.6 栈存储操作
对于Cortex-M3,栈的PUSH和POP除了可以被普通软件控制外,还可在进入或退出异常/中断处理时自动执行。本节中,我们来看一下软件栈操作(异常处理期间的栈操作在第9章中介绍)。
3.6.1 栈的基本操作
般来说,栈操作也就是存储器的写或读操作,只是地址被指定为SP。寄存器中的数据由PUSH操作存人栈存储中,而且稍后可以由POP操作恢复到寄存器中。在PUSH和 POP期间,SP的内容自动调整,因此,多次PUSH不会清除之前的压栈数据。
栈的功能为将寄存器内容存到存储器中,以便处理任务结束后它们还可以被恢复。例如,对于每次存储(PUSH),都必须有对应的读(POP),而且POP操作的地址应该同PUSH操作的地址相匹配。使用PUSH/POP指令时,SP自动增加/减小。
当程序控制返回到主程序时,R0~R2的内容同之前的一致,应该注意PUSH和POP的顺序:POP应是PUSH的反顺序。
由于PUSH和POP指令允许多次存储和加载,因此这些操作可以简化。在这种情况下,处理器会将寄存器POP的顺序自动反过来(见图3.12)。
RETURN可以同POP操作合并在一起,要实现这个目的,可以将LR存入栈中,并且在子例程结束时将其送回PC(见图3.13)。
3.6.2 Cortex-M3 栈的应用
Cortex-M3使用了一种满递减的栈操作模型,SP指向压入栈存储的最后一个数据,SP在新的PUSH操作前减小。图3.14为指令PUSH{R0}执行的例子。
对于POP操作,数据被从SP指向的位置中读出,然后SP增加。存储器位置的数据不会变化,但下次PUSH操作发生时则会被覆盖(见图3.15)。
由于每次PUSH/POP操作传输4字节的数据(每个寄存器包含1个字,也就是4字节),SP每次会增加/减小4字节,如果多于1个寄存器需要压栈或出栈则是4的倍数。
在Cortex-M3中,R13被定义为SP。当中断发生时,多个寄存器会自动压栈,在这个过程中,R13会被用作SP。类似地,在退出中断处理时,压栈的寄存器会自动地恢复/出栈,SP的值也会得到调整。
3.6.3 Cortex-M3 的双栈模型
之前已经提到Cortex-M3具有两个SP:MSP和PSP。实际使用哪个SP由控制寄存器的第1位控制(下面所说的CONTROL[1])。
当CONTROL[1]为0时,线程模式和处理模式都会使用MSP(见图3.16)。按照这种设计,主程序和异常处理共用相同的栈存储空间,这也是上电后的默认设置。
当CONTROL[1]为1时,线程模式使用PSP(见图3.17)。按照这种设计,主程序和异常处理可以使用独立的栈存储区域。这样用户程序的栈出现错误时,就不会破坏OS使用的栈了(假定用户程序只运行在线程模式,OS内核运行在处理模式)。
在这种情况下,应该注意的是,自动压栈和出栈机制将会使用PSP,不过异常处理中的栈操作会使用MSP。
也可以直接对PSP和MSP进行读/写操作,而无须考虑R13代表的含义。假定当前处于特权等级,你可以这样访问MSP和PSP的值:
一般来说,由于栈存储可能会用于存储局部变量,我们不建议在C函数中改变当前选定SP的值。要用汇编访问SP,可以使用MSR和MRS指令:
通过MRS指令读出PSP的值后,OS可以读出用户应用程序栈中的数据(如SVC操作前的寄存器内容)。另外,OS可以改变PSP指针数值,比如多任务系统上下文切换期间。
3.7 复位流程
在退出复位后,处理器会从存储器中读取两个字:
- 地址
0x00000000
:R13的初始值(SP)。 - 地址
0x00000004
:复位向量(程序执行的起始地址,LSB应该置1表示Thumb状态)。
这点同传统的ARM处理器不同,之前的ARM处理器从0x0地址开始执行程序,另外,之前的ARM设备中向量表为指令(你必须在此处放入一个跳转指令,异常处理则可以放在另外一个位置)。
对于Cortex-M3,MSP的初始值位于存储器映射的开始处,后面紧接着的是向量表,也就是向量的地址值(在程序执行过程中,向量表稍后可以被重新分配到另外一个地址)。另外,向量表的内容为地址值,而非跳转指令。向量表中的第一个向量(异常类型1)为复位向量,这是在处理器复位后读取的第二块数据。
由于Cortex-M3的栈操作为满递减的(存储前SP减小),SP的初始值应该置为栈顶后的第一个存储器地址。例如,若栈存储区域的范周为0x20007C00到0x20007FFF(1KB),栈的初始值应该置为0x20008000。
向量表在SP的初始值之后,第一个向量为复位向量。应该注意的是,在Cortex-M3中,向量表中的向量地址的最低位应置1,以表明它们为Thumb代码。由于这个原因,前面例子的复位向量为0x101,而启动代码是从0x100地址开始的(见图3.19)。取出复位向量后,Cortex-M3可以从复位向量地址处执行程序,并且开始正常操作。SP需要被初始化,这是因为复位后就可能会发生一些异常(如NMI),这些异常可能是需要栈存储的。
不同的开发工具在指定SP的初始值和复位向量时的方式可能不同,若要了解这方面更多的信息,最好参考开发工具提供的工程实例。第10章和第20章提供了ARM工具的一些例子,GNU工具链的例子则在第19章。