第二章 CortexM3/M4基础

第二章 CortexM3/M4基础


前言

上一章中我们讲解了如何在 STM32F407开发板上移植 UCOSII操作系统, 我们在移植操作系统的时候一定要对处理器的架构有一定的了解,本章就讲解一下 Cortex-M3/M4的基础知识,了解了处理器的基础知识以后就能够看懂移植过程的一些重要文件, 因为这些文件都是和处理器密切相关的,本章分为如下几个部分:

2.1 Cortex-M3/M4通用寄存器
2.2操作模式和特权级别
2.3 FPU单元
2.4堆栈
2.5 SVC和 PendSV异常


2.1 Cortex-M3/M4通用寄存器

2.1.1 通用寄存器

我们首先了解一下 M3/M4的寄存器,M4相对于 M3多了一个浮点单元 FPU,
其他的基本和 M3是一样的,以下内容参考自《ARM Cortex-M3权威指南》和《Cortex-M3与 M4权威指南》如我们所见, Cortex-M3/M4系列处理器拥有通用寄存器 :
R0‐ R15以及一些特殊功能寄存器。
R0‐ R12是最“通用目的”的,但是绝大多数的16位指令只能使用 R0‐R7(低组寄存器),
而 32位的 Thumb‐2指令则可以访问所有通用寄存器。
特殊功能寄存器有预定义的功能,而且必须通过专用的指令来访问,
Cortex-M3/M4的通用寄存器如图 2.1.1所示。
第二章  CortexM3/M4基础

1. 通用目的寄存器 R0-R12

(1)通用目的寄存器 R0-R7
R0‐R7也被称为低组寄存器。所有指令都能访问它们。它们的字长全是32位,复位后
的初始值是不可预料的。

(2)通用目的寄存器 R8-R12
R8‐R12:也被称为高组寄存器。这是因为只有很少的16位Thumb指令能访问它们。
32位的指令则不受限制。它们也是32位字长,且复位后的初始值是不可预料的。

1. 功能寄存器 R13-R15

(3)堆栈指针 R13
R13是堆栈指针。在 CM3/CM4 处理器内核*有两个堆栈指针,于是也就支持两个堆栈。当引用 R13(或写作 SP)时,你引用到的是当前正在使用的那一个,另一个必须用特殊的指令来访问( MRS,MSR指令)。这两个堆栈指针分别是:
–>主堆栈指针(MSP),或写作 SP_main。这是缺省的堆栈指针,它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
–>进程堆栈指针(PSP),或写作 SP_process。用于常规的应用程序代码(不处于异常服用例程中时)。要注意的是,并不是每个应用都必须用齐两个堆栈指针。简单的应用程序只使用 MSP就够了。堆栈指针用于访问堆栈,并且PUSH指令和POP指令默认使用SP。

(4) 连接寄存器 R14
R14是连接寄存器(LR) 在一个汇编程序中,你可以把它写作 bothLR 和 R14。
LR用于在调用子程序时存储返回地址。例如,当你在使用BL(分支并连接, Branch and Link)指令时,就自动填充 LR的值。尽管 PC的LSB总是0(因为代码至少是字对齐的), LR的LSB却是可读可写的。
这是历史遗留的产物。在以前,由位 0来指示 ARM/Thumb状态。因为其它有些ARM处理器支持 ARM和 Thumb状态并存,为了方便汇编程序移植, CM3/CM4需要允许 LSB可读可写。

5)程序计数器 R15
R15是程序计数器,在汇编代码中你也可以使用名字“PC”来访问它。因为 CM3/CM4内部使用了指令流水线,读PC时返回的值是当前指令的地址+4。比如说:
0x1000: MOV R0, PC ; R0 = 0x1004

如果向PC中写数据,就会引起一次程序的分支(但是不更新LR寄存器)。
CM3/CM4中的指令至少是半字对齐的,所以PC的LSB总是读回0。然而,在分支时,无论是直接
写 PC的值还是使用分支指令,都必须保证加载到 PC的数值是奇数(即 LSB=1),用以表明这是在 Thumb状态下执行。倘若写了 0,则视为企图转入 ARM模式,CM3将产生一个fault异常。


2.1.2 特殊功能寄存器组

Cortex-M3/M4 有一个特殊功能寄存器组,如图 2.1.2所示
第二章  CortexM3/M4基础
Cortex‐M3 /M4中的特殊功能寄存器包括:

状态字寄存器S(三合一)( PSRs或 xPSR)
中断屏蔽寄存器组( PRIMASK, FAULTMASK,以及 BASEPRI)
控制寄存器( CONTROL)

它们只能被专用的 MSR和 MRS指令访问,而且它们也没有存储器地址。

MRS <gp_reg>, <special_reg> ;读特殊功能寄存器的值到通用寄存器
MSR <special_reg> ,<gp_reg> ;写通用寄存器的值到特殊功能寄存器

1 状态字寄存器S(三合一)(xPSR):

应用程序 PSR( APSR)
中断号 PSR( IPSR)
执行 PSR( EPSR)
通过 M RS / M S R指令,这 3个 PSR s既可以单独访问,也可以组合访问( 2个组合, 3个组合都可以)。
当使用三合一的方式访问时,应使用名字“ xPSR”或者“ PSR”。这三个寄存器的各个位的含意如表2.1.1所示。
第二章  CortexM3/M4基础

2 中断屏蔽寄存器S

PRIMASK
FAULTMASK
BASEPRI

这三个寄存器很重要,这三个寄存器用于控制异常的使能和除能,这三个寄存器的介绍如表2.1.2所示

第二章  CortexM3/M4基础
对于时间‐关键任务而言, PRIMASK 和 BASEPRI对于暂时关闭中断是非常重要的。而FAULTMASK则可以被OS用于暂时关闭 fault处理机能,这种处理在某个任务崩溃时可能需要。因为在任务崩溃时,常常伴随着一大堆 faults。在系统料理“后事”时,通常不再需要响应这些 fault——人死帐清。FAULTMASK就是专门留给 OS用的。要访问 PRIMASK, FAULTMASK以及 BASEPRI,同样要使用 MRS/MSR指令,如:

	MRS  R0,BASEPRI   ;读取  BASEPRI到  R0中
	MRS  R0,FAULTMASK  ;同上
	MRS  R0,PRIMASK    ;同上
	
	MSR  BASEPRI,  R0  ;写入  R0到  BASEPRI中
	MSR  FAULTMASK,  R0  ;同上
	MSR  PRIMASK,  R0    ;同上

只有在特权级下,才允许访问这 3个寄存器,为了快速地开关中断,CM3/CM4还专门设置了一条 CPS指令,有4种用法,这四种用法非常重要,我们在移植UCOS操作系统的时候就是用这下面的方法来开关中断的。

	CPSID I ;PRIMASK=1,  ;关中断
	CPSIE I ;PRIMASK=0,  ;开中断
	CPSID F ;FAULTMASK=1, ;关异常
	CPSIE F ;FAULTMASK=0  ;开异常

3 控制寄存器(CONTROL)

CONTROL寄存器用于定义特权级别和堆栈指针的使用,CONTROL寄存器如表2.1.3所示,注
意CONTROL[2]只有Cortex-M4才有。
第二章  CortexM3/M4基础
CONTROL[2]
在Cortex-M4中有FPU单元,如果我们使用了FPU,那么在处理异常时就需要保存 FPU环境,此位用来指示是否需要保存浮点环境。

CONTROL[1]
在 Cortex‐M3的 handler模式中, CONTROL[1]总是 0。在线程模式中则可以为 0或1。
仅当处于特权级的线程模式下,此位才可写,其它场合下禁止写此位。改变处理器的模式也有其它的方式:在异常返回时,通过修改 LR的位 2,也能实现模式切换。

CONTROL[0]
仅当在特权级下操作时才允许写该位。一旦进入了用户级,唯一返回特权级的途径,就是触发一个(软)中断,再由服务例程改写该位。CONTROL寄存器也是通过 MRS和 MSR指令来操作的:

	MRS R0, CONTROL
	MSR CONTROL, R0

9)EXC_RETURN
在进入异常服务程序后, L R的值被自动更新为特殊的 EXC_RETURN,对于 Cortex-M4
来说这是一个高 27位全为1的值(M3是高28位都为 1)。
M4中[4:0]位有意义,在 M3中[3:0]有意义,并且和 M4中的相同,
EXC_RETURN位段如表 2.1.4所示

注意!EXC_RETURN的 bit4非常重要,我们可以根据此位的值来获知硬件会对哪些寄存器进
行自动压入栈和出栈处理,这个我们会在讲浮点寄存器和堆栈的时候详细讲解。
第二章  CortexM3/M4基础
第二章  CortexM3/M4基础

2.2操作模式和特权级别

Cortex-M3/CM4处理器支持两种处理器的操作模式,还支持两级特权操作。
两种操作模式分别为:处理者模式 (handler mode)和线程模式(thread mode)。
引入两个模式的本意,是用于区别普通应用程序的代码和异常服务例程的代码——包括中断服务例程的代码。Cortex-M3/M4的另一个侧面则是特权的分级——特权级和用户级。这可以提供一种存储器访问的保护机制,使得普通的用户程序代码不能意外地,甚至是恶意地执行涉及到要害的操作。处理器支持两种特权级,如表 2.2.1所示,这也是一个基本的安全模型。
第二章  CortexM3/M4基础
在 CM3/CM4运行主应用程序时(线程模式),既可以使用特权级,也可以使用用户级;
但是异常服务例程必须在特权级下执行。复位后,处理器默认进入线程模式,特权级访问。
在特权级下,程序可以访问所有范围的存储器(如果有 MPU,还要在 MPU规定的禁地之外),
并且可以执行所有指令。在特权级下的程序可以为所欲为,但也可能会把自己给玩进去——切换到用户级。一旦进入用户级,再想回来就得走“法律程序”了 ——用户级的程序不能简简单单地试图改写
CONTROL寄存器就回到特权级,事实上,从用户级到特权级的唯一途径就是异常:如果在程
序执行过程中触发了一个异常,处理器总是先切换入特权级,并且在异常服务例程执行完毕退
出时,返回先前的状态。


2.3 FPU单元

在 Coretex-M4处理器中有一个可选的单精度 FPU单元,我们开发板选用的 STM32F407就有 FPU单元,如果使能了FPU单元的话就可以使用它来对单精度浮点数进行计算双精度浮点数的计算仍然要使用到 C运行库。

2.3.1 FPU寄存器

FPU单元包含一系列的寄存器:

	CPACR寄存器,在  SCB块中
	浮点寄存器块,S0-S31
	FPU状态和控制寄存器:FPSCR
	其他的一些 FPU寄存器

1)CPACR寄存器
通过 CPACR寄存器来使能FPU,CPACR寄存器的地址为 0XE000ED88,我们也可以通过
“SCB->CPACR”来访问 CPACR寄存器,CPACR寄存器的 bit0-bit19和 bit24-bit31不允许使用,为保留位,其中[20:21]为 CP10,[22:23]为 CP11。我们通过设置 CP10和 CP11来开启 FPU,CP10和 CP11设置情况如表 2.2.1所示,注意 CP10和 CP11都为 2bit第二章  CortexM3/M4基础

默认情况下 CP10 和 CP11都为00,如果要使用 FPU的话需要软件设置CPACR来开启FPU,通过设置 CP10和 CP11都为 11来开启,实例代码如下:

SCB->CPACR|=0X00F00000; //使能 FPU

2)浮点寄存器块
浮点寄存器快包含 32个 32位的寄存器,这 32个寄存器可以两两组合成一个 64位的双精
度寄存器,如图 2.3.1所示。
第二章  CortexM3/M4基础

S0-S15是 caller-saved寄存器,如果一个应用 A调用了另外一个应用 B,那么应用 A在调
用 B之前一定要保存这些寄存器,因为在调用的时候这些寄存器会被改变。
S16-S31被称为 callee-saved寄存器,如果一个应用 A调用应用 B,而且 B需要大于 16个寄存器来做计算,那么应用 B就需要保存这些寄存器。并且在返回应用 A的时候必须恢复这些寄存器。

2.3.2 Lazy Stacking

对于 Cortex-M4来说 Lazy Stacking是一个重要的特性,在使用 FPU的情况下,不使用这
个特性会在异常处理的时候消耗 29个时钟周期,因为要将 25个寄存器压栈,以前只需要将 8
个压栈。如果使用 Lazy Stacking这个特性的话,那么在异常处理的时候只需要消耗 12个时钟
周期,默认情况下 Lazy Stacking是使能的可以看出如果在任务切换中使用 Cortex-M4的这个特性将会极大的提高任务切换的速度,我们在移植 UCOSII的时候就使用了这个特性。


2.4 堆栈

2.4.1 Cortex-M3/M4堆栈操作

Cortex-M3/M4使用的是“向下生长的满栈”模型。堆栈指针 SP指向最后一个被压入堆栈的 32位数值。
在下一次压栈时,SP先自减4,再存入新的数值, 如图 2.4.1所示。

第二章  CortexM3/M4基础

POP操作刚好相反:先从SP指针处读出上一次被压入的值,再把SP指针自增4。如图
2.4.2所示。

第二章  CortexM3/M4基础

在进入ISR时,CM3/CM4会自动把一些寄存器压栈,这里使用的是进入ISR之前使用的SP指针(MSP或者是PSP)。离开 ISR后,只要 ISR没有更改过 CONTROL[1],就依然使用先前的 SP指针来执行出栈操作。

2.4.2 双堆栈机制

我们已经知道了 CM3/CM4的堆栈是分为两个:主堆栈和进程堆栈,CONTROL[1]决定如何选择。当 CONTROL[1]=0时,只使用 MSP,此时用户程序和异常 handler共享同一个堆栈。
这也是复位后的缺省使用方式,如图 2.4.3所示。
第二章  CortexM3/M4基础

当 CONTROL[1]=1时,线程模式将不再使用 PSP,而改用 MSP(handler模式永远使用MSP)。
此时,进入异常时的自动压栈使用的是进程堆栈,进入异常 handler后才自动改为MSP,
退出异常时切换回 PSP,并且从进程堆栈上弹出数据,如图 2.4.4所示。

第二章  CortexM3/M4基础

在特权级下,可以指定具体的堆栈指针,而不受当前使用堆栈的限制,示例代码如下:

MRS  R0, MSP  ;读取主堆栈指针到   R0
MSR  MSP,  R0   ;写入  R0的值到主堆栈中
MRS  R0 ,  PSP  ;读取进程堆栈指针到   R0
MSR  PSP,R0   ;写入  R0的值到进程堆栈中

通过读取 PSP的值,OS就能够获取用户应用程序使用的堆栈,进一步地就知道了在发生
异常时,被压入寄存器的内容,而且还可以把其它寄存器进一步压栈的书写形式)。
OS还可以修改 PSP,用于实现多任务中的任务上下文切换。

2.4.3 Stack frames

在进入异常服务程序的时候会将一些数据压入堆栈中,这些数据所占用的数据块被称为Stack frames,对于 M3和 M4没有使用 FPU的时候其 Stack frames总是 8个字(一个字 32bit)如图 2.4.5所示。

第二章  CortexM3/M4基础

在符合 AAPCS的应用程序中,对于响应异常时的堆栈操作是要进行双字对齐的。在 M3或 M4处理器中如果堆栈指针 SP未双字对齐的话,那么就会在堆栈中自动的增加一个填充位使其双字对齐。双字对齐是一个可选项,欲使能此特性,需要把NVIC配置控制寄存器的STKALIGN置位,如下面汇编代码所演示:

LDR  R0,=0xE000ED14 ; R0=NVIC CCR的基址
LDR  R1, [R0]
ORR. W R1,R1, #0x200  ;设置   STKALIGN位
STR  R1,[R0]  ;更新   NVIC CCR

如果使用  C语言,则代码如下:

#define NVIC_CCR ((volatile unsigned long *)(0xE000ED14))
*NVIC_CCR= *NVIC_CCR | 0x200;     //设置 STKALIGN位

xPSP寄存器的 bit9被用来指示 SP是否需要对齐,bit9如果为 1的话就需要双字对齐,如
果为 0的话就不需要双字对齐,未使用 FPU时采用双字对齐的 Stack frame如图 2.4.6所示。

我们可以看到这里只是将 xPSR、PC、LR、R12、R0-R3这 8个寄存器自动入栈,
其余的8个寄存器 R4-R11就需要我们自己手动入栈了,入栈顺序不能乱了。

第二章  CortexM3/M4基础

对于 Cortex-M4来说因为有 FPU单元,如果使能了 FPU单元的话,那么 Stack frame就会
增加 S0-S15和 FPSCR寄存器,也可选择是否使用双字对齐,在 Cortex-M4中默认开启了双字
对齐功能,Stack frame如图 2.4.7所示。
第二章  CortexM3/M4基础

Stack frame是在进入异常处理服务时被硬件自动入栈的,
使用 FPU的时候就会将FPSCR、S0-S15、xPSR、PC、LR、R12、R0-R3这 25个寄存器自动入栈。在图 2.4.7中我们可以看到不管是否双字对齐在 FPSCR的上方都会有一个蓝色的区域没有存放任何数据,也就是 Stack frame一开始的地址是一个空区域。这一点一定要注意到,我们在修改 UCOSII的堆栈初始化函数 OSTaskStkInit()的时侯堆栈的第一个位置写 0就是这么来的。我们可以看出还有 R4-R11,S16-S31这些寄存器没有做处理,那么我们就要手动对这些寄存器做入栈和出栈处理。


2.5 SVC和 PendSV异常

2.5.1 SVC异常

SVC(系统服务调用,亦简称系统调用)用于产生系统函数的调用请求。
例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,
用户程序使用 SVC发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。
因此,当用户程序想要控制特定的硬件时,它就会产生一个 SVC异常,
然后操作系统提供的SVC异常服务例程得到执行,它再调用相关的操作系统函数,
后者完成用户程序请求的服务。

这种“提出要求——得到满足”的方式,很好、很强大、很方便、很灵活、很能可持续发展。
首先,它使用户程序从控制硬件的繁文缛节中解脱出来,而是由 OS负责控制具体的硬件。
第二,OS的代码可以经过充分的测试,从而能使系统更加健壮和可靠。
第三,它使用户程序无需在特权级下执行,用户程序无需承担因误操作而瘫痪整个系统的风险。
第四,通过 SVC的机制,还让用户程序变得与硬件无关,因此在开发应用程序时无需了解硬件的操作细节,从而简化了开发的难度和繁琐度,并且使应用程序跨硬件平台移植成为可能。

开发应用程序唯一需要知道的就是操作系统提供的应用编程接口(API),并且了解各个请求代号和参数表,然后就可以使用 SVC来提出要求了。其实,严格地讲,操作硬件的工作是由设备驱动程序完成的,只是对应用程序来说,它们也是操作系统的一部分,如图 2.5.1所示。

第二章  CortexM3/M4基础

SVC异常通过执行”SVC”指令来产生。该指令需要一个立即数,充当系统调用代号。SVC异常服务例程稍后会提取出此代号,从而解释本次调用的具体要求,再调用相应的服务函数。
例如

	SVC	0x3	;调用3号系统服务

在SVC服务例程执行后,上次执行的SVC指令地址可以根据自动入栈的返回地址计算出。
找到了 SVC指令后,就可以读取该 SVC指令的机器码,从机器码中萃取出立即数,就获知了请求执行的功能代号。
如果用户程序使用的是PSP,服务例程还需要先执行

	MRS  Rn,PSP

指令来获取应用程序的堆栈指针。通过分析 LR的值,可以获知在 SVC指令执行时正在使用哪个堆栈。
在 UCOS中并未使用 SVC这个功能,大家了解一下就行了。

2.5.2 PendSV异常

PendSV(可悬起的系统调用),它和 SVC协同使用。
一方面,SVC异常是必须立即得到响应的应用程序,执行 SVC时都是希望所需的请求立即得到响应。
另一方面,PendSV则不同,它是可以像普通的中断一样被悬起的(不像 SVC那样会*)。
OS可以利用它“缓期执行”一个异常,直到其它重要的任务完成后才执行动作。
悬起PendSV的方法是:手工往NVIC的PendSV悬起寄存器中写1。悬起后,如果优先级不够高,则将缓期等待执行。

PendSV的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:
1.执行一个系统调用
2.系统滴答定时器(SYSTICK)中断,(轮转调度中需要)

让我们举个简单的例子来辅助理解。假设有这么一个系统,里面有两个就绪的任务,并且通过 SysTic k异常启动上下文切换,如图 2.5.2所示
第二章  CortexM3/M4基础

图 2.5.2 是两个任务轮转调度的示意图。但若在产生 Sys Tick异常时正在响应一个中断,则 SysTick异常会抢占其 ISR。
在这种情况下,OS不得执行上下文切换,否则将使中断请求被延迟,而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能容忍这种事。
因此,在 CM3/CM4中也是严禁没商量——如果OS在某中断活跃时尝试切入线程模式,将触犯用法 fault异常,如图 2.5.3所示。
第二章  CortexM3/M4基础

为解决此问题,早期的 OS会检测当前是否有中断在活跃中,只有没有任何中断需要响应时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切换动作拖延很久(因为如果抢占了 IRQ,本次 SysTick在执行后不得作上下文切换,只能等待下一次 SysTick异常),尤其是当某中断源的频率和 Sys Tick异常的频率比较接近时,会发生“共振”。导致永远无法切换任务。

现在好了,PendSV来完美解决这个问题了。PendSV异常会自动延迟上下文切换的请求,直到其它的 ISR都完成了处理后才放行。为实现这个机制,需要把 PendSV编程为最低优先级的异常。如果OS检测到某 IRQ正在活动并且被SysTick抢占,它将悬起一个PendSV异常,以便缓期执行上下文切换,如图 2.5.4。

第二章  CortexM3/M4基础

图 2.5.4中事件的流水账记录如下:

(1)任务A呼叫SVC来请求任务切换(例如,等待某些工作完成)
(2)OS接收到请求,做好上下文切换的准备,并且pend一个PendSV异常。
(3)当CPU退出SVC后,它立即进入PendSV,从而执行上下文切换。
(4)当PendSV执行完毕后,将返回到任务B,同时进入线程模式。

(5)发生了一个中断,并且中断服务程序开始执行
(6)在ISR执行过程中,发生SysTick异常,并且抢占了该ISR。
(7)OS执行必要的操作,然后pend起PendSV异常以作好上下文切换的准备。
(8)当SysTick退出后,回到先前被抢占的 ISR中, ISR继续执行
(9)ISR执行完毕并退出后PendSV服务例程开始执行,并且在里面执行上下文切换。
(10)当PendSV执行完毕后,回到任务A,同时系统再次进入线程模式。


总结

上一篇:金融企业如何通过存储网关实现老旧、异构存储系统整合?


下一篇:支持向量机SVM原理(参数解读和python脚本)