STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-消息队列

消息队列

CMSIS-RTOS v2 (CMSIS-RTOS2) 为基于 Arm® Cortex® 处理器的设备提供通用 RTOS 接口。 它为需要 RTOS 功能的软件组件提供标准化 API,从而为用户和软件行业带来巨大的好处。

本文将详细介绍在CMSIS-RTOS V2中如何使用队列(Message Queue)。

1、消息队列介绍

一个队列可以容纳有限数量的固定大小的数据项。 队列可以容纳的最大项目数称为“长度”。 每个数据项的长度和大小都是在创建队列时设置的。

队列通常用作先进先出 (FIFO) 缓冲区,其中数据被写入队列的末端(尾部)并从队列的前端(头部)移除。 下图演示了正在用作 FIFO 的队列中写入和读取的数据。 也可以写入队列的前面,并覆盖已经在队列前面的数据。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 队列发送的所有数据必须属于同一类型,在队列创建阶段声明。 它可以是简单的变量或结构。
  • 队列长度在创建阶段声明,定义为将通过队列发送的项目数。
  • 队列内的操作在关键部分执行(通过对 BASEPRI 寄存器进行编程来阻止中断,以便在队列上进行操作。
  • 任务可以超时或无限地阻塞队列发送或接收数据。
  • 如果多个任务被阻塞等待从队列接收/发送数据,那么当数据/空间可用时,只有具有最高优先级的任务才会被解除阻塞。 如果两个任务具有相同的优先级,则等待时间最长的任务将被解除阻塞。

1.2 CMSIS-RTOS V2中的消息队列

消息传递是线程之间的另一种基本通信模型。 在消息传递模型中,一个线程显式发送数据,而另一个线程接收数据。 该操作更像是某种 I/O,而不是直接访问要共享的信息。 在 CMSIS-RTOS 中,这种机制称为消息队列。 数据以类似 FIFO 的操作从一个线程传递到另一个线程。 使用消息队列功能,可以控制、发送、接收或等待消息。 要传递的数据可以是整数或指针类型:

在这里插入图片描述

与内存池相比,消息队列通常效率较低,但可以解决更广泛的问题。 有时,线程没有公共地址空间或使用共享内存会引发问题,例如互斥。

  • 在 CMSIS-RTOS API 中有两种类型的队列:
    • 只能发送整数类型数据或指针的消息
    • 可以发送内存块的邮件
1
2
注意:函数 osMessageQueuePut、osMessageQueueGet、osMessageQueueGetCapacity、osMessageQueueGetMsgSize、osMessageQueueGetCount、osMessageQueueGetSpace 可以从中断服务程序中调用。

CMSIS-RTOS V2提供了如下API:

在这里插入图片描述

  • **osMessageQueueId_t osMessageQueueNew(uint32_t msg_count,uint32_t msg_size,const osMessageQueueAttr_t * attr)**: 创建并初始化一个消息队列对象。 该函数返回一个消息队列对象标识符或在发生错误时返回 NULL。

该函数可以在内核初始化后使用 osKernelInitialize 调用。 可以在使用 osKernelStart 启动 RTOS 内核之前创建消息队列对象。

消息队列数据所需的内存总量至少为 msg_count * msg_size。 msg_size 向上舍入为双偶数以确保内存块的 32 位对齐。

从消息队列分配的内存块具有使用参数 msg_size 定义的固定大小。

注意:该函数不能在中断服务中调用

  • **osStatus_t osMessageQueuePut(osMessageQueueId_t mq_id,const void * msg_ptr,uint8_t msg_prio,uint32_t timeout)**:将msg_ptr指向的消息放入参数mq_id指定的消息队列中。

    • 参数 msg_prio 用于在插入时根据消息的优先级(数字越大表示优先级越高)对消息进行排序。

    • 参数 timeout 指定系统等待将消息放入队列的时间。 在系统等待期间,调用此函数的线程将进入 BLOCKED 状态。 参数 timeout 可以有以下值:

      • 当 timeout 为 0 时,函数立即返回(即尝试语义)。
      • 当 timeout 设置为 osWaitForever 时,函数将无限等待直到消息被传递(即等待语义)。
      • 所有其他值在内核滴答中指定超时时间(即定时等待语义)。返回值osStatus_t :
    • osOK:消息已放入队列。

    • osErrorTimeout:消息无法在给定时间内放入队列(等待时间语义)。

    • osErrorResource:队列中没有足够的空间(尝试语义)。

    • osErrorParameter:参数 mq_id 为 NULL 或无效,在 ISR 中指定的非零超时。注意:如果参数 timeout 设置为 0,则可以从中断服务程序调用。

简单的示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include "cmsis\_os2.h" // CMSIS RTOS header file

/\*----------------------------------------------------------------------------
\* Message Queue creation & usage
\*---------------------------------------------------------------------------\*/

#define MSGQUEUE\_OBJECTS 16 // number of Message Queue Objects

typedef struct { // object data type
uint8\_t Buf[32];
uint8\_t Idx;
} MSGQUEUE_OBJ_t;

osMessageQueueId\_t mid_MsgQueue; // message queue id

osThreadId\_t tid_Thread_MsgQueue1; // thread id 1
osThreadId\_t tid_Thread_MsgQueue2; // thread id 2

void Thread\_MsgQueue1 (void \*argument); // thread function 1
void Thread\_MsgQueue2 (void \*argument); // thread function 2

int Init\_MsgQueue (void) {

mid_MsgQueue = osMessageQueueNew(MSGQUEUE_OBJECTS, sizeof(MSGQUEUE_OBJ_t), NULL);
if (mid_MsgQueue == NULL) {
; // Message Queue object not created, handle failure
}

tid_Thread_MsgQueue1 = osThreadNew(Thread_MsgQueue1, NULL, NULL);
if (tid_Thread_MsgQueue1 == NULL) {
return(-1);
}
tid_Thread_MsgQueue2 = osThreadNew(Thread_MsgQueue2, NULL, NULL);
if (tid_Thread_MsgQueue2 == NULL) {
return(-1);
}

return(0);
}

void Thread\_MsgQueue1 (void \*argument) {
MSGQUEUE_OBJ_t msg;

while (1) {
; // Insert thread code here...
msg.Buf[0] = 0x55U; // do some work...
msg.Idx = 0U;
osMessageQueuePut(mid_MsgQueue, &msg, 0U, 0U);
osThreadYield(); // suspend thread
}
}

void Thread\_MsgQueue2 (void \*argument) {
MSGQUEUE_OBJ_t msg;
osStatus\_t status;

while (1) {
; // Insert thread code here...
status = osMessageQueueGet(mid_MsgQueue, &msg, NULL, 0U); // wait for message
if (status == osOK) {
; // process data
}
}
}

  • **osStatus_t osMessageQueueGet(osMessageQueueId_t mq_id,void * msg_ptr,uint8_t * msg_prio,uint32_t timeout)**:从参数 mq_id 指定的消息队列中检索一条消息,并将其保存到参数 msg_ptr 指向的缓冲区中。 如果不是 token{NULL},则消息优先级存储到参数 msg_prio。

    • 参数 timeout 指定系统等待从队列中检索消息的时间。 在系统等待期间,调用此函数的线程将进入 BLOCKED 状态。

    • 参数 timeout 可以有以下值:

      • 当 timeout 为 0 时,函数立即返回(即尝试语义)。
      • 当超时设置为 osWaitForever 时,该函数将等待无限时间,直到检索到消息(即等待语义)。
      • 所有其他值在内核滴答中指定超时时间(即定时等待语义)。返回值osStatus_t :
    • osOK:消息已从队列中检索。

    • osErrorTimeout:在给定时间内无法从队列中检索消息(定时等待语义)。

    • osErrorResource:没有从队列中得到(尝试语义)。

    • osErrorParameter:参数 mq_id 为 NULL 或无效,在 ISR 中指定的非零超时。注意:如果参数 timeout 设置为 0,则可以从中断服务程序调用。

  • **osStatus_t osMessageQueueDelete(osMessageQueueId_t mq_id)**:删除由参数 mq_id 指定的消息队列对象。 它释放用于消息队列处理的内部内存。 在此调用之后,mq_id 不再有效,无法使用。 可以使用函数 osMessageQueueNew 再次创建消息队列。

返回的结果如下:

+ **osOK**:消息队列对象已被删除。
+ **osErrorParameter**:参数 mq\_id 为 NULL 或无效。
+ **osErrorResource**:消息队列处于无效状态。
+ **osErrorISR**:无法从中断服务例程调用 osMessageQueueDelete。

2、在STM32CubeIDE中配置消息队列

第一步: 新建工程

新建工程,请参考前面文章:

串口配置,请参考前面文章:

第二步: 配置时钟

配置时钟,请参考前面文章:

第三步:配置FreeRTOS

配置FreeRTOS,及FreeRTOS的堆栈,请参考:

第四步:添加线程

创建线程,请参考前面文章:

在本次实例中定义的线程如下:

在这里插入图片描述

第五步:添加消息队列

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

第六步:保存生成代码

3、消息队列功能代码实现

main.c文件的main函数中,RTOS启动调用如下:

1
2
3
4
5
6
7
/\* Init scheduler \*/
osKernelInitialize(); /\* Call init function for freertos objects (in freertos.c) \*/
MX\_FREERTOS\_Init();

/\* Start scheduler \*/
osKernelStart();

freertos.c文件中,生成定义的消息队列、线程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
osThreadId\_t defaultTaskHandle;
const osThreadAttr\_t defaultTask_attributes = {
.name = "defaultTask",
.stack_size = 128 \* 4,
.priority = (osPriority\_t) osPriorityNormal,
};
/\* Definitions for senderHandle \*/
osThreadId\_t senderHandleHandle;
const osThreadAttr\_t senderHandle_attributes = {
.name = "senderHandle",
.stack_size = 256 \* 4,
.priority = (osPriority\_t) osPriorityNormal,
};
/\* Definitions for receiverHandle \*/
osThreadId\_t receiverHandleHandle;
const osThreadAttr\_t receiverHandle_attributes = {
.name = "receiverHandle",
.stack_size = 256 \* 4,
.priority = (osPriority\_t) osPriorityNormal,
};
/\* Definitions for counterQueue \*/
osMessageQueueId\_t counterQueueHandle;
const osMessageQueueAttr\_t counterQueue_attributes = {
.name = "counterQueue"
};

1
2
3
4
void StartDefaultTask(void \*argument);
void senderTask(void \*argument);
void receiverTask(void \*argument);

线程执行代码功能实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/\*\*
\* @brief Function implementing the defaultTask thread.
\* @param argument: Not used
\* @retval None
\*/
/\* USER CODE END Header\_StartDefaultTask \*/
void StartDefaultTask(void \*argument)
{
/\* USER CODE BEGIN StartDefaultTask \*/
/\* Infinite loop \*/
for (;;) {
osDelay(1);
}
/\* USER CODE END StartDefaultTask \*/
}

/\* USER CODE BEGIN Header\_senderTask \*/
/\*\*
\* @brief Function implementing the senderHandle thread.
\* @param argument: Not used
\* @retval None
\*/
/\* USER CODE END Header\_senderTask \*/
void senderTask(void \*argument)
{
/\* USER CODE BEGIN senderTask \*/
/\* Infinite loop \*/
int counter = 0;
for (;;) {
osDelay(1000);
counter++;
osMessageQueuePut(counterQueueHandle, &counter, 0, 100);
printf("queue sent:%d\r\n",counter);
}
/\* USER CODE END senderTask \*/
}

/\* USER CODE BEGIN Header\_receiverTask \*/
/\*\*
\* @brief Function implementing the receiverHandle thread.
\* @param argument: Not used
\* @retval None
\*/
/\* USER CODE END Header\_receiverTask \*/
void receiverTask(void \*argument)
{
/\* USER CODE BEGIN receiverTask \*/
/\* Infinite loop \*/
uint16\_t localVa = 0;
for(;;){
//osDelay(100);
osMessageQueueGet(counterQueueHandle, &localVa, 0, 500);
printf("queue received:%d\r\n",localVa);
}
/\* USER CODE END receiverTask \*/
}

freertos.c文件中的MX_FREERTOS_Init函数中,创建消息队列:

1
2
3
counterQueueHandle = osMessageQueueNew(16, sizeof(uint16\_t),
&counterQueue_attributes);

创建线程:

1
2
3
4
5
6
7
8
9
10
11
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL,
&defaultTask_attributes);

/\* creation of senderHandle \*/
senderHandleHandle = osThreadNew(senderTask, NULL,
&senderHandle_attributes);

/\* creation of receiverHandle \*/
receiverHandleHandle = osThreadNew(receiverTask, NULL,
&receiverHandle_attributes);

最后,编译生成程序并下载到开板板。

4、STM32F1与STM32CubeIDE快速入门系列文章

序号 内容
1 STM32F1与STM32CubeIDE快速入门-开发环境搭建
2 STM32F1与STM32CubeIDE快速入门-STM32F1微控制器概述
3 STM32F1与STM32CubeIDE快速入门-GPIO概述与点亮LED
4 STM32F1与STM32CubeIDE快速入门-按键与LED控制
5 STM32F1与STM32CubeIDE快速入门-中断、NVIC与EXTI概述
6 STM32F1与STM32CubeIDE快速入门-外部中断配置与功能实现
7 STM32F1与STM32CubeIDE快速入门-USART/UART串口通信
8 STM32F1与STM32CubeIDE快速入门-定时器(Timer)概述
9 STM32F1与STM32CubeIDE快速入门-定时器定时模式
10 STM32F1与STM32CubeIDE快速入门-定时器计数模式
11 STM32F1与STM32CubeIDE快速入门-定时器PWM模式
12 STM32F1与STM32CubeIDE快速入门-定时器编码(Encoder)模式
13 STM32F1与STM32CubeIDE快速入门-定时器输入捕获模式(Input Capture Mode)实现频率计数
14 STM32F1与STM32CubeIDE快速入门-DMA概述
15 STM32F1与STM32CubeIDE快速入门-USART通过DMA进行数据接收与发送
16 STM32F1与STM32CubeIDE快速入门-ADC概述
17 STM32F1与STM32CubeIDE快速入门-ADC轮询方式实现PWM调光器
18 STM32F1与STM32CubeIDE快速入门-ADC中断方式实现PWM调光器
19 STM32F1与STM32CubeIDE快速入门-ADC通过DMA方式与PWM实现调光器
20 STM32F1与STM32CubeIDE快速入门-DAC概述
21 STM32F1与STM32CubeIDE快速入门-SPI概述
22 STM32F1与STM32CubeIDE快速入门-M25P16串行闪存驱动
23 STM32F1与STM32CubeIDE快速入门-I2C概述
24 STM32F1与STM32CubeIDE快速入门-I2C驱动LCD1602显示屏(基于PCF8574)
25 STM32F1与STM32CubeIDE快速入门-独立看门狗(IWDG)
26 STM32F1与STM32CubeIDE快速入门-OLED-SSD1306-I2C驱动
27 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2配置(基于FreeRTOS)
28 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-线程管理
29 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS v2-延时
30 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-定时器管理
31 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-互斥(Mutex)管理
32 STM32F1与STM32CubeIDE编程实例-CMSIS-RTOS V2-信号量(Semaphore)

文章来源: https://iotsmart.blog.csdn.net/article/details/124706653