学习目标

  1. 理解队列的概念
  2. 掌握消息队列开发流程
  3. 掌握基本数据类型消息队列
  4. 掌握复杂数据类型消息队列

    学习内容

    队列

    队列(Queue)是一种数据结构,用于存储和管理元素的线性集合。它遵循先进先出(FIFO,First-In-First-Out)的原则,即最先进入队列的元素将首先被移出队列。
    队列通常具有两个基本操作:

  5. 入队(Enqueue):将元素添加到队列的末尾。新元素进入队列后成为新的队尾。

  6. 出队(Dequeue):从队列的头部移除并返回元素。被移除的元素为队列中存在时间最长的元素,即最先入队的元素。

队列的特性使其非常适合在任务间进行数据传递和通信。任务可以将数据或消息按顺序放入队列,并按照先入先出的原则进行处理。这种方式可以有效地实现任务间的解耦和异步通信。
队列可以具有固定大小或动态增长的能力,取决于具体的实现和需求。固定大小的队列在创建时需要指定最大容量,而动态队列可以根据需要进行扩展。
060.png

消息队列

顾名思义,消息队列就是存储消息的队列。遵循队列的规则。
061.png
在 FreeRTOS 中,消息队列(Message Queue)是一种用于任务间通信的机制,允许任务之间传递和共享数据。消息队列提供了一种异步的通信方式,发送任务可以将消息放入队列,接收任务则可以从队列中获取消息。
以下是 FreeRTOS 消息队列的一些关键概念:

  1. 队列大小(Queue Size):消息队列具有固定的容量,即可以容纳的消息数量。在创建队列时,需要指定队列的大小。
  2. 消息类型(Message Type):消息队列可以传递不同类型的消息,消息类型可以是任意数据结构,如整数、结构体、指针等。
  3. 发送任务(Sending Task):发送任务负责将消息发送到队列中。发送任务通过调用 xQueueSend() 或 xQueueSendFromISR() 函数将消息发送到队列。
  4. 接收任务(Receiving Task):接收任务负责从队列中获取消息。接收任务通过调用 xQueueReceive() 或 xQueueReceiveFromISR() 函数从队列中获取消息。
  5. 阻塞与非阻塞操作:发送任务和接收任务可以选择在操作队列时是阻塞还是非阻塞的。阻塞操作会使任务在队列操作无法立即执行时进入阻塞状态,而非阻塞操作则会立即返回,无论队列操作是否成功。

使用消息队列可以实现任务之间的数据传递和同步。发送任务可以将数据封装成消息,并将其发送到队列中,接收任务则可以从队列中获取消息并进行处理。消息队列可以用于解耦任务之间的通信,提高系统的灵活性和可维护性。

功能介绍

基础API如下

功能 描述
xQueueCreate 创建队列
xQueueSend 向队列中添加元素
xQueueReceive 从队列中取出元素

创建队列

  1. QueueHandle_t xQueueCreate(const UBaseType_t uxQueueLength,
  2. const UBaseType_t uxItemSize);

参数说明:

  1. const UBaseType_t uxQueueLength消息队列的长度
  2. const UBaseType_t uxItemSize队列中单个元素的所占字节数。

返回值:
QueueHandle_t为消息队列的句柄。

入队

  1. BaseType_t xQueueSend( QueueHandle_t xQueue,
  2. const void * const pvItemToQueue,
  3. TickType_t xTicksToWait);

参数说明:

  1. QueueHandle_t xQueue:队列句柄。
  2. const void * const pvItemToQueue:数据。
  3. TickType_t xTicksToWait:入队列等待时间。

返回值:
BaseType_t:队列是否成功或者失败,pdTrue或者pdFalse

出队

  1. BaseType_t xQueueReceive( QueueHandle_t xQueue,
  2. void * const pvBuffer,
  3. TickType_t xTicksToWait);

参数说明:

  1. QueueHandle_t xQueue:队列句柄。
  2. void * const pvBuffer:数据。
  3. TickType_t xTicksToWait:出队列等待时间。

返回值:
BaseType_t:出队列是否成功或者失败,pdTrue或者pdFalse

基本类型数据

通常基本数据类型用uint8_t``uint16_t``uint32_t等进行表示。
此处以uint8_t为例。
构建时定义,定义数据的长度。

  1. QueueHandle_t queue = xQueueCreate(128, sizeof(uint8_t));

入队列操作

  1. uint8_t cnt = 0;
  2. xQueueSend(queue, &cnt, portMAX_DELAY);
  3. cnt++;

出队列操作

  1. uint8_t data;
  2. xQueueReceive(queue, &data, portMAX_DELAY);
  3. printf("queue recv: %d\r\n", data);

完整示例逻辑:

  1. #include "gd32f4xx.h"
  2. #include "systick.h"
  3. #include <stdio.h>
  4. #include "main.h"
  5. #include "FreeRTOS.h"
  6. #include "task.h"
  7. #include "queue.h"
  8. #include "Usart0.h"
  9. TaskHandle_t task_handler;
  10. TaskHandle_t task_key_handler;
  11. TaskHandle_t task1_handler;
  12. QueueHandle_t queue;
  13. void task1(void *pvParameters) {
  14. BaseType_t result;
  15. uint8_t value;
  16. while(1) {
  17. xQueueReceive(queue, &value, portMAX_DELAY);
  18. printf("queue recv: %d\r\n", value);
  19. }
  20. }
  21. void task_key(void *pvParameters) {
  22. FlagStatus pre_state = RESET;
  23. uint8_t cnt = 0;
  24. while(1) {
  25. FlagStatus state = gpio_input_bit_get(GPIOA, GPIO_PIN_0);
  26. if(SET == state && pre_state == RESET) {
  27. // 当前高电平, 上一次为低电平,按下
  28. pre_state = state;
  29. xQueueSend(queue, &cnt, portMAX_DELAY);
  30. cnt++;
  31. } else if(RESET == state && pre_state == SET) {
  32. // 当前高电平, 上一次为低电平,抬起
  33. pre_state = state;
  34. }
  35. vTaskDelay(20);
  36. }
  37. }
  38. void start_task(void *pvParameters) {
  39. taskENTER_CRITICAL();
  40. xTaskCreate(task_key, "task_key", 64, NULL, 2, &task_key_handler);
  41. xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
  42. vTaskDelete(task_handler);
  43. taskEXIT_CRITICAL();
  44. }
  45. void Usart0_recv(uint8_t *data, uint32_t len)
  46. {
  47. printf("recv: %s\n", data);
  48. }
  49. static void GPIO_config() {
  50. // 时钟初始化
  51. rcu_periph_clock_enable(RCU_GPIOA);
  52. // 配置GPIO模式
  53. gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0);
  54. }
  55. int main(void)
  56. {
  57. systick_config();
  58. GPIO_config();
  59. Usart0_init();
  60. queue = xQueueCreate(128, sizeof(uint8_t));
  61. xTaskCreate(start_task, "start_task", 128, NULL, 1, &task_handler);
  62. vTaskStartScheduler();
  63. while(1) {}
  64. }

复杂类型数据

通常复杂类型用struct表示。
例如可以定义一个结构体:

  1. typedef struct {
  2. char buffer[64];
  3. uint32_t len;
  4. } MyData_t;

构建时定义,定义数据的长度。

  1. QueueHandle_t queue = xQueueCreate(128, sizeof(MyData_t));

入队列操作

  1. MyData_t data;
  2. sprintf(data.buffer, "hello %d", cnt++);
  3. data.len = sizeof(data.buffer);
  4. data.buffer[data.len] = '\0';
  5. xQueueSend(queue, &data, portMAX_DELAY);

出队列操作

  1. MyData_t data;
  2. xQueueReceive(queue, &data, portMAX_DELAY);
  3. printf("queue recv: %d %s\r\n", data.len, data.buffer);

完整示例。

  1. #include "gd32f4xx.h"
  2. #include "systick.h"
  3. #include <stdio.h>
  4. #include "main.h"
  5. #include "FreeRTOS.h"
  6. #include "task.h"
  7. #include "queue.h"
  8. #include "Usart0.h"
  9. #include <string.h>
  10. TaskHandle_t task_handler;
  11. TaskHandle_t task_key_handler;
  12. TaskHandle_t task1_handler;
  13. QueueHandle_t queue;
  14. typedef struct {
  15. char buffer[64];
  16. uint32_t len;
  17. } MyData_t;
  18. void task1(void *pvParameters) {
  19. BaseType_t result;
  20. MyData_t data;
  21. while(1) {
  22. xQueueReceive(queue, &data, portMAX_DELAY);
  23. printf("queue recv: %d %s\r\n", data.len, data.buffer);
  24. }
  25. }
  26. void task_key(void *pvParameters) {
  27. FlagStatus pre_state = RESET;
  28. MyData_t data;
  29. uint32_t cnt = 0;
  30. while(1) {
  31. FlagStatus state = gpio_input_bit_get(GPIOA, GPIO_PIN_0);
  32. if(SET == state && pre_state == RESET) {
  33. // 当前高电平, 上一次为低电平,按下
  34. pre_state = state;
  35. sprintf(data.buffer, "hello %d", cnt++);
  36. data.len = sizeof(data.buffer);
  37. data.buffer[data.len] = '\0';
  38. xQueueSend(queue, &data, portMAX_DELAY);
  39. } else if(RESET == state && pre_state == SET) {
  40. // 当前高电平, 上一次为低电平,抬起
  41. pre_state = state;
  42. }
  43. vTaskDelay(20);
  44. }
  45. }
  46. void start_task(void *pvParameters) {
  47. taskENTER_CRITICAL();
  48. xTaskCreate(task_key, "task_key", 64, NULL, 2, &task_key_handler);
  49. xTaskCreate(task1, "task1", 64, NULL, 2, &task1_handler);
  50. vTaskDelete(task_handler);
  51. taskEXIT_CRITICAL();
  52. }
  53. void Usart0_recv(uint8_t *data, uint32_t len)
  54. {
  55. printf("recv: %s\n", data);
  56. MyData_t d;
  57. d.len = len;
  58. sprintf(d.buffer, "%s", data);
  59. d.buffer[d.len] = '\0';
  60. //xQueueSend(queue, &d, portMAX_DELAY);
  61. xQueueSendFromISR(queue, &d, NULL);
  62. }
  63. static void GPIO_config() {
  64. // 时钟初始化
  65. rcu_periph_clock_enable(RCU_GPIOA);
  66. // 配置GPIO模式
  67. gpio_mode_set(GPIOA, GPIO_MODE_INPUT, GPIO_PUPD_PULLDOWN, GPIO_PIN_0);
  68. }
  69. int main(void)
  70. {
  71. systick_config();
  72. GPIO_config();
  73. Usart0_init();
  74. queue = xQueueCreate(128, sizeof(MyData_t));
  75. xTaskCreate(start_task, "start_task", 128, NULL, 1, &task_handler);
  76. vTaskStartScheduler();
  77. while(1) {}
  78. }

信号量和消息队列

  1. 从数据结构上进行比较。

消息队列是一种数据结构,用于在任务之间传递和共享数据或消息。它可以存储多个消息,并且可以按照先进先出(FIFO)的顺序进行访问。
信号量是一种同步机制,用于控制对共享资源的访问和任务之间的同步。它可以表示可用资源的数量或资源的占用状态。

  1. 从操作方式上进行比较。

消息队列可以存储和获取多个消息。
信号量只有获取和释放两种操作。

  1. 从特性上进行比较。

消息队列可以按照先进先出的顺序处理消息。
信号量只表示资源的可用状态或占用状态。

练习题

  1. 开启两个任务,一个任务负责向队列中添加基本数据,一个向队列中获取基本数据。
  2. 开启两个任务,一个任务负责向队列中添加复杂数据,一个向队列中获取复杂数据。