You are on page 1of 45

Tuesday, December 2, 2008

LED 7-Segment Multiplexing


In my first Digital Clock, I use 6 pcs. of CD4543,BCD to 7-Segment decoder, to drive 6 digit
LED 7-Segment display for the sake of simplicity of the software. However, the hardware needs
many components. As you can see in the post, the PCB of the clock is quite big and containing a
lot of solder points. To reduce the number of components, I will integrate the function of
CD4543 into the firmware. One digit requires 7 connections (wires) for all segments and 1
connection for common cathode (or anode). If I connect 6 digits to the MCU without any
modification, I will need 7-segment x 6 digit = 42 connections . That means I need to use MCU
with atleast 42 I/O pins. As you know, it is a waste for using a lot of MCU pins just for display.
The required pins can be reduced dramatically by using a technique called Multiplexing.
Multiplexing
Multiplexing technique is based on the idea of Persistence of vision of the human eyes. The
sample schematic of 3 digits multiplexing is shown below. Segment a-g of each digit are
connected together. Each digit is switched on-off by controlling signal at Digit 1, Digit 2 and
Digit 3. For example, if Digit 1 is '1' , Digit 1 will be on. If Digit 1 is '0', Digit 1 will be off.
People will see all 3 digits display in the same time if each digit switch on and off fast enough.

By using multiplexing technique the number of required connections for 6 digits display is
reduced from 42 pins to 7-Segment+6 digits = 13 pins Wow!! I can drive 6 digits 7-segment
display by using just a PIC16F627A.
Next time, I will post the program for LED 7-Segment Multiplexing

Sunday, December 21, 2008


6 Digits LED 7-Segment Multiplexing
In the post 'LED 7-Segment Multiplexing', I have explained about the concept and benefits of
multiplexing. Multiplexing implementation is very similar to driving Led Dot Matrix. I use
Timer0 interrupt for switching through each digit. Timer0 or TMR0 is an 8-bit timer which
overflows every 256 (0xFF) counts. It's known that the refresh rate above 50Hz would be enough
for human's eyes to see the display without recognizing the flickering. If I set TMR0 with 1:8
Prescaler, the multiplexing frequency will be
4MHz(internal OSC.)/4(working OSC)/8(prescaler)/256(max counts of TMR0)/6(number of
digits) = 81.3 Hz which is good for a display.
Just an example, I have implemented (in Proteus) a 999999-second counter by using 6
Digits LED 7-Segment Multiplexing technique. There are 2 main components in the
project, PIC16F627A or PIC16F628 and 6 x LED7-segment display. The schematic shows
below. The crystal is 32.768KHz as usual. There is a 10KOhm pull up resistor at RA4 pin as
this pin is an open-drain pin as I described in "Open-Drain RA4 pin on PIC
Microcontroller".

Wednesday, December 31, 2008


6 Digits 7-Segment LED Multiplexing using a Shift Register
Multiplexing technique can reduce number of needed I/O pins of the MCU as I have explained in
'LED 7-Segment Multiplexing' and '6 Digits LED 7-Segment Multiplexing'. In those posts, I
used 13 I/O pins for driving 6 digits LED 7-Segment. However, the PIC16F627A and
PIC16F628 have only 15 usable I/O pins that include 2 pins for external 32.768KHz oscillator.
So, there is no pin left for time setting buttons. I can change to the PIC that has more I/O pins,
but I don't think it's a good solution. From my searches, I can overcome this I/O pins shortage
problem by using shift register to expand the MCU I/O pins.
The concept is very similar to led dot matrix driving technique. Each digit is multiplexed via a
shift register 74HC595 which is required 3 pins of the MCU. Each segment of the 7-segment
2

display is driven by PORTA of the PIC16F628. As a result, the required pins for driving 6-Digit
7-Segment display are just 3+7 = 10 pins!. With this configuration, there are 3 I/O pins that are
free for time setting buttons and driving blinking second LEDs.
I use TMR2 module for scanning digits. TMR2 is an 8-bit timer which overflows every 256
(0xFF) counts. It's known that the refresh rate above 50Hz would be enough for human's eyes to
see the display without recognizing the flickering. If I set TMR2 with 1:8 Prescaler (T2CON =
0x3C), the multiplexing frequency will be 81.3Hz (4MHz/4/256/8/6 = 81.3Hz) which is enough
for flicker free display.
PORTA is used to drive each segment of the 7-segment displays. However, I have to skip the
RA5 as it's a MCLR pin and it can be only input pin. So, my 7-segment digit mask is different
then the normal 7-segment digit mask.
my PORTA 7-segment digit mask : {0x5F, 0x06, 0x9b, 0x8f, 0xC6, 0xCd,0xDD, 0x07,
0xDf, 0xCf}
Normal 7-segment digit mask : {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f} for
number 0-9 respectively.

Below is the example schematic of 999999-second counter using the PIC16F627A or PIC16F628
and a shift register. I will not implement a clock with this configuration as I need more free MCU
pins for driving Alarm buzzer and other things.

The source code in MikroC is listed below: (.hex is also available, please feel free to contact me)
//PIC16F627A
//4MHz Internal OSC
//MUX by the MUC itself with Interrupt
//TMR0 .. check the prescelar+delay in scan routine as they are
related
//punkky@gmail.com
unsigned short number [10] = {
0x5F, 0x06, 0x9b, 0x8f, 0xC6, 0xCd, 0xDD, 0x07,
0xDf, 0xCf
};
unsigned short digit [6];
unsigned short counter;
unsigned short shift_register;
unsigned short x1;
unsigned short x2;
unsigned short x3;
unsigned short x4;
4

unsigned short x5;


unsigned short x6;
unsigned short tick;
void interrupt ()
{
if (INTCON.T0IF)
{
//Scan digits with TMR0
INTCON.T0IF = 0;
if (counter == 5)
{
PORTA = number [digit [counter]];
Delay_us (500);
shift_register = 0x01;
PORTB = ~shift_register;
PORTA = 0x00;
counter = 0;
} else
{
PORTA = number [digit [counter]];
Delay_us (500);
shift_register = shift_register << 1;
PORTB = ~shift_register;
PORTA = 0x00;
counter ++;
}
}
if (PIR1.TMR1IF)
{
TMR1H = 0x80;
PIR1.TMR1IF = 0;
tick = 1;
//update current time
x6 ++;
if (x6 > 9)
{
x6 = 0;
x5 ++;
if (x5 > 9)
{
x5 = 0;
5

x4 ++;
if (x4 > 9)
{
x4 = 0;
x3 ++;
if (x3 > 9)
{
x3 = 0;
x2 ++;
if (x2 > 9)
{
x2 = 0;
x1 ++;
if (x1 > 9)
{
x1 = 0;
}
}
}
}
}
}
}
}
void main ()
{
//Digital I/O for PORTA
CMCON = 0x07;
TRISA = 0x00;
PORTA = 0x00;
TRISB = 0x00;
PORTB = 0x00;
//Internal Clock 4MHz
PCON.OSCF = 1;
counter = 0;
// Enable TMR0
OPTION_REG.T0CS = 0;
// Enable Prescaler
OPTION_REG.PSA = 0;
// PS0,1,2 = 010 = 3
// 3 means 1:8 prescaler
6

// 1:2, 1:4, 1:8, 1:16, 1:32, 1:64, 1:128, 1:256


OPTION_REG.PS2 = 0;
OPTION_REG.PS1 = 1;
OPTION_REG.PS0 = 0;
INTCON.T0IF = 0;
INTCON.T0IE = 1;
INTCON.GIE = 1;
INTCON.PEIE = 1;
T1CON = 0x0F;
TMR1H = 0x80;
TMR1L = 0x00;
// Enable TMR1 interrupt
PIE1.TMR1IE = 1;
shift_register = 0x01;
x1 = 0;
x2 = 0;
x3 = 0;
x4 = 0;
x5 = 0;
x6 = 0;
while (1)
{
if (tick)
{
tick = 0;
//update digits
digit [0] = x1;
digit [1] = x2;
digit [2] = x3;
digit [3] = x4;
digit [4] = x5;
digit [5] = x6;
}
}
}

Tuesday, August 4, 2009


Small LED dot matrix development board
7

I was very busy for the past two months so this blog just didn't move. As you may know, the
LED dot matrix display is my favorite device. I have designed a small development board for
testing my led dot matrix related programs.
The schematic is as the following:

Schematic of 8x8 Led Dot Matrix Clock using PIC16F627A

The above schematic is 8x8 Led Dot Matrix clock using PIC16F627A (or PIC16F628
which is more expensive but larger memory) and a shift register 74HC595. The PIC
is running with 4MHz internal oscillator so pin 15 (RA6) and 16 (RA7) are free.
Therefore, the PIC16F627a can drive the 8x8 Led Drive matrix with these 2 free pins
(RA6, RA7) and RB0-5, . The Timer1 (TMR1) external clock is generated by the watch
crystal 32.768KHz + load capacitors 15pF and fed to RB6 and RB7. The pins are
used up!. Method of setting time is similar to the 5x7 Led Dot Matrix Clock.
I have intention to make this with SMD components so the 8x8 Dot Matrix will be
the biggest component. The whole clock will be just 20x20mm which is small
enough to be a small pocket watch or even a wristwatch. The only problem is the
power supply.

Saturday, October 11, 2008


8x8 Led Dot Matrix Clock using PIC16F627A

Actually, I have made this long before the 5x7 led


dot matrix clock. The clock utilizes a PIC16F627A and a 74HC595 to drive a small 8x8 Led Dot
Matrix display (20x20mm). My dream is transforming these clocks to watches that I can ware on
my wrist.

Posted by punkky at Saturday, October 11, 2008 9 comments


Topics: Clock, Dot Matrix, PIC16F627a, PIC16F628

Monday, October 6, 2008


Big 5x7 Led Dot Matrix Clock with new font
Now, I'm so crazy about led dot matrix clocks and led dot matrix watches. Just recently, I have
upgraded my small 5x7 Led Dot Matrix Clock with a bigger green 5x7 led dot matrix display
and a new font.
10

For the images below, the time 21:44:37 was displaying on the clock. The clock display is big
comparing to a 44mm Panerai (PAM 4).

11

The code for the new font is showing below:


const unsigned char char2[][4]={
{0x0E,0x11,0x0E,0x00}, //0
{0x09,0x1F,0x01,0x00}, //1
{0x13,0x15,0x09,0x00}, //2
{0x11,0x15,0x0A,0x00}, //3
{0x1C,0x04,0x0F,0x00}, //4
{0x19,0x15,0x12,0x00}, //5
{0x0E,0x15,0x12,0x00}, //6
{0x13,0x14,0x18,0x00}, //7
{0x0A,0x15,0x0A,0x00}, //8
{0x09,0x15,0x0E,0x00}, //9
{0x0A,0x04,0x0A,0x00}, //Comma1
{0x04,0x0A,0x04,0x00} //Comma2
};

Friday, September 26, 2008


Source Code for one chip 5x7 led dot matrix clock
As promised, listed below is the source code in MikroC for the 5x7 Led Dot Matrix Clock. The
free demo version of MikroC can compile this code without any problem. Of course, the code is
just for educational purpose only and use it with your own risk. The explanation about the source
code will come later.
Setting time
- Press and hold MODE button until the digits blink
- The blinking digits are the hour is being set
- Press SET button to count up the hour
- When get the right hour, press MODE button to confirm hour setting
- Now the minute digits are blinking and ready to be set
- Press SET button to count up the minute
- When get the right minute, press MODE button to confirm minute setting and the second will
be set to 0 automatically
- Now, the clock is set and the clock is running
Have fun!!!
Next improvements:
12

1. Using DS1307 as the time base will improve the clock accuracy and backup battery is also a
nice feature of the DS1307
2. Dot matrix will be scanned and updated via interrupt routine to give the PIC more free time
for doing something else
== Updated on 29 Nov 2009 ===
This firmware is written for The 5x7 Dot matrix Cathode Row. If you use Anode Row you have
to make change to make_dotmatrix() by inverting the output PORTB and PORTD
===========================
//One 5x7 Led Dot Matrix Clock
// PIC16F887
// I will use DS1307 as the RTC for the next improvement
// punkky@gmail.com
// 26 Sep 2008
#define MYMODE PORTA.F0
#define SET PORTA.F1
//External OSC 32.768KHz
#define HTMR1
0x80
#define LTMR1
0x00
//Constants for 4MHz Internal Clock
#define PAUSE_TIME 40
#define SCROLL_TIME_DELAY 60
#define HM_PAUSE_TIME 8
#define DTime 200
//Cursor Positions
#define HR_H_POS 0
#define HR_L_POS 0
#define DOT_1_POS 32
#define MIN_H_POS 12
#define MIN_L_POS 12
#define DOT_2_POS 56
#define SEC_H_POS 64
#define SEC_L_POS 72
//5x7 font
//Vertical scan , Little endian order
13

const unsigned char char2[][4]={


{0x1F,0x11,0x1F,0x00},
//0
{0x09,0x1F,0x01,0x00},
{0x17,0x15,0x1D,0x00},
{0x11,0x15,0x1F,0x00},
{0x1C,0x04,0x1F,0x00},
{0x1D,0x15,0x17,0x00},
{0x1F,0x15,0x17,0x00},
{0x10,0x10,0x1F,0x00},
{0x1F,0x15,0x1F,0x00},
{0x1D,0x15,0x1F,0x00},
//9
{0x0E,0x0E,0x0E,0x00}, //Comma1
{0x04,0x0E,0x04,0x00}
//Comma2
};
unsigned char output[8];
unsigned char data[40];
unsigned char ptr;
unsigned char base_ptr;
unsigned char result_ptr;
unsigned char pp;
unsigned char pause_cnt;
unsigned char hr_pause_cnt;
unsigned char min_pause_cnt;
unsigned short counter;
unsigned short sec_h;
unsigned short sec_l;
unsigned short min_h;
unsigned short min_l;
unsigned short hr_h;
unsigned short hr_l;
unsigned short tick;
unsigned short num;
unsigned short i;
unsigned short j;
unsigned short time[8];
unsigned short setting_time;
unsigned short change_MYMODE;
void check_bt();
void button_fn(unsigned short param1, unsigned short pos1, unsign
ed short pos2);
14

void make_dotmatrix();
void interrupt() { //Internal clock
PIR1.TMR1IF = 0;
// clears TMR1IF
TMR1H = HTMR1; //Set only high byte
tick = 1;
// increment counter
}
void update_time();
void make_time();
void fill_data();
void show_digit(unsigned short pos);
void scroll();
void pause_digit(unsigned short position,unsigned short pause_del
ay);
unsigned short shift_register;
void main(){
setting_time = 0;
change_MYMODE = 0;
ANSEL = 0x00;
//Digital I/O for PORTA
TRISA = 0x03;
PORTA = 0x03;
TRISB = 0x00;
PORTB = 0xFF;
TRISC = 0x0E;
TRISD = 0x00;
PORTD = 0x00;
ANSELH = 0x00; //Digital Input for PORTB
OSCCON = 0x65 ; //pic16f887 : 0110 0101, Internal Osc at 4MHz
for lowest power consumption
counter = 0;
shift_register = 0x01;
hr_h = 1;
hr_l = 2;
min_h = 3;
min_l =4;
sec_h = 0;
sec_l = 0;
tick = 0;
15

fill_data();
pause_cnt =0;
hr_pause_cnt = 0;
min_pause_cnt = 0;
//TMR1 setup
T1CON = 0x8F;
// 887 TMR1 Prescaler 1
:1
external clock
INTCON = 0xC0;
// Set GIE, PEIE
TMR1H = 0x80;
TMR1L = 0x00;
Delay_ms(10); // Delay for setting up TMR1
PIE1.TMR1IE = 1;
// enable interupt
tick =0;
while(1){
scroll();
}
}
void show_digit(unsigned short pos){
for(i=0;i<8;i++){
output[i] = data[i+pos];
}
}
void update_time(){
if(tick){
tick =0;
sec_l++;
make_time();
fill_data();
}
}
void make_time(){
if(sec_l>9){
sec_l = 0;
sec_h++;
}
if(sec_h>5){
sec_h=0;
16

min_l++;
}
if(min_l>9){
min_l = 0;
if(setting_time == 0){
min_h++;
}
}
if(min_h>5){
min_h = 0;
if(setting_time == 0){
hr_l++;
}
}
if(hr_l>9){
hr_l = 0;
if(setting_time == 0){
hr_h++;
}
}
if(hr_h >2){
hr_h = 0;
}
if(hr_h >=2 && hr_l>3){
if(setting_time == 0){
hr_h = 0;
}
hr_l = 0;
}
}
void fill_data(){
time[0] = hr_h;
time[1] = hr_l;
time[2] = 10;
time[3] = min_h;
time[4] = min_l;
time[5] = 11;
time[6] = sec_h;
time[7] = sec_l;
for(i = 0;i<8;i++){
17

for(j=0;j<4;j++){
data[j+4*i] = char2[time[i]][j];
}
}
}
void button_fn(unsigned short param1, unsigned short pos1, unsign
ed short pos2){
for(;;){
if(SET == 0){
Delay_ms(10);
while(SET == 0);
Delay_ms(10);
switch (param1){
case 1: hr_h++; break;
case 2: hr_l++; break;
case 3: min_h++;break;
case 4: min_l++;break;
}
make_time();
fill_data();
show_digit(pos1);
}
if(MYMODE == 0){
Delay_ms(10);
while(MYMODE == 0);
Delay_ms(10);
if( param1 == 4){
setting_time = 0;
ptr = 0;
base_ptr = 0;
}else{
show_digit(pos2);
}
break;
}
make_dotmatrix();
Delay_ms(1);
}

18

}
void check_bt(){
while(MYMODE == 0){
Delay_ms(10);
for(i=0;i<100;i++){
for(j=0;j<10;j++){
make_dotmatrix();
Delay_ms(1);
if(MYMODE != 0){
setting_time = 0;
change_MYMODE = 1;
break;
}
}
}
while(MYMODE == 0){
// Set Hr high
setting_time = 1;
show_digit(HR_H_POS);
make_dotmatrix();
}
if(change_MYMODE){
ptr=8;
base_ptr = 0;
change_MYMODE = 0;
}
}
if(setting_time){
INTCON.GIE = 0 ;
button_fn(1,
button_fn(2,
button_fn(3,
button_fn(4,

//Stop time counting

HR_H_POS, HR_L_POS);
HR_L_POS, MIN_H_POS);
MIN_H_POS, MIN_L_POS);
MIN_L_POS, 0);

//Reset timer1
TMR1L = LTMR1;
TMR1H = HTMR1;
INTCON.GIE = 1 ;
sec_l =0;
sec_h =0;

// start clock

19

tick=0;
make_time();
fill_data();
}
}
void pause_digit(unsigned short position, unsigned short pause_de
lay){
if(base_ptr == position){
if(pause_cnt<pause_delay){
base_ptr--;
pause_cnt++;
}else{
base_ptr = position+1;
pause_cnt=0;
}
}
}
void scroll(){
ptr = 0;
//start pointer
base_ptr = 0;
while(base_ptr<41){
result_ptr = (base_ptr+ptr)%40;
output[ptr] = data[result_ptr];
ptr++;
if(ptr == 8){
ptr = 0;
base_ptr++;
//Sec pause
pause_digit(25,PAUSE_TIME);
//Min pause
pause_digit(13,HM_PAUSE_TIME);
//Hr pause
pause_digit(1,HM_PAUSE_TIME);

20

}
for(pp=0;pp<SCROLL_TIME_DELAY;pp++){
update_time();
make_dotmatrix();
}
update_time();
check_bt();
make_dotmatrix();
}
}
void make_dotmatrix(){
if(counter == 7){
PORTB =
output[counter];
//sent font data to PORTB
Delay_us(DTime);
PORTB = 0x00; //Turn off dots
shift_register = 0x01;
PORTD = ~shift_register;
//sent scan data to
PORTD
counter = 0;
} else{
PORTB =
output[counter];
Delay_us(DTime);
PORTB = 0x00; //Turn off dots
shift_register = shift_register << 1;
PORTD = ~shift_register;
counter++;
}
}

21

22

Wednesday, March 11, 2009


Setting Internal Oscillator for PIC16F627A
I love to use PIC16F627A and PIC16F628 because they come with internal oscillators. That
means I can make a project with lower component count (without 1 crystal and 2 load
capacitors). The project setting of MikroC for using internal oscillator of the PIC16F627A shows
below:

Wednesday, December 31, 2008


6 Digits 7-Segment LED Multiplexing using a Shift Register
Multiplexing technique can reduce number of needed I/O pins of the MCU as I have explained in
'LED 7-Segment Multiplexing' and '6 Digits LED 7-Segment Multiplexing'. In those posts, I
used 13 I/O pins for driving 6 digits LED 7-Segment. However, the PIC16F627A and
PIC16F628 have only 15 usable I/O pins that include 2 pins for external 32.768KHz oscillator.
So, there is no pin left for time setting buttons. I can change to the PIC that has more I/O pins,
but I don't think it's a good solution. From my searches, I can overcome this I/O pins shortage
23

problem by using shift register to expand the MCU I/O pins.


The concept is very similar to led dot matrix driving technique. Each digit is multiplexed via a
shift register 74HC595 which is required 3 pins of the MCU. Each segment of the 7-segment
display is driven by PORTA of the PIC16F628. As a result, the required pins for driving 6-Digit
7-Segment display are just 3+7 = 10 pins!. With this configuration, there are 3 I/O pins that are
free for time setting buttons and driving blinking second LEDs.
I use TMR2 module for scanning digits. TMR2 is an 8-bit timer which overflows every 256
(0xFF) counts. It's known that the refresh rate above 50Hz would be enough for human's eyes to
see the display without recognizing the flickering. If I set TMR2 with 1:8 Prescaler (T2CON =
0x3C), the multiplexing frequency will be 81.3Hz (4MHz/4/256/8/6 = 81.3Hz) which is enough
for flicker free display.
PORTA is used to drive each segment of the 7-segment displays. However, I have to skip the
RA5 as it's a MCLR pin and it can be only input pin. So, my 7-segment digit mask is different
then the normal 7-segment digit mask.
my PORTA 7-segment digit mask : {0x5F, 0x06, 0x9b, 0x8f, 0xC6, 0xCd,0xDD, 0x07,
0xDf, 0xCf}
Normal 7-segment digit mask : {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f} for
number 0-9 respectively.

Below is the example schematic of 999999-second counter using the PIC16F627A or PIC16F628
and a shift register. I will not implement a clock with this configuration as I need more free MCU
pins for driving Alarm buzzer and other things.

The source code in MikroC is listed below: (.hex is also available, please feel free to contact me)
//PIC16F627A
//4MHz Internal OSC
//MUX by the MUC itself with Interrupt
//TMR0 .. check the prescelar+delay in scan routine as they are
related
//punkky@gmail.com
unsigned short number [10] = {
0x5F, 0x06, 0x9b, 0x8f, 0xC6, 0xCd, 0xDD, 0x07,
0xDf, 0xCf
24

};
unsigned short digit [6];
unsigned short counter;
unsigned short shift_register;
unsigned short x1;
unsigned short x2;
unsigned short x3;
unsigned short x4;
unsigned short x5;
unsigned short x6;
unsigned short tick;
void interrupt ()
{
if (INTCON.T0IF)
{
//Scan digits with TMR0
INTCON.T0IF = 0;
if (counter == 5)
{
PORTA = number [digit [counter]];
Delay_us (500);
shift_register = 0x01;
PORTB = ~shift_register;
PORTA = 0x00;
counter = 0;
} else
{
PORTA = number [digit [counter]];
Delay_us (500);
shift_register = shift_register << 1;
PORTB = ~shift_register;
PORTA = 0x00;
counter ++;
}
}
if (PIR1.TMR1IF)
{
TMR1H = 0x80;
PIR1.TMR1IF = 0;
tick = 1;
//update current time
25

x6 ++;
if (x6 > 9)
{
x6 = 0;
x5 ++;
if (x5 > 9)
{
x5 = 0;
x4 ++;
if (x4 > 9)
{
x4 = 0;
x3 ++;
if (x3 > 9)
{
x3 = 0;
x2 ++;
if (x2 > 9)
{
x2 = 0;
x1 ++;
if (x1 > 9)
{
x1 = 0;
}
}
}
}
}
}
}
}
void main ()
{
//Digital I/O for PORTA
CMCON = 0x07;
TRISA = 0x00;
PORTA = 0x00;
TRISB = 0x00;
PORTB = 0x00;
//Internal Clock 4MHz
26

PCON.OSCF = 1;
counter = 0;
// Enable TMR0
OPTION_REG.T0CS = 0;
// Enable Prescaler
OPTION_REG.PSA = 0;
// PS0,1,2 = 010 = 3
// 3 means 1:8 prescaler
// 1:2, 1:4, 1:8, 1:16, 1:32, 1:64, 1:128, 1:256
OPTION_REG.PS2 = 0;
OPTION_REG.PS1 = 1;
OPTION_REG.PS0 = 0;
INTCON.T0IF = 0;
INTCON.T0IE = 1;
INTCON.GIE = 1;
INTCON.PEIE = 1;
T1CON = 0x0F;
TMR1H = 0x80;
TMR1L = 0x00;
// Enable TMR1 interrupt
PIE1.TMR1IE = 1;
shift_register = 0x01;
x1 = 0;
x2 = 0;
x3 = 0;
x4 = 0;
x5 = 0;
x6 = 0;
while (1)
{
if (tick)
{
tick = 0;
//update digits
digit [0] = x1;
digit [1] = x2;
digit [2] = x3;
digit [3] = x4;
digit [4] = x5;
digit [5] = x6;
}
27

}
}

1.
/*
2.
Trial1 displaying matrix
3. */
4.
5. sbit clock_a at rc0_bit;
6. sbit data_a at rc1_bit;
7. sbit latch_a at rc2_bit;
8.
9. sbit clock_b at rd6_bit;
10. sbit reset at rd7_bit;
11. sbit enable at rd5_bit;
12.
13. const unsigned short arrays[][8] = {{0xFF,0xFF,0x80,0x77,0x77,0x77,0x80,0xFF}
14.
};
15. unsigned int scanner, scanner2, scantime, disp, row, column, mask, repeat;

28

16.
17. void
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50. }

main() {
LATC = 0x00;
CMCON = 0x07;
ADCON0 = 0x00;
ADCON1 = 0x0F;
TRISC = 0x00;
TRISD = 0x00;
scanner2 = 0;
while(1) {
for(column=0;column<1;column++){
for (repeat=0;repeat<80;repeat++){
for(row=0;row<8;row++){
disp = arrays[column][row];
mask = 0x01;
for (scantime=0;scantime<8;scantime++){
scanner = mask & disp;
if (scanner==0)
data_a = 0;
else data_a = 1;
mask = mask << 1;
clock_a = 1;
clock_a = 0;
}
latch_a = 1;
latch_a = 0;
clock_b = 1;
clock_b = 0;
delay_us(100);
}
}
}
}

LAB 15: SCROLLING TEXT MESSAGE ON AN LED


DOT-MATRIX DISPLAY

In Lab 12, we learned about the basic structure of a monochrome (single color) LED dot matrix
and its interface with a microcontroller to display static characters and symbols. Todays lab is
its continuation, and we will be discussing on displaying a scrolling text message on a 168
LED dot matrix. The microcontroller used is again the same PIC18F2550 from StartUSB for
PIC board. The 16 columns of the LED matrix are driven individually by two shift registers
(74HC595), whereas the eight combined rows are driven by the decoded outputs from a
29

decade counter (CD4017). In Lab 12, columns were scanned, but here we will be scanning
across the rows and feed the column lines with appropriate logic levels. An analog input from
a potentiometer is read by the microcontroller to determine the speed of the scrolling
message. The technique will be demonstrated for right to left scroll, but can be easily
implemented for scrolling in other directions. The program for PIC18F2550 is developed with
mikroC Pro for PIC compiler.

Scrolling message display on 16x8 LED dot matrix

Circuit Diagram
The internal structure of LED dot matrix displays have already been discussed in Lab 12 and is
not going to be repeated here. Two 88 LED matrices are used in this experiment. The similar
rows (cathodes) of both are connected together so that there are 8 combined rows in total,
whereas the columns are driven separately, and hence there are 16 columns altogether. The
combined current of all the LEDs in each row sinks through a darlington-pair transistor array
inside an ULN2803 IC. The 16 column lines (anodes) are driven by the outputs of two shift
registers (74HC595) with current limiting resistors (220 ?) in series, as shown below.

30

Circuit diagram for displaying a scrolling message on LED dot matrix

Role of shift registers (74HC595)


The use of shift registers minimizes the number of I/O pins required to drive the columns of
the LED matrix. For driving 16 columns separately, we need 16 I/O pins of microcontroller,
however, with the use of two 74HC595 ICs, this number is reduced to 3. 74HC595 is an 8-stage
serial-in, serial or parallel-out shift register, with a storage register. The shift register
and storage register have separate clocks: SH_CP (pin 11) and ST_CP (pin 12). Data is fed
serially into the register through DS pin (14) and is shifted on the positive-going transitions of
the SH_CP input. However, the data in each register does not appear at the output pin of
74HC595 unless it is transferred to the storage register. This happens on a positive-going
transition of the ST_CP input. 74HC595 also provides a serial standard output, Q7 (pin 9) for
cascading, which is needed in this experiment. The serial output of the first shift register is
connected to the serial input (DS pin) of the second shift register, so that the 16-bit column
data can be transferred serially through the DS pin of the first shift register. This requires 16
clock pulses on SH_CP followed by a clock pulse on ST_CP. The asynchronous reset pin (MR) is

31

always pulled high (deactivated) whereas the output enable (OE) pin is permanently grounded
(always enabled).

Role of counter (CD4017)


As mentioned in the beginning of this article, this time the rows will be fast-scanned from the
top to the bottom, unlike the columns like we did in Lab 12. Eight I/O pins are required to scan
8 rows in sequence. You can use the PORTB pins of PIC18F2550 for this purpose. But if you
think you will need them for some other purpose (some of PORTB pins have interrupt-onchange feature), you can use a port expander, such as CD4017, that will serve the purpose and
require only two I/O pins of microcontroller. CD4017 is a 5-stage divide-by-10 Johnson counter
with 10 decoded outputs and a carry out bit. The counter is cleared to zero count by a logical
1 on its reset line (15). The counter is advanced on the positive edge of the clock signal (pin
14), when the clock inhibit pin (13) is grounded. The 10 decoded outputs are normally in the
logical 0 state and go to the logical 1 state only at their respective time slot. Each decoded
output remains high for 1 full clock cycle. The carry-out signal completes a full cycle for every
10 clock input cycles and is used as a ripple carry signal to any succeeding stages. The 8 rows
of LED matrix are sequentially connected to the decoded outputs, Q0- Q7, of CD4017 through
ULN2803 IC that has eight Darlington pairs, each of which provides a ground path to sink the
combined current of all LEDs in a row. At the end of every 8th clock cycle, the microcontroller
will reset the counter by issuing a logical 1 to its Reset pin (15).
The microcontroller pins used for driving these signals for 74HC595 and CD4017 are shown
below. A 10K pot is connected to RA0 pin of PIC18F2550 microcontroller that will control the
speed of the scrolling message on the LED matrix display.

32

Microcontroller I/O pins for driving the LED matrix

Circuit setup on breadboard with StartUSB for PIC board

Software

33

If you are unfamiliar with how static characters are displayed on a LED dot matrix, I would
recommend to read Lab 12 first. In row scanning, each row is selected for a very short time
(about 1 ms) and the columns are fed with appropriate logic levels. By quickly scanning across
the rows (> 100 times per second), and turning on the respective LEDs in each column of that
row, the persistence of vision comes in to play, and we perceive the display image as still. The
picture below shows the active LEDs in each row to display the character A on a 88 dotmatrix format. This information for all printable ASCII characters (0-9, A-Z, a-z, etc) will be
stored in a two-dimensional constant array CharData[][8] in the program memory of
PIC18F2550.

8x8 dot matrix values for character A

Defining the column values for printable ASCII characters in 8x8 format

One question: How would you find the right index of a particular character in this array? The
answer is simple. This array is created sequentially for ASCII characters starting from Space

34

(decimal value, 32) to ~ (decimal value 126). So you need to subtract 32 from the ASCII value
of the character itself to get the corresponding row index of CharData array. For example, if
you want to display a dollar sign, $, its ASCII value is 36 (decimal). If you subtract 32, you get 4,
which in fact, is the right row index of $ in CharData array (see above).
Now lets talk about the scrolling effect. We will also define a display buffer for storing the bit
information of 168 LEDs in the matrix. It would be an integer array (16-bit) of size 8 (for 8
rows). The content of this array is what displays on the matrix. The picture below shows the bit
values of the buffer for blank display, i.e., all LEDs are turned off.

8x16 bit DisplayBuffer

Now lets consider the case of displaying a message that scrolls from right to left. For
displaying a character on the matrix, you need to switch among the rows very fast while
feeding the column lines with appropriate logic levels (character specific) for each active row.
If you want to move the character from right to left, you have to shift the column values for all
rows in to left direction with an appropriate amount (Shift Step). Once the character has been
shifted sufficiently, you can start feeding the column values of next character in the message.
In each shift, you need to update the display buffer. The formula for updating the
DisplayBuffer that would create scrolling effect from right to left is,

DisplayBuffer[i] = (DisplayBuffer[i] << ShiftAmount) BIT OR (CharacterRow[i] >> (8ShiftAmount)


e.g., Suppose, the display is initially blank and you are scrolling character A from right, one
column at a time. If you see the matrix pattern for A in one of the pictures above, you will see
that the first three columns from left are all blanks, so nothing would appear on the 168
35

matrix until the fourth column (ShiftAmount = 4) is shifted into the DisplayBuffer. The picture
below shows the stage where the initially-blank DisplayBuffer has already been shifted to left
by 7 columns (ShiftAmount = 7). DisplayBuffer for row 1 was initially all zeros, 00000000
00000000. The new DisplayBuffer for the first row after the character A is partially loaded (up
to its 7 columns) is 00000000 00000111. This can be obtained by first shifting the previous
value of DisplayBuffer to left by 7, which still gives DisplayBuffer[1] all zeros. The first row of
character A in an 88 matrix format is 00001110. If you shift this right by 8-7 = 1 step, we get
00000111. If you bit-OR this with the new DisplayBuffer[1], you will get 00000000 00000111.
The value of ShiftAmount must be increased sequentially up to 8, after which the 88
character is fully loaded in to the display buffer. Then, ShiftAmount restarts from 1 again and
starts loading the next character from the right, while the display buffer itself shifts left. This
continues until all the characters in the message are loaded.

Display Buffer after shifting 7 bits to the left

The following routine serially feeds the two shift registers with a 16-bit column value.
voidsend_data(unsignedinttemp){
unsignedintMask=0x0001,t,Flag;
for(t=0;t<16;t++){
Flag=temp&Mask;
if(Flag==0)Serial_Data=0;

36

elseSerial_Data=1;
SH_Clk=1;
SH_Clk=0;
Mask=Mask<<1;
}
//ApplyclockonST_Clk
ST_Clk=1;
ST_Clk=0;
}

And, the following code does the scrolling of message on to the matrix display. The value of

shift_step determines how many columns you want to shift at a time. I set it to 1.
for(k=0;k<StringLength;k++){
for(scroll=0;scroll<(8/shift_step);scroll++){
for(ShiftAmount=0;ShiftAmount<8;ShiftAmount++){
index=message[k];
temp=CharData[index32][ShiftAmount];
DisplayBuffer[ShiftAmount]=(DisplayBuffer[ShiftAmount]<<shift_step)|
(temp>>((8shift_step)scroll*shift_step));
}
speed=10+ADC_Read(0)/10;
for(l=0;l<speed;l++){
for(i=0;i<8;i++){
send_data(DisplayBuffer[i]);
CD4017_Clk=1;
CD4017_Clk=0;
Delay_ms(1);
}//i
CD4017_Rst=1;
CD4017_Rst=0;
}//l
}//scroll
}

You can download the complete mikroC project files for this tutorial from the link below.
Download mikroC project files

Dot matrix LED display.


In this project, I show you how to control multi dot matrix LED with only 3 pins of micro-controller using
multiplexing and cascading technique. Let's take a look at dot matrix. Dot matrix LED display is LEDs that
are arranged in a rectangular configuration. Figure below shown a 5x7 dot matrix arranged and a common
anode equivalent circuit. One 5 x 7 martix contains 35 LEDs, 12 control pins -- R1 to R7 and C1 to C5.

37

Software control multiplexing simply mean turning ON LED for a short period of time and doing this
repeatedly for each LED. In this project, first step is turn ON the first column of the matrix LEDs then turn
ON second column of the LED then do this though fifth column and repeat all step over. This require 12 pins
of micro-controller, adding one more matrix LED require 5 more pin. Imagine if you want 10 matrix LED.

74HC595
74HC595 is a 8-bit serial input, 8-bit serial or parallel output. Figure below shown pin configuration and
functional diagram of 74HC595.

38

74HC595 receives data in series and converts to parallel output. Data is shifted in to DS pin to shift register
on rising edge on SH_CP input clock. When compleat 8-bit shifted in, giving rising edge of ST_CP to make
data appear on Q0-Q7 pin (OE must be LOW). The Q7' is an output pin to make cascading by conect Q7' pin
to DS pin of another 74H595. So that makes only 3 pins of micro-controller to control many matrix LED.

2 5x7 Dot matrix LED example.


Figure shown below is a schematic that contain of a MCU, 3 74HC595s, 2 5x7 dot matrix LEDs, 5 transistors
and several current limit resistors.

39

This project can be done with a small 8 pins MCU but I have PIC18F2620 in my development board so I
decided to stick with it. 3 cascading 74HC595-- 2 to control column data, one to control row data with 5
transistor LEDs driver.
Now it's time for software control. Since data are sent in a series form, we need Shifting routine.

#define sClock
#define sData
#define sLoad

LATB.F0
LATB.F1
LATB.F2

void Shift_OutMSB (char sd) {


char i;
char mask = 128;
for (i=1; i<=8; i++){
if (!(sd&mask)) sData = 0; else sData = 1;
sClock = 1;

40

delay_us (1);
sClock = 0;
mask = mask >> 1;
}
}
The routine convert 8-bits data to serial sData pin while clock sent to sClock pin, both sData and sClock
must define, I used RB0 and RB1. 3 bytes data are transfer to matrix LED 2nd, matrix LED 1st, and column
control (C1-C5) in order. LEDs are ON for a short time. The short time is about 4ms that make compleat 5
column take 20ms which is fast enough for the eyes can caught it was blink. Timer0 is configed as 8-bits
timer with 1:16 prescaler and the system clock is 4Mhz internal oscillator. Timer0 rollover = 256*16 us, =
4096us (approximate 4 ms).

//******** clock source set up ****************


IRCF2_bit = 1;
IRCF1_bit = 1;
IRCF0_bit = 0;
//4Mhz clock
SCS1_bit = 1;
//internal osc
//***** Timer0 setup ************************
T0CS_bit = 0;
//Fosc/4 clock source.
PSA_bit = 0;
//Prescaler assigned to Timer0.
T0PS0_bit = 1;
//1:16 prescaler.
T0PS1_bit = 1;
T0PS2_bit = 0;
TMR0IP_bit = 0;
//timer0 low piority.
T08bit_bit = 1;
//timer0 is 8 bit.
TMR0IE_bit = 1;
//enable timer0 interrupt.
when Timer0 interrupt flag is set, the flag must be clear in software. Code below is interrupt routine.

void interrupt(){
char i;
//timer0 is free-running at 1:16 prescaler causes interrupt every 4ms.
if(TMR0IF_bit){
TMR0IF_bit = 0;
//clear interrupt flag.
counter++;
//increment counter.
if(counter >= 150) {
//change pattern delay 600ms (4*150).
toggle ^= 1;
counter = 0;
if(toggle){
for(i=0; i<10; i++){
display_mem[i] = pt2[i];
//load pattern2.
}
}
else{
for(i=0; i<10; i++){
display_mem[i] = pt1[i]; //load pattern1.
}
}
}
if(index > 4) index = 0;
Shift_OutMSB(display_mem[index + 5]); //send data to second matrix led
Shift_OutMSB(display_mem[index]);
//send data to first matrix led
switch (index){
//select which column to display
case 0: Shift_OutMSB(1);
break;
case 1: Shift_OutMSB(2);
break;
case 2: Shift_OutMSB(4);

41

break;
case 3: Shift_OutMSB(8);
break;
case 4: Shift_OutMSB(16);
}
//send Load clock to active 595 for diplay current column
sLoad = 1;
delay_us (1);
sLoad = 0;
index++;
//increment to next column
}
}

Side scrolling message board.


I built a small side-scrolling message board (1 x 12 characters) with 5x7 martrix LEDs using PIC16F876. The
board can store 4 messages select by 2 switches and each message can have up to 59 characters. All
message stores in EEPROM inside the PIC16F876, no extra EEPROM and bettery backup need. Each message
can be change by PC terminal via RS-232. Below shown configuration of the message board.

42

43

Software Diagram.

44

45

You might also like