建單任務(wù)與多任務(wù)(SRAM 動(dòng)態(tài)內(nèi)存))
一、前言在 FreeRTOS 中任務(wù)創(chuàng)建有兩種方式動(dòng)態(tài)內(nèi)存創(chuàng)建和靜態(tài)內(nèi)存創(chuàng)建。本文使用最常見的動(dòng)態(tài)內(nèi)存創(chuàng)建方式通過(guò)STM32F103 標(biāo)準(zhǔn)庫(kù) FreeRTOS演示創(chuàng)建單任務(wù)讓一個(gè) LED 閃爍創(chuàng)建多任務(wù)讓兩個(gè) LED 以不同頻率閃爍。硬件STM32F103C8T6LED1 接 PC13LED2 接 PA0若你的板子不同修改引腳即可。軟件Keil MDK5、標(biāo)準(zhǔn)外設(shè)庫(kù) V3.5、FreeRTOS V10.x。二、動(dòng)態(tài)內(nèi)存創(chuàng)建任務(wù)原理調(diào)用xTaskCreate()時(shí)FreeRTOS 會(huì)從堆中動(dòng)態(tài)申請(qǐng)兩塊內(nèi)存任務(wù)控制塊TCB保存任務(wù)狀態(tài)、優(yōu)先級(jí)、棧指針等信息任務(wù)棧任務(wù)運(yùn)行時(shí)局部變量和函數(shù)調(diào)用使用。因此必須滿足configSUPPORT_DYNAMIC_ALLOCATION為1configTOTAL_HEAP_SIZE足夠大例如 8KB。三、工程準(zhǔn)備標(biāo)準(zhǔn)庫(kù)移植注意將 FreeRTOS 源碼加入工程并添加頭文件路徑準(zhǔn)備一個(gè)標(biāo)準(zhǔn)庫(kù)點(diǎn)燈工程重要注釋掉stm32f10x_it.c中的以下三個(gè)中斷函數(shù)避免與 FreeRTOS 沖突// stm32f10x_it.c 中注釋掉下面三個(gè)函數(shù)// void SVC_Handler(void) {}// void PendSV_Handler(void) {}// void SysTick_Handler(void) {}然后在FreeRTOSConfig.h中做中斷映射#definevPortSVCHandlerSVC_Handler#definexPortPendSVHandlerPendSV_Handler#definexPortSysTickHandlerSysTick_Handler四、FreeRTOSConfig.h 關(guān)鍵配置#ifndefFREERTOS_CONFIG_H#defineFREERTOS_CONFIG_H#defineconfigUSE_PREEMPTION1#defineconfigUSE_IDLE_HOOK0#defineconfigUSE_TICK_HOOK0#defineconfigCPU_CLOCK_HZ(72000000UL)#defineconfigTICK_RATE_HZ(1000UL)#defineconfigMAX_PRIORITIES(5)#defineconfigMINIMAL_STACK_SIZE((unsignedshort)128)#defineconfigTOTAL_HEAP_SIZE(8*1024)#defineconfigMAX_TASK_NAME_LEN(16)#defineconfigUSE_16_BIT_TICKS0#defineconfigIDLE_SHOULD_YIELD1#defineconfigUSE_MUTEXES0#defineconfigQUEUE_REGISTRY_SIZE0#defineconfigCHECK_FOR_STACK_OVERFLOW0#defineconfigUSE_RECURSIVE_MUTEXES0#defineconfigUSE_MALLOC_FAILED_HOOK0#defineconfigUSE_APPLICATION_TASK_TAG0#defineconfigUSE_COUNTING_SEMAPHORES0#defineconfigGENERATE_RUN_TIME_STATS0/* 動(dòng)態(tài)內(nèi)存創(chuàng)建必須為 1 */#defineconfigSUPPORT_DYNAMIC_ALLOCATION1#defineconfigSUPPORT_STATIC_ALLOCATION0/* 使能 vTaskDelay 等 API */#defineINCLUDE_vTaskDelay1#defineINCLUDE_vTaskDelete1/* 中斷函數(shù)映射 */#definevPortSVCHandlerSVC_Handler#definexPortPendSVHandlerPendSV_Handler#definexPortSysTickHandlerSysTick_Handler#endif/* FREERTOS_CONFIG_H */五、創(chuàng)建單任務(wù)完整代碼#includestm32f10x.h#includeFreeRTOS.h#includetask.h/* LED1 定義 */#defineLED1_PORTGPIOC#defineLED1_PINGPIO_Pin_13/* 任務(wù)函數(shù)聲明 */staticvoidLed1_Task(void*pvParameters);/* LED1 初始化 */staticvoidLED1_Init(void){GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);GPIO_InitStructure.GPIO_PinLED1_PIN;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(LED1_PORT,GPIO_InitStructure);GPIO_SetBits(LED1_PORT,LED1_PIN);/* 默認(rèn)熄滅 */}/* LED1 閃爍任務(wù) */staticvoidLed1_Task(void*pvParameters){(void)pvParameters;while(1){GPIO_ResetBits(LED1_PORT,LED1_PIN);/* 點(diǎn)亮 */vTaskDelay(500/portTICK_PERIOD_MS);/* 延時(shí) 500ms */GPIO_SetBits(LED1_PORT,LED1_PIN);/* 熄滅 */vTaskDelay(500/portTICK_PERIOD_MS);/* 延時(shí) 500ms */}}intmain(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);/* 必須 */LED1_Init();/* 動(dòng)態(tài)內(nèi)存創(chuàng)建單任務(wù) */BaseType_t xReturnxTaskCreate(Led1_Task,/* 任務(wù)函數(shù) */Led1Task,/* 任務(wù)名稱 */configMINIMAL_STACK_SIZE,/* 棧深度字 */NULL,/* 任務(wù)參數(shù) */1,/* 優(yōu)先級(jí) */NULL/* 任務(wù)句柄 */);if(xReturnpdPASS){vTaskStartScheduler();/* 啟動(dòng)調(diào)度器 */}while(1){}/* 不會(huì)執(zhí)行到這里 */}說(shuō)明這就是最簡(jiǎn)單的單任務(wù)創(chuàng)建流程LED1 以 1Hz 頻率閃爍。六、創(chuàng)建多任務(wù)完整代碼在單任務(wù)基礎(chǔ)上增加第二個(gè) LEDPA0和第二個(gè)任務(wù)兩個(gè)任務(wù)并發(fā)運(yùn)行。#includestm32f10x.h#includeFreeRTOS.h#includetask.h/* LED 定義 */#defineLED1_PORTGPIOC#defineLED1_PINGPIO_Pin_13#defineLED2_PORTGPIOA#defineLED2_PINGPIO_Pin_0/* 任務(wù)函數(shù)聲明 */staticvoidLed1_Task(void*pvParameters);staticvoidLed2_Task(void*pvParameters);/* LED 初始化 */staticvoidLED_Init(void){GPIO_InitTypeDef GPIO_InitStructure;/* 使能 GPIOC 和 GPIOA 時(shí)鐘 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOA,ENABLE);/* LED1: PC13 推挽輸出 */GPIO_InitStructure.GPIO_PinLED1_PIN;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(LED1_PORT,GPIO_InitStructure);GPIO_SetBits(LED1_PORT,LED1_PIN);/* 默認(rèn)熄滅 *//* LED2: PA0 推挽輸出 */GPIO_InitStructure.GPIO_PinLED2_PIN;GPIO_InitStructure.GPIO_ModeGPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_SpeedGPIO_Speed_50MHz;GPIO_Init(LED2_PORT,GPIO_InitStructure);GPIO_SetBits(LED2_PORT,LED2_PIN);/* 默認(rèn)熄滅 */}/* LED1 閃爍任務(wù)慢閃 */staticvoidLed1_Task(void*pvParameters){(void)pvParameters;while(1){GPIO_ResetBits(LED1_PORT,LED1_PIN);/* 點(diǎn)亮 */vTaskDelay(500/portTICK_PERIOD_MS);GPIO_SetBits(LED1_PORT,LED1_PIN);/* 熄滅 */vTaskDelay(500/portTICK_PERIOD_MS);}}/* LED2 閃爍任務(wù)快閃 */staticvoidLed2_Task(void*pvParameters){(void)pvParameters;while(1){GPIO_ResetBits(LED2_PORT,LED2_PIN);/* 點(diǎn)亮 */vTaskDelay(200/portTICK_PERIOD_MS);GPIO_SetBits(LED2_PORT,LED2_PIN);/* 熄滅 */vTaskDelay(200/portTICK_PERIOD_MS);}}intmain(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);/* 必須 */LED_Init();/* 創(chuàng)建任務(wù)1LED1 閃爍優(yōu)先級(jí) 1 */BaseType_t xReturn1xTaskCreate(Led1_Task,Led1Task,configMINIMAL_STACK_SIZE,NULL,1,/* 優(yōu)先級(jí) 1 */NULL);/* 創(chuàng)建任務(wù)2LED2 閃爍優(yōu)先級(jí) 2更高 */BaseType_t xReturn2xTaskCreate(Led2_Task,Led2Task,configMINIMAL_STACK_SIZE,NULL,2,/* 優(yōu)先級(jí) 2 */NULL);/* 兩個(gè)任務(wù)都創(chuàng)建成功則啟動(dòng)調(diào)度器 */if(xReturn1pdPASSxReturn2pdPASS){vTaskStartScheduler();}while(1){}/* 不會(huì)執(zhí)行到這里 */}現(xiàn)象LED1 慢閃1HzLED2 快閃2.5Hz互不干擾這就是多任務(wù)并發(fā)的直觀體現(xiàn)。七、代碼解析1. 任務(wù)函數(shù)必須是無(wú)返回的死循環(huán)每個(gè)任務(wù)都是一個(gè)獨(dú)立執(zhí)行的函數(shù)不能return否則會(huì)導(dǎo)致系統(tǒng)異常。2.vTaskDelay()的作用讓任務(wù)進(jìn)入阻塞狀態(tài)CPU 可以執(zhí)行其他任務(wù)。單任務(wù)時(shí) CPU 大部分空閑多任務(wù)時(shí)兩個(gè)任務(wù)輪流獲得 CPU。3.xTaskCreate()參數(shù)參數(shù)含義任務(wù)函數(shù)名任務(wù)入口函數(shù)任務(wù)名稱字符串僅用于調(diào)試最長(zhǎng)configMAX_TASK_NAME_LEN棧深度單位是字4字節(jié)configMINIMAL_STACK_SIZE128即 512 字節(jié)任務(wù)參數(shù)傳給任務(wù)函數(shù)的指針不需要傳NULL優(yōu)先級(jí)數(shù)字越大優(yōu)先級(jí)越高0 為最低configMAX_PRIORITIES-1為最高任務(wù)句柄用于后續(xù)操作任務(wù)刪除、掛起等不需要時(shí)傳NULL4. 多任務(wù)與單任務(wù)的區(qū)別多任務(wù)只是多次調(diào)用xTaskCreate()而已。每個(gè)任務(wù)擁有獨(dú)立的棧和 TCB調(diào)度器自動(dòng)切換。注意每個(gè)任務(wù)棧大小要足夠否則棧溢出優(yōu)先級(jí)可相同或不同相同優(yōu)先級(jí)會(huì)時(shí)間片輪轉(zhuǎn)任務(wù)數(shù)量受限于堆大小configTOTAL_HEAP_SIZE。八、常見問(wèn)題1. 任務(wù)創(chuàng)建失敗檢查configSUPPORT_DYNAMIC_ALLOCATION是否為1增大configTOTAL_HEAP_SIZE。2. 編譯報(bào)錯(cuò)SVC_Handler重復(fù)定義確保已注釋stm32f10x_it.c中的SVC_Handler、PendSV_Handler、SysTick_Handler。3. 進(jìn)入 HardFault中斷優(yōu)先級(jí)分組必須為NVIC_PriorityGroup_4確認(rèn)FreeRTOSConfig.h中的中斷映射正確。4. 兩個(gè)任務(wù)似乎不同步閃爍這是正常的因?yàn)?FreeRTOS 調(diào)度器按時(shí)間片切換任務(wù)LED 閃爍頻率由各自的vTaskDelay決定互不影響??偨Y(jié)本文使用標(biāo)準(zhǔn)庫(kù) FreeRTOS 動(dòng)態(tài)內(nèi)存演示了單任務(wù)和多任務(wù)的創(chuàng)建方法。核心就是xTaskCreate()函數(shù)多任務(wù)只是重復(fù)調(diào)用它。掌握這個(gè)流程后復(fù)雜應(yīng)用不過(guò)是創(chuàng)建更多任務(wù)并通過(guò)隊(duì)列、信號(hào)量進(jìn)行通信。希望本文能幫你快速入門 FreeRTOS 的多任務(wù)編程。