For whoever is reading this still.
The rotary encoder module arrived from ebay and finally I put in on breadboard earlier on. It is vert badly made with the actual encoder soldered at 30 degree angle to the little PCB. It is also missing one 10K pullup resistor. But I got it to work 110% reliably.
First of all it looks like this :
http://www.ebay.co.uk/itm/321293171237?_trksid=p2059210.m2749.l2649&ssPageName=STRK%3AMEBIDX%3AITIt is basically a simple rotary encoder which also has a momentary switch. The PCB contains 3 pullup resistors, one for the switch and two for the rotary encoder.
Wiring is simple: 10K pullup resistors to the 5V line (already provided on the PCB) and then a low pass filter for each pin, in my case a 10K resistor followed by a 100nF capacitor to ground.
WARNING: the momentaty switch, at least, will not function without the low pass filter. Do not try to fix it in the code, it simply sends multiple "phantom" presses.
So the basic code is as follows.
First the switch, the code would need to know if it has been pressed. Maybe also how many times - which means by the time you actually try to do something intelligent in the code, the switch may have been pressed again, so we need some sort of a queue, very similar to Windows mouse queue where clicks are stored for later processing. To keep things simple I dispensed with a queue and simply used a counter, you press the switch the counter goes up, you "read" the switch, the counter goes down. So you keep reading until the counter goes to 0. Of course it can be done more sophisticated with a queue storing the switch press as well as the time it was pressed, and like Windows mouse clicks it could also present double clicks. But it is not needed here.
So I tested the switch and it works faultlessly, does not miss a press, and does not give more presses than needed.
The code uses interrupt channel 0 (digital pin 2) on the Arduino for the switch and is set to trigger on going low. The ISR simply increments a counter could not be more simple than that.
Now for the rotary encoder.
From the two pulled-up lines, I have attached the "DT" line (whatever that means) to interrupt channel 1 (digital pin 3 on the Arduino). After some experimentation and head scratching, the ISR is also very simple, as simple as it gets. The ISR triggers on "change". I tested and unless you turn and click the encoder you will not get suprious interrupts. So if the ISR triggers it means the rotary encoder was turned either right or left (cw or ccw). After examining the values of the two data pins on each interrupt invokation, it is clear that the designer is ingenious! If the two pins are at the same value, either 0s or 1s, it means the switch was turned clockwise. If the pins have different values, it means the switch was turned counter clockwise.
Rotary movements are stored in a circular buffer / queue so that there is some history of how the switch was turned. I have tested this and it is bullet proof. It is also so simple it defies belief.
Here is the interrupt routine for the rotary encoder:
void ea_interrupt()
{
byte ea=digitalRead(ROTARY_ENCODER_EA)==HIGH?1:0;
byte eb=digitalRead(ROTARY_ENCODER_EB)==HIGH?1:0;
int iRotation = (ea ^ eb ) ? 2 : 1 ;
insert_to_rotation(iRotation);
}
"insert_to_rotation" simply inserts a rotation action into the buffer/queue, where 1 means clockwise and 2 means counter. Can't get any simpler. I do remember reading a post with some very convoluted code, that had to remember previous positions and half-positions, I am not sure how this can happen, I have literally jumped up and down on this switch and the code above is infallible. And it is a shitty switch from ebay.
I am not sure how "expensive" the two interrupts are, in terms of stealing CPU time, not my code, but the framework underneath. Would it be better to poll very often, it was suggested 500 times a second, that implies a "loop" delay of just 2ms, and it also assumes we do nothing else in the meantime. So I stick with the interrupts for the time being.
Here is ALL the code for the breadboarded encoder switch - it may help someone to just get started with one of those ebay rotary encoders:
char buf[256];
#define ROTARY_ENCODER_SW 2
#define ROTARY_ENCODER_EA 3
#define ROTARY_ENCODER_EB 4
int switch_count;
byte rotation_buffer[256];
byte rotation_head, rotation_tail;
bool ReadSwitch()
{
if (switch_count>0)
{
switch_count--;
return true;
}
else
return false;
}
// 1=cw, 2=ccw
void insert_to_rotation(byte bDirection)
{
rotation_tail++;
if (rotation_tail>=sizeof(rotation_buffer)) rotation_tail=0;
// fix overflow issues here, tail overtakes head
rotation_buffer[rotation_tail]=bDirection;
}
// 0 nothing, 1=cw, 2=ccw
byte ReadRotation()
{
if(rotation_tail!=rotation_head)
{
rotation_head++;
if (rotation_head>=sizeof(rotation_buffer)) rotation_head=0;
return rotation_buffer[rotation_head];
}
else
return 0;
}
void switch_interrupt()
{
switch_count++;
}
void ea_interrupt()
{
byte ea=digitalRead(ROTARY_ENCODER_EA)==HIGH?1:0;
byte eb=digitalRead(ROTARY_ENCODER_EB)==HIGH?1:0;
//snprintf(buf,sizeof(buf),"rotation ea:%u - eb:%u\r\n",ea,eb);
//Serial.print(buf);
int iRotation = (ea ^ eb ) ? 2 : 1 ;
insert_to_rotation(iRotation);
}
void setup()
{
Serial.begin(115200);
snprintf(buf,sizeof(buf),"in setup\r\n");
Serial.print(buf);
pinMode(ROTARY_ENCODER_SW, INPUT);
pinMode(ROTARY_ENCODER_EA, INPUT);
pinMode(ROTARY_ENCODER_EB, INPUT);
switch_count=0;
rotation_head=rotation_tail=0;
attachInterrupt(0, switch_interrupt,FALLING);
attachInterrupt(1, ea_interrupt,CHANGE);
}
long lLastTime=millis();
void loop()
{
if (millis() - lLastTime > 1000)
{
lLastTime=millis();
snprintf(buf,sizeof(buf),"in loop()\r\n");
Serial.print(buf);
}
if (ReadSwitch())
{
snprintf(buf,sizeof(buf),"switch is pressed\r\n");
Serial.print(buf);
}
byte rotation=ReadRotation();
if (rotation!=0)
{
snprintf(buf,sizeof(buf),"rotation %s\r\n",rotation==1?"cw":"ccw");
Serial.print(buf);
}
delay(250);
}