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