如果不想看的可以直接使用git把我的代碼下載出來,里面工程挺全的,后期會慢慢的補注釋之類的
碼云地址:stm32學習筆記: stm32學習筆記源碼
如果不會使用git快速下載可以選擇直接下載壓縮包或者去看看git的使用
Git入門教程-CSDN博客
一? 調度機制
1.1 任務調度介紹
在創建任務之前,我們先熟悉下調度機制
在freertos中,我們一般使用的就是搶占式+時間片
在不同優先級的情況下:默認為搶占式,就是誰優先級高,優先級高的任務進入就緒態后可以直接執行。
相同優先級下:使用時間片調度,在時間片調度下每個任務會固定執行一個時間片
攜程式調度:這個官方自己不更新了,并且不管是什么優先級的任務,如果任務不釋放CPU任務就會一直繼續運行。所以我感覺沒那么方便控制,這邊就不寫了,有興趣了的可以自己去試試。
1.1.1 搶占式調度
搶占式調度,需要任務在不同優先級下才能體現出來。
1 高優先級會先執行? ?2高優先級任務不停止,低優先級任務就不會執行? ?3被搶占的任務會進入就緒態(什么是就緒態后面會寫,可以直接跳目錄到任務狀態那里去)
這里有個示例:如大家可以看看上面的圖。
這邊我們寫個代碼測試一下:(先看看效果,具體實現后續代碼會細講)
/* START_TASK 任務 配置* 包括: 任務句柄 任務優先級 堆棧大小 創建任務*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );/* TASK1 任務 配置* 包括: 任務句柄 任務優先級 堆棧大小 創建任務*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );/* TASK2 任務 配置* 包括: 任務句柄 任務優先級 堆棧大小 創建任務*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );void freertos_demo(void)
{ xTaskCreate((TaskFunction_t ) start_task,(char * ) "start_task",(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,(void * ) NULL,(UBaseType_t ) START_TASK_PRIO,(TaskHandle_t * ) &start_task_handler );vTaskStartScheduler();
}void start_task( void * pvParameters )
{taskENTER_CRITICAL(); /* 進入臨界區 */xTaskCreate((TaskFunction_t ) task1,(char * ) "task1",(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(TaskHandle_t * ) &task1_handler );xTaskCreate((TaskFunction_t ) task2,(char * ) "task2",(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL);taskEXIT_CRITICAL(); /* 退出臨界區 */
}void task1( void * pvParameters )
{while (1){printf("task 1\r\n");vTaskDelay(pdMS_TO_TICKS(500));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");for(int i=0;i<10000000;i++){}}
}
這里我們使用xTaskCreate() 創建了3個任務,參數看不懂沒關系,后面會細講,先了解下調度機制和任務狀態
分別是一個啟動任務:這個啟動任務在去創建了兩個任務,創建完成之后刪除自身,防止任務被重復創建
兩個任務:
task1:優先級為2? 打印一個task 1 ,之后釋放CPU500ms
task1:優先級為2? 打印一個task 1 ,之后釋放CPU500ms
?taskENTER_CRITICAL(); ? ? ? ? ? ? ? /* 進入臨界區 */
taskEXIT_CRITICAL(); ? ? ? ? ? ? ? ?/* 退出臨界區 */
這兩個函數是防止創建任務之后任務直接被啟動,比如task1創建之后,不管后面的task2優先級有多高,task都會先運行一下,直到task2創建完成,搶占他的cpu,我們等會也會對其關閉測試。
1 高優先級任務不釋放CPU(開啟臨界區了的)
這個就是我們上面的代碼跑出來的結果,搶占式調度:高優先級的任務如果不釋放CPU那么低優先級的任務永遠也執行不了
2 高優先級的任務不釋放CPU(關閉臨界區了的)
void start_task( void * pvParameters )
{//taskENTER_CRITICAL(); /* 進入臨界區 */xTaskCreate((TaskFunction_t ) task1,(char * ) "task1",(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(TaskHandle_t * ) &task1_handler );xTaskCreate((TaskFunction_t ) task2,(char * ) "task2",(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL);//taskEXIT_CRITICAL(); /* 退出臨界區 */
}
我們上面屏蔽臨界區代碼之后產生的效果,如上所說,不管優先級怎么樣,在task2創建出來之前task1會直接執行。
3 高優先級任務加入CPU釋放(開啟臨界區了的)
void task2( void * pvParameters )
{while (1){printf("task 2\r\n");vTaskDelay(pdMS_TO_TICKS(500));}
}
可以明顯的看見,我們這里task2運行之后釋放掉了CPU,之后task運行。
4 高優先級任務不釋放cpu(開啟臨界區了的)
這里大家根據1.1.1的搶占式調度的任務圖,大家覺得會怎么樣?是task1一直運行,還是說task進入就緒態后直接搶占task1?
void task1( void * pvParameters )
{while (1){printf("task 1\r\n");for(int i=0;i<10000000;i++){}}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");vTaskDelay(pdMS_TO_TICKS(500));}
}
這里很明顯,在task2執行完成后,釋放掉cpu,這時候task1運行,結論就是低優先級的就算不釋放cpu高優先級的一樣占用。在經過500ms之后,task2又進入了就緒態,直接搶占了task1,這時候task1的for循環都還沒有執行完,等task1的for循環執行完成后,又能執行打印task1了。
這個就是搶占式調度的測試了。
1.1.2 時間片調度
同等優先級的任務會輪流使用CPU(一般是1個時間片)
task1 task2 task3只使用一個時間片,如果task1第一次只使用了0.1個時間片,第二次并不會給他補0.9(這樣做會讓cpu等待的時間累積的越來越多,很不合理)
這里我們可以再次測試
首先將優先級改為一致
/* TASK1 任務 配置* 包括: 任務句柄 任務優先級 堆棧大小 創建任務*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );/* TASK2 任務 配置* 包括: 任務句柄 任務優先級 堆棧大小 創建任務*/
#define TASK2_PRIO 2
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
之后測試:
1.不釋放CPU
void task1( void * pvParameters )
{while (1){printf("task 1\r\n");//for(int i=0;i<10000000;i++){}//vTaskDelay(pdMS_TO_TICKS(500));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");//for(int i=0;i<10000000;i++){}//vTaskDelay(pdMS_TO_TICKS(500));}
}
這里會怎么樣呢?我的一個時間片是1ms,先運行task1之后,這里會一直循環打印task1,在運行1ms之后,運行task2
這里可以看見,時間戳都給我干卡死了,并且已經全亂碼了,在一個時間片來回切換串口資源導致串口已經亂掉了。
2 釋放CPU
這里釋放了1ms cpu但是還是肉眼可見的亂碼了
之后直接釋放了10個時間片打印才正常(這里就引出了一個問題,過快的訪問外設資源導致的亂碼問題,這種時候我們需要添加一個互斥鎖這個后面互斥鎖章節說)
這里兩種調度模式就說完了,時間片調度就是這個效果了,如果不釋放cpu就是1ms內循環執行(這里上面的任務圖說的是1ms內沒執行完就丟掉,但是我們這里是在一個while循環中,并不會說執行完就完了,所以就會循環執行)
1.2 任務狀態
1.2.1 任務狀態轉換圖介紹
在freertos中任務有四種狀態:掛起、就緒、運行、阻塞
所有狀態想進入運行態都需要先進入就緒態
掛起態:運行態的時候或者就緒態的時候調用函數vtasksuspend
就緒態:cpu釋放的時間到了(阻塞態的時間結束了),就會進入就緒態。掛起態直接使用函數也能進入就緒態
阻塞態:就是上面的vTaskDelay(pdMS_TO_TICKS(500));這種類型的函數
運行態:就緒態列表的第一個任務就會進入運行態(會按優先級排序)
二 創建任務的函數與整體框架
2.1 整體框架
2.1.1main函數
main函數:單片機最先進入的c語言部分
int main(void)
{ //LCD 初始化ILI9341_Init (); /* USART config */USART_Config(); //其中0、3、5、6 模式適合從左至右顯示文字,//不推薦使用其它模式顯示文字 其它模式顯示文字會有鏡像效果 //其中 6 模式為大部分液晶例程的默認顯示方向 ILI9341_GramScan ( 6 );freertos_demo();
}
這里有個freertos_demo函數,就是創建任務的函數,上面的這些都是外設初始化,其實這些外設初始化也可以使用一個單獨的函數來初始化,但是需要注意各個外設的開啟·時間,防止硬件不執行。
2.1.2 freertos_demo函數
freertos_demo函數內容一個創建任務函數和啟動任務調度器
啟動任務調度器必須在所有初始化工作(如硬件初始化、任務創建、信號量創建等)完成后調用,且整個系統中只能調用一次。這個start_task就是一個典型的啟動任務了。里面包含所有任務,在創建完任務后,就執行任務調度器即可。
void freertos_demo(void)
{ xTaskCreate((TaskFunction_t ) start_task,(char * ) "start_task",(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,(void * ) NULL,(UBaseType_t ) START_TASK_PRIO,(TaskHandle_t * ) &start_task_handler );vTaskStartScheduler();
}
2.1.3?start_task函數
start_task:創建所有的任務,方便統一管理
start_task
?是典型的?“啟動任務”(或叫初始化任務),它的唯一職責是:
- 集中創建其他業務任務(如?
task1
、task2
)。 - 完成初始化后立即刪除自己,釋放系統資源(棧空間、任務控制塊等)。
void start_task( void * pvParameters )
{taskENTER_CRITICAL(); /* 進入臨界區 */xTaskCreate((TaskFunction_t ) task1,(char * ) "task1",(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(TaskHandle_t * ) &task1_handler );xTaskCreate((TaskFunction_t ) task2,(char * ) "task2",(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL);taskEXIT_CRITICAL(); /* 退出臨界區 */
}
2.1.4 task函數
task:具體任務的處理,你需要處理數據在其內部處理即可
void task1( void * pvParameters )
{while (1){printf("task 1\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}
這里就是整個代碼的大概框架了
2.2 本章使用到的函數講解
2.2.1 動態創建任務函數
xTaskCreate:函數原型如下
動態與靜態的區別:
動態:任務的任務控制塊以及任務的棧空間所需的內存,均由 FreeRTOS 從 FreeRTOS 管理的堆中分配
靜態:任務的任務控制塊以及任務的棧空間所需的內存,需用戶分配提供
這里來說一下函數原型需要傳入的參數:PS文章為使用講解哈,不涉及函數的實現,這個可以在后面的系統設計講解
1 pxTaskCode?指向任務函數的指針
就是把你函數的名字傳進去即可
2 pcName
給任務起一個名稱,主要用于調試和可視化分析。這個名稱可以幫助開發者在調試過程中,通過調試工具(如 FreeRTOS 的可視化調試插件)快速識別不同的任務
3 uxStackDepth
指定任務棧的大小:單位是字,不是字節寫128的話就是128 * 4
?字節
4?pvParameters
用于向任務函數傳遞參數。當任務函數需要接收外部數據時,可以通過這個參數傳入:我們這里沒有傳入東西,還有后面用到的時候理解
5?uxPriority
優先級:越大越高理論上軟件可以開任意優先級數量,但是由于硬件等限制,在配置文件中一般都寫的32
6?pxCreatedTask
是一個指向任務句柄的指針。用于獲取新創建任務的句柄。任務句柄是任務的唯一標識,通過任務句柄可以在運行時對任務進行管理,比如掛起、恢復、刪除任務等操作。
類型定義為TaskHandle_t即可
2.2.2?vTaskDelete()函數
就是刪除任務:傳入句柄即可,本文使用:創建完task1和taks2后刪除start任務,如果是刪除自身,函數內田NULL即可刪除自己,如果是在自己函數內需要刪除其他任務,就需要填寫對應的句柄,句柄保存了任務的內存地址。
2.2.3 vTaskStartScheduler()函數
啟動任務調度器函數
啟動任務調度器必須在所有初始化工作(如硬件初始化、任務創建、信號量創建等)完成后調用,且整個系統中只能調用一次。這個start_task就是一個典型的啟動任務了。里面包含所有任務,在創建完任務后,就執行任務調度器即可。
2.2.4 臨界區函數
taskENTER_CRITICAL(); ? ? ? ? ? ? ? /* 進入臨界區 */
taskEXIT_CRITICAL(); ? ? ? ? ? ? ? ?/* 退出臨界區 */
進入臨界區后會停止調度,這時候低優先級的任務就算先創建了也不會先運行了。
三 附上本文代碼(同步上傳gitee了)
不想下載的可以直接復制
#include "FreeDome.h"/* START_TASK 任務 配置* 包括: 任務句柄 任務優先級 堆棧大小 創建任務*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );/* TASK1 任務 配置* 包括: 任務句柄 任務優先級 堆棧大小 創建任務*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );/* TASK2 任務 配置* 包括: 任務句柄 任務優先級 堆棧大小 創建任務*/
#define TASK2_PRIO 2
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );// 全局共享常量(所有任務共用)
#define CHAR_WIDTH WIDTH_CH_CHAR // 中文字符寬度
#define SCREEN_WIDTH LCD_X_LENGTH // 屏幕寬度
#define FONT_HEIGHT LCD_GetFont()->Height // 字體高度void freertos_demo(void)
{ xTaskCreate((TaskFunction_t ) start_task,(char * ) "start_task",(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,(void * ) NULL,(UBaseType_t ) START_TASK_PRIO,(TaskHandle_t * ) &start_task_handler );vTaskStartScheduler();
}void start_task( void * pvParameters )
{taskENTER_CRITICAL(); /* 進入臨界區 */xTaskCreate((TaskFunction_t ) task1,(char * ) "task1",(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK1_PRIO,(TaskHandle_t * ) &task1_handler );xTaskCreate((TaskFunction_t ) task2,(char * ) "task2",(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,(void * ) NULL,(UBaseType_t ) TASK2_PRIO,(TaskHandle_t * ) &task2_handler ); vTaskDelete(NULL);taskEXIT_CRITICAL(); /* 退出臨界區 */
}void task1( void * pvParameters )
{while (1){printf("task 1\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}void task2( void * pvParameters )
{while (1){printf("task 2\r\n");//for(int i=0;i<10000000;i++){}vTaskDelay(pdMS_TO_TICKS(10));}
}
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./lcd/bsp_ili9341_lcd.h"
#include "./flash/bsp_spi_flash.h"
#include "FreeDome.h"int main(void)
{ //LCD 初始化ILI9341_Init (); /* USART config */USART_Config(); //其中0、3、5、6 模式適合從左至右顯示文字,//不推薦使用其它模式顯示文字 其它模式顯示文字會有鏡像效果 //其中 6 模式為大部分液晶例程的默認顯示方向 ILI9341_GramScan ( 6 );freertos_demo();
}