RTT_PIN

Tutorial: 卓越工程师 Category: 嵌入式 Published: 2026-10-23 11:05:29 Views: 1 Likes: 0 Comments: 0

269.3.5RTT_PIN

#include <rtthread.h>                                      // 引入 RT-Thread 内核接口,本例用到 rt_kprintf() 和 MSH 导出宏。
#include <rtdevice.h>                                      // 引入 RT-Thread 设备接口,本例用到 PIN 设备 API 及相关宏。

#ifndef BEEP_PIN_NUM                                       // 仅在工程没有预先定义蜂鸣器引脚号时使用下面的默认值。
    #define BEEP_PIN_NUM            24                    // 将蜂鸣器控制端设为本 BSP 中的 24 号 PIN,也就是 PB8。
#endif                                                     // 结束 BEEP_PIN_NUM 的条件定义。
#ifndef KEY0_PIN_NUM                                       // 仅在工程没有预先定义第一个按键引脚号时使用默认值。
    #define KEY0_PIN_NUM            66                    // 将第一个按键设为本 BSP 中的 66 号 PIN,也就是 PE2。
#endif                                                     // 结束 KEY0_PIN_NUM 的条件定义。
#ifndef KEY1_PIN_NUM                                       // 仅在工程没有预先定义第二个按键引脚号时使用默认值。
    #define KEY1_PIN_NUM            67                    // 将第二个按键设为本 BSP 中的 67 号 PIN,也就是 PE3。
#endif                                                     // 结束 KEY1_PIN_NUM 的条件定义。

void beep_on(void *args)                                  // 定义第一个按键的中断回调;本例没有使用 args 参数。
{                                                         // beep_on 回调函数体开始。
    rt_kprintf("turn on beep!\n");                       // 在默认控制台打印打开蜂鸣器的提示。

    rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);                  // 把 PB8 输出为高电平,使本实验板的蜂鸣器发声。
}                                                         // beep_on 回调函数体结束。

void beep_off(void *args)                                 // 定义第二个按键的中断回调;本例没有使用 args 参数。
{                                                         // beep_off 回调函数体开始。
    rt_kprintf("turn off beep!\n");                      // 在默认控制台打印关闭蜂鸣器的提示。

    rt_pin_write(BEEP_PIN_NUM, PIN_LOW);                   // 把 PB8 输出为低电平,使本实验板的蜂鸣器停止发声。
}                                                         // beep_off 回调函数体结束。

static void pin_beep_sample(void)                          // 定义蜂鸣器与两个按键的初始化函数,并在文件内使用该函数名。
{                                                         // pin_beep_sample 函数体开始。
    rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT);            // 把 PB8 配置为推挽输出,用它驱动蜂鸣器控制信号。
    rt_pin_write(BEEP_PIN_NUM, PIN_LOW);                   // 初始化为低电平,避免配置完成后蜂鸣器默认发声。

    rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);      // 把 PE2 配置为内部上拉输入,使未按下时保持高电平。
    rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL); // 给 PE2 绑定下降沿中断,触发时调用 beep_on,且不传自定义参数。
    rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE);       // 使能 PE2 的 PIN 中断,使绑定的回调可以真正执行。

    rt_pin_mode(KEY1_PIN_NUM, PIN_MODE_INPUT_PULLUP);      // 把 PE3 配置为内部上拉输入,使未按下时保持高电平。
    rt_pin_attach_irq(KEY1_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_off, RT_NULL); // 给 PE3 绑定下降沿中断,触发时调用 beep_off,且不传自定义参数。
    rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE);       // 使能 PE3 的 PIN 中断,使绑定的回调可以真正执行。
}                                                         // pin_beep_sample 函数体结束。
MSH_CMD_EXPORT(pin_beep_sample, pin beep sample);          // 把函数导出为可在 MSH 终端执行的 pin_beep_sample 命令。

本节继续使用 13IO_DEVICE 工程,在 applications 目录中新建 pin_test.c。上方代码取自课程配套工程:业务语句、数值、函数名和字符串均保持原样,只移除了旧注释并补充逐行说明。配套源码曾把 66/67 误注为 PD8/PD9,但同一工程的 drv_gpio.c 明确表明它们是 PE2/PE3,因此注释已按实际映射纠正。

一、PIN设备

1.1 简介

PIN 设备是 RT-Thread 对 MCU GPIO 的统一抽象。应用层调用 rt_pin_mode()、rt_pin_write() 等接口,PIN 框架再调用当前芯片的 BSP 驱动;本工程最终由 STM32 HAL 配置 GPIO 寄存器。这样,应用代码不必直接操作 STM32 的 RCC、GPIO 和 EXTI 寄存器。

原稿中的 get_mode 不是本工程的 PIN API。常用接口如下:

接口作用本例是否使用
rt_pin_mode(pin, mode)设置输入、输出、上下拉或开漏等模式是
rt_pin_write(pin, value)将输出引脚置为高电平或低电平是
rt_pin_read(pin)读取输入引脚当前的逻辑电平否
rt_pin_get(name)按名称取得 PIN 编号,是否可用取决于 BSP否
rt_pin_attach_irq(...)为指定引脚绑定中断触发模式、回调和参数是
rt_pin_irq_enable(...)使能或关闭已经绑定的引脚中断是
rt_pin_detach_irq(pin)解除中断回调绑定否

本工程头文件提供五种模式:PIN_MODE_OUTPUT、PIN_MODE_INPUT、PIN_MODE_INPUT_PULLUP、PIN_MODE_INPUT_PULLDOWN 和 PIN_MODE_OUTPUT_OD。其中推挽输出与开漏输出是两种不同的电气结构,不能合称“推挽开路”:

  • 推挽输出可以主动输出高、低两种电平,适合普通数字控制信号。本例的 PB8 使用这种模式。
  • 开漏输出只能主动拉低;需要外部或内部上拉才能得到高电平,常用于 I²C、线与和电平转换等场景。
  • 浮空输入没有内部上拉或下拉,外部信号悬空时电平容易漂移。
  • 上拉输入用内部弱上拉让空闲电平保持为高;按键把信号接地后变为低。本例的 PE2、PE3 使用这种模式。
  • 下拉输入用内部弱下拉让空闲电平保持为低,适合按下后接高电平的电路。
1.1.1 PIN 编号不是芯片封装脚号

RT-Thread PIN 编号由具体 BSP 定义,不是芯片手册中的封装管脚序号,也不是字符串 PB8 本身。本工程 drv_gpio.c 每个端口分配 16 个连续编号,因此可按“端口序号 × 16 + 位号”核对:A、B、C、D、E 的端口序号依次是 0、1、2、3、4。

板上信号MCU GPIO计算过程本工程 PIN 编号用途
BEEPPB81 × 16 + 824蜂鸣器控制输出
第一个按键PE24 × 16 + 266下降沿触发 beep_on
第二个按键PE34 × 16 + 367下降沿触发 beep_off

因此,官方通用示例中的 35、55、56 不能直接照搬到这块板。本工程里 35 实际是 PC3,55 是 PD7,56 是 PD8;视频根据当前开发板原理图和当前 BSP,把它们改为 24、66、67。同一工程也可以用 GET_PIN(B, 8)、GET_PIN(E, 2)、GET_PIN(E, 3) 得到这些编号,不过本节必须保持课程源码中的数字写法。

1.1.2 GPIO、PIN 框架与硬件寄存器的关系

调用 rt_pin_mode() 并不是绕过硬件寄存器。其调用路径可理解为:应用层 PIN API → RT-Thread PIN 设备 → STM32 BSP 的 drv_gpio.c → STM32 HAL → GPIO/EXTI 寄存器。

  • 本工程的 rt_hw_pin_init() 已为存在的 GPIOA~GPIOH 端口打开外设时钟,并把设备注册为 pin,所以本例不需要在 pin_test.c 中再次手动开启 GPIOB、GPIOE 时钟。
  • rt_pin_mode(..., PIN_MODE_OUTPUT) 在这个 BSP 中会落到 GPIO_MODE_OUTPUT_PP,即推挽输出;PIN_MODE_INPUT_PULLUP 会落到带内部上拉的输入模式。
  • 这不是“HAL 库天然不需要时钟配置”。真正省掉应用层时钟代码的是 RT-Thread BSP 已在底层统一完成了时钟和设备注册。
1.1.3 按键下降沿为什么能触发回调

本实验的按键按低电平有效:

  1. 按键未按下时,PE2/PE3 由内部上拉维持高电平。
  2. 按下按键后,引脚经按键电路被拉到地,电平从高变低。
  3. 高到低的跳变就是下降沿,EXTI 检测到后进入对应中断服务程序。
  4. BSP 根据已登记的回调表调用 beep_on() 或 beep_off()。
  5. 回调改变 PB8 电平,本实验板用高电平打开蜂鸣器、低电平关闭蜂鸣器。

rt_pin_attach_irq() 的最后一个参数会原样传给回调。本例传入 RT_NULL,所以两个回调虽然保留了 void *args 形参,却没有使用它。

RT-Thread 的通用 PIN 头文件定义了上升沿、下降沿、双边沿、高电平和低电平五种触发宏,但能否使用还取决于具体 MCU 和 BSP。本工程的 STM32F1 drv_gpio.c 在使能中断时只明确处理上升沿、下降沿和双边沿三种模式,不能仅凭通用头文件就认定高、低电平触发在这里可用。

1.2 快速上手

1.2.1 建立并运行示例
  1. 继续使用 13IO_DEVICE 工程,不新建工程。
  2. 在 applications 下新建 C 源文件 pin_test.c,加入文首课程源码。
  3. 确认蜂鸣器和按键映射分别为 PB8、PE2、PE3,即 PIN 编号 24、66、67。
  4. 编译、下载并复位开发板,在 MSH 控制台执行 pin_beep_sample。
  5. 执行命令本身只完成初始化,不会立即打印 turn on beep! 或 turn off beep!;按下对应按键、触发回调后才会看到提示。

程序初始化顺序是:PB8 配为输出并先写低电平 → PE2 配为上拉输入、绑定下降沿回调并使能 → PE3 做同样配置。之后不需要专门创建轮询按键的线程,因为硬件中断会在电平出现下降沿时通知程序。

1.2.2 运行结果
  • 按下 PE2 对应按键:控制台打印 turn on beep!,随后 PB8 输出高电平,蜂鸣器发声。
  • 按下 PE3 对应按键:控制台打印 turn off beep!,随后 PB8 输出低电平,蜂鸣器停止发声。
  • 视频中一次按键操作偶尔出现多条提示,主要原因是机械按键触点在闭合瞬间会抖动,形成多个下降沿,而本示例没有做硬件或软件消抖。
1.2.3 常见问题排查
  • 命令不存在:确认 pin_test.c 已参与构建,并且文件末尾保留了 MSH_CMD_EXPORT。
  • 按键没有反应:先核对原理图中的 GPIO,再核对 drv_gpio.c 的 PIN 编号;不要把封装脚号、GPIO 名和 RT-Thread PIN 编号混为一谈。
  • 一按就打印多次:这是按键抖动,不一定是中断绑定了多次。教学例程只演示 PIN 中断;实际项目可在中断中记录事件,再由线程延时确认稳定电平。
  • 蜂鸣器逻辑相反:高电平发声、低电平静音是本实验板的连接方式,不是所有蜂鸣器模块的通用规律;换板后应重新查原理图和有效电平。
  • 绑定或使能失败却无提示:rt_pin_attach_irq() 和 rt_pin_irq_enable() 都有返回值,但课程示例没有检查。工程化代码应检查错误码,以便发现无效引脚、EXTI 冲突或驱动不支持等问题。

二、结束

  • 本节的核心不是直接操作 STM32 GPIO,而是用 RT-Thread 的 PIN API 完成“输出控制 + 输入中断”。BSP 已封装 GPIO 时钟、模式配置、EXTI 和 NVIC 设置。
  • 正确映射是 PB8→24、PE2→66、PE3→67。课程配套源码中 66/67 后面的 PD8/PD9 旧注释错误,不能据此接线。
  • 两个按键采用上拉输入和下降沿触发:PE2 打开蜂鸣器,PE3 关闭蜂鸣器;初始化时先把 PB8 拉低,避免上电后误鸣。
  • GPIO 中断回调运行在中断上下文。课程为了直观演示而在回调中调用 rt_kprintf();实际项目应让回调尽量短,只记录状态或发送非阻塞通知,把耗时日志和业务处理交给线程。
  • 解除中断时应先用 rt_pin_irq_enable(pin, PIN_IRQ_DISABLE) 关闭中断,再调用 rt_pin_detach_irq(pin) 解除回调;仅解绑回调不等于关闭硬件中断。
  • PE2 和 PE3 分别占用 EXTI2、EXTI3。STM32F1 的同一 EXTI 线不能同时映射到不同 GPIO 端口,例如 PA2 与 PE2 不能各自独立占用 EXTI2;本例使用不同线,因此没有这一冲突。
  • #ifndef 让工程可以从外部预定义三个 PIN 宏来覆盖默认编号;但在本课程工程中,默认值就是实际使用值。

参考资料

Prev: RTT_UART_DMA Next: RTT_ADC