This shows the setup on breadboard with the Pi header brought out via a 40 pin ribbon onto the breadboard, a single MCP23S17 configured at address 0 – i.e. A0-A2 tied to Vss and a single PEC16 rotary encoder wired to pins GPA1/2

Logging into the Pi, loading the device driver and instantiating the chip at its address on SPI bus 0, on CS-0 and address 0 we can set the GPIO’s as inputs and enable the internal pull-up 100kΩ resistors we can then read the state of all the GPIO’s  and expect the result to be 16 1’s

Copy to Clipboard

Hmm – that result was not what I was expecting…!! After a little head scratching and checking I hadn’t goofed up the wiring I thought I’d take a look at what was going on using an oscilloscope, and noting that the read results were not even consistent readGPIO() now returning 0 so with Chan-A on CLK and Chan-B on MOSI and serial decoding in the bottom portion.-

For a start the signal looks pretty ragged, perhaps laying out on an unshielded breadboard isn’t the most optimal but the signals look horrid, and after zooming in on the blue CLK signal I see why – it’s indicating the clock is running at around 125MHz – according to the Microchip datasheet these expander chips are only rated for 10MHz maximum clock…..

After a quick look at how the driver is calling the SPI library (i.e. in /usr/local/lib/python3.5/dist-packages/RPiMCP23S17/ I see the spi device name is  _spi, and a quick look at the SPIDEV module I see there is a max speed option –


Lets read what it’s set to and then set it to something sensible like 10MHz – the manufacturers rated max clock speed and re-test –

Copy to Clipboard

So better but still not correct – the device initialisation code is setting  all the outputs to be pull-up so a read of them should show all 1’s…

The issue I suspect is that after instantiating the object with the open command, the driver has tried to initialise the device at then default 125MHz clock speed and only after initialisation am I resetting the clock speed to a saner value.

One could modify the source library located in –


However if at some later point I update the driver I’ll lose my edits, so my strategy will be to make a local copy of the library within my working directory, so that if for whatever reason the driver is updated I’ll have a reminder of the changes I’ve made.

Lets assume my local copy is called, then to import it as opposed to the original library we can –

>>> from MCP23S17 import MCP23S17

To fix the immediate issue we need to put the _spi.max_speed_hz is immediately after the spi bus is opened –

Copy to Clipboard

Now we should be able to consistently read all 16 GPIO’s, below I’ll sequentially short to Vss – 0v pairs of the GPIO and read the device using the readGPIO() method –

Copy to Clipboard

Ok, so it looks like we can successfully read the GPIO pins, however it looks like if the most significant or subsequent bits are zero the read is returning less than 16 bits.

Looking at the bus on the scope shows that the SPi bus is returning the full 16 bits with leading zero’s so somewhere within either the SPi driver or the readGPIO() function these are being dropped.

Because (as I have observed) these rotary switches are capable of resting at a detent position with one or both outputs at zero then we need to cater for this condition and since in extreme all of the GPB bank could for example be zero then we cannot safely assume that the resultant 8 bits that would be returned all came from the GPA bank pins.

As I can’t see in either the SPi driver code nor the MCP23S17 library where this glitch is being introduced lets workaround it instead by reading each bank separately, bitstuff with zero’s if less than 8 bits in length and then join the two banks to reconstitute the 16 bit register.

As can be seen in the scope view of a read operation the SPi is returning the leading zero, but something downstream is obviously dropping it.

Lets modify our readGPIO function so that we get the register data into a more consistent state. To do this I’m using the bitstream library since it make bit manipulations quite simple, and in particular use the BitArray function since this then makes iterating through the returned bytes easy –

Copy to Clipboard

So we can successfully read the GPIO pins, lets see if we can reliably read the rotary encoder and deduce the direction of rotation and number of detent -clicks.

This is not as simple as it appears because these switches bounce quite a bit-  the switch transitions are not clean  – to be fair they are built at a very low cost such that you can buy them in quantities at less than a $ each. The drawback is that if you look at their electrical characteristics at the sub-millisecond level you realise that they bounce quite a bit and unless you pay maybe $30 each for optical encoder or perhaps magnetic (Hall effect) switches we’ll have to find some way of taming the output signals. One method is to dampen the switch by slugging it with a resistor/capacitor between the switch contacts and Gnd – the precise methodology is described far more eloquently than I could manage by this guy Jack Ganssle, if you’re up for the low down and dirty I thoroughly recommend reading it!

Long story short, you can characterise a switch and then measure the appropriate parameters and use a combination of a capacitor and 2 resistors to minimise the bounce and hence get reliable and predictable responses, however the issue I have is that whilst doing this for say a commercial audio mixing desk where maybe you have a hundred or so of these switches, because they are relatively co-located you have a pretty constant environment one switch to another – or at least have the opportunity to equalise the track lengths such that the impedance of each are similar, whereas I on the other hand are using these switches across my house – the wiring lengths, interference from mains cables and even Ethernet data cables are going to be different for each individual switch.

What’s the solution? – we can minimise switch bounce in hardware striking a happy medium between dampening the switch bounce, yet maintaining the responsiveness if a rotary switch is turned fast, and kill all the false signals by using logic since we know given a switches current state we can predict what the possible next state should be (one click clockwise/anticlockwise) 🙂

Here’s how…