Freertos应用与源码分析:消息队列 - 高级示例

目录

一、概述

1、数据存储

2、共享特性

3、读队列

4、写队列

二、函数介绍

1、队列创建

2、队列发送

3、队列接收

三、队列应用

四、队列发送指针应用

五、柔性数组与队列的高级应用

1、柔性数组

2、队列中的高级应用

一、概述

1、数据存储

        队列可以保存有限个具有确定长度的数据单元。队列可以保存的最大单元数目被称为队列的“深度”。在队列创建时需要设定其深度和每个单元的大小。通常情况下,队列被作为 FIFO(先进先出)使用,即数据由队列尾写入,从队列首读出。

2、共享特性

        队列是具有自己独立权限的内核对象,并不属于或赋予任何任务。所有任务都可以向同一队列写入和读出。一个队列由多方写入是经常的事,但由多方读出倒是很少遇到。

3、读队列

        当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。

4、写队列

        同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级相同,那么被解除阻塞的任务将是等待最久的任务。

二、函数介绍

1、队列创建
QueueHandle_t xQueueGenericCreate( 
                                const UBaseType_t uxQueueLength,   //队列能够存储的最大单元数目,即队列深度。
                                const UBaseType_t uxItemSize       //队列中数据单元的长度,以字节为单位。
                                );
//返回类型 
//NULL 表示没有足够的堆空间分配给队列而导致创建失败。
//非 NULL 值表示队列创建成功。
                                  

返回类型 QueueHandle_t

返回值 NULL 创建失败

非 NULL 值表示队列创建成功。表示队列的句柄。

  • uxQueueLength 队列能够存储的最大单元数目,即队列深度。
  • uxItemSize 队列中数据单元的长度,以字节为单位。
2、队列发送
BaseType_t xQueueGenericSend( 
                              QueueHandle_t xQueue,   //队列句柄
                              const void * const pvItemToQueue, //数据地址
                              TickType_t xTicksToWait,  //阻塞时间
                              );

返回类型 BaseType_t

返回值 pdPASS 数据被成功发送到队列

errQUEUE_FULL 如 果 由 于 队 列 已 满 而 无 法 将 数 据 写 入 , 则 将 返 回errQUEUE_FULL。

  • xQueue 创建的队列句柄
  • pvItemToQueue 数据地址
  • xTicksToWait 发送阻塞时间
3、队列接收
BaseType_t xQueueReceive( 
                            QueueHandle_t xQueue,   //队列句柄
                          void * const pvBuffer,    //接收数据buff
                          TickType_t xTicksToWait   ///接收阻塞时间
                        )

返回类型 BaseType_t

返回值 pdPASS 成功地从队列中读到数据。

errQUEUE_FULL 如果在读取时由于队列已空而没有读到任何数据,则将返回errQUEUE_FULL。

  • xQueue 被读队列的句柄。
  • pvBuffer 接收缓存指针。其指向一段内存区域,用于接收从队列中拷贝来的数据。
  • xTicksToWait 阻塞超时时间。如果在接收时队列为空,则这个时间是任务处于阻塞状态以等待队列数据有效的最长等待时间。

三、队列应用

        本次例程会常见两个线程,其中一个线程负责队列发送,一个线程负责队列接收。完成整体的队列收发工作。

#include "main.h"

//创建队列句柄
QueueHandle_t Queue_Data_Handle;
#define queue_data_length 10

//宏定义 send_task
BaseType_t retA;		
TaskHandle_t Pt_send_Task_TaskHandle;
void queue_sned_Task(void *p);
#define Sned_Task_Name 		  "queue_send"
#define Sned_Task_StackD  		128
#define Sned_Task_Priority		1

//宏定义 rece_task
BaseType_t retB;
TaskHandle_t Pt_rece_Task_TaskHandle;
void queue_receive_Task(void *p);
#define Rece_Task_Name 		  "queue_rece"
#define Rece_Task_StackD  		128
#define Rece_Task_Priority		1


int main(void)
{
	bsp_Init();
	/*创建队列 注意队列长度 和 数据字节大小*/
	Queue_Data_Handle = xQueueCreate( (const UBaseType_t)queue_data_length,  //队列长度
									  (const UBaseType_t)sizeof(uint32_t));  //单个数据大小四字节
									
	/*创建发送任务*/
	retA = xTaskCreate(	(TaskFunction_t)    queue_send_Task,
											(const char *)Sned_Task_Name,
											(uint16_t)Sned_Task_StackD,
											(void *)NULL,
											(UBaseType_t)Sned_Task_Priority,
											(TaskHandle_t *)&Pt_send_Task_TaskHandle);
	/*创建接收任务*/
	retA = xTaskCreate(	(TaskFunction_t)    queue_receive_Task,
											(const char *)Rece_Task_Name,
											(uint16_t)Rece_Task_StackD,
											(void *)NULL,
											(UBaseType_t)Rece_Task_Priority,
											(TaskHandle_t *)&Pt_rece_Task_TaskHandle);
	/*开始调度*/
	vTaskStartScheduler();
	/*不会执行到这里*/
	while (1) {		
		;
	}
}

void queue_send_Task(void *p)
{	
	uint32_t send_value = 0;
	while(1)
	{    
		xQueueSend(Queue_Data_Handle, &value, portMAX_DELAY);
		{	//如果BUFF满,则不会执行如下程序
			send_value++;
			if(send_value > 100)
			{
				send_value = 0;
			}
			vTaskDelay(100);
		}
	}
}

void queue_receive_Task(void *p)
{
	uint32_t rece_value = 0;
	while(1)
	{
		xQueueReceive(Queue_Data_Handle, &rece_value, portMAX_DELAY);
		{
			//如果BUFF空,则不会执行如下程序
			printf("rece_value = %d\r\n", rece_value);
			vTaskDelay(100);
		}		
	}
}

四、队列发送指针应用

        在发送任务中,动态申请内存,然后把分配的这块内存指针头通过队列发送给接收队列任务,在实际的操作中,大内存数据适合通过指针发送。减少队列内存分配。

typedef struct{
	uint32_t index;
	uint32_t length;
	uint8_t buff[128];
}rx_buff_data_t;

//创建队列句柄
QueueHandle_t Queue_Data_Handle;
#define queue_data_length 3

//宏定义 send_task
BaseType_t retA;		
TaskHandle_t Pt_send_Task_TaskHandle;
void queue_sned_Task(void *p);
#define Sned_Task_Name 		  "queue_send"
#define Sned_Task_StackD  		128
#define Sned_Task_Priority		1

//宏定义 rece_task
BaseType_t retB;
TaskHandle_t Pt_rece_Task_TaskHandle;
void queue_receive_Task(void *p);
#define Rece_Task_Name 		  "queue_rece"
#define Rece_Task_StackD  		128
#define Rece_Task_Priority		1

int main(void)
{
	bsp_Init();

	/*创建队列 注意队列长度 和 数据字节大小*/
	Queue_Data_Handle = xQueueCreate( (const UBaseType_t)queue_data_length,  //队列长度
									  (const UBaseType_t)sizeof(rx_buff_data_t *));  
									
	/*创建发送任务*/
	retA = xTaskCreate(	(TaskFunction_t)    queue_send_Task,
											(const char *)Sned_Task_Name,
											(uint16_t)Sned_Task_StackD,
											(void *)NULL,
											(UBaseType_t)Sned_Task_Priority,
											(TaskHandle_t *)&Pt_send_Task_TaskHandle);
	/*创建接收任务*/
	retA = xTaskCreate(	(TaskFunction_t)    queue_receive_Task,
											(const char *)Rece_Task_Name,
											(uint16_t)Rece_Task_StackD,
											(void *)NULL,
											(UBaseType_t)Rece_Task_Priority,
											(TaskHandle_t *)&Pt_rece_Task_TaskHandle);
	/*开始调度*/
	vTaskStartScheduler();
	/*不会执行到这里*/
	while (1) {		
		;
	}
}

void queue_send_data_reset(rx_buff_data_t *p_data)
{	//重置数据
	p_data->length = 0;
	p_data->index = 0;
	memset(p_data->buff, 0, sizeof(p_data->buff)); //清空数据
}

void queue_send_Task(void *p)
{	
	while(1)
	{   //动态申请内存
		rx_buff_data_t *p_data = (rx_buff_data_t *)pvPortMalloc(sizeof(rx_buff_data_t));

		if(!p_data)
		{
			vTaskDelay(100);
			continue;
		}
        //初始化分配内存数据
		queue_send_data_reset(p_data);
        //处理处理
		for(uint32_t cnt = 0; cnt < 100; cnt++)
		{
			p_data->length++;
			p_data->index = cnt;
			p_data->buff[cnt] = cnt;
		}	
    	//发送数据
		if(xQueueSend(Queue_Data_Handle, &p_data, portMAX_DELAY) != pdPASS)
		{	//发送失败
			vPortFree(p_data);	
		}
		vTaskDelay(100);
		
	}
}

void queue_receive_Task(void *p)
{
	rx_buff_data_t *rece_value = NULL;
	while(1)
	{	
		if(xQueueReceive(Queue_Data_Handle, &rece_value, portMAX_DELAY) == pdPASS)
		{
			if(!rece_value)
			{
				vTaskDelay(100);
				continue;
			}
			for(uint32_t cnt = 0; cnt < rece_value->length; cnt++)
			{
				printf("rece_value->buff[%d] = %d \r\n", cnt, rece_value->buff[cnt]);
			}
			//接收成功并释放
			vPortFree(rece_value);	
			rece_value = NULL;	
		}
		vTaskDelay(100);		
	}
} 

五、柔性数组与队列的高级应用

        如果要接收一个大内存的数据,而且是不定长的数据包,并且要通过队列发送与接收的话,应该怎么实现呢?我们可以定义一个柔性数组结构体,确定该柔性数组的长度和数据包,分配对应的内存。指针指向首地址,并通过队列发送与接收。

1、柔性数组

        柔性数组是C99标准引入的一种特殊数组类型,允许在结构体的最后一个成员位置声明一个未指定大小的数组。这种设计主要用于实现动态内存管理,尤其在需要处理可变长度数据的场景中具有显著优势。结构体中最后一个未指定大小的数组不占用任何内存,不过需要进行malloc管理。

typedef struct  {
    int length;
    int data[];  // 柔性数组
}FlexArray_t;

分配公式:总内存 = 结构体大小 + 柔性数组元素个数 × 元素大小

// 分配一个包含10个整数的柔性数组
FlexArray_t *FlexArray = malloc(sizeof(FlexArray_t) + 10 * sizeof(int));
flex->length = 10;
2、队列中的高级应用

        假设接收到的数据包不定长度,我们创建队列的大小并不好把控,此时我们可以通过指针来传递数据包的首地址。其中创建队列只需要创建对应结构体指针的大小即可。在发送任务中,根据数据的长度来分配柔性数组的长度。ble_uuid1_write_val属于蓝牙的回调函数,属于发送方。ble_app_task属于处理蓝牙的指令,属于接收方。

typedef struct {
    sizeof(uint16_t) len;        // 数据长度
    uint8_t data[];     // 柔性数组存储实际数据
}ble_rx_data_t;

static ssize_t ble_uuid1_write_val(struct bt_conn *conn, const struct bt_gatt_attr *attr,
                                   const void *buf, u16_t len, u16_t offset,
                                   u8_t flags)
{
    // sizeof(ble_rx_data_t)等于sizeof(uint16_t) 这里uin8_t占用一个字节,所以直接使用len即可
    ble_rx_data_t *recv_data = (ble_rx_data_t *)pvPortMalloc(sizeof(ble_rx_data_t) + len);
    if(!recv_data){
        printf("malloc failed\r\n");
        return 0;
    }
    recv_data->len = len;
    memcpy(recv_data->data, buf, len);
    
    if(xQueueSend(ble_queue_handle, &recv_data, 100) != pdTRUE)
    {
        vPortFree(recv_data);
        return 0;
    }

    return len;
}

void ble_app_task(void *params) 
{
    ble_rx_data_t *ble_px = NULL;
    ble_queue_handle =  xQueueCreate( 10 , sizeof(ble_rx_data_t*) );

    while (true) 
    {
        xQueueReceive(ble_queue_handle,&ble_px,portMAX_DELAY);
        {
            if(ble_px != NULL){
                //处理蓝牙发送过来的数据
                vPortFree(ble_px);
            }
        }
        vTaskDelay(100); 
    }
}

你可能感兴趣的:(Freertos应用与源码分析,arm开发,架构,单片机,stm32,mcu)