4. 设备驱动框架总览
RT-Thread 的设备驱动框架是一套“以对象为中心”的统一抽象层。它把 GPIO、UART、SPI、I2C、ADC、PWM、传感器等外设统一抽象为“设备对象”,向上提供标准的 open/close/read/write/control 接口,向下由具体驱动实现硬件操作。理解这套模型,是后续编写任何驱动的前提。
一、I/O 设备模型的核心思想
RT-Thread 的 I/O 设备模型借鉴了 UNIX 的“一切皆文件”思想,但更贴近嵌入式场景。它通过 rt_device 结构体描述一个设备,通过设备类型区分不同外设,通过设备操作集 rt_device_ops 绑定具体驱动函数。应用层只依赖抽象接口,不关心底层是寄存器操作还是外设库调用。
这种设计带来三个直接好处:一是应用代码与硬件解耦,更换芯片时上层几乎不改;二是驱动可以分层复用,例如串口驱动可复用通用框架;三是设备可被统一管理、查找和调试。
二、设备对象与设备类型
设备对象由 struct rt_device 定义,关键成员包括:type(设备类型)、flag(访问权限)、open_flag(打开标志)、ref_count(引用计数)、device_id(设备 ID)、ops(操作集)、user_data(私有数据)。设备类型是框架识别设备类别的关键,常见类型如下表所示。
| 设备类型 | 宏定义 | 典型设备 | 常用接口 |
|---|---|---|---|
| 字符设备 | RT_Device_Class_Char | 串口、控制台 | read/write/control |
| 块设备 | RT_Device_Class_Block | SD卡、Flash | read/write/control |
| 网络设备 | RT_Device_Class_NetIf | 以太网、WiFi | netdev 接口 |
| MTD 设备 | RT_Device_Class_MTD | NOR/NAND Flash | read/write/erase |
| CAN 设备 | RT_Device_Class_CAN | CAN 控制器 | send/recv |
| RTC 设备 | RT_Device_Class_RTC | 实时时钟 | control |
| 声音设备 | RT_Device_Class_Sound | 音频编解码 | read/write |
| 图形设备 | RT_Device_Class_Graphic | LCD 屏 | control |
| 杂项设备 | RT_Device_Class_Miscellaneous | 看门狗、ADC | control |
设备操作集 rt_device_ops 是驱动与框架之间的契约,通常包含 init、open、close、read、write、control 六个回调。驱动开发者只需实现其中必要的部分,未实现的回调可置为 RT_NULL。
三、设备注册与查找机制
设备注册使用 rt_device_register(),它把设备对象挂入内核设备链表,并分配设备 ID。注册后设备即可被查找。查找使用 rt_device_find(),按名称在链表中匹配,返回设备句柄。典型流程如下:
/* 1. 定义并初始化设备对象 */
static struct rt_device dev;
/* 2. 注册设备,名称为 "uart2" */
rt_device_register(&dev, "uart2", RT_DEVICE_FLAG_RDWR);
/* 3. 应用层查找设备 */
rt_device_t uart2 = rt_device_find("uart2");
if (uart2 == RT_NULL) {
rt_kprintf("find uart2 failed\n");
}
/* 4. 打开设备并读写 */
rt_device_open(uart2, RT_DEVICE_OFLAG_RDWR);
rt_device_write(uart2, 0, buf, len);
rt_device_close(uart2);
注册与查找是“名称—对象”的映射关系。名称必须唯一,重复注册会失败。查找失败通常意味着驱动未注册、名称拼写错误或初始化顺序不对。
四、驱动分层设计思想
RT-Thread 驱动框架强调分层:最上层是应用接口层,中间是设备框架层,下层是硬件驱动层。框架层负责设备管理、引用计数、权限检查、接口分发;驱动层只负责具体硬件操作。以串口为例,应用调用 rt_device_write(),框架层找到设备并调用其 ops->write,最终进入串口驱动的发送函数。
分层设计的关键在于“框架不碰硬件,驱动不碰管理”。框架层不直接操作寄存器,驱动层不直接维护设备链表。这样,新增一款芯片时,只需实现新的驱动层,框架层和应用层保持不变。
五、典型场景与常见误区
假设要新增一个 ADC 设备。正确做法是:定义 rt_device 对象,实现 control 回调处理采样命令,调用 rt_device_register(&adc_dev, "adc0", RT_DEVICE_FLAG_RDONLY) 注册,应用层通过 rt_device_find("adc0") 获取句柄后使用 rt_device_control() 读取采样值。
list_device 命令查看当前已注册设备及类型;若 rt_device_find() 返回 RT_NULL,优先检查注册顺序、名称拼写和初始化返回值。