当前位置: 首页 > java >正文

【FreeRTOS-消息队列】

参照正点原子以及以下gitee笔记整理本博客,并将实验结果附在文末。
https://gitee.com/xrbin/FreeRTOS_learning/tree/master

一、队列简介

1、FreeRTOS中的消息队列是什么

答:消息队列是任务到任务、任务到中断、中断到任务数据交流的一种机制(消息传递)。

2、消息队列和全局变量的区别

答:消息队列作用有点类似于全局变量,但消息队列在RTOS中比全局变量更安全可靠。

假设有一个全局变量a=0,现在有两个任务都要写这个变量a。
在这里插入图片描述
上图中任务1和任务2在RTOS中相互争取修改a的值,a的值容易受损错乱。

下图表示如果使用全局变量,当任务1运行到②时,更高优先级的任务2抢占并完全执行得到r0为1赋给a,然后回到任务1执行③,最终结果执行完任务1和任务2后,a=1
在这里插入图片描述
全局变量的弊端:数据无保护,导致数据不安全,当多个任务同时对该变量操作时,数据易受损。

3、使用队列的情况

答:使用队列的情况如下:
在这里插入图片描述
读写队列做好了保护,防止多任务或中断同时访问产生冲突。我们只需直接调用API函数即可,简单易用。

注意:FreeRTOS基于队列,实现了多种功能,其中包括队列集、互斥信号量、计数信号量、二值信号量、递归互斥信号量,因此很有必要深入了解FreeRTOS的队列。

补充:任务调度是由于PendSV引起的,他的中断优先级是最低的,关闭中断后,PendSV无法触发,因此任务A在写队列是,任务B是无法打断任务A的执行的。

4、队列项目和队列长度

答:在队列中可以存储数量有限、大小固定的数据。队列中的每个数据就叫做 “队列项目” ,队列能够存储 “队列项目” 的最大数量称为队列的长度。
在这里插入图片描述
在创建队列时,就要指定队列长度以及队列项目的大小

5、FreeRTOS队列特点

答:

  1. 数据入队出队方式 : 队列通常采用 “先进先出(FIFO)” 的数据存储缓冲机制,即先入队的数据会先从队列中被读取,FreeRTOS中也可以配置为 “后进先出(LIFO)” 方式。
    在这里插入图片描述

  2. 数据传递方式 : FreeRTOS中队列采用实际值传递,即将数据拷贝到队列中进行传递,FreeRTOS采用拷贝数据传递,也可以传递指针,所以在传递较大的数据的时候采用指针传递

  3. 多任务访问 : 队列不属于某个任务,任何任务和中断都可以向队列写入/读取消息。

  4. 出队、入队阻塞 : 当任务向一个队列发送/读取消息时,可以指定一个阻塞时间,假设此时当队列已满无法入队。
    在这里插入图片描述

  • 若阻塞时间为0 :直接返回不会等待。
  • 若阻塞时间为0~port_MAX_DELAY :等待设定阻塞时间,若在该时间内无法入队/出队,超时后直接返回不再等待。
  • 若阻塞时间为port_MAX_DELAY :死等,一直等到可以入队/出队为止。

6、入队/出队阻塞过程

答:

入队阻塞:
在这里插入图片描述
队列满了,此时写不进去数据

  1. 将该任务的状态列表项(就绪列表)挂载在pxDelayedTaskList(阻塞列表)

  2. 将该任务的事件列表项挂载在xTasksWaitingToSend(等待发送列表)

出队阻塞:
在这里插入图片描述
队列为空,此时读取不了数据

  1. 将该任务的状态列表项(就绪列表)挂载在pxDelayedTaskList(阻塞列表)

  2. 将该任务的事件列表项挂载在xTasksWaitingToReceive(等待读取列表)

    (当队列中有数据,那么解除xTasksWaitingToReceive,移除pxDelayedTaskList,放到就绪列表里,此时任务B就能够执行)

7、当多个任务写入消息给一个 “满队列” 时,这些任务都会进入阻塞状态,也就是说有多个任务在等待同一个队列的空间。那当队列有空间时,哪个任务会进入就绪态?

答:

  1. 优先级最高的任务

  2. 如果大家的优先级相同,那等待时间最久的任务进入就绪态。
    在这里插入图片描述

8、队列创建、写入和读出过程

答:
在这里插入图片描述

二、队列结构体介绍

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;

当用于互斥信号量和递归互斥信号量时:

typedef struct SemaphoreData
{TaskHandle_t xMutexHolder;		    /* 互斥信号量持有者 */UBaseType_t uxRecursiveCallCount;	/* 递归互斥信号量的获取计数器 */
} SemaphoreData_t;

队列结构体示意图:

队列结构体存储区+队列项(消息)存储区

int8_t * pcHead;                       /* 存储区域的起始地址 */
int8_t * pcWriteTo;                    /* 下一个写入的位置 */
int8_t * pcTail;                 /* 存储区的结束地址 */
int8_t * pcReadFrom;             /* 最后一个读取队列的地址 */

在这里插入图片描述

三、队列相关API函数介绍

1、队列使用流程

答:使用队列的主要流程:创建队列 —> 写队列 —> 读队列。

2、创建队列函数

答:
在这里插入图片描述
函数返回值:
在这里插入图片描述

3、各种功能所对应的队列

答:

#define queueQUEUE_TYPE_BASE                    ( ( uint8_t ) 0U )  /* 队列 */
#define queueQUEUE_TYPE_SET                     ( ( uint8_t ) 0U )  /* 队列集 */
#define queueQUEUE_TYPE_MUTEX                   ( ( uint8_t ) 1U )  /* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE      ( ( uint8_t ) 2U )  /* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE        ( ( uint8_t ) 3U )  /* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX         ( ( uint8_t ) 4U )  /* 递归互斥信号量 */

4、队列写入消息函数

答:

任务级+中断级
在这里插入图片描述

#define  xQueueSend( xQueue, pvItemToQueue, xTicksToWait  )xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )
#define  xQueueSendToBack( 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种写入位置:

#define queueSEND_TO_BACK                ( ( BaseType_t ) 0 )       /* 写入队列尾部 */
#define queueSEND_TO_FRONT              ( ( BaseType_t ) 1 )        /* 写入队列头部 */
#define queueOVERWRITE                  ( ( BaseType_t ) 2 )        /* 覆写队列*/
注意:==覆写方式写入队列,只有在队列的队列长度为 1 时,才能够使用== 。往队列写入消息函数入口参数解析:
BaseType_t      xQueueGenericSend(  QueueHandle_t       xQueue,const void * const  pvItemToQueue,TickType_t          xTicksToWait,const BaseType_t    xCopyPosition   );

函数参数:
在这里插入图片描述
函数返回值:
在这里插入图片描述

5、队列读出消息函数

答:
在这里插入图片描述

BaseType_t    xQueueReceive( QueueHandle_t  xQueue, void *   const pvBuffer,  TickType_t     xTicksToWait )

函数参数:
在这里插入图片描述
函数返回值:
在这里插入图片描述

BaseType_t   xQueuePeek( QueueHandle_t   xQueue,void * const   pvBuffer,TickType_t   xTicksToWait )

此函数用于在任务中,从队列中读取消息, 但与函数 xQueueReceive()不同,此函数在成功读取消息后,并不会移除已读取的消息!

函数参数:
在这里插入图片描述
函数返回值:
在这里插入图片描述

四、队列操作实验

实验简介

在这里插入图片描述

实验现象

task2和task3无需调用延时函数,由于调用队列接收函数,如果队列无数据,表明无法接收数据,任务会自动进入阻塞态。
在这里插入图片描述

部分代码

/******************************************************************************************************* @file        freertos.c* @author      正点原子团队(ALIENTEK)* @version     V1.4* @date        2022-01-04* @brief       FreeRTOS 移植实验* @license     Copyright (c) 2020-2032, 广州市星翼电子科技有限公司***************************************************************************************************** @attention** 实验平台:正点原子 探索者F407开发板* 在线视频:www.yuanzige.com* 技术论坛:www.openedv.com* 公司网址:www.alientek.com* 购买地址:openedv.taobao.com******************************************************************************************************/#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"/******************************************************************************************************/
/*FreeRTOS配置*//* START_TASK 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/
#define START_TASK_PRIO  1
#define START_TASK_STACK_SIZE  128
TaskHandle_t start_task_handle;
void start_task( void * pvParameters );/* TASK1 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/#define TASK1_PRIO  2
#define TASK1_STACK_SIZE  128
TaskHandle_t task1_handle;
void task1(void * pvParameters);/* TASK2 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/#define TASK2_PRIO  3
#define TASK2_STACK_SIZE  128
TaskHandle_t task2_handle;
void task2(void * pvParameters);/* TASK3 任务 配置* 包括: 任务句柄 任务优先级 堆栈大小 创建任务*/#define TASK3_PRIO  4
#define TASK3_STACK_SIZE  128
TaskHandle_t task3_handle;
QueueHandle_t key_queue;          /* 小数据 */
QueueHandle_t big_data_queue;      /* 大数据 */
char big_buff[100] = {"我是大数组,大大的数组 12123dafenhagendasi"};
void task3(void * pvParameters);/******************************************************************************************************//*** @brief       FreeRTOS例程入口函数* @param       无* @retval      无*/
void freertos_demo(void)
{    /* 队列创建 */key_queue = xQueueCreate( 2, sizeof(uint8_t) );       /* 由于有两个按键,因此队列项长度为2 ;按键键值的大小 */if(key_queue != NULL){printf("Key_queue队列创建成功!\r\n");}else{printf("Key_queue队列创建失败!\r\n");}big_data_queue = xQueueCreate( 1, sizeof(char *) );       /* 由于有大数据数组,因此队列项长度为2 ;数组首地址 */if(big_data_queue != NULL){printf("big_data_queue队列创建成功!\r\n");}else{printf("big_data_queue队列创建失败!\r\n");}xTaskCreate( (TaskFunction_t        )   start_task,(char *                )   "start_task",(configSTACK_DEPTH_TYPE)   START_TASK_STACK_SIZE,(void *                )   NULL,   (UBaseType_t           )   START_TASK_PRIO,(TaskHandle_t *        )   &start_task_handle);//开启任务调度器vTaskStartScheduler();
}void start_task( void * pvParameters )
{taskENTER_CRITICAL();                     /*进入临界区,任务切换不会进行*/xTaskCreate( (TaskFunction_t        )     task1,(char *                )   "task1",(configSTACK_DEPTH_TYPE)   TASK1_STACK_SIZE,(void *                )   NULL,   (UBaseType_t           )   TASK1_PRIO,(TaskHandle_t *        )   &task1_handle);xTaskCreate( (TaskFunction_t        )     task2,(char *                )   "task2",(configSTACK_DEPTH_TYPE)   TASK2_STACK_SIZE,(void *                )   NULL,   (UBaseType_t           )   TASK2_PRIO,(TaskHandle_t *        )   &task2_handle);xTaskCreate( (TaskFunction_t        )     task3,(char *                )   "task3",(configSTACK_DEPTH_TYPE)   TASK3_STACK_SIZE,(void *                )   NULL,   (UBaseType_t           )   TASK3_PRIO,(TaskHandle_t *        )   &task3_handle);vTaskDelete( NULL );taskEXIT_CRITICAL();                       /*退出临界区,才会开始任务切换*//*简单而言,临界区保护,就是保护那些不想被打断的从程序段							 */							 }/* 任务一,实现入队 */
void task1(void * pvParameters)
{uint8_t key_value = 0;char * buf;BaseType_t err = 0;buf = big_buff;    /* buf = &buff[0] */while(1){ key_value = key_scan(0);if(key_value == KEY0_PRES || key_value == KEY1_PRES){err = xQueueSend(key_queue, &key_value, portMAX_DELAY);if(err != pdTRUE){printf("队列发送失败\r\n");}}else if(key_value == WKUP_PRES){err = xQueueSend(big_data_queue, &buf, portMAX_DELAY);if(err != pdTRUE){printf("队列发送失败\r\n");}}vTaskDelay(10);}
}/* 任务二,实现小数据出队 */
void task2(void * pvParameters)
{uint8_t key = 0;BaseType_t err = 0;while(1){err = xQueueReceive(key_queue,&key,portMAX_DELAY);  /* 即使没有调用延时函数,由于队列接收函数接收不了数据而变为阻塞态 */if(err != pdTRUE){printf("key_queue队列读取失败\r\n");}else{printf("key_queue读取对列成功,数据%d\r\n",key);}}
}/* 任务二,实现大数据出队 */
void task3(void * pvParameters)
{char * buf;BaseType_t err = 0;while(1){err = xQueueReceive(big_data_queue,&buf,portMAX_DELAY);if(err != pdTRUE){printf("big_data_queue队列读取失败\r\n");}else{printf("big_data_queue读取对列成功,数据%s\r\n",buf);}}}
http://www.xdnf.cn/news/4553.html

相关文章:

  • PyQt5 实现自定义滑块,效果还不错
  • grpc到底是啥! ! !!
  • shell操作文件上传
  • 第3章 模拟法
  • SDC命令详解:使用get_ports命令进行查询
  • 浅谈广告投放从业者底层思维逻辑
  • C语言 指针(8)
  • 第七章 模板制作工具
  • ubuntu 挂载硬盘
  • 当“信任”遇上“安全”:如何用Curtain Logtrace记录文件操作活动 守护团队与数据的双重底线?
  • 2398.预算内的最多机器人数目 滑动窗口+单调队列
  • springboot集成langchain4j记忆对话
  • 通道注意力-senet
  • HDMI布局布线
  • Loly: 1靶场渗透
  • 大模型 Function Calling 学习路线图
  • Solana批量转账教程:提高代币持有地址和生态用户空投代币
  • 缓存菜品-04.功能测试
  • C++ 静态成员
  • 大模型系列(四)--- GPT2: Language Models are Unsupervised Multitask Learners​
  • Java 多线程编程:从基础到实战!
  • Ceph集群OSD运维手册:基础操作与节点扩缩容实战
  • MSTP 实验拓扑配置(ENSP)
  • 自动化创业机器人:现状、挑战与Y Combinator的启示
  • hadoop中的序列化和反序列化(3)
  • React学习路线-Deepseek版
  • 搭建spark伪分布集群
  • windows10 环境下通过huggingface_hub下载huggingface社区模型
  • 子集树算法文档
  • 驱动开发硬核特训 · 专题篇:Vivante GPU 与 DRM 图形显示体系全解析(i.MX8MP 平台实战)