arduino board to monitor two linear encoders

Hello everyone,

I have a system consisting two quadrature linear encoders controlling two stepper motors. These two stepper motors move a stage (one in x, the other in y direction). My goal is to know when the two stepper motors move and stop (it would also be nice to know their exact positions). The first thing I did was to tab out the channels A and B and GND of each linear encoder. Second, I plan to connect them to an arduino board and use interrupt pins similar to what has been described here (https://www.youtube.com/watch?v=S1JJc8YAJqQ).

Since there are two linear encoders to monitor (four channels total), should I buy an arduino board with at least four interrupt pins? (i.e. the UNO board won't work and I need a Mega).

Thank you for your help,

Post a link to the datasheet for your linear encoders.

If you are using stepper motors why do you need encoders? Isn't the stepper position sufficiently accurate?

What is the maximum rate (pulses per second) of encoder pulses you will need to detect?

...R Stepper Motor Basics

You can use pin change interrupt that covers every single pin on arduino UNO. That is 18 pins. If your encoder is mechanical, (I don't see a Vcc you mention), you need to debounce it within the interrupt routine as well.

Hello, my encoder is HEDS-9200-Q00. I have the datasheet with it. The encoder has 5 pins: GND, N.C., Channel A, Vcc and Channel B.

http://www.avagotech.com/products/motion-control-encoders/incremental-encoders/transmissive-encoders/heds-9200q00

The encoders and stepper motors are parts of a mass spectrometer stage. So I believe they are there for the accuracy of the stepper motors. I keep the current connections between the encoders, motors and the mass spectrometer, and tab out the wires to channels A, B and GND to monitor the encoders. Does that make sense?

Thank you,

liudr: You can use pin change interrupt that covers every single pin on arduino UNO. That is 18 pins. If your encoder is mechanical, (I don't see a Vcc you mention), you need to debounce it within the interrupt routine as well.

I read about pin change interrupt but I do not quite know how to implement it. In addition, I have heard that the pin change interrupts would be (much) slower than the external interrupts.

I don't know how to answer Robin2's question about maximum pulses per second. I know that for our experiments, the stepper motors will move and stop, and the time they (both) stop can be from 0.1s (min) to 0.5s (max).

Below is the sketch I have. I assume that I use a Arduino Mega. The way I calculate displacement based on count is likely wrong. I try to use the equation Displacement = 1/xN*(1/PPI) (http://www.ni.com/tutorial/7109/en/) where N is pulse per revolution of the encoder, PPI is pulse per inch and x is encoding type. I couldn't find N for my encoder (HEDS-9200-QOO).

volatile long countAB = 0; // count for encoder 1
volatile long countCD = 0; // count for encoder 2
volatile long positionAB, positionCD;
unsigned long timep, time, etime;
volatile boolean A,B,C,D;  // C is channel A of encoder 2, D is channel B of encoder 2
volatile byte stateAB, stateABp, indexAB, stateCD, stateCDp, indexCD;
int QEM[16]={0,-1,0,1,1,0,-1,0,0,1,0,-1,-1,0,1,0};

void setup()
{
  Serial.begin(9600);
  pinMode(2, INPUT);//Channel A
  pinMode(3, INPUT);//Channel B
  pinMode(21, INPUT);//Channel C
  pinMode(20, INPUT);//Channel D
  attachInterrupt(0,Achange,CHANGE);
  attachInterrupt(1,Bchange,CHANGE);
  attachInterrupt(2,Cchange,CHANGE);
  attachInterrupt(3,Dchange,CHANGE);
  timep = micros(); //set intial time
  //read the intial value of A & B
  A = digitalRead(2);
  B = digitalRead(3);
  C = digitalRead(21);
  D = digitalRead(20);
  //set intial state value
  if ((A==HIGH)&&(B==HIGH)) stateABp = 1;
  if ((A==HIGH)&&(B==LOW)) stateABp = 2;
  if ((A==LOW)&&(B==LOW)) stateABp = 3;
  if ((A==LOW)&&(B=HIGH)) stateABp = 4;
  if ((C==HIGH)&&(D==HIGH)) stateCDp = 1;
  if ((C==HIGH)&&(D==LOW)) stateCDp = 2;
  if ((C==LOW)&&(D==LOW)) stateCDp = 3;
  if ((C==LOW)&&(D=HIGH)) stateCDp = 4;
}

void loop()
{
  time = micros();
  etime =  time - timep;
  if (etime > 100000) //0.1s
  {
    Serial.println(countAB);
    Serial.println(positionAB);
    Serial.println(countCD);
    Serial.println(positionCD);
    timep = time;
  }
}

void Achange()
{
  A = digitalRead(2);
  B = digitalRead(3);
  //determine state value
  if ((A==HIGH)&&(B==HIGH)) stateAB = 0;
  if ((A==HIGH)&&(B==LOW)) stateAB = 1;
  if ((A==LOW)&&(B==LOW)) stateAB = 2;
  if ((A==LOW)&&(B==HIGH)) stateAB = 3;
  indexAB = 4*stateAB + stateABp;
  countAB = countAB + QEM[indexAB];
  positionAB = (countAB/4)*(1/180);
  stateABp = stateAB;
} 

void Bchange()
{
  A = digitalRead(2);
  B = digitalRead(3);
  //determine state value
  if ((A==HIGH)&&(B==HIGH)) stateAB = 0;
  if ((A==HIGH)&&(B==LOW)) stateAB = 1;
  if ((A==LOW)&&(B==LOW)) stateAB = 2;
  if ((A==LOW)&&(B==HIGH)) stateAB = 3;
  indexAB = 4*stateAB + stateABp;
  countAB = countAB + QEM[indexAB];
  positionAB = (countAB/4)*(1/180);
  stateABp = stateAB;
}

void Cchange()
{
  C = digitalRead(21);
  D = digitalRead(20);
  //determine state value
  if ((C==HIGH)&&(D==HIGH)) stateCD = 0;
  if ((C==HIGH)&&(D==LOW)) stateCD = 1;
  if ((C==LOW)&&(D==LOW)) stateCD = 2;
  if ((C==LOW)&&(D==HIGH)) stateCD = 3;
  indexCD = 4*stateCD + stateCDp;
  countCD = countCD + QEM[indexCD];
  positionCD = (countCD/4)*(1/180);
  stateCDp = stateCD;
}

void Dchange()
{
  C = digitalRead(21);
  D = digitalRead(20);
  //determine state value
  if ((C==HIGH)&&(D==HIGH)) stateCD = 0;
  if ((C==HIGH)&&(D==LOW)) stateCD = 1;
  if ((C==LOW)&&(D==LOW)) stateCD = 2;
  if ((C==LOW)&&(D==HIGH)) stateCD = 3;
  indexCD = 4*stateCD + stateCDp;
  countCD = countCD + QEM[indexCD];
  positionCD = (countCD/4)*(1/180);
  stateCDp = stateCD;
}

Please modify Reply #5 and use the code button </>

so your code looks like this

and is easy to copy to a text editor.

The speed difference between pinChange interrupts and external interrupts is insignificant. However the Mega has more external interrupts than an Uno so you may not need pinChange interrupts.

The reason I asked about pulses per second is because encoders can impose a significant computational load on an Arduino. Do you know how far the device moves in a given time and do you know what is the precision of the encoder - pulses per millimetre?

…R

Robin2:
do you know what is the precision of the encoder - pulses per millimetre?

…R

Is that the same as “resolution counts per mm”? If so then the datasheet reports the value as 7.09 counts per mm or 180 counts per inch.

Would you mind checking my code in the previous post to see if I can calculate position (displacement) that way? I used the equation

displacement = Count/(xN) * (1/PPI)

x = encoding type (x = 4?)
PPI = 180
I can’t find the value of N for my encoder from the datasheet.

About pinChange, should I just modify my original code to include (here I change PIN 15).

#include<PinChangeInt.h>
#include<PinChangeIntConfig.h>

#define PIN 15

void setup() {
  Serial.begin(9600);
  pinMode(PIN,INPUT); //set pin to input
  PCintPort::attachInterrupt(PIN,ISR,CHANGE);
}

void loop() {
}

If I want to change 2 pins, I should choose them such that they have interrupt vectors?

Thank you,

supatucsb: About pinChange, should I just modify my original code to include (here I change PIN 15).

I was not thinking of the pinChange library. I don't know anything about it.

Are you using a Mega or an Uno?

You should be able to verify the logic of your maths with a calculator or a spreadsheet. However you need to be careful with integer variables on an Arduino to be sure you don't overflow or underflow in the middle of the calculation. That can usually be avoided by choosing the order of the calculations. Work through an example with a pencil and paper.

...R

Robin2: I was not thinking of the pinChange library. I don't know anything about it.

Are you using a Mega or an Uno?

You should be able to verify the logic of your maths with a calculator or a spreadsheet. However you need to be careful with integer variables on an Arduino to be sure you don't overflow or underflow in the middle of the calculation. That can usually be avoided by choosing the order of the calculations. Work through an example with a pencil and paper.

...R

The reason I said above that the pinChange can be slow because I was thinking about using the library. People told me it would be slower than the external interrupt? Can you please show me how to pinChange without using the library?

I haven't bought the Arduino board yet. It will take me 5 minutes to get one from the electronics shop next door. I would like to use the Uno because it is cheaper and this is my first time using Arduino (I am a chemist). I can buy the Mega but if I accidentally do anything wrong with it, then ... I will regret more. :)

Read about interrupts and timing here http://www.gammon.com.au/interrupts I think it will answer your questions. Uno is a good starter board, easy to replace chip when you blow an output.

Here is my first try. I apply pin Change to pin D9 and D7 only and use them to monitor one encoder. I hope that this code is not terribly wrong.

long count=0;
unsigned long timep, time, etime;
boolean A,B;
byte state, statep, index;
volatile int QEM[16]={0,-1,0,1,1,0,-1,0,0,1,0,-1,-1,0,1,0};

void activateInterrupt ()
{
  EICRA &= ~3;//clear outstanding interrupts
  EICRA |= 1;// response to CHANGE
  EIMSK |= 1;//enable
}

void setup()
{
  Serial.begin(9600);
  PCMSK0 |= bit(PCINT1);//pin 9
  PCIFR  |= bit (PCIF0);//clear outstanding interrupts
  PCICR  |= bit (PCIE0);
  PCMSK2 |= bit(PCINT23);//pin 7
  PCIFR  |= bit (PCIF2);//clear outstanding interrupts
  PCICR  |= bit (PCIE2);
  pinMode(9, INPUT);//Channel A
  pinMode(7, INPUT);//Channel B
  activateInterrupt();
  timep = micros(); //set intial time
  //read the intial value of A & B
  A = digitalRead(9);
  B = digitalRead(7);
  //set intial state value
  if ((A==HIGH)&&(B==HIGH)) statep = 0;
  if ((A==HIGH)&&(B==LOW)) statep = 1;
  if ((A==LOW)&&(B==LOW)) statep = 2;
  if ((A==LOW)&&(B=HIGH)) statep = 3;
}
void loop()
{
  time = micros();
  etime =  time - timep;
  if (etime > 100000) //0.1s
  {
    Serial.println(count);
    timep = time;
  }
}

ISR (PCINT0_vect)
{
  A = digitalRead(9);
  B = digitalRead(7);
  //determine state value
  if ((A==HIGH)&&(B==HIGH)) state = 0;
  if ((A==HIGH)&&(B==LOW)) state = 1;
  if ((A==LOW)&&(B==LOW)) state = 2;
  if ((A==LOW)&&(B==HIGH)) state = 3;
  index = 4*state + statep;
  count = count + QEM[index];
  statep = state;
} 

ISR (PCINT2_vect)
{
  A = digitalRead(9);
  B = digitalRead(7);
  //determine state value
  if ((A==HIGH)&&(B==HIGH)) state = 0;
  if ((A==HIGH)&&(B==LOW)) state = 1;
  if ((A==LOW)&&(B==LOW)) state = 2;
  if ((A==LOW)&&(B==HIGH)) state = 3;
  index = 4*state + statep;
  count = count + QEM[index];
  statep = state;
}

The pin change interrupts are grouped according to the different I/O ports. If you use two pins on the same port you have to test the pins to figure out which of the caused the interrupt.

However AFAIK if you put each connection to a pin in a different port then you know automatically which pin caused the interrupt without needing to test anything. And if you also use the external interrupts this should give you 4 simple interrupts.

Have a close look at the Atmel datasheet.

...R

Robin2: The pin change interrupts are grouped according to the different I/O ports. If you use two pins on the same port you have to test the pins to figure out which of the caused the interrupt.

However AFAIK if you put each connection to a pin in a different port then you know automatically which pin caused the interrupt without needing to test anything. And if you also use the external interrupts this should give you 4 simple interrupts.

Have a close look at the Atmel datasheet.

...R

From what I know:

D0 to D7 use the same interrupt vector D8 to D13 use the same interrupt vector A0 to A5 use the same interrupt vector

That's why I attempt to change D7 and D9. But I don't know if the above code does what I wanted. :(

supatucsb: That's why I attempt to change D7 and D9. But I don't know if the above code does what I wanted. :(

As usual I am behind the times :)

Why not try your code? Or write a test code that responds to manual triggers.

...R

In general, I think changing a pin to interrupt pin is as simple as

void setup ()
  { 
  // pin change interrupt (example for D4)
  PCMSK2 |= bit (PCINT20);  // want pin 4, getting the correct PCINT for the wanted pin
  PCIFR  |= bit (PCIF2);    // clear any outstanding interrupts
  PCICR  |= bit (PCIE2);    // enable 
  pinMode (4, INPUT_PULLUP);
  }  // end of setup

but I don't understand where I should set whether the interrupt responses to LOW/CHANGE/FALLING/RISING

as whether the below code is necessary and WHERE to put it in the code (i.e. inside void setup () or inside an ISR)

EICRA &= ~3;  // clear existing flags
EICRA |= 1;   // set wanted flags (0=LOW,1=CHANGE,2=FALL,3=RISE)
EIMSK |= 1;   // enable it

With pinChange interrupts you don't have the choice of LOW, FALLING or RISING - the clue is in the name.

You put the interrupt enable code wherever in your code it is necessary to start the interrupts to operate. Putting it in setup() means they will be operational all the time (unless you stop them later). It can be quite legitimate to use one interrupt to stop or start other interrupts - but I suspect you just need them operating all the time.

I presume you are carefully studying the Atmel datasheet. I find it becomes much clearer after 12 or 14 reads :)

I cannot over-emphasize the value I attach to writing short exploratory programs to try out ideas.

...R