以下轉載自安富萊電子: http://forum.armfly.com/forum.php
摘自:https://www.cnblogs.com/yangguang-it/p/7149048.html
本章節為大家介紹 FreeRTOS 的調試方法,這里的調試方法主要是教會大家如何獲取任務的執行情況,通過獲取的任務信息,可以進一步的配置和優化工程,這種方法非常實用,建議初學者必須掌握。
串口打印調試說明
很多時候,我們需要了解任務的執行狀態,任務棧的使用情況以及各個任務的 CPU 使用率,這時就
需要用到官方提供的兩個函數 vTaskList 和 vTaskGetRunTimeStats。用戶就可以通過這兩個函數獲得任
務的執行情況。
獲取了任務執行情況后,可以通過串口將其打印出來,當然,也可以通過任何其它方式將其顯示出來。
本教程配套的例子統一采用串口打印的方式顯示任務的執行情況。另外有一點要特別注意,這種調試方式
僅限測試目的,實際項目中不要使用,這種測試方式比較影響系統實時性。
為了獲取 FreeRTOS 的任務信息,需要創建一個定時器,這個定時器的時間基准精度要高於系統時鍾
節拍,這樣得到的任務信息才准確。這里提供的函數僅用於測試目的,切不可將其用於實際項目,原因有兩點:
1. FreeRTOS 的系統內核沒有對總的計數時間做溢出保護。
2. 定時器中斷是 50us 進入一次,比較影響系統性能。
這里使用的是 32 位變量來保存 50us 一次的計數值,最大支持計數時間:2^32 * 50us / 3600s =
59.6 分鍾。 運行時間超過了 59.6 分鍾將不准確。
具體在 FreeRTOS 的工程中如何做才可以實現任務信息獲取呢?
使能相關宏定義
需要在 FreeRTOSConfig.h 文件中使能如下宏定義:
/***freertosconfig.h***/ extern volatile uint32_t ulHighFrequencyTimerTicks ; /* Run time and task stats gathering related definitions. */ #define configUSE_TRACE_FACILITY 1 #define configGENERATE_RUN_TIME_STATS 1 #define configUSE_STATS_FORMATTING_FUNCTIONS 1 #define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() (ulHighFrequencyTimerTicks = 0ul) #define portGET_RUN_TIME_COUNTER_VALUE() ulHighFrequencyTimerTicks
之前的博客:
使能了宏定義之后,必要完成portCONFIGURE_TIMER_FOR_RUN_TIME_STATS()和portGET_RUN_TIME_COUNTER_VALUE()的定義,那么我們究竟應該如何定義並使用它們呢?
freertos官方網站給了提示(點擊這里查看更多):
本次實驗采用上面的做法,下面還有一個例子,但是沒有上面好用:
現在我使用第一種方式,STM32F429,TIM6基本定時器,產生一個50us周期的中斷,進一次中斷,計數器的值加1:
由於在進入
/* 第三步:啟動FreeRTOS,開始多任務調度,啟動成功則不返回 */
vTaskStartScheduler();(在這個函數中調用了portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() )
這個函數之前,定時器計數已經執行計數功能很多次了,為了保證我們的基石相對准確,我們在啟動freertos的API函數中將其置零,這也就是為什么那個宏#define portCONFIGURE_TIMER_FOR_RUN_TIME_STATS() 要這樣操作這個計數器ulHighFrequencyTimerTicks=0UL;這樣從任務開啟時,計數器從零開始計數,而portGET_RUN_TIME_COUNTER_VALUE()是為了得到最后的計數值,可以是一個函數,返回之前用戶自定義的計數器的值,也可以直接把那個計數器的值進行宏替換,在uxTaskGetSystemState()函數調用形式如下:
可以知道,我們只是需要這個計數器的值作為右值最后打印顯示。(一個比較好的參考,點擊這里)-(stack overflow參考)-(博客參考)。
有了前面的鋪墊之后,再來說說我們的驗證試驗:
功能描述:按下K1按鍵,打印出任務信息,函數如下:
static void vTaskWork(void *pvParameters) { uint8_t pcWriteBuffer[500]; while(1) { if (key1_flag==1) { key1_flag=0; /* K1鍵按下 打印任務執行情況 */ printf("=======================================================\r\n"); printf("任務名 任務狀態 優先級 剩余棧 任務序號\r\n"); vTaskList((char *)&pcWriteBuffer); printf("%s\r\n", pcWriteBuffer); printf("\r\n任務名 運行計數 使用率\r\n"); vTaskGetRunTimeStats((char *)&pcWriteBuffer); printf("%s\r\n", pcWriteBuffer); /* 其他的鍵值不處理 */ } vTaskDelay(20); } }
任務一,執行打印,任務二,LED閃爍,任務三,蜂鳴器
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 ); /* 任務句柄 */ }
void vTaskLed1(void *pvParameters) { /* 任務都是一個無限,不能返回 */ while(1) { LED3_ON; /* 阻塞延時,單位ms */ vTaskDelay( 500 ); LED3_OFF; vTaskDelay( 500 ); } }
void vTaskBeep(void *pvParameters) { /* 任務都是一個無限循環,不能返回 */ while(1) { BEEP_ON; /* 阻塞延時,單位ms */ vTaskDelay( 20 ); BEEP_OFF; vTaskDelay( 500 ); } }
基本硬件初始化:
static void BSP_Init(void) { /* * STM32中斷優先級分組為4,即4bit都用來表示搶占優先級,范圍為:0~15 * 優先級分組只需要分組一次即可,以后如果有其他的任務需要用到中斷, * 都統一用這個優先級分組,千萬不要再分組,切忌。 */ NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 ); /* LED 初始化 */ LED_GPIO_Config(); /*串口初始化*/ Debug_USART_Config(); /*按鍵初始化*/ EXTI_Key_Config(); /*定時器6初始化*/ TIMx_Configuration(); /* 蜂鳴器初始化 */ Beep_GPIO_Config(); }
關於裸機部分的外設初始化配置不再贅述。這樣,下載程序之后,LED燈閃爍並且蜂鳴器鳴叫,在按下k1按鍵時,串口輸出如下:
有了這個可以知道,我們512字的任務棧空間實在有點浪費,最后的剩余棧單位也是字。
最后,關於vTaskList((char *)&pcWriteBuffer);vTaskGetRunTimeStats((char *)&pcWriteBuffer);這個緩沖區的大小,官網說的,一個任務,大約40個字節足夠,我們取50字節,所以定義的緩沖區大小uint8_t pcWriteBuffer[500];可以足夠打印10個任務狀態。