Last Updated: July 28, 2021
Originally Published: May 29, 2021
Skill Level: Intermediate
Table Of Contents
- Introduction
- What Is Needed
- Background Information
- Building The Circuit
- Installing The Library
- Changing Single Outputs Using Familiar Pin Setting Functionality
- Changing All Outputs Using Binary Values
- Changing All Outputs Using Defined Names
- An LED Cycling Example
- Additional Resources
- Summary
Introduction
Sometimes, a project needs more digital I/O than what is available on your development board. This often happens when you connect to components that require a lot of pins for their interface, e.g. some displays, or your project uses many discrete sensors and/or actuators.
This three-part tutorial teaches you how to add more digital inputs and outputs to your CircuitPython compatible board. Each part focuses on a specific integrated circuit (IC) chip.
Part 1 – The 74HC595 (currently reading) describes how to add digital outputs using the 74HC595 8-bit serial-in parallel-out (SIPO) shift register IC.
Part 2 – The 74HC165 will describe how to add digital inputs using the 74HC165 8-bit parallel-in serial-out (PISO) shift register IC.
Part 3 – The MCP23017 will describe how to add both digital inputs and outputs using the MCP23017 16-Bit I2C I/O Expander With Serial Interface IC.
A basic understanding of electronics and programming is expected along with some familiarity with the CircuitPython ecosystem. If you are new to CircuitPython, or would just like to refresh your knowledge, please see our Getting Started With CircuitPython On Compatible Microcontroller Boards or Getting Started With CircuitPython On Raspberry Pi With Blinka tutorials before proceeding with this one.
This tutorial uses a solderless breadboard to build a circuit from a schematic diagram. The All About Circuit’s Understanding Schematics, SparkFun’s How to Read a Schematic, Core Electronics’ How to Use Breadboards, and Science Buddies’ How to Use a Breadboard guides are good resources for learning how to translate a schematic to a breadboard.
The resources created for this tutorial are available on GitHub for your reference.
What Is Needed
- Linux, macOS, Or Windows Based Computer With A USB Port
- Mu Python Editor (recommended)
- Either A CircuitPython Compatible Microcontroller Board With Compatible USB Cable (available on Adafruit) Or A Raspberry Pi Running Raspberry Pi OS Or Similar Linux Based OS (available on Raspberry Pi Foundation and Adafruit)
- Solderless Breadboard (available on Adafruit and SparkFun)
- Preformed Breadboard Jumper Wire Kit (available on SparkFun and CanaKit)
- 5 x Male/Male Jumper Wires (available on Adafruit and Arrow)
- 74HC595 8-Bit Shift Registers With 3-State Output Register IC (available on Adafruit and Digi-Key)
- 8 x Standard 5mm LEDs (available on Adafruit and SparkFun)
- 0.1 µF Ceramic Capacitor (available on SparkFun and Jameco)
- 8 x 330 Ω Resistors (available on SparkFun and Amazon)
Background Information
The low-cost standard 7400 series 74HC595 IC is a good choice to incorporate into your design when you only need additional digital outputs for your project. It is an 8-bit serial-in parallel-out (SIPO) shift register that provides the ability to serially shift data into the chip and latch that data into separate parallel digital outputs. Data can be shifted from your CircuitPython compatible board into the shift register by using the dedicated SPI serial bus.
A single 74HC595 chip provides 8 additional digital outputs with a single 8-bit data transfer. One nice feature about the ‘595s is that they can be daisy chained together to get even more outputs without utilizing any additional connections to your development board. For instance, incorporating four daisy chained ‘595s into your design will provide you an additional 32 (4 x 8) outputs with a 32-bit data transfer. Daisy chaining simply involves connecting the QH’ output from one ‘595 to the SER input of another ‘595. When more than 8 bits are shifted into one shift register, they continue to be propagated into the next shift register.
There are multiple ways to visualize and send your digital output data to the shift register within a CircuitPython program. Each has its pros and cons and can be heavily dependent on the nature of the additional outputs required. For instance, are the additional outputs highly disparate and need to be manipulated separately, or are they more homogeneous and can be referred to as a single block? I will present three different approaches in this tutorial, all producing the same basic functionality, so that you can easily compare among them and choose the best approach for your own design. While all of these approaches eventually shift and update all the output values in a single write to the 74HC595 shift register, each approach provides a different way to manipulate individual output changes. I will also include a fun little LED animation example at the end.
I am using Adafruit’s Feather M4 Express microcontroller board connected to a macOS based computer with the Mu Python editor for this tutorial. I also verified the CircuitPython program created in this tutorial works on a Raspberry Pi 3 Model B running the Raspberry Pi OS operating system using the Blinka library for CircuitPython support. If you are using a different CircuitPython compatible board, computer setup, or code editor, the vast majority of this tutorial should still apply, however, some minor changes may be necessary.
If you need assistance with your particular setup, post a question in the comments section below and I, or someone else, can try to help you.
Building The Circuit
Before connecting any circuitry to your CircuitPython compatible board, disconnect it from power and your computer. This avoids accidental damage during wiring.
Place the components and wire up the circuit on a breadboard according to the schematic diagram shown below.

Eight LEDs, via 330 Ω resistors, are connected to the shift register’s outputs (QA – QH) constituting the 8 digital outputs being added to the system.
A 0.1 µF bypass capacitor was placed across the Vcc and ground pins of the shift register, as recommended in the 74HC595 datasheet, in order to reduce any power supply noise that may be present.
The OE (13) pin of the 74HC595 IC is tied directly to ground to enable constantly driving outputs. The SRCLR (10) pin is tied directly to 3.3 V to disable hardware based clearing of the shift register.
The SPI_MOSI, SPI_SCK, and D5 pins shown on the schematic should be connected to the associated pins of your development board, e.g. the MO, SCK, and 5 pins of a general CircuitPython compatible microcontroller board or the GPIO10 (SPI0 MOSI), GPIO11 (SPI0 SCLK), and GPIO5 pins of a Raspberry Pi.
The circuit should look similar to the one shown below once completed.

Once the circuit is built, connect your general CircuitPython compatible microcontroller board to your computer with the USB cable. For a Raspberry Pi, connect the power and boot it up.
Installing The Library
Before installing any libraries, make sure you are running the latest stable release of CircuitPython on your compatible microcontroller board or the Blinka library on a Raspberry Pi.
$ pip3 install --upgrade Adafruit-Blinka
We will take advantage of the existing Adafruit_CircuitPython_74HC595 driver library to communicate with the 74HC595 IC in our circuit. On a general CircuitPython compatible microcontroller board, copy the adafruit_74hc595.mpy file from the latest stable bundle to the lib directory of the board’s CIRCUITPY drive. On a Raspberry Pi, install the library with the following command.
$ pip3 install adafruit-circuitpython-74hc595
Changing Single Outputs Using Familiar Pin Setting Functionality
This first approach implements the standard CircuitPython pin setting mechanism by changing the value
attribute of a single pin. It should be very familiar to most CircuitPython users and the easiest to understand. However, when updating multiple outputs, it will involve more shift operations than other approaches since only one output at a time can be changed.
Open Mu or your favorite code editor and create a CircuitPython program with the code shown below.
from time import sleep import board import digitalio import adafruit_74hc595 osr_latch_pin = digitalio.DigitalInOut(board.D5) SHIFT_REGISTERS_NUM = 1 osr = adafruit_74hc595.ShiftRegister74HC595(board.SPI(), osr_latch_pin, SHIFT_REGISTERS_NUM) def change_single_outputs(): # Output pin definitions (pin references) led_0 = osr.get_pin(0) led_1 = osr.get_pin(1) led_2 = osr.get_pin(2) led_3 = osr.get_pin(3) led_4 = osr.get_pin(4) led_5 = osr.get_pin(5) led_6 = osr.get_pin(6) led_7 = osr.get_pin(7) # Set individual LEDs led_1.value = True # turn on LED 1 only sleep(1) led_1.value = False # turn off LED 1 only led_6.value = True # turn on LED 6 only sleep(1) led_6.value = False # turn off LED 6 only sleep(1) # Set multiple LEDs led_0.value = True # turn on even numbered LEDs led_2.value = True led_4.value = True led_6.value = True sleep(1) led_0.value = False # turn off even numbered LEDs led_2.value = False led_4.value = False led_6.value = False led_1.value = True # turn on odd numbered LEDs led_3.value = True led_5.value = True led_7.value = True sleep(1) led_1.value = False # turn off odd numbered LEDs led_3.value = False led_5.value = False led_7.value = False sleep(1) # Main while True: change_single_outputs()
The 74HC595 shift register only needs three pins to communicate with a CircuitPython compatible board. The SPI MOSI pin sends data that is shifted (clocked) into the register with the SPI SCK pin. Together, along with the ignored SPI MISO pin, these pins constitute the SPI serial bus.
The osr_latch_pin
, specified as D5
on line 6, is used to latch the data, after the data has been shifted in, into the register.
The 74HC595 shift register library instance is defined on line 10 as osr
(short for output shift register) and requires references to the board’s SPI port and the latch pin that will be utilized for shifting operations. The last argument, SHIFT_REGISTERS_NUM
defined on line 8, is optional and specifies the number of 74HC595 shift registers that are daisy chained together.
Since we will be applying multiple approaches, I separated each approach into its own distinct function. Therefore, the change_single_outputs()
function, this first approach, is the only function I am currently calling within the main section’s endless loop (while True:
).
Within the change_single_outputs()
function, we first define our named variables, led_0
– led_7
, to make our calls easier to understand. They use the library’s get_pin()
method to retrieve references to the 74HC595’s output pins. The variables are defined within this example function, instead of the typical location at the top of the file, in order to avoid collisions with different definitions in other approaches.
The rest of the function just uses the standard CircuitPython pin setting mechanism to turn on and off a couple of LEDs and then turn on and off either the even or odd numbered LEDs.
Now that our circuit is built and our software is written, let’s run and test our program.
On a general CircuitPython compatible microcontroller board, save the program as code.py to the top level of the board’s CIRCUITPY drive. It will begin running automatically.
On a Raspberry Pi, save the program as output_shift_register.py and then run the program.
$ python3 output_shift_register.py
You should see the 74HC595 shift register’s LED outputs being updated. First, LED 1 turns on, then only LED 6 is on, then only the even numbered LEDs are lit, then only the odd numbered LEDs are lit. This sequence will be continuously repeated.
On a Raspberry Pi, press CTRL-C to exit the program when you are done.
Changing All Outputs Using Binary Values
This next approach is the simplest of all the approaches. It sends a byte of data to the shift register with each bit representing each output. It produces the most concise code, but it does not indicate the meaning of each of the individual outputs. This approach would be a good option to use when all outputs are of the same type and you want to refer to the entire collection as a single entity.
Add the following function to your program, just before the main section,
def change_outputs_with_binary_values(): outputs = osr.gpio # retrieve current shift register output values # Set individual LEDs outputs[0] = 0b00000010 # turn on LED 1 only osr.gpio = outputs # set new shift register output values sleep(1) outputs[0] = 0b01000000 # turn on LED 6 only osr.gpio = outputs sleep(1) outputs[0] = 0b00000000 # turn off all LEDs osr.gpio = outputs sleep(1) # Set multiple LEDs outputs[0] = 0b01010101 # turn on only even numbered LEDs osr.gpio = outputs sleep(1) outputs[0] = 0b10101010 # turn on only odd numbered LEDs osr.gpio = outputs sleep(1) outputs[0] = 0b00000000 # turn off all LEDs osr.gpio = outputs sleep(1)
and then change the contents of the endless loop within the main section to the following so that you’re calling the newly added function instead of the first one.
# change_single_outputs()
change_outputs_with_binary_values()
The library’s current shift register output values (osr.gpio
) are retrieved and assigned to the outputs
variable on line 2. This ensures we are obtaining not only the correct data type for holding the output values, but also the full array of bytes available in the case we are using multiple daisy chained 74HC595 ICs. It also enables us to view the current outputs before they are changed, if we so choose.
As shown on lines 5 and 6, to write values to the shift register, the outputs
variable is updated and then assigned back to the library’s register’s values (osr.gpio
) that initiates the shift operation. If you are using daisy chained ICs, make sure to set the appropriate index (0 for the first 74HC595, 1 for the second 74HC595, and so on) of the outputs
variable when updating values.
Save and run your program to test the code. You should see the same LED sequence as we saw in the previous section.
Changing All Outputs Using Defined Names
This approach is an extension of the previous one using binary values. It adds variables that name each of the outputs (bits) that can then be grouped together before being sent as a single byte. The output values are determined by performing bitwise OR operations with all outputs that are to be set True
(1
). All other outputs default to False
(0
).
Add the following function to your program, just before the main section,
def change_outputs_with_defined_names(): # Output pin definitions (bit positions) led_0 = 0b00000001 led_1 = 0b00000010 led_2 = 0b00000100 led_3 = 0b00001000 led_4 = 0b00010000 led_5 = 0b00100000 led_6 = 0b01000000 led_7 = 0b10000000 outputs = osr.gpio # retrieve current shift register output values # Set individual LEDs outputs[0] = led_1 # turn on LED 1 only osr.gpio = outputs # set new shift register output values sleep(1) outputs[0] = led_6 # turn on LED 6 only osr.gpio = outputs sleep(1) outputs[0] = 0 # turn off all LEDs osr.gpio = outputs sleep(1) # Set multiple LEDs outputs[0] = led_0 | led_2 | led_4 | led_6 # turn on only even numbered LEDs osr.gpio = outputs sleep(1) outputs[0] = led_1 | led_3 | led_5 | led_7 # turn on only odd numbered LEDs osr.gpio = outputs sleep(1) outputs[0] = 0 # turn off all LEDs osr.gpio = outputs sleep(1)
and then change the contents of the endless loop to the following.
# change_single_outputs()
# change_outputs_with_binary_values()
change_outputs_with_defined_names()
Save and run your program and you should see the same LED sequences we saw previously.
An LED Cycling Example
This is just an example of a fun animation (Knight Rider style) that shows how to set outputs a little differently than the previous approaches.
Add the following function to your program, just before the main section,
def cycle_leds(): leds = [osr.get_pin(n) for n in range(8 * osr.number_of_shift_registers)] for position, led in enumerate(leds): if position == len(leds) - 1: break # skip the last LED led.value = True sleep(0.1) led.value = False for position, led in enumerate(reversed(leds)): if position == len(leds) - 1: break # skip the first LED led.value = True sleep(0.1) led.value = False
and then change the contents of the endless loop to the following.
# change_single_outputs()
# change_outputs_with_binary_values()
# change_outputs_with_defined_names()
cycle_leds()
All LED output pins are grouped together into a list (leds
), on line 2, and then for
loops are used, beginning on lines 3 and 8, to cycle through the LEDs from one end to the other with a small delay between shifts. The last LED is ignored in each loop in order to provide the same timing across all LEDs.
Again, save and run your program. The LED’s should be turning on and off in sequence, back and forth, across all LEDs.
Additional Resources
- 74HC595 Datasheet
- Adafruit_CircuitPython_74HC595 Library Documentation and Repository
- Adafruit 74HC595 Shift Register Learning Guide
Summary
In this tutorial, we learned how to add digital outputs to your CircuitPython compatible board using the 74HC595 serial-in parallel-out (SIPO) shift register. I presented multiple approaches for how to represent the outputs in your CircuitPython code so that you can compare and choose the right implementation in your own designs. They range from a simplistic approach that updates all outputs with a single write to the shift register to updating a single output at a time using CircuitPython’s familiar pin setting mechanism. I also included an LED animation as a fun example.
The final source code and schematic used for this tutorial are available on GitHub. The GitHub version of the code is fully commented to include additional information, such as the program’s description, circuit connections, code clarifications, and other details. The comments are also Sphinx compatible in case you want to generate the code documentation.
The next part of this three-part tutorial, Part 2 – The 74HC165, will describe how to add digital inputs using the 74HC165 8-bit parallel-in serial-out (PISO) shift register IC.
Thank you for joining me along this journey and I hope you enjoyed the experience. Please feel free to share your thoughts or questions in the comments section below.
Leave a Comment