Today it is often not necessary to add external IO to a micro controller. If you realise a shortage of pins, you can just step up to the next package and get additional pins without any hassle. But what if the package is set for some reason, you want to add new functionality to an existing project or it just makes sense for the board layout? There are many reasons why external IO might be a good solution. But as always it comes with its pros and cons:

  • Pros
    • Possible extension of existing projects
    • Often easier routing
    • IO features like enhanced current capabilities or voltage level shifting
  • Cons
    • Requires CPU cycles for bus access
    • Slower access
    • Higher complexity (no register mapping)
    • Additional cost/parts

I came across this problem several times now and often used the very common Microchip port expanders like the MCP23008. Such a device offers 8 to 16 configurable pins which can be inputs or outputs with pull-up/down resistors. Actually those parts work great but they can be very expensive depending on your project. Another downside is the protocol overhead which you get as a trade-off for the flexibility of those devices. In case you only want to have a fixed number of inputs or outputs the good old 74 series might be a better solution.

I assume it is widely known that the SPI-Bus is ideal for the connection of shift registers to a micro controller. The 74 logic series offers a wide range of such registers. Especially the 74xx595 and the 74xx165 are good candidates for port expansion.

74HC595 connection to the SPI-Bus

The 74xx595 is a serial-in parallel-out (SIPO) shift register with an output latch, it offers eight outputs and can be directly connected to a standard SPI bus which is set to CPOL=0 CPHA=0. The only downside is that if you don’t want the outputs to have an unpredictable state before the first transfer, you also have to control the output enable line. In case you only want to control some LEDs you might not care and set the output enable permanently.

74HC165 connection on the SPI-Bus

The 74xx165 is a parallel-in serial-out (PISO) shift register with an input latch, it offers eight inputs. Unfortunately it is a bit less compatible to the SPI defacto standards. It is still possible to use it but you need an active high chip enable and invert the SPI clock (CPOL=1 CPHA=0). Unfortunately this makes it also incompatible with the 74xx595. But what if you need inputs and outputs for your project and you don’t want to mess around with two busses, different chip select lines or clock polarity settings?

The solution is quite simple with another part from the 74 series. Just an inverter e.g. 74xx04 on the latch input and on the clock line of the 74xx165 will do the job. Now the PISO also works with a negative chip select and the common CPOL=0 CPHA=0 settings. The 74xx595 and the 74xx165 can co-exist on one SPI bus and even better: you just need one SPI transfer to set the outputs and read the inputs.

Connecting 74HC595 and 74HC165 on the same SPI-Bus

Another great feature of this circuit is its expandability. If you need 8 more inputs or outputs, you just add another shift register. For the 74xx595 this can be achieved by connecting the nG, RCLK, nSCLR and SCKL in parallel and the serial output (QHser) with the new chips serial input (SER). The same for the 74xx165: connect SH and CLK in parallel and the serial output (QH) of the new chip to the serial input (SER).

The presented circuit assumes an own bus for the port expansion. If you want to share the bus with other peripherals, you need to get the SPI MISO line in a tri-state if the chip select line is high. This can be achieved with a tri-state buffer like the 74xx125.

Tri-state MISO for shared bus access

There are many derivatives of the shift registers and other parts that can be used for the circuits shown above.

  • 74HCT595 / 74HCT165 – Only for 5V systems
  • 74HC595 / 74HC165 – OK for 3.3V applications
  • 74LVC595 / 74LVC165 – 3.3V system + 5V tolerant
  • 74LVC2G04 Dual inverter (in case you don’t have space for the 74xx04)
  • 74LVC1G125 – Single tri-state buffer for bus sharing (replaces 74xx125)
  • TPIC6B595 – High current version of the 74xx595 with open drain outputs
  • etc.

All parts are generic and you can get them from different manufacturers. This allows you to buy all required ICs for as low as 0.20 USD (already @100 qty) from one of the major brands and even cheaper from a no-name brand .

This should be enough information to get you started on the hardware side, but what about the software? Most things on the cons list are software related. Can we get around some of the problems? Maybe! At least we don’t have any protocol overhead. In case of the circuit in the last picture, we transfer 8 bits with which we set the state of 8 outputs and simultaneously read the state of 8 inputs. With the right hardware we can even offload the CPU cycles to the DMA controller and speed up the transfer rate without causing any CPU load. If we reserve a fixed address for the DMA transfer we can even resemble a “register” for the input and output states. In order to use this mechanism it is crucial that the SPI controller supports to set the chip select line in the right way. The controller has to pull the CS line low on the beginning of a transfer and reset it to high after it. Most microcontrollers that I know have this feature but there are exceptions (most of the STM32 controllers).

Connected 74xx595 and 74xx165 (up-counting of the outputs on 74xx595 and read back via 74xx165). CS and SCK directly connected to 74xx165