//============================================================================= /* (set TABS to 4) * Project name; LED12x7.c www.RomanBlack.com - open source, please mention me (LN_serial.C) * Description; Using 28-pin 16F876 * This is the serial input display version... Serial input commands (all are 1 byte); 1000 pppp = move to pos pppp, ready to write segment data 0sss ssss = write segment data, 1=lit, GFEDCBA, then move to next pos 1100 pppp = write a dec point at pos, then move to next pos (pos is auto incremented, and wraps to back to zero (left)) * Version; v1.0 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 or 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 PORTC 0-5 - out, drives 6 digit cathodes (1-8) PORTC 7 - input, is USART RX serial in PORTC 6 - out, not used PORTA 1,2,3,4,5 - out, drives 5 digit cathodes (9-12) PORTA 0, out, drives digit cathode 12 */ //============================================================================= // 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 needed for serial and control unsigned char pos; // active digit position (digit) 0-11 unsigned char RXdata; // holds incoming serial byte unsigned char temp_data; unsigned char bootup; // used for safe bootup delay //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 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 = 0b10111111; // PORTC all high impedance, RC6 not used 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 = 0b10111110; // goto int_matrix_done; } if(duty == 6) { matrixC = 0b10111101; // goto int_matrix_done; } if(duty == 7) { matrixC = 0b10111011; // goto int_matrix_done; } if(duty == 8) { matrixC = 0b10110111; // goto int_matrix_done; } if(duty == 9) { matrixC = 0b10101111; // goto int_matrix_done; } if(duty == 10) { matrixC = 0b10011111; // goto int_matrix_done; } if(duty == 11) { matrixA = 0b00111110; // digit 11 is special, it uses RA0 matrixC = 0b10111111; // 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_SEGS //============================================================================= void led_segs(unsigned char data) { //---------------------------------------------------- // lights the segments on the LED display with data. // // it just creates a mask for the 7 segments and puts // them in segs[] var, however it needs to transpose segments // as we use funky pinout to make the PCB routing easier. // data = DpGFEDCBA /* PCB LED segments PORTB = B7 C B6 Dp B5 A B4 E B3 D B2 G B1 B B0 F */ //---------------------------------------------------- // first is a special case, if this is JUST to activate the // decimal point, we leave the other segments lit. This // allows adding a decimal point later to an existing digit. if(data == 0b10000000) // if just a dec point { segs[pos].F6 = 1; // set Dp segment, don't change others pos++; // auto move to next char if(pos > 11) pos = 0; // safe limit } //---------------------------------------------------- else // else clear dec point and write the 7 segs { temp_data = 0; // clear all segs if(data.F0) temp_data.F5 = 1; // A (light each segment) if(data.F1) temp_data.F1 = 1; // B if(data.F2) temp_data.F7 = 1; // C if(data.F3) temp_data.F3 = 1; // D if(data.F4) temp_data.F4 = 1; // E if(data.F5) temp_data.F0 = 1; // F if(data.F6) temp_data.F2 = 1; // G segs[pos] = temp_data; // now write it to segs } //---------------------------------------------------- } //----------------------------------------------------------------------------- //============================================================================= // 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 = 0b00000000; // PORTA, all ins, PORTB = 0; TRISB = 0b00000000; // PORTB, all outs PORTC = 0b00000000; TRISC = 0b10000000; // PORTC; RC7 = USART RX input // RC6, unused, set to out 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 encoder checking //------------------------------------------------- // 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 bootup delay and serial timeout testing //------------------------------------------------- // TMR2 setup T2CON = 0b00000000; // TMR2 is OFF //------------------------------------------------- // Initialise vars //------------------------------------------------- duty = 0; // write the welcome screen to LED display, // this stays until user sends serial data // "-Serial v1.0-" pos = 0; led_segs(0b01000000); // - pos = 1; led_segs(0b01101101); // S pos = 2; led_segs(0b01111001); // E pos = 3; led_segs(0b01010000); // r pos = 4; led_segs(0b00000100); // i pos = 5; led_segs(0b01110111); // A pos = 6; led_segs(0b00111000); // L pos = 7; led_segs(0b00000000); // pos = 8; led_segs(0b00011100); // v pos = 9; led_segs(0b00000110); // 1 pos = 9; led_segs(0b10000000); // . pos = 10; led_segs(0b00111111); // 0 pos = 11; led_segs(0b01000000); // - // enable TMR0 interrupt // this displays the LED data while we bootup INTCON = 0b10100000; //------------------------------------------------- // do a safe bootup delay here before we enable serial // input to avoid getting trashed serial input. // 16MHz, TMR1 at 1:1, it overflows at 61Hz so a // delay of 30 is about 500mS bootup delay // delay about 500mS bootup = 30; TMR1H = 0; while(bootup) { if(PIR1.TMR1IF) { PIR1.TMR1IF = 0; bootup--; } } //------------------------------------------------- // setup USART for serial receive! //------------------------------------------------- PIE1 = 0; // USART int is not used // set the USART up and select baudrate... // for incoming serial data at 19200 baud standard 8N1 // using 16MHz xtal // BRGH formula; // for BRGH==HI; xtal / 16 / (SPBRG +1) // for BRGH==LO; xtal / 64 / (SPBRG +1) // for 16MHz xtal; TXSTA = 0b00000100; // TXEN bit5 =0 (disable transmit) // BRGH bit2 =1 (high baudrate) SPBRG = 51; // at 16 Mhz, 51 gives 19231 baud // finally enable the USART RCSTA = 0b10010000; // SPEN bit7 =1 enables serial port // CREN bit4 =1 allows continuous serial receive // now clean serial register in case it had crap in RXdata = RCREG; // clear the USART fifo buffers RXdata = RCREG; //------------------------------------------------- // MAIN RUN MODE AFTER HERE //------------------------------------------------- pos=0; // ready to write to digit0 first // loop always while(1) { //------------------------------------------- // since LED multiplexing is done in the TMR0 int, // all we do here is check for incoming serial bytes // and put the segment data in the segs[] array. //------------------------------------------- // see if we received a serial byte if(PIR1.RCIF == 1) // if we got a byte! { TMR1H = 0; // reset TMR1 to check for timeout RXdata = RCREG; // store the 1byte command // check if it is a move command if(RXdata.F7) // if a move { pos = (RXdata & 0b00001111); // set digit position if(pos > 11) pos = 0; // safe limit if(RXdata.F6) led_segs(0b10000000); // light dec point if needed } else // else is segment data { led_segs(RXdata); // light the segments! pos++; // auto inc to next digit if(pos > 11) pos = 0; // wrap back to digit0 if needed } } //--------------------------------------------------- // also check for serial errors, this clears // the serial if it has been more than 10mS since // last serial byte was received. // 16Mhz, TMR1 1:1 each TMR1H tick is 64uS // so 10mS = TMR1H >= 156 if(TMR1H >= 156) // if >10mS since last RX byte received { TMR1H = 0; // check if we have a serial overrun error, if so clear it // also if we had a serial framing error, if so clear it if(RCSTA.OERR || RCSTA.FERR) { RXdata = RCREG; // clear the USART fifo buffers RXdata = RCREG; RCSTA.CREN = 0; // clear error bits by resetting USART RCSTA.CREN = 1; } } //--------------------------------------------------- } } //----------------------------------------------------------------------------- //============================================================================= // BLANK //============================================================================= void blank(void) { //---------------------------------------------------- //---------------------------------------------------- } //-----------------------------------------------------------------------------