miscounting of encoder pulses

Hi Everyone,

I'm using an Arduino Mega 2560 to build a controller for a microscope stage, by basically controlling 2 DC motors (one for each axis of motion).

The motors are equipped with optical encoders that provide 1000 pulses per revolution, which gives 1000 pulses per mm of motion.

As this is a microscope, accuracy is very important to me (say, for moving automatically between different setpoints).

For the counting I'm using interrupts. Here's the relevant code parts:

const byte ENCODER_X_A = 21; //Sets timer/counter pins 21 (Timer counter 2) and 20 (Timer counter 3) as encoder A inputs for both axes
const byte ENCODER_Y_A = 20;

volatile long int x_position = 0; //Global, relative coordinates
volatile long int y_position = 0;

byte right = 0, up = 0;

...

void setup() 
{ 

...

 pinMode(ENCODER_X_A,INPUT);  //Encoder inputs
  digitalWrite(ENCODER_X_A,HIGH);  
  pinMode(ENCODER_Y_A,INPUT);
  digitalWrite(ENCODER_Y_A,HIGH);  
  
  attachInterrupt(2, doEncoderX, RISING); //Define interrupts on counters 2,3 for x,y axes respectively upon rise in waveform
  attachInterrupt(3, doEncoderY, RISING);

}

/****************************************************************************************************************************************************************************************
* Encoder interrupt service routines
*****************************************************************************************************************************************************************************************/

void doEncoderX(){
  x_position += (right) ? +1 : -1; // and adjust counter + if going right and - if opposite
}

void doEncoderY(){
  y_position += (up) ? +1 : -1; // and adjust counter + if going up and -  if opposite
}

I had a suspicion that I might be missing counts, and therefore I inputed an external pulse train at length 10,000, 1,000 and 100 pulses from a function generator. I got these strange results:

100Khz: missing 12 out of 10,000 pulses, 0 out of 1,000 pulses and 0 out of 100 pulses.
50Khz: missing 1 out of 10,000 pulses, 0 out of 1,000 pulses and 0 out of 100 pulses.
10Khz: missing 20 out of 10,000 pulses, 2 out of 1,000 pulses and 0 out of 100 pulses.
1Khz: missing 15 out of 10,000 pulses, 1 out of 1,000 pulses and 0 out of 100 pulses.
0.5Khz: missing 17 out of 10,000 pulses, 1 out of 1,000 pulses and 1 out of 100 pulses.
0.2Khz: missing 12 out of 10,000 pulses, 4 out of 1,000 pulses and 0 out of 100 pulses.

This, as you can imagine, causes my "go back to the setpoint" function to go nearly back to setpoint.. Bummer.. :astonished:

Any ideas as to what the problem might be?

Thanks a lot!

Gadi.

please post the whole code as I suspect some race condition in the code.

It is probbly inputting interrupts too fast for the arduino to cope with. This is because the arduino runs under interrupts for things like the millis timer.
What if you try slowing down your motor, does it get better?

Not knowing what else is happening in your code... Are you using any of the internal functions that use an interrupt? Have you verified this with a dial indicator or other measuring device?

At what frequency are your interrupts happening?

The results from the pulse generator are inconsistent at best - are you sure the signals are at the proper voltage? Some capacitance sneaking in there?

Hi and thanks for the quick replies,

robtillaart:
please post the whole code as I suspect some race condition in the code.

Here. I hope it's not too much.. If you have any questions about the code please ask.

The entire code is too long so I skipped the unimportant parts.

...

const byte ENCODER_X_A = 21; //Sets timer/counter pins 21 (Timer counter 2) and 20 (Timer counter 3) as encoder A inputs for both axes
const byte ENCODER_Y_A = 20;

...

volatile long int x_position = 0; //Global, relative coordinates
volatile long int y_position = 0;

byte right = 0, up = 0;

long int setpoint[2] = {0,0};

...

int x = 0;
int y = 0;


/****************************************************************************************************************************************************************************************
* Pulsed motor control function
*****************************************************************************************************************************************************************************************/

void pulsedMotor(int delay_time, byte x_or_y, byte upright_or_downleft)
{
  if (x_or_y) 
  {
    Motor2(90, !upright_or_downleft);
    delay(delay_time);
    Motor2(0, !upright_or_downleft);
    delay(100);
  }
  if (!x_or_y) 
  {
    Motor1(90, upright_or_downleft);
    delay(delay_time);
    Motor1(0, upright_or_downleft);
    delay(100);
  }
}

/****************************************************************************************************************************************************************************************
* Gotoxy function
*****************************************************************************************************************************************************************************************/

void gotoxy(long int x_set, long int y_set)
{
  int x_count = 0, y_count = 0;
  byte in_position = ((x_position == x_set) && (y_position == y_set));// If already in position return
  if (in_position) return;
  
  Serial.print("ORIGIN: ");// Print point of origin onto serial monitor
  displayCoordinates(x_position, y_position, x_set, y_set);
   
  coarseGotoxy(x_set, y_set);// Go to the area arround the requested point
  
  Serial.print("COARSE DESTINATION: ");
  displayCoordinates(x_position, y_position, x_set, y_set);
  
  x_count = fineGotoxy(x_set, 1);// Go to exact location
  y_count = fineGotoxy(y_set, 0);
  
  ...
}

/****************************************************************************************************************************************************************************************
* Fine Gotoxy function
*****************************************************************************************************************************************************************************************/

int fineGotoxy(long int set, byte x_or_y)
{
  int delta = 0, count = 0;
  
  if (!x_or_y) // If we're fine tuning Y
  {
    while (y_position != set) 
    {
      delta = set - y_position;
      up = delta >= 0;
      
      if (abs(delta) >= 100) 
      {
        pulsedMotor(20, 0, delta > 0);
        count++;
        continue;
      } 
      if ((abs(delta) >= 20) && (abs(delta) < 100)) 
      {
        pulsedMotor(10, 0, delta > 0);
        count++;
        continue;
      }
      if ((abs(delta) >= 5) && (abs(delta) < 20)) 
      {
        pulsedMotor(5, 0, delta > 0);
        count++;
        continue;
      }
      if (abs(delta) < 5) pulsedMotor(1, 0, delta > 0);
      count++;
    }
    return count;
  }
  
  if (x_or_y) // If we're fine tuning X
  {
    while (x_position != set) 
  {
    delta = set - x_position;
    right = delta >= 0;
    
    if (abs(delta) >= 100) 
    {
      pulsedMotor(25, 1, delta < 0);
      count++;
      continue;
    }
    if ((abs(delta) >= 20) && (abs(delta) < 100)) 
    {
      pulsedMotor(10, 1, delta < 0);
      count++;
      continue;
    }
    if ((abs(delta) >= 5) && (abs(delta) < 20)) 
    {
      pulsedMotor(5, 1, delta < 0);
      count++;
      continue;
    }
    if (abs(delta) < 5) pulsedMotor(1, 1, delta < 0);
    count++;
  }
  return count;
  }
}

/****************************************************************************************************************************************************************************************
* Coarse Gotoxy function
*****************************************************************************************************************************************************************************************/

void coarseGotoxy(long int x_set, long int y_set)
{
  right = x_position <= x_set; //Set direction
  up = y_position <= y_set;
  
  while ((up && (y_position < y_set)) || (!up && (y_position > y_set))) // Coarse positioning
    {
      Motor1(90, up); 
    }
    Motor1(0, up);
    while ((right && (x_position < x_set)) || (!right && (x_position > x_set))) 
    {
      Motor2(90, !right); 
    }
  Motor2(0, up);
  delay(100);
}

/****************************************************************************************************************************************************************************************
* Initial setup
*****************************************************************************************************************************************************************************************/

void setup() 
{ 
 
 ...
  
  pinMode(ENCODER_X_A,INPUT);  //Encoder inputs
  digitalWrite(ENCODER_X_A,HIGH);  
  pinMode(ENCODER_Y_A,INPUT);
  digitalWrite(ENCODER_Y_A,HIGH);  
  
  attachInterrupt(2, doEncoderX, RISING); //Define interrupts on counters 2,35 for x,y axes respectively upon rise in waveform
  attachInterrupt(3, doEncoderY, RISING);
} 

/****************************************************************************************************************************************************************************************
* Main Program
*****************************************************************************************************************************************************************************************/

void loop()
{
  
  if (!digitalRead(PIN_BUTTON_UP)) //If UPPER button is pressed - SET COORDINATES TO ZERO and display
  {
    x_position = 0;
    y_position = 0;
    displayCoordinates(x_position, y_position, setpoint[0], setpoint[1]);
  }
  
  if (!digitalRead(PIN_BUTTON_LEFT)) //If LEFT button is pressed - SAVE AS SETPOINT and display 
  {
    setpoint[0] = x_position;
    setpoint[1] = y_position;
    displayCoordinates(x_position, y_position, setpoint[0], setpoint[1]);
  }

  if (!digitalRead(PIN_BUTTON_RIGHT)) //If RIGHT button is pressed - GOTO SETPOINT  
  {
    gotoxy(setpoint[0], setpoint[1]);
  }
  
  x = analogRead(PIN_ANALOG_X)/2-256; //Transform analog input of 0:1023 to range -255:255
  if (x == -256) x = -255;
  y = analogRead(PIN_ANALOG_Y)/2-256;
  if (y == -256) y = -255;
  
  if (abs(x) < 10 ) x = 0;  //Threshholding
  if (abs(y) < 10) y = 0;
  
  right = x >= 0;
  up = y >= 0;
   
  limit_x = CheckLimitSwitches(1, x < 0); //Check if limit switches are activated
  limit_y = CheckLimitSwitches(0, y > 0);
   
  if (digitalRead(PIN_BUTTON_SELECT))  //If select button is NOT pressed - go at speed dictated by the joystick
  {
    if (!limit_y) Motor1(abs(y), y > 0); //Send info to motors
    if (!limit_x) Motor2(abs(x), x < 0);
  }
  else   //If select button IS pressed - go at speed 2 times slower
  {
    x = x/2;
    y = y/2;
    if (!limit_y) Motor1(abs(y), y > 0);
    if (!limit_x) Motor2(abs(x), x < 0);
  }
}

/****************************************************************************************************************************************************************************************
* Encoder interrupt service routines
*****************************************************************************************************************************************************************************************/

void doEncoderX(){
  x_position += (right) ? +1 : -1; // and adjust counter + if going right and - if opposite
}

void doEncoderY(){
  y_position += (up) ? +1 : -1; // and adjust counter + if going up and -  if opposite
}

Continued from previous message..

Grumpy_Mike:
It is probbly inputting interrupts too fast for the arduino to cope with. This is because the arduino runs under interrupts for things like the millis timer.
What if you try slowing down your motor, does it get better?

Well, I dont think I can slow it down more than 200 Hz.

The encoders work at 1000 pusles/rev. and 1 rev/mm. I estimate the speed at around 1 cm/sec, so - roughly 10KHz..

kf2qd:
Not knowing what else is happening in your code... Are you using any of the internal functions that use an interrupt? Have you verified this with a dial indicator or other measuring device?

At what frequency are your interrupts happening?

The results from the pulse generator are inconsistent at best - are you sure the signals are at the proper voltage? Some capacitance sneaking in there?

I attached the rest of the code. The function generator is supposedly very precise..

Thanks!

Gadi.

I spotted one problem with your code, although it doesn't explain the results with the pulse generator. Whenever you read the values of x_position and y_position outside of the ISR, you should disable interrupts, copy x_position and/or y_position to local variables, and enable interrupts. Then use the copied values in your code. This guards against reading some of the 4 bytes of the 'long' variables just before an interrupt occurs, and the remaining bytes after the interrupt.

The Arduino should be able to handle interrupts at a rate of 10KHz provided the ISR code is short and you are not using software serial or anything else that disables interrupts for long periods. You could make the ISRs a little faster by changing the types of 'up' and 'right' to long, and setting them to +1 or -1 as required - then in the ISR you can just do e.g. "y_position += up". Alternatively, do "if (up) { ++y_position; } else { --y_position; }".

Does your pulse generator output a precise number of pulses, or does it output a precise frequency and you were counting them over a particular time interval?

dc42:
I spotted one problem with your code, although it doesn't explain the results with the pulse generator. Whenever you read the values of x_position and y_position outside of the ISR, you should disable interrupts, copy x_position and/or y_position to local variables, and enable interrupts. Then use the copied values in your code. This guards against reading some of the 4 bytes of the 'long' variables just before an interrupt occurs, and the remaining bytes after the interrupt.

The Arduino should be able to handle interrupts at a rate of 10KHz provided the ISR code is short and you are not using software serial or anything else that disables interrupts for long periods. You could make the ISRs a little faster by changing the types of 'up' and 'right' to long, and setting them to +1 or -1 as required - then in the ISR you can just do e.g. "y_position += up". Alternatively, do "if (up) { ++y_position; } else { --y_position; }".

Cool. I'll sure try that.

dc42:
Does your pulse generator output a precise number of pulses, or does it output a precise frequency and you were counting them over a particular time interval?

Precise number of pulses.

Thanks!

"The encoders work at 1000 pusles/rev. and 1 rev/mm."

gadi,
The optical encoders probably works by way of a wheel attached to the axis of the motors .. perforated by 1000 holes that is exposed to a light-emitting diode on one side and a light-detector on the other. It 'works' at the speed of the motor until it goes too fast ..
.. my 5p's worth ..

Carsten53T:
"The encoders work at 1000 pusles/rev. and 1 rev/mm."

gadi,
The optical encoders probably works by way of a wheel attached to the axis of the motors .. perforated by 1000 holes that is exposed to a light-emitting diode on one side and a light-detector on the other. It 'works' at the speed of the motor until it goes too fast ..
.. my 5p's worth ..

Hi Carsten53T,

I don't think I understand what you meant. I know that the encoders are optical and I have the specs.

Naturally the pulses come faster when the motor runs at a higher speed - I estimate that the average speeds I go in are around 10mm/sec, which gives 10000 pulses/sec or 10KHz.

Gadi.

gadi,
If it's the sort I've met, then it's very 'mechanical' and 'breaks down' in dependancy on the electronics (detecting a blink) later that ie the pulses that generates the steps for a motor (if it's the same frequency). I would rely on the steps counted. .. can you controle the motor fine enough to somehow compensate for the discrepancies?

Well, I dont think I can slow it down more than 200 Hz.

Why not?

Another question - Do you really NEED that resolution? And what is the real repeatability of the slide?

Having worked with building and servicing CNC machines, you can sometimes get better performance using a lower resolution because it gives you the time to actually process and use the data you collect.

Carsten53T:
gadi,
If it's the sort I've met, then it's very 'mechanical' and 'breaks down' in dependancy on the electronics (detecting a blink) later that ie the pulses that generates the steps for a motor (if it's the same frequency). I would rely on the steps counted. .. can you controle the motor fine enough to somehow compensate for the discrepancies?

Hi,

Perhaps I still misunderstand, but how can I rely on the steps counted when I've inputed a known number of pulses at a known frequency and wound up with miscounts? I've proven that Its not counting correctly.

Grumpy_Mike:

Well, I dont think I can slow it down more than 200 Hz.

Why not?

Two reasons:

  1. I've tested inputs at 200Hz up to 1Mhz and they all miscount roughly the same number of pulses (except 50Khz in which it seems to be very precise.. STRANGE..)
  2. Say I somehow restrict the motors to run at a speed corresponding to 100HZ. That means 0.1mm/sec of motion which is too slow for what I need.

kf2qd:
Another question - Do you really NEED that resolution? And what is the real repeatability of the slide?

Having worked with building and servicing CNC machines, you can sometimes get better performance using a lower resolution because it gives you the time to actually process and use the data you collect.

Well, if I want to be able to reliably toggle between, say, bacteria under the microscope - I need it to be precise up to a maximum of 2-3 microns (corresponding to 5 pulses) over a range of motion of 20,000-50,000 pulses.

Plus- as I said to Grumpy_Mike, I've proven that even if I lower the rate to 200Hz it still miscounts just as much as for 200KHz (again, except for the oddity at 50KHz).

kf2qd:
The results from the pulse generator are inconsistent at best - are you sure the signals are at the proper voltage? Some capacitance sneaking in there?

The voltage is ok, What would the capacitance do? How do I undo it?

Thanks for the help!

Gadi.

One possibility is that the motor pulses are causing noise that is affecting the counting. What happens if you just count the pulses from the pulse generator and don't move the motors? Or is that what you were doing anyway?

What code did you use to report the number of pulses received? Maybe something in that code is disabling interrupts for too long. Are you using any libraries?

Say I somehow restrict the motors to run at a speed corresponding to 100HZ. That means 0.1mm/sec of motion which is too slow for what I need.

No I am not suggesting you slow it down permanently but as a test so you can try and see what is going wrong.

except for the oddity at 50KHz

That is telling you something so do not dismiss it.
Slow it down even more, do you get accuracy at even slower speeds. Starting and stopping a stepping motor at full speed often leads to skipping. Most high speed systems accelerate up to speed and decelerate down to a stop.

dc42:
One possibility is that the motor pulses are causing noise that is affecting the counting. What happens if you just count the pulses from the pulse generator and don't move the motors? Or is that what you were doing anyway?

That's what I was doing.

What code did you use to report the number of pulses received? Maybe something in that code is disabling interrupts for too long. Are you using any libraries?

It's what I published in the beginning of the post. No interrupt disabling at all and no libraries..

Grumpy_Mike:

Say I somehow restrict the motors to run at a speed corresponding to 100HZ. That means 0.1mm/sec of motion which is too slow for what I need.

No I am not suggesting you slow it down permanently but as a test so you can try and see what is going wrong.

except for the oddity at 50KHz

That is telling you something so do not dismiss it.
Slow it down even more, do you get accuracy at even slower speeds. Starting and stopping a stepping motor at full speed often leads to skipping. Most high speed systems accelerate up to speed and decelerate down to a stop.

I guess it is telling me something. I wish I knew what though..

I can try to slow it down even more but I don't see the point as it definitely won't operate at these frequencies. Even the 200Hz was stretching it.

as it definitely won't operate at these frequencies.

I find that hard to understand, it should work down to DC.

gadi,

I've been through the code.
The motors responds to the progression of x_position and y_position. They are on their side controlled by interrupts. They ought to respond to the feedback from the encoders instead of the interrupts.
.. alternatively I'm just lost as to what's happening

Grumpy_Mike:

as it definitely won't operate at these frequencies.

I find that hard to understand, it should work down to DC.

I'm sure it can work at these frequencies, but practically I won't be running it like that. It's too slow for what I want it to do.

Carsten53T:
gadi,

I've been through the code.
The motors responds to the progression of x_position and y_position. They are on their side controlled by interrupts. They ought to respond to the feedback from the encoders instead of the interrupts.
.. alternatively I'm just lost as to what's happening

Hi. Thanks a million for the time and effort. I'll try to explain:

The motors are controlled either by a analog input coming from a joystick:

x = analogRead(PIN_ANALOG_X)/2-256; //Transform analog input of 0:1023 to range -255:255
  if (x == -256) x = -255;
  y = analogRead(PIN_ANALOG_Y)/2-256;
  if (y == -256) y = -255;
  
  if (abs(x) < 10 ) x = 0;  //Threshholding
  if (abs(y) < 10) y = 0;
  
  right = x >= 0;
  up = y >= 0;
   
  limit_x = CheckLimitSwitches(1, x < 0); //Check if limit switches are activated
  limit_y = CheckLimitSwitches(0, y > 0);
   
  if (digitalRead(PIN_BUTTON_SELECT))  //If select button is NOT pressed - go at speed dictated by the joystick
  {
    if (!limit_y) Motor1(abs(y), y > 0); //Send info to motors
    if (!limit_x) Motor2(abs(x), x < 0);
  }
  else   //If select button IS pressed - go at speed 2 times slower
  {
    x = x/2;
    y = y/2;
    if (!limit_y) Motor1(abs(y), y > 0);
    if (!limit_x) Motor2(abs(x), x < 0);
  }

..or by a gotoxy function that runs constant voltage to the motors, depending on the current position:

void gotoxy(long int x_set, long int y_set)
{
  int x_count = 0, y_count = 0;
  byte in_position = ((x_position == x_set) && (y_position == y_set));// If already in position return
  if (in_position) return;
  
  Serial.print("ORIGIN: ");// Print point of origin onto serial monitor
  displayCoordinates(x_position, y_position, x_set, y_set);
   
  coarseGotoxy(x_set, y_set);// Go to the area arround the requested point
  
  Serial.print("COARSE DESTINATION: ");
  displayCoordinates(x_position, y_position, x_set, y_set);
  
  x_count = fineGotoxy(x_set, 1);// Go to exact location
  y_count = fineGotoxy(y_set, 0);
...

Now, on the other hand, x_position and y_position are constantly updated by the interrupts so that the entire program (including gotoxy function) can use them:

void doEncoderX(){
  x_position += (right) ? +1 : -1; // and adjust counter + if going right and - if opposite
}

void doEncoderY(){
  y_position += (up) ? +1 : -1; // and adjust counter + if going up and -  if opposite
}

I hope it's clearer now. If not - please ask again!

Thanks.