CMSIS/FreeRTOS中队列(Queue)的使用

在有流式数据处理的嵌入式系统中,队列(Queue)是几乎必然被使用的工具,但大多数开发板提供的FreeRTOS例程是不包含队列的,要使用还要自己研究。这次我的样例把串口收到的数据按字节塞入队列,再让另一个线程处理,是一种相对画蛇添足的做法,这么做主要目的是说明队列如何使用。队列更适合用来处理ADC/DAC采样数据、通信模块固定大小的数据包等。

这次我仍是使用自制的STM32F0模块实验,与上次一样,我们还是用STM32CubeMX来生成初始代码,在FreeRTOS中添加队列:
队列设置这里,我设队列的每个项目为uint8_t类型,队列元素最多16个。其实更常见的是设置成某个结构体,这可以等到代码生成后,在代码里修改。这一设置变成了生成代码中的以下代码:

osMessageQueueId_t QueueUartByteHandle;
const osMessageQueueAttr_t QueueUartByte_attributes = {
  .name = "QueueUartByte"
};

QueueUartByteHandle = osMessageQueueNew (16, sizeof(uint8_t), &QueueUartByte_attributes);

FreeRTOS的队列占用空间可以根据以下公式计算:

S

p

a

c

e

(

B

y

t

e

s

)

=

92

+

E

l

e

m

S

i

z

e

×

n

E

l

e

m

Space(Bytes) =92 + ElemSize \times nElem

Space(Bytes)=92+ElemSize×nElem
比如我这里设置一个元素是1字节,最多16个,那么就会占用92+16=108字节。

我们还是用CMSIS v2的API,而非直接用FreeRTOS的API。参考CMSIS的文档,CMSIS把队列叫做“消息队列”(Message Queue),实现FIFO功能,入队和出队API分别为:

osStatus_t 	osMessageQueuePut (osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout);
osStatus_t 	osMessageQueueGet (osMessageQueueId_t mq_id, void *msg_ptr, uint8_t *msg_prio, uint32_t timeout);

其中,mq_id是队列的变量名,即我们定义的QueueUartByteHandlemsg_ptr是要入队/出队的数据块指针;msg_prio比较奇怪,入队和出队对应的变量类型不同,表示优先级,但一般都赋NULL;最后的timeout,不等待就为0,永远等待用osWaitForever。这两个函数都可以在中断中运行。

对于FreeRTOS而言,各个API都有普通线程版本和中断版本,CMSIS在编写它的API时,就根据当时状态判断,以CMISIS的osMessageQueuePut为例,通过IS_IRQ()宏进行判断:

// From cmsis_os2.c
osStatus_t osMessageQueuePut (osMessageQueueId_t mq_id, const void *msg_ptr, uint8_t msg_prio, uint32_t timeout) {
  QueueHandle_t hQueue = (QueueHandle_t)mq_id;
  osStatus_t stat;
  BaseType_t yield;

  (void)msg_prio; /* Message priority is ignored */

  stat = osOK;

  if (IS_IRQ()) {
    if ((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) {
      stat = osErrorParameter;
    }
    else {
      yield = pdFALSE;

      if (xQueueSendToBackFromISR (hQueue, msg_ptr, &yield) != pdTRUE) {
        stat = osErrorResource;
      } else {
        portYIELD_FROM_ISR (yield);
      }
    }
  }
  else {
    if ((hQueue == NULL) || (msg_ptr == NULL)) {
      stat = osErrorParameter;
    }
    else {
      if (xQueueSendToBack (hQueue, msg_ptr, (TickType_t)timeout) != pdPASS) {
        if (timeout != 0U) {
          stat = osErrorTimeout;
        } else {
          stat = osErrorResource;
        }
      }
    }
  }

  return (stat);
}

我让串口接收中断回调函数把接收到的数据压入队列,让一个任务线程去处理,中断回调通过一个计数信号量通知任务,信号量也在STM32CubeMX中定义:
计数信号量代码生成后,还要把信号量的初始值改为0:

semAddToQueueHandle = osSemaphoreNew(16, 0, &semAddToQueue_attributes);

最后,我把中断回调函数和任务线程函数的代码拍上来,展示如何信号量如何同步、如何向队列存取数据:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	uint8_t recv_char;
	HAL_UART_Receive_IT(huart, &recv_char, 0x01);
	osMessageQueuePut(QueueUartByteHandle, &recv_char, 0U, 0U); // without waiting
	osSemaphoreRelease(semAddToQueueHandle);
	HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13); // just to show uart is working
}
void task1fxn(void *argument)
{
  /* Infinite loop */
	uint8_t  recv_char;
  for(;;)
  {
		osSemaphoreAcquire(semAddToQueueHandle,osWaitForever);
    osDelay(1);
		osMessageQueueGet(QueueUartByteHandle, &recv_char, NULL, 0);
		HAL_UART_Transmit_IT(&huart1, &recv_char, 0x01);
  }
}

版权声明:本文为CSDN博主「HanMenglin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/linmh0130/article/details/122734813

生成海报
点赞 0

HanMenglin

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

趣聊51之串口通信(概念篇)

对于刚刚接触单片机的同学们来说,串口通信似乎是一个神秘感十足的东西,笔者在刚刚开始学习51单片机时,读的是郭天祥先生的那本著名的《新概念51单片机教程》,贼厚的一本书,但是等