quadrature rotary encoder - number of counts not scaling up (interrupts)

Hi all. I'm currently using a 1024 pulse per revolution optical incremental rotary encoder --- omron brand.

I'm using only 1 encoder channel only...... eg. Channel A. So I'm not using quadrature mode at all.

What I'm noticing (at the moment) is that I'm getting a consistent number of counts (such as 130 counts over a 2 millisecond window) when the arduino attach-interrupt mode (associated with a single encoder channel) is set to 'FALLING'..... which triggers interrupts on each falling edge of the encoder waveform. But getting slightly higher than expected counts when I use the 'CHANGE' mode for the 'attach-interrupt'.

Eg..... my unaveraged raw number of counts is shown in the box below, which were 'serial printed' every 2 seconds, with corresponding converted approximate RPM value. The DC motor I'm driving is in open loop mode..... with PWM level set to max.... ie. PWM level 255.

counts= 131  3837.89
counts= 131  3837.89
counts= 131  3837.89
counts= 131  3837.89
counts= 130  3808.59
counts= 131  3837.89
counts= 132  3867.19
counts= 131  3837.89
counts= 132  3867.19
counts= 131  3837.89
counts= 131  3837.89
counts= 132  3867.19
counts= 132  3867.19
counts= 131  3837.89
counts= 132  3867.19
counts= 131  3837.89
counts= 131  3837.89
counts= 130  3808.59

But when I change the attach-interrupt mode to 'CHANGE' (to trigger on both the RISING and FALLING edge of the single encoder waveform), I assume that the number of counts should be approximately twice the number as before..... such as close to 260 counts (plus or minus a couple of counts).

However, the measured results that I'm getting right is around the 270 region.

The results below are the raw counts (still taken over 2 millisecond windows for each number of counts) .... serial printed every 2 seconds.

counts= 268  3925.78
counts= 272  3984.37
counts= 270  3955.08
counts= 270  3955.08
counts= 270  3955.08
counts= 272  3984.37
counts= 268  3925.78
counts= 266  3896.48
counts= 272  3984.37
counts= 272  3984.37
counts= 270  3955.08
counts= 270  3955.08
counts= 270  3955.08
counts= 271  3969.73
counts= 276  4042.97
counts= 270  3955.08
counts= 266  3896.48
counts= 266  3896.48
counts= 268  3925.78
counts= 270  3955.08
counts= 272  3984.37
counts= 270  3955.08

So, for the attach-interrupt mode set to "CHANGE" , I'm seeing counts of around 270, instead of an assumed (or expected) 260.

I'm seeing no issues with inconsistent or abrupt counting errors for raw counts. But thought I'd ask somebody here to see if I can understand what I'm measuring....such as the reason behind the number of counts not scaling up (doubling) from 130 counts to 260 counts when I alter the interrupt mode from 'FALLING' to 'CHANGE'. Instead....the measured number of counts in the 'CHANGE' mode is around 270.

While I already mentioned that I'm using Channel A only. I also get the same trend if I use Channel B only.

For Channel A, I'm using digital pin '3' for the 'attach-interrupt' on the MEGA 2560. Every 'number of counts' reading (done during 2 millisecond window) always starts at zero count. So my counting routine always waits for the next occurring waveform edge before commencing to measure a set of counts (over a 2 millisec duration).

The 'bandwidth' of this encoder (as the give in their data specifications is 100 kHz).

Thanks for any advice and/or recommendations in advance.

I'd wager the problem is in your code. :confused:

Yes, you need to post your code.

Either method counts transistions, but the FALLING one will only see every other transition.

My suspicion is you are trying to zero the count in the main code - this is usually done wrong.
You may have forgotten a volatile declaration?

Thanks for your comments MarkT and Fred.

I can certainly post my code, no problem at all. Here's the code that I'm using right now. Please me know if you can spot anything that I can fix up in there. In the meantime, I'm still looking through it myself.

I haven't grounded the shield wire of the encoder yet...... I forgot to mention that in my previous post. So I better do that, just in case. Although, at the moment, there doesn't appear to be noise issues, but I'm going to ground the encoder's shield wire at the arduino ground. Thanks again!

int pwmPin = 4;      // mega2560 pins 4 and 13 : 976.56Hz pwm frequency. Other pins : 490.20 Hz.

int digitalPinA = 3;   // pin 3

unsigned long T = 2000; //sampling period in microseconds

unsigned long ref_time = 0;

unsigned long observe_ref = 0;    //reference time for user observations
unsigned long observe_period = 2000000;  // in microseconds...2 seconds between user observation readings

int ppr = 1024; //number of pulses per revolution of rotary encoder PER CHANNEL
int cpr = 2048; //set this to 1024 if using attach-interrupt mode "FALLING". Set this value to 2048 if using attach-interrupt mode "CHANGE".

volatile int count = 0;

int count_ref = 0;

int counter = 0;

int count_capture = 0;

int oneshot = 0;

volatile byte startflag = 0;   //use 'byte' since it is 8 bit, and reading 'startflag' will be 'atomic'..... as in done with 1 instruction cycle.


void setup() {

  pinMode(pwmPin, OUTPUT);   // sets the pin as output


  pinMode(digitalPinA, INPUT_PULLUP);  //using internal pullup.


  Serial.begin(115200); //show this if serial data is to be displayed in the serial monitor window
  delay(20);

  count_ref = 0;

  attachInterrupt(1, State_A, CHANGE);  // interrupt number 1 translates to pin 3 on the MEGA2560

  ref_time = micros();   // initialise reference time
  analogWrite(pwmPin, 255);  // apply maximum PWM level
  delay(5000);  //wait 5 seconds

  observe_ref = micros();   //set a reference time for user observations
}

void loop() {

  if (oneshot == 0) {     //"oneshot" refers to the process of capturing a set of counts over a chosen sampling period (duration), eg. over 2 millisecond duration.
    oneshot = 1;

    clearstartflag();  //clears the start flag that allows sync function to work properly
    while ( sync() == 0 ) {}  //wait for sync with next available edge, and this edge depends on whether attach-interrupt mode has been set to 'FALLING' or 'CHANGE'.

    ref_time = micros();   //set a reference time for edge counting
  }

  if (oneshot == 1) {
    if ( micros() - ref_time >= T ) {

      noInterrupts();
      count_capture = count;
      count = 0;  //reset the counter to zero
      interrupts();

      oneshot = 0;  //marks the end of the count capturing cycle, so clear the "oneshot" flag
    }
  }
  if (micros() - observe_ref >= observe_period) {
    Serial.print("counts= "); Serial.print(count_capture); Serial.print("  "); Serial.println(((count_capture / (float) cpr) / (T * 1e-6)) * 60.0); //for testing, print the RPM
    observe_ref = micros();  //update the user observation time reference    
    oneshot = 0;  //resynchronise with next edge, and start a new set of counts (to avoid serial print causing time delays with the edge counting).
  }

} //loop


void State_A() //
{
  count++;
  if (startflag == 0) {
    startflag = 1;
  }
}


int sync() // synchronisation
{
  while (startflag == 0) {     //wait for pulse edge alignment
    //wait for start marker flag --- for pulse edge alignment
    //do nothing until we get a pulse edge .... for synchronisation.
  }

  noInterrupts();
  count = 0;
  count_ref = count;  //reset count reference.
  interrupts();

  return 1;
}


int clearstartflag() //clears the startflag for purposes of synchronisation with the next edge of a pulse
{
  noInterrupts();
  startflag = 0;  //clear startflag associated with ISR
  EIFR = 0x01;  //clear interrupt pending flag
  interrupts();
}

Here's the code that I'm using right now.

I can't confirm your issue with a 50KHz square wave generated with this code inserted into setup() of your code. and pin 9 jumpered to pin 3

Timer1.initialize(20); //50KHz
  pinMode(9,OUTPUT);//timer output on 9, jumper to 3
  Timer1.pwm(9, 512);//50% duty cycle, square wave

I see counts of 201 with CHANGE and counts of 100/101 with either RISING or FALLING.

Hi cattledog! Thanks for your help and time - for helping me to check things like that. That was a really good method of testing. I will certainly use that method and pass it on to others as well. Really appreciated that a lot.

This is clearly losing you interrupts:

int clearstartflag() //clears the startflag for purposes of synchronisation with the next edge of a pulse
{
  noInterrupts();
  startflag = 0;  //clear startflag associated with ISR
  EIFR = 0x01;  //clear interrupt pending flag
  interrupts();
}

Don't do that, every interrupt is required to be handled or clearly you'll get missed counts.

Your ISR is not doing quadrature decoding:

void State_A() //
{
  count++;
  if (startflag == 0) {
    startflag = 1;
  }
}

That's just pulse counting.

Anyway, all this startflag stuff is bogus, lose it and just sample the count in a critical section and you'll
not lose any counts.

long last_my_count = 0L ;
unsigned long last_time = 0L;

void loop()
{
  if (millis() - last_time > 1000)
  {
    last_time += 1000 ;

    noInterrupts () ;
    long my_count = count ;  // sample count, don't change it
    interrupts() ;

    int counts_since_last_time = my_count - last_my_count ;
    last_my_count = my_count ;

    Serial.println (counts_since_last_time) ;
  }
}

MarkT..... thanks very much for your comment about the clearing of the 'interrupt pending' flag....... to not do it. I'll try that out.

You're right Mark..... I was doing pulse counting, and I want to start a set of counts at the moment (or close enough to the moment) where the next occurring interrupt will fire. That would set the counter to 0. So the counter would then start incrementing from that initial time (ie. counter is zero at the initialisation edge, and then jumps to 1 at the next relevant edge).

My synchronisation routine involves making the counter begin at zero when the synchronisation condition is detected.

The clearing (or setting to zero) of the 'startflag' was to ensure that there's nothing pending (waiting in the wings) when counting begins. I have another version of this code for taking averaged RPM readings (eg. printed out every 2 seconds). So this 'startflag' thing was deliberately done.

Right now.... my 'sampling period' quantity "T" is really just a capture window duration. Eventually, I want to involve sampling (or close to it). That will be for getting 'sampled' RPM readings. That's where the synchronisation step won't be feasible. For sampling the RPM, I would just try to accurately capture a certain number of edges occurring within one sampling period - no sync'ing would be involved.

You're also absolutely right about the testing I was doing ...... not using quadrature mode to get four times the number of single-channel falling edge counts. I will definitely take a look at the quadrature mode though, as I really want to use that mode.

Thanks again Mark! I'll be following that recommendation of not doing that clearstartflag step. Appreciated.