新手友好 | 一文了解ARM内部架构

科技   2024-11-09 17:20   湖北  

一、地址空间


1.1ARM中访问寄存器方式


ARM中怎么访问寄存器?就像访问内存一样:怎么访问寄存器?用指针:

int   a;  unsigned int *p  = &a;   // p等于“a的地址”
*p = val;   // 写这个地址,就是写aval = *p;   // 读这个地址,就是读a
unsigned int *p  = 0x40010800; // p等于某个寄存器的地址
*p = val;   // 写这个地址,也就是写这个寄存器val = *p;  // 读寄存器


1.2 ARM与X86的区别


在ARM、CPU看来,内存、IO的操作是一样的:



CPU发出的地址可以让其直接访问到对应的外设,这些外设的地址属于CPU的地址空间,但在上图中CPU发出的地址不能到达Flash,CPU若想访问Flash,必须通过EMMC控制器,Flash属于另一个地址空间,分为几种家族关系,即只能隔代访问,不能越代访问。


在X86架构中内存和 IO 是分开的:



1.3 RISC与CISC


1.3.1 RISC


ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:

① 对内存只有读、写指令 

② 对于数据的运算是在CPU内部实现

③ 使用RISC指令的CPU复杂度小一点,易于设计



对于上图所示的乘法运算a = a * b,在RISC中要使用4条汇编指令:

① 读内存 a

② 读内存 b

③ 计算 a*b

④ 把结果写入内存


1.3.2 CISC


x86属于复杂指令集计算机(CISC:Complex Instruction Set Computing),它所用的指令比较复杂,比如某些复杂的指令,它是通过“微程序”来实现的。


比如执行乘法指令时,实际上会去执行一个“微程序”,在“微程序”里,一样是去执行这4个操作:

① 读内存a

② 读内存b

③ 计算a*b

④ 把结果写入内存

上图操作对于程序员来说,他看不到“微程序”,他好像用一条指令就搞定了这一切!


1.3.3 RISC和CISC比较


  • CISC的指令能力强,但多数指令使用率低却增加了CPU的复杂度,指令是可变长格式;

  • RISC的指令大部分为单周期指令,指令长度固定,操作寄存器,对于内存只有Load/Store操作;

  • CISC支持多种寻址方式;RISC支持多种寻址方式;

  • CISC通过微程序控制技术实现;

  • RISC增加了通用寄存器,硬布线逻辑控制为主,采用流水线;

  • CISC的研制周期长;

  • RISC优化编译,有效支持高级语言;


二、ARM内部寄存器


2.1 CPU内部寄存器


无论是cortex-M3/M4,还是cortex-A7,CPU内部都有R0、R1、……、R15寄存器;它们可以用来“暂存”数据。



  • 寄存器R0--R12为通用目的寄存器,前8个(RO--R7)也被称作低寄存器。由于指令中可用的空间有限,许多16位指令只能访问低寄存器。高寄存器(R8--R12)则可以用于32位指令和几个16位指令,如MOV(move)。RO--R12的初始值是未定义的。


  • R13为栈指针,可通过PUSH和POP操作实现栈存储的访问。物理上存在两个栈指针:主栈指针(MSP,有些ARM文献也称其为SP_main)为默认的栈指针,在复位后或处理器处于处理模式时,其会被处理器选择使用。另外一个栈指针名为进程栈指针(PSP,有些ARM文献也称其为SP_process),其只能用于线程模式。栈指针的选择由特殊寄存器CONTROL决定,4.2.3节中有对该寄存器的描述。对于一般的程序,这两个寄存器只会有一个可见。MSP和PSP都是32位的,不过指针(MSP或PSP)的最低两位总是为0,对这两位的写操作不起作用。对于ARMCortex-M处理器,PUSH和POP总是32位的,栈操作的地址也必须对齐到32位的字边界上。


  • R14也被称作链接寄存器(LR),用于函数或子程序调用时返回地址的保存(用来保存返回地址)。在函数或子程序结束时,程序控制可以通过将LR的数值加载程序计数器(PC)中返回调用程序处并继续执行。当执行了函数或子程序调用后,LR的数值会自动更新。若某函数需要调用另外一个函数或子程序,则它需要首先将LR的数值保存在栈中,否则,当执行了函数调用后,R的当前值会丢失。


  • R15为程序计数器(PC),是可读可写的,读操作返回当前指令地址加4(由于设计的流水线特性及同ARM7TDMI处理器兼容的需要)。写PC(例如,使用数据传输/处理指令)会引起跳转操作。(表示当前指令地址,写入新值即可跳转)


2.2 CPU内部寄存器分类


cortex-M3/M4:



作为对比,cortex-A7也是类似的:



2.3 例子


比较两个数时,不同的CPU寄存器是怎么处理的:对于cortex-M3/M4,还要一个Program Status Register



对于cortex-M3/M4来说,xPSR实际上对应3个寄存器:


① APSR:Application PSR,应用PSR

② IPSR:Interrupt PSR,中断PSR

③ EPSR:Exectution PSR,执行PSR


这3个寄存器的含义如下图所示:



这3个寄存器,可以单独访问:

MRS  R0, APSR  ;读APSRMRS  R0, IPSR    ;读IPSRMSR  APSR, R0   ;写APSR


这3个寄存器,也可以一次性访问:

MRS  R0,  PSR  ; 读组合程序状态MSR  PSR, R0   ; 写组合程序状态


所谓组合程序状态,如下图所示:



对于cortex-A7,还要一个Current Program Status Register



三、ARM汇编


3.1 概述


一开始,ARM公司发布两类指令集:


① ARM指令集,这是32位的,每条指令占据32位,高效,但是太占空间


② Thumb指令集,这是16位的,每条指令占据16位,节省空间


要节省空间时用Thumb指令,要效率时用ARM指令。


一个CPU既可以运行Thumb指令,也能运行ARM指令。


怎么区分当前指令是Thumb还是ARM指令呢?


程序状态寄存器中有一位,名为“T”,它等于1时表示当前运行的是Thumb指令。



我们可以往PC寄存器里写入函数A或B的地址,就可以调用A或B,但是怎么让CPU在执行A函数时进入Thumb状态,在执行B函数时进入ARM状态?


调用函数A时,让PC寄存器的BIT0等于1,即:PC=函数A地址+(1<<0);


调用函数B时,让PC寄存器的BIT0等于0:,即:PC=函数B地址


日常工作中,只需要这么几条汇编指令,从名字就可以猜出含义:

MOVLDR/STRLDM/STMAND/ORADD/SUBB/BLDCDADR/LDRCMP


3.2 汇编指令格式


参考《DEN0013D_cortex_a_series_PG.pdf》P70、《ARM Cortex-M3与Cortex-M4权威指南.pdf》第5章



汇编指令可以分为几大类:数据处理、内存访问、跳转、饱和运算、其他指令。以“数据处理”指令为例,UAL汇编格式为:

Operation{cond}iS} Rd, Rn, Operand2  


  • Operation表示各类汇编指令,比如ADD、MOV;

  • cond表示conditon,即该指令执行的条件;

  • S表示该指令执行后,会去修改程序状态寄存器;

  • Rd为目的寄存器,用来存储运算的结果;

  • Rn、Operand2是两个源操作数 Operation表示各类汇编指令,比如ADD、MOV;如下图:



cond有多种取值,如下:



3.3 分支/跳转指令


参考《DEN0013D_cortex_a_series_PG.pdf》P327、P328、P329

核心指令是B、BL:B:Branch,跳转BL:Branch with Link,跳转前先把返回地址保持在LR寄存器中BX:Branch and eXchange,根据跳转地址的BIT0切换为ARM或Thumb状态(0:ARM状态,1:Thumb状态)BLX:Branch with Link and eXchange,根据跳转地址的BIT0切换为ARM或Thumb状态(0:ARM状态,1:Thumb状态)



3.4 立即数


这样一条指令: MOV   R0, #VAL 意图是把VAL这个值存入R0寄存器。


问:VAL可以是任意值吗?

答:不可以,必须是立即数。


问:为什么?

答:假设VAL可以是任意数,”MOV R0, #VAL”本身是16位或32位,哪来的空间保存任意数值的VAL?


所以,VAL必须符合某些规定。



3.5 LDR伪指令


去判断一个VAL是否立即数,麻烦!


并且我就是想把任意数值赋给R0,怎么办?


可以使用伪指令: LDR   R0,  =VAL 


“伪指令”,就是假的、不存在的指令。


注意LDR作为“伪指令”时,指令中有一个“=”,否则它就是真实的LDR(load regisgter)指令了。


有以下两种情况(分为立即数和非立即数):


编译器会把“伪指令”替换成真实的指令,比如:

LDR  R0,  =0x12    


0x12是立即数,那么替换为:

MOV  R0,  #0x12
LDR  R0, =0x12345678


0x12345678不是立即数,那么替换为:

LDR  R0, [PC, #offset]          // 2. 使用Load Register读内存指令读出值,offset是链接程序时确定的……Label  DCD  0x12345678    // 1. 编译器在程序某个地方保存有这个值


3.6 ADR伪指令


ADR的意思是:address,用来读某个标号的地址



示例:

ADR  R0,  Loop
Loop    ADD  R0, R0, #1


它是“伪指令”,会被转换成某条真实的指令,比如:

ADD R0, PC, #val   ; val在链接时确定Loop    ADD  R0, R0, #1


3.7 ARM编译器与GCC编译器语法差异



四、ARM汇编模拟器

4.1 模拟器VisUAL


VisUAL是一款ARM汇编模拟器,

下载地址:

https://salmanarif.bitbucket.io/visual/downloads.html

使用方法:

https://salmanarif.bitbucket.io/visual/user_guide/index.html 


VisUAL模拟的ARM板子如下图所示,它没有模拟外设,仅仅模拟了CPU、ROM、RAM


  • 红色区域是ROM,不能读不能写,只能运行其中的程序;

  • ROM区域本来可以读的,这是VisUAL的局限;

  • RAM区域可读可写



4.2 VisUAL支持的汇编指令


注意:下图中的DCD、FILL、END等是ARM汇编器语法,GCC汇编语法稍有不同,后面会介绍。



4.3 VisUAL设置


基本不需要设置,我也就设置字体大小和背景色:设置大小后,必须回车才其效果。



4.4 编写汇编指令


在左边代码栏中输入汇编代码:



运行:



但在上述汇编程序中,是有一定问题存在的:

① 第一条指令的0x20000不是立即数,修改成立即数;

② 第二条指令的0x21234不是立即数,修改成立即数;



修改错误:

点击“Reset”后修改为:

MOV R0, #0x20000MOV R1, #0x1234



查看指针、内存:



可以使用工具查看内存地址的内存值:



修改上述汇编代码,让R1保存在R0+4的位置,并查看指针示意图和内存图:



我们也可以编写稍微复杂点的汇编程序:


本文共八个章节,由于篇幅较长,更多精彩内容可点击文末左下角“阅读原文”获取噢~


讲师招募


招募要求

完成符合要求的机器人相关视频制作

总时长需达到 3小时以上

视频内容需为精品课程,确保高质量和专业性


讲师奖励

享受课程收入分成

赠送 2门 古月学院在售精品课程(训练营除外)


联系我们

添加工作人员微信:GYH-xiaogu





古月居
专业的ROS机器人知识社区和产业服务平台
 最新文章