嵌入式Linux应用开发完全手册(二)GPIO

时间:2021-01-11 18:48:21

5 GPIO接口

5.1 GPIO硬件接口介绍

  • GPIO General Purpose I/O Port,通用输入、输出端口。简单说就是这个端口可以配成输入的(读电平信号),也可以配成输出的(设置电平信号)
  • 无论是输入还是输出都是通过寄存器来实现的
    • 输入 通过读某个寄存器来确定引脚电平是高还是低,是1还是0
    • 输出 通过写入某个寄存器让这个引脚输出高电平或者低电平,1或者0
  • 具体的寄存器设置需要看硬件手册,这里以2440为例
    • GPxCON寄存器
    • GPxDAT寄存器
    • GPxUP寄存器
      嵌入式Linux应用开发完全手册(二)GPIO
      嵌入式Linux应用开发完全手册(二)GPIO
      嵌入式Linux应用开发完全手册(二)GPIO
      以上是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组是通用的
    嵌入式Linux应用开发完全手册(二)GPIO
    嵌入式Linux应用开发完全手册(二)GPIO
    嵌入式Linux应用开发完全手册(二)GPIO
    这是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的连线
    嵌入式Linux应用开发完全手册(二)GPIO
    能看到一共有3个LED灯,电源3.3V,分别连接了连线nLED1, nLED2, nLED4

那么着条线分别连接到了2440的哪个接口呢

嵌入式Linux应用开发完全手册(二)GPIO

从上图可以看到,这三条线分别连接到了2440的GPF4, GPF5,GPF6。
这样的话,通过设置这三个端口到输出端口,如果是高电平,LED灯没有电势差,不会亮;如果输出0,那么有电势差,LED灯会亮。
* 点亮LED灯的步骤如下 *
- 设置GPIO F组的控制寄存器,GPF4到GPF6为输出端口
- 控制GPF4到GPF6
- 1 高电平 LED不亮
- 0 低电平 LED亮

嵌入式Linux应用开发完全手册(二)GPIO

从上图查到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;
}