一般分三步去点亮一个LED:
1.看原理图,确定控制LED的引脚;(确定是输出1点亮还是输出0点亮,即是高电平还是低电平点亮LED)看原理图得知通过GPF4来控制LED的亮灭
2.看芯片手册,确定如何设置控制这个引脚
3.写程序
通过寄存器来操作GPIO引脚
GPxCON用于选择引脚的功能,GPxDAT用于读/写引脚数据,GPxUP用于确定是否使用内部上拉电阻。x为A...H等。
1.GPxCON寄存器
用于选择引脚的功能,每一位对应一根引脚,以GPACON为例,当其中一位被设为0时,相应的引脚为输出引脚,此时可以向GPADAT中相应位写入0或1,让此引脚输出低电平或者高电平。
2.GPXDAT寄存器
GPxDAT用于读/写引脚:当引脚被设置为输入时,读此寄存器可知相应的引脚的点平状态是高电平还是低电平;当引脚被设置为输出时,写此寄存器相应位可令此引脚输出高电平或低电平。
3.GPxUP寄存器
GPxUP:某位为1时,相应的引脚无内部上拉电阻;为0时,相应的引脚使用内部上拉电阻。
上拉、下拉电阻的作用在于,当GPIO引脚处于第三种状态时(既不是输出高电平也不是输出低电平,而是呈高阻态,即相当于没有接芯片)时,它的电平状态由上拉电阻、下拉电阻确定。
GPF4怎么设置为输出1或者0?
1.配置引脚为输出引脚
2.设置状态
因此,设置GPF4CON[9:8] = 0b01,即GPF4配置为输出;设置GPF4DAT[4] = 1或者0,即输出高电平或者低电平。
s3c2440框架与启动过程:
Nor启动:
Nor Flash的基地址为0,片内RAM的地址为0x40000000;
CPU读出Nor上的第一个指令(前4字节),执行
CPU继续读出其他指令执行
Nand 启动
片内4KRAM的基地址为0,Nor Flash不可访问
2440硬件把Nand前4K的内容复制到片内的RAM中,然后cpu从0地址取出第一条指令执行
在开始写第1个程序前,先了解一些概念。
2440是一个SOC,它里面的CPU有R1、R2、..R15 寄存器;
它里面的GPIO控制器也有很多寄存器,如 GPFCON、GPFDAT。
这两个寄存器是有差异的,在写代码的时候,CPU里面的寄存器可以直接访问,其它的寄存器要以地址进行访问。
把GPF4配置为输出,需要把0x100写入GPFCON这个寄存器,即写到0x5600 0050上;
把GPF4输出1,需要把0x10写到地址0x5600 0054上(即GPFDAT寄存器);
把GPF4输出0,需要把0x00写到地址0x5600 0054上(即GPFDAT寄存器);
这里的写法会破坏寄存器的其它位,其它位是控制其它引脚的,为了让第一个裸板程序尽可能的简单,才简单粗暴的这样处理。
写程序需要用到几条汇编代码:
①LDR (load):读寄存器
举例:LDR R0,[R1]
假设R1的值是x,读取地址x上的数据(4字节),保存到R0中;
②STR (store):写寄存器
举例:STR R0,[R1]
假设R1的值是x,把R0的值写到地址x(4字节);
③B 跳转(不带返回的跳转)
④MOV (move)移动,赋值 举例1:MOV R0,R1
把R1的值赋值给R0;
举例2:MOV R0,#0x100
把0x100赋值给R0,即R0=0x100;
⑤LDR
举例:LDR R0,=0x12345678
这是一条伪指令,即实际中并不存在这个指令,他会被拆分成几个真正的ARM指令,实现一样的效果。 最后结果是R0=0x12345678。
为什么会引入伪指令?
在ARM的32位指令中,有些字节表示指令,有些字节表示数据,因此表示数据的没有32位,不能表示一个32位的任意值,只能表示一个较小的简单值,这个简单值称为立即数。引入伪指令后,利用LDR可以为R0赋任意大小值,编译器会自动拆分成真正的的指令,实现目的。
立即数:
1.0-255之间的数
2.右移偶数位在0-255之间的数
3.取反后在0-255之间的数
add r0,r1,#4
r0 = r1+4
sub r0,r1,#4
r0 = r1 - 4
sub r0,r1,r2
r0 = r1 - r2
bl xxx
1.调到xxx
2.把返回地址保存在lr寄存器中
db 预先减少
da 过后减少
ib 预先增加
ia 过后增加
stmdb sp!,(fp,ip,lr,pc)高编号寄存器存在高地址,此命令是操纵多个寄存器,而且是先减少后存
ldmia sp,(fp,sp,lr,pc)
正好与上图相反
1 /* 2 *点亮LED1:gpf4 3 */ 4 .text 5 .global _start 6 7 8 _start: 9 /* 10 *配置gpf4为输出引脚 11 *把0x100写到地址0x56000050 12 */ 13 ldr r1, = 0x56000050 14 ldr r0, = 0x100 15 str r0, [r1] 16 /* 17 *配置gpf4输出低电平 18 */ 19 ldr r1, = 0x56000054 20 ldr r0, = 0 21 str r0, [r1] 22 /*死循环*/ 23 halt: 24 b halt
1 all: 2 arm-linux-gcc -c -o led_on.o led.s 3 arm-linux-ld -Ttext 0 led_on.o -o led_on.elf 4 arm-linux-objcopy -O binary -S led_on.elf led_on.bin 5 clean: 6 rm *.bin *.o *.elf
字节序:
假设 int a = 0x12345678
16进制中每位是4bit,在内存中,是以8个bit作为1byte进行存储的,因此因此0x12345678中每两位作为1byte,其中0x78是低位,0x12是高位。
0x12345678的低位(0x78)存在低地址,即方式1,叫做小字节序(Little endian);
0x12345678的高位(0x12)存在低地址,即方式2,叫做大字节序(Big endian);
位操作:
1.左移
int a = 0x123;int b = a <<2;--> b = 0x48c
右移:
int a = 0x123;int b = a >>2;--> b = 0x48
左移多少位就是乘以2的几次方,右移多少位就是除以2的几次方
2.取反 就是0变1,1变0
3.位与
1&1 = 1
1&0 = 0
4.位或
1|0 = 1
有短路原则:即或的时候前面为1后面的就不用再计算了,因为1或上任意的都为一,与的时候前面为0后面的也就不用算了,因为0与上任何数都为0.
5.清位:把a的bit7、bit8清位(变为0)
int a = 0x123;int b = a &(~(1<<7)&~(1<<8))
6.置位 把a的bit7、bit8置位(变为1)
int a = 0x123; int b = a|(1<<7)|(1<<8)
在以后操纵寄存器的时候要先清位然后再置位
1 int main() 2 { 3 unsinged int *pGPFCON = (unsigned int *)0x56000050; 4 unsigned int *pGPFDAT = (unsigned int *)0x56000054; 5 /*配置GPF4为输出引脚*/ 6 *pGPFCON = 0x100; 7 /*配置GPF4输出0*/ 8 *pGPFDAT = 0; 9 return 0; 10 }
1 .text 2 .global _start 3 _start: 4 /*设置内存:sp栈*/ 5 ldr sp,=4096 /*nand启动*/ 6 // ldr sp, =0x40000000 /*nor启动*/ 7 /*调用main*/ 8 bl main 9 halt: 10 b halt
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
003_led.c内部机制分析:
start.S:
①设置栈;
②调用main,并把返回值地址保存到lr中;
led.c的main()内容:
①定义2个局部变量;
②设置变量;
③return 0;
问题:
①为什么要设置栈?
因为c函数要用。
②怎么使用栈?
a.保存局部变量;
b.保存lr等寄存器;
③调用者如何传参数给被调用者?
④被调用者如何传返回值给调用者?
⑤怎么从栈中恢复那些寄存器?
在arm中有个ATPCS规则,约定r0-r15寄存器的用途。
r0-r3:调用者和被调用者之间传参数;
r4-r11:函数可能被使用,所以在函数的入口保存它们,在函数的出口恢复它们;
下面分析个实例 start.S:
.text
.global _start
_start: /* 设置内存: sp 栈 */ ldr sp, =4096 /* nand启动 */ // ldr sp, =0x40000000+4096 /* nor启动 */ /* 调用main */ bl main halt: b halt
led.c:
int main()
{
unsigned int *pGPFCON = (unsigned int *)0x56000050; unsigned int *pGPFDAT = (unsigned int *)0x56000054; /* 配置GPF4为输出引脚 */ *pGPFCON = 0x100; /* 设置GPF4输出0 */ *pGPFDAT = 0; return 0; }
将前面的程序反汇编得到led.dis如下:
led.elf: file format elf32-littlearm Disassembly of section .text: 00000000 <_start>: 0: e3a0da01 mov sp, #4096 ; 0x1000 4: eb000000 bl c <main> 00000008 <halt>: 8: eafffffe b 8 <halt> 0000000c <main>: 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