STM32CubeMX学习笔记24---FreeRTOS(消息队列)
队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中 断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之 间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的 长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以 也称为消息队列。FreeRTOS 中的信号量的也是依据队列实现的!所
一. 队列简介
队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中 断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之 间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的 长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以 也称为消息队列。FreeRTOS 中的信号量的也是依据队列实现的!所以有必要深入的了解 FreeRTOS 的队列。
1、数据存储
通常队列采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据的时候(也叫入队)永 远都是发送到队列的尾部,而从队列提取数据的时候(也叫出队)是从队列的头部提取的。但是 也可以使用 LIFO 的存储缓冲,也就是后进先出,FreeRTOS 中的队列也提供了 LIFO 的存储缓 冲机制。 数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在 队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传 递。学过 UCOS 的同学应该知道,UCOS 的消息队列采用的是引用传递,传递的是消息指针。 采用引用传递的话消息内容就必须一直保持可见性,也就是消息内容必须有效,那么局部变量 这种可能会随时被删掉的东西就不能用来传递消息,但是采用引用传递会节省时间啊!因为不 用进行数据拷贝。 采用值传递的话虽然会导致数据拷贝,会浪费一点时间,但是一旦将消息发送到队列中原 始的数据缓冲区就可以删除掉或者覆写,这样的话这些缓冲区就可以被重复的使用。FreeRTOS 中使用队列传递消息的话虽然使用的是数据拷贝,但是也可以使用引用来传递消息啊,我直接 往队列中发送指向这个消息的地址指针不就可以了!这样当我要发送的消息数据太大的时候就 可以直接发送消息缓冲区的地址指针,比如在网络应用环境中,网络的数据量往往都很大的, 采用数据拷贝的话就不现实。
2、多任务访问
队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中 提取消息。
3、出队阻塞
当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任 务从队列中读取消息无效的时候任务阻塞的时间。出队就是就从队列中读取消息,出队阻塞是 针对从队列中读取消息的任务而言的。比如任务 A 用于处理串口接收到的数据,串口接收到数 据以后就会放到队列 Q 中,任务 A 从队列 Q 中读取数据。但是如果此时队列 Q 是空的,说明 还没有数据,任务 A 这时候来读取的话肯定是获取不到任何东西,那该怎么办呢?任务 A 现在 有三种选择,一:二话不说扭头就走,二:要不我在等等吧,等一会看看,说不定一会就有数 据了,三:死等,死也要等到你有数据!选哪一个就是由这个阻塞时间决定的,这个阻塞时间 单位是时钟节拍数。阻塞时间为 0 的话就是不阻塞,没有数据的话就马上返回任务继续执行接 下来的代码,对应第一种选择。如果阻塞时间为 0~ portMAX_DELAY,当任务没有从队列中获 取到消息的话就进入阻塞态,阻塞时间指定了任务进入阻塞态的时间,当阻塞时间到了以后还 没有接收到数据的话就退出阻塞态,返回任务接着运行下面的代码,如果在阻塞时间内接收到 了数据就立即返回,执行任务中下面的代码,这种情况对应第二种选择。当阻塞时间设置为 portMAX_DELAY 的话,任务就会一直进入阻塞态等待,直到接收到数据为止!这个就是第三种选择。
4、入队阻塞
入队说的是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队 列发送消息的话也可以设置阻塞时间。比如任务 B 向消息队列 Q 发送消息,但是此时队列 Q 是 满的,那肯定是发送失败的。此时任务 B 就会遇到和上面任务 A 一样的问题,这两种情况的处 理过程是类似的,只不过一个是向队列 Q 发送消息,一个是从队列 Q 读取消息而已。
5、队列操作过程图示
队列的简化操如入下图所示,从此图可知:
- 队列可以包含若干个数据:队列中有若干项,这被称为"长度"(length)
- 每个数据大小固定
- 创建队列时就要指定长度、数据大小
- 数据的操作采用先进先出的方法(FIFO,First In First Out):写数据时放到尾部,读数据时从头部读
- 也可以强制写队列头部:覆盖头部数据
更详细的操作入下图所示:
以上引用于:FreeRTOS消息队列-CSDN博客
二、STM32CubeMX设置
(1)、根据上一章的步骤创建两个任务:
STM32CubeMX学习笔记22---FreeRTOS(任务创建和删除)-CSDN博客
任务LED1用作发送,LED2用作接收。
(2)、创建消息队列
- Queue Name: 队列名称
- Queue Size: 队列能够存储的最大单元数目,即队列深度
- Queue Size: 队列中数据单元的长度,以字节为单位
- Allocation: 分配方式:Dynamic 动态内存创建
- Buffer Name: 缓冲区名称
- Buffer Size: 缓冲区大小
- Conrol Block Name: 控制块名称
(3)、生成代码
三、程序编程
相关函数
1、队列ID:osMessageQId
在freertos.c文件下定义了队列的ID,对队列的操作都需要用到这个ID,例如,对osMessageCreate
的调用返回。可用作参数到osMessageDelete
以删除队列。
osMessageQId myQueue01Handle;
2、使用动态内存的方式创建一个新的队列:osMessageCreate
函数 | osMessageQId osMessageCreate (const osMessageQDef_t *queue_def, osThreadId thread_id) |
---|---|
参数 | queue_def: 引用由osMessageQDef定义的队列 thread_id: 线程ID或NULL |
返回值 | 成功返回任务ID,失败返回0 |
例:
/* Create the queue(s) */
/* definition and creation of myQueue01 */
osMessageQDef(myQueue01, 16, uint32_t);//定义myQueue01队列,第2参数:消息队列的长度,第3参数:消息的大小
myQueue01Handle = osMessageCreate(osMessageQ(myQueue01), NULL);//创建队列
3、队列删除:osMessageDelete
队列删除函数是根据消息队列ID直接删除的,删除之后这个消息队列的所有信息都会被系统回收清空,而且不能再次使用这个消息队列了。
函数 | osStatus osMessageDelete (osMessageQId queue_id) |
---|---|
参数 | queue_id: 消息队列ID,表示的是要删除哪个想队列 |
返回值 | 错误码 |
例:
osMessageDelete(TestQueueHandle);
4、向队列尾部发送一个队列消息:osMessagePut
用于向队列尾部发送一个队列消息。消息以拷贝的形式入队,而不是以引用的形式。可用在中断服务程序中。
函数 | osStatus osMessagePut (osMessageQId queue_id, uint32_t info, uint32_t millisec) |
---|---|
参数 | queue_id: 目标队列ID。这个句柄即是调用 osMessageCreate() 创建该队列时的返回值 info: 发送数据的指针。其指向将要复制到目标队列中的数据单元。由于在创建队列时设置了队列中数据单元的长度,所以会从该指针指向的空间复制对应长度的数据到队列的存储区域。 millisec: 队列空时,阻塞超时的最大时间。如果该参数设置为 0,函数立刻返回。超时时间的单位为系统节拍周期,常量 portTICK_PERIOD_MS 用于辅助计算真实的时间,单位为 ms。如果 INCLUDE_vTaskSuspend 设置成 1,并且指定延时为 portMAX_DELAY 将导致任务无限阻塞(没有超时)。 |
返回值 | 错误码 |
例:
osMessagePut(TestQueueHandle,send_data1,0);
//三个参数分别为:消息队列的句柄,发送的消息内容,等待时间
5、从一个队列中接收消息并把消息从队列中删除:osMessageGet
用于从一个队列中接收消息并把消息从队列中删除。接收的消息是以拷贝的形式进行的,所以我们必须提供一个足够大空间的缓冲区。具体能够拷贝多少数据到缓冲区,这个在队列创建的时候已经设定。可用在中断服务程序中。
例:
osMessageGet(TestQueueHandle,osWaitForever);
//两个参数分别为:消息队列的句柄,等待时间(此时为一直等待)
//使用范例
osEvent event;
event=osMessageGet(TestQueueHandle,osWaitForever);//一直等待
6、从队列中接收数据单元,但是并不删除接收到的单元:osMessagePeek
osMessagePeek() 也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。osMessagePeek() 从队列首接收到数据后,不会修改队列中的数据,也不会改变数据在队列中的存储序顺。可用在中断服务程序中。
7、查询队列中当前有校的数据单元个数:osMessageWaiting
例:
uint32_t a=osMessageWaiting(TestQueueHandle);
编写代码
1、传输数字
(1)、发送
void LED1_Task1(void const * argument)
{
/* USER CODE BEGIN LED1_Task1 */
/* Infinite loop */
osEvent xReturn;
uint32_t send_data1;
for(;;)
{
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("this LED1 run %d\r\n",++send_data1);
xReturn.status=osMessagePut(myQueue01Handle,send_data1,0);
if(osOK!=xReturn.status)
{
printf("send fail\n");
}
osDelay(500);
}
/* USER CODE END LED1_Task1 */
}
(2)、接收
void LED2_Task03(void const * argument)
{
/* USER CODE BEGIN LED2_Task03 */
/* Infinite loop */
osEvent event;
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); //LED1状态每500s翻转一次
event=osMessageGet(myQueue01Handle,osWaitForever);
if(osEventMessage==event.status)
{
printf("receive data:%d\n",event.value.v);
}
else
{
printf("error:0x%d\n",event.status);
}
// osDelay(600);
}
/* USER CODE END LED2_Task03 */
}
(3)、下载验证:
程序编译无误后下载到板子上,可以看到LED1发送的数据,LED2均能接收。
2、传输字符串或者结构体
(1)、发送
typedef struct
{
uint8_t* name;
uint8_t id;
uint8_t age;
}T_data;
/* USER CODE END Header_LED1_Task1 */
void LED1_Task1(void const * argument)
{
/* USER CODE BEGIN LED1_Task1 */
/* Infinite loop */
osEvent xReturn;
uint32_t send_data1;
for(;;)
{
T_data m_data;
uint8_t name1[]="H2ZStr";
m_data.age=20;
m_data.id=2;
m_data.name=name1;
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("LED1 send !!\n");
xReturn.status=osMessagePut(myQueue01Handle,(uint32_t)&m_data,0);
if(osOK!=xReturn.status)
{
printf("send fail\n");
}
osDelay(500);
}
/* USER CODE END LED1_Task1 */
}
(2)、接收
void LED2_Task03(void const * argument)
{
/* USER CODE BEGIN LED2_Task03 */
/* Infinite loop */
osEvent event;
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); //LED1状态每500s翻转一次
event=osMessageGet(myQueue01Handle,osWaitForever);
if(event.status==osEventMessage)
{
T_data *pData=(T_data *)event.value.p;
printf("data=%d\n",pData->age);
printf("id=%d\n",pData->id);
printf("name=%s\n",pData->name);
}
// osDelay(600);
}
/* USER CODE END LED2_Task03 */
}
(3)、下载验证:
程序编译无误后下载到板子上,可以看到LED1发送的数据,LED2均能接收。
3、以邮箱的形式传输数据
邮箱不能使用cubemx自动生成,需要手动添加
(1)、邮箱发送
typedef struct
{
uint8_t* name;
uint8_t id;
uint8_t age;
}T_data;
osMailQId mailQ01Handle; //定义邮箱ID
/* USER CODE END Header_LED1_Task1 */
void LED1_Task1(void const * argument)
{
/* USER CODE BEGIN LED1_Task1 */
/* Infinite loop */
osMailQDef(mailQ01,15,T_data);
mailQ01Handle=osMailCreate(osMailQ(mailQ01),NULL); //创建邮箱
osEvent xReturn;
uint32_t send_data1;
for(;;)
{
T_data m_data;
uint8_t name1[]="H2ZStr";
m_data.age=20;
m_data.id=2;
m_data.name=name1;
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_5); //LED1状态每500s翻转一次
printf("LED1 send !!\n");
osMailPut(mailQ01Handle,&m_data); //邮箱发送
osDelay(500);
}
/* USER CODE END LED1_Task1 */
}
(2)邮箱接收
void LED2_Task03(void const * argument)
{
/* USER CODE BEGIN LED2_Task03 */
/* Infinite loop */
for(;;)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_5); //LED1状态每500s翻转一次
osEvent event=osMailGet(mailQ01Handle,osWaitForever);
if(event.status==osEventMail)
{
T_data *m_Data=(T_data *)event.value.p;
printf("data=%d\n",m_Data->age);
printf("id=%d\n",m_Data->id);
printf("name=%s\n",m_Data->name);
}
// osDelay(600);
}
/* USER CODE END LED2_Task03 */
}
(3)、下载验证:
程序编译无误后下载到板子上,可以看到LED1发送的数据,LED2均能接收。
四、参考文献
韦东山freeRTOS系列教程之【第五章】队列(queue)_freertos queue-CSDN博客
STM32CubeMX学习笔记(29)——FreeRTOS实时操作系统使用(消息队列)_cubemx的sys里的timebase source-CSDN博客 stm32cubemx hal学习记录:FreeRTOS消息队列_osmessagecreate-CSDN博客
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)