nanesp32arduino-esp32inf

Intermittent NaN and INF Values on ESP32


I am developing some PID code for an ESP32 dev module. Most times that I execute the code, the integral value is NaN or INF. However, upon unplugging and replugging in the USB connection, occasionally the integral value will calculate as expected. I have tested across multiple different types of dev kits and observed the same behavior, which leads me to believe this is a software bug.

What could be the cause of this error? What more troubleshooting steps would you recommend?

//==============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
}

Solution

  • I'll point out a few easily spotted issues.

    1. This statement does not account for cases where PeriodCount is 0. That's how you get an Inf :)
      freq = 40000000.00 / ((double) PeriodCount);
    2. All your variables are 8 bytes in length (double and uint64_t). You cannot safely share those between an interrupt and task loop on a 4 byte architecture. That's because the interrupt can drop in and change value between the main loop handling the first and second 4-byte half of the variable. As a result the main loop's computation runs on a thrashed value. That's how you get a NaN.
      The solution is to protect them with a mutex or other thread synchronization mechanism.
    3. Repeating the comment by romkey - variables shared between main and interrupt must be volatile, or the compiler will take its liberty with them.