IO设备模型框架
265.3.1IO设备模型框架
源码边界说明: 本节是 I/O 设备模型框架的概念导入,没有新增或修改应用层业务代码。视频只展示框架图、设备注册/访问接口和 Windows 设备管理器,因此不把 RT-Thread 内核或 BSP 驱动源码复制到文首冒充业务源码;正文结合课程配套
13IO_DEVICE工程说明这些接口如何衔接。
一、RT-Thread的设备模型框架
RT-Thread 设备模型的核心目的,是在应用程序与具体硬件驱动之间建立一套统一约定。应用通常先按名称取得设备句柄,再用统一接口打开、读写或控制设备;底层驱动则创建具体设备对象、填入操作函数并完成注册。
驱动侧:创建设备对象 → 绑定操作函数 → 注册设备名称
↓
应用侧:按名称查找设备 → 打开设备 → 读 / 写 / 控制 → 关闭设备
“统一接口”不表示所有硬件的能力完全相同,而是让它们都通过同一种入口形式被管理。UART、ADC、PWM、I2C 等设备的控制命令、数据单位和运行模式仍由各自的设备类别与驱动决定。
1.1 设备模型框架介绍
这里的两个关键词分别是:
- 设备模型:用一个设备对象描述名称、类型、状态、打开次数、回调以及
init/open/close/read/write/control等操作。 - 设备框架:规定应用层、通用设备管理代码、某类设备的公共驱动代码和芯片级驱动怎样协作。
课程说它“仿照 Linux 驱动框架”,可以理解为两者都采用了分层、设备对象、注册和统一访问接口等思想,但不能进一步推导为“内部实现与 Linux 完全相同”。RT-Thread 面向 MCU,结构、对象模型、初始化机制和 API 都有自己的实现;学习它有助于建立驱动分层思维,却不能代替 Linux 驱动本身的学习。
RT-Thread 标准版与 Nano 版的课程语境也要分清:
| 项目 | RT-Thread Nano | RT-Thread 标准版 |
|---|---|---|
| 主要目标 | 资源非常受限的 MCU,只保留精简实时内核 | 在内核之上提供可裁剪的设备框架、组件和软件包生态 |
| 设备概念 | 官方驱动文档所述 Nano 版本不包含标准版的设备模型 | 启用 RT_USING_DEVICE 后使用统一设备对象与接口 |
| 外设访问 | 可由用户直接编写或调用芯片 HAL/LL 驱动 | 可通过设备框架访问,也不意味着技术上禁止直接调用 HAL |
| 取舍 | ROM/RAM 占用更小,但需要自行组织更多外设代码 | 增加一定代码与对象开销,换取复用、移植和统一管理能力 |
因此,选择 Nano 不是因为“HAL 只能在 Nano 中使用”,选择标准版也不等于所有外设会自动可用。最终仍要根据 Flash、RAM、驱动需求和项目维护成本裁剪配置。
本课程接下来的 UART、PIN、ADC、PWM、I2C 等实验,是从不同设备类别观察同一套框架:先学应用接口,再沿注册过程回看底层原理。PIN 是 RT-Thread 对引脚设备的命名;I2C 才是规范写法,课程思维导图中的 “IIC” 是常见口语写法。
1.2 IO设备
I/O 是 Input/Output(输入/输出),这里指能与系统交换数据或控制信息的设备,不是专指 GPIO 引脚。
GPIO 的完整名称是 General-Purpose Input/Output(通用输入输出)。GPIO 可以作为一种 I/O 资源,但 I/O 设备的范围远大于 GPIO。判断输入与输出时,通常以处理器或应用程序为观察中心:
| 类型 | 数据方向 | 示例 |
|---|---|---|
| 输入设备 | 外部世界 → 系统 | 按键、鼠标、麦克风、ADC 采样 |
| 输出设备 | 系统 → 外部世界 | 显示器、扬声器、PWM 输出 |
| 双向设备 | 两个方向都可传输 | UART、SPI、I2C、网络接口、存储设备 |
课程把 MCU 的 UART、ADC、SPI、I2C、PWM、PIN 等都作为 I/O 设备来理解是合适的。视频提到 DMA 后又立即说“DMA 就算了”,原因是 DMA 更准确地说是数据搬运机制/控制器:它能帮助外设与内存传输数据,但通常不是应用层按普通 I/O 设备语义独立读写的对象。
课程用电脑上的 CH340 串口举例时,要把两侧硬件分开:
电脑应用(串口助手)
↓ 选择 COM 号
Windows 串口驱动与设备枚举
↓ USB
CH340 USB 转串口芯片
↓ TX / RX / GND(TTL 电平 UART)
STM32 的 UART 引脚与片内 USART 外设
- 拔掉 CH340 后,Windows 中对应 COM 端口消失,是因为电脑的 USB 总线检测到设备离线。
- CH340 把电脑的 USB 通信转换成 MCU 可使用的 UART 电平信号;它不是 STM32 片内 UART 本身。
- Windows 设备管理器显示 COM 端口,并不代表 RT-Thread 自动发现了外接 CH340。STM32 侧是否存在
uart1等逻辑设备,取决于 BSP 配置、参与编译的驱动和启动时的注册过程。 - 串口接线至少要共地,并通常交叉连接 TX→RX、RX←TX;还要确认双方电平兼容,不能把 RS-232 电平直接接到 STM32 的 3.3 V UART 引脚。
1.3 设备模型框架的分层
官方文档把位于应用与硬件之间的 I/O 设备框架分成三层:I/O 设备管理层、设备驱动框架层、设备驱动层。若把框架外部的应用程序和硬件也一起画出来,整条路径才呈现为五个位置:
| 位置 | 主要职责 | 课程工程中的例子 |
|---|---|---|
| 应用程序 | 按名称查找并通过统一 API 使用设备 | 用户线程、main()、后续串口实验代码 |
| I/O 设备管理层 | 管理已注册设备对象,提供查找、打开、读写、控制等统一入口 | rt_device_find()、rt_device_open() 等 |
| 设备驱动框架层 | 抽取同类设备的公共逻辑,并把统一设备操作映射到该类驱动 | RT-Thread serial、SPI、I2C、PIN 等框架 |
| 设备驱动层 | 实现某款 MCU/控制器特有的寄存器、时钟、中断和 DMA 操作 | STM32 UART、GPIO、I2C 驱动及 HAL |
| 硬件 | 真正执行收发、采样或输出 | STM32 USART、GPIO、ADC、外接 SPI Flash 等 |
不要把“设备驱动框架层”和“设备驱动层”简单合并后就忽略两者的边界:
- 框架层解决“同一类设备有哪些共同行为”,例如串口的缓冲、打开模式和统一读写入口。
- 驱动层解决“这颗芯片具体怎样工作”,例如 USART 寄存器地址、IRQ、DMA 通道和 GPIO 复用。
- 对逻辑非常简单的设备,驱动也可以直接注册到 I/O 设备管理层,不一定必须经过某个专用子框架。
一次典型调用可理解为:应用调用 rt_device_write(),设备管理代码通过设备对象中登记的 write 操作找到串口框架,串口框架再调用 STM32 驱动提供的底层发送操作,最后才访问 USART/DMA 硬件。统一入口负责“转发”,真正驱动硬件的是下层实现。本工程没有启用 RT_USING_DEVICE_OPS,所以 struct rt_device 中保存的是各个独立操作函数指针;启用该配置的版本才通过统一的 ops 函数表保存这些入口。
1.3.1 设备管理器
课程借 Windows 设备管理器帮助理解“集中管理设备”,这个类比只在宏观层面成立:上层看到统一的设备名称/属性,底层驱动把设备交给系统管理。但两者的发现机制并不相同。
RT-Thread 的 I/O 设备管理层不是一个会自动扫描电路板的硬件探测器。在本课程 13IO_DEVICE 工程中,串口注册的真实链路是:
board.h / rtconfig.h 选择启用哪些串口与串口组件
↓
rt_hw_board_init() → hw_board_init()
↓
启用 RT_USING_SERIAL 时调用 rt_hw_usart_init()
↓
STM32 驱动为每个已配置 UART 准备对象与 stm32_uart_ops
↓
rt_hw_serial_register(..., "uart1", ...)
↓
串口框架绑定通用的 rt_serial_init/open/read/write/control 等操作
↓
rt_device_register(..., "uart1", ...)
↓
设备对象进入 RT-Thread 设备对象容器
rt_device_register() 在 RT-Thread 5.0.2 中主要完成以下工作:检查设备指针和重名、把对象初始化为 RT_Object_Class_Device、记录名称与能力标志,并把引用计数和打开标志清零。它注册的是软件设备对象,不会凭空检测引脚上是否焊接了模块,也不会验证外接器件一定能正常通信。
应用调用 rt_device_find("uart1") 时,只是在设备对象容器中按名称查找。找不到通常意味着设备没有注册、名称不一致、配置宏未启用、驱动未参与构建或初始化失败,并不应先假定 UART 硬件损坏。启用 FinSH/MSH 时可用 list_device 查看已经注册的设备名称、类型和引用次数。
Windows 示例中“串口助手从设备管理器取得 COM 口列表”的说法也只是便于入门的简化描述。准确地说,应用通过操作系统提供的设备枚举和文件/通信 API 使用串口;Windows 图形化设备管理器只是这些系统设备信息的一种展示界面,不是所有应用调用都必须经过这个窗口程序。
1.3.2 设备模型框架的用途
设备框架的价值不只是“把很多设备放到一起”,更重要的是隔离变化:
- 隔离芯片差异:STM32、NXP、GigaDevice GD32 等芯片的寄存器和中断不同,由各自 BSP/驱动处理。
- 复用同类公共逻辑:不同厂商的 UART 都可复用 RT-Thread 串口框架的设备接口、缓冲与回调机制。
- 降低应用耦合:应用主要依赖设备名称和标准 API,不必在每个业务文件里直接操作寄存器。
- 统一生命周期:注册、查找、打开、访问、关闭和注销都有明确入口,便于诊断和维护。
- 支持裁剪:不用的设备类别和驱动可以不启用,避免把所有芯片、所有外设代码都编入固件。
原稿把“GD32”和“兆易创新”列成两种并列芯片不够准确:GD32 是兆易创新(GigaDevice)的 MCU 产品系列。课程真正要表达的是 RT-Thread 需要适配多个芯片厂商、多个 MCU 系列以及每颗芯片上的多类外设。
统一 API 也有边界。例如,字符设备的 read/write 长度通常按字节理解,而块设备可能按块解释 pos 和 size;不同设备的 control 命令更不相同。因此可移植的是访问模式和上层结构,不是所有参数语义都天然完全一致。
1.3.3 设备模型框架使用方法
视频把使用方法分成“设备注册”和“访问 I/O 设备”两部分,方向正确;更准确地说,前者主要属于驱动/BSP 侧,后者主要属于应用侧。
| 阶段 | 常用接口 | 初学者需要掌握的含义 |
|---|---|---|
| 注册 | rt_device_register() | 把已准备好的设备对象按名称加入设备管理系统;同名设备不能重复注册 |
| 注销 | rt_device_unregister() | 从管理系统移除设备对象;只注销并不等于释放该对象占用的内存 |
| 查找 | rt_device_find() | 按注册名称取得 rt_device_t 句柄;失败返回 RT_NULL |
| 初始化 | rt_device_init() | 调用设备的初始化操作;已激活设备不会重复初始化 |
| 打开 | rt_device_open() | 按读写、中断、DMA 等模式打开;若尚未初始化,本版本会先尝试初始化 |
| 关闭 | rt_device_close() | 与打开配对,减少引用计数,并在满足条件时执行底层关闭操作 |
| 读取/写入 | rt_device_read() / rt_device_write() | 经设备操作函数表转发到具体驱动,返回实际完成的数据量 |
| 控制 | rt_device_control() | 传递设备类别/驱动支持的命令,例如修改配置或查询状态 |
| 接收指示 | rt_device_set_rx_indicate() | 登记“有数据到达”时的回调;它本身不读取数据 |
| 发送完成 | rt_device_set_tx_complete() | 登记发送完成回调;它本身不负责发出数据 |
应用侧的常见生命周期是:
rt_device_find(name)
↓ 成功取得句柄
rt_device_open(dev, mode)
↓
rt_device_read / rt_device_write / rt_device_control
↓
rt_device_close(dev)
是否需要显式调用 rt_device_init() 要看版本与项目习惯。本课程使用的 RT-Thread 5.0.2 在 rt_device_open() 中会检查激活标志,未初始化时自动调用设备的 init 操作;因此“查找后必须先手工 init 才能 open”不是绝对规则。无论采用哪种写法,都必须检查查找、打开和控制等接口的返回值。
最后回答视频留下的两个问题:
- 怎样才算注册了设备? 驱动准备好设备对象及操作函数后,注册链最终成功执行
rt_device_register(),该对象进入设备对象容器,并能用注册名称被rt_device_find()找到,才算完成框架意义上的注册。 - RT-Thread 内核怎样知道板子上有什么设备? 它主要依赖工程配置、BSP/驱动代码及启动阶段的注册,不会仅靠读取 MCU 型号就知道板上外接了什么。板载 UART、PIN 等由配置宏决定是否创建和注册;外接传感器、Flash 等还需要总线配置、设备实例或探测逻辑。注册成功只代表软件对象存在,实际硬件能否工作仍要由接线、供电、时钟、地址和通信结果验证。
核对依据: 分层定义、注册与访问流程已与 RT-Thread 官方 I/O 设备框架文档交叉核对;注册、查找和操作转发以课程配套 RT-Thread 5.0.2 的
device.c及serial.c为准;Nano 与标准版差异参考官方驱动概览。本工程串口注册链已对照13IO_DEVICE/drivers/drv_common.c、drv_usart.c、board.h和rtconfig.h。