ESP32-C3 测试(二、GPIO中断、按键驱动测试)

测试第二课,主要了解GPIO中断使用,和测试按键驱动

前言

接下来的ESP32-C3 功能测试都是基于自己设计的开发板:

自己画一块ESP32-C3 的开发板(第一次使用立创EDA)(PCB到手)

开发环境是乐鑫官方的 ESP-IDF, 基于VScode插件搭建好的:

ESP32-C3 VScode开发环境搭建(基于乐鑫官方ESP-IDF)

1、GPIO示例测试

在开发板上面,我们预留了2个按键,一个普通按键接口 GPIO7:
在这里插入图片描述
此外还有一个用于观察启动模式的按键 GPIO9

(设计目的是可以根据按下与不按下观察 ESP32-C3的不同启动模式,同时检测一下芯片启动后是否能够当做普通 GPIO 口使用):
在这里插入图片描述

1.1 GPIO基础测试

基础测试是基于 官方的generic_gpio示例新建工程:
在这里插入图片描述
针对自己的开发板进行代码调整:

  • 使用GPIO7 和 GPIO9 两个按键
  • 添加代码注释
  • 注释掉示例中的以IO口作为中断的输出源的对应部分
/* GPIO Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"

/**
 * Brief:
 * This test code shows how to configure gpio and how to use gpio interrupt.
 *
 * GPIO status:
 * GPIO18: output
 * GPIO19: output
 * GPIO4:  input, pulled up, interrupt from rising edge and falling edge
 * GPIO5:  input, pulled up, interrupt from rising edge.
 *
 * Test:
 * Connect GPIO18 with GPIO4
 * Connect GPIO19 with GPIO5
 * Generate pulses on GPIO18/19, that triggers interrupt on GPIO4/5
 *
 * myboard GPIO7 ,  GPIO9(test)
 */

// #define GPIO_OUTPUT_IO_0    18
// #define GPIO_OUTPUT_IO_1    19
// #define GPIO_OUTPUT_PIN_SEL  ((1ULL<<GPIO_OUTPUT_IO_0) | (1ULL<<GPIO_OUTPUT_IO_1))

// #define GPIO_INPUT_IO_0     4
// #define GPIO_INPUT_IO_1     5
#define GPIO_INPUT_IO_0  7
#define GPIO_INPUT_IO_1  9
// #define GPIO_INPUT_PIN_SEL  1ULL<<GPIO_INPUT_IO_0
#define GPIO_INPUT_PIN_SEL  ((1ULL<<GPIO_INPUT_IO_0) | (1ULL<<GPIO_INPUT_IO_1))
#define ESP_INTR_FLAG_DEFAULT 0

static xQueueHandle gpio_evt_queue = NULL;

static void IRAM_ATTR gpio_isr_handler(void* arg)
{
    
    
    uint32_t gpio_num = (uint32_t) arg;
    xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); //freertos中断中发送消息队列
}

static void gpio_task_example(void* arg)
{
    
    
    uint32_t io_num;
    for(;;) {
    
    
        if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
    
    
            printf("GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num));
        }
    }
}

void app_main(void)
{
    
    
    /*
    typedef struct {
    uint64_t pin_bit_mask;          !< GPIO pin: set with bit mask, each bit maps to a GPIO 
    gpio_mode_t mode;               !< GPIO mode: set input/output mode                     
    gpio_pullup_t pull_up_en;       !< GPIO pull-up                                         
    gpio_pulldown_t pull_down_en;   !< GPIO pull-down                                       
    gpio_int_type_t intr_type;      !< GPIO interrupt type                                  
    } gpio_config_t;
    */

    gpio_config_t io_conf;
    //disable interrupt
    // io_conf.intr_type = GPIO_INTR_DISABLE;
    // //set as output mode
    // io_conf.mode = GPIO_MODE_OUTPUT;
    // //bit mask of the pins that you want to set,e.g.GPIO18/19
    // io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;
    // //disable pull-down mode
    // io_conf.pull_down_en = 0;
    // //disable pull-up mode
    // io_conf.pull_up_en = 0;
    // //configure GPIO with the given settings
    // /*
    // 此部分是输出,按键不需要初始化
    // */
    // gpio_config(&io_conf);

    //interrupt of rising edge
    io_conf.intr_type = GPIO_INTR_NEGEDGE; //按键下降沿
    //bit mask of the pins, use GPIO4/5 here
    io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;
    //set as input mode
    io_conf.mode = GPIO_MODE_INPUT;
    //enable pull-up mode
    io_conf.pull_up_en = 1;
    gpio_config(&io_conf);

    // //change gpio intrrupt type for one pin
    gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_POSEDGE);//单独改变某个IO口的中断设置

    //create a queue to handle gpio event from isr
    gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t)); //创建消息队列
    //start gpio task
    xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);//创建任务

    /*
    install gpio isr service
    This function is incompatible with gpio_isr_register() - if that function is used, 
    a single global ISR is registered for all GPIO interrupts. 
    If this function is used, 
    the ISR service provides a global GPIO ISR and individual pin handlers are registered via the gpio_isr_handler_add() function.
    */
    gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
    //hook isr handler for specific gpio pin
    gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler, (void*) GPIO_INPUT_IO_1);

    //remove isr handler for gpio number.   
    // gpio_isr_handler_remove(GPIO_INPUT_IO_0);   //这部分不太理解,为什么重复一次
    // //hook isr handler for specific gpio pin again
    // gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);

    printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());

    // int cnt = 0;
    while(1) {
    
    
        // printf("cnt: %d\n", cnt++);
        vTaskDelay(1000 / portTICK_RATE_MS);//使用了FreeRTOS,这里必须要有延时,这里的while(1)也类似一个任务
        // gpio_set_level(GPIO_OUTPUT_IO_0, cnt % 2);
        // gpio_set_level(GPIO_OUTPUT_IO_1, cnt % 2);
    }
}

测试结果基本正常,但是有一个小问题,就是对于按键中断类型设置为下降沿,结果却是按下和弹起都会触发,结果如下:
在这里插入图片描述
初步推测设置为下降沿触发(按下弹起都会引起中断),是因为 硬件电路设计的时候加了防抖动的电容的原因,为了检测这个问题,做了一个测试:

使用官方原来的那种方式,使用 GPIO 口 作为中断源,上面示例代码中注释掉的的初始化输出的正常使用:
在这里插入图片描述然后看一下测试效果,在设置为GPIO_INTR_NEGEDGE的时候,下降沿触发,触发后值为0:
在这里插入图片描述
在设置为GPIO_INTR_POSEDGE的时候,上升沿触发,触发后值为1:在这里插入图片描述测试完成,一切是正常的。

1.1.1 不使用FreeRTOS组件测试

但是对于示例中,使用了 FreeRTOS 的相关组件,但实际上并没有使用开始调度的函数,对于这点,目前还是不太习惯。

其实为了这点,还单独测试了一下不用消息队列,直接按照以前 STM32 系列芯片的方式,在中断中直接至位标志位或者简单的操作,延时函数还是用了vTaskDelay。
(printf打印一般不能在中断处理函数中使用,只能作为测试,这里确的确是在中断中使用了printf导致了错误):
在这里插入图片描述
程序编译运行倒是没有什么问题,就是一旦触发中断,就会报错复位:
在这里插入图片描述上面的程序报错,考虑到可能是因为中断中使用了printf函数,所以稍微修改了一下:
在这里插入图片描述
测试按键GPIO7点亮LED灯, GPIO9关闭LED灯。测试正常!

1.2 按键驱动测试

在GItee仓库上有一个大佬的 基于ESP32-C3 的开源项目:

wumei-esp32-c3 Demo GItee 地址

在这个工程中有一个按键驱动,觉得非常好用,所以拿来测试一下。我们使用blink.c样例来添加一下这个驱动进行测试:

现在我们还不熟悉 ESP-IDF 下面的工程结构,如何添加自己的驱动文件,这个后面会单独用一篇文章来介绍

所以我们下面测试操作完一步就得编译一下,防止出错不知道怎么解决。首先,上面的例程下载下来是能够直接编译通过的:
在这里插入图片描述

1.2.1 驱动移植

标题虽然是驱动移植,其实就是拷贝一份 = =!。首先在main同目录下面新建一个components文件夹
(名字不能是别的,因为对于这个components 名字的文件夹 SDK 好像有支持,当然自己的驱动用别的文名字也是可以的,只是修改的地方会比较多,在我们不熟悉的情况下,还是少做修改):
在这里插入图片描述

然后我们将示例中components文件夹下的button文件夹直接拷贝过来,当然也包括文件夹里面的配置文件等:
在这里插入图片描述

复制过来以后编译一下 BLINK 工程(复制过来的文件对工程并无影响):
在这里插入图片描述
接下来在main文件夹下面新建一个my_button.c文件作为按键测试(下图中的注释,头文件需要额外多添加一些,具体可以看下面我放的修改的源码):
在这里插入图片描述
my_button.c文件中的内容是参考示例工程中drv_button.c文件:
在这里插入图片描述
我把简单拷贝修改的源码放一下:

#include <string.h>
#include "esp_log.h"
#include "iot_button.h"
#include "driver/gpio.h"
#include "button_gpio.h"
#include "esp_log.h"


static const char* TAG = "my_button";

#define IO_SWITCH_BUTTON 7

static void button_single_click_cb(void *arg){
    
    
    uint32_t gpio_num = (uint32_t) arg;
    ESP_LOGI(TAG, "BTN%d: BUTTON_SINGLE_CLICK\n", gpio_num);
}

static void button_long_press_start_cb(void *arg){
    
    
    uint32_t gpio_num = (uint32_t) arg;
    ESP_LOGI(TAG, "BTN%d: BUTTON_LONG_PRESS_START\n", gpio_num);
}

static void button_press_repeat_cb(void *arg){
    
    
    uint32_t gpio_num = (uint32_t) arg;
    ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_REPEAT\n",gpio_num);
}

void button_start()
{
    
    
    //初始化按键
    button_config_t cfg = {
    
    
        .type = BUTTON_TYPE_GPIO,
        .gpio_button_config = {
    
    
            .gpio_num = IO_SWITCH_BUTTON,
            .active_level = 0,
        },
    };
    button_handle_t gpio_btn = iot_button_create(&cfg);
    if(NULL == gpio_btn) {
    
     ESP_LOGE(TAG, "Button create failed"); }
    iot_button_register_cb(gpio_btn, BUTTON_SINGLE_CLICK, button_single_click_cb);            //短按
    iot_button_register_cb(gpio_btn, BUTTON_LONG_PRESS_START, button_long_press_start_cb);    //长按
    iot_button_register_cb(gpio_btn, BUTTON_PRESS_REPEAT, button_press_repeat_cb);            //连续短按
}

上面我们增加了一个.c 文件,直接编译工程还是没有问题的,但是要用起来,还得新建一个.h文件:
在这里插入图片描述

1.2.2 测试结果

需要修改的代码移植完毕,接下来在blink.c文件中包含以下这个头文件,调用button_start();函数:
在这里插入图片描述
编译通过烧录…

最终改完后建议全清除一下,再次编译

测试结果,还是让人满意的(代码中直接复制的,没注意,把 arg 地址打出来了,应该是*arg的,不要在意这些细节= =!):
在这里插入图片描述
额外说明:如果不是用的 VScode 中的插件开发,可能修改完代码后是自己需要修改配置文件和重新配置环境变量的,使用插件好像会自动进行配置好,比如,编译完成后的CMakeLists.txt文件自动更新了:
在这里插入图片描述

2、 ESP32-C3 GPIO相关介绍

对于ESP32-C3 GPIO的介绍,在乐鑫的官网有很详细的说明,官方链接如下:

乐鑫官方ESP32-C3 GPIO部分说明

2.1 ESP32-C3 GPIO基础

实际上在我的博文 自己画一个ESP32-C3的开发板中,也有关于GPIO的说明,这里根据官方说明,简单介绍一下:

  1. ESP32-C3 一共22个GPIO口,其中 GPIO2、GPIO8、GPIO9决定着芯片的启动模式;
    在这里插入图片描述
  2. GPIO12-17 用于 SPI flash 和 PSRAM,不建议用作其他功能;
    在这里插入图片描述
  3. GPIO18 和 GPIO19 默认作为 USB-JTAG. 要把他们当做普通 GPIO,需要进行设置;
    在上面的示例中,其实使用了 GPIO18 和 GPIO19 当做普通IO口,作为输出使用;
  4. 在深度睡眠模式下可以使用的GPIO口有 GPIO0-5.

在这里插入图片描述

2.2 ESP32-C3 GPIO函数

GPIO的函数,在官方文档也都都介绍,在工程文件中#include "driver/gpio.h"头文件中,也能全部看到, 总的来说 GPIO的操作是比较基础而且简单的。

相对于以前使用过 STM32 芯片的人来说,只需要了解一下 与中断有关的 带有 _isr 字样的函数,和更睡眠有关的带有_sleep字样的函数。
在这里插入图片描述GPIO使用还是比较简单,就不多废话了,需要用到什么函数,通过测试一下就能明白。

猜你喜欢

转载自blog.csdn.net/weixin_42328389/article/details/122284439