1.GPIO简介
ESP32-C3是QFN32封装,GPIO引脚一共有22个,从GPIO0到GPIO21。
理论上,所有的IO都可以复用为任何外设功能,但有些引脚用作连接芯片内部FLASH或者外部FLASH功能时,官方不建议用作其它用途。
通过开发板的原理图,可以看到开发板上的ESP32引脚连接情况。这里我们使用BOOT按键,来学习一下GPIO功能。
ESP32的GPIO,可以用作输入、输出,可以配置内部上拉、下拉,可以配置为中断引脚。
这里我们把连接 BOOT按键的IO9 引脚,设置为GPIO中断,接收BOOT按键请求。
2.编写程序
我们复制 esp-idf-v5.1.4\examples\get-started\sample_project
这个工程到我们的实验文件夹,然后把这个文件夹的名称修改为 gpio_key,方便日后搞清楚这个工程的作用。
在ESP-IDF的安装路径下,打开官方例程:D:\ESP_IDF\INS\Espressif\frameworks\esp-idf-v5.1.4\examples\get-started
使用 VSCode 打开 gpio_key 这个文件夹。单击打开工程一级目录下的 CMakeLists.txt 文件(注意不是 main 目录下的),然后我们把工程名字修改为 gpio_key,保存后关闭此文件。
点击打开main.c文件,发现里面只写了这么几行代码:
#include <stdio.h>
void app_main(void)
{
}
从这个工程原来的文件夹名字就可以知道,这是一个示例工程,我们现在需要实现按键中断,比较简单,所以在这个工程上写就可以了。
现在再打开一个VSCode软件,然后打开esp-idf-v5.1.4
整个工程文件夹,然后我们依次找到
examples\peripherals\gpio\generic_gpio
这个工程作为参考,注意不要修改这个工程中的内容和配置,只是作为参考。
单击gpio_example_main.c
打开这个文件,找到app_main
函数。
/* 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 <inttypes.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 interrupts.
*
* GPIO status:
* GPIO18: output (ESP32C2/ESP32H2 uses GPIO8 as the second output pin)
* GPIO19: output (ESP32C2/ESP32H2 uses GPIO9 as the second output pin)
* GPIO4: input, pulled up, interrupt from rising edge and falling edge
* GPIO5: input, pulled up, interrupt from rising edge.
*
* Note. These are the default GPIO pins to be used in the example. You can
* change IO pins in menuconfig.
*
* Test:
* Connect GPIO18(8) with GPIO4
* Connect GPIO19(9) with GPIO5
* Generate pulses on GPIO18(8)/19(9), that triggers interrupt on GPIO4/5
*/
#define GPIO_OUTPUT_IO_0 CONFIG_GPIO_OUTPUT_0
#define GPIO_OUTPUT_IO_1 CONFIG_GPIO_OUTPUT_1
#define GPIO_OUTPUT_PIN_SEL ((1ULL<<GPIO_OUTPUT_IO_0) | (1ULL<<GPIO_OUTPUT_IO_1))
#define GPIO_INPUT_IO_0 CONFIG_GPIO_INPUT_0
#define GPIO_INPUT_IO_1 CONFIG_GPIO_INPUT_1
#define GPIO_INPUT_PIN_SEL ((1ULL<<GPIO_INPUT_IO_0) | (1ULL<<GPIO_INPUT_IO_1))
#define ESP_INTR_FLAG_DEFAULT 0 // 中断标志的宏定义
// 将 GPIO 中断事件发送到这个队列gpio_evt_queue,以便在任务中处理。
// gpio_evt_queue 被用来接收中断处理程序发送的 GPIO 事件然后由 gpio_task_example 任务处理这些事件。
static QueueHandle_t gpio_evt_queue = NULL; // 静态队列句柄
// ISR 中断服务函数
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
uint32_t gpio_num = (uint32_t) arg;
//xQueueSendFromISR是在 ISR(中断服务例程)中发送数据到 FreeRTOS 队列的函数调用。
//gpio_evt_queue:要发送数据的队列句柄。
//&gpio_num:要发送的数据,这里是指向 GPIO 编号的指针,表示触发中断的引脚。
//NULL:优先级参数
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
// GPIO 任务函数
static void gpio_task_example(void* arg)
{
uint32_t io_num;
for(;;) {
// 任务处理 处理gpio_evt_queue里面的任务
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
printf("GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));
}
}
}
void app_main(void)
{
// 零初始化配置结构
gpio_config_t io_conf = {};
// 配置输出引脚
io_conf.intr_type = GPIO_INTR_DISABLE; // 禁用中断
io_conf.mode = GPIO_MODE_OUTPUT; // 设置为输出模式
io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL; // 设置引脚gpio0/gpio1
io_conf.pull_down_en = 0; // 禁用下拉模式
io_conf.pull_up_en = 0; // 禁用上拉模式
gpio_config(&io_conf); // 配置 GPIO
// 配置输入引脚
io_conf.intr_type = GPIO_INTR_POSEDGE; // 上升沿中断
io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL; // 设置引脚gpio0/gpio1
io_conf.mode = GPIO_MODE_INPUT; // 设置为输入模式
io_conf.pull_up_en = 1; // 启用上拉模式
gpio_config(&io_conf); // 配置 GPIO
// 改变 GPIO 中断类型
gpio_set_intr_type(GPIO_INPUT_IO_0, GPIO_INTR_ANYEDGE); // 任何边缘中断
// 创建队列以处理 ISR 的 GPIO 事件
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
// 启动 GPIO 任务
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);
// 安装 GPIO ISR 服务
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
// 钩住 ISR 处理程序
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
gpio_isr_handler_add(GPIO_INPUT_IO_1, gpio_isr_handler, (void*) GPIO_INPUT_IO_1);
// 从 GPIO 中移除 ISR 处理程序(可选)
gpio_isr_handler_remove(GPIO_INPUT_IO_0);
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0); // 再次钩住 ISR
printf("Minimum free heap size: %"PRIu32" bytes\n", esp_get_minimum_free_heap_size());
int cnt = 0;
while(1) {
printf("cnt: %d\n", cnt++);
vTaskDelay(1000 / portTICK_PERIOD_MS);
gpio_set_level(GPIO_OUTPUT_IO_0, cnt % 2); // 切换输出引脚状态
gpio_set_level(GPIO_OUTPUT_IO_1, cnt % 2); // 切换输出引脚状态
}
}
复制它的前几行语句(第80~93行)到我们自己的gpio_key工程中,如下所示:
#include <stdio.h>
void app_main(void)
{
//zero-initialize the config structure.
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; // 上下拉电阻,0是关闭,1是打开
//disable pull-up mode
io_conf.pull_up_en = 0;
//configure GPIO with the given settings
gpio_config(&io_conf);// 使用gpio_config函数进行配置
}
在gpio_example_main.c
文件中的GPIO_INTR_DISABLE上单击右键,然后选择“转到定义”,就可以找到这几个宏定义,如下所示:
typedef enum {
GPIO*INTR_DISABLE = 0, //*!< Disable GPIO interrupt 禁用 GPIO 中断 _/
GPIO_INTR_POSEDGE = 1, //_!< GPIO interrupt type : rising edge 中断类型:上升沿_/
GPIO_INTR_NEGEDGE = 2, //_!< GPIO interrupt type : falling edge 中断类型:下降沿_/
GPIO_INTR_ANYEDGE = 3, //_!< GPIO interrupt type : both rising and falling edge 中断类型:上升沿和下降沿均可_/
GPIO_INTR_LOW_LEVEL = 4, //_!< GPIO interrupt type : input low level trigger 中断类型:低电平触发_/
GPIO_INTR_HIGH_LEVEL = 5,//_!< GPIO interrupt type : input high level trigger 中断类型:高电平触发\_/
GPIO_INTR_MAX,
} gpio_int_type_t;
上面的代码,总结来说一下,就是先定义一个GPIO结构体,然后给GPIO结构体成员变量赋值,然后使用GPIO配置函数配置GPIO。给结构体成员变量赋值,也可以在定义的时候直接赋值,如下代码所示:
方法1:
void app_main(void)
{
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_NEGEDGE, //falling edge interrupt
.mode = GPIO_MODE_INPUT, //set as input mode
.pin_bit_mask = 1<<GPIO_NUM_9, //bit mask of the pins GPIO9
.pull_down_en = 0, //disable pull-down mode
.pull_up_en = 1 //enable pull-up mode
};
//configure GPIO with the given settings
gpio_config(&io_conf);
}
方法2:
void app_main(void)
{
//zero-initialize the config structure.
gpio_config_t io_conf = {};
//falling edge interrupt
io_conf.intr_type = GPIO_INTR_NEGEDGE;
//set as input mode
io_conf.mode = GPIO_MODE_INPUT;
//bit mask of the pins GPIO9
io_conf.pin_bit_mask = 1<<GPIO_NUM_9;
//disable pull-down mode
io_conf.pull_down_en = 0;
//enable pull-up mode
io_conf.pull_up_en = 1;
//configure GPIO with the given settings
gpio_config(&io_conf);
}
接下来,我们再复制gpio_example_main.c
文件中的第108~116行代码到我们的main.c
文件中。
//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
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);
开始,定义了一个队列句柄,用来处理gpio队列消息。
然后,定义了两个函数。
第一个函数是gpio 中断服务函数,当GPIO产生中断的时候呢,会进入这个函数,xQueueSendFromISR函数将参数GPIO_NUM_9添加到队列消息。
第二个函数是gpio的 任务函数,在任务函数中,接收队列消息,当接收到一个队列消息时,打印字符串。
PRIu32 是C语言中用于格式化输出的宏,用于打印32位无符号整数。它是由C99标准
引入的,位于inttypes.h
头文件中。在使用该宏时,需要包含inttypes.h头文件。
gpio_get_level
函数用于获取引脚的电平。 这些内容,不需要做修改。 接下来,我们再把需要的头文件添加到我们的main.c文件就可以了。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
使用printf函数,需要添加stdio.h头文件。string.h和stdlib.h我们这里用不着,可以去掉。接下来是3个freeRTOS的头文件,最后一个头文件是用于gpio的配置。
3.编译和下载
接下来,依次配置VSCode左下角的配置选项,串口号、目标芯片、下载方式、menuconfig里面,把FLASH大小修改为8MB,其它不做修改。
中断输入是项目中常用的方式,如果你想试试查询法,可以不开中断,使用gpio_get_level函数查询引脚电平。 引脚设置为输出,可以使用gpio_set_level来控制引脚的电平。具体使用方法,可以查看我们刚才参考的gpio例程。 ESP32还有一个特殊的引脚,就是GPIO11,它默认是VDD_SPI引脚,VDD_SPI默认是一个3.3V输出的电源引脚,可以用来给外部的FLASH芯片供电,这个引脚也可以修改为GPIO11,作为通用引脚使用。需要注意的是,这个修改是不可逆的,修改成GPIO11以后,就不能再修改为3.3V输出电源引脚了。我们在设计电路的时候,可以根据需求,决定是否用这个引脚给外部FLASH供电。我们的开发板没有用这个引脚给外部FLASH供电,而是作为GPIO引脚。这个引脚设置为GPIO的方法,我们会在I2S音频接口章节介绍。
源文件 main.c
#include <stdio.h>
#include <inttypes.h>// C99标准 PRIu32无符号32位数据类型
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "driver/gpio.h"
// 声明一个静态队列句柄,用于处理 GPIO 事件
static QueueHandle_t gpio_evt_queue = NULL;
// GPIO 中断服务例程(ISR)处理函数
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
uint32_t gpio_num = (uint32_t) arg; // 获取触发中断的 GPIO 编号
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); // 将 GPIO 编号发送到队列
}
// GPIO 任务示例
static void gpio_task_example(void* arg)
{
uint32_t io_num; // 存储接收到的 GPIO 编号
for(;;) {
// 从队列中接收 GPIO 事件,阻塞直到有数据可用 portMAX_DELAY表示如果没有接收到消息,则会一直等待
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
// 打印触发中断的 GPIO 编号及其当前电平值
printf("GPIO[%"PRIu32"] intr, val: %d\n", io_num, gpio_get_level(io_num));
// gpio_set_level( GPIO_NUM_9,1)
}
}
}
// 主应用程序入口
void app_main(void)
{
// 零初始化配置结构
gpio_config_t io_conf = {
.intr_type = GPIO_INTR_NEGEDGE, // 设置为下降沿中断
.mode = GPIO_MODE_INPUT, // 设置为输入模式
.pin_bit_mask = 1ULL << GPIO_NUM_9, // 设置引脚位掩码,指定为 GPIO_NUM_9
.pull_down_en = 0, // 禁用下拉模式
.pull_up_en = 1 // 启用上拉模式
};
// 根据配置设置 GPIO
gpio_config(&io_conf);
// 创建一个队列(句柄),用于处理 ISR 发送的 GPIO 事件
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
// 创建一个任务
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);//任务的函数、任务的名称、堆栈大小、传递给任务函数的参数、任务的优先级,值越大表示优先级越高、用于接收任务句柄的指针
// 安装 GPIO ISR 服务
gpio_install_isr_service(0); // 默认值0
// 为特定 GPIO 引脚钩住 ISR 处理程序
gpio_isr_handler_add(GPIO_NUM_9, gpio_isr_handler, (void*) GPIO_NUM_9);// 中断源 中断函数的名称 中断函数的入口参数(void* arg)
}
xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL);
是用于在 FreeRTOS 中创建新任务的函数调用。具体说明如下:
参数说明:
gpio_task_example
:要执行的任务函数名。"gpio_task_example"
:任务的名称,用于调试和监控。2048
:为任务分配的堆栈大小(以字节为单位),这里为 2048 字节。NULL
:任务的参数,这里设置为NULL
,表示没有传递参数。10
:任务的优先级,值越大优先级越高。这里设置为 10。NULL
:用于接收任务句柄的指针,这里设置为NULL
,表示不需要句柄。
功能:
- 创建一个名为
gpio_task_example
的任务,该任务将运行gpio_task_example
函数。 - 这个任务将负责从 GPIO 中断队列中接收事件,并处理这些事件。
如果你有更多问题或需要进一步的解释,请告诉我!
4.实验效果
5.参考资料
- [1] 立创·实战派ESP32-C3开发板 立创开发板技术文档中心
- [2] 【B站】立创·实战派ESP32-C3【手把手带你拥有项目经验】教程-04-gpio_key按键