通用 GPIO 设备应用笔记

摘要

本应用笔记描述了如何使用 RT-Thread 的通用 GPIO 设备驱动,包括驱动的配置、相关 API 的应用。并给出了在正点原子 STM32F4 探索者开发板上验证的代码示例。

本文的目的和结构

本文的目的和背景

为了给用户提供操作 GPIO 的通用 API,方便应用程序开发,RT-Thread 中引入了通用 GPIO 设备驱动。并提供类似 Arduino 风格的 API 用于操作 GPIO,如设置 GPIO 模式和输出电平、读取 GPIO 输入电平、配置 GPIO 外部中断。本文说明了如何使用 RT-Thread 的通用 GPIO 设备驱动。

本文的结构

本文首先描述了 RT-Thread 通用 GPIO 设备驱动的基本情况,接下来给出了在正点原子 STM32F4 探索者开发板上验证的代码示例,最后详细描述了通用 GPIO 设备驱动 API 的参数取值和注意事项。

问题阐述

RT-Thread 提供了一套简单的 I/O 设备管理框架,它把 I/O 设备分成了三层进行处理:应用层、I/O 设备管理层、硬件驱动层。应用程序通过 RT-Thread 的设备操作接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互。RT-Thread 提供给上层应用的是一个抽象的设备操作接口,给下层设备提供的是底层驱动框架。对于通用 GPIO 设备,应用程序既可以通过设备操作接口访问,又可以直接通过通用 GPIO 设备驱动来访问。一般来说,我们都是使用第二种方式,那么如何在 RT-Thread 中使用通用 GPIO 设备驱动从而操作 GPIO 呢?

RT-Thread 设备管理框架

问题的解决

本文基于正点原子 STM32F4 探索者开发板,给出了通用 GPIO 设备的具体应用示例代码,包含管脚输入、输出和外部中断的使用方法。由于 RT-Thread 上层应用 API 的通用性,因此这些代码不局限于具体的硬件平台,用户可以轻松将它移植到其它平台上。正点原子 STM32F4 探索者开发板使用的 MCU 是 STM32F407ZGT6,板载 2 颗 LED 和 4 个独立按键。LED 分别连接到 MCU 的 GPIOF9、GPIOF10,KEY0 按键连接到 GPIOE4,KEY1 按键连接到 GPIOE3,KEY2 按键连接到 GPIOE2,WK_UP 按键连接到 GPIOA0,2 颗 LED 均为低电平点亮,独立按键 KEY0、KEY1、KEY2 按下为低电平;WK_UP 按下为高电平。

正点原子开发板

准备和配置工程

  • 下载 RT-Thread 源码

  • 下载 GPIO 设备驱动示例代码

  • 进入 rt-thread\bsp\stm32f4xx-HAL 目录,在 env 命令行中输入 menuconfig,进入配置界面,使用 menuconfig 工具(学习如何使用)配置工程。

(1) 在 menuconfig 配置界面依次选择 RT-Thread Components —-> Device Drivers —-> Using generic GPIO device drivers,如图所示:

menuconfig 中开启 GPIO 驱动

(2) 输入命令scons —target=mdk5 -s生成 mdk5 工程。将示例代码附带的 main.c 替换掉 bsp 中的 main.c,如图所示:

加入测试代码

(3) 编译,下载程序,在终端输入 list_device 命令可以看到 device 是 pin、类型是 Miscellaneous Device 就说明通用 GPIO 设备驱动添加成功了。

查看 pin 设备

下面是 3 个通用 GPIO 设备驱动 API 应用示例,分别是:GPIO 输出、GPIO 输入、GPIO 外部中断,这些代码在正点原子 STM32F4 探索者开发板上验证通过。

GPIO 输出配置

示例 1:配置 GPIO 为输出,点亮 LED。根据原理图,GPIOF9 连接到了板载红色 LED,丝印为 DS0;GPIOF10 连接到了板载绿色 LED,丝印为 DS1。GPIOF9 输出低电平则点亮 DS0,GPIOF9 输出高电平则 DS0 不亮;GPIOF10 输出低电平则点亮 DS1,GPIOF10 输出高电平则 DS1 不亮。

LED 原理图

  1. #define LED0 21 //PF9--21,在 drv_gpio.c 文件 pin_index pins[] 中查到 PF9 编号为 21
  2. #define LED1 22 //PF10--22,在 drv_gpio.c 文件 pin_index pins[] 中查到 PF10 编号为 22
  3. void led_thread_entry(void* parameter)
  4. {
  5. // 设置管脚为输出模式
  6. rt_pin_mode(LED0, PIN_MODE_OUTPUT);
  7. // 设置管脚为输出模式
  8. rt_pin_mode(LED1, PIN_MODE_OUTPUT);
  9. while (1)
  10. {
  11. // 输出低电平,LED0 亮
  12. rt_pin_write(LED0, PIN_LOW);
  13. // 输出低电平,LED1 亮
  14. rt_pin_write(LED1, PIN_LOW);
  15. // 挂起 500ms
  16. rt_thread_delay(rt_tick_from_millisecond(500));
  17.  
  18. // 输出高电平,LED0 灭
  19. rt_pin_write(LED0, PIN_HIGH);
  20. // 输出高电平,LED1 灭
  21. rt_pin_write(LED1, PIN_HIGH);
  22. // 挂起 500ms
  23. rt_thread_delay(rt_tick_from_millisecond(500));
  24. }
  25. }

在线程入口函数 led_thread_entry 里首先调用 rt_pin_mode 设置管脚模式为输出模式,然后就进入 while(1) 循环,间隔 500ms 调用 rt_pin_write 来改变 GPIO 输出电平。下面是创建线程的代码:

  1. rt_thread_t tid;// 线程句柄
  2. /* 创建 led 线程 */
  3. tid = rt_thread_create("led",
  4. led_thread_entry,
  5. RT_NULL,
  6. 1024,
  7. 3,
  8. 10);
  9. /* 创建成功则启动线程 */
  10. if (tid != RT_NULL)
  11. rt_thread_startup(tid);

编译、下载程序,我们将看到 LED 间隔 500ms 闪烁的现象。

GPIO 输入配置

示例 2:配置 GPIOE3、GPIOE2 为上拉输入,GPIOA0 为下拉输入,检测按键信号。根据原理图,GPIOE3 连接到按键 KEY1,按键被按下时 GPIOE3 应读取到低电平,按键没有被按下时 GPIOE3 应读取到高电平;GPIOE2 连接到按键 KEY2,按键被按下时 GPIOE2 应读取到低电平,按键没有被按下时 GPIOE2 应读取到高电平;GPIOA0 连接到按键 WK_UP,按键被按下时 GPIOA0 应读取到高电平,按键没有被按下时 GPIOA0 应读取到低电平。

按键原理图

  1. #define KEY1 2 //PE3--2,在 drv_gpio.c 文件 pin_index pins[] 中查到 PE3 编号为 2
  2. #define KEY2 1 //PE2--1,在 drv_gpio.c 文件 pin_index pins[] 中查到 PE2 编号为 1
  3. #define WK_UP 34 //PA0--34,在 drv_gpio.c 文件 pin_index pins[] 中查到 PA0 编号为 34
  4. void key_thread_entry(void* parameter)
  5. {
  6. //PE2、PE3 设置上拉输入
  7. rt_pin_mode(KEY1, PIN_MODE_INPUT_PULLUP);
  8. rt_pin_mode(KEY2, PIN_MODE_INPUT_PULLUP);
  9. //PA0 设置为下拉输入
  10. rt_pin_mode(WK_UP, PIN_MODE_INPUT_PULLDOWN);
  11.  
  12. while (1)
  13. {
  14. // 检测到低电平,即按键 1 按下了
  15. if (rt_pin_read(KEY1) == PIN_LOW)
  16. {
  17. rt_kprintf("key1 pressed!\n");
  18. }
  19. // 检测到低电平,即按键 2 按下了
  20. if (rt_pin_read(KEY2) == PIN_LOW)
  21. {
  22. rt_kprintf("key2 pressed!\n");
  23. }
  24. // 检测到高电平,即按键 wp 按下了
  25. if (rt_pin_read(WK_UP) == PIN_HIGH)
  26. {
  27. rt_kprintf("WK_UP pressed!\n");
  28. }
  29. // 挂起 10ms
  30. rt_thread_delay(rt_tick_from_millisecond(10));
  31. }
  32. }

在线程入口函数 key_thread_entry 里首先调用 rt_pin_mode 设置管脚 GPIOE3 为上拉输入模式。这样当用户按下按键 KEY1 时,GPIOE3 读取到的电平是低电平;按键未被按下时,GPIOE3 读取到的电平是高电平。然后进入 while(1) 循环,调用 rt_pin_read 读取管脚 GPIOE3 电平,如果读取到低电平则表示按键 KEY1 被按下,就在终端打印字符串 "key1 pressed!"。每隔 10ms 检测一次按键输入情况。下面是创建线程的代码:

  1. rt_thread_t tid;
  2. /* 创建 key 线程 */
  3. tid = rt_thread_create("key",
  4. key_thread_entry,
  5. RT_NULL,
  6. 1024,
  7. 2,
  8. 10);
  9. /* 创建成功则启动线程 */
  10. if (tid != RT_NULL)
  11. rt_thread_startup(tid);

编译、下载程序,我们按下开发板上的用户按键,终端将打印提示字符。

GPIO 中断配置

示例 3:配置 GPIO 为外部中断模式、下降沿触发,检测按键信号。根据原理图,GPIOE4 连接到按键 KEY0,按键被按下时 MCU 应探测到电平下降沿。

  1. #define KEY0 3 //PE4--3,在 gpio.c 文件 pin_index pins[] 中查到 PE4 编号为 3
  2. void hdr_callback(void *args)// 回调函数
  3. {
  4. char *a = args;// 获取参数
  5. rt_kprintf("key0 down! %s\n",a);
  6. }
  7.  
  8. void irq_thread_entry(void* parameter)
  9. {
  10. // 上拉输入
  11. rt_pin_mode(KEY0, PIN_MODE_INPUT_PULLUP);
  12. // 绑定中断,下降沿模式,回调函数名为 hdr_callback
  13. rt_pin_attach_irq(KEY0, PIN_IRQ_MODE_FALLING, hdr_callback, (void*)"callback
  14. args");
  15. // 使能中断
  16. rt_pin_irq_enable(KEY0, PIN_IRQ_ENABLE);
  17.  
  18. }

在线程入口函数 irq_thread_entry 里首先调用 rt_pin_attach_irq 设置管脚 GPIOE4 为下降沿中断模式,并绑定了中断回调函数,还传入了字符串 "callback args"。然后调用 rt_pin_irq_enable 使能中断,这样按键 KEY0 被按下时 MCU 会检测到电平下降沿,触发外部中断,在中断服务程序中会调用回调函数 hdr_callback,在回调函数中打印传入的参数和提示信息。下面是创建线程的代码:

  1. rt_thread_t tid;// 线程句柄
  2. /* 创建 irq 线程 */
  3. tid = rt_thread_create("exirq",
  4. irq_thread_entry,
  5. RT_NULL,
  6. 1024,
  7. 4,
  8. 10);
  9. /* 创建成功则启动线程 */
  10. if (tid != RT_NULL)
  11. rt_thread_startup(tid);

编译、下载程序,我们按下按键 KEY0,终端将打印提示字符。

I/O 设备管理框架和通用 GPIO 设备的联系

RT-Thread 自动初始化功能依次调用 rt_hw_pin_init ===> rt_device_pin_register ===> rt_device_register 完成了 GPIO 硬件初始化。rt_device_register 注册设备类型为 RT_Device_Class_Miscellaneous,即杂类设备,从而我们就可以使用统一的 API 操作 GPIO。

通用 GPIO 驱动和设备管理框架联系

更多关于 I/O 设备管理框架的说明和串口驱动实现细节,请参考《RT-Thread 编程手册》第 6 章I/O 设备管理

在线查看地址:链接

参考

本文所有相关的 API

用户应用代码要使用 RT-Thread GPIO 驱动接口需在 menuconfig 中开启 GPIO 驱动,引用头文件 rtdevice.h

menuconfig 中开启 GPIO 驱动

API 列表

API 头文件
rt_pin_mode rt-thread\components\drivers\include\drivers\pin.h
rt_pin_write rt-thread\components\drivers\include\drivers\pin.h
rt_pin_read rt-thread\components\drivers\include\drivers\pin.h
rt_pin_attach_irq rt-thread\components\drivers\include\drivers\pin.h
rt_pin_detach_irq rt-thread\components\drivers\include\drivers\pin.h
rt_pin_irq_enable rt-thread\components\drivers\include\drivers\pin.h

核心 API 详解

rt_pin_mode()

函数原型

  1. void rt_pin_mode(rt_base_t pin, rt_base_t mode)

函数参数

参数 描述
pin 管脚编号
mode 模式

函数返回

此函数可设置管脚模式。

管脚编号,由驱动定义,在 drv_gpio.c 的 pin_index pins[] 中可以找到,以本文使用的 STM32F407ZGT6 为例,该芯片管脚数为 100,在 pin_index pins[] 中可以找到如下代码:

pin 编号

其中 STM32_PIN(1, E, 2) 表示 GPIOE2 的编号为 1,STM32_PIN(9, C, 15) 表示 GPIOC15 的编号为 9,以此类推。STM32_PIN() 的第一个参数为管脚编号,第二个参数为端口,第三个参数为管脚号。

模式可取如下 5 种之一:

PIN_MODE_OUTPUT 输出,具体模式看 drv_gpio.c 源码实现,本文使用的是推挽输出PIN_MODE_INPUT 输入PIN_MODE_INPUT_PULLUP 上拉输入PIN_MODE_INPUT_PULLDOWN 下拉输入PIN_MODE_OUTPUT_OD 开漏输出

rt_pin_write()

函数原型

  1. void rt_pin_write(rt_base_t pin, rt_base_t value)

函数参数

参数 描述
pin 管脚编号
value 电平逻辑值,可取 2 种值之一,PIN_LOW 低电平,PIN_HIGH 高电平

函数返回

此函数可设置管脚输出电平。

rt_pin_read()

函数原型

  1. int rt_pin_read(rt_base_t pin)

函数参数

参数 描述
pin 管脚编号

函数返回

返回值 描述
PIN_LOW 低电平
PIN_HIGH 高电平

此函数可读取输入管脚电平。

rt_pin_attach_irq()

函数原型

  1. rt_err_t rt_pin_attach_irq( rt_int32_t pin, rt_uint32_t mode,
  2. void (*hdr)(void *args), void *args)

函数参数

参数 描述
pin 管脚编号
mode 中断触发模式
hdr 中断回调函数,用户需要自行定义这个函数,其返回值为 void
args 中断回调函数的参数,不需要时设置为 RT_NULL

函数返回

返回值 描述
RT_EOK 成功
RT_ENOSYS 无系统
RT_EBUSY

中断触发模式可取以下 3 种值之一:

PIN_IRQ_MODE_RISING 上升沿触发PIN_IRQ_MODE_FALLING 下降沿触发PIN_IRQ_MODE_RISING_FALLING 边沿触发(上升沿和下降沿都触发)

此函数可绑定中断回调函数。

绑定中断回调函数传递字符串示例:

  1. rt_pin_attach_irq(3, PIN_IRQ_MODE_FALLING,hdr_callback, (void*)"callback args");
  2. void hdr_callback(void *args)
  3. {
  4. char *a = args;
  5.  
  6. rt_kprintf("%s",a);
  7.  
  8. }

输出为 "callback args"。

传递数值示例:

  1. rt_pin_attach_irq(3, PIN_IRQ_MODE_FALLING,hdr_callback, (void*)6);
  2. void hdr_callback(void *args)
  3. {
  4. Int a = (int)args;
  5.  
  6. rt_kprintf("%d",a);
  7. }

输出为 6。

rt_pin_detach_irq()

函数原型

  1. rt_err_t rt_pin_detach_irq(rt_int32_t pin)

函数参数

参数 描述
pin 管脚编号

函数返回

返回值 描述
RT_EOK 成功
RT_ENOSYS 出错

此函数可使管脚中断回调函数脱离。

rt_pin_irq_enable()

函数原型

  1. rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled)

函数参数

参数 描述
pin 管脚编号
enabled 状态,可取 2 种值之一:PIN_IRQ_ENABLE 开启,PIN_IRQ_DISABLE 关闭

函数返回

返回值 描述
RT_EOK 成功
RT_ENOSYS 出错

原文: https://www.rt-thread.org/document/site/rtthread-application-note/driver/gpio/an0002-rtthread-driver-gpio/