exploringarduino.com/content2/ch9
wiley.com/go/exploringarduino2e
As you plug away building exciting new projects with your Arduino, you might already be thinking: “What happens when I run out of pins?” Indeed, one of the most common uses for the Arduino platform is to put an enormous number of blinking LEDs on just about anything. Light up your room! Light up your computer! Light up your dog! Okay, maybe not that last one.
But there's a problem: What happens when you want to start blinking 50 LEDs (or controlling other digital outputs), but you've used up all of your I/O pins? That's where shift registers can come in handy. With shift registers, you can expand the I/O capabilities of your Arduino without having to pay a lot more for an expensive microcontroller with additional I/O pins. In this chapter, you'll learn how shift registers work, and you'll implement both the software and hardware necessary to interface your Arduino with shift registers for the purpose of expanding the digital output capabilities of your Arduino. Once you've completed the exercises in this chapter, you will be familiar with shift registers and will also be able to make more informed design decisions when developing a project with a large number of digital outputs.
A shift register is a device that accepts a stream of serial bits and simultaneously outputs the values of those bits onto parallel I/O pins. Most often, shift registers are used for controlling large numbers of LEDs, such as the configurations found in seven-segment displays or LED matrices. Before you dive into using a shift register with your Arduino, consider the diagram in Figure 9-1, which shows the inputs and outputs to a serial-to-parallel shift register. Variations to this diagram throughout the chapter illustrate how different inputs affect the outputs.
The eight circles represent LEDs connected to the eight outputs of the shift register (through current-limiting resistors, of course). The three inputs are the serial communication lines that connect the shift register to the Arduino.
There are essentially two ways to send multiple bits of data. Recall that the Arduino, like all microcontrollers, is digital; it only understands 1s and 0s. So, if you want sufficient data to control eight LEDs digitally (each one on or off), you need to find a way to transmit a total of 8 bits of information. In previous chapters, you did this in parallel by using the digitalWrite()
and analogWrite()
commands to exert control over multiple I/O pins. For an example of parallel information transmission, suppose that you were to turn on eight LEDs with eight digital outputs; all the bits would be transmitted on independent I/O pins at the same time.
In Chapter 7, “USB Serial Communication,” you learned about serial transmission, which transmits 1 bit of data at a time. Shift registers allow you to easily convert between serial and parallel data transmission techniques. This chapter focuses on serial-to-parallel shift registers, sometimes called serial in, parallel out (SIPO) shift registers. With these handy devices, you can “clock in” multiple bytes of data serially, and output them from the shift register in parallel. You can also chain together shift registers, and thus control hundreds of digital outputs with just three Arduino I/O pins.
For this project, you'll be using the 74HC595 shift register. Take a look at the pin-out diagram from the datasheet shown in Figure 9-2.
Following is a breakdown of the shift register pin functions:
You will not be using the or pins in these examples, but you might want to use them for your project, so it's worth understanding what they do. stands for “output enable.” The bar over the pin name indicates that it is active low. In other words, when the pin is held low, the output will be enabled. When it is held high, the output will be disabled. In these examples, this pin will be connected directly to ground, so that the parallel outputs are always enabled. You could alternatively connect this pin to an I/O pin of the Arduino to simultaneously turn all the LEDs on or off. The pin is the serial clear pin. When pulled low, it empties the contents of the shift register. For your purposes in this chapter, you will tie it directly to 5V to prevent the shift register values from being cleared.
The shift register is a synchronous device; it only acts on the rising edge of the clock signal. Every time the clock signal transitions from low to high, all the values currently stored in the eight output registers are shifted over one position. (The last one is either discarded or output on the QH' pin if you are cascading registers.) Simultaneously, the value currently on the DATA input is shifted into the first position. When this is done eight times, the present values are shifted out and the new values are shifted into the register. The LATCH pin is set high at the end of this cycle to make the newly shifted values appear on the outputs. The flowchart shown in Figure 9-3 further illustrates this program flow. Suppose, for example, that you want to set every other LED to the ON state (QA, QC, QE, QG). Represented in binary, you would want the output of the parallel pins on the shift register to look like this: 10101010.
Now, follow the previously described steps for writing to the shift register. First, the LATCH pin is set low so that the current LED states are not changed while new values are shifted in. Then, the LED states are shifted into the registers in order on the CLOCK edge from the DATA line. After all the values have been shifted in, the LATCH pin is set high again, and the values are output from the shift register.
Now that you understand what's happening behind the scenes, you can write the Arduino code to control the shift register. As with all your previous experiments, you can use a convenient function that's built in to the Arduino IDE to shift data into the register IC. The shiftOut()
function lets you easily shift out 8 bits of data onto an arbitrary I/O pin. It accepts four parameters:
shiftOut()
function as follows:shiftOut(DATA, CLOCK, MSBFIRST, B10101010);
The DATA
and CLOCK
constants are set to the pin numbers for those lines. MSBFIRST
indicates that the most significant bit will be sent first (the leftmost bit when looking at the binary number to send). You could alternatively send the data with the LSBFIRST
setting, which would start by transmitting the bits from the right side of the binary data. The final parameter is the number to be sent. By putting a capital B
before the number, you are telling the Arduino IDE to interpret the following numbers as a binary value rather than as a decimal integer.
Next, you will build a physical version of the system that you learned about in the previous sections. First, you need to get the shift register wired up to your Arduino:
Don't forget to use current-limiting resistors with your LEDs. Reference the diagram shown in Figure 9-4 to set up the circuit.
Now, using your understanding of how shift registers work, and of the shiftOut()
function, you can use the code in Listing 9-1 to write the alternating LED pattern to the attached LEDs.
Because the shift register will latch the values, you need to send them only once in the setup; they will then stay at those values until you change them to something else. This program follows the same steps that were shown graphically in Figure 9-3. The LATCH
pin is set low, the 8 bits of data are shifted in using the shiftOut()
function, and then the LATCH
pin is set high again so that the shifted values are output on the parallel output pins of the shift register IC.
In Listing 9-1, the LED state information was written as a binary string of digits. This string helps you visualize which LEDs will be turned on and off. However, you can also write the pattern as a decimal value by converting between base2 (binary) and base10 (decimal) systems. Each bit in a binary number (starting from the rightmost, or least significant, bit) represents an increasing power of 2. Converting binary representations to decimal representations is very straightforward. Consider the binary number from earlier in the chapter, now displayed in Figure 9-5 with the appropriate decimal conversion steps.
The binary value of each bit represents an incrementing power of 2. In the number in this example, bits 7, 5, 3, and 1 are high. So, to find the decimal equivalent, you add 27, 25, 23, and 21. The resulting decimal value is 170. You can prove to yourself that this value is equivalent by substituting it into the code listed earlier. Replace the shiftOut()
line with the following:
shiftOut(SER, CLK, MSBFIRST, 170);
You should see the same result as when you used the binary notation.
In the previous example, you built a static display with a shift register. However, you'll probably want to display more dynamic information on your LEDs. In the next two examples, you will use a shift register to control a lighting effect and a physical bar graph.
The light rider is a neat effect that makes it look like the LEDs are chasing each other back and forth. You will use the same circuit that you used previously. The shiftOut()
function is very fast, and you can use it to update the shift register several thousand times per second. Because of this, you can quickly update the shift register outputs to make dynamic lighting animations. Here, you light up each LED in turn, “bouncing” the light back and forth between the leftmost and rightmost LEDs. Watch a demo video of this project at exploringarduino.com/content2/ch9
if you want to see what the finished project will look like before you build it.
You first want to figure out each animation state so that you can easily cycle through them. For each time step, the LED that is currently illuminated turns off, and the next light turns on. When the lights reach the end, the same thing happens in reverse. The timing diagram in Figure 9-6 shows how the lights will look for each time step and the decimal value required to turn that specific LED on.
Recalling what you learned earlier in the chapter, convert the binary values for each light step to decimal values that can easily be cycled through. Using a for
loop, you can cycle through an array of each of these values and shift them out to the shift register one at a time. The code in Listing 9-2 does just that.
By adjusting the value within the delay
function, you can change the speed of the animation. Try changing the values of the seq
array to make different pattern sequences.
Using the same circuit but adding an IR distance sensor, you can make a bar graph that responds to how close you get. To mix it up a bit more, try using multiple LED colors. The circuit diagram in Figure 9-7 shows the circuit modified with different-colored LEDs and an IR distance sensor.
Using the knowledge you already have from working with analog sensors and the shift register, you should be able to make thresholds and set the LEDs accordingly based on the distance reading. Figure 9-8 shows the decimal values that correspond to each binary representation of LEDs.
As you discovered in Chapter 3, “Interfacing with Analog Sensors,” the range of usable values for the IR distance sensor is not the full 10-bit range. (I found that a maximum value of around 500 worked for me, but your setup will probably differ.) Your minimum might not be zero either. It's best to test the range of your sensor and fill in the appropriate values. You can place all the bar graph decimal representations in an array of nine values. By mapping the IR distance sensor (and constraining it) from 0 to 500 down to 0 to 8, you can quickly and easily assign distances to bar graph configurations. The code in Listing 9-3 shows this method in action.
Load this program on to your Arduino and move your hand back and forth in front of the distance sensor—you should see the bar graph respond by going up and down in parallel with your hand. If you find that the graph hovers too much at “all on” or “all off,” try adjusting the maxVal
and minVal
values to better fit the readings from your distance sensor. To test the values you are getting at various distances, you can initialize a serial connection in the setup()
command and call Serial.println(distance);
right after you perform the analogRead(DIST);
step.
In this chapter, you learned the following: