FreeRTOS消息队列实验与出现的问题

目录

实验名字:队列操作实验

1、实验目的

2、实验设计

3、实验工程

4、实验程序与分析

●任务设置

[● 其他应用函数](#● 其他应用函数)

[● main()函数](#● main()函数)

[● 任务函数](#● 任务函数)

●中断初始化及处理过程

5.程序运行结果分析

6.进行实验移植时所遇到的问题

1.项目中mymalloc等函数缺少

2.L6218E:xTimerPendFunctionCallFromISR报错

L6218E:xTimerCreateTimerTask报错


实验名字:队列操作实验

1 、实验目的

学习使用 FreeRTOS 的队列相关 API 函数,学会如何在任务或中断中向队列发送消息或者 从队列中接收消息。

2 、实验设计

本实验设计三个任务:start_task、task1_task 、Keyprocess_task 这三个任务的任务功能如下: start_task:用来创建其他 2 个任务。

task1_task :读取按键的键值,然后将键值发送到队列 Key_Queue 中,并且检查队列的剩 余容量等信息。

Keyprocess_task :按键处理任务,读取队列 Key_Queue 中的消息,根据不同的消息值做相 应的处理。

实验需要三个按键 KEY_UP 、KEY2 和 KEY0 ,不同的按键对应不同的按键值, 任务 task1_task 会将这些值发送到队列 Key_Queue 中。

实验中创建了两个队列 Key_Queue 和 Message_Queue,队列 Key_Queue 用于传递按键值, 队列 Message_Queue 用于传递串口发送过来的消息。

实验还需要两个中断,一个是串口 1 接收中断,一个是定时器 2 中断,他们的作用如下: 串口 1 接收中断:接收串口发送过来的数据,并将接收到的数据发送到队列 Message_Queue 中。 定时器 2 中断:定时周期设置为 500ms,在定时中断中读取队列 Message_Queue 中的消息,并 将其显示在 LCD 上。

3 、实验工程

下面是实验例程,有需要自取。

通过百度网盘分享的文件:FreeRTOS实验13-1 FreeRTOS队列操作实验
链接:https://pan.baidu.com/s/19wyteow1w0eVeIwCqQB7gg
提取码:y1bf

4 、实验程序与分析

●任务设置

复制代码
//任务优先级
#define START_TASK_PRIO		1
//任务堆栈大小	
#define START_STK_SIZE 		256  
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);

//任务优先级
#define TASK1_TASK_PRIO		2
//任务堆栈大小	
#define TASK1_STK_SIZE 		256  
//任务句柄
TaskHandle_t Task1Task_Handler;
//任务函数
void task1_task(void *pvParameters);

//任务优先级
#define KEYPROCESS_TASK_PRIO 3
//任务堆栈大小	 
#define KEYPROCESS_STK_SIZE  256 
//任务句柄
TaskHandle_t Keyprocess_Handler;
//任务函数
void Keyprocess_task(void *pvParameters);

(1)、队列 Key_Queue 用来传递按键值的,也就是一个 u8 变量,所以队列长度为 1 就行了。 并且消息长度为 1 个字节。

(2)、队列 Message_Queue 用来传递串口接收到的数据,队列长度设置为 4,每个消息的长 度为 USART_REC_LEN(在 usart.h 中有定义)。

其他应用函数

在 main.c 中还有一些其他的函数,如下:

复制代码
//LCD刷屏时使用的颜色
int lcd_discolor[14]={	WHITE, BLACK, BLUE,  BRED,      
						GRED,  GBLUE, RED,   MAGENTA,       	 
						GREEN, CYAN,  YELLOW,BROWN, 			
						BRRED, GRAY };

//用于在LCD上显示接收到的队列的消息
//str: 要显示的字符串(接收到的消息)
void disp_str(u8* str)
{
	LCD_Fill(5,230,110,245,WHITE);					//先清除显示区域
	LCD_ShowString(5,230,100,16,16,str);
}

//加载主界面
void freertos_load_main_ui(void)
{
	POINT_COLOR = RED;
	LCD_ShowString(10,10,200,16,16,"ATK STM32F103/407");	
	LCD_ShowString(10,30,200,16,16,"FreeRTOS Examp 13-1");
	LCD_ShowString(10,50,200,16,16,"Message Queue");
	LCD_ShowString(10,70,220,16,16,"KEY_UP:LED1 KEY0:Refresh LCD");
	LCD_ShowString(10,90,200,16,16,"KEY1:SendMsg KEY2:BEEP");
	
	POINT_COLOR = BLACK;
	LCD_DrawLine(0,107,239,107);		//画线
	LCD_DrawLine(119,107,119,319);		//画线
	LCD_DrawRectangle(125,110,234,314);	//画矩形
	POINT_COLOR = RED;
	LCD_ShowString(0,130,120,16,16,"DATA_Msg Size:");
	LCD_ShowString(0,170,120,16,16,"DATA_Msg rema:");
	LCD_ShowString(0,210,100,16,16,"DATA_Msg:");
	POINT_COLOR = BLUE;
}

//查询Message_Queue队列中的总队列数量和剩余队列数量
void check_msg_queue(void)
{
    u8 *p;
	u8 msgq_remain_size;	//消息队列剩余大小
    u8 msgq_total_size;     //消息队列总大小
    
    taskENTER_CRITICAL();   //进入临界区
    msgq_remain_size=uxQueueSpacesAvailable(Message_Queue);//得到队列剩余大小
    msgq_total_size=uxQueueMessagesWaiting(Message_Queue)+uxQueueSpacesAvailable(Message_Queue);//得到队列总大小,总大小=使用+剩余的。
	p=mymalloc(SRAMIN,20);	//申请内存
	sprintf((char*)p,"Total Size:%d",msgq_total_size);	//显示DATA_Msg消息队列总的大小
	LCD_ShowString(10,150,100,16,16,p);
	sprintf((char*)p,"Remain Size:%d",msgq_remain_size);	//显示DATA_Msg剩余大小
	LCD_ShowString(10,190,100,16,16,p);
	myfree(SRAMIN,p);		//释放内存
    taskEXIT_CRITICAL();    //退出临界区
}

定时器 9 的中断服务函数会调用函数 disp_str()在 LCD 上显示从队列 Message_Queue 接收 到 的消 息 。 函数 freertos_load_main_ui() 就 是在屏幕上 画 出 实验 的初始 UI 界面 。 函数 check_msg_queue()用于查询队列Message_Queue 的相关信息,比如队列总大小,队列当前剩余 大小。

(1)、调用函数 uxQueueSpacesAvailable()获取队列 Message_Queue 的剩余大小。

(2)、调用函数 uxQueueMessagesWaiting()获取队列当前消息数量,也就是队列的使用量,将 其与函数 uxQueueSpacesAvailable()获取到的队列剩余大小相加就是队列的总大小。

main() 函数

复制代码
int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4	 
	delay_init();	    				//延时函数初始化	 
	uart_init(115200);					//初始化串口
	LED_Init();		  					//初始化LED
	KEY_Init();							//初始化按键
	BEEP_Init();						//初始化蜂鸣器
	LCD_Init();							//初始化LCD
	TIM2_Int_Init(5000,7200-1);			//初始化定时器2,周期500ms
	my_mem_init(SRAMIN);            	//初始化内部内存池
    freertos_load_main_ui();        	//加载主UI
	
	//创建开始任务
    xTaskCreate((TaskFunction_t )start_task,            //任务函数
                (const char*    )"start_task",          //任务名称
                (uint16_t       )START_STK_SIZE,        //任务堆栈大小
                (void*          )NULL,                  //传递给任务函数的参数
                (UBaseType_t    )START_TASK_PRIO,       //任务优先级
                (TaskHandle_t*  )&StartTask_Handler);   //任务句柄              
    vTaskStartScheduler();          //开启任务调度
}

任务函数

复制代码
//开始任务任务函数
void start_task(void *pvParameters)
{
    taskENTER_CRITICAL();           //进入临界区
	
	//创建消息队列
    Key_Queue=xQueueCreate(KEYMSG_Q_NUM,sizeof(u8));        //创建消息Key_Queue
    Message_Queue=xQueueCreate(MESSAGE_Q_NUM,USART_REC_LEN); //创建消息Message_Queue,队列项长度是串口接收缓冲区长度
	
    //创建TASK1任务
    xTaskCreate((TaskFunction_t )task1_task,             
                (const char*    )"task1_task",           
                (uint16_t       )TASK1_STK_SIZE,        
                (void*          )NULL,                  
                (UBaseType_t    )TASK1_TASK_PRIO,        
                (TaskHandle_t*  )&Task1Task_Handler);   
    //创建TASK2任务
    xTaskCreate((TaskFunction_t )Keyprocess_task,     
                (const char*    )"keyprocess_task",   
                (uint16_t       )KEYPROCESS_STK_SIZE,
                (void*          )NULL,
                (UBaseType_t    )KEYPROCESS_TASK_PRIO,
                (TaskHandle_t*  )&Keyprocess_Handler); 
    vTaskDelete(StartTask_Handler); //删除开始任务
    taskEXIT_CRITICAL();            //退出临界区
}

//task1任务函数
void task1_task(void *pvParameters)
{
	u8 key,i=0;
    BaseType_t err;
	while(1)
	{
		key=KEY_Scan(0);            	//扫描按键
        if((Key_Queue!=NULL)&&(key))   	//消息队列Key_Queue创建成功,并且按键被按下
        {
            err=xQueueSend(Key_Queue,&key,10);
            if(err==errQUEUE_FULL)   	//发送按键值
            {
                printf("队列Key_Queue已满,数据发送失败!\r\n");
            }
        }
        i++;
        if(i%10==0) check_msg_queue();//检Message_Queue队列的容量
        if(i==50)
        {
            i=0;
            LED0=!LED0;
        }
        vTaskDelay(10);                           //延时10ms,也就是10个时钟节拍	
	}
}


//Keyprocess_task函数
void Keyprocess_task(void *pvParameters)
{
	u8 num,key;
	while(1)
	{
        if(Key_Queue!=NULL)
        {
            if(xQueueReceive(Key_Queue,&key,portMAX_DELAY))//请求消息Key_Queue
            {
                switch(key)
                {
                    case WKUP_PRES:		//KEY_UP控制LED1
                        LED1=!LED1;
                        break;
                    case KEY2_PRES:		//KEY2控制蜂鸣器
                        BEEP=!BEEP;
                        break;
                    case KEY0_PRES:		//KEY0刷新LCD背景
                        num++;
                        LCD_Fill(126,111,233,313,lcd_discolor[num%14]);
                        break;
                }
            }
        } 
		vTaskDelay(10);      //延时10ms,也就是10个时钟节拍	
	}
}

(1)、在使用队列之前要先创建队列,调用函数 xQueueCreate()创建队列 Key_Queue,队列 长度为 1,每个队列项(消息)长度为 1 个字节。

(2) 、同样的创建队列 Message_Queue ,队列长度为 4 ,每个队列项 ( 消息) 长度为 USART_REC_LEN ,USART_REC_LEN 为 50。

(3)、获取到按键键值以后就调用函数 xQueueSend()发送到队列Key_Queue 中,由于只有一 个队列 Key_Queue 只有一个队列项,所以此处也可以用覆写入队函数 xQueueOverwrite()。

(4)、调用函数 check_msg_queue()检查队列信息,并将相关的信息显示在 LCD 上,如队列 总大小,队列剩余大小等。

(5)、调用函数 xQueueReceive()获取队列 Key_Queue 中的消息

(6)、变量 key 保存着获取到的消息,也就是按键值,这里根据不同的按键值做不同的处理。

●中断初始化及处理过程

本实验用到了两个中断,串口 1 的接收中断和定时器 9 的定时中断,串 口 1 的具体配置看 基础例程中的串口实验就可以了,这里要将串口中断接收缓冲区大小改为 50,如下:

#define USART_REC_LEN 50 //定义最大接收字节数 50
#define EN_USART1_RX 1 //使能(1)/禁止(0)串口1接收

还要注意!由于要在中断服务函数中使用 FreeRTOS 中的 API 函数,所以一定要注意中断 优先级的设置,这里设置如下:

复制代码
void uart_init(u32 bound)
{
	//GPIO端口设置
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
	//USART1_RX	  GPIOA.10初始化
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
	GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

	//Usart1 NVIC 配置
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=7 ;//抢占优先级7
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;		//子优先级0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
	//USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//收发模式

	USART_Init(USART1, &USART_InitStructure); //初始化串口1
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
	USART_Cmd(USART1, ENABLE);                    //使能串口1 
}

注意串口中断优先级设置,这里设置抢占优先级为 7,子优先级为 0。这个优先 级中可以调用 FreeRTOS 中的 API 函数。串口 1 的中断服务函数如下:

复制代码
void USART1_IRQHandler(void)                	//串口1中断服务程序
{
	u8 Res;
	BaseType_t xHigherPriorityTaskWoken;
	
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
	{
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
	
	 //就向队列发送接收到的数据
	if((USART_RX_STA&0x8000)&&(Message_Queue!=NULL))
	{
		xQueueSendFromISR(Message_Queue,USART_RX_BUF,&xHigherPriorityTaskWoken);//向队列中发送数据
		
		USART_RX_STA=0;	
		memset(USART_RX_BUF,0,USART_REC_LEN);//清除数据接收缓冲区USART_RX_BUF,用于下一次数据接收
	
		portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
	}
} 

(1)、判断是否接收到数据,如果接收到数据的话就要将数据发送到队列Message_Queue 中,

(2)、调用函数 xQueueSendFromISR()将串口接收缓冲区 USART_RX_BUF[]中接收到的数据 发送到队列 Message_Queue 中。

(3)、发送完成以后要将串口接收缓冲区 USART_RX_BUF[]清零。

(4) 、 如 果 需 要 进 行 任 务 调 度 的 话 在 退 出 串 口 中 断 服 务 函 数 之 前 调 用 函 数 portYIELD_FROM_ISR()进行一次任务调度。

在定时 2 的中断服务函数中请求队列 Message_Queue 中的数据并将请求到的消息显示在 LCD 上,定时器 2 的定时周期设置为 500ms,定时器初始化很简单,唯一要注意的就是中断优 先级的设置,如下:

//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 8; //先占优先级4级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器

定时器 2 的中断服务函数是重点,代码如下:

复制代码
extern QueueHandle_t Message_Queue;	//信息队列句柄
extern void disp_str(u8* str);

//定时器2中断服务函数
void TIM2_IRQHandler(void)
{
	u8 *buffer;
	BaseType_t xTaskWokenByReceive=pdFALSE;
	BaseType_t err;
	
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET) //溢出中断
	{
		buffer=mymalloc(SRAMIN,USART_REC_LEN);
        if(Message_Queue!=NULL)
        {
			memset(buffer,0,USART_REC_LEN);	//清除缓冲区
			err=xQueueReceiveFromISR(Message_Queue,buffer,&xTaskWokenByReceive);//请求消息Message_Queue
            if(err==pdTRUE)			//接收到消息
            {
				disp_str(buffer);	//在LCD上显示接收到的消息
            }
        }
		myfree(SRAMIN,buffer);		//释放内存
		
		portYIELD_FROM_ISR(xTaskWokenByReceive);//如果需要的话进行一次任务切换
	}
	TIM_ClearITPendingBit(TIM2,TIM_IT_Update);  //清除中断标志位
}

(1)、从队列中获取消息也是采用数据拷贝的方式,所以我们要先准备一个数据缓冲区用来 保存从队列中获取到的消息。这里通过动态内存管理的方式分配一个数据缓冲区,这个动态内 存管理方法是 ALIENTEK 编写的,具体原理和使用方法请参考基础例程中的内存管理实验。当 然了,也可以使用 FreeRTOS 提供的动态内存管理函数。直接提供一个数组也行,这个数据缓 冲区的大小一定要和队列中队列项大小相同,比如本例程就是 USART_REC_LEN。

(2) 、清除缓冲区。

(3)、调用函数 xQueueReceiveFromISR()从队列 Message_Queue 中获取消息。

(4)、如果获取消息成功的话就调用函数 disp_str()将获取到的消息显示在 LCD 上。

(5)、使用完成以后就释放(3)中申请到的数据缓冲区内存。

(6)、 如 果 需 要 进 行 任 务 调 度 的 话 在 退 出 定 时 器 的 中 断 服 务 函 数 之 前 调 用 函 数 portYIELD_FROM_ISR()进行一次任务调度。

5.程序运行结果分析

编译并下载实验代码到开发板中,打开串口调试助手,LCD 默认显示如图;

通过串口调试助手给开发板发送一串字符串,比如"ALIENTEK " ,由于定时器 9 会周期 性的读取队列 Message_Queue 中的数据,当读取成功以后就会将相应的数据显示在 LCD 上, 所以 LCD 上会显示字符串"ALENTEK "

通过串口向开发板发送数据的时候注意观察队列 Message_Queue 剩余大小的变化,最后按 不同的按键看看有什么反应,是否和我们的代码中设置的相同

6.进行实验移植时所遇到的问题

1.项目中mymalloc等函数缺少

原因:源文件缺少malloc.c的相关代码,相关代码在例程中被包含在MALLOC文件夹里,移植时按需要添加。

2.L6218E:xTimerPendFunctionCallFromISR报错

L6218E:xTimerCreateTimerTask报错

解决方法,

1.查看( configUSE_TRACE_FACILITY == 1 ) && ( INCLUDE_xTimerPendFunctionCall == 1 ) && ( configUSE_TIMERS == 1 )是否成立,需要Ctrl+F 强制寻找,如果没有上面三个函数没有定义或没有置为1,则将他们置为1.

2.将FreeRTOS附加代码timers.c添加到工程文件中,即可解决该问题。

相关推荐
cjy_Somnr1 天前
keil5报错显示stm32的SWDIO未连接不能烧录
stm32·单片机·嵌入式硬件
Lay_鑫辰1 天前
西门子诊断-状态和错误位(“轴”工艺对象 V1...3)
服务器·网络·单片机·嵌入式硬件·自动化
无垠的广袤1 天前
【工业树莓派 CM0 NANO 单板计算机】本地部署 EMQX
linux·python·嵌入式硬件·物联网·树莓派·emqx·工业物联网
雲烟1 天前
嵌入式设备EMC安规检测参考
网络·单片机·嵌入式硬件
泽虞1 天前
《STM32单片机开发》p7
笔记·stm32·单片机·嵌入式硬件
田甲1 天前
【STM32】 数码管驱动
stm32·单片机·嵌入式硬件
up向上up1 天前
基于51单片机垃圾箱自动分类加料机快递物流分拣器系统设计
单片机·嵌入式硬件·51单片机
单片机日志2 天前
【单片机毕业设计】【mcugc-mcu826】基于单片机的智能风扇系统设计
stm32·单片机·嵌入式硬件·毕业设计·智能家居·课程设计·电子信息
松涛和鸣2 天前
从零开始理解 C 语言函数指针与回调机制
linux·c语言·开发语言·嵌入式硬件·排序算法
小曹要微笑2 天前
STM32F7 时钟树简讲(快速入门)
c语言·stm32·单片机·嵌入式硬件·算法