ESP32 上的间歇性 NaN 和 INF 值

Intermittent NaN and INF Values on ESP32

提问人:Mitchell Conrad 提问时间:11/8/2023 更新时间:11/9/2023 访问量:32

问:

我正在为 ESP32 开发模块开发一些 PID 代码。大多数情况下,我执行代码时,值是 NaN 或 INF。但是,在拔下和重新插入 USB 连接时,积分值偶尔会按预期计算。我已经测试了多种不同类型的开发工具包,并观察到相同的行为,这让我相信这是一个软件错误。integral

此错误的原因可能是什么?您还建议采取哪些故障排除步骤?

//==============Variables================
// Frequency Counter
const byte        interruptPin = 22;              // Assign the interrupt pin
volatile uint64_t StartValue = 0;                 // First interrupt value
volatile uint64_t PeriodCount;                    // period in counts
float             freq;                           // frequency

hw_timer_t * timer = NULL;                        // pointer to a variable of type hw_timer_t

// PID
const int OUTPUT_PIN = 23;

double dt = 0.1;
double last_time = 0;
double integral = 0;
double previous = 0;
double output = 75;
double kp, ki, kd;
double setpoint = 35.00;                          // idle speed of ~35 Hz

//==============Functions================
// Interrupt
void IRAM_ATTR handleInterrupt()
{
  uint64_t TempVal = timerRead(timer);            // value of timer at interrupt
  PeriodCount = TempVal - StartValue;             // period count between rising edges
  StartValue = TempVal;                           // puts latest reading as start for next calculation
}

// PID Function
double pid(double error)
{
  double proportional = error;
  double area = error * dt;
  integral += area;                               // issue found: integral = inf or nan
  double derivative = (error - previous) / dt;
  previous = error;
  double output = (kp * proportional) + (ki * integral) + (kd * derivative);
  Serial.print("integral: ");
  Serial.println(integral, 6);
}
//=======================================

void setup(){
  Serial.begin(115200);
  pinMode(interruptPin, INPUT);                                       // sets pin as input
  
  attachInterrupt(interruptPin, handleInterrupt, FALLING);            // attaches pin to interrupt on Falling Edge
  timer = timerBegin(0, 2, true);                                     // configure timer 
  // 0 = first timer
  // 2 is prescaler so 80 MHZ divided by 2 = 40 MHZ signal
  // true - counts up
  timerStart(timer);                                                  // starts the timer

//=======================================
  // PID setup
  kp = 0.8;
  ki = 0.5;
  kd = 0;
  last_time = 0;
  analogWrite(OUTPUT_PIN, 23);
}
  
void loop(){
  freq = 40000000.00 / ((double) PeriodCount);                       // calculate frequency 

//=======================================
  // PID Loop
  double now = millis();
  dt = (now - last_time) / ((double) 1000);
  last_time = now;
  delay(1);
  
  double error = freq - setpoint;
  output = pid(error);                                              // output is the PWM value

  analogWrite(OUTPUT_PIN, output);                                  // set the output pin to the PWM value determined by the output variable
}
NAN ARDUINO-ESP32 INF

评论

0赞 rsaxvc 11/8/2023
PID 循环多久运行一次?它是否曾经在同一毫秒内运行两次?看起来不应该有 delay(1),但可能值得 delay(100) 看看,因为 dt=0 可能会导致 pid() 中的 INF。PeriodCount 也是如此 - 它是否永远为零(如果 IRQ 在每个计时器滴答中发生两次)?
1赞 romkey 11/8/2023
您确实需要声明为 .在中断处理程序内部和代码中可能被中断的任何变量都需要这样做,以便编译器知道它们的值可能会发生不可预测的更改,并避免某些优化,如果使用该变量的代码在使用过程中中断,这些优化将失败。另外,您这样做而不是仅仅使用已经工作的现有 PID 库是否有原因?PeriodCountvolatilevolatile

答:

1赞 Tarmo 11/9/2023 #1

我将指出一些容易发现的问题。

  1. 此语句不考虑为 0 的情况。这就是您获得 Inf :) 的方式PeriodCount
    freq = 40000000.00 / ((double) PeriodCount);
  2. 所有变量的长度均为 8 个字节(double 和 uint64_t)。您无法在 4 字节架构上的中断和任务循环之间安全地共享它们。这是因为中断可以在处理变量的第一个和第二个 4 字节半部分的主循环之间插入并更改值。因此,主循环的计算在抖动值上运行。这就是你获得 NaN 的方式。
    解决方案是使用互斥锁或其他线程同步机制来保护它们。
  3. 通过 romkey 重复注释 - main 和 interrupt 之间共享的变量必须是可变的,否则编译器将随便使用它们。