// File Name	: ee281proj.c
// 
// Title		: LCD Desk Clock
// Revision		: 1.0
// Notes		:	
// Target MCU	: Atmel AVR series
// Editor Tabs	: 4
// 
// Revision History:
// When			Who			Description of change
// -----------	-----------	-----------------------
// 14-Oct-2002	pstang		Created the program
// 10-Nov-2002	gangxie@stanford.edu
//*****************************************************************************

 
//----- Include Files ---------------------------------------------------------
#include 			// include I/O definitions (port names, pin names, etc)
#include 	// include "signal" names (interrupt names)
#include 	// include interrupt support
#include 

#include "global.h"		// include our global settings
#include "timer.h"		// include timer function library
#include "lcd.h"


//----- Functions -------------------------------------------------------------
void InitSysState(void);
void InitIOPins(void);
void BottonOneClick(void);
void BottonTwoClick(void);
void BottonIncClick(void);
void BottonDecClick(void);
void timer0OutForButtonClick(void);
void UpdateFieldEdited (void);
void ChangeFieldValue(u08 isInc);
u08 AdjustOnoff(u08 val);
void Timer1CmpA_Isr(void);
void StopPumping(void);
void TurnToNextPage(void);
void PageTimeout(void);
void ChangeSpeakerFreq(void);
u08 NeedSpeakerWork(void);


//----- Global Variables ------------------------------------------------------
//The number of editable fields in each LCD page
static char __attribute__ ((progmem)) NumOfFieldsInPage[] = {7, 4, 2, 2, 3, 6};

RtcTimeType		gRtcTime;
RtcTimeType		gAlarmTime;
PumpInfoType	gPumpInfo;
SysStateType	gSysState;


//----- Begin Code ------------------------------------------------------------
int main(void)
{
	u08 temp;

	InitIOPins();					//Iitialized first for security
	timerInit(); 					//Initialize the timer systems	
	InitializeRtc();				//Initialize clock
	InitializeAlarm();				//Initialize alarm
	InitializePump();				//Initialize pump
	InitSysState();				

	lcdInit();
	lcdInitDisp();

	SetupTimer0();					//timer0 for switches/buttons
	SetupTimer1();					//Initialize Timer1 for speaker

	while (1) 
	{
		temp = inp(SW_PORTI);		//read SW
		if ((~temp)&SW_BTNONE)
			BottonOneClick();
		if ((~temp)&SW_BTNTWO)
			BottonTwoClick();
		if ((~temp)&SW_BTNINC)
			BottonIncClick();
		if ((~temp)&SW_BTNDEC)
			BottonDecClick();

		UpdateFieldEdited();		//LCD
		PageTimeout();
		lcdUpdateDisp();
		ChangeSpeakerFreq();		//Speaker
	}
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void InitSysState(void)
{
	gSysState.pumpActivated = 0;
	gSysState.pumpSoundOn	= 0;

	gSysState.alarmOn		= 0;
	gSysState.alarmCount	= 0;

	gSysState.displayPage	= kPage0_Norm;
	gSysState.displayState	= kState_View;
	gSysState.displayTimeout= 0;
	gSysState.displayPrevPage=kPage0_Norm;

	gSysState.bntOneCycCnt	= 0;
	gSysState.bntTwoCycCnt	= 0;
	gSysState.bntIncCycCnt	= 0;
	gSysState.bntDecCycCnt	= 0;

	gSysState.update_rtc_year	= 0;
	gSysState.update_rtc_month	= 0;
	gSysState.update_rtc_day	= 0;
	gSysState.update_rtc_sunsat	= 0;
	gSysState.update_rtc_hour	= 0;
	gSysState.update_rtc_minute	= 0;
	gSysState.update_rtc_second	= 0;
	gSysState.update_info		= 0;
	gSysState.update_info_num	= 0;
	gSysState.update_alm_hour	= 0;
	gSysState.update_alm_minute	= 0;
	gSysState.update_alm_second	= 0;
	gSysState.update_alm_sound	= 0;
	gSysState.update_pa_active	= 0;
	gSysState.update_pa_sound	= 0;
	gSysState.update_pd_minute	= 0;
	gSysState.update_pd_second	= 0;
	gSysState.update_pf_day	= 0;
	gSysState.update_pf_hour	= 0;
	gSysState.update_pf_minute	= 0;
	gSysState.update_pn_year	= 0;
	gSysState.update_pn_month	= 0;
	gSysState.update_pn_day	= 0;
	gSysState.update_pn_hour	= 0;
	gSysState.update_pn_minute	= 0;
	gSysState.update_pn_second	= 0;

	gSysState.fieldAdjusted		= 0;

	gSysState.spkPosVolt		= 1;
	gSysState.spkNoteNum		= 0;
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void InitIOPins(void)
{
	//initialize SW
	outp(0x00, SW_PORTR);				//input
	outp(0xff, SW_PORTI);

	//initialize SPK
	outp(0xff, SPK_PORTR);				//output
	outp(0x00, SPK_PORTO);				//this line is very important for the pump
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void StopPumping(void)
{
	cbi(SPK_PORTO, PUMP_POSPIN);		//clear 0=>stop bumping
	CalcNextPumpTime();
	gPumpInfo.duration.year = 0;
	gSysState.update_info = 1;
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void TurnToNextPage(void)
{
	gSysState.displayPage ++;
	if (gSysState.displayPage > kPage5_PumpNext)
		gSysState.displayPage = kPage0_Norm;
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void BottonOneClick(void)
{
	if (gSysState.alarmCount > 0)
	{
		gSysState.alarmCount = 0;
		gSysState.bntOneCycCnt = kIgnoreClickDuration;
		return;
	}

	if (gPumpInfo.duration.year > 0)
	{
		StopPumping();
		gSysState.bntOneCycCnt = kIgnoreClickDuration;
		return;
	}

	gSysState.displayTimeout = kPageViewTimeout;

	if (gSysState.bntOneCycCnt == 0)
	{
		if (gSysState.displayState == kState_View)
		{
			gSysState.displayPrevPage = gSysState.displayPage;
			TurnToNextPage();
		}
		else
			gSysState.fieldAdjusted ++;

		gSysState.bntOneCycCnt = kIgnoreClickDuration;
	}
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void BottonTwoClick(void)
{
	if (gSysState.alarmCount > 0)
	{
		gSysState.alarmCount = 0;
		gSysState.bntTwoCycCnt = kIgnoreClickDuration;
		return;
	}

	if (gPumpInfo.duration.year > 0)
	{
		StopPumping();
		gSysState.bntTwoCycCnt = kIgnoreClickDuration;
		return;
	}

	gSysState.displayTimeout = kPageViewTimeout;

	if (gSysState.bntTwoCycCnt == 0)
	{
		if (gSysState.displayState == kState_View)	//view->edit
		{	
			gSysState.displayState = kState_Edit;
			gSysState.fieldAdjusted = 0;
		}
		else										//edit->view
		{
			gSysState.displayState = kState_View;
			lcdStopBlink();
			CalcNextPumpTime();
		}

		gSysState.bntTwoCycCnt = kIgnoreClickDuration;
	}
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void BottonIncClick(void)
{
	if (gSysState.alarmCount > 0)
	{
		gSysState.alarmCount = 0;
		gSysState.bntIncCycCnt = kIgnoreClickDuration;
		return;
	}

	if (gPumpInfo.duration.year > 0)
	{
		StopPumping();
		gSysState.bntIncCycCnt = kIgnoreClickDuration;
		return;
	}

	gSysState.displayTimeout = kPageViewTimeout;

	if (gSysState.bntIncCycCnt == 0)
	{
		if (gSysState.displayState==kState_Edit)
			ChangeFieldValue(1);
		gSysState.bntIncCycCnt = kIgnoreClickDuration;
	}
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void BottonDecClick(void)
{
	if (gSysState.alarmCount > 0)
	{
		gSysState.alarmCount = 0;
		gSysState.bntDecCycCnt = kIgnoreClickDuration;
		return;
	}

	if (gPumpInfo.duration.year > 0)
	{
		StopPumping();
		gSysState.bntDecCycCnt = kIgnoreClickDuration;
		return;
	}

	gSysState.displayTimeout = kPageViewTimeout;

	if (gSysState.bntDecCycCnt == 0)
	{
		if (gSysState.displayState==kState_Edit)
			ChangeFieldValue(0);
		gSysState.bntDecCycCnt = kIgnoreClickDuration;
	}
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void ChangeFieldValue(u08 isInc)
{
	u08 aid, tmp;

	//on/off/active/inactive
	switch (gSysState.displayPage)
	{
	case kPage0_Norm:
		switch (gSysState.fieldAdjusted)
		{
		case 0:
			gRtcTime.month = Adjust2DgtNumber(gRtcTime.month, isInc, 1, 12);
			gSysState.update_rtc_month = 1;
			break;
		case 1:
			tmp = CalcDaysInMonth(gRtcTime.month, gRtcTime.year);
			gRtcTime.day = Adjust2DgtNumber(gRtcTime.day, isInc, 1, tmp);
			gSysState.update_rtc_day = 1;
			break;
		case 2:
			gRtcTime.year = AdjustYear(gRtcTime.year, isInc);
			gSysState.update_rtc_year = 1;
			break;
		case 3:
			gRtcTime.sunsat = Adjust2DgtNumber(gRtcTime.sunsat, isInc, 0, 6);
			gSysState.update_rtc_sunsat = 1;
			break;
		case 4:
			gRtcTime.hour = Adjust2DgtNumber(gRtcTime.hour, isInc, 0, 23);
			gSysState.update_rtc_hour = 1;
			break;
		case 5:
			gRtcTime.minute = Adjust2DgtNumber(gRtcTime.minute, isInc, 0, 59);
			gSysState.update_rtc_minute = 1;
			break;
		case 6:
			gRtcTime.second = Adjust2DgtNumber(gRtcTime.second, isInc, 0, 59);
			gSysState.update_rtc_second = 1;
			break;
		}
		break;
	case kPage1_Alarm:
		switch (gSysState.fieldAdjusted)
		{
		case 0:
			gAlarmTime.hour = Adjust2DgtNumber(gAlarmTime.hour, isInc, 0, 23);
			gSysState.update_alm_hour = 1;
			break;
		case 1:
			gAlarmTime.minute = Adjust2DgtNumber(gAlarmTime.minute, isInc, 0, 59);
			gSysState.update_alm_minute = 1;
			break;
		case 2:
			gAlarmTime.second = Adjust2DgtNumber(gAlarmTime.second, isInc, 0, 59);
			gSysState.update_alm_second = 1;
			break;
		case 3: 
			gSysState.alarmOn = AdjustOnoff(gSysState.alarmOn);
			gSysState.update_alm_sound = 1;
			break;
		}
		break;
	case kPage2_PumpActive:
		switch (gSysState.fieldAdjusted)
		{
		case 0:
			gSysState.pumpActivated = AdjustOnoff(gSysState.pumpActivated);
			gSysState.update_pa_active = 1;
			break;
		case 1:
			gSysState.pumpSoundOn = AdjustOnoff(gSysState.pumpSoundOn);
			gSysState.update_pa_sound = 1;
			break;
		}
		break;
	case kPage3_PumpDuration:
		switch (gSysState.fieldAdjusted)
		{
		case 0:
			aid = gPumpInfo.duration.minute;
			tmp = Adjust2DgtNumber(aid, isInc, 0, 99);
			gPumpInfo.duration.minute = tmp;
			gSysState.update_pd_minute = 1;
			break;
		case 1:
			aid = gPumpInfo.duration.second;
			tmp = Adjust2DgtNumber(aid, isInc, 0, 59);
			gPumpInfo.duration.second = tmp;
			gSysState.update_pd_second = 1;
			break;
		}
		break;
	case kPage4_PumpFreq:
		switch (gSysState.fieldAdjusted)
		{
		case 0:
			aid = gPumpInfo.frequency.day;
			tmp = Adjust2DgtNumber(aid, isInc, 0, 99);
			gPumpInfo.frequency.day = tmp;
			gSysState.update_pf_day = 1;
			break;
		case 1:
			aid = gPumpInfo.frequency.hour;
			tmp = Adjust2DgtNumber(aid, isInc, 0, 23);
			gPumpInfo.frequency.hour = tmp;
			gSysState.update_pf_hour = 1;
			break;
		case 2:
			aid = gPumpInfo.frequency.minute;
			tmp = Adjust2DgtNumber(aid, isInc, 0, 59);
			gPumpInfo.frequency.minute = tmp;
			gSysState.update_pf_minute = 1;
			break;
		}
		break;
	case kPage5_PumpNext:
		switch (gSysState.fieldAdjusted)
		{
		case 0:
			aid = gPumpInfo.next.hour;
			tmp = Adjust2DgtNumber(aid, isInc, 0, 23);
			gPumpInfo.next.hour = tmp;
			gSysState.update_pn_hour = 1;
			break;
		case 1:
			aid = gPumpInfo.next.minute;
			tmp = Adjust2DgtNumber(aid, isInc, 0, 59);
			gPumpInfo.next.minute = tmp;
			gSysState.update_pn_minute = 1;
			break;
		case 2:
			aid = gPumpInfo.next.second;
			tmp = Adjust2DgtNumber(aid, isInc, 0, 59);
			gPumpInfo.next.second = tmp;
			gSysState.update_pn_second = 1;
			break;
		case 3:
			aid = gPumpInfo.next.month;
			tmp = Adjust2DgtNumber(aid, isInc, 1, 12);
			gPumpInfo.next.month = tmp;
			gSysState.update_pn_month = 1;
			break;
		case 4:
			aid = gPumpInfo.next.day;
			tmp = CalcDaysInMonth(gPumpInfo.next.month, gPumpInfo.next.year);
			gPumpInfo.next.day = Adjust2DgtNumber(aid, isInc, 1, tmp);
			gSysState.update_pn_day = 1;
			break;
		case 5:
			gPumpInfo.next.year = AdjustYear(gPumpInfo.next.year, isInc);
			gSysState.update_pn_year = 1;
			break;
		}
		break;
	}
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
u08 AdjustOnoff(u08 val)
{
	if (val == 0)
		return 1;
	return 0;
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void UpdateFieldEdited (void)
{
	u08 tot;

	if (gSysState.displayState == kState_Edit)
	{
		tot = PRG_RDB(&NumOfFieldsInPage[gSysState.displayPage]);
		if (gSysState.fieldAdjusted >= tot)
		{
			gSysState.fieldAdjusted -= tot;
			TurnToNextPage();
		}
	}
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
void PageTimeout(void)
{
	if (gSysState.displayTimeout == 0)
	{
		if (gSysState.displayState == kState_Edit)
		{
			lcdStopBlink();
			gSysState.displayState= kState_View;
			CalcNextPumpTime();
		}

		if (gSysState.displayPage != kPage0_Norm)
		{
			gSysState.displayPrevPage = gSysState.displayPage;
			gSysState.displayPage = kPage0_Norm;
		}
	}
}

//---------------------------------------------------------------------------------
// In order to filter out noise during pushbutton clicks  
//---------------------------------------------------------------------------------
void timer0OutForButtonClick(void)
{
	if (gSysState.bntOneCycCnt > 0)
		gSysState.bntOneCycCnt--;

	if (gSysState.bntTwoCycCnt > 0)
		gSysState.bntTwoCycCnt--;

	if (gSysState.bntIncCycCnt > 0)
		gSysState.bntIncCycCnt--;

	if (gSysState.bntDecCycCnt > 0)
		gSysState.bntDecCycCnt--;
}

//---------------------------------------------------------------------------------
// Timer1CmpA_Isr()
// Generate waves for speaker at 2000 Hz
//---------------------------------------------------------------------------------
void Timer1CmpA_Isr(void)
{
	if (NeedSpeakerWork()==0)
	{
		cbi(SPK_PORTO, SPK_POSPIN);		//clear 0
		return;
	}

	if (gSysState.spkPosVolt)
	{
		gSysState.spkPosVolt = 0;
		sbi(SPK_PORTO, SPK_POSPIN);		//set 1
	}
	else
	{
		gSysState.spkPosVolt = 1;
		cbi(SPK_PORTO, SPK_POSPIN);		//clear 0
	}
}

//---------------------------------------------------------------------------------
// Note			Frequency
// A5			880
// B5			987
// C6			1046
// D6			1174
// E6			1318
// F6			1397
// G6			1568
// A6			1760
//---------------------------------------------------------------------------------
void ChangeSpeakerFreq(void)
{
	if (NeedSpeakerWork())
	{
		switch (gSysState.spkNoteNum)
		{
		//																		  Freq	Note
		case 0: outp(0x02, OCR1AH); outp(0x0B, OCR1AL);	break;	//3686000/8/523 = 880 =>A4
		case 1: outp(0x01, OCR1AH); outp(0xD2, OCR1AL);	break;	//3686000/8/466 = 987 =>B4
		case 2: outp(0x01, OCR1AH); outp(0xB8, OCR1AL);	break;	//3686000/8/440 = 1046 =>B4
		case 3: outp(0x01, OCR1AH); outp(0x88, OCR1AL);	break;	//3686000/8/392 = 1174 =>A4
		case 4: outp(0x01, OCR1AH); outp(0x5D, OCR1AL);	break;	//3686000/8/349 = 1318 =>B4
		case 5: outp(0x01, OCR1AH); outp(0x49, OCR1AL);	break;	//3686000/8/329 = 1397 =>B4
		case 6: outp(0x01, OCR1AH); outp(0x25, OCR1AL);	break;	//3686000/8/293 = 1568 =>B4
		default: outp(0x01, OCR1AH); outp(0x05, OCR1AL);	break;	//3686000/8/261 = 1760 =>B4
		}
	}
}

//---------------------------------------------------------------------------------
//---------------------------------------------------------------------------------
u08 NeedSpeakerWork(void)
{
	if (((gSysState.alarmOn==1) && 
		 (gSysState.alarmCount>0)) ||
		((gSysState.pumpActivated==1) && 
		 (gSysState.pumpSoundOn==1) && 
		 (gPumpInfo.duration.year>0)))
	return 1;

	return 0;
}