//============================================================================= /* (set TABS to 4) * Project name; LN_time_12.c v1.0 (orig; LED12x7.x) www.RomanBlack.com - open source, please mention me * Description; Using 28-pin 16F876 Is a 12-hour LED desk clock, with hundredths of seconds. There is an AM/PM indicator on LED digit0, if you don't want to display the AM/PM indicator then don't connect pin1 of the LED (this disables digit0). * Version; v1.0 initial testing, started 24/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! * Finished! * Test configuration; MCU: PIC16F876 Dev.Board: EasyPIC4 Oscillator: HS, 16 MHz 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, "set hours" button, pressed = LO PORTC 7 - input, "set minutes" button, pressed = LO */ //============================================================================= // 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; unsigned char matrixC; // vars used for the clock 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 char am; unsigned int timer_bres; //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 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 // // it just creates a mask for the 7 segments and puts // them in segs[] var. /* 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 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 16F877 ADCON0 = 0; // 0= ADC is off ADCON1 = 0x07; // 0x07 = PORTA digital pins, NOT adc pins PORTA = 0; TRISA = 0b00111111; // PORTA, all ins PORTB = 0; TRISB = 0b00000000; // PORTB, all outs PORTC = 0b00000000; TRISC = 0b11111111; // PORTC; RC7 = USART RX serial in, rest ins 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 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 = 0b00000001; // TMR1 ON, 1:1 prescale // TMR1 is used for button debouncing //------------------------------------------------- // TMR2 setup T2CON = 0b00000111; // TMR2 is ON, at 1:16 prescale // TMR2 free running, is used for 100th second detection //PIE1 = 0b00000010; // TMR2 int enable ON //------------------------------------------------- // Initialise vars //------------------------------------------------- duty = 0; hour10 = 1; hour = 2; mins10 = 0; mins = 0; sec10 = 0; sec = 0; hund10 =0; hund = 0; am = 1; // show the clock start values on the LED display if(am) segs[0] = 0b00001000; // '_' char else segs[0] = 0b00100000; // '^' char 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); //------------------------------------------------- // MAIN RUN MODE AFTER HERE //------------------------------------------------- // enable TMR0 interrupt INTCON = 0b10100000; // loop always while(1) { //------------------------------------------- // check if the "set hour" button is pressed, // and update the hour (to next hour) if(!PORTC.F6) // lo = pressed { // increase to next hour hour++; if(hour >= 10) { hour = 0; hour10++; } if(hour10 == 1 && hour == 2) // == 12:00 o'clock { if(!am) am = 1; // toggle am/pm else am = 0; } if(hour10 == 1 && hour == 3) // >= 13:00 o'clock { hour10 = 0; // reset to 1:00 hour = 1; } // update hours on LED display if(am) segs[0] = 0b00001000; // '_' char else segs[0] = 0b00100000; // '^' char led_digit(1,hour10); led_digit(2,hour); // debounce button, by waiting here until button released debounce_hours: if(!PORTC.F6) goto debounce_hours; // if pressed // then make sure button is released for at least 12mS // TMR1 16Mhz at 1:1 prescale; 200 = 12mS TMR1H = 0; while(TMR1H < 200) { if(!PORTC.F6) goto debounce_hours; } } //------------------------------------------- // check if the "set minute" button is pressed, // and update the minute (to next minute) AND // also reset seconds and hundredths at the same time // to "synch" the minute to the start of a minute. if(!PORTC.F7) // lo = pressed { // increase to next minute mins++; if(mins >= 10) { mins = 0; mins10++; if(mins10 >= 6) mins10 = 0; } // also reset seconds and hundredths sec10 = 0; sec = 0; hund10 =0; hund = 0; // update min-sec-hund on LED display led_digit(4,mins10); led_digit(5,mins); led_digit(7,sec10); led_digit(8,sec); led_digit(10,hund10); led_digit(11,hund); // debounce button, by waiting here until button released debounce_minutes: if(!PORTC.F7) goto debounce_minutes; // if pressed // then make sure button is released for at least 12mS // TMR1 16Mhz at 1:1 prescale; 200 = 12mS TMR1H = 0; while(TMR1H < 200) { if(!PORTC.F7) goto debounce_minutes; } } //------------------------------------------- // this updates the clock display, 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 overflowed { PIR1.TMR2IF = 0; // do the 10mS bres routine timer_bres += 1024; if(timer_bres >= 10000) // detected another 100th sec { timer_bres -= 10000; // subtract 10mS from total // update clock values now // counts hh.mm.ss.xx hund++; 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 == 1 && hour == 2) // == 12:00 o'clock { if(!am) am = 1; // toggle am/pm else am = 0; } if(hour10 == 1 && hour == 3) // wrap at 12:59 { hour10 = 0; hour = 1; } } } // update values on LED display every 100th sec if(am) segs[0] = 0b00001000; // '_' char else segs[0] = 0b00100000; // '^' char 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) { //---------------------------------------------------- //---------------------------------------------------- } //-----------------------------------------------------------------------------