学习目标
- 理解队列的概念
- 掌握消息队列开发流程
- 掌握基本数据类型消息队列
-
学习内容
队列
队列(Queue)是一种数据结构,用于存储和管理元素的线性集合。它遵循先进先出(FIFO,First-In-First-Out)的原则,即最先进入队列的元素将首先被移出队列。
队列通常具有两个基本操作: 入队(Enqueue):将元素添加到队列的末尾。新元素进入队列后成为新的队尾。
- 出队(Dequeue):从队列的头部移除并返回元素。被移除的元素为队列中存在时间最长的元素,即最先入队的元素。
队列的特性使其非常适合在任务间进行数据传递和通信。任务可以将数据或消息按顺序放入队列,并按照先入先出的原则进行处理。这种方式可以有效地实现任务间的解耦和异步通信。
队列可以具有固定大小或动态增长的能力,取决于具体的实现和需求。固定大小的队列在创建时需要指定最大容量,而动态队列可以根据需要进行扩展。
消息队列
顾名思义,消息队列就是存储消息的队列。遵循队列的规则。
在 FreeRTOS 中,消息队列(Message Queue)是一种用于任务间通信的机制,允许任务之间传递和共享数据。消息队列提供了一种异步的通信方式,发送任务可以将消息放入队列,接收任务则可以从队列中获取消息。
以下是 FreeRTOS 消息队列的一些关键概念:
- 队列大小(Queue Size):消息队列具有固定的容量,即可以容纳的消息数量。在创建队列时,需要指定队列的大小。
- 消息类型(Message Type):消息队列可以传递不同类型的消息,消息类型可以是任意数据结构,如整数、结构体、指针等。
- 发送任务(Sending Task):发送任务负责将消息发送到队列中。发送任务通过调用 xQueueSend() 或 xQueueSendFromISR() 函数将消息发送到队列。
- 接收任务(Receiving Task):接收任务负责从队列中获取消息。接收任务通过调用 xQueueReceive() 或 xQueueReceiveFromISR() 函数从队列中获取消息。
- 阻塞与非阻塞操作:发送任务和接收任务可以选择在操作队列时是阻塞还是非阻塞的。阻塞操作会使任务在队列操作无法立即执行时进入阻塞状态,而非阻塞操作则会立即返回,无论队列操作是否成功。
使用消息队列可以实现任务之间的数据传递和同步。发送任务可以将数据封装成消息,并将其发送到队列中,接收任务则可以从队列中获取消息并进行处理。消息队列可以用于解耦任务之间的通信,提高系统的灵活性和可维护性。
功能介绍
基础API如下
功能 | 描述 |
---|---|
xQueueCreate | 创建队列 |
xQueueSend | 向队列中添加元素 |
xQueueReceive | 从队列中取出元素 |
创建队列
QueueHandle_t xQueueCreate(const UBaseType_t uxQueueLength,
const UBaseType_t uxItemSize);
参数说明:
const UBaseType_t uxQueueLength
消息队列的长度const UBaseType_t uxItemSize
队列中单个元素的所占字节数。
入队
BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * const pvItemToQueue,
TickType_t xTicksToWait);
参数说明:
QueueHandle_t xQueue
:队列句柄。const void * const pvItemToQueue
:数据。TickType_t xTicksToWait
:入队列等待时间。
返回值:BaseType_t
:队列是否成功或者失败,pdTrue
或者pdFalse
出队
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait);
参数说明:
QueueHandle_t xQueue
:队列句柄。void * const pvBuffer
:数据。TickType_t xTicksToWait
:出队列等待时间。
返回值:BaseType_t
:出队列是否成功或者失败,pdTrue
或者pdFalse
基本类型数据
通常基本数据类型用uint8_t``uint16_t``uint32_t
等进行表示。
此处以uint8_t
为例。
构建时定义,定义数据的长度。
QueueHandle_t queue = xQueueCreate(128, sizeof(uint8_t));
入队列操作
uint8_t cnt = 0;
xQueueSend(queue, &cnt, portMAX_DELAY);
cnt++;
出队列操作
uint8_t data;
xQueueReceive(queue, &data, portMAX_DELAY);
printf("queue recv: %d\r\n", data);
完整示例逻辑:
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "Usart0.h"
TaskHandle_t task_handler;
TaskHandle_t task_key_handler;
TaskHandle_t task1_handler;
QueueHandle_t queue;
void task1(void *pvParameters) {
BaseType_t result;
uint8_t value;
while(1) {
xQueueReceive(queue, &value, portMAX_DELAY);
printf("queue recv: %d\r\n", value);
}
}
void task_key(void *pvParameters) {
FlagStatus pre_state = RESET;
uint8_t cnt = 0;
while(1) {
FlagStatus state = gpio_input_bit_get(GPIOA, GPIO_PIN_0);
if(SET == state && pre_state == RESET) {
// 当前高电平, 上一次为低电平,按下
pre_state = state;
xQueueSend(queue, &cnt, portMAX_DELAY);
cnt++;
} else if(RESET == state && pre_state == SET) {
// 当前高电平, 上一次为低电平,抬起
pre_state = state;
}
vTaskDelay(20);
}
}
void start_task(void *pvParameters) {
taskENTER_CRITICAL();
xTaskCreate(task_key, "task_key", 64, NULL, 2, &task_key_handler);
xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
vTaskDelete(task_handler);
taskEXIT_CRITICAL();
}
void Usart0_recv(uint8_t *data, uint32_t len)
{
printf("recv: %s\n", data);
}
static void GPIO_config() {
// 时钟初始化
rcu_periph_clock_enable(RCU_GPIOA);
// 配置GPIO模式
gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0);
}
int main(void)
{
systick_config();
GPIO_config();
Usart0_init();
queue = xQueueCreate(128, sizeof(uint8_t));
xTaskCreate(start_task, "start_task", 128, NULL, 1, &task_handler);
vTaskStartScheduler();
while(1) {}
}
复杂类型数据
通常复杂类型用struct
表示。
例如可以定义一个结构体:
typedef struct {
char buffer[64];
uint32_t len;
} MyData_t;
构建时定义,定义数据的长度。
QueueHandle_t queue = xQueueCreate(128, sizeof(MyData_t));
入队列操作
MyData_t data;
sprintf(data.buffer, "hello %d", cnt++);
data.len = sizeof(data.buffer);
data.buffer[data.len] = '\0';
xQueueSend(queue, &data, portMAX_DELAY);
出队列操作
MyData_t data;
xQueueReceive(queue, &data, portMAX_DELAY);
printf("queue recv: %d %s\r\n", data.len, data.buffer);
完整示例。
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "main.h"
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "Usart0.h"
#include <string.h>
TaskHandle_t task_handler;
TaskHandle_t task_key_handler;
TaskHandle_t task1_handler;
QueueHandle_t queue;
typedef struct {
char buffer[64];
uint32_t len;
} MyData_t;
void task1(void *pvParameters) {
BaseType_t result;
MyData_t data;
while(1) {
xQueueReceive(queue, &data, portMAX_DELAY);
printf("queue recv: %d %s\r\n", data.len, data.buffer);
}
}
void task_key(void *pvParameters) {
FlagStatus pre_state = RESET;
MyData_t data;
uint32_t cnt = 0;
while(1) {
FlagStatus state = gpio_input_bit_get(GPIOA, GPIO_PIN_0);
if(SET == state && pre_state == RESET) {
// 当前高电平, 上一次为低电平,按下
pre_state = state;
sprintf(data.buffer, "hello %d", cnt++);
data.len = sizeof(data.buffer);
data.buffer[data.len] = '\0';
xQueueSend(queue, &data, portMAX_DELAY);
} else if(RESET == state && pre_state == SET) {
// 当前高电平, 上一次为低电平,抬起
pre_state = state;
}
vTaskDelay(20);
}
}
void start_task(void *pvParameters) {
taskENTER_CRITICAL();
xTaskCreate(task_key, "task_key", 64, NULL, 2, &task_key_handler);
xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
vTaskDelete(task_handler);
taskEXIT_CRITICAL();
}
void Usart0_recv(uint8_t *data, uint32_t len)
{
printf("recv: %s\n", data);
MyData_t d;
d.len = len;
sprintf(d.buffer, "%s", data);
d.buffer[d.len] = '\0';
//xQueueSend(queue, &d, portMAX_DELAY);
xQueueSendFromISR(queue, &d, NULL);
}
static void GPIO_config() {
// 时钟初始化
rcu_periph_clock_enable(RCU_GPIOA);
// 配置GPIO模式
gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0);
}
int main(void)
{
systick_config();
GPIO_config();
Usart0_init();
queue = xQueueCreate(128, sizeof(MyData_t));
xTaskCreate(start_task, "start_task", 128, NULL, 1, &task_handler);
vTaskStartScheduler();
while(1) {}
}
信号量和消息队列
- 从数据结构上进行比较。
消息队列是一种数据结构,用于在任务之间传递和共享数据或消息。它可以存储多个消息,并且可以按照先进先出(FIFO)的顺序进行访问。
信号量是一种同步机制,用于控制对共享资源的访问和任务之间的同步。它可以表示可用资源的数量或资源的占用状态。
- 从操作方式上进行比较。
消息队列可以存储和获取多个消息。
信号量只有获取和释放两种操作。
- 从特性上进行比较。
消息队列可以按照先进先出的顺序处理消息。
信号量只表示资源的可用状态或占用状态。
练习题
- 开启两个任务,一个任务负责向队列中添加基本数据,一个向队列中获取基本数据。
- 开启两个任务,一个任务负责向队列中添加复杂数据,一个向队列中获取复杂数据。