原创 八、freeRTOS 队列

2020-8-23 16:49 2341 24 24 分类: 汽车电子 文集: FreeRTOS

队列简介:

注意,队列长度并不是每个队列的字节数,而是能存多少条数据。如我们创建一个队列,每条消息最大10字节,队列可以保存4条消息,那么队列的长度为4。

队列的功能:

1 数据拷贝

为什么一定要使用数据拷贝?

考虑以下情况,我们在任务中创建了一个局部变量,而局部变量可能随时会被删掉,此时如果使用引用传递(指针),数据可能就会出现问题;另外,如果采用指针,则在这个变量被队列引用之前,变量值是不能改变的,这就意味着此时任务将不能继续随意使用此变量;如果采用数据拷贝,则不会出现上述问题。

2 多任务访问

队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。

3 出队阻塞

4 入队阻塞

队列操作过程

1 创建队列

2 写入队列

3 读取队列

队列结构体原型:

  1. typedef struct QueueDefinition
  2. {
  3. int8_t *pcHead; /* 指向队列存储区开始地址 */
  4. int8_t *pcTail; /* 指向队列存储区最后一个字节 */
  5. int8_t *pcWriteTo; /* 指向存储区中下一个空闲区域 */
  6. union /* Use of a union is an exception to the coding standard to ensure two mutually exclusive structure members don't appear simultaneously (wasting RAM). */
  7. {
  8. int8_t *pcReadFrom; /* 当用作队列的时候指向最后一个出队的队列项首地址 */
  9. UBaseType_t uxRecursiveCallCount; /* 当用作递归互斥量的时候用来记录递归互斥量被调用的次数 */
  10. } u;
  11. List_t xTasksWaitingToSend; /* 等待发送任务列表,那些因为队列满导致入队失败而进入阻塞态的任务就会挂到此列表上 */
  12. List_t xTasksWaitingToReceive; /* 等待接收任务列表,那些因为队列空导致出队失败而进入阻塞态的任务就会挂到此列表上 */
  13. volatile UBaseType_t uxMessagesWaiting; /* 队列中当前队列项数量,也就是消息数 */
  14. UBaseType_t uxLength; /* 创建队列时指定的队列长度,也就是队列中最大允许的队列项(消息)数量 */
  15. UBaseType_t uxItemSize; /* 创建队列时指定的每个队列项(消息)最大长度,单位字节 */
  16. /* 当队列上锁以后用来统计从队列中接收到的队列项数量,也就是出队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED */
  17. volatile int8_t cRxLock;
  18. /* 当队列上锁以后用来统计发送到队列中的队列项数量,也就是入队的队列项数量,当队列没有上锁的话此字段为 queueUNLOCKED */
  19. volatile int8_t cTxLock;
  20. #if ((configSUPPORT_STATIC_ALLOCATION == 1) && (configSUPPORT_DYNAMIC_ALLOCATION == 1))
  21. uint8_t ucStaticallyAllocated; /* 如果使用静态存储的话此字段设置为 pdTURE */
  22. #endif
  23. #if (configUSE_QUEUE_SETS == 1)
  24. struct QueueDefinition *pxQueueSetContainer;
  25. #endif
  26. #if (configUSE_TRACE_FACILITY == 1)
  27. UBaseType_t uxQueueNumber;
  28. uint8_t ucQueueType;
  29. #endif
  30. } xQUEUE;
  31. typedef xQUEUE Queue_t;

 

创建队列

1 动态创建队列

函数原型:

实现的功能,就是动态内存申请需要大小的内存 = 消息数 × 消息长度,然后将队列存储区指针偏移到缓存区。

  1. QueueHandle_t xQueueGenericCreate(const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType)
  2. {
  3. Queue_t *pxNewQueue;
  4. size_t xQueueSizeInBytes;
  5. uint8_t *pucQueueStorage;
  6. configASSERT(uxQueueLength > (UBaseType_t)0);
  7. if (uxItemSize == (UBaseType_t)0)
  8. {
  9. /* There is not going to be a queue storage area. */
  10. xQueueSizeInBytes = (size_t)0;
  11. }
  12. else
  13. {
  14. /* Allocate enough space to hold the maximum number of items that
  15. can be in the queue at any time. */
  16. xQueueSizeInBytes = (size_t)(uxQueueLength * uxItemSize); /*lint !e961 MISRA exception as the casts are only redundant for some ports. */
  17. }
  18. pxNewQueue = (Queue_t *)pvPortMalloc(sizeof(Queue_t) + xQueueSizeInBytes);
  19. if (pxNewQueue != NULL)
  20. {
  21. /* Jump past the queue structure to find the location of the queue
  22. storage area. */
  23. pucQueueStorage = ((uint8_t *)pxNewQueue) + sizeof(Queue_t);
  24. #if (configSUPPORT_STATIC_ALLOCATION == 1)
  25. {
  26. /* Queues can be created either statically or dynamically, so
  27. note this task was created dynamically in case it is later
  28. deleted. */
  29. pxNewQueue->ucStaticallyAllocated = pdFALSE;
  30. }
  31. #endif /* configSUPPORT_STATIC_ALLOCATION */
  32. prvInitialiseNewQueue(uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue);
  33. }
  34. return pxNewQueue;
  35. }

函数说明:

静态创建队列:

入队函数:

入队函数说明:

函数参数说明:

通用发送函数简单分析:

数据拷贝过程:

中断级队列发送:
 

下面是正点原子串口中断中发送队列的程序:

  1. void USART1_IRQHandler(void) //串口1中断服务程序
  2. {
  3. u8 Res;
  4. BaseType_t xHigherPriorityTaskWoken;
  5. if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
  6. {
  7. Res = USART_ReceiveData(USART1); //读取接收到的数据
  8. if ((USART_RX_STA & 0x8000) == 0) //接收未完成
  9. {
  10. if (USART_RX_STA & 0x4000) //接收到了0x0d
  11. {
  12. if (Res != 0x0a)
  13. USART_RX_STA = 0; //接收错误,重新开始
  14. else
  15. USART_RX_STA |= 0x8000; //接收完成了
  16. }
  17. else //还没收到0X0D
  18. {
  19. if (Res == 0x0d)
  20. USART_RX_STA |= 0x4000;
  21. else
  22. {
  23. USART_RX_BUF[USART_RX_STA & 0X3FFF] = Res;
  24. USART_RX_STA++;
  25. if (USART_RX_STA > (USART_REC_LEN - 1))
  26. USART_RX_STA = 0; //接收数据错误,重新开始接收
  27. }
  28. }
  29. }
  30. }
  31. //就向队列发送接收到的数据
  32. if ((USART_RX_STA & 0x8000) && (Message_Queue != NULL))
  33. {
  34. xQueueSendFromISR(Message_Queue, USART_RX_BUF, &xHigherPriorityTaskWoken); //向队列中发送数据
  35. USART_RX_STA = 0;
  36. memset(USART_RX_BUF, 0, USART_REC_LEN); //清除数据接收缓冲区USART_RX_BUF,用于下一次数据接收
  37. portYIELD_FROM_ISR(xHigherPriorityTaskWoken); //如果需要的话进行一次任务切换
  38. }
  39. }

其实这个程序,个人觉得局限性比较大,因为它规定了数据格式。总体思路是,在串口接收中断中,将接收到的数据一个一个的保存到缓冲区里,等待串口数据接收完毕(可以通过读取寄存器标志位,串口空闲标志位来判断接收完毕)时,将缓冲区里的数据发送到队列中,然后清空缓冲区,并根据相关变量确定是否需要进行任务切换。

 

出队函数

删除读取队列函数参数说明:

不删除读取队列函数参数说明:

同样的,中断读取队列函数:

中断中进行任务切换的判别函数:

portYIELD_FROM_ISR(xHigherPriorityTaskWoken);

 

队列实验

设计2个任务,其中按键任务用于获取键值,并发送至队列;按键处理任务获取队列中的消息,并处理键值。

按键队列任务如下所示:

  1. key_queue = xQueueCreate(1, sizeof(uint8_t)); // 创建按键队列
  2. if (key_queue == NULL)
  3. {
  4. printf("按键队列创建失败!\n");
  5. }
  6. void key_task(void *pvParameters)
  7. {
  8. uint8_t key_value;
  9. BaseType_t error_value;
  10. for (;;)
  11. {
  12. if (key_scan(KEY1_GPIO_Port, KEY1_Pin) == KEY_ON)
  13. {
  14. key_value = 1;
  15. if (key_queue != NULL)
  16. {
  17. // error_value = xQueueSendToBack(key_queue, &key_value, 10); // 发送键值至队列
  18. error_value = xQueueOverwrite(key_queue, &key_value); // 覆盖发送键值至队列
  19. if (error_value == errQUEUE_FULL)
  20. {
  21. printf("按键队列已满,消息发送失败!\n");
  22. }
  23. }
  24. }
  25. if (key_scan(KEY2_GPIO_Port, KEY2_Pin) == KEY_ON)
  26. {
  27. key_value = 2;
  28. if (key_queue != NULL)
  29. {
  30. error_value = xQueueSendToBack(key_queue, &key_value, 10); // 发送键值至队列
  31. if (error_value == errQUEUE_FULL)
  32. {
  33. printf("按键队列已满,消息发送失败!\n");
  34. }
  35. }
  36. }
  37. vTaskDelay(50);
  38. }
  39. }
  40. void print_task(void *pvParameters)
  41. {
  42. uint8_t rx_key_value;
  43. BaseType_t rx_queue_state;
  44. for (;;)
  45. {
  46. if (key_queue != NULL)
  47. {
  48. // 等待从队列中读取数据
  49. rx_queue_state = xQueueReceive(key_queue, &rx_key_value, portMAX_DELAY);
  50. if (rx_queue_state == pdTRUE)
  51. {
  52. printf("key_value = %d\n", rx_key_value);
  53. switch (rx_key_value)
  54. {
  55. case 1:
  56. LED_RED;
  57. break;
  58. case 2:
  59. LED_BLUE;
  60. break;
  61. default:
  62. LED_ALL_OFF;
  63. break;
  64. }
  65. }
  66. }
  67. vTaskDelay(50);
  68. }
  69. }

测试结果如下所示:

中断服务函数使用队列

这个比较复杂,总体思路是,在串口接受回调函数中,将接收到的数据保存至RX_BUFFER;在空闲中断中,将RX_BUFFER的数据全部发送至队列;在队列处理任务中,将队列中的数据读到TX_BUFFER中,然后打印输出。

1 使能接收中断和空闲中断

  1. // 使能串口接收中断和空闲中断,并清除标志位
  2. __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE);
  3. __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);
  4. __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
  5. __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);

2 接收中断处理中,将接收到的字符保存至RX_BUFFER缓冲区

  1. // 串口数据接收完成,串口空闲时,将RX_BUFFER发送至队列
  2. if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
  3. {
  4. i = 0;
  5. // 发送RX_BUFFER至usart_queue队列
  6. xQueueSendToBackFromISR(usart_queue, RX_BUFFER, &pxHigherPriorityTaskWoken);
  7. // 判断是否需要进行任务切换
  8. if (pxHigherPriorityTaskWoken == pdTRUE)
  9. {
  10. taskYIELD();
  11. }
  12. // 清空RX_BUFFER缓冲区
  13. memset(RX_BUFFER, 0, USART_QUEUE_LEN);
  14. // 清除空闲中断(依次读取SR DR)
  15. temp = huart1.Instance->SR;
  16. temp = huart1.Instance->DR;
  17. }

整个串口中断服务函数如下所示:

  1. void USART1_IRQHandler(void)
  2. {
  3. static uint16_t i = 0;
  4. uint32_t temp;
  5. // 保存接收数据到RX_BUFFER缓冲区
  6. if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))
  7. {
  8. RX_BUFFER[i++] = huart1.Instance->DR;
  9. __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);
  10. }
  11. // 串口数据接收完成,串口空闲时,将RX_BUFFER发送至队列
  12. if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
  13. {
  14. i = 0;
  15. // 发送RX_BUFFER至usart_queue队列
  16. xQueueSendToBackFromISR(usart_queue, RX_BUFFER, &pxHigherPriorityTaskWoken);
  17. // 判断是否需要进行任务切换
  18. if (pxHigherPriorityTaskWoken == pdTRUE)
  19. {
  20. taskYIELD();
  21. }
  22. // 清空RX_BUFFER缓冲区
  23. memset(RX_BUFFER, 0, USART_QUEUE_LEN);
  24. // 清除空闲中断(依次读取SR DR)
  25. temp = huart1.Instance->SR;
  26. temp = huart1.Instance->DR;
  27. }
  28. }

读取串口消息队列数据的任务如下所示:

  1. void usart_task(void *pvParameters)
  2. {
  3. BaseType_t rx_queue_state;
  4. for (; ;)
  5. {
  6. if (usart_queue != NULL)
  7. {
  8. // 等待从串口消息队列中读取数据
  9. rx_queue_state = xQueueReceive(usart_queue, TX_BUFFER, portMAX_DELAY);
  10. if (rx_queue_state == pdTRUE)
  11. {
  12. printf("%s\n", TX_BUFFER);
  13. memset(TX_BUFFER, 0, USART_QUEUE_LEN);
  14. }
  15. }
  16. vTaskDelay(50);
  17. }
  18. }

注意,这个实验要求,串口的中断优先级可以被屏蔽,即 ≥ 5(5为预设的屏蔽优先级,只有这样,才能在中断服务函数中使用fromISR函数,发送队列),如下所示:

测试结果如下所示:

完整的工程代码:

main.c

  1. #include "main.h"
  2. #include "i2c.h"
  3. #include "usart.h"
  4. #include "gpio.h"
  5. #include "bsp_led.h"
  6. #include "bsp_key.h"
  7. #include "bsp_usart.h"
  8. #include "FreeRTOS.h"
  9. #include "task.h"
  10. #include "queue.h"
  11. #include
  12. #define USART_QUEUE_DEPTH 1
  13. #define USART_QUEUE_LEN 100
  14. void start_task(void *pvParameters);
  15. void key_task(void *pvParameters);
  16. void print_task(void *pvParameters);
  17. void usart_task(void *pvParameters);
  18. /* 任务相关参数 */
  19. #define START_TASK_SIZE 128
  20. #define START_TASK_PRIO 1
  21. TaskHandle_t start_Task_Handle;
  22. #define KEY_TASK_SIZE 128
  23. #define KEY_TASK_PRIO 2
  24. TaskHandle_t key_task_Handle;
  25. #define PRINT_TASK_SIZE 128
  26. #define PRINT_TASK_PRIO 3
  27. TaskHandle_t print_task_Handle;
  28. #define USART_TASK_SIZE 128
  29. #define USART_TASK_PRIO 3
  30. TaskHandle_t usart_task_Handle;
  31. /* 队列相关参数 */
  32. QueueHandle_t key_queue;
  33. QueueHandle_t usart_queue;
  34. uint8_t RX_BUFFER[USART_QUEUE_LEN]; // 串口接收缓冲区
  35. uint8_t TX_BUFFER[USART_QUEUE_LEN]; // 串口发送缓冲区
  36. int main(void)
  37. {
  38. HAL_Init();
  39. SystemClock_Config();
  40. MX_GPIO_Init();
  41. MX_USART1_UART_Init();
  42. MX_I2C1_Init();
  43. MX_I2C2_Init();
  44. // 使能串口接收中断和空闲中断,并清除标志位
  45. __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_IDLE);
  46. __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);
  47. __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE);
  48. __HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
  49. // 创建开始任务
  50. xTaskCreate((TaskFunction_t )start_task,
  51. (char * )"start_task",
  52. (uint16_t )START_TASK_SIZE,
  53. (void * )NULL,
  54. (UBaseType_t )START_TASK_PRIO,
  55. (TaskHandle_t * )&start_Task_Handle);
  56. vTaskStartScheduler(); // 开启调度器
  57. }
  58. void start_task(void *pvParameters)
  59. {
  60. taskENTER_CRITICAL();
  61. /* 创建按键队列 */
  62. key_queue = xQueueCreate(1, sizeof(uint8_t));
  63. if (key_queue == NULL)
  64. {
  65. printf("按键队列创建失败!\n");
  66. }
  67. /* 创建串口消息队列 */
  68. usart_queue = xQueueCreate(USART_QUEUE_DEPTH, USART_QUEUE_LEN);
  69. if (usart_queue == NULL)
  70. {
  71. printf("串口消息队列创建失败!\n");
  72. }
  73. // 创建按键任务
  74. xTaskCreate((TaskFunction_t )key_task,
  75. (char * )"key_task",
  76. (uint16_t )KEY_TASK_SIZE,
  77. (void * )NULL,
  78. (UBaseType_t )KEY_TASK_PRIO,
  79. (TaskHandle_t * )&key_task_Handle);
  80. // 创建消息处理任务
  81. xTaskCreate((TaskFunction_t )print_task,
  82. (char * )"print_task",
  83. (uint16_t )PRINT_TASK_SIZE,
  84. (void * )NULL,
  85. (UBaseType_t)PRINT_TASK_PRIO,
  86. (TaskHandle_t * )&print_task_Handle);
  87. // 创建串口队列任务
  88. xTaskCreate((TaskFunction_t )usart_task,
  89. (char * )"usart_task",
  90. (uint16_t )USART_TASK_SIZE,
  91. (void * )NULL,
  92. (UBaseType_t )USART_TASK_PRIO,
  93. (TaskHandle_t * )&usart_task_Handle);
  94. taskEXIT_CRITICAL();
  95. // 删除开始任务
  96. vTaskDelete(start_Task_Handle);
  97. }
  98. void key_task(void *pvParameters)
  99. {
  100. uint8_t key_value;
  101. BaseType_t error_value;
  102. for (;;)
  103. {
  104. if (key_scan(KEY1_GPIO_Port, KEY1_Pin) == KEY_ON)
  105. {
  106. key_value = 1;
  107. if (key_queue != NULL)
  108. {
  109. // error_value = xQueueSendToBack(key_queue, &key_value, 10); // 发送键值至队列
  110. error_value = xQueueOverwrite(key_queue, &key_value); // 覆盖发送键值至队列
  111. if (error_value == errQUEUE_FULL)
  112. {
  113. printf("按键队列已满,消息发送失败!\n");
  114. }
  115. }
  116. }
  117. if (key_scan(KEY2_GPIO_Port, KEY2_Pin) == KEY_ON)
  118. {
  119. key_value = 2;
  120. if (key_queue != NULL)
  121. {
  122. error_value = xQueueSendToBack(key_queue, &key_value, 10); // 发送键值至队列
  123. if (error_value == errQUEUE_FULL)
  124. {
  125. printf("按键队列已满,消息发送失败!\n");
  126. }
  127. }
  128. }
  129. vTaskDelay(50);
  130. }
  131. }
  132. void print_task(void *pvParameters)
  133. {
  134. uint8_t rx_key_value;
  135. BaseType_t rx_queue_state;
  136. for (;;)
  137. {
  138. if (key_queue != NULL)
  139. {
  140. // 等待从队列中读取数据
  141. rx_queue_state = xQueueReceive(key_queue, &rx_key_value, portMAX_DELAY);
  142. if (rx_queue_state == pdTRUE)
  143. {
  144. printf("key_value = %d\n", rx_key_value);
  145. switch (rx_key_value)
  146. {
  147. case 1:
  148. LED_RED;
  149. break;
  150. case 2:
  151. LED_BLUE;
  152. break;
  153. default:
  154. LED_ALL_OFF;
  155. break;
  156. }
  157. }
  158. }
  159. vTaskDelay(50);
  160. }
  161. }
  162. void usart_task(void *pvParameters)
  163. {
  164. BaseType_t rx_queue_state;
  165. for (; ;)
  166. {
  167. if (usart_queue != NULL)
  168. {
  169. // 等待从串口消息队列中读取数据
  170. rx_queue_state = xQueueReceive(usart_queue, TX_BUFFER, portMAX_DELAY);
  171. if (rx_queue_state == pdTRUE)
  172. {
  173. printf("%s\n", TX_BUFFER);
  174. memset(TX_BUFFER, 0, USART_QUEUE_LEN);
  175. }
  176. }
  177. vTaskDelay(50);
  178. }
  179. }

中断服务函数:

  1. void USART1_IRQHandler(void)
  2. {
  3. static uint16_t i = 0;
  4. uint32_t temp;
  5. // 保存接收数据到RX_BUFFER缓冲区
  6. if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE))
  7. {
  8. RX_BUFFER[i++] = huart1.Instance->DR;
  9. __HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_RXNE);
  10. }
  11. // 串口数据接收完成,串口空闲时,将RX_BUFFER发送至队列
  12. if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_IDLE))
  13. {
  14. i = 0;
  15. // 发送RX_BUFFER至usart_queue队列
  16. xQueueSendToBackFromISR(usart_queue, RX_BUFFER, &pxHigherPriorityTaskWoken);
  17. // 判断是否需要进行任务切换
  18. if (pxHigherPriorityTaskWoken == pdTRUE)
  19. {
  20. taskYIELD();
  21. }
  22. // 清空RX_BUFFER缓冲区
  23. memset(RX_BUFFER, 0, USART_QUEUE_LEN);
  24. // 清除空闲中断(依次读取SR DR)
  25. temp = huart1.Instance->SR;
  26. temp = huart1.Instance->DR;
  27. }
  28. }

文章评论0条评论)

登录后参与讨论
我要评论
0
24
关闭 站长推荐上一条 /2 下一条