日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

从零入门 FreeRTOS 操作系统之创建任务流程

發布時間:2025/3/13 windows 45 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从零入门 FreeRTOS 操作系统之创建任务流程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

從零入門 FreeRTOS 操作系統之創建任務

1 啟動方式

在 main() 函數中將硬件和 RTOS 系統先初始化好,在主函數內部創建一個啟動任務后就啟動調度器,然后在啟動任務里面創建各種應用任務,當所有任務都創建成功后,啟動任務把自己刪除。

由于 AppTaskCreate 任務執行一次之后就進行刪除,并不影響系統的運行,所以,只執行一次的任務在執行完畢要記得及時刪除。

2 創建靜態內存任務

創建 SRAM 靜態內存任務的要點是:configSUPPORT_STATIC_ALLOCATION 這個宏定義必須為 1(在 FreeRTOSConfig.h 文件中)。并且任務使用的棧和任務控制塊都使用 靜態內存,即預先定義好的全局變量,這些預先定義好的全局變量都存在內部的 SRAM 中。

代碼編寫流程1

打開 FreeRTOSConfig.h 文件,將宏定義 configSUPPORT_STATIC_ALLOCATION 的值改為 1,代碼如下所示:

//支持靜態內存 #define configSUPPORT_STATIC_ALLOCATION 1

2.1 定義任務函數

任務實際上就是一個無限循環且不帶返回值的 C 函數。

注意事項1: 任務必須是一個死循環,否則任務將通過 LR 返回,如果 LR 指向了非法的內存就會產生 HardFault_Handler,而 FreeRTOS 指向一個死循環,那么任務返回之后就在死循環中執行,這樣子的任務是不安全的,所以避免這種情況,任務一般都是死循環并且無返回值的。

注意事項2: 任務里面的延時函數必須使用 FreeRTOS 里面提供的延時函數,并不能使用我們裸機編程中的那種延時。

這兩種的延時的區別是 FreeRTOS 里面的延時是阻塞延時,即調用 vTaskDelay() 函數的時候,當前任務會被掛起,調度器會切換到其它就緒的任務,從而實現多任務。如果還是使用裸機編程中的那種延時,那么整個任務就成為了一個死循環,如果恰好該任務的優先級是最高的,那么系統永遠都是在這個任務中運行,比它優先級更低的任務無法運行,根本無法實現多任務。

代碼編寫流程2

根據啟動方式,我們需要創建一個啟動任務,讓它專門負責創建各種應用任務,因此需要同時創建啟動任務、應用任務。

首先,我們在主函數的后面定義應用任務函數,如下代碼所示:

/*********************************************************************** @ 函數名 : 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");} }

通過上述代碼,可以看出此應用任務的功能是讓 LED 間隔 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(); //退出臨界區 }

此啟動任務的函數的功能就是創建應用任務,里面的代碼下面會著重講,在這只需要知道其創建應用任務即可。

同時通過上面這個函數我們還可以看到,當創建應用任務完畢之后,啟動任務自行刪除:vTaskDelete(AppTaskCreate_Handle);

為了代碼的規范性,還需將 應用任務函數、啟動任務的功能函數 在主函數的前面進行聲明。

2.2 空閑任務與定時器任務堆棧函數

用戶設定空閑(Idle)任務與定時器(Timer)任務的堆棧大小分別由 vApplicationGetIdleTaskMemory() 與 vApplicationGetTimerTaskMemory() 兩個函數實現,且必須由用戶自己分配,而不能是動態分配。

代碼編寫流程3

在主函數的前面創建如下代碼所示的 空閑、定時器任務堆棧及空閑、定時器任務控制塊 代碼:

// 聲明空閑任務、定時器任務堆棧及控制塊 /* 空閑任務任務堆棧 */ 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 : 任務堆棧大小* @author fire* @version V1.0* @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 : 任務堆棧大小* @author fire* @version V1.0* @date 2018-xx-xx***********************************************************************/ 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 系統中,每一個任務都是獨立的,他們的運行環境都單獨的保存在他們的棧空間當中。那么在定義好任務函數之后,我們還要為任務定義一個棧,目前我們使用的是靜態內存,所以任務棧是一個獨立的全局變量。

任務的棧占用的是 MCU 內部的 RAM,當任務越多的時候,需要使用的棧空間就越大,即需要使用的RAM 空間就越多。一個 MCU 能夠支持多少任務,就得看你的 RAM 空間有多少。

代碼編寫流程4

從上文可以知道,我們的 FreeRTOS 操作系統的啟動方式是:通過創建一個啟動任務,然后啟動此任務,啟動完畢之后,再創建應用任務,創建完畢自行刪除。

因此,我們需要將啟動任務、應用任務所占用的棧空間定義出來。

接下來在主函數的前面定義 啟動任務 AppTaskCreate 任務堆棧及應用任務 LED 任務堆棧,代碼如下所示:

/* AppTaskCreate 任務任務堆棧 */ static StackType_t AppTaskCreate_Stack[128]; /* LED 任務堆棧 */ static StackType_t LED_Task_Stack[128];

在大多數系統中需要做棧空間地址對齊,在 FreeRTOS 中是以 8 字節大小對齊,并且會檢查堆棧是否已經對齊,其中 portBYTE_ALIGNMENT 是在 portmacro.h 里面定義的一個宏,其值為 8,就是配置為按 8 字節對齊,當然用戶可以選擇按 1、2、4、8、16、32 等字節對齊,目前默認為 8。如果有需要,可以打開 portmacro.h 文件,找到 portBYTE_ALIGNMENT 宏定義,并更改其值。

在文件中聲明 portBYTE_ALIGNMENT 宏可以應有的值,如下所示:

#if portBYTE_ALIGNMENT == 32#define portBYTE_ALIGNMENT_MASK ( 0x001f ) #endif#if portBYTE_ALIGNMENT == 16#define portBYTE_ALIGNMENT_MASK ( 0x000f ) #endif#if portBYTE_ALIGNMENT == 8#define portBYTE_ALIGNMENT_MASK ( 0x0007 ) #endif#if portBYTE_ALIGNMENT == 4#define portBYTE_ALIGNMENT_MASK ( 0x0003 ) #endif#if portBYTE_ALIGNMENT == 2#define portBYTE_ALIGNMENT_MASK ( 0x0001 ) #endif#if portBYTE_ALIGNMENT == 1#define portBYTE_ALIGNMENT_MASK ( 0x0000 ) #endif

2.4 定義任務控制塊

定義好任務函數和任務棧之后,我們還需要為任務定義一個任務控制塊,通常我們稱這個任務控制塊為任務的身份證。在 C 代碼上,任務控制塊就是一個結構體,里面有非常多的成員,這些成員共同描述了任務的全部信息。

代碼編寫流程5

在主函數的前面定義如下代碼所示的 AppTaskCreate 、LED_Task 任務控制塊。

/* AppTaskCreate 任務控制塊 */ static StaticTask_t AppTaskCreate_TCB; /* LED_Task 任務控制塊 */ static StaticTask_t LED_Task_TCB;

2.5 靜態創建任務

一個任務的三要素是 任務主體函數、任務棧、任務控制塊。FreeRTOS 里面有一個叫靜態任務創建函數 xTaskCreateStatic(),它將任務主體函數、任務棧(靜態的)和任務控制塊(靜態的)這三者聯系在一起,讓任務可以隨時被系統啟動。

代碼編寫流程6

首先在主函數的前面定義啟動任務的任務句柄,是一個指針,用于指向一個任務,當任務創建好之后,它就具有了一個任務句柄,以后我們要想操作這個任務都需要通過這個任務句柄。

/* 創建任務句柄 */ static TaskHandle_t AppTaskCreate_Handle;

之后,在主函數內部創建如下代碼所示的啟動任務。

/* 創建 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(); /* 啟動任務,開啟調度 */

其中:

  • 任務入口函數,即任務函數的名稱,需要我們自己定義并且實現。
  • 任務名字,字符串形式,最大長度由 FreeRTOSConfig.h 中定義的 configMAX_TASK_NAME_LEN 宏指定,多余部分會被自動截掉,這里任務名字最好要與任務函數入口名字一致,方便進行調試。
  • 任務堆棧大小,單位為字,在32 位的處理器下(STM32),一個字等于 4 個字節,那么任務大小就為 128 * 4 字節。
  • 任務入口函數形參,不用的時候配置為 0 或者 NULL 即可。
  • 任務的優先級的范圍根據 FreeRTOSConfig.h 中的宏
    configMAX_PRIORITIES 決定,如果使能 configUSE_PORT_OPTIMISED_TASK_SELECTION 這個宏定義,則最多支持 32 個優先級;如果不用特殊方法查找下一個運行的任務,那么則不強制要求限制最大可用優先級數目。在 FreeRTOS 中,數值越大優先級越高,0 代表最低優先級。
  • 任務棧起始地址,只有在使用靜態內存的時候才需要提供,在使用動態內存的時候會根據提供的任務棧大小自動創建。
  • 任務控制塊指針,在使用靜態內存的時候,需要給任務初始化函數 xTaskCreateStatic() 傳遞預先定義好的任務控制塊的指針。在使用動態內存的時候,任務創建函數 xTaskCreate() 會返回一個指針指向任務控制塊,該任務控制塊是 xTaskCreate() 函數里面動態分配的一塊內存。

上面所述代碼為創建啟動任務,根據啟動方式,我們需要在啟動任務中創建更多的應用任務,代碼在定義任務函數部分已列出來。

至此,我們的配置部分已經完畢。

2.6 啟動任務

當任務創建好后,任務就處于就緒狀態(Ready),就緒態的任務可以參與操作系統的調度。但是此時任務僅僅是創建了,還未開啟任務調度器,也沒創建空閑任務與定時器任務(如果使能了 configUSE_TIMERS 這個宏定義),而這兩個任務的實現就是在啟動任務調度器中完成的。每個操作系統,任務調度器只啟動一次,之后就不會再次執行了,FreeRTOS 中啟動任務調度器的函數是 vTaskStartScheduler(),并且啟動任務調度器的時候就不會返回,從此任務管理都由 FreeRTOS 管理,此時才是真正進入實時操作系統中的第一步。

因此,需要在主函數最后添加如下代碼來開啟調度器。

if(NULL != AppTaskCreate_Handle) /* 創建成功 */vTaskStartScheduler(); /* 啟動任務,開啟調度 */

3 創建動態內存任務

動態內存,及堆,也屬于 SRAM。

任務使用的棧和任務控制塊是在創建任務的時候 FreeRTOS 動態分配的,并不是預先定義好的全局變量。

在創建 SRAM 靜態內存任務的時候,任務控制塊和任務棧的內存空間都是從內部的 SRAM 里面分配的,具體分配到哪個地址由編譯器決定。那么創建 SRAM 動態內存任務的時候,FreeRTOS 做法是在 SRAM 里面定義一個大數組,也就是堆內存,供 FreeRTOS 的動態內存分配函數使用,在第一次使用的時候,系統會將定義的堆內存進行初始化,在 FreeRTOS 提供的內存管理方案中實現(heap_1.c、heap_2.c、heap_4.c 等)。

首先,在 FreeRTOSConfig.h 文件中,定義有堆內存大小的宏為 configTOTAL_HEAP_SIZE,同時定義有宏 configSUPPORT_DYNAMIC_ALLOCATION,但是要注意這個宏
定義在使用 FreeRTOS 操作系統的時候必須開啟。

//支持動態內存申請 #define configSUPPORT_DYNAMIC_ALLOCATION 1 //系統所有總的堆大小 #define configTOTAL_HEAP_SIZE ((size_t)(36*1024))

由于我們使用 heap_4.c,因此在此文件中有如下代碼,從內部 SRAMM 里面定義一個靜態數組 ucHeap,大小由 configTOTAL_HEAP_SIZE 這個宏決定,目前定義為 36KB。要注意定義的堆大小不能超過內部 SRAM 的總大小。

static uint8_t ucHeap[ configTOTAL_HEAP_SIZE ];

同時在 heap_4.c 文件中,有如下代碼用來將堆進行初始化(如果這是第一次調用 malloc),以設置空閑塊列表,方便以后分配內存,初始化完成之后會取得堆的結束地址。

/* If this is the first call to malloc then the heap will requireinitialisation to setup the list of free blocks. */ if( pxEnd == NULL ) {prvHeapInit(); } else {mtCOVERAGE_TEST_MARKER(); }

3.1 定義任務函數

使用動態內存的時候,任務的主體函數與使用靜態內存時是一樣的,代碼如下所示:

/*********************************************************************** @ 函數名 : 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");} }

同時,要注意代碼的規范性,記得將函數在主函數前面進行聲明。

3.2 定義任務棧

使用動態內存的時候,任務棧在任務創建的時候創建,不用跟使用靜態內存那樣要預先定義好一個全局的靜態的棧空間,動態內存就是按需分配內存,隨用隨取。

3.3 定義任務控制塊指針

使用動態內存時候,不用跟使用靜態內存那樣要預先定義好一個全局的靜態的任務控制塊空間。任務控制塊是在任務創建的時候分配內存空間創建,任務創建函數會返回一個指針,用于指向任務控制塊,所以要預先為任務棧定義一個任務控制塊指針,也是我們常說的任務句柄。

在主函數的前面,預先聲明任務句柄,代碼如下所示:

/**************************** 任務句柄 ********************************/ /* * 任務句柄是一個指針,用于指向一個任務,當任務創建好之后,它就具有了一個任務句柄* 以后我們要想操作這個任務都需要通過這個任務句柄,如果是自身的任務操作自己,那么* 這個句柄可以為NULL。*/ /* 創建任務句柄 */ static TaskHandle_t AppTaskCreate_Handle = NULL; /* LED任務句柄 */ static TaskHandle_t LED_Task_Handle = NULL;

3.4 動態創建任務

使用靜態內存時,使用 xTaskCreateStatic() 函數來創建一個任務,而使用動態內存的時,則使用 xTaskCreate() 函數來創建一個任務,兩者的函數名不一樣,具體的形參也有區別。

由于啟動方式還是通過創建一個啟動任務,之后由啟動任務來創建應用任務,因此需要在主函數的內部創建啟動任務,代碼如下所示:

xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate, /* 任務入口函數 */(const char* )"AppTaskCreate", /* 任務名字 */(uint16_t )512, /* 任務棧大小 */(void* )NULL, /* 任務入口函數參數 */(UBaseType_t )1, /* 任務的優先級 */(TaskHandle_t* )&AppTaskCreate_Handle); /* 任務控制塊指針 */

代碼詳解:

  • 任務入口函數:即任務函數的名稱,需要我們自己定義并且實現。
  • 任務名字:字符串形式,最大長度由 FreeRTOSConfig.h 中定義的 configMAX_TASK_NAME_LEN 宏指定,多余部分會被自動截掉,這里任務名字最好要與任務函數入口名字一致,方便進行調試。
  • 任務堆棧大小:單位為字,在 32 位的處理器下(STM32),一個字等于 4 個字節,那么任務大小就為 128 * 4 字節。
  • 任務入口函數形參:不用的時候配置為 0 或者 NULL 即可。
  • 任務的優先級:優先級范圍根據 FreeRTOSConfig.h 中的宏
    configMAX_PRIORITIES 決定,如果使能 configUSE_PORT_OPTIMISED_TASK_SELECTION 這個宏定義,則最多支持 32 個優先級;如果不用特殊方法查找下一個運行的任務,那么則不強制要求限制最大可用優先級數目。在 FreeRTOS 中,數值越大優先級越高,0 代表最低優先級。
  • 任務控制塊指針:在使用內存的時候,需要給任務初始化函數 xTaskCreateStatic() 傳遞預先定義好的任務控制塊的指針。在使用動態內存的時候,任務創建函數 xTaskCreate() 會返回一個指針指向任務控制塊,該任務控制塊是 xTaskCreate() 函數里面動態分配的一塊內存。

3.5 啟動任務

當任務創建好后,任務處于就緒狀態(Ready),在就緒態的任務可以參與操作系統的調度。但是此時任務僅僅是創建了,還未開啟任務調度器,也沒創建空閑任務與定時器任務(如果使能了 configUSE_TIMERS 這個宏定義),那這兩個任務就是在啟動任務調度器中實現。每個操作系統,任務調度器只啟動一次,之后就不會再次執行了,FreeRTOS 中啟動任務調度器的函數是 vTaskStartScheduler(),并且啟動任務調度器的時候就不會返回,從此任務管理都由 FreeRTOS 管理,此時才是真正進入實時操作系統中的第一步。

因此,需要在上面創建啟動任務之后開啟調度器,具體代碼如下所示:

/* 啟動任務調度 */ if(pdPASS == xReturn)vTaskStartScheduler(); /* 啟動任務,開啟調度 */ elsereturn -1;

總結

以上是生活随笔為你收集整理的从零入门 FreeRTOS 操作系统之创建任务流程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。