学编程,第一个程序肯定是“hello world”,学单片机或ARM,最初学的肯定是GPIO的操作。在这一节课里主要涉及到了点亮LED和按键控制LED。
1、要想点亮一个LED,分以下几个步:
①、了解相关硬件
②、配置Gpio的相关寄存器
③、编写相关代码
④、编译下载
2、一般来说,要想点亮LED,只需要控制相关的gpio口输出高电平或低电平就可以了。通过查看2440的原理图可以知道(如下图所示):
3个LED的阴极经过1K的限流电阻连接到GPF4、GPF5和GPF6三个Gpio口上了。因此我们只要让PF4、GPF5和GPF6输出低电平,就能点亮LED,高电平熄灭LED。
3、查看2440的DataSheet 可以知道,Gpio主要有三个寄存器需要设置,再本篇中分别是GPFCON、GPFDAT和GPFUP。
其中:
GPFCON是控制寄存器,主要是控制GPIO的功能,主要有输入、输出和中断三个功能。其中每个GPIO口有寄存器的两位来控制,00:输入,01:输出,10:中断,10:保留。
GPFDAT是数据寄存器,主要是控制GPIO输出高电平还是低电平,0:低电平,1:高电平。
GPFUP 是设置内部上拉电阻的寄存器,0:不设置上拉电阻,1:设置上拉电阻。
4、编辑代码
在教程中的第一个代码使用汇编语言编写的,代码非常简单。如下:
1 .text 2 .global _start 3 _start: 4 LDR R0,=0x56000050 5 MOV R1,#0x00000400 6 STR R1,[R0] 7 LDR R0,=0x56000054 8 MOV R1,#0x00000000 9 STR R1,[R0] 10 MAIN_LOOP: 11 B MAIN_LOOP
下面主要分一下这个代码:
.text:这个是GCC编译器里面的一个关键词,它指定了后续编译出来的代码放在可执行代码段,是处理器开始执行代码的地方。
.global _start 告诉编译器后续跟的是一个全局可见的名字(可能是变量,也可以是函数名),.global让 _start 符号成为可见的标识符,这样链接器就知道跳转到程序中的什么地方并开始执行。_start是一个函数的起始地址,也是编译、链接后程序的起始地址。由于程序是通过加载器来加载的,必须要找到 _start名字的函数,因此_start必须定义成全局的,以便存在于编译后的全局符合表中,供其它程序寻找到。
_start是程序的起始地址
LDR R0,=0x56000050
MOV R1,#0x00000400
STR R1,[R0]
这三句主要实现了对GPFCON寄存器的设置,这里首先是把R0设为了GPFCON的寄存器地址,然后给R1赋值,最后把R1的值赋到以R0为地址的寄存器里,从而配置相应端口的功能。这里是0x00000400,正好是GPFCON的[11:10]设置为了01 ,即是GPF5位输出功能。
LDR R0,=0x56000054
MOR R1,#0x00000000
STR R1,[R0]
这三句代码和上面类似,只要实现了GPFDAT输出低电平,这里是GPF5输出低电平,因为只有GPF5端口被设置为了输出功能。
MAIN_LOOP:
B MAIN_LOOP
这句是个死循环没有什么太大意思。
5、编译下载
程序的编译命令时通过编写Makefile文件完成的。
1 led_on.bin : led_on.S 2 arm-linux-gcc -g -c -o led_on.o led_on.S 3 arm-linux-ld -Ttext 0x0000000 -g led_on.o -o led_on_elf 4 arm-linux-objcopy -O binary -S led_on_elf led_on.bin 5 clean: 6 rm -f led_on.bin led_on_elf *.o
arm-linux-gcc:
-g:产生供gdb调试用的可执行文件
-c:只编译不连接
-o: 指定输出文件名。这里指定的是生成led_on.o
arm-linux-ld:
-o:指定输出文件名。这里生成lec_on.elf。
-Ttext: 设置代码段的起始地址:0x0000000。
arm-linux-objcopy:
-O: 输出的格式, binary指二进制文件。这里生成led_on.bin。
-S: 不从源文件中复制重定位信息和符号信息到目标文件中
Clean:
是清除命令,清除make生成的文件,这里指的是清除led_on.bin 、led_on_elf以及所有的.o文件。*是通配符。
然后我们linux环境下,执行make命令来编译文件生成可执行文件并下载到开发板上,可以观察到led2被点亮。开发板自带的有下载教程“如何烧写S3C2440裸板程序.pdf”。
在接下来的视频教程中,主要讲解了如何用c语言来编写程序点亮led灯。用c程序来操作gpio口,首先要设置相关硬件,关看门狗,设置栈,这些功能是在汇编文件里编写的。
1 .text 2 .global _start 3 _start: 4 ldr r0, =0x53000000 5 mov r1, #0x0 6 str r1,[r0] 7 ldr sp, =1024*4 8 bl main 9 halt_loop: 10 b halt_loop
代码和上面的类似:
ldr r0, =0x53000000
mov r1, #0x0
str r1,[r0]
这三句代码是通过写0关闭看门狗,否则CPU会不断地重启。
ldr sp, =1024*4
设置我们的堆栈空间的大小,这里设置为4K,因为2440的内部RAM只有4K。
bl main
调用我们写的c函数
接下来是个死循环。
C函数:
1 #define GPFCON (*(volatile unsigned long *) 0x56000050) 2 #define GPFDAT (*(volatile unsigned long *) 0x56000054) 3 int main() 4 { 5 GPFCON = 0x00000100; 6 GPFDAT = 0x00000000; 7 8 return 0; 9 }
在这里最重要的是GPFCON和GPFDAT这两个宏的定义,这是指向这两个寄存器地址的指针。
例如:
int a;
int *p;
p=&a;
*p = 0x100;//这就是把变量a赋值为0x100
在我们的程序中:
地址是(volatile unsigned long *) 0x56000050,即是p=(volatile unsigned long *) 0x56000050,*p=(*(volatile unsigned long *) 0x56000050),GPFCON和GPFDAT两个宏的意思就是*p。
其他就是往寄存器里赋值就行。
编译下载:
1 led_on_c.bin : crt0.S led_on_c.c 2 arm-linux-gcc -g -c -o crt0.o crt0.S 3 arm-linux-gcc -g -c -o led_on_c.o led_on_c.c 4 arm-linux-ld -Ttext 0x0000000 -g crt0.o led_on_c.o -o led_on_c_elf 5 arm-linux-objcopy -O binary -S led_on_c_elf led_on_c.bin 6 arm-linux-objdump -D -m arm led_on_c_elf > led_on_c.dis 7 clean: 8 rm -f led_on_c.dis led_on_c.bin led_on_c_elf *.o
这个Makefile只是比前面多了一句:
arm-linux-objdump -D -m arm led_on_c_elf > led_on_c.dis
只一句是用来生成反汇编代码的语句,
-D:反汇编所有代码。
-m:反汇编目标文件时使用的架构。这里是arm架构。
下载如上。
接下来的流水灯实验和按键控制led的实验,本质上与上面的c程序类似,重要点主要是涉及的位操作。
1 #define GPF4_out (1<<(4*2)) 2 #define GPF5_out (1<<(5*2)) 3 #define GPF6_out (1<<(6*2)) 4 5 #define GPF4_msk (3<<(4*2)) 6 #define GPF5_msk (3<<(5*2)) 7 #define GPF6_msk (3<<(6*2)) 8 // LED1,LED2,LED4对应的3根引脚设为输出 9 GPFCON &= ~(GPF4_msk | GPF5_msk | GPF6_msk); //对寄存器的相应位的清零操作。 10 GPFCON |= GPF4_out | GPF5_out | GPF6_out;//对寄存器的相应位的赋值操作。
位操作的好处是,清零时只清零相应位,赋值时也只赋值相应位,对其他为不产生影响,防止破坏程序。
好了,第一节课的笔记到此为止,这也是第一次写博客,没什么太难的内容,只是上课的一些笔记(记性不好,还是写下来忘了方便查看)。再接再厉。