探讨多任务下喂狗的方式

一直以来,我不断的探索RTOS的使用方法,以UC/OS-II为基础。当然努力的结果是逐渐形成了一个可以使用的软件平台。但我意识到如果没有相应文档的支持或许这个东西也许只有我自己能使用了。另外提高的空间有限。所以今天就从这个困扰我许久的问题入手,慢慢的介绍我这两年来努力的成果。
关于多任务下如何才能有效的使用看门狗这个问题其实也不是很难。关键是在于喂狗的策略。你没办法像以前一样。在程序的开头初始化看门狗,然后在程序中的某一点调用喂狗指令,清除看门狗计数器。因为你要明白,你想要清楚的知道每一个任务都必需有条不紊的工作的。任何一个任务死掉就得停止喂狗。这就是问题的关键了。所以你不可以在每个任务里直接调用喂狗指令。那是混乱的没有实际意义。
那么不能直接喂狗,自然就想到间接喂狗的方式了。如何才能做到间接喂狗。我使用了软件看门狗的方式。给每个任务一个软件模拟出来的看门狗计数器。然后在适当的时候定时的查询每一个软件看门狗的喂狗情况,由此决定是否调用喂狗指令,清除看门狗计数器。如果发现有任何一个软件看门狗溢出,就停止喂狗,让系统复位。


如何定时查询软件看门狗的喂狗情况? UC/OS-II系统时间节拍钩子函数是个不错的选择。随后再给出具有体的实现。
void OSTimeTickHook (void)
{
 

      SoftWdtISR();         //定时查询每一个软件看门狗喂狗状况。
}
软件看门狗是一个什么样的东西??从数据结构上来说,它是这样子的:

typedef struct soft_wach_dog_timer{
        uint16 watchDogTimeOut;                //看门狗计数超时初值
        uint16 watchDogTime;                //看门狗定时器
        uint8    watchDogCountBack;        //上一次看门喂狗计数器
        uint8    watchDogCount;                //看门狗喂狗计数器
        SWDT_STAT    watchDogState;        //看门狗定时器状态
        uint8    NOUSE8;
}SOFT_WATCH_DOG_TIMER;
好吧,我来一个个介绍,才几个变量而以,很容易的。
先讲第二个成员吧,uint16 watchDogTime; 这个就是软件看门狗的定时器了,每当调用SoftWdtISR的时候如果相应看门狗没有喂狗操作该值会被减去1,否则将从第一个成员uint16 watchDogTimeOut复制一份拷贝。
所以第一个成员变量叫做看门狗计数超时初值。
那么第三个和第四个成员变量是干吗用的呢,这两个就是用来判断是否有喂狗操作。
第五个成员是表示软件看门狗状态的。总共有三种状态。
typedef enum{
        SWDT_STAT_IDLE,                                //软件看门狗空闲
        SWDT_STAT_SUSPEN,                        //软件看门狗挂起
        SWDT_STAT_RUN                                //软件看门狗运行
}SWDT_STAT;

接下来看看SoftWdtISR的实现吧。
void SoftWdtISR(void){

        SOFT_WATCH_DOG_TIMER *SoftWatchDogTimerPtr = SoftWatchDogTimerList;
        uint8 i = 0;
       
        if(StopWDTFedMake == TRUE){
                return;
        }
        for(i=0; i<MAX_SWDT_ID; i++){
                //对挂起和空闲的看门狗定时器不进行检查获
                if(SoftWatchDogTimerPtr->watchDogState == SWDT_STAT_RUN){
                        //软件看门狗有喂食,重装看门狗计数上限
                        if(SoftWatchDogTimerPtr->watchDogCount != SoftWatchDogTimerPtr->watchDogCountBack){
                               
                            SoftWatchDogTimerPtr->watchDogCountBack = SoftWatchDogTimerPtr->watchDogCount;
                            SoftWatchDogTimerPtr->watchDogTime = SoftWatchDogTimerPtr->watchDogTimeOut;
                        }else if(--SoftWatchDogTimerPtr->watchDogTime == 0){ //没有喂狗,看门狗计时器减一操作
                            //其中任一个运行着的看门狗超时停止喂硬件看门狗
                            StopWDTFedMake = TRUE;
                            return;
                        }
                }
                SoftWatchDogTimerPtr++;
        }       
        //调用硬件喂狗
        FeedDog();
}
SoftWdtISR函数只对状态为运行的软件看门狗定时器进行检查,每次都要判断watchDogCountBack 是否等于watchDogCount,相等就表示该软件看门狗没有调用喂狗操作,然后执行软件看门狗计数watchDogTime值减一操作。如果不相等就把watchDogCount的值赋给watchDogCountBack,。并且把watchDogTimeOut的值赋给watchDogTime,清除看门狗计数器。
当watchDogTime值为0,表示软件看门狗溢出,需要复位系统。

这有些曲折和复杂了,以后会改进。到时会去掉watchDogCountBack 、watchDogCount,这两个成员。


那么该如何使用看门狗呢,我们要知道总共有几个任务,为每个任务分配一个软件看门狗,为每个看门狗分配一个ID。然后要清空所有软件看门狗和初始化硬件看门狗:
typedef enum{//往这里添加软件看门狗ID
        KEY_TASK_SWDT_ID,
        MP3_TASK_SWDT_ID,
        MAX_SWDT_ID
}SWDT_ID;
//软件看门狗定时器数组
SOFT_WATCH_DOG_TIMER SoftWatchDogTimerList[MAX_SWDT_ID] = {0};

void SoftWDTInit(void){

        OS_MEMSET(SoftWatchDogTimerList, 0, sizeof(SOFT_WATCH_DOG_TIMER)*MAX_SWDT_ID);
        StopWDTFedMake = 0;
        //初始化硬件看门狗
        WatchDogInit(1000,1);
}
软件看门狗的使用:
Void KeyBoardTask(void *pdata){
        //任务开始的地方,初始化软件看门狗
    SoftWdtInit(KEY_TASK_SWDT_ID,2000);

    While(1){
        const DEV_FUN* Device = NULL;
        HAL_ERR_CODE err;
        uint8 key_value[2] = {0};
        uint8 data_addr = 0;
       
        SoftWdtFed(KEY_TASK_SWDT_ID);        //任务主循环中喂狗
        //这里要预示我下一篇文档的内容,关于如何实现一个硬件抽象层。
            Device = DeviceOpen(I2C0_ID,&err);       

        ……………………………..//以下代码省略。
        DeviceClose(&Device);
    }
}
首先就是在任务开始的地方调用SoftWdtInit(KEY_TASK_SWDT_ID,2000);设置软件看门狗溢出时间,单位mS.
然后在任务的主循环中调用SoftWdtFed(KEY_TASK_SWDT_ID);喂狗.
接下来看看软件看门狗初始化,和喂狗的实现。

软件看门狗的初始化:
//初始化软件看门狗
BOOL SoftWdtInit(SWDT_ID SwdtId, uint16 TimerTop){

SOFT_WATCH_DOG_TIMER *SoftWatchDogTimerPtr = SoftWatchDogTimerList;
        uint16 osTick = 0;

        if(SwdtId >= MAX_SWDT_ID){
                return 0;
        }
        if(SoftWatchDogTimerPtr->watchDogState == SWDT_STAT_IDLE){
            SoftWatchDogTimerPtr += SwdtId;
            osTick = MsToOSTicks(TimerTop);//将mS时间换算成系统时钟节拍
            SoftWatchDogTimerPtr->watchDogTimeOut = osTick;
            SoftWatchDogTimerPtr->watchDogTime = osTick;
            SoftWatchDogTimerPtr->watchDogState = SWDT_STAT_RUN;

            return 1;
        }else{
                    return 0;
        }
}
这里就没什么好说的,从软件看门狗定时器数组里,根据ID找到相应的软件看门狗,然后初始化watchDogTimeOut,置watchDogState状态为运行。


喂狗:
//软件看门狗喂食
void SoftWdtFed(SWDT_ID SwdtId){

        SOFT_WATCH_DOG_TIMER *SoftWatchDogTimerPtr = SoftWatchDogTimerList;
        if(SwdtId >= MAX_SWDT_ID){
                return;
        }
        SoftWatchDogTimerPtr += SwdtId;
        OS_ENTER_CRITICAL();
        SoftWatchDogTimerPtr->watchDogCount++;
        //保证这两个值使终不相等
        if(SoftWatchDogTimerPtr->watchDogCount ==
SoftWatchDogTimerPtr->watchDogCountBack){
                SoftWatchDogTimerPtr->watchDogCount++;
        }
        OS_EXIT_CRITICAL();
}
这个也很简单,根据ID找到相应的软件看门狗,然后执行SoftWatchDogTimerPtr->watchDogCount++;
并保证
SoftWatchDogTimerPtr->watchDogCount 不等于SoftWatchDogTimerPtr->watchDogCountBack
多余的部分:
之前说过watchDogCount 、watchDogCountBack这两个是多余的所以将来会改成:
void SoftWdtFed(SWDT_ID SwdtId){

        SOFT_WATCH_DOG_TIMER *SoftWatchDogTimerPtr = SoftWatchDogTimerList;

        if(SwdtId >= MAX_SWDT_ID){
                return;
        }
        SoftWatchDogTimerPtr += SwdtId;
        OS_ENTER_CRITICAL();
        SoftWatchDogTimerPtr->watchDogTime = SoftWatchDogTimerPtr->watchDogTimeOut;
        OS_EXIT_CRITICAL();
}
相应的SoftWdtISR也需要简化。
最后介绍两个函数:
void SuspenWdt(SWDT_ID SwdtId);        //挂起软件看门狗。
void RunWdt(SWDT_ID SwdtId);                //恢复运行软件看门狗。

注:各个任务的最大执行时间(该时间内必须喂软狗一次) < 硬件狗的定时器时间 (该时间内必须喂硬狗一次)
关键点:
各个任务的超时时间设定:设定为任务的最大指定执行时间。
硬件狗的定时器时间设定:监视任务的周期(1.5s)要小于硬件狗的定时器周期(2s)