看门狗是Nuttx中实现的一种定时器,该定时器底层基于硬件定时器,通过设置硬件定时器的定时触发来驱动看门狗。看门狗为系统提供了精确定时执行回调任务的机制。
注册底层硬件定时器
注册硬件定时器
系统在启动初始化过程中,通过
void up_initialize(void)
函数调用
void up_initialize(void)
,在
up_initialize()
函数中初始化硬件定时器,作为watchdog的底层硬件定时器。这个定时器初始化模式为
oneshot
,即为一次性定时,而非周期性定时。
启动硬件定时器
在Nuttx中,启动硬件定时器开始oneshot定时通过函数
up_timer_start()
函数完成。该函数有一个参数ts,为定时时间。
int up_timer_start(FAR const struct timespec *ts)
{
return stm32_oneshot_start(&g_tickless.oneshot, stm32_oneshot_handler, NULL, ts);
}
看门狗定基于该定时器,每当一个看门狗任务定时时间到,该看门狗的服务函数开始执行。如果需要周期性地执行看门狗任务,那么可以在看门狗服务函数中重新调度该看门狗。
系统中可能注册了很多看门狗,当一个定时器时间到后,系统根据当前已注册的看门狗,计算出下一个定时时间间隔,重新调用
up_time_start()
函数。
看门狗
数据结构
- 系统为看门狗分配了一个基本的数据结构
数组,每一个wdogs_s可以用来初始化一个看门狗。struct wdog_s
static struct wdog_s g_wdpool[CONFIG_PREALLOC_WDOGS];
- 看门狗空闲资源单链表。该链表上的节点代表空闲的看门狗资源,系统分配看门狗的时候从该链表上取出一个空闲的wdgs_s,将其初始化,然后被链接到处于活动状态的看门狗链表上(
)g_wdactivelist
sq_queue_t g_wdfreelist;
- 活动状态的看门狗链表。当一个看门狗wdgs_s被分配后,其被链接到
链表上g_wdactivelist
sq_queue_t g_wdactivelist;
- 空闲看门狗资源个数
uint16_t g_wdnfree;
- struct wdg_s
struct wdog_s
{
FAR struct wdog_s *next; /* Support for singly linked lists. */
wdentry_t func; /* Function to execute when delay expires */
#ifdef CONFIG_PIC
FAR void *picbase; /* PIC base address */
#endif
int lag; /* Timer associated with the delay */
uint8_t flags; /* See WDOGF_* definitions above */
uint8_t argc; /* The number of parameters to pass */
wdparm_t parm[CONFIG_MAX_WDOGPARMS];
};
- 第一个成员为wdg_s指针,方便链表管理
- 第二个成员是该看门狗的服务函数指针。当看门狗定时时间到,该服务函数将被调用
- 第三个成员在使用了PIC之后才有意义,这里暂不解释
- 第四个成员是定时延迟时间
- 第五个成员记录该看门的一些信息,如该看门狗是从系统预先定义的看门狗资源池中分配的还是动态在内存中分配的。
- 最后两个成员记录了给看门服务函数的参数,一个代表参数个数,一个是参数的指针数组
看门狗初始化
void wd_initialize(void)
看门狗初始化函数比较简单,包括
- 链表头
和g_wdfreelist
的初始化g_wdactivelist
- 将所有的看门狗资源
挂载到wdog_s
上,以供后续分配看门狗g_wdfreelist
- 赋值
为g_wdnfree
CONFIG_PREALLOC_WDOGS
分配看门狗
WDOG_ID wd_create (void)
为了保证在中断函数中能够快速分配到看门狗,系统为中断预留看门狗资源。
对于普通函数分配看门狗,如果系统空闲看门狗资源充足(大于为中断预留的看门狗资源),那么系统从看门狗资源空闲链表上分配,否则,在内存中分配一个看门狗数据结构,并且标志flag位。
启动看门狗
int wd_start(WDOG_ID wdog, int32_t delay, wdentry_t wdentry, int argc, ...)
wd_start()
函数的一个参数为wdog ID,实质是
wd_create()
返回的看门狗指针。
第二个参数为看门狗的延迟时间
第三个参数为看门狗服务函数
后面的参数为看门口服务函数的输入参数
首先初始化看门狗的服务函数和服务函数参数,程序代码片段如下
/* Save the data in the watchdog structure */
wdog->func = wdentry; /* Function to execute when delay expires */
up_getpicbase(&wdog->picbase);
wdog->argc = argc;
va_start(ap, argc);
for (i = ; i < argc; i++)
{
wdog->parm[i] = va_arg(ap, wdparm_t);
}
#ifdef CONFIG_DEBUG_FEATURES
for (; i < CONFIG_MAX_WDOGPARMS; i++)
{
wdog->parm[i] = ;
}
#endif
va_end(ap);
调整delay时间,使得delay时间在看门狗可以操作的范围内
/* Calculate delay+, forcing the delay into a range that we can handle */
if (delay <= )
{
delay = ;
}
else if (++delay <= )
{
delay--;
}
如果看门狗的处于活动状态链表为空,那么将本次初始化的看门狗添加到活动状态链表头。如果非空,则需要计算其他看门狗的延迟时间,在链表中找到一个合适的位置,将本次初始化的看门狗添加到活动装填链表中。
if (g_wdactivelist.head == NULL)
{
sq_addlast((FAR sq_entry_t *)wdog, &g_wdactivelist);
}
这个函数有点长,重点分析一下其实现的逻辑。
进入 活动状态链表不为空的条件分支,首先找到第一个有效的定时节点,即定时时间还没有到时的节点。定时节点的值可以为负数,表示超过定时的时间。
注意: 链表上节点中存储的相对于前一个节点的相对延迟时间。
while ((now += curr->lag) < && curr->next)
{
prev = curr;
curr = curr->next;
}
其次找出定时时间超过新节点定时时间的节点B(如果存在)
/* Advance past shorter delays */
while (now <= delay && curr->next)
{
prev = curr;
curr = curr->next;
now += curr->lag;
}
如果找到定时时间超过该新节点A延迟时间的节点B,则更新节点A和节点B的相对延迟时间,并将该新节点A插入到节点B之前。
if (delay < now)
{
/* The relative delay time is smaller or equal to the current delay
* time, so decrement the current delay time by the new relative
* delay time.
*/
delay -= (now - curr->lag);
curr->lag -= delay;
/* Insert the new watchdog in the list */
if (curr == (FAR struct wdog_s *)g_wdactivelist.head)
{
/* Insert the watchdog at the head of the list */
sq_addfirst((FAR sq_entry_t *)wdog, &g_wdactivelist);
}
else
{
/* Insert the watchdog in mid- or end-of-queue */
sq_addafter((FAR sq_entry_t *)prev, (FAR sq_entry_t *)wdog,
&g_wdactivelist);
}
}
上面这段函数即为找到了一个后继节点B,使得新节点A的延迟时间小于节点B的延迟时间。
如果
delay < now
条件不成立,那么意味着新节点A的延迟时间比活动状态链表上最后一个节点的延迟时间长,那么将新节点A插入到活动状态链表的最后。
else
{
delay -= now;
if (!curr->next)
{
sq_addlast((FAR sq_entry_t *)wdog, &g_wdactivelist);
}
else
{
next = curr->next;
next->lag -= delay;
sq_addafter((FAR sq_entry_t *)curr, (FAR sq_entry_t *)wdog,
&g_wdactivelist);
}
}
上面片段中else分支,我有些不理解,希望有人指点。
至此,新的看门狗被启动了,等到该看门狗的定时时间到达,它的服务函数就被调用。