文章目录
前言
写博客是为了总结知识,提炼知识不做知识的搬运者
文章不一定全是原创但是所写内容都有经过自身研读与提炼
喜欢我博客的朋友可以跟我交流与点赞,
一、消息队列是什么?
队列是任务间通信的主要形式。它们可用于在任务之间以及中断和任务之间发送消息。在大多数情况下,它们用作线程安全的 FIFO(先进先出)缓冲区,将新数据发送到队列的后面,尽管数据也可以发送到前面。
- 是一种常用于任务间通信的数据结构队列可以在任务与任务间、中断和任务间传递信息,实现了任务接收来自其他任务或中断的不固定长度的消息,任务能够从队列里面读取消息。
- 当队列中的消息是空时,读取消息的任务将被阻塞,用户还可以指定阻塞的任务时间 xTicksToWait,在这段时间中,如果队列为空,该任务将保持阻塞状态以等待队列数据有效。当队列中有新消息时,被阻塞的任务会被唤醒并处理新消息;当等待的时间超过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转为就绪态。
- 通过消息队列服务,任务或中断服务例程可以将一条或多条消息放入消息队列中。同
样,一个或多个任务可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常
是将先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入消息队列的
消息,即先进先出原则(FIFO),但是也支持后进先出原则(LIFO)。
总结:队列是单片机数据结构,用于任务间的通信,任务可以在一个队列(这个队列是自己定义的大小)中发送和收取多个消息,可以设置阻塞状态,超过阻塞状态会自动就绪处于发送或者接收信息
二、消息队列的作用
- 使用消息队列可以让 RTOS 内核有效地管理任务,而全局数组是无法做到的,任务的超时等机制需要用户自己去实现
- 使用了全局数组就要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心。
- 使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题。
- FIFO 机制更有利于数据的处理。
- 消息队列可以应用于发送不定长消息的场合,包括任务与任务间的消息交换,队列是
FreeRTOS 主要的任务间通讯方式,可以在任务与任务间、中断和任务间传送信息,发送到
队列的消息是通过拷贝方式实现的,这意味着队列存储的数据是原数据,而不是原数据的
引用。
这部分内容由于实践项目应用不够无法多于总结
三、FreeRtos中消息队列API的调用
使用丰富的API23个函数可以实现 FreeRTOS 的消息队列官网链接:API链接
重要函数理解:
函数 xQueueCreate
函数返回类型为指针QueueHandle_t所以需要提前定义一个指针QueueHandle_t queuex;用于函数的返回,即
queue =QueueHandle_t xQueueCreate(x,x)
QueueHandle_t xQueueCreate
( UBaseType_t uxQueueLength, /* 消息个数 */
UBaseType_t uxItemSize ); /* 每个消息大小,单位字节 */
函数 xQueueSend
BaseType_t xQueueSend(
QueueHandle_t xQueue, /* 消息队列句柄 */
const void * pvItemToQueue, /* 要传递数据地址 */
TickType_t xTicksToWait /* 等待消息队列有空间的最大等待时间 */
);
重点:
- 接收到消息以后可以自动删除当前已经接收的消息接收下一个队列里面的消息,
- 区分一下中断里面的xQueueSendFromISR,
- 了解调用API的返回值
函数 xQueueReceive
BaseType_t xQueueReceive(
QueueHandle_t xQueue, /* 消息队列句柄 */
void *pvBuffer, /* 接收消息队列数据的缓冲地址 */
TickType_t xTicksToWait /* 等待消息队列有数据的最大等待时间 */
);
函数 xQueueSendFromISR
BaseType_t xQueueSendFromISR
(
QueueHandle_t xQueue, /* 消息队列句柄 */
const void *pvItemToQueue, /* 要传递数据地址 */
BaseType_t *pxHigherPriorityTaskWoken /* 高优先级任务是否被唤醒的状态保存 */
);
中断函数里面调用发送队列的信息
函数 uxQueueMessagesWaiting
UBaseType_t uxQueueMessagesWaiting(QueueHandle_t xQueue);
函数 uxQueueSpacesAvailable
UBaseType_t uxQueueSpacesAvailable(QueueHandle_t xQueue);
函数 xQueuePeek
BaseType_t xQueuePeek(
QueueHandle_t xQueue,
无效 *pvBuffer,
TickType_t xTicksToWait
);
函数 xQueueOverwrite
BaseType_t xQueueOverwrite(
QueueHandle_t xQueue,
const void * pvItemToQueue
);
接收到消息以后不会自动删除当前已经接收的消息不会接收下一个队列里面的消息,一直卡在了队头
四、基于esp32-idf实验
这个实验不拘泥于任何单片机,可以在任何单片机上运行
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
TaskHandle_t myTask1Handle = NULL;
TaskHandle_t myTask2Handle = NULL;
//队列句柄
QueueHandle_t queue1;
void task1(void *arg){
char txbuff[50];
queue1= xQueueCreate(5, sizeof(txbuff));
//创建队列返回已创建队列的句柄
if( queue1 == 0 )
{
printf("failed to create queue1= %p \n",queue1);
// Failed to create the queue.
}
sprintf(txbuff,"hello world! 1");//c语言库赋值
xQueueSend(queue1, (void*)txbuff,(TickType_t)0 );
sprintf(txbuff,"hello world! 2");
xQueueSend(queue1, (void*)txbuff,(TickType_t)0 );
sprintf(txbuff,"hello world! 3");
xQueueSendToFront(queue1, (void*)txbuff,(TickType_t)0 );
while(1){
printf("data waiting to be read : %d
available spaces: %d \n",
uxQueueMessagesWaiting(queue1),//队列中待读取的消息
uxQueueSpacesAvailable(queue1));//队列剩余空间
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void task2(void *arg)
{
char rxbuff[50];
while(1)
{ //if(xQueuePeek(queue1, &(rxbuff) , (TickType_t)5 ))
if(xQueueReceive(queue1, &(rxbuff) , (TickType_t)5 )){
printf("got a data from queue! === %s \n",rxbuff); }
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(){
xTaskCreate(task1, "task1", 4096, NULL, 10, &myTask1Handle);
xTaskCreatePinnedToCore(task2, "task2", 4096, NULL, 10, &myTask2Handle,1);
}
进一步实验
按键中断通信里面发送消息给队列与增加发送检测
【if(xQueueSend(queue1, (void*)txbuff,(TickType_t)0 ) !=1】为什么这么写呢?根据官方解释返回值
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
#define ESP_INTR_FLAG_DEFAULT 0
#define CONFIG_LED_PIN 32
#define CONFIG_BUTTON_PIN 0
TaskHandle_t myTask1Handle = NULL;
TaskHandle_t myTask2Handle = NULL;
QueueHandle_t queue1;
TaskHandle_t ISR = NULL;
// interrupt service routine, called when the button is pressed
void IRAM_ATTR button_isr_handler(void* arg) {
//响应中断服务程序的函数:
//发送数据需要用xQueueSendFromISR函数
//不可以实验xQueuesend
char txbuff[50];
sprintf(txbuff,"hello from ISR!");
xQueueSendFromISR(queue1, &txbuff, NULL);
}
void task1(void *arg){
char txbuff[50];
queue1= xQueueCreate(1, sizeof(txbuff));
if( queue1 == 0 ) {
printf("failed to create queue1= %p \n",queue1); // Failed to create the queue.
}
sprintf(txbuff,"hello world! 1");
if(xQueueSend(queue1, (void*)txbuff,(TickType_t)0 ) !=1){
printf("could not sended this message = %s \n",txbuff); }
sprintf(txbuff,"hello world! 2");
if(xQueueSend(queue1, (void*)txbuff , (TickType_t)0 ) !=1) {
printf("could not sended this message = %s \n",txbuff); }
sprintf(txbuff,"hello world! 3");
if(xQueueOverwrite(queue1, (void*)txbuff ) !=1) {
printf("could not sended this message = %s \n",txbuff); }
while(1){
vTaskDelay(pdMS_TO_TICKS(10));
printf("data waiting to be read : %d available spaces: %d \n",uxQueueMessagesWaiting(queue1),uxQueueSpacesAvailable(queue1));
vTaskDelay(pdMS_TO_TICKS(1000)); }}
void task2(void *arg){char rxbuff[50];while(1)
{ if(xQueueReceive(queue1, &(rxbuff) ,
(TickType_t)5 ))
{
printf("got a data from queue! === %s \n",rxbuff); }
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(){
gpio_pad_select_gpio(CONFIG_BUTTON_PIN);
gpio_pad_select_gpio(CONFIG_LED_PIN);
gpio_set_direction(CONFIG_BUTTON_PIN,
GPIO_MODE_INPUT);
gpio_set_direction(CONFIG_LED_PIN,
GPIO_MODE_OUTPUT); // enable interrupt on falling (1->0) edge for button pin
gpio_set_intr_type(CONFIG_BUTTON_PIN,
GPIO_INTR_NEGEDGE);
//Install the driver’s GPIO ISR handler service, which allows per-pin GPIO interrupt handlers. // install ISR service with default configuration
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);// attach the interrupt service routine
gpio_isr_handler_add(CONFIG_BUTTON_PIN,
button_isr_handler, NULL);
xTaskCreate(task1,
"task1", 4096, NULL, 10, &myTask1Handle);
xTaskCreatePinnedToCore(task2,
"task2", 4096, NULL, 10, &myTask2Handle,1);
}
五、总结
- API的调用注重调用机制与流程,入口
- 需要记忆一些常见函数的返回值,入库参数的用法
- 作者Lee1989写于2021.09.10喜欢文章欢迎点赞关注