For any serious application, a robot must know how far it has traveled and how fast it is going. A wheel or motor encoder is required for such purposes, but of course wheels may still slip. Sadly, few robots on the market offer motors or wheels with encoders.
I previously posted code that makes use of the WheelWatcher by Nubotics, but this unit is very limited in applicability. For those wanting to “roll their own” I offer code to decode a standard quadrature encoder with AB outputs.
For this application I used a surplus H-P dial encoder (QEDS-7596, 256 stripes or 1024 counts/revolution). The decoding algorithm is from Scott Edwards (see C code for the reference). Despite the algorithm’s simplicity, output changes come thick and fast when you spin the dial and interrupts are required to keep up with the encoder position. I used “interrupt on pin change” to read the AB input on PORTC pins 4&5, which doesn’t seem to miss states even when the dial is rapidly rotated.
For motor control, the interrupt routine should be written in assembler for maximum speed. I would certainly welcome suggestions.
Code with tabs may be downloaded from
uoxray.uoregon.edu/orangutan/encoder.c
Cheers, Jim
/*
Quadrature encoder for the Orangutan, ATmega168
This code assumes that the A and B outputs of a quadrature encoder are connected to PORTC, pins 4&5
If the decoded direction is incorrect, swap the A&B terminals or change the logic of the interrupt
service routine accordingly
The algorithm is from Scott Edwards, in Nuts&Volts Vol 1 Oct. 1995 (Basic Stamp #8, available on line at [rambal.com/descargas/libros/ ... erface.pdf](http://www.rambal.com/descargas/libros/Nuts%20and%20Volts/1/Rotary%20Encoders%20Provide%20Friendly%20Spin%20and%20Grin%20Interface.pdf)
Interrupts are required to avoid missing encoder states. However, if skipped states are not a problem
(e.g a volume control), the decoding can take place in the main routine.
Jim Remington, sjames_remington at yahoo dot com
*/
#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
//global variables: encoder position and direction of rotation
volatile unsigned int enc_pos;
volatile unsigned char enc_dir;
/*
PORTC Pin change interrupt service routine. Decodes the encoder.
For algorithm, see Scott Edwards article from Nuts&Volts V1 Oct. 1995
(righthand bit of old A,B) xor (lefthand bit of new A,B) => dir.
Increment or decrement encoder position accordingly
*/
ISR (PCINT1_vect) {
static unsigned char enc_last=0,enc_now;
enc_now = (PINC & (3<<4))>>4; //read the port pins and shift result to bottom bits
enc_dir = (enc_last & 1)^((enc_now & 2) >> 1); //determine direction of rotation
if(enc_dir==0) enc_pos++; else enc_pos--; //update encoder position
enc_last=enc_now; //remember last state
}
#include "lcd.c"
int main(void)
{
unsigned char buf[8];
enc_pos=0; //Initialize encoder position
LCDInit(); //Initialize LCD display
DDRC &=~(3<<4); //Port C pins 4 and 5 as input
PCMSK1 |= (3<<PCINT12); //enable interrupt on pin change, bits 4&5 PORTC
PCICR |= 1<<PCIE1; //enable interrupt on pin change, PORTC
sei(); //enable global interrupts
while (1){
LCDSendCommand(LCD_Line1); //display current direction of rotation on line 1
itoa(enc_dir,buf,10);
LCDPrintString(buf);
LCDSendCommand(LCD_Line2); //display current encoder position on line 2
itoa(enc_pos,buf,10);
LCDPrintString(buf);
}
}
Edit: update link to N&V