Rotary Encoders on the i2c bus

I have been getting to grips with rotary encoders on the Arduino, and to add a little drama I have gotten this working on the i2c bus. Here I will be showing how to set up the necessary hardware and demonstrate a program for the Arduino. I have used a similar setup to control an LED RGB light strip, with three rotary encoders to control the Red, Green and Blue and a fourth for special effects.

The i2c bus

The i2c bus allows connection of multiple devices to the Arduino on just two wires, these can be just about anything from temperature sensors to motor controllers with each device having its own address, up to eight of these can be used using just two wires from the Arduino.

For this project I’ll be using a single MCP23017 port expander, with which I can add sixteen digital I/O pins to the Arduino

MCP23017 pinout

The address for the expander is set on pins 15, 16 and 17 (A0, A1 and A2), for a single encoder set all of these to ground. Should you require more expanders the addresses can be set as in the table below, the MCP address is for the Arduino program.

hardwired address i2c
A2 A1 A0
000 GND GND GND 0x20 0
001 GND GND 3v3 0x21 1
010 GND 3v3 GND 0x22 2
011 GND 3v3 3v3 0x23 3
100 3v3 GND GND 0x24 4
101 3v3 GND 3v3 0x25 5
110 3v3 3v3 GND 0x26 6
111 3v3 3v3 3v3 0x27 7

Rotary Encoders

The  encoders I’m using are the SparkFun 12-step rotary encoder with integrated push-button

rotary encoder

Inside they have mechanical contacts that output two square waves when rotated, A and B these are 90o out of phase with each other so when rotated clockwise output A is ahead of B, and counter-clockwise output B takes the lead, this is a two bit Grey code.

rotary encoder square wave
rotary switch outputs (source: datasheet)

So by comparing the two outputs we can determine the direction of rotation.

A Test Circuit

My test setup comprises of two rotary encoders, one Arduino Uno, one MCP23017 port expander, and a couple of resistors. External pull-up resistors are not required on the GPx input ports as the MCP23017 has these internally. Encoder A uses GPA0, GPA1 and GPA2 for the push button. Encoder B is on GPA4, GPA5, and GPA6.

MCP23017 rotary encoder test circuit


My program uses the Adafruit MCP23017 and standard wire libraries. The Adafriut library addresses the GPx ports from 0-15, so GPA2 is 2, and GPB2 is 9. I have written this to output the state of rotation to the serial port at 9600 baud.



18 thoughts on “Rotary Encoders on the i2c bus”

  1. Hi Karl this is a great explanation! How would you go about using more than one MCP23017? I have 2 of them connected but would love my code to group all encoders in the same array, to easily map to a single encoder data array. Thanks for letting me know

  2. Helle Karl,

    i came across your tutorial about rotary encoder on the I2C bus and first of all I want to say thank you for sharing your expirence.

    What I like to know before I start to implement this into my project, would it be easy to connect a second and a third MCP23017. I want to connect 16 encoder and 16 buttons via the mcp23017 but i’m not shure if this will work out with your code.



    1. Sebastian
      This should be OK in theory, I’ve not tried it. Don’t forget to set the address on each MCP, then in the software initialise additional MCP’s , eg: Adafruit_MCP23017 mcp1; and in the setup add mcp1.begin(1); and in other places add in extras along the lines what exists already for mcp0. Don’t forget though that i2c isn’t particularly fast and there are limits to how many encoders you can sensibly use.

      1. Hey Karl,

        thank you for your reply and we still could manage to get 8 encoder working on an mcp23017. but our problem ist to address the second one. unfortunately we couldn’t get it to work. do you might be have any input for us to solve this problem? i think it can’t be really so hard to address the second mcp but we are might be missing something.

  3. Hi, thanks for your tutorial!

    I have a little issue, I tried your library and it works as expected but if I turn the encoders too fast it doesn’t work properly, every now and then it reads a CCW for a CW and viceversa.

    Have you experienced such problem? Might be the way I soldered the board?

    Thanks for your help 🙂

    1. Hello Francesco,
      Yes I had that problem too, I got round it by not turning the knobs too fast :-). I seem to remember reading that these types of encoder are a bit poor at high speeds, something to do with the way they are constructed. I suppose you could add some de-bounce logic to the program, and try adding de-bounce hardware (google will help you with this).

      1. Hi, thanks for your reply!

        Before using MCP23017 I used a single rotary encoder and I had no issues with speed, and now I realise that this library ( has all these features:

        – Debounce handling with support for high rotation speeds
        – Correctly handles direction changes mid-step
        – Checks for valid state changes for more robust counting / noise immunity
        – Interrupt based or polling in loop()
        – Counts full-steps (default) or half-steps

        So, now I’ll explore these subjects and learn about them 🙂


  4. My MCP will stop detecting rotations and buttons pressed after a while and then starts detecting again. Could you please help?

  5. Anyway, I was able to figure it out on other posts on the net. Basically, you cannot connect the rest pin directly to +5V. You must connect it to +5V trough a 1K Ohm resistor. Solved my issue. Now it works continuously. Hopefully this will help someone.

  6. Hi there,
    Question, you are assigning INPUT’s and Pullups to pin[0] and[1] both used for encoder A?
    mcpX.pinMode(pin[0], INPUT); // A
    mcpX.pullUp(pin[0], HIGH);
    mcpX.pinMode(pin[1], INPUT); // B
    mcpX.pullUp(pin[1], HIGH);

    it seems to me you are only adressing the first Rotary encoder
    Should’t it be like this? :
    mcpX.pinMode(pin[0], INPUT); // ENC_A-A
    mcpX.pullUp(pin[0], HIGH);
    mcpX.pinMode(pin[1], INPUT); // ENC_A-B
    mcpX.pullUp(pin[1], HIGH);
    mcpX.pinMode(pin[3], INPUT); // ENC_B-A
    mcpX.pullUp(pin[3], HIGH)
    mcpX.pinMode(pin[4], INPUT); // ENC_B-B
    mcpX.pullUp(pin[4], HIGH)

    Funny thing though; if you comment those line’s out /* text */
    The sketch still works.

    i am working on a keypad on GPA0-7, and 4 encoders on GPB0-7
    The 4 rotary’s work, but pretty often i get whilst turning CW, CCW value’s.
    it doesnt matter with using external Pullups (10k) or with a debounce circuit)

    Kind regards, and thanks for your work.


    1. Robert,
      The pinMode and pullUP is being set in a loop, look in the setup() you will see:
      // setup the pins using loops, saves coding when you have a lot of encoders and buttons
      for (int n = 0; n < encCount0; n++) { encPinsSetup(mcp0, encPins0[n]); encoders0[n] = 1; // default state } where encPins0 is a 2D array, each element containing the rotaty encoder pins A and B.

  7. I know this is an old blog but it is exactly what I’ve been looking for.
    The problem is that the”MCP23017 rotary encoder test circuit” picture is missing. Broken link?

    1. Looks to be OK now, I recently re-drew the diagram and wordpress has been a bit slow updating.

  8. Hi, i try your code and works fine for one of my encoder (the one on GPA0/GPA1) but i have 4 encoders on GPB0/GPB1 GPB2/GPB3 GPB4/GPB5 GPB6/GPB7 and I can not figure out how to configure the pins.

    here i have to set the number of the encoders
    const int encCount0 = 5; // number of rotary encoders

    const int encPins0[encCount0][2] = {
    {0,1}, // enc:0 AA GPA0,GPA1 – pins 21/22 on MCP23017
    {?,?} // enc:1 BB GPB1,GPA2



Comments are closed.