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

ESP8266_NONOS_SDK转Arduino框架:GPIO中断移植异常求助

ESP8266 Arduino框架下GPIO中断时间计算异常排查与修复

问题背景

我是专注API与后端开发的C#开发者,对固件开发/C/C++完全陌生。因ESP8266_NONOS_SDK已过时,正在将现有项目迁移至Arduino+PlatformIO框架,WiFi连接、OTA等基础功能已正常实现,但移植GPIO14的接收器中断处理时遇到问题:串口输出显示current_time与previous_time数值相同,导致pulseWidth出现不合理的高值。

原NONOS SDK可正常运行的中断代码

LOCAL void ICACHE_FLASH_ATTR gpio_intr_handler(void *dummy)
{
    static uint32 pulseCount, i;
    static uint8 invCRC;
    uint8 crcCalculated;
    volatile uint32 temp = 2500;
    uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);

    // if the interrupt was by GPIO14
    if (gpio_status & BIT(WROOM_DATA_IN_GPIO))
    {
        // disable interrupt for GPIO14
        gpio_pin_intr_state_set(GPIO_ID_PIN(WROOM_DATA_IN_GPIO), GPIO_PIN_INTR_DISABLE);
        // clear interrupt status for GPIO14
        GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & BIT(WROOM_DATA_IN_GPIO));
    
        // Calculate pulse width
        current_time = get_long_systime(); // system_get_time();
        pulseWidth = current_time - previous_time;
        previous_time = current_time;
        if (pulseWidth > 100)
        {
            pulseTime[pulseCount] = pulseWidth;
            pulseCount++;
        }
         }
    // Omitted as it's just calculations that I cannot share
    
    out:
        if (pulseWidth > MAX_WIDTH || pulseCount > MAX_PULSES)
        {
    
            pulseCount = 0;
            byteIndex = 0;
            bitIndex = 0;
            tag_alarm = 0;
            tag_batt = 0;
            tag_tlm = 0;
            for (i = 0; i < 5; i++)
                tagId[i] = 0;
        }
        // Reactivate interrupts for GPIO14
        gpio_pin_intr_state_set(GPIO_ID_PIN(WROOM_DATA_IN_GPIO), GPIO_PIN_INTR_ANYEDGE);

}

移植到Arduino框架后的中断代码

LOCAL void IRAM_ATTR gpio_intr_handler()
{
    static uint32 pulseCount, i;
    static uint8 invCRC;
    uint8 crcCalculated;
    volatile uint32 temp = 2500;

    uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
    if (gpio_status & BIT(WROOM_DATA_IN_GPIO))
    {
        Serial.println("");
    
        // Disable GPIO14 Interrupt
        Serial.println("Detach Interrupt");
        detachInterrupt(digitalPinToInterrupt(WROOM_DATA_IN_GPIO));
    
        // clear interrupt status for GPIO14
        Serial.println("Clear GPIO Interrupt");
        // GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & BIT(WROOM_DATA_IN_GPIO));
        GPIO_OUTPUT_SET(GPIO_STATUS_W1TC_ADDRESS, gpio_status & BIT(WROOM_DATA_IN_GPIO));
    
        current_time = micros(); // system_get_rtc_time();
        pulseWidth = (current_time - previous_time);
        previous_time = current_time;
    
        Serial.print("Current Time: ");
        Serial.print(current_time);
        Serial.print(" Previous Time: ");
        Serial.print(previous_time);
        Serial.print(" Pulse Width: ");
        Serial.print(pulseWidth);
        Serial.print(" Pulse Count: ");
        Serial.println(pulseCount);
    
        if (pulseWidth > 100)
        {
            pulseTime[pulseCount] = pulseWidth;
            pulseCount++;
    
            Serial.print("Updated Pulse Count: ");
            Serial.println(pulseCount);
        }
    
        if (pulseWidth > MAX_WIDTH || pulseCount > MAX_PULSES)
        {
            pulseCount = 0;
            byteIndex = 0;
            bitIndex = 0;
            tag_alarm = 0;
            tag_batt = 0;
            tag_tlm = 0;
            for (i = 0; i < 5; i++)
                tagId[i] = 0;
        }
        attachInterrupt(digitalPinToInterrupt(USER_GPIO_IN), gpio_intr_handler, CHANGE);
     }

}

问题根源分析

  1. 变量未初始化:previous_time没有在首次中断触发前赋值,首次进入中断时是随机垃圾值,大概率和current_time相等,后续逻辑也会因为时序混乱导致值异常。
  2. ISR中执行串口操作:中断服务函数(ISR)里调用Serial.println()是致命错误——串口依赖系统中断,在ISR中调用会引发死锁或时序错乱,直接干扰micros()的时间获取。
  3. GPIO中断清除方式错误:GPIO_OUTPUT_SET不能用于清除中断状态,必须用原SDK的GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, ...),错误的清除逻辑会导致中断重复触发,打乱时间计算。
  4. 中断绑定参数不匹配:最后attachInterrupt用了USER_GPIO_IN,但前面判断的是WROOM_DATA_IN_GPIO,参数不一致导致中断绑定错误。

修复后的代码示例

// 全局变量必须加volatile,防止编译器优化
volatile uint32_t previous_time = 0;
volatile uint32_t current_time = 0;
volatile uint32_t pulseWidth = 0;
volatile uint32_t pulseTime[MAX_PULSES] = {0};
volatile uint32_t pulseCount = 0;
// 其他共享变量也需要添加volatile修饰
volatile uint8_t byteIndex = 0;
volatile uint8_t bitIndex = 0;
volatile uint8_t tag_alarm = 0;
volatile uint8_t tag_batt = 0;
volatile uint8_t tag_tlm = 0;
volatile uint8_t tagId[5] = {0};

void IRAM_ATTR gpio_intr_handler()
{
    uint32 gpio_status = GPIO_REG_READ(GPIO_STATUS_ADDRESS);
    if (gpio_status & BIT(WROOM_DATA_IN_GPIO))
    {
        // 先清除中断状态,再禁用中断,避免重复触发
        GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, gpio_status & BIT(WROOM_DATA_IN_GPIO));
        detachInterrupt(digitalPinToInterrupt(WROOM_DATA_IN_GPIO));
    
        current_time = micros();
        // 首次触发时初始化时间基准
        if (previous_time == 0) {
            previous_time = current_time;
            attachInterrupt(digitalPinToInterrupt(WROOM_DATA_IN_GPIO), gpio_intr_handler, CHANGE);
            return;
        }
        pulseWidth = current_time - previous_time;
        previous_time = current_time;
    
        if (pulseWidth > 100)
        {
            // 防止数组越界
            if (pulseCount < MAX_PULSES) {
                pulseTime[pulseCount] = pulseWidth;
                pulseCount++;
            }
        }
    
        if (pulseWidth > MAX_WIDTH || pulseCount >= MAX_PULSES)
        {
            pulseCount = 0;
            byteIndex = 0;
            bitIndex = 0;
            tag_alarm = 0;
            tag_batt = 0;
            tag_tlm = 0;
            for (uint32_t i = 0; i < 5; i++)
                tagId[i] = 0;
        }
        // 绑定回正确的GPIO引脚
        attachInterrupt(digitalPinToInterrupt(WROOM_DATA_IN_GPIO), gpio_intr_handler, CHANGE);
     }
}

void setup() {
    // 初始化串口(仅在主循环使用)
    Serial.begin(115200);
    // 初始化GPIO14为输入
    pinMode(WROOM_DATA_IN_GPIO, INPUT);
    // 提前初始化时间基准,避免首次中断异常
    previous_time = micros();
    // 绑定中断
    attachInterrupt(digitalPinToInterrupt(WROOM_DATA_IN_GPIO), gpio_intr_handler, CHANGE);
}

void loop() {
    // 如果需要调试,在主循环中打印ISR中的数据
    if (pulseCount > 0) {
        noInterrupts(); // 读取共享变量前关闭中断,防止数据不一致
        uint32_t temp_count = pulseCount;
        uint32_t temp_width = pulseWidth;
        interrupts();
        Serial.print("Pulse Count: ");
        Serial.println(temp_count);
        Serial.print("Pulse Width: ");
        Serial.println(temp_width);
    }
    // 其他主循环逻辑
}

关键注意事项

  • ISR严格轻量化:禁止在ISR中执行串口打印、浮点运算、延时等耗时操作,所有调试数据需放到全局缓冲区,在主循环中处理。
  • 共享变量加volatile:所有在ISR和主循环之间共享的变量必须用volatile修饰,防止编译器优化导致值被缓存,出现逻辑异常。
  • 中断操作顺序:先清除中断状态,再禁用中断,避免中断重复触发。
  • 变量初始化:所有用于时间计算、计数的变量必须在首次使用前明确初始化,不能依赖默认值。

内容的提问来源于stack exchange,提问作者A-Lone-Developer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:48:11