03. 外部中断

一、外部中断简介

  外部中断属于硬件中断,由微控制器外部事件触发。微控制器的特定引脚被设计为对特定事件(如按钮按压、传感器信号变化等)作出响应,这些引脚通常称为 “外部中断引脚”。一旦外部中断事件发生,当前程序执行将立即暂停,并跳转到相应的中断服务程序(ISR)进行处理。处理完毕后,程序会恢复执行,从被中断的地方继续。

CPU中断处理过程

  ESP32 S3 的外部中断具备两种触摸类型:电平触发边沿触发

  • 电平触发:高、低电平触发,要求保持中断的电平状态直到 CPU 响应。
  • 边沿触发:上升沿和下降沿触发,这类型的中断一旦触发,CPU 即可响应。

  ESP32 S3 支持 六级中断,同时支持中断嵌套,也就是优先级中断可以被高优先级中断打断。优先级的数字越大表明该中断的优先级越高。其中,NMI 中断拥有最高优先级,此类中断已经触发,CPU 必须处理。

ESP32S3中断

  在 ESP32 S3 中,中断系统被用于响应各种内部和外部事件。这些中断按照其触发方式和优先级进行分类。

  • 中断号每个中断的唯一标识符,用于在程序中引用和配置特定的中断。
  • 类别中断的来源类型,分为 外部中断内部中断外部中断外部设备信号触发,如按键、传感器等;内部中断 则由 微控制器内部的硬件事件触发,如定时器溢出、软件中断等。
  • 种类中断的触发方式,包括 电平触发边沿触发电平触发 是在 输入信号达到特定电平(如高电平或低电平)时触发中断边沿触发 则是在 输入信号从一种电平状态变化到另一种状态时触发中断
  • 优先级中断的响应优先级。当多个中断同时发生时,微控制器会根据中断的优先级来决定先处理哪个中断。较高的优先级意味着中断将优先得到处理。

二、外部中断常用函数

2.1、配置GPIO函数

  我们可以使用 gpio_config() 函数 配置 GPIO 的模式、上下拉等功能,其函数原型如下所示:

/**
 * @brief 设置GPIO配置
 * 
 * @param pGPIOConfig GPIO配置结构体的指针
 * @return esp_err_t ESP_OK配置成功,其它配置失败
 */
esp_err_t gpio_config(const gpio_config_t *pGPIOConfig);

  形参 pGPIOConfig 为 GPIO 配置结构体指针,它的主要成员如下:

typedef struct 
{
    uint64_t pin_bit_mask;              // 配置引脚位
    gpio_mode_t mode;                   // 设置引脚模式
    gpio_pullup_t pull_up_en;           // 设置上拉
    gpio_pulldown_t pull_down_en;       // 设置下拉
    gpio_int_type_t intr_type;          // 中断配置
} gpio_config_t;

  成员 pin_bit_mask 用来 设置的引脚位,我们可以填写的参数格式如下:(1 << x),其中 x 为 ESP32 S3 中可用 GPIO。比如我们使用 IO1 引脚,则可以写为:(1ull << GPIO_NUM_1)

  成员 mode 用来 设置引脚模式,它的可选值如下:

typedef enum
{
    GPIO_MODE_DISABLE = GPIO_MODE_DEF_DISABLE,                                                          // 失能输入输出模式
    GPIO_MODE_INPUT = GPIO_MODE_DEF_INPUT,                                                              // 输入模式
    GPIO_MODE_OUTPUT = GPIO_MODE_DEF_OUTPUT,                                                            // 输出模式
    GPIO_MODE_OUTPUT_OD = ((GPIO_MODE_DEF_OUTPUT) | (GPIO_MODE_DEF_OD)),                                // 输出开漏输出模式
    GPIO_MODE_INPUT_OUTPUT_OD = ((GPIO_MODE_DEF_INPUT) | (GPIO_MODE_DEF_OUTPUT) | (GPIO_MODE_DEF_OD)),  // 输入输出开漏模式
    GPIO_MODE_INPUT_OUTPUT = ((GPIO_MODE_DEF_INPUT) | (GPIO_MODE_DEF_OUTPUT)),                          // 输入输出模式
} gpio_mode_t;

  成员 pull_up_en 用来 配置上拉,它的可选值如下:

typedef enum 
{
    GPIO_PULLUP_DISABLE = 0x0,      // 失能上拉
    GPIO_PULLUP_ENABLE = 0x1,       // 使能上拉
} gpio_pullup_t;

  成员 pull_down_en 用来 配置下拉,它的可选值如下:

typedef enum 
{
    GPIO_PULLDOWN_DISABLE = 0x0,    // 失能下拉
    GPIO_PULLDOWN_ENABLE = 0x1,     // 使能下拉
} gpio_pulldown_t;

  成员 intr_type 用来 配置中断,它的可选值如下:

typedef enum 
{
    GPIO_INTR_DISABLE = 0,      // 失能中断
    GPIO_INTR_POSEDGE = 1,      // 上升沿
    GPIO_INTR_NEGEDGE = 2,      // 下降沿
    GPIO_INTR_ANYEDGE = 3,      // 上升沿和下降沿
    GPIO_INTR_LOW_LEVEL = 4,    // 输入低电平触发
    GPIO_INTR_HIGH_LEVEL = 5,   // 输入高电平触发
    GPIO_INTR_MAX,
} gpio_int_type_t;

  该函数返回 ESP_OK 表示 配置成功,返回 ESP_FAIL 表示 配置失败

#define ESP_OK          0       // 配置成功
#define ESP_FAIL        -1      // 配置失败

2.2、注册中断函数

  我们可以使用 gpio_install_isr_service() 函数用来 注册中断服务,它的函数原型如下:

/**
 * @brief 注册中断函数
 * 
 * @param intr_alloc_flags 分配中断的标志
 * @return esp_err_t ESP_OK注册成功,其它注册失败
 */
esp_err_t gpio_install_isr_service(int intr_alloc_flags);

  形参 intr_alloc_flags中断标志位,可选值如下:

#define ESP_INTR_FLAG_LEVEL1        (1<<1)      // 使用 Level 1 中断级别。在中断服务程序执行期间禁用同级别的中断
#define ESP_INTR_FLAG_LEVEL2        (1<<2)      // 使用 Level 2 中断级别。在中断服务程序执行期间禁用同级别和 Level 1 的中断
#define ESP_INTR_FLAG_LEVEL3        (1<<3)      // 使用 Level 3 中断级别。
#define ESP_INTR_FLAG_LEVEL4        (1<<4)      // 使用 Level 4 中断级别。
#define ESP_INTR_FLAG_LEVEL5        (1<<5)      // 使用 Level 5 中断级别。
#define ESP_INTR_FLAG_LEVEL6        (1<<6)      // 使用 Level 6 中断级别。
#define ESP_INTR_FLAG_NMI           (1<<7)      // 使用 Level 7 中断级别。
#define ESP_INTR_FLAG_SHARED        (1<<8)      // 中断可以在ISR之间共享
#define ESP_INTR_FLAG_EDGE          (1<<9)      // 使用边沿触发方式
#define ESP_INTR_FLAG_IRAM          (1<<10)     // 如果缓存被禁用,可以调用ISR
#define ESP_INTR_FLAG_INTRDISABLED  (1<<11)     // 禁用此中断后返回

#define ESP_INTR_FLAG_LOWMED    (ESP_INTR_FLAG_LEVEL1|ESP_INTR_FLAG_LEVEL2|ESP_INTR_FLAG_LEVEL3)                        // 使用中低水平触发方式
#define ESP_INTR_FLAG_HIGH      (ESP_INTR_FLAG_LEVEL4|ESP_INTR_FLAG_LEVEL5|ESP_INTR_FLAG_LEVEL6|ESP_INTR_FLAG_NMI)      // 使用高电平触发方式

2.3、设置中断服务函数

  我们可以使用 gpio_isr_handler_add() 设置中断服务函数,它的函数原型如下:

/**
 * @brief 设置中断服务函数
 * 
 * @param gpio_num GPIO引脚编号
 * @param isr_handler 外部中断的回调函数
 * @param args 外部中断的回调函数的传入参数
 * @return esp_err_t ESP_OK设置成功,其它设置失败
 */
esp_err_t gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void *args);

  形参 gpio_numGPIO 引脚号。该参数在 gpio_types.h 文件中枚举 gpio_num_t 有定义。

typedef enum 
{
    GPIO_NUM_NC = -1,   // Use to signal not connected to S/W
    GPIO_NUM_0 = 0,     // GPIO0, input and output
    GPIO_NUM_1 = 1,     // GPIO1, input and output
    GPIO_NUM_2 = 2,     // GPIO2, input and output
    GPIO_NUM_3 = 3,     // GPIO3, input and output
    GPIO_NUM_4 = 4,     // GPIO4, input and output
    GPIO_NUM_5 = 5,     // GPIO5, input and output
    GPIO_NUM_6 = 6,     // GPIO6, input and output
    GPIO_NUM_7 = 7,     // GPIO7, input and output
    GPIO_NUM_8 = 8,     // GPIO8, input and output
    GPIO_NUM_9 = 9,     // GPIO9, input and output
    GPIO_NUM_10 = 10,   // GPIO10, input and output
    GPIO_NUM_11 = 11,   // GPIO11, input and output
    GPIO_NUM_12 = 12,   // GPIO12, input and output
    GPIO_NUM_13 = 13,   // GPIO13, input and output
    GPIO_NUM_14 = 14,   // GPIO14, input and output
    GPIO_NUM_15 = 15,   // GPIO15, input and output
    GPIO_NUM_16 = 16,   // GPIO16, input and output
    GPIO_NUM_17 = 17,   // GPIO17, input and output
    GPIO_NUM_18 = 18,   // GPIO18, input and output
    GPIO_NUM_19 = 19,   // GPIO19, input and output
    GPIO_NUM_20 = 20,   // GPIO20, input and output
    GPIO_NUM_21 = 21,   // GPIO21, input and output
    GPIO_NUM_26 = 26,   // GPIO26, input and output
    GPIO_NUM_27 = 27,   // GPIO27, input and output
    GPIO_NUM_28 = 28,   // GPIO28, input and output
    GPIO_NUM_29 = 29,   // GPIO29, input and output
    GPIO_NUM_30 = 30,   // GPIO30, input and output
    GPIO_NUM_31 = 31,   // GPIO31, input and output
    GPIO_NUM_32 = 32,   // GPIO32, input and output
    GPIO_NUM_33 = 33,   // GPIO33, input and output
    GPIO_NUM_34 = 34,   // GPIO34, input and output
    GPIO_NUM_35 = 35,   // GPIO35, input and output
    GPIO_NUM_36 = 36,   // GPIO36, input and output
    GPIO_NUM_37 = 37,   // GPIO37, input and output
    GPIO_NUM_38 = 38,   // GPIO38, input and output
    GPIO_NUM_39 = 39,   // GPIO39, input and output
    GPIO_NUM_40 = 40,   // GPIO40, input and output
    GPIO_NUM_41 = 41,   // GPIO41, input and output
    GPIO_NUM_42 = 42,   // GPIO42, input and output
    GPIO_NUM_43 = 43,   // GPIO43, input and output
    GPIO_NUM_44 = 44,   // GPIO44, input and output
    GPIO_NUM_45 = 45,   // GPIO45, input and output
    GPIO_NUM_46 = 46,   // GPIO46, input and output
    GPIO_NUM_47 = 47,   // GPIO47, input and output
    GPIO_NUM_48 = 48,   // GPIO48, input and output
    GPIO_NUM_MAX,
} gpio_num_t;

  形参 isr_handler 指向中断处理函数的函数指针。中断处理函数是一个用户定义的回调函数,将在中断发生时执行。

  形参 args 用来 传递给中断处理程序的参数。这是一个指向用户特定数据的指针,可以在中断处理程序中使用。

  在设置中断服务函数后,我们需要实现一个中断服务程序的回调函数,在该函数中处理中断响应。中断处理函数需要声明为 IRAM_ATTR(其中函数名可以随意起名,但是要符合 C 语言标准),以确保其运行在内存中的可执行区域。

// IRAM_ATTR 属性用于将中断处理函数存储在内部 RAM 中,目的在于减少延迟
void IRAM_ATTR gpio_isr_handler(void* arg) 
{
    // 处理中断响应
}

2.4、开启外部中断函数

  我们可以使用 gpio_intr_enable() 函数 开启外部中断,它的函数原型如下:

/**
 * @brief 开启外部中断
 * 
 * @param gpio_num GPIO引脚编号
 * @return esp_err_t ESP_OK开启成功,其它开始失败
 */
esp_err_t gpio_intr_enable(gpio_num_t gpio_num);

在使用 gpio_intr_enable() 函数之前,开发者需要先通过 gpio_install_isr_service() 函数和 gpio_isr_handler_add() 函数来安装和注册中断处理程序。

三、实验例程

  这里,我们在【components】文件夹下新建一个【peripheral】文件夹,然后再在【peripheral】文件夹下新建一个【inc】文件夹(用来存放头文件)和【src】文件夹(用来存放源文件)。

  新建一个 bsp_exit.h 头文件,用来存放变量和函数的声明。

#ifndef __BSP_EXIT_H__
#define __BSP_EXIT_H__

#include "driver/gpio.h"
#include "esp_system.h"

void bsp_exit_init(gpio_num_t gpio_num, uint8_t trigger_edge);

#endif // !__BSP_EXIT_H__

  新建一个 bsp_exit.c 源文件,用来存放变量和函数的定义。

#include "bsp_exit.h"

static void bsp_exit_gpio_isr_handler(void *arg);

/**
 * @brief 外部中断初始化
 * 
 * @param gpio_num GPIO引脚
 * @param trigger_edge 触发边沿
 */
void bsp_exit_init(gpio_num_t gpio_num, uint8_t trigger_edge)
{
    gpio_config_t gpio_config_struct = {0};

    gpio_config_struct.pin_bit_mask = 1ULL << gpio_num;                         // 设置引脚
    gpio_config_struct.mode = GPIO_MODE_INPUT;                                  // 输入模式
    if (trigger_edge) 
    {
        gpio_config_struct.pull_down_en = GPIO_PULLDOWN_ENABLE;                 // 使用下拉
        gpio_config_struct.pull_up_en = GPIO_PULLUP_DISABLE;                    // 不使用上拉
        gpio_config_struct.intr_type = GPIO_INTR_POSEDGE;                       // 上升沿触发
    } 
    else 
    {
        gpio_config_struct.pull_up_en = GPIO_PULLUP_ENABLE;                     // 使用上拉
        gpio_config_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;                // 不使用下拉
        gpio_config_struct.intr_type = GPIO_INTR_NEGEDGE;                       // 下降沿触发
    }
    gpio_config(&gpio_config_struct);                                           // 配置GPIO

    gpio_install_isr_service(ESP_INTR_FLAG_EDGE);                               // 注册中断服务函数
    gpio_isr_handler_add(gpio_num, bsp_exit_gpio_isr_handler, (void *)gpio_num);// 设置GPIO的中断回调函数
    gpio_intr_enable(gpio_num);                                                 // 使能GPIO模块中断信号
}

/**
 * @brief 外部中断服务函数
 * 
 * @param arg 传入的参数
 */
static void IRAM_ATTR bsp_exit_gpio_isr_handler(void *arg)
{
    gpio_num_t gpio_num = (gpio_num_t) arg;

    if (gpio_num == GPIO_NUM_2)
    {
        gpio_set_level(GPIO_NUM_1, !gpio_get_level(GPIO_NUM_1));                // 设置GPIO电平
    }
}

  在【components】文件夹下的【peripheral】文件夹下新建一个 CMakeLists.txt 文件。

# 源文件路径
set(src_dirs src)

# 头文件路径
set(include_dirs inc)

# 设置依赖库
set(requires driver)

# 注册组件到构建系统的函数
idf_component_register(
    # 源文件路径
    SRC_DIRS ${src_dirs}
    # 自定义头文件的路径
    INCLUDE_DIRS ${include_dirs}
    # 依赖库的路径
    REQUIRES ${requires}
)

# 设置特定组件编译选项的函数
# -ffast-math: 允许编译器进行某些可能减少数学运算精度的优化,以提高性能。
# -O3: 这是一个优化级别选项,指示编译器尽可能地进行高级优化以生成更高效的代码。
# -Wno-error=format: 这将编译器关于格式字符串不匹配的警告从错误降级为警告。
# -Wno-format: 这将完全禁用关于格式字符串的警告。
component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)

  修改【main】文件夹下的 main.c 文件。

#include "freertos/FreeRTOS.h"

#include "bsp_exit.h"

#include "led.h"

// app_main()函数是ESP32的入口函数,它是FreRTOS的一个任务,任务优先级是1
// main()函数是C语言入口函数,它会在编译过程中插入到二进制文件中的
void app_main(void)
{   
    led_init(GPIO_NUM_1);
    bsp_exit_init(GPIO_NUM_2, 0);

    while (1)
    {
        // 将一个任务延迟给定的滴答数,IDF中提供pdMS_TO_TICKS可以将指定的ms转换为对应的tick数
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}
posted @ 2025-03-07 20:00  星光映梦  阅读(95)  评论(0)    收藏  举报