Assembly
汇编语法。
顺序结构
label ; 可省略,用于跳转到此位置
助记符 operand1, operand2, … ; Comments
MOV r1, #0x01 ; 数据0x01放入r1
MOV r1, #'A' ; 数据A的ascii码放入r1
MOV R0, R1 ; move R1 into R0
MOVS R0, R1 ; move R1 into R0, 并且更新APSR的状态
LDR R1, [R0] ; R0存的是一个地址值如0x2000 0000, 这个指令是取出R0代表的地址中的数据存入R1
STR R1, [R0] ; 写回去
LDR R0, =0x12345678 ; Set R0 to 0x12345678
; 等效于:
; LDR R0, [PC, #offset]
; ...
; DCD 0x12345678
; 也就是先在文档末尾的一条指令里写入数据0x12345678,然后编译器自动计算PC+多少offset到达DCD的位置,把其值返给R0
; DCD是声明一个字 32bit,DCB是声明一个Byte
; 如果多个数值的声明可以用标签声明
LDR R3, =MY_NUMBER
ALIGN 4 ; 字要先用这个声明,代表停止长度
MY_NUMBER DCD 0x2000ABCC
HELLO_TEXT DCB “Hello\n”, 0 ; Null terminated string
LDRB R1, [R0] ; B: 只写8位,就是说R0地址处的数据写入R1后,R1高24位清零
SDRH R1, [R0] ; H: 只写16位
LDRSH R1, [R0] ; 视作signed有符号数,写16位
LDRB R0, [R1, #0x3] ; 从R1+3读取一个字节给R0
LDR R3, [R0, R2, LSL #2] ; 从R0+(R2<<2)读取一个字节给R3
LDR R0, [R1], #4 ; 赋完值后,令R1=R1+4
ADD R0, R0, R1
ADDS R0, R0, R1 ; 加完更新APSR状态,比如有溢出或者进位则更新
ADC R0, R1, R2 ; R1+R2还要+APSR的carry位
; SUB SBC类似
MUL R0, R1, R2
UDIV R0, R1, R2
SDIV R0, R1, R2 ; signed
例题:应该是因为有可能减成负的所以signed
指令有1字长,半字长的。hw1是指明功能用的,hw2是一些拓展比如立即数。
地址从低到高分别是:4F F0 0A 00 0A 68 10 44……
PC每次取到半个字 hw,就+2B跳转到下一个hw。
选择结构
CMP R0, R1 ; 相当于if,比较后更新APSR。EQ= LT< GT> LE<= GE >=
BEQ BRANCH_1 ; B是跳转,BL是跳转到函数执行完后返回,BX是根据地址最低位判断目标地址是arm还是thumb在决定跳转到整字还是半字。bx操作数不能是立即数,必须是寄存器
B BRANCH_2
BRANCH_1
...
B IFEND ; 不写这个就继续执行BRANCH_2了,像switch的break
BRANCH_2
...
B IFEND
循环结构
WHILE_BEGIN
UDIV R2, R0, R1 ; R2 = n / x
MUL R3, R2, R1 ; R3 = R2 * x
CMP R0, R3 ; n == (n / x) * x
BEQ WHILE_END
SUBS R1, R1, #1 ; x--
B WHILE_BEGIN ; loop back
WHILE_END
Stack
内存中有一片内存空间类似栈的数据结构。SP指针指向栈顶。
这个栈地址是从低到高的,也就是存入数据 SP++,取出数据 SP–,类似一个翻转过来的,倒着的书堆。
满堆栈:sp指针指向最后一个栈顶数据。
空堆栈:指向最后一个数据的下一个要放入数据的空位置。
我们的课程中使用空堆栈,指向下一个空位置,存数据就先存入再SP+4,取数据就先SP-4再出栈。不过这两条指令都不需要我们手动执行,有专门的指令:
PUSH {R0, R4-R7} ; Push r0, r4, r5, r6, r7
POP {R2-R3, R5} ; Pop to r2, r3, r5。入栈出栈顺序不是按照书写顺序而是自动根据寄存器地址,高地址值给高地址寄存器
存入5个数据和取出3个数据。
Functions
BL先保存当前PC值到LR,然后PC跳转到函数地址,
BX LR跳转到LR中的地址用于函数返回。
Architecture Procedure Call Standard (AAPCS) :规范定义哪些寄存器主函数和函数通用,哪些是独有的。
arm AAPCS规定:r0-r3是通用寄存器(类似全局变量),但main和函数的R4 – R8, R10-R11不通用(类似临时变量,到了函数里这些值就变了,不是原函数的),要压入栈保存。函数调用和返回的时候要保存和恢复通用寄存器值。这些由调用原函数的子函数 callee-procedure 执行。
简单的参数的函数调用:传参给R0-R3作为函数参数,R4-R11压入栈,然后跳转到函数处。
Program Memory Use
ROM里都是只读数据,比如常量常数。
const, static, volatile
貌似是不会过多涉及具体代码实现的部分,就先简单介绍一下了。
const 就是定义常量变量,定义后无法再次修改。
static 通常定义静态函数,静态函数里的值是通用的,也就是每次调用该函数其值都是接着上次调用该函数的值继续。
volatile:一个在嵌入式里挺重要的东西,软考题里出现过几次。大概就是禁止编译器优化该变量来防止不必要的错误。
比如编译器优化num变量,这样每次修改num变量的值的时候都不会立刻写入内存中,可能会先把修改时的值写入寄存器,函数返回时写回内存。
现在比如我们在main中num+=5, 修改值后的num暂时存在寄存器里。然后我们调用中断,从内存中读取当前num的值并+1.但是内存中值还没改,还是原值。返回后,main再把自己手中的num值写回内存,最后内存中num值只+5,而不是我们期望的+6.
volatile 声明后的变量不会做这样的优化,值改变了就立刻写回内存,虽然可能效率低但是安全。