Reading a Rotary Encoder from a Raspberry Pi


I wanted to attach a knob to my Raspberry Pi to act as a vol­ume con­trol for my MPD based jukebox. Tra­di­tion­ally vol­ume con­trol devices are imple­mented with poten­tiome­ters act­ing as inputs for ampli­fiers. A poten­tiome­ter is a hard­ware device with a knob or a slider that, in a word allows for a vari­able volt­age input into a cir­cuit. 1 The prob­lem here is that this is a very hard­ware ori­ented method of set­ting vol­ume, when what I really wanted to do was set the software vol­ume set­ting on the Rasp­berry Pi. I wanted to be able to change the vol­ume from the oper­at­ing sys­tem (a la alsamix­er) or through the MPD client, and still be able change it up or down by phys­i­cally turn­ing the knob on the device. Essen­tial­ly, I wanted the ALSA PCM ele­ment vol­ume to increase as I turned the knob clock­wise, and decrease as I turned the knob coun­ter­clock­wise, I wanted to actual vol­ume to be inde­pen­dent of the posi­tion of the knob and because a poten­tiome­ter is an absolute input device which returns it’s posi­tion and can­not be turned indef­i­nitely there was no way to make one work for what I needed.

What I needed was a rotary encoder. A rotary encoder, is in essence a pair of switches which change as a shaft is turned, and by read­ing those switch­es, one can tell whether and in which direc­tion the shaft is turn­ing. A rotary encoder can­not tell absolute posi­tion, but it does­n’t need to, plus it can spin infinitely.2 This, of course, was exactly what I need­ed. Unfor­tu­nately there isn’t a lot of infor­ma­tion on get­ting a rotary encoder work­ing with a Rasp­berry Pi.

I did some research and found a number of articles about get­ting a rotary encoder to work with an Arduino. This code could be adapted to the Rasp­berry Pi, but it’s impor­tant to note that an Arduino has a real-­time oper­at­ing sys­tem (that is, it has almost no oper­at­ing sys­tem) so pro­grams can exe­cute with­out the unpre­dictable delays of time-shar­ing/­mul­ti­task­ing, buffer­ing, or garbage col­lec­tion. The Rasp­berry Pi is a Linux machine, which is a time-shar­ing sys­tem, so it can’t make guar­an­tees to the mil­lisec­ond about per­for­mance the same way an Arduino can. This can be a prob­lem because to prop­erly read a rotary encoder, a pro­gram needs to track every change of the switch­es, else one could get back­ward or con­fused read­ings.

For­tu­nately this is a solv­able prob­lem. The Rasp­berry Pi has inter­rupts, which allows us to trig­ger func­tions upon switch read­ing changes, and the wiringPi library makes it easy to use them. So, here’s what I ulti­mately did:

Con­nect­ing a rotary encoder could­n’t be much sim­pler. Rotary encoders only have three pins.3 The mid­dle pin is ground and the oth­ers are con­nected to the switches and pro­duce the A sig­nal and B sig­nal respec­tive­ly. You can con­nect these pins to the Rasp­berry Pi direct­ly. Rotary Encoder pins:

connection diagram

And how to con­nect it to the Rasp­berry Pi:

connection diagram

Using the built in pul­l-up resis­tors on the Rasp­berry Pi for the pins con­nected to A and B means that when the respec­tive switches are closed, the pins will read low (0) and when the switches are open, the same pins will read high (1). There’s no need for any addi­tional resis­tors or capac­i­tors. As the encoder shaft is turned, the switches will alter­na­tively open and close so that they progress through a series of states known as Gray Code. These states are as follows:

00, 01, 11, 10

By read­ing any two suc­ces­sive states, one can tell which direc­tion the encoder is turn­ing. 00->01 might be clock­wise while 00->10 might be coun­ter-­clock­wise. Using inter­rupts, we can get the Pi to record each change of a pin and to record the direc­tion the encoder is turn­ing. Here’s my code:

//rotaryencoder.h
//17 pins / 2 pins per encoder = 8 maximum encoders
#define max_encoders 8

struct encoder
{
    int pin_a;
    int pin_b;
    volatile long value;
    volatile int lastEncoded;
};

//Pre-allocate encoder objects on the stack so we don't have to
//worry about freeing them
struct encoder encoders[max_encoders];

/*
  Should be run for every rotary encoder you want to control
  Returns a pointer to the new rotary encoder structer
  The pointer will be NULL is the function failed for any reason
*/
struct encoder *setupencoder(int pin_a, int pin_b); 

and:

//rotaryencoder.c
#include <wiringPi.h>

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include "rotaryencoder.h"

int numberofencoders = 0;

void updateEncoders()
{
    struct encoder *encoder = encoders;
    for (; encoder < encoders + numberofencoders; encoder++)
    {
        int MSB = digitalRead(encoder->pin_a);
        int LSB = digitalRead(encoder->pin_b);

        int encoded = (MSB << 1) | LSB;
        int sum = (encoder->lastEncoded << 2) | encoded;

        if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoder->value++;
        if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoder->value--;

        encoder->lastEncoded = encoded;
    }
}

struct encoder *setupencoder(int pin_a, int pin_b)
{
    if (numberofencoders > max_encoders)
    {
        printf("Maximum number of encodered exceded: %i\n", max_encoders);
        return NULL;
    }

    struct encoder *newencoder = encoders + numberofencoders++;
    newencoder->pin_a = pin_a;
    newencoder->pin_b = pin_b;
    newencoder->value = 0;
    newencoder->lastEncoded = 0;

    pinMode(pin_a, INPUT);
    pinMode(pin_b, INPUT);
    pullUpDnControl(pin_a, PUD_UP);
    pullUpDnControl(pin_b, PUD_UP);
    wiringPiISR(pin_a,INT_EDGE_BOTH, updateEncoders);
    wiringPiISR(pin_b,INT_EDGE_BOTH, updateEncoders);

    return newencoder;
}

This is a library, so I can include it in my project with the code:

#include "rotaryencoder.h"

And by mak­ing sure to include the library at com­pile time (eg):

gcc -lwiringPi program.c rotaryencoder.c

I can setup a new encoder using the code:

struct encoder *encoder = setupencoder(pin_a, pin_b);

And I can then get the cur­rent rota­tion of the encoder with:

encoder->value;

That’s all there is to it. The encoder object is allo­cated on the stack (Eight are pre-al­lo­cat­ed, which is the max­i­mum num­ber you pos­si­bly use,) so there is no need to free them. Sim­ply query the cur­rent rota­tion with “encoder->value” when­ever you need it.

I put my code on Github so you can get it here: https://github.com/astine/rotaryencoder/blob/master/rotaryencoder.c

  1. Potentiometers are called ‘pots’ for short and their very common. You probably interact with them every day even if you don’t know it. If you’ve ever used a physical volume control on an amplifier or a pair of speakers or an amplifier or a knob on a dimmer switch, that knob was probable attached to a ‘pot’. 
  2. Rotary encoders are probably nearly as common as pots, but they only work with digital devices. If the knob spins infinitely, it might be rotary encoder. 
  3. unless the encoder has a built-in press switch or led, which many do. 

Last update: 30/01/2013

blog comments powered by Disqus