ESP32C3学习&开发之路——(GPIO中断+按键)控灯
通过软件配置,GPIO可以有多种特定的功能,除了可以驱动外部设备外还有一个重要的功能“中断”。中断是嵌入式开发的重要组成部分,通过中断可以很方便的实现复杂的功能。ESP32C3也支持中断,其中断是由“中断控制器”来管理的,这次通过对ESP32C3中断的学习然后实现用GPIO中断+按键来控制led灯。freertos的队列定义可以实现任务间的同步,相关知识点小伙伴们可查看freertos的手册。
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
通过软件配置,GPIO可以有多种特定的功能,除了可以驱动外部设备外还有一个重要的功能“中断”。中断是嵌入式开发的重要组成部分,通过中断可以很方便的实现复杂的功能。ESP32C3也支持中断,其中断是由“中断控制器”来管理的,这次通过对ESP32C3中断的学习然后实现用GPIO中断+按键来控制led灯。
一、ESP32C3的中断
1.中断控制器
1.1特性
ESP32C3的中断控制器能够捕获、屏蔽来自 RISC-V CPU 外部的中断源,并对中断源的优先级进行动态仲裁。
中断控制器具有以下特性:
• 多达 31 个具有唯一 ID (1-31) 的异步中断
• 支持通过读写存储器匹配寄存器进行配置
• 15 个优先级级别,可以分配给不同的中断
• 支持电平触发或边沿触发的中断源
• 可配置的全局阈值,用于屏蔽优先级较低的中断
• 与异常向量地址偏移量匹配的中断 ID
与中断相关的寄存器有很多,分为:
• 中断源映射寄存器
• 中断源状态寄存器
• 中断时钟寄存器
• CPU 中断寄存器
• 版本寄存器
中断寄存器的完整列表及详细描述请见章节 8 中断矩阵 (INTMTRX),8.4小节中的“CPU 中断寄存器”。
1.2功能描述
在ESP32C3中每个配置了的中断都会有一个“中断ID”,每个中断ID都有5个属性:
- 使能状态 (0-1):
• 决定是否允许由 CPU 捕获和处理中断。
• 通过写入 INTERRUPT_CORE0_CPU_INT_ENABLE_REG 相应的域进行配置。 - 类型 (0-1):
• 在中断信号的上升沿使能闩锁状态。
• 通过写入 INTERRUPT_CORE0_CPU_INT_TYPE_REG 相应的域进行配置。
• 类型保持为 0 的中断称为“电平”类型中断。
• 类型保持为 1 的中断称为“边沿”类型中断 - 优先级(1-15):
• 当有多个中断在等待时,决定CPU 先处理哪一个中断。
• 通过写入中断IDn(1-31)的ERRUPT_CORE0_CPU_INT_PRI_n_REG 进行配置。
• 优先级为零或小于 INTERRUPT_CORE0_CPU_INT_THRESH_REG 指定阈值的中断将被屏蔽。
• 优先级最低为 1,最高为 15。
• 具有相同优先级的中断通过其 ID 静态确定优先级, ID 越小,优先级越高 - 等待状态(0-1):
• 反映已使能且未被屏蔽的中断信号被捕获时的状态。
• 通过读取 INTERRUPT_CORE0_CPU_INT_EIP_STATUS_REG 中的相应位获得每个中断 ID 的等待状态。
• 如果没有更高优先级的中断在等待,则当前在等待的中断将导致 CPU 进入异常。
• 如果在等待的中断抢占 CPU 并导致其跳转到相应的异常向量地址,则称该中断为“已声明”。
• 所有在等待的中断都为“未声明”。 - 清除状态 (0-1):
• 切换此属性将仅清除已声明的边沿类型中断的等待状态。
• 通过先置位然后清零 INTERRUPT_CORE0_CPU_INT_CLEAR_REG 中的相应位进行切换。
• 电平类型的中断的等待状态不受切换操作的影响,而必须从中断源中清除。
• 未声明的边沿类型中断的等待状态可以被清空,方法是先清零 INTERRUPT_CORE0_CPU_INT_ENABLE_REG中的相应位再置位 INTERRUPT_CORE0_CPU_INT_CLEAR_REG 中的相同位。
当 CPU 处理在等待的中断时,会进行以下操作:
• 将当前未执行指令的地址保存在 mepc 中,以便之后恢复执行。
• 将 mcause 的值更新为正在处理的中断 ID。
• 将 MIE 的状态复制到 MPIE,然后清零 MIE,从而全局禁用中断。
• 通过跳转到 mtvec 中存储的地址的字对齐偏移量进入异常。
软件可以在 ISR 内部实现中断嵌套。
中断控制器具有以下行为特点:
• 仅当中断具有非零优先级、大于或等于阈值寄存器中的值时,它才会反映在 INTERRUPT_CORE0_CPU_INT_EIP_STATUS中。
• 如果一个中断反映在 INTERRUPT_CORE0_CPU_INT_EIP_STATUS_REG 中但是还未被处理,则可以通过降低它的优先级或提高全局阈值将其屏蔽(进而防止 CPU 对其进行处理)。
• 如果一个中断反映在INTERRUPT_CORE0_CPU_INT_EIP_STATUS_REG 中,要清除它(防止被处理),则必须将其禁用(如果是边沿属性的中断则需要清除)。
2.中断矩阵
ESP32-C3 中断矩阵将任一外部中断源单独映射到 ESP-RISC-V CPU 的任一外部中断上,以便在外设中断信号产生后,及时通知 CPU 进行处理。ESP32-C3 有 62 个外部中断源,但 CPU 只支持 31 个中断。因此,将这些外部中断源映射至 CPU 中断必须使用中断矩阵。
• 接收 62 个外部中断源作为输入
• 生成 31 个 CPU 的外部中断作为输出
• 支持查询外部中断源当前的中断状态
• 支持配置 CPU 的中断优先级、中断类型、中断阈值以及中断使能。
中断矩阵的结构如图所示:
中断源:硬件中断和软件中断
硬件中断:响应芯片外设发生的中断事件,如GPIO引脚输入的电平发生变化而被检测到后产生的事件。
软件中断:响应软件触发的事件,如定时器计数溢出,定时器的中断是软件中断的一种。
二、电路设计
测试电路设计如图:
GPIO4接二极管负极,GPIO5接按键。GPIO5为下降沿输入中断。
三、程序设计
1.代码解析
1.1头文件

1.2相关定义

freertos的队列定义可以实现任务间的同步,相关知识点小伙伴们可查看freertos的手册。
ESP_INTR_FLAG_DEFAULT宏的定义可能是用于中断优先级设置的,但是我在库函数中没有找到有关中断优先级的配置项,希望有了解的大佬可以指点一下。
1.3GPIO初始化
GPIO的初始化方式与SMT32的开发方式挺类似的。
定义一个GPIO配置结构体变量 gpio_config_t io_conf,对相关的结构体成员赋值后调用配置函数gpio_config(&io_conf),即可对配置参数相关的寄存器进行设置。
这里将GPIO4配置成输出模式,GPIO5配置成上拉输入并配置下降沿触发中断。通过gpio_isr_handler_add注册中断回调函数gpio_isr_handler(),
1.4GPIO中断服务函数
中断服务函数都在定义的时候加上“IRAM_ATTR”的修饰,这样是为了提高中断服务函数的响应速度。
在中断服务函数中最好不要进行复杂的处理,如图所示。
1.5led控制任务
中断一次翻转一次led灯所接引脚的输出状态,实现按键控制led灯的亮灭。
设置GPIO的输出函数gpio_set_level().
当没有按下按键时该任务会处于等待挂起的状态。
1.6main函数
在main函数中创建freertos队列、调用GPIO初始化、创建led任务。
1.7中断配置的追踪
从第一节了解到ESP32C3的中断是由中断控制器来控制的,中断源是需要通过中断矩阵映射到中断控制器中,而且中断源要多与可分配的中断ID。
中断的配置流程一般为:配置外设的中断(配置外设为中断模式),将外设的中断源映射到中断矩阵中,注册中断服务函数,开启中断。
在GPIO的驱动库函数的gpio_config(const gpio_config_t *pGPIOConfig)函数中会根据pGPIOConfig->intr_type是否置位来使能中断。
当外设中断使能后再配置中断触发的条件gpio_set_intr_type(GPIO5_INPUT_IO, GPIO_INTR_LOW_LEVEL)。
安装中断服务,即将中断源配置到中断控制器中,调用函数gpio_install_isr_service(int intr_alloc_flags),该函数会进行中断注册,源码如下:
最终会调用esp_intr_alloc()中断分配函数。
之后再调用gpio_isr_handler_add()函数,将中断与对应的中断服务函数进行匹对,注意:在ESP32系列中不同的中断挂钩中断服务函数的方式可能不同。
2.完整代码
代码结构和布局是根据我自己的风格进行了修改,与官方例程有所不同。
#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:
* GPIO4: output
* GPIO5: input, pulled up, interrupt from falling edge
*
*/
#define GPIO4_OUTPUT_IO 4
#define GPIO_OUTPUT_PIN_SEL (1ULL<<GPIO4_OUTPUT_IO)
#define GPIO5_INPUT_IO 5
#define GPIO_INPUT_PIN_SEL (1ULL<<GPIO5_INPUT_IO)
#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);
}
static void led_task(void* arg)
{
uint32_t io_num;
bool led = true;
for(;;) {
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
printf("GPIO[%d] intr, val: %d\n", io_num, gpio_get_level(io_num));
if(io_num == GPIO5_INPUT_IO){
led = !led;
printf("led[%d]\n",led);
gpio_set_level(GPIO4_OUTPUT_IO, led);
}
}
}
}
void gpio_init(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.GPIO4
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_POSEDGE;
//bit mask of the pins, use GPIO5 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(GPIO5_INPUT_IO, GPIO_INTR_LOW_LEVEL);
//install gpio isr service
gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);
//hook isr handler for specific gpio pin
gpio_isr_handler_add(GPIO5_INPUT_IO, gpio_isr_handler, (void*) GPIO5_INPUT_IO);
gpio_set_level(GPIO4_OUTPUT_IO, 1);
}
void app_main(void)
{
//create a queue to handle gpio event from isr
gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));
/* 初始化GPIO */
gpio_init();
printf("Minimum free heap size: %d bytes\n", esp_get_minimum_free_heap_size());
//start led task
xTaskCreate(led_task, "led_task", 2048, NULL, 10, NULL);
}
3.调试现象
接好电路后,将工程编译成功生成的bin文件下载到开发板中,即可以用按键来控制led灯如图:

由于微动按键的抖动过大造成中断过平方,所以led灯的控制效果并不好,本次主要是为了学习中断所以并不对控制做要求,如想要较好的控制效果用“软件延时法”去抖动即可。
四、总结
学习到的:
以上就是本次的学习和开发的内容,主要是学习ESP32C3的中断知识并结合上次GPIO的相关知识进行应用,中断是非常重要的一部分知识,要深入的学习需要较长的时间,目前只是入门而已,在之后的定时器,串口,DMA的学习中也会有中断,一步一个脚印吧,咋把原理搞懂先,路还长着呢。
存在的疑问:
中断优先级的配置是怎么实现的?希望有了解的大佬指点一下,谢谢。
更多推荐



所有评论(0)