diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" new file mode 100644 index 0000000000000000000000000000000000000000..44c4ac6462c7ae81dcfc1201f564a565ee7f5f0a --- /dev/null +++ "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\344\275\234\344\270\232/\347\254\254\345\233\233\345\244\251\344\275\234\344\270\232/test_dev.c" @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2006-2021, RT-Thread Development Team + * + * SPDX-License-Identifier: Apache-2.0 + * + * Change Logs: + * Date Author Notes + * 2026-08-21 Vincent the first version + */ +#include "test_dev.h" + +#define DBG_TAG "test_dev" +#define DBG_LVL DBG_INFO +#include + +/* 框架层设备操作集(对接 I/O 设备管理层) */ +static rt_err_t test_dev_init(struct rt_device *dev) +{ + LOG_I("test drv init"); + return RT_EOK; +} + +static rt_err_t test_dev_open(struct rt_device *dev, rt_uint16_t oflag) +{ + LOG_I("test drv open flag = %d", oflag); + return RT_EOK; +} + +static rt_err_t test_dev_close(struct rt_device *dev) +{ + LOG_I("test drv close"); + return RT_EOK; +} + +static rt_size_t test_dev_read(struct rt_device *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 test_dev_write(struct rt_device *dev, + rt_off_t pos, + const void *buffer, + rt_size_t size) +{ + /* test_dev 主要是读取设备,write 可根据需求实现 */ + LOG_I("test drv write pos = %d, size = %d", pos, size); + return size; +} + +static rt_err_t test_dev_control(struct rt_device *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 = test_dev_init; + test_dev->open = test_dev_open; + test_dev->close = test_dev_close; + test_dev->read = test_dev_read; + test_dev->write = test_dev_write; + test_dev->control = test_dev_control; + + if(rt_device_register(test_dev, "test_driver", 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_driver"); + 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); + diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821183406160.png" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821183406160.png" new file mode 100644 index 0000000000000000000000000000000000000000..19a35c10636be6404e57b4a3b9c386975b659c75 Binary files /dev/null and "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821183406160.png" differ diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821184411212.png" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821184411212.png" new file mode 100644 index 0000000000000000000000000000000000000000..488c9a3455e7e43c1d3726e5b369e29835828522 Binary files /dev/null and "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821184411212.png" differ diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821184717708.png" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821184717708.png" new file mode 100644 index 0000000000000000000000000000000000000000..1da07e38e599baca645e457fbe8db34b1d84eb10 Binary files /dev/null and "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821184717708.png" differ diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821184720317.png" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821184720317.png" new file mode 100644 index 0000000000000000000000000000000000000000..1da07e38e599baca645e457fbe8db34b1d84eb10 Binary files /dev/null and "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/figures/image-20260821184720317.png" differ diff --git "a/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..fff3e744a1ab1eb0efec2c253823edc6cb11b1cf --- /dev/null +++ "b/2026/\347\254\2542\347\273\204/\351\255\217\346\200\235\346\264\213/\347\254\224\350\256\260/\347\254\254\345\233\233\345\244\251\347\254\224\350\256\260.md" @@ -0,0 +1,215 @@ +# 1. [I/O 设备模型](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/device?id=io-设备模型) + +![image-20260821183406160](figures/image-20260821183406160.png) + +​ 如图是RT_Thread设备驱动框架,由上往下分为三层,分别是**IO设备管理层**、**设备驱动框架层**和**设备驱动层**。**以下是对三层进行的一点个人理解** + +1. IO设备管理层: + +​ 创建并且定义rt_device_open()、close()等函数,将ops的open()、close()等函数绑定到rt_device_open()、close()等函数 + +2. 设备驱动框架层: + 定义结构体,包括ID、type、ops的open()、close()等函数,将驱动层代码绑定到ops +3. 设备驱动层: + 芯片逻辑驱动代码 + +## 1.1 I/O设备模型API + +```C +# 创建设备 +rt_device_t rt_device_create(int type, int attach_size); +# 摧毁设备 +void rt_device_destroy(rt_device_t device); +# 注册设备 +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_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 oflag); +rt_err_t rt_device_close(rt_device_t dev); +# 控制设备 +rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg); +# 读写设备 +rt_ssize_t rt_device_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); +rt_ssize_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); +# 数据收发回调 +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)); +rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer)); +``` + +## 1.2 开发流程 + +### 1.2.1 RT-Thread Studio内部框架开发流程(以看门狗为例) + +![image-20260821184720317](figures/image-20260821184720317.png) + +​ 上图是完整的运行流程,而应用开发一般只需要使用IO设备管理器提供的API接口进行开发,所以应用开发只需要看最左侧的流程。从上图可以看出`rt_device_create()`函数进行创建设备,是因为**看门狗是RT-Thread Studio自带设备,框架已经内部创建并且注册了**,所以可以不用创建、注册 + +​ 所以完整的开发流程应该是: + +```C +# 1. 查找设备 +rt_device_t rt_device_find(const char* name); +# 2. 初始化设备 +rt_err_t rt_device_init(rt_device_t dev); +# 3. 打开和关闭设备 +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag); +rt_err_t rt_device_close(rt_device_t dev); +``` + +### 1.2.2 自定义驱动框架开发流程 + +​ 由于是**自定义的驱动**,所以RTT并不知道驱动函数以及设备是什么,完整的开发流程应该是: + +​ ① 编写**驱动框架(rt_device_ops)**函数 ——> ② 创建设备对象(rt_device_create())——> ③ 将**板级驱动**函数绑定到**自定义的驱动框架(rt_device_ops)**函数 ——> ④ 注册设备(rt_device_register()) ——> ⑤ 应用层使用 + +```c +# 第三步将板级驱动函数绑定到自定义的驱动框架(rt_device_ops)函数 如下所示 +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; +#由于是直接使用rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg)等函数 +#而rt_device_write函数内部调用了device_write(dev, pos, buffer, size),其中device_write是一个dev->write宏定义 +#在以上代码相当于rt_device_control函数内部调用test_dev->write = drv_test_write; +``` + +# 2. I2C与SPI总线驱动开发 + +## 2.1 I2C总线驱动开发 + +​ 在 RT-Thread 中将 I2C 主机虚拟为 I2C总线设备,I2C 从机通过 I2C 设备接口和 I2C 总线通讯。RT-Thread已经将I2C所需要用到的数据发送、数据读取和消息数据结构都已经统一定义了,只需要按照裸机开发流程一样正常开发 + +​ 唯一的区别是需要注意RTT将**所有外设都定义为设备**,所以需要像(1.2.1)一样,先进行设备的查找`rt_device_find()`,才能正常进行后续的开发 + +```c +# 官方示例 +#define AHT10_I2C_BUS_NAME "i2c1" /* 传感器连接的I2C总线设备名称 */ +#define AHT10_ADDR 0x38 /* 从机地址 */ +struct rt_i2c_bus_device *i2c_bus; /* I2C总线设备句柄 */ + +/* 查找I2C总线设备,获取I2C总线设备句柄 */ +i2c_bus = (struct rt_i2c_bus_device *)rt_device_find(name); + +/* 读传感器寄存器数据 */ +static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf) +{ + struct rt_i2c_msg msgs; + + msgs.addr = AHT10_ADDR; /* 从机地址 */ + msgs.flags = RT_I2C_RD; /* 读标志 */ + msgs.buf = buf; /* 读写数据缓冲区指针 */ + msgs.len = len; /* 读写数据字节数 */ + + /* 调用I2C设备接口传输数据 */ + if (rt_i2c_transfer(bus, &msgs, 1) == 1) + { + return RT_EOK; + } + else + { + return -RT_ERROR; + } +} +``` + +## 2.2 SPI总线驱动开发 + +​ SPI相较于I2C更为特殊的是,在 RT-Thread 中,SPI 设备分为 “**SPI 总线**”和“ **SPI 设备**”两大类,**SPI 总线对应 SPI 控制器**,**SPI 设备对应不同 CS 连接的从设备**,使用前需要先注册 SPI 总线,再把从设备挂载到总线上。 + +​ SPI总线对应: + +> MCU内部的SPI控制器。 + +​ SPI设备对应: + +> 挂载在SPI总线上的具体从设备。 + +SPI设备使用流程: + +​ ① 注册SPI总线 + +​ ② 创建SPI设备对象 + +​ ③ attach到SPI总线 + +​ ④ 配置SPI参数 + +​ ⑤ find获取设备 + +​ ⑥ SPI数据通信 + +```c +/* + * 程序清单:这是一个 SPI 设备使用例程 + * 例程导出了 spi_w25q_sample 命令到控制终端 + * 命令调用格式:spi_w25q_sample spi10 + * 命令解释:命令第二个参数是要使用的SPI设备名称,为空则使用默认的SPI设备 + * 程序功能:通过SPI设备读取 w25q 的 ID 数据 +*/ + +#include +#include + +#define W25Q_SPI_DEVICE_NAME "qspi10" + +static void spi_w25q_sample(int argc, char *argv[]) +{ + struct rt_spi_device *spi_dev_w25q; + char name[RT_NAME_MAX]; + rt_uint8_t w25x_read_id = 0x90; + rt_uint8_t id[5] = {0}; + + if (argc == 2) + { + rt_strncpy(name, argv[1], RT_NAME_MAX); + } + else + { + rt_strncpy(name, W25Q_SPI_DEVICE_NAME, RT_NAME_MAX); + } + + /* 查找 spi 设备获取设备句柄 */ + spi_dev_w25q = (struct rt_spi_device *)rt_device_find(name); + if (!spi_dev_w25q) + { + rt_kprintf("spi sample run failed! can't find %s device!\n", name); + } + else + { + /* 方式1:使用 rt_spi_send_then_recv()发送命令读取ID */ + rt_spi_send_then_recv(spi_dev_w25q, &w25x_read_id, 1, id, 5); + rt_kprintf("use rt_spi_send_then_recv() read w25q ID is:%x%x\n", id[3], id[4]); + + /* 方式2:使用 rt_spi_transfer_message()发送命令读取ID */ + 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); + rt_kprintf("use rt_spi_transfer_message() read w25q ID is:%x%x\n", id[3], id[4]); + + } +} +/* 导出到 msh 命令列表中 */ +MSH_CMD_EXPORT(spi_w25q_sample, spi w25q sample); +``` +