ESP32运行6个并行FreeRTOS任务触发Core0 LoadProhibited报错
ESP32 FreeRTOS任务启动后LoadProhibited崩溃修复
问题表现
在ESP32上尝试启动6个无限循环的FreeRTOS任务后,设备立即触发未处理异常自动重启,崩溃日志如下:
Guru Meditation Error: Core 0 panic'ed (LoadProhibited). Exception was unhandled. Core 0 register dump: PC : 0x400868b4 PS : 0x00060033 A0 : 0x80085442 A1 : 0x3ffb0b50 0x400868b4: xTaskIncrementTick at C:/Users/preet/esp/esp-idf/components/freertos/tasks.c:3157 A2 : 0x00000001 A3 : 0x80059301 A4 : 0x00000000 A5 : 0x00000132 A6 : 0x00000003 A7 : 0x00060023 A8 : 0x00000000 A9 : 0x3ffb0b30 A10 : 0x3ffb26b8 A11 : 0x00000003 A12 : 0x00060b20 A13 : 0x00060b23 A14 : 0x3ffb6300 A15 : 0x3ffb53fc SAR : 0x00000016 EXCCAUSE: 0x0000001c EXCVADDR: 0x80059309 LBEG : 0x00000000 LEND : 0x00000000 LCOUNT : 0x00000000 Backtrace:0x400868b1:0x3ffb0b500x4008543f:0x3ffb0b70 0x40085199:0x3ffb0b90 0x400826e9:0x3ffb0ba0 0x400e4b6b:0x3ffb6210 0x400d18ef:0x3ffb6230 0x40086462:0x3ffb6250 0x40087991:0x3ffb6270 0x400868b1: xTaskIncrementTick at C:/Users/preet/esp/esp-idf/components/freertos/tasks.c:3156 0x4008543f: xPortSysTickHandler at C:/Users/preet/esp/esp-idf/components/freertos/port/port_systick.c:167 0x40085199: _frxt_timer_int at C:/Users/preet/esp/esp-idf/components/freertos/port/xtensa/portasm.S:329 0x400826e9: _xt_lowint1 at C:/Users/preet/esp/esp-idf/components/freertos/port/xtensa/xtensa_vectors.S:1111 0x400e4b6b: cpu_ll_waiti at C:/Users/preet/esp/esp-idf/components/hal/esp32/include/hal/cpu_ll.h:183 (inlined by) esp_pm_impl_waiti at C:/Users/preet/esp/esp-idf/components/esp_pm/pm_impl.c:837 0x400d18ef: esp_vApplicationIdleHook at C:/Users/preet/esp/esp-idf/components/esp_system/freertos_hooks.c:63 0x40086462: prvIdleTask at C:/Users/preet/esp/esp-idf/components/freertos/tasks.c:3973 (discriminator 1) 0x40087991: vPortTaskWrapper at C:/Users/preet/esp/esp-idf/components/freertos/port/xtensa/port.c:131 ELF file SHA256: 142fe637d0302132 Rebooting...
崩溃点落在FreeRTOS内核的tick中断处理函数xTaskIncrementTick,属于典型的内存被破坏后内核调度器访问非法地址触发的panic。
根因定位
逐行排查业务代码后,确认有6个直接/间接导致崩溃的问题:
- 核心触发点:任务函数签名不符合FreeRTOS规范。
MAGNETIC_END_SENSOR任务入口没有定义void *pvParameters形参,FreeRTOS启动任务时会按约定向任务栈压入传入的参数,函数形参不匹配会导致栈帧错位,任务执行时直接篡改栈上的返回地址、内核数据结构,最终触发内存访问错误。 - 任务栈分配过小。电机控制类任务仅分配512字节栈,ESP32上调用
ESP_LOG*打印日志、执行GPIO操作至少需要1024字节以上栈空间,栈溢出会直接踩踏相邻内存的任务控制块、信号量等内核对象,破坏任务链表结构,和崩溃点落在调度器tick处理函数的现象完全吻合。 - 任务删除逻辑存在野指针风险。任务自删除时传入全局任务句柄而非
NULL,如果任务启动后快速执行到删除逻辑,xTaskCreate还没来得及把任务句柄写入全局变量,就会向vTaskDelete传入野指针,直接破坏内核任务链表。 - 重复初始化GPIO。多个任务中反复对同一个继电器、传感器引脚执行方向配置、引脚选择操作,并发执行时会篡改GPIO寄存器配置,同时带来不必要的内存访问风险。
- 轮询任务无延时占满CPU。传感器、按键轮询的无限循环中没有加
vTaskDelay让出CPU,高优先级任务会抢占全部时间片,导致IDLE任务无法执行,系统无法回收已删除任务的内存,最终耗尽堆内存触发内存错误。 - 无任务存在性校验。按键触发任务创建时没有判断对应任务是否已经在运行,重复触发会重复创建任务,快速耗尽堆内存。
修复方案
按以下步骤修改即可解决崩溃问题:
- 统一修正所有任务的函数签名,所有FreeRTOS任务入口必须遵循
void 任务函数名(void *pvParameters)格式,即使不使用传入参数也必须保留该形参。 - 统一在
app_main中完成所有GPIO的初始化配置,仅执行一次,禁止在各个业务任务中重复初始化引脚。 - 调整任务栈大小:所有涉及日志打印、外设操作的任务栈最小分配2048字节,禁止分配小于1024字节的任务栈。
- 修正任务删除逻辑:任务删除自身时直接传
NULL给vTaskDelete;跨任务删除其他任务前必须校验句柄非空,删除完成后立即将全局句柄置为NULL,避免野指针。 - 所有轮询类无限循环任务必须增加至少10ms的
vTaskDelay,主动让出CPU时间片,避免饿死IDLE任务。 - 创建任务前增加句柄校验,对应任务已经在运行(句柄非空)时禁止重复创建,避免堆内存泄漏。
- 信号量创建后增加有效性校验,确保信号量创建成功再启动业务逻辑。
关键修正代码示例
修正后的app_main初始化逻辑:
void app_main(void) { // 一次性初始化所有GPIO gpio_pad_select_gpio(PUSH_BUTTON); gpio_pad_select_gpio(FORCE_STOP_BUTTON); gpio_pad_select_gpio(IR_PIN); gpio_pad_select_gpio(RELAY_1_PIN); gpio_pad_select_gpio(RELAY_2_PIN); gpio_pad_select_gpio(MAGNET_1_PIN); gpio_pad_select_gpio(MAGNET_2_PIN); gpio_set_direction(PUSH_BUTTON, GPIO_MODE_INPUT); gpio_set_direction(FORCE_STOP_BUTTON, GPIO_MODE_INPUT); gpio_set_direction(IR_PIN, GPIO_MODE_INPUT); gpio_set_direction(RELAY_1_PIN, GPIO_MODE_OUTPUT); gpio_set_direction(RELAY_2_PIN, GPIO_MODE_OUTPUT); gpio_set_direction(MAGNET_1_PIN, GPIO_MODE_INPUT); gpio_set_direction(MAGNET_2_PIN, GPIO_MODE_INPUT); // 创建信号量并校验 I_F_F_O_SIGNAL = xSemaphoreCreateBinary(); M_F_F_O_SIGNAL = xSemaphoreCreateBinary(); M_F_R_O_SIGNAL = xSemaphoreCreateBinary(); configASSERT(I_F_F_O_SIGNAL && M_F_F_O_SIGNAL && M_F_R_O_SIGNAL); // 初始化所有全局任务句柄 FFO_xHandle = FRO_xHandle = MS_xHandle = NULL; MFS_xHandle = MRS_xHandle = IR_xHandle = MAGNET_xHandle = NULL; MOTOR_STATE = 0; // 启动按键检测任务 xTaskCreate(&BUTTON_OPERATION, "BUTTON_OPERATION", 2048, NULL, tskIDLE_PRIORITY, NULL); }
修正后的磁传感器任务示例:
// 补全符合规范的任务形参 void MAGNETIC_END_SENSOR(void * pvParameters) { while (1) { if (gpio_get_level(MAGNET_1_PIN) == 1 || gpio_get_level(MAGNET_2_PIN) == 1){ if (MOTOR_STATE == 0){ xSemaphoreGive(M_F_F_O_SIGNAL); ESP_LOGW(TAG, "F Signal sent."); } else if (MOTOR_STATE == 1){ xSemaphoreGive(M_F_R_O_SIGNAL); ESP_LOGW(TAG, "R Signal sent."); } ESP_LOGW(TAG, "Gate reached to the destination, Stopping the motor."); // 校验句柄避免重复创建任务 if(MS_xHandle == NULL) { xTaskCreate(&MOTOR_STOP, "MOTOR_STOP", 2048, NULL, 1, &MS_xHandle); } MOTOR_STATE = !MOTOR_STATE; ESP_LOGW(TAG, "Motor stopped and Motor State changed."); MAGNET_xHandle = NULL; vTaskDelete(NULL); // 自删除传NULL,避免野指针 } vTaskDelay(10 / portTICK_PERIOD_MS); // 加延时让出CPU } }
其余任务按照相同规则修正签名、栈大小、删除逻辑、轮询延时即可。
内容的提问来源于stack exchange,提问作者preetjhota
相关产品推荐
相关产品推荐

