/*
**************************************************************************************************
* This is a project for debug and test out basic concept of software power On/Off
* which we use it everyday from our mobile phones.
* 
* A long press and hold on a certain key will bring the device to switch it on or off.
* Sleep mode is used to save power during power off. A key is debounced by a state machine 
* in the uiTask(). No complicated OLED display applied yet. A blinking LED is used as an indicator 
* for a system running.
* 
* How to use this program: Instead of using INT pin, one of the RB pins (RB4) has been
* chosen for interrupt on change wakeup. Because the keypad with joystick onboard has 
* been wired as a matrix, we need to take RE6 pin low with RB4 as KEY7 for a single key action.
* Similar, tri-color LED's green component has been chosen as an indicator with RA5 as control gate open/close.
* 
* Instead of using software delay by common DelayMs(), a tick timer approach adopted in this example.
* This ensures a responsive system. Most often by using DelayMs(), the MCU will be held up
* in an infinite loop. With Tick timer approach, it is possible to assign two global timer t1 and t2
* for key scan-&-debounce as well as LED blinking.

* Once the board programmed with bootloader, reset it either from HID bootloader application
* or reset key onboard. Try press and hold KEY7 for 2 sec or more for testing.
* One of the difficult parts is to keep this program robust. It is just to find out, please mess it around 
* with erratic key presses and let's see if the device will hang up.
* An extremely fast keypress without hanging up the device will prove the robustness!
*
* Once we are happy with a blinking LED, similar program with the OLED could be created then.
* It would be easy to modify this for PWR|PLAY key on/off, too.
*  
* Result of measurement:
* At sleep mode, current drawn ~ 20uA at battery = 4V
* At running mode, current drawn = 4.5mA to 7.9mA with LED blinking
*
* Project : 	TestOut Software LEDBlink.mcp
* File:     	MainDemo.c
* Hardware: 	PIC18 OLED EVK Rev1E
* Compiler: 	C18 v3.35 under MPLAB v8.30
* Date: 		22nd Oct 2010
* Programmer: 	John Leung @ TechToys Co.
**************************************************************************************************
*/



//On PIC18 devices, addresses 0x00, 0x08, and 0x18 are used for
//the reset, high priority interrupt, and low priority interrupt
//vectors.  However, the current Microchip USB bootloader 
//examples are intended to occupy addresses 0x00-0x7FF or
//0x00-0xFFF depending on which bootloader is used.  Therefore,
//the bootloader code remaps these vectors to new locations
//as indicated below.  This remapping is only necessary if you
//wish to program the hex file generated from this project with
//the USB bootloader.  If no bootloader is used, comment out the following define:

#include "MainDemo.h"

        #pragma config XINST    = OFF   	// Extended instruction set
        #pragma config STVREN   = ON      	// Stack overflow reset
        #pragma config PLLDIV   = 3         // (12 MHz crystal used on this board)
        #pragma config WDTEN    = OFF      	// Watch Dog Timer (WDT)
        #pragma config CP0      = OFF      	// Code protect
        #pragma config CPUDIV   = OSC1      // OSC1 = divide by 1 mode
        #pragma config IESO     = OFF      	// Internal External (clock) Switchover
        #pragma config FCMEN    = OFF      	// Fail Safe Clock Monitor
        #pragma config FOSC     = HSPLL		// Firmware must also set OSCTUNE<PLLEN> to start PLL!
        #pragma config WDTPS    = 32768
        #pragma config MSSPMSK  = MSK5
        #pragma config CCP2MX   = DEFAULT   

/*********************** PRIVATE PROTOTYPES **************************/

void YourHighPriorityISRCode(void);
void YourLowPriorityISRCode(void);


/*************** 				Defintions				*****************/
enum LED_STATE		{OFF=0, ON};
enum MCU_STATE		{SLEEP=0, TRANSITION, RUN};
enum KEY_STATE		{KEY_UP, KEY_DEBOUNCE, KEY_DOWN};

/*************** 			Global variables			*****************/
BYTE MCUState = SLEEP;
BYTE MCUStateSave = SLEEP;
BYTE LEDState = OFF;
BYTE KeyScanState = KEY_UP;
BYTE KeyDownTmr = 0;
BOOL KeyHitKey = FALSE;

TICK	t1;
TICK	t2;

/** VECTOR REMAPPING ***********************************************/
#if defined(__18CXX)
	#if defined(PROGRAMMABLE_WITH_USB_HID_BOOTLOADER)
		#define REMAPPED_RESET_VECTOR_ADDRESS			0x1000
		#define REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS	0x1008
		#define REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS	0x1018
	#elif defined(PROGRAMMABLE_WITH_USB_MCHPUSB_BOOTLOADER)	
		#define REMAPPED_RESET_VECTOR_ADDRESS			0x800
		#define REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS	0x808
		#define REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS	0x818
	#else	
		#define REMAPPED_RESET_VECTOR_ADDRESS			0x00
		#define REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS	0x08
		#define REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS	0x18
	#endif
	
	#if defined(PROGRAMMABLE_WITH_USB_HID_BOOTLOADER)||defined(PROGRAMMABLE_WITH_USB_MCHPUSB_BOOTLOADER)
	extern void _startup (void);        // See c018i.c in your C18 compiler dir
	#pragma code REMAPPED_RESET_VECTOR = REMAPPED_RESET_VECTOR_ADDRESS
	void _reset (void)
	{
	    _asm goto _startup _endasm
	}
	#endif
	
	#pragma code REMAPPED_HIGH_INTERRUPT_VECTOR = REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS
	void Remapped_High_ISR (void)
	{
	     _asm goto YourHighPriorityISRCode _endasm
	}
	#pragma code REMAPPED_LOW_INTERRUPT_VECTOR = REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS
	void Remapped_Low_ISR (void)
	{
	     _asm goto YourLowPriorityISRCode _endasm
	}
	
	
	#if defined(PROGRAMMABLE_WITH_USB_HID_BOOTLOADER)||defined(PROGRAMMABLE_WITH_USB_MCHPUSB_BOOTLOADER)
	//Note: If this project is built while one of the bootloaders has
	//been defined, but then the output hex file is not programmed with
	//the bootloader, addresses 0x08 and 0x18 would end up programmed with 0xFFFF.
	//As a result, if an actual interrupt was enabled and occured, the PC would jump
	//to 0x08 (or 0x18) and would begin executing "0xFFFF" (unprogrammed space).  This
	//executes as nop instructions, but the PC would eventually reach the REMAPPED_RESET_VECTOR_ADDRESS
	//(0x1000 or 0x800, depending upon bootloader), and would execute the "goto _startup".  This
	//would effective reset the application.
	
	//To fix this situation, we should always deliberately place a 
	//"goto REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS" at address 0x08, and a
	//"goto REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS" at address 0x18.  When the output
	//hex file of this project is programmed with the bootloader, these sections do not
	//get bootloaded (as they overlap the bootloader space).  If the output hex file is not
	//programmed using the bootloader, then the below goto instructions do get programmed,
	//and the hex file still works like normal.  The below section is only required to fix this
	//scenario.
	#pragma code HIGH_INTERRUPT_VECTOR = 0x08
	void High_ISR (void)
	{
	     _asm goto REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS _endasm
	}
	#pragma code LOW_INTERRUPT_VECTOR = 0x18
	void Low_ISR (void)
	{
	     _asm goto REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS _endasm
	}
	#endif	//end of "#if defined(PROGRAMMABLE_WITH_USB_HID_BOOTLOADER)||defined(PROGRAMMABLE_WITH_USB_LEGACY_CUSTOM_CLASS_BOOTLOADER)"

	#pragma code
	
	
	//These are your actual interrupt handling routines.
	#pragma interrupt YourHighPriorityISRCode
	void YourHighPriorityISRCode()
	{
		//RB port change interrupt as high priority by default
		if(INTCONbits.RBIF)
		{ 	if(INTCONbits.RBIE)				//This is tricky b'cause TMR0IF sharing the same high priority
			{								//interrupt, this is to avoid a false isr run when INTCONbits.RBIE=0;
			if(!KEY7){						//only handle a high to low transition
			MCUStateSave = MCUState;
			MCUState = TRANSITION;
			KeyHitKey = TRUE;
			INTCONbits.RBIE = 0;	//b'cause there is a key scan time as long as 30 msec, 
			}
			PORTB = PORTB;			//we need to avoid erratic key conditions by a RBIE disable
			INTCONbits.RBIF = 0;
			}
		}
		//TMR0 is a high priority ISR by default
		if(INTCONbits.TMR0IF)
		{
			TickUpdate();
			INTCONbits.TMR0IF = 0;
		}
	
	}	//This return will be a "retfie fast", since this is in a #pragma interrupt section 
	#pragma interruptlow YourLowPriorityISRCode
	void YourLowPriorityISRCode()
	{
		if(INTCON3bits.INT1IF)
		{
			INTCON3bits.INT1IF = 0;
		}

	}	//This return will be a "retfie", since this is in a #pragma interruptlow section 
#endif

/*******************************************TASKS**********************************************/
void uiTask(void);		//it is all user interaction, incl. rtc, battery level, menus etc.
void ledTask(void);		//ledTask as a visual indicator that the MCU is running.

/*******************************************MAIN**********************************************/
void main(void)
{
	ANCON0 = 0xFF;			//all digital operation
	ANCON1 = 0xFF;			//all digital operation

	LED_GATE_TRIS 	= 0;		//close control gate for tri-color LED
	LED_GATE		= 1;		
	LED_TRIS 		= 0;		//set LED port as output for debug
	LED				= LED_OFF;	//set LED off to start	

	KEY7_CTRL_TRIS	= 0;		//validate KEY7 as a single switch instead of a matrix keypad
	KEY7_CTRL		= 0;

	TickInit();							//heart beat initialization, at 10ms for a TICK

	//enable global interrupt
	WDTCONbits.REGSLP 	= 1;			//On-chip regulator enters low-power operation with Sleep()
	INTCONbits.RBIE		= 1;			//enable RB port change interrupt for RB4 (power key)	
	RCONbits.IPEN		= 1;			//enable interrupt priority level
	INTCONbits.PEIE 	= 1;
	INTCONbits.GIE  	= 1;
	
	while(1)
	{
		uiTask();
		ledTask();
	}
	
}
/* $PAGE */
/*
*******************************************************************************************************
* Description:
*******************************************************************************************************
*/
void uiTask(void)
{
	//MCU sleep handler
	if(MCUState==SLEEP)
		{
			//perform all necessary content saving prior going sleep
			LED = LED_OFF;
			INTCONbits.RBIF = 0;
			INTCONbits.RBIE = 1;
			Sleep();
			Nop();
		}

	//keyScan handler
	if( TickGetDiff(TickGet(),t1) >= 3)					//30ms tick delay to sample key press
	{
		switch(KeyScanState){							//Key scan here
			case KEY_UP:
					if(KEY7==0) {
						KeyDownTmr = 0;					//reset key down timer
						KeyScanState = KEY_DEBOUNCE;
					}
					break;
			case KEY_DEBOUNCE:
					if(KEY7==0)
						{KeyScanState = KEY_DOWN;}
					else
						{KeyScanState = KEY_UP;}
					break;
			case KEY_DOWN:
					if(KEY7==0){
						KeyDownTmr++;
					}else{
						KeyScanState = KEY_DEBOUNCE;
					}						
			}

		if(KeyHitKey)						//is a valid RB port trigger
		{
			if(KeyScanState==KEY_DOWN)		//is a valid key down, sample every 30msec in this case
			{
				if(KeyDownTmr>=66)			//it is a key and hold > 2sec
				{
					(MCUStateSave==SLEEP)?(MCUState = RUN):(MCUState = SLEEP);	
					if(MCUState==SLEEP) KeyDownTmr=0; //reset down time to avoid immediate wait up from Sleep on a momentarily key press				
				}
	
			} 
			else if (KeyScanState==KEY_UP)	//key released, MCUState may have been toggled or not ever toggled
			{
					if(KeyDownTmr<66)			//is a valid press and hold <2sec
						MCUState = MCUStateSave;//no change in MCUState, RB port trigger handled
					KeyHitKey = FALSE;			//RB port trigger handled
					INTCONbits.RBIF = 0;
					INTCONbits.RBIE = 1;
			}	
		}

		//LED blink handler
		if(MCUState==RUN || (MCUState==TRANSITION && MCUStateSave==RUN))
			{LEDState = ON;}
		else
			{LEDState = OFF;}

		t1 = TickGet();
	} //sample key press finished
}

/* $PAGE */
/*
*******************************************************************************************************
* Description:
*******************************************************************************************************
*/
void ledTask(void)
{
	//Firmware execution only when LEDflash is at RUNNING state
	//This is a simple indication for a running system

	if(LEDState==ON)
	{	LED_GATE = 0;							//open the gate
		if( TickGetDiff(TickGet(),t2) >= 10)	//100ms LED toggle rate	
		{
			LED = ~LED;

			t2 = TickGet();	
		}
	} else {
		LED_GATE = 1;							//close the gate
		LED = LED_OFF;
	}

}