freertos入门—ARM架构简明教程
1 硬件架构
我们在组装电脑时会得到一个主板,然后我们去买CPU,将CPU插入主板,然后去买内存条,将内存条也插入主板,接着我们还需要将硬盘通过接线也连接上电脑。如下图所示:
对于我们主板,我们CPU、内存条、硬盘它们是离散的,它们需要通过主板组装在一起。对于我们单片机,它被称为SOC(System on Chip),它是指在一个芯片上具有一个完整的或者相对完整的系统,比如说一款ARM芯片,在这一款芯片上它包含CPU、内存、Flash。Flash负责保存我们的程序,CPU负责运行程序、程序运行过程中我们还需要用到内存。内存没有计算功能,只负责将数据读取与写入,所有的计算都是在CPU内部执行的,如下图所示:
ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:
- 对内存只有读、写指令
- 对于数据的运算是在CPU内部实现
- 使用RISC指令的CPU复杂度小一点,易于设计
对于下图所示的乘法运算a = a * b,在RISC中要使用4条汇编指令:
- 读内存a
- 读内存b
- 计算a * b
- 把结果写入内存
那么,在CPU内部,用什么去保存 a 、b 、a * b 呢?无论是 cortex-M3/M4, 还是 cortex-A7 , CPU内部都有 R0、R1、…、R15寄存器,他们可以用来暂存数据。
对于 R13、R14、R15,还另有用途:
1. R13 : 别名SP(Stack Pointer),栈指针
2. R14 : 别名LR(Link Register),用来保存返回地址
3. R15 : 别名PC(Program Counter),程序计数器,表示当前指令地址,写入新值即可跳转
因此可以将 a = a + b 运算的流程写得更加具体些,如下图所示:
2 汇编指令
- 读内存:Load
# 示例
LDR R0,[R1,#4] ; 读地址“R1+4”,得到的4字节数据存入R0
- 写内存:Stroe
#示例
STR R0,[R1,#4] ; 把R0的4字节数据写入地址“R1+4”
- 加减
ADD R0,R1,R2 ; R0=R1+R2
ADD R0,R0,#1 ; R0=R0+1
SUB R0,R1,R2 ; R0=R1-R2
SUB R0,R0,#1 ; R0=R0-1
- 比较
CMP R0,R1 ; 结果保存在PSR(程序状态寄存器)
- 跳转
B main ; Branch,直接跳转
BL main ; Branch and Link,先把返回地址保存在LR寄存器里再跳转
3 汇编实例
将下面的c函数代码放入keil工程中:
int add(volatile int a,volatile int b)
{
volatile int sum;
sum = a + b;
return sum;
}
我们若是想要查看反汇编码,我们需要在keil中配置:
为了方便复制,制作反汇编的指令如下:
fromelf --text -a -c --output=test.dis xxx.axf
编译后可以看到生成反汇编文件:
将 OLED_Test 的c代码与反汇编代码放到一起进行比较:
上述的 cnt = add(cnt,1) 主要实现两个作用,一是将数据写入寄存器,而是实现跳转指令。当我们实现跳转指令,我们就会去执行 add 函数,接下来我们看看add函数如何执行:
接下来,我们分析下CPU是如何从 flash 中读取汇编指令并运行的:
当执行BL add指令时,程序会跳转到上述Flash中,CPU读取addr中的机器码并执行,那么这些机器码有何具体含义,接下来我们进行分析机器码所对应的汇编指令:
PUSH指令是写内存,也就是STR指令的变种,它将r0,r1,lr寄存器的值写入到栈中并且调整栈的位置。注意:在栈中保存的是这些寄存器的值,r1 = 1,r0 = cnt,lr = 返回地址。
SUB指令将sp的栈指针位置减少4,即空出一块空间。
LDRD读取 [sp,#4] 地址的数据写入r0,r1,即向寄存器 r0 写入cnt,想 r1 写入1。ADD指令将 r0 寄存器与 r1 寄存器中的值相加并写入 r0。
接着将 r0 的值写入 [sp,#0] 。
接着我们使用POP指令将栈中的数值写入R1R2R3,仅仅是为了后面把LR的值POP到PC寄存器,仅仅是为了恢复栈。我们用了一个例子讲解了下汇编代码的运行流程,实际上一个c代码它的本质就是读内存写内存加加减减跳转。