//============================================================================= /* (set TABS to 4) * Project name; (orig LED12x7.c) LN_stop.c v1.0 www.RomanBlack.com - open source, please mention me * Description; Interfacing to a "retro" 12 digit 7 seg led display with just a PIC, no resistors. This makes a nifty 999 hour stopwatch that displays right down to 1/100ths seconds; HHH-MM-SS-xx For more info and schematic see; www.RomanBlack.com * Version; v1.0 28/05/2009 * Operation... This uses no resistors, or driver ICs. The LED is matrixed from PIC port pins directly. To avoid over current situation we use a very short pulse (ie low duty cycle). This works very well, and final current consumption is only about 1mA per lit segment with good visibility! Results; 0.076v on 1.5ohm shunt = 51mA driving '012345678901' at 1:24 multiplex. That's 55 segments, so is <1mA per segment average!! Full instantateous source current is 51mA / 2.29 segments which is 22.3mA per PORTB (anode) PIC pin. Average PORTB source current is 51mA, max is 4x 22.3 or 89.2mA. Full instantaneous sink current is max 4 segments, 4x 22.3mA of max of 89.2mA into any PORTA or PORTC (cathode) pin. These figures are well within the PIC port/pin safe ratings, so... It works! * Setup; MCU: PIC16F876 Dev.Board: EasyPIC4 Oscillator: HS, 16 MHz resonator SW: mikroC v7.0.0.3 * PIC pins; PORTB - all 8 out, drives LED anodes (segments) B7 C (LED segments) B6 Dp B5 A B4 E B3 D B2 G B1 B B0 F PORTA 1,2,3,4,5 - out, drives 5 digit cathodes (0-4) PORTA 0, out, drives digit cathode (11) PORTC 0-5 - out, drives 6 digit cathodes (5-10) PORTC 6 - input, button, LOW = reset stopwatch count back to zero PORTC 7 - input, button or switch, LOW = pause stopwatch at current time */ //============================================================================= // global vars // vars needed for driving the LED display unsigned char duty; // used to control duty cycle unsigned char top; // top/bottom half of digit unsigned char segs[12]; // segment data for the 8 segments, for 12 digits unsigned char matrixA; // mask values to write to PORTs for LED matrixing unsigned char matrixB; // (used in the int) unsigned char matrixC; // vars used for the stopwatch clock unsigned char hour100; unsigned char hour10; unsigned char hour; unsigned char mins10; unsigned char mins; unsigned char sec10; unsigned char sec; unsigned char hund10; unsigned char hund; unsigned int timer_bres; // bresenham value, generates 1/100th second //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ void interrupt(void) { //---------------------------------------------------- // this is the TMR0 int // 16MHz TMR0 is 1:4 prescale so we get here // every 256uS (1024 instructions) // matrix speed per segment is 162 Hz // // The correct brightness with no resistors was found // at 1:24 duty cycle. To reduce strain on the PIC // we drive each of 12 digits in turn but only 4 segments, // which gives 1:24 duty but also means no more than // 4 segments can ever be on at the same time. // We drive the top 4 segments of the digit first // then the bottom 4 (includes .) // The digits are driven left to right so the first one // is digit zero. //---------------------------------------------------- // here we set the PORT pins with data from last int // to drive the segment matrixing with no latency PORTA = 0; // always make sure these are cathode pull-downs PORTC = 0; PORTB = 0; // all segments off TRISA = matrixA; // set ONE pull down for 1 cathode digit TRISC = matrixC; // using mask we made last int PORTB = matrixB; // activate the anodes! //---------------------------------------------------- // now calc all the matrixing data // which will be used next int //---------------------------------------------------- // load the segment data for this 7-seg digit matrixB = segs[duty]; if(top.F0) matrixB = (matrixB & 0b11110000); // odd=top else matrixB = (matrixB & 0b00001111); // even=bottom // now get cathode mask ready for PORTA or PORTC, // to drive 1 of the 12 digit cathodes // (make only 1 digit an output, the other 11 are high impedance) if(duty < 5 ) { // the digit driver is on PORTA matrixC = 0b11111111; // PORTC all high impedance, RC6,RC7 are ins if(duty == 0) { matrixA = 0b00111101; // digit LO driver 0 goto int_matrix_done; } if(duty == 1) { matrixA = 0b00111011; // 1 goto int_matrix_done; } if(duty == 2) { matrixA = 0b00110111; // 2 goto int_matrix_done; } if(duty == 3) { matrixA = 0b00101111; // 3 goto int_matrix_done; } if(duty == 4) { matrixA = 0b00011111; // 4 goto int_matrix_done; } } else // else if duty >= 5 { // digit driver is on PORTC! matrixA = 0b00111111; // PORTA all high impedance if(duty == 5) { matrixC = 0b11111110; // goto int_matrix_done; } if(duty == 6) { matrixC = 0b11111101; // goto int_matrix_done; } if(duty == 7) { matrixC = 0b11111011; // goto int_matrix_done; } if(duty == 8) { matrixC = 0b11110111; // goto int_matrix_done; } if(duty == 9) { matrixC = 0b11101111; // goto int_matrix_done; } if(duty == 10) { matrixC = 0b11011111; // goto int_matrix_done; } if(duty == 11) { matrixA = 0b00111110; // digit 11 is special, it uses RA0 matrixC = 0b11111111; // PORTC off } } //---------------------------------------------------- // now we have the segments loaded for the whole digit, // but we only display top or bottom half int_matrix_done: // get next digit ready for next time duty++; if(duty >= 12) { duty = 0; top++; // and toggle top } //---------------------------------------------------- // reset the TMR0 int flag and exit INTCON.T0IF = 0; } //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ //============================================================================= // LED_DIGIT //============================================================================= void led_digit(unsigned char pos,unsigned char data) { //---------------------------------------------------- // draws a numeral digit 0-9 on the LED display. // pos = 0-11, is digit POSition to display, 0=left // data = 0-9, is the numeral to display // // This just creates a mask for the 7 segments and puts // them in segs[] var. The segments are on a weird pinout // because it makes the PCB much easier to make. /* B7 C (LED segments) B6 Dp B5 A B4 E B3 D B2 G B1 B B0 F */ //---------------------------------------------------- if(data == 0) { data = 0b10111011; goto ld_done; } if(data == 1) { data = 0b10000010; goto ld_done; } if(data == 2) { data = 0b00111110; goto ld_done; } if(data == 3) { data = 0b10101110; goto ld_done; } if(data == 4) { data = 0b10000111; goto ld_done; } if(data == 5) { data = 0b10101101; goto ld_done; } if(data == 6) { data = 0b10011101; goto ld_done; } if(data == 7) { data = 0b10100010; goto ld_done; } if(data == 8) { data = 0b10111111; goto ld_done; } if(data == 9) { data = 0b10100111; goto ld_done; } // error value just light one segment to show error data = 0b00100000; //---------------------------------------------------- ld_done: if(pos > 11) pos = 11; // safe segs[pos] = data; //---------------------------------------------------- } //----------------------------------------------------------------------------- //============================================================================= // MAIN //============================================================================= void main() { //------------------------------------------------- // Setup PIC registers etc //------------------------------------------------- // rough PIC setup for now! // Setup 16F873 ADCON0 = 0; // 0= ADC is off ADCON1 = 0x07; // 0x07 = PORTA digital pins, NOT adc pins PORTA = 0; TRISA = 0b00000000; // PORTA, outs (at startup) PORTB = 0; TRISB = 0b00000000; // PORTB, all outs (at startup) PORTC = 0; TRISC = 0b11000000; // PORTC; RC7 = button, lo = pause stopwatch // RC6 = button, lo = reset stopwatch INTCON = 0; // clear INTCON //------------------------------------------------- // TMR0 setup /* ; OPTION_REG setup 16F876 movlw b'10001000' ; 7 portb pullups, 1=disabled, 0=enabled ; 6 RBO/INT pin edge select, 1= rising, 0=falling edge ; 5 TMR0 clock source, 1=ext RA4, 0=int clock ; 4 TMR0 source ext RA4 edge, 0=rising edge ; 3 PSA prescaler assign, 1=WDT, 0=TMR0 ; 2 these three are the 3 bit prescaler rate; ; 1 000-111 = 2-256 for TMR0, 1-128 for WDT ; 0 (using 001 = 1:4 prescale) */ OPTION_REG = 0b10001001; // TMR0 free-running int is used for LED multiplexing //------------------------------------------------- // TMR1 setup /* ; timer1 control movlw b'00000001' ; 7------- na ; -6------ na ; --5----- TIMER1 prescaler... two bits (5,4) ; ---4---- 00=1, 01=2, 10=4, 11=8 prescale (using 00= 1:1) ; ----3--- T1OSCEN - timer1 osc enable, 1=en ; -----2-- T1SYNC - 1=ext sync ; ------1- TMR1CS - ext/inter, 0=internal clock ; -------0 TMR1ON - 1=timer1 enabled */ T1CON = 0b00000000; // TMR1 OFF //------------------------------------------------- // TMR2 setup T2CON = 0b00000111; // TMR2 is ON, at 1:16 prescale // TMR2 is free running, it is used to generate 1/100 second // events to update the stopwatch. //------------------------------------------------- // Initialise vars //------------------------------------------------- duty = 0; // reset stopwatch to zero hour100 = 0; hour10 = 0; hour = 0; mins10 = 0; mins = 0; sec10 = 0; sec = 0; hund10 =0; hund = 0; timer_bres = 0; // show the stopwatch start values on the LED display led_digit(0,hour100); // hours led_digit(1,hour10); led_digit(2,hour); segs[3] = 0b00000100; // '-' char led_digit(4,mins10); // mins led_digit(5,mins); segs[6] = 0b00000100; // '-' char led_digit(7,sec10); // secs led_digit(8,sec); segs[9] = 0b00000100; // '-' char led_digit(10,hund10); // hundredths led_digit(11,hund); //------------------------------------------------- // MAIN RUN MODE AFTER HERE //------------------------------------------------- // enable TMR0 interrupt, starts stopwatch AND displaying on LEDs INTCON = 0b10100000; // loop always while(1) { //------------------------------------------- // reset stopwatch to zero if RC6 is LOW if(!PORTC.F6) { // reset stopwatch digits hour100 = 0; hour10 = 0; hour = 0; mins10 = 0; mins = 0; sec10 = 0; sec = 0; hund10 =0; hund = 0; // update the new (reset) values to LED display led_digit(0,hour100); led_digit(1,hour10); led_digit(2,hour); segs[3] = 0b00000100; // '-' char led_digit(4,mins10); led_digit(5,mins); segs[6] = 0b00000100; // '-' char led_digit(7,sec10); led_digit(8,sec); segs[9] = 0b00000100; // '-' char led_digit(10,hund10); led_digit(11,hund); // reset stopwatch timers timer_bres = 0; TMR2 = 0; PIR1.TMR2IF = 0; continue; } //------------------------------------------- // halt (pause) stopwatch count if PORTC.F7 is LOW if(!PORTC.F7) { continue; // don't update timer } //------------------------------------------- // this updates the stopwatch timer, it is in hundredths // of seconds. // we use 16MHz TMR2 at 1:16 prescale, so TMR2 overflow is 1024uS // we need to detect 100th second (10mS) = 10000 uS // This is a version of a bresenham routine, used to // convert 1024uS periods to the nearest 10000uS period // with zero cumulative error. if(PIR1.TMR2IF) // if TMR2 overflowed { PIR1.TMR2IF = 0; // do the 10mS bresenham routine timer_bres += 1024; // add 1024uS if(timer_bres >= 10000) // detected 10mS ! (100th sec) { timer_bres -= 10000; // subract 10mS and preserve error // gets here every 1/100th second, // so update clock values now. // counts hhh.mm.ss.xx (decimal hours up to 999) hund++; // add another 1/100th sec if(hund >= 10) { hund = 0; hund10++; if(hund10 >= 10) { hund10 = 0; sec++; if(sec >= 10) { sec = 0; sec10++; if(sec10 >= 6) { sec10 = 0; mins++; } } } } if(mins >= 10) { mins = 0; mins10++; if(mins10 >= 6) { mins10 = 0; hour++; if(hour >= 10) { hour = 0; hour10++; if(hour10 >= 10) { hour10 = 0; hour100++; if(hour100 >= 10) { hour100 = 0; } } } } } // update new values on LED display every 100th sec led_digit(0,hour100); led_digit(1,hour10); led_digit(2,hour); segs[3] = 0b00000100; // '-' char led_digit(4,mins10); led_digit(5,mins); segs[6] = 0b00000100; // '-' char led_digit(7,sec10); led_digit(8,sec); segs[9] = 0b00000100; // '-' char led_digit(10,hund10); led_digit(11,hund); } } //------------------------------------------- } } //----------------------------------------------------------------------------- //============================================================================= // BLANK //============================================================================= void blank(void) { //---------------------------------------------------- //---------------------------------------------------- } //-----------------------------------------------------------------------------