【連載】從單片機到操作系統④——FreeRTOS創建任務&開啟調度詳解

【連載】從單片機到操作系統④——FreeRTOS創建任務&開啟調度詳解

來自專欄創客飛夢空間

創客的兄弟姐妹們大家好,我是傑傑。又到了更新的時候了。

開始今天的內容之前,先補充一下上篇文章【連載】從單片機到操作系統③——走進FreeRTOS的一點點遺漏的知識點。

1BaseType_t xTaskCreate( TaskFunction_t pvTaskCode, 2 const char * const pcName, 3 uint16_t usStackDepth, 4 void *pvParameters, 5 UBaseType_t uxPriority, 6 TaskHandle_t *pvCreatedTask 7 ); 8創建任務中的堆棧大小問題,在task.h中有這樣子的描述: 9/**10* @param usStackDepth The size of the task stack specified as the number of variables the stack * can hold - not the number of bytes. For example, if the stack is 16 bits wide and 11* usStackDepth is defined as 100, 200 byteswill be allocated for stack storage.12*/

代碼可左右滑動

當任務創建時,內核會分為每個任務分配屬於任務自己的唯一堆棧。usStackDepth 值用於告訴內核為它應該分配多大的棧空間。

這個值指定的是棧空間可以保存多少個字(word) ,而不是多少個位元組(byte)。

文檔也有說明,如果是16位寬度的話,假如usStackDepth = 100;那麼就是200個位元組(byte)。

當然,我用的是stm32,32位寬度的, usStackDepth=100;那麼就是400個位元組(byte)。

好啦,補充完畢。下面正式開始我們今天的主題。


我自己學的是應用層的東西,很多底層的東西我也不懂,水平有限,出錯了還請多多包涵。

其實我自己寫文章的時候也去跟著火哥的書看著底層的東西啦,但是本身自己也是不懂,不敢亂寫。所以,這個《從單片機到操作系統》系列的文章,我會講一點底層,更多的是應用層,主要是用的方面。

按照一般的寫代碼的習慣,在main函數裡面各類初始化完畢了,並且創建任務成功了,那麼,可以開啟任務調度了。

1int main(void) 2{ 3 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//設置系統中斷優先順序分組4 4 Delay_Init(); //延時函數初始化 5 Uart_Init(115200); //初始化串口 6 LED_Init(); //初始化LED 7 KEY_Init(); 8 //創建開始任務 9 xTaskCreate((TaskFunction_t )start_task, //任務函數10 (const char* )"start_task", //任務名稱11 (uint16_t )START_STK_SIZE, //任務堆棧大小12 (void* )NULL, //傳遞給任務函數的參數13 (UBaseType_t )START_TASK_PRIO, //任務優先順序14 (TaskHandle_t* )&StartTask_Handler); //任務句柄 15 vTaskStartScheduler(); //開啟任務調度16}

來大概看看分析一下創建任務的過程,雖然說會用就行,但是也是要知道了解一下的。

注意:下面說的創建任務均為xTaskCreate(動態創建)而非靜態創建。

1pxStack = ( StackType_t * ) pvPortMalloc( ( ( ( size_t ) usStackDepth ) * sizeof( StackType_t ) ) ); 2/*lint !e961 MISRA exception as the casts are only redundant for some ports. */ 3 if( pxStack != NULL ) 4 { 5 /* Allocate space for the TCB. */ 6 pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) ); 7 /*lint !e961 MISRA exception as the casts are only redundant for some paths. */ 8 if( pxNewTCB != NULL ) 9 {10 /* Store the stack location in the TCB. */11 pxNewTCB->pxStack = pxStack;12 }13 else14 {15 /* The stack cannot be used as the TCB was not created. Free16 it again. */17 vPortFree( pxStack );18 }19 }20 else21 {22 pxNewTCB = NULL;23 }24 }

首先是利用pvPortMalloc給任務的堆棧分配空間,if( pxStack != NULL )如果內存申請成功,就接著給任務控制塊申請內存。pxNewTCB = ( TCB_t * ) pvPortMalloc( sizeof( TCB_t ) );同樣是使用pvPortMalloc();如果任務控制塊內存申請失敗則釋放 之前已經申請成功的任務堆棧的內存vPortFree( pxStack );

然後就初始化任務相關的東西,並且將新初始化的任務控制塊添加到列表中prvAddNewTaskToReadyList( pxNewTCB );

最後返回任務的狀態,如果是成功了就是pdPASS,假如失敗了就是返回errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;

1prvInitialiseNewTask( pxTaskCode, 2 pcName, 3 ( uint32_t ) usStackDepth, 4 pvParameters, 5 uxPriority, 6 pxCreatedTask, 7 pxNewTCB, 8 NULL ); 9 prvAddNewTaskToReadyList( pxNewTCB );10 xReturn = pdPASS;11 }12 else13 {14 xReturn = errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;15 }16 return xReturn;17 }18// 相關宏定義19#define pdPASS ( pdTRUE )20#define pdTRUE ( ( BaseType_t ) 1 )21/* FreeRTOS error definitions. */22#define errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY ( -1 )

具體的static void prvInitialiseNewTask(()實現請參考FreeRTOS的tasks.c文件的767行代碼。具體的static void prvAddNewTaskToReadyList( TCB_t *pxNewTCB )實現請參考FreeRTOS的tasks.c文件的963行代碼。

因為這些是tasks.c中的靜態的函數,僅供xTaskCreate創建任務內部調用的,我們無需理會這些函數的實現過程,當然如果需要請自行了解。

創建完任務就開啟任務調度了:

1vTaskStartScheduler(); //開啟任務調度

在任務調度裡面,會創建一個空閑任務(我們將的都是動態創建任務,靜態創建其實一樣的)

1xReturn = xTaskCreate( prvIdleTask, 2 "IDLE", configMINIMAL_STACK_SIZE, 3 ( void * ) NULL, 4 ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), 5 &xIdleTaskHandle ); 6/*lint !e961 MISRA exception, justified as it is not a redundant explicit cast to all supported compilers. */ 7 } 8相關宏定義: 9#define tskIDLE_PRIORITY ( ( UBaseType_t ) 0U )10#ifndef portPRIVILEGE_BIT11 #define portPRIVILEGE_BIT ( ( UBaseType_t ) 0x00 )12#endif13#define configUSE_TIMERS 1 14 //為1時啟用軟體定時器

從上面的代碼我們可以看出,空閑任務的優先順序是tskIDLE_PRIORITY為0,也就是說空閑任務的優先順序最低。當CPU沒事幹的時候才執行空閑任務,以待隨時切換優先順序更高的任務。

如果使用了軟體定時器的話,我們還需要創建定時器任務,創建的函數是:

1#if ( configUSE_TIMERS == 1 )2 BaseType_t xTimerCreateTimerTask( void )3

然後還要把中斷關一下

1portDISABLE_INTERRUPTS();

至於為什麼關中斷,也有說明:

1/* Interrupts are turned off here, toensure a tick does not occur 2before or during the call toxPortStartScheduler(). The stacks of 3the created tasks contain a status wordwith interrupts switched on 4so interrupts will automatically getre-enabled when the first task 5starts to run. */ 6/ *中斷在這裡被關閉,以確保不會發生滴答 7在調用xPortStartScheduler()之前或期間。堆棧 8創建的任務包含一個打開中斷的狀態字 9因此中斷將在第一個任務時自動重新啟用10開始運行。*/

那麼如何打開中斷呢????這是個很重要的問題

別擔心,我們在SVC中斷服務函數裡面就會打開中斷的

看代碼:

1__asm void vPortSVCHandler( void ) 2{ 3 PRESERVE8 4 ldr r3, =pxCurrentTCB /* Restore the context. */ 5 ldrr1, [r3] /* UsepxCurrentTCBConst to get the pxCurrentTCB address. */ 6 ldrr0, [r1] /* Thefirst item in pxCurrentTCB is the task top of stack. */ 7 ldmiar0!, {r4-r11} /* Pop theregisters that are not automatically saved on exception entry and the criticalnesting count. */ 8 msrpsp, r0 /*Restore the task stack pointer. */ 9 isb10 movr0, #011 msr basepri, r012 orrr14, #0xd13 bxr1414}

1msr basepri, r0

就是它把中斷打開的。看不懂沒所謂,我也不懂彙編,看得懂知道就好啦。

1xSchedulerRunning = pdTRUE;

任務調度開始運行

1/* If configGENERATE_RUN_TIME_STATS isdefined then the following2macro must be defined to configure thetimer/counter used to generate3the run time counter time base. */4portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();

如果configGENERATE_RUN_TIME_STATS使用時間統計功能,這個宏為1,那麼用戶必須實現一個宏portCONFIGURE_TIMER_FOR_RUN_TIME_STATS();用來配置一個定時器或者計數器。

來到我們的重點了,開啟任務調度,那麼任務到這了就不會返回了。

1if( xPortStartScheduler() != pdFALSE )2 {3 /*Should not reach here as if the scheduler is running the4 functionwill not return. */5 }

然後就能開啟第一個任務了,感覺好難是吧,我一開始也是覺得的,但是寫了這篇文章,覺得還行吧,也不算太難,可能也是在查看代碼跟別人的書籍吧,寫東西其實還是蠻好的,能加深理解,寫過文章的人就知道,懂了不一定能寫出來,所以,我還是很希望朋友們能投稿的。傑傑隨時歡迎。。。

開始任務就按照套路模板添加自己的代碼就好啦,很簡單的。

先創建任務:

1 xTaskCreate((TaskFunction_t )led0_task, 2 (const char* )"led0_task", 3 (uint16_t )LED0_STK_SIZE, 4 (void* )NULL, 5 (UBaseType_t )LED0_TASK_PRIO, 6 (TaskHandle_t* )&LED0Task_Handler); 7 //創建LED1任務 8 xTaskCreate((TaskFunction_t )led1_task, 9 (const char* )"led1_task", 10 (uint16_t )LED1_STK_SIZE,11 (void* )NULL,12 (UBaseType_t )LED1_TASK_PRIO,13 (TaskHandle_t* )&LED1Task_Handler);

創建完任務就開啟任務調度:

1vTaskStartScheduler(); //開啟任務調度

然後具體實現任務函數:

1//LED0任務函數 2void led0_task(void *pvParameters) 3{ 4 while(1) 5 { 6 LED0=~LED0; 7 vTaskDelay(500); 8 } 9} 10//LED1任務函數11void led1_task(void *pvParameters)12{13 while(1)14 {15 LED1=0;16 vTaskDelay(200);17 LED1=1;18 vTaskDelay(800);19 }20}

好啦,今天的介紹到這了為止,後面還會持續更新,敬請期待哦~

歡迎大家一起來討論操作系統的知識

我們的群號是:783234154

【連載】從單片機到操作系統①

【連載】從單片機到操作系統②

【連載】從單片機到操作系統③——走進FreeRTOS

創客:

「創客飛夢空間」是開源公眾號

歡迎大家分享出去

也歡迎大家投稿


推薦閱讀:

A、B按鈕控制數字的增減、循環
使用Arduino開發板和ESP8266從互聯網讀取數據
電腦還能這麼小?指甲蓋大小的電腦也能控制機器人!

TAG:單片機 | 嵌入式系統 | 操作系統 |