Getting rotary encoders working with an Arduino Uno
I’ve been doing a bunch of stuff with an Arduino Uno recently. In particular, one of those things is trying to get an incremental rotary encoder (in my case, an ALPS STEC11B04) working with the Uno. I thought this would be a simple task - wire it up, write some code, and it’s all good. Oh, how wrong I was.
Overview
Here’s a quick overview on how rotary encoders work electrically. You connect one of the legs (the centre one on the ALPS above) to ground, and the other two legs are connected to both your Arduino and a pull-up resistor to 5V. When you turn the encoder, the legs are shorted to ground. Here’s a diagram, courtesy of the ALPS data sheet: (Note that you’d also connect an Arduino to terminals A and B in order to read off the data.)
While we’re on the subject, if you are an idiot like me and forget to include pull-up resistors in your
circuit design, and don’t realise until it’s too late to change it, don’t worry - turns out Arduinos
have built-in pull-up resistors
you can use - just use pinMode(pin, INPUT_PULLUP)
. (If you use the encoder
library mentioned later, it even does that for you!)
Okay, but what kind of signal do you get? Well, according to the data sheet again, one like this:
That is, when you turn the rotary encoder clockwise, it shorts terminal A to ground, then terminal B.
If you turn it anticlockwise, it shorts terminal B to ground, then terminal A. Therefore, all you need to
do is to keep a record of what was shorted when and see which came first to determine the direction of rotation.
In fact, there’s a really nice library from pjrc.com - the PJRC Encoder Library -
which does all of this for you, such that you just call encoder.read()
and it’s all good.
Problem
However, when I actually wired all of this up and sent some example test code, a problem manifested itself:
the rotary encoder only counted in one direction, and somewhat sporadically so at that! Even more strangely,
this problem only seemed to present itself when my code was doing lots of things - calling Serial.println(encoder.read())
in a
tight loop worked fine, but doing anything more complex made the encoder go all funny.
Going back to the PJRC library documentation, it became apparent that I had made a bit of a mistake in my circuit design. To quote that website:
Encoders have 2 signals, which must be connected to 2 pins. There are three options.
- Best Performance: Both signals connect to interrupt pins.
- Good Performance: First signal connects to an interrupt pin, second to a non-interrupt pin.
- Low Performance: Both signals connect to non-interrupt pins, details below.
Going to the ‘details below’:
If neither pin has interrupt capability, Encoder can still work. The signals are only checked during each use of the read() function. As long as your program continues to call read() quickly enough, you will get accurate results. Low resolution rotary encoders used for dials or knobs turned only by human fingers are good candidates for low performance polling mode. High resolution encoders attached to motors usually require interrupts!
The problem I had, of course, was that I hadn’t connected the rotary encoder to interrupt-capable pins - meaning that
the call to read()
wasn’t happening quickly enough to detect the very fast pulses, and so the rotary encoder library
malfunctioned. Usually, the solution to this problem would have been to plug the encoder into different pins. On the Uno,
these were pins 2 and 3. (There’s a full table on that PJRC website). However, I couldn’t do this: I had already made
a PCB with this flaw, and pins 2 and 3 were being used for something else!
A bodgy (but still functional) solution
So, the rotary encoder wants to be polled quickly, eh? Well, the Arduino Uno has a hardware timer capable of running some code
at a predefined interval - in fact, it has three, and they’re usually used for tasks like updating the counter that powers the millis()
function (which tells you the number of milliseconds since execution began), and for driving the pulse-width modulation (PWM)
signals on some pins - on the Uno, pins 9 and 10 (again, there’s another helpful PJRC website
that explains this). So, I decided to just attach an interrupt service routine (ISR) to the timer that would poll the rotary
encoder at a suitably fast interval, allowing it to work properly.
Instructions
Here’s what I did, if you want to follow along at home.
- Step 1: Download the TimerOne library from the arduino-timerone project on the Google Code Archive. (The PJRC one I linked above works slightly differently; I haven’t tested it with that one, and my example code probably won’t work with it.)
- Step 2: Add the library to the Arduino IDE (Sketch > Include Library > Add .ZIP Library…), and include it (
#include <TimerOne.h>
). - Step 3: Define an interrupt service routine that will poll the encoder. Here’s a minimal example: (yes, I called the rotary encoder ‘rotato’ - I was getting driven somewhat insane by the amount of time spent debugging this problem…)
Note that the rot
variable was declared as volatile
- this tells the compiler that it might be changed unpredictably (by
an interrupt service routine), and thus that it can’t optimize away loads/stores to it - it has to assume that the variable
could have changed, every time you read from it.
- Step 4: Attach the interrupt service routine to run at a sane interval. I went ahead and defined some
init_rotato()
anddeinit_rotato()
functions that would attach and detach the routine respectively:
- Step 5: When you want to use the encoder, call
init_rotato()
, then read from therot
variable. When you’re done, calldeinit_rotato()
.
It’s important to note that the interrupt service routine tends to screw up lots of things, like writing to an OLED screen (and
sometimes the serial interface as well - I can’t remember if it broke serial as well, to be honest, but it definitely broke
the screen I was using - update: @xrisk on GitHub suggests that it wouldn’t
have broken serial after all!).
As such, you shouldn’t do anything potentially time-sensitive after calling init_rotato()
.
In my code, I just wanted to detect when the encoder was turned a bit, so I did something like this:
(Note: abridged from actual code, may not actually work.) This function takes a pointer to an index variable, and increments it by 1 or decrements it by 1 if the user turns the rotary encoder right or left (respectively).
- Step 6: Enjoy an actually working rotary encoder (hopefully!)
Peroration
Hopefully this was mildly useful. (I certainly would have hoped for a post like this after spending hours figuring out what was wrong with the damn rotary encoder!) As I said earlier, it really pays off to do your research on new stuff before just wiring it into your project willy-nilly - had I known about the interrupt pin thing, I would have avoided all this palaver.
The solution proposed in this blog post may not even be necessary for your use-case - if it works fine just calling read()
, you
don’t need the interrupt service routine. However, I personally didn’t want to take any chances, and found that the ISR method
worked pretty reliably - now, the rotary encoder broke the other components (by running an ISR every 1ms), instead of the other way
round (with some components’ libraries taking tons of time to do their thing, meaning that the rotary encoder wasn’t polled quickly
enough).
Anyway, thanks for reading. I’m trying to write a bit more frequently nowadays, so tune in again soon™ for the next crazy blog post about some esoteric topic!
Syndications
This blog post has been syndicated to the following places (feel free to leave a comment there!):