ARM Cortex -M 体系结构————————ARM微控制器与嵌入式系统(清华大学慕课记录)

ARM的发展过程

对于ARM公司来讲,ARM公司只做CPU设计,采用出售IP的方式运营,半导体产商无需自己设计CPU,是生产关系的革命,提高了生产力。下面这张图ARM核的多个系列,我们可以看到ARM从V7核开始,就分为了A,R和M三个系列,分别对应高端的多媒体计算,中间的实时性系统以及低端的微控制器,而为控制器对应着我们学习的这个领域,Cortex-M系列的来讲,它保持了高度的兼容性。
在这里插入图片描述

32位 ARM Cortex M位单片机寄存器组

在数据和地址的通用寄存器角度,ARM Cortex M 系列的单片机都基本上有如下所示的寄存器组,从R0-R15一共16个寄存器构成了单片机的内部寄存器组
在这里插入图片描述
从上图可以看到从R0-R12是单片机的通用寄存器组,,R13,R14和R15是特殊功能的寄存器。

  • R13是单片机的堆栈指针寄存器:
    它保存的值是一个地址,表征的是单片机中的堆栈空间所被使用掉的空间所在的地址,在这个地址往上就是堆栈还未使用的空间,从这个地址往下就是堆栈已经使用掉的空间。
  • R14是单片机的连接寄存器:
    它所保存的也是一个地址,所表征的是当程序发生一次函数嵌套调用时,用于保存子函数的返回地址,这样的机制使得当函数发生一次嵌套调用时,不必将函数的返回地址进行压栈,加快了程序运行的效率
  • R15是单片机的程序计数器
    程序计数器又被称为单片机的PC指针寄存器,在分析CPU的运行机制的时候,我们知道,PC指针寄存器是用来保存待执行指令的地址的,也就是指令中即将运行的下一条指令的地址。

特殊功能寄存器

对于通用计算机CPU模型的时候,会存在一个状态寄存器,来保存CPU在运算时的得0,得1,溢出的这些状态。另外,对于CPU来讲,我们还需要有一些设置的功能,对于通常的8位或者16位CPU来讲,通常都会把设置的这个功能合在一个寄存器里,包括开关中断,包括运算状态,而在ARM的体系结构里,ARM把刚刚所述的归总在下图所示的5个寄存器中,图中最上面的xPSR是用来保存CPU设置状态的,第二个PRIMASK是用来设置中断的开和关的,它是一个中断开关,最下面的CONTROL寄存器,它只有1-2个bit可以使用,是用来设置CPU的实际工作模式和状态的。第三个寄存器FAULTMASK的功能是用来配置不可屏蔽中断的开和关,BASEPRI寄存器具备按优先级关闭中断的功能。
在这里插入图片描述
上图所示的寄存器在ARM Cortex M系列是一致兼容的,主要的区别在于对ARM Cortex M3和 ARM Cortex M3的单片机来讲,FAULTMASK和BASEPRI两个寄存器是可以使用的,但是对于M0的核是没有的。

xPSR寄存器

xPSR是一个32位的寄存器,每一位都有特定的功能,便于程序员对其进行访问,并获得CPU此刻的状态。为了编程的方便,这个寄存器有三个别名,分别是APSR,IPSR,EPSR.我们用三个别名来加以访问的时候,只关注和读取其中特定的字段来实现单一的功能。
在这里插入图片描述

APSR(应用程序状态寄存器)

当我们使用APSR来访问这个寄存器的时候,读到的主要是高四位的值,后面如图所示是Reserve的。那么这高四位的值入如所示,存的分别是:
N:是否有得0
Z:是否有负数
C:是否有借位进位
Q:是否有溢出
这些位随着指令的运行会被CPU自动的更新,而又可能被后续的指令拿来作为程序判断和跳转的依据。

IPSR(中断程序状态寄存器)

从图中可以看到他的高位是Reserve的,我们主要读到的是后面的这几位,这几位保留的是当发生中断的时候,发生异常的时候,这个中断的中断号。

EPSR(可执行程序状态寄存器)

这一位能读的只是T这一位,来记录单片机是否发生了异常或者是否发生了中断。

PRIMASK寄存器

在这里插入图片描述
由图中可以看到,这是一个只有一位可读的寄存器,当他置1时,就关闭掉了所有的可屏蔽的异常,换句话说,他能够控制所有中断的使能或者关闭,是中断的总开关。

CONTROL 寄存器

在这里插入图片描述
这个寄存器有两个比特可以使用,分别是nPRIV和SPSEL,对于nPRIV来说,它决定了CPU是工作在用户态还是特权态,在M0和M0+这两种核上,CPU是不支持这两种状态的。对于SPSEL来说,它的功能是规定了单片机所使用的堆栈是主堆栈还是进程堆栈。对于主对栈指针和进程堆栈指针来说,当我们不使用操作系统的时候,它只有一个,当我们使用操作系统的时候,堆栈指针就出现主堆栈指针和进程堆栈指针,他们分别指向内存的两个不同的区域,因此这两个区域一个给我们的操作系统的内核来使用,一个区域给用户程序来使用,也就是任务来使用,这样当任务跑飞的时候,对堆栈使用出现问题的时候,不会使得整个操作系统崩溃。

CPU的工作状态

在这里插入图片描述
上图表示了CPU的工作状态,对于M0 和M0+的CPU来讲,它的工作模式只有图中左边的那一列。

线程模式

它工作在跑普通的程序,我们称是程序的线程模式,也就是说,当单片机在跑我们所写的main函数或者是main函数所调用的子函数的时候,我们认为是工作在线程模式之下的。

handler模式

handler它对应的是当它发生中断的时候,去响应中断的那个状态

特权级和用户级

当使用的是M3或者M4的CPU的时候,为了考虑实时操作系统的使用,考虑到实时操作系统内核和用户程序的区分,因此出现了用户态和特权态。用户态就是操作系统的事件运行所在的状态,而特权态就是运行操作系统内核时单片机所处的状态,而这种特权态到用户态,用户态到特权态的这种跳转,需要一次特殊的中断调用才能得以实现。

不同内核之间的指令集的差异

在这里插入图片描述
从图中我们可以看到最中间的绿色的指令集是ARM Cortex M0和M1的指令集,蓝色的区域是ARM Cortex M3的指令,紫色的是ARM Cortex M4的指令集,粉色的是ARM Cortex M4F的指令集,我们可以看到这些指令集是向下兼容的。

ARM架构单片机运行详细例子

例子中涉及到的汇编指令:

  • NOP:不做任何操作,不做任何事
  • MOVS:移动数据,更新APSR寄存器
  • PUSH:将寄存器中的数据压入堆栈
  • POP:将数据从堆栈中弹出并放入寄存器
  • BL:跳转到子函数,跟新LR寄存器
  • BX:从子函数中返回

要执行的指令

第一步运行

在这里插入图片描述
蓝色箭头指向的是当前已经执行完了的指令,经过这条指令,NOP,什么也没做,但是相应的PC指针要发生一定的变化,PC指针代表的是指令中即将运行的下一条指令的地址。因此涉及到的变量变化为:
SP = 0x20002ff8
PC = 0x00000804
r4 = xx
r5 = xx
LR = xx
内存空间的变化如下图所示:
在这里插入图片描述

第二步运行

在这里插入图片描述
这条指令的意思是将18这个十进制数放入r4寄存器中,执行这条指令之后,所涉及的相关变量有了如下变化:
SP = 0x2000 2ff8
PC = 0x0000 0806
r4 = 0x0000 0012(12是十进制18的十六进制转换)
r5 = xx
LR = xx

第三步运行

在这里插入图片描述
这条指令的意思是将十进制52复制给r5这个寄存器,指令执行完毕后设计的相关变量变化如下:
SP = 0x2000 2ff8
PC = 0x0000 0808
r4 = 0x0000 0012
r5 = 0x0000 0034(十进制的52)
对应的内存空间没有发生改变。

第四步运行

在这里插入图片描述
这条语句的意思是将r4寄存器的值进行压栈,执行这个操作后涉及的变量发生了如下的变化:
SP = 0x2000 2ff4
PC = 0x0000 080a
r4 = 0x0000 0012
r5 = 0x0000 0034
LR = xx
我们可以看到SP指针的值由于堆栈有新值得压入而减小了4个字节的大小,对应的内存空间发生了如下的变化:

在这里插入图片描述

第五步运行

在这里插入图片描述
将r5寄存器的值压入堆栈,相应涉及到的变量发生了如下的变化:
SP = 0x2000 2ff0
PC = 0x0000 080c
r4 = 0x0000 0012
r5 = 0x0000 0034
LR = xx
从上述变化的值我们可以看到因为r5寄存器值压入堆栈,从而导致了堆栈寄存器的值又减少了4,对应的内存空间变化如下:
在这里插入图片描述

第六步运行

在这里插入图片描述
第六条指令的意思是跳转到子函数的执行,执行这条指令后,涉及到的变量变化为:
SP = 0x2000 2ff0
PC = 0x0000 0814
r4 = 0x0000 0012
r5 = 0x0000 0811
LR = 0x0000 0811
上述变量的变化,我们可以看到PC指针指向了子函数的第一条指令的地址,相对应的堆栈指针的值没有发生变化,这是因为在ARM32位处理器架构上,当函数发生一级调用的时候,函数的返回地址并不进行压栈,而是将返回地址保存在LR寄存器上,也就是R14寄存器上。因此LR的值发生改变,但是改变的值却是0x0000 0811,但是对于ARM32位的处理器来说,它的地址全是偶数,这是因为ARM 指令的一个向下兼容性,当这个保存的返回地址的最低位是1的时候,函数跳转和任何函数返回的时候,CPU仍然工作在Thumb指令集状态下,而如果变成了0就告诉CPU在返回的时候应该切换到ARM指令集的模式,因为现在使用的是ARM Cortex M的系列,所以函数返回的时候,自动的把最低位置为1,而在返回的时候,这一位被忽略,只取它偶数的部分作为我们的返回地址,存的信息是0811,实际的信息是返回Thumb模式,回到0x0000 0810 这个地址。

第七步运行

在这里插入图片描述
这条指令的作用是什么也没干,涉及到的相关变量发生了如下的变化:
SP = 0x2000 2ff0
PC = 0x0000 0816
r4 = 0x0000 0012
r5 = 0x0000 0034
LR = 0x0000 0811
可以看到上述寄存器中,发生变化的寄存器是PC指针寄存器,寄存器的值变为了子函数的下一条指令。
对应的内存空间没有发生变化。

第八步运行

在这里插入图片描述
这条指令的意思是从子函数中返回,并把LR寄存器中的值赋值给PC指针,因此涉及到的变量发生了如下的变化:
SP = 0x2000 2ff0
PC = 0x0000 0810
r4 = 0x0000 0012
r5 = 0x0000 0034
LR = 0x0000 0811
可以看到此时PC指针的值就是之前放入LR寄存器中的值(去除末尾的1).
对应的内存空间没有发生变化。

第九步运行

在这里插入图片描述
上述的指令的意思是从堆栈中弹出一个数并赋值给r4寄存器,经过这条指令后,涉及到的相关变量发生了如下的变化:
SP = 0x2000 2ff4
PC = 0x0000 0812
r4 = 0x0000 0034
r5 = 0x0000 0034
LR = 0x0000 0811
首先因为堆栈中的数据被弹出,因此堆栈指针要加4,指令运行结束,PC指针的值也要进行更新,而刚才的指令是把堆栈中的值弹出来赋值给r4寄存器,因此r4寄存器的值也进行了更新。
相应的内存空间发生了如下的变化:
在这里插入图片描述

第十步运行

在这里插入图片描述
在这一步指令的作用是堆栈中弹出一个值然后赋值给r5寄存器,所涉及到的变量变化分别为:
SP = 0x2000 2ff0
PC = 0x0000 0814
r4 = 0x0000 0034
r5 = 0x0000 0012
LR = 0x0000 0811
由此可知,当执行最后一条指令时,堆栈中的数据被弹出赋值给r5,PC指针寄存器的值发生变化,SP指针的值加4,对应的内存空间如下图所示:
在这里插入图片描述

总结

回顾上面的十个过程,我们会发现,指令运行结束之后,r4和r5寄存器的值发生了调换,发生调换的原因是因为我们使用了堆栈这样的一种机制,堆栈的特性是先入后出的,因此最开始把r4和r5寄存器的值依次压入堆栈,取的时候的顺序和压入堆栈的顺序却是刚好相反的,因此也就造成了最终的r4和r5寄存器的值发生了调换。

发布了19 篇原创文章 · 获赞 6 · 访问量 1730

猜你喜欢

转载自blog.csdn.net/weixin_42616791/article/details/103333615