7-segment LED Display

8 minute read

In this post we’re going to connect a 7-segment LED display to a PIC18F452 and set it up so that it will show numbers from 0 to 9 in regular intervals, like a stopwatch. This will be useful for calibration of an IR sensor and generally for displaying any number output when debugging.

The hardware

What you’ll need

  • 7-segment LED display
  • 8x 220Ω resistors (the 8th resistor is for the decimal point)
  • previous setup where we programmed a PIC to blink an LED

Wire up the 7-segment LED display

Before we start, disconnect the blinking LED and its resistor from pin RD0 which we connected there in the last part since we’re going to use that pin for the LED display.

First, we need to determine how to wire up the LED display — to do so, find the datasheet of your display and look for its schematic and pinout. For my display, it’s shown in its datasheet (section “Internal Circuit Diagram”) and here’s how it looks like:

7-segment-LED-display-pinout

This 7-segment display has 10 pins total with 5 and 5 pins on either side. Pin 1 is marked on the diagram and it’s the top-left pin. Other pins are marked in a counter-clockwise manner — pins 1-5 are on the left side from top to bottom and pins 6-10 are on the right side from bottom to top, with the 10th pin in the top-right corner. From the direction of the LEDs on the right schematic you can see that pins 3 and 8 are the common anode for the LEDs and the other pins are their cathode1. To light them up, pins 3 and 8 are connected to Vdd (+5V) and the other 8 pins are connected through 220Ω resistors to ground. However, if we would do that, the number 8 with a dot will be shown which is not what we want, so we’ll have to control somehow which of the segments would be lit so that a certain number would be displayed — and this can be done using the PIC microcontroller. So, instead of connecting those 8 pins to GND, we’ll connect them to the PIC’s D register and by controlling which of the pins will be set to GND and which to Vcc we’ll be able to control which segments will light up and which not. To make the programming easier, I’ll connect the pins representing segments A, B, C, D, E, F, G, DP to pins RD0 to RD7 respectively in that order, since then I’ll have RD0 pin controlling the segment A, RD1 pin controlling the segment B and so on. So, here’s the schematic:

7-segment-LED-display-schematic

Now, notice that to light up an LED, just the difference of potentials it’s connected to is important — so if both its leads are connected to GND, or if both its leads are connected to Vcc, the LED will not light up; to light it up, we need the anode to be connected to Vcc and the cathode to GND and this is the only case in which it will light up. We’ll use this when programming the PIC to set values of the RD0 to RD7 pins — if the value would be set to Vcc (or 1), the LED would not light up since both of its leads would be at Vcc; in the opposite case when the value would be set to GND, the segment would light up since its anode would be at Vcc and its cathode, connected to the PIC, would be at GND. With this in mind, let’s program the microcontroller.

The software

Before we begin programming, open the program for blinking the LED and delete the lines with pin RD0 which were used to blink the LED since now we’re going to use that pin for something else.

Define the numbers using segments

First, we need to set all the pins of the D register to output. We could do this one by one pin, but it’s easier to do this for the whole register at once. This can be done by setting the TRISD register to a binary value with 8 bits, where each bit represents if a pin will be output (0) or input (1) pin and from right to left, the bits correspond to pins RD0 to RD7. Since we want all the 8 bits from RD0 to RD7 to be output pins, we set them all to 0:

1
TRISD = 0b00000000;

Now, let’s display, say, a number 2. When we look at the diagram from the datasheet at the top of this page, we can see that for displaying the number 2, we need to light up the segments A, B, G, E and D. Again, I’ll write into the whole register instead of its individual pins since it’s faster. Since we know that segment A is controlled by the pin RD0, segment B is controlled by RD1, and so on, we can achieve this combination by writing into the register values 0 where we want the pin to be at GND and the segment to light up and values 1 where we want the pin to be at Vcc and the segment not to light up:

1
LATD = 0b10100100;

Notice that the 8th pin (8th digit from right) is the RD7 pin controlling the DP segment. Since I don’t want the decimal point to be lit up by default, I set the pin to 1 to turn it off. Now, after writing more of such lines it may become quite confusing to see which segments are on and which are off since we’re usually used to 1 representing ON and 0 representing OFF. To achieve this, we may change all 0’s to 1’s and all 1’s to 0’s and then negate the binary value which will change the 0’s back to 1’s and 1’s back to 0’s as before. Although it may seem like an unnecessary complication, the code will become much more readable. To negate a binary number in C, the tilde symbol (~) can be prepended before the number. The line then becomes:

1
LATD = ~0b01011011;

Now you can compile the program and if all went well program the PIC. and you should see the display showing number 2.

Now it’s time to add other numbers from 0 to 9 in a similar way — for every number look at the diagram which segments are used for displaying that number and then write into the LATD register what segments you’d like to light up. It’s not necessary, but I’ve made a function which gets as the argument what number to show and, according to the number, sets the LATD register to light up the right segments of the LED display. If I want just to display the decimal point, I can pass the number -1 (or any number except 0, 1, …, 9) to the function which will light up the DP segment — this can be useful later for debugging.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void displayNumber(int number) {
  switch(number) {
    case  0: LATD = ~0b00111111; break;
    case  1: LATD = ~0b00000110; break;
    case  2: LATD = ~0b01011011; break;
    case  3: LATD = ~0b01001111; break;
    case  4: LATD = ~0b01100110; break;
    case  5: LATD = ~0b01101101; break;
    case  6: LATD = ~0b01111101; break;
    case  7: LATD = ~0b00000111; break;
    case  8: LATD = ~0b01111111; break;
    case  9: LATD = ~0b01101111; break;
    default: LATD = ~0b10000000; break; // display decimal point
 }
}

Now if you want to display, say, a number 5, you just call the function displayNumber with the argument 5 and the number is displayed:

1
displayNumber(5);

Make a stopwatch program

We can now combine the delay function from before with this number displaying and make a simple stopwatch which will count from 0 to 9 and then start from 0 again. To do so, we’ll have an infinite loop with a variable t which will hold the actual time and which will be increased every second. After each second, its value will be passed to the displayNumber function which will display the new time on the LED display. If the time will be increased from 9 to 10, we’ll set it back to 0 since we can only display one-digit numbers. It will look like this:

1
2
3
4
5
6
7
int t = 0;
while(1){
  displayNumber(t);
  delay_ms(1000);
  t++;
  if(t == 10) t = 0;
}

And for completeness, here’s the whole program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <xc.h>

#pragma config OSC = HS // High-Speed oscillator
#pragma config WDT = OFF // Watchdog timer OFF
#pragma config LVP = OFF // Low-voltage programming OFF

#define _XTAL_FREQ 20000000 // 20MHz crystal

void delay_ms(int ms){ // waits given number of milliseconds
 for(int i = 0; i < ms; i++) __delay_ms(1);
}

void displayNumber(int number) { // displays given number
  switch(number) {
    case  0: LATD = ~0b00111111; break;
    case  1: LATD = ~0b00000110; break;
    case  2: LATD = ~0b01011011; break;
    case  3: LATD = ~0b01001111; break;
    case  4: LATD = ~0b01100110; break;
    case  5: LATD = ~0b01101101; break;
    case  6: LATD = ~0b01111101; break;
    case  7: LATD = ~0b00000111; break;
    case  8: LATD = ~0b01111111; break;
    case  9: LATD = ~0b01101111; break;
    default: LATD = ~0b10000000; break; // display decimal point
  }
}

int main() {
  TRISD = 0b00000000; //set every register D pin to output
  int t = 0;
  while(1){
    displayNumber(t);
    delay_ms(1000);
    t++;
    if(t == 10) t = 0; // we can't display 2-digit numbers
  }
  return 0;
}

Now you can compile the code and program the PIC and if all went well you should see something like a stopwatch. If you want to manually start counting from 0 again, you can just press the reset button we added there in the last part and the whole program starts from the beginning, setting the time to 0 again.

So, that was it and in the next part I’ll show you how to set up analog to digital conversion (ADC) which is necessary for all kinds of sensors, including the IR sensor we’re going to use.

Documentation

  1. I wondered why they’ve chosen pins 3,8 to be the common anode and the rest to be cathodes and not the other way around but I’ve heard that this way it’s more efficient for some reason. []

Leave a comment