/**
 * Watch timer.  Uses either a 4MHz internal clock or a 1MHz external 
 * one.
 *
 * It works by counting one million pulses per second on T0.  It lets 
 * the T0 register overflow, thereby dividing it by 256, and uses a
 * 1:256 prescaler, dividing it by 256*256 or 65536.
 *
 * Thereby comes a problem.  1 million is not a nice round binary 
 * number, so dividing by 2^16 makes it into some crazy fraction that
 * we can't get whole numbers out of without worrying about remainders
 * and such.
 *
 * So I use c0 to measure the exact number of clock pulses, and count 
 * seconds only when c0 exceeds one million.
 *
 * Now, c0 is only a 16-bit number, but one happy fact means we only 
 * need a 16-bit number:  Both one million and 65536 divide evenly by 
 * 64.  So we just add (65536/64) clock pulses per interrupt,
 * and count a second when it exceeds ((1000*1000)/64).
 *
 * It'd be far, far easier to just use a 1:128 prescaler and a 32.768KHz 
 * crystal.  Then you could just count one second per clock pulse 
 * without needing any fancy modulous math.  But I blew up my only 
 * 32.768KHz crystal oscillator and can't find or build a replacement!
 */
#define __16f628a
#include "pic/pic16f628a.h"
#include "tsmtypes.h"
#include "tsmserial.h"
#include "tsmdelay.h"
 
// Set the __CONFIG word:
// I usually set it to _EXTCLK_OSC&_WDT_OFF&_LVP_OFF&_DATA_CP_OFF&_PWRTE_ON
Uint16 at 0x2007  __CONFIG = CONFIG_WORD;

// Counts in binary-coded decimal, ie.
// 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
// ... 0x99, 0x00.
#define INC_BCD(X)	do	{			\
		(X)++;					\
		if( ((X)&0x0f) == 0x0a)			\
			(X)+=(~0x0a + 1 + 0x10);	\
		if( ((X)&0xf0) == 0xa0)			\
			(X) &= 0x0f;			\
	} while(0)

enum
{
	PSA_T0_2,
	PSA_T0_4,
	PSA_T0_8,
	PSA_T0_16,
	PSA_T0_32,
	PSA_T0_64,
	PSA_T0_128,
	PSA_T0_256
};

Uint16 c0;
Uint8 seconds, minutes, hours;

static void Intr(void) interrupt 0
{
	Uint8 sel=0;

	/**
	 * Using 1:256 prescaler, and counting 256 pulses after the 
         * prescaler per interrupt, that's precisely 65536 pulses per 
         * interrupt.  Add that to c0, divided by 64 since it and one 
	 * million have that as a common multiple.
	 */
	c0 += (65536L/64L);

	/**
	 * If we've exceeded one million clock pulses, then we've 
	 * exceeded one second.
	 */
	if(c0 > ((1000L*1000L)/64L))
	{

		c0 -= ((1000L*1000L)/64L);

		// If B4 is high, count minutes.
		if(PORTB&0x10)	sel=1;
		// If B5 is high, count hours.
		if(PORTB&0x20)	sel=2;

		switch(sel)
		{
		case 0:	// Normal case
			INC_BCD(seconds);
			if(seconds != 0x60)	break;
			seconds=0x00;
		case 1:	// FALL THROUGH
			INC_BCD(minutes);
			if(minutes != 0x60)	break;
			minutes=0x00;
		case 2:	// FALL THROUGH
			INC_BCD(hours);
			if(hours != 0x25)	break;
			hours=0x01;
		default:
			break;
		}

		SENDHEX(hours);
			SEND(':');
		SENDHEX(minutes);
			SEND(':');
		SENDHEX(seconds);
		SEND('\r');
		SEND('\r');
	}

	T0IF = 0;	// Clear the Timer 0 interrupt.
}

void main(void)
{
#ifdef __16f628a	// Only compile this section for PIC16f628a
	CMCON = 0x07;	/** Disable comparators.  NEEDED FOR NORMAL PORTA
			 *  BEHAVIOR ON PIC16f628a!
			 */
#endif
	c0	=0;	// Counts timer cycles/64.
	seconds	=0x00;
	hours	=0x12;
	minutes	=0x00;

	TRISA=0xff;	// PORTA all inputs
	TRISB=0xff;	// PORTB all inputs

	ASYNC_INIT();

	T0CS = 0;	// Set to 0 to count from CPU clock.
//	T0CS = 1;	// Set to 1 to count from RA4.

	PSA = 0;	// Clear to assign prescaler to Timer 0.

	// Set prescaler to 1:256
	OPTION_REG &= ~0x07;
	OPTION_REG |= PSA_T0_256;

	INTCON = 0;	// Clear interrupt flag bits.
	GIE = 1;	// Enable all interrupts.

	T0IE = 1;	// Enable Timer 0 interrupts.
	TMR0 = 0;	// Enable peripheral interrupts.

	// Loop forever.  
	while(1);
}  
