PIC LED Controller – 7-Segment driver

While working on the LED controller I decided that it would be nice if the blinking interval (the length of time a LED is on or off) will be controlled by a potentiometer. Then I remembered that I bought a few 3-digit 7-segment displays (see footnote) and it would be even nicer if I’ll show the pot setting on the display. It will add some color to the LED controller. I also bought some new PIC16F1825 controllers with 14 pins which are perfectly suited for the job. So I had everything ready and to my surprise it took me much longer to get it working than I anticipated.

I had to learn a few new things to make this work:

  • Using the ADC (Analog to digital feature of the PIC16F1826)
  • Connecting the display to power and to the controller
  • Driving the 7-segment display.

First of all, here is the final system. Notice how the numbers on the display change as I turn to pot with the screwdriver.

I’ll start by describing the wiring of the circuit.

Wiring

See Important update to the wiring scheme below

The image below shows the wiring of the potentiometer (marked), the PIC16F1825 controller and the display.

Wiring

Wiring

The VDD and VSS pins are connected to the power via the two grey wires at the right side.

The two wires marked “control” – a purple one connected to RA0 and a brown one connected to RA1 – control which digit is “displayed”, or more correctly – powered up. This display is a common anode display. This means that all the segments of a digit are connected to a single positive pin. There are three such pins, one for each digit. In order to light up a segment, the digit’s control pin must be positive and the segment’s pin must be negative. In order to display two or three digits at the same time, the program must switch the control pins on and off rapidly. In this project I’m using only two digits – to display numbers in the range 0 – 99.

Pin RA2 is connected to the potentiometer.

The yellow and orange wires connect each of the 7 segments to a pin on the controller according to the table below. The table shows the assignment of the controller’s pins to segments (segments are marked by the letters A – G) and the segments that must be lit to display each digit. For example, to display the number one, segments B and C must be on. This table is actually good for a common cathode display – I made it before I realized that I have a common anode display. So you’ll see that in the code the bits are negated.

7-segment-table

 

Pin RA4 is connected to segment G.

The problems

I had to take care of several issues in the code and I’ll explain how each one is handled. The main problems were:

  • The display is of the common anode type, meaning that to light a segment, its pin must be low (0)
  • The display has only 11 pins to control 3 digits, each with 8 segments (the 8th is the decimal dot). This means that the controller needs to turn them on and off in a round-robin way.
  • The pot is completely not stable – it sends different readings continuously. I had to “smooth” out the constantly changing readings

The initial, naive implementation was really dumb and had all the mistakes possible in such small program. When I ran it all the segments on both digits were flickering wildly and one of the digits was brighter than the other. The problems were:

  • I assumed that a positive voltage on a pin will light a segment but my display was of the common anode type, so a pin must actually be low to light a segment. The lighted segments didn’t resemble any number of course.
  • I understood that I have to switch each digit on and off, but the code first switched on digit 1, then switched it off and right after that, in the same function, switched digit 2 on and only after that changed the lit segments to show digit 2. This implementation really had all the possible errors and it caused one digit to be bright and the second to be dim.
  • I didn’t remember that the pot keeps sending different readings all the time, so the digits never stabilised (and of course, I couldn’t make out any digit because of the confusion with common anode vs. common cathode).

Solutions that didn’t work

I first thought that the controller can’t source enough current to light up the digits, so I connected the control pins to the 5V input via a transistor. This of course made no difference.

I realised that the pot is fluctuating wildly so I installed a low-pass filter – a resistor and capacitor in series. This was an interesting exercise and I played with various combinations of resistors and capacitors and watched the effects. This help a little but not much. I understood finally that a low pass filter is not a solution to the problem because even if it smooths out the input a little bit, some readings will pass through and make the display flicker.

So I started simplifying the circuit and the code and trying to isolate problems – first light up a single hard-coded digit, then two digits and then connect it to the pot.

Solutions that did help

I read the display’s data sheet to the end and found that the duty cycle of each digit is 1/10 and the actual time that it must be lit was 0.1 millisecond. So I understood that the correct way to drive the display was the following:

  1. Turn digit 1 on
  2. Wait 0.1 ms
  3. Turn digit 1 off
  4. Wait 0.1 ms
  5. Turn digit 2 on
  6. Wait 0.1 ms
  7. Turn digit 2 off
  8. Wait 0.9 ms

I did a few more changes:

  1. I fixed the segment table to match a common anode display
  2. I added code that discarded pot readings if they were within 10 points from the previous reading.
  3. The main change – driving the segments correctly.

I will highlight these areas in the code.

The code

We will describe the code in section. I skipped the configuration byte definition in order to save space.

#define INTERVAL1 4
#define INTERVAL2 40

Interval 1 is more or less 0.1 millisecond – it determines the time that  a digit is lit.
Interval 2 is the interval when both digits are off. Since the duty cycle is 1/10 interval 2 is ten times interval 1.

unsigned int adcvalue; // The input value from the pot
int timer0count;       // counts timer 0 interrupts
int state;             // state – moves the system between states.

The states are:

  •  0 – turn digit 1 on
  •  1 – turn digit 1 off
  •  2 – turn digit 2 on
  •  3 – turn digit 2 off
  •  4 – read the value on ADC

each state lasts interval 1 ticks

  • 5 – wait interval 2

// pin to segment assignment is:
// C0-C1-C2-C3-C4-C5-A4
// A -B -C -D -E -F -G
// Pin A2 used for ADC
// Pin A0 used for display pin 8 – digit 1 – ones
// Pin A1 used for display pin 9 – digit 2 – tens

unsigned char digits[] = {
0b01000000, // 0 

  0b01111001, // 1
  0b00100100, // 2
  0b00110000, // 3
  0b00011001, // 4
  0b00010010, // 5
  0b00000010, // 6
  0b01111000, // 7 
  0b00000000, // 8
  0b00011000  // 9
};

Note that in the digits array above a segment is lit if its value is 0. Hence the value for the number 8 is all zeros.

Next is the interrupt service routine – it increments the time tick counter and reads the pot value from the A2 pin.

void interrupt ISR()
{
  if (INTCONbits.TMR0IF) {
    timer0count++;
    INTCONbits.TMR0IF = 0;
    TMR0 = 0;
  }

  if (PIR1bits.ADIF) {
    adcvalue = (short) ADRES;
    PIR1bits.ADIF = 0;
    ADCON0bits.GO = 1;
  }
}

The show_number function sets the individual segments on or off according to their setting in the digits array above.

void show_number(unsigned char number)
{
  LATC0 = ((number & 0x1) > 0) ? 1 : 0;
  LATC1 = ((number & 0x2) > 0) ? 1 : 0;
  LATC2 = ((number & 0x4) > 0) ? 1 : 0;
  LATC3 = ((number & 0x8) > 0) ? 1 : 0;
  LATC4 = ((number & 0x10) > 0) ? 1 : 0;
  LATC5 = ((number & 0x20) > 0) ? 1 : 0;
  LATA4 = ((number & 0x40) > 0) ? 1 : 0;
}

And now to the main function. I will skip the set up code of the input/output directions of pins and the register settings for Analog-to-digital converter (ADC) and the timer 0 interrupt counter.

state = 0;
timer0count = 0;

adcvalue = 0;
previous_value = 0;
while(1)  {
  if (state == 5) {
    if (timer0count >= INTERVAL2) {
    timer0count = 0;
    state = 0;
  }
  else 
    continue;
  }
  if (timer0count < INTERVAL1) {
    continue;
  }

  switch (state) {
    case 0:
      LATA0 = 0;
      LATA1 = 1;
      show_number(digits[digit1index]);
      break;
    case 1:
LATA1 = 0;
       break;
    case 2:
      LATA0 = 1;
      LATA1 = 0;
      show_number(digits[digit2index]);
      break;
    case 3:
      LATA0 = 0;
      break;
    case 4:
      if (adcvalue > 990)
        adcvalue = 990;
      if (adcvalue < 10)
        adcvalue = 10;
      if (adcvalue >= previous_value)
        delta = adcvalue – previous_value;
      else
delta = previous_value – adcvalue;
if (delta > 10) {

        value = adcvalue / 10;
        previous_value = adcvalue;
      }
      digit1index = value / 10;
      digit2index = value % 10;
      ADCON0bits.GO = 1;
      break;
    }
    state++;
    timer0count = 0;
};

Updated wiring scheme

My good friend Tomer pointed out to me that I must add resistors to protect the chip from running at maximum current output from the chip. The wiring above is identical to short-circuit between the output chips and it will lead to damaging the chip.

So I added 220 Ohm resistors on all the segment pins to limit the current to around 22mA (5 / 220 according to Ohm’s law. Here is the updated wiring photo.

7-segment-updated

 

========

Data sheet for the 7-segment display can be found here. They are manufactured by Wayjun and cost me $2.55 for 10 units on Ali Express.

Leave a Reply

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s