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.

Dear readers, please note that this is the old website of maxEmbedded. The articles are now no longer supported, updated and maintained. Please visit the new website here and search for this post. Alternatively, you can remove .wordpress from the address bar to reach the new location.

Example: If the website address is http://maxEmbedded.wordpress.com/contact/, then removing .wordpress from it will become http://maxEmbedded.com/contact/.

We apologize for the inconvenience. We just want to give you a better viewing and learning experience! Thanks!

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

30 responses to “Seven Segment Multiplexing

  1. Thanks for your work. I did a Mega32 4 digit multiplexed 7 segment
    display and used an idea from an old Heathkit H8 8080 micro computer. It was based on a suitable timer interrupt which always displayed the same RAM locations to the 4 digits. So it was always displaying something and depended on the code to place the proper digits when a change was required including blanking the segments when appropiate. This worked very well and I just mention it if it might be appropiate for someones project.

  2. i am trying to make an object counter and didn’t knew how to interface the push button to avr. This tutorial is really cool and I actually had that EUREKA!!!! moment.

  3. i tried to implement this in the software,but the displays i used,were already multiplexed internally,so to activate a particular segment ones need to make the enable buttton of the pin 9. i implemented the entire code with a little change of making the port d low to activate the segment instead of high.The code worked,pretty well,but the thing is as i used to press d button,the last segment i.e the msb segment used to lit off while the code is being executed until the count is incremented…!! why is that so.?? I tried this with a 16mhz frequency

  4. Group 4: Use four multiplexed seven segments to make a stop watch. Connect three push
    buttons with P3.5, P3.6 and P3.7 to control start, stop and reset operation of the stop watch
    respectively.

    can u help me out

    • Hi Sadia,
      We are here NOT to do anybody’s homework/assignment/project. We are here to teach you the concepts and to enable you to do your projects on your own!
      If there is anything else, then please reach out to us.
      Thanks.

  5. Thanks dude! Amazing work. Your blog helps me a lot 🙂 🙂 i read your article about adc and this 7 segment multiplexing and built a digital thermometer using lm35 and 3 ssds. Keep up the good work. *cheers*

    • Hi Muthu,
      Mind if you send the video of your project so that we can share it here for the benefit of other readers as well?
      Your video will be posted on the maxEmbedded’s youtube channel, which will be shared here, and you will be given the credit for the video (of course!).
      Send the video to max@maxEmbedded.com if you wish.
      Cheers! 🙂

  6. And can you explain me about this syntax :
    “#define F_CPU 100000000”
    i can vaguely understand that its defines the processing speed but enlighten me more please and what i can achieve my manipulating this code .

    • Hello Muthu!
      This syntax (#define F_CPU … ) is for defining the clock frequency on which the microcontroller would be running. The default value for F_CPU is 1MHz, which is the internal Clock frequency for a number of AVR microcontrollers. However, this syntax is useful when the Microcontroller would be running on external clock source. For more information on this syntax, refer to this post.

      • Yup got it . I’m using a 16MHz external crystal so im defining my clock frequency as 16mhz for accuracy.In the above code You are using 100mhz clock frequency which means your cpu runs pretty much faster . so if you define 100ms it actually means 1ms in real world right? provided you are using 1mhz oscillator .Correct me if im wrong

        • Thanks for pointing out! I had added two ‘0’ by mistake. My MCU is actually running on a 1Mhz clock source only.

          Yup, you are right! 🙂

  7. thanks for your effort and sharing i want to ask about resistance you said connect to suitable resistance before base or it will not work
    1)why we use resistance ?
    2)how i know its suitable?

    • Hello Shady!
      Here i have used a BC547 N-P-N transistor. To act as a switch, it must be used in saturation region. When in saturation region, the base-collector and the collector-emitter junction are forward biased and hence the collector-emitter get shorted, since the base current is very less compared to collector current.
      We use resistance to bring the transistor in saturation region.
      However, if we do not use a resistance and hence a large amount of current flows into the base, the base and emitter get shorted instead of collector-emitter being shorted, and hence it does not solve our purpose.
      Since the V-I characteristic curve at the saturation region is hyperbolic, there is no proper demarkation in active region and saturation region.
      A simple trick to know whether a resistance is suitable or not is to measure the current which is to flow into the collector. The current flowing into the base should be much less than that flowing into the collector.

  8. Hi,
    This is a great tutorial.. Thank you.
    But I’m using a common anode Seven segment. In that, the pins 3 or 8 should be supplied with +5 V. So while multiplexing, how should i connect the pins 3 or 8 to +5 V through a transistor. And also which transistor should i use and how to make the connection.
    Thank you.

  9. thanks for this explanation but actually when i applied these concepts in my code for AVR atmega16 i face a problem that the simulator not working probably the 7segment is flashing and the proteus give error called ( simulation is not running in real time due to excessive CPU load).
    could any one help me !

    • You applied the same code?
      Because this code is tested many times in hardware, and works perfectly everytime.

    • Hello Grace,
      In order to reset the counter, you could have another reset switch, which when pressed, should write zero into the EEPROM memory.

We'd love to hear from you!

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s