赞
踩
前一章节将rt-thread设备驱动框架的实现原理做了介绍,框架大体上可以划分成三个层次,分别为应用层、核心层和驱动相关层。
rt_device
来描述,这个结构体包含一个设备内核对象struct rt_object parent
,每个设备都会通过这个设备内核对象挂载到内核的设备信息链表上,从而对设备形成统一管理。另外,rt_device
还包含一系列的设备操作函数,这些操作函数与应用接口一一对应,需要驱动开发人员实现这些操作函数。rt_device
设备对象,然后现实设备操作函数,最后调用rt_device_register
函数注册设备对象。下图为整个设备驱动的整体框架:
根据这个图,设备驱动开发和应用程序调用流程应该很清晰。前面文章都是介绍驱动框架的实现逻辑,下面将通过看门狗驱动来进行加深。
看门狗的作用就是在程序跑飞或者异常的时候能够主动复位。看门狗本质上可以认为是一个递减的定时器,当计时到达的时候就会产生复位动作,喂狗的作用就是通过重置这个定时器让看们狗不能计时到达。程序正常运行就会正常喂狗,就不会产生复位,若是程序异常执行没能及时喂狗就会触发复位动作。
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); }
设备会用一个结构体进行抽象,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
函数。至此,这个驱动的开发流程和调用流程就清楚了,使用下图作为总结。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。