Friday, March 27, 2009

Multiplexing two 7segment LEDs

This is a follow up on my previous post on 7segment LED display.

When it comes to displaying 2 digits, there are at least 2 choices. The simplest choice is: In addition to the existing 7bits for the first digit, add 7 more data bits and let them drive the second digit. The obvious drawback with this approach is the need for large number of data lines. With increase in the number of digits, you need 7bits for each additional digit. At some point the idea does not scale and goes impractical.

The second choice is to use Time Division Multiplexing (TDM). In this approach the same data bus (7bit always) is used to show the digits across all the 7segment LEDs. A separate control signal is added (1 bit per digit -- simple appraoch; ideally 'log (base 2) n' control lines are enough for n digits). The control signal is used as 'chip-select' to select the appropriate digit and the data at the data bus at that moment is used to light up that segment appropriately. An important caveat in TDM is that, the 7Seg LEDs will not retain the digit when the control transfers to the next segment (obvious?). As a result only one 7seg will be lit at any point in time. Thanks to the persistence-of-vision property of the human eye, by switching the control between the LEDs at a fast pace, it is possible to "virtually" light up more than one 7seg at the same time. And that's the idea behind this project.

Hardware:
The 7bit data bus control the digit to be displayed (as in my previous post with single 7seg). Additionally, 2 control lines, each connected to the common anode of each of the 7seg select the digits by supplying the positive voltage(+5V). It is actually a good idea to connect the control signals to the base of a transistor and use the transistor as a switching device to turn on/off the positive voltage to the LED -- I don't have transistor at the moment; given that the 7seg does not draw too much current, it was safe to drive them directly from the uC's output pins. I would not recommend this though.

Software:
The software part is little complicated. The idea of the program is to display 2 digits of a running counter. The counter has to be incremented at a slow pace (once per second?) so human eye can follow the counter. However, the 7segs have to be refreshed at a very high rate otherwise we would see flickering of digits (remember only one of them is lit at any moment). To implement this, it is possible to run a loop with few ms sleep interval and keep refreshing the digits; and increment the counter only after every 100 iterations (so in effect the counter is incremented only after a second or so). This is naive and may not scale when there is more functionality than just incrementing the counter. So the ideal method is to make use of timer interrupts. ATMega8 has 3 timers. I have made use of timer0. Once enabled, whenever the counter belonging to the timer (in this case TCNT0) overflows beyond its size (in this case 8 bit), the uC invokes the appropriate interrupt handler. In the interrupt handler, I've written code to update one digit at every invocation.

Here is the code:

/* Author: Gerald Naveen A (ageraldnaveen at gmail dot com) */

// Write the digit on PORTD (0-7 bits)
// Select the digit on PORTC (0-1 bits)
#include <avr/io.h>
#include <avr/interrupt.h>

#define F_CPU 1000000 // 1MHz
#include <util/delay.h>

//my implementation that wraps writing a digit to 7seg
//implements seg7_write_digit
#include <gerald/7seg.h>

// volatile makes sense
volatile int g_cur_val = 0;

void initialize_timer0()
{
TCCR0 |= (1 << CS01); // configure the prescaler for timer0
TIMSK |= (1 << TOIE0); // enable timer0 interrupt
TCNT0 = 0; // initialize timer0 counter to 0
}

// the TIMER0 overflow interrupt handler
ISR(TIMER0_OVF_vect)
{
static int n = 0; // decides which digit to update now.

if(!n) {
// make sure you disable the control signal before changing
// the data bits. otherwise you can notice small leakage of
// data onto other digit.
PORTC = 0;
seg7_write_digit(g_cur_val % 10); // output ones
PORTC = 0x1;
}
else {
PORTC = 0;
seg7_write_digit((g_cur_val/10) % 10); // output tens
PORTC = 0x2;
}
n = !n; // toggle the digit selection
}

int main()
{
DDRD = 0x7F;
DDRC = 0x03;
PORTD = 0xFF;
PORTC = 0; // disable control signals by default

sei(); // enable global interrupts

initialize_timer0();

while(1)
{
g_cur_val++; // just keep incrementing the counter
_delay_ms(100);
}
return 0;
}
Here is the project in action:


No comments:

Post a Comment