《STM32MP1 M4裸机HAL库开发指南》 第七章 Cortex-M4内核简介
本帖最后由 正点原子 于 2022-10-21 14:45 编辑1)实验平台:正点原子STM32MP157开发板
2)购买链接:https://item.taobao.com/item.htm?&id=629270721801
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子STM32MP157技术交流群:691905614
第七章Cortex-M4内核简介
本章节我们来学习Cortex-M3/M4内核的基础知识和了解处理器的架构。如果在开发板上移植UCOSII操作系统的话,会涉及一些和处理器密切相关的文件,通过本章的基础知识,我们就可以够看懂移植过程的一些重要文件。本章分为如下几个部分:
7.1、Cortex-M3/M4处理器的差别;
7.2、Cortex-M3/M4通用寄存器;
7.3、操作模式和特权级别;
7.4、FPU单元;
7.5、堆栈;
7.6、SVC和PendSV异常;
7.1 Cortex-M3/M4处理器
Cortex-M3为ARM于2005年发布的第一个32位的Cortex处理器,其具有先进的中断结构、可选的存储保护(MPU)和绝佳的计算性能以及极低的动态与静态功耗,随后,2007年6月,ST在北京发布了全球第一款基于ARM Cortex-M3内核32位通用微控制器芯片STM32F103,因其性能优异、资源丰富、超高性的价比而迅速占领市场。此后,ARM于2010年发布Cortex-M4处理器,相比于Cortex-M3,Cortex-M4增加了FPU 单元和 DSP 指令集,非常适合用于数字信号处理和浮点运算需求的应用中,而ST也相继推出了STM32F407、STM32F429/39等系列芯片,资源更丰富、性能更高。
图7.1. 1 Cortex-M3和Cortex-M4资源对比
7.2 Cortex-M3/M4通用寄存器
我们首先了解一下M3/M4的寄存器,M4相对于M3多了一个浮点单元FPU,其他的基本和M3是一样的,以下内容参考自《ARM Cortex-M3和Cortex-M4处理器权威指南》,资料位于开发板光盘A-基础资料\4、参考资料下。
如我们所见, Cortex-M3/M4系列处理器拥有通用寄存器 R0~R15 以及一些特殊功能寄存器。R0~R12 为32位通用寄存器,其中R0~R7为低组寄存器,R8~R12为高组寄存器,绝大多数的16位指令只能使用低组寄存器,而32位的Thumb~2指令则可以访问所有通用寄存器。此外,R13为堆栈指针(SP),R14为连接寄存器(LR),R15为程序计数器(PC)。
特殊功能寄存器有预定义的功能,而且必须通过专用的指令来访问,我们下面会进行介绍。Cortex-M3/M4的通用寄存器如图7.2.1所示。
图7.2. 1 Cortex-M3/M4通用寄存器
1)通用目的寄存器R0~R7
R0~R7 也被称为低组寄存器。所有指令都能访问它们。它们的字长全是32位,复位后
的初始值是不定的。
2)通用目的寄存器 R8~R12
R8~R12也被称为高组寄存器。这是因为只有很少的16位Thumb 指令能访问它们,32
位的humb-2指令则不受限制。它们也是 32 位字长,且复位后的初始值是不定的。
3)堆栈指针 R13
R13 是堆栈指针。在 CM3/CM4处理器内核中共有两个堆栈指针,于是也就支持两个堆栈。当引用 R13(或写作 SP)时,你引用到的是当前正在使用的那一个,另一个必须用特殊的指令来访问( MRS,MSR 指令)。这两个堆栈指针分别是:
主堆栈指针(MSP),或写作 SP_main。这是缺省的堆栈指针,它由OS内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。
进程堆栈指针(PSP),或写作 SP_process。用于常规的应用程序代码(不处于异常服用例程中时)。要注意的是,并不是每个应用都必须用齐两个堆栈指针。简单的应用程序只使用 MSP就够了。堆栈指针用于访问堆栈,并且 PUSH 指令和 POP 指令默认使用 SP。
4)连接寄存器 R14
R14是连接寄存器(LR)。在一个汇编程序中,你可以把它写作both LR和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模式,将产生一个fault 异常。
6)特殊功能寄存器组
Cortex-M3/M4有一个特殊功能寄存器组,如图7.2.2所示。
图7.2. 2特殊功能寄存器组
Cortex‐M3 /M4中的特殊功能寄存器包括:
程序状态寄存器组( PSRs 或曰 xPSR)
中断屏蔽寄存器组( PRIMASK, FAULTMASK,以及 BASEPRI)
控制寄存器( CONTROL)
它们只能被专用的 MSR 和 MRS 指令访问,而且它们也没有存储器地址。
MRS <gp_reg>, <special_reg> ; 读特殊功能寄存器的值到通用寄存器
MSR <special_reg>,<gp_reg> ; 写通用寄存器的值到特殊功能寄存器
7)程序状态寄存器(PSRs或曰PSR)
应用程序 PSR( APSR)
中断号 PSR( IPSR)
执行 PSR( EP SR)
通过 M RS / M S R 指令,这 3 个 PSR s 既可以单独访问,也可以组合访问( 2 个组合, 3 个组合都可以)。当使用三合一的方式访问时,应使用名字“ xPSR”或者“ PSR”。这三个寄存器的各个位的含意如表7.2.1所示。 表7.2. 1 Cortex-M3/M4程序状态寄存器(xPSR)
PRIMASK,FAULTMASK和BASEPRI
这三个寄存器很重要,这三个寄存器用于控制异常的使能和除能,这三个寄存器的介绍如表7.2.2所示
表7.2. 2 Cortex-M3/M4屏蔽寄存器
对于时间‐关键任务而言, PRIMASK 和 BASEPRI 对于暂时关闭中断是非常重要的。而FAULTMASK 则可以被 OS 用于暂时关闭 fault 处理机能,这种处理在某个任务崩溃时可能需要。因为在任务崩溃时,常常伴随着一大堆 faults。在系统料理“后事”时,通常不再需要
响应这些 fault——人死帐清。总之 FAULTMASK 就是专门留给 OS 用的。
要访问 PRIMASK, FAULTMASK 以及 BASEPRI,同样要使用 MRS/MSR 指令,如:
MRSR0, BASEPRI ; 读取 BASEPRI 到 R0 中
MRSR0, FAULTMASK ; 同上
MRSR0, PRIMASK ; 同上
MSRBASEPRI, R0 ; 写入 R0 到 BASEPRI 中
MSRFAULTMASK, R0 ; 同上
MSRPRIMASK, R0 ; 同上
只有在特权级下,才允许访问这 3 个寄存器,为了快速地开关中断,CM3/CM4 还专门设置了一条 CPS 指令,有 4 种用法,这四种用法非常重要,我们在移植UCOS操作系统的时候就是用这下面的方法来开关中断的。
CPSID I ;PRIMASK=1, ; 关中断
CPSIE I ;PRIMASK=0, ; 开中断
CPSID F ;FAULTMASK=1, ; 关异常
CPSIE F ;FAULTMASK=0 ; 开异常
8)控制寄存器(CONTROL)
CONTROL寄存器用于定义特权级别和堆栈指针的使用,CONTROL寄存器如表7.2.3所示,注意CONTROL只有Cortex-M4才有。
表7.2. 3 CONTROL寄存器
CONTROL
在Cortex-M4中有FPU单元,如果我们使用了FPU,那么在处理异常时就需要保存FPU环境,此位用来指示是否需要保存浮点环境。
CONTROL
在 Cortex‐M3 的 handler 模式中, CONTROL总是 0。在线程模式中则可以为 0 或 1。仅当处于特权级的线程模式下,此位才可写,其它场合下禁止写此位。改变处理器的模式也有其它的方式:在异常返回时,通过修改 LR 的位 2,也能实现模式切换。
CONTROL
仅当在特权级下操作时才允许写该位。一旦进入了用户级,唯一返回特权级的途径,就
是触发一个(软)中断,再由服务例程改写该位。CONTROL 寄存器也是通过 MRS 和 MSR 指令来操作的:
MRS R0, CONTROL
MSR CONTROL, R0
9)EXC_RETURN
在进入异常服务程序后, L R的值被自动更新为特殊的 EXC_RETURN,对于Cortex-M4来说这是一个高27位全为1的值(M3是高28位都为1)。M4中位有意义,在M3中有意义,并且和M4中的相同,EXC_RETURN位段如表7.2.4所示。
注意!EXC_RETURN的bit4非常重要,我们可以根据此位的值来获知硬件会对哪些寄存器进行自动压入栈和出栈处理,这个我们会在讲浮点寄存器和堆栈的时候详细讲解。
表7.2. 4 EXC_RETURN位段详解
从表7.2.5中我们可得到EXC_RETURN的最终返回值共有6个,如表7.2.5所示。
表7.2.5合法的EXC_RETURN值及其功能7.3 操作模式和特权级别
Cortex-M3/CM4处理器支持两种处理器的操作模式,还支持两级特权操作。
两种操作模式分别为:处理者模式 (handler mode)和线程模式(thread mode)。引入两个模式的本意,是用于区别普通应用程序的代码和异常服务例程的代码——包括中断服务例程的代码。
Cortex-M3/M4的另一个侧面则是特权的分级——特权级和用户级。这可以提供一种存储器访问的保护机制,使得普通的用户程序代码不能意外地,甚至是恶意地执行涉及到要害的操作。处理器支持两种特权级,如表7.3.1所示,这也是一个基本的安全模型。
表7.3. 1 Cortex-M3/M4下的操作模式和特权级别
在CM3/CM4运行主应用程序时(线程模式),既可以使用特权级,也可以使用用户级;但是异常服务例程必须在特权级下执行。复位后,处理器默认进入线程模式,特权级访问。在特权级下,程序可以访问所有范围的存储器(如果有MPU,还要在MPU规定的禁地之外),并且可以执行所有指令。
在特权级下的程序可以为所欲为,但也可能会把自己给玩进去——切换到用户级。一旦进入用户级,再想回来就得走“法律程序”了——用户级的程序不能简简单单地试图改写 CONTROL 寄存器就回到特权级,事实上,从用户级到特权级的唯一途径就是异常:如果在程序执行过程中触发了一个异常,处理器总是先切换入特权级,并且在异常服务例程执行完毕退出时,返回先前的状态。
7.4 FPU单元
在Coretex-M4处理器中有一个可选的单精度FPU单元,如果使能了FPU单元的话就可以使用它来对单精度浮点数进行计算,双精度浮点数的计算仍然要使用到C运行库。
7.4.1 FPU寄存器
FPU单元包含一系列的寄存器:
CPACR寄存器,在SCB块中
浮点寄存器块,S0-S31
FPU状态和控制寄存器:FPSCR
其他的一些FPU寄存器
1)CPACR寄存器
通过CPACR寄存器来使能FPU,CPACR寄存器的地址为0XE000ED88,我们也可以通过“SCB->CPACR”来访问CPACR寄存器,CPACR寄存器的bit0-bit19和bit24-bit31不允许使用,为保留位,其中为CP10,为CP11。我们通过设置CP10和CP11来开启FPU,CP10和CP11设置情况如表7.4.1.1所示,注意CP10和CP11都为2bit。
表7.4.1. 1 CP10和CP11设置表
默认情况下CP10和CP11都为00,如果要使用FPU的话需要软件设置CPACR来开启FPU,通过设置CP10和CP11都为11来开启,实例代码如下:
SCB->CPACR|=0X00F00000; /* 使能FPU */
2) 浮点寄存器块
浮点寄存器快包含32个32位的寄存器,这32个寄存器可以两两组合成一个64位的双精度寄存器,如图7.4.1.1所示。
图7.4.1. 1浮点寄存块
S0-S15是caller-saved寄存器,如果一个应用A调用了另外一个应用B,那么应用A在调用B之前一定要保存这些寄存器,因为在调用的时候这些寄存器会被改变。
S16-S31被称为callee-saved寄存器,如果一个应用A调用应用B,而且B需要大于16个寄存器来做计算,那么应用B就需要保存这些寄存器。并且在返回应用A的时候必须恢复这些寄存器。
7.4.2 Lazy Stacking
对于Cortex-M4来说Lazy Stacking是一个重要的特性,在使用FPU的情况下,不使用这个特性会在异常处理的时候消耗29个时钟周期,因为要将25个寄存器压栈,以前只需要将8个压栈。如果使用Lazy Stacking这个特性的话,那么在异常处理的时候只需要消耗12个时钟周期,默认情况下Lazy Stacking是使能的。可以看出如果在任务切换中使用Cortex-M4的这个特性将会极大的提高任务切换的速度。。
7.5 堆栈
7.5.1 Cortex-M3/M4堆栈操作
Cortex-M3/M4使用的是“向下生长的满栈”模型。堆栈指针SP指向最后一个被压入堆栈的32位数值。在下一次压栈时,SP先自减 4,再存入新的数值,如图7.5.1.1所示。
图7.5.1. 1堆栈的PUSH操作
POP操作刚好相反:先从 SP 指针处读出上一次被压入的值,再把SP指针自增 4。如图7.5.1.2所示。
图7.5.1. 2堆栈的POP操作
在进入 ISR 时,CM3/CM4会自动把一些寄存器压栈,这里使用的是进入ISR之前使用的 SP指针(MSP 或者是 PSP)。离开ISR后,只要ISR没有更改过CONTROL,就依然使用先前的 SP 指针来执行出栈操作。
7.5.2 双堆栈机制
我们已经知道了CM3/CM4的堆栈是分为两个:主堆栈和进程堆栈,CONTROL决定如何选择。当CONTROL=0时,只使用 MSP,此时用户程序和异常 handler 共享同一个堆栈。这也是复位后的缺省使用方式,如图7.5.2.1所示。
图7.5.2. 1 CONTROL=0时的堆栈使用情况
当CONTROL=1 时,线程模式将不再使用PSP,而改用MSP(handler 模式永远使用MSP)。此时,进入异常时的自动压栈使用的是进程堆栈,进入异常handler后才自动改为 MSP,退出异常时切换回PSP,并且从进程堆栈上弹出数据,如图7.5.2.2所示。
图7.5.2. 2 CONTROL=1时的堆栈使用情况
在特权级下,可以指定具体的堆栈指针,而不受当前使用堆栈的限制,示例代码如下:
MRS R0, MSP ; 读取主堆栈指针到 R0
MSR MSP, R0 ; 写入 R0 的值到主堆栈中
MRS R0, PSP ; 读取进程堆栈指针到 R0
MSR PSP, R0 ; 写入 R0 的值到进程堆栈中
通过读取PSP的值,OS就能够获取用户应用程序使用的堆栈,进一步地就知道了在发生异常时,被压入寄存器的内容,而且还可以把其它寄存器进一步压栈的书写形式)。OS还可以修改 PSP,用于实现多任务中的任务上下文切换。
7.6 SVC和 PendSV异常
7.6.1 SVC异常
SVC(系统服务调用,亦简称系统调用)用于产生系统函数的调用请求。例如,操作系统不让用户程序直接访问硬件,而是通过提供一些系统服务函数,用户程序使用SVC发出对系统服务函数的呼叫请求,以这种方法调用它们来间接访问硬件。因此,当用户程序想要控制特定的硬件时,它就会产生一个SVC异常,然后操作系统提供的 SVC 异常服务例程得到执行,它再
调用相关的操作系统函数,后者完成用户程序请求的服务。这种“提出要求——得到满足”的方式,很好、很强大、很方便、很灵活、很能可持续发展。首先,它使用户程序从控制硬件的繁文缛节中解脱出来,而是由OS负责控制具体的硬件。第二,OS的代码可以经过充分的测试,从而能使系统更加健壮和可靠。第三,它使用户程序无需在特权级下执行,用户程序无需承担因误操作而瘫痪整个系统的风险。第四,通过SVC的机制,还让用户程序变得与硬件无关,因此在开发应用程序时无需了解硬件的操作细节,从而简化了开发的难度和繁琐度,并且使应用程序跨硬件平台移植成为可能。开发应用程序唯一需要知道的就是操作系统提供的应用编程接口(API),并且了解各个请求代号和参数表,然后就可以使用SVC来提出要求了。其实,严格地讲,操作硬件的工作是由设备驱动程序完成的,只是对应用程序来说,它们也是操作系统的一部分,如图7.6.1.1所示。
图7.6.1. 1 SVC作为操作系统函数门户示意图
SVC异常通过执行”SVC”指令来产生。该指令需要一个立即数,充当系统调用代号。 SVC异常服务例程稍后会提取出此代号,从而解释本次调用的具体要求,再调用相应的服务函数。
例如,
SVC 0x3; 调用 3 号系统服务
在SVC服务例程执行后,上次执行的 SVC 指令地址可以根据自动入栈的返回地址计算出。找到了 SVC 指令后,就可以读取该 SVC 指令的机器码,从机器码中萃取出立即数,就获知了请求执行的功能代号。如果用户程序使用的是 PSP,服务例程还需要先执行 MRSRn,PSP指令来获取应用程序的堆栈指针。通过分析LR的值,可以获知在SVC指令执行时正在使用哪个堆栈。
在UCOS中并未使用SVC这个功能,大家了解一下就行了。
7.6.2 PendSV异常
PendSV(可悬起的系统调用),它和 SVC 协同使用。一方面, SVC异常是必须立即得到响应的,应用程序执行SVC时都是希望所需的请求立即得到响应。另一方面,PendSV则不同,它是可以像普通的中断一样被悬起的(不像SVC那样会上访)。OS可以利用它“缓期执行”一个异常,直到其它重要的任务完成后才执行动作。悬起PendSV的方法是:手工往 NVIC 的 PendSV 悬起寄存器中写1。悬起后,如果优先级不够高,则将缓期等待执行。
PendSV 的典型使用场合是在上下文切换时(在不同任务之间切换)。例如,一个系统中有两个就绪的任务,上下文切换被触发的场合可以是:
执行一个系统调用
系统滴答定时器(SYSTICK)中断,(轮转调度中需要)
让我们举个简单的例子来辅助理解。假设有这么一个系统,里面有两个就绪的任务,并
且通过 SysTic k 异常启动上下文切换,如图7.6.2.1所示。
图7.6.2. 1两个任务通过SysTick轮转调度的简单模式
图7.6.2.1是两个任务轮转调度的示意图。但若在产生 Sys Tick 异常时正在响应一个中断,则SysTick异常会抢占其 ISR。在这种情况下,OS不得执行上下文切换,否则将使中断请求被延迟,而且在真实系统中延迟时间还往往不可预知——任何有一丁点实时要求的系统都决不能容忍这种事。因此,在CM3/CM4中也是严禁没商量——如果OS在某中断活跃时尝试切入线程模式,将触犯用法fault异常,如图7.6.2.2所示。
图7.6.2. 2发生IRQ时上下文切换的问题
为解决此问题,早期的OS会检测当前是否有中断在活跃中,只有没有任何中断需要响应时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切换动作拖延很久(因为如果抢占了IRQ,本次SysTick 在执行后不得作上下文切换,只能等待下一次SysTick 异常),尤其是当某中断源的频率和 SysTick 异常的频率比较接近时,会发生“共振”。
现在好了, PendSV 来完美解决这个问题了。PendSV异常会自动延迟上下文切换的请求,直到其它的 ISR 都完成了处理后才放行。为实现这个机制,需要把PendSV编程为最低优先级的异常。如果OS检测到某 IRQ 正在活动并且被 SysTick 抢占,它将悬起一个PendSV异常,以便缓期执行上下文切换,如图7.6.2.3。
图7.6.2. 3使用PendSV控制上下文切换
图7.6.2.3中事件的流水账记录如下:
(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 服务例程开始执行,并且在里面执行上下文切换。
当 PendSV 执行完毕后,回到任务 A,同时系统再次进入线程模式。
页:
[1]