Introduction: Multiplexing 7 Segment Displays With Arduino and Shift Registers

In this instructable, I will be teaching the basics of multiplexing 7 segment displays using an Arduino and a couple of shift registers. This project is well suited for displaying numerical information or if you want to control a bunch of LEDs. For beginners, like me, I had no clue on how to tackle this project. But after trial and error and blood, sweat, and tears, I can say that I have a better understanding of multiplexing and how best to implement it on an Arduino.

First off, what is multiplexing? What about Charlieplexing? Any differences?
Actually, they are they same... Charlieplexing just takes multiplexing to a higher level. Both are techniques used to not only reduce the number of microcontroller pins needed, but also to reduce the power requirements substantially. However, at the cost of time and/or brightness.

In multiplexing, an entire digit or row of LEDs are shown at one time. After some time, the whole digit or row is turned off and the next digit/row is turned on, etc... Simple!

However, Charlieplexing is a bit more complicated in that it goes deeper than multiplexing. Instead of turning on a whole digit or row, a single segment or individual LED is turned on/off. After some time, the segment/LED is turned off and the next segment/individual LED is turned on, etc... After cycling through a digit/row, the process repeats with the next digit/row. So, if you're charlieplexing a 7-segment, you would consume a max of 20mA vs 160mA in multiplexing since only 1 segment is on at a time. The severe downside is that it takes longer to display information and brightness is reduced because the program needs to cycle through all the 7 segments + decimal or each LED first before moving to the next digit or row. You will also notice a slight flicker as you chain more displays/LEDs.

Look above for a comparison on multiplexing and charlieplexing. Notice how charlieplexing requires more time to display a number?

Before you tackle your multiplexing project, you must lay everything out--research as much as you can. Otherwise, you will end up wasting time, money, and pulling your hair out of frustration.

Step 1: Plan the Hardware

To multiplex 7 segment displays, you will need the following:

1. 7 segment displays -- I'm using 3 x 4.0 Inch Super Red 7 Segments from Kingbright (SA40-19SRWA)
I strongly suggest you purchase COMMON ANODE displays. Common anode means all the anodes (+) pins are connected. You apply + voltage to the anode and use shift registers to ground the segments and form a complete circuit. Very simple! 

However, with common cathode, all the ground (-) pins are connected. You then use shift registers to divert power to the anodes of the segments. However, the problem with this setup, as I've learned the hard way, is that you need to worry about sourcing AND sinking current. Most uControllers and shift registers cannot source nor sink a lot of current. Otherwise, you'd burn it out. If you require more voltage or current, you'll then need to worry about transistors or darlington arrays (external drivers) since you're using shift registers to tell them which segments need power (high voltage or current) and when to ground it. In other words, the hardware and code get more complicated and drives up cost.

2. Microcontroller
I strongly suggest getting an Arduino. The environment is much more intuitive and there is a huge pool of resources out there if you get stuck. If you're prone to making mistakes, get the Ruggeduino. It's only $10 more than Arduino Uno and protects you and your precious uController from stupid mistakes.

3. Serial-In Parallel-Out Shift Register
If you have the money, buy from the TPIC6x595 or TPIC6x596 family of shift registers by Texas Instruments. I use the TPIC6B596 in this instructable. The difference between its siblings (A, B, and C series) is the current handling capacity. In addition, the 596 family provides better reliability in cascading applications. When choosing shift registers, always make sure you do not exceed their current-handling limits.

Side notes:
- I would avoid the popular 74HC series as it can only source/sink a max of 70mA through the chip and cannot handle high voltages.

- I would also avoid using the common cathode / MAX7219/7221 setup with high voltage displays. Trust me... It's not worth it! You don't want to know the trouble I've been through with this setup. Even though there's a good library out there, it's best to understand and have control of the underlying mechanism behind shift registers and multiplexing.

4. Regulated DC Power Adapter
If you're planning to drive high-power displays, consider buying an regulated dc adapter that is greater than the forward voltage of your display. A regulated DC adapter provides stable voltage under any load. Just make sure its current rating is greater than what's required. In most cases, ratings higher than 500mA are enough (higher is better).

5. Resistors
You always need resistors to turn down current going through the LEDs.

The formula for calculating required resistor is:
(Supply Voltage - Minimum or Typical Forward Voltage per segment) / Desired forward current in Amps

Note that the desired current should always be a bit less than the absolute maximum forward current stated in the datasheet in order to extend the life of the LEDs. Don't forget that you need to do a separate calculation for the decimal point which may have a lower forward voltage requirement.

Also, be careful with datasheets of large displays. If they show low forward voltages (< 5V), that rating may be for the individual LED in a segment rather than the entire segment. So, for example, if there are  5 LEDs in series per segment and the datasheet shows a forward voltage of 2.6V for a 5 inch display, you probably need to multiply it by 5 to get the correct forward voltage for the entire segment. Here is one such example. It's more complicated if it's series/parallel arrangement which is beyond the scope of this instructable.

6. Breadboard and jumper wires
I recommend buying large solderless breadboards and a lot of jumper wires of various configurations (Male to Male, Male to Female). They are also known as dupont cables.

Step 2: Draw the Schematic

Now that you've got your hardware, you need to lay down and draw the connections between all the components. It may seem overwhelming, but it's real easy once you understand the general pattern.

Before we begin, we need to know what each pin of the TPIC6B596 shift register does and where it connects to (I'll also give aliases that other manufacturers use):
Vcc: The chip's power supply (Connects to +5V)

SER IN: The input pin where data is received from the uController. (Connects to MOSI or Arduino user-defined pin). Aka Serial In, SER, MOSI, DIN... 

Drain 0 to 7: The pins where segments may connect to ground (Connects to segments A-DP respectively). Aka Source/Sink outputs, Qa-Qh, D0-D7...

SRCLR: When high (+5), data can be transferred to the output buffer. When low (0V), all registers are cleared. (Connects to Vcc or +5 so it's always high). Aka Serial Clear, MR (master reset)

G(bar): Enables/Disables drain pins. When low (0V), register data is transparent to the output buffer. When high (+5), drain pins are disabled but data is retained in the storage register. (Connects to Arduino pin with a pull-up resistor to +5V. We do this to ensure that no garbage will be seen during boot up.) Aka OE (output enable)

SER OUT: The output pin where data is transmitted to the next shift register (Connects to SER IN of the next shift register). Aka Serial Out, DOUT, Qh'...

SRCK: The pin where the clock signal is received. (Connects to SCK or Arduino user-defined pin and all SRCK pins). Aka Serial Clock, CLK, SRCLK

RCK: The pin where the latch signal is received. (Connects to SS or Arduino user-defined pin and all RCK pins). Aka Register Clock, RCLK, LATCH, SS, LOAD, CS

GND: Connects to ground (0V). (Note that there are 3 ground pins. You can connect one of them to ground as they are internally connected. If you have problems later on, try connecting each to ground). Aka 0V, PGND, LGND

Now that you know where each pin connects to, you can now draw up your schematic. The schematic I give is simpler and easier to understand than others out there. However, you may need to change the resistor values and voltage going to the anodes of your displays.

Step 3: Time to Code!

By now, you probably have an idea of how you will code your program based on your schematic. 

To start you off, here is a simple sketch that will display 456 with decimal in a time-delay manner to demonstrate multiplexing. I also made it flexible to accommodate various configurations you may have. The source is also available at the bottom to download. Be sure to understand the code first, then copy and paste into a new sketch. Next, make the necessary adjustments to the variables and pin assignments as they may be different in your situation. Once done, upload to the Arduino.

One thing you should note is the direction of the data flow. If you remember my schematic, the left most shift register will receive the first byte but will dump that data out of the SER OUT if another byte is received. So, if you want to send 11111111 to the right-most digit, you would need to shift out 11111111 then 00000000 and finally 00000000. Don't worry, this is handled automatically by the code.

BEGIN CODE

/*
  Code for interfacing with 7 segment displays
using the multiplexing method
and the TPIC6B595 Shift Register (1 per digit)
By K.O.
*/

//Pin Assignments (You should change these)
const int CLK       = 9;           //Connected to TPIC pin 13: SRCLK (aka Clock)
const int LATCH     = 10;          //Connected to TPIC pin 12: RCLK (aka Latch/load/CS/SS...)
const int OE        = 11;          //Connected to TPIC pin 9: OE (Output Enable)
const int DOUT      = 12;          //Connected to TPIC pin 3: SER (aka MOSI)

//Number Patterns (0-9)
//***Drains 0-7 must be connected to segments A-DP respectively***
const byte numTable[] =
{
  B11111100,
  B01100000,
  B11011010,
  B11110010,
  B01100110,
  B10110110,
  B10111110,
  B11100000,
  B11111110,
  B11110110
};

//Global Variables
int numDevices = 1;                       //The number of x-digit display modules you plan to use
int maxDisplays = 3;                      //The maximum displays that could be accommodated (see note 1)
int maxDigits = 3;                        //The maximum digits you plan on displaying per display module (each SR can handle a max of 8 digits)
int SRData[3][3];                         //The storage location for the digit information. We must specify a fixed array at compile time (see note 2)
boolean debug = true;                     //Change to true to print messages
int delayTime = 1000;                     //Optional (just for demonstrating multiplexing)

/*
  Notes
1. It is recommended to use an external power supply to avoid oversource/sinking the microcontroller
    or if you need to power high voltage, high current displays. This code will turn on/off all segments in a digit for ***each*** display.
    So, if using 2x 3-digit displays all displaying an 8 + DP, the max consumption will be:
       20mA (desired forward current) * 8 (segments that are on) * 2 (displays showing identical info) = 320mA
2. The first dimension should equal maxDisplays. The second dimension should equal the number of digits
*/

void setup()
{
  Serial.begin(9600);

  //Set pin modes
  pinMode(CLK,OUTPUT);
  pinMode(LATCH,OUTPUT);
  pinMode(DOUT, OUTPUT);
  pinMode(OE, OUTPUT);

  //7-Segment Display Init
  digitalWrite(OE,LOW);        //Enables SR Operation
  initializeSRData();          //Prepares SR and clears data on serial line

  //Test
  setDigit(0,0,4,true);
  setDigit(0,1,5,true);
  setDigit(0,2,6,true);
}

void loop()
{
  refreshDisplay();            //Cycles through all displays and digits
}

//==========BEGIN SR Functions==========
void initializeSRData()
{
  //Display Scanner (Iterates through each display module)
  digitalWrite(LATCH,LOW);      //Tells all SRs that uController is sending data

  for(int dispID = 0; dispID < maxDisplays; dispID++)
  {   
    //Digit Scanner (Iterates through each SR (digit) in a display module)
    for(int digit = 0; digit < maxDigits; digit++)
    {
      //Clears any garbage on the serial line
      shiftOut(DOUT,CLK,LSBFIRST,0);          //Shift out 0s to all displays
      SRData[dispID][digit] = 0;              //Stores a 0 for each digit so its completely off
    }
  }
  digitalWrite(LATCH,HIGH);      //Tells all SRs that uController is done sending data
}

void printSRData()
{
  if(!debug)
    return;

  Serial.println("Printing SR Data...");

  //Display Scanner
  for(int dispID = 0; dispID < maxDisplays; dispID++)
  {   
    Serial.print("Display # ");
    Serial.println(dispID);

    //Digit Scanner
    for(int digit = 0; digit < maxDigits; digit++)
    {
      Serial.print("Digit ");
      Serial.print(digit);
      Serial.print(": ");
      Serial.println(SRData[dispID][digit],BIN);
    }
    Serial.println();
  }
}

void setDigit(int dispID, int digit, int value, boolean dp)
{
  //Parameter checker
  if(dispID < 0 || dispID >= numDevices)
  {
    Serial.println("dispID OoB!");         //OoB = Out of bounds
    return;
  }

  if(digit < 0 || digit > maxDigits)
  {
    Serial.println("digit OoB!");
    return;
  }

  if(value < 0 || value > 9)
  {
    Serial.println("Invalid value!");
    return;
  }

  value = numTable[value];

  //Toggle dp if needed
  if(dp)
    value |= B00000001;          //Turns on the first binary digit (segment) using an OR bitmask

  //Store the digit
  SRData[dispID][digit] = value;

  if(debug)
    printSRData();
}

void setSegments(int dispID, int digit, byte value)
{
  //Parameter checker
  if(dispID < 0 || dispID >= numDevices)
  {
    Serial.println("dispID OoB!");
    return;
  }

  if(digit < 0 || digit > maxDigits)
  {
    Serial.println("digit OoB!");
    return;
  }

  if(value < 0 || value > 255)
  {
    Serial.println("Invalid byte!");
    return;
  }

  //Store the digit
  SRData[dispID][digit] = value;

  if(debug)
    printSRData();
}

void clearDisplay(int dispID)
{
  initializeSRData();
  refreshDisplay();
}

void refreshDisplay()
{
  //Digit Scanner
  for(int digit = 0; digit < maxDigits; digit++)
  {  
    //Display Scanner
    digitalWrite(LATCH,LOW);
    for(int dispID = numDevices -  1; dispID >= 0; dispID--)
    {
      //Pre-Digit blanker (shifts out 0s to correct digits before sending segment data to desired digit)
      for(int blanks = (maxDigits - 1 - digit); blanks > 0; blanks--)
        shiftOut(DOUT,CLK,LSBFIRST,0);

      shiftOut(DOUT,CLK,LSBFIRST,SRData[dispID][digit]);

      //Post-Digit blanker (shifts out 0s to remaining digits)
      for(int blanks = digit; blanks > 0; blanks--)
        shiftOut(DOUT,CLK,LSBFIRST,0);
    }
    digitalWrite(LATCH,HIGH);

    //Demonstrates multiplexing operation
    delay(delayTime);
    delayTime -= 10;
    if(delayTime <= 0)
      delayTime = 0;
  }
}

//==========END SR Functions==========

END CODE

Step 4: Build It!

Now that you've got an understanding of the hardware and operation, its time to build!

With your schematic at hand, layout the ICs first as nicely and efficiently as you can giving consideration to the placement of resistors and other components. The order of placing components should be ICs > resistors and other component parts > wire connections. 

Tip: When wiring, always do power connections LAST! If you apply voltage to the wrong pin, then you and the components are toast! Always double and triple check wirings! Don't forget that if using the large solderless breadboard, you need to bridge the power rails when necessary. (Took me some hours to figure that out the first time).

If you've followed my schematic, your breadboard should look like the one above

Step 5: Test It!

Now that you've built it, its time to power up the Arduino--don't forget the external power!. If all goes well, your display should operate similar to the video above. Cool eh?

If not, check your wirings! A lot of things can go wrong if you forget to connect grounds, mixup clock wires, mixup connections to segment pins etc... Also, check your code if you messed up pin assignments or didn't update the global variables section. 

If it does, great! You can now adapt it to whatever you wish. This code doesnt really do anything useful other than make interfacing with 7 segments easier... To make it more practical, you would need to create an interrupt routine to pause the refreshing of the display and do other tasks. But that is beyond the scope of this instructable.

Multiplexing is one of the most important techniques you need to master in the world since it's used everywhere. I hope this tutorial has been a great help!