Reliable use of mechanical encoders requires two components: a hardware tweak and (of course) code. Although there are many sample code for rotary encoder, I feel the following is the simplest code for rotary encoders. This version is interrupt-based.
The interrupt routine simply detects direction of motion:
void decoder()Here we connect terminal A of the rotary encoder to Arduino's digital pin 4 and terminal B to digitalpin 2. The third terminal of the encoder is connected to GND. In this configuration the rotary encoder pulses will be pulling the pins to GND. In this example, we are controlling volume so one direction is volume up and the other direction is volume down.
{
if (digitalRead(4) == digitalRead(2))
{
volUp = 1; //if encoder channels are the same, direction is CW
}
else
{
volDown = 1; //if they are not the same, direction is CCW
}
}
We attach the interrupt with:
attachInterrupt(0, decoder, CHANGE);and we detects CHANGE of the pulse. We need to detect rising edge and falling edge of the pulse in order to utilize the full resolution of the rotary encoder.
During set up, we want to enable the pull-up resistors for pin 2 and pin 4 with:
pinMode(VOLUPPIN, INPUT); // Pin 2Note: it doesn't matter which channel is connected to which pin to start with. You can always switch the connections if you expect the opposite result.
digitalWrite(VOLUPPIN, HIGH); // Enable pull-up resistor
pinMode(VOLDOWNPIN, INPUT); // Pin 4
digitalWrite(VOLDOWNPIN, HIGH); // Enable pull-up resistor
Then in a loop we detect when the flags are set and do accordingly:
while(volUp==1) // CW motion in the rotary encoder
{
volUp=0; // Reset the flag
// Do something
}
while(volDown==1) // CCW motion in rotary encoder
{
volDown=0; // clear the flag
// Do something else
}
13 comments:
Great work as usual glt (Lazy engineer) - I came across this LCD - too late for this project but maybe for next/others http://ie.mouser.com/Search/ProductDetail.aspx?qs=sGAEpiMZZMt7dcPGmvnkBq9RSAvEJGmRspzq0zQda%252bA%3d
It's 146 X 62.5 X 14.5 mm - quite a bit bigger than the standard
Actually, cheaper in Digikey http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail&name=NHD-0420E2Z-NSW-BBW-ND
dweeb4, That is one BIG LCD. Won't fit in my case though :-). I'm happy with the 9x large numbers. Also will have to use parallel communication unless you get a serial adapter. Thanks for your comments!
Hello,
I am the newest bee ever.
I have a performance problem between arduino mega and an incremental encoder (WDG 58-10-2500-ABN-r05-L3) and am trying to optimize my arduino code.
I have a problem with this code above. I am not able to understand which interrupt number is for which pins.
I use 3 interrupts, for encoder A (interrupt 0, pin 2), 1 for encoder B (interrupt 1, pin 3)and for Index (interrupt 5, pin 18).I found this code below and try to modify it accordingly, can you give suggestions pls?
Thanks!!
#define encoder0PinA 2
#define encoder0PinB 3
#define encoder0Index 18
volatile unsigned int encoder0Pos = 0;
unsigned int tmp_Pos = 1;
boolean doPrint = false;
int valR = 0;
void setup() {
//int x;
pinMode(encoder0PinA, INPUT);
pinMode(encoder0PinB, INPUT);
pinMode(encoder0Index, INPUT);
pinMode(13, OUTPUT);
// encoder pin on interrupt 0 (pin 2)
attachInterrupt(0, doEncoderA, CHANGE);
// encoder pin on interrupt 1 (pin 3)
attachInterrupt(1, doEncoderB, CHANGE);
// encoder pin on interrupt 5 (pin 18)
attachInterrupt(5, doEncoderIndex, CHANGE);
Serial.begin (9600);
}
void loop(){
//Check each second for change in position
if (tmp_Pos != encoder0Pos) {
tmp_Pos = encoder0Pos;
Serial.println(encoder0Pos, DEC);
}
if (doPrint == true) {
Serial.print("R:");
Serial.println(encoder0Pos, DEC);
doPrint = false;
}
}
// Interrupt on A changing state
void doEncoderA(){
// look for a low-to-high on channel A
if (digitalRead(encoder0PinA) == HIGH) {
// check channel B to see which way encoder is turning
if (digitalRead(encoder0PinB) == LOW) {
encoder0Pos += 1; // CW
}
else {
encoder0Pos = encoder0Pos - 1; // CCW
}
}
else // must be a high-to-low edge on channel A
{
// check channel B to see which way encoder is turning
if (digitalRead(encoder0PinB) == HIGH) {
encoder0Pos = encoder0Pos + 1; // CW
}
else {
encoder0Pos = encoder0Pos - 1; // CCW
}
}
}
void doEncoderB(){
// look for a low-to-high on channel B
if (digitalRead(encoder0PinB) == HIGH) {
// check channel A to see which way encoder is turning
if (digitalRead(encoder0PinA) == HIGH) {
encoder0Pos = encoder0Pos + 1; // CW
}
else {
encoder0Pos = encoder0Pos - 1; // CCW
}
}
// Look for a high-to-low on channel B
else {
// check channel B to see which way encoder is turning
if (digitalRead(encoder0PinA) == LOW) {
encoder0Pos = encoder0Pos + 1; // CW
}
else {
encoder0Pos = encoder0Pos - 1; // CCW
}
}
}
void doEncoderIndex(){
doPrint = true;
valR = encoder0Pos;
}
Hello newest bee ever,
A couple of suggestions:
1- You need to turn on the pull-up resistors with digitalWrite
2- You can vastly simplify the code if you are only checking for turning direction on the rotary encoder (look at the code I wrote)
Hope this helps.
3- You can also post your question at the Arduino forums
I have found that the original code was totally unreliable. The use of interrupts without hardware debounce only makes a bad situation worse.
So, I started to write the code using a fixed timing for taking regular samples. I thought that the millis() function should be the way to start, but found it to be unreliable. I did finally find a library MsTimer2 that works very well. I set it to generate a 1 millisecond sampling rate. Within the function flash() is the code to sample each encoder phase 12 times (12 successive 1 millisecond samples). I submit the following code, which satisfies my need for a reliable encoder input:
#include
#include
int ledPin = 13; // LED connected to digital pin 13
int ENCODE_A = 2;
int ENCODE_B = 3;
boolean CW = HIGH;
int counter = 0;
int oldCounter = 0;
void flash()
{
static uint16_t stateA = 0; // Current debounce status A
static uint16_t stateB = 0; // Current debounce status B
stateA = (stateA<<1) | digitalRead(ENCODE_A);
stateB = (stateB<<1) | digitalRead(ENCODE_B);
// High to Low transitions
if ((stateA == 0xf000) && (stateB == 0xffff)) counter++;
if ((stateB == 0xf000) && (stateA == 0x0000)) counter++;
// Low to High transitions
if ((stateA == 0x0fff) && (stateB == 0x0000)) counter++;
if ((stateB == 0x0fff) && (stateB == 0xffff)) counter++;
// High to Low transitions
if ((stateB == 0xf000) && (stateA == 0xffff)) counter--;
if ((stateA == 0xf000) && (stateB == 0x0000)) counter--;
// Low to High transitions
if ((stateB == 0x0fff) && (stateA == 0x0000)) counter--;
if ((stateA == 0x0fff) && (stateB == 0xffff)) counter--;
}
void setup()
{
// sets the digital pin as output
pinMode(ledPin, OUTPUT);
//During set up, we want to enable the pull-up resistors
//for pin 2 and pin 4 with:
pinMode(ENCODE_A, INPUT); // Pin 2
digitalWrite(ENCODE_A, HIGH); // Enable pull-up resistor
pinMode(ENCODE_B, INPUT); // Pin 3
digitalWrite(ENCODE_B, HIGH); // Enable pull-up resistor
MsTimer2::set(1, flash); // 1ms period
MsTimer2::start();
Serial.begin(9600);
}
void loop()
{
if (counter != oldCounter)
{
oldCounter = counter;
Serial.println(counter,DEC);
}
}
Yes, without the capacitors added to the rotary encoder, the s/w I used is totally unreliable. However, the h/w mod is very simple and the code is very simple and does nothing if you are not turning the knob
#include
#include
int ledPin = 13; // LED connected to digital pin 13
int ENCODE_A = 2;
int ENCODE_B = 3;
boolean CW = HIGH;
int counter = 0;
int oldCounter = 0;
void flash()
{
static uint16_t stateA = 0; // Current debounce status A
static uint16_t stateB = 0; // Current debounce status B
stateA = (stateA<<1) | digitalRead(ENCODE_A);
stateB = (stateB<<1) | digitalRead(ENCODE_B);
// High to Low transitions
if ((stateA == 0xf000) && (stateB == 0xffff)) counter++;
if ((stateB == 0xf000) && (stateA == 0x0000)) counter++;
// Low to High transitions
if ((stateA == 0x0fff) && (stateB == 0x0000)) counter++;
if ((stateB == 0x0fff) && (stateB == 0xffff)) counter++;
// High to Low transitions
if ((stateB == 0xf000) && (stateA == 0xffff)) counter--;
if ((stateA == 0xf000) && (stateB == 0x0000)) counter--;
// Low to High transitions
if ((stateB == 0x0fff) && (stateA == 0x0000)) counter--;
if ((stateA == 0x0fff) && (stateB == 0xffff)) counter--;
}
void setup()
{
// sets the digital pin as output
pinMode(ledPin, OUTPUT);
//During set up, we want to enable the pull-up resistors
//for pin 2 and pin 4 with:
pinMode(ENCODE_A, INPUT); // Pin 2
digitalWrite(ENCODE_A, HIGH); // Enable pull-up resistor
pinMode(ENCODE_B, INPUT); // Pin 3
digitalWrite(ENCODE_B, HIGH); // Enable pull-up resistor
MsTimer2::set(1, flash); // 1ms period
MsTimer2::start();
Serial.begin(9600);
}
void loop()
{
if (counter != oldCounter)
{
oldCounter = counter;
Serial.println(counter,DEC);
}
}
Hi for this code , why did you leave the two prepocessor directives blank? sorry im quite new for arduino , so what are those supposed to be ?
Would please tell me what rotary encoder you are using? ...and thanks alot for posting all the info.
Greatly appreciated...
Jim
hi , here is the encoder that i'm using : http://www.robotshop.ca/5v-mechanical-quadrature-encoder.html
The one used in the project documented in this blog is this: $.99 panasonic: http://www.goldmine-elec-products.com/prodinfo.asp?number=G16267
But in order to make it work with my code you need to solder some capacitors like this: http://hifiduino.blogspot.com/2009/05/blog-post_06.html
Hope this helps.
dude... THIS IS EXACTLY what i needed for my project!!!
works perfect, easy to implement and understand... THANK YOU!
I too had issues with debounce code..so i used your cap. solution... worked like a charm!!
Learned Nerd:
I am happy it worked for you. My s/w skills are just not good enough to implement a s/w debouncer. But I think a h/w-s/w combination is a more elegant solution
Post a Comment