esp32s3外部中断(任务通知)
·
一、中断基础
中断分类
1.外部中断(EXTI)
GPIO 中断
2.外设中断
定时器中断、UART 中断、SPI 中断、ADC / DMA 中断、Wi-Fi / BLE 中断
中断优先级
1-7级 1级优先级最低 (指的esp32 不针对全部)
设置中断优先级函数: gpio_install_isr_service(0) :默认Level 1(最低优先级)
中断触发方式
1.上升沿触发(Rising Edge)
2.下降沿触发(Falling Edge)
3.边沿触发双沿(Any Edge)
4.高电平触发(High Level)
5.低电平触发(Low Level)
中断处理注意事项
中断服务函数(ISR)是嵌入式开发中的关键部分,编写时需遵循以下原则以确保系统稳定性和实时性:
- 保持简短:ISR 应尽可能短小,只做最必要的处理(如设置标志位、清除中断标志),将耗时操作移至主循环或任务中。
- 避免阻塞函数:禁止在 ISR 内调用可能引起阻塞的库函数(如
printf()、delay()、动态内存分配等),这些操作会严重破坏实时性并可能导致系统崩溃。- 保护共享数据:ISR 与主程序共享的变量需进行保护。常用方法包括:
- 使用
volatile关键字声明变量,防止编译器优化导致读写不一致。- 在访问关键数据区时临时关闭中断(
portENTER_CRITICAL()/portEXIT_CRITICAL()或类似机制),但应尽量缩短关中断时间。- 使用原子操作或硬件支持的同步原语。
- 注意可重入性:如果中断可能嵌套或被更高优先级中断打断,需确保 ISR 使用的函数和数据结构是可重入的。
- 及时清除中断标志:在 ISR 退出前,务必清除对应的中断标志位,避免中断被连续触发。
不良写法 vs 推荐写法对比
不良写法示例(在 ISR 中进行复杂处理和打印):
// 中断服务函数
void IRAM_ATTR gpio_isr_handler(void* arg) {
int sensor_value = read_adc(); // 可能耗时的操作
process_data(sensor_value); // 复杂计算
printf("ADC value: %d\n", sensor_value); // 阻塞式打印
// ... 其他操作
}
推荐写法示例(ISR 仅设置标志,主循环处理):
// 全局变量,使用 volatile 修饰
volatile bool adc_data_ready = false;
volatile int adc_raw_value = 0;
// 中断服务函数
void IRAM_ATTR gpio_isr_handler(void* arg) {
adc_raw_value = read_adc(); // 快速读取原始值
adc_data_ready = true; // 设置标志
// 清除中断标志
gpio_clear_intr_status(GPIO_NUM_4);
}
// 主循环中处理
void main_task(void* arg) {
while (1) {
if (adc_data_ready) {
// 关中断保护共享变量(可选,取决于架构)
portENTER_CRITICAL();
int local_value = adc_raw_value;
adc_data_ready = false;
portEXIT_CRITICAL();
// 在主循环中进行耗时处理
process_data(local_value);
printf("ADC value: %d\n", local_value); // 在主循环中打印
}
vTaskDelay(10 / portTICK_PERIOD_MS);
}
}
通过遵循上述注意事项,可以编写出高效、可靠的中断服务程序,确保嵌入式系统的实时响应与稳定运行。
二、任务通知(Freertos延伸)
任务通知原理
1.中断里发消息到别的线程
xTaskNotifyFromISR
2.让别的线程去执行操作
xTaskNotifyWait
这样就不会占用中断执行的时间
BaseType_t xTaskNotifyFromISR(
TaskHandle_t xTaskToNotify, // 要通知的目标任务句柄
uint32_t ulValue, // 通知值(可以传递数据)
eNotifyAction eAction, // 通知动作(如何更新通知值)
BaseType_t *pxHigherPriorityTaskWoken // 是否有更高优先级任务被唤醒
);
BaseType_t xTaskNotifyWait(
uint32_t ulBitsToClearOnEntry, // 进入时清除的通知位
uint32_t ulBitsToClearOnExit, // 退出时清除的通知位
uint32_t *pulNotificationValue, // 出参:接收到的通知值
TickType_t xTicksToWait // 等待超时时间
);
中断相关代码
#include "bsp_exti.h"
TaskHandle_t reg_exti_task_handle = NULL; // 外部中断通知的目标任务句柄
/**
* @brief GPIO中断服务函数声明(IRAM_ATTR表示该函数存放在IRAM中,提高中断响应速度)
*/
static void IRAM_ATTR gpio_isr_handler(void* arg);
/**
* @brief 初始化外部中断
* @param gpio_num 要配置的GPIO引脚号
* @note 配置GPIO为输入模式,使能上拉电阻,下降沿触发中断
*/
void bsp_exti_init(gpio_num_t gpio_num)
{
// 配置GPIO参数:输入模式、上拉使能、下降沿触发中断
gpio_config_t key_config = {
.pin_bit_mask = (1ULL<<gpio_num), // 设置目标GPIO引脚位掩码
.mode = GPIO_MODE_INPUT, // 设置为输入模式
.pull_up_en = GPIO_PULLUP_ENABLE, // 使能上拉电阻
.pull_down_en = GPIO_PULLDOWN_DISABLE, // 禁用下拉电阻
.intr_type = GPIO_INTR_NEGEDGE // 下降沿触发中断
};
gpio_config(&key_config); // 应用GPIO配置
// 静态变量标记ISR服务是否已安装,确保只安装一次
static bool isr_service_installed = 0;
if(0 == isr_service_installed)
gpio_install_isr_service(0); // 安装GPIO ISR服务,参数0表示使用默认配置
isr_service_installed = 1;
// 为指定GPIO添加中断处理函数,传入GPIO编号作为参数
gpio_isr_handler_add(gpio_num, gpio_isr_handler, (void *)gpio_num);
}
/**
* @brief GPIO中断服务函数
* @param arg 传入的参数,此处为GPIO编号
* @note IRAM_ATTR属性确保函数存放在IRAM中,避免从Flash加载带来的延迟
* 实现了20ms软件消抖功能
*/
static void IRAM_ATTR gpio_isr_handler(void* arg)
{
static uint64_t last_time = 0; // 上次中断触发时间,用于软件消抖
uint64_t now = esp_timer_get_time(); // 获取当前时间(微秒)
// 软件消抖:如果距离上次中断不足20ms,则忽略本次中断
if (now - last_time < 20000) return;
last_time = now;
BaseType_t xHigherPriorityTaskWoken = pdFALSE; // 标记是否有更高优先级任务被唤醒
gpio_num_t gpio_num = (gpio_num_t)arg; // 获取触发中断的GPIO编号
// 从中断中发送任务通知,携带GPIO编号,使用覆盖模式
xTaskNotifyFromISR(reg_exti_task_handle, gpio_num, eSetValueWithOverwrite, &xHigherPriorityTaskWoken);
// 如果有更高优先级任务被唤醒,进行上下文切换
if(xHigherPriorityTaskWoken)
{
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
}
/**
* @brief 注册外部中断通知的目标任务句柄
* @param task_handle 目标任务句柄,中断发生时会向该任务发送通知
*/
void bsp_exti_register_task_handle(TaskHandle_t task_handle)
{
reg_exti_task_handle = task_handle; // 保存任务句柄,供中断服务函数使用
}
任务通知的代码
#include "task_exti_prco.h"
TaskHandle_t exti_task_prco_handle = NULL; // 外部中断处理任务的任务句柄,供中断服务函数使用
/**
* @brief 外部中断处理任务函数
* @param pvParameters 任务创建时传入的参数(本任务未使用)
* @note 该任务通过任务通知接收中断信号,根据GPIO编号执行相应操作
*/
void exti_task_prco(void *pvParameters)
{
uint32_t notify_value = 0; // 用于存储接收到的通知值(即触发中断的GPIO编号)
// 将本任务句柄注册到BSP层,使中断服务函数知道向哪个任务发送通知
bsp_exti_register_task_handle(exti_task_prco_handle);
while (1)
{
// 永久等待任务通知,进入时不清除任何位,退出时清除所有位
// 收到通知后,notify_value 中保存的是触发中断的GPIO编号
xTaskNotifyWait(0, ULONG_MAX, ¬ify_value, portMAX_DELAY);
// 根据接收到的GPIO编号判断是哪个按键触发的中断
if(notify_value == KEY_GPIO)
{
bsp_gpio_toggle_level(LED_GPIO); // 翻转LED电平状态
printf("key gpio notify\n"); // 打印调试信息:KEY按键触发
}
else if(notify_value == BOOT_GPIO)
{
bsp_gpio_toggle_level(LED_GPIO); // 翻转LED电平状态
printf("boot gpio notify\n"); // 打印调试信息:BOOT按键触发
}
}
}
/**
* @brief 创建外部中断处理任务
* @return BaseType_t 任务创建结果,pdPASS表示成功,errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY表示内存不足
* @note 任务栈大小4096字节,优先级10
*/
BaseType_t exti_task_prco_create(void)
{
// 创建外部中断处理任务
// 参数说明:任务函数、任务名称、栈深度(字)、任务参数、优先级、任务句柄指针
BaseType_t ret = xTaskCreate(exti_task_prco, "exti_task_prco",4096, NULL, 10, &exti_task_prco_handle);
return ret;
}
注释是由我让TRAE插件自动生成的,方便后续查看函数意思。
更多推荐



所有评论(0)