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

ESP32开发板PID代码integral值频繁出现NaN/INF问题求助

问题原因分析

1. 初始除以零引发的INF扩散

全局变量PeriodCount未显式初始化,默认值为0。程序启动后第一次执行freq = 40000000.00 / ((double) PeriodCount)时,会触发除以零操作,直接让freq变为INF(无穷大)。后续error = freq - setpoint同样为INF,乘以dt后的结果累加进integral,瞬间导致integral变成INF。偶尔重启后正常是因为恰好中断在第一次计算freq前触发,给PeriodCount赋了有效值。

2. PID函数无返回值导致未定义行为

pid()函数声明返回double类型,但函数体内没有return output;语句。这会导致output = pid(error)得到未定义的随机值,可能引发后续analogWrite参数异常,进一步干扰系统状态,甚至间接加剧积分异常。

3. 32位架构下64位变量读取的原子性问题

ESP32是32位处理器,PeriodCount是volatile uint64_t类型,读取时需要分两次操作(高32位和低32位)。若此时中断触发修改PeriodCount,主程序会读到不完整的错误值,进而计算出异常的freq(比如超大或超小值),最终让积分出现NaN/INF。

4. 积分无限制导致溢出

即使初始问题解决,若系统长期存在稳态误差,integral会持续累加直至超出double类型的范围,最终变为INF。缺少积分限幅机制会放大这个问题。


进一步排查与修复步骤
  • 修复初始除以零问题:在计算freq前检查PeriodCount是否有效,同时用临界区保证原子读取,避免中断干扰:

    uint64_t temp_period;
    noInterrupts();
    temp_period = PeriodCount;
    interrupts();
    
    if (temp_period > 0) {
      freq = 40000000.00 / (double)temp_period;
    } else {
      // 初始状态或异常时使用设定值,避免freq异常
      freq = setpoint;
    }
    
  • 补全PID函数的返回值:在pid()函数末尾添加返回语句,保证输出值合法:

    double pid(double error)
    {
      double proportional = error;
      double area = error * dt;
      integral += area;
      double derivative = (error - previous) / dt;
      previous = error;
      double output = (kp * proportional) + (ki * integral) + (kd * derivative);
      Serial.print("integral: ");
      Serial.println(integral, 6);
      return output; // 必须添加返回值
    }
    
  • 添加积分限幅机制:限制integral的范围,防止溢出:

    integral += area;
    // 根据输出范围计算合理限幅值,示例假设输出0-255
    const double integral_limit = 255.0 / ki;
    integral = constrain(integral, -integral_limit, integral_limit);
    
  • 确保dt的有效性:避免导数计算时除以零,过滤异常小的时间差:

    double now = millis();
    dt = (now - last_time) / 1000.0;
    last_time = now;
    
    if (dt <= 0.0001) { // 忽略异常小的时间差
      dt = 0.001;
    }
    
  • 添加关键变量调试输出:在loop中打印核心变量,观察异常触发时的状态:

    Serial.print("PeriodCount: ");
    Serial.print(temp_period);
    Serial.print(" | freq: ");
    Serial.print(freq);
    Serial.print(" | error: ");
    Serial.println(error);
    

内容的提问来源于stack exchange,提问作者Mitchell Conrad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 14:44:52