5 GPIO接口
5.1 GPIO硬件接口介绍
- GPIO General Purpose I/O Port,通用输入、输出端口。简单说就是这个端口可以配成输入的(读电平信号),也可以配成输出的(设置电平信号)
- 无论是输入还是输出都是通过寄存器来实现的
- 输入 通过读某个寄存器来确定引脚电平是高还是低,是1还是0
- 输出 通过写入某个寄存器让这个引脚输出高电平或者低电平,1或者0
- 具体的寄存器设置需要看硬件手册,这里以2440为例
- GPxCON寄存器
- GPxDAT寄存器
- GPxUP寄存器
以上是GPACON GPADAT寄存器的手册说明。
从中可以看到A组GPIO的控制和数据寄存器的地址,编码格式。
- GPACON和GPADAT都是4字节,32位的寄存器,地址分别是0x56000000 0x56000004
- GPACON的有效配置位是0到24,分别对应GPA0到GPA24,每个端口一位
- GPACON的每一个端口,配置成0表示用作output端口,配置为1代表不同的控制信号,用作各种专门的用途
- GPADAT的0到24位,对应GPA0到24,用作output端口时,管脚的电平根据寄存器的设置来输出;用作其他用途的时候根据各自的用途,芯片自动设置
- GP A组的管脚作用特殊,我们可以看下B组的控制寄存器格式,B组是通用的
这是B组的GPIO寄存器格式,用3个寄存器控制。GPBCON的控制配置,GPBDAT的数据配置,GPBUP的状态配置。 - 与A组不同,GPBCON每2位控制一个GPIO管脚,可以有4种工作方式
- 00 输入
- 01 输出
- 10 特殊功能
- 11 保留
- GPBDAT
- 对应管脚是输入管脚的时候,通过对应的寄存器位判断输入信号
- 对应管脚是输出管脚的时候,通过设置对应的寄存器位,输出信号
- GPBUP
- 0 对应的管脚使用上拉电阻
- 1 对应管脚不使用上拉电阻
- 关于上拉和下拉,指的是管脚悬空的时候,保持高电平状态还是低电平状态,可以简单的这里理解
- 寄存器的操作 *
- 寄存器的地址和格式定义都已经了解了,下面就是怎么操作
- 寄存器地址类型转换为 volatile unsigned long 指针
- 对这个指针进行间接寻址操作,位操作,清零,置1
#define GPBCON (*(volatile unsigned long *)0x56000010)
#define GPBDAT (*(volatile unsigned long *)0x56000014)
#define GPB5_out (1 << (5 * 2))
GPBCON |= GPB5_out; // GPB5管脚设置为输出管脚,其他部分不变
GPBDAT &= ~(1 << 5); // GPB5输出低电平
5.2 实例 点亮LED
汇编实现
- 先看电路图,找到LED的连线
能看到一共有3个LED灯,电源3.3V,分别连接了连线nLED1, nLED2, nLED4
那么着条线分别连接到了2440的哪个接口呢
从上图可以看到,这三条线分别连接到了2440的GPF4, GPF5,GPF6。
这样的话,通过设置这三个端口到输出端口,如果是高电平,LED灯没有电势差,不会亮;如果输出0,那么有电势差,LED灯会亮。
* 点亮LED灯的步骤如下 *
- 设置GPIO F组的控制寄存器,GPF4到GPF6为输出端口
- 控制GPF4到GPF6
- 1 高电平 LED不亮
- 0 低电平 LED亮
从上图查到GPF的寄存器地址。
[led_on.s]
.text
.global _start
_start:
LDR R0,=0x56000050 @ R0设为GPBCON寄存器,选择F组GPIO引脚功能
MOV R1,#0x00000500
STR R1,[R0] @ 这3个指令是存入某个寄存器一个给定值的套路
LDR R0,=0x56000054
MOV R1,#0x00000000
STR R1,[R0] @ GPF4,5输出0,点亮LED1,2 保持LED3不亮
MAIN_LOOP: @ 代码部分注意不要在中文输入模式,例如这句的冒号,如果是中文冒号,会显示“无效指令 main_loop:”
B MAIN_LOOP
[Makefile]
led_on.bin : led_on.s
arm-linux-gcc -c -o led_on.o led_on.s
arm-linux-ld -Ttext 0x00000000 -o led_on_elf led_on.o
arm-linux-objcopy -O binary -S led_on_elf led_on.bin
clean :
rm *.o led_on_elf led_on.bin
生成的bin文件烧入开发板的0地址,即可观察结果。
C语言实现
- C语言应用程序的入口是main,但是在基于操作系统的C语言程序中,调用main是操作系统完成的。在调用main之前还调用了crtl.o crti.o crtend.o ctrn.o这几个启动文件。裸板程序无法依赖这些启动文件,因此需要自己编写启动文件,启动之后再调用main函数。 *
- 编写启动代码
[crt0.s]
.text
.global _start
_start:
ldr r0,=0x53000000 @ 第一步关闭看门狗
mov r1,#0
str r1,[r0]
ldr sp,=1024*4 @ 设置堆栈
bl main @ 调用C语言程序main函数
halt_loop:
b halt_loop
- 编写C语言程序
[led_on_c.c]
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPF_OUT(x) (1 << (2 * (x)))
#define GPF_SET(x) (1 << (x))
int main()
{
/* 设置GPF4 5 6为输出端口,GPF4 GPF6为高电平,LED2亮,LED1和3不亮 */
GPFCON = GPF_OUT(4) | GPF_OUT(5) | GPF_OUT(6);
GPFDAT = GPF_SET(4) | GPF_SET(6);
return 0;
}
- 编写Makefile
[Makefile]
led_on_c.bin : led_on_c.c crt0.s
arm-linux-gcc -c led_on_c.c -o led_on_c.o
arm-linux-gcc -c crt0.s -o crt0.o
arm-linux-ld -Ttext 0x00000000 crt0.o led_on_c.o -o led_on_c_elf
arm-linux-objcopy -O binary -S led_on_c_elf led_on_c.bin
clean :
rm *.o led_on_c_elf led_on_c.bin
- 扩展,让LED灯按照2进制计数器的方式闪烁
#define GPFCON (*(volatile unsigned long *)0x56000050)
#define GPFDAT (*(volatile unsigned long *)0x56000054)
#define GPF_OUT(x) (1 << (2 * (x)))
#define LED_NUM(x) (~(((x) & ~((~0) << 3)) << 4))
int main()
{
unsigned int i;
unsigned long j;
/* led to count, in binary */
GPFCON = GPF_OUT(4) | GPF_OUT(5) | GPF_OUT(6);
for (i = 0;;i = ++i % 8)
{
GPFDAT = LED_NUM(i);
for (j = 0; j < 1000000; j++)
;
}
return 0;
}