提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

通过软件配置,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个属性:

  1. 使能状态 (0-1):
    • 决定是否允许由 CPU 捕获和处理中断。
    • 通过写入 INTERRUPT_CORE0_CPU_INT_ENABLE_REG 相应的域进行配置。
  2. 类型 (0-1):
    • 在中断信号的上升沿使能闩锁状态。
    • 通过写入 INTERRUPT_CORE0_CPU_INT_TYPE_REG 相应的域进行配置。
    • 类型保持为 0 的中断称为“电平”类型中断。
    • 类型保持为 1 的中断称为“边沿”类型中断
  3. 优先级(1-15):
    • 当有多个中断在等待时,决定CPU 先处理哪一个中断。
    • 通过写入中断IDn(1-31)的ERRUPT_CORE0_CPU_INT_PRI_n_REG 进行配置。
    • 优先级为零或小于 INTERRUPT_CORE0_CPU_INT_THRESH_REG 指定阈值的中断将被屏蔽。
    • 优先级最低为 1,最高为 15。
    • 具有相同优先级的中断通过其 ID 静态确定优先级, ID 越小,优先级越高
  4. 等待状态(0-1):
    • 反映已使能且未被屏蔽的中断信号被捕获时的状态。
    • 通过读取 INTERRUPT_CORE0_CPU_INT_EIP_STATUS_REG 中的相应位获得每个中断 ID 的等待状态。
    • 如果没有更高优先级的中断在等待,则当前在等待的中断将导致 CPU 进入异常。
    • 如果在等待的中断抢占 CPU 并导致其跳转到相应的异常向量地址,则称该中断为“已声明”。
    • 所有在等待的中断都为“未声明”。
  5. 清除状态 (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的学习中也会有中断,一步一个脚印吧,咋把原理搞懂先,路还长着呢。
存在的疑问:
中断优先级的配置是怎么实现的?希望有了解的大佬指点一下,谢谢。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐