第一个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

 

posted @ 2018-03-21 23:42  伊斯科明  阅读(306)  评论(0编辑  收藏  举报