设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备,对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中,使用序列图如下图所示,主要有以下 2 点:
-
设备驱动根据设备模型定义,创建出具备硬件访问能力的设备实例,将该设备通过
rt_device_register()
接口注册到 I/O 设备管理器中。 -
应用程序通过
rt_device_find()
接口查找到设备,然后使用 I/O 设备管理接口来访问硬件。
设备创建:
rt_device_t rt_device_create(int type, int attach_size);
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 /* 杂类设备 */
attach_size | 用户数据大小 |
返回值:
设备句柄 | 创建成功 |
RT_NULL | 创建失败,动态内存分配失败 |
销毁设备,释放内存
void rt_device_destroy(rt_device_t device);
设备被创建后,需要注册到 I/O 设备管理器中,应用程序才能够访问,注册设备的函数如下所示:
rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags);
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_err_t rt_device_unregister(rt_device_t dev);
访问I/O设备
应用程序通过 I/O 设备管理接口来访问硬件设备,当设备驱动实现后,应用程序就可以访问该硬件。I/O 设备管理接口与 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);
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 发送模式打开 */
关闭设备
应用程序打开设备完成读写等操作后,如果不需要再对设备进行操作则可以关闭设备,通过如下函数完成:
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);
参数 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_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size);
参数 | 描述 |
---|---|
dev | 设备句柄 |
pos | 读取数据偏移量 |
buffer | 内存缓冲区指针,读取的数据将会被保存在缓冲区中 |
size | 读取数据的大小 |
返回 | —— |
读到数据的实际大小 | 如果是字符设备,返回大小以字节为单位,如果是块设备,返回的大小以块为单位 |
0 | 需要读取当前线程的 errno 来判断错误状态 |
调用这个函数,会从 dev 设备中读取数据,并存放在 buffer 缓冲区中,这个缓冲区的最大长度是 size,pos 根据不同的设备类别有不同的意义。
向设备中写入数据,可以通过如下函数完成:
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size);
复制错误复制成功
参数 | 描述 |
---|---|
dev | 设备句柄 |
pos | 写入数据偏移量 |
buffer | 内存缓冲区指针,放置要写入的数据 |
size | 写入数据的大小 |
返回 | —— |
写入数据的实际大小 | 如果是字符设备,返回大小以字节为单位;如果是块设备,返回的大小以块为单位 |
0 | 需要读取当前线程的 errno 来判断错误状态 |
调用这个函数,会把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的最大长度是 size,pos 根据不同的设备类别存在不同的意义。
创建一个简单的应用实例
在driver文件夹下创建drv_demo.c
#include <rtdevice.h>
#include <rtdbg.h>
#include <drv_log.h>
rt_device_t drv_demo;
/*
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);
*/
rt_err_t drv_demo_init(rt_device_t dev)
{
rt_kprintf("drv_demo_init\n");
return 0;
}
rt_err_t drv_demo_open(rt_device_t dev, rt_uint16_t oflag)
{
rt_kprintf("drv_demo_open\n");
return 0;
}
rt_err_t drv_demo_close(rt_device_t dev)
{
rt_kprintf("drv_demo_close\n");
return 0;
}
int rt_drv_demo_init()
{
rt_kprintf("rt_drv_demo_init...\n");
drv_demo = rt_device_create(RT_Device_Class_Char, 32);
if(drv_demo == RT_NULL){
LOG_E("rt_device_create falied...\n");
return RT_ENOMEM;
}
LOG_D("rt_device_create successed...\n");
drv_demo->init = drv_demo_init;
drv_demo->open = drv_demo_open;
drv_demo->close = drv_demo_close;
rt_device_register(drv_demo,"demo",RT_DEVICE_FLAG_RDWR );
return 0;
}
//板级初始化,在系统上电后自动调用该函数初始化
INIT_BOARD_EXPORT(rt_drv_demo_init);
在main.c中访问创建的设备
#include <rtthread.h>
#include <rtdevice.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
rt_device_t demo;
int main(void)
{
demo = rt_device_find("demo");//通过设备名字获取设备句柄
if(demo == RT_NULL){
LOG_E("rt_device_find failed...\n");
return RT_EEMPTY;
}
rt_device_init(demo);
rt_device_open(demo,RT_DEVICE_OFLAG_RDWR );
rt_device_close(demo);
return RT_EOK;
}
运行结果:
[2022-10-27_18:14:58:592]rt_drv_demo_init...
[2022-10-27_18:14:58:592] \ | /
[2022-10-27_18:14:58:592]- RT - Thread Operating System
[2022-10-27_18:14:58:592] / | \ 4.0.3 build Oct 24 2022
[2022-10-27_18:14:58:592] 2006 - 2020 Copyright by rt-thread team
[2022-10-27_18:14:58:592]drv_demo_init
[2022-10-27_18:14:58:592]drv_demo_open
[2022-10-27_18:14:58:592]drv_demo_close
[2022-10-27_18:14:58:592]msh >