在有流式数据处理的嵌入式系统中,队列(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 是队列的变量名,即我们定义的QueueUartByteHandle ;msg_ptr 是要入队/出队的数据块指针;msg_prio 比较奇怪,入队和出队对应的变量类型不同,表示优先级,但一般都赋NULL ;最后的timeout ,不等待就为0,永远等待用osWaitForever 。这两个函数都可以在中断中运行。
对于FreeRTOS而言,各个API都有普通线程版本和中断版本,CMSIS在编写它的API时,就根据当时状态判断,以CMISIS的osMessageQueuePut 为例,通过IS_IRQ() 宏进行判断:
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;
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);
osSemaphoreRelease(semAddToQueueHandle);
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
}
void task1fxn(void *argument)
{
uint8_t recv_char;
for(;;)
{
osSemaphoreAcquire(semAddToQueueHandle,osWaitForever);
osDelay(1);
osMessageQueueGet(QueueUartByteHandle, &recv_char, NULL, 0);
HAL_UART_Transmit_IT(&huart1, &recv_char, 0x01);
}
}
|