【FreeRTOS学习 - 消息队列学习】

FreeRTOS全部项目自取链接:

https://gitee.com/chenshao777/free-rtos_-study


本文章一共分为一下几个部分

1. 创建队列

2. 写队列

3. 读队列

4. 队列阻塞访问

5. 分辨数据源头

6. 传输大块数据


1. 创建队列

步骤: 包含队列头文件 -> 定义队列句柄全局变量 -> 在main函数中创建队列

#include "queue.h"  //首先包含一下队列的头文件

/* 定义 QueueHandle_t 类型变量,用于保存队列句柄 */
QueueHandle_t xQueue;
int main()
{
    
    
	/* 创建队列: 长度为5,数据大小为4字节(存放一个整数) */
    xQueue = xQueueCreate( 5, sizeof( int32_t ) );
    //.....
}

重点:
xQueueCreate 函数的第一个参数是 栈的长度,第二个参数是栈内每个数据的长度
该函数会返回一个队列句柄,写队列和读队列就是通过这个句柄来进行操作

--------主函数代码(对应 2. 写队列3. 读队列--------

int main()
{
    
    
	SysTick_Init(72);
	USART1_Init(115200);
	printf("串口初始化成功!\r\n");

	/* ------主要是下面的代码,上面是初始化------ */

	/* 创建队列: 长度为5,数据大小为4字节(存放一个整数) */
    xQueue = xQueueCreate( 5, sizeof( int32_t ) );
    /* 创建三个任务写队列,并传递写入的参数 */
	xTaskCreate(TaskSend, "taskSend1", 200, (void*)100, 1, NULL);
	xTaskCreate(TaskSend, "taskSend2", 200, (void*)110, 1, NULL);
	xTaskCreate(TaskSend, "taskSend3", 200, (void*)120, 1, NULL);
	
	/* 创建一个任务读队列 */
	xTaskCreate(TaskRead, "TaskRead", 200, NULL, 2, NULL);

	/* 启动调度器,任务开始执行 */
	vTaskStartScheduler();          //开启任务调度
}


2. 写队列

写队列任务代码,使用的 xQueueSendToBack 函数,数据写入队列尾

/*
 * 写队列
*/
void TaskSend(void *pvParameters)
{
    
    
	int param;
	BaseType_t status;
	/* 将传递参数强转为 int 型 */
	param = (int)pvParameters;

	for(;;)
	{
    
    
		/* 写队列,参数:目标队列的句柄, 发送数据的指针, 阻塞超时时间*/
		status = xQueueSendToBack(Queue_t, &param, 0);
		/* 判断是否写入成功 */
		if(status == pdPASS){
    
    
			printf("write success!\r\n");
		}
		
		/* 允许其它发送任务执行。 taskYIELD()通知调度器现在就切换到其它任务,而不必等到本任务的时间片耗尽 */ 
		taskYIELD(); 
		
		/* 不能使用该延时函数,否则当队列满后,只有第一个写任务可以得到调度,后面的写任务会被饿死 */
		//vTaskDelay(1); 
	}
}

3. 读队列

读队列代码,使用的 xQueueReceive 函数,从队头读取一个数据,并将其删除

/*
 * 读队列
*/
void TaskRead(void *pvParameters)
{
    
    
	int read_data;   //定义一个int类型变量,用于读取队列后存放数据
	BaseType_t result;
	BaseType_t count;

	for(;;)
	{
    
    
		/* 查询队列中数据个数 */
		//count = uxQueueMessagesWaiting(Queue_t);
		//printf("c = %d\r\n", count);
		
		/* 读队列 */
		// 50表示当没有读到数据时,等待超时时间为50ms
		// 我的项目FreeRTOS默认一个节拍中断为1ms,可以通过portTICK_PERIOD_MS宏查看
		result = xQueueReceive(Queue_t, &read_data, 50);   
		if(result == pdPASS){
    
    
			printf("Read:  %d\r\n", read_data);
		}
		else
			printf("Read NULL\r\n");
	}
}

4. 队列阻塞访问

队列阻塞访问有两种情况

一、写队列时发现队列已经满了
二、读队列时发现队列是空的

我们发现 写队列函数读队列函数 的最后一个参数都可以传入一个 超时时间

情况一:

写队列时发现队列满了,如果设置了超时时间,则任务进入阻塞状态
-------->>>
在超时时间内,如果有 其他任务读取了队列,则会空出位置,那么会第一时间 唤醒正在阻塞的写队列任务
-------->>>
那么唤醒哪一个呢(唤醒优先级最高的、同优先级内唤醒等待时间最长的

情况二:

读队列时发现队列空,如果设置了超时时间,则任务进入阻塞状态
-------->>>
在超时时间内,如果有 其他任务写队列了,则队列里有数据了,那么会第一时间 唤醒正在阻塞的读任务
-------->>>
那么唤醒哪一个呢(唤醒优先级最高的、同优先级内唤醒等待时间最长的

情况三:

如果超时时间设置为0,则读不到数据(队列为空)或者写入失败(队列满),则立即返回不等待。

情况四:

如果把 xTicksToWait 设置为 portMAX_DELAY ,并且在FreeRTOSConig.h 中设定INCLUDE_vTaskSuspend 为 1,那么阻塞等待将没有超时限制,一直等。


5. 分辨数据源头

分辨数据源的方法: 使用结构体

/* 定义数据结构体:包含数据源ID & 数据 */
typedef struct{
    
    
	char id;       // 用于标识数据来源
	uint32_t data;
}SendStruct;

定义一个结构体,结构体成员包括 id 和 数据变量, 其中 id 用于标识数据来源

全部代码

#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "SysTick.h"
#include "queue.h"
#include "string.h"


/* 定义数据结构体:包含数据源ID & 数据 */
typedef struct{
    
    
	char id;
	uint32_t data;
}SendStruct;

#define	 task1_id     1 
#define  task2_id     2 
#define  task1_data   10 
#define  task2_data   20 

/* 
*  任务发送到队列数据的结构体实例 
*  包括每个任务的id和数据
*/
const SendStruct xSend_Buff[2] = 
{
    
    
	{
    
    task1_id, task1_data},
	{
    
    task2_id, task2_data}
};

/* 创建队列返回句柄 */
QueueHandle_t Queue_t;

/*
* 写队列
*/
void TaskSend(void *pvParameters)
{
    
    
	BaseType_t status;

	for(;;)
	{
    
    
		/* 写队列,参数:目标队列的句柄, 发送数据的指针, 阻塞超时时间*/
		status = xQueueSendToBack(Queue_t, pvParameters, 10);
		
		/* 如果写队列失败,则打印信息 */
		if(status != pdPASS){
    
    
			printf("send fail %d \r\n", ((SendStruct *)pvParameters)->id);
		}
		/* 允许其它发送任务执行。 taskYIELD()通知调度器现在就切换到其它任务
		   而不必等到本任务的时间片耗尽 */ 
//		taskYIELD(); 
	}
}

/*
 * 读队列
*/
void TaskRead(void *pvParameters)
{
    
    
	SendStruct read_data;
	BaseType_t result;
	for(;;)
	{
    
    
		/* 读队列 */
		result = xQueueReceive(Queue_t, &read_data, 10);
		/* 读到了数据 */
		if(result == pdPASS){
    
    
			/* 输出数据来源 和 数据内容 */
			printf("ID: %d  DATA: %d\r\n", read_data.id, read_data.data);
		}else
			printf("Read NULL\r\n");
	}
}


int main()
{
    
    
	SysTick_Init(72);
	USART1_Init(115200);
	printf("串口初始化成功!\r\n");
	
	/*  创建队列, 用于保存最多5个值,每个数据单元都有足够的空间来存储一个 int 型变量  */
	Queue_t = xQueueCreate(5, sizeof(SendStruct));
	if(Queue_t != NULL)
		printf("队列创建成功!\r\n");
	
	/* 创建任务写队列 */
	xTaskCreate(TaskSend, "taskSend1", 200, (void *)&(xSend_Buff[0]), 3, NULL);
	xTaskCreate(TaskSend, "taskSend2", 200, (void *)&(xSend_Buff[1]), 3, NULL);
	
	/* 创建任务读队列 */
	xTaskCreate(TaskRead, "TaskRead", 200, NULL, 4, NULL);
	
	/* 启动调度器,任务开始执行 */
	vTaskStartScheduler();          //开启任务调度
}

6. 传输大块数据

传输大块数据的方法: 传输指针

定义一个大容量数组

写队列时只需写入一个指向该数组的指针的地址即可
这里注意,写入的是指向该数组的指针的地址
如果直接传入数组名(也就是数组的首地址)是不行的

举个例子
定义一个 sendBuff 数组,然后将该数组首地址写入队列,读队列时读取到的就是数组首地址了

QueueHandle_t xQueue;
char sendBuff[200];  // 全局缓冲区数组
......
......
void taskSend(void *pvParameters)
{
    
    
	static int count = 0;
	char *buff;
	for(;;)
	{
    
    
		/* 向缓冲区里放数据 */
		sprintf(sendBuff, "My name is ChenShao %d\r\n", count++);
		buff = sendBuff;  //应该加这一句,让一个指针指向该数组
		/* 写队列 */
		//xQueueSendToBack(xQueue, sendBuff, 0); //错误写法
		//xQueueSendToBack(xQueue, &sendBuff, 0); //错误写法
		//xQueueSendToBack(xQueue, buff, 0);    //错误写法
		xQueueSendToBack(xQueue, &buff, 0);     //正确写法
	}
}
......
......

以下这三种写法都不行

  1. xQueueSendToBack(xQueue, sendBuff, 0); //错误写法
  2. xQueueSendToBack(xQueue, &sendBuff, 0); //错误写法
  3. xQueueSendToBack(xQueue, buff, 0); //错误写法

只能这样写

  1. xQueueSendToBack(xQueue, &buff, 0); //正确写法

个人理解:
因为 xQueueSendToBack 函数的第二个参数需要传入一个指针

并且这个指针本身不存储数据,它只是指向一片存储数据的内存


从而可以从那片内存中拷贝数据到队列里

所以,如果按照前三种写法,实际上传入的是 sendBuff数组的 首地址,但是这个首地址 并不指向任何内存,它只是用来存储我们传输的数据的。

将这四个传输的地址打印出来分析

在这里插入图片描述
可以看到 图中1 2 3 打印出的都是 sendBuff 的首地址
此时给 xQueueSendToBack 函数的第二个参数是 &sendBuff ,可以看到程序只打印出 “read data :” 就没了
左边的报错是 “*** error 65: access violation at 0x6E20794D : no ‘read’ permission”
表示内存地址访问没有权限

但是如果 将第二个参数改为 &buff , 则可以顺利运行,如下图所示

在这里插入图片描述
&buff 是一个地址,这个地址上存储的是 0x20000164 这个值,也就是 sendBuff 数组的首地址
所以传入一个 指向数组首地址的指针的地址 才是正确的

============== 传输大块数据全部代码 ==============

#include "stm32f10x.h"
#include "led.h"
#include "usart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "SysTick.h"
#include "queue.h"
#include "string.h"
#include "stdlib.h"

/* 定义 QueueHandle_t 类型变量,用于保存队列句柄 */
QueueHandle_t xQueue;

char sendBuff[200];   // 大块数据缓冲区

/*
 * 写入队列大块数据 
 */
void taskSend(void *pvParameters)
{
    
    
	static int count = 0;
	BaseType_t res;
	char *buff;
	
	for(;;)
	{
    
    
		/* 向缓冲区里放数据 */
		sprintf(sendBuff, "My name is ChenShao %d\r\n", count++);
		buff = sendBuff;
		
		printf("sendBuff = 0x%016lx\r\n", (long unsigned int)sendBuff);
		printf("&sendBuff = 0x%016lx\r\n", (long unsigned int)&sendBuff);
		
		printf("buff = 0x%016lx\r\n", (long unsigned int)buff);
		printf("&buff = 0x%016lx\r\n", (long unsigned int)&buff);
		/*
		 * 这里不能传入 sendBuff、&sendBuff、buff ,因为这里需要传入一个指向一片内存的指针的地址
		 * 而sendBuff、&sendBuff、buff都是数组首地址,首地址没有指向任何内存
		*/
		res = xQueueSendToBack(xQueue, &buff, 0);  
		if(res != pdPASS){
    
    
			printf("write fail \r\n");
		}
	}
}

/*
 * 读取队列大块数据 
 */
void taskRead(void *pvParameters)
{
    
    
	const TickType_t delay = 100 / portTICK_PERIOD_MS;
	char *str;
	BaseType_t res;
	for(;;)
	{
    
    
		res = xQueueReceive(xQueue, &str, delay);
		if(res == pdPASS){
    
    
			printf("read data : %s\r\n", str);
		}else{
    
    
			printf("read null\r\n");
		}
	}
}

int main()
{
    
    
	SysTick_Init(72);
	USART1_Init(115200);
	printf("串口初始化成功!\r\n");
	
	/* 创建队列,1个字节,仅用于保存大块数据首地址 */
	xQueue = xQueueCreate(1, sizeof(char *));
	/* 写队列 */
	xTaskCreate(taskSend, "taskSend", 1000, NULL, 1, NULL);
	/* 读队列 */
	xTaskCreate(taskRead, "taskRead", 1000, NULL, 2, NULL);
	
	/* 启动调度器 */
	vTaskStartScheduler();
}

猜你喜欢

转载自blog.csdn.net/HuangChen666/article/details/129876196