目标:点亮LED
怎么点亮LED?看原理图,确定LED是怎么接线的。
相关原理图:
简化的原理图:
从原理图中可以看出GPF4=0,灯亮;GPF4=1,灯灭
怎么设置GPF4的值呢?
GPF4既然做为一个管脚,就可以做为输入管脚、也可以做为输出管脚。因此我们需要配置GPF的功能是输入还是输出还是中断(这里应该配置成输出)?配置完以后,需要设置它的值。具体怎么样设置?操作相应的寄存器,需要进一步查看芯片手册找到各自的寄存器(配置寄存器、设置寄存器)。
芯片手册中的GPF控制器说明如下:
如图可知要配置成输出引脚,需要将第8-9位设置成01即可。
设置寄存器说明如下:
如上图可知只需要将设置设置寄存的第四位即可。
操作原理如下:
程序怎么实现:
首先要找到两个寄存的地址,由芯片手册可知,配置寄存器、设置寄存器分别的寄存器地址为:0x56000050、0x56000054。找到寄存器的地址后,只需要向这两个寄存器相应的位写入数据即可。
汇编语言实现代码如下:
@******************************************************************************
@ File:led_on.S
@ 功能:LED点灯程序,点亮LED1
@******************************************************************************
.text
.global _start
_start:
LDR R0,=0x56000050 @ R0设为GPFCON寄存器。此寄存器
@ 用于选择端口B各引脚的功能:
@ 是输出、是输入、还是其他
MOV R1,#0x00000100
STR R1,[R0] @ 设置GPF4为输出口, 位[8:7]=0b01
LDR R0,=0x56000054 @ R0设为GPBDAT寄存器。此寄存器
@ 用于读/写端口B各引脚的数据
MOV R1,#0x00000000 @ 此值改为0x00000010,
@ 可让LED1熄灭
STR R1,[R0] @ GPF4输出0,LED1点亮
MAIN_LOOP:
B MAIN_LOOP
基中 MOV R1,#0x00000100 表示要往控制器设置的数字值,因为二进制 01000 0000 转成 16进制数据 为:0x00000100。
STR 是一个写内存指令,STR R1,[R0] 表示把R1中的值写到R0(GPF4控制寄存器),执行完这条指令以后,GPF4就被配置成了输出引脚。
同理,设置GPBDAT寄存器的值。
其中
MAIN_LOOP:
B MAIN_LOOP
表示一个死循环
汇编程序写完以后,需要进一步编译makefile文件进行编译。makefile文件内容如下:
led_on.bin : led_on.S
arm-linux-gcc -g -c -o led_on.o led_on.S
arm-linux-ld -Ttext 0x0000000 -g led_on.o -o led_on_elf
arm-linux-objcopy -O binary -S led_on_elf led_on.bin
clean:
rm -f led_on.bin led_on_elf *.o
-g表示加入一些调试信号;
-c表示编译不链接;程序的编译过程:预处理(语法分析)、编译(.c编译成.s文件)、汇编(.s文件变成.o文件)、链接(把多个.o文件合并一个可执行文件)
-c自动将文件汇编成.o文件;
-o 表示输出;
-Ttext 0x0000000:表示代码段的地址为0;
-O binary:表示输出二进制
为什么要把代码段的地址设置为0?
2440有两种启动方式,一种是Nand启动,另一种是NoR启动。这里我们采用Nand启动;以Nand方式启动的时候,硬件上会强制把Nand前面4K的内容拷贝到SARM中,内部的4K的SARM的起始地址为0(即CPU从0地址开始取指执行)。我们的程序是烧写到Nand中的,因此需要设置成0地址。如果Nand放一些乱七八糟的数据,而不是放的程序,系统将不会被启动。
NOR启动:
Nand ,NoR的地址是不一样的。
NoR的0地址不在SRAM中,而在NOR,CPU也是从0地址开始执行。
NoR Flash可以像内存一样读数据,但是不能像内存一样写数据。
首先编译成elf格式的文件,然后将其转换成bin文件,最后将bin文件烧写到开发板中即可。
然后将 文件上传到服务器编译即可。
由上可知ADS中的开发步骤:编辑、编译、烧定/启动
在Linux下开发步骤:编辑、Makefile、烧定/启动
最后烧写bin文件即可。
用C语言点亮LCD:
一般我们在做应用程序开发时,一上来就会写main函数。main函数并没有什么特别,一样会被别人调用。执行完以后,一样会返回。那么由谁来调用main函数,执行完以后,返回到哪里。
我们在主机上开发程序时,一般会由一些系统库 加上 我们的main函数,这些系统库是由系统做好的。
在C函数之前,我们需要写启动文件,启动文件负责调用main,设置栈(函数调用)、设置main函数的返回地址;main返回以后启动文件还要负责一些清理工作。
所谓的设置栈,就是把栈指针 sp 指向某块内存;这里的这块内存正好是片内的SRAM,不需要初始化就可以使用,如果这块内存是SDRAM,需要先对它进行初始化。
硬件方面的初始化:
1,关看门狗,所谓的看门狗其实是一个定时器,2440默认一电,这个定时器就会自启动,并且倒计时。如果3秒之内没有关闭这个定时器,它就会重启整个系统。
2,初始化时钟,2440最快能跑到400MB HZ,当一上电的话时候只有12MB HZ,如果 我们想程序跑的更快,需要对其修改。
3,初始化SDARM.
启动文件可以称之为软件相关的初始化。
硬件初始化+软件初始化 = 启动文件
先看一下本LED的启动文件代码:
@******************************************************************************
@ File:crt0.S
@ 功能:通过它转入C程序
@******************************************************************************
.text
.global _start
_start:
ldr r0, =0x53000000 @ WATCHDOG寄存器地址
mov r1, #0x0
str r1, [r0] @ 写入0,禁止WATCHDOG,否则CPU会不断重启
ldr sp, =1024*4 @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K
@ nand flash中的代码在复位后会移到内部ram中,此ram只有4K
bl main @ 调用C程序中的main函数
halt_loop:
b halt_loop
首先需要关看门狗,只需要向想就的寄存器写入0即可,这样系统就不会不断的重启。代码如下:
ldr r0, =0x53000000 @ WATCHDOG寄存器地址
mov r1, #0x0
str r1, [r0] @ 写入0,禁止WATCHDOG,否则CPU会不断重启
接着设置栈,让sp 指向内存。
ldr sp, =1024*4 @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K
@ nand flash中的代码在复位后会移到内部ram中,此ram只有4K
在片内的内存从0地址开始。
设置返回地址:
bl 指令会跳转到main函数,并且把main的返回地址存在lr寄存器里面
bl main @ 调用C程序中的main函数
清理工作:
这里没有做任何的工作,只是不断的循环。
halt_loop:
b halt_loop
然后看一下C 语言的main函数代码:
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
int main()
{
GPFCON = 0x00000100; // 设置GPF4为输出口, 位[9:8]=0b01
GPFDAT = 0x00000000; // GPF4输出0,LED1点亮
return 0;
}
关于其中定义的两个宏的理解,先看以下关于指针的代码,如果想让a = 0, 有两种方法;
volatile表示让编译器不要去优化它,编译器有时候发现这个变量没有用到,可能不有什么意义,编译器会自做主张对其进行优化。
在C语言代码中,其中
GPFCON = 0x00000100; // 设置GPF4为输出口, 位[9:8]=0b01
表示设置控制器为输出寄存器。
然后将想关代码上传到服务器进行编译,编译后把bin文件烧写到开发板上面。
用oflash烧写 bin方法:
用C语言实现轮流点亮三个LECD:
看原理图,看原理图的方法同第一个LECD的方法一样。
初始化代码如下:
@******************************************************************************
@ File:crt0.S
@ 功能:通过它转入C程序
@******************************************************************************
.text
.global _start
_start:
ldr r0, =0x53000000 @ WATCHDOG寄存器地址
mov r1, #0x0
str r1, [r0] @ 写入0,禁止WATCHDOG,否则CPU会不断重启
ldr sp, =1024*4 @ 设置堆栈,注意:不能大于4k, 因为现在可用的内存只有4K
@ nand flash中的代码在复位后会移到内部ram中,此ram只有4K
bl main @ 调用C程序中的main函数
halt_loop:
b halt_loop
代码同点亮第一个LED的原理一样。
Main函数代码如下:
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPF4_out (1<<(4*2))
#define GPF5_out (1<<(5*2))
#define GPF6_out (1<<(6*2))
void wait(volatile unsigned long dly)
{
for(; dly > 0; dly--);
}
int main(void)
{
unsigned long i = 0;
GPFCON = GPF4_out|GPF5_out|GPF6_out; // 将LED1,2,4对应的GPF4/5/6三个引脚设为输出
while(1){
wait(30000);
GPFDAT = (~(i<<4)); // 根据i的值,点亮LED1,2,4
if(++i == 8)
i = 0;
}
return 0;
}
关于GPF4_out、GPF5_out的设置理由如下:
GPFDAT = (~(i<<4)); 4,5,6寄存器,i左移4位,刚好是寄存器4开始。
看一下makefile 文件代码:
CFLAGS := -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer -ffreestanding
leds.bin : crt0.S leds.c
arm-linux-gcc $(CFLAGS) -c -o crt0.o crt0.S
arm-linux-gcc $(CFLAGS) -c -o leds.o leds.c
arm-linux-ld -Ttext 0x0000000 crt0.o leds.o -o leds_elf
# arm-linux-ld -Tleds.lds crt0.o leds.o -o leds_elf
arm-linux-objcopy -O binary -S leds_elf leds.bin
arm-linux-objdump -D -m arm leds_elf > leds.dis
clean:
rm -f leds.dis leds.bin leds_elf *.o
用按键控制LED:
简化版的原理图
怎么做?
判断按钮状态(松开还是按下);点/灭LED;
配置按钮为输入管脚,LED为输出;
读按钮状态,根据读到的值来设置LED;
启动文件的代码跟上面的一样,该处忽略;
看一下C代码:
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPGCON (*(volatile unsigned long *)0x56000060)
#define GPGDAT (*(volatile unsigned long *)0x56000064)
/*
* LED1,LED2,LED4对应GPF4、GPF5、GPF6
*/
#define GPF4_out (1<<(4*2))
#define GPF5_out (1<<(5*2))
#define GPF6_out (1<<(6*2))
#define GPF4_msk (3<<(4*2))
#define GPF5_msk (3<<(5*2))
#define GPF6_msk (3<<(6*2))
/*
* S2,S3,S4对应GPF0、GPF2、GPG3
*/
#define GPF0_in (0<<(0*2))
#define GPF2_in (0<<(2*2))
#define GPG3_in (0<<(3*2))
#define GPF0_msk (3<<(0*2))
#define GPF2_msk (3<<(2*2))
#define GPG3_msk (3<<(3*2))
int main()
{
unsigned long dwDat;
// LED1,LED2,LED4对应的3根引脚设为输出
GPFCON &= ~(GPF4_msk | GPF5_msk | GPF6_msk);
GPFCON |= GPF4_out | GPF5_out | GPF6_out;
// S2,S3对应的2根引脚设为输入
GPFCON &= ~(GPF0_msk | GPF2_msk);
GPFCON |= GPF0_in | GPF2_in;
// S4对应的引脚设为输入
GPGCON &= ~GPG3_msk;
GPGCON |= GPG3_in;
while(1){
//若Kn为0(表示按下),则令LEDn为0(表示点亮)
dwDat = GPFDAT; // 读取GPF管脚电平状态
if (dwDat & (1<<0)) // S2没有按下
GPFDAT |= (1<<4); // LED1熄灭
else
GPFDAT &= ~(1<<4); // LED1点亮
if (dwDat & (1<<2)) // S3没有按下
GPFDAT |= (1<<5); // LED2熄灭
else
GPFDAT &= ~(1<<5); // LED2点亮
dwDat = GPGDAT; // 读取GPG管脚电平状态
if (dwDat & (1<<3)) // S4没有按下
GPFDAT |= (1<<6); // LED3熄灭
else
GPFDAT &= ~(1<<6); // LED3点亮
}
return 0;
}
位操作的原因:不要影响不要别的位;以前的代码是直接设置整个寄存的值,这样可能会影响别的位。
其中
GPFCON &= ~(GPF4_msk | GPF5_msk | GPF6_msk);
表示这个三个寄存器对应的位先全部清0;
其中GPFCON |= GPF4_out | GPF5_out | GPF6_out;
设置相应寄存器的值;
读取GPF寄存器
dwDat = GPFDAT; // 读取GPF管脚电平状态
dwDat = GPGDAT; // 读取GPG管脚电平状态
这里读取是整个寄存的值,需要根据寄存器的某一位来判断相应管脚的状态。
至此,整个通过按键控制LED的代码分析完毕。
最后总结一下按位操作:
如果想对某一位清0操作?位与
如果 想对某一位置1? 位或