IMPLEMENTED SO FAR

- Support for 4x20 LCD Display and large number display
- Brightness and contrast adjustment with remote
- (OPUS/Wolfson WM8741) DAC volume control: remote and rotary encoder
- (OPUS/Wolfson WM8741) DAC random filter selection 1 to 5 with remote
- (OPUS/Wolfson WM8741) DAC upsampling selection (L, M, H -this is the OSR setting)
- I2C level shifting (5V to 3.3V)
- Optimized power-up sequence

Monday, June 1, 2009

Arduino Code for Rotary Encoder

(Update: I've improved the code. Take a look here. You have the choice to do h/w deboucing or s/w debouncing or both. The s/w debouncing code is just one line of code)

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()
{
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
}
}
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.

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 2
digitalWrite(VOLUPPIN, HIGH); // Enable pull-up resistor

pinMode(VOLDOWNPIN, INPUT); // Pin 4
digitalWrite(VOLDOWNPIN, HIGH); // Enable pull-up resistor
Note: 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.

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:

dweeb4 said...

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

dweeb4 said...

Actually, cheaper in Digikey http://search.digikey.com/scripts/DkSearch/dksus.dll?Detail&name=NHD-0420E2Z-NSW-BBW-ND

Anonymous said...

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!

Anonymous said...

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;
}

Anonymous said...

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

cash said...

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);
}
}

Anonymous said...

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

Sazha said...

#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 ?

Jim Martin said...

Would please tell me what rotary encoder you are using? ...and thanks alot for posting all the info.

Greatly appreciated...

Jim

Sazha said...

hi , here is the encoder that i'm using : http://www.robotshop.ca/5v-mechanical-quadrature-encoder.html

Anonymous said...

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.

The Learned Nerd said...

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!!

The Lazy Engineer said...

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