From 7966b779539450d96854895cdefce4acdd01bdbe Mon Sep 17 00:00:00 2001 From: HYJ <17451398+hyjhellogitee@user.noreply.gitee.com> Date: Sun, 23 Aug 2026 11:39:34 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E5=9B=9B=E5=A4=A9=E4=BD=9C=E4=B8=9A?= =?UTF-8?q?=E7=AC=94=E8=AE=B0=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...\233\233\345\244\2512026-08-23 111050.png" | Bin 0 -> 97682 bytes ...244\251\344\275\234\344\270\232test_dev.c" | 378 ++++++ ...76\350\256\241\346\212\245\345\221\212.md" | 367 ++++++ ...46\344\271\240\347\254\224\350\256\260.md" | 1159 +++++++++++++++++ 4 files changed, 1904 insertions(+) create mode 100644 "2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\2512026-08-23 111050.png" create mode 100644 "2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232test_dev.c" create mode 100644 "2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232\350\231\232\346\213\237\350\256\276\345\244\207\351\251\261\345\212\250\346\241\206\346\236\266\345\261\202\350\256\276\350\256\241\346\212\245\345\221\212.md" create mode 100644 "2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260RT-Thread\350\256\276\345\244\207\351\251\261\345\212\250\346\241\206\346\236\266\345\255\246\344\271\240\347\254\224\350\256\260.md" diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\2512026-08-23 111050.png" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\2512026-08-23 111050.png" new file mode 100644 index 0000000000000000000000000000000000000000..efad0ff59db552673d3c2dda5333cd85d6568e65 GIT binary patch literal 97682 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 U!T0X3H9$%|UHx3vIVCg!0P-OmssI20 literal 0 HcmV?d00001 diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232test_dev.c" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232test_dev.c" new file mode 100644 index 0000000..7b30554 --- /dev/null +++ "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232test_dev.c" @@ -0,0 +1,378 @@ +/* + * 虚拟设备驱动框架层 (test_dev) + * + * 功能: + * 1. 自定义虚拟设备结构体 + * 2. 设备注册表管理 + * 3. 设备操作接口(open/close/read/write/control) + * 4. 虚拟内存回环设备实现 + * + * 执行 test_dev 命令后,完成:注册、初始化、打开、写入、读取、控制、关闭 + */ + +#include +#include +#include + +#define DRV_DEBUG +#define LOG_TAG "vdev.test" +#include + +/* ==================== 虚拟设备框架层定义 ==================== */ + +/* 虚拟设备结构体 */ +struct vdev_device { + char name[16]; /* 设备名称 */ + rt_uint8_t *buffer; /* 数据缓冲区 */ + rt_size_t buffer_size; /* 缓冲区大小 */ + rt_size_t data_len; /* 当前数据长度 */ + rt_uint32_t ref_count; /* 引用计数 */ + rt_bool_t initialized; /* 是否已初始化 */ + + /* 设备操作函数指针 */ + rt_err_t (*init)(struct vdev_device *dev); + rt_err_t (*open)(struct vdev_device *dev, rt_uint16_t oflag); + rt_err_t (*close)(struct vdev_device *dev); + rt_size_t (*read)(struct vdev_device *dev, rt_off_t pos, void *buffer, rt_size_t size); + rt_size_t (*write)(struct vdev_device *dev, rt_off_t pos, const void *buffer, rt_size_t size); + rt_err_t (*control)(struct vdev_device *dev, int cmd, void *args); +}; + +/* 设备注册表 */ +#define VDEV_MAX_DEVICES 8 +static struct vdev_device *vdev_table[VDEV_MAX_DEVICES]; +static rt_size_t vdev_count = 0; + +/* ==================== 虚拟设备框架层接口实现 ==================== */ + +/** + * 注册虚拟设备 + */ +rt_err_t vdev_register(struct vdev_device *dev) +{ + rt_size_t i; + + if (dev == RT_NULL) + { + LOG_E("vdev register: device is NULL"); + return -RT_ERROR; + } + + /* 检查设备是否已存在 */ + for (i = 0; i < vdev_count; i++) + { + if (rt_strcmp(vdev_table[i]->name, dev->name) == 0) + { + LOG_E("vdev register: device '%s' already exists", dev->name); + return -RT_ERROR; + } + } + + /* 检查注册表是否已满 */ + if (vdev_count >= VDEV_MAX_DEVICES) + { + LOG_E("vdev register: table is full"); + return -RT_ERROR; + } + + /* 注册设备 */ + vdev_table[vdev_count++] = dev; + LOG_I("vdev register: device '%s' registered successfully", dev->name); + + return RT_EOK; +} + +/** + * 查找虚拟设备 + */ +struct vdev_device *vdev_find(const char *name) +{ + rt_size_t i; + + if (name == RT_NULL) + return RT_NULL; + + for (i = 0; i < vdev_count; i++) + { + if (rt_strcmp(vdev_table[i]->name, name) == 0) + return vdev_table[i]; + } + + return RT_NULL; +} + +/** + * 打开虚拟设备 + */ +rt_err_t vdev_open(struct vdev_device *dev, rt_uint16_t oflag) +{ + if (dev == RT_NULL) + return -RT_ERROR; + + /* 首次打开时初始化 */ + if (!dev->initialized && dev->init != RT_NULL) + { + if (dev->init(dev) != RT_EOK) + { + LOG_E("vdev open: init failed for '%s'", dev->name); + return -RT_ERROR; + } + dev->initialized = RT_TRUE; + } + + /* 调用设备 open 回调 */ + if (dev->open != RT_NULL) + { + if (dev->open(dev, oflag) != RT_EOK) + return -RT_ERROR; + } + + dev->ref_count++; + LOG_I("vdev open: '%s' opened, ref_count=%d", dev->name, dev->ref_count); + + return RT_EOK; +} + +/** + * 关闭虚拟设备 + */ +rt_err_t vdev_close(struct vdev_device *dev) +{ + if (dev == RT_NULL || dev->ref_count == 0) + return -RT_ERROR; + + /* 调用设备 close 回调 */ + if (dev->close != RT_NULL) + { + if (dev->close(dev) != RT_EOK) + return -RT_ERROR; + } + + dev->ref_count--; + LOG_I("vdev close: '%s' closed, ref_count=%d", dev->name, dev->ref_count); + + return RT_EOK; +} + +/** + * 从虚拟设备读取数据 + */ +rt_size_t vdev_read(struct vdev_device *dev, rt_off_t pos, void *buffer, rt_size_t size) +{ + if (dev == RT_NULL || dev->read == RT_NULL) + return 0; + + return dev->read(dev, pos, buffer, size); +} + +/** + * 向虚拟设备写入数据 + */ +rt_size_t vdev_write(struct vdev_device *dev, rt_off_t pos, const void *buffer, rt_size_t size) +{ + if (dev == RT_NULL || dev->write == RT_NULL) + return 0; + + return dev->write(dev, pos, buffer, size); +} + +/** + * 控制虚拟设备 + */ +rt_err_t vdev_control(struct vdev_device *dev, int cmd, void *args) +{ + if (dev == RT_NULL || dev->control == RT_NULL) + return -RT_ERROR; + + return dev->control(dev, cmd, args); +} + +/* ==================== 虚拟内存回环设备实现 ==================== */ + +#define VDEV_MEM_BUFFER_SIZE 256 + +/* 虚拟内存设备私有数据 */ +struct vdev_mem { + struct vdev_device parent; + rt_uint8_t data[VDEV_MEM_BUFFER_SIZE]; +}; + +/* 虚拟内存设备操作回调 */ +static rt_err_t vdev_mem_init(struct vdev_device *dev) +{ + struct vdev_mem *mem = (struct vdev_mem *)dev; + + LOG_I("vdev_mem init: buffer_size=%d", mem->parent.buffer_size); + rt_memset(mem->data, 0, VDEV_MEM_BUFFER_SIZE); + mem->parent.data_len = 0; + + return RT_EOK; +} + +static rt_err_t vdev_mem_open(struct vdev_device *dev, rt_uint16_t oflag) +{ + LOG_I("vdev_mem open: oflag=0x%04x", oflag); + return RT_EOK; +} + +static rt_err_t vdev_mem_close(struct vdev_device *dev) +{ + LOG_I("vdev_mem close"); + return RT_EOK; +} + +static rt_size_t vdev_mem_read(struct vdev_device *dev, rt_off_t pos, void *buffer, rt_size_t size) +{ + struct vdev_mem *mem = (struct vdev_mem *)dev; + rt_size_t read_size; + + if (pos >= mem->parent.data_len) + { + LOG_W("vdev_mem read: pos=%d >= data_len=%d", pos, mem->parent.data_len); + return 0; + } + + read_size = (pos + size > mem->parent.data_len) ? (mem->parent.data_len - pos) : size; + rt_memcpy(buffer, &mem->data[pos], read_size); + + LOG_I("vdev_mem read: pos=%d, size=%d, read_size=%d", pos, size, read_size); + LOG_I("vdev_mem read: data=[%.*s]", read_size, (char *)buffer); + + return read_size; +} + +static rt_size_t vdev_mem_write(struct vdev_device *dev, rt_off_t pos, const void *buffer, rt_size_t size) +{ + struct vdev_mem *mem = (struct vdev_mem *)dev; + rt_size_t write_size; + + if (pos >= mem->parent.buffer_size) + { + LOG_W("vdev_mem write: pos=%d >= buffer_size=%d", pos, mem->parent.buffer_size); + return 0; + } + + write_size = (pos + size > mem->parent.buffer_size) ? (mem->parent.buffer_size - pos) : size; + rt_memcpy(&mem->data[pos], buffer, write_size); + + if (pos + write_size > mem->parent.data_len) + mem->parent.data_len = pos + write_size; + + LOG_I("vdev_mem write: pos=%d, size=%d, write_size=%d", pos, size, write_size); + LOG_I("vdev_mem write: data=[%.*s]", write_size, (char *)buffer); + + return write_size; +} + +static rt_err_t vdev_mem_control(struct vdev_device *dev, int cmd, void *args) +{ + struct vdev_mem *mem = (struct vdev_mem *)dev; + + switch (cmd) + { + case 0: /* 获取设备信息 */ + LOG_I("vdev_mem control: cmd=GET_INFO, data_len=%d, buffer_size=%d", + mem->parent.data_len, mem->parent.buffer_size); + break; + + case 1: /* 清空数据 */ + rt_memset(mem->data, 0, VDEV_MEM_BUFFER_SIZE); + mem->parent.data_len = 0; + LOG_I("vdev_mem control: cmd=CLEAR, data cleared"); + break; + + default: + LOG_W("vdev_mem control: unknown cmd=%d", cmd); + return -RT_ERROR; + } + + return RT_EOK; +} + +/* ==================== 测试命令实现 ==================== */ + +static int test_dev_cmd(void) +{ + struct vdev_mem *mem_dev; + char write_data[] = "Hello, Virtual Device!"; + char read_data[64]; + rt_size_t ret; + + LOG_I("========== test_dev command start =========="); + + /* 1. 创建虚拟内存设备 */ + mem_dev = (struct vdev_mem *)rt_malloc(sizeof(struct vdev_mem)); + if (mem_dev == RT_NULL) + { + LOG_E("malloc vdev_mem failed"); + return -RT_ENOMEM; + } + + rt_memset(mem_dev, 0, sizeof(struct vdev_mem)); + rt_strncpy(mem_dev->parent.name, "vdev_mem", sizeof(mem_dev->parent.name) - 1); + mem_dev->parent.buffer = mem_dev->data; + mem_dev->parent.buffer_size = VDEV_MEM_BUFFER_SIZE; + + /* 2. 设置设备操作回调 */ + mem_dev->parent.init = vdev_mem_init; + mem_dev->parent.open = vdev_mem_open; + mem_dev->parent.close = vdev_mem_close; + mem_dev->parent.read = vdev_mem_read; + mem_dev->parent.write = vdev_mem_write; + mem_dev->parent.control = vdev_mem_control; + + /* 3. 注册设备 */ + LOG_I("[Step 1] Register device..."); + if (vdev_register(&mem_dev->parent) != RT_EOK) + { + LOG_E("register device failed"); + rt_free(mem_dev); + return -RT_ERROR; + } + + /* 4. 打开设备(会自动调用 init) */ + LOG_I("[Step 2] Open device..."); + if (vdev_open(&mem_dev->parent, 0x0003) != RT_EOK) /* RT_DEVICE_OFLAG_RDWR */ + { + LOG_E("open device failed"); + return -RT_ERROR; + } + + /* 5. 写入数据 */ + LOG_I("[Step 3] Write data..."); + ret = vdev_write(&mem_dev->parent, 0, write_data, rt_strlen(write_data)); + if (ret == 0) + { + LOG_E("write data failed"); + return -RT_ERROR; + } + + /* 6. 控制设备 - 获取信息 */ + LOG_I("[Step 4] Control device (GET_INFO)..."); + vdev_control(&mem_dev->parent, 0, RT_NULL); + + /* 7. 读取数据 */ + LOG_I("[Step 5] Read data..."); + rt_memset(read_data, 0, sizeof(read_data)); + ret = vdev_read(&mem_dev->parent, 0, read_data, sizeof(read_data) - 1); + if (ret == 0) + { + LOG_E("read data failed"); + return -RT_ERROR; + } + LOG_I("Read back: [%s]", read_data); + + /* 8. 控制设备 - 清空数据 */ + LOG_I("[Step 6] Control device (CLEAR)..."); + vdev_control(&mem_dev->parent, 1, RT_NULL); + + /* 9. 关闭设备 */ + LOG_I("[Step 7] Close device..."); + vdev_close(&mem_dev->parent); + + LOG_I("========== test_dev command end =========="); + LOG_I("All operations completed successfully!"); + + return RT_EOK; +} +MSH_CMD_EXPORT(test_dev_cmd, test virtual device framework); diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232\350\231\232\346\213\237\350\256\276\345\244\207\351\251\261\345\212\250\346\241\206\346\236\266\345\261\202\350\256\276\350\256\241\346\212\245\345\221\212.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232\350\231\232\346\213\237\350\256\276\345\244\207\351\251\261\345\212\250\346\241\206\346\236\266\345\261\202\350\256\276\350\256\241\346\212\245\345\221\212.md" new file mode 100644 index 0000000..76b9f85 --- /dev/null +++ "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232\350\231\232\346\213\237\350\256\276\345\244\207\351\251\261\345\212\250\346\241\206\346\236\266\345\261\202\350\256\276\350\256\241\346\212\245\345\221\212.md" @@ -0,0 +1,367 @@ +# 虚拟设备驱动框架层(test_dev)设计报告 + +## 1. 设计目标 + +本设计旨在实现一个**自定义的虚拟设备驱动框架层**,不依赖 RT-Thread 原生的 `rt_device` 设备框架,而是从零开始设计一套完整的设备管理机制。主要目标包括: + +1. **自定义设备结构体**:设计虚拟设备的数据结构,包含设备属性和操作回调 +2. **设备注册表管理**:实现设备的注册、查找功能 +3. **统一操作接口**:提供 open/close/read/write/control 等标准设备操作接口 +4. **虚拟设备实现**:基于框架层实现一个具体的虚拟内存回环设备 +5. **完整流程验证**:通过 msh 命令验证注册、初始化、打开、写入、读取、控制、关闭的完整流程 + +## 2. 框架结构设计 + +### 2.1 整体架构 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 应用层 (test_dev_cmd) │ +│ 调用 vdev_open/read/write/control/close │ +└─────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ 虚拟设备框架层 (vdev_xxx 接口) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │ +│ │ vdev_register│ │ vdev_find │ │ vdev_open/close │ │ +│ │ vdev_read │ │ vdev_write │ │ vdev_control │ │ +│ └─────────────┘ └─────────────┘ └─────────────────┘ │ +│ │ │ +│ ┌──────────┴──────────┐ │ +│ │ vdev_table[8] │ 设备注册表 │ +│ └─────────────────────┘ │ +└─────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────┐ +│ 具体设备实现层 (vdev_mem_xxx) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ vdev_mem_init│ │ vdev_mem_read│ │vdev_mem_write│ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │vdev_mem_open │ │vdev_mem_close│ │vdev_mem_ctrl │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────┘ +``` + +### 2.2 设计原则 + +- **分层设计**:框架层与具体设备实现分离,框架层提供通用接口,具体设备实现回调函数 +- **面向对象思想**:通过函数指针实现多态,不同设备可以有不同的操作实现 +- **引用计数**:支持设备多次打开/关闭,通过 ref_count 管理设备状态 +- **延迟初始化**:首次打开设备时自动调用 init 回调 + +## 3. 核心数据结构 + +### 3.1 虚拟设备结构体 + +```c +struct vdev_device { + char name[16]; /* 设备名称 */ + rt_uint8_t *buffer; /* 数据缓冲区 */ + rt_size_t buffer_size; /* 缓冲区大小 */ + rt_size_t data_len; /* 当前数据长度 */ + rt_uint32_t ref_count; /* 引用计数 */ + rt_bool_t initialized; /* 是否已初始化 */ + + /* 设备操作函数指针 */ + rt_err_t (*init)(struct vdev_device *dev); + rt_err_t (*open)(struct vdev_device *dev, rt_uint16_t oflag); + rt_err_t (*close)(struct vdev_device *dev); + rt_size_t (*read)(struct vdev_device *dev, rt_off_t pos, void *buffer, rt_size_t size); + rt_size_t (*write)(struct vdev_device *dev, rt_off_t pos, const void *buffer, rt_size_t size); + rt_err_t (*control)(struct vdev_device *dev, int cmd, void *args); +}; +``` + +**字段说明**: + +| 字段 | 类型 | 说明 | +|------|------|------| +| name | char[16] | 设备唯一标识名称,最大 15 字符 | +| buffer | rt_uint8_t* | 设备数据缓冲区指针 | +| buffer_size | rt_size_t | 缓冲区总容量 | +| data_len | rt_size_t | 当前已存储的数据长度 | +| ref_count | rt_uint32_t | 设备引用计数,支持多次打开 | +| initialized | rt_bool_t | 设备是否已完成初始化 | +| init | 函数指针 | 设备初始化回调 | +| open | 函数指针 | 设备打开回调 | +| close | 函数指针 | 设备关闭回调 | +| read | 函数指针 | 设备读取回调 | +| write | 函数指针 | 设备写入回调 | +| control | 函数指针 | 设备控制回调 | + +### 3.2 设备注册表 + +```c +#define VDEV_MAX_DEVICES 8 +static struct vdev_device *vdev_table[VDEV_MAX_DEVICES]; +static rt_size_t vdev_count = 0; +``` + +- 使用静态数组存储设备指针,最多支持 8 个设备 +- `vdev_count` 记录当前已注册的设备数量 + +### 3.3 虚拟内存设备结构体 + +```c +struct vdev_mem { + struct vdev_device parent; /* 继承基类 */ + rt_uint8_t data[VDEV_MEM_BUFFER_SIZE]; /* 256 字节数据缓冲区 */ +}; +``` + +- 通过组合方式"继承" `vdev_device` 基类 +- 内嵌 256 字节数据缓冲区,实现内存回环功能 + +## 4. 接口设计 + +### 4.1 框架层接口 + +| 接口 | 原型 | 说明 | +|------|------|------| +| vdev_register | `rt_err_t vdev_register(struct vdev_device *dev)` | 注册设备到框架 | +| vdev_find | `struct vdev_device *vdev_find(const char *name)` | 按名称查找设备 | +| vdev_open | `rt_err_t vdev_open(struct vdev_device *dev, rt_uint16_t oflag)` | 打开设备 | +| vdev_close | `rt_err_t vdev_close(struct vdev_device *dev)` | 关闭设备 | +| vdev_read | `rt_size_t vdev_read(struct vdev_device *dev, rt_off_t pos, void *buffer, rt_size_t size)` | 从设备读取数据 | +| vdev_write | `rt_size_t vdev_write(struct vdev_device *dev, rt_off_t pos, const void *buffer, rt_size_t size)` | 向设备写入数据 | +| vdev_control | `rt_err_t vdev_control(struct vdev_device *dev, int cmd, void *args)` | 控制设备 | + +### 4.2 接口实现细节 + +#### vdev_register - 设备注册 + +```c +rt_err_t vdev_register(struct vdev_device *dev) +{ + // 1. 检查设备指针是否为空 + // 2. 检查设备名称是否已存在(防止重复注册) + // 3. 检查注册表是否已满 + // 4. 将设备指针存入 vdev_table + // 5. vdev_count++ +} +``` + +#### vdev_open - 设备打开 + +```c +rt_err_t vdev_open(struct vdev_device *dev, rt_uint16_t oflag) +{ + // 1. 首次打开时自动调用 init 回调 + // 2. 调用设备的 open 回调 + // 3. ref_count++ +} +``` + +**特点**:延迟初始化机制,首次打开时自动初始化设备。 + +#### vdev_close - 设备关闭 + +```c +rt_err_t vdev_close(struct vdev_device *dev) +{ + // 1. 调用设备的 close 回调 + // 2. ref_count-- +} +``` + +#### vdev_read / vdev_write - 数据读写 + +```c +rt_size_t vdev_read(struct vdev_device *dev, rt_off_t pos, void *buffer, rt_size_t size) +{ + // 直接调用设备的 read 回调 + return dev->read(dev, pos, buffer, size); +} +``` + +**特点**:框架层不处理具体逻辑,直接转发到设备回调。 + +#### vdev_control - 设备控制 + +```c +rt_err_t vdev_control(struct vdev_device *dev, int cmd, void *args) +{ + // 直接调用设备的 control 回调 + return dev->control(dev, cmd, args); +} +``` + +## 5. 虚拟内存回环设备实现 + +### 5.1 设备功能 + +虚拟内存回环设备(vdev_mem)模拟一个具有 256 字节缓冲区的存储设备: + +- **write**:将数据写入内部缓冲区 +- **read**:从内部缓冲区读取数据 +- **control**:支持获取设备信息(cmd=0)和清空数据(cmd=1) + +### 5.2 操作回调实现 + +#### vdev_mem_init - 初始化 + +```c +static rt_err_t vdev_mem_init(struct vdev_device *dev) +{ + struct vdev_mem *mem = (struct vdev_mem *)dev; + rt_memset(mem->data, 0, VDEV_MEM_BUFFER_SIZE); + mem->parent.data_len = 0; + return RT_EOK; +} +``` + +清空缓冲区,重置数据长度。 + +#### vdev_mem_write - 写入数据 + +```c +static rt_size_t vdev_mem_write(struct vdev_device *dev, rt_off_t pos, + const void *buffer, rt_size_t size) +{ + // 1. 检查 pos 是否超出缓冲区范围 + // 2. 计算实际可写入的大小(防止越界) + // 3. 将数据拷贝到缓冲区 + // 4. 更新 data_len + // 5. 返回实际写入的字节数 +} +``` + +#### vdev_mem_read - 读取数据 + +```c +static rt_size_t vdev_mem_read(struct vdev_device *dev, rt_off_t pos, + void *buffer, rt_size_t size) +{ + // 1. 检查 pos 是否超出已存储数据范围 + // 2. 计算实际可读取的大小 + // 3. 从缓冲区拷贝数据到用户 buffer + // 4. 返回实际读取的字节数 +} +``` + +#### vdev_mem_control - 控制命令 + +```c +static rt_err_t vdev_mem_control(struct vdev_device *dev, int cmd, void *args) +{ + switch (cmd) + { + case 0: // GET_INFO - 获取设备信息 + LOG_I("data_len=%d, buffer_size=%d", ...); + break; + + case 1: // CLEAR - 清空数据 + rt_memset(mem->data, 0, VDEV_MEM_BUFFER_SIZE); + mem->parent.data_len = 0; + break; + + default: + return -RT_ERROR; + } + return RT_EOK; +} +``` + +## 6. 测试验证 + +### 6.1 测试命令 + +通过 msh 命令 `test_dev_cmd` 触发完整的设备操作流程测试。 + +### 6.2 测试流程 + +``` +Step 1: Register device → vdev_register() +Step 2: Open device → vdev_open() [自动调用 init] +Step 3: Write data → vdev_write() +Step 4: Control (GET_INFO) → vdev_control(cmd=0) +Step 5: Read data → vdev_read() +Step 6: Control (CLEAR) → vdev_control(cmd=1) +Step 7: Close device → vdev_close() +``` + +### 6.3 预期输出 + +``` +[I/vdev.test] ========== test_dev command start ========== +[I/vdev.test] [Step 1] Register device... +[I/vdev.test] vdev register: device 'vdev_mem' registered successfully +[I/vdev.test] [Step 2] Open device... +[I/vdev.test] vdev_mem init: buffer_size=256 +[I/vdev.test] vdev open: 'vdev_mem' opened, ref_count=1 +[I/vdev.test] [Step 3] Write data... +[I/vdev.test] vdev_mem write: pos=0, size=22, write_size=22 +[I/vdev.test] vdev_mem write: data=[Hello, Virtual Device!] +[I/vdev.test] [Step 4] Control device (GET_INFO)... +[I/vdev.test] vdev_mem control: cmd=GET_INFO, data_len=22, buffer_size=256 +[I/vdev.test] [Step 5] Read data... +[I/vdev.test] vdev_mem read: pos=0, size=63, read_size=22 +[I/vdev.test] vdev_mem read: data=[Hello, Virtual Device!] +[I/vdev.test] Read back: [Hello, Virtual Device!] +[I/vdev.test] [Step 6] Control device (CLEAR)... +[I/vdev.test] vdev_mem control: cmd=CLEAR, data cleared +[I/vdev.test] [Step 7] Close device... +[I/vdev.test] vdev close: 'vdev_mem' closed, ref_count=0 +[I/vdev.test] ========== test_dev command end ========== +[I/vdev.test] All operations completed successfully! +``` + +### 6.4 验证要点 + +| 验证项 | 验证方法 | 预期结果 | +|--------|----------|----------| +| 设备注册 | 查看日志 | 设备名称 'vdev_mem' 注册成功 | +| 自动初始化 | 首次 open 时查看日志 | init 回调被调用,buffer_size=256 | +| 引用计数 | open/close 日志 | open 后 ref_count=1,close 后 ref_count=0 | +| 数据写入 | write 日志 | 写入 22 字节 "Hello, Virtual Device!" | +| 数据读取 | read 日志 | 读回相同数据 | +| 控制命令 | control 日志 | GET_INFO 显示 data_len=22,CLEAR 清空数据 | +| 完整流程 | 最终日志 | "All operations completed successfully!" | + +## 7. 设计总结 + +### 7.1 设计亮点 + +1. **完整的框架层抽象**:独立于 RT-Thread 原生设备框架,实现了完整的设备管理机制 +2. **面向对象设计**:通过函数指针实现多态,框架层与具体设备解耦 +3. **延迟初始化**:首次打开设备时自动初始化,简化设备管理 +4. **引用计数**:支持设备多次打开/关闭,增强健壮性 +5. **日志完善**:每个操作都有详细日志输出,便于调试和验证 + +### 7.2 与 RT-Thread 原生框架对比 + +| 特性 | 本设计 (vdev) | RT-Thread (rt_device) | +|------|---------------|----------------------| +| 设备结构体 | struct vdev_device | struct rt_device | +| 注册方式 | vdev_register() | rt_device_register() | +| 查找方式 | vdev_find() | rt_device_find() | +| 操作接口 | vdev_open/close/read/write/control | rt_device_open/close/read/write/control | +| 设备管理 | 静态数组 vdev_table[8] | 全局链表 object_list | +| 初始化时机 | 首次 open 时 | 注册时或首次 open 时 | + +### 7.3 扩展性 + +本框架具有良好的扩展性,可以方便地添加新的虚拟设备: + +1. 定义新的设备结构体(继承 vdev_device) +2. 实现设备的 init/open/close/read/write/control 回调 +3. 调用 vdev_register() 注册设备 +4. 通过 vdev_find() 查找并使用设备 + +例如可以扩展实现: +- 虚拟串口设备(模拟串口通信) +- 虚拟网络设备(模拟网络数据包收发) +- 虚拟传感器设备(模拟温度、湿度等传感器数据) + +### 7.4 结论 + +本设计成功实现了一个完整的虚拟设备驱动框架层,验证了设备驱动框架的核心设计思想。通过自定义结构体、注册表、操作接口和具体设备实现,完整演示了设备驱动框架层的工作原理,为理解 RT-Thread 等嵌入式操作系统的设备管理机制提供了实践基础。 + +--- + +**文件位置**:`applications/test_dev.c` +**测试命令**:`test_dev_cmd` +**编译环境**:RT-Thread 4.1.1 + STM32F407ZGT6 diff --git "a/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260RT-Thread\350\256\276\345\244\207\351\251\261\345\212\250\346\241\206\346\236\266\345\255\246\344\271\240\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260RT-Thread\350\256\276\345\244\207\351\251\261\345\212\250\346\241\206\346\236\266\345\255\246\344\271\240\347\254\224\350\256\260.md" new file mode 100644 index 0000000..61f5146 --- /dev/null +++ "b/2026/\347\254\2542\347\273\204/\351\273\204\344\274\212\346\235\260/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260RT-Thread\350\256\276\345\244\207\351\251\261\345\212\250\346\241\206\346\236\266\345\255\246\344\271\240\347\254\224\350\256\260.md" @@ -0,0 +1,1159 @@ +# RT-Thread 设备驱动框架 学习笔记 + +> 基于《day4-驱动框架》培训材料整理,并参考 RT-Thread 官方文档进行完善 +> 覆盖:I/O 设备框架概念、I/O API、GPIO 外设、I2C 外设、SPI 外设,以及设备驱动框架(DM)进阶 + +--- + +## 目录 + +- [培训1:RT-Thread I/O 设备框架概念](#培训1rt-thread-io-设备框架概念) +- [培训2:RT-Thread I/O API](#培训2rt-thread-io-api) +- [培训3:GPIO(PIN)外设开发](#培训3gpio-pin外设开发) +- [培训3:I2C 外设开发](#培训3i2c-外设开发) +- [培训4:SPI 外设开发](#培训4spi-外设开发) +- [进阶:设备驱动框架(Driver Model)](#进阶设备驱动框架driver-model) +- [总结](#总结) +- [参考资料](#参考资料) + +--- + +## 培训1:RT-Thread I/O 设备框架概念 + +### 1.1 问题引入:驱动开发的碎片化 + +不同厂家的 `SPI` API 设计各不相同,例如: + +| 厂家/芯片 | SPI 数据传输接口 | +|-----------|------------------| +| 兆易创新 GD | `spi_i2s_data_transmit` | +| ST | `HAL_SPI_Transmit` | +| NXP | `LPSPI_MasterTransferBlocking` | +| LPC | `SPI_MasterTransferBlocking` | + +**常见开发痛点场景:** + +- 项目A:`STM32 + SPI + W25Q128 + FATFS` +- 项目B:换芯片为 LPC(由于直接使用 SPI 编写 W25Q128,基本等于重写) +- 项目C:`LPC + SPI + RW007(WiFi) + Lwip` + +**由此产生的疑问:** + +> 这些工作能够复用吗?对团队效率高吗? + +**两个核心问题:** +1. **驱动开发碎片化**:代码复用率低,都是非通用的驱动设备代码。 +2. **学习成本高**:同一个工程师需要花时间学习不同厂家的 API 设计。 + +### 1.2 解决思路:驱动与设备分离 + 统一 API + +问:不同厂家同一外设的开发逻辑和 API 是否相同? + +核心思想——**驱动代码与设备驱动代码分离**: + +``` + RW007设备驱动 W25Q128设备驱动 + \ / + \ / + ★ SPI 统一 API ★ + / \ + STM32 SPI对接驱动 LPC SPI对接驱动 +``` + +**分离后的优势:** +- 更换 MCU 只需改变对应的「对接驱动」。 +- 重新驱动设备,只需重写设备驱动相关代码。 +- 同一 API 接口,学习成本低。 +- 设备驱动可以**入库**,供公司其他项目使用,减少碎片化开发、防止反复造轮子。 +- 代码框架会变复杂,但从优点看是值得的。 + +### 1.3 框架演进 → 再演进 + +**第一版框架演进(引入 I/O 层):** + +``` +应用层: RW007设备驱动 | W25Q128设备驱动 | CS43L22设备驱动 | SHT30设备驱动 + | | | | +I/O 层: SPI 统一 API | I2C 统一 API + | | | | +硬件对接: STM32 SPI | LPC SPI | STM32 I2C | LPC I2C(对接驱动) +``` + +- 第一层改进:把设备驱动与硬件都隔离开,中间定义统一 API。 + +**第二版框架再演进(拆成 BSP 与 Package):** + +- 应用层可做成 `Package`(软件包)——`RW007/W25Q128/…` 驱动做成软件包复用。 +- I/O 层提供 **设备框架接口**(SPI 设备框架接口、I2C 设备框架接口)。 +- 硬件层做成**特定平台的 BSP 驱动**(STM32 SPI、LPC SPI、STM32 I2C、LPC I2C…)。 + +### 1.4 RT-Thread I/O 设备模型框架(官方 I/O 设备介绍) + +绝大部分嵌入式系统都包括一些 I/O(Input/Output,输入/输出)设备,例如: +- 仪器上的数据显示屏 +- 工业设备上的串口通信 +- 数据采集设备上用于保存数据的 Flash 或 SD 卡 +- 网络设备的以太网接口 + +**官方三层模型:** RT-Thread 提供了一套简单的 I/O 设备模型框架,位于硬件和应用程序之间,从上到下分为三层: + +``` +┌───────────────────────────────┐ +│ I/O 设备管理层 │ 封装设备驱动,向上提供标准接口 +├───────────────────────────────┤ +│ 设备驱动框架层 │ 同一类设备驱动的抽象,抽取共性、留出接口 +├───────────────────────────────┤ +│ 设备驱动层 │ 创建并注册 I/O 设备,驱动具体硬件 +└───────────────────────────────┘ +``` + +- **I/O 设备管理层**:实现对设备驱动程序的封装。应用程序通过标准接口(如 `open/read/write/control/close`)访问底层设备,设备驱动程序的升级、更替不会影响上层应用,降低耦合与复杂性。 +- **设备驱动框架层**:对同类硬件设备驱动的抽象,将不同厂家同类硬件驱动中相同的部分抽取出来,不同部分留出接口由驱动实现。 +- **设备驱动层**:一组驱使硬件设备工作的程序,负责**创建和注册 I/O 设备**。 + +**两种注册/访问路径:** + +1. **简单设备**(逻辑简单,不需经过驱动框架层):设备驱动根据设备模型定义创建设备实例 → 通过 `rt_device_register()` 注册到 I/O 设备管理器 → 应用程序用 `rt_device_find()` 查找后通过 I/O 管理接口访问硬件。 + +2. **复杂设备**(如看门狗):设备驱动先创建设备实例 → 注册到对应的**设备驱动框架**(如 `rt_hw_watchdog_register()`)→ 再由驱动框架通过 `rt_device_register()` 注册到 I/O 设备管理器 → 应用程序访问。 + +**头文件组织:** +- 具体驱动头文件逐步整理到 `drivers/dev_*.h` 路径下,如 `drivers/dev_spi.h`、`drivers/dev_can.h`、`drivers/dev_rtc.h`、`drivers/dev_pin.h`。 +- 应用层仍建议包含聚合头文件 `#include `,由它根据配置引入对应设备头文件。 + +### 1.5 I/O 设备模型与设备对象 + +RT-Thread 的设备模型建立在**内核对象模型**基础上,设备被视为一类对象纳入对象管理器,每个设备对象由基对象派生,可继承父类属性并派生出私有属性。 + +设备对象 `struct rt_device`(部分关键成员): + +```c +struct rt_device { + struct rt_object parent; /* 内核对象基类 */ + struct rt_bus *bus; /* 挂载的总线 (RT_USING_DM) */ + struct rt_driver *drv; /* 设备驱动 */ + enum rt_device_class_type type; /* 设备类型 */ + rt_uint16_t flag; /* 设备参数 */ + rt_uint16_t open_flag; /* 设备打开标志 */ + rt_uint8_t ref_count; /* 设备被引用次数 */ + rt_uint8_t device_id; /* 设备 ID, 0-255 */ + /* 数据收发回调函数 */ + rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); + rt_err_t (*tx_complete)(rt_device_t dev, void *buffer); + const struct rt_device_ops *ops; /* 设备操作方法 */ + ... + void *user_data; /* 设备私有数据 */ +}; +``` + +> 开启 `RT_USING_DM` 后,设备对象会额外记录电源域单元、主设备标识等;开启 `RT_USING_DMA` 时还会记录 DMA 操作表。 + +### 1.6 I/O 支持的设备类型(完整枚举) + +| 类型宏 | 说明 | +|--------|------| +| `RT_Device_Class_Char` | 字符设备 | +| `RT_Device_Class_Block` | 块设备 | +| `RT_Device_Class_NetIf` | 网络接口设备 | +| `RT_Device_Class_MTD` | 内存设备 | +| `RT_Device_Class_RTC` | RTC 设备 | +| `RT_Device_Class_Sound` | 声音设备(音频编解码 CS43L22、录音驱动) | +| `RT_Device_Class_Graphic` | 图形设备(LCD 屏幕) | +| `RT_Device_Class_I2CBUS` | I2C 总线设备 | +| `RT_Device_Class_USBDevice` | USB device 设备 | +| `RT_Device_Class_USBHost` | USB host 设备 | +| `RT_Device_Class_SPIBUS` | SPI 总线设备 | +| `RT_Device_Class_SPIDevice` | SPI 设备 | +| `RT_Device_Class_SDIO` | SDIO 设备 | +| `RT_Device_Class_Miscellaneous` | 杂类设备 | +| `RT_Device_Class_Pin` | GPIO(PIN)设备 | + +(完整定义见 `rt-thread/include/rtdef.h` 的 `rt_device_class_type` 枚举。) + +### 1.7 字符设备与块设备 + +**字符设备特点:** +- 提供连续的数据流,应用程序顺序读取,通常不支持随机存取。 +- 支持按字节/字符读写。 +- 典型例子:键盘、串口、Modem。 +- 字符模式设备允许非结构化的数据传输(串行形式,每次一个字节)。 + +**块设备特点:** +- 应用程序可随机访问,程序自行确定读取位置。 +- 数据的读写只能以块为单位(通常是 512B 的倍数)。 +- 典型例子:硬盘、软盘、CD-ROM、闪存、SD 卡。 +- 块设备驱动对读/写可能必须执行附加工作:当写入数据大小不是整块时,需要先读出对应块,把写入数据"覆盖"到读出数据上合成新块,再作为一个整块写回。 + +| 对比项 | 字符设备 | 块设备 | +|--------|----------|--------| +| 数据访问 | 连续数据流,顺序访问 | 可随机访问 | +| 读写单位 | 字节/字符 | 块(512B 倍数) | +| 根本区别 | —— | 能否被随机访问 | + +### 1.8 为什么要对设备分类 + +- MSH 可以重定向到任意字符设备上,例如将 LCD 模拟成字符设备,把打印输出到 LCD;或实现空字符设备把 msh 重定向。 +- 文件系统依赖块设备驱动(如 SD、Flash),也可用 RAM 模拟块设备。 +- 不同组件和应用依赖不同设备,分类后可对一类设备做同样的控制。 + +--- + +## 培训2:RT-Thread I/O API + +### 2.1 创建 / 销毁设备 + +| 功能 | 接口 | +|------|------| +| 创建设备 | `rt_device_t rt_device_create(int type, int attach_size)` | +| 销毁设备 | `void rt_device_destroy(rt_device_t device)` | + +**`rt_device_create()` 参数:** + +| 参数 | 描述 | +|------|------| +| `type` | 设备类型,取 `RT_Device_Class_*` 值 | +| `attach_size` | 附加在设备结构后的用户数据大小 | + +返回:创建设备句柄(成功);`RT_NULL`(失败,动态内存分配失败)。 + +> 系统会从动态堆中分配一块「`struct rt_device` + attach_size」大小的设备控制块。设备被创建后,需要实现它访问硬件的操作方法 `ops`。可用 `rt_device_destroy()` 释放动态创建的设备。 + +### 2.2 设备操作方法 `struct rt_device_ops` + +```c +struct rt_device_ops { + rt_err_t (*init) (rt_device_t dev); + rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); + rt_err_t (*close) (rt_device_t dev); + rt_ssize_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); + rt_ssize_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); + rt_err_t (*control)(rt_device_t dev, int cmd, void *args); +}; +``` + +**各方法说明:** + +| 方法 | 说明 | +|------|------| +| `init` | 初始化设备,成功后 `flag` 置为已激活状态(`RT_DEVICE_FLAG_ACTIVATED`);若已激活则再次调用会立即返回 | +| `open` | 打开设备,建议在 open 时才使能设备(避免上层未准备好就默认接收数据) | +| `close` | 关闭设备。设备控制块维护打开计数,open 时 +1、close 时 -1,当计数变 0 时才真正关闭 | +| `read` | 从设备读取数据。`pos` 为偏移量(串口类可忽略);块设备中 `pos`/`size` 以数据块大小为单位。返回 `rt_ssize_t`(读到的字节数/块数),<0 表示错误 | +| `write` | 向设备写入数据,语义与 read 对称 | +| `control` | 根据 `cmd` 命令控制设备,命令由底层驱动自定义实现(如 `RT_DEVICE_CTRL_BLK_GETGEOME` 获取块设备大小) | + +### 2.3 注册 / 注销设备 + +```c +rt_err_t rt_device_register(rt_device_t dev, const char *name, rt_uint16_t flags); +rt_err_t rt_device_unregister(rt_device_t dev); +``` + +**`rt_device_register()` 参数:** + +| 参数 | 描述 | +|------|------| +| `dev` | 设备句柄 | +| `name` | 设备名称,最大长度由 `RT_NAME_MAX` 决定,多余部分自动截掉 | +| `flags` | 设备模式标志 | + +返回:`RT_EOK`(成功);`-RT_ERROR`(失败,`dev` 为空或 `name` 已存在)。 + +> 应当避免重复注册已经注册的设备,以及注册相同名字的设备。注销设备后其从设备管理器移除,无法再被查找;注销**不会释放**设备控制块占用的内存。 + +**`flags` 完整取值(可用 `|` 组合):** + +``` +#define RT_DEVICE_FLAG_RDONLY 0x001 /* 只读 */ +#define RT_DEVICE_FLAG_WRONLY 0x002 /* 只写 */ +#define RT_DEVICE_FLAG_RDWR 0x003 /* 读写 */ +#define RT_DEVICE_FLAG_REMOVABLE 0x004 /* 可移除 */ +#define RT_DEVICE_FLAG_STANDALONE 0x008 /* 独立 */ +#define RT_DEVICE_FLAG_SUSPENDED 0x020 /* 挂起 */ +#define RT_DEVICE_FLAG_STREAM 0x040 /* 流模式 */ +#define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收 */ +#define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA 接收 */ +#define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送 */ +#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA 发送 */ +``` + +> `RT_DEVICE_FLAG_STREAM`(流模式)用于向串口终端输出字符串:当输出的字符是 `"\n"` 时,会自动在前面补一个 `"\r"` 做分行。 + +**查看已注册设备:** 注册成功后可用 FinSH 命令查看系统所有设备信息: + +``` +msh />list_device +device type ref count +-------- -------------- ---------- +e0 Network Interface 0 +sd0 Block Device 1 +rtc RTC 0 +uart1 Character Device 0 +uart0 Character Device 2 +``` + +### 2.4 实验一:注册字符设备 `test` + +在自己的开发板上注册一个 `test` 字符设备: +1. 调用 `rt_device_create()` 创建设备对象。 + +2. 填写设备操作方法 `ops`。 + +3. 调用 `rt_device_register()` 把设备注册到系统(驱动名称为 `test`)。 + +4. `list_device` 查看注册情况。 + + ``` + #include + #include + + + + #if defined(BSP_USING_DRV_TEST) + + #define DRV_DEBUG + #define LOG_TAG "drv.test" + #include + + static rt_err_t drv_test_init(rt_device_t dev) + { + LOG_I("test drv init"); + return RT_EOK; + } + + static rt_err_t drv_test_open(rt_device_t dev, rt_uint16_t oflag) + { + LOG_I("test drv open flag = %d", oflag); + return RT_EOK; + } + + static rt_err_t drv_test_close(rt_device_t dev) + { + LOG_I("test drv close"); + return RT_EOK; + } + + static rt_size_t drv_test_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) + { + LOG_I("test drv read pos = %d, size = %d", pos, size); + return size; + } + + static rt_size_t drv_test_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) + { + LOG_I("test drv write pos = %d, size = %d", pos, size); + return size; + } + + static rt_err_t drv_test_control(rt_device_t dev, int cmd, void *args) + { + LOG_I("test drv control cmd = %d", cmd); + return RT_EOK; + } + + int rt_drv_test_init(void) + { + rt_device_t test_dev = rt_device_create(RT_Device_Class_Char, 0); + if(!test_dev) + { + LOG_E("test drv create failed!"); + return -RT_ERROR; + } + + test_dev->init = drv_test_init; + test_dev->open = drv_test_open; + test_dev->close = drv_test_close; + test_dev->read = drv_test_read; + test_dev->write = drv_test_write; + test_dev->control = drv_test_control; + + if(rt_device_register(test_dev, "test_drv", RT_DEVICE_FLAG_RDWR) != RT_EOK) + { + LOG_E("test drv register failed!"); + return -RT_ERROR; + } + + return RT_EOK; + } + INIT_BOARD_EXPORT(rt_drv_test_init); + + static int drv_test_app(void) + { + rt_device_t test_dev = rt_device_find("test_drv"); + if(test_dev == RT_NULL) + { + LOG_E("can not find test drv!"); + return -RT_ERROR; + } + + rt_device_open(test_dev, RT_DEVICE_OFLAG_RDWR); + rt_device_control(test_dev, RT_DEVICE_CTRL_CONFIG, RT_NULL); + rt_device_write(test_dev, 100, RT_NULL, 1024); + rt_device_read(test_dev, 20, RT_NULL, 128); + + rt_device_close(test_dev); + + return RT_EOK; + } + MSH_CMD_EXPORT(drv_test_app, enable test drv app); + + #endif /*defined(BSP_USING_DRV_TEST)*/ + + ``` + + 注册情况:![](D:\Honor Share\Screenshot\屏幕截图 2026-08-23 103737.png) + + ![](D:\Honor Share\Screenshot\屏幕截图 2026-08-23 105641.png) + +### 2.5 访问 I/O 设备(管理接口与 ops 映射) + +应用程序通过 I/O 设备管理接口访问硬件设备,管理接口与设备 ops 方法存在映射关系: + +| 功能 | 接口 | +|------|------| +| 查找设备 | `rt_device_t rt_device_find(const char* name)` | +| 初始化设备 | `rt_err_t rt_device_init(rt_device_t dev)` | +| 打开设备 | `rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags)` | +| 关闭设备 | `rt_err_t rt_device_close(rt_device_t dev)` | +| 控制设备 | `rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg)` | +| 读设备 | `rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size)` | +| 写设备 | `rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size)` | + +### 2.6 查找设备 + +```c +rt_device_t rt_device_find(const char* name); +``` + +返回:设备句柄(找到);`RT_NULL`(未找到对应设备对象)。 + +### 2.7 初始化设备 + +```c +rt_err_t rt_device_init(rt_device_t dev); +``` + +返回:`RT_EOK`(成功);错误码(失败)。 + +> 当一个设备已经初始化成功后,调用此接口将不再重复初始化。 + +### 2.8 打开和关闭设备 + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag); +rt_err_t rt_device_close(rt_device_t dev); +``` + +打开设备时,系统会检测设备是否已初始化,未初始化则默认调用初始化接口。**打开标志位 `oflag`:** + +- `RT_DEVICE_OFlag_RDONLY`:只读 +- `RT_DEVICE_OFlag_WRONLY`:只写 +- `RT_DEVICE_OFlag_RDWR`:可读写 +- (其他流式/非阻塞标志按需组合) + +返回:`RT_EOK`(打开成功);`-RT_EBUSY`(若设备注册时指定了 `RT_DEVICE_FLAG_STANDALONE`,则不允许重复打开);其他错误码(失败)。 + +### 2.9 控制设备 + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +按 `cmd` 命令控制设备,命令由底层驱动自定义。 + +### 2.10 数据接收回调 `rx_indicate` + +当硬件设备收到数据时,通过该函数设置**数据接收指示**,通知上层应用线程有数据到达: + +```c +rt_err_t rt_device_set_rx_indicate(rt_device_t dev, + rt_err_t (*rx_ind)(rt_device_t dev, rt_size_t size)); +``` + +### 2.11 发送完成回调 `tx_complete` + +在应用调用 `rt_device_write()` 写入数据时,若底层硬件支持自动发送,可设置回调,在底层硬件数据发送完成后(如 DMA/FIFO 传送完成或写入完毕产生完成中断时)被调用: + +```c +rt_err_t rt_device_set_tx_complete(rt_device_t dev, + rt_err_t (*tx_done)(rt_device_t dev, void* buffer)); +``` + +### 2.12 I/O 框架调用关系图 + +``` +应用程序 + │ rt_device_open/read/write/control/close + ▼ +I/O 设备管理层(I/O 设备管理接口) + │ 按 name 查找设备,并通过 ops 分发 + ▼ +设备操作方法 ops(init/open/close/read/write/control) + │ + ▼ +硬件底层驱动(具体 BSP 实现) +``` + + + +--- + +## 培训3:GPIO(PIN)外设开发 + +### 3.1 引脚概念与原理 + +芯片上的引脚一般分为 4 类:**电源、时钟、控制与 I/O**。I/O 口在使用模式上分为: +- **GPIO**(General Purpose Input Output,通用输入/输出) +- **功能复用 I/O**(如 SPI/I2C/UART 功能脚) + +大多数 MCU 引脚不止一个功能,可通过不同配置切换引脚实际功能。**通用 I/O 口主要特性:** + +- **可编程控制中断**:触发模式可配置,一般有 **5 种中断触发模式**(上升沿、下降沿、双沿、高电平、低电平)。 +- **输入输出模式可控制**: + - 输出模式:推挽、开漏、上拉、下拉,可配置输出高/低电平。 + - 输入模式:浮空、上拉、下拉、模拟,可读取引脚电平状态。 + +### 3.2 PIN 设备管理接口总览 + +| 接口 | 功能 | +|------|------| +| `rt_pin_get()` | 获取引脚编号 | +| `rt_pin_mode()` | 设置引脚模式 | +| `rt_pin_write()` | 设置引脚电平 | +| `rt_pin_read()` | 读取引脚电平 | +| `rt_pin_attach_irq()` | 绑定引脚中断回调函数 | +| `rt_pin_irq_enable()` | 使能引脚中断 | +| `rt_pin_detach_irq()` | 脱离引脚中断回调函数 | + +### 3.3 获取引脚编号(重要) + +RT-Thread 提供的引脚编号**与芯片引脚号不是同一个概念**,引脚编号由 PIN 设备驱动程序定义,与具体芯片相关。有 3 种方式获取: + +**① 使用 API:** +```c +pin_number = rt_pin_get("PF.9"); +``` + +**② 使用宏定义(`rt-thread/bsp/stm32` BSP):** +```c +#include +#include // 不包含可能报 "没有 'F' 定义" +#define LED0_PIN GET_PIN(F, 9) // 获取 PF9 +``` + +**③ 查看驱动文件 `drv_gpio.c`:** 该文件有数组存放每个 PIN 脚的编号信息,如: +```c +static const rt_uint16_t pins[] = { + __STM32_PIN(2, A, 15), /* 2 为 RT-Thread 引脚编号,A 为端口,15 为引脚号 */ + __STM32_PIN(3, B, 5), + ... }; +``` +即 PA15 对应的 RT-Thread 引脚编号为 2。 + +### 3.4 设置引脚模式 + +```c +void rt_pin_mode(rt_base_t pin, rt_uint8_t mode); +``` + +`mode` 可取 5 种宏之一(实际支持模式看驱动实现): + +| 宏 | 值 | 含义 | +|----|----|------| +| `PIN_MODE_OUTPUT` | 0x00 | 输出 | +| `PIN_MODE_INPUT` | 0x01 | 输入 | +| `PIN_MODE_INPUT_PULLUP` | 0x02 | 上拉输入 | +| `PIN_MODE_INPUT_PULLDOWN` | 0x03 | 下拉输入 | +| `PIN_MODE_OUTPUT_OD` | 0x04 | 开漏输出 | + +### 3.5 设置 / 读取引脚电平 + +```c +void rt_pin_write(rt_base_t pin, rt_ssize_t value); /* value: PIN_LOW / PIN_HIGH */ +rt_ssize_t rt_pin_read(rt_base_t pin); /* 返回 PIN_LOW / PIN_HIGH */ +``` + +示例: +```c +#define BEEP_PIN_NUM 35 /* PB0 */ +rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT); +rt_pin_write(BEEP_PIN_NUM, PIN_LOW); /* 输出低电平 */ +int status = rt_pin_read(BEEP_PIN_NUM); /* 读取电平 */ +``` + +### 3.6 绑定引脚中断回调函数 + +```c +rt_err_t rt_pin_attach_irq(rt_base_t pin, rt_uint8_t mode, + void (*hdr)(void *args), void *args); +``` + +**中断触发模式 `mode` 可取 5 种宏之一:** + +| 宏 | 值 | 含义 | +|----|----|------| +| `PIN_IRQ_MODE_RISING` | 0x00 | 上升沿触发 | +| `PIN_IRQ_MODE_FALLING` | 0x01 | 下降沿触发 | +| `PIN_IRQ_MODE_RISING_FALLING` | 0x02 | 边沿触发(上下沿都触发) | +| `PIN_IRQ_MODE_HIGH_LEVEL` | 0x03 | 高电平触发 | +| `PIN_IRQ_MODE_LOW_LEVEL` | 0x04 | 低电平触发 | + +### 3.7 使能 / 脱离引脚中断 + +```c +rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint8_t enabled); /* enabled: PIN_IRQ_ENABLE / PIN_IRQ_DISABLE */ +rt_err_t rt_pin_detach_irq(rt_base_t pin); +``` + +> 脱离回调函数后,中断并没有关闭,还可以调用 `rt_pin_attach_irq()` 再次绑定其他回调函数。 + +### 3.8 完整示例:按键控制蜂鸣器(官方 pin_beep_sample) + +```c +#include +#include + +#define BEEP_PIN_NUM 35 /* PB0 */ +#define KEY0_PIN_NUM 55 /* PD8 */ +#define KEY1_PIN_NUM 56 /* PD9 */ + +void beep_on(void *args) +{ + rt_kprintf("turn on beep!\n"); + rt_pin_write(BEEP_PIN_NUM, PIN_HIGH); +} +void beep_off(void *args) +{ + rt_kprintf("turn off beep!\n"); + rt_pin_write(BEEP_PIN_NUM, PIN_LOW); +} +static void pin_beep_sample(void) +{ + /* 蜂鸣器引脚:输出模式,默认低电平 */ + rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT); + rt_pin_write(BEEP_PIN_NUM, PIN_LOW); + + /* 按键0:上拉输入,下降沿触发 */ + rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP); + rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL); + rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE); + + /* 按键1:上拉输入,下降沿触发 */ + rt_pin_mode(KEY1_PIN_NUM, PIN_MODE_INPUT_PULLUP); + rt_pin_attach_irq(KEY1_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_off, RT_NULL); + rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE); +} +MSH_CMD_EXPORT(pin_beep_sample, pin beep sample); +``` + +### 3.9 按键库 FlexibleButton + +- 在 menuconfig 中勾选 **FlexibleButton** 按键库。 +- 用于按键消抖、单击/双击/长按等状态机处理,配合 GPIO 中断使用。 + +--- + +## 培训3:I2C 外设开发 + +### 4.1 I2C 总线简介 + +I2C(Inter-Integrated Circuit,读作 I-squared-C),由**飞利浦(PHILIPS)公司于 1980 年代**提出,是**半双工、双向二线制同步串行总线**,用于连接主板、嵌入式系统或手机的低速周边外设。 + +I2C 只需两根信号线: +- **SDA**(serial data):双向数据线。 +- **SCL**(serial clock):双向时钟线。 + +> 与 SPI(两根线分别收/发数据)不同,I2C 只用一根线做数据收发。I2C 与 SPI 一样以主从方式工作,但**允许多个主设备**同时存在,每个器件有唯一地址,同一时刻只允许一个主设备。 + +**常见传输速率模式:** + +| 模式 | 速率 | +|------|------| +| 低速模式 | 10 Kbit/s | +| 标准模式 | 100 Kbit/s | +| 快速模式 | 400 Kbit/s | +| 高速模式 | 3.4 Mbit/s | + +### 4.2 数据传输格式与协议术语 + +总线空闲时 SDA 和 SCL 均为高电平。主机通讯流程:发送**开始条件** → 发送**从机地址和读写控制位** → **传输数据** → 发送**停止条件**。每个字节 8 位,高位在前、低位在后。 + +| 术语 | 说明 | +|------|------| +| **开始条件** | SCL 为高电平时,主机将 SDA 拉低,表示数据即将开始 | +| **从机地址** | 主机发送的第一个字节,高 7 位为地址,最低位为 R/W 读写控制位(1=读,0=写)。有 7 位/10 位两种模式;10 位模式第一位字节头 7 位是 `11110XX`,后 8 位在第二个字节 | +| **应答信号 ACK** | 每传完一个字节,接收方回复 ACK。写数据时由从机发送 ACK,读数据时由主机发送 ACK;主机读到最后一个字节时可发 NACK 再跟停止条件 | +| **数据** | 从机地址后可能发一些指令(依从机而定),然后开始传数据,每字节 8 位,字节数不限 | +| **重复开始条件** | 一次通信中,主机需与不同从机通信或切换读写时,可再次发送开始条件 | +| **停止条件** | SDA 为低电平时,主机将 SCL 拉高并保持,再拉高 SDA,表示传输结束 | + +**起始位(S)/结束位(P) 与电平关系:** +- **起始位(S)**:SCL 高电平时,SDA 由高变低。 +- **结束位(P)**:SCL 高电平时,SDA 由低变高。 + +**ACK/NACK:** +- **ACK**:拉低 SDA,并在 SCL 高电平期间保持 SDA 为低。 +- **NACK**:不拉低 SDA(SDA 保持高电平),并在 SCL 高电平期间保持 SDA 为高。 + +### 4.3 主机向从机写 / 读数据 + +**写数据流程:** +1. 发送起始位。 +2. 主设备发送从设备地址(7bit 地址 + 最低位写标志组成的 8bit 数据)。 +3. 释放 SDA 线,等待从机应答。 +4. 每一字节数据传输都跟一个应答位。 +5. 数据传完发送结束位,释放 I2C 总线。 + +**读数据流程:** +1. 起始位后,主设备先发送「从设备地址 + 写标志」(指定寄存器/内部地址)。 +2. 释放 SDA,从机应答后再次发送起始位(重复开始条件)。 +3. 主设备再发送「从设备地址 + 读标志」,从机应答后把数据发给主设备,主设备应答。 +4. 读完后发送 NACK + 结束位。 + +### 4.4 从机常用模式 / 访问 I2C 总线设备接口 + +RT-Thread 将 **I2C 主机虚拟为 I2C 总线设备**,I2C 从机通过 I2C 设备接口与总线通讯: + +| 函数 | 描述 | +|------|------| +| `rt_device_find()` | 根据 I2C 总线设备名称查找设备获取设备句柄 | +| `rt_i2c_transfer()` | 传输数据(消息为单位传输) | + +| 操作 | 说明 | +|------|------| +| 向从机某个寄存器写一个字节 | 一次写 | +| 向从机某个寄存器写多个字节 | 写地址 + 连续写 | +| 从从机某个寄存器读一个字节 | 写寄存器 + 读数据 | +| 从从机某个寄存器读多个字节 | 写寄存器 + 连续读 | + +### 4.5 查看 / 探测 I2C 总线设备 + +- 开启 I2C 驱动后用 `list_device` 命令查看总线注册情况。 +- 勾选 **i2c-tools** 软件包方便调试,探测总线设备: +``` +msh> i2c scan i2c3 +``` + +### 4.6 查找 I2C 总线设备 + +```c +rt_device_t rt_device_find(const char* name); +``` + +一般注册名称为 `i2c0`、`i2c1` 等: +```c +#define AHT10_I2C_BUS_NAME "i2c1" +struct rt_i2c_bus_device *i2c_bus; +i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(AHT10_I2C_BUS_NAME); +``` + +### 4.7 I2C 数据传输函数 `rt_i2c_transfer` + +```c +rt_ssize_t rt_i2c_transfer(struct rt_i2c_bus_device *bus, + struct rt_i2c_msg msgs[], rt_uint32_t num); +``` + +- `bus`:I2C 总线设备句柄;`msgs[]`:待传输消息数组指针;`num`:消息元素个数。 +- 返回消息数组元素个数(成功)或错误码(失败)。 +- 与 SPI 自定义传输类似,以消息为单位;如需发送**重复开始条件**,则发送 2 个消息。 + +> ⚠️ `rt_i2c_transfer()` 会调用 `rt_mutex_take()`,**不能在中断服务程序里调用**,否则会导致 assertion 报错。 + +**消息结构 `struct rt_i2c_msg`:** + +```c +struct rt_i2c_msg { + rt_uint16_t addr; /* 从机地址 */ + rt_uint16_t flags; /* 读、写标志等 */ + rt_uint16_t len; /* 读写数据字节数 */ + rt_uint8_t *buf; /* 读写数据缓冲区指针 */ +}; +``` + +> 从机地址 `addr` 支持 7 位和 10 位二进制地址。**RT-Thread I2C 设备接口使用的从机地址均不包含读写位**,读写位控制需修改标志 `flags`。 + +**`flags` 可取值(可用 `|` 组合):** + +| 宏 | 值 | 含义 | +|----|----|------| +| `RT_I2C_WR` | 0x0000 | 写标志(不可与读标志 `|`) | +| `RT_I2C_RD` | 1<<0 | 读标志(不可与写标志 `|`) | +| `RT_I2C_ADDR_10BIT` | 1<<2 | 10 位地址模式 | +| `RT_I2C_NO_START` | 1<<4 | 无开始条件 | +| `RT_I2C_IGNORE_NACK` | 1<<5 | 忽视 NACK | +| `RT_I2C_NO_READ_ACK` | 1<<6 | 读的时候不发送 ACK | +| `RT_I2C_NO_STOP` | 1<<7 | 不发送结束位 | + +### 4.8 推荐使用的从设备读写便捷 API + +以下两个函数封装自 `rt_i2c_transfer()`,更简单易用,推荐使用: + +**发送数据:** +```c +rt_ssize_t rt_i2c_master_send(struct rt_i2c_bus_device *bus, rt_uint16_t addr, + rt_uint16_t flags, const rt_uint8_t *buf, rt_uint32_t count); +``` + +**接收数据:** +```c +rt_ssize_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus, rt_uint16_t addr, + rt_uint16_t flags, rt_uint8_t *buf, rt_uint32_t count); +``` + +> `flags` 只能传除 `RT_I2C_WR`/`RT_I2C_RD` 之外的其他标志位(可 `|` 组合)。 + +**小技巧——拼接发送一条数据(prefix + buffer):** +```c +rt_i2c_master_send(_bus, _addr, RT_I2C_NO_STOP, prefix, prefix_len); /* 只发起始位,不发停止位 */ +rt_i2c_master_send(_bus, _addr, RT_I2C_NO_START, buffer, len); /* 不发起始位,只发停止位 */ +``` + +### 4.9 I2C 使用思路与示例代码 + +**使用思路:** +1. 查找 I2C 总线设备(`rt_device_find`)。 +2. 构造 `msgs` 消息(填充地址、标志、缓冲区)。 +3. 启动传输(`rt_i2c_transfer` 或 `rt_i2c_master_send/recv`)。 +4. 处理结果(校验返回值)。 + +**写一个字节数据:** +```c +struct rt_i2c_msg msgs = { .addr = slave_addr, .flags = RT_I2C_WR, .buff = &data, .len = 1 }; +rt_i2c_transfer(bus, &msgs, 1); +``` + +**写多字节 / 读数据:** +```c +/* 读寄存器:第一条消息写寄存器地址(W) */ +msg1.addr = dev_addr; msg1.flags = RT_I2C_WR; msg1.buf = reg; msg1.len = 1; +/* 第二条消息读数据(R) */ +msg2.addr = dev_addr; msg2.flags = RT_I2C_RD; msg2.buf = data; msg2.len = n; +rt_i2c_transfer(bus, msgs, 2); +``` + +**完整 AHT10 温湿度传感器例程**(官方 `i2c_aht10_sample`,`MSH_CMD_EXPORT` 导出,命令格式 `i2c_aht10_sample i2c1`):查找总线 → 写命令/校准 → 延时 → 读 6 字节 → 按位解析湿度与温度。要点: +- `write_reg()`/`read_regs()` 分别调用 `rt_i2c_transfer()`。 +- 读温湿度前用 `rt_thread_mdelay(400)` 等待传感器就绪。 + +### 4.10 应用开发常见错误 / 总线死锁 + +**现象:** 单个设备挂掉导致总线死锁,`rt_i2c_transfer` 函数执行返回 `-5`。 + +**死锁原因:** +- 当 I2C 主机正与从机通信,主机准备发第 9 个时钟时,`SCL` 为高,从机开始拉低 `SDA` 做 ACK 准备,等待主机 `SCL` 变低后从机再释放 `SDA` 为高。 +- 如果此时主机复位,`SCL` 还没来得及变低,而主机发现 `SDA` 一直为低,也在等待从机释放 `SDA` 为高,**主机和从机都进入相互等待的死锁状态**。 + +**解锁思路:** +- 在主机启动传输前,先控制 `SCL` 时钟线产生 **9 个时钟脉冲**(针对 8 位数据),这样 I2C 从设备可以完成被挂起的读操作,从死锁状态恢复。 + +**`rt_i2c_transfer` 返回 `-5` 的另一个原因:设备地址错误**(从机地址与设备手册不符)。 + +### 4.11 软件 I2C 驱动编写 + +1. 开启 I2C 框架(menuconfig 使能 `RT_USING_I2C`)。 +2. 选中 **I2C 软件模拟设备**功能。 +3. 配置内核(`menuconfig` 配置 I2C 引脚)。 +4. 编写 I2C 软件模拟驱动模板(实现总线 start/stop/读/写 基元)。 + +--- + +## 培训4:SPI 外设开发 + +### 5.1 SPI 总线概念与原理 + +SPI(Serial Peripheral Interface,串行外设接口)是一种**高速、全双工、同步**通信总线,常用于短距离通讯,主要应用于 EEPROM、FLASH、实时时钟、AD 转换器、数字信号处理器和数字信号解码器之间。SPI 一般使用 **4 根线**通信: + +| 线 | 含义 | +|----|------| +| **MOSI** | 主机输出 / 从机输入(Master Output/Slave Input) | +| **MISO** | 主机输入 / 从机输出(Master Input/Slave Output) | +| **SCLK** | 串行时钟线(Serial Clock),主设备输出时钟至从设备 | +| **CS** | 从设备选择线(Chip select),也叫 SS、CSB、CSN、EN 等 | + +**工作方式:** +- SPI 以主从方式工作,通常一个主设备 + 一个或多个从设备。通信由主设备发起,通过 CS 选择从设备,SCLK 提供时钟,数据经 MOSI 输出、MISO 接收。 +- 一个 SPI 主设备(控制器)可连接多个从设备;同一控制器下的从设备**共享 SCK、MISO、MOSI** 三根线,但每个从设备的 **CS 引脚独立**(一般为低电平有效)。 +- 任何时刻一个主设备只有一个 CS 处于有效状态。 + +**时序模式(CPOL / CPHA):** +- **CPOL**(时钟极性):表示时钟信号的初始电平,0=初始低电平,1=初始高电平。 +- **CPHA**(时钟相位):表示在哪个时钟沿采样数据,0=首个时钟变化沿采样,1=第二个时钟变化沿采样。 +- 组合出 4 种模式:①CPOL=0,CPHA=0 ②CPOL=0,CPHA=1 ③CPOL=1,CPHA=0 ④CPOL=1,CPHA=1。 + +**SPI Flash 扩展类型:** +- **QSPI**(Queued SPI):Motorola 推出的 SPI 扩展,增加队列传输机制,一次可传多达 16 个 8/16 位数据的队列,启动后到结束都无需 CPU 干预;最大结构特点是**以 80 字节 RAM 代替发送/接收数据寄存器**。 +- **Dual SPI Flash**:SPI Flash 全双工不常用,可发命令字节进入 Dual 半双工模式加倍速率,MOSI→SIO0、MISO→SIO1,一个时钟周期传 2 bit。 +- **Quad SPI Flash**:增加 SIO2、SIO3 两根线,一个时钟传 4 bit。 + +> 标准 SPI / Dual SPI / Quad SPI Flash 三者:相同时钟下线数越多速率越高。 + +### 5.2 RT-Thread SPI 开发模式 + +在 RT-Thread 中 SPI 设备分为 **SPI 总线**和 **SPI 设备**两大类: +- **SPI 总线**:对应 SPI 控制器(主设备)。 +- **SPI 设备**:对应不同 CS 连接的从设备。 + +使用前需先注册 SPI 总线,再把从设备挂载到总线上。**开发模式流程:** +1. 编写 SPI BUS 驱动 → 2. 注册/挂载 SPI Device → 3. 打开 SPI Device → 4. 使用 API 收发 → 5. 关闭/卸载。 + +### 5.3 挂载 SPI 设备 + +SPI 驱动会注册 SPI 总线,SPI 设备需挂载到已注册的总线。 + +**新接口(RT-Thread 5.0.0 起,推荐):** 基于 PIN 框架绑定片选引脚,避免不同 BSP 片选操作不统一: + +```c +rt_err_t rt_spi_bus_attach_device_cspin(struct rt_spi_device *device, + const char *name, const char *bus_name, rt_base_t cs_pin, void *user_data); +``` + +- `cs_pin` 可通过 PIN 框架 `rt_pin_get("PB.14")` 或 BSP 级 `GET_PIN(B, 14)` 获取;`user_data` 用不到可设为 `RT_NULL`。 + +**兼容旧接口(5.0.0 前):** 片选引脚通过 `user_data` 挂载: + +```c +rt_err_t rt_spi_bus_attach_device(struct rt_spi_device *device, + const char *name, const char *bus_name, void *user_data); +``` + +**命名原则:** SPI 总线命名 `spix`(如 `spi1`),SPI 设备命名 `spixy`(如 `spi10` 表示挂载在 spi1 总线上的 0 号设备)。旧接口 `user_data` 一般为 CS 引脚指针。 + +**从总线卸载设备(动态申请场景):** +```c +rt_err_t rt_spi_bus_detach_device(struct rt_spi_device *device); /* 应用层优先使用 */ +rt_err_t rt_spi_bus_detach_device_cspin(struct rt_spi_device *device); +``` +卸载时先从框架注销设备;若该设备是总线当前 owner 则清空;配置了有效 `cs_pin` 则将该引脚重新设为输入模式;最后把 `device->bus` 置为 `RT_NULL`。卸载后可 `rt_free()` 释放动态申请的设备。 + +**W25Q128 挂载示例:** +```c +static int rt_hw_spi_flash_init(void) +{ + struct rt_spi_device *spi_device = (struct rt_spi_device *)rt_malloc(sizeof(struct rt_spi_device)); + if (RT_EOK != rt_spi_bus_attach_device_cspin(spi_device, "spi10", "spi1", GET_PIN(B, 14), RT_NULL)) + return -RT_ERROR; + if (RT_NULL == rt_sfud_flash_probe("W25Q128", "spi10")) + return -RT_ERROR; + return RT_EOK; +} +INIT_COMPONENT_EXPORT(rt_hw_spi_flash_init); +``` + +### 5.4 配置 SPI 设备 + +```c +rt_err_t rt_spi_configure(struct rt_spi_device *device, struct rt_spi_configuration *cfg); +``` + +**`struct rt_spi_configuration`:** +```c +struct rt_spi_configuration { + rt_uint8_t mode; /* 模式 */ + rt_uint8_t data_width; /* 数据宽度:8/16/32 位 */ + rt_uint16_t reserved; /* 保留 */ + rt_uint32_t max_hz; /* 最大频率(波特率) */ +}; +``` + +**`mode` 常用宏组合:** +``` +RT_SPI_LSB / RT_SPI_MSB /* bit[2]: 传输顺序 LSB/MSB 在前 */ +RT_SPI_MASTER / RT_SPI_SLAVE /* bit[3]: 主从模式 */ +RT_SPI_MODE_0 /* CPOL=0, CPHA=0 */ +RT_SPI_MODE_1 /* CPOL=0, CPHA=1 */ +RT_SPI_MODE_2 /* CPOL=1, CPHA=0 */ +RT_SPI_MODE_3 /* CPOL=1, CPHA=1 */ +RT_SPI_CS_HIGH /* bit[4]: 片选高有效 */ +RT_SPI_NO_CS /* bit[5]: 无片选 */ +RT_SPI_3WIRE /* bit[6]: SI/SO 共用 */ +RT_SPI_READY /* bit[7]: 从机拉低暂停 */ +``` + +**配置示例:** +```c +struct rt_spi_configuration cfg; +cfg.data_width = 8; +cfg.mode = RT_SPI_MASTER | RT_SPI_MODE_0 | RT_SPI_MSB; +cfg.max_hz = 20 * 1000 * 1000; /* 20MHz */ +rt_spi_configure(spi_dev, &cfg); +``` + +**QSPI 配置:** +```c +rt_err_t rt_qspi_configure(struct rt_qspi_device *device, struct rt_qspi_configuration *cfg); +``` +`struct rt_qspi_configuration` 继承 `struct rt_spi_configuration parent`,并增加 `medium_size`(介质大小)、`ddr_mode`(双倍速率)、`qspi_dl_width`(QSPI 总线位宽:单线 1、双线 2、四线 4)。 + +### 5.5 SPI 访问接口总览 + +| 函数 | 描述 | +|------|------| +| `rt_device_find()` | 根据 SPI 设备名称查找设备获取设备句柄 | +| `rt_spi_transfer_message()` | 自定义传输数据(消息链) | +| `rt_spi_transfer()` | 传输一次数据 | +| `rt_spi_send()` | 发送一次数据 | +| `rt_spi_recv()` | 接收一次数据 | +| `rt_spi_send_then_send()` | 连续两次发送 | +| `rt_spi_send_then_recv()` | 先发送后接收 | + +> ⚠️ SPI 数据传输接口会调用 `rt_mutex_take()`,**不能在中断服务程序中调用**,否则 assertion 报错。 + +### 5.6 查找 SPI 设备 + +```c +rt_device_t rt_device_find(const char* name); +``` +一般注册名称为 `spi10`、`qspi10` 等: +```c +#define W25Q_SPI_DEVICE_NAME "qspi10" +struct rt_spi_device *spi_dev_w25q = (struct rt_spi_device *)rt_device_find(W25Q_SPI_DEVICE_NAME); +``` + +### 5.7 自定义传输 `rt_spi_transfer_message` + +```c +struct rt_spi_message *rt_spi_transfer_message(struct rt_spi_device *device, + struct rt_spi_message *message); +``` +返回 `RT_NULL`(成功)或指向未发送完消息的指针(失败)。可传输一串消息(单向链表),`struct rt_spi_message` 原型: + +```c +struct rt_spi_message { + const void *send_buf; /* 发送缓冲区指针,RT_NULL 表示只接收 */ + void *recv_buf; /* 接收缓冲区指针,RT_NULL 表示只发送 */ + rt_size_t length; /* 收发数据字节数 */ + struct rt_spi_message *next; /* 指向继续发送的下一条消息 */ + unsigned cs_take : 1; /* 置 1:传输前将对应 CS 设为有效 */ + unsigned cs_release : 1; /* 置 1:数据传输结束后释放 CS */ +}; +``` + +- `length` 单位为 word:数据 8 位时 1 个 length 占用 1 字节;16 位时占 2 字节。 +- 多条消息通过 `next` 以单向链表连接;第一条消息 `cs_take` 需置 1,最后一条 `cs_release` 置 1。 +- 当 `send_buf` 或 `recv_buf` 非空时,两者的可用空间都不得小于 `length`。 + +**读取 W25Q ID 示例:** +```c +rt_uint8_t w25x_read_id = 0x90; /* 命令 */ +rt_uint8_t id[5] = {0}; +struct rt_spi_message msg1, msg2; + +msg1.send_buf = &w25x_read_id; msg1.recv_buf = RT_NULL; msg1.length = 1; +msg1.cs_take = 1; msg1.cs_release = 0; msg1.next = &msg2; + +msg2.send_buf = RT_NULL; msg2.recv_buf = id; msg2.length = 5; +msg2.cs_take = 0; msg2.cs_release = 1; msg2.next = RT_NULL; + +rt_spi_transfer_message(spi_dev_w25q, &msg1); +``` + +### 5.8 传输一次数据 `rt_spi_transfer` + +```c +rt_ssize_t rt_spi_transfer(struct rt_spi_device *device, + const void *send_buf, void *recv_buf, rt_size_t length); +``` +返回 0(失败)或成功传输的字节数。此函数**不需要手动控制片选**,等同于调用 `rt_spi_transfer_message()` 传输一条消息(`cs_take=1, cs_release=1`),发送时片选选中、返回时释放。 + +### 5.9 单独发送 / 单独接收 + +```c +rt_err_t rt_spi_send(struct rt_spi_device *device, const void *send_buf, rt_size_t length); /* 只发送,忽略接收 */ +rt_err_t rt_spi_recv(struct rt_spi_device *device, void *recv_buf, rt_size_t length); /* 只接收 */ +``` + +### 5.10 连续两次发送 `rt_spi_send_then_send` + +```c +rt_err_t rt_spi_send_then_send(struct rt_spi_device *device, + const void *send_buf1, rt_size_t send_length1, + const void *send_buf2, rt_size_t send_length2); +``` +先发第一条数据时片选选中,发送完第二条后释放片选。**适用场景**:先发送地址,再发送指定长度数据,中途不释放片选。 + +### 5.11 先发送后接收 `rt_spi_send_then_recv` + +```c +rt_err_t rt_spi_send_then_recv(struct rt_spi_device *device, + const void *send_buf, rt_size_t send_length, + void *recv_buf, rt_size_t recv_length); +``` +发送第一条数据时开始片选(忽略接收数据),然后发送第二条数据,主设备会发送 `0xFF`,接收数据保存在 `recv_buf` 中,返回时释放片选。**适用场景**:从 SPI 从设备读取一块数据,先发命令/地址再接收指定长度数据。 + +--- + +## 进阶:设备驱动框架(Driver Model) + +> 本节整理自官方《设备驱动框架》章节(对应 `components/drivers`、公共头文件和 BSP 驱动实现),主要面向**复杂 SoC 平台驱动**。 + +### 6.1 驱动目录组织(`components/drivers`) + +`components/drivers/Kconfig` 集中组织 DM 相关子系统,设备树、总线匹配、统一资源管理和通用子系统是复杂平台驱动的主要组织方式。 + +| 子系统 | 主要代码位置 | 说明 | +|--------|--------------|------| +| 驱动模型核心 | `drivers/core` | DM、platform bus、设备树资源解析、系统关机/复位回调 | +| Clock Time | `drivers/clock_time` | 统一承载 cputime/hwtimer,提供时钟源、事件源、clock timer、hrtimer、boottime | +| 电源时钟资源 | `clk`/`reset`/`regulator`/`pmdomain` | 面向 SoC 的电源和时钟资源框架 | +| DMA | `drivers/dma` | DMA 控制器、通道、循环/单次传输和 DMA 内存池 | +| VirtIO/RPMsg | `drivers/virtio`/`rpmsg` | 虚拟化设备、AMP/remote processor 通信 | +| 图形和输入 | `graphic`/`input` | DM 图形设备、backlight、framebuffer、input event | +| 块设备/存储 | `block`/`ata`/`nvme`/`scsi`/`ufs`/`sdio` | 统一块设备层和多类存储控制器 | +| 固件和 SCMI | `firmware/arm_scmi` | SCMI transport、clock、reset、regulator、power domain 等 | + +这些模块大多依赖 `RT_USING_DM`,并通过 `rtdevice.h` 纳入公共设备头文件。 + +### 6.2 DM 核心能力 + +`components/drivers/core/dm.c` 和 `include/drivers/core/dm.h` 提供设备侧资源读取能力,驱动在 `probe` 中常用接口: + +- `rt_dm_dev_iomap()` / `rt_dm_dev_iomap_by_name()`:根据设备树 `reg` 映射寄存器。 +- `rt_dm_dev_get_irq()` / `rt_dm_dev_get_irq_by_name()`:读取并映射中断号。 +- `rt_dm_dev_prop_read_u32()` / `rt_dm_dev_prop_read_string()` 等:读取设备树属性。 +- `rt_dm_dev_set_name_auto()` / `rt_dm_dev_get_name_id()`:按 master id 自动分配稳定设备名。 +- `rt_dm_dev_bind_fwdata()` / `rt_dm_dev_unbind_fwdata()`:绑定固件节点和 RT-Thread 设备对象。 + +`power.c`/`power.h` 用于系统级关机、重启和 reboot mode:`rt_dm_power_off_handler()`、`rt_dm_reboot_mode_register()`。其中 `mode` 区分 shutdown/reset,`priority` 控制平台、普通驱动、固件等回调顺序。 + +### 6.3 平台驱动编写推荐顺序 + +1. 由 OFW/platform 匹配设备节点。 +2. 应用 pinctrl 默认状态。 +3. 获取 regulator、power domain、clock、reset 等资源。 +4. 映射寄存器并获取中断。 +5. 初始化控制器私有结构体。 +6. 注册到对应功能子系统(UART、SPI、I2C、block、input、graphic)。 + +> 按此顺序可减少"设备已注册,但电源/时钟/复位还没准备好"的问题。 + +### 6.4 驱动编写注意事项 + +- 复杂 SoC 驱动建议优先走 DM,不要在 BSP 中手写大量资源解析逻辑。 +- 复杂子系统多依赖设备树,Kconfig 中通常有 `depends on RT_USING_DM` 或 `RT_USING_OFW`。 +- 若 BSP 只使用传统设备接口,可以继续保留传统路径;复杂 driver 建议优先落在 DM 路径。 +- 涉及 DMA、cache、framebuffer、VirtIO 的驱动,需同时检查 DMA 地址、cache 维护和内存属性。 + +--- + +## 总结 + +1. **I/O 设备框架本质**:通过「驱动代码与设备驱动分离 + 统一 API」解决跨芯片、多厂家的驱动碎片化问题,使代码可复用、可入库、可做成软件包。 +2. **三层设备模型**:I/O 设备管理层 → 设备驱动框架层 → 设备驱动层;简单设备直接 `create→register`,复杂设备先注册到驱动框架再由框架注册。 +3. **I/O API 五步式**:应用层通过 `open → read/write → control → close` 访问设备,底层由设备 `ops` 分发到具体硬件驱动;配套 `find/init`、`rx_indicate/tx_complete` 回调。 +4. **GPIO(PIN)**:一套 `rt_pin_*` 接口统一配置模式、读写电平、绑定/使能/脱离中断;引脚编号需通过 `rt_pin_get / GET_PIN / drv_gpio.c` 三种方式之一获取。 +5. **I2C**:理解总线协议(起止位、ACK/NACK、读写格式、重复开始),掌握 `rt_i2c_msg` 消息结构、`rt_i2c_transfer` 与推荐使用的 `rt_i2c_master_send/recv`,以及总线死锁成因与「9 个 SCL 时钟」解锁方法。 +6. **SPI**:掌握 4 线通信、CPOL/CPHA 时序、总线/设备分离挂载、命名规则与各类传输 API 的适用场景;`rt_spi_transfer_message` 支持消息链表自定义传输,推荐 5.0.0 后使用 `rt_spi_bus_attach_device_cspin` 绑定片选。 +7. **共同注意**:SPI/I2C 传输接口内部调用互斥锁,**不能在中断服务程序中调用**。 +8. **进阶(DM)**:复杂 SoC 驱动优先走 Driver Model,结合设备树、电源/时钟/复位资源有序初始化,减少传统 BSP 手写资源解析。 + +--- + +## 参考资料 + +- RT-Thread 官方文档《I/O 设备模型》: +- RT-Thread 官方文档《设备驱动框架》: +- RT-Thread 官方文档《PIN 设备》: +- RT-Thread 官方文档《I2C 总线设备》: +- RT-Thread 官方文档《SPI 设备》: +- RT-Thread 设备类型定义:`rt-thread/include/rtdef.h`(`rt_device_class_type`) \ No newline at end of file -- Gitee