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.
Very nice project!
Thanks. You’re welcome!
Can You send me library with KW4-561?
Please!
Sabina
I don’t have a libray for the display. All the code to drive the led segments is in the code of the article. Of course you could extract that and make a library out of it, but I never did.