【FreeRTOS】實驗:任務管理 消息隊列

學習兩章的筆記:

-----------------------------------------------------
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?在公司的項目中能夠運用按時完善的寫出代碼完成任務?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章