Trouble with encoder inaccuracy

Good day All,

I am having a huge amount of issues with the readings from my rotary optical encoder. The problem seems to be with the interrupt in my program (which is quite large). The encoder reads the revolution speed of a hydraulic motor and uses this for further speed calculations. The encoder I am using is a 1000PPR optical encoder with the hydraulic motor running at the maximum of approx. 500 rpm which means an interrupt occurs every 0.12ms.

If I run a small sketch with only the speed calculations and the interrupt, it works fine. However in the larger program the "diff" variable which is the difference between current encoder count and previous encoder count is CHAOTIC.

In the same sketch I have a while loop running (See funtion "Calibrate" near the bottom of the sketch) and all works fine. The problem just occurs inside my loop. (See from Sketch Section "Determine encoder Speed").

How can I possibly solve this problem?

Thanks for all the advice in advance!!

//Define Encoder wires
#define ENCA 2 //Green
#define ENCB 3 //White

#define WINDOW_SIZE 5

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Include Libraries
#include <SimpleKalmanFilter.h>
#include <PID_v1.h>
#include <SoftwareSerial.h>
#include <TinyGPS++.h>
#include <EasyNextionLibrary.h>
#include <trigger.h>
#include <EEPROM.h>
#include <ezButton.h>
#include <Encoder.h>


//Kalman Library
SimpleKalmanFilter Filter(2, 2, 0.01);
SimpleKalmanFilter Filterdiff(2, 2, 0.01);



//Define Encoder
int INDEX=0;
int SUM=0;
int READINGS[WINDOW_SIZE];
int AVERAGE=0;



//Define GPS Port
TinyGPSPlus gps;
SoftwareSerial mySerial(10, 11);



//Define Nextion Serial Port
EasyNex myNex(Serial);



//Define time variables
unsigned long prevT = 0;
float deltaT = 0.0;
unsigned long currT = 0;
unsigned long previousMillis = 0;
unsigned long advcurT;
unsigned long advprevT = 0;
unsigned long RPM_T;
unsigned long RPM_Ti = 0;
int RPM_deltaT;



//Define Encoder variables
int a;
float PPRev = 1000.0;
unsigned long diff = 0;
int diff_Filt;
float velocity2;
float v2;
float vFilt = 0.0;
float vFilt_i = 0.0;
float vPrev = 0;
unsigned long pos_i = 0;
unsigned long pos = 0;
unsigned long count_i = 0;
unsigned long count = 0;




//Define other variables
float GSpeed_current_old = 0.0;
float GSpeed_current_new = 0.0;
int GSpeed_display;
float GSpeed_set;
float GSpeed_save_cal = 0.0;
int Rate;
float Rate_display;
float Rate_save_cal = 0.0;
int Rate_set;
int MSpeed_set;
float MSpeed_conv;
float MSpeed_save_conv;
float MSpeed_current;
float MSpeed_percentage;
float MSpeed_cal;
int Satellites;
float Grad;
int fan_col = 0;
int i = 0;
bool run0 = 0;
bool run1 = 0;
bool run2 = 0;
bool fan = 0;
int LS = 1;
int Motor = 3;
int Diff_save;
int RPM_max;
int RPM_set;
int Diff_set;
float aggKp_set;
float Kp_set;
float aggKi_set;
float Ki_set;
float aggKd_set;
float Kd_set;

float Input_i = 0.0;
long prevT_i;
long currT_i;
float velocity_i = 0.0;
static boolean rotate= false;


//Define EEPROM variables
float Rate_save;
float MSpeed_save;
float GSpeed_save;
float Rpmmax_save;
float Diffpid_save;
float aggKp_save;
float aggKi_save;
float aggKd_save;
float Kp_save;
float Ki_save;
float Kd_save;



//PID variables
double Kp, Ki, Kd, aggKp, aggKi, aggKd;
double Input, Output, Setpoint;
PID encoder(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);



void setup() {

  //Start communication & Define pins
  Serial.begin(9600);
  mySerial.begin(9600);
  //  pinMode (7, OUTPUT);
  //  digitalWrite(7, LOW);
  pinMode(ENCA, INPUT_PULLUP);
  pinMode(Motor, OUTPUT);
  encoder.SetMode(AUTOMATIC);
  encoder.SetSampleTime(300);
  attachInterrupt(digitalPinToInterrupt(ENCA), readEncoderA, RISING);


  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //Retrieve EEPROM values
  GSpeed_save = EEPROM.read(0);
  GSpeed_save_cal = GSpeed_save / 10.0;
  Serial.println("GSpeed_save_cal");
  Serial.println(GSpeed_save_cal);
  Rate_save = EEPROM.read(10);
  Rate_save_cal = Rate_save * 1.0;
  Serial.println("Rate_save_cal");
  Serial.println(Rate_save_cal);

  MSpeed_save = EEPROM.read(15);
  Serial.println("Mspeed_save_cal");
  Serial.println(MSpeed_save);
  MSpeed_save_conv = MSpeed_save / 100.0;
  Serial.println("MSpeed_save_conv");
  Serial.println(MSpeed_save_conv);

  Rpmmax_save = EEPROM.read(20);
  RPM_max = Rpmmax_save;

  aggKp_save = EEPROM.get(25, aggKp_set);
  aggKp = aggKp_save / 100.0;
  aggKi_save = EEPROM.get(30, aggKi_set);
  aggKi = aggKi_save / 1000.0;
  aggKd_save = EEPROM.read(40);
  aggKd = aggKd_save / 100.0;
  Kp_save = EEPROM.get(50, Kp_set);
  Kp = Kp_save / 100.0;
  Ki_save = EEPROM.get(60, Ki_set);
  Ki = Ki_save / 1000.0;
  Kd_save = EEPROM.read(70);
  Kd = Kd_save / 100.0;
  Diffpid_save = EEPROM.read(80);
  Diff_save = Diffpid_save;

  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //MotorSpeed conversion
  MSpeed_conv = MSpeed_save_conv * 255.0;
  //Serial.println(MSpeed_conv);
  Grad = MSpeed_conv / 800.0;


  Serial.println(Rate_save);
  Serial.println(MSpeed_save);
  Serial.println(Rpmmax_save);
  Serial.println(aggKp);
  Serial.println(aggKi, 3);
  Serial.println(aggKd);
  Serial.println(Kp);
  Serial.println(Ki, 3);
  Serial.println(Kd);
  Serial.println(Diff_save);

}

void loop() {
  //Listen for HMI commands


  myNex.NextionListen();


  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  // Determine GPS Values
  //  while (mySerial.available()) {
  //    gps.encode(mySerial.read());
  //
  //  }
  //
  //  myNex.NextionListen();
  //
  //
  //
  //  if (gps.location.isUpdated())
  //  {
  //    //    Serial.println("Satelite Count:");
  //    Satellites = gps.satellites.value();
  //        Serial.println(Satellites);
  //        Serial.println();
  //
  //    myNex.NextionListen();
  //
  //    //    //    Serial.println("GSpeed km/h");
  //    //    GSpeed_current_old = gps.speed.kmph();
  //    //    GSpeed_current_old *= 1;
  //    //        Serial.println("GPS SPEED:");
  //    //        Serial.println(GSpeed_display);
  //
  //    //    Serial.println("GSpeed km/h");
  //    GSpeed_current_old = gps.speed.kmph();
  //    GSpeed_current_old *= 1;
  //    GSpeed_display = GSpeed_current_old * 10;
  //    //        Serial.println(GSpeed_display);
  //  }


  myNex.NextionListen();

  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //Determine Encoder Speed

    currT = millis();
    deltaT = currT - prevT;
  
    myNex.NextionListen();
  
  
  
    if (deltaT >= 10) {
      diff=0;
      deltaT /= 1.0e03 ;

      SUM=SUM-READINGS[INDEX];
        int diff=count-count_i;
        diff*=5;

        READINGS[INDEX]=diff;
        SUM+=diff;
        INDEX=(INDEX+1)%WINDOW_SIZE;
        AVERAGE=SUM/WINDOW_SIZE;
        
      diff_Filt=Filterdiff.updateEstimate(AVERAGE);
      diff_Filt=Filterdiff.updateEstimate(diff_Filt);
  velocity2 = ((float)count - count_i) / deltaT;
  count_i = count;
  v2 = (velocity2 / PPRev) * 60.0;
  vFilt = Filter.updateEstimate(v2);

  myNex.NextionListen();

//        Serial.println();
//        Serial.print("Count");
//        Serial.print(" ");
//        Serial.print("Count_i");
//        Serial.print(" ");
//        Serial.println("diff");
//        Serial.print(count);
//        Serial.print(" ");
//        Serial.print(count_i);
//        Serial.print(" ");
    Serial.println(diff);


  Input = (vFilt / RPM_max) * 255;
  prevT = currT;
  //pos_i = pos;

   }



  //
  //    if (vFilt < RPM_max && vFilt > 0) {
  //      vFilt_i = vFilt;
  //    }
  //
  //    if (vFilt > RPM_max || vFilt < 0) {
  //      vFilt = vFilt_i;
  //    }



  //    Serial.println();
  //    Serial.println("deltaT");
  //    Serial.println(pos);

  myNex.NextionListen();


  Input = (vFilt / RPM_max) * 255;

  //  Serial.println(v2);




  //
  //    if (Input < 255 && Input > 0) {
  //      Input_i = Input;
  //    }
  //
  //    if (Input > 255 || Input < 0) {
  //      Input = Input_i;
  //    }


  mySerial.begin(9600);


  myNex.NextionListen();


  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //Start Run loop
  if ( run0 == 1 && LS == 1 ) {
    GSpeed_current_new = GSpeed_current_old;

    myNex.NextionListen();

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //Set Motor Speed
    MSpeed_current = (GSpeed_current_new / GSpeed_save_cal) * MSpeed_conv;
    //      Serial.println(GSpeed_current_new);
    //      Serial.println(MSpeed_current);
    MSpeed_percentage = (MSpeed_current / 255.0) * 100.0;

    myNex.NextionListen();

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //Determine PID Parameters
    Setpoint = MSpeed_current;
    int gap = abs(Setpoint - Input);

    myNex.NextionListen();


    if (gap < Diffpid_save) {
      encoder.SetTunings(Kp, Ki, Kd);
    } else
    {
      encoder.SetTunings(aggKp, aggKi, aggKd);
    }

    myNex.NextionListen();

    //Write PWM to motor
    encoder.Compute();
    analogWrite(Motor, Output);

    myNex.NextionListen();

    //Determine GPS Display Speed
    GSpeed_display = GSpeed_current_old * 10;

    //Determine Rate
    Rate_display = (Input / MSpeed_current) * Rate_save_cal;

    myNex.NextionListen();

    //            Serial.print("Setpoint");
    //            Serial.print(" ");
    //            Serial.print("Input");
    //            Serial.print(" ");
    //            Serial.println("Output");
    //            Serial.print(Setpoint);
    //            Serial.print(" ");
    //            Serial.print(Input);
    //            Serial.print(" ");
    //            Serial.println(Output);

  } else {
    analogWrite(Motor, 0);
    myNex.NextionListen();
  }



  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //Delay 500ms >> Write to HMI
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis > 500) {
    previousMillis = currentMillis;

    myNex.NextionListen();


    //    if (MSpeed_percentage < 100)
    //    {
    //      myNex.writeNum("Speed.val", MSpeed_percentage);
    //    } else {
    //      myNex.writeNum("Speed.val", 100);
    //    }

    myNex.NextionListen();

    //Write to HMI
    myNex.writeNum("Sats.val", Satellites);
    myNex.writeNum("RGSpeed.val", GSpeed_display);
    myNex.writeNum("RRate.val", Rate_display);
    myNex.writeNum("Speed.val", MSpeed_percentage);
    myNex.writeNum("VSrpm.val", Setpoint);
    myNex.writeNum("VArpm.val", Input);

  }
  //delay(1);
  myNex.NextionListen();
}


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Read Encoder interrupt
void readEncoderA() {

   count++;
  long currT_i = micros();
  deltaT = ((float)(currT_i - prevT_i)) / 1.0e06;
  float velocity_i = (count-count_i) / deltaT;
  prevT_i = currT_i;
  //pos_i = pos;
  count_i=count;

Serial.println(velocity_i);


}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Enable Stop Funtion
void trigger1() {
  run0 = 0;
  MSpeed_current = 0;
  analogWrite(Motor, MSpeed_current);
  myNex.writeNum("Speed.val", 0);
  myNex.writeNum("Speed.bco", 65535);
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Enable Start Function
void trigger2() {
  i = 0;
  run0 = 1;
  myNex.writeNum("Speed.bco", 1024);
  unsigned long startMillis = millis();
  unsigned long stopMillis = millis();
  myNex.writeNum("Speed.val", MSpeed_save);
  //  while (stopMillis - startMillis < 5000 && MSpeed_current <= MSpeed_save && run0 == 1) {
  //
  //    myNex.NextionListen();
  //
  //
  //    stopMillis = millis();
  //    i++;
  //    MSpeed_current = Grad * i;
  //    analogWrite(Motor, MSpeed_current);
  //
  //    MSpeed_percentage = (MSpeed_current / MSpeed_conv) * 100.0;
  //    myNex.writeNum("Speed.val", MSpeed_percentage);
  //  }
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Save Button
void trigger3() {

  //Read & Save new values
  Rate_set = myNex.readNumber("Rate.val");
  MSpeed_set = myNex.readNumber("MSpeed.val");
  //  GSpeed_set = myNex.readNumber("SGSpeed.val");

  if (Rate_save != Rate_set) {
    EEPROM.write(10, Rate_set);
  }

  if (MSpeed_save != MSpeed_set) {
    EEPROM.write(15, MSpeed_set);
  }

  if (EEPROM.read(10) == Rate_set) {
    myNex.writeStr("b0.txt", "SAVED");
  }


  delay(1000);
  myNex.writeStr("b0.txt", "SAVE");

  setup();
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Cailbrate Confirm
void trigger11() {


  GSpeed_set = myNex.readNumber("CGSpeed.val");
  GSpeed_set = myNex.readNumber("CGSpeed.val");
  Rate_set = myNex.readNumber("CRate.val");
  MSpeed_set = myNex.readNumber("CMSpeed.val");

  if (GSpeed_save != GSpeed_set) {
    EEPROM.write(0, GSpeed_set);
  }

  if (Rate_save != Rate_set) {
    EEPROM.write(10, Rate_set);
  }

  if (MSpeed_save != MSpeed_set) {
    EEPROM.write(15, MSpeed_set);
  }

  Serial.println("GSpeed_set");
  Serial.println(GSpeed_set);
  Serial.println("Rate_set");
  Serial.println(Rate_set);
  Serial.println("MSpeed_set");
  Serial.println(MSpeed_set);

  delay(500);
  setup();
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Calibrate GO
void trigger6() {
  run1 = 1;

  MSpeed_cal = myNex.readNumber("SMS.val");
  MSpeed_cal = myNex.readNumber("SMS.val");

  Calibrate();
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Calibrate Stop
void trigger7() {

  run1 = 0;
  Calibrate();
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Blower Fan
void trigger8() {

  if (fan == 0) {
    fan = 1;
    myNex.writeNum("t7.bco", 1024);
    myNex.writeNum("t7.bco", 1024);
    myNex.writeNum("t8.bco", 50674);
  } else if (fan == 1) {
    fan = 0;
    myNex.writeNum("t7.bco", 50674);
    myNex.writeNum("t7.bco", 50674);
    myNex.writeNum("t8.bco", 63488);
  }

  if (fan == 1) {
    digitalWrite(7, HIGH);
    //    Serial.println("FAN ON");
  }
  else {
    digitalWrite(7, LOW);
    //    Serial.println("FAN OFF");
  }
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Adv Cal Stop
void trigger10() {
  run2 = 0;

  analogWrite(Motor, 0);
  analogWrite(Motor, 0);

  AdvCalibrate();
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Adv Cal Run
void trigger9() {

  run2 = 1;
  MSpeed_cal = 255.0;
  AdvCalibrate();

}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Adv Cal Save
void trigger4() {

  //Read Maximum RPM
  RPM_set = myNex.readNumber("rpmlock.val");
  RPM_set = myNex.readNumber("rpmlock.val");
  run2 = 0;
  analogWrite(Motor, 0);


  if (RPM_set != RPM_max) {
    EEPROM.write(20, RPM_set);

  }

  if (EEPROM.read(20) == RPM_set) {
    myNex.writeStr("rpmsave.txt", "SAVED");
    delay(1000);
  }

  myNex.writeStr("rpmsave.txt", "SAVE");

  setup();
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Adv Cal1 Save
void trigger5() {
  Diff_set = myNex.readNumber("diff.val");
  aggKp_set = myNex.readNumber("aggKp.val");
  Kp_set = myNex.readNumber("Kp.val");
  aggKi_set = myNex.readNumber("aggKi.val");
  Ki_set = myNex.readNumber("Ki.val");
  aggKd_set = myNex.readNumber("aggKd.val");
  Kd_set = myNex.readNumber("Kd.val");

  //  aggKi_set /= 1000;
  Serial.println();
  Serial.println(aggKp_set);
  Serial.println(Kp_set);



  if (Diff_set != Diff_save) {
    EEPROM.write(80, Diff_set);
  }

  if (aggKp_set != aggKp_save) {
    EEPROM.put(25, aggKp_set);
  }

  if (Kp_set != Kp_save) {
    EEPROM.put(50, Kp_set);
  }

  if (aggKi_set != aggKi_save) {
    EEPROM.put(30, aggKi_set);
  }

  if (Ki_set != Ki_save) {
    EEPROM.put(60, Ki_set);
  }

  if (aggKd_set != aggKd_save) {
    EEPROM.write(40, aggKd_set);
  }

  if (Kd_set != Kd_save) {
    EEPROM.write(70, Kd_set);
  }

  if (EEPROM.get(80, Diff_set) == Diff_set) {
    myNex.writeStr("pidsave.txt", "SAVED");
    delay(1000);
  }

  myNex.writeStr("pidsave.txt", "SAVE");

  setup();
}



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Run Calibration Function
void Calibrate() {

  MSpeed_cal = (MSpeed_cal / 100.0) * 255.0;

  while (run1 == 1) {

    mySerial.end();

    myNex.NextionListen();


    currT = millis();
    deltaT = currT - prevT;

    myNex.NextionListen();


    if (deltaT >= 10) {
      deltaT /= 1.0e03 ;
      diff = pos - pos_i;
      velocity2 = diff / deltaT;
      v2 = (velocity2 / PPRev) * 60.0;
      vFilt = Filter.updateEstimate(v2);

      myNex.NextionListen();

      Serial.println(vFilt);


      Input = (vFilt / RPM_max) * 255;
      prevT = currT;
      pos_i = pos;

    }

    myNex.NextionListen();


    Setpoint = MSpeed_cal;
    int gap = abs(Setpoint - Input);

    myNex.NextionListen();


    if (gap < Diffpid_save) {
      encoder.SetTunings(Kp, Ki, Kd);
    } else
    {
      encoder.SetTunings(aggKp, aggKi, aggKd);
    }

    myNex.NextionListen();


    encoder.Compute();
    analogWrite(Motor, Output);


    myNex.NextionListen();
    //
    //    Serial.print("Setpoint");
    //    Serial.print(" ");
    //    Serial.print("Input");
    //    Serial.print(" ");
    //    Serial.println("Output");
    //    Serial.print(Setpoint);
    //    Serial.print(" ");
    //    Serial.print(Input);
    //    Serial.print(" ");
    //    Serial.println(Output);

    //    myNex.writeNum("VSrpm.val", Setpoint);
    //    myNex.writeNum("VArpm.val", Input);


    if (run1 == 0) {
      analogWrite(Motor, 0);
      mySerial.begin(9600);
      break;

    }
  }
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Run Advanced Calibration Function
void AdvCalibrate() {



  while (run2 == 1) {

    mySerial.end();


    myNex.NextionListen();


    currT = millis();
    deltaT = currT - prevT;

    myNex.NextionListen();


    if (deltaT >= 200) {
      deltaT /= 1.0e03 ;
      diff = pos - pos_i;
      velocity2 = diff / deltaT;
      v2 = (velocity2 / PPRev) * 60.0;
      vFilt = Filter.updateEstimate(v2);

      myNex.NextionListen();


      Input = (vFilt / RPM_max) * 255;
      prevT = currT;
      pos_i = pos;

      //    Serial.print("v2");
      //    Serial.print(" ");
      //    Serial.println("V Filt");
      //    Serial.print(v2);
      //    Serial.print(" ");
      //    Serial.println(vFilt);


    }
    //myNex.writeNum("rpmtest.val", vFilt);

    myNex.NextionListen();


    analogWrite(Motor, 255);

    myNex.NextionListen();


    advcurT = millis();
    if (advcurT - advprevT >= 500) {
      myNex.writeNum("rpmtest.val", vFilt);
      advprevT = advcurT;
    }

    myNex.NextionListen();



    if (run2 == 0) {

      analogWrite(Motor, 0);
      mySerial.begin(9600);

      break;

    }
  }
}

Separate the encoder step detection (interrupt) from all further processing. Give lengthy calculations their time to complete and don't try to catch up with every single encoder pulse.

Any advice on doing this?

I don't know anything about your encoder library. Eventually it's missing to disable interrupts while reading the current encoder count?

Pardon the library. I should’ve taken it out because I am not using it at the moment. It worked horribly. Only using attachInterrupt on encoder pin A.

In readEncoderA() only increment the count, and don't print anything. Flag count as volatile and read it only with interrupts disabled (atomically). It's sufficient to take the time when count is read, not required when it's written in the ISR.

If you Use this encoder library, it’s quite efficient - use interrupt capable pins

Thanks for the advice!! Do you perhaps have a practical example of how I can do this?

Do what?
I'll take a rest now.

Read when the interrupt is disabled as well as take the time when a count is taken?

you don't need to mess with the interrupts if you use the library.

here is a simple code that prints the instant RPM over the last sampling period

you define the min amount of time for the sampling period in

const uint32_t minRrpmCheckPeriod_us = 100000ul; // min period in µs to wait before checking the RPM (-> 10Hz max)

and you define how many pulses your rotary encoder gives for 1 revolution here

constexpr uint32_t pulsesPerRevolution = 60; // 30 ticks of 2 pulses for a KY40, adjust to the PPR of your rotary encoder

I used 60 because that's what my KY40 was giving. You probably want 1000 there for your encoder.

#include <Encoder.h>  // https://www.pjrc.com/teensy/td_libs_Encoder.html

const byte encoderCLKPin = 2;    // encoder encoder CLK
const byte encoderDTPin  = 3;    // encoder encoder DT
Encoder encoder(encoderDTPin, encoderCLKPin);

const uint32_t minRrpmCheckPeriod_us = 100000ul; // min period in µs to wait before checking the RPM (-> 10Hz max)

constexpr uint32_t pulsesPerRevolution = 60; // 30 ticks of 2 pulses for a KY40, adjust to the PPR of your rotary encoder
constexpr int32_t rpmFactor = 60ul * 1000000ul / pulsesPerRevolution; // 60s in a minute, 1000000µs in 1 s

int32_t currentRPM = 0;

int32_t getRpm() {
  static int32_t oldPulseCount = 0;
  static uint32_t lastSamplingTime_us = 0;
  uint32_t now = micros();
  int32_t newPulseCount = encoder.read();
  int32_t deltaT = micros() - lastSamplingTime_us;                          // ∆t is in µs
  int32_t rpm = ((newPulseCount - oldPulseCount) * rpmFactor) / deltaT;     // RMP = revolutions per minute
  oldPulseCount = newPulseCount;
  lastSamplingTime_us = now;
  return rpm;
}

inline void checkRPM() {
  static uint32_t lastRPMCheck = 0;
  if (micros() - lastRPMCheck >= minRrpmCheckPeriod_us) {
    int32_t newRpm = getRpm() ;
    if (currentRPM !=  newRpm) {
      currentRPM = newRpm;
      Serial.print(F("RPM: ")); Serial.println(currentRPM);
    }
    lastRPMCheck = micros();
  }
}

void setup() {
  Serial.begin(115200); Serial.println();
  Serial.println(F("Ready"));
}

void loop() {
  checkRPM();
}

that code should print to the RPM to the Serial monitor (and maintain in the currentRPM variable the instant RMP value over the last sampling period (if the rotary encoder did not change position over the last sampling period it will tell you 0))

Like this:

noInterrupts();
count_i = count; //atomic copy
interrupts();
currT_i = millis(); //better micros(); ?

The filters use the number of counts occurred between the last and current reading. Bogus may happen if the elapsed time is zero, when e.g. less than 1 ms.

I'd go one step further and increment the count and set a flag in the ISR that it changed. Then in the main code, you check that flag and only do any of the count-related stuff if it was set.

WARNING: SoftwareSerial will keep interrupts disabled for the duration of each received (or sent) character. At 9600 baud that's over 900 microseconds. No wonder your interrupts at 120-microsecond intervals are getting dropped.

Can you switch to a MEGA or Leonardo/Micro so you can use a hardware serial port for the GPS?

The chipset is used on a PCB so currently thats not possible.

Is this code implemented in the main code or in the interrupt itself?

Thanks a lot Jackson. I will try and implement this in my code. Previously I only used encoder.read() in my main loop. This really didnt work well for me.

Will give feedback on your code implementation!

Something like a static boolean?

Hi,
Can you please post link to specs/data of the encoder?
Can you please post a copy of your circuit, in CAD or a picture of a hand drawn circuit in jpg, png?
Hand drawn and photographed is perfectly acceptable.
Please include ALL hardware, component names and pin labels.

How are you powering the encoder?

Thanks.. Tom.. :smiley: :+1: :coffee: :australia:

If it is not possible to get rid of SoftwareSerial you might get good results with an external quadrature counter chip.