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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/guanchunsheng/article/details/77145529

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;
}

猜你喜欢

转载自blog.csdn.net/guanchunsheng/article/details/77145529