RTT_PIN
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 编号 | 用途 |
|---|---|---|---|---|
BEEP | PB8 | 1 × 16 + 8 | 24 | 蜂鸣器控制输出 |
| 第一个按键 | PE2 | 4 × 16 + 2 | 66 | 下降沿触发 beep_on |
| 第二个按键 | PE3 | 4 × 16 + 3 | 67 | 下降沿触发 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 按键下降沿为什么能触发回调
本实验的按键按低电平有效:
- 按键未按下时,PE2/PE3 由内部上拉维持高电平。
- 按下按键后,引脚经按键电路被拉到地,电平从高变低。
- 高到低的跳变就是下降沿,EXTI 检测到后进入对应中断服务程序。
- BSP 根据已登记的回调表调用
beep_on()或beep_off()。 - 回调改变 PB8 电平,本实验板用高电平打开蜂鸣器、低电平关闭蜂鸣器。
rt_pin_attach_irq() 的最后一个参数会原样传给回调。本例传入 RT_NULL,所以两个回调虽然保留了 void *args 形参,却没有使用它。
RT-Thread 的通用 PIN 头文件定义了上升沿、下降沿、双边沿、高电平和低电平五种触发宏,但能否使用还取决于具体 MCU 和 BSP。本工程的 STM32F1 drv_gpio.c 在使能中断时只明确处理上升沿、下降沿和双边沿三种模式,不能仅凭通用头文件就认定高、低电平触发在这里可用。
1.2 快速上手
1.2.1 建立并运行示例
- 继续使用
13IO_DEVICE工程,不新建工程。 - 在
applications下新建 C 源文件pin_test.c,加入文首课程源码。 - 确认蜂鸣器和按键映射分别为 PB8、PE2、PE3,即 PIN 编号 24、66、67。
- 编译、下载并复位开发板,在 MSH 控制台执行
pin_beep_sample。 - 执行命令本身只完成初始化,不会立即打印
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 宏来覆盖默认编号;但在本课程工程中,默认值就是实际使用值。