I had seen some examples for rotary encoders but found the logic hard to understand. So I did my own implementation based on a table. I have seen other people using a similar method, but I think this method is easier to understand.
Prev | Next | Code | Direction | | Array positon | Code | Description |
11 | 01 | 13 | CW | | 0 | 0 | Invalid |
01 | 00 | 4 | CW | | 1 | -1 | CCW |
00 | 10 | 2 | CW | | 2 | 1 | CW |
10 | 11 | 11 | CW | | 3 | 0 | Invalid |
11 | 10 | 14 | CCW | | 4 | 1 | CW |
10 | 00 | 8 | CCW | | 5 | 0 | Invalid |
00 | 01 | 1 | CCW | | 6 | 0 | Invalid |
01 | 11 | 7 | CCW | | 7 | -1 | CCW |
| | | | | 8 | -1 | CCW |
| | | | | 9 | 0 | Invalid |
| | | | | 10 | 0 | Invalid |
| | | | | 11 | 1 | CW |
| | | | | 12 | 0 | Invalid |
| | | | | 13 | 1 | CW |
| | | | | 14 | -1 | CCW |
| | | | | 15 | 0 | Invalid |
In the two tables above you can see the combinations. The green table is the sequence that follows when turning clockwise or counter clockwise. What I did is combine the two and put it in a table. Each combination is unique. By looking at the code you can determine if the data read is valid or the direction the encoder is going.
There are three main sections.
First is the setup, which defines the pins to use, enable interrupts and initialize the previous pin state. Next is the interrupt service routine, and then the actual code that will check for the rotation and increment counters if applicable.
Below is an extract from the data sheet for the ATMEGA 1280, 2560 chip:
PCICR – Pin Change Interrupt Control Register
• Bit 1 – PCIE1: Pin Change Interrupt Enable 1
When the PCIE1 bit is set (one) and the I-bit in the Status Register (SREG) is set (one), pin
change interrupt 1 is enabled. Any change on any enabled PCINT15:8 pin will cause an interrupt.
The corresponding interrupt of Pin Change Interrupt Request is executed from the PCI1
Interrupt Vector. PCINT15:8 pins are enabled individually by the PCMSK1 Register.
This is the code that enables interrupts on those pins:
void InitialiseInterrupt(){
// switch interrupts off while messing with their settings
cli();
PCICR =0x01; // Enable PCIE0: interrupt
PCMSK0 = 0b00110000; // Only want pins 10 and 11
sei(); // turn interrupts back on
}
The service routine reads the state of the encoder pins and combines with the previous state to have an index to the array that contained the valid combinations.
Finally the code in the main loop that checks to see if a rotation occurred. Only a valid rotation will cause it to do something. I also have a counter, because in reality each detent positions generates 4 pulses, which can cause the menu to change to rapidly and hard to select.
Below is the full code:
// Code by Manuel Negri
// July 2014
// Released to the public domain! Enjoy!
#define encoder0PinA 10 // INT4 2
#define encoder0PinB 11 //INT5 3
boolean aa=true;
int prevRot =0;
int curRot =0;
int positionIndex =0;
int rotDirection=0;
int menuInc =0;
int menuDec=0;
int menuItem=0;
// keep track of the position,
//1 is cw , -1 is ccw and 0 is invalid
int positionTrack[16]= {
0,-1, 1, 0,1, 0, 0, -1, -1, 0, 0,1,0, 0, -1, 1};
void setup() {
pinMode(encoder0PinA, INPUT);
pinMode(encoder0PinB, INPUT);
pinMode(13,OUTPUT);
// we just have to set, cause they are clear to start
InitialiseInterrupt();
if (digitalRead(encoder0PinA) == HIGH) {
bitSet(prevRot,1);
}
if (digitalRead(encoder0PinB) == HIGH) {
bitSet(prevRot,0);
}
Serial.begin (19200);
Serial.println ("start");
}
void InitialiseInterrupt(){
// switch interrupts off while messing with their settings
cli();
PCICR =0x01; // Enable PCIE0: interrupt
PCMSK0 = 0b00110000; // Only want pins 10 and 11
sei(); // turn interrupts back on
}
ISR(PCINT0_vect) {
// Interrupt service routine.
// Every single PCINT0..7 (=ADC0..5) change
// will generate an interrupt. This will
// always call this method
curRot=0;
if (digitalRead(encoder0PinA) == HIGH) {
bitSet(curRot,1);
}
if (digitalRead(encoder0PinB) == HIGH) {
bitSet(curRot,0);
}
// he we combine the bits for the previous
// position with the current
// move the prevRot to the upper nibble
positionIndex = prevRot<<2 | curRot;
// and depending on the combination , the rotDirection
// variable will contain which way the encoder turned
// or if it was an invalid code
// with invalid combination, we do nothing.
rotDirection =positionTrack[positionIndex];
// The current postion now becomes the previous position
prevRot = curRot;
}
void loop(){ //Do stuff here }
// my main code actually checks every 20 msec, so that
// is why i have this delay
delay(20);
if (rotDirection != 0 ) { // only check if a valid direction
if (rotDirection == 1 ){
rotDirection=0; // reset the direction
menuInc++;
// menuInc counts how many pulses where generated
// in this direction. Basically it is so we dont go
// to fast when turning the knob.
//
if (menuInc >=4){
if ( menuItem==15) { // I have 16 menu items
menuItem=0; // this is to catch the overflow
}
else {
menuItem++; // no overflow, so increment
}
Serial.print (menuItem);
Serial.println (" menu inc ");
menuInc= 0; // since we moved a position
menuDec = 0; // reset the two counters
}
}
else {
rotDirection=0; // reset the direction
menuDec--;
// menuDec counts how many pulses where generated
// in this direction. Basically it is so we dont go
// to fast when turning the knob.
if (menuDec <=-4){
if ( menuItem==0) { // prevent underflow
menuItem=15;
}
else {
menuItem--;
}
Serial.print (menuItem);
Serial.println (" menu dec ");
menuInc= 0; // since we moved a position
menuDec = 0; // reset the two counters
}
}
}
}