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 获取引脚编号

三种方式:

  1. API获取rt_pin_get("PF.9")
  2. 宏定义GET_PIN(F, 9)
  3. 查看驱动文件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;
}

工作流程

  1. 串口收到数据 → 触发中断 → 调用 uart_input 回调 → 释放信号量
  2. 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 注意事项

  1. 线程安全:Ringbuffer 不内置线程同步,多线程使用时需加互斥锁或禁中断保护
  2. 满策略put 满时丢弃数据,put_force 满时覆盖旧数据
  3. 缓冲区大小:根据数据速率和处理速度合理设置

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 简化启动流程

掌握了设备驱动框架,你就能自如地操控各种硬件外设。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐