FreeRTOS基础教程第一章创建任务
1.硬件初始化
本章創建的任務需要用到開發板上的 LED,所以先要將 LED 相關的函數初始化好, 為了方便以后統一管理板級外設的初始化,我們在 main.c 文件中創建一個 BSP_Init()函數, 專門用于存放板級外設初始化函數,
/************************************************************************ @ 函數名 : BSP_Init* @ 功能說明: 板級外設初始化,所有板子上的初始化均可放在這個函數里面* @ 參數 : * @ 返回值 : 無*********************************************************************/ static void BSP_Init(void) {/** STM32中斷優先級分組為4,即4bit都用來表示搶占優先級,范圍為:0~15* 優先級分組只需要分組一次即可,以后如果有其他的任務需要用到中斷,* 都統一用這個優先級分組,千萬不要再分組,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 串口初始化 */USART_Config();}? ? ?執行到 BSP_Init()函數的時候,操作系統完全都還沒有涉及到,即 BSP_Init()函數所做 的工作跟我們以前編寫的裸機工程里面的硬件初始化工作是一模一樣的。運行完 BSP_Init () 函數,接下來才慢慢啟動操作系統,最后運行創建好的任務。有時候任務創建好,整個系 統跑起來了,可想要的實驗現象就是出不來,比如 LED 不會亮,串口沒有輸出,LCD 沒 有顯示等等。如果是初學者,這個時候就會心急如焚,四處求救,那怎么辦?這個時候如 何排除是硬件的問題還是系統的問題,這里面有個小小的技巧,即在硬件初始化好之后, 順便測試下硬件,測試方法跟裸機編程一樣,
/************************************************************************ @ 函數名 : BSP_Init* @ 功能說明: 板級外設初始化,所有板子上的初始化均可放在這個函數里面* @ 參數 : * @ 返回值 : 無*********************************************************************/ static void BSP_Init(void) {/** STM32中斷優先級分組為4,即4bit都用來表示搶占優先級,范圍為:0~15* 優先級分組只需要分組一次即可,以后如果有其他的任務需要用到中斷,* 都統一用這個優先級分組,千萬不要再分組,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 測試硬件是否正常工作 */ LED1_ON;/* 讓程序停在這里,不再繼續往下執行 */while(1);/* 串口初始化 */USART_Config();}2.創建單任務—SRAM靜態內存
這里,我們創建一個單任務,任務使用的棧和任務控制塊都使用靜態內存,即預先定 義好的全局變量,這些預先定義好的全局變量都存在內部的 SRAM 中。
2.1定義任務函數
任務實際上就是一個無限循環且不帶返回值的 C 函數。目前,我們創建一個這樣的任 務,讓開發板上面的 LED 燈以 500ms 的頻率閃爍,
/*********************************************************************** @ 函數名 : LED_Task* @ 功能說明: LED_Task任務主體* @ 參數 : * @ 返回值 : 無********************************************************************/ static void LED_Task(void* parameter) { while (1){LED1_ON;vTaskDelay(500); /* 延時500個tick */printf("LED_Task Running,LED1_ON\r\n");LED1_OFF; vTaskDelay(500); /* 延時500個tick */ printf("LED_Task Running,LED1_OFF\r\n");} }2.2空閑任務與定時器任務堆棧函數實現
當我們使用了靜態創建任務的時候,configSUPPORT_STATIC_ALLOCATION 這個宏 定 義 必 須為 1 (在 FreeRTOSConfig.h 文 件 中 ) , 并且 我 們需 要 實 現兩 個 函數 : vApplicationGetIdleTaskMemory()與 vApplicationGetTimerTaskMemory(),這兩個函數是用 戶設定的空閑(Idle)任務與定時器(Timer)任務的堆棧大小,必須由用戶自己分配,而 不能是動態分配,
/* 空閑任務任務堆棧 */ static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE]; /* 定時器任務堆棧 */ static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];/* 空閑任務控制塊 */ static StaticTask_t Idle_Task_TCB; /* 定時器任務控制塊 */ static StaticTask_t Timer_Task_TCB;/************************************************************************* @brief 獲取空閑任務的任務堆棧和任務控制塊內存* ppxTimerTaskTCBBuffer : 任務控制塊內存* ppxTimerTaskStackBuffer : 任務堆棧內存* pulTimerTaskStackSize : 任務堆棧大小* @date 2018-xx-xx***********************************************************************/ void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) {*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;/* 任務控制塊內存 */*ppxIdleTaskStackBuffer=Idle_Task_Stack;/* 任務堆棧內存 */*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;/* 任務堆棧大小 */ }/************************************************************************ @brief 獲取定時器任務的任務堆棧和任務控制塊內存* ppxTimerTaskTCBBuffer : 任務控制塊內存* ppxTimerTaskStackBuffer : 任務堆棧內存* pulTimerTaskStackSize : 任務堆棧大小***********************************************************************/ void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize) {*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任務控制塊內存 */*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任務堆棧內存 */*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任務堆棧大小 */ }2.3 定義任務棧
目前我們只創建了一個任務,當任務進入延時的時候,因為沒有另外就緒的用戶任務, 那么系統就會進入空閑任務,空閑任務是 FreeRTOS 系統自己啟動的一個任務,優先級最 低。當整個系統都沒有就緒任務的時候,系統必須保證有一個任務在運行,空閑任務就是 為這個設計的。當用戶任務延時到期,又會從空閑任務切換回用戶任務。 在 FreeRTOS 系統中,每一個任務都是獨立的,他們的運行環境都單獨的保存在他們 的棧空間當中。那么在定義好任務函數之后,我們還要為任務定義一個棧,目前我們使用 的是靜態內存,所以任務棧是一個獨立的全局變量,具體見代碼清單 14-5。任務的棧占用 的是 MCU 內部的 RAM,當任務越多的時候,需要使用的棧空間就越大,即需要使用的 RAM 空間就越多。一個 MCU 能夠支持多少任務,就得看你的 RAM 空間有多少。
/* AppTaskCreate任務任務堆棧 */ static StackType_t AppTaskCreate_Stack[128]; /* LED任務堆棧 */ static StackType_t LED_Task_Stack[128];2.4定義任務控制塊
定義好任務函數和任務棧之后,我們還需要為任務定義一個任務控制塊,通常我們稱 這個任務控制塊為任務的身份證。在 C代碼上,任務控制塊就是一個結構體,里面有非常 多的成員,這些成員共同描述了任務的全部信息,
/* AppTaskCreate 任務控制塊 */ static StaticTask_t AppTaskCreate_TCB; /* AppTaskCreate 任務控制塊 */ static StaticTask_t LED_Task_TCB;2.5 靜態創建任務
一個任務的三要素是任務主體函數,任務棧,任務控制塊,那么怎么樣把這三個要素 聯合在一起?FreeRTOS 里面有一個叫靜態任務創建函數 xTaskCreateStatic(),它就是干這 個活的。它將任務主體函數,任務棧(靜態的)和任務控制塊(靜態的)這三者聯系在一 起,讓任務可以隨時被系統啟動,
/* 創建 AppTaskCreate 任務 */AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t )AppTaskCreate, //任務函數(const char* )"AppTaskCreate", //任務名稱(uint32_t )128, //任務堆棧大小(void* )NULL, //傳遞給任務函數的參數(UBaseType_t )3, //任務優先級(StackType_t* )AppTaskCreate_Stack, //任務堆棧(StaticTask_t* )&AppTaskCreate_TCB); //任務控制塊 if(NULL != AppTaskCreate_Handle)/* 創建成功 */vTaskStartScheduler(); /* 啟動任務,開啟調度 */2.6 啟動任務
當任務創建好后,是處于任務就緒(Ready),在就緒態的任務可以參與操作系統的調度。 但是此時任務僅僅是創建了,還未開啟任務調度器,也沒創建空閑任務與定時器任務(如果使 能了 configUSE_TIMERS 這個宏定義),那這兩個任務就是在啟動任務調度器中實現,每個操 作系統,任務調度器只啟動一次,之后就不會再次執行了,FreeRTOS 中啟動任務調度器的函 數是 vTaskStartScheduler(),并且啟動任務調度器的時候就不會返回,從此任務管理都由 FreeRTOS管理,此時才是真正進入實時操作系統中的第一步,
? vTaskStartScheduler(); ? /* 啟動任務,開啟調度 */
2.7 main.c文件內容全貌
現在我們把任務主體,任務棧,任務控制塊這三部分代碼統一放到 main.c 中,我們在 main.c 文件中創建一個 AppTaskCreate 任務,這個任務是用于創建用戶任務,為了方便管 理,我們的所有的任務創建都統一放在這個函數中,在這個函數中創建成功的任務就可以 直接參與任務調度了,
/* FreeRTOS頭文件 */ #include "FreeRTOS.h" #include "task.h" /* 開發板硬件bsp頭文件 */ #include "bsp_led.h" #include "bsp_usart.h" static void AppTaskCreate(void);/* 用于創建任務 */static void LED_Task(void* pvParameters);/* LED_Task任務實現 */static void BSP_Init(void);/* 用于初始化板載相關資源 */ /**************************** 任務句柄 ********************************/ /* * 任務句柄是一個指針,用于指向一個任務,當任務創建好之后,它就具有了一個任務句柄* 以后我們要想操作這個任務都需要通過這個任務句柄,如果是自身的任務操作自己,那么* 這個句柄可以為NULL。*//* 創建任務句柄 */ static TaskHandle_t AppTaskCreate_Handle; /* LED任務句柄 */ static TaskHandle_t LED_Task_Handle; /********************************** 內核對象句柄 *********************************/ /** 信號量,消息隊列,事件標志組,軟件定時器這些都屬于內核的對象,要想使用這些內核* 對象,必須先創建,創建成功之后會返回一個相應的句柄。實際上就是一個指針,后續我* 們就可以通過這個句柄操作這些內核對象。** 內核對象說白了就是一種全局的數據結構,通過這些數據結構我們可以實現任務間的通信,* 任務間的事件同步等各種功能。至于這些功能的實現我們是通過調用這些內核對象的函數* 來完成的* *//******************************* 全局變量聲明 ************************************/ /** 當我們在寫應用程序的時候,可能需要用到一些全局變量。*/ /* AppTaskCreate任務任務堆棧 */ static StackType_t AppTaskCreate_Stack[128]; /* LED任務堆棧 */ static StackType_t LED_Task_Stack[128];/* AppTaskCreate 任務控制塊 */ static StaticTask_t AppTaskCreate_TCB; /* AppTaskCreate 任務控制塊 */ static StaticTask_t LED_Task_TCB;/* 空閑任務任務堆棧 */ static StackType_t Idle_Task_Stack[configMINIMAL_STACK_SIZE]; /* 定時器任務堆棧 */ static StackType_t Timer_Task_Stack[configTIMER_TASK_STACK_DEPTH];/* 空閑任務控制塊 */ static StaticTask_t Idle_Task_TCB; /* 定時器任務控制塊 */ static StaticTask_t Timer_Task_TCB;/*** 使用了靜態分配內存,以下這兩個函數是由用戶實現,函數在task.c文件中有引用* 當且僅當 configSUPPORT_STATIC_ALLOCATION 這個宏定義為 1 的時候才有效*/ void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize);void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize);/****************************************************************** @brief 主函數* @param 無* @retval 無* @note 第一步:開發板硬件初始化 第二步:創建APP應用任務第三步:啟動FreeRTOS,開始多任務調度****************************************************************/ int main(void) { /* 開發板硬件初始化 */BSP_Init();printf("這是一個[野火]-STM32全系列開發板-FreeRTOS-靜態創建單任務!\r\n");/* 創建 AppTaskCreate 任務 */AppTaskCreate_Handle = xTaskCreateStatic((TaskFunction_t )AppTaskCreate, //任務函數(const char* )"AppTaskCreate", //任務名稱(uint32_t )128, //任務堆棧大小(void* )NULL, //傳遞給任務函數的參數(UBaseType_t )3, //任務優先級(StackType_t* )AppTaskCreate_Stack, //任務堆棧(StaticTask_t* )&AppTaskCreate_TCB); //任務控制塊 if(NULL != AppTaskCreate_Handle)/* 創建成功 */vTaskStartScheduler(); /* 啟動任務,開啟調度 */while(1); /* 正常不會執行到這里 */ }/************************************************************************ @ 函數名 : AppTaskCreate* @ 功能說明: 為了方便管理,所有的任務創建函數都放在這個函數里面* @ 參數 : 無 * @ 返回值 : 無**********************************************************************/ static void AppTaskCreate(void) {taskENTER_CRITICAL(); //進入臨界區/* 創建LED_Task任務 */LED_Task_Handle = xTaskCreateStatic((TaskFunction_t )LED_Task, //任務函數(const char* )"LED_Task", //任務名稱(uint32_t )128, //任務堆棧大小(void* )NULL, //傳遞給任務函數的參數(UBaseType_t )4, //任務優先級(StackType_t* )LED_Task_Stack, //任務堆棧(StaticTask_t* )&LED_Task_TCB); //任務控制塊 if(NULL != LED_Task_Handle)/* 創建成功 */printf("LED_Task任務創建成功!\n");elseprintf("LED_Task任務創建失敗!\n");vTaskDelete(AppTaskCreate_Handle); //刪除AppTaskCreate任務taskEXIT_CRITICAL(); //退出臨界區 }/*********************************************************************** @ 函數名 : LED_Task* @ 功能說明: LED_Task任務主體* @ 參數 : * @ 返回值 : 無********************************************************************/ static void LED_Task(void* parameter) { while (1){LED1_ON;vTaskDelay(500); /* 延時500個tick */printf("LED_Task Running,LED1_ON\r\n");LED1_OFF; vTaskDelay(500); /* 延時500個tick */ printf("LED_Task Running,LED1_OFF\r\n");} }/************************************************************************ @ 函數名 : BSP_Init* @ 功能說明: 板級外設初始化,所有板子上的初始化均可放在這個函數里面* @ 參數 : * @ 返回值 : 無*********************************************************************/ static void BSP_Init(void) {/** STM32中斷優先級分組為4,即4bit都用來表示搶占優先級,范圍為:0~15* 優先級分組只需要分組一次即可,以后如果有其他的任務需要用到中斷,* 都統一用這個優先級分組,千萬不要再分組,切忌。*/NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );/* LED 初始化 */LED_GPIO_Config();/* 測試硬件是否正常工作 */ LED1_ON;/* 讓程序停在這里,不再繼續往下執行 */while(1);/* 串口初始化 */USART_Config();}/************************************************************************* @brief 獲取空閑任務的任務堆棧和任務控制塊內存* ppxTimerTaskTCBBuffer : 任務控制塊內存* ppxTimerTaskStackBuffer : 任務堆棧內存* pulTimerTaskStackSize : 任務堆棧大小* @date 2018-xx-xx***********************************************************************/ void vApplicationGetIdleTaskMemory(StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize) {*ppxIdleTaskTCBBuffer=&Idle_Task_TCB;/* 任務控制塊內存 */*ppxIdleTaskStackBuffer=Idle_Task_Stack;/* 任務堆棧內存 */*pulIdleTaskStackSize=configMINIMAL_STACK_SIZE;/* 任務堆棧大小 */ }/************************************************************************ @brief 獲取定時器任務的任務堆棧和任務控制塊內存* ppxTimerTaskTCBBuffer : 任務控制塊內存* ppxTimerTaskStackBuffer : 任務堆棧內存* pulTimerTaskStackSize : 任務堆棧大小***********************************************************************/ void vApplicationGetTimerTaskMemory(StaticTask_t **ppxTimerTaskTCBBuffer, StackType_t **ppxTimerTaskStackBuffer, uint32_t *pulTimerTaskStackSize) {*ppxTimerTaskTCBBuffer=&Timer_Task_TCB;/* 任務控制塊內存 */*ppxTimerTaskStackBuffer=Timer_Task_Stack;/* 任務堆棧內存 */*pulTimerTaskStackSize=configTIMER_TASK_STACK_DEPTH;/* 任務堆棧大小 */ }/********************************END OF FILE****************************/注意:在使用靜態創建任務的時候必須要將 FreeRTOSConfig.h 中 的 configSUPPORT_STATIC_ALLOCATION 宏配置為 1。
下載后,可以看到板子上面的 LED 燈已經在 閃爍,說明我們創建的單任務(使用靜態內存)已經跑起來了。
本篇內容摘自野火《FreeRTOS內核實現與應用開發》
總結
以上是生活随笔為你收集整理的FreeRTOS基础教程第一章创建任务的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 牛人自述模拟电路学习历程
- 下一篇: Python ——告白小程序,添加微信号