SPI MCP23S17 and RGB Rotary Encorders

Not open for further replies.


Well-known member
Discussion on using the SPI MCP23S17 GPIO expander to communicate with Sparkfun’s RGB Rotary Encoders with Sumotoy’s library, with Teensy 3.5 (for 5v tolerance).

I am in the process of building a controller, part of which is a 16 step sequencer. I decided to use these Sparkfun RGB rotary encoders with SPI bus. I found a lot of discussion in a range of threads on the forum around using the MCP23S17 SPI port expander chips, and also using these in connection with the SParkfun RGB led Rotary Encoders. However, there were a lot of gotchas there weren’t obvious from the discussions, and the information was scattered around the web. Hopefully this will act as a resource for anyone considering something similar.

Here is a proof of concept module, showing two of 16 step encoders
There are a number of different libraries to use, but I found the Sumotoys was stable and relatively easy to use, allowing access to the range of registers to manipulate the ports. These chips are very useful as they remove the need for polling, and work by using dedicated interrupt pins. For me, these chips allow both the freeing up from constant polling, and the freeing up of pins by using a single SPI bus for eight chips (= 16 RGB rotary encoders). The encoders and the chips work with fiver volts. I have used the Teensy 3.5 for five volt tolerance.

The RGB rotary encoders:

The MCP23S17 is produced by Microchip:

Some of the relevant posts are:



A video of the rotary encoders working with their RGB and some ws2811 leds being used to visualise the encoder values.

The MCP23S17 chips have two ports that can be manipulated to act as all input, or output, or have individual bits of the each port act as an input while others are outputs. This is done via setting the registers. This allows us to use the chips to both read and write to each of the eight bits via SPI. These chips are addressable and allow up to eight chips in a chain on the same SPI, this is termed HAEN, or Hardware Address Enable Bit. This is a limitation of having only three hardware address pins, which are either tied to HIGH or LOW (zero or 1) to give one of eight possible addresses.

Using the encoders combined with the expander chip allows for one encoder per port. Each of the rotary encoder requires three pins for the LEDS (R, G and B), two pins for the encoder, and one for the button. The encoder also has power and ground pins. So, for each of the two ports, six out of eight bits will be used.

Sumotoy’s library can be found here:
Download and put into your Arduino IDE library area. There is a good description of the general principles, but it took a lot of trial and error to resolve setting the registers for both input and output (example given below).

The approach I used was modular, that is, testing a single chip with two encoder ports first. Here is the schematic
Schematic for two rgb rotary encoders with MCP23S17

The register set up is not easy to figure out. Below are the settings using the ports for hybrid input/output, with interrupts.
The three key register changes to the standard setup are:
mcp0.gpioRegisterWriteByte(mcp0.IOCON, 0b00101000);//HAEN,SEQOP,MIRROR -remove mirror (ie INT pin connection)
mcp0.gpioRegisterWriteByte(mcp0.GPPU, 0xff, true);//pull-up only required input pins???
mcp0.gpioRegisterWriteWord(mcp0.GPINTEN, 0x1f1f);// enable interrupts on these

  pinMode(R0_pinA_T35, INPUT);//interrupt pin to input
  pinMode(R0_pinB_T35, INPUT);//interrupt pin to input
  Serial.begin(38400);//for debug
  SPI.setSCK(14);//set SPI clk pin to avoid led pin 13
  mcp0.begin();//x.begin(1) will override automatic SPI initialization
  mcp0.gpioRegisterWriteByte(mcp0.IODIR,0b00011111, true);//zero = output //gpio 0 to 3 = output, gpio 4 to 7 = input
  //setup the gpio!
  /*        7     6     5       4     3   2     1    0
  mcp0.gpioRegisterWriteByte(mcp0.IOCON, 0b00101000);//HAEN,SEQOP,MIRROR -remove mirror (ie INT pin connection)
  mcp0.gpioRegisterWriteByte(mcp0.GPPU, 0xff, true);//pull-up only required input pins???
  mcp0.gpioRegisterWriteWord(mcp0.GPINTEN, 0x1f1f);// enable interrupts on these
  mcp0.gpioRegisterReadByte(mcp0.INTCAP);    //read interrupt capture port A (it clear port)
  mcp0.gpioRegisterReadByte(mcp0.INTCAP + 1);//read interrupt capture port B (it clear port)
  attachInterrupt(R0_pinA_T35, keypressA, FALLING);//attack interrupt mcp0 portA
  attachInterrupt(R0_pinB_T35, keypressB, FALLING);//attack interrupt mcp0 portB

MCP23S17 register setup.png
Last edited:
previously ive used majenko's library on mega2560 and teensy, however, i dropped them in favour of writing my own quad function read/write of chip's registers/pins

i would recommend majenko's as ive been using it for the past few years and its really easy to use.

Also be aware of the chips errata for the addressing of the upper 4 chips, they basically mirror each other as the HAEN is not set when configured
The command byte is different and I don't even see them listed in both the libraries, but I implemented it in mine and successfully controlling 6 of the 8 chips, yes, i have alot of inputs and outputs.

Last edited:
thanks for the headsup. Thats strange as my tests have all 8 addresses functioning.
Also be aware of the chips errata for the addressing of the upper 4 chips, they basically mirror each other as the HAEN is not set when configured
I have only tested using port bits for outputs, using leds on each output bit, and the led responds to HIGH or LOW bit setting for each address combination 0 to 7 (all +ve). I will run further tests on address responsiveness. So far though, I have not chained eight MCP23S17 together, but used two on the same SPI but changed chip addresses. Did this issue only manifest when eight chips were used?
this test will ensure if its good or not, set chip #5 to all inputs then read register on chip #6,7 and 8, so if its all input pins. then switch the chip #5 to output, if chip #6,7 and 8 are all set to outputs, the proper command byte, which should be sent right after the initial one when initializing the chips, was not done, effective mirroring each other
you need to initiate 2 different commands side by side to initialize lower and upper chips before doing commands to the chips. a feature i worked hard on and developing, is my code for the mcp23s17 (of 8 banks per cs) is also hot pluggable, and unlimited chips can be controlled by the same 4 small functions which reduces alot of overhead and doesnt waste extra resources on classes

mcpDW(14, 0x27, 12, 1); digitalwrite
CS, chip address, pin, high/low

mcpDR(14, 0x24, 5); // digital read
cs, address, pin

mcpRR(14, 0x25, 0x12); // read register of chip address 0x25 gpio register bank a
mcpRW(14, 0x25, 0x13); // write register of chip address 0x25 gpio register bank b

Last edited:
the issue starts when you add the 6th, you wont notice it with 5 as it is running alone

im not using interrupts in my setup tho, so i didnt need to write extra code, the spi is pretty fast polling as it is in my 24/7 setup
Last edited:
Great, useful info thanks. Will do a fuller test with the eight MCP chain and check for mirroring with some more probing code.
@tonton81. Have run further tests with chip addresses 6 to 8 and they do seem to switch from input to output as expected, independently of each other. I used set all to output for chip6, while holding chip 8 to mix of input output, each responded as expected, then switched 6 to all output, and results were as expected with independent in/out control for each chip. Will keep an eye out for mirroring however. One point, I set the pins to the in/out mix at startup, and leave for the whole time, I do not try to switch from output to input during runtime.

Quick update. Have a proof of concept working for for RGB rotary encoders as part of the 16 step sequencer.

A short video is at:

This is the second stage in building the 16 step sequencer. this proof of concept shows the rotary encoders being used to set each step note. At the moment this is sending a midi to ableton live. In this test, each note is taken from a scale array, when the rotary knob increases or decreases, it moves through the scale array values. This array was C major. Fixed at the moment, but selectable later. The test also looks at step loop length, going from one, two, and four steps by reading the analogue value from the potentiometer. The proof also uses a potentiometer to adjust the clock speed and send this to ableton live. These rotary encoders have built in RGB leds, the code changes between blue and red to signify if the step note should be played or silent.

This setup uses MCP23S17 port expanders. These are SPI based, and work on a single chain, with each of the MCP chips having a separate hardware address. The chain length is limited to 8 chips, but each chip has enough GPIO pins to cater for two of the rotary encoders. These RGB Rotary encoders are from Sparkfun.

There are still some issues with the smoothness of the rotary encoders turns being registered. I will need to review the code that assess increments, as well as looking at whether each of the interrupts is working. Further work is also required to deal with more than one interrupt being fired at the same time.

Also, still need to resolve potential issue with using the WS2811 leds with a library that might clash with interrupts.
Last edited:
so, i made some progress on the 16 step sequencer based around the mcp23s17 and spark fun rotary encoer. Here is a short video of it:
early test of the first panel of a step sequencer. This uses rotary encoders from spark fun, with RGB leds and push button. The GPIO port expanders are all on a single SPI bus, these are MCP23s17 chips. The test uses the onboard Teensy 3.5 SD card to hold a pattern in memory. This in the step on/off value and the note value. The memory slot is recalled and loaded to the sequencer by a button push. The midi bpm is controllable, as well as step number and direction. The sequencer test sends midi signals to Ableton Live for testing . These GPIO expanders also have the capability of using interrupts, but this was proving difficult to manipulate, and so the current setup is only using polling in the loop.
Last edited:
update: 16 step midi sequencer using rgb rotary encoder finally working. Heres a video. It is using polling for the 8 mcp23s17 chips as interrupts was proving difficult.

using the cardboard as a temporary place holder. underneath looks like this:
16 rgb rotary.jpg
Step Sequencer update

Well, thanks to KurtE SPIN library, and Sumotoy's MCP23s17 library, I finally have the 16 rgb rotary encoder step sequencer functioning, along with the adafruit trellis and the ili9341 screen. Using three trellis button grids for momentary and push and hold menu options at the same time. Only using the sequencer to test as a usb controller for Ableton live at the moment, but all functioning well. Using the rotary encoders to change note value for each step. The faceplate is Acrylic, and had it lasercut locally at a hobby store. The homemade acid etch pcbs for the rotaries seem to be holding out, and the 8 chain MCP chips are working well with just polling, not using interrupts.



16 step sequencer update

Finally have a solid four voice 16 step sequencer running using the rotary encoders. currently running usbMidi four channels/voices to Ableton. Here is an example of a fugue from the teensy step sequencer. the original idea for this sequencer is to produce a fugue, where the simple original melody is repeated by four separate voices, using different timing and instrument to create harmonies brought about by a melody/theme that lends itself to tonal augmentation. Plenty of jobs still to do regarding sync options for bar length etc.

Very nicely done.

In the early schematic you show a C104 and 10K to ground for the A and B outputs.
Q1. What value for C104 did you settle on?
Q2. Is this similar to the circuit on the A and B outputs you used in your video of the PCBoard ?
Q3. I am doing a new version of a color generator. Would you be willing to sell the stuffed boards?


@RichardFerraro - i stuck with 100nf capacitors. the circuit is fundamentally the same as the PCBoard used here: https://forum.pjrc.com/threads/64850-16-rgb-rotary-SPI-port-expander-panel-testers

I have one of those boards remaining, which you may have for free. It has one error on it, a small ground trace was missed, but I have rectified this manually. However, the panel is fixed for 16 rotaries, and not easily split for smaller numbers. the dimensions are:

The rotary shafts are 22mm apart in the x8 direction, and 25mm part in the 2x direction.
The whole panel is 46mm by 183mm.

PM me if you would like one, and I will post to you.

all the best

Thank you so much. I have built three previous generations of my palette generator (specifically for led panels). I have been using i2cEncoder boards which work great - but require unmanageable labor.

I would really appreciate receiving a board.
[address deleted]

Hope all is well in London.

thanks again,

Last edited by a moderator:
@RIchard, will get it in the post in the next day or two. Board is populated with everything other than the rotary encoders.
feel free to delete you address from above message, I have taken a copy.

all the best
Not open for further replies.