目录
1.查看原理图和数据手册,设置IO口功能
2.S3C2440框架和启动过程
3.编写程序点亮LED灯思路
4.一些汇编语言的知识
5.编写汇编代码
6.编译文件
7.下载到开发版
8.查看伪指令解析后的汇编指令
练习1:修改led1.S驱动LED2
练习2:修改bin文件点亮LED3
9.用c语言控制LED灯
提示:此处使用的芯片是三星的S3C2440A
1.1.LED1,连接到GPF4的IO口,从原理图看出是低电平点亮LED灯。
1.2查看数据手册,配置寄存器
小结:
控制GPF4需要设置两个寄存器,GPFCON,GPFDAT
1.设置GPF[9:8] = 0b01 --GPF4配置为输出
2.设置GPFDAT[4]= 0或者1; --GPF4输出低/高电平
2.1.基本框架
S3C2440是一个SOC,在一块芯片上面集成了有CPU、GPIO控制器、Nand控制器、Nor控制器、以及4K的SRAM。
2.2.启动过程(大多数ARM芯片从0地址启动)
1.NoR启动,NoR Flash 基地址为0(片内RAM地址为:0x40000000),CPU读出NOR读出上的第一个指令(前四个字节)执行,CPU继续读出其他指令执行。
2.Nand启动,片内4K SRAM 基地之为0(Nor Falsh 不可访问),硬件2240把NAND前4K内容复制到片内内存SRAM中,然后CPU从0地址取出第一条指令(前四个字节)执行。
3.1.设置GPFCON寄存器的 【9:8】为 0 1 ,也就是网GOFCON这个地址(0x56000050)写值
使用寄存器助手看一下,设置的值为多少?设置的值为:0x100
即把值0x100写到地址:0x56000050 上
3.2.点亮或者熄灭LED灯
熄灭:设置GPFDAT寄存器的第4位为1,也就是网GOFDAT这个地址(0x56000054)写值,写的值为:0x10
点亮:设置GPFDAT寄存器的第4位为0,也就是网GOFDAT这个地址(0x56000054)写值,写的值为:0
注意:此方法会破坏寄存器原本的位,即改变这个寄存器所有的位,因为此时控制一个LED所以不管别的位。
5.1.新建给汇编文件
5.1.使用source insight 打开文件,编辑文件,代码如下:
/*
*点亮LED,GPF4
*/
.text
.global _start
_start:
/*配置GPF4为输出引脚*/
/*0x100写到地址0x56000050*/
//使用伪指令,把GPFCON的地址赋值给R1
ldr r1,=0x56000050
//把0x100赋值给R0
mov r0,#0x100 /*ldr r0,=0x100*/
//把r0的值写到地址r1去
str r0,[r1]
/*配置GPF4输出低电平,点亮LED*/
/*把0写到地址0x56000054*/
//使用伪指令,把GPFDAT的地址赋值给R1
ldr r1,=0x56000054
//把0赋值给R0
mov r0,#0 /*ldr r0,=0*/
//把r0的值写到地址r1去
str r0,[r1]
/*死循环*/
halt:
b halt
6.1.打开Linux的虚拟机,查看虚拟机的IP地址
6.2.在window系统使用远程登陆工具(filezilla Pro),登陆Linux账户,进行文件的传输
6.3.双击led1.S,文件自动进行传输;
6.4.进入Linux系统,进入work目录
开始编译:
使用命令:
预编译:arm-linux-gcc -c -o led1.o led1.S
链接 :arm-linux-ld -Ttext 0 led1.o -o led1.elf
得到bin文件:arm-linux-objcopy -O binary -S led1.elf led1.bin
一次输入上面的命令得到,下面的编译文件:
提示:手动输入比较容易出错,所以在这里制作一个Makefile文件
使用NotePad打开,编辑如下:
上传Makefile到Linux系统上(使用filezilla Pro):
以后可以使用命令:
- make clean --清除编译文件
- make --编译
7.1.把Linux系统编译好的led1.bin文件传回windows系统
7.2.使用oflash烧写bin文件到开发版
进入window命令行,跳转到bin文件所在目录,执行oflash led1.bin
选择eop烧写:
选择2440:
烧写到Nand Flash:
从0地址开始烧写:
烧写完成,拔掉开发板的eop烧写器,使开发板从Nand Flash启动,重启开发版。重启之后就可以在开开发版中看到连接到GPF4的LED被点亮了。
8.1.修改Makefile 把 .elf文件反汇编,查看真正的汇编指令
使用指令:arm-linux-objdump -D led1.elf > led1.dis
修改后入下:
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
arm-linux-objdump -D led1.elf > led1.dis
clean:
rm *.bin *.o *.elf
使用远程登陆工具(filezilla Pro)上传Makefile文件到Linux虚拟机(因为已经上传过一次,所以此处应该为覆盖文件)
8.2.回到Linux虚拟机,使用make命令进行编译
生成的 led1.dis文件传回到windows
使用Notepad打开这个文件
想要看懂汇编语言语句,需要对ARM寄存器有所了解:
注意:ARM的指令的运行采用的是流水线型,当指令地址A的指令时,CPU已经对地址A+4的指令进行译码,同时已经在读取地址A+8的指令。所以pc的值是当前的地址+8;
8.3汇编代码指令解析:
第一句指令:ldr r1, [pc, #20]
第二句指令:mov r0, #256
解析:把0x100的值赋值给寄存器r0
第三句指令:str r0, [r1]
解析:把ro的值(0x100)写到r1对应的内存,即把值0x100写入到内存:【0x56000050】
第四句指令:ldr r1, [pc, #12]
第五句指令:mov r0, #0
解析:把0赋值给r0寄存器
第六五句指令:str r0, [r1]
解析:把ro的值(0x0)写到r1对应的内存,即把值0x0写入到内存:【0x56000054】
小结:
在CPU的角度来看,GPFCON、GPFDAT他们没有本质的区别,都当作内存来用。
8.4.编译器的功能
编译过程,我们的编译器会把汇编码转换成机器码,而机器码就是bin文件(二进制文件)的内容
把Linux编译好的 .bin文件从Linux拉出来,用Hex工具打开和.dis文件的机器码进行比较
可以看出他们是一样的。
修改led1.S文件驱动连接GPF5的LED灯
1.由原理图可知,需要操作的的是GPF5
通过查看寄存器GPFCON的描述可知,需要设置GPFCON[11:10] = 0b01
即:网0x56000050地址写 0x400
2.修改led1.S文件
代码如下:
.text
.global _start
_start:
/*配置GPF5为输出引脚*/
/*0x400写到地址0x56000050*/
//使用伪指令,把GPFCON的地址赋值给R1
ldr r1,=0x56000050
//把0x100赋值给R0
mov r0,#0x400 /*ldr r0,=0x400*/
//把r0的值写到地址r1去
str r0,[r1]
/*配置GPF5输出低电平,点亮LED1*/
/*把0写到地址0x56000054*/
//使用伪指令,把GPFDAT的地址赋值给R1
ldr r1,=0x56000054
//把0赋值给R0
mov r0,#0 /*ldr r0,=0*/
//把r0的值写到地址r1去
str r0,[r1]
/*死循环*/
halt:
b halt
3.上传到Linux进行编译:
4.传回windows 烧录到开发版
5.使用oflash 烧录到Nand Flash中,重启开发版,LED2被点亮。
附加:查看反编译的机器码是什么,下个练习用到
既然机器码是真正写到硬件的二进制代码,理论上我们通过修改二进制代码就可以修改他们的功能了。所以我们需要去查看RAM架构手册中对于 mov指令的描述,它每一个位的含义是什么?
MOV指令机器码如下所示:
当使用命令:mov r0 ,#1024 --把0x400赋值给r0的时候(也就是点亮LED2的时候)的机器码是:0xe3a00b01
把该值赋值到寄存器查看助手,查看一下哪一个位被设置了
如果想通过修改机器码去修改程序的执行结果,就是要修改mov指令机器码的立即数【11:0】的值
计算一下0x400的立即数是什么是否和上面的解释的一样
所以我们就可以通过改变机器码里边的立即数来改变最终程序的执行结果了。这样子的话,只要我们知道了我们需要设置的立即数,我们就可以推算出机器码,然后把这个机器码写入到硬件里面,程序就可以按我们的想法执行了。
具体看练习2的例子。
1.由原理图可知,需要操作的的是GPF6
如果是使用汇编语言的话,那么和练习1的句子应该只有一句不同,只需要将上面的GPFCON[13:12]=0b01
使用寄存器助手看一下需要设置的值,设置为0x1000
可以看出这个程序的立即数是:0x1000,那么这个立即数 对应的机器码是什么?
0x1000可以转化成二进制可以表示为: 19个0加上1000000000000,用机器码表示成:1右移20位得到的数
那么机器码的rotate就是:20/2=10; immed_8=1;
那么整个机器码我们可以表示为:e3a00a01
直接复制练习1的例子中的bin文件,把e3a00b01 改成 e3a00a01
烧录修改后的bin文件到开发版中,烧录重启后,可以看到开发版的LED3亮灯。
9.1.编写思路
通过查数据手册,知道了寄存器GPFCON和GPFDAT的数值,它们是32位的地址,其实本质来说也是内存,不过我们修改了这个内存的值就会使程序的运行结果发生变化,如果能根据数据手册的要求去改变这个寄存器所在地址的数值的话就能控制对应IO了。
在C语言中可以定义一个指针变量来存放这两个32位的地址,然后在程序中改变这个32位地址的(改变寄存器)值。
9.2.编写c代码
新建一个.c文件,很简单几条语句
int main()
{
unsigned int *pGPFCON=(unsigned int *)0x56000050;
unsigned int *pGPFDAT=(unsigned int *)0x56000054;
/*配置GPF4的引脚为输出引脚*/
*pGPFCON=0x100;
/*配置GPF4的引脚输出低电平(点亮LED)*/
*pGPFDAT=0;
return ;
}
问题:
1.编写的main函数谁来调用
2.main函数定义的变量保存在内存中,内存地址是多少
答:写一个汇编代码,给main函数设置使用的内存,调用main函数。
9.3.编写汇编代码
新建一个启动文件,命名为start.S,具体内容如下:
.text
.global _start
_start:
/*c语言中局部变量保存在栈中,栈对应的是一块内存*/
/*设置内存 :sp 栈*/
ldr sp ,=4096; //Nand Flash 启动
/*对于2440来说,当设置为Nand启动,从0开始的4k空间
对应的是片内内存,把栈设置在内存的顶部*/
// ldr sp ,=0x40000000+4096; //Nand Flash 启动
/*对于2440来说,当设置为Nor启动,片内4k内存的地址是0x40000000
对应的是片内内存,把栈设置在内存的顶部*/
/*调用main函数,跳转到main函数*/
bl main
halt:
b halt
9.4.编写一个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
9.5.上传到Linux系统进行编译
在Linux系统进入源文件所在目录进行编译
9.6.把Linux系统编译好的文件传回window,然后烧写led.bin文件到开发版,可以看到GPF4对应的led灯亮了,这样这个c语言代码就便宜完成了。