Learning Goals 5 min
Yesterday was SPI in theory. Today you wire the cheapest SPI peripheral in the world — the 74HC595 shift register — and use it to drive 8 LEDs from only 3 Arduino pins. The same chip is what most LED bar graphs, LED matrices and big seven-segment displays use internally. By the end of this lesson you will:
- Wire a 74HC595 to a UNO using SPI pins (D11 = data, D13 = clock) plus a latch pin (D10) to control 8 output pins (Q0–Q7).
- Use the built-in
shiftOut()function — or the fasterSPIlibrary — to set the 8 outputs to any pattern by writing one byte. - Cascade two 74HC595s to drive 16 LEDs from the same 3 control pins — the headline benefit of shift registers.
Warm-Up 10 min
Locate the 74HC595 — a 16-pin DIP IC with a half-moon notch at one end. The pinout is:
| Pin | Name | Role |
|---|---|---|
| 1 | Q1 | Output bit 1 |
| 2 | Q2 | Output bit 2 |
| 3 | Q3 | Output bit 3 |
| 4 | Q4 | Output bit 4 |
| 5 | Q5 | Output bit 5 |
| 6 | Q6 | Output bit 6 |
| 7 | Q7 | Output bit 7 |
| 8 | GND | Ground |
| 9 | Q7' | Serial out (for cascading) |
| 10 | MR (SRCLR) | Master Reset — active low. Tie HIGH normally. |
| 11 | SH_CP (SRCLK) | Shift clock — bits move on rising edges |
| 12 | ST_CP (RCLK) | Latch clock — moves shift register to output on rising edge |
| 13 | OE | Output Enable — active low. Tie LOW normally. |
| 14 | DS (SER) | Serial data input |
| 15 | Q0 | Output bit 0 |
| 16 | VCC | +5 V |
Eight output pins (Q0–Q7), three control pins (DS, SH_CP, ST_CP), plus power, ground, MR, and OE.
Mental model of a shift register
Imagine 8 boxes in a row. Each "shift clock" pulse moves every box's contents one position to the right, and reads a new bit into the leftmost box from the serial data pin. After 8 clocks, the byte you serially fed in is sitting in the 8 boxes — but the output pins still show whatever was there before. A "latch clock" pulse copies the box contents to the output pins all at once.
Two-stage design: feed bits in serially without disturbing the LEDs, then commit them all in a single latch pulse. Avoids the LEDs flickering as bits shift through.
New Concept · The wiring + the API 25 min
Wiring on a breadboard
Place the 74HC595 across the central gap. Pins 1–8 are on one side, 9–16 on the other (read counter-clockwise from the notch).
| 74HC595 pin | Connect to |
|---|---|
| 16 VCC | UNO 5V |
| 8 GND | UNO GND |
| 10 MR | UNO 5V (always reset-disabled) |
| 13 OE | UNO GND (always output-enabled) |
| 14 DS | UNO D11 (SPI COPI) |
| 11 SH_CP | UNO D13 (SPI SCK) |
| 12 ST_CP | UNO D10 (latch) |
| 1, 2, 3, 4, 5, 6, 7, 15 (Q1..Q7, Q0) | each through a 220 Ω resistor to an LED to GND |
API option 1 — built-in shiftOut()
Arduino has a built-in shiftOut() that bit-bangs SPI. It's the simplest, but slower than hardware SPI.
const int DATA_PIN = 11;
const int CLOCK_PIN = 13;
const int LATCH_PIN = 10;
void writeShift(byte pattern) {
digitalWrite(LATCH_PIN, LOW);
shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, pattern);
digitalWrite(LATCH_PIN, HIGH);
}
void setup() {
pinMode(DATA_PIN, OUTPUT);
pinMode(CLOCK_PIN, OUTPUT);
pinMode(LATCH_PIN, OUTPUT);
}
void loop() {
for (int i = 0; i < 256; i++) {
writeShift(i); // count up through every 8-bit pattern
delay(50);
}
}The LEDs will count in binary from 0 (all off) to 255 (all on) and roll over forever. Each bit's position in the byte (LSB to MSB) maps to Q0..Q7 with MSBFIRST.
API option 2 — hardware SPI library (much faster)
#include <SPI.h>
const int LATCH_PIN = 10;
void writeShift(byte pattern) {
digitalWrite(LATCH_PIN, LOW);
SPI.transfer(pattern);
digitalWrite(LATCH_PIN, HIGH);
}
void setup() {
pinMode(LATCH_PIN, OUTPUT);
SPI.begin(); // configures pins 11, 12, 13 as SPI
}
void loop() {
for (int i = 0; i < 256; i++) {
writeShift(i);
delay(50);
}
}Same observable behaviour, but ~100× faster bit shifting (8 µs vs 800 µs at the default 4 MHz SPI clock). For a slow LED counter you can't tell the difference; for a 32-LED-strip refresh at 60 fps you need the hardware version.
How MSBFIRST maps to LED positions
The first bit shifted in ends up at Q7 (the bit furthest from the data input). The 8th bit ends up at Q0. So writing byte 0b10000000 with MSBFIRST lights only Q7; 0b00000001 lights only Q0.
For natural "binary counter" behaviour (bit 0 of your value → first LED), use MSBFIRST and arrange your LEDs from Q7 on the left to Q0 on the right.
Cascading two chips for 16 outputs
Wire Q7' (pin 9) of chip A to DS (pin 14) of chip B. Both chips share the same SH_CP and ST_CP wires. Now you can shift in 16 bits — the first 8 end up in chip B, the next 8 in chip A. Same 3 control pins from the Arduino, double the outputs.
void writeShift16(uint16_t pattern) {
digitalWrite(LATCH_PIN, LOW);
SPI.transfer((byte)(pattern >> 8)); // chip B (further from Arduino)
SPI.transfer((byte)(pattern & 0xFF)); // chip A (nearest)
digitalWrite(LATCH_PIN, HIGH);
}Three chips? Five chips? Same pattern — chain Q7' → DS, share the clock and latch, push more bytes per transaction. The shift register is the cheapest way to add "more output pins" to any microcontroller.
Worked Example · Binary counter + Knight Rider 25 min
Step 1 — wire it
Follow the table in §3. Eight LEDs with their 220 Ω resistors fanned out from the chip. Use a row of breadboard tie points for the LED anodes so it's visually clean.
Step 2 — minimal test
Upload the SPI-library counter from §3. The LEDs should count 0 → 255 in binary, ~50 ms per step, then loop. If only some LEDs light, check those LEDs and resistors with a multimeter — chip outputs sourcing weakly is rare; bad solder joints are common.
Step 3 — Knight Rider pattern
The classic "single LED sweeping back and forth":
#include <SPI.h>
const int LATCH_PIN = 10;
void writeShift(byte p) {
digitalWrite(LATCH_PIN, LOW);
SPI.transfer(p);
digitalWrite(LATCH_PIN, HIGH);
}
void setup() {
pinMode(LATCH_PIN, OUTPUT);
SPI.begin();
}
void loop() {
for (int i = 0; i < 8; i++) {
writeShift(1 << i); // single bit walks left
delay(80);
}
for (int i = 7; i >= 0; i--) {
writeShift(1 << i); // and back
delay(80);
}
}1 << i shifts a single bit to position i. So i = 0 gives 0b00000001 (one LED on); i = 3 gives 0b00001000 (a different one on); etc. The loop walks the lit LED across the bar.
Step 4 — bar graph from a pot
int pot = analogRead(A0);
int leds = map(pot, 0, 1023, 0, 8); // 0..8 LEDs lit
byte pattern = 0;
for (int i = 0; i < leds; i++) {
pattern |= (1 << i);
}
writeShift(pattern);
delay(20);Turn the pot, watch the LED bar fill up like a volume meter. The same pattern we built on an OLED yesterday (L03-19) — now in raw LED form, no display needed.
Step 5 — cascade a second chip (optional, if you have two)
Wire chip A's pin 9 (Q7') to chip B's pin 14 (DS). Share GND, VCC, MR, OE, SH_CP, ST_CP between both. Add 8 more LEDs to chip B's Q0–Q7. Use the writeShift16 helper from §3 to push 16-bit patterns. A 16-LED bar graph now uses the same 3 Arduino pins as the 8-LED one — that's the headline win.
Step 6 — verify the speed gain
Time how long 1000 writeShift() calls take using shiftOut vs the SPI library:
unsigned long t0 = micros();
for (int n = 0; n < 1000; n++) writeShift(n & 0xFF);
unsigned long t1 = micros();
Serial.print("1000 writes took ");
Serial.print(t1 - t0);
Serial.println(" us");Typical: shiftOut takes ~800 ms (800 µs per byte); SPI.transfer takes ~10 ms (10 µs per byte). 80× speedup. For static patterns the difference is invisible; for animations it's the difference between smooth and laggy.
Try It Yourself 15 min
Goal: Make the LEDs spell a one-byte message by lighting them in a slow Morse pattern. Use the L01-45 Morse blinker as your reference. Single LED for the "active beat".
Hint
The shift register is just a way to write one byte at a time to your 8 LEDs. You don't even need to use 8 LEDs for this — pick one (e.g. Q0) and write 0b00000001 for "on", 0b00000000 for "off". Same Morse code as L01-45.
Goal: Animated "sparkle" — every 100 ms, light a random set of 3 LEDs out of the 8.
Hint
byte sparkle() {
byte p = 0;
for (int i = 0; i < 3; i++) p |= (1 << random(0, 8));
return p;
}
// in loop: writeShift(sparkle()); delay(100);random(0, 8) picks a number 0..7 (the upper bound is exclusive). Sometimes you'll get duplicates → fewer than 3 LEDs lit — that's part of the sparkle character.
Goal: "VU meter" — read a sound sensor (or just use the pot as a stand-in) and light up a smoothly-decaying bar. The bar shows the current value; the decay means brief peaks visibly hang for a moment.
Hint
int peak = 0; // current displayed level
unsigned long lastDecay = 0;
void loop() {
int level = map(analogRead(A0), 0, 1023, 0, 8);
if (level > peak) peak = level;
// slow decay - drop peak by 1 every 80 ms
if (millis() - lastDecay >= 80) {
lastDecay = millis();
if (peak > 0) peak--;
}
byte pattern = 0;
for (int i = 0; i < peak; i++) pattern |= (1 << i);
writeShift(pattern);
}This is exactly how a 1980s hi-fi VU meter works — fast rise to the input level, slow fall, so quick transients are visible. Try a microphone breakout for a real audio response.
Mini-Challenge · Write a reusable helper 10 min
Create a small shift595.h header next to your other helpers:
// shift595.h - minimal 74HC595 helper
#ifndef SHIFT595_H
#define SHIFT595_H
#include <Arduino.h>
#include <SPI.h>
struct ShiftReg {
int latchPin;
int chipCount; // 1 for a single chip, 2 for cascade
};
inline void shiftInit(const ShiftReg& s) {
pinMode(s.latchPin, OUTPUT);
digitalWrite(s.latchPin, HIGH);
SPI.begin();
}
inline void shiftWrite(const ShiftReg& s, uint32_t pattern) {
digitalWrite(s.latchPin, LOW);
for (int i = s.chipCount - 1; i >= 0; i--) {
SPI.transfer((byte)(pattern >> (i * 8)));
}
digitalWrite(s.latchPin, HIGH);
}
#endifUsage:
#include "shift595.h"
const ShiftReg leds = {10, 1}; // latch on D10, one chip
void setup() {
shiftInit(leds);
}
void loop() {
shiftWrite(leds, 0b10101010);
}Any future shift-register-driven LED panel or relay board uses this helper. We'll re-use it in tomorrow's dashboard project.
Recap 5 min
The 74HC595 is the simplest SPI peripheral imaginable: 8 outputs, 3 control pins, write one byte to set them all. Built-in shiftOut() bit-bangs the protocol; SPI.transfer() uses the hardware peripheral and is ~80× faster. The latch / RCLK pin commits the shift-register bits to the actual output pins — without it, nothing visible changes. Cascade by wiring Q7' → DS on the next chip; push more bytes per transaction. From here on, "I need more output pins" has a known cheap answer: add a shift register. Tomorrow we combine the OLED + a shift register into a dashboard project — Cluster D's capstone.
- Shift register
- A chip with a chain of flip-flops that shift their contents one position each clock pulse. The 74HC595 is a serial-in, parallel-out version with an extra latch stage.
- 74HC595
- The classroom 8-bit shift register. Serial in (DS) + shift clock (SH_CP) + latch clock (ST_CP) → 8 parallel outputs (Q0–Q7). Plus power, ground, OE (output enable), MR (master reset), and Q7' (cascade out).
- Latch
- The extra register between the shift register and the output pins. A pulse on the latch clock copies the shift register's current state into the latch, which drives the outputs. Lets you load 8 bits silently and reveal them all at once.
shiftOut(dataPin, clockPin, order, value)- Arduino built-in. Bit-bangs SPI on any two pins. Slow (~100 µs/byte) but works on pins other than the hardware SPI ones.
SPI.transfer(byte)- Hardware SPI single-byte send. Returns the byte clocked back on CIPO. ~10 µs/byte at default UNO speed.
SPI.begin()- Configures pins 11, 12, 13 as SPI on the UNO. Must be called once in
setup()before anySPI.transfer(). - Cascade
- Chaining shift registers: Q7' of one chip into DS of the next. Multiplies the number of outputs without using more Arduino pins.
- MSBFIRST / LSBFIRST
- Bit-order argument to
shiftOut. MSBFIRST sends the highest bit first; LSBFIRST the lowest. Most SPI peripherals expect MSBFIRST.
Homework 5 min
- Save the binary counter and Knight Rider sketches.
- Save your
shift595.hhelper. It joinsmotor.h, the I²C scanner, and the OLED boilerplate in your "reusable parts" folder. - Bring tomorrow: a 10-segment LED bar graph (or just leave the 8-LED setup wired). Plus your OLED from L03-19. We'll combine them on a single board for the dashboard project.
Bring back next class:
- Sketches + helper file.
- 74HC595 + LEDs still wired up.
- OLED ready to re-wire.