介绍
这是开发板的第一个硬件实验,以最简单的GPIO为实验对象,通过操作IO端口的输出电压,进行LED灯的点亮实验。
关闭看门狗
看门狗定时器内部有一个递减计数器,当该计数器递减为0的时候,就会自动发出复位信号。如果我们在计数器递减为0之前,将其重新设置一下(喂狗),那么就不会产生复位信号。假如机器设备出现异常情况如死机、程序跑飞等情况,CPU就不会去重置计数器。当计数器递减为0时,会产生复位信号,机器就会重新启动,恢复正常执行流程。这样的设计原理就解决了很多环境恶劣的情况下,对SoC进行重启的任务。上面的重置计数器的操作通常叫做“喂狗”。
为简单起见,我们先关闭看门狗功能,防止CPU自动重启。关于使能看门狗后怎么设置看门狗寄存器,在后续文章中在进行介绍。
从上图可知,想要关闭看门狗,只需将WTCON寄存器全置0即可。
@ WATCHDOG寄存器地址
ldr R0, =0x53000000
@ 写入0,禁止WATCHDOG,否则CPU会不断重启
mov R1, #0x0
str R1, [R0]
硬件原理图
GPIO的全称是通用的输入/输出(General Purpose Input/Output),每个端口引脚有三个功能:
- 输入功能
- 输出功能
- 其他功能,通常情况下为中断功能
想要点亮LED灯,从原理图上可以看出,需要对应的引脚nLED_1、nLED_2和nLED_4输出低电平。而这三个引脚对应的SoC的GPIO分别为GPF4、GPF5和GPF6,如上图红框中所示。
配置寄存器
GPIO
通过查看S3C2440用户手册第9章IO Ports,可以看到S3C2440的GPIO分为9组,如下所示:
而我们所需要配置的三个引脚属于GPF组。在S3C2440中,大多数的引脚都是复用引脚。所以我们需要选择每个引脚处于什么功能状态,这就需要我们配置PORT CONFIGURATION REGISTER (GPACON-GPJCON)。
GPFCON
对于GPF4、GPF5、GPF6这三个引脚,我们需要配置其为输出功能,即将GPFCON[9:8]、GPFCON[11:10]、GPFCON[13:12]均配置为0b01。
GPFDAT
当选定了引脚的功能之后,就需要对GPFDAT数据寄存器进行操作了。如果将端口设置为输出功能,那么可以写入GPFDAT寄存器相应的比特位来控制端口输出电压的高低;如果将端口设置为输入功能,那可以通过读取GPFDAT寄存器相应的比特位来获取对应端口输入电压的高低。
实验1 汇编点亮LED
为简单起见,我们先采用ARM汇编语言来实现点灯功能,实现一次性点亮3个LED灯。
LED.S文件
.text
.global _start
_start:
@ WATCHDOG寄存器地址
ldr R0, =0x53000000
@ 写入0,禁止WATCHDOG,否则CPU会不断重启
mov R1, #0x0
str R1, [R0]
@ 将R0设为GPFCON寄存器的地址
@ 此寄存器用于选择端口F各引脚的功能是输出、输入还是其他
LDR R0, =0x56000050
@ 设置GPF4、GPF5、GPF6为输出口
MOV R1, #((0x1 << 8) | (0x1 << 10) | (0x1 << 12))
STR R1,[R0]
@ R0设为GPFDAT寄存器的地址
@ 此寄存器用于读/写端口F各引脚的数据
LDR R0,=0x56000054
@ 此值改为0x00000010,可让LED1、LED2、LED4熄灭
MOV R1,#0x00000000
@ GPF4、GPF5、GPF6输出0,LED1、LED2、LED4点亮
STR R1,[R0]
MAIN_LOOP:
B MAIN_LOOP
Makefile文件
LED.bin : LED.S
arm-linux-gcc -g -c -o LED.o $^
arm-linux-ld -Ttext 0x0000000 -g LED.o -o LED.elf
arm-linux-objcopy -O binary -S LED.elf $@
clean:
rm -f LED.bin LED.elf *.o
编译烧写到NandFlash之后,启动开发板,可以看到三个LED灯均被点亮。
实验2 C语言循环点亮LED
我们接着采用C语言来实验同样的功能。如果想要使用C语言,则需要初始化栈寄存器sp。想要设置栈指针,就需要先了解内存分布情况。查看用户手册第5章内存控制器,可知内存分布情况如下图所示:
当使用Nand Flash启动时,0x00000000开始的4KB内存空间映射到了SoC片内SRAM上。由于现在片外的SDRAM芯片尚未初始化,故我们只有片内的4KB内存可用。又由于ARM的栈默认是向下生长,故我们将栈指针sp设置为最高的4KB地址处。
故可得我们的CRT0.S文件如下所示:
.text
.global _start
_start:
@ WATCHDOG寄存器地址
ldr R0, =0x53000000
@ 写入0,禁止WATCHDOG,否则CPU会不断重启
mov R1, #0x0
str R1, [R0]
ldr sp, =4096
@ 调用C的main函数
bl main
HALT_LOOP:
B HALT_LOOP
LEDs.c文件内容如下所示:
#define GPFCON (*(volatile unsigned int *)0x56000050)
#define GPFDAT (*(volatile unsigned int *)0x56000054)
void wait(volatile unsigned int delay) {
unsigned int idx = delay;
for(; idx > 0; idx--)
;
}
int main(void) {
//设置GPF4、GPF5、GPF6为输出口
GPFCON = ((0x1 << 8) | (0x1 << 10) | (0x1 << 12));
while(1) {
//GPF4输出0,LED1点亮
GPFDAT = ~(0x1 << 4);
//加入延时,方便观察
wait(60000);
//GPF5输出0,LED2点亮
GPFDAT = ~(0x1 << 5);
//加入延时,方便观察
wait(60000);
//GPF6输出0,LED4点亮
GPFDAT = ~(0x1 << 6);
//加入延时,方便观察
wait(60000);
}
return 0;
}
相应的Makefile文件如下所示:
LEDs.bin : CRT0.o LEDs.o
arm-linux-ld -Ttext 0x0000000 -g $^ -o LEDs.elf
arm-linux-objcopy -O binary -S LEDs.elf $@
CRT0.o : CRT0.S
arm-linux-gcc -g -c -o $@ $^
LEDs.o : LEDs.c
arm-linux-gcc -g -c -o $@ $^
clean:
rm -f LEDs.bin LEDs.elf *.o
编译烧写到NandFlash之后,启动开发板,可以看到三个LED灯被循环点亮。