[RT-Thread 设备驱动开发] I/O抽象层+总线层+驱动层+硬件层
总结型笔记,内容会不断扩充。
一、框架介绍
问题提出
实际项目开发中,我们往往会进行不同的硬件尝试来看是否满足业务功能需求,如WIFI、FLASH等芯片的更换甚至可能是主控MCU的更换。但由于同一功能不同品牌的硬件之间使用差异性较大,需要查阅用户使用手册进行重新编码实现相关接口,同时应用层业务代码也需要进行接口修改。
当你终于更改完了,操蛋的leader说还是换回原有硬件方案吧,但此时你又在此基础上开发了新的业务代码。于是你又要吭哧吭哧的把新硬件平台上的新业务代码拷贝到旧硬件平台上并做一些接口的修改,捣腾两次这种无趣且又需要仔细的工作后,好了你人没了 ! ! !
基于以上问题,提出几个想法:
- 更换新硬件平台时,可不可以不用去查阅用户使用手册实现接口? ->
事先有人提你实现好
- 更换新硬件平台时,可不可以不做应用层业务代码的修改? ->
调用统一的抽象接口
- 更换回旧硬件平台时,可不可以一键进行切换? ->
引入"总线"替我们进行管理
思考一下上面几个问题,我们可以发现本质上是因为我们的业务代码和底层硬件代码是直接依赖的、强耦合的,在程序设计中我们常用的解耦方法有:使用抽象层(抽象数据结构和函数接口、模块化编程)、模块间通信(回调函数、消息传递)、依赖注入(通过参数传递所需的依赖)、接口设计(避免直接访问对方的内部实现)等。
解决方案
-
问题1解决方案,
设备驱动代码
以模块化的方式进行开发,使用者则不需要对内部实现进行探究。 -
问题3解决方案,引入"总线"(在RT-Thread中称为
设备驱动框架层
)将程序分离为设备驱动抽象层(DAL)与硬件抽象层(HAL),使用不同硬件平台时,只需要注册不同硬件平台(STM32、LPC)来与某一设备驱动代码进行匹配。
至此前面三个问题其实都能得到解决了,在总线这一抽象层
可以提供统一的接口
来对硬件进行操作。但是还不够优雅,除了spi还有iic、uart、can等系列总线,对于大型项目只开发应用层代码的人,他学起来还是很困难太多了,再给我抽象一下也就是RT-Thread中的I/O设备管理层
。
需要注意的是,对于简单设备会省略设备驱动框架层这一步骤,而是直接向上层I/O设备管理层进行注册,这样的坏处是不利于统一管理,对应Linux就是不使用platform框架进行字符设备开发。
二、设备驱动开发
API接口函数
- 以看门狗设备开发为例,下图为实际开发序列图:
底层部分
- 设备节点创建、销毁
rt_device_t rt_device_create(int type, int attach_size); // attach_size 用户数据大小
void rt_device_destroy(rt_device_t device);
/*--------------------------------------type-----------------------------------------*/
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 /* 声音设备 */
RT_Device_Class_Graphic /* 图形设备 */
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 /* 杂类设备 */
/*-----------------------------------------------------------------------------------*/
typedef struct rt_device *rt_device_t;
struct rt_device {
...
#ifdef RT_USING_DEVICE_OPS
const struct rt_device_ops *ops;
#else
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);
#endif
...};
- 设备驱动代码注册与取消
rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags);
rt_err_t rt_device_unregister(rt_device_t dev);
/*--------------------------------------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_t->ops------------------------------------*/
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_size_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
rt_size_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);
};
应用层部分
- 访问I/O设备与数据收发回调
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);
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));
/*-------------------------------------oflags----------------------------------------*/
#define RT_DEVICE_OFLAG_CLOSE 0x000 /* 设备已经关闭(内部使用)*/
#define RT_DEVICE_OFLAG_RDONLY 0x001 /* 以只读方式打开设备 */
#define RT_DEVICE_OFLAG_WRONLY 0x002 /* 以只写方式打开设备 */
#define RT_DEVICE_OFLAG_RDWR 0x003 /* 以读写方式打开设备 */
#define RT_DEVICE_OFLAG_OPEN 0x008 /* 设备已经打开(内部使用)*/
#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 发送模式打开 */
/*--------------------------------------cmd------------------------------------------*/
#define RT_DEVICE_CTRL_RESUME 0x01 /* 恢复设备 */
#define RT_DEVICE_CTRL_SUSPEND 0x02 /* 挂起设备 */
#define RT_DEVICE_CTRL_CONFIG 0x03 /* 配置设备 */
#define RT_DEVICE_CTRL_SET_INT 0x10 /* 设置中断 */
#define RT_DEVICE_CTRL_CLR_INT 0x11 /* 清中断 */
#define RT_DEVICE_CTRL_GET_INT 0x12 /* 获取中断状态 */
/* 当硬件设备收到数据时,可以通过如下函数回调另一个函数来设置数据接收指示,通知上层应用线程有数据到达。 */
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));
驱动快速使用
如何从零编写一个自己的驱动?
- 打开ulog组件
- 新建
libraries\HAL_Drivers\drivers\drv_test.c
,相当于Linux中的module_init
#include <rtthread.h>
#include <rtdevice.h>
// #define DRV_DEBUG
#define LOG_TAG "drv.test"
#include <drv_log.h>
static rt_err_t dev_test_init(rt_device_t dev) {
LOG_I("test dev init");
return RT_EOK;
}
static rt_err_t dev_test_open(rt_device_t dev, rt_uint16_t oflag) {
LOG_I("test dev open flag = %d", oflag);
return RT_EOK;
}
static rt_err_t dev_test_close(rt_device_t dev) {
LOG_I("test dev close");
return RT_EOK;
}
static rt_ssize_t dev_test_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) {
LOG_I("tese dev read pos = %d, size = %d", pos, size);
return RT_EOK;
}
static rt_ssize_t drv_test_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) {
LOG_I("tese dev write pos = %d, size = %d", pos, size);
return RT_EOK;
}
static rt_err_t drv_test_control(rt_device_t dev, int cmd, void *args) {
LOG_I("test dev 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 dev create failed.");