第一个LED程序(汇编篇)
1、简单的汇编知识
(1)LDR : 读内存
LDR R0, [R1] : 假设R1的值为x,读取地址x上的数据(4字节),保存到R0中。所以[ ]是去读取R1保存地址所指向的内存
(2)STR : 写内存命令
STR R0, [R1] : 假设R1的值为x,把R0的值写到地址x (4字节)
(3)B : 跳转
(4)MOV
MOV R0, R1 : 把 R1的值赋给R0
也可以, MOV R0, #0x100 也就是R0等于0x100
(5)LDR R0, =0x12345678 (这里可以是任意值) 也就是R0等于0x12345678
这是一条伪指令,最终都会被拆分为几条指令。
另外,MOV R0, 0x12345678 是错误的,因为一条32位的ARM指令存MOV和R0,剩下的空间就不够存0x12345678了
(6)add r3, r3, #80
r3 + 80,然后值保存到r3寄存器中
(7)sub r0, r1, r2
就是 r0 = r1 - r2
(8)bl : branch and link
也就是调到某条指令,并且把返回地址(下一条指令的地址)保存到lr寄存器里面
(9)ldm : 读内存,写入多个寄存器。之前的ldr是只能读一个寄存器
(10)stm : 把多个寄存器的值写入内存。str是每次只能写一个寄存器
(11)
再来看一下 ldmia 和 stmdb指令,ARM手册中有这句描述:
ia ib da db,意思分别是过后增加(Increment After)、预先增加(Increment Before)、过后减少(Decrement After)、预先减少(Decrement Before)。
来看一个例子:
stmdb sp!, {fp, ip, lr, pc}
stmdb的意思就是先减少后写入。假设sp = 4096,那么先减操作sp = 4096 - 4 = 4092,然后再把{}中的寄存器的值写入内存。那么到底先写哪个寄存器的值到内存呢?
有一个原则是:高编号寄存器,存在高地址。其中fp = R11, ip = R12, lr = R14, pc = R15。
所以会先把pc的值写到内存地址4092-4095中。以此类推,直到sp = 4080。那么sp!中的感叹号的意思是,sp等于最终的值,也就是4080,如果没有感叹号,sp还是4096
2、了解一下ARM的寄存器:
User 模式 SVC 模式 IRQ 模式 FIQ 模式 APCS
R0 ------- R0 ------- R0 ------- R0 a1
R1 ------- R1 ------- R1 ------- R1 a2
R2 ------- R2 ------- R2 ------- R2 a3
R3 ------- R3 ------- R3 ------- R3 a4
R4 ------- R4 ------- R4 ------- R4 v1
R5 ------- R5 ------- R5 ------- R5 v2
R6 ------- R6 ------- R6 ------- R6 v3
R7 ------- R7 ------- R7 ------- R7 v4
R8 ------- R8 ------- R8 R8_fiq v5
R9 ------- R9 ------- R9 R9_fiq v6
R10 ------ R10 ------ R10 R10_fiq sl
R11 ------ R11 ------ R11 R11_fiq fp
R12 ------ R12 ------ R12 R12_fiq ip
R13 R13_svc R13_irq R13_fiq sp
R14 R14_svc R14_irq R14_fiq lr
------------- R15 / PC ------------- pc
(1)其中pc是program counter的简写,也就是程序计数器。当把地址值写入到pc(R15)寄存器,程序就会跳到相应的地址
(2)lr寄存器(R14)是Link Register的简写,用于保存返回地址,比如函数调用结束后返回的地址就是保存在里面
(3)sp寄存器(R13)是Stack Pointer的简写,栈指针
3、简单的led汇编程序
1 /* 2 点亮LED:GPF4 3 */ 4 5 .text 6 .global _start 7 8 _start: 9 10 11 /*配置GPF4为输出引脚:把0x100写入到地址0x56000050*/ 12 ldr r1, =0x56000050 13 ldr r0, =0x100 14 str r0, [r1] 15 16 /*设置GPF4输出为低电平:把0写入到地址0x56000054*/ 17 ldr r1, =0x56000054 18 ldr r0, =0 19 str r0, [r1] 20 21 /*死循环*/ 22 halt: 23 b halt
4、Makefile的书写
all: arm-linux-gcc -c -o led_on.o led_on.S arm-linux-ld -Ttext 0 led_on.o -o led_on.elf arm-linux-objcopy -O binary -S led_on.elf led_on.bin #编译出来可烧写进板子的二进制 arm-linux-objdump -D led_on.elf > led_on.dis #反汇编 clean: rm *.elf *.o *.bin
5、反汇编出来的代码
led_on.elf: file format elf32-littlearm Disassembly of section .text: 00000000 <_start>: 0: e59f1014 ldr r1, [pc, #20] ; 1c <.text+0x1c> 4: e3a00c01 mov r0, #256 ; 0x100 8: e5810000 str r0, [r1] c: e59f100c ldr r1, [pc, #12] ; 20 <.text+0x20> 10: e3a00000 mov r0, #0 ; 0x0 14: e5810000 str r0, [r1] 00000018 <halt>: 18: eafffffe b 18 <halt> 1c: 56000050 undefined 20: 56000054 undefined
我们可以看到反汇编出来的汇编代码的第一条指令是:
ldr r1, [pc, #20] ; 1c <.text+0x1c>
那么我们pc的值是多少呢?答案是:pc = 当前指令 + 8
在ARM系统中,指令都是流水线方式执行的,当执行地址A的指令,已经在对地址A+4的指令进行译码,且已经读取地址A+8的指令,所以pc的值就是A+8了
所以回到我们的指令,r1 = pc + 20 = 0 + 8 + 20 = 28 = 0x1c
所以就是把0x1c地址的值写到r1中,所以r1 = 0x56000050