Using a Shift Register to Control a Bunch of LEDs
Shift registers are very useful tools; using a few pins connected to a shift register, we can increase the number of output data pins that are available to us.
In this experiment, we’ll be using a shift register to control eight LEDs, but we’ll only be using three pins from the ATmega.
Shift Register
A shift register is an external integrated circuit (IC) that can be used to expand the number of output pins available to us. Essentially they let you turn serial input from a single pin (one bit after the other) into multiple parallel output signals (all at once on separate lines).
The shift register used in your kit is the popular 74HC595. It has 8 output lines which allows you to manipulate and use bytes for output in your code.
Overview
So how does this work? The IC is made up of two registers, units of memory that can hold up several binary values in order (8 for the IC in your kit). They are:
- The shift register, which holds 8 values before they are written to the output pins. Values can be “shifted” through this register from one position to the next, starting at position “A” to position “H”.
- The storage register, which takes values from the shift register and sends them to the data output lines, labelled
QA
toQH
. For example, a logical1
in position “C” of the storage register would create a HIGH signal onQC
.
There are three pins on the IC that we use to control it with the Omega. Two of these pins are clocks: special inputs that trigger the IC to do something when they receive a signal that changes from LOW to HIGH (also known as a pulse or a rising edge).
Pin | Name | Purpose |
---|---|---|
SER | Serial data pin | This is the serial data input line. When we pulse the serial clock (SRCLK), the signal on this line is stored in the 1st position (“A”) of the shift register. |
SRCLK | Serial clock | When pulsed, shifts each value in the shift register forwards by one position, then loads the value from the SER pin into position “A”. Note that this does not change the signals on the output lines until you pulse the register clock (RCLK). |
RCLK | Register clock, or “latch pin” | When pulsed, updates the storage register with new values from the shift register, sending a new set of signals to the 8 output pins. This happens so quickly that they all seem to change simultaneously! |
Bit Order
Keep in mind that the first value you send the shift register will be shifted towards the last output pin as you send it more data.
Let’s say we want to send the following bits: 10101010
. Intuitively, it seems easiest to send each bit in the number from left to right as if it were a string. In Python, this would look something like:
bytestring = '10101010'
bytestring = list(bytestring) # turns the string into a list: ["1", "0", "1", "0", ...]
for bit in bytestring:
shift(bit) # sends 1, then 0, then 1, then 0, ...
However, sending it this way means that after we’ve sent all eight, the 1st bit would actually be shifted all the way to the last output (QH
), the 2nd bit would be shifted to the 2nd-to-last output (QG
), and so on until everything is reversed! This way of shifting values out is known as most-significant bit (MSB) first. If we used this method in our shift register class, we would have to wire everything up backwards, and this could make it confusing to assemble or debug circuits.
We can get around this issue by sending the rightmost, or least significant bit (LSB), first. We can modify the above code into something like this:
Pinout Diagram
The pinout diagram for the 74HC595 is shown below:
On the right side of the chip, you can see the three control pins described above, as well as the first output (QA). On the left side, you can see the other 7 outputs (QB - QH).
Controlling a Shift Register
So how can this let us control multiple outputs with one data pin? Well, let’s say we have 8 LEDs hooked up to the data lines, and we want to turn on the 2nd, 4th, and the 8th LEDs like so:
LED | Data Line | Desired Value |
---|---|---|
1 | QA | LOW |
2 | QB | HIGH |
3 | QC | LOW |
4 | QD | HIGH |
5 | QE | LOW |
6 | QF | LOW |
7 | QG | LOW |
8 | QH | HIGH |
First, we’ll clear out the register so all LEDs are off by writing eight 0’s to the shift register, then pulsing the latch pin to write the outputs to the data lines. This is done by setting and holding SER LOW, then pulsing SRCLK 8 times, then pulsing RCLK once.
Then, using the LSB method, we will reverse the bytestring to get 10001010
. For each of these values:
- Set SER to the specified value (HIGH or LOW).
- Pulse SRCLK from LOW to HIGH to shift the value of SER into the shift register.
We repeat the 2 steps above (for example, by using a loop) until all 8 values have been shifted in. Then pulse the RCLK pin to write these values to the storage register and data lines, which turns on the LEDs!
In this way, we can control up to 8 different outputs with only 3 GPIOs. This is an incredibly powerful technique that you can use to work with many components at once.
Here’s the diagram from before to summarize what we’ve just described.
Daisy-Chaining
Shift registers can also be connected in series to each other to extend the number of data lines that can be controlled at once. We can do this by using the QH'
pin, which is connected inside the shift register to the last output QH
.
Simply connect the SER pin of one shift register to the QH'
pin on another, and connect their SRCLK and RCLK pins together. That way, when you pulse SRCLK, the 2nd chip will read from the last output of the 1st, and when you pulse RCLK, both chips will update their output lines. This is great because this does not require any additional GPIOs from the Omega!
You’ve now just created a 16-bit shift register, and you can extend the chain further by adding more chips in the same way as above. This is known as daisy-chaining.
Detailed Specifications
if you’re curious about the clock cycle timings or other information about the IC, you can refer to the datasheet for the SN74HC595 shift register. The clock cycle timing diagram can be found on page 8.
Building the Circuit
For this experiment, we will send a 8 bits (a byte) serially from the ATmega to the shift register. When the latch pin of the shift register is set LOW, the shift register will use the stored 8 bits to set its 8 output pins accordingly. We will attach one LED to each of the 8 output pins and make them light up like Knight Rider’s KITT. By the power of the shift register, we can do this using only three ATmega pins!
This is the diagram of the circuit we’ll be building:
What You’ll Need
Prepare the following components from your kit:
- Omega plugged into Arduino Dock
- USB Micro-B cable for power
- Breadboard
- Jumper wires
- 8x 200Ω Resistor
- 8x LED color of your choice!
- 1x 74HC595 Shift register
Hooking up the Components
The IC should be plugged in across the channel of your breadboard (the slot running down the middle separating the abcde
columns from the fghij
columns). If you don’t do this you will short out the pins across your IC. You may need to bend the pins just a bit in order to get it to fit.
Lets take a look at how the 16 pins of the 74HC595 shift register chip are defined. We’ll be referring to each pin by the numbers provided in the diagram above. When plugged in with the letters being right-side up, the bottow row of pins are pin 1 to 8 going from left to right. The top row of pins are pin 9 to 16 going from right to left.
Note: Your IC will have a semi-circle indentation that indicates “up”. Make sure that you plug it in properly so you know which pins are where.
Here’s the steps to get there:
- Connecting your shift register to the breadboard
- Start by plugging in your shift register across the channel so that the each pin has its own row.
- Connect the supply voltage pin (
Vcc
) and the master reset pin (MR
) on the IC to theVcc
rail on the breadboard - Connect the
GND
pin and output enable pin (OE
) to theGND
rail on the breadboard
The jumpers at the top will be wired to the LEDs eventually.
- Connecting your LEDs
- Connect the anodes of the eight LED each to one of the eight output pins of the 74HC595 - pin
15
and pin1
to7
, corresponding toQ0
toQ7
. Place the LEDs left to right in the following pin order:Q0
,Q1
,Q2
,Q3
,Q4
,Q5
,Q6
,Q7
.
- Connect the anodes of the eight LED each to one of the eight output pins of the 74HC595 - pin
- Attach eight 100Ω current limiting resistors from cathodes of the LEDs to the
GND
rail on the breadboard.
- Connecting your Arduino Dock
- Connect the
GND
pin on the Dock to theGND
rail on the breadboard. - Connect Arduino Dock digital pin 4 to
DS
on the shift register - this is where our input is sent. - Connect Arduino Dock digital pin 5 to
STCP
on the shift register. - Connect Arduino Dock digital pin 6 to
SHCP
on the shift register. - Connect the
Vcc
rail to a5V
pin on the Arduino Dock
Writing the Code
#define NUM_LEDS 8
// duration to pause
int delayTime = 100;
// the pin connected to the latch pin, RCLK (pin 12 of the shift register)
// setting the latch LOW will send the 8 bits in storage to the output pins
int latchPin = 5;
// the pin connected to the clock pin, SRCLK (pin 11 of the shift register)
int clockPin = 6;
// the pin connected to the serial data pin, SER (pin 14 of the shift register)
int dataPin = 4;
// This code runs once when the program starts, and no more
void setup()
{
// initialize all the pins connected to the shift register as outputs
pinMode(latchPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(clockPin, OUTPUT);
}
// function which sends the stored byte to the output pins by setting the latch pin LOW
void updateShiftRegister(byte storageByte)
{
// set the latch pin LOW
digitalWrite(latchPin, LOW);
// send the storage byte to the shift register with the LSB first
// since the latch is LOW, set the 8 output pins based on the stored 8 bits and in turn light the correct LED
shiftOut(dataPin, clockPin, LSBFIRST, storageByte);
// set the latch pin HIGH again
digitalWrite(latchPin, HIGH);
}
// The code in here will run continuously until we turn off the Arduino Dock
void loop()
{
// the byte (8 bits) to be stored in the shift register
// initialize to 00000001, representing the first LED on
byte storageByte = 0x01;
// create the effect of having the light travel to the left
for (int i = 0; i < NUM_LEDS-1; i++)
{
// send the 8 bits to the shift register and set latch LOW
updateShiftRegister(storageByte);
// bitwise shift to the left by 1 bit
// the MSB will disappear and a 0 will be shifted in for the LSB
// ex. 10000001 to 00000010
storageByte = storageByte << 1;
// wait before moving on to the next LED to enhance the animation
delay(delayTime);
}
// create the effect of having the light travel in the opposite direction
for (int i = 0; i < NUM_LEDS-1; i++)
{
// send the 8 bits to the shift register and set latch LOW
updateShiftRegister(storageByte);
// bitwise shift to the right by 1 bit
// the LSB will disappear and a 0 will be shifted in for the MSB
// i.e. 10000000 to 01000000
storageByte = storageByte >> 1;
// wait before moving on to the next LED to enhance the animation
delay(delayTime);
}
}
What to Expect
The eight LEDs will light up like KITT from Knight Rider. The first LEDs will turn on, then the next will turn on and the previous one will turn off. This will repeat for all the LEDs in a loop from left to right and then from right to left. Only one LED should be lit up at a time.
It should look a little like this:
See, just like KITT:
A Closer Look at the Code
We are only using three Arduino Dock pins to control eight LEDs by taking advantage of the shift register. Lets begin by declaring the three pin variables (latchPin
, clockPin
and dataPin
) and initializing the three pins as output in setup()
.
Each time we want to light up a different LED, we update the shift register to send the shift register new signals for each LED.
Updating the Shift Register
We control the shift register using a single function updateShiftRegister
. The first thing it does is to set the latch pin low using a call to digitalWrite()
:
Then we use a function that’s included in the default Arduino libraries, shiftOut()
, to send the byte:
This function does the following actions:
- Sets the SER pin to either HIGH or LOW according to the bit of the byte you want to send
- Sets the clock pin HIGH, then LOW to load the SER bit into the shift register
- Repeats the above two steps until all bits in the byte have been sent
The function takes an argument, bitOrder
, which determines whether it sends the right-most (least significant) bit, or the left-most (most significant) bit first. Here we’ve decided to send it least significant bit first (LSBFIRST
) so that our wiring order can match the order of the shift register’s outputs.
Once the byte has been sent, we set the latch pin HIGH to trigger the clock that updates the shift register’s outputs. This is done with another call to digitalWrite()
:
You’ll notice we left in a slight delay before every update. This is because if we let it run as fast the CPU can go,it will be too fast for us to see. Instead, the lights will appear as if they were all on at the same time. The shift register can accurately update at 100MHz - much faster than our eyes can perceive! In order to actually see the effect, we slow it down by adding the delay.