芯片框架
本实验开发板所用 ARM 芯片是 S3C2440 芯片,其框架如下:
S3C2440 芯片是从 0 地址开始启动的,有 Nor 启动和 Nand 启动两种启动方式。
Nor 启动时,Nor Flash 的基地址为 0,片内 SDRAM 地址为0x4000 0000 (见芯片手册 Memory Map)。启动后,CPU 从Nor Flash 0 地址读出第 1 个指令(前4字节),执行;CPU 继续读出其它指令执行。
Nand 启动时,片内 4kSDRAM 基地址为 0,Nor Flash 不可访问。S3C2440 芯片硬件会把 Nand Flash 前 4K 内容复制到片内的SDRAM,然后 CPU 从0地址取出第1条指令执行。
原理图确定引脚
第一,从原理图确定 LCD 的引脚。
引脚 nLED1、2、4 输出高电平时灯熄灭,低电平时灯点亮。
GPF 寄存器地址和设置说明:
所以,要控制 LED1 :
- 需要设置 GPFCON[9:8] = 0b01, 把 GPF4 配置为输出引脚。即把 0x100 写入 GPFCON 这个寄存器,地址为0x5600 0050
- 控制 LED 的亮灭,需要设置 GPFDAT[4] = 1 或者0,输出高电平或低电平。即把 0x10 或 0x0 写到地址 0x5600 0054
注: 上面的写法会破坏寄存器的其它位,其它位是控制其它引脚的。但为了让第一个裸板程序尽可能的简单,此实验才简单粗暴的这样处理。实际要用位操作,只设置对应的位!
编写汇编程序
汇编程序 led_on.S 代码如下:
/*
* 点亮LED1: gpf4
*/
.text
.global _start
_start:
/* 配置GPF4为输出引脚
* 把0x100写到地址0x56000050
*/
ldr r1, =0x56000050
ldr r0, =0x100 /* mov r0, #0x100 */
str r0, [r1]
/* 设置GPF4输出低电平
* 把0写到地址0x56000054
*/
ldr r1, =0x56000054
ldr r0, =0 /* mov r0, #0 */
str r0, [r1]
/* 死循环 */
halt:
b halt
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 //生成bin文件
arm-linux-objdump -D led_on.elf > led_on.dis //反汇编
clean:
rm *.bin *.o *.elf *.dis
编译后烧写到开发板上,即可看到LED1亮。
分析反汇编代码
代码中的ldr r1, =0x56000050
这条伪指令的真实指令时什么呢?
可以通过反汇编来查看。生成的 led_on.dis 就是反汇编文件。led_on.dis 如下:
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 :
18: eafffffe b 18
1c: 56000050 undefined
20: 56000054 undefined
//第一列是地址,第二列是机器码,第三列是汇编;
//在反汇编文件里可以看到,ldr r1, =0x56000050 被转换成 ldr r1, [pc, #20], pc+20地址的值为0x56000050,通过这种方式为r1赋值。 对于立即数0x100而言,ldr r0,=0x100即是转换成了mov r0,#256;
上面的反汇编程序解析如下:
0: e59f1014 ldr r1, [pc, #20] ; 1c <.text+0x1c>
// r1 = [pc + 20] = [0 + 8 + 20] = [0x1c] = 0x56000050
4: e3a00c01 mov r0, #256 ; 0x100
8: e5810000 str r0, [r1]
// 把r0 即0x100写r1对应的内存,即0x100写到地址0x56000050
c: e59f100c ldr r1, [pc, #12] ; 20 <.text+0x20>
// r1 = [pc + 12] = [0xc + 8 + 12] = [32] = [0x20] = 56000054
10: e3a00000 mov r0, #0 ; 0x0
14: e5810000 str r0, [r1]
00000018 :
18: eafffffe b 18
1c: 56000050 undefined
20: 56000054 undefined
编写 C 程序
lcd.c
int main()
{
unsigned int *pGPFCON = (unsigned int *)0x56000050;
unsigned int *pGPFDAT = (unsigned int *)0x56000054;
/*配置GPF4为输出引脚*/
*pGPFCON = 0x100;
/*配置GPF4输出0*/
*pGPFDAT = 0;
return 0;
}
还需要写一个汇编程序, 给 main 函数设置内存, 调用main函数。
start.S:
.text
.global _start
_start:
/*设置内存:sp栈*/
ldr sp,=4096 /*nand启动*/
// ldr sp, =0x40000000 /*nor启动*/
/*调用main*/
bl main
halt:
b halt
bl main 跳转执行main函数,并把"返回地址"(即下一条指令地址)保存在lr寄存器中,这里返回地址是halt。
Makefile:
all:
arm-linux-gcc -c -o led.o led.c
arm-linux-gcc -c -o start.o start.S
arm-linux-ld -Ttext 0 start.o led.o -o led.elf
arm-linux-objcopy -O binary -S led.elf led.bin
arm-linux-objdump -D led.elf > led.dis
clean:
rm *.bin *.o *.elf *.dis
最后即可编译,烧写到开发板,看到LED1亮。
用反汇编代码分析C程序的执行过程
汇编程序 start.S所做的事情是 :1. 设置栈;2. 调用 main,并把返回值地址保存到 lr 中。
C程序 led.c 的 main() 所做的事情是 :1. 定义2个局部变量;2. 设置变量;3. return 0。
从反汇编代码来分析一下C程序执行过程,解答下面的问题。
- 为什么要设置栈?因为c函数要用。
- 栈的作用是什么? a.保存局部变量;b.保存 lr、fp、ip、sp、pc 等寄存器的值;
- 调用者如何传参数给被调用者?
- 被调用者如何传返回值给调用者?
- 怎么从栈中恢复那些寄存器?
ATPCS规则
在arm中有个ATPCS规则,约定 r0 - r15 寄存器的用途。即ARM-THUMB procedure call standard(ARM-Thumb子程序调用规则)
r0 - r3: 用于调用者和被调用者之间传参数;
r4-r11: 可能被子函数使用,所以在函数的入口保存它们,在函数的出口恢复它们;
反汇编程序 led.dis :
led.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e3a0da01 mov sp, #4096 ; 0x1000
4: eb000000 bl c
00000008 :
8: eafffffe b 8
0000000c :
c: e1a0c00d mov ip, sp
10: e92dd800 stmdb sp!, {fp, ip, lr, pc}
14: e24cb004 sub fp, ip, #4 ; 0x4
18: e24dd008 sub sp, sp, #8 ; 0x8
1c: e3a03456 mov r3, #1442840576 ; 0x56000000
20: e2833050 add r3, r3, #80 ; 0x50
24: e50b3010 str r3, [fp, #-16]
28: e3a03456 mov r3, #1442840576 ; 0x56000000
2c: e2833054 add r3, r3, #84 ; 0x54
30: e50b3014 str r3, [fp, #-20]
34: e51b2010 ldr r2, [fp, #-16]
38: e3a03c01 mov r3, #256 ; 0x100
3c: e5823000 str r3, [r2]
40: e51b2014 ldr r2, [fp, #-20]
44: e3a03000 mov r3, #0 ; 0x0
48: e5823000 str r3, [r2]
4c: e3a03000 mov r3, #0 ; 0x0
50: e1a00003 mov r0, r3
54: e24bd00c sub sp, fp, #12 ; 0xc
58: e89da800 ldmia sp, {fp, sp, pc}
Disassembly of section .comment:
00000000 <.comment>:
0: 43434700 cmpmi r3, #0 ; 0x0
4: 4728203a undefined
8: 2029554e eorcs r5, r9, lr, asr #10
c: 2e342e33 mrccs 14, 1, r2, cr4, cr3, {1}
10: Address 0x10 is out of bounds.
Nand 启动时,硬件机制会把 nand flash 上前 4K 的机器码拷贝到片内的 4K SDRAM上。然后从0地址开始执行。
开发板上电后,将从0地址开始执行,即开始执行:
mov sp, #4096 //:设置栈地址在4k RAM的最高处,sp=4096;
bl c //:跳到c地址处的main函数,并保存下一行代码地址到lr,即lr=8;
mov ip, sp //:给ip赋值sp的值,ip=sp=4096
stmdb sp!, {fp, ip, lr, pc} //:按高编号寄存器存在高地址,依次将pc、lr、ip、fp存入sp-4中;
sub fp, ip, #4 //:fp的值为ip-4=4096-4=4092;
sub sp, sp, #8 //:sp的值为sp-8=(4096-4x4)-8=4072;
mov r3, #1442840576 //:r3赋值0x5600 0000;
add r3, r3, #80 //:r3的值加0x50,即r3=0x5600 0050;
str r3, [fp, #-16] //:r3存入[fp-16]所在的地址,即地址4076处存放0x5600 0050;
mov r3, #1442840576 //:r3赋值0x5600 0000;
add r3, r3, #84 //:r3的值加0x54,即r3=0x5600 0054;
str r3, [fp, #-20] //:r3存入[fp-20]所在的地址,即地址4072处存放0x5600 0054;
ldr r2, [fp, #-16] //:r2取[fp-16]地址处的值,即[4076]地址的值,r2=0x5600 0050;
mov r3, #256 //:r3赋值为0x100;
str r3, [r2] //:将r3写到r2内容所对应的地址,即0x5600 0050地址处的值为0x100;;对应c语言*pGPFCON = 0x100;;
ldr r2, [fp, #-20] //:r2取[fp-20]地址处的值,即[4072]地址的值,r2=0x5600 0054;
mov r3, #0 //:r3赋值为0x00;
str r3, [r2] //:将r3写到r2内容所对应的地址,即0x5600 0054地址处的值为0x00;对应c语言*pGPFDAT = 0;
mov r3, #0 //:r3赋值为0x00;
mov r0, r3 //:r0=r3=0x00;
sub sp, fp, #12 //:sp=fp-12=4092-12=4080;
ldmia sp, {fp, sp, pc} //:从栈中恢复寄存器,fp=4080地址处的值=原来的fp,sp=4084地址处的值=4096,pc=4088地址处的值=8,随后跳到0x08地址处继续执行。
//注意点: 调用main函数之前sp = 4096, main函数返回后sp仍然是4096
汇编程序传递参数给C程序分析
前面的例子,汇编程序调用 main.c 并没有传递参数,这里修改下 c 程序,添加带参数的函数。
led.c:
void delay(volatile int d) //这里volatile关键字是为了避免编译器自作聪明的把delay函数优化掉. 编译器可能从逻辑上看,省掉delay函数也没什么关系。
{
while (d--);
}
int led_on(int which)
{
unsigned int *pGPFCON = (unsigned int *)0x56000050;
unsigned int *pGPFDAT = (unsigned int *)0x56000054;
if (which == 4)
{
/* 配置GPF4为输出引脚 */
*pGPFCON = 0x100;
}
else if (which == 5)
{
/* 配置GPF5为输出引脚 */
*pGPFCON = 0x400;
}
/* 设置GPF4/5输出0 */
*pGPFDAT = 0;
return 0;
}
start.S:
.text
.global _start
_start:
/* 设置内存: sp 栈 */
ldr sp, =4096 /* nand启动 */
// ldr sp, =0x40000000+4096 /* nor启动 */
mov r0, #4 //r0-r3:调用者和被调用者之间传参数;
bl led_on
ldr r0, =100000
bl delay
mov r0, #5
bl led_on
halt:
b halt
led.elf:
分析见注释:
led.elf: file format elf32-littlearm
Disassembly of section .text:
00000000 <_start>:
0: e3a0da01 mov sp, #4096 ; 0x1000 //:设置栈地址在4k RAM的最高处,sp=4096;
4: e3a00004 mov r0, #4 ; 0x4 //:r0=4,作为参数;
8: eb000012 bl 58 //:调到58地址处的led_on函数,并保存下一行代码地址到lr,即lr=8;在led_on中会使用到r0;
c: e59f000c ldr r0, [pc, #12] ; 20 <.text+0x20> //:r0=[pc+12]处的值=[0xc+8+12]=0x20 处的值 = 0x186a0 = 1000000,作为参数;
10: eb000003 bl 24 //:调用24地址处的delay函数,并保存下一行代码地址到lr,即lr=24;在delay中会使用到r0;
14: e3a00005 mov r0, #5 ; 0x5 //:r0=5,作为参数;
18: eb00000e bl 58 //:调到58地址处的led_on函数,并保存下一行代码地址到lr,即lr=58;在led_on中会使用到r0;
0000001c :
1c: eafffffe b 1c
20: 000186a0 andeq r8, r1, r0, lsr #13
00000024 :
24: e1a0c00d mov ip, sp
28: e92dd800 stmdb sp!, {fp, ip, lr, pc}
2c: e24cb004 sub fp, ip, #4 ; 0x4
30: e24dd004 sub sp, sp, #4 ; 0x4
34: e50b0010 str r0, [fp, #-16]
38: e51b3010 ldr r3, [fp, #-16]
3c: e2433001 sub r3, r3, #1 ; 0x1
40: e50b3010 str r3, [fp, #-16]
44: e51b3010 ldr r3, [fp, #-16]
48: e3730001 cmn r3, #1 ; 0x1
4c: 0a000000 beq 54
50: eafffff8 b 38
54: e89da808 ldmia sp, {r3, fp, sp, pc}
00000058 :
58: e1a0c00d mov ip, sp
5c: e92dd800 stmdb sp!, {fp, ip, lr, pc}
60: e24cb004 sub fp, ip, #4 ; 0x4
64: e24dd00c sub sp, sp, #12 ; 0xc
68: e50b0010 str r0, [fp, #-16]
6c: e3a03456 mov r3, #1442840576 ; 0x56000000
70: e2833050 add r3, r3, #80 ; 0x50
74: e50b3014 str r3, [fp, #-20]
78: e3a03456 mov r3, #1442840576 ; 0x56000000
7c: e2833054 add r3, r3, #84 ; 0x54
80: e50b3018 str r3, [fp, #-24]
84: e51b3010 ldr r3, [fp, #-16]
88: e3530004 cmp r3, #4 ; 0x4
8c: 1a000003 bne a0
90: e51b2014 ldr r2, [fp, #-20]
94: e3a03c01 mov r3, #256 ; 0x100
98: e5823000 str r3, [r2]
9c: ea000005 b b8
a0: e51b3010 ldr r3, [fp, #-16]
a4: e3530005 cmp r3, #5 ; 0x5
a8: 1a000002 bne b8
ac: e51b2014 ldr r2, [fp, #-20]
b0: e3a03b01 mov r3, #1024 ; 0x400
b4: e5823000 str r3, [r2]
b8: e51b3018 ldr r3, [fp, #-24]
bc: e3a02000 mov r2, #0 ; 0x0
c0: e5832000 str r2, [r3]
c4: e3a03000 mov r3, #0 ; 0x0
c8: e1a00003 mov r0, r3
cc: e24bd00c sub sp, fp, #12 ; 0xc
d0: e89da800 ldmia sp, {fp, sp, pc}
Disassembly of section .comment:
00000000 <.comment>:
0: 43434700 cmpmi r3, #0 ; 0x0
4: 4728203a undefined
8: 2029554e eorcs r5, r9, lr, asr #10
c: 2e342e33 mrccs 14, 1, r2, cr4, cr3, {1}
10: Address 0x10 is out of bounds.