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.)

wiring diagram

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:

fancy pulses diagram

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…)
#include <TimerOne.h>
#include <Encoder.h>

/* rotary encoder pins */
#define ROTA 13
#define ROTB 12

Encoder rotato(ROTA, ROTB); // rotary encoder object
volatile int rot = 0; // used to store position

void rotato_isr() {
  rot = rotato.read();
}

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() and deinit_rotato() functions that would attach and detach the routine respectively:
void init_rotato() {
  // 1000 microseconds worked okay as an interrupt period.
  // Other periods are available.
  //
  // Really tiny periods, like 1, just break your Arduino - the CPU never
  // has a chance to do anything other than service your ISR.
  Timer1.attachInterrupt(rotato_isr, 1000);
}
void deinit_rotato() {
  Timer1.detachInterrupt();
}
  • Step 5: When you want to use the encoder, call init_rotato(), then read from the rot variable. When you’re done, call deinit_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). 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:

void poll_for_rotation(int* idx) {
    init_rotato();
    int rot_old = rot;
    for (;;) {
      if ((rot - rot_old) > 10) {
        *idx += 1;
	break;
      }
      if ((rot - rot_old) < -10) {
        *idx -= 1;
	break;
      }
    }
    deinit_rotato();
}

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