I/O 设备模型框架
RT-Thread 提供了一套简单的 I/O 设备模型框架,如下图所示,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层。
应用程序通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互。
设备驱动框架层是对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。
设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备,对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中。
对于另一些设备,如看门狗等,则会将创建的设备实例先注册到对应的设备驱动框架中,再由设备驱动框架向 I/O 设备管理器进行注册,主要有以下几点:看门狗设备驱动程序根据看门狗设备模型定义,创建出具备硬件访问能力的看门狗设备实例,并将该看门狗设备通过rt_hw_watchdog_register() 接口注册到看门狗设备驱动框架中。
看门狗设备驱动框架通过 rt_device_register() 接口将看门狗设备注册到 I/O 设备管理器中。
应用程序通过 I/O 设备管理接口来访问看门狗设备硬件。
IO设备模型
RT-Thread 的设备模型是建立在内核对象模型基础之上的,设备被认为是一类对象,被纳入对象管理器的范畴。每个设备对象都是由基对象派生而来,每个具体设备都可以继承其父类对象的属性,并派生出其私有属性,下图是设备对象的继承和派生关系示意图。
struct rt_device
{
struct rt_object parent; /* 内核对象基类 /
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;
};
typedef struct rt_device *rt_device_t;
I/O 设备类型
RT-Thread 支持多种 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 / 声音设备 /
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 / 杂类设备 */
创建和注册 I/O 设备
驱动层负责创建设备实例,并注册到 I/O 设备管理器中,可以通过静态申明的方式创建设备实例,也可以用下面的接口进行动态创建:
rt_device_t rt_device_create(int type, int attach_size);
参数 描述
type 设备类型,可取前面小节列出的设备类型值
attach_size 用户数据大小
返回 ——
设备句柄 创建成功
RT_NULL 创建失败,动态内存分配失败
调用该接口时,系统会从动态堆内存中分配一个设备控制块,大小为 struct rt_device 和 attach_size 的和,设备的类型由参数 type 设定。设备被创建后,需要实现它访问硬件的操作方法。
struct rt_device_ops
{
/* common device interface */
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_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_device_find() 口查找到看门狗设备,获得设备句柄,然后通过 rt_device_init() 口初始化设备,通过 rt_device_control() 口设置看门狗设备溢出时间。
#include <rtthread.h>
#include <rtdevice.h>
#define IWDG_DEVICE_NAME “iwg”
static rt_device_t wdg_dev;
static void idle_hook(void)
{
/* 在空闲线程的回调函数里喂狗 */
rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
rt_kprintf("feed the dog!\n ");
}
int main(void)
{
rt_err_t res = RT_EOK;
rt_uint32_t timeout = 1000; /* 溢出时间 */
/* 根据设备名称查找看门狗设备,获取设备句柄 */
wdg_dev = rt_device_find(IWDG_DEVICE_NAME);
if (!wdg_dev)
{
rt_kprintf("find %s failed!\n", IWDG_DEVICE_NAME);
return RT_ERROR;
}
/* 初始化设备 */
res = rt_device_init(wdg_dev);
if (res != RT_EOK)
{
rt_kprintf("initialize %s failed!\n", IWDG_DEVICE_NAME);
return res;
}
/* 设置看门狗溢出时间 */
res = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout);
if (res != RT_EOK)
{
rt_kprintf("set %s timeout failed!\n", IWDG_DEVICE_NAME);
return res;
}
/* 设置空闲线程回调函数 */
rt_thread_idle_sethook(idle_hook);
return res;
}