Optical Encoder Issues - High Resolution Encoder

I'm using a couple Honeywell Optical Encoders (these are the ones I'm using: http://www.alliedelec.com/Search/ProductDetail.aspx?SKU=753-0060) for a project as these are very accurate encoders. The challenges I'm facing is that the pulses/resolution is 128. So even when using a single interrupt per encoder (Arduino only has two) I'm getting inaccurate results when I rotate the encoder fast.

However, if I rotate the encoder very slow it is very accurate, the faster I go, it repeats the values like:

Current Bottom Pos: 1 Current Bottom Pos: 0 Current Bottom Pos: 1 Current Bottom Pos: 2 Current Bottom Pos: 3 Current Bottom Pos: 4 Current Bottom Pos: 5 Current Bottom Pos: 4 Current Bottom Pos: 5 Current Bottom Pos: 4 Current Bottom Pos: 5 Current Bottom Pos: 6 Current Bottom Pos: 7

I really only need it to be accurate at speeds just a bit faster than it is when the encoder returns correctly. What is the best way to get these encoders to work with the Arduino? I have two encoders attached. One as a top encoder and bottom and they are both independent from each other, requiring different readings.

Cheers! Danny

I really only need it to be accurate at speeds just a bit faster than it is when the encoder returns correctly.

Most cases of ‘missing counts’ with encoders using interrupts is that too much time is being consumed executing the statements in the ISR function. If you can post your ISR function we can possible give ideas on how to shorten it or speed it up. If your presently close to the speed it needs to be it shouldn’t be too hard to tighten up the ISR code some.

Lefty

 // ++++++
  // encoder/sensors
  // ++++++ 
   
   // top encoder
   pinMode(encoder0PinA, INPUT); 
  // digitalWrite(encoder0PinA, HIGH);       // turn on pullup resistor
   pinMode(encoder0PinB, INPUT); 
  // digitalWrite(encoder0PinB, HIGH);       // turn on pullup resistor
   attachInterrupt(0, doEncoderTOP, CHANGE);  // encoder pin on interrupt 0 - pin 2
   
   // bottom encoder
   pinMode(encoder1PinA, INPUT); 
   //digitalWrite(encoder1PinA, HIGH);       // turn on pullup resistor
   pinMode(encoder1PinB, INPUT); 
   //digitalWrite(encoder1PinB, HIGH);       // turn on pullup resistor
   attachInterrupt(1, doEncoderBOTTOM, CHANGE);  // encoder pin on interrupt 1 - pin 3
  

void doEncoderBOTTOM(){

  if (digitalRead(encoder1PinA) == HIGH) {    // found a low-to-high on channel A

   if (digitalRead(encoder1PinB) == LOW) {    // check channel B to see which way encoder is turning
     currentBottomPos = currentBottomPos + 1; // CCW
   } else {
     currentBottomPos = currentBottomPos - 1; // CW
   }
   
 } else {                                     // found a high-to-low on channel A 

   if (digitalRead(encoder1PinB) == LOW) {    // check channel B to see which way encoder is turning  
     currentBottomPos = currentBottomPos - 1; // CW
   } else {
     currentBottomPos = currentBottomPos + 1; // CCW
   }
   
 }

 Serial.print("Current Bottom Pos: ");
 Serial.println (currentBottomPos, DEC);      // debug - remember to comment out 



}

void doEncoderTOP(){
  
 if (digitalRead(encoder0PinA) == HIGH) {       // found a low-to-high on channel A

   if (digitalRead(encoder0PinB) == LOW) {      // check channel B to see which way encoder is turning
     currentTopPos = currentTopPos + 1;         // CCW
   } else {
     currentTopPos = currentTopPos - 1;         // CW
   }
   
 } else {                                       // found a high-to-low on channel A 

   if (digitalRead(encoder0PinB) == LOW) {     // check channel B to see which way encoder is turning  
     currentTopPos = currentTopPos - 1;        // CW
   } else {
     currentTopPos = currentTopPos + 1;        // CCW
   }
   
 }

  Serial.print("Current Top Pos: ");
  Serial.println(currentTopPos, DEC);           // debug - remember to comment out
  //Serial.print("Target Top Pos: ");
  //Serial.println(targetTopPos, DEC);           // debug - remember to comment out
        
}

Here is the code related to the encoder...

Two places to improve.

  1. The serial print statements take a lot of time, they really shouldn't be used inside a ISR. I see you plan on commenting them out once everything is working, so in fact your speed might be fine just by leaving them out. If you indeed need to able to print the position variables do a simple flag (set a global flag variable to one) and have your loop function test the flag and perform the print statements inside the loop function. The position variables will have to be made static and global also if they aren't already.

  2. If your ISR is not fast enough even with printing removed then the next easiest thing is to use direct port access instead of the digitalRead() statments. The Arduino I/O method using digitalRead and write statement is great for abstracting the physical pin/port addresses of all the I/O pins on the various AVR processors supported, but comes at quite a speed penalty compared to direct port access mode. The trade off is your code will be less portable between say a 328 based board and a 1280 based board, as you would have to change the port and pin position values when loading to the different processor types. If your application is always going to be run on a 168 or 328 processor chip then there is no trade-off to be made, just a easy speed up.

See: http://www.arduino.cc/en/Reference/PortManipulation

I played around with two optical encoders wired to my Arduino a year ago or so and was able to improve the speed response a ton by using port/pin digital reads.

Here are my ISR functions I used. The comments show the original digitalRead method and the code show direct port access method:

                                                    // Start of Interrupt routine for encoder #1
                                                    // added direct port access to speed up ISR handling of encoder interrupts
void doEncoder1() {
    if (PIND & 0x04) {                              // test for a low-to-high interrupt on channel A, same as if(digitalRead(encoderPinA) == HIGH)
        if ( !(PIND & 0x10)) {                      // check channel B for which way encoder turned, same as if(digitalRead(encoderPinB) == LOW)
           encoderPos = ++encoderPos;               // CW rotation
           PORTD = PIND | 0x40;                     // set direction output pin to 1 = forward, same as digitalWrite(encoderdir, HIGH);
          }
        else {
           encoderPos = --encoderPos;               // CCW rotation
           PORTD =PIND & 0xBF;                      // Set direction output pin to 0 = reverse, same as digitalWrite(encoderdir, LOW);
          }
    }
    else {                                          // it was a high-to-low interrupt on channel A
        if (PIND & 0x10) {                          // check channel B for which way encoder turned, same as if(digitalRead(encoderPinB)==HIGH)
           encoderPos = ++encoderPos;               // CW rotation
           PORTD = PIND | 0x40;                     // Set direction output pin to 1 = forward, same as digitalWrite(encoderdir, HIGH);
           }
        else {
           encoderPos = --encoderPos;               // CCW rotation
           PORTD =PIND & 0xBF;                      // Set direction output pin to 0 = reverse, same as digitalWrite(encoderdir, LOW);
           }
         }
    PORTD = PIND | 0x80;                            //  digitalWrite(encoderstep, HIGH);   generate step pulse high
    PORTD = PIND | 0x80;                            //  digitalWrite(encoderstep, HIGH);   add a small delay
    PORTD = PIND & 0x7F;                            //  digitalWrite(encoderstep, LOW);    reset step pulse
}                                                   // End of interrupt code for encoder #1


                                                    // Start of Interrupt routine for encoder #2
                                                    // added direct port access to speed up ISR handling of encoder interrupts
void doEncoder2(){
  if (PIND & 0x08) {                                // test for a low-to-high interrupt on channel A, same as if(digitalRead(encoder2PinA) == HIGH)
     if (!(PIND & 0x20)) {                          // check channel B for which way encoder turned, same as if(digitalRead(encoder2PinB) == LOW)
      encoder2Pos = ++encoder2Pos;                  // CW rotation
      PORTB = PINB | 0x01;                          // Set direction output pin to 1 = forward, same as digitalWrite(encoder2dir, HIGH);
     }
     else {
      encoder2Pos = --encoder2Pos;                  // CCW rotation
      PORTD =PIND & 0xFE;                           // Set direction output pin to 0 = reverse, same as digitalWrite(encoder2dir, LOW);
     }
  }
  else {                                            // it was a high-to-low interrupt on channel A
     if (PIND & 0x20) {                             // check channel B for which way encoder turned, same as if(digitalRead(encoder2PinB)==HIGH)
      encoder2Pos = ++encoder2Pos;                  // CW rotation
      PORTB = PINB | 0x01;                          // Set direction output pin to 1 = forward, same as digitalWrite(encoder2dir, HIGH);
      }
     else {
      encoder2Pos = --encoder2Pos;                  // CCW rotation
      PORTB =PINB & 0xFE;                           // Set direction output pin to 0 = reverse, same as digitalWrite(encoder2dir, LOW);
     }
  }
  PORTB = PINB | 0x02;                              // digitalWrite(encoder2step, HIGH);   generate step pulse high
  PORTB = PINB | 0x02;                              // digitalWrite(encoder2step, HIGH);   used to add a small delay
  PORTB = PINB & 0xFD;                              // digitalWrite(encoder2step, LOW);    reset step pulse
}                                                   // End of interrupt code for encoder #2

Lefty

Thanks for the tips!

Do you have or know of any examples I can look at demonstrate direct port access using interrupts?

Cheers! Danny

Do you have or know of any examples I can look at demonstrate direct port access using interrupts?

Yes, I just posted it into my prior posting. However even with them, if you still rely on the serial prints inside the ISR you will probably still have speed problems I think.

Lefty

Looks like direct access did the trick! Thanks.

Danny