在 RT-Thread 或 Linux 等嵌入式系统中,驱动分层结构(前述式驱动架构,“前入式” 实际应为 “前述”)是实现设备驱动复用、解耦、模块化的关键。以下是一个通用的驱动分层结构图及解释,结合 RT-Thread 为例:
🚦 驱动分层结构概览
┌──────────────┐
│ 应用层 (App) │ ← 用户代码、逻辑调用
└──────┬───────┘
│
┌──────▼───────┐
│ 设备抽象层 │ ← 统一接口,如 rt_device_t、lcd_draw_point()
│ (Device HAL) │
└──────┬───────┘
│
┌──────▼────────────┐
│ 驱动中间层 │ ← 驱动管理、注册、查找、驱动接口定义
│ (Driver Framework)│ 比如:lcd_ops_t、sensor_ops_t
└──────┬────────────┘
│
┌──────▼────────────┐
│ 具体驱动实现层 │ ← ST7735、ILI9341、W25Q64、DP83848 等驱动
│ (Device Driver) │ 提供具体 init / read / write 等实现
└───────────────────┘
🔧 各层职责说明
1️⃣ 应用层
- 用户调用设备功能,例如
lcd_clear()
,sensor_read()
- 不直接关心具体硬件细节
2️⃣ 设备抽象层(Device HAL)
提供统一 API,例如:
lcd_clear(uint16_t color); lcd_draw_point(x, y, color);
通过结构体函数指针调用底层驱动,如:
lcd_ops->draw_point(x, y, color);
3️⃣ 驱动中间层(Driver Framework)
负责设备登记、驱动查找、调度,例如:
device_register("st7735", DEV_TYPE_LCD, &st7735_ops); lcd = find_device_by_name("st7735");
提供 ops 接口结构体,如:
struct lcd_ops { void (*init)(void); void (*draw_point)(int x, int y, uint16_t color); };
4️⃣ 驱动实现层
具体型号驱动实现:
st7735_init()
ili9341_draw_point()
由厂商提供或自己实现
实现并注册自己的
lcd_ops
✅ 举例说明:LCD 驱动
// lcd_ops.h
typedef struct lcd_ops
{
void (*init)(void);
void (*draw_point)(int x, int y, uint16_t color);
void (*clear)(uint16_t color);
} lcd_ops_t;
// lcd_framework.c
static struct lcd_device {
const char *name;
lcd_ops_t *ops;
} lcd_list[MAX_LCD];
void lcd_register(const char *name, lcd_ops_t *ops);
// st7735_driver.c
static void st7735_init(void) { ... }
static void st7735_draw_point(int x, int y, uint16_t c) { ... }
static lcd_ops_t st7735_ops = {
.init = st7735_init,
.draw_point = st7735_draw_point,
};
void lcd_st7735_driver_init(void)
{
lcd_register("st7735", &st7735_ops);
}
INIT_DEVICE_EXPORT(lcd_st7735_driver_init);
📦 这样分层的好处
优势 | 说明 |
---|---|
✅ 解耦 | 应用不依赖具体硬件,便于换驱动 |
✅ 可组合 | 支持多个驱动并存,如 LCD 支持 ST7735、ILI9341 互换 |
✅ 易维护 | 分离硬件逻辑,便于调试、定位问题 |
✅ 支持动态管理 | 可动态注册、卸载、热插拔(如 USB、传感器) |
✅ 适合组件化和自动注册 | 结合 initcall,实现“插电即用”的自动化加载流程 |
我们来先详细讲解“include 反向依赖”(有时也称为“依赖反转”或“接口倒置”)在 Linux 驱动 和 RT-Thread 驱动框架中的实际应用。
✅ 一、什么是 include 反向依赖?
传统依赖结构:
[驱动层].c -----> [上层接口].h
反向依赖结构:
[上层接口].h <----- [驱动层].c
也就是说,驱动层 不主动包含上层代码,而是 由上层提供接口头文件和回调注册机制,驱动自己注册进去,从而实现:
- 驱动层不依赖上层实现
- 上层框架不关心具体驱动实现
- 模块解耦,便于添加、替换和移植驱动
✅ 二、RT-Thread 中的典型实现
RT-Thread 的驱动框架非常多采用这种模式,比如:
示例 1:Sensor 框架
框架头文件(sensor.h
)
// sensor.h
struct rt_sensor_ops
{
int (*fetch_data)(struct rt_sensor_device *sensor, void *buf, int len);
int (*control)(struct rt_sensor_device *sensor, int cmd, void *args);
};
// 驱动注册接口
rt_err_t rt_hw_sensor_register(struct rt_sensor_device *sensor,
const char *name,
struct rt_sensor_ops *ops,
void *user_data);
驱动实现(sensor_xxx.c
)
#include "sensor.h" // 只包含接口头文件
static int xxx_fetch_data(struct rt_sensor_device *sensor, void *buf, int len)
{
// 从硬件采集数据
}
static int xxx_control(struct rt_sensor_device *sensor, int cmd, void *args)
{
// 控制接口
}
static struct rt_sensor_ops xxx_sensor_ops =
{
.fetch_data = xxx_fetch_data,
.control = xxx_control,
};
int rt_hw_xxx_sensor_init(void)
{
static struct rt_sensor_device sensor;
// 填充 sensor 成员信息
return rt_hw_sensor_register(&sensor, "xxx", &xxx_sensor_ops, RT_NULL);
}
👉 RT-Thread 框架只调用函数指针,不直接依赖具体驱动源码。
示例 2:RT-Thread 的 SPI 驱动模型(Device 驱动)
// spi_dev.h 中定义统一的 SPI 接口结构体
struct rt_spi_ops
{
rt_err_t (*configure)(struct rt_spi_device *device, struct rt_spi_configuration *cfg);
rt_uint32_t (*xfer)(struct rt_spi_device *device, struct rt_spi_message *message);
};
// spi_flash_w25qxx.c 中注册自己的 ops
static const struct rt_spi_ops w25qxx_ops = {
.configure = w25qxx_configure,
.xfer = w25qxx_transfer,
};
rt_hw_spi_device_register(&w25qxx_ops);
✅ 三、Linux 驱动中的 include 反向依赖
Linux 使用得更广泛,典型代表是 platform_device / platform_driver 和 device_driver 结构体系。
示例 1:platform_driver(设备驱动解耦)
设备树中声明设备
my_gpio@50000000 {
compatible = "mycompany,mygpio";
reg = <0x50000000 0x1000>;
};
驱动注册代码
// mygpio.c
static const struct of_device_id mygpio_of_match[] = {
{ .compatible = "mycompany,mygpio" },
{},
};
static struct platform_driver mygpio_driver = {
.driver = {
.name = "mygpio",
.of_match_table = mygpio_of_match,
},
.probe = mygpio_probe,
.remove = mygpio_remove,
};
module_platform_driver(mygpio_driver);
👉 Linux Kernel 不需要驱动代码提前包含什么“平台定义”。只需要你注册自己支持哪些硬件(通过 compatible),系统就会把你“反向注入”。
示例 2:子系统调用驱动的函数指针
// sound/soc/codecs/wm8994.c
static struct snd_soc_dai_driver wm8994_dai = {
.playback = {...},
.capture = {...},
};
static struct snd_soc_codec_driver soc_codec_dev_wm8994 = {
.probe = wm8994_probe,
...
};
module_snd_soc_codec_driver(wm8994, soc_codec_dev_wm8994);
// 上层声卡平台(machine driver)调用 codec 驱动
snd_soc_register_codec(&pdev->dev, &soc_codec_dev_wm8994, ...);
👉 Codec 只注册接口,Platform 通过接口操作 Codec —— 平台不关心 Codec 的具体实现,只依赖接口(回调表)。
✅ 四、总结对比
对比项 | RT-Thread 驱动模型 | Linux 驱动模型 |
---|---|---|
模块关系 | 上层框架暴露注册接口,驱动模块注册 | 平台设备绑定驱动,驱动通过接口注册 |
是否解耦 | ✅ 完全解耦 | ✅ 完全解耦 |
实现方式 | 结构体 + 回调函数注册 | platform_driver + 接口结构体 |
优点 | 易于裁剪、动态加载、组件化 | 支持设备树,便于内核动态匹配 |
驱动扩展 | 不需修改主框架 | 新设备只需添加 compatible |
✅ 五、建议实践方式(你自己写驱动时)
📌 推荐驱动结构
📁 my_driver/
├── my_device.h <- 抽象接口定义(给上层用)
├── my_device.c <- 驱动注册 + 实现
└── my_device_hw.c <- 硬件接口封装(HAL/SPI/GPIO)
📌 接口设计建议
- 尽量提供接口结构体 + 回调
- 用
xxx_register()
函数实现驱动注册 - 驱动文件中只 include 自己的接口头文件
我们来演示一个完整的 mini 驱动模块,结合 RT-Thread 和 Linux 驱动“解耦式设计”思想。
🎯 场景目标
我们设计一个 LED 控制驱动,要求:
- 上层控制函数:
led_on()
、led_off()
(只依赖抽象接口) - 驱动模块:
led_driver.c
,硬件实现由它负责 - 可以轻松替换硬件层而无需改上层逻辑
📁 项目结构(驱动模块分层)
project/
├── led.h ← 提供给上层的控制接口
├── led_driver.c ← 驱动实现(注册回调)
└── app.c ← 上层应用调用 led_on/led_off
① led.h
(接口定义,框架用)
#ifndef __LED_H__
#define __LED_H__
typedef struct led_ops
{
void (*on)(void);
void (*off)(void);
} led_ops_t;
// 提供接口注册
void led_register(const led_ops_t *ops);
// 上层使用的统一接口
void led_on(void);
void led_off(void);
#endif
② led_driver.c
(驱动层,硬件实现)
#include <stdio.h>
#include "led.h"
// 模拟底层控制
static void led_hw_on(void)
{
printf("[HW] LED ON\n");
}
static void led_hw_off(void)
{
printf("[HW] LED OFF\n");
}
// 注册到框架的驱动实现
static const led_ops_t my_led_ops = {
.on = led_hw_on,
.off = led_hw_off,
};
// led_register 实现(框架内维护 ops)
static const led_ops_t *g_led_ops = NULL;
void led_register(const led_ops_t *ops)
{
g_led_ops = ops;
}
void led_on(void)
{
if (g_led_ops && g_led_ops->on)
g_led_ops->on();
}
void led_off(void)
{
if (g_led_ops && g_led_ops->off)
g_led_ops->off();
}
// 驱动初始化函数(main 中或开机时调用)
void led_driver_init(void)
{
led_register(&my_led_ops);
}
③ app.c
(上层使用)
#include "led.h"
extern void led_driver_init(void);
int main(void)
{
// 初始化 LED 驱动(可由 BSP 初始化统一调用)
led_driver_init();
// 使用 LED 控制接口(不关心底层)
led_on();
led_off();
return 0;
}
✅ 输出结果:
[HW] LED ON
[HW] LED OFF
🔁 可拓展性
如果你要换一个平台,比如 LED 是 I2C 控制的,只需要:
- 新建一个
i2c_led_driver.c
- 实现
led_hw_on/led_hw_off
- 调用
led_register()
注册新的led_ops
上层代码完全不变。
🧠 总结亮点
特性 | 实现方式 |
---|---|
解耦 | 通过 led_ops 函数指针结构体 |
驱动注册 | led_register() |
上层使用 | 统一接口 led_on/led_off() |
替换驱动 | 不需改上层 |
类似 | RT-Thread 的 rt_device_register 、Linux 的 platform_driver |
下面我们来完整演示一个 LCD 屏幕驱动接口分层设计,模仿 RT-Thread 和 Linux 驱动架构解耦风格,体现如何做到:
- 抽象接口(统一上层 API)
- 底层驱动可替换(屏幕型号变化不影响上层)
- 驱动注册机制(面向组件化、BSP 分离)
🎯 场景目标:
我们模拟两个 LCD 型号:
st7735
(SPI 接口)ili9341
(FSMC 或 SPI)
我们要做到:
- 上层通过
lcd_draw_pixel(x, y, color)
绘图 - LCD 驱动注册自己的实现,屏幕驱动层解耦
- 可在不同 LCD 控制器之间自由切换
📁 目录结构(模块化)
lcd/
├── lcd.h # 上层接口定义
├── lcd_core.c # 框架实现(注册/调度)
├── drv_st7735.c # ST7735 屏幕驱动
├── drv_ili9341.c # ILI9341 屏幕驱动
main.c # 上层应用
✅ 1. lcd.h(抽象接口)
#ifndef __LCD_H__
#define __LCD_H__
#include <stdint.h>
typedef struct lcd_ops
{
void (*init)(void);
void (*draw_pixel)(uint16_t x, uint16_t y, uint16_t color);
void (*fill_rect)(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color);
} lcd_ops_t;
// 注册驱动
void lcd_register(const lcd_ops_t *ops);
// 上层使用接口
void lcd_init(void);
void lcd_draw_pixel(uint16_t x, uint16_t y, uint16_t color);
void lcd_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color);
#endif
✅ 2. lcd_core.c(LCD 框架实现)
#include "lcd.h"
static const lcd_ops_t *g_lcd_ops = NULL;
void lcd_register(const lcd_ops_t *ops)
{
g_lcd_ops = ops;
}
void lcd_init(void)
{
if (g_lcd_ops && g_lcd_ops->init)
g_lcd_ops->init();
}
void lcd_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
{
if (g_lcd_ops && g_lcd_ops->draw_pixel)
g_lcd_ops->draw_pixel(x, y, color);
}
void lcd_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
if (g_lcd_ops && g_lcd_ops->fill_rect)
g_lcd_ops->fill_rect(x, y, w, h, color);
}
✅ 3. drv_st7735.c(ST7735 驱动)
#include <stdio.h>
#include "lcd.h"
static void st7735_init(void)
{
printf("ST7735 init\n");
// SPI 初始化等
}
static void st7735_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
{
printf("ST7735 draw pixel at (%d,%d) color=0x%04X\n", x, y, color);
}
static void st7735_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
printf("ST7735 fill rect (%d,%d %dx%d) color=0x%04X\n", x, y, w, h, color);
}
static const lcd_ops_t st7735_ops = {
.init = st7735_init,
.draw_pixel = st7735_draw_pixel,
.fill_rect = st7735_fill_rect,
};
void lcd_st7735_driver_init(void)
{
lcd_register(&st7735_ops);
}
✅ 4. drv_ili9341.c(ILI9341 驱动)
#include <stdio.h>
#include "lcd.h"
static void ili9341_init(void)
{
printf("ILI9341 init\n");
// FSMC/SPI 初始化等
}
static void ili9341_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
{
printf("ILI9341 draw pixel at (%d,%d) color=0x%04X\n", x, y, color);
}
static void ili9341_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
printf("ILI9341 fill rect (%d,%d %dx%d) color=0x%04X\n", x, y, w, h, color);
}
static const lcd_ops_t ili9341_ops = {
.init = ili9341_init,
.draw_pixel = ili9341_draw_pixel,
.fill_rect = ili9341_fill_rect,
};
void lcd_ili9341_driver_init(void)
{
lcd_register(&ili9341_ops);
}
✅ 5. main.c(上层业务)
#include "lcd.h"
// 选择驱动
extern void lcd_st7735_driver_init(void);
extern void lcd_ili9341_driver_init(void);
int main(void)
{
// 使用哪块屏幕就初始化哪一个
lcd_st7735_driver_init();
// lcd_ili9341_driver_init();
lcd_init();
lcd_draw_pixel(10, 10, 0xF800); // 红色
lcd_fill_rect(20, 20, 50, 30, 0x07E0); // 绿色
return 0;
}
✅ 输出示例
ST7735 init
ST7735 draw pixel at (10,10) color=0xF800
ST7735 fill rect (20,20 50x30) color=0x07E0
换 lcd_ili9341_driver_init()
后输出变成 ILI9341 的。
📌 总结设计价值
设计目标 | 实现方式 |
---|---|
上下层解耦 | lcd_ops_t 操作集封装 |
驱动注册机制 | lcd_register() |
可替换控制器 | 驱动独立文件实现,接口相同 |
多 LCD 型号支持 | drv_xxx.c 各自实现 |
⚡ 类比 RT-Thread / Linux 驱动框架
系统 | 本例等价结构 |
---|---|
rt_device_t |
lcd_ops_t |
rt_device_register() |
lcd_register() |
ioctl , read , write |
draw_pixel , fill_rect |
Linux struct file_operations |
同上,驱动行为封装 |
你说得完全正确,而且已经触及了 驱动自动注册机制 + 驱动查找机制(按名称获取句柄),这是 RT-Thread、Linux 等操作系统常见的设计风格,也符合组件化架构原则。
我们来详细演示如何做到以下目标:
✅ 驱动不需要用户手动调用初始化函数,而是自动注册。
✅ 用户通过lcd_find("st7735")
这样的函数获取句柄,再使用操作函数。
🧱 分层架构调整图
main.c <-- 调用 lcd_find("xxx") 获取句柄
│
lcd_core.c <-- 管理 lcd_driver 列表(注册、查找)
│
drv_st7735.c <-- 提供驱动实现 + 自动注册到框架中
drv_ili9341.c <-- 同上
✅ 1. 修改 lcd.h 接口(返回句柄)
#ifndef __LCD_H__
#define __LCD_H__
#include <stdint.h>
typedef struct lcd_ops
{
void (*init)(void);
void (*draw_pixel)(uint16_t x, uint16_t y, uint16_t color);
void (*fill_rect)(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color);
} lcd_ops_t;
typedef struct lcd_device
{
const char *name;
const lcd_ops_t *ops;
} lcd_device_t;
// 注册驱动
void lcd_register(const char *name, const lcd_ops_t *ops);
// 查找驱动
lcd_device_t *lcd_find(const char *name);
#endif
✅ 2. lcd_core.c:注册 & 查找机制
#include <string.h>
#include "lcd.h"
#define MAX_LCD_DEV 4
static lcd_device_t lcd_dev_list[MAX_LCD_DEV];
static int lcd_dev_count = 0;
void lcd_register(const char *name, const lcd_ops_t *ops)
{
if (lcd_dev_count < MAX_LCD_DEV)
{
lcd_dev_list[lcd_dev_count].name = name;
lcd_dev_list[lcd_dev_count].ops = ops;
lcd_dev_count++;
}
}
lcd_device_t *lcd_find(const char *name)
{
for (int i = 0; i < lcd_dev_count; i++)
{
if (strcmp(lcd_dev_list[i].name, name) == 0)
return &lcd_dev_list[i];
}
return NULL;
}
✅ 3. drv_st7735.c:自动注册(使用 __attribute__((constructor))
)
#include <stdio.h>
#include "lcd.h"
static void st7735_init(void) { printf("ST7735 init\n"); }
static void st7735_draw_pixel(uint16_t x, uint16_t y, uint16_t color)
{
printf("ST7735 draw pixel (%d,%d) color=0x%04X\n", x, y, color);
}
static void st7735_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color)
{
printf("ST7735 fill rect (%d,%d %dx%d) color=0x%04X\n", x, y, w, h, color);
}
static const lcd_ops_t st7735_ops = {
.init = st7735_init,
.draw_pixel = st7735_draw_pixel,
.fill_rect = st7735_fill_rect,
};
// 自动注册(GCC特性,或 RT-Thread INIT_COMPONENT_EXPORT)
__attribute__((constructor))
static void st7735_auto_register(void)
{
lcd_register("st7735", &st7735_ops);
}
✅ 4. main.c:用户只需查找名称
#include "lcd.h"
int main(void)
{
lcd_device_t *lcd = lcd_find("st7735");
if (lcd)
{
lcd->ops->init();
lcd->ops->draw_pixel(10, 10, 0xF800);
lcd->ops->fill_rect(20, 20, 50, 30, 0x07E0);
}
else
{
printf("LCD not found\n");
}
return 0;
}
✅ 示例输出
ST7735 init
ST7735 draw pixel (10,10) color=0xF800
ST7735 fill rect (20,20 50x30) color=0x07E0
🧠 小知识:自动注册常见方式
平台 | 方法 | 示例 |
---|---|---|
GCC/Clang | __attribute__((constructor)) |
上文示例 |
RT-Thread | INIT_COMPONENT_EXPORT() 宏 |
注册到组件初始化阶段 |
Linux 内核 | module_init() / subsys_init() |
初始化驱动模块 |
在 GCC 编译器(以及兼容 GCC 的编译器,如 Clang)中,attribute((constructor)) 是一个特殊的函数属性,用于指定在 main() 函数执行之前自动调用的函数。
作用与特性
执行时机:被该属性修饰的函数会在程序启动阶段(main() 函数执行前)自动执行。
执行顺序:如果有多个构造函数属性的函数,可通过添加优先级参数(如 attribute((constructor(101))))指定执行顺序,数值越小优先级越高(1~100 为内部保留)。
适用场景:常用于初始化操作(如全局资源初始化、注册模块、钩子函数设置等)。
✅ 优点总结
特性 | 实现方式/说明 |
---|---|
用户零感知驱动注册 | 自动注册段/构造函数 |
支持多个屏幕驱动共存 | 按 name 注册多个 |
解耦上层与驱动实现 | 只依赖 lcd_ops 指针 |
后续支持 GUI 框架调用 | 将 lcd_device_t 替换为 disp_device_t 等统一接口 |
「驱动管理注册表(device registry)」的思想 —— 在 RT-Thread、Zephyr、Linux 都广泛使用:
✅ 驱动注册时自动登记到一个统一列表里
✅ 用户或系统可通过list_device()
或者lcd list
等命令查看所有驱动
✅ 有时还能根据类型进行分类,例如 LCD、TOUCH、SENSOR、AUDIO…
✅ 示例:登记所有驱动并支持 list_device
命令打印
我们以 LCD
驱动为例:
🧱 Step 1. 修改 lcd_core.c
支持遍历
// lcd_core.c
#include <stdio.h>
#include <string.h>
#include "lcd.h"
#define MAX_LCD_DEV 4
static lcd_device_t lcd_dev_list[MAX_LCD_DEV];
static int lcd_dev_count = 0;
void lcd_register(const char *name, const lcd_ops_t *ops)
{
if (lcd_dev_count < MAX_LCD_DEV)
{
lcd_dev_list[lcd_dev_count].name = name;
lcd_dev_list[lcd_dev_count].ops = ops;
lcd_dev_count++;
}
}
lcd_device_t *lcd_find(const char *name)
{
for (int i = 0; i < lcd_dev_count; i++)
{
if (strcmp(lcd_dev_list[i].name, name) == 0)
return &lcd_dev_list[i];
}
return NULL;
}
void lcd_list_devices(void)
{
printf("Registered LCD drivers:\n");
for (int i = 0; i < lcd_dev_count; i++)
{
printf(" [%d] %s\n", i, lcd_dev_list[i].name);
}
}
🧱 Step 2. 增加 main.c
中的测试命令
#include "lcd.h"
int main(void)
{
lcd_list_devices(); // 显示所有注册的LCD驱动
lcd_device_t *lcd = lcd_find("st7735");
if (lcd)
{
lcd->ops->init();
lcd->ops->draw_pixel(10, 10, 0xF800);
}
return 0;
}
✅ 输出示例:
Registered LCD drivers:
[0] st7735
[1] ili9341
✅ 进阶:加入 RT-Thread 的设备框架方式
如果你在 RT-Thread 上运行,可以借助其设备框架机制,例如:
#include <rtdevice.h>
static struct rt_device lcd_dev;
lcd_dev.type = RT_Device_Class_Graphic;
lcd_dev.init = your_lcd_init_func;
lcd_dev.open = your_open_func;
lcd_dev.close = your_close_func;
lcd_dev.read = your_read_func;
lcd_dev.write = your_write_func;
lcd_dev.control = your_ctrl_func;
rt_device_register(&lcd_dev, "lcd", RT_DEVICE_FLAG_RDWR);
然后在 msh
中运行:
msh > list_device
device type ref count
-------- ---- ---------
lcd Graphic 0
✅ 总结你想做的目标设计
功能 | 实现方式 |
---|---|
驱动自动注册 | lcd_register() + 构造函数/init宏 |
查找某个驱动 | lcd_find("st7735") |
打印所有驱动 | lcd_list_devices() |
RT-Thread 设备框架接入 | rt_device_register() |
用户使用时无需知道细节 | 提供统一的 lcd_device_t * 接口 |
下面我为你演示一个完整的跨设备类型注册管理框架(通用于 LCD / TOUCH / SENSOR / AUDIO 等),实现以下目标:
🎯 目标
- 驱动初始化时自动注册到系统设备列表
- 支持不同类型分类(LCD / TOUCH / SENSOR / AUDIO)
- 用户可以通过命令查看所有驱动(
list_device()
) - 用户可以通过名字查找(
find_device("ili9341")
)并调用统一接口
🧱 1. 定义统一的设备类型和结构体
// device_framework.h
#pragma once
typedef enum {
DEV_TYPE_LCD,
DEV_TYPE_TOUCH,
DEV_TYPE_SENSOR,
DEV_TYPE_AUDIO,
DEV_TYPE_UNKNOWN
} device_type_t;
typedef struct {
const char *name;
device_type_t type;
const void *ops; // 由具体驱动自行强转
} device_node_t;
void device_register(const char *name, device_type_t type, const void *ops);
const device_node_t *find_device(const char *name);
void list_device(void);
🧱 2. 实现设备注册表逻辑
// device_framework.c
#include "device_framework.h"
#include <string.h>
#include <stdio.h>
#define MAX_DEVICES 16
static device_node_t dev_list[MAX_DEVICES];
static int dev_count = 0;
void device_register(const char *name, device_type_t type, const void *ops)
{
if (dev_count < MAX_DEVICES) {
dev_list[dev_count].name = name;
dev_list[dev_count].type = type;
dev_list[dev_count].ops = ops;
dev_count++;
}
}
const device_node_t *find_device(const char *name)
{
for (int i = 0; i < dev_count; i++) {
if (strcmp(dev_list[i].name, name) == 0)
return &dev_list[i];
}
return NULL;
}
const char *device_type_str(device_type_t type)
{
switch (type) {
case DEV_TYPE_LCD: return "LCD";
case DEV_TYPE_TOUCH: return "TOUCH";
case DEV_TYPE_SENSOR:return "SENSOR";
case DEV_TYPE_AUDIO: return "AUDIO";
default: return "UNKNOWN";
}
}
void list_device(void)
{
printf("=== Registered Devices ===\n");
for (int i = 0; i < dev_count; i++) {
printf(" [%2d] %-10s | Type: %-6s\n", i, dev_list[i].name,
device_type_str(dev_list[i].type));
}
}
🧱 3. 定义具体设备(如 LCD)接口和注册逻辑
// lcd_driver.h
#pragma once
typedef struct {
void (*init)(void);
void (*draw_pixel)(int x, int y, int color);
} lcd_ops_t;
void lcd_driver_st7735_init(void);
void lcd_driver_ili9341_init(void);
// lcd_driver.c
#include "lcd_driver.h"
#include "device_framework.h"
#include <stdio.h>
static void st7735_init(void) {
printf("ST7735 LCD init done\n");
}
static void st7735_draw(int x, int y, int color) {
printf("Draw pixel (%d, %d) color: %d on ST7735\n", x, y, color);
}
static const lcd_ops_t st7735_ops = {
.init = st7735_init,
.draw_pixel = st7735_draw,
};
void lcd_driver_st7735_init(void)
{
device_register("st7735", DEV_TYPE_LCD, &st7735_ops);
}
🧱 4. 主程序:统一调用 + 命令列出
// main.c
#include "device_framework.h"
#include "lcd_driver.h"
int main(void)
{
lcd_driver_st7735_init(); // 模拟驱动初始化注册
lcd_driver_ili9341_init(); // 可以继续添加其他驱动
list_device(); // 打印所有注册设备
// 获取 ST7735 驱动
const device_node_t *dev = find_device("st7735");
if (dev && dev->type == DEV_TYPE_LCD) {
const lcd_ops_t *ops = (const lcd_ops_t *)dev->ops;
ops->init();
ops->draw_pixel(5, 5, 0xFFFF);
}
return 0;
}
✅ 示例输出:
=== Registered Devices ===
[ 0] st7735 | Type: LCD
ST7735 LCD init done
Draw pixel (5, 5) color: 65535 on ST7735
🔌 扩展支持触摸屏 / 摄像头 / 音频驱动
你可以继续这样注册:
// 触摸驱动注册
device_register("ft6x06", DEV_TYPE_TOUCH, &touch_ops_ft6x06);
// 摄像头注册
device_register("ov5640", DEV_TYPE_SENSOR, &sensor_ops_ov5640);
// 音频注册
device_register("wm8994", DEV_TYPE_AUDIO, &audio_ops_wm8994);
然后统一 list_device()
即可查看所有设备!
如果你是在 RT-Thread 上,可以把这些注册入口统一放到 .init_device
段,通过 INIT_DEVICE_EXPORT()
自动注册,无需用户手动调用 xxx_driver_init()
.
非常好,我们在原有框架基础上加上 动态卸载设备 支持,只需做以下几点修改即可:
✅ 目标
- 支持
unregister_device(const char *name)
动态卸载设备 - 不影响其他已注册设备
- 更新后的
list_device()
可实时反映当前状态
🧱 1. 修改注册列表结构:加入有效标志
// device_framework.h(新增)
typedef struct {
const char *name;
device_type_t type;
const void *ops;
int valid; // 是否已注册(未被卸载)
} device_node_t;
void device_register(const char *name, device_type_t type, const void *ops);
void device_unregister(const char *name); // ⭐️ 新增卸载接口
🧱 2. 修改实现:添加 unregister
实现
// device_framework.c(更新)
#include "device_framework.h"
#include <string.h>
#include <stdio.h>
#define MAX_DEVICES 16
static device_node_t dev_list[MAX_DEVICES];
static int dev_count = 0;
void device_register(const char *name, device_type_t type, const void *ops)
{
for (int i = 0; i < dev_count; i++) {
if (dev_list[i].valid == 0) { // 复用已删除项
dev_list[i].name = name;
dev_list[i].type = type;
dev_list[i].ops = ops;
dev_list[i].valid = 1;
return;
}
}
if (dev_count < MAX_DEVICES) {
dev_list[dev_count].name = name;
dev_list[dev_count].type = type;
dev_list[dev_count].ops = ops;
dev_list[dev_count].valid = 1;
dev_count++;
}
}
void device_unregister(const char *name)
{
for (int i = 0; i < dev_count; i++) {
if (dev_list[i].valid && strcmp(dev_list[i].name, name) == 0) {
dev_list[i].valid = 0;
printf("Device [%s] unregistered\n", name);
return;
}
}
printf("Device [%s] not found\n", name);
}
const device_node_t *find_device(const char *name)
{
for (int i = 0; i < dev_count; i++) {
if (dev_list[i].valid && strcmp(dev_list[i].name, name) == 0)
return &dev_list[i];
}
return NULL;
}
void list_device(void)
{
printf("=== Registered Devices ===\n");
for (int i = 0; i < dev_count; i++) {
if (dev_list[i].valid) {
printf(" [%2d] %-10s | Type: %-6s\n", i, dev_list[i].name,
device_type_str(dev_list[i].type));
}
}
}
🧪 3. 主程序中调用卸载
int main(void)
{
lcd_driver_st7735_init();
lcd_driver_ili9341_init();
list_device();
printf("== Test ST7735 ==\n");
const device_node_t *dev = find_device("st7735");
if (dev && dev->type == DEV_TYPE_LCD) {
const lcd_ops_t *ops = (const lcd_ops_t *)dev->ops;
ops->init();
ops->draw_pixel(10, 10, 0xAAAA);
}
printf("== Unregister ST7735 ==\n");
device_unregister("st7735");
list_device(); // 再次查看,ST7735 应该已被移除
}
✅ 输出预期:
=== Registered Devices ===
[ 0] st7735 | Type: LCD
[ 1] ili9341 | Type: LCD
== Test ST7735 ==
ST7735 LCD init done
Draw pixel (10, 10) color: 43690 on ST7735
== Unregister ST7735 ==
Device [st7735] unregistered
=== Registered Devices ===
[ 1] ili9341 | Type: LCD
✅ 优点
- 不需要动态分配内存,适合裸机/RTOS
- 可复用已注销槽位,避免内存泄露
- 可扩展
device_unregister_all()
批量卸载