ESP32 外部中断原理分析 & GPIO外部中断实战

ESP32 外部中断原理分析 & GPIO外部中断实战

阅读建议:

  有一定Cortex-m架构、Xtensa® 32-bit LX6 架构知识基础。

软件环境

  • VSCODE-ESP32-IDF4.3 插件版
  • LVGL project for ESP32

硬件环境

  • ESP32-D2WD

外部中断原理

ESP32-GPIO

  ESP32 共有 34 个 GPIO 管脚,通过配置对应的寄存器,可以为这些管脚分配不同的功能,包括如下几类 GPIO:只有数字功能的 GPIO、带模拟功能的 GPIO、带电容触摸功能的 GPIO 等。
  带模拟功能的 GPIO 和带电容触摸功能的 GPIO 可以被配置为数字 GPIO。大部分带数字功能的 GPIO 都可以被配置为内部上拉/下拉,或者被设置为高阻。当被配置为输入时,可通过读取寄存器获取输入值。输入管脚也可以被设置为通过边缘触发或电平触发来产生 CPU 中断。大部分数字 IO 管脚都是双向、非反相和三态的,包括带有三态控制的输入和输出缓冲器。这些管脚可以复用作其他功能,例如SDIO、UART、SPI 等(更多信息请参考附录 IO_MUX)。当芯片低功耗运行时,GPIO 可被设定为保持状态。

ESP32-INTERRUPT

  ESP32 中断矩阵将任一外部中断源单独分配到每个 CPU 的任一外部中断上。这提供了强大的灵活性,能适应不同的应用需求。这和Cortex-m架构的外部中断并不相同并不是通过NVIC向量表的方式而是中断矩阵来设置。

图1: 中断矩阵结构图

主要特性
• 接受 71 个外部中断源作为输入
• 为两个 CPU 分别生成 **26 个外部中断(总共 52 个)**作为输出
• 屏蔽 CPU 的 NMI 类型中断
• 查询外部中断源当前的中断状态

值得注意的是这里分出了两个概念:外部中断源CPU的外部中断 ,这是理解ESP32中断的核心。

ESP32-外部中断源

  ESP32 总共有 71 个外部中断源。图2列出了所有外部中断源。ESP32 中的 71 个外部中断源中有 67 个可以分配给两个 CPU。其余的 4 个外部中断源只能分配给特定的 CPU,每个 CPU 2 个GPIO_INTERRUPT_PROGPIO_INTERRUPT_PRO_NMI 只可以分配PRO _CPUGPIO_INTERRUPT_APPGPIO_INTERRUPT_APP_NMI 只可以分配给 APP_CPU。因此,PRO_CPUAPP_CPU 各可以分配到 69 个外部中断源

图2: 外部中断源

图3: 外部GPIO中断源

ESP32-CPU的外部中断

两个 CPU(PRO_CPUAPP_CPU)各有 32 个中断,其中 26 个为外部中断。图3列出了每个 CPU 所有的
中断。

图4: CPU的外部中断
### ESP32-CPU的外部中断 & 外部中断源 每一个外设的中断对应着一个中断源,每一个中断源对应着一个中断源重定向寄存器,如图3所示,**GPIO_INTERRUPT_PRO**中断源就对应着**PRO_GPIO_INTERRUPT_PRO_MAP_REG**寄存器通过设置这个寄存器就可以映射到对应的**CPU的外部中断**如图4所示。每当中断发生时,就可以通过对应**CPU的外部中断**的回调函数实现中断处理与服务。 值得注意的是:CPU的一个外部中断可以由多个中断源触发,只要在对应的寄存器中写入对应的外部中断号即可。 (将各个寄存器 **PRO_Xn_MAP_REG** (**APP_Xn_MAP_REG**) 都配成同样的 **中断**号。这些外设中断都会触发 **CPU Interrupt_P**)。

外部中断实战

  1. 首先配置GPIO

  ESP-IDF提供了一个结构体方便对其进行初始化,结构体如下:

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,剩下的就是模式设置和中断管理成员。
下面展示一个初始化的例子

#define GPIO(n)  (1ULL<<n)
#define EXTI_Num 0

 	gpio_config_t EXTI_config;
    EXTI_config.pin_bit_mask=GPIO(EXTI_Num);          /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */
    EXTI_config.mode=GPIO_MODE_INPUT;         /*!< GPIO mode: set input/output mode*/    
    EXTI_config.pull_up_en = 1;              /*!< GPIO pull-up*/
    EXTI_config.pull_down_en = 0; 
    EXTI_config.intr_type=GPIO_INTR_NEGEDGE;    /*!< GPIO interrupt type*/

  这个例子通过宏定义 #define GPIO(n) (1ULL<<n) 来设定对应GPIO的位,这里就是设置 GPIO0,模式因为是外部中断所以设定为输入模式,同时因为是低电平触发,需要将IO口先拉高,所以这里使能了EXTI_config.pull_up_en = 1;将其拉高,最后一个是中断模式的设定,官方一共给出了6种中断模式,如下。

typedef enum {
    GPIO_INTR_DISABLE = 0,     /*!< Disable GPIO interrupt                             */
    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_config函数来将结构体的设置导入到对应寄存器。

gpio_config(&EXTI_config);

接下来就是对应中断的设定。
  首先开启GPIO中断,注意这个函数并不是需要每一个GPIO中断都要使用一次,而是开启整个gpio的中断,并向当前运行的核心注册GPIO中断,也就是说你调用这个函数在哪个核心,中断处理函数就在哪个核心进行运行。函数如下:

gpio_install_isr_service(ESP_INTR_FLAG_LEVEL3);

  其中 ESP_INTR_FLAG_LEVEL3 也是一个宏定义它规定了这个中断的优先级如上文所属,所有中断优先级定义如下:

/** @brief Interrupt allocation flags
 *
 * These flags can be used to specify which interrupt qualities the
 * code calling esp_intr_alloc* needs.
 *
 */

//Keep the LEVELx values as they are here; they match up with (1<<level)
#define ESP_INTR_FLAG_LEVEL1        (1<<1)  ///< Accept a Level 1 interrupt vector (lowest priority)
#define ESP_INTR_FLAG_LEVEL2        (1<<2)  ///< Accept a Level 2 interrupt vector
#define ESP_INTR_FLAG_LEVEL3        (1<<3)  ///< Accept a Level 3 interrupt vector
#define ESP_INTR_FLAG_LEVEL4        (1<<4)  ///< Accept a Level 4 interrupt vector
#define ESP_INTR_FLAG_LEVEL5        (1<<5)  ///< Accept a Level 5 interrupt vector
#define ESP_INTR_FLAG_LEVEL6        (1<<6)  ///< Accept a Level 6 interrupt vector
#define ESP_INTR_FLAG_NMI           (1<<7)  ///< Accept a Level 7 interrupt vector (highest priority)
#define ESP_INTR_FLAG_SHARED        (1<<8)  ///< Interrupt can be shared between ISRs
#define ESP_INTR_FLAG_EDGE          (1<<9)  ///< Edge-triggered interrupt
#define ESP_INTR_FLAG_IRAM          (1<<10) ///< ISR can be called if cache is disabled
#define ESP_INTR_FLAG_INTRDISABLED  (1<<11) ///< Return with this interrupt disabled

#define ESP_INTR_FLAG_LOWMED    (ESP_INTR_FLAG_LEVEL1|ESP_INTR_FLAG_LEVEL2|ESP_INTR_FLAG_LEVEL3) ///< Low and medium prio interrupts. These can be handled in C.
#define ESP_INTR_FLAG_HIGH      (ESP_INTR_FLAG_LEVEL4|ESP_INTR_FLAG_LEVEL5|ESP_INTR_FLAG_LEVEL6|ESP_INTR_FLAG_NMI) ///< High level interrupts. Need to be handled in assembly.

#define ESP_INTR_FLAG_LEVELMASK (ESP_INTR_FLAG_LEVEL1|ESP_INTR_FLAG_LEVEL2|ESP_INTR_FLAG_LEVEL3| \
                                 ESP_INTR_FLAG_LEVEL4|ESP_INTR_FLAG_LEVEL5|ESP_INTR_FLAG_LEVEL6| \
                                 ESP_INTR_FLAG_NMI) ///< Mask for all level flags

最后是注册中断回调函数开启中断:

gpio_isr_handler_add(EXTI_Num,EXIT_Handelr,NULL);

到这里一个外中断就初始化完成了。

版权声明:本文为CSDN博主「据说这是zzy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_20540901/article/details/120817569

生成海报
点赞 0

据说这是zzy

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

rt_thread hc32f460开发四:pwm驱动移植

RT-Thread 驱动开发简介 RT-Thread驱动开发最开始应该是要阅读官方的文档,理解驱动的运行原理和使用方法。PWM部分的文档在这里https://www.rt-thread.org/document/site/#/rt