/**
 * Watchdog Timer example.
 *
 * This one is capable of discerning whether it has been
 * rebooted or woken, and how, and reports this over the UART.
 *
 * RB0 and RB3 are inputs with the following function:
 *	RB3	RB0	Function
 *	0	0	Do Nothing
 *	0	1	Constantly refresh WDT
 *	1	0	Sleep
 *	1	1	Do Nothing
 *
 * RB2 is of course the UART's output.
 *
 * When first powered on, it will detect a power-on reset happened and
 * print POR to the UART.  What happens next depends on RB0 and RB3.
 *
 * If it's doing nothing, the watchdog timer will time out and reboot it
 * about twice a second.  The PIC will detect it was kicked by the 
 * watchdog during normal operation and print WDTR.
 *
 * If it's refreshing the WDT, nothing will be printed on the screen 
 * unless you hit the reset button, in which case it will detect
 * that a reset during normal operation happened and print MCLR.
 *
 * If it's sleeping, the watchdog timer will time out and restart it.  
 * The PIC will detect it was woken up by the watchdog and print WTDW.
 * If you hit the reset button it will detect that a reset happened 
 * during sleep and print MCLW.
 *
 * It can also theoretically detect brownout but I'm not completely sure 
 * how to test that.
 */
#define __16f628a
#include "pic/pic16f628a.h"
#include "tsmtypes.h"

/* tsmdelay.h needs KHZ to be defined to something. */
#ifndef KHZ
#define KHZ 4000
#endif

/**
 * If you're linking several .c files together, INSTANTIATE_DELAY
 * should only be defined in one of them or you'll get multiple definitions
 * of loop variables and cycle_eater.
 */
#define INSTANTIATE_DELAY
#include "tsmdelay.h"
#include "tsmserial.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 = _INTOSC_OSC_CLKOUT &	// Internal oscillator
				_WDT_ON		&	// Watchdog ON!
				_LVP_OFF	&	// No Low-Volt Program.
				_DATA_CP_OFF	&	// No Code Protect
				_PWRTE_ON;

/**
 *	How to use the various power and state flags to check whether
 *	you were rebooted by button, brownout, or watchdog timer
 */
void check_power_state(void)
{
/**
 *	POR	BOR	TO	PD	Condition
 *	0	x	1	1	POR
 *	0	x	0	x	Illegal
 *	0	x	x	0	Illegal
 *	1	0	x	x	Brownout
 *	1	1	0	u	WDT Reset
 *	1	1	0	0	WDT Wakeup
 *	1	1	u	u	MCLR Reset
 *	1	1	1	0	MCLR Wake
 */
	if(!NOT_POR)
	{
		NOT_POR=1;	NOT_BOR=1;
		SEND('P');	SEND('O');	SEND('R');
		SEND(' ');
	}
	else if(!NOT_BOR)
	{
		NOT_POR=1;	NOT_BOR=1;
		SEND('B');	SEND('O');	SEND('R');
		SEND(' ');
	}
	else if(!NOT_TO)
	{
		SEND('W');	SEND('D');	SEND('T');

		if(NOT_PD)	SEND('R');
		else		SEND('W');
	}
	else
	{
		SEND('M');	SEND('C');	SEND('L');

		if(!NOT_PD)	SEND('W');
		else		SEND('R');
	}

	SEND('\r');
}

void setup_wdt(void)
{
	/**
	 * This could done in one line with OPTION_REG=0x0d, but
	 * this is an example, so we show you exactly what bits we're 
	 * setting to what why.
	 */
	
	PSA=1;	// WDT uses the prescaler, not the timer

	/**
	 *	PS2 PS1 PS0
	 *	0   0   0	=	1:1
	 *	0   0   1	=	1:2
	 *	0   1   0	=	1:4
	 *	0   1   1	=	1:8
	 *	1   0   0	=	1:16
	 *	1   0   1	=	1:32
	 *	1   1   0	=	1:64
	 *	1   1   1	=	1:128
	 */

	PS2=1;	// A 1:32 ratio at 4MHz gives us about 3 reboots a second.
	PS1=0;
	PS0=1;
}

void main(void)
{
#ifdef __16f628a
	CMCON = 0x07;	/** Disable comparators.  NEEDED FOR NORMAL PORTA
			 *  BEHAVIOR ON 16f628a!
			 */
#endif
	NOT_RBPU=0;	// Enable pullups
	TRISB=0xff;	// PORTB all inputs
	TRISA=0xaa;	// PORTA all outputs
	ASYNC_INIT();	// Initialize serial port
	DELAY_BIG_US(50000UL);	// Wait for serial LCD to init
	check_power_state();
	setup_wdt();	// Set up watchdog timer prescaler

	PORTA=0x01;	// Set 1 to B0
		DELAY_BIG_US(100000UL);	// Wait 100ms
	PORTA=0x00;	// Set 0 to B0

END:
	switch(PORTB&(0x01|0x08))
	{
	default:
	case 0x00:	// Do nothing
		break;

	case 0x01:	// Refresh watchdog timer
		__asm clrwdt __endasm;
		setup_wdt();
		break;

	case 0x08:	// Sleep
		__asm sleep __endasm;
		check_power_state();
		// Seems necessary after wakeup from WDT
		__asm clrwdt __endasm;
		setup_wdt();
		/** Need to wait for serial data to send before
		 *  looping back to sleep.
		 */
		DELAY_BIG_US(10000UL);
		break;
	}
	goto END;	// Loop forever, waiting for WDT or reset button
}  
