Last Updated: August 18, 2021
Originally Published: February 4, 2021
Skill Level: Intermediate
Table Of Contents
- Introduction
- What Is Needed
- Background Information
- Building The Circuit
- Changing Single Outputs Using Familiar digitalWrite() Functionality
- Changing All Outputs Using Binary Values
- Changing All Outputs Using Defined Names
- Changing Outputs Using Bit Operations
- An LED Cycling Example
- Summary
Introduction
Sometimes, a project needs more digital I/O than what is available on your Arduino 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 Arduino development 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 Arduino platform. If you are new to Arduino, or would just like to refresh your knowledge, please see our Blink: Making An LED Blink On An Arduino Uno tutorial before proceeding with this one. In addition, this tutorial will use 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
- Arduino IDE
- Arduino Uno (R3 available on Arduino and SparkFun; WiFi Rev2 on Arduino and SparkFun) With Compatible USB Cable
- 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 560 Ω Resistors (available on SparkFun and Amazon)
Background Information
If you only need additional digital outputs for your project, the low-cost standard 7400 series 74HC595 IC is a good choice to incorporate into your design. 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 an Arduino into the shift register by using either the dedicated SPI serial bus (hardware implementation) or the standard Arduino shiftOut()
function (software implementation) on a generic digital pin. I chose to use the shiftOut()
function for this tutorial due to its simplicity along with saving a digital I/O pin when the SPI bus is not needed for other connections.
A single 74HC595 chip will provide 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 Arduino. 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 an Arduino sketch. 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 four 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.
My development system consists of the Arduino Uno WiFi Rev2 development board connected to a macOS based computer running the desktop Arduino IDE. If you are using a different Arduino board or computer setup, 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 Arduino 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 are connected to the shift register’s outputs (QA – QH) constituting the 8 digital outputs being added to the system.
The typical 74HC595 IC has a total maximum current draw of 70 mA. In order to not overload the IC, I chose to be extra conservative and used 560 Ω resistors instead of the 330 Ω resistors I typically use with LEDs. These higher resistance values will slightly reduce the brightness of the LEDs, but will make sure we do not overload the IC when using LEDs whose voltage and current specifications may vary among different manufactures.
I also included a 0.1 µF bypass capacitor 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 5 V to disable hardware based clearing of the shift register.
The circuit should look similar to the one shown below once completed.

Once the circuit is built, connect your Arduino to your computer with the USB cable.
Changing Single Outputs Using Familiar digitalWrite() Functionality
This first approach implements the same calling mechanism as Arduino’s standard digitalWrite()
function. It should be very familiar to long time Arduino 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 the Arduino IDE and create a sketch named OutputShiftRegister with the code shown below.
const uint8_t OSRDataPin = 2; // connected to 74HC595 SER (14) pin const uint8_t OSRLatchPin = 3; // connected to 74HC595 RCLK (12) pin const uint8_t OSRClockPin = 4; // connected to 74HC595 SRCLK (11) pin void setup() { // 74HC595 shift register pinMode(OSRDataPin, OUTPUT); pinMode(OSRLatchPin, OUTPUT); pinMode(OSRClockPin, OUTPUT); osrWriteRegister(0); // turn off all LEDs delay(1000); // wait a second } void loop() { changeOutputsWithDigitalWrite(); } void osrWriteRegister(uint8_t outputs) { // Initiate latching process, next HIGH latches data digitalWrite(OSRLatchPin, LOW); // Shift output data into the shift register, most significant bit first shiftOut(OSRDataPin, OSRClockPin, MSBFIRST, outputs); // Latch outputs into the shift register digitalWrite(OSRLatchPin, HIGH); } void osrDigitalWrite(uint8_t pin, uint8_t value) { static uint8_t outputs = 0; // retains shift register output values if (value == HIGH) bitSet(outputs, pin); // set output pin to HIGH else if (value == LOW) bitClear(outputs, pin); // set output pin to LOW osrWriteRegister(outputs); // write all outputs to shift register } void changeOutputsWithDigitalWrite() { // Output pin definitions const uint8_t LED0 = 0; const uint8_t LED1 = 1; const uint8_t LED2 = 2; const uint8_t LED3 = 3; const uint8_t LED4 = 4; const uint8_t LED5 = 5; const uint8_t LED6 = 6; const uint8_t LED7 = 7; // Set individual LEDs osrDigitalWrite(LED1, HIGH); // turn on LED1 only delay(1000); osrDigitalWrite(LED1, LOW); // turn off LED1 only osrDigitalWrite(LED6, HIGH); // turn on LED6 only delay(1000); osrDigitalWrite(LED6, LOW); // turn off LED6 only delay(1000); // Set multiple LEDs osrDigitalWrite(LED0, HIGH); // turn on even numbered LEDs osrDigitalWrite(LED2, HIGH); osrDigitalWrite(LED4, HIGH); osrDigitalWrite(LED6, HIGH); delay(1000); osrDigitalWrite(LED0, LOW); // turn off even numbered LEDs osrDigitalWrite(LED2, LOW); osrDigitalWrite(LED4, LOW); osrDigitalWrite(LED6, LOW); osrDigitalWrite(LED1, HIGH); // turn on odd numbered LEDs osrDigitalWrite(LED3, HIGH); osrDigitalWrite(LED5, HIGH); osrDigitalWrite(LED7, HIGH); delay(1000); osrDigitalWrite(LED1, LOW); // turn off odd numbered LEDs osrDigitalWrite(LED3, LOW); osrDigitalWrite(LED5, LOW); osrDigitalWrite(LED7, LOW); delay(1000); }
Let’s take a look at some of the more interesting parts of the code.
The 74HC595 shift register only needs three pins to communicate with the Arduino. The OSRDataPin
sends shifted data that is clocked with the OSRClockPin
. The OSRLatchPin
is used to latch the output data, after the data has been shifted in, into the register.
Since we will be applying multiple approaches, I separated each approach into its own distinct function. Therefore, the changeOutputsWithDigitalWrite()
function, this first approach, is the only function I am currently calling within the loop()
function.
The osrWriteRegister()
function is the main workhorse in this sketch as it is the one that actually sends the output data to the 74HC595 shift register. It first drives the latch pin LOW
to initiate the latching process. It then shifts out the output data into the register with the Arduino standard shiftOut()
function. Finally, the latch pin is driven HIGH
locking in and driving the new outputs.
The osrDigitalWrite()
function implements the familiar mechanism we are used to with Arduino’s digitalWrite()
function, but updates the shift register’s outputs instead of the Arduino board’s standard digital pins. An outputs
variable is defined that keeps track of and retains (with the static
keyword) the output values. Values are set (HIGH
) and cleared (LOW
) using bit operations. All outputs are then sent with the osrWriteRegister()
function call. Note, as with digitalWrite()
, the osrDigitalWrite()
function only updates one output pin per call.
The changeOutputsWithDigitalWrite()
function provides an example utilizing the osrDigitalWrite()
approach. We first define our named constants, LED0
– LED7
, to make our calls easier to understand. The constants 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 osrDigitalWrite()
function to turn on and off a couple of LEDs and then turn on and off either the even or odd numbered LEDs.
If there is something that needs further explanation, please let me know in the comment section and I will try to answer your question.
Save your program when you are done editing.
Now that our circuit is built and our software is written, let’s run and test our program. Upload the sketch to your Arduino board and you should see the 74HC595 shift register’s LED outputs being updated. First, LED1 turns on, then only LED6 is on, then only the even numbered LEDs are lit, then only the odd numbered LEDs are lit. This sequence will be continuously repeated.
Changing All Outputs Using Binary Values
This next approach is the simplest of all the approaches. It simply 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 the end of your sketch
void changeOutputsWithBinaryValues() { // Set individual LEDs osrWriteRegister(0b00000010); // turn on LED1 only delay(1000); osrWriteRegister(0b01000000); // turn on LED6 only delay(1000); osrWriteRegister(0b00000000); // turn off all LEDs delay(1000); // Set multiple LEDs osrWriteRegister(0b01010101); // turn on only even numbered LEDs delay(1000); osrWriteRegister(0b10101010); // turn on only odd numbered LEDs delay(1000); osrWriteRegister(0b00000000); // turn off all LEDs delay(1000); }
and then change the body of the loop()
function to the following so that you’re calling the newly added function instead of the first one.
// changeOutputsWithDigitalWrite(); changeOutputsWithBinaryValues();
Save your work and upload the updated sketch 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 constants 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 HIGH (1). All other outputs default to LOW (0).
Add the following function to the end of your sketch
void changeOutputsWithDefinedNames() { // Output pin definitions const uint8_t LED0 = 0b00000001; const uint8_t LED1 = 0b00000010; const uint8_t LED2 = 0b00000100; const uint8_t LED3 = 0b00001000; const uint8_t LED4 = 0b00010000; const uint8_t LED5 = 0b00100000; const uint8_t LED6 = 0b01000000; const uint8_t LED7 = 0b10000000; // Set individual LEDs osrWriteRegister(LED1); // turn on LED1 only delay(1000); osrWriteRegister(LED6); // turn on LED6 only delay(1000); osrWriteRegister(0); // turn off all LEDs delay(1000); // Set multiple LEDs osrWriteRegister(LED0 | LED2 | LED4 | LED6); // turn on only even numbered LEDs delay(1000); osrWriteRegister(LED1 | LED3 | LED5 | LED7); // turn on only odd numbered LEDs delay(1000); osrWriteRegister(0); // turn off all LEDs delay(1000); }
and then change the body of the loop()
function to the following.
// changeOutputsWithDigitalWrite(); // changeOutputsWithBinaryValues(); changeOutputsWithDefinedNames();
Save your work and upload the sketch. You should see the same LED sequence as before.
Changing Outputs Using Bit Operations
This last approach is somewhat of a compromise among all the approaches shown so far. It has the ability of individually changing named outputs while also being able to update multiple outputs in a single write to the shift register, but it has the drawback of producing lengthy code when many outputs are required to change simultaneously.
Add the following function to the end of your sketch
void changeOutputsWithBitOperations() { // Output pin definitions const uint8_t LED0 = 0; const uint8_t LED1 = 1; const uint8_t LED2 = 2; const uint8_t LED3 = 3; const uint8_t LED4 = 4; const uint8_t LED5 = 5; const uint8_t LED6 = 6; const uint8_t LED7 = 7; uint8_t outputs = 0; // holds shift register output values // Set individual LEDs bitSet(outputs, LED1); // turn on LED1 osrWriteRegister(outputs); delay(1000); bitClear(outputs, LED1); // turn off LED1 bitSet(outputs, LED6); // turn on LED6 osrWriteRegister(outputs); delay(1000); bitClear(outputs, LED6); // turn off LED6 osrWriteRegister(outputs); delay(1000); // Set multiple LEDs bitSet(outputs, LED0); // turn on even numbered LEDs bitSet(outputs, LED2); bitSet(outputs, LED4); bitSet(outputs, LED6); osrWriteRegister(outputs); delay(1000); bitClear(outputs, LED0); // turn off even numbered LEDs bitClear(outputs, LED2); bitClear(outputs, LED4); bitClear(outputs, LED6); bitSet(outputs, LED1); // turn on odd numbered LEDs bitSet(outputs, LED3); bitSet(outputs, LED5); bitSet(outputs, LED7); osrWriteRegister(outputs); delay(1000); bitClear(outputs, LED1); // turn off odd numbered LEDs bitClear(outputs, LED3); bitClear(outputs, LED5); bitClear(outputs, LED7); osrWriteRegister(outputs); delay(1000); }
and then change the body of the loop()
function to the following.
// changeOutputsWithDigitalWrite(); // changeOutputsWithBinaryValues(); // changeOutputsWithDefinedNames(); changeOutputsWithBitOperations();
An outputs
variable is used to keep track of the output values. The LED0
–LED7
constants are defined as their bit position in the outputs
variable. The bitSet()
and bitClear()
bit operations can then be used on the outputs
variable to set (HIGH) and clear (LOW) each individual output (bit) respectively. Any number of bit operations can be performed before sending the final updated values with a call to osrWriteRegister()
.
Save your work and upload the sketch. You should see the same LED sequence as before.
An LED Cycling Example
This is just an example of a fun animation (Knight Rider style) that shows how to use the bit operations a little differently than the previous approach.
Add the following function to the end of your sketch
void cycleLEDs() { uint8_t outputs = 0; // holds shift register output values // Cycle through individual LEDs from LED0 to LED6 for (uint8_t i = 0; i < 7; i++) { bitSet(outputs, i); osrWriteRegister(outputs); bitClear(outputs, i); delay(100); } // Cycle through individual LEDs from LED7 to LED1 for (uint8_t i = 7; i > 0; i--) { bitSet(outputs, i); osrWriteRegister(outputs); bitClear(outputs, i); delay(100); } }
and then change the body of the loop()
function to the following.
// changeOutputsWithDigitalWrite(); // changeOutputsWithBinaryValues(); // changeOutputsWithDefinedNames(); // changeOutputsWithBitOperations(); cycleLEDs();
For loops are used to cycle through the LEDs from one end to the other with a small delay between shifts.
Again, save your work and upload the sketch. The LED’s should be turning on and off in sequence, back and forth, across all LEDs.
Before we end, now would be a good time to upload the BareMinimum sketch (Main Menu > File > Examples > 01.Basics > BareMinimum) to reset all pins back to their default states. This ensures no outputs are being driven when plugging in your board for your next project.
Summary
In this tutorial, we learned how to add digital outputs to your Arduino board using the 74HC595 serial-in parallel-out (SIPO) shift register. I presented multiple approaches for how to represent the outputs in your Arduino 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 the familiar digitalWrite()
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 Doxygen 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.
I appreciate your info on the Arduino and the 74HC165. I am trying to send specific commands to a 74CH165 that is part of a board that controls relays… I need to send one of 4 “commands” to the 74CH165. It appears that each cycle is comprised of 40ms. Each cycle consists of high and low pulses. Some cycles must be repeated to convey the correct command. Is this possible to do with the Arduino? If you need a graphic, I would be happy to provide. Thanks
I measured the length of time it took to read a byte from the 74HC165 shift register and write a byte to the 74HC595 register and found that they took only 128 and 108 microseconds respectively. It only took an extra 68 us to also print a value to the Serial Monitor.
Based on these values, you should not have any problems reading from or writing to one of the registers every 40 ms as long you don’t have too much other stuff running as well.
I am trying to use the Uno with a 4×4 keypad and 2×16 lcd display to send basic code to the card explained on pages 3-5 on this site: http://tuffyparts.com/OmegaTek_Expander_Card.pdf. I am just doing this so I can test these in my shop. I am NOT trying to do anything else. The board uses 74HC165 and 74HC595 chips to process the data. The input is via 3 pins as explained but I believe are clock, data and ground. I have the basic if then statements to handle input from the 4×4 keypad to send the signals. I am just not sure how to set up the output pins and handle the clock and data. Any help would be appreciated. Thank you
I’m sorry, but I am unable to discern much from that document. I wish you luck in figuring it out, though.
How about arduino code when we have two ICs Parallel connection.
I’m not sure I understand your question. Daisy chaining allows you to utilize two or more ICs.