學習兩章的筆記:
-----------------------------------------------------
FreeRTOS的任務管理:
/*任務與調度器的基本概念
任務的狀態
FreeRTOS的任務相關函數*/
任務:一個while(1)的函數,可認爲是一系列獨立任務的集合。每個任務在自己的環境中運行
調度器:在任務切入切出時保存上下文環境(寄存器值、堆棧內容)是調度器主要的職責。
搶佔式、時間片輪轉
任務狀態:就緒Reday
運行Running
阻塞Blocked
掛起Suspended (掛起的任務對調度器是不可見的,讓一個任務進入掛起狀態的唯一方法:vTaskSuspend()函數 )
常見任務函數:
1.任務延時函數: void vTaskDelay( const TickType_t xTicksToDelay )
用於阻塞延時,調用該函數後,任務將進入阻塞狀態(進入阻塞態的任務將讓出 CPU 資源)。
想使用 FreeRTOS 中的 vTaskDelay()函數必須在 FreeRTOSConfig.h 中把 INCLUDE_vTaskDelay 定義爲 1 來使能
形參xTicksToDelay延時的時長,比如系統的時鐘節拍週期爲 1ms,那麼調用 vTaskDelay(1)的延時時間則爲 1ms
該函數的延時是相對的(它的延時是等 vTaskDelay ()調用完畢後開始計算的)。
2.絕對延時函數: void vTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
const TickType_t xTimeIncrement );
它以固定頻率定期執行,而不受外部的影響,任務從上一次運行開始到下一次運行開始的時間間隔是絕對的
3.任務掛起函數: void vTaskSuspend( TaskHandle_t xTaskToSuspend )
4.任務恢復函數: void vTaskResume( TaskHandle_t xTaskToResume )
5.任務刪除函數: void vTaskDelete( TaskHandle_t xTaskToDelete )
嵌入式開發任務設計要點:
對以下了如指掌:任務優先級,任務與中斷的處理,任務的運行時間、邏輯、狀態,任務運行的上下文環境
FreeRTOS 中程序運行的上下文包括:中斷服務函數、 普通任務、空閒任務
中斷服務函數:在這個上下文環境中不能使用掛起當前任務的操作,不允許調用任何會阻塞運行的 API 函數接口
最好保持精簡短小,快進快出,一般在中斷服務函數中只做標記事件的發生,然後通知任務,讓對應任務去執行相關處理
(因爲中斷服務函數的優先級高於任何優先級的任務,如果中斷處理時間過長,將會導致整個系統的任務無法正常運行)
任務:不能出現沒有阻塞機制的死循環。因爲死循環的時候,任務不會主動讓出 CPU,低優先級的任務是不可能得到CPU使用權
所以在進行任務設計時,就應該保證任務在不活躍的時候,任務可以進入阻塞態以交出 CPU 使用權,這就需要我們自己明確知道什麼情況下讓任務進入阻塞態,保證低優先級任務可以正常運行
空閒任務(idle任務):是 FreeRTOS 系統中沒有其他工作進行時自動進入的系統任務。
當調用 vTaskStartScheduler()時,調度器會自動創建一個空閒任務,空閒任務是一個非常短小的循環 。
空閒任務是唯一一個不允許出現阻塞情況的任務,因爲 FreeRTOS 需要保證系統永遠都有一個可運行的任務。
----------------------------------------------------------------------------------------
消息隊列:任務間通信的數據結構,異步通信,不固定長度的消息
任務/中斷服務函數將一條/多條消息放入消息隊列。同樣也可以得到消息
實現異步通信:(如下特點)
消息支持先進先出方式排隊,支持異步讀寫工作方式。
讀寫隊列均支持超時機制。
消息支持後進先出方式排隊,往隊首發送消息(LIFO)。
可以允許不同長度(不超過隊列節點最大值)的任意類型消息。
一個任務能夠從任意一個消息隊列接收和發送消息。
多個任務能夠從同一個消息隊列接收和發送消息。
當隊列使用結束後,可以通過刪除隊列函數進行刪除。
運作機制:
創建消息隊列
發送消息隊列
讀取消息:讀不到會阻塞(阻塞時間用戶來制定)
消息隊列不再被使用時,應該刪除它以釋放資源
阻塞機制:
出隊
入隊
消息隊列控制塊:
消息的對應內存空間
頭指針pcHead
尾指針pcTail
消息大小uxItemSize
隊列長度uxLength
當前隊列消息個數uxMessagesWaiting
應用場景:FreeRTOS中任務和任務通信主要靠隊列
發送隊列的消息都是拷貝進行了,所以消息都是源消息而不是消息的引用
常用函數:
1.消息隊列創建函數 xQueueCreate()
函數原型:QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,UBaseType_t uxItemSize );
功能:用於創建一個新的隊列
參數:uxQueueLength 隊列能夠存儲的最大消息單元數目,即隊列長度。
uxItemSize 隊列中消息單元的大小,以字節爲單位。
返回值:如果創建成功則返回一個隊列句柄,用於訪問創建的隊列。
如果創建不成功則返回NULL,可能原因是創建隊列需要的 RAM 無法分配成功。
xQueueCreate() -> xQueueGenericCreate() -> prvInitialiseNewQueue()
分配消息隊列內存
void vInitQueue() //用戶自定義創建消息隊列函數
{
QueueHandle_t Test_Queue =NULL;
#define QUEUE_LEN 4 /* 隊列的長度,最大可包含多少個消息 */
#define QUEUE_SIZE 4 /* 隊列中每個消息大小(字節) */
BaseType_t xReturn = pdPASS;/* 定義一個創建信息返回值,默認爲 pdPASS */
taskENTER_CRITICAL(); //進入臨界區
/* 創建 Test_Queue */
Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息隊列的長度 */
(UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
if (NULL != Test_Queue)
printf("創建 Test_Queue 消息隊列成功!\r\n");
taskEXIT_CRITICAL();
}
有靜態創建和動態創建,靜態創建麻煩需要手動配置空間,動態創建簡單些需要將config中的宏配置爲1
2.消息隊列刪除函數 vQueueDelete()
根據消息隊列句柄直接刪除的,刪除之後這個消息隊列的所有信息都會被系統回收清空,而且不能再次使用這個消息隊列
3.向消息隊列發送消息函數
xQueueSend()與 xQueueSendToBack()
xQueueSendFromISR()與 xQueueSendToBackFromISR()
xQueueSendToFront()
xQueueSendToFrontFromISR()
通用消息隊列發送函數 xQueueGenericSend()(任務)
消息隊列發送函數 xQueueGenericSendFromISR()(中斷)
4.從消息隊列讀取消息函數
xQueueReceive()與 xQueuePeek()
xQueueReceiveFromISR()與 xQueuePeekFromISR()
從隊列讀取消息函數 xQueueGenericReceive()
注意事項:
1. 使用 xQueueSend()、xQueueSendFromISR()、xQueueReceive()等這些函數之前應先
創建需消息隊列,並根據隊列句柄進行操作。
2. 隊列讀取採用的是先進先出(FIFO)模式,會先讀取先存儲在隊列中的數據。當
然也 FreeRTOS 也支持後進先出(LIFO)模式,那麼讀取的時候就會讀取到後進
隊列的數據。
3. 在獲取隊列中的消息時候,我們必須要定義一個存儲讀取數據的地方,並且該數
據區域大小不小於消息大小,否則,很可能引發地址非法的錯誤。
4. 無論是發送或者是接收消息都是以拷貝的方式進行,如果消息過於龐大,可以將
消息的地址作爲消息進行發送、接收。
5. 隊列是具有自己獨立權限的內核對象,並不屬於任何任務。所有任務都可以向同
一隊列寫入和讀出。一個隊列由多任務或中斷寫入是經常的事,但由多個任務讀
出倒是用的比較少。
實驗:實驗一的兩個任務代碼 實驗二的兩個任務代碼
實驗一:一個LED任務:就是流水燈閃爍
一個KEY任務,按鍵1將LED任務掛起,按鍵2將LED任務恢復
實驗二:一個發送消息任務,按鍵1發送520,按鍵2發送1314
一個接受消息任務,接受所有發送者的消息
//FreeRTOS頭文件
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "stm32f10x.h"
//外設頭文件
#include "bsp_usart.h"
#include "bsp_led.h"
#include "bsp_key.h"
/***************************************************************
宏定義
***************************************************************/
#define QUEUE_LEN 4 //隊列的長度,最大可包含多少個消息
#define QUEUE_SIZE 4 //隊列中每個消息大小(字節)
//一個指針,用於指向任務;一個任務創建好之後就會有一個句柄,如果以後要操作這個任務就對這個句柄進行操作
/***************************************************************
任務句柄
***************************************************************/
//創建任務句柄
static TaskHandle_t AppTaskCreate_Handle = NULL;
//LED任務句柄
static TaskHandle_t LED_Task_Handle = NULL;
//Key任務句柄
static TaskHandle_t KEY_Task_Handle = NULL;
//接受/發送 消息的任務句柄
static TaskHandle_t Receive_Task_Handle = NULL;
static TaskHandle_t Send_Task_Handle = NULL;
/*內核對象句柄:信號量 消息隊列 事件標誌組 軟件定時器*/
//內核對象:就是全局的數據結構,通過調用內核對象的函數實現相應功能
//消息隊列 任務句柄
QueueHandle_t Test_Queue_Handle = NULL;
/***************************************************************
全局變量聲明
***************************************************************/
/***************************************************************
函數聲明
***************************************************************/
//硬件外設的初始化函數聲明 所有外設
static void BSP_Init();
//定義led任務函數:led流水燈的任務主體
static void LED_Task();
//定義key任務函數:Key用來檢測按下,如果K1按下就掛起LED任務,按K2就恢復led任務
static void KEY_Task();
//創建任務函數:所有的任務創建,統一放在這裏
static void AppTaskCreate();
//接受消息任務
static void Receive_Task();
//發送消息任務
static void Send_Task();
//實驗一的兩個任務:LED和KEY 任務掛起和恢復
static void TEST_1_LED_KEY();
//實驗二的任務:消息隊列 接受函數和發送函數(按下 KEY1 或者 KEY2 發送隊列消息,Receive 任務接收到消息在串口回顯)
static void TEST_2_Receive_Send();
/***************************************************************
主函數
***************************************************************/
int main(void)
{
//硬件外設初始化
BSP_Init();
printf("This is Queue_Send_Receive Test.\n\n");
BaseType_t xReturn = NULL;
//創建AppStackCreate任務
xReturn = xTaskCreate( (TaskFunction_t )AppTaskCreate,
(const char* )"AppTaskCreate",//任務名稱
(uint32_t )512, //任務堆棧大小
(void* )NULL, //傳遞給任務函數的參數
(UBaseType_t )1, //任務優先級
(TaskHandle_t* )&AppTaskCreate_Handle); //任務控制塊指針
if(NULL != xReturn) //創建成功,則進入調度
{
printf("AppTask Create OK !\n");
vTaskStartScheduler();
}
//不會執行到這裏了
while (1);
}
/***************************************************************
函數實現
***************************************************************/
static void BSP_Init()
{
//中斷優先級分組爲4
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
LED_GPIO_Config();
Key_GPIO_Config();
USART_Config();
}
static void TEST_1_LED_KEY()
{
BaseType_t xReturn = NULL;
//創建LED任務
xReturn = xTaskCreate( (TaskFunction_t) LED_Task,//任務入口
(const char*) "LED_Task",//任務名稱
(uint32_t ) 128, //任務堆棧的大小
(void *) NULL, //傳給任務函數的參數
(UBaseType_t) 2, //任務優先級
(TaskHandle_t*) &LED_Task_Handle //任務句柄
);
if(NULL != xReturn)
printf("LED_Task create OK!\n");
else
printf("LED_Task create Error!\n");
xReturn = NULL;
//創建Key任務
xReturn = xTaskCreate( (TaskFunction_t) KEY_Task,
(const char *) "KEY_Task",
(uint32_t) 256,
(void*) NULL,
(UBaseType_t) 3,
(TaskHandle_t* ) &KEY_Task_Handle
);
if(NULL != xReturn)
printf("KEY_Task create OK!\n");
else
printf("KEY_Task create Error!\n");
}
static void TEST_2_Receive_Send()
{
//創建消息隊列
Test_Queue_Handle = xQueueCreate( (UBaseType_t) QUEUE_LEN, (UBaseType_t) QUEUE_SIZE);
if(NULL != Test_Queue_Handle)
{
printf("xQueueCreate OK!\n");
}
else
{
printf("xQueueCreate ERROR!\n");
}
BaseType_t xReturn = NULL;
//創建接受消息任務
xReturn = xTaskCreate( (TaskFunction_t) Receive_Task, //任務入口
(const char*) "Receive_Task",//任務名稱
(uint32_t ) 256,
(void*) NULL,
(UBaseType_t) 2,
(TaskHandle_t* ) &Receive_Task_Handle );
if(NULL != xReturn)
printf("Receive_Task create OK!\n");
else
printf("Receive_Task create Error!\n");
xReturn = NULL;
//創建發送消息任務
xReturn = xTaskCreate( (TaskFunction_t) Send_Task, //任務入口
(const char*) "Send_Task",//任務名稱
(uint32_t ) 256,
(void*) NULL,
(UBaseType_t) 3,
(TaskHandle_t* ) &Send_Task_Handle );
if(NULL != xReturn)
printf("Send_Task create OK!\n");
else
printf("Send_Task create Error!\n");
}
static void AppTaskCreate()
{
//進入臨界區
taskENTER_CRITICAL();
//創建 實驗一的任務
TEST_1_LED_KEY();
//創建 實驗二的任務
TEST_2_Receive_Send();
vTaskDelete(AppTaskCreate_Handle);//刪除APPTaskCreate任務
taskEXIT_CRITICAL(); //退出臨界區
}
static void LED_Task()
{
while(1)
{
LED_RED;
vTaskDelay (500);//1000個tick
LED_BLUE;
vTaskDelay(500);//1000個tick
LED_GREEN;
vTaskDelay(500);//1000個tick
}
}
static void KEY_Task()
{
while(1)
{
if(KEY_ON == Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN))
{
vTaskSuspend(LED_Task_Handle);
printf("Suspend LED_Task...\n");
}
if(KEY_ON == Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN))
{
vTaskResume(LED_Task_Handle);
printf("Resume LED_Task...\n");
}
vTaskDelay(50);//這個延時不能省略
}
}
static void Receive_Task()
{
printf("come in [static void Receive_Task())]\n\n");
BaseType_t xReturn = NULL;//創建信息返回值
uint32_t recv_queue;//接受消息的變量
while(1)
{
//消息隊列句柄 接受信息 等待時間:一直等
xReturn = xQueueReceive(Test_Queue_Handle,&recv_queue,portMAX_DELAY);
if(xReturn == pdTRUE)
{
printf("Receive_Task receive data:%d\n",recv_queue);
}
else
{
printf("Receive_Task receive ERROR :0x%lx\n",xReturn);
}
}
}
static void Send_Task()//按下KET1 發送data1 ,按下KEY2 發送data2
{
printf("come in [static void Send_Task()]\n\n");
BaseType_t xReturn = NULL;//創建信息返回值
uint32_t send_data1 = 520;
uint32_t send_data2 = 1314;
while(1)
{
if(Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)
{
printf("sending data1...\n");
xReturn = xQueueSend(Test_Queue_Handle,&send_data1,0);
if(pdPASS == xReturn)
{
printf("data1 send OK!\n");
}
}
if(Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON)
{
printf("sending data2...\n");
xReturn = xQueueSend(Test_Queue_Handle,&send_data2,0);
if(pdPASS == xReturn)
{
printf("data2 send OK!\n");
}
}
vTaskDelay(50);
}
}
一個BUG改了一天...我去...
使用消息隊列的函數時,需要在參數中添加消息隊列的內核句柄,結果寫錯了這個參數成了一個任務句柄,導致運行時進入HardFault_Handle(硬件錯誤:我理解的是Linux下的段錯誤 ,越界訪問非法內存...)
感想:
這幾天在開發板上移植了一個FreeRTOS,感受就是和Linux很像,在Linux中寫程序需要了解Linux的很多系統調用,Linux中有信號量、消息隊列、進程、線程、鎖.....等等這些;FreeRTOS就像一個簡化版的Linux,不過它是實時操作系統強調實時性,這個系統中也有一些信號量、消息隊列、任務...等等,編程的時候也是要了解系統的這些函數(相當於系統調用),學會API的是使用時第一步,之後最好將系統中的這個函數實現一步一步瞭解.....
這樣一想,我確實只是學了個皮毛,就會個調用函數而已,而且就會調用一部分。。。。。。
我不禁思考,學習FreeRTOS的之後的路線是怎樣的?瞭解很多API?在公司的項目中能夠運用按時完善的寫出代碼完成任務?