rt-thread设备驱动模型-看门狗驱动
1. 回顾
前一章节将rt-thread设备驱动框架的实现原理做了介绍,框架大体上可以划分成三个层次,分别为应用层、核心层和驱动相关层。
- 应用层会调用驱动框架提供的标准设备操作接口对设备进行操作。
- 核心层主要是用于统一管理注册到内核中的设备,内核中对设备进行了抽象,用结构体
rt_device
来描述,这个结构体包含一个设备内核对象struct rt_object parent
,每个设备都会通过这个设备内核对象挂载到内核的设备信息链表上,从而对设备形成统一管理。另外,rt_device
还包含一系列的设备操作函数,这些操作函数与应用接口一一对应,需要驱动开发人员实现这些操作函数。 - 驱动相关层就是需要实现的设备驱动程序,主要就是定义
rt_device
设备对象,然后现实设备操作函数,最后调用rt_device_register
函数注册设备对象。
下图为整个设备驱动的整体框架:
根据这个图,设备驱动开发和应用程序调用流程应该很清晰。前面文章都是介绍驱动框架的实现逻辑,下面将通过看门狗驱动来进行加深。
2. 看门狗的使用
看门狗的作用就是在程序跑飞或者异常的时候能够主动复位。看门狗本质上可以认为是一个递减的定时器,当计时到达的时候就会产生复位动作,喂狗的作用就是通过重置这个定时器让看们狗不能计时到达。程序正常运行就会正常喂狗,就不会产生复位,若是程序异常执行没能及时喂狗就会触发复位动作。
rt-thread中对看门狗的使用流程如下:
#define IWDG_DEVICE_NAME "wdt"
static rt_device_t wdg_dev;
rt_uint32_t wdg_timeout = 5; /*s*/
/* 1. 查找设备 */
wdg_dev = rt_device_find(IWDG_DEVICE_NAME);
/* 2. 设置看门狗溢出时间 */
ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &wdg_timeout);
/* 3. 设置空闲线程钩子函数*/
rt_thread_idle_sethook(idle_hook);
/* 4. 启动看门狗 */
ret = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_START, NULL);
/*------------------------------------------------------------*/
/* 空闲钩子函数 */
static void idle_hook(void) {
/*对看门狗进行喂狗*/
rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL);
}
3. 看门狗驱动框架
设备会用一个结构体进行抽象,rt-thread对看门狗也进行了抽象,使用struct rt_watchdog_device
进行描述:
struct rt_watchdog_device
{
struct rt_device parent;
const struct rt_watchdog_ops *ops;
};
该结构体包含了一个rt_device
,其作用就是与前文介绍的设备驱动框架进行挂接。rt_watchdog_ops
是定义的看门狗操作函数,需要驱动开发者进行实现。
struct rt_watchdog_ops
{
rt_err_t (*init)(rt_watchdog_t *wdt);
rt_err_t (*control)(rt_watchdog_t *wdt, int cmd, void *arg);
};
看到这里会引出一些问题:
rt_device
中的rt_device_ops
成员在哪儿赋值的?rt_device
中的rt_device_ops
成员与rt_watchdog_ops
是怎么关联的?
应用调用的标准设备操作接口,是与rt_device
中的rt_device_ops
中的函数一一对应的,所以第二个问题应该是rt_device_ops
中的函数调用了rt_watchdog_ops
中的函数。
stm32平台对看门狗设备使用struct stm32_wdt_obj
进行描述。
struct stm32_wdt_obj
{
rt_watchdog_t watchdog;
IWDG_HandleTypeDef hiwdg;
rt_uint16_t is_start;
};
typedef struct rt_watchdog_device rt_watchdog_t;
可见,stm32_wdt_obj
包含了rt_watchdog_device
,其他两个成员不是关心的重点。在系统启动的时候会对看门狗设备进行初始化,流程如下:
rt_wdt_init
1. ops.init = &wdt_init;
2. ops.control = &wdt_control;
3. stm32_wdt.watchdog.ops = &ops;
4. rt_hw_watchdog_register(&stm32_wdt.watchdog,"wdt",RT_DEVICE_FLAG_DEACTIVATE, RT_NULL)
在看门狗初始化函数中主要就是对rt_watchdog_ops
中的函数赋值,即wdt_init
和wdt_control
,这两个函数就是stm32看门狗硬件初始化函数和控制函数,最终就是调用stm32的标准库函数。如下:
rt_err_t wdt_control(rt_watchdog_t *wdt, int cmd, void *arg)
{
switch (cmd)
{
/* feed the watchdog */
case RT_DEVICE_CTRL_WDT_KEEPALIVE:
break;
/* set watchdog timeout */
case RT_DEVICE_CTRL_WDT_SET_TIMEOUT:
HAL_IWDG_Init(&stm32_wdt.hiwdg)
break;
case RT_DEVICE_CTRL_WDT_GET_TIMEOUT:
break;
case RT_DEVICE_CTRL_WDT_START:
HAL_IWDG_Init(&stm32_wdt.hiwdg)
default:
LOG_W("This command is not supported.");
return -RT_ERROR;
}
return RT_EOK;
}
上述函数就是stm32看门狗的控制函数,为了减少篇幅进行了大量删减,可以看到最终调用的就是HAL_IWDG_Init
,这就很熟悉了吧。
在rt_wdt_init
的最后一步就是调用rt_hw_watchdog_register
对rt_watchdog_device
进行注册。看到这是不是觉得驱动开发流程还是挺简单,就是定义一个设备对象,然后填充相关的设备操作函数,最后调用接口进行注册。来看看rt_hw_watchdog_register
做了什么。
rt_hw_watchdog_register
struct rt_device *device;
1. device = &(wtd->parent);
2. device->init = rt_watchdog_init;
device->open = rt_watchdog_open;
device->close = rt_watchdog_close;
device->read = RT_NULL;
device->write = RT_NULL;
device->control = rt_watchdog_control;
3. rt_device_register
rt_hw_watchdog_register
函数首先做的就是得到rt_watchdog_device
中包含的rt_device
,然后初始化rt_device
中的rt_deice_ops
的函数,这些函数都是跟应用调用接口一一对应的。最后就是调用rt_device_register
将rt_device
注册到内核中,这个函数是上一篇文章着重讲的,在此不做介绍了。
前文说过应用设备操作接口与rt_device_ops
中的函数一一对应,在rt_device_ops
的函数中又会调用驱动开发者实现的rt_watchdog_ops
中的函数,看看rt_watchdog_control
是不是这样调用的。
static rt_err_t rt_watchdog_control(struct rt_device *dev,
int cmd,
void *args)
{
rt_watchdog_t *wtd;
RT_ASSERT(dev != RT_NULL);
wtd = (rt_watchdog_t *)dev;
return (wtd->ops->control(wtd, cmd, args));
}
可以看到,这个函数的内部果然是调用的rt_watchdog_device
中的rt_watchdog_ops
的control
函数。至此,这个驱动的开发流程和调用流程就清楚了,使用下图作为总结。