You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 09:08:11