通用 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 呢?
问题的解决
本文基于正点原子 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 源码
进入 rt-thread\bsp\stm32f4xx-HAL 目录,在 env 命令行中输入 menuconfig,进入配置界面,使用 menuconfig 工具(学习如何使用)配置工程。
(1) 在 menuconfig 配置界面依次选择 RT-Thread Components —-> Device Drivers —-> Using generic GPIO device drivers,如图所示:
(2) 输入命令scons —target=mdk5 -s
生成 mdk5 工程。将示例代码附带的 main.c 替换掉 bsp 中的 main.c,如图所示:
(3) 编译,下载程序,在终端输入 list_device 命令可以看到 device 是 pin、类型是 Miscellaneous Device 就说明通用 GPIO 设备驱动添加成功了。
下面是 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 不亮。
- #define LED0 21 //PF9--21,在 drv_gpio.c 文件 pin_index pins[] 中查到 PF9 编号为 21
- #define LED1 22 //PF10--22,在 drv_gpio.c 文件 pin_index pins[] 中查到 PF10 编号为 22
- void led_thread_entry(void* parameter)
- {
- // 设置管脚为输出模式
- rt_pin_mode(LED0, PIN_MODE_OUTPUT);
- // 设置管脚为输出模式
- rt_pin_mode(LED1, PIN_MODE_OUTPUT);
- while (1)
- {
- // 输出低电平,LED0 亮
- rt_pin_write(LED0, PIN_LOW);
- // 输出低电平,LED1 亮
- rt_pin_write(LED1, PIN_LOW);
- // 挂起 500ms
- rt_thread_delay(rt_tick_from_millisecond(500));
- // 输出高电平,LED0 灭
- rt_pin_write(LED0, PIN_HIGH);
- // 输出高电平,LED1 灭
- rt_pin_write(LED1, PIN_HIGH);
- // 挂起 500ms
- rt_thread_delay(rt_tick_from_millisecond(500));
- }
- }
在线程入口函数 led_thread_entry 里首先调用 rt_pin_mode 设置管脚模式为输出模式,然后就进入 while(1) 循环,间隔 500ms 调用 rt_pin_write 来改变 GPIO 输出电平。下面是创建线程的代码:
- rt_thread_t tid;// 线程句柄
- /* 创建 led 线程 */
- tid = rt_thread_create("led",
- led_thread_entry,
- RT_NULL,
- 1024,
- 3,
- 10);
- /* 创建成功则启动线程 */
- if (tid != RT_NULL)
- rt_thread_startup(tid);
编译、下载程序,我们将看到 LED 间隔 500ms 闪烁的现象。
GPIO 输入配置
示例 2:配置 GPIOE3、GPIOE2 为上拉输入,GPIOA0 为下拉输入,检测按键信号。根据原理图,GPIOE3 连接到按键 KEY1,按键被按下时 GPIOE3 应读取到低电平,按键没有被按下时 GPIOE3 应读取到高电平;GPIOE2 连接到按键 KEY2,按键被按下时 GPIOE2 应读取到低电平,按键没有被按下时 GPIOE2 应读取到高电平;GPIOA0 连接到按键 WK_UP,按键被按下时 GPIOA0 应读取到高电平,按键没有被按下时 GPIOA0 应读取到低电平。
- #define KEY1 2 //PE3--2,在 drv_gpio.c 文件 pin_index pins[] 中查到 PE3 编号为 2
- #define KEY2 1 //PE2--1,在 drv_gpio.c 文件 pin_index pins[] 中查到 PE2 编号为 1
- #define WK_UP 34 //PA0--34,在 drv_gpio.c 文件 pin_index pins[] 中查到 PA0 编号为 34
- void key_thread_entry(void* parameter)
- {
- //PE2、PE3 设置上拉输入
- rt_pin_mode(KEY1, PIN_MODE_INPUT_PULLUP);
- rt_pin_mode(KEY2, PIN_MODE_INPUT_PULLUP);
- //PA0 设置为下拉输入
- rt_pin_mode(WK_UP, PIN_MODE_INPUT_PULLDOWN);
- while (1)
- {
- // 检测到低电平,即按键 1 按下了
- if (rt_pin_read(KEY1) == PIN_LOW)
- {
- rt_kprintf("key1 pressed!\n");
- }
- // 检测到低电平,即按键 2 按下了
- if (rt_pin_read(KEY2) == PIN_LOW)
- {
- rt_kprintf("key2 pressed!\n");
- }
- // 检测到高电平,即按键 wp 按下了
- if (rt_pin_read(WK_UP) == PIN_HIGH)
- {
- rt_kprintf("WK_UP pressed!\n");
- }
- // 挂起 10ms
- rt_thread_delay(rt_tick_from_millisecond(10));
- }
- }
在线程入口函数 key_thread_entry 里首先调用 rt_pin_mode 设置管脚 GPIOE3 为上拉输入模式。这样当用户按下按键 KEY1 时,GPIOE3 读取到的电平是低电平;按键未被按下时,GPIOE3 读取到的电平是高电平。然后进入 while(1) 循环,调用 rt_pin_read 读取管脚 GPIOE3 电平,如果读取到低电平则表示按键 KEY1 被按下,就在终端打印字符串 "key1 pressed!"。每隔 10ms 检测一次按键输入情况。下面是创建线程的代码:
- rt_thread_t tid;
- /* 创建 key 线程 */
- tid = rt_thread_create("key",
- key_thread_entry,
- RT_NULL,
- 1024,
- 2,
- 10);
- /* 创建成功则启动线程 */
- if (tid != RT_NULL)
- rt_thread_startup(tid);
编译、下载程序,我们按下开发板上的用户按键,终端将打印提示字符。
GPIO 中断配置
示例 3:配置 GPIO 为外部中断模式、下降沿触发,检测按键信号。根据原理图,GPIOE4 连接到按键 KEY0,按键被按下时 MCU 应探测到电平下降沿。
- #define KEY0 3 //PE4--3,在 gpio.c 文件 pin_index pins[] 中查到 PE4 编号为 3
- void hdr_callback(void *args)// 回调函数
- {
- char *a = args;// 获取参数
- rt_kprintf("key0 down! %s\n",a);
- }
- void irq_thread_entry(void* parameter)
- {
- // 上拉输入
- rt_pin_mode(KEY0, PIN_MODE_INPUT_PULLUP);
- // 绑定中断,下降沿模式,回调函数名为 hdr_callback
- rt_pin_attach_irq(KEY0, PIN_IRQ_MODE_FALLING, hdr_callback, (void*)"callback
- args");
- // 使能中断
- rt_pin_irq_enable(KEY0, PIN_IRQ_ENABLE);
- }
在线程入口函数 irq_thread_entry 里首先调用 rt_pin_attach_irq 设置管脚 GPIOE4 为下降沿中断模式,并绑定了中断回调函数,还传入了字符串 "callback args"。然后调用 rt_pin_irq_enable 使能中断,这样按键 KEY0 被按下时 MCU 会检测到电平下降沿,触发外部中断,在中断服务程序中会调用回调函数 hdr_callback,在回调函数中打印传入的参数和提示信息。下面是创建线程的代码:
- rt_thread_t tid;// 线程句柄
- /* 创建 irq 线程 */
- tid = rt_thread_create("exirq",
- irq_thread_entry,
- RT_NULL,
- 1024,
- 4,
- 10);
- /* 创建成功则启动线程 */
- if (tid != RT_NULL)
- 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。
更多关于 I/O 设备管理框架的说明和串口驱动实现细节,请参考《RT-Thread 编程手册》第 6 章I/O 设备管理
在线查看地址:链接
参考
本文所有相关的 API
用户应用代码要使用 RT-Thread GPIO 驱动接口需在 menuconfig 中开启 GPIO 驱动,引用头文件 rtdevice.h
。
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()
函数原型
- 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[] 中可以找到如下代码:
其中 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()
函数原型
- void rt_pin_write(rt_base_t pin, rt_base_t value)
函数参数
参数 | 描述 |
---|---|
pin | 管脚编号 |
value | 电平逻辑值,可取 2 种值之一,PIN_LOW 低电平,PIN_HIGH 高电平 |
函数返回 无
此函数可设置管脚输出电平。
rt_pin_read()
函数原型
- int rt_pin_read(rt_base_t pin)
函数参数
参数 | 描述 |
---|---|
pin | 管脚编号 |
函数返回
返回值 | 描述 |
---|---|
PIN_LOW | 低电平 |
PIN_HIGH | 高电平 |
此函数可读取输入管脚电平。
rt_pin_attach_irq()
函数原型
- rt_err_t rt_pin_attach_irq( rt_int32_t pin, rt_uint32_t mode,
- 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 边沿触发(上升沿和下降沿都触发)
此函数可绑定中断回调函数。
绑定中断回调函数传递字符串示例:
- rt_pin_attach_irq(3, PIN_IRQ_MODE_FALLING,hdr_callback, (void*)"callback args");
- void hdr_callback(void *args)
- {
- char *a = args;
- rt_kprintf("%s",a);
- }
输出为 "callback args"。
传递数值示例:
- rt_pin_attach_irq(3, PIN_IRQ_MODE_FALLING,hdr_callback, (void*)6);
- void hdr_callback(void *args)
- {
- Int a = (int)args;
- rt_kprintf("%d",a);
- }
输出为 6。
rt_pin_detach_irq()
函数原型
- rt_err_t rt_pin_detach_irq(rt_int32_t pin)
函数参数
参数 | 描述 |
---|---|
pin | 管脚编号 |
函数返回
返回值 | 描述 |
---|---|
RT_EOK | 成功 |
RT_ENOSYS | 出错 |
此函数可使管脚中断回调函数脱离。
rt_pin_irq_enable()
函数原型
- 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 | 出错 |