1.回顾

前一章节将rt-thread设备驱动框架的实现原理做了介绍,框架大体上可以划分成三个层次,分别为应用层、核心层和驱动相关层。

应用层会调用驱动框架提供的标准设备操作接口对设备进行操作。 核心层主要是用于统一管理注册到内核中的设备,内核中对设备进行了抽象,用结构体rt_device来描述,这个结构体包含一个设备内核对象struct rt_object parent,每个设备都会通过这个设备内核对象挂载到内核的设备信息链表上,从而对设备形成统一管理。另外,rt_device还包含一系列的设备操作函数,这些操作函数与应用接口一一对应,需要驱动开发人员实现这些操作函数。 驱动相关层就是需要实现的设备驱动程序,主要就是定义rt_device设备对象,然后现实设备操作函数,最后调用rt_device_register函数注册设备对象。

下图为整个设备驱动的整体框架: Image

根据这个图,设备驱动开发和应用程序调用流程应该很清晰。前面文章都是介绍驱动框架的实现逻辑,下面将通过看门狗驱动来进行加深。

2. 看门狗的使用

看门狗的作用就是在程序跑飞或者异常的时候能够主动复位。看门狗本质上可以认为是一个递减的定时器,当计时到达的时候就会产生复位动作,喂狗的作用就是通过重置这个定时器让看们狗不能计时到达。程序正常运行就会正常喂狗,就不会产生复位,若是程序异常执行没能及时喂狗就会触发复位动作。

rt-thread中对看门狗的使用流程如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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 进行描述:

1
2
3
4
5
struct rt_watchdog_device
{
    struct rt_device parent;
    const struct rt_watchdog_ops *ops;
};

该结构体包含了一个rt_device,其作用就是与前文介绍的设备驱动框架进行挂接。rt_watchdog_ops是定义的看门狗操作函数,需要驱动开发者进行实现。

1
2
3
4
5
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进行描述。

1
2
3
4
5
6
7
8
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,其他两个成员不是关心的重点。在系统启动的时候会对看门狗设备进行初始化,流程如下:

1
2
3
4
5
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的标准库函数。如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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做了什么。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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是不是这样调用的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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函数。至此,这个驱动的开发流程和调用流程就清楚了,使用下图作为总结。

Image