(五)RT-Thread设备驱动实战--IO模型PIN与UART
·
RT-Thread设备驱动实战 — I/O模型、PIN与UART
前言
从内核走向硬件,设备驱动是连接软件与物理世界的桥梁。RT-Thread通过三层I/O设备模型实现了硬件抽象,让应用程序可以用统一的接口操作不同的硬件设备。本文将介绍I/O设备模型的架构,并通过PIN引脚控制和UART串口通信两个最常用的设备进行实战。
一、I/O 设备模型框架
RT-Thread的I/O设备模型像一座桥梁,连接底层硬件与上层应用,分为三层:
| 层级 | 名称 | 功能 | 比喻 |
|---|---|---|---|
| 上层 | I/O设备管理层 | 统一管理设备驱动,提供标准API | 调度员 |
| 中层 | 设备驱动框架层 | 对同类设备抽象统一管理 | 家电通用接口 |
| 底层 | 设备驱动层 | 直接操作硬件 | 实际控制电路 |
通过这个模型,应用程序无需关心硬件差异,只需调用统一的 rt_device_* 接口。
1.1 设备操作流程
查找设备 → 打开设备 → 读写数据 → 控制设备 → 关闭设备
| 步骤 | API | 说明 |
|---|---|---|
| 查找 | rt_device_find(name) |
按名称查找已注册的设备 |
| 打开 | rt_device_open(dev, oflags) |
打开设备并指定工作模式 |
| 读取 | rt_device_read(dev, pos, buf, size) |
从设备读取数据 |
| 写入 | rt_device_write(dev, pos, buf, size) |
向设备写入数据 |
| 控制 | rt_device_control(dev, cmd, arg) |
配置设备参数 |
| 关闭 | rt_device_close(dev) |
关闭设备 |
1.2 常见设备类型
- 字符设备:每次传输一个字符(如键盘、串口)
- 块设备:每次传输一块数据(如SD卡、硬盘)
1.3 设备管理示例:看门狗
#include <rtthread.h>
#include <rtdevice.h>
#define IWDG_DEVICE_NAME "wdt"
static rt_device_t wdg_dev;
static void idle_hook(void)
{
rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
}
int main(void)
{
rt_uint32_t timeout = 10;
wdg_dev = rt_device_find(IWDG_DEVICE_NAME);
if (!wdg_dev) return RT_ERROR;
rt_device_init(wdg_dev);
rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
rt_thread_idle_sethook(idle_hook);
return RT_EOK;
}
二、PIN 引脚设备
2.1 引脚分类
| 类型 | 说明 |
|---|---|
| 电源引脚 | 为芯片供电 |
| 时钟引脚 | 提供时钟信号 |
| 控制引脚 | 状态控制 |
| GPIO(通用I/O) | 可编程输入/输出 |
| 复用功能引脚 | SPI、I2C、UART等专用通信功能 |
GPIO 支持多种模式:上拉输入、下拉输入、推挽输出、开漏输出等,并支持5种中断触发模式(上升沿、下降沿、双边沿、高电平、低电平)。
2.2 PIN 设备 API 速查表
| API | 功能 | 示例 |
|---|---|---|
rt_pin_get("PF.9") |
获取引脚编号 | GET_PIN(F, 9) |
rt_pin_mode(pin, mode) |
设置引脚模式 | rt_pin_mode(35, PIN_MODE_OUTPUT) |
rt_pin_write(pin, value) |
设置输出电平 | rt_pin_write(35, PIN_HIGH) |
rt_pin_read(pin) |
读取引脚电平 | int val = rt_pin_read(35) |
rt_pin_attach_irq(pin, mode, hdr, args) |
绑定中断回调 | 见下方示例 |
rt_pin_irq_enable(pin, enabled) |
启用/禁用中断 | rt_pin_irq_enable(55, PIN_IRQ_ENABLE) |
2.3 获取引脚编号
三种方式:
- API获取:
rt_pin_get("PF.9") - 宏定义:
GET_PIN(F, 9) - 查看驱动文件:
drv_gpio.c
2.4 LED 闪烁示例
#include <rtthread.h>
#include <rtdevice.h>
#define LED_PIN GET_PIN(F, 9)
int main(void)
{
rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT);
while (1)
{
rt_pin_write(LED_PIN, PIN_HIGH);
rt_thread_mdelay(500);
rt_pin_write(LED_PIN, PIN_LOW);
rt_thread_mdelay(500);
}
}
2.5 按键中断示例
#include <rtthread.h>
#include <rtdevice.h>
#define KEY_PIN GET_PIN(A, 0)
#define BEEP_PIN GET_PIN(B, 8)
void beep_on(void *args)
{
rt_kprintf("按键按下!蜂鸣器响铃\n");
rt_pin_write(BEEP_PIN, PIN_HIGH);
rt_thread_mdelay(200);
rt_pin_write(BEEP_PIN, PIN_LOW);
}
int main(void)
{
rt_pin_mode(BEEP_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(KEY_PIN, PIN_MODE_INPUT_PULLUP);
/* 绑定下降沿中断 */
rt_pin_attach_irq(KEY_PIN, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
rt_pin_irq_enable(KEY_PIN, PIN_IRQ_ENABLE);
return 0;
}
2.6 PIN 框架设计原理
RT-Thread的PIN框架通过 rt_pin_ops 结构体实现硬件抽象:
struct rt_pin_ops
{
void (*pin_mode)(struct rt_device *dev, rt_base_t pin, rt_base_t mode);
void (*pin_write)(struct rt_device *dev, rt_base_t pin, rt_base_t value);
int (*pin_read)(struct rt_device *dev, rt_base_t pin);
rt_err_t (*pin_attach_irq)(...);
rt_err_t (*pin_detach_irq)(...);
rt_err_t (*pin_irq_enable)(...);
};
每个硬件平台(如STM32)只需实现这组接口,上层API(rt_pin_mode等)会自动调用对应的底层实现。
三、UART 串口设备
3.1 UART 基础
UART(通用异步收发传输器)是嵌入式中最常用的通信接口。关键参数:
| 参数 | 说明 | 常用值 |
|---|---|---|
| 波特率 | 数据传输速率 | 9600、115200 |
| 数据位 | 每帧数据位数 | 8位 |
| 停止位 | 帧结束标志 | 1位 |
| 校验位 | 错误检测 | 无校验 |
3.2 UART 操作接口
| API | 说明 |
|---|---|
rt_device_find(name) |
查找串口设备 |
rt_device_open(dev, oflags) |
打开设备(指定收发模式) |
rt_device_read(dev, pos, buf, size) |
读取数据 |
rt_device_write(dev, pos, buf, size) |
写入数据 |
rt_device_control(dev, cmd, arg) |
配置串口参数 |
rt_device_set_rx_indicate(dev, callback) |
设置接收回调 |
rt_device_set_tx_complete(dev, callback) |
设置发送完成回调 |
rt_device_close(dev) |
关闭设备 |
打开模式(oflags):
| 标志 | 说明 |
|---|---|
RT_DEVICE_FLAG_INT_RX |
中断接收模式 |
RT_DEVICE_FLAG_DMA_RX |
DMA接收模式 |
RT_DEVICE_FLAG_INT_TX |
中断发送模式 |
RT_DEVICE_FLAG_DMA_TX |
DMA发送模式 |
3.3 配置串口参数
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
config.baud_rate = BAUD_RATE_115200;
config.data_bits = DATA_BITS_8;
config.stop_bits = STOP_BITS_1;
config.parity = PARITY_NONE;
config.bufsz = 128;
rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
3.4 示例一:中断接收 + 轮询发送
这是最常用的串口使用模式:
#include <rtthread.h>
#include <rtdevice.h>
#define UART_NAME "uart1"
static rt_device_t uart;
static struct rt_semaphore rx_sem;
/* 接收回调:收到数据时释放信号量 */
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
rt_sem_release(&rx_sem);
return RT_EOK;
}
/* 数据处理线程:回显收到的数据 */
static void uart_task(void *parameter)
{
char ch;
while (1)
{
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
while (rt_device_read(uart, -1, &ch, 1) == 1)
{
rt_device_write(uart, 0, &ch, 1); // 回显
}
}
}
int uart_init(void)
{
uart = rt_device_find(UART_NAME);
rt_device_open(uart, RT_DEVICE_FLAG_INT_RX);
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
rt_device_set_rx_indicate(uart, uart_input);
rt_thread_t thread = rt_thread_create("uart", uart_task, RT_NULL, 1024, 25, 10);
rt_thread_startup(thread);
return 0;
}
工作流程:
- 串口收到数据 → 触发中断 → 调用
uart_input回调 → 释放信号量 uart_task线程被唤醒 → 读取数据 → 回显
3.5 示例二:DMA 接收 + 消息队列
适用于大数据量场景:
#include <rtthread.h>
#include <rtdevice.h>
#define SAMPLE_UART_NAME "uart1"
static rt_device_t serial;
static struct rt_messagequeue rx_mq;
struct rx_msg {
rt_device_t dev;
rt_size_t size;
};
static rt_err_t uart_input(rt_device_t dev, rt_size_t size)
{
struct rx_msg msg = { .dev = dev, .size = size };
rt_mq_send(&rx_mq, &msg, sizeof(msg));
return RT_EOK;
}
static void serial_thread_entry(void *parameter)
{
struct rx_msg msg;
char buffer[RT_SERIAL_RB_BUFSZ + 1];
while (1)
{
rt_mq_recv(&rx_mq, &msg, sizeof(msg), RT_WAITING_FOREVER);
rt_device_read(msg.dev, 0, buffer, msg.size);
rt_device_write(serial, 0, buffer, msg.size);
}
}
static int uart_dma_sample(void)
{
static char msg_pool[256];
serial = rt_device_find(SAMPLE_UART_NAME);
rt_device_open(serial, RT_DEVICE_FLAG_DMA_RX);
rt_mq_init(&rx_mq, "rx_mq", msg_pool,
sizeof(struct rx_msg), sizeof(msg_pool), RT_IPC_FLAG_FIFO);
rt_device_set_rx_indicate(serial, uart_input);
rt_thread_t thread = rt_thread_create("serial", serial_thread_entry,
RT_NULL, 1024, 25, 10);
rt_thread_startup(thread);
return 0;
}
INIT_APP_EXPORT(uart_dma_sample);
3.6 串口收发模式对比
| 模式 | 接收方式 | 优点 | 适用场景 |
|---|---|---|---|
| 轮询 | CPU主动查询 | 简单 | 低频、少量数据 |
| 中断 | 硬件中断通知 | 实时性好 | 一般数据量 |
| DMA | 硬件直接搬运 | CPU占用最低 | 大数据量、高频传输 |
四、环形缓冲区(Ringbuffer)
4.1 概念
Ringbuffer 是一种高效的FIFO数据结构,数据在固定大小的缓冲区中循环读写,常用于串口通信、数据采集等场景。
4.2 核心API
| API | 说明 |
|---|---|
rt_ringbuffer_create(length) |
动态创建 |
rt_ringbuffer_init(rb, pool, size) |
静态初始化 |
rt_ringbuffer_put(rb, ptr, length) |
写入数据块 |
rt_ringbuffer_put_force(rb, ptr, length) |
强制写入(覆盖旧数据) |
rt_ringbuffer_get(rb, ptr, length) |
读取数据块 |
rt_ringbuffer_putchar(rb, ch) |
写入单字节 |
rt_ringbuffer_getchar(rb, ch) |
读取单字节 |
rt_ringbuffer_data_len(rb) |
获取已存数据长度 |
rt_ringbuffer_reset(rb) |
重置缓冲区 |
rt_ringbuffer_destroy(rb) |
销毁动态缓冲区 |
4.3 注意事项
- 线程安全:Ringbuffer 不内置线程同步,多线程使用时需加互斥锁或禁中断保护
- 满策略:
put满时丢弃数据,put_force满时覆盖旧数据 - 缓冲区大小:根据数据速率和处理速度合理设置
4.4 综合示例:Ringbuffer + UART + 超时判帧
#include <rtthread.h>
#include <rtdevice.h>
#include <ipc/ringbuffer.h>
#include <string.h>
#define BUFFER_SIZE 256
#define UART_DEVICE_NAME "uart1"
#define UART_TIMEOUT_TICKS 50
static uint8_t rb_pool[BUFFER_SIZE];
static struct rt_ringbuffer uart_rb;
static struct rt_device *uart_dev;
static rt_tick_t last_rx_tick = 0;
/* 接收回调:数据写入ringbuffer */
static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size)
{
uint8_t temp_buffer[BUFFER_SIZE];
last_rx_tick = rt_tick_get(); // 记录最后接收时间
rt_size_t read_size = rt_device_read(uart_dev, 0, temp_buffer, BUFFER_SIZE);
if (read_size > 0)
{
rt_ringbuffer_put(&uart_rb, temp_buffer, read_size);
}
return RT_EOK;
}
/* 处理线程:超时后认为一帧数据接收完毕 */
static void uart_thread_entry(void *parameter)
{
uint8_t read_buffer[BUFFER_SIZE];
while (1)
{
rt_tick_t current = rt_tick_get();
if (current - last_rx_tick >= UART_TIMEOUT_TICKS
&& rt_ringbuffer_data_len(&uart_rb) > 0)
{
rt_size_t size = rt_ringbuffer_get(&uart_rb, read_buffer,
sizeof(read_buffer) - 1);
if (size > 0)
{
read_buffer[size] = '\0';
rt_kprintf("收到数据: %s\r\n", read_buffer);
memset(read_buffer, 0, sizeof(read_buffer));
}
}
rt_thread_delay(50);
}
}
int uart_app_init(void)
{
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
rt_ringbuffer_init(&uart_rb, rb_pool, sizeof(rb_pool));
uart_dev = rt_device_find(UART_DEVICE_NAME);
if (!uart_dev) return RT_ERROR;
config.baud_rate = BAUD_RATE_115200;
rt_device_control(uart_dev, RT_DEVICE_CTRL_CONFIG, &config);
rt_device_open(uart_dev, RT_DEVICE_FLAG_INT_RX);
rt_device_set_rx_indicate(uart_dev, uart_rx_ind);
rt_thread_t thread = rt_thread_create("uart_th", uart_thread_entry,
RT_NULL, 4096,
RT_THREAD_PRIORITY_MAX / 2, 10);
if (thread) rt_thread_startup(thread);
return RT_EOK;
}
INIT_APP_EXPORT(uart_app_init);
设计思路:
- 中断接收数据 → 写入Ringbuffer
- 处理线程定时检查 → 超时未收到新数据 → 认为一帧完整 → 取出处理
- 适用于不定长数据的接收场景
五、自动初始化机制
RT-Thread提供了 INIT_*_EXPORT 宏,让初始化函数自动在系统启动时执行,无需在 main() 中手动调用:
| 宏 | 阶段 | 优先级 | 典型用途 |
|---|---|---|---|
INIT_BOARD_EXPORT(fn) |
板级初始化 | 0 | 硬件初始化 |
INIT_DEVICE_EXPORT(fn) |
设备初始化 | 1 | 注册设备驱动 |
INIT_COMPONENT_EXPORT(fn) |
组件初始化 | 2 | 文件系统、网络栈 |
INIT_APP_EXPORT(fn) |
应用初始化 | 3 | 应用程序模块 |
总结
| 要点 | 内容 |
|---|---|
| I/O设备模型 | 三层架构:管理层 → 框架层 → 驱动层 |
| 设备操作流程 | find → open → read/write → control → close |
| PIN设备 | rt_pin_mode / write / read / attach_irq |
| UART设备 | 支持轮询/中断/DMA三种模式 |
| Ringbuffer | 串口数据缓存利器,注意线程安全 |
| 自动初始化 | INIT_APP_EXPORT 简化启动流程 |
掌握了设备驱动框架,你就能自如地操控各种硬件外设。
更多推荐

所有评论(0)