不管我们在计算机上做了多么复杂的动作,包括编写C语言代码、汇编代码、makefile文件等,最后都只不过是为了得到一个bin文件。因此,原则上当我们失去所有工具时,只要你足够强大,就能拿着一些官方文档直接制作bin文件,然后点亮一个LED灯。
在JZ2440中,LED原理图如下所示:
从上面两张图可知,JZ2440的LED为低电平启动,LED与GPIO引脚对应关系如下:
二极管 | 引脚 |
---|---|
nLED_1 | GPF4 |
nLED_2 | GPF5 |
nLED_4 | GPF6 |
接下来,打开S3C2440A芯片手册,找到PORT F CONTROL REGISTERS(GPFCON, GPFDAT) ,如下图所示:
从上图可知,GPFCON
的地址为0X56000050
,GFPDAT
的地址为0X56000054
,并且GPFCON[9:8]
控制GPF4
,将这两位设置成01
就能够将GPF4设置成输出。
再来看GPFDAT
寄存器表格:
从Description
可知,写1
就是高电平,写0
就是低电平。
点点LED1的汇编代码led1.S
如下所示
.text //代码段
.global _start
_start:
//配置GFPCON,GPFCON[9:8]写为01
ldr r1,=0x56000050
ldr r0,=0x100
str r0,[r1]
//配置GPFDAT,简单粗暴全部写0即可将LED1点亮
ldr r1,=0x56000054
ldr r0,=0
str r0,[r1]
//死循环 防止CPU乱执行
halt:
b halt
并编写Makefile
文件
all:
arm-linux-gcc -c -o led1.o led1.S #编译
arm-linux-ld -Ttext 0 led1.o -o led1.elf #链接
arm-linux-objcopy -O binary -S led1.elf led1.bin #elf转换为bin文件
clean:
rm *.bin *.o *.elf
将bin文件烧写到JZ2440右边第一个LED就亮了。
从上面的代码可知,S3C2440点亮LED比起STM32要简单许多,前面没有时钟配置的操作。猜测是S3C2440的定位不是低功耗单片机,因此时钟默认都是打开的,然后直接配置控制寄存器和数据寄存器就可以。
在命令行中输入:
arm-linux-objdump -D led1.elf >led1.dis
就可得到led.bin的反汇编文件,注意反汇编文件是.dis
。该文件内容如下:
led1.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
其中第一列是指令的地址,第二列是机器指令码,第三列是反汇编出来的汇编码。
看第一条指令:
0: e59f1014 ldr r1, [pc, #20]
对应的是ldr r1,=0x56000050
,0x56000050
的地址是0x1c
,那为什么是[pc,#20]
呢?
首先,#20
表示十进制数20,[pc,#20]
表示将pc
的值加上20。
然后,arm采用流水线的形式执行指令。当CPU在执行A地址的指令时,地址A+4的指令就会被译码,而地址A+8的指令就会被加载。因此,pc=当前执行指令的地址+8
。
最后,[pc,#20]
就是0+8+20=28=0X1c
,就和0x56000050
的地址对应上了
接下来看最关键的两条指令
4: e3a00c01 mov r0, #256
和
10: e3a00000 mov r0, #0 ; 0x0
4:
是配置寄存,10:
是写入数据寄存器,由于输出低电平直接写全0就可以了,修改代码只要修改4:
这一句。
led1.bin
文件先来看看led1.bin
的内容,这里用Hex Editor Neo
打开。
为了方便查看,点击View
→
Group By
→
Double words
。
可以看到,led1.bin
文件的16进制码和led1.dis
文件的机器码是按顺序对应的。
因此,只要修改01
列的数据就可以了。
现在,打开ARM Architecture Reference Manual.pdf
(可直接google得到)查看mov
指令的格式。
再来看看
e3a00c01
的二进制形式:
[31:28]
不用管
[27:21]
与mov
指令的格式一致
[20:16]
不懂,也不管了
[15:12]
代表寄存器编号,这里是R0
,因此全0
就可以了
[11:0]
是shifter_operand
,要重点说明一下。
[11:0]又分为[11:8](记为rotate)和[7:0](记为immed_8),mov r0, #256中的#256就是由shifter_operand
转化而来。转化关系如下:
立即数=immed_8 >> 2*rotate
#256
为0X100
,可表示成0x1循环右移24(总共32位)位来表示,因此,[11:8]=1100b=0xc=12
,而2*12=24
为了点亮nLED_2
,要将GPFCON[11:10]
设置成01b
,汇编语句为
ldr r0,=0x400
0x400
可以表示成0x1
循环右移22位,因此:
rotate=11=0xb
immed_8=0x01
则shifter_operand=0xb01
将led1.bin
文件的01
列改为e3a00b01
,另存为led2.bin
,然后将led2.bin
烧写到JZ2440就可以看到中间的LED被点亮了。