FreeRTOS消息队列

张开发
2026/4/14 7:46:14 15 分钟阅读

分享文章

FreeRTOS消息队列
FreeRTOS消息队列文章目录一、队列简介1、FreeRTOS中的消息队列是什么2、消息队列和全局变量的区别3、使用队列的情况4、队列项目和队列长度5、FreeRTOS队列特点6、消息队列阻塞时间设置7、入队/出队阻塞过程8、当多个任务写入消息给一个 “满队列” 时这些任务都会进入阻塞状态也就是说有多个任务在等待同一个队列的空间。那当队列有空间时哪个任务会进入就绪态9、队列创建、写入和读出过程二、队列结构体介绍1、队列结构体三、队列相关API函数介绍1、队列使用流程2、创建队列函数3、各种功能所对应的队列4、队列写入消息函数5、队列读出消息函数四、代码实现1、实验目的2、实验设计**1.start_task:** 创建2.task1、task2、task3任务**2.task1** 当按键key0或key1按下将按键值拷贝到队列key_queue入队**3.task2** 读取key_queue中的消息出队打印出接受的按键值**4.task3** 从队列big_date_queue读取大数据地址通过地址访问大数据一、队列简介1、FreeRTOS中的消息队列是什么消息队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)。2、消息队列和全局变量的区别消息队列作用有点类似于全局变量但消息队列在RTOS中比全局变量更安全可靠。假设有一个全局变量a0现在有两个任务都要写这个变量a。上图中任务1和任务2在RTOS中相互争取修改a的值a的值容易受损错乱。全局变量的弊端数据无保护导致数据不安全当多个任务同时对该变量操作时数据易受损。3、使用队列的情况使用队列的情况如下读写队列做好了保护防止多任务或中断同时访问产生冲突。我们只需直接调用API函数即可简单易用。注意FreeRTOS基于队列实现了多种功能其中包括队列集、互斥信号量、计数信号量、二值信号量、递归互斥信号量因此很有必要深入了解FreeRTOS的队列。4、队列项目和队列长度在队列中可以存储数量有限、大小固定的数据。队列中的每个数据就叫做 “队列项目” 队列能够存储 “队列项目” 的最大数量称为队列的长度。在创建队列时就要指定队列长度以及队列项目的大小5、FreeRTOS队列特点数据入队出队方式 队列通常采用 “先进先出(FIFO)” 的数据存储缓冲机制即先入队的数据会先从队列中被读取FreeRTOS中也可以配置为 “后进先出(LIFO)” 方式。数据传递方式 FreeRTOS中队列采用实际值传递即将数据拷贝到队列中进行传递FreeRTOS采用拷贝数据传递也可以传递指针所以在传递较大的数据的时候采用指针传递。多任务访问 队列不属于某个任务任何任务和中断都可以向队列写入/读取消息。出队、入队阻塞 当任务向一个队列发送/读取消息时可以指定一个阻塞时间假设此时当队列已满无法入队。6、消息队列阻塞时间设置若阻塞时间为0 直接返回不会等待。若阻塞时间为0~port_MAX_DELAY 等待设定阻塞时间若在该时间内无法入队/出队超时后直接返回不再等待。若阻塞时间为port_MAX_DELAY 死等一直等到可以入队/出队为止。7、入队/出队阻塞过程入队阻塞队列满了此时写不进去数据将该任务的状态列表项挂载在pxDelayedTaskList将该任务的事件列表项挂载在xTasksWaitingToSend出队阻塞队列为空此时读取不了数据将该任务的状态列表挂载在pxDelayedTaskList将该任务的事件列表项挂载在xTasksWaitingToReceive8、当多个任务写入消息给一个 “满队列” 时这些任务都会进入阻塞状态也就是说有多个任务在等待同一个队列的空间。那当队列有空间时哪个任务会进入就绪态优先级最高的任务如果大家的优先级相同那等待时间最久的任务进入就绪态。9、队列创建、写入和读出过程二、队列结构体介绍1、队列结构体typedef struct QueueDefinition { int8_t * pcHead; /* 存储区域的起始地址 */ int8_t * pcWriteTo; /* 下一个写入的位置 */ union { QueuePointers_t xQueue; SemaphoreData_t xSemaphore; } u ; List_t xTasksWaitingToSend; /* 等待发送列表 */ List_t xTasksWaitingToReceive; /* 等待接收列表 */ volatile UBaseType_t uxMessagesWaiting; /* 非空闲队列项目的数量 */ UBaseType_t uxLength /* 队列长度 */ UBaseType_t uxItemSize; /* 队列项目的大小 */ volatile int8_t cRxLock; /* 读取上锁计数器 */ volatile int8_t cTxLock; /* 写入上锁计数器 */ /* 其他的一些条件编译 */ } xQUEUE;当用于队列使用时typedef struct QueuePointers { int8_t * pcTail; /* 存储区的结束地址 */ int8_t * pcReadFrom; /* 最后一个读取队列的地址 */ } QueuePointers_t;当用于互斥信号量和递归互斥信号量时typedefstructSemaphoreData{TaskHandle_t xMutexHolder;/* 互斥信号量持有者 */UBaseType_t uxRecursiveCallCount;/* 递归互斥信号量的获取计数器 */}SemaphoreData_t;队列结构体示意图三、队列相关API函数介绍1、队列使用流程使用队列的主要流程创建队列 — 写队列 — 读队列。2、创建队列函数动态和静态创建队列之间的区别队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中分配而静态创建需要用户自行分配内存。#define xQueueCreate ( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), (queueQUEUE_TYPE_BASE ))此函数用于使用动态方式创建队列队列所需的内存空间由 FreeRTOS 从 FreeRTOS 管理的堆中分配。函数参数函数返回值3、各种功能所对应的队列#definequeueQUEUE_TYPE_BASE((uint8_t)0U)/* 队列 */#definequeueQUEUE_TYPE_SET((uint8_t)0U)/* 队列集 */#definequeueQUEUE_TYPE_MUTEX((uint8_t)1U)/* 互斥信号量 */#definequeueQUEUE_TYPE_COUNTING_SEMAPHORE((uint8_t)2U)/* 计数型信号量 */#definequeueQUEUE_TYPE_BINARY_SEMAPHORE((uint8_t)3U)/* 二值信号量 */#definequeueQUEUE_TYPE_RECURSIVE_MUTEX((uint8_t)4U)/* 递归互斥信号量 */4、队列写入消息函数#definexQueueSend(xQueue,pvItemToQueue,xTicksToWait)xQueueGenericSend((xQueue),(pvItemToQueue),(xTicksToWait),queueSEND_TO_BACK)#definexQueueSendToBack(xQueue,pvItemToQueue,xTicksToWait)xQueueGenericSend((xQueue),(pvItemToQueue),(xTicksToWait),queueSEND_TO_BACK)#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )#define xQueueOverwrite( xQueue, pvItemToQueue ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )可以看到这几个写入函数调用的是同一个函数xQueueGenericSend( )只是指定了不同的写入位置队列一共有3种写入位置#definequeueSEND_TO_BACK((BaseType_t)0)/* 写入队列尾部 */#definequeueSEND_TO_FRONT((BaseType_t)1)/* 写入队列头部 */#definequeueOVERWRITE((BaseType_t)2)/* 覆写队列*/注意覆写方式写入队列只有在队列的队列长度为 1 时才能够使用 。往队列写入消息函数入口参数解析BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition );函数参数函数返回值5、队列读出消息函数BaseType_txQueueReceive(QueueHandle_t xQueue,void*constpvBuffer,TickType_t xTicksToWait)此函数用于在任务中从队列中读取消息并且消息读取成功后会将消息从队列中移除。函数参数函数返回值BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )此函数用于在任务中从队列中读取消息 但与函数 xQueueReceive()不同此函数在成功读取消息后并不会移除已读取的消息函数参数函数返回值四、代码实现1、实验目的学习FreeRTOS的队列相关API函数的使用实现队列的入队和出队操作2、实验设计/* freeRTOS任务配置 *//* FreeRTOS消息队列配置 */QueueHandle_t key_queue;/* 小数据句柄 */QueueHandle_t big_date_queue;/* 大数据句柄 */charbuff[100]{我是一个大数组大大的数组 124214 uhsidhaksjhdklsadhsaklj};/* freeRTOS任务配置 *//*** * 任务创建任务配置 * 包括: 任务优先级 堆栈大小 任务句柄 创建任务 */#defineSTART_PRIO1/* 任务优先级 */#defineSTART_STK_SIZE64/* 任务堆栈大小字为单位1字等于4字节 */staticTaskHandle_t StartTask_HandlerNULL;/* 创建任务的任务句柄 */staticvoidStartTaskCreate(void*parameter);/* 创建任务的任务函数名 *//*** * TASK1任务配置 * 包括: 任务优先级 堆栈大小 任务句柄 创建任务 */#defineTASK1_PRIO1/* 任务优先级 */#defineTASK1_STK_SIZE64/* 任务堆栈大小字为单位1字等于4字节 */staticTaskHandle_t Task1_HandleNULL;/* 任务1的任务句柄 */staticvoidTask1(void*parameter);/* 任务1的任务函数名 *//*** * TASK2任务配置 * 包括: 任务优先级 堆栈大小 任务句柄 创建任务 */#defineTASK2_PRIO2/* 任务优先级 */#defineTASK2_STK_SIZE64/* 任务堆栈大小字为单位1字等于4字节 */staticTaskHandle_t Task2_HandleNULL;/* 任务2的任务句柄 */staticvoidTask2(void*parameter);/* 任务2的任务函数名 *//*** * TASK3任务配置 * 包括: 任务优先级 堆栈大小 任务句柄 创建任务 */#defineTASK3_PRIO3/* 任务优先级 */#defineTASK3_STK_SIZE64/* 任务堆栈大小字为单位1字等于4字节 */staticTaskHandle_t Task3_HandleNULL;/* 任务3的任务句柄 */staticvoidTask3(void*parameter);/* 任务3的任务函数名 */1.start_task:创建2.task1、task2、task3任务freeRTOS实验函数voidfreeRTOS_demo(void){BaseType_t xReturnpdPASS;/* 定义一个创建信息返回值 pdPASS成功 *//* key_queue队列的创建 */key_queuexQueueCreate(2,sizeof(uint8_t));/* 队列长度为2成员大小为sizeof(uint8_t) */if(key_queue!NULL)printf(key_queue队列创建成功\r\n);elseprintf(key_queue队列创建失败\r\n);/* big_date_queue队列的创建 */big_date_queuexQueueCreate(1,sizeof(char*));/* 队列长度为1成员大小为sizeof(char*) */if(big_date_queue!NULL)printf(big_date_queue队列创建成功\r\n);elseprintf(big_date_queue队列创建失败\r\n);/* 动态创建创建任务的任务 */xReturnxTaskCreate((TaskFunction_t)StartTaskCreate,/* 任务函数 */(constchar*)StartTaskCreate,/* 任务名称 */(uint32_t)START_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传递给任务函数的参数 */(UBaseType_t)START_PRIO,/* 任务优先级 */(TaskHandle_t*)StartTask_Handler);/* 任务句柄 */if(xReturnpdPASS)printf(StartTaskCreate任务创建成功!\r\n);elseprintf(StartTaskCreate任务创建失败!\r\n);vTaskStartScheduler();/* 启动任务调度器 */}用于创建任务的任务staticvoidStartTaskCreate(void*parameter){BaseType_t xReturnpdPASS;/* 定义一个创建信息返回值 pdPASS成功 */taskENTER_CRITICAL();/* 进入临界区创建任务过程我们必须保证在临界区 *//* 动态创建Task1任务 */xReturnxTaskCreate((TaskFunction_t)Task1,/* 任务函数 */(constchar*)Task1,/* 任务名称 */(uint32_t)TASK1_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传递给任务函数的参数 */(UBaseType_t)TASK1_PRIO,/* 任务优先级 */(TaskHandle_t*)Task1_Handle);/* 任务句柄 */if(xReturnpdPASS)printf(Task1任务创建成功!\r\n);elseprintf(Task1任务创建失败!\r\n);/* 动态创建Task2任务 */xReturnxTaskCreate((TaskFunction_t)Task2,/* 任务函数 */(constchar*)Task2,/* 任务名称 */(uint32_t)TASK2_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传递给任务函数的参数 */(UBaseType_t)TASK2_PRIO,/* 任务优先级 */(TaskHandle_t*)Task2_Handle);/* 任务句柄 */if(xReturnpdPASS)printf(Task2任务创建成功!\r\n);elseprintf(Task2任务创建失败!\r\n);/* 动态创建Task3任务 */xReturnxTaskCreate((TaskFunction_t)Task3,/* 任务函数 */(constchar*)Task3,/* 任务名称 */(uint32_t)TASK3_STK_SIZE,/* 任务堆栈大小 */(void*)NULL,/* 传递给任务函数的参数 */(UBaseType_t)TASK3_PRIO,/* 任务优先级 */(TaskHandle_t*)Task3_Handle);/* 任务句柄 */if(xReturnpdPASS)printf(Task3任务创建成功!\r\n);elseprintf(Task3任务创建失败!\r\n);vTaskDelete(StartTask_Handler);/* 删除开始任务 */taskEXIT_CRITICAL();/* 退出临界区 */}2.task1当按键key0或key1按下将按键值拷贝到队列key_queue入队当按键key_up按下将传输大数据拷贝大数据地址到big_date_queue中staticvoidTask1(void*parameter){uint8_tkey0;char*buf;BaseType_t err0;bufbuff[0];while(1){keyKEY_Scan();switch(key){/* KEY0按下 */case1:errxQueueSend(key_queue,key,portMAX_DELAY);if(err!pdTRUE)printf(key_queue队列发送失败\r\n);elseprintf(key_queue队列发送成功\r\n);break;/* KEY1按下 */case2:errxQueueSend(big_date_queue,buf,portMAX_DELAY);if(err!pdTRUE)printf(key_queue队列发送失败\r\n);elseprintf(key_queue队列发送成功\r\n);break;default:break;}vTaskDelay(10);/* 延时10个tick */}}3.task2读取key_queue中的消息出队打印出接受的按键值staticvoidTask2(void*parameter){uint8_tkey0;BaseType_t err0;while(1){errxQueueReceive(key_queue,key,portMAX_DELAY);if(err!pdTRUE)printf(key_queue队列读取失败\r\n);elseprintf(key_queue读取队列成功数据%d\r\n,key);vTaskDelay(100);/* 延时100个tick */}}4.task3从队列big_date_queue读取大数据地址通过地址访问大数据staticvoidTask3(void*parameter){char*buf;BaseType_t err0;while(1){errxQueueReceive(big_date_queue,buf,portMAX_DELAY);if(err!pdTRUE)printf(big_date_queue队列读取失败\r\n);elseprintf(数据%s\r\n,buf);vTaskDelay(100);/* 延时100个tick */}}

更多文章