【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?在公司的项目中能够运用按时完善的写出代码完成任务?

你可能感兴趣的:(嵌入式)