【esp32】xQueueReceive 函数调试踩坑记录

项目过程描述

在项目过程中基于ESP32开发用到了 FreeRTOS中的任务相关的管理,其中一个典型的例子如下,记录下来作为启发。
项目中配置ESP32的某个GPIO引脚为上升沿和下降沿都触发中断,并注册中断服务函数,再创建一个 task 任务执行中断需要处理的相关功能。但是发现, 触发一次中断只执行一次任务 但是中断处理函数中是循环的
简单的说,就是在中断处理中,想完成以下事情,触发一次中断 cnt 开始累加,并不断打印出结果,但是实际上,触发一次中断,只打印一个结果。

while(1){
	if(触发中断)
	{  处理 }
	   cnt++;
	   printf("cnt = %d",cnt);
}

程序代码分析

主函数、中断服务函数:

xQueueHandle gpio_evt_queue = NULL; /*创建一个队列*/

/*中断服务函数*/
void IRAM_ATTR gpio_isr_handler(void* arg){
    uint32_t gpio_num = (uint32_t) arg;
    xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);  /*若某个gpio口发生中断,便将中断发送到队列中*/
}

void main(){
	
    gpio_init();  // GPIO 初始化配置,此处略,配置某个GPIO口为中断触发状态
    xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);   /*创建一个任务*/
    gpio_install_isr_service(0);   /*在MCU的 core0 安装中断服务*/
    gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);   // 添加 gpio0 的中断服务函数
    char cnt = 0;
    while(1) {
        printf("cnt: %d\n", cnt++); /*循环打印 cnt 的值*/
        vTaskDelay(1000 / portTICK_RATE_MS);  /*任务延时1s*/
    }
}

中断服务函数与任务:

/*任务实例*/
void gpio_task_example(void* arg){
   uint32_t io_num;
   char test_cnt = 0;
   while(1){        
        if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {   /*注意这个地方*/
            printf("GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num));
        }
       test_cnt++;
       printf("test_cnt = %d\n",test_cnt);      
   }    
}

分析:原本我以为的过程是,主函数中有一个while(1)循环,开启的任务中也有一个while(1)循环,在任务中的死循环中,如果没有收到任何GPIO的中断,if条件不满足,会打印 test_cnt 变量的值,所以我认为应该输出的结果是以下:
cnt = 1
cnt = 2
cnt = 3
test_cnt = 1
test_cnt = 2
cnt = 4
cnt = 5
test_cnt = 3
………………………………

两个 while 循环交替执行,因为freertos采用的是任务时间片的方法,结果……

调试的时候去惊呆了,代码根本不是和我想的一样执行,打印出的结果只有cnt = 1
cnt = 2
cnt = 3
cnt = 4
cnt = 5
cnt = 6 ,
当我给一个中断的时候,才会打印出一个 test_cnt = 1;于是我把 main 函数中的 while(1)去掉,正常应该打印 test_cnt = 1 test_cnt = 2 test_cnt = 3 test_cnt = 4 ,但是结果确实只有给中断的时候才打印结果而且, test_cnt 的结果和给中断的次数是一样的,也就是只有 xQueueReceive返回true之后,才会执行此函数后边的内容,于是各种不解,开始了打破砂锅问到底的过程。

xQueueReceive 函数分析

【xQueueReceive】这个函数到底有什么作用:xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)
函数作用:从队列中 接收消息并删除消息
参数1:消息队列句柄
参数2:从消息队列中复制出数据后,所存储的缓冲地址,缓冲区空间要大于等于消息队列创建函数 xQueueCreate 所指定的单个消息大小,否则取出的数据无法全部存储到缓冲区,从而造成内存溢出。
参数3:超时等待时间。指定函数(该任务)的阻塞时间。消息队列为空时,等待消息队列有数据的最大等待时间,单位系统时钟节拍。比如可以设置为
const TickType_t xMaxBlockTime = pdMS_TO_TICKS(300); /* 设置最大等待时间为300ms */

注意: 不可以在中断中调用该函数, 如果消息队列为空且第三个参数为0,则函数会立即返回(执行完毕,不会阻塞),若果参数3设置为 portMAX_DELAY,则此函数会永久等待直到消息队列有数据

函数的解释为,从 gpio_evt_queue 这个队列中接受消息,并将接收到的中断 GPIO 端口号赋值给 io_num 这个变量中,portMAX_DELAY 为最大等待超时时间。当有消息产生并且被 xQueueReceive接受(消费后),队列中的消息会被自动删除,如果不想删除就用 xQueuePeek 函数。

注意:在 0 - portMAX_DELAY 这段时间内,当 gpio_evt_queue 队列中没有中断消息产生时,是 卡在这里死等(无限期等待),因此只有给一个GPIO口中断时,其下边的代码才会执行。

总结:在任务的while(1)循环中,当参数3设置为0时,当队列中无中断产生,则函数立即返回,那么会卡在while(1)死循环中无法break,超过任务看门狗的喂狗时间会造成 task watchdog reset。但是当参数3设置为最大超时等待时间时,在未接收到中断时,中断消息队列为空,task处于阻塞状态,虽然卡在 while(1) 循环中,但是不会造成任务看门狗复位。

代码调试过程

1. 测试任务的“死等”,无限期等待

测试:将任务实例改为以下:

/*任务实例*/
void gpio_task_example(void* arg){
   uint32_t io_num;
   char test_cnt = 0;
   while(1){ 
       test_cnt++;
       printf("test_cnt = %d\n",test_cnt);   
        if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {   /*注意这个地方*/
            printf("GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num));
        }   
   }    
}

结果:
test num = 1
cnt: 0
cnt: 1
cnt: 2
cnt: 3
cnt: 4
cnt: 5
cnt: 6

………………当给中断后………………

GPIO[4] intr, val: 0 test num = 2 cnt: 108
GPIO[4] intr, val: 1 test num = 3
cnt: 109

在这里插入图片描述

2. 测试 xQueueReceive 函数 的超时等待时间参数

gpio_task_example函数内容同上,将上边的实例,portMAX_DELAY 参数改为 0,不无限期等待,队列中没有消息就直接跳过。

这样等同于以下第三种方式,输出的结果和3一样

3. 测试无“死等”,但是task中存在耗时较长的循环

如果把 gpio_task_example内容改为:

void gpio_task_example(void* arg){
   char test_cnt = 0;
   while(1){ 
       test_cnt++;
       printf("test_cnt = %d\n",test_cnt);  }   
}

输出结果:直接卡在task的死循环中,都不执行main函数的while循环了,执行一段时间会提示 任务看门狗复位了,因为卡在task中太长时间,导致没有喂狗,造成复位。关于任务看门狗超时复位时间,参考博客:【ESP32学习笔记(40)——Watchdog看门狗使用】
在这里插入图片描述

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

积跬步、至千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值