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