ESP32基于中断的UART读取代码触发CPU1中断看门狗超时崩溃
ESP32 UART中断驱动改造后出现Interrupt WDT超时问题
问题背景
将ESP32的UART轮询代码改为中断驱动版本后,触发interrupt wdt on CPU1错误。已移除所有日志输出、为中断处理函数添加IRAM_ATTR属性,但问题仍未解决。
报错信息
Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1). Core 1 register dump: PC : 0x40098df4 PS : 0x00060935 A0 : 0x80097d66 A1 : 0x3ffbf15c A2 : 0x3ffcd3b8 A3 : 0x3ffd0d6c A4 : 0x00000004 A5 : 0x00060923 A6 : 0x00060923 A7 : 0x00000001 A8 : 0x3ffd0d6c A9 : 0x00000018 A10 : 0x3ffd0d6c A11 : 0x00000018 A12 : 0x3ffc8734 A13 : 0x00060923 A14 : 0x007bf318 A15 : 0x003fffff SAR : 0x0000001e EXCCAUSE: 0x00000006 EXCVADDR: 0x00000000 LBEG : 0x400860a1 LEND : 0x400860a9 LCOUNT : 0x00000027 Core 1 was running in ISR context: EPC1 : 0x40109c5b EPC2 : 0x00000000 EPC3 : 0x00000000 EPC4 : 0x00000000 Backtrace:0x40098df1:0x3ffbf15c |<-CORRUPTED
当前代码实现
#define BMS_UART_VERSION2 true #define RXD GPIO_NUM_34 #define TXD GPIO_NUM_14 #define BmsSerial Serial2 const int MESSAGE_LENGTH = PACKET_LENGTH; const byte START_BYTE = 0xA5; /// The queue where we push incoming UART messages from the BMS QueueHandle_t BmsUart :: incomingMessageQueue = xQueueCreate(100, PACKET_LENGTH); /// @brief Installs interrupt handler void BmsUart :: setupInterrupt() { log("Installing UART interrupt handler"); attachInterrupt(digitalPinToInterrupt(RXD), BmsUart :: processIncomingByteToForgeAMessage, FALLING); } /// @brief Process incoming byte to forge a coherent message and push to the queue void IRAM_ATTR BmsUart :: processIncomingByteToForgeAMessage() { static byte message[MESSAGE_LENGTH]; static int messageIndex = 0; const int receivedInt = BmsSerial.read(); // Nothing available? if (receivedInt<0) return; const byte receivedByte = receivedInt&0xff; // Waiting for start of message byte if (messageIndex == 0 && receivedByte != START_BYTE) { // Discard bytes until the start byte is found bmsUart.log("Start byte is wrong: %02x, discard till it is right (%02x)", receivedByte, START_BYTE); return; } bmsUart.log(">%02x ", receivedByte&0xff); // Add the received byte to the message buffer message[messageIndex++] = receivedByte; if (messageIndex>PACKET_LENGTH) { // Reset the index for the next message messageIndex = 0; bmsUart.error("Buffer overflow at %d bytes, resetting to 0", messageIndex); } if (messageIndex == MESSAGE_LENGTH) { const byte messageId = message[PACKET_MESSAGE_ID_OFFSET]; const esp_err_t err = xQueueSend(BmsUart :: incomingMessageQueue, message, RS485_QUEUE_MAX_TICKS_DELAY); switch (err){ // Ok, posted in the queue? case pdTRUE: break; // Queue is full? case errQUEUE_FULL: bmsUart.warn("Queue is full: unable to post the message %02x to the queue", messageId); break; default: bmsUart.warn("Error %02x: Unable to post the message %02x to the queue, even after %d ticks", err, messageId, RS485_QUEUE_MAX_TICKS_DELAY); break; } // Reset the index for the next message messageIndex = 0; } } /// @brief Gets pending message if any /// @param message A PACKET_LENGTH -sized byte buffer to hold the next message, if nay /// @return true if a message is there otherwise false bool BmsUart :: getNextMessage(byte *message) { // If a message is available, process it if (xQueueReceive(incomingMessageQueue, &message, RS485_QUEUE_MAX_TICKS_DELAY) == pdPASS) { // Log the available message logMessage("Pending message", message); return true; } return false; } void BmsUart :: setup() { /// Stack depth stackDepth = 4000; /// Every 300 mseconds taskExecutionPeriod = 300; log("Serial connection thru UART2 (pins %d/%d)…", RXD,TXD); #if BMS_UART_VERSION2 // == Installs interrupt handler setupInterrupt(); #endif // == driver output enable //pinMode (ENABLE_PIN, OUTPUT); BmsSerial.begin(9600, SERIAL_8N1, RXD, TXD); while (!BmsSerial) { log(">."); } log("BmsSerial began!"); }
问题分析与解决方向
- 错误根源:Interrupt WDT超时说明CPU1在ISR中执行时间过长,或ISR被高频触发导致无法喂狗。当前用GPIO电平中断监听RX引脚,每接收一个UART字节就触发一次中断,高频中断加上ISR内的冗余操作会耗尽CPU时间。
- 替换为UART硬件中断:放弃
attachInterrupt监听RX引脚的方式,改用ESP32 UART外设原生的接收中断(如RX FIFO满、RX超时中断),这是更高效的UART中断处理方案。 - ISR内操作最小化:
- 用
xQueueSendFromISR替代xQueueSend,ISR中不能使用阻塞超时参数(RS485_QUEUE_MAX_TICKS_DELAY需设为0),避免ISR阻塞。 - 彻底移除ISR内的所有日志输出(即使注释也要确保没有残留),日志操作会占用大量时间。
- 用
- 确保ISR相关代码在IRAM中:检查ISR中调用的所有函数(如
BmsSerial.read())是否在IRAM中,若使用Arduino Serial API存在不确定性,建议改用ESP-IDF原生UART API,确保所有ISR调用的代码都标记IRAM_ATTR。 - 队列操作优化:ISR中发送队列时,若队列已满直接丢弃或标记错误,不要等待,避免ISR占用过多时间。
内容的提问来源于stack exchange,提问作者Stéphane de Luca
相关产品推荐
相关产品推荐

