Pages Menu
TwitterRssFacebook
Categories Menu

Posted by on Jan 30, 2013 in Atmel AVR, Electronics, Microcontrollers | 43 comments

Seven Segment Multiplexing

Seven Segment Multiplexing

So after a long hiatus from AVR tutorials, here we are again on it! This time, its about Seven Segment Multiplexing! This post is written by Yash Tambi, a core committee member of roboVITics.

What is a Seven Segment Display?

A Seven Segment Display (SSD) is one of the most common, cheap and simple to use display. It looks like this-

The pin configuration is as follows-

imgres

SSD consists of a total of 8 segments, out of which 7 are for displaying numbers, and one is for decimal point. Each segment has 1 led inside it.

Types of SSDs

Seven Segment Displays are of two types-

  1. Common cathode – In the common cathode type SSD, the –ve terminal of all the LEDs is commonly connected to the ‘COM’ pin. A segment can be lighted up when ‘1’ is given to the respective LED segment and ground is connected to the common. The internals are given as below:imgres-1
  2. Common anode – In the common anode type SSD, the +ve terminal of all the LEDs is commonly connected to the ‘COM’ port of the SSD. A segment can be lighted up when the ‘COM’ port is connected to the +ve battery supply, and ground is given to the respective segment.

imgres copy

A common cathode SSD is simpler to use with an MCU, and hence I have written the code accordingly using a common cathode SSD.

For multiplexing, we often have to use a BJT, so here is how a BJT works:

bc547_large

In my circuit, I have used the NPN BC547 Transistor.

For the simple use of a BJT as a switch, the emitter-collector junctions get shorted when there is an input signal at the base terminal, else it remains cut-off. The input should be given through a suitable resistor.

Why Multiplexing?

Often we need to use two, three or more SSDs and that too using only a single MCU, but one problem that we face is the lack of I/O pins in the MCU, as one SSD would take 8 pins, and so three SSDs would take 24 pins. In ATmega8, we have only 23 I/O pins. So what is the solution?

One possibility is that we use a bigger MCU with more I/O pins, like ATmega32, which has 26 I/O pins. But then we are still restricted to only a maximum of 3 SSDs that can be used.

Another much better and recommended solution to this problem is to multiplex the Seven Segment Displays.

What is Multiplexing?

Wikipedia says ‘ In telecommunications and computer networks, multiplexing (also known as muxing) is a method by which multiple analog message signals or digital data streams are combined into one signal over a shared medium. The aim is to share an expensive resource.’

What we mean by multiplexing of seven-segment display is that we will be using only 7 output ports to give the display on all of the SSDs.

How to achieve this?

Here, we will use ‘Persistence of Vision‘. Now you must have across this term already before. Yes, this is the same technique which is used in cinematography (display images so fast that our brain cannot distinguish any lag between two consecutive images). Similarly, when we mux more than one SSD, we display only one SSD at a time, and we switch between them so fast that our brain cannot distinguish between them.

Lets say each display is active for only 5 milliseconds at a time, i.e. it gets lighted up 1/0.0045 times a seconds, that’s roughly equal to 222 times/second. Our eyes cannot sense a change so fast, and thus what we see is that all the displays are working simultaneously. What is actually happening in the hardware is that the MCU gives ‘1’ to the pin (remember, giving ‘1’ to the base of a BJT shorts the Collector and emitter junction?), which is connected to the base of the transistor of the respective displays, keeps the port ‘ON’ for 5 milliseconds, and then turns it off again. This procedure is put in an endless loop, so that we see the display continuously.

So now we are clear with what actually is multiplexing and how it works! But how to achieve this? There are two methods:

  •  Using timers and delay loops
  •  Using ( _delay_ ) commands

We will here use the 2nd option because it is simpler to understand. If you wish to use to use the first option, then I would suggest you to read Mayank’s awesome timer tutorials.

Connections

Lets discuss the hardware connections first. The microcontroller used here is the 28 pin AVR microcontroller ATmega8. It is assumed that you have prior idea about I/O port operations. If not, take a detour, go through this and get back!

  1. The ‘a’ segment of all the displays is connected in parallel. And likewise, the other segments are connected.
  2. They are connected to the output ports, say for ‘a’ segment; we connect the line to PB3 pin of ATmega8.
  3. The common line of the display is connected to a transistor, BC547 or BC548. The common line of display is connected to the collector and the ground line to emitter of the transistor. The base is connected to one of the output ports, say for Display1; we connect the base to PDx port through a 330Ω.
  4. Note: connect the base to the MCU through a suitable resistor; otherwise the transistor will not act as a switch.

For the purpose of this tutorial, I have made a simple counter. There is a small button attached to the microcontroller. When the button is pressed, the value in the display increases by 1.

Schematics

Seven Segment Schematics

Seven Segment Schematics (Click to Enlarge)

These are the schematics which I designed using Eagle, which is a very powerful tool for creating schematics and PCBs. A voltage regulator ‘IC 7805’ is used to regulate the input voltage. The output from the IC is 5 volts for inputs > 6 Volts.

  • The common lines of the Seven Segment Displays have been connected to the portD as per their placing. For example, the common line of the SSD1 is controlled by PD0. Choosing the pin0 has an advantage, as it makes the coding much easier, as you would realize this in the code.
  • The switch is connected to the PC0, which is on active low configuration. This switch is used to short the pin to the Ground.

Working Model

To test the circuit, I soldered them onto a general purpose PCB and made the circuit. Here is a glimpse of how it looks.

This is the Back part of the Counter I made

Press Me Counter – Back View

This is the front part of the Counter I made

Press Me Counter – Front View

Code Explained

Now that we are done with the hardware, lets discuss how does one go about programming them.

Taking inputs

For I/O port operations, refer to this post. The code below is for taking the inputs through the switch.

while(1)
{
  x=PINC&0b00000001; //CHECK CONDITION OF INPUT '1' at PC0
  if (x==0)
   {
    c++;
    breakup(c); //PASS THE VALUE OF 'c' FOR DIGIT BREAKUP AND DISPLAY
    _delay_ms(100);
    eeprom_update_word((uint16_t*)500,c); //UPDATE ‘C’ IN EEPROM
   }
}
  • The switch has been connected to the pin PC0.
  • Internal pull-up resistors are enabled on the PC0 pin. This is done so that the pin is on Active Low configuration. If you have any queries regarding internal pull-ups, kindly comment below and I will see to it.
  • To enable internal pull-up resistors on a pin (for taking inputs only), the following code can be written:
DDRC|=(0<<0) //define PC0 as input
PORTC|=(1<<0) //enable internal pull-ups
  • I have taken the input from port PC0. X is the variable that stores the masked value of PINC.
  • X=PINC&0b00000001 means that the 0th bit of PINC is used for comparison. Since my switch is used to give an input 0, ANDing it with 1 will give the value of X as 0. When the condition X==0 is satisfied, the next steps are processed.

Storing Individual Digits in an Array

This is the traditional C program, where you need to break up the number into its digits. For example, we have a number “12345”, and I want to store “1”, “2”, “3”, “4” and “5” as individual digits in an array. And yes, you guessed right! We use the modulo-10 method for this!

void breakup(uint16_t num) //FUNCTION TO FIND THE INDIVIDUAL DIGITS OF THE NUMBER
{                          // AND STORE THEM IN A GLOBAL VARIABLE
  DDRD=0xFF; // INITIALIZE PORTD AS ALL OUTPUT
  PORTD=0x00;
  unsigned int i=0;
  while (num!=0)
  {
   digits[i]=num%10;
   num=num/10;
   i++;
  }
  for(i=0;i<5;i++)
  {
   PORTD=(1<<i); // 'i'th PORTD GIVEN HIGH
   display(digits[i]);
   _delay_ms(5);
   PORTD=(0<<i); //'i'th PORTD GIVEN LOW
  }
}
  • The function breakup is used to separate the units, tens, hundreds etc. digits and store them in an array.
  • We repeat the loop 5 times because we want a 5 digit number to be displayed.

Here comes the Multiplexing!

Now here is the ‘multiplexing‘ part of the code!

for(i=0;i<5;i++)
  {
   PORTD=(1<<i); // 'i'th PORTD GIVEN HIGH
   display(digits[i]);
   _delay_ms(5);
   PORTD=(0<<i); //'i'th PORTD GIVEN LOW
  }
  • In the above code, portD is Output
  • The common lines of the SSDs are connected to the ‘base’ of the BJT, which is used to short the Ground line with the common line of the SSD
  • As the value of ‘i’ increases, the corresponding SSD gets activated.
  • Once again, the loop repeats 5 times because there are 5 SSDs.

So in the above lines, we activate a display for 5 milliseconds and then turn it off. This goes on continuously.

EEPROM Operations

Now this is not required unless you don’t want your counter to reset every time you power off your device. For example, you are using this device to count the number of visitors in a particular shop. You obviously wouldn’t want the device to reset every time there is a power cut or when you power off the device every night, right? So how to come over this?

In high school, you must have come across RAM and ROM. Your computer teacher would always tell you the difference between them, but I am sure most of you wouldn’t understand it (just like me)! Lets recall what she used to teach-

RAM (Random Access Memory) is a volatile memory, which gets cleared after the device is turned off.

Whereas ROM (Read Only Memory) is a non-volatile memory, which has the capability to retain the memory even if the device is turned off.

Eureka! Got it!! So all we need to do is to store the values in EEPROM (Electrically Erasable Programmable ROM) whenever the button is pressed, and read it from there as soon as it is powered on!

AVR has inbuilt EEPROM, which can be accessed using simple functions present in the avr/eeprom.h library. The following code illustrates it.

int main(void)
{
  DDRB=0xFF; //INITIALIZE PORTB AS ALL OUTPUT
  DDRC=0b11111110; //PORTC0 DEFINED AS INPUT AND PORTD5 AS OUTPUT
  PORTC|=(1<<0); //enable internal pull-up resistors in PC0 and PC2
  char x;
  uint16_t i, c=0,z=0;
  z=eeprom_read_word((uint16_t*)500); //read the value stored in memory
  eeprom_update_word((uint16_t*)500,0); //write ‘0’ in the memory
  c=z;
  while(1)
   {
     x=PINC&0b00000001; //CHECK CONDITION OF INPUT '1' at PC0
     If (x==0)
       {
        c++;
        breakup(c); //PASS THE VALUE OF 'c' FOR DIGIT BREAKUP AND DISPLAY
        _delay_ms(1000);
        eeprom_update_word((uint16_t*)500,c); //UPDATE ‘C’ IN EEPROM
       }
    }
}
  • The function eeprom_update_word((uint16_t*)m,y) is used to write a 16bit value y to the memory location m in the EEPROM. This keyword is included in the <avr/eeprom.h> header file.
  • To read a 16 bit value z from the EEPROM, the function eeprom_read_word((uint16_t*)m) is used, where m is for the ‘mth’ memory location in the EEPROM.

NOTE: In ATMega8, EEPROM memory locations are from 0 up to 511, so the value of ‘m’ has to be in the interval 0 to 511. I have taken 500 here. You can choose any value of your choice.

Complete Code

Now lets combine all the code snippets together into a full code. This code is also available in the AVR Code Gallery over here.

/*
 * counter.c
 *
 * Created: 1/12/2013 2:48:21 PM
 *  Author: yash
 *
 * COMMON LINES
 * pd0: SSD1
 * pd1: SSD2
 * PD2: SSD3
 * pd3: SSD4
 * pd4: SSD5
 */

#include <avr/io.h>
#include <util/delay.h>
#include <avr/eeprom.h>

#define F_CPU 1000000

volatile uint16_t digits[5]={0,0,0,0,0}; //INITIALIZE VARIABLE TO STORE INDIVIDUAL DIGITS OF THE NUMBER

void breakup(uint16_t num) //FUNCTION TO FIND THE INDIVIDUAL DIGITS OF THE NUMBER
{                            // AND STORE THEM IN A GLOBAL VARIABLE
    DDRD=0xFF; // INITIALIZE PORTD AS ALL OUTPUT
    PORTD=0x00;
    unsigned int i=0;
    while (num!=0)
    {
        digits[i]=num%10;
        num=num/10;
        i++;
    }
    for(i=0;i<5;i++)
    {
        PORTD=(1<<i); // 'i'th PORTD GIVEN HIGH
        display(digits[i]);
        _delay_ms(5);
        PORTD=(0<<i); //'i'th PORTD GIVEN LOW
    }

}

void display (uint16_t num) // FUNCTION TO DISPLAY VALUES ON SSD
{
    DDRB=0b11111111;
    PORTB=0xFF;
    switch(num)
    {
        case 0:
        PORTB=0b11011101;
        break;
        case 1:
        PORTB=0b01010000;
        break;
        case 2:
        PORTB=0b10011011;
        break;
        case 3:
        PORTB=0b11011010;
        break;
        case 4:
        PORTB=0b01010110;
        break;
        case 5:
        PORTB=0b11001110;
        break;
        case 6:
        PORTB=0b11001111;
        break;
        case 7:
        PORTB=0b01011000;
        break;
        case 8:
        PORTB=0b11011111;
        break;
        case 9:
        PORTB=0b11011110;
        break;
        default:
        PORTB=0xFF;
    }
}

int main(void)
{
    DDRB=0xFF; //INITIALIZE PORTB AS ALL OUTPUT
    DDRC=0b11111110;   //PORTC0 DEFINED AS INPUT AND PORTD5 AS OUTPUT
    PORTC|=(1<<0); //enable internal pull-up resistors in PC0
        char x;
    uint16_t i, c=0,z=0;
        z=eeprom_read_word((uint16_t*)500); //read the value stored in the memory
    c=z;
    while(1)
        {
        x=PINC&0b00000001; //CHECK CONDITION OF INPUT '1' at PC0
            if (x==0)
        {
               c++;
               breakup(c); //PASS THE VALUE OF 'c' FOR DIGIT BREAKUP AND DISPLAY
               _delay_ms(1000);
               eeprom_update_word((uint16_t*)500,c); //update memory
        }

        else
        breakup(c);
    }
}

Building and Burning the Code

The next step is to build the code and burn it into your microcontroller. You can refer to this post to know about it.

Video

This is the final video of the device made by me. You can see that the SSDs are displayed very clearly. You cannot even determine that its actually a persistence of vision! Also, turning off the power will have no effect on the reading of the counter.

So folks this was it on how to multiplex SSDs on a single MCU! :)

If you have any clarifications required, suggestions, doubts, criticism, please please and please comment below! Do leave your compliments below as to how you liked this post, since it will encourage me to write more articles here on maxEmbedded! Also, do not forget to like the maxEmbedded FB page, and do subscribe to maxEmbedded to stay tuned!

Cheers!

Written by–
Yash Tambi
VIT University, Vellore
yash@delta.robovitics.in

QC and mentorship by–
Mayank Prasad
VIT University, Vellore
max@maxEmbedded.com

43 Comments

  1. Hey Max
    Really a good stuff wtitten
    But if i got 4 – 7 seg and the a- h pins are connected to 4 different port pins like PA1, pb3, pb2, pc4 like this then how to multiplex it

    • If the pins are connected to individual pins of multiple ports, then you’ll have to control each pin individually. What PORTx does is that it allows you to control the entire port all at once, but you can also modify their individual bits if you like.

Leave a Reply

%d bloggers like this: