Cortex-M3/4 关键特点:
哈佛架构
使用 NVIC 管理中断
32 位寻址,支持 4GB 空间。
Thumb-16/32 指令
寄存器
通用寄存器R0-R12
R13:SP
R14:LR
R15:PC
xPSR 状态寄存器
R13 堆栈指针:同一时刻只能有一个生效
进程堆栈指针PSP:任务使用
主堆栈指针MSP:中断、异常使用
指令集
ARM 指令集
Thumb 指令集
一个新的项目默认是空白的:

从编译结果反汇编:
fromelf --text -a -c --output=编译输出目录\自定义文件名.dis 译输出目录\可执行文件名称.axf
fromelf --text -a -c --output="$L@L.dis" "#L"编译输出目录和可执行文件名称不是随便写的,是项目中配置的,要对应上,否则找不到文件。

因此这里是:fromelf --text -a -c --output=ceu6_test\ceu6_test.dis ceu6_test\ceu6_test.axf

Flash 中保存的是机器码。
从 Keil 中打开项目所在的文件夹:
随便打开文件,然后文件名上右键:

反汇编也可以用:
fromelf --text -c -o "$L@L.asm" "#L"生成.asm文件,这种比较精简,没.dis文件丰富。??加上-a不和.dis一样了吗,待验证。
常用汇编指令
赋值
MOV R0,#666R0 寄存器的值设置为
MOVS
读写 LDR、STR
LDR 用于读,STR 用于写。
这样记: Load 加载,Store 存储,后面有个R表示寄存器,即读写寄存器。
数据传输三要素:源 目标 长度
LDR 数据是向左流动的 <---
MOV r1, #0x20000000
LDR r0, [r1]
LDR R0,[R1, #2] ; 源的地址就是R1寄存器中的地址 + 2LDR 伪指令
STR 数据是向右流动的 -->
STR 同样STR也有伪指令
长度体现在指令上,LDR、STR 的单位都是 4 个字节,LDRH、STRH 表示读写 2 个字节,LDRB、STRB 表示读写一个字节。
LDRD double,即一次性操作两个 LDR
汇编中一个函数的写法:
rw_ram PROC ; 标记函数开始
EXPORT rw_ram [WEAK] ; 导出函数,这样 C 语言中才能访问
MOV r1, #0x20000000
LDR r0, [r1]
BX LR
ENDP ; 标记函数结束验证一下:

验证 LDRB:

由于 Cortex-M3 是小端序,因此读的是最低地址的字节。
验证STR:

写入的是4个字节,默认是0,使用STRB就不会影响其它字节了。
出入栈
入栈就是
出栈就是
要成对出现。
加减法
比较 CMP
按位与、或运算
跳转 BL
跳转命令 修改PC寄存器,跳转到其它地方执行。
BL:1、将下一条指令的地址赋值到LR寄存器,并且bit0设置为1,表示是Thrumb指令 2、跳转
B、BL 后面其实不是完整的地址,只是方便开发人员调试,观看。
其实是偏移量,因为存不了完整的地址。
BL 用于函数之间这种需要返回的跳转
B 用于跳转,不需要返回,因此用不到LR寄存器
BX R0 跳到 R0 所表示的那个地址。
BLX RO 1、让LR=下一跳指令的地址 2、跳转到R0表示的地址
这个视频中也讲了MOV不能是特殊的数的原因
伪指令,不用我们自己去手动定义了。
函数调用过程
ARM架构过程调用标准 AAPCS:
使用 R0-R3 传递参数和返回值,返回值使用R0传递
C函数执行前后,R4-R11 的值不会被修改
函数调用行为:
调用者:由于 R0-R3 寄存器很可能被被调用者修改,所以标准要求R0-R3 由调用者负责保存,还有 R12、S0-S15。
被调用者:R4-R11 被调用者一般用不到的,所以标准规定了由被调用者保存,如果用到了,需要入栈保存,结束时出栈,其他的还有 S16-S31
最大支持n个参数
在main函数中调用 add 函数
int Add(volatile int a, volatile int b)
{
volatile int sum;
sum = a + b;
return sum;
}
void main() {
...
volatile int sum_main = Add(100, 1);
...
}分析汇编代码:

可以看出寄存器顺序是参数从左到右依次增大的

i.Add
Add
0x080002f4: b503 .. PUSH {r0,r1,lr} ; 入栈,保存参数、跳转地址
0x080002f6: b081 .. SUB sp,sp,#4 ; 栈指针减4,分配空间
0x080002f8: e9dd0101 .... LDRD r0,r1,[sp,#4] ; 读参数到寄存器中
0x080002fc: 4408 .D ADD r0,r0,r1 ; 加法运算
0x080002fe: 9000 .. STR r0,[sp,#0] ; 返回值存入R0
0x08000300: bd0e .. POP {r1-r3,pc} ; 出栈,LR赋值给PC进行跳转,这里R1-R3没有实际的意义,只是用于占位堆栈的变化

void BB(volatile int d, volatile char val)
{
volatile char buf[101];
buf[d] = val;
} i.BB
BB
0x08000302: b503 .. PUSH {r0,r1,lr}
0x08000304: b09a .. SUB sp,sp,#0x68 ; 分配内存就是减堆栈指针,因为要对齐所以不是0x65
0x08000306: f89d006c ..l. LDRB r0,[sp,#0x6c]
0x0800030a: 9a1a .. LDR r2,[sp,#0x68]
0x0800030c: f80d0002 .... STRB r0,[sp,r2]
0x08000310: b01c .. ADD sp,sp,#0x70
0x08000312: bd00 .. POP {pc}内存对齐到4的整数
i.CC
CC
0x08000316: b508 .. PUSH {r3,lr} ; R3可以直接用,但是这里入栈,是为了变量分配空间
0x08000318: 4408 .D ADD r0,r0,r1
0x0800031a: 4410 .D ADD r0,r0,r2
0x0800031c: 4418 .D ADD r0,r0,r3
0x0800031e: 9000 .. STR r0,[sp,#0]
0x08000320: bd08 .. POP {r3,pc}