mortonkopf
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:
https://www.sparkfun.com/products/10982
The MCP23S17 is produced by Microchip:
http://www.microchip.com/wwwproducts/en/MCP23S17
Some of the relevant posts are:
https://forum.pjrc.com/threads/35879-MIDI-Controller-using-16-Rotary-Encoders?
https://forum.pjrc.com/threads/34666-16-SPI-devices-Is-it-possible?
https://forum.pjrc.com/threads/26875-Library-for-MCP23S17-plus-rotary-encoders?
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:
https://github.com/sumotoy/gpio_expander
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
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:
https://www.sparkfun.com/products/10982
The MCP23S17 is produced by Microchip:
http://www.microchip.com/wwwproducts/en/MCP23S17
Some of the relevant posts are:
https://forum.pjrc.com/threads/35879-MIDI-Controller-using-16-Rotary-Encoders?
https://forum.pjrc.com/threads/34666-16-SPI-devices-Is-it-possible?
https://forum.pjrc.com/threads/26875-Library-for-MCP23S17-plus-rotary-encoders?
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:
https://github.com/sumotoy/gpio_expander
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
Code:
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
IOCON = BANK MIRROR SEQOP DISSLW HAEN ODR INTPOL -NC- */
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
Last edited: