应用程序通过I/O设备管理接口来访问硬件设备,当设备驱动实现后,应用程序就可以访问该硬件。
查找设备:应用程序根据设备名称获取设备句柄,进而操作设备。
获得设备句柄后,应用程序可使用如下函数对设备进行初始化操作:
rt_err_t rt_device_init(rt_device_t device);
当一个设备已经初始化成功后,调用这个接口将不再重复做初始化。
打开和关闭设备:
通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用接口初始化设备。通过如下函数打开设备:
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t 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_DEVICE_FLAG_INT_RX或者RT_DEVICE_FLAG_DMA_RX的方式打开设备,否则不会回调函数。
应用程序打开设备完成读写等操作后,如果不需要再对设备进行操作则可以关闭设备,通过如下函数完成:
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 *args);
#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));
该函数的回调函数由调用者提供。
当硬件设备接收到数据时,会回调这个函数并把收到的数据长度放在size参数中传递给上层,上层应用线程在收到指示后,从设备中读取数据。
在应用程序调用rt_device_write()写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用。可以通过如下函数设置设备发送完成指示,函数参数及返回值见:
rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
调用这个函数时,回调函数由调用者提供,当硬件设备发送完数据时,由驱动程序回调这个函数并把发送完成的数据块地址作为参数传递给上层。上层应用(线程)在收到指示时会根据发送 buffer 的情况,释放 buffer 内存块或将其作为下一个写数据的缓存。
首先通过rt_device_find()找到看门狗设备,获得设备句柄,然后通过rt_device_init()口初始化设备,通过rt_device_control()口设置看门狗设备溢出时间。
在C语言中,将一个变量前面加上“static”关键字的意义取决于变量的上下文和声明位置。
#include
#include
#define IWDG_DEVICE_NAME "wdt"
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 = 10; /* 溢出时间 */
/* 根据设备名称查找看门狗设备,获取设备句柄 */
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;
}
图中各类里的.c文件是各类对应的管理接口所在,比如设备基类rt_device的管理接口在device.c中。
图中设备驱动框架层有很多RT-Thread写好的类,图中只列出2类,其它类用“xxx”来表示,这些省略的类及其管理接口。
该图横向看是分层思想,纵向看是各类派生继承关系。从下到上不断抽象、屏蔽下层差异,体现了面向对象的抽象的思想。
子类受到父类的接口约束,子类各自实现父类提供的统一接口,又体现了面向接口编程的思想。
比如从驱动层到驱动框架层,由不同厂商的相同硬件模块创建了很多子类对象,然后对接到同一个父类接口上,多对一,体现面向对象的抽象的威力。
以串口设备为例,不管下层是STM32,GD32还是别的平台,只要都是串口设备,都对接到RT-Thread的串口设备类——如图所示,多个硬件对象对接同一个父类对象接口。
同理,从设备驱动框架层到IO设备管理接口层,又是多对一,又是再一次的屏蔽差异,再一次的抽象。
开发者只需开发驱动层即可,设备驱动框架层和I/O设备管理层RT-Thread已经写好了。