ESP32 GPIO中断执行NVS擦除触发lock_acquire_generic错误求助
问题描述
我尝试在GPIO中断服务程序(ISR)中执行NVS闪存擦除操作,为避免DI引脚高电平频繁触发中断,在中断处理里添加了3秒消抖逻辑,代码如下:
static void IRAM_ATTR gpio_isr_handler(void *arg) { uint32_t current_time = xTaskGetTickCountFromISR(); // Debounce: Only process if enough time has passed since the last interrupt if ((current_time - last_interrupt_time) > (3000 / portTICK_PERIOD_MS)) { // button_state = true; esp_err_t err = nvs_flash_erase(); if (err == ESP_OK) { ESP_LOGI("NVS", "NVS flash erased successfully."); } else { ESP_LOGE("NVS", "Failed to erase NVS flash: %s", esp_err_to_name(err)); } last_interrupt_time = current_time; } }
运行后出现以下错误,已确认无堆损坏,求解决办法:
abort() was called at PC 0x40082edf on core 0 --- 0x40082edf: lock_acquire_generic at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/newlib/locks.c:133 Backtrace: 0x40081e0a:0x3ffb1b20 0x4008aa05:0x3ffb1b40 0x40092f55:0x3ffb1b60 0x40082edf:0x3ffb1bd0 0x4008301d:0x3ffb1c00 0x400830aa:0x3ffb1c20 0x4017e74e:0x3ffb1c50 0x40171c09:0x3ffb1f70 0x4018e435:0x3ffb1fa0 0x400918b9:0x3ffb1fd0 0x400831a9:0x3ffb2020 0x40083799:0x3ffb2040 0x4008386b:0x3ffb2070 0x40083545:0x3ffb20a0 0x40089723:0x3ffbbe70 0x400d47c3:0x3ffbbe90 0x4008c5d5:0x3ffbbeb0 0x4008b511:0x3ffbbed0 --- 0x40081e0a: panic_abort at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/esp_system/panic.c:463 0x4008aa05: esp_system_abort at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/esp_system/port/esp_system_chip.c:92 0x40092f55: abort at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/newlib/abort.c:38 0x40082edf: lock_acquire_generic at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/newlib/locks.c:133 0x4008301d: _lock_acquire_recursive at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/newlib/locks.c:162 0x400830aa: __retarget_lock_acquire_recursive at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/newlib/locks.c:321 0x4017e74e: _vfprintf_r at /builds/idf/crosstool-NG/.build/HOST-x86_64-w64-mingw32/xtensa-esp-elf/src/newlib/newlib/libc/stdio/vfprintf.c:846 (discriminator 2) 0x40171c09: vprintf at /builds/idf/crosstool-NG/.build/HOST-x86_64-w64-mingw32/xtensa-esp-elf/src/newlib/newlib/libc/stdio/vprintf.c:34 0x4018e435: esp_log_writev at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/log/log.c:210 0x400918b9: esp_log_write at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/log/log.c:220 0x400831a9: gpio_isr_handler at C:/Users/Admin/Desktop/gateway/main/wifi.c:168 (discriminator 1) 0x40083799: gpio_isr_loop at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/esp_driver_gpio/src/gpio.c:466 0x4008386b: gpio_intr_service at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/esp_driver_gpio/src/gpio.c:496 0x40083545: _xt_lowint1 at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/xtensa/xtensa_vectors.S:1240 0x40089723: xt_utils_wait_for_intr at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/xtensa/include/xt_utils.h:82 (inlined by) esp_cpu_wait_for_intr at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/esp_hw_support/cpu.c:55 0x400d47c3: esp_vApplicationIdleHook at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/esp_system/freertos_hooks.c:58 0x4008c5d5: prvIdleTask at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/freertos/FreeRTOS-Kernel/tasks.c:4344 (discriminator 1) 0x4008b511: vPortTaskWrapper at C:/Users/Admin/.espressif/frameworks/esp-idf-v5.3/components/freertos/FreeRTOS-Kernel/portable/xtensa/port.c:134 ELF file SHA256: 1a4a2a846 Rebooting... I (13) boot: ESP-IDF v5.3-dirty 2nd stage bootloader I (13) boot: compile time Dec 5 2024 15:23:00 I (13) boot: Multicore bootloader
问题原因及解决办法
原因分析
从回溯信息可看出,崩溃发生在lock_acquire_generic,根源是在ISR中调用了非中断安全的函数:
nvs_flash_erase():NVS操作涉及闪存访问、锁操作,这类操作无法在ISR上下文执行ESP_LOGI()/ESP_LOGE():标准日志函数会调用libc的vfprintf,内部用到递归锁,ISR中无法安全获取这类锁,直接触发abort
ISR的执行上下文有严格限制:只能调用带ISR后缀的FreeRTOS API,或明确标注为中断安全的ESP-IDF函数,且执行时间必须极短。
解决步骤
正确做法是用任务通知把NVS擦除操作从ISR转移到普通任务中执行:
1. 定义全局任务句柄和消抖变量
#include "freertos/FreeRTOS.h" #include "freertos/task.h" static TaskHandle_t nvs_erase_task_handle = NULL; static volatile uint32_t last_interrupt_time = 0;
2. 创建处理NVS擦除的后台任务
void nvs_erase_task(void *arg) { while(1) { // 等待ISR发送的任务通知 ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // 执行NVS擦除操作 esp_err_t err = nvs_flash_erase(); if (err == ESP_OK) { ESP_LOGI("NVS", "NVS flash erased successfully."); } else { ESP_LOGE("NVS", "Failed to erase NVS flash: %s", esp_err_to_name(err)); } } } // 在app_main中初始化任务 void app_main() { // ... 其他初始化代码(GPIO、NVS等) xTaskCreate(nvs_erase_task, "nvs_erase_task", 4096, NULL, 5, &nvs_erase_task_handle); }
3. 修改ISR,仅处理消抖并发送任务通知
static void IRAM_ATTR gpio_isr_handler(void *arg) { uint32_t current_time = xTaskGetTickCountFromISR(); // 消抖判断 if ((current_time - last_interrupt_time) > (3000 / portTICK_PERIOD_MS)) { // 非阻塞方式发送任务通知给后台处理任务 vTaskNotifyGiveFromISR(nvs_erase_task_handle, NULL); last_interrupt_time = current_time; } }
额外注意事项
last_interrupt_time必须声明为volatile类型,防止编译器优化导致数值错误- ISR中只能调用
xTaskGetTickCountFromISR()、vTaskNotifyGiveFromISR()这类明确支持ISR的API - NVS擦除操作耗时较长,放在后台任务执行不会阻塞中断响应,符合实时系统要求
内容的提问来源于stack exchange,提问作者mauli02k
相关产品推荐
相关产品推荐

