Quadrature rotary encoders, also known as rotary pulse generators, are popular input devices for embedded platforms, including Arduino. Several rotary encoder code examples are posted on Arduino site and elsewhere, however, they treat encoder as a pair of switches, adding decoding/debouncing overhead. For many years, I used an algorithm based on the fact that quadrature encoder is a Gray code generator and if treated as such, can be read reliably in 3 straight step without need for debouncing. As a result, the code I’m using is very fast and simple, works very well with cheap low-quality encoders, but is somewhat cryptic and difficult to understand. Soon after posting one of my projects where I used rotary encoder to set motor speed i started receiving e-mails asking to explain the code. This article is a summary of my replies – I’m presenting small example written for the purpose of illustrating my method. I’m also going through the code highlighting important parts.
The hardware setup can be seen on title picture. The encoder from Sparkfun is connected to a vintage Atmega168-based Arduino Pro. Common pin of the encoder is connected to ground, pins A and B are connected to pins 14 and 15, AKA Analog pins 0 and 1, configured as digital inputs. We also need a serial connection to the PC to receive power, program the Arduino, and send program output to the terminal. For this purpose, Sparkfun FTDI basic breakout is used.
Connecting encoder pins to pins 0 and 1 of 8-bit MCU port makes encoder reading code very simple. If analog pins are needed for something else, it is possible to move encoder to digital pins 8,9 or 0,1 (losing serial port) with no modification of code logic. While technically using any two consecutive port pins is possible with a bit of tweaking, using non-consecutive pins for encoder input with this method is not recommended. Lastly, it sometimes hard to determine which encoder pin is A and which is B; it is easier to connect them at random and if direction is wrong, swap the pins.
Example code is posted below. It is complete sketch – you can copy it from this page and paste into Arduino IDE window, compile, upload and run. The result of rotating the encoder can be seen in terminal window. Note that serial speed in the sketch is set to 115200, you will need to set your PC terminal to that speed as well. The explanation of the code is given after the listing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | /* Rotary encoder read example */ #define ENC_A 14 #define ENC_B 15 #define ENC_PORT PINC void setup() { /* Setup encoder pins as inputs */ pinMode(ENC_A, INPUT); digitalWrite(ENC_A, HIGH); pinMode(ENC_B, INPUT); digitalWrite(ENC_B, HIGH); Serial.begin (115200); Serial.println("Start"); } void loop() { static uint8_t counter = 0; //this variable will be changed by encoder input int8_t tmpdata; /**/ tmpdata = read_encoder(); if( tmpdata ) { Serial.print("Counter value: "); Serial.println(counter, DEC); counter += tmpdata; } } /* returns change in encoder state (-1,0,1) */ int8_t read_encoder() { static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0}; static uint8_t old_AB = 0; /**/ old_AB <<= 2; //remember previous state old_AB |= ( ENC_PORT & 0x03 ); //add current state return ( enc_states[( old_AB & 0x0f )]); } |
First three #defines set names for port pins. This way, if you want to use different pins for your encoder, you can do it here without sifting through code looking for pin names. If you’d like to move encoder pins to Arduino pins 8 and 9, ENC_PORT shall be defined as PINB, and for pins 0,1 as PIND.
In setup() function hardware is initialized. pinMode sets the pin as input so we can read it and digitalWrite turns on pull-up resistor on the pin so it won’t dangle in the air when switch is open. Finally, the serial port is initialized and message is printed as an indicator that port is working – if you can’t see “Start” on your terminal screen, check PC port settings.
Let’s go down to line 30, where read_encoder() function starts. enc_states[] array is a look-up table; it is pre-filled with encoder states, with “-1″ or “1″ being valid states and “0″ being invalid. We know that there can be only two valid combination of previous and current readings of the encoder – one for the step in a clockwise direction, another one for counterclockwise. Anything else, whether it’s encoder that didn’t move between reads or an incorrect combination due to switch bouncing, is reported as zero.
Note that old_AB is declared static. This means that it’s value will be retained between function calls therefore previous encoder reading will be preserved. When function is called, old_AB gets shifted left two times (line 36) saving previous reading and setting two lower bits to “0″ so the current reading can be correctly ORed here. Then ENC_PORT & 0×03 reads the port to which encoder is connected and sets all but two lower bits to zero so when you OR it with old_AB bits 2-7 would stay intact. Then it gets ORed with old_AB (line 37, old_AB |= ( ENC_PORT & 0×03 )). At this point, we have previous reading of encoder pins in bits 2,3 of old_AB, current readings in bits 0,1, and together they form index of (AKA pointer to) enc_states[] array element containing current state – either increment, decrement, or no change. All that is left to do is return this state to the calling function, and line 38 does just that. Upper half of old_AB gets zeroed by old_AB & 0x0f – if we don’t do this, we will be reading memory past enc_states[] array.
The above paragraph was pretty long explanation for mere 5 lines of code (counting two declarations); if you are reading this, 98% of work is done. The rest of the code is very easy. We are now moving up to line 17, where loop() function is residing. counter variable is the one which is modified by encoder and tmpdata is used to hold the reading. On line 22, encoder is read. If encoder state has changed, i.e., read_encoder() returned non-zero, current value of counter variable is printed and then counter is updated with tmpdata. This is done within conditional statement on lines 22-27. The loop then ends and starts again.
Now let’s briefly talk about real-life applications of this method. If you’re using Sparkfun encoder, you’ll notice that it gives 4 increments per click and it’s impossible to make it hold position between clicks. Therefore, to count clicks you will need to divide counter variable by 4. Also, if counter larger than 255 is necessary, the declaration for it would have to be changed to uint16_t or longer. Another nice modification, left out to keep code simple, would be to move enc_states[] array to program memory using PROGMEM type saving several bytes of precious RAM.
In order for this method to work well, read_encoder() function needs to be called fairly often . To see what happens when loop is slow, lower serial port speed – go to line 13, change “115200″ to “9600″, recompile the sketch and run (don’t forget to reconfigure the terminal on PC side). Picture on the left shows the result. Note that encoder goes down from 237 to 229, then jumps up to 230, then continues going down. Sometimes it counts correctly but gives 2 or 3 states per click instead of 4. Instabilities like this are good indication of slow loop. If encoder is used to things like setting motor speed or sound volume, this behavior won’t do much harm because encoder will never skip back more than one state and overall counting would still be in the right direction. However, if encoder is used for interfacing with display, glitches like that are very annoying. Therefore, for user interface applications, loop time should be kept short and in some cases it may be even necessary to convert encoder reading function into interrupt service routine. Given the size and speed of encoder read function, such conversion can be done without much difficulty.
To summarize: I presented a method to convert quadrature rotary encoder output into direction information using look-up table. The method makes debouncing unnecessary, and as a result, conversion function implemented with this method is very short, fast, and works very well on small 8-bit MCU platforms, such as Arduino.
Oleg.
[EDIT] I posted a new article describing reading an encoder from an ISR.
Related posts:



Ok thanks a lot – that is really helpful. But as soon as I integrated this code with some more complex code I have using pulseIn() function to understand the input of a standard RC receiver, and a motor driver controlling two motors, the encoder seems totally incorrect: When I had the the same functions working on the encoder, I was getting super clean values. But when I had more code and the encoder stuff running side by side, my counter values are totally off (see below, when spinning the encoder round and round) . So it seems like the timing is totally not working –
I’m wondering what I might be doing wrong because nothing too “high level” is happening in my code, and I took out all my “println”s.
I can attach my code if that’d be helpful, but was wondering your thoughts / experience (re using 2 encoders with some other processes going on in Arduino UNO)
Thank you so so much again for all the help and explanation.
¾ÖReady
Counter value2: 0
Counter value2: 255
Counter value2: 0
Counter value2: 255
Counter value2: 0
Counter value2: 255
Counter value2: 0
Counter value2: 255
Counter value2: 0
Counter value2: 255
Counter value2: 254
Counter value2: 253
Counter value2: 254
Counter value2: 253
Counter value2: 252
Counter value2: 251
Counter value2: 252
Counter value2: 251
Counter value2: 252
Counter value2: 251
Counter value2: 252
Counter value2: 251
Counter value2: 252
Counter value2: 251
Counter value2: 252
Counter value2: 253
Counter value2: 252
Counter value2: 251
Counter value2: 252
Counter value2: 251
Counter value2: 250
The code you’ve added is blocking somewhere. You need to either fix it or switch to interrupt-driven encoder reading, like this one -> https://www.circuitsathome.com/mcu/rotary-encoder-interrupt-service-routine-for-avr-micros
Hm. Yea I commented everything out and the encoders work great. When I add back in stuff to read the receiver , that’s the point at which the encoders seem to stop working:
Channel2Value = pulseIn (RX1, HIGH, 20000); //read RC channel 2
Channel1Value = pulseIn (RX3, HIGH, 20000); //read RC channel 2
// Serial.print(” motor_body : “);
I’m wondering what you mean by the code is blocking somewhere… or what a “fix” would be ? ?
Thanks a lot again
Blocking is when a piece of code is waiting for some event to happen, interrupting the flow of execution of the main loop. For example,
delay()is blocking; the program won’t continue until time specified as delay has expired. If you have a delay of one second in your main loop, you can rotate your encoder full circle and miss every pulse.Fixing means finding blocking code and writing it differently. What does ’20000′ mean in
pulseIn?Ok I guess I have to use an interrupt? I wanted to be able to use Arduino UNO with the two encoders. Would there be a way to use two encoders with Arduino UNO , even if the two default interupt pins are 2 and 3?
the third parameter (20000) is the time out in microseconds.
just fyi, PulseIN() : Reads a pulse (either HIGH or LOW) on a pin. For example, if value is HIGH, pulseIn() waits for the pin to go HIGH, starts timing, then waits for the pin to go LOW and stops timing. Returns the length of the pulse in microseconds. Gives up and returns 0 if no pulse starts within a specified time out. so it seems the problem is it “delays” and waits (ie blocking)
Yes, try interrupt code and see if it works for you. The code uses interrupt on change, you can configure any pin to be a source of this interrupt.
I was wondering why you needed a timeout of 2000? Would the serial not still work fine without? (I think that timeout may be an example of blocking).
Hi Oleg
I really appreciate your tutorials, they are great and am wondering if you could put me in the right direction as I am building a bi-directional people sensor and counter in a room using two infrared sensors (S1 and S2).What I want is the first sequence on breaking S1 and S2 the change should signal entry and increment a counter whilst breaking the beam in the second sequence S2 first and S1 will signal exit and decrement a counter and display the count value on a 3 digit 7 segment display.
I have thought of using the idea of reading two rotary encoder using external interrupts on an arduino. Is this the right way to go??
Much Thanks.
Steffano.
I can’t see any encoder application here. Your project is quite straightforward, just code your sensor states directly.
Hi Oleg,
Please could you kindly elaborate on what you mean by coding the sensor states directly?since it’s a sequence.
am using IR sensors with make and break beam.I will appreciate any more information.
Thank you.
Staffano.
Hi Oleg,
This is the far have come so far and am serial printing the counts. Any idea how I could hold old value and update this old value with the current val count to a 3 digit seven segment display? depending on how the sensors are tripped? Any correction or suggestions am very willing to learn
int counter, S1_SIG=0, S2_SIG=1;
void setup(){
attachInterrupt(0, S1_RISE, RISING);//hooked to pin2 on mega 2560
attachInterrupt(1, S2_RISE, RISING);//hooked to pin3
Serial.begin(115200);
}//setup
void loop(){
}
void S1_RISE(){
detachInterrupt(0);
S1_SIG=1;
if(S2_SIG==0)
counter++;//entry
if(S2_SIG==1)
counter–;//exit
Serial.println(counter);
attachInterrupt(0, S1_FALL, FALLING);
}
void S1_FALL(){
detachInterrupt(0);
S1_SIG=0;
if(S2_SIG==1)
counter++;//entry
if(S2_SIG==0)
counter–;//exit
Serial.println(counter);
attachInterrupt(0, S1_RISE, RISING);
}
void S2_RISE(){
detachInterrupt(1);
S2_SIG=1;
if(S1_SIG==1)
counter++;//entry
if(S1_SIG==0)
counter–;//exit
Serial.println(counter);
attachInterrupt(1, S2_FALL, FALLING);
}
void S2_FALL(){
detachInterrupt(1);
S2_SIG=0;
if(S1_SIG==0)
counter++;//entry
if(S1_SIG==1)
counter–;//exit
Serial.println(counter);
attachInterrupt(1, S2_RISE, RISING);
}
Steffano
I managed to do my 2 ISR for the two sensors and the 3 digit seven segment display, however the only problem have got is when my sensors are broken at first the count goes negative for 2 or 3 cycles then comes to positive(normal) does anyone have an idea how to correct this??please. I have declared static int i in loop.
Hi, thanks for the great tutorial. I adapted your code (with minor changes) to run on an STM32F100B (ARM) micro. Works flawlessly with my encoder without the need for any hardware debouncing circuitry!
Hi Mr. Oleg,
Good day to you and thanks for the great effort helping a rookie like me to start with the arduino, I have tried your code using Uno and it works flawlessly but when i tried it using the Leonardo, it didn’t work, wandering what could be the problem.
Hi Mr. Oleg,
Below is the code I used with Uno and Leonardo, it works with Uno but not with Leonardo.
/* Rotary encoder read example */
#define ENC_A 14
#define ENC_B 15
#define ENC_PORT PINC
// constants won’t change. They’re used here to
// set pin numbers:
const int buttonPin = 2; // the number of the pushbutton pin
const int ledPin = 13; // the number of the LED pin
// variables will change:
int buttonState = 0; // variable for reading the pushbutton status
int switch0=0;
void setup() {
/* Setup encoder pins as inputs */
pinMode(ENC_A, INPUT);
digitalWrite(ENC_A, HIGH);
pinMode(ENC_B, INPUT);
digitalWrite(ENC_B, HIGH);
// initialize serial communication at 9600 bits per second:
Serial.begin(115200);
Serial.println(“Start”);
delay(1);
// initialize the LED pin as an output:
pinMode(ledPin, OUTPUT);
// initialize the pushbutton pin as an input:
pinMode(buttonPin, INPUT);
}
void loop(){
static uint8_t counter = 0; //this variable will be changed by encoder input
int8_t tmpdata;
/**/
tmpdata = read_encoder();
if( tmpdata ) {
Serial.print(“Counter value: “);
Serial.println(counter, DEC);
counter += tmpdata;
}
// read the state of the pushbutton value:
buttonState = digitalRead(buttonPin);
// check if the pushbutton is pressed.
// if it is, the buttonState is HIGH:
if (buttonState == LOW) {
// turn LED on:
digitalWrite(ledPin, HIGH);
// print out the state of the button:
if (switch0 == 0){
Serial.print(“Pinindot”);
delay(1);
Serial.println(buttonState);
delay(1); // delay in between reads for stability
switch0=1;
}
}
else {
// turn LED off:
digitalWrite(ledPin, LOW);
if (switch0==1){
Serial.print(“Pinindot”);
delay(1);
Serial.println(buttonState);
delay(1); // delay in between reads for stability
switch0=0;
}
}
}
/* returns change in encoder state (-1,0,1) */
int8_t read_encoder()
{
static int8_t enc_states[] = {0,-1,1,0,1,0,0,-1,-1,0,0,1,0,1,-1,0};
static uint8_t old_AB = 0;
/**/
old_AB <<= 2; //remember previous state
old_AB |= ( ENC_PORT & 0×03 ); //add current state
return ( enc_states[( old_AB & 0x0f )]);
}
The debounce did not work for me but instead I used the switch which is perfect. The only output shown in the Serial Monitor is when the switch is being pressed but not the encoder, I am using an encoder with a switch.
Ok, I get it and I like it a lot. The only thing that I cannot figure out is how to keep it from going from 255 to 0 or from 0 to 255. Is there a way that I can stop it from overflowing the register and just park it at max or min until the user decreases the value?
Using two or more encoder with the Arduino ?
It all depends on the total of tasks being performed by the Arduino, and the number of
pulses received by the processor.
Say for example you have a small slow moving xy table, with 2 rotary US Digital encoders generating say 4000 pulses per second and driving two stepper motors with an “Easydriver” board you would soon be running out of head room on your project.
My suggestions would be to use, two Tiny13 chips from Atmel to encode the rotary encoders, and than feed the resultant signal into your Arduino, where it will give an excellent performance. In my humble opnion it may even support three channels, XY and Z.
You would need some machine code experience to do this, but this would be worth learning.
Good luck.
Hi,
Thank you for this great useful article… I’m wondering if I can use you idea with Arduino Uno?? I mean to use the rotary encoder with Arduino Uno in order to select the power of my RC car which I’m building…
thanx again
UNO should work.
Hi,
Sweet bit of code! I am using it in a project at work, using the encoder to display a motor speed selection on a seven segment display and it works very nicely indeed. Once you remove the serial.print commands it is difficult to spin the bare encoder shaft quickly enough to generate a misread. With a suitably sized knob on the shaft it is going to slow rotation speed and make it virtually impossible for the casual operator to get an erroneous reading…
Thanks!