重庆做网站建设团队大连开发区盛京医院
本博文内容导读📕🎉🔥
ESP32开发板的中断矩阵、功能描述与实现、相关API和示例程序进行介绍
ESP32中断矩阵将任一外部中断源单独分配到每个CPU的任一外部中断上,提供了强大的灵活性,能适应不同的应用需求。
ESP32中断主要有以下特性:
接收71个外部中断源作为输入,为两个CPU分别生成26个外部中断(总共52个)作为输出,屏蔽CPU的NMI类型中断,查询外部中断源当前的中断状态,
包括外设中断配置寄存器、中断源、中断矩阵和中断输出寄存器。

1.外部中断源
ESP32共有71个外部中断源,有67个可以分配给两个CPU,
其余4个外部中断源只能分配给特定的CPU,每个CPU2个。GPIO_INTERRUPT_PRO和GPIO_INTERRUPT_PRO_NMI只可以分配给PRO_CPU,GPIO_INTERRUPT_APP和GPIO_INTERRUPT_APP_NMI只可以分配给APP_CPU。
因此,PRO_CPU与APP_CPU各分配到69个外部中断源。
2.CPU中断源
两个CPU(PRO_CPU和APP_CPU)各有32个中断,其中26个为外部中断。
|   编号  |   类别  |   种类  |   优先级  |   编号  |   类别  |   种类  |   优先级  | 
|   0  |   外部中断  |   电平触发  |   1  |   16  |   内部中断  |   定时器 2  |   5  | 
|   1  |   外部中断  |   电平触发  |   1  |   17  |   外部中断  |   电平触发  |   1  | 
|   2  |   外部中断  |   电平触发  |   1  |   18  |   外部中断  |   电平触发  |   1  | 
|   3  |   外部中断  |   电平触发  |   1  |   19  |   外部中断  |   电平触发  |   2  | 
|   4  |   外部中断  |   电平触发  |   1  |   20  |   外部中断  |   电平触发  |   2  | 
|   5  |   外部中断  |   电平触发  |   1  |   21  |   外部中断  |   电平触发  |   2  | 
|   6  |   内部中断  |   定时器 0  |   1  |   22  |   外部中断  |   边沿触发  |   3  | 
|   7  |   内部中断  |   软件  |   1  |   23  |   外部中断  |   电平触发  |   3  | 
|   8  |   外部中断  |   电平触发  |   1  |   24  |   外部中断  |   电平触发  |   4  | 
|   9  |   外部中断  |   电平触发  |   1  |   25  |   外部中断  |   电平触发  |   4  | 
|   10  |   外部中断  |   边沿触发  |   1  |   26  |   外部中断  |   电平触发  |   5  | 
|   11  |   内部中断  |   解析  |   3  |   27  |   外部中断  |   电平触发  |   3  | 
|   12  |   外部中断  |   电平触发  |   1  |   28  |   外部中断  |   边沿触发  |   4  | 
|   13  |   外部中断  |   电平触发  |   1  |   29  |   内部中断  |   软件  |   3  | 
|   14  |   外部中断  |   NMI  |   NMI  |   30  |   外部中断  |   边沿触发  |   4  | 
|   15  |   内部中断  |   定时器 1  |   3  |   31  |   外部中断  |   电平触发  |   5  | 
3.分配外部中断源至CPU外部中断
首先,按照如下规则描述中断:记号Source_X代表某个外部中断源。记号PRO_X_MAP_REG(或 APP_X_MAP_REG)表示PRO_CPU(或 APP_CPU)的某个外部中断配置。寄存器,且此外部中断配置寄存器与外部中断源Source_X相对应。
即表4-7中“PRO_CPU(APP_CPU)-外设中断配置寄存器”一列中与“外设中断源名称”一列中的某个外部中断源处于同一行的寄存器。记号Interrupt_P表示CPU中断序号为Num_P的外部中断,Num_P的取值范围为是0~5、8~10、12~14、17~28、30~31。记号Interrupt_I表示CPU中断序号为Num_I的内部中断,Num_I的取值范围是6、7、11、15、16、29。
其次,根据中断源、寄存器、内外中断,可以这样描述中断矩阵控制器操作:将外部中断源Source_X分配到CPU(PRO_CPU或APP_CPU)。
寄存器PRO_X_MAP_REG(APP_X_MAP_REG)配成Num_P。Num_P可以取任意CPU外部中断值,CPU中断可以被多个外设共享。关闭CPU(PRO_CPU或APP_CPU)外部中断源Source_X。将寄存器PRO_X_MAP_REG(APP_X_MAP_REG)配成任意 Num_I。由于任何被配成 Num_I 的中断都没有连接到2 CPU上,选择特定内部中断值不会造成影响。
将多个外部中断源Source_Xn ORed分配到PRO_CPU(APP_CPU)的外部中断。将各个寄存器PRO_Xn_MAP_REG (APP_Xn_MAP_REG)配成同样的Num_P。这些外设中断都会触发CPU的Interrupt_P。
4.屏蔽CPU的NMI类型中断
中断矩阵能够根据信号PRO_CPU的NMI中断屏蔽(或APP_CPU的NMI中断屏蔽)暂时屏蔽所有被分配到PRO_CPU(或APP_CPU)的外部中断源的NMI中断。
信号PRO_CPU的NMI中断屏蔽和APP_CPU的NMI中断屏蔽分别来自外设进程号控制器。
5.查询外部中断源当前的中断状态
读寄存器 PRO_INTR_STATUS_REG_n(APP_INTR_STATUS_REG_n)中特定位的值就可以获知外部中断源当前的中断状态。
寄存器PRO_INTR_STATUS_REG_n(APP_INTR_STATUS_REG_n)与外部中断源的对应关系。
中断类型定义
中断的大多数功能与GPIO有关,从GPIO的头文件中有很多定义和函数是针对中断的,本节对其主要的定义和函数进行总结。在gpio_types.h或者gpio.h,头文件中的预定义。gpio_int_type_t是ESP32中断类型的定义,是枚举类型,定义如下:
typedef enum {GPIO_INTR_DISABLE = 0,     /*禁用GPIO中断*/GPIO_INTR_POSEDGE = 1,     /*GPIO中断类型:上升沿*/GPIO_INTR_NEGEDGE = 2,     /*GPIO中断类型:下降沿*/GPIO_INTR_ANYEDGE = 3,     /*GPIO中断类型:上升和下降沿*/GPIO_INTR_LOW_LEVEL = 4,   /*GPIO中断类型:低电平触发*/GPIO_INTR_HIGH_LEVEL = 5,  /*GPIO中断类型:高电平触发*/GPIO_INTR_MAX,
} gpio_int_type_t;
 
中断示例程序
基于ESP IDF的VS Code、Arduino和MicroPython环境的三种代码实现。
本程序将GPIO18定义为输出,GPIO4定义为输入,上拉状态,从上升沿触发中断,将GPIO18与GPIO4通过导线直接连接,GPIO18产生的脉冲触发,开始计数,对GPIO4进行余4运算,每隔4s产生中断,在串口打印中断信息。
1. 基于ESP IDF的VS Code开发环境实现方式
 代码如下:
#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"
#define GPIO_OUTPUT_IO_0    18
#define GPIO_OUTPUT_PIN_SEL  (1ULL<<GPIO_OUTPUT_IO_0)
#define GPIO_INPUT_IO_0     4
#define GPIO_INPUT_PIN_SEL  (1ULL<<GPIO_INPUT_IO_0)
#define ESP_INTR_FLAG_DEFAULT 0
static xQueueHandle gpio_evt_queue = NULL;          //FreeRTOS的队列句柄
static void IRAM_ATTR gpio_isr_handler(void* arg)     //函数gpio_isr_handler的调用规范
{uint32_t gpio_num = (uint32_t) arg;xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);    
}
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)                                  //主函数
{gpio_config_t io_conf;                            //定义结构体io_conf.intr_type = GPIO_PIN_INTR_DISABLE;      //禁用中断io_conf.mode = GPIO_MODE_OUTPUT;            //设置输出模式io_conf.pin_bit_mask = GPIO_OUTPUT_PIN_SEL;    //GPIO18的比特掩码io_conf.pull_down_en = 0;                         //禁用下拉模式io_conf.pull_up_en = 0;                           //禁用上拉模式gpio_config(&io_conf);                           //使用以上参数初始化GPIOio_conf.intr_type = GPIO_PIN_INTR_POSEDGE;     //上升沿触发中断io_conf.pin_bit_mask = GPIO_INPUT_PIN_SEL;      //GPIO4的比特掩码io_conf.mode = GPIO_MODE_INPUT;              //设置输入模式io_conf.pull_up_en = 1;                           //使能上拉模式gpio_config(&io_conf);                           //使用以上参数配置gpio_evt_queue = xQueueCreate(10, sizeof(uint32_t));  //创建队列处理中断xTaskCreate(gpio_task_example, "gpio_task_example", 2048, NULL, 10, NULL); //开启任务gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT);  //安装GPIO中断服务
gpio_isr_handler_add(GPIO_INPUT_IO_0, gpio_isr_handler, (void*) GPIO_INPUT_IO_0);
//GPIO引脚挂钩ISR处理程序int cnt = 0;while(1) {printf("cnt: %d\n", cnt++);                       //打印计数vTaskDelay(1000 / portTICK_RATE_MS);         //延时1Sgpio_set_level(GPIO_OUTPUT_IO_0, cnt % 4);     //每隔4个计数,打印一次中断//gpio_set_level(GPIO_OUTPUT_IO_1, cnt % 2);}
} 
2. Arduino开发环境实现
 代码如下: 
void callBack(void)
{Serial.printf("GPIO 4 Interrupted\n");
}
void setup()
{Serial.begin(115200);                //设置串口监视器波特率Serial.println();pinMode(18, OUTPUT);              //GPIO18为输出模式pinMode(4, INPUT);                 //GPIO4为输入模式attachInterrupt(4, callBack, RISING);   //上升沿触发中断
}
int cnt = 0;
void loop()                         //主函数
{Serial.printf("cnt: %d\n", cnt++);    //打印计数digitalWrite(18, cnt % 4);         //每隔4个计数,打印一次中断delay(1000);                   //延时1S//detachInterrupt(4); //关闭中断
} 
3. MicroPython开发环境实现
 代码如下:
import time
import machine
from machine import Pin 
GPIO_OUTPUT=Pin(18,Pin.OUT)
GPIO_INPUT=Pin(4,Pin.IN, Pin.PULL_UP)
cnt=0                          #定义计数
interrupt = 0
interruptsCounter = 0              #计算中断事件次数
def callback(pin):                 #定义回调函数global interrupt, interruptsCounter  #声明为全局变量interrupt = 1interruptsCounter = interruptsCounter+1
GPIO_INPUT.irq(trigger=Pin.IRQ_RISING, handler=callback)
while True:GPIO_OUTPUT.value(cnt%4)time.sleep(1)cnt=cnt+1if interrupt:#state = machine.disable_irq()  #禁用计数器interrupt = 0#machine.enable_irq(state)  #重新启动计数器print("Interrupt has occurred: " + str(interruptsCounter))