//=============================================================================
/* (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)
{
	//----------------------------------------------------



	//----------------------------------------------------
}
//-----------------------------------------------------------------------------


