在 Cortex-M3 中,有专门的指令负责堆栈操作——PUSH 和 POP。它俩的汇编语言语法如下例所
演示
PUSH {R0} ; *(--R13)=R0。R13 是 long*的指针
POP {R0} ; R0= *R13++
PUSH 和 POP 还能一次操作多个寄存器
PUSH {R0-R7, R12, R14} ; 保存寄存器列表
… ; 执行处理
POP {R0-R7, R12, R14} ; 恢复寄存器列表
BX R14 ; 返回到主调函数
寄存器的 PUSH 和 POP 操作永远都是 4 字节对齐的——也就是说他们的地址必须是0x4,0x8,0xc,……。事实上,R13 的最低两位被硬线连接到 0,并且总是读出 0(Read As Zero)。
R14 是连接寄存器(LR)
main ;主程序
…
BL function1 ; 使用“分支并连接”指令呼叫 function1
; PC= function1,并且 LR=main 的下一条指令地址
… Function1
… ; function1 的代码
BX LR ; 函数返回(如果 function1 要使用 LR,必须在使用前 PUSH,
; 否则返回时程序就可能跑飞了——译注)
CM3 内部使用了指令流水线,读 PC 时返回的值是当前指令的地址+4。比如说:
0x1000: MOV R0, PC ; R0 = 0x1004
PUSH {R0-R3, LR}
POP {R0-R3, PC}
请注意:POP 的最后一个寄存器是 PC,并不是先前 PUSH 的 LR。这其实是一个返回的小技巧。与其按部就班地把先前 LR 的值弹回 LR,再复制给 PC 来返回;不如干脆绕过 LR,直接传给 PC!那不怕 LR 的值没有被恢复吗?不怕,因为 LR 在子程序调用中的唯一用处,就是在返回时提供返回地址。因此,在返回后,先前保存的返回地址就没有利用价值了,所以只要PC 得到了正确的值,不恢复也没关系。
标号
操作码 操作数 1, 操作数 2, … ;注释
立即数必须以“#”开头,如
MOV R0, #0x12 ; R0写入0x12
MOV R1, #’A’ ; R1写入字母 A 的 ASCII 码值
还可以使用 EQU 指示字来定义常数,然后在代码中使用它们,例如:
NVIC_IRQ_SETEN0 EQU 0xE000E100 ; 注意:常数定义必须顶格写
NVIC_IRQ0_ENABLE EQU 0x1
…
LDR R0, =NVIC_IRQ_SETEN0 ;在这里的 LDR 是个伪指令,它会被汇编器转换成
一条“相对 PC 的加载指令”
MOV R1, #NVIC_IRQ0_ENABLE ; 把立即数传送到 R1 中
STR R1, [R0] ; *R0=R1,执行完此指令后 IRQ #0 被使能。
LDR R3, =MY_NUMBER ; R3= MY_NUMBER
LDR R4, [R3] ; R4= *R3
…
LDR R0, =HELLO_TEXT ; R0= HELLO_TEXT
BL PrintText ; 呼叫 PrintText 以显示字符串,R0 传递参数
…