以下轉載自安富萊電子: http://forum.armfly.com/forum.php
通過前面的幾個章節,我們基本已經完成了 FreeRTOS 所有功能的講解,本章節為大家介紹一種使用
獨立看門狗監測任務執行狀態的方法,借此為大家提供一種在軟件或者硬件死機時,FreeRTOS 系統如何
保證系統復位的思路。
什么是獨立看門狗
假設有一只飢餓的狗正在看守一座房子,而有人要闖入。 如果這個強盜的同謀以 2 分鍾的時間間隔不
停的向看門狗扔肉。 那么這只狗將忙於吃肉而忽視保衛工作,因此將不會犬叫。 然而,如果同謀扔完了肉
或者由於其它原因忘了喂肉,狗將開始犬叫,從而驚動鄰居、 房屋主人或者警察。
嵌入式化的獨立看門狗定時器遵循同樣的方法。 用戶需要每隔一段時間就刷新看門狗定時器,否則將
導致看門狗定時器溢出。 在大多數情況下,看門狗定時器的溢出將使得系統復位。 即使經過仔細規划和設
計,嵌入式系統也有可能由於出乎預料的問題而死機,看門狗定時器就是用來處理類似情況的,看門狗定
時器可用於從這種狀態恢復。
教程使用的 STM32F103,STM32F407 和 STM32F429 都自帶獨立看門狗,使用也比較簡單,用戶
初始化好看門狗,並設置好看門狗溢出時間即可,剩下就是在溢出時間范圍內及時喂狗。
下面就提供一種利用獨立看門狗監測多任務的執行狀態的思路。
多任務監測實現思路
為了保證 FreeRTOS 的所有用戶任務都在正常的運行,我們通過獨立看門狗的形式來監測,一旦發現
有某個任務長時間沒有執行,看門狗就會將系統復位。
運行條件:
創建 5 個用戶任務 Task1,Task2,Task3,Task4 和 Task5。 其中 Task5 的優先級最高,然后依次
是 Task4,Task3,Task2,Task1。
任務 Task1 到 Task4 定期發事件標志給任務 Task5,表示任務運行正常。
實現思路:
喂狗程序放在最高優先級的任務 Task5 里面,其它的 4 個任務都定期的向最高優先級任務發送事件標
志,只有四個任務都發來了事件標志才進行喂狗。
看門狗的復位時間設置為多少合適呢?這個要根據四個任務 Task1 到 Task4 的最大發送事件標志間隔
來確定。 假設測試發現,最大的發送事件標志時間間隔是由 Task4 產生的,間隔是 6s,我們可以在
此基礎上再設置一些時間容限,把看門狗的復位時間設置為 10s,也就是說,四個任務 Task1 到 Task4
需要在 10s 內給任務 Task5 發送事件標志,讓 Task5 執行喂狗操作,否則看門狗定時器溢出,從而導
致系統將復位。
推薦在最高優先級任務里面實現喂狗,這樣才可以保證其它低優先級任務發來了事件標志后,Task5
可以及時的喂狗。 如果放在一個低優先級的任務里面會存在問題,比如所有的任務都已經發送了表示
自己正常運行的事件標志,但是此低優先級任務在執行喂狗程序前被其它高優先級的任務搶占了,造
成不能及時喂狗,從而導致系統復位,這種誤判斷會使得系統不能夠正常工作。
按照上面的實現思路,我們在開發板上面實戰演練下。
代碼練兵場:
首先設置4個任務,其中一個任務使用按鍵控制阻塞20s,當按鍵不按下的時候,其余任務正常發送事件消息給優先級最高的任務,如果4s內有任何任務沒有給接收事件的任務發送消息,將會導致喂狗失敗而系統復位。
創建事件組:
static void AppObjCreate (void) { /* 創建事件標志組 */ xCreatedEventGroup = xEventGroupCreate(); if(xCreatedEventGroup == NULL) { /* 沒有創建成功,用戶可以在這里加入創建失敗的處理機制 */ printf("create event failure!\n"); } }
所有任務創建函數:
static void AppTaskCreate(void) { xTaskCreate(vTaskWork, /* 任務函數 */ "vTaskWork", /* 任務名 */ 512, /* 任務棧大小,單位word,也就是4字節 */ NULL, /* 任務參數 */ 1, /* 任務優先級*/ &xHandleTaskWork ); /* 任務句柄 */ xTaskCreate( vTaskLed1, /* 任務函數 */ "vTaskLed1", /* 任務名 */ 512, /* 任務棧大小,單位word,也就是4字節 */ NULL, /* 任務參數 */ 2, /* 任務優先級*/ &xHandleTaskLED1); /* 任務句柄 */ xTaskCreate( vTaskBeep, /* 任務函數 */ "vTaskBeep", /* 任務名 */ 512, /* 任務棧大小,單位word,也就是4字節 */ NULL, /* 任務參數 */ 3, /* 任務優先級*/ &xHandleTaskBeep ); /* 任務句柄 */ xTaskCreate( vTaskStart, /* 任務函數 */ "vTaskStart", /* 任務名 */ 512, /* 任務棧大小,單位word,也就是4字節 */ NULL, /* 任務參數 */ 4, /* 任務優先級*/ &xHandleTaskStart ); /* 任務句柄 */ }
分別實現:
static void vTaskStart(void *pvParameters) { EventBits_t uxBits; const TickType_t xTicksToWait = 1000 / portTICK_PERIOD_MS; /* 最大延遲1000ms */ /* 開始執行啟動任務主函數前使能獨立看門狗。 設置LSI是64分頻,下面函數參數范圍0-0xFFF,分別代表最小值2ms和最大值8192ms 下面設置的是4s,如果4s內沒有喂狗,系統復位。 */ IWDG_Config(IWDG_Prescaler_64 ,625*4); /* 打印系統開機狀態,方便查看系統是否復位 */ printf("=====================================================\r\n"); printf("=系統開機執行\r\n"); printf("=====================================================\r\n"); while(1) { /* 等待所有任務發來事件標志 */ uxBits = xEventGroupWaitBits(xCreatedEventGroup, /* 事件標志組句柄 */ TASK_BIT_ALL, /* 等待TASK_BIT_ALL被設置 */ pdTRUE, /* 退出前TASK_BIT_ALL被清除,這里是TASK_BIT_ALL都被設置才表示“退出”*/ pdTRUE, /* 設置為pdTRUE表示等待TASK_BIT_ALL都被設置*/ xTicksToWait); /* 等待延遲時間 */ if((uxBits & TASK_BIT_ALL) == TASK_BIT_ALL) { IWDG_Feed(); printf("五個用戶任務都正常運行\r\n"); } else { printf("五個用戶任務並非都正常運行\r\n"); /* 基本是每xTicksToWait進來一次 */ /* 通過變量uxBits簡單的可以在此處檢測那個任務長期沒有發來運行標志 */ } } }
void vTaskBeep(void *pvParameters) { while(1) { xEventGroupSetBits(xCreatedEventGroup, TASK_BIT_3); BEEP_TOGGLE; vTaskDelay(200); } }
void vTaskLed1(void *pvParameters) { TickType_t xLastWakeTime; const TickType_t xFrequency = 200; /* 獲取當前的系統時間 */ xLastWakeTime = xTaskGetTickCount(); while(1) { LED3_TOGGLE; xEventGroupSetBits(xCreatedEventGroup, TASK_BIT_2); /* vTaskDelayUntil是絕對延遲,vTaskDelay是相對延遲。*/ vTaskDelayUntil(&xLastWakeTime, xFrequency); } }
static void vTaskWork(void *pvParameters) { const TickType_t xTicksToWait = 20000 / portTICK_PERIOD_MS; /* 最大延遲20s */ while(1) { if (key1_flag==1) { key1_flag=0; } /* K2鍵按下,向xQueue1發送數據 */ if(key2_flag==1) { key2_flag=0; printf("K2按鍵按下,讓vTaskWork任務延遲20s,以實現看門狗復位情況\r\n"); vTaskDelay(xTicksToWait); // TIM_Mode_Config(); } xEventGroupSetBits(xCreatedEventGroup, TASK_BIT_1); vTaskDelay(20); } }
當按鍵不按下的時候:
當按鍵按下之后,事件發送有一個會阻塞20s:
解釋一下為什么按鍵K2按下之后,會顯示一次都正常運行,才識別到有非正常運行的。
K2按下,這個按鍵任務會被阻塞20s,此時,xEventGroupWaitBits函數由於超時返回,而超時返回,不會清除之前的置位信息,此時還是會保持上次正常狀態的值,這樣會打印任務都正常,當打印正常之后,標志會被清零。所以再等到下一次vTaskStart任務等待響應時,按鍵阻塞的任務遲遲沒有發來消息,故打印出不是所有任務都正常運行。要清除置位標志,必須是超時以外的情形。官方解釋:
如果xClearOnExit設置為pdTRUE,則在xEventGroupWaitBits()返回時,如果xEventGroupWaitBits()由於超時之外的任何原因而返回,則在作為uxBitsToWaitFor參數傳遞的值中設置的任何位將被清除。 超時值由xTicksToWait參數設置。
順帶一句,如果vTaskStart任務中的等待超時時間小於(比如100ms)其他任務的阻塞時間,那么接收事件標志位不會和你程序寫的那樣顯示打印消息,因為當第一次其他任務向vTaskStart發送事件時,是可以正確響應的,而當其他任務,比如Beep任務需要阻塞1s,在這1s的阻塞中,vTaskStart函數的接收超時已經過去好幾次了,這樣,在Bee還在阻塞時,會打印並非都正常運行,而當Beep從阻塞回到運行時,又會正確打印所有任務正常運行。所以,知道這個了之后,vTaskStart任務的超時時間需要結合實際項目好好思考,而且下面的else判斷一定要謹慎思考邏輯之后再使用。
按下超過4s之后,看門狗喂狗失敗導致復位了: