Cortex_M3基础

目录

1、寄存器组

2、堆栈指针 R13

2.1、堆栈

2.2、堆栈区的操作

2.3、Cortex-M3 堆栈的实现

3、连接寄存器 R14

4、程序计数器 R15

5、特殊功能寄存器组

5.1、程序状态寄存器(PSRs 或曰 PSR)

5.2、PRIMASK, FAULTMASK 和 BASEPRI

5.3、控制寄存器(CONTROL)

5.3.1、操作模式

5.3.2、Cortex-M3 的双堆栈机制

6、异常和中断

6.1、 向量表

7、复位操作


本小节通过Cortex-M3 的寄存器组进行展开,分别介绍了CM3的堆栈、处理器模式、异常以及上电复位的等知识。本小节需要特别掌握的知识是处理器的模式和堆栈指针的使用,特别是双堆的使用方式,虽然在工作过程中不需要我们操作这些寄存器,但是对于需要移植操作系统的工程师来说,理解这部分的知识是非常有必要的。为什么CM3要有两个堆栈指针。这么设计的好处是什么?可以带着这个问题阅读下面内容。

1、 寄存器组

在这里插入图片描述
如上图所示,CM3 拥有通用寄存器 R0‐R15 以及一些特殊功能寄存器。R0‐R12 是最“通用目的”的,但是绝大多数的 16 位指令只能使用 R0‐R7(低组寄存器),而 32 位的 Thumb‐2指令则可以访问所有通用寄存器。特殊功能寄存器有预定义的能,而且必须通过专用的指令来访问。

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

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

2、堆栈指针 R13

R13 是堆栈指针。在 CM3 处理器内核中有两个堆栈指针,这两个堆栈指针分别是:

主堆栈指针(MSP),或写作 SP_main。这是缺省的堆栈指针,它由 OS 内核、异常服务例程以及所有需要特权访问的应用程序代码来使用。

进程堆栈指针(PSP),或写作 SP_process。用于常规的应用程序代码(不处于异常服用例程中时)

要注意的是,当引用 R13(或写作 SP)时,引用到的是当前正在使用的那一个,另一个必须用特殊的指令来访问(MRS,MSR 指令)。并不是每个应用都必须用齐两个堆栈指针。简单的应用程序只使用 MSP就够了。

堆栈指针用于访问堆栈,PUSH 指令和 POP 指令默认使用 SP。

PUSH指令:寄存器入栈指令

POP指令:数据出栈指令

它俩的汇编语言语法,如下例所演示:

PUSH {R0} ; *(--R13)=R0。R13 是 long*的指针
POP {R0}  ; R0= *R13++

2.1、堆栈

在介绍CM3的堆栈数据结构之前,有必要先描述堆栈的概念,不然很容易混淆,堆和栈都是对一段连续内存的统称,在程序中堆和栈都是用来存储数据的,不同的地方就是,堆用在动态分配的内存,如使用malloc函数申请的一段空间实际分配的是堆的空间,在操作系统的内存管理算法中,如内存池等都是堆空间的管理。而栈以“先进后出”的缓存方式保存函数调用过程中的中间数据,如函数临时变量等。因为栈是被内核动态管理的,所以被调用函数退出后,中间变量也就失去意义。在内核层面栈保存的是寄存器中的数据。本文将栈统称为堆栈。读者在这里一定要清楚,堆和栈在操作系统的层面是两个概念,不能混为一谈。
言归正传,下面回到介绍PUSH和POP指令对堆栈的操作形式。
在这里插入图片描述

如上图,内存地址从上到下依次增大的,所以PUSH和POP指令操作的是向下生长的满堆栈。即数据入栈时,堆栈指针先减一个单元,数据出栈时,先将数据POP出去然后SP自增一个单元。通常在进入一个子程序后,第一件事就是把寄存器的值先PUSH 入堆栈中,在子程序退出前再 POP 曾经 PUSH 的那些寄存器。子程序进入后汇编代码如下所示:

 subroutine_1 
     PUSH {R0-R7, R12, R14} ; 保存寄存器列表
     …                      ; 执行处理
     POP {R0-R7, R12, R14}  ; 恢复寄存器列表
     BX R14                 ; 返回到主调函数

可以使用 SP 表示 R13。在程序代码中,MSP 和 PSP 都被称为 R13/SP。可以通过 MRS/MSR 指令来指名道姓地访问具体的堆栈指针。

寄存器的 PUSH 和 POP 操作永远都是 4 字节对齐的——也就是说他们的地址必须是0x4,0x8,0xc,……。即R13 的最低两位被硬线连接到0,并且总是读出0。

2.2、堆栈区的操作

笼统地讲,堆栈操作就是对内存的读写操作,但是其地址由SP给出。寄存器的数据通过PUSH操作存入堆栈,以后用POP操作从堆栈中取回。在PUSH与POP的操作中,SP的值会按堆栈的使用法则自动调整,以保证后续的PUSH不会破坏先前PUSH进去的内容。堆栈的功能就是把寄存器的数据放入内存,以便将来能恢复,当一个任务或一段子程序执行完毕后恢复。正常情况下,PUSH与POP 必须成对使用,而且参与的寄存器,不论是身份还是先后顺序都必须完全一致。当PUSH/POP指令执行时,SP指针的值也根着自减/自增。示例代码如下所示:
在这里插入图片描述
如果参与的寄存器比较多,可以使用PUSH和POP操作多个寄存器。像这样:

PUSH {R0-R2}         ;压入 R0-R2 
PUSH {R3-R5,R8, R12} ;压入 R3-R5,R8,以及 R12 

在POP时,可以如下操作:

POP {R0-R2}         ;弹出 R0-R2 
POP {R3-R5,R8, R12} ;弹出 R3-R5,R8,以及 R12 

注意:不管在寄存器列表中,寄存器的序号是以什么顺序给出的,汇编器都将把它们升序排序。然后PUSH指令按照从大到小的顺序依次入栈,POP则按从小到大的顺序依次出栈。如果不按升序写寄存器,有些汇编器可能会给出一个语法错误。

PUSH/POP 还有这样一种特殊形式,形如

PUSH {R0-R3, LR}
POP {R0-R3, PC} 

注意:POP的最后一个寄存器是PC,并不是先前PUSH的LR。这其实是一个返回的小技巧。因为总要把先前LR的值弹出来,再使用此值返回,干脆绕过LR,直接传给PC!因为 LR 在子程序返回时的唯一用处就是提供返回地址,在返回后,先前保存的返回地址就没有利用价值了,所以只要PC得到了正确的值,不恢复也没关系。

PUSH 指令等效于与使用 R13 作为地址指针的STMDB指令,而POP指令则等效于使用R13作为地址指针的LDMIA指令——STMDB/LDMIA。

2.3、Cortex-M3 堆栈的实现

Cortex‐M3使用的是“向下生长的满栈”模型。堆栈指针SP指向最后一个被压入堆栈的 32位数值。在下一次压栈时,SP先自减4,再存入新的数值。
在这里插入图片描述
POP 操作刚好相反:先从SP指针处读出上一次被压入的值,再把SP指针自增4。
在这里插入图片描述

重点:在进入ISR时,CM3会自动把一些寄存器压栈,这里使用的是进入ISR之前使用的SP指针(MSP或者是PSP)。离开ISR后,只要ISR没有更改过CONTROL[1],就依然使用先前的SP指针来执行出栈操作。
入栈操作一般发生在以下两种情况:
1)、子程序调用之前对LR有入栈操作
2)、进入子程序之后根据实际情况做现场保护的入栈操作
3)、进入ISR服务程序之前入栈操作(硬件自动执行)
出栈操作一般发生在以下两种情况:
1)、子程序调用结束前恢复现场的出栈操作
2)、退出子程序调用后LR出栈操作
2)、推出中断服务程序之后出栈操作(硬件自动执行)

3、连接寄存器 R14

R14 是连接寄存器(LR)。在一个汇编程序中,可以把它写作 LR 或者 R14。LR 在调用函数或者子程序时存储返回地址。当函数或者子程序执行完成后加载LR到PC寄存器返回调用程序处继续执行。例如,使用 BL(分支并连接,Branch and Link)指令时,就自动填充 LR 的值。子程序调用示例如下:

 main                   ;主程序
         … 
         BL function1   ; 使用“分支并连接”指令呼叫 function1 
                        ; PC= function1,并且 LR=main 的下一条指令地址
         … 
 Function1                                                                           
         …              ; function1 的代码
         BX LR          ; 函数返回(如果 function1 要使用 LR,必须在使用前 PUSH,
                        ; 否则返回时程序就可能跑飞了)

当函数或者子程序被调用时,LR的值由硬件自动更新,如果被调函数或者子程序还需要调用其他函数或者子程序,在调用函数或者子程序之前需要对LR进行入栈操作,否则被调函数的返回地址会被丢失。

当系统发生异常时,进入异常时,LR被硬件自动更新为EXC_RETURN的值。这个用于异常的退出。

4、程序计数器 R15

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

5、特殊功能寄存器组

Cortex‐M3 中的特殊功能寄存器包括:

程序状态寄存器组(PSRs 或曰 xPSR)

中断屏蔽寄存器组(PRIMASK, FAULTMASK,以及 BASEPRI)

控制寄存器(CONTROL)

它们只能被专用的 MSR 和 MRS 指令访问,指令格式如下所示。

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

5.1、程序状态寄存器(PSRs 或曰 PSR)

在这里插入图片描述
在这里插入图片描述

程序状态寄存器如上图所示,在其内部又被分为三个子状态寄存器:

应用程序 PSR(APSR)

中断号 PSR(IPSR)

执行 PSR(EPSR)

通过 MRS/MSR 指令,这 3 个 PSRs 即可以单独访问,也可以组合访问(2 个组合,3 个组合都可以)。当使用三合一的方式访问时,应使用名字“xPSR”或者“PSR”。
个人理解 IPSR使用记录当前的中断编号,CM3根据中断向量基地址和中断编号进行异常入口地址计算,且这个操作有硬件操作完成,提高了异常响应速度。参考STM32的IPSR寄存器定义如下:
在这里插入图片描述

5.2、PRIMASK, FAULTMASK 和 BASEPRI

这三个寄存器用于控制异常的使能和除能。

在这里插入图片描述
要访问 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 个寄存器。
在这里插入图片描述

5.3、控制寄存器(CONTROL)

控制寄存器用于定义特权级别,还用于选择当前使用哪个堆栈指针。
在这里插入图片描述

CONTROL[1]

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

CONTROL[0]

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

MRS R0, CONTROL

MSR CONTROL, R0

注意:异常进入和退出不会修改CONTROL的值。

5.3.1、操作模式

Cortex‐M3 支持 2 个模式和两个特权等级。
在这里插入图片描述
当处理器处在线程状态下时,既可以使用特权级,也可以使用用户级;另一方面,handler模式总是特权级的。在复位后,处理器进入线程模式+特权级。

在特权级下的代码可以通过置位 CONTROL[0]来进入用户级。而不管是任何原因产生了任何异常,处理器都将以特权级来运行其服务例程,异常返回后将回到产生异常之前的特权级。用户级下的代码不能再试图修改 CONTROL[0]来回到特权级。它必须通过一个异常 handler,由那个异常 handler 来修改 CONTROL[0],才能在返回到线程模式后拿到特权级。系统特权级变更如下图所示:
在这里插入图片描述
把代码按特权级和用户极分开对待,有利于使架构更加安全和健壮。例如,当某个用户代码出问题时,不会让它成为害群之马,因为用户级的代码是禁止写特殊功能寄存器和 NVIC中寄存器的。另外,如果还配有 MPU,保护力度就更大,甚至可以阻止用户代码访问不属于它的内存区域。

为了避免系统堆栈因应用程序的错误使用而毁坏,可以给应用程序专门配一个堆栈,不让它共享操作系统内核的堆栈。在这个管理制度下,运行在线程模式的用户代码使用 PSP,而异常服务例程则使用 MSP。这两个堆栈指针的切换是全自动的,就在出入异常服务例程时由硬件处理。

如前所述,特权等级和堆栈指针的选择均由 CONTROL 负责。当 CONTROL[0]=0 时,在异常处理的始末,只发生了处理器模式的转换,如下图所示:
在这里插入图片描述
但若 CONTROL[0]=1(线程模式+用户级),则在中断响应的始末,both 处理器模式和特权等极都要发生变化,如下图所示:
在这里插入图片描述

CONTROL[0]只有在特权级下才能访问。用户级的程序如想进入特权级,通常都是使用一条“系统服务呼叫指令(SVC)”来触发“SVC 异常”,该异常的服务例程可以选择修改CONTROL[0]。

5.3.2、Cortex-M3 的双堆栈机制

已经知道了CM3的堆栈是分为两个:主堆栈和进程堆栈,CONTROL[1]决定如何选择。

当 CONTROL[1]=0 时,只使用MSP,此时用户程序和异常handler共享同一个堆栈。这也是复位后的缺省使用方式。
在这里插入图片描述
当 CONTROL[1]=1 时,线程模式将不再使用MSP,而改用PSP(handler 模式永远使用MSP)。
在这里插入图片描述
在特权级下,可以指定具体的堆栈指针,而不受当前使用堆栈的限制,示例代码如下:

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

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

6、异常和中断

Cortex‐M3 支持大量异常,包括 16‐4‐1=11 个系统异常,和最多 240 个外部中断——简称 IRQ。具体使用了这 240 个中断源中的多少个,则由芯片制造商决定。由外设产生的中断信号,除了 SysTick 的之外,全都连接到 NVIC 的中断输入信号线。典型情况下,处理器一般支持 16 到 32 个中断,当然也有在此之外的。

作为中断功能的强化,NVIC 还有一条 NMI 输入信号线。NMI 究竟被拿去做什么,还要视处理器的设计而定。在多数情况下,NMI 会被连接到一个看门狗定时器,有时也会是电压监视功能块,以便在电压掉至危险级别后警告处理器。NMI 可以在任何时间被激活,甚至是在处理器刚刚复位之后。异常描述如下表所示:
在这里插入图片描述
上表列出了 Cortex‐M3 可以支持的所有异常。有一定数量的系统异常是用于 fault 处理,它们可以由多种错误条件引发。NVIC 还提供了一些 fault 状态寄存器,以便于 fault 服务例程找出导致异常的具体原因。

6.1、 向量表

当一个发生的异常被 CM3 内核接受,对应的异常 handler 就会执行。为了决定 handler 的入口地址,CM3 使用了“向量表查表机制”。向量表其实是一个 WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该异常 handler 的入口地址。向量表的存储位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 处必须包含一张向量表,用于初始时的异常分配。向量表结构如下图所示:
在这里插入图片描述在这里插入图片描述
举个例子,如果发生了异常 11(SVC),则 NVIC 会计算出偏移移量是 11x4=0x2C,然后从那里取出服务例程的入口地址并跳入。0 号异常的功能则是个另类,它并不是什么入口地址,而是给出了复位后 MSP 的初值。

7、复位操作

在这里插入图片描述
CM3在上电是首先进入复位状态,在离开复位状态后,CM3 做的第一件事就是读取下列两个 32 位整数的值(如上图MDK反汇编之后向量表的值):
从地址 0x0000,0000 处取出 MSP 的初始值。
从地址 0x0000,0004 处取出 PC 的初始值——这个值是复位向量,LSB 必须是 1。然后从这个值所对应的地址处取指。
在这里插入图片描述
如上图是CM3退出复位后的过程,取出0地址的数据给到MSP初始化堆栈,同时将0x4的地址给到PC,而0x4的地方就是复位函数的入口地址,熟悉启动文件的同学就知道,CM3是从复位函数进入最后调到C环境的main函数的。

请注意,这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。在 CM3中,0 地址处提供 MSP 的初始值,然后就是向量表(向量表在以后还可以被移至其它位置)。向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令。

关于STM32的启动知识的可以参考下面的链接。
STM32启动文件介绍

  • 11
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
cortex_m3 core_cminstr.h是指Cortex-M3处理器中的core_cminstr.h文件。Cortex-M3是ARM架构中的一种32位嵌入式处理器核,它广泛应用于各种嵌入式系统。core_cminstr.h文件包含了Cortex-M3处理器核的指令集,以及相关的操作函数和宏定义。 core_cminstr.h文件中定义了Cortex-M3处理器核所支持的指令,包括数据处理指令、乘法指令、逻辑指令、移位指令、分支跳转指令等等。这些指令能够执行各种算术运算、逻辑运算、移位操作、内存访问等操作,是实现嵌入式系统功能的基础。 此外,core_cminstr.h文件中还定义了一些用于操作指令和寄存器的宏定义和函数,例如获取当前程序计数器值、设置寄存器值、获取状态寄存器值等。这些宏和函数能够方便地操作处理器核的寄存器和执行指令,对于开发者来说提供了方便和灵活性。 通过使用core_cminstr.h文件,开发者可以更加高效地编写嵌入式系统的软件,利用Cortex-M3处理器核的强大功能和指令集,实现各种嵌入式应用程序。同时,core_cminstr.h文件还可以用于调试和分析嵌入式系统,通过查看和操作处理器的指令和寄存器状态,帮助开发者定位和解决问题。 总之,cortex_m3 core_cminstr.h是嵌入式系统开发中不可或缺的头文件,定义了Cortex-M3处理器核的指令集和相关操作函数,提供了强大而灵活的代码编写和调试工具,对于实现各种嵌入式应用程序具有重要意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值