# Driving the 4 digit 7 segment display

Here is the documentation of my next step: driving the LED display. The design and the C code were not really difficult; the steps to put the results here were.

As I noted before I use a PIC 16F887, but CadSoft Eagle didn’t have this component in its library, so I drew a 877 which is pin compatible. Now I wanted to get it right and so I started my own library. Copying the 877 to it and then renaming it to 887 was simple enough. The next step was to get my 4 digit 7 segment display (datasheet in PDF)  in Eagle. I did find a 3 segment display that came close and it took me a bit of time to add the fourth digit. Now my custom library contains two components!

Ok, all that was necessary so I could draw this:

I stole heavily from all around the web to put this together. Most examples I saw were using common cathode displays, whereas mine is a common anode variant.

The schematic uses the octal latch 74HCT573 from my previous step. Whilst it is really not necessary in this example, I still kept it because I think I will need it later on. Plus it was on my breadboard anyway. PIC pin RC5 drives the latch.

The output of port D (through the latch) is split in two: RD0..RD3 contain the binary coded number (BCD) for a single digit, RD4..RD7 are the pins that select the display to be displayed. We need a mechanism called multiplexing to drive the display. David Meiklejohn at Gooligum really explains it very well in a tutorial, that he made freely available on GitHub.

The 4543 (datasheet, PDF) translates the BCD number to outputs for each of the segments of a digit. The ULN2803A (datasheet, PDF) is a darlington transistor array that allows much more current to flow through the display. The discrete darlington transistors BC516 are the switches that select the digit.

I wrote a small program that just counts each 0.5 second from 0 to 9999. The interrupt service routine does two things. Every 0.5 second it increments the number to display and splits this number in its parts – thousands, hundreds, tens and ones. Every millisecond a digit displays its number. We have four digits to drive, so the refresh rate for each digit is 250Hz.

I have heavily commented the code, so I hope you understand what is going on.

```// First PIC experiments by Bart van der Velden
// Based on tutorials written by Devlin Thyne and gooligum.com.au
//
// This program increments a counter every 500 ms and puts the
// value on a four digit 7 segment display.
//
// Port D is used as output port connected to two octal latches.
// RC5 is used to select the octal latch to output to.
//
// Delay routines by Tyler Montbriand at burningsmell.org, but not
// needed in this example.
//
// The timer for the interrupt is set as explained by the tutorials
// at www.gooligum.com.au
//
// To compile:
// sdcc -mpic14 -p16f887 uartrx.c
//
// To program the chip using pk2cmd:
// pk2cmd -M -W -R -PPIC16f887 -Fuartrx.hex

#include "pic16f887.h"

#include "tsmtypes.h"

#define TRUE 1
#define FALSE 0

//Set the configuration words:
Uint16 __at _CONFIG1 configWord1 =
_DEBUG_OFF & // In circuit debugging off (I don't have an ICD 2)
_LVP_OFF &   // Low voltage programming off
_FCMEN_ON &  // Fail-safe clock monitor is enabled
_IESO_ON &   // Internal/external switchover mode is enabled
_BOR_ON &    // Brown-out reset enabled
_CPD_OFF &   // Data memory code protection is disabled
_CP_OFF &    // Program memory code protection is disabled
_MCLRE_ON &  // MCLR pin is MCLR (not a digital input)
_PWRTE_ON &  // Power-up timer is disabled
_WDT_OFF &   // Watchdog timer is disabled
_HS_OSC;     // External high speed crystal on OSC1 and OSC2

// No write protection and brown-out reset at 4.0V
Uint16 __at _CONFIG2 configWord2 = 0x3fff;

// Setup variables
volatile Uint8 sPortC = 0; // shadow variable for PORTC
volatile Uint8 sPortD = 0; // shadow variable for PORTD

Uint16 bcdCount = 0;
Uint8 bcdThousands = 0;      // The thousands part of the value to display
Uint8 bcdHundreds = 0;       // The hundreds part of the value to display
Uint8 bcdTens = 0;           // The tens part of the value to display
Uint8 bcdOnes = 0;           // The ones part of the value to display
Uint8 bcdDigitToDisplay = 0; // The digit to show

// Set the output on the second octal latch
// The 4 bits on the left control the segment to light at any point. The bit that goes low
// selects the segment.
void Set7SegmentDisplay()
{
Uint8 pattern = 0;
if (bcdDigitToDisplay == 0) // 1st digit from the right
{
pattern = bcdOnes; // Set the value for the ones
pattern |= 0xE0; // Select the 1st digit from the right
}
if (bcdDigitToDisplay == 1) // 2nd digit from the right
{
pattern = bcdTens; // Set the value for the tens
// If this is a leading zero, set a pattern that blanks the digit
if (bcdTens == 0 && bcdHundreds == 0 && bcdThousands == 0)
pattern = 0x0A;
pattern |= 0xD0; // Select the 2nd digit from the right
}
if (bcdDigitToDisplay == 2) // 3rd digit from the right
{
pattern = bcdHundreds; // Set the value for the hundred
// If this is a leading zero, set a pattern that blanks the digit
if (bcdHundreds == 0 && bcdThousands == 0)
pattern = 0x0A;
pattern |= 0xB0; // Select the 3rd digit from the right
}
if (bcdDigitToDisplay == 3) // 4th digit from the right
{
pattern = bcdThousands; // Set the value for the thousands
// If this is a leading zero, set a pattern that blanks the digit
if (bcdThousands == 0)
pattern = 0x0A;
pattern |= 0x70; // Select the 4th digit from the right
}
// Prepare the next digit to display
bcdDigitToDisplay++;
if (bcdDigitToDisplay == 4)
{
bcdDigitToDisplay = 0;
}
// Set the actual outputs
sPortC |= 0x10;    // Enable latch on RC4;
PORTC = sPortC;

PORTD = pattern;   // Set output on PORTD

sPortC &= 0xEF;    // Disable latch on RC4;
PORTC = sPortC;
}

void isr(void) __interrupt
{
// Handle Timer0 interrupt
static Uint16 tmr0Cnt = 0; // counts Timer0 overflows
static Uint16 displayCount = 0;

GIE = 0;

// Handle Timer0 overflow interrupt if it happened
if (T0IF)
{
// Now make the Timer0 interrupt fire every 250 cycles, which
// is every 50 us with my 20MHz oscillator. See details at
// http://gooligum.com.au/tutorials/midrange/PIC_Mid_C_3.pdf
// around page 8.
TMR0 += 256-250+3;
T0IF = 0;

++tmr0Cnt; // increment interrupt count (every 50 us)
if (tmr0Cnt == 500000/50) // every 0.5 sec
{
Uint16 tmp = 0;
tmr0Cnt = 0;
sPortD++;
// Count from 0 to 9999 and the start over
bcdCount++;
if (bcdCount == 9999)
{
bcdCount = 0;
}
// Split the number in its BCD components
bcdThousands = bcdCount / 1000;
tmp = bcdCount % 1000;
bcdHundreds = tmp / 100;
tmp = tmp % 100;
bcdTens = tmp / 10;
bcdOnes = tmp % 10;
}

// Update the 4 digit 7 segment display every 1 ms.
// This gives a refresh rate per digit of 250 Hz.
++displayCount;
if (displayCount == 20) // every 1 ms
{
displayCount = 0;
Set7SegmentDisplay();
}
}
GIE = 1;
}

void main(void)
{
PORTC = 0;
PORTD = 0;

// Set PORTC and PORTD to all outputs
TRISC = 0b10000000;
TRISD = 0x00;
ANSEL = 0;
TRISA = 0b11110000;

OPTION_REG = 0b11011111; // Setup Timer0
//--0-----  timer mode (TOCS = 0)
//----1---  no prescaler (assigned to WDT, PSA = 1)

// Setup Interrupt Control Register
INTCON = 0b10100000;
//1------- GIE - enable global interrupts
//--1----- TOIE - enable Timner0 interrupt

TMR0 = 0; // Set Timer0 to 0

// Loop forever, everything relevant is in the interrupt routine
while (1)
{
}
}```

I hope this is of use for someone, as it took me a bit of time to collect the information, and – more importantly – to understand how this all works.

## 4 thoughts on “Driving the 4 digit 7 segment display”

1. Miranda Iacobescu says:

Very nice project!

Thanks. You’re welcome!

2. Sabina says:

Can You send me library with KW4-561?