学习目标
- 理解队列的概念
- 掌握消息队列开发流程
- 掌握基本数据类型消息队列
-
学习内容
队列
队列(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)的顺序进行访问。
信号量是一种同步机制,用于控制对共享资源的访问和任务之间的同步。它可以表示可用资源的数量或资源的占用状态。
- 从操作方式上进行比较。
消息队列可以存储和获取多个消息。
信号量只有获取和释放两种操作。
- 从特性上进行比较。
消息队列可以按照先进先出的顺序处理消息。
信号量只表示资源的可用状态或占用状态。
练习题
- 开启两个任务,一个任务负责向队列中添加基本数据,一个向队列中获取基本数据。
- 开启两个任务,一个任务负责向队列中添加复杂数据,一个向队列中获取复杂数据。
