为什么要正经做驱动框架

很多人上 RT-Thread 只是把它当一个带定时器的裸机框架用:所有传感器的读代码都堆在 main 线程里,延时靠 delay_ms,串口打印靠 rt_kprintf。这种写法在 demo 阶段没毛病,一旦上产品就会出现:加一个传感器要改三个文件、串口打印被采样打断、死机不知道是哪段代码在踩内存。

RT-Thread 的真正价值不是"能跑多线程",而是它提供了一套标准化的设备模型和组件生态,让你把硬件抽象掉,业务代码不用关心它接在哪条总线上。
RT-Thread 配图:开发板连接若干传感器模块与齿轮组件
设备模型让业务代码只认名字,不碰总线细节

这篇把 RT-Thread 的组件与驱动框架真正用起来:写一个温湿度传感器驱动、用消息队列把采样数据送到业务线程、再把常用组件接进来,形成一个能上产品的骨架。

RT-Thread 设备模型速览

RT-Thread 把所有硬件抽象成一类对象:块设备、字符设备、网络设备。你用 rt_device_find("sht30") 按名字拿到一个设备句柄,然后统一用 rt_device_read / rt_device_write 操作它。底层是 I2C、SPI 还是 GPIO,业务线程根本不关心。

设备模型三件套
  • rt_device统一的设备结构体,挂在对象容器上。
  • rt_device_register注册你的驱动到系统。
  • rt_device_find业务层按名字取设备。

好处是:将来你把 SHT30 换成 SHT40,只要新驱动注册同一个名字,业务代码一行都不用改。

消息队列在传感器采集中的角色

典型的 IoT 设备有两类线程:采样线程(要周期跑、不能被打断)、上报线程(要发网络、可能阻塞几秒)。这两类线程之间不能靠全局变量传数据,必须用消息队列把一帧一帧的数据安全地递过去。

// 创建一个 8 帧、每帧 16 字节的队列
rt_mq_t mq = rt_mq_create("sens_mq", 16, 8, RT_IPC_FLAG_FIFO);

// 采样线程:每 2 秒发一帧
void sensor_thread_entry(void *arg) {
    while (1) {
        sens_frame_t f;
        sht30_read(&f.temp, &f.humi);
        rt_mq_send(mq, &f, sizeof(f));
        rt_thread_mdelay(2000);
    }
}

// 上报线程:阻塞等队列,有数据再发
void report_thread_entry(void *arg) {
    sens_frame_t f;
    while (1) {
        rt_mq_recv(mq, &f, sizeof(f), RT_WAITING_FOREVER);
        mqtt_publish(&f);
    }
}

队列的价值是:采样线程永远不被网络波动拖住,上报线程慢一拍也不丢数据,队列满了再考虑丢策略。

写一个温湿度传感器驱动

下面是 SHT30 的最小驱动骨架:把它注册成字符设备,业务层就可以像读文件一样读温湿度。

static rt_err_t sht30_read(rt_device_t dev, rt_off_t pos,
                           void *buf, rt_size_t size) {
    uint8_t cmd[2] = {0x2C, 0x06};
    i2c_write_read(dev->parent.user_data, cmd, 2, buf, size);
    return RT_EOK;
}

static rt_err_t sht30_init(rt_device_t dev) {
    return i2c_probe(dev->parent.user_data, 0x44);
}

int rt_hw_sht30_init(const char *bus_name, uint16_t addr) {
    static struct rt_i2c_client client;
    client.bus = rt_i2c_bus_device_find(bus_name);
    client.client_addr = addr;

    rt_device_t dev = rt_calloc(1, sizeof(struct rt_device));
    dev->type = RT_Device_Class_Char;
    dev->init = sht30_init;
    dev->read = sht30_read;
    dev->parent.user_data = &client;
    rt_device_register(dev, "sht30", RT_DEVICE_FLAG_RDONLY);
    return RT_EOK;
}

业务层只用:rt_device_t d = rt_device_find("sht30"); rt_device_read(d, 0, &frame, 6);,跟硬件细节完全解耦。