嵌入式驱动分层设计

发布于:2025-08-01 ⋅ 阅读:(20) ⋅ 点赞:(0)

在 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_driverdevice_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() 批量卸载


网站公告

今日签到

点亮在社区的每一天
去签到