Go Down

Topic: Problem reading an encoder correctly (Read 1 time) previous topic - next topic

gadi

Hi everyone,

I'm trying to control a microscope stage with a home-built controller.
I'm using the Arduino Mega 2560 with a DFRobot 1A motor shield and a Sparkfun joystick kit.

The stage motors each have an encoder with 1000 pulses per revolution and a 1mm/rev gear factor.

When using the following code (I'm attaching only the relevant parts), I get very funny readouts:

Code: [Select]

/*

This program uses the following hardware:

1. Arduino mega2560
2. DFRobot 1A motor shield
3. Sparkfun joystick shield kit

...

It also supplies 5V to the encoders of the motors and reads data from the motors' limit
switches and encoder using interrupts - currently configured to count only full steps
as opposed to half-steps. This saves two timer\counter sockets (in order to count
half-steps as well one has to move encoder B outputs to timer\counter sockets and duplicate
interrupt function). The counting is done using interrupt socket #2 and #3, only available on
Arduino Mega.

Furthermore it prints out data on the serial monitor.

...

Motor 2 is defined as the x axis and Motor 1 is defined as the y axis

*/

const byte ENCODER_POWER_X = 23; //Sets pins 23 and 22 (arbitrary) as the 5V power sources for the x and y axis encoder respectively
const byte ENCODER_POWER_Y = 22;
const byte ENCODER_A_X = 21; //Sets timer/counter pins 21 (Timer counter 2) and 20 (Timer counter 3) as encoder A inputs for both axes
const byte ENCODER_A_Y = 20;
const byte ENCODER_B_X = 28; //Sets pins 28 and 29 as encoder B inputs for both axes
const byte ENCODER_B_Y = 29;

volatile unsigned int x_position = 0; //Global coordinates, remembering last position
unsigned int last_x_position = 1;
volatile unsigned int y_position = 0;
unsigned int last_y_position = 1;

boolean x_A_set = false; //Used for encoder reading mechanism
boolean x_B_set = false;
boolean y_A_set = false;
boolean y_B_set = false;

//Auxilary function for displaying coordinates on serial monitor

void displayCoordinates(int x_position, int y_position)
{
  Serial.print("(x,y) in pulses:(");
  Serial.print(x_position, DEC);
  Serial.print(",");
  Serial.print(y_position, DEC);
  Serial.print(")");
  Serial.print("(x,y) in mm:("); //For 1000 pulses per revolution and 1 mm per revolution
  Serial.print(x_position/1000, DEC);
  Serial.print(",");
  Serial.print(y_position/1000, DEC);
  Serial.print(")");
  Serial.println();
}

//Initial setup

void setup()
{
    Serial.begin(9600);  //Activate serial monitor
   
  pinMode(ENCODER_A_X,INPUT);  //Encoder inputs
  digitalWrite(ENCODER_A_X,HIGH); 
  pinMode(ENCODER_A_Y,INPUT);
  digitalWrite(ENCODER_A_Y,HIGH); 
  pinMode(ENCODER_B_X,INPUT);
  digitalWrite(ENCODER_B_X,HIGH); 
  pinMode(ENCODER_B_Y,INPUT);
  digitalWrite(ENCODER_B_Y,HIGH); 
 
  pinMode(ENCODER_POWER_X, OUTPUT);  //Set encoder power source pins to output and produce 5V
  digitalWrite(ENCODER_POWER_X, HIGH);
  pinMode(ENCODER_POWER_Y, OUTPUT); 
  digitalWrite(ENCODER_POWER_Y, HIGH);

attachInterrupt(2,doEncoderX,CHANGE); //Define interrupts on counters 2,3 for x,y axes respectively upon change in waveform
attachInterrupt(3,doEncoderY,CHANGE);

}

//Main Program

void loop()
{
 
  if (!digitalRead(PIN_BUTTON_UP)) //If upper button is pressed - set coordinates to zero
  {
    x_position = 0;
    y_position = 0;
  }
 
  if ((last_x_position != x_position) || (last_y_position != y_position)) //If there has been motion display new coordinates
  {
    displayCoordinates(x_position,y_position);
    last_x_position = x_position;
    last_y_position = y_position;
  }

}

//The interrupt functions, first for x axis A wave changing state and then the same for y

void doEncoderX()
{
  x_A_set = digitalRead(ENCODER_A_X) == HIGH; // Test transition
  x_B_set = digitalRead(ENCODER_B_X);
 
  x_position += (x_A_set != x_B_set) ? -1 : +1; // and adjust counter - if A leads B and + if opposite
}

void doEncoderY()
{
  y_A_set = digitalRead(ENCODER_A_Y) == HIGH; // Test transition
  y_B_set = digitalRead(ENCODER_B_Y);

  y_position += (y_A_set != y_B_set) ? -1 : +1;  // and adjust counter - if A leads B and + if opposite
}


What I get is that the (x,y) count displayed is very low, the x axis count doesn't change at all and the y count goes up regardless of the actual direction of motion.

I'd appreciate any useful ideas.. I'm new to all of this..  :smiley-eek:

Thanks,

Gadi.

MarkT

You have interrupts attached to pins 20 and 21 only - which are your encoder A inputs.

However the interrupt routines are also looking at encoder B inputs - you have to handle each encoder separately...

Perhaps use pins 2 and 3 for encoder B?
[ I won't respond to messages, use the forum please ]

gadi

First of all thank you for the quick reply!

Quote
You have interrupts attached to pins 20 and 21 only - which are your encoder A inputs.

However the interrupt routines are also looking at encoder B inputs - you have to handle each encoder separately...

Perhaps use pins 2 and 3 for encoder B?


When I read the examples here:

http://arduino.cc/playground/Main/RotaryEncoders#Example2

I was under the impression that if I'm willing to miss half of the state transitions I only need to plug one channel (say, A) into the timer/counter socket, and leave the other channel connected to a regular digital pin. Is this incorrect?

Thanks,

Gadi.


retrolefty

#3
May 20, 2012, 06:24 pm Last Edit: May 20, 2012, 06:31 pm by retrolefty Reason: 1
Quote
I was under the impression that if I'm willing to miss half of the state transitions I only need to plug one channel (say, A) into the timer/counter socket, and leave the other channel connected to a regular digital pin. Is this incorrect?


No you are correct, you have options of how to process the encoder steps. Encoder 'steps per revolution' Vs interrupt handling can be a little confusing. Encoder manufactures normally define their encoders 'steps' as every four transitions (edge changes) of the two quadrature output signals per 'step'. That gives you quite a few options of how to process your encoder.

So assuming you have a 2 channel quadrature encoder rated at say 128 'steps' per revolution by the manufacture, and you are going to use interrupts to process the encoder you will find:

If you use just one channel as a interrupt trigger (and then read the other channel as a digital input) AND use the rising or falling interrupt trigger option you will get 128 counts per revolution.

If you use just one channel as a interrupt trigger (and then read the other channel as a digital input) AND use the change interrupt trigger option you will get 256 counts per revolution.

If you use both channels as interrupt triggers AND use the rising or falling interrupt trigger option you will also get 256 counts per revolution.

If you use both channels as interrupt triggers AND use the change interrupt trigger option you will get 512 counts per revolution.

It sounds more complicated then it really is, but basically using just software changes and options you have a choice of how much counting resolution you can get out of a 2 channel quadrature encoder Vs the rated steps per revolution. Either the 'rated' steps, 2 X the rated steps, or 4 X the rated steps.

Hope that helps.

Lefty

gadi


So assuming you have a 2 channel quadrature encoder rated at say 128 'steps' per revolution by the manufacture, and you are going to use interrupts to process the encoder you will find:

If you use just one channel as a interrupt trigger (and then read the other channel as a digital input) AND use the rising or falling interrupt trigger option you will get 128 counts per revolution.

If you use just one channel as a interrupt trigger (and then read the other channel as a digital input) AND use the change interrupt trigger option you will get 256 counts per revolution.

If you use both channels as interrupt triggers AND use the rising or falling interrupt trigger option you will also get 256 counts per revolution.

If you use both channels as interrupt triggers AND use the change interrupt trigger option you will get 512 counts per revolution.



Thanks Lefty!

I was going for the second thing you wrote, but somehow got very strange results using the code I posted earlier:

The x axis encoder didn't seem to count at all and the y axis gave very small values (of up to about 30) and counted up for both directions of motion..

Thanks for your help!

Gadi.

MarkT

The problem is that both interrupts are driven by encoder A and none by encoder B, and the interrupt routines are confusing the two encoders - first get encoder A working with one interrupt handler and free of references to B, then duplicate this for the other encoder - you'll have to re-assign your pins so each encoder is driving an interrupt input (or use pin-change interrupts).
[ I won't respond to messages, use the forum please ]

gadi


The problem is that both interrupts are driven by encoder A and none by encoder B, and the interrupt routines are confusing the two encoders - first get encoder A working with one interrupt handler and free of references to B, then duplicate this for the other encoder - you'll have to re-assign your pins so each encoder is driving an interrupt input (or use pin-change interrupts).


Hi Mark,

But they're different encoders.. One interrupt is driven by channel A of the x axis motor encoder and one by channel A of the y axis motor encoder. Channel B of both encoders is connected to a regular digital input, which is a valid connection as far as the examples go.

Gadi.

MarkT

Then the variables should be called ENCODER_X_A, ENCODER_X_B etc - ie (ENCODER_A)_X is how everyone reads such names as it fits in with the field-access syntax  ("encoder.y.b" in the C language means field 'b' of (field 'y' of structure 'encoder')

Given that then first I'd rewrite the routines:
Code: [Select]

void doEncoderX()
{
  byte x_A_set = digitalRead(ENCODER_A_X);
  byte x_B_set = digitalRead(ENCODER_B_X);
 
  x_position += (x_A_set != x_B_set) ? -1 : +1; // and adjust counter - if A leads B and + if opposite
}

And remove the global definitions of x_A_set etc.  They weren't volatile, and there is no reason to be global in scope.
byte variables take less time than ints on an 8-bit machine so I'd tend to favour them for an interrupt routine, and very importantly I set them the same way so that the != comparison is comparing like with like (digitalRead returns the values HIGH or LOW (zero) only, the != operator is defined to return zero or non-zero).
[ I won't respond to messages, use the forum please ]

gadi


Then the variables should be called ENCODER_X_A, ENCODER_X_B etc - ie (ENCODER_A)_X is how everyone reads such names as it fits in with the field-access syntax  ("encoder.y.b" in the C language means field 'b' of (field 'y' of structure 'encoder')

Given that then first I'd rewrite the routines:
Code: [Select]

void doEncoderX()
{
  byte x_A_set = digitalRead(ENCODER_A_X);
  byte x_B_set = digitalRead(ENCODER_B_X);
 
  x_position += (x_A_set != x_B_set) ? -1 : +1; // and adjust counter - if A leads B and + if opposite
}

And remove the global definitions of x_A_set etc.  They weren't volatile, and there is no reason to be global in scope.
byte variables take less time than ints on an 8-bit machine so I'd tend to favour them for an interrupt routine, and very importantly I set them the same way so that the != comparison is comparing like with like (digitalRead returns the values HIGH or LOW (zero) only, the != operator is defined to return zero or non-zero).


Thanks!

Did all that and still - the x axis doesn't count at all and the y axis gives really low numbers (of the order of one).

Any more suggestions?

Gadi.

MarkT

You are sure the encoders are sending pulses in quadrature?  Try driving LEDs with them and turning slowly...
[ I won't respond to messages, use the forum please ]

gadi


You are sure the encoders are sending pulses in quadrature?  Try driving LEDs with them and turning slowly...


I'll try checking with an oscilloscope.

Do you think that perhaps the frequency (estimated) of

~(1000 pulses/rev) X (1 rev/mm) X (10 mm/sec) = 10kHz

is too much for the interrupt method? Should I use hardware counting? If so - can you refer me to a good source in which I can learn how to do this?

Thanks again for the help. It is really helpful!  :)

Gadi.

kf2qd

Don't know why you are supplying power from an Arduino output to the encoder - Just power the encoder from 5V.

Most encoders I have worked with source the A & B so they actually supply 5V when high and 0V when low. Or are your encoders sinking type?

Using 1 interrupt and 1 other input per encoder - Assuming the A phase goes to the interrupt pin, and the B phase goes to any input pin -
the interrupt routine would look at the B input pin and use that to increment or decriment the position -


volatile long Xposition;

AttachInterrupt(XAPin,CHANGE);

void XEncoder(){
  int XStat;
  XStat=digitalRead(XBpin);
  if (XStat == HIGH) {
      Xposition +=1
     }
   else {
     XPosition -=1;
   }
}
You don't have to know the state of the A oin because it changed and the B pin will let you determine direction of rotation.

Keep the code inside of the interrupt handler just a simple as you possibly can so that it will execute as rapidly as possible. If the encoders are rotating very fast you may have to code this in assembly language (not all that hard to do) as C has quite a bit of overhead(a factor of 12 or so).
You can then use Xposition in Loop() as part of a calculation to display the real world position.

gadi

Don't know why you are supplying power from an Arduino output to the encoder - Just power the encoder from 5V.


Well, the encoder needs 5V and I don't want to use a separate power source from the one I use for the Arduino controller and the motors.. Is there some problem with this approach?


Most encoders I have worked with source the A & B so they actually supply 5V when high and 0V when low. Or are your encoders sinking type?

Using 1 interrupt and 1 other input per encoder - Assuming the A phase goes to the interrupt pin, and the B phase goes to any input pin -
the interrupt routine would look at the B input pin and use that to increment or decriment the position -


volatile long Xposition;

AttachInterrupt(XAPin,CHANGE);

void XEncoder(){
   int XStat;
   XStat=digitalRead(XBpin);
   if (XStat == HIGH) {
       Xposition +=1
      }
    else {
      XPosition -=1;
    }
}
You don't have to know the state of the A oin because it changed and the B pin will let you determine direction of rotation.

Keep the code inside of the interrupt handler just a simple as you possibly can so that it will execute as rapidly as possible. If the encoders are rotating very fast you may have to code this in assembly language (not all that hard to do) as C has quite a bit of overhead(a factor of 12 or so).
You can then use Xposition in Loop() as part of a calculation to display the real world position.


Do you think that using interrupts with a 10kHz signal is ok for the Arduino Mega? It's not too fast?

Gadi.

jraskell



Do you think that using interrupts with a 10kHz signal is ok for the Arduino Mega? It's not too fast?

Gadi.


If the Mega isn't doing anything else, no problem at all to handle a 10kHz signal.
If the Mega is doing light processing, still shouldn't be a problem.
If the Mega is doing some heavy processing, could very well be a problem.

MarkT




Do you think that using interrupts with a 10kHz signal is ok for the Arduino Mega? It's not too fast?

Gadi.


If the Mega isn't doing anything else, no problem at all to handle a 10kHz signal.
If the Mega is doing light processing, still shouldn't be a problem.
If the Mega is doing some heavy processing, could very well be a problem.


Only a problem for the processing, the interrupts always get serviced - its other interrupt routines that you have to worry about.

If you have code that is disabling interrupts for more than a few microseconds at a time though, that could be a big issue.  Don't do that!

One thought - replacing the digitalRead() calls in the interrupt routines with direct port manipulation can be a big win.
[ I won't respond to messages, use the forum please ]

Go Up