大多數主機或桌面系統(好比Linux,Mac或Windows)都有一個正常的用例,你能夠在早上啓動操做系統,而後在晚上關閉它,而後你就離開機器。嵌入式系統是不一樣的:他們沒有參加,他們應該「永遠」運行。並不是每一個嵌入式系統都須要運行操做系統(或者在那個世界中:實時操做系統或RTOS),但這一樣適用於:在RTOS啓動後,並不意味着它將關閉並從新啓動。在某種程度上,他們根本不支持「關閉」和「重啓」功能。若是收集覆蓋率信息,這將很是有用:html
來自FreeRTOS應用程序的覆蓋信息git
對於FreeRTOS:若是我真的須要關閉RTOS並從新啓動它會怎麼樣,由於默認狀況下不支持。這就是本文的內容......github
介紹web
嵌入式系統與桌面系統根本不一樣:雖然不時關閉和從新啓動臺式機或筆記本電腦系統是正常的,但這不是計劃或打算用於嵌入式系統:本質上它應該「始終」運行。從嵌入式系統的「主要」進一步能夠看出這一點:一般主要永遠不會返回並保持運行,以下所示:架構
void main(void) {less InitClocks();eclipse InitPins();ide InitDrivers();函數 for(;;) {工具 AppRun(); } /* 從未離開主程序 */ } |
若是使用RTOS運行,相似的東西適用於嵌入式系統,其中看起來相似於:
void main(void) { InitClocks(); InitPins(); InitDrivers(); CreateInitialTasks(); StartScheduler(); /* 調度程序永遠不會終止,因此不該該到達這裏 */ for(;;) { } /* 從未離開主程序 */ } |
爲何關機並重啓?
顯然,對於嵌入式RTOS而言,RTOS關閉或重啓的需求可能不是最須要的。我仍然發現它很是有用:
在調度程序關閉後寫入gcov覆蓋信息
從任務結束FreeRTOS調度程序
vTaskStartScheduler()後跟低功耗模式
因此我但願你明白爲何RTOS的關閉和重啓是有意義的。包括FreeRTOS在內的大多數RTOS都可以「靜音」調度程序(例如vTaskSuspendAll()),但仍然存在RTOS並使用系統資源。可是,若是須要,徹底「刪除」並從新啓動它的能力將是很酷的事情。
FreeRTOS
FreeRTOS確實有一個調度程序啓動函數(vTaskStartScheduler()),甚至在其API中有一個vTaskEndScheduler()函數:
FreeRTOS vTaskEndScheduler()API函數
但正如論壇和API描述中所述,它僅支持PC端口(請參閱API說明):「 注意:這僅適用於x86實模式PC端口。「
原來的FreeRTOS端口也是如此。我擴展的端口確實支持這個,我在ARM Cortex-M和HCS08應用程序中使用它:-)。
vTaskEndScheduler()和vTaskStartScheduler()
雖然RTOS已準備好將其關閉的API調用,但FreeRTOS在調用vTaskEndScheduler()以後沒有適當的基礎結構來從新啓動RTOS。但這正是我想要的:在結束後重啓RTOS。
要可以結束調度程序,必須在FreeRTOSConfig.h中將如下宏設置爲1:
#define INCLUDE_vTaskEndScheduler 1 |
挑戰在於:在調度程序啓動以後,在vTaskStartScheduler()調用以後當即返回代碼並不容易。由於具備本身的堆棧的任務,加上FreeRTOS和M3的標準端口,M4和M7甚至會重置MSP堆棧指針(參見本論壇討論)。所以,若是我想返回調度程序已啓動的位置,我須要阻止重置ARM Cortex上的MSP堆棧指針。這就是我爲FreeRTOS添加一個設置來配置它的緣由:
重置MSP設置
這將設置如下配置:
#ifndef configRESET_MSP #define configRESET_MSP (0) /*!< 1: reset MSP at scheduler start (Cortex M3/M4/M7 only); 0: do not reset MSP */ #endif |
將此設置設置爲0,個人端口在調度程序起始點執行*不*重置MSP。
端口代碼重置msp堆棧指針
這意味着並不是完整的MSP堆棧可用於中斷(參見「 ARM Cortex-M中斷和FreeRTOS:第3部分 」),但這一般也能夠。
爲了可以跳回到調度程序的起點,我使用了C庫的一個很酷的功能:setjmp / longjmp(參見http://web.eecs.utk.edu/~huangj/cs360/360/notes/ Setjmp / lecture.html)。
首先,我須要一個跳轉緩衝區變量:
#if INCLUDE_vTaskEndScheduler #include <setjmp.h> static jmp_buf xJumpBuf; /* Used to restore the original context when the scheduler is ended. */ #endif |
在xPortStartScheduler()裏面我設置了跳轉緩衝區:
#if INCLUDE_vTaskEndScheduler if(setjmp(xJumpBuf) != 0 ) { /* here we will get in case of a call to vTaskEndScheduler() */ __asm volatile( " movs r0, #0 \n" /* Reset CONTROL register and switch back to the MSP stack. */ " msr CONTROL, r0 \n" " dsb \n" " isb \n" ); return pdFALSE; } #endif vPortStartFirstTask(); /* Start the first task. */ /* Should not get here! */ return pdFALSE; |
若是創建跳轉緩衝區,setjmp()返回0,而且在調用setjmp()的狀況下返回!= 0將在vTaskEndScheduler()期間調用。
void vTaskEndScheduler( void ) { /* Stop the scheduler interrupts and call the portable scheduler end routine so the original ISRs can be restored if necessary. The port layer must ensure interrupts enable bit is left in the correct state. */ portDISABLE_INTERRUPTS(); xSchedulerRunning = pdFALSE; vPortEndScheduler(); } |
而後,vPortEndscheduler()執行RTOS的全部清理和重置。重置要求不會形成調試器對任何RTOS認知的混淆:
void vPortEndScheduler(void) { vPortStopTickTimer(); vPortInitializeHeap(); uxCriticalNesting = 0xaaaaaaaa; /* Jump back to the processor state prior to starting the scheduler. This means we are not going to be using a task stack frame so the task can be deleted. */ #if INCLUDE_vTaskEndScheduler longjmp(xJumpBuf, 1); #else for(;;){} /* wait here */ #endif } |
有了這個,調度程序優雅地終止,我又回到了主堆棧上:
在調用vTaskEndScheduler()以後在msp堆棧上
摘要
默認狀況下,對於嵌入式目標,FreeRTOS不支持結束調度程序或在結束後從新啓動調度程序。本文描述了ARM Cortex-M的一個端口,它實現了vTaskEndScheduler(),並可以在沒有上電覆位的狀況下再次調用vTaskStartScheduler()。結束和啓動調度程序對於低功耗應用程序很是有用,在應用程序的實時循環或引導加載程序應用程序中不須要RTOS的狀況。該概念用於恩智浦的不一樣ARM Cortex-M內核,包括8/16位S08微控制器,但能夠輕鬆用於任何其餘微控制器架構。
參考連接
聲明:本文爲翻譯文章,原文做者是:Erich Styger,原文網址爲:https://mcuoneclipse.com/2019/01/20/freertos-how-to-end-and-restart-the-scheduler/
歡迎關注: