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

2019-07-12 16:04发布

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