写点什么

ESP32-C3 入门教程 基础篇(二、GPIO 中断、按键驱动测试)

作者:矜辰所致
  • 2022 年 9 月 19 日
    江苏
  • 本文字数:5546 字

    阅读完需:约 18 分钟

ESP32-C3入门教程 基础篇(二、GPIO中断、按键驱动测试)

前言

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


测试使用的开发板:

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

https://xie.infoq.cn/article/30387388381a0d915b2494f91

测试使用的开发环境:

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

https://xie.infoq.cn/article/5b639e112cabba00cc1b8941a

基础篇系列相关博文:

ESP32-C3 入门教程 基础篇(一、ADC 采样)

https://xie.infoq.cn/article/78eff739dd2ed4971f445272a

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.1.2 ESP32-C3 应用程序启动流程(更正上面错误说法!)


对于文中以前提到的使用了 FreeRTOS ,但是没有开始调度的说法,是错误的!进行改正!


函数app_mian其实只是一个 FreeRTOS 的一个任务! 系统跑到app_mian的时候早就开始了调度!!!


在乐鑫官方,有详细的启动流程说明:乐鑫官方对于ESP32-C3 应用程序的启动流程 说明



FreeRTOS 调度器会在组件初始化完成以后开启!


这才是为什么在示例中可以创建任务而且能够执行的原因!


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 使用还是比较简单,就不多废话了,需要用到什么函数,通过测试一下就能明白。

发布于: 刚刚阅读数: 4
用户头像

矜辰所致

关注

CSDN、知乎、微信公众号: 矜辰所致 2022.08.02 加入

不浮夸,不将就,认真对待学知识的我们,矜辰所致,金石为开! 为了活下去的嵌入式工程师,画画板子,敲敲代码,玩玩RTOS,搞搞Linux ...

评论

发布
暂无评论
ESP32-C3入门教程 基础篇(二、GPIO中断、按键驱动测试)_GPIO_矜辰所致_InfoQ写作社区