测试第二课,主要了解GPIO中断使用,和测试按键驱动
前言
接下来的ESP32-C3 功能测试都是基于自己设计的开发板:
自己画一块ESP32-C3 的开发板(第一次使用立创EDA)(PCB到手)
开发环境是乐鑫官方的 ESP-IDF, 基于VScode插件搭建好的:
ESP32-C3 VScode开发环境搭建(基于乐鑫官方ESP-IDF——Windows和Ubuntu双环境)
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 的开源项目:
在这个工程中有一个按键驱动,觉得非常好用,所以拿来测试一下。我们使用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的介绍,在乐鑫的官网有很详细的说明,官方链接如下:
2.1 ESP32-C3 GPIO基础
实际上在我的博文 自己画一个ESP32-C3的开发板中,也有关于GPIO的说明,这里根据官方说明,简单介绍一下:
- ESP32-C3 一共22个GPIO口,其中 GPIO2、GPIO8、GPIO9决定着芯片的启动模式;
- GPIO12-17 用于 SPI flash 和 PSRAM,不建议用作其他功能;
- GPIO18 和 GPIO19 默认作为 USB-JTAG. 要把他们当做普通 GPIO,需要进行设置;
在上面的示例中,其实使用了 GPIO18 和 GPIO19 当做普通IO口,作为输出使用;
- 在深度睡眠模式下可以使用的GPIO口有 GPIO0-5.
2.2 ESP32-C3 GPIO函数
GPIO的函数,在官方文档也都都介绍,在工程文件中#include "driver/gpio.h"
头文件中,也能全部看到, 总的来说 GPIO的操作是比较基础而且简单的。
相对于以前使用过 STM32 芯片的人来说,只需要了解一下 与中断有关的 带有 _isr
字样的函数,和更睡眠有关的带有_sleep
字样的函数。
GPIO使用还是比较简单,就不多废话了,需要用到什么函数,通过测试一下就能明白。