/**********************************************************************
 * AVR RGB Clock (v1.0) by Rickard Gunée, 2007                  
 *
 * About this program:
 * This clock display the time with two RGB leds, one for hours 
 * and one for seconds, using the standard resistor colors with
 * two additional colors (pink and cyan) to get 12 colors to be 
 * able to show a 12h clock and minutes in 5min steps.          
 * The time is set with one single button, or updated from a    
 * DCF77 receiver. Except for the hours- and minutes-LEDs there
 * is also one status LED to indicate if the clock is updated   
 * If more than 72h since last update it will be blinking in    
 * red in pace with the received DCF77 signal. Less than 40h    
 * since last received DCF signal it will be lit in green. For  
 * times in between these two it will be yellow.                
 *                 
 * For more info:                                          
 * see www.rickard.gunee.com/projects            
 *
 * License:
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

#include <avr/io.h>
#include <avr/signal.h>

#include "dcf77.h"
#include "main.h"

extern unsigned int dcf_time_ok;
extern dcf_time_t global_time;

u32 secpart			= 0;
u08 sw_state		= SW_S_UP;
u08 time_state		= TIME_S_SHOW_TIME;
u08 display_minute	= 0;
u08 display_hour	= 0;
u08	blink_counter	= 0;

// color table, defines what colors representing the numbers 0..11

unsigned char colors[12][3] =
{
	{0,0,0},
	{20,2,0},
	{255,0,0},	
	{255,32,0},	
	{255,128,0},
	{0,255,0},	
	{0,0,255},	
	{150,0,200},		
	{48,48,48},		
	{255,255,255},	
	{255,64,64},
	{0,255,127}	
};

// switch handler, this handles setting the time

void handle_sw()
{
	static unsigned char sw_time;
	unsigned char next_sw_state = sw_state;

	switch(sw_state)
	{	
		case SW_S_UP:
			if(!sw_bit && sw_time > SW_TIME_DEBOUNCE)
				next_sw_state = SW_S_DOWN;
			break;
	
		case SW_S_DOWN:
			if(sw_bit && sw_time > SW_TIME_DEBOUNCE)
			{
				next_sw_state = SW_S_UP;
				switch(time_state)
				{
					case TIME_S_SET_MINUTE:
						display_minute = display_minute>54?0:display_minute+5;
						break;
					
					case TIME_S_SET_HOUR:
						display_hour = display_hour==23?0:display_hour+1;
						break;
					
					case TIME_S_SHOW_TIME:
					default:
						break;
				}
			}
			else if(sw_time > SW_TIME_LONG)
			{
				next_sw_state = SW_S_LONG;
				time_state = time_state>1?0:time_state+1;
				
				if(time_state == TIME_S_SHOW_TIME)
				{
					global_time.minute	=	display_minute;
					global_time.hour	=	display_hour;
				}
			}
			break;
		
		case SW_S_LONG:
			if(sw_bit && sw_time > SW_TIME_DEBOUNCE)
				next_sw_state = SW_S_UP;
			break;
	}

	if(sw_state != next_sw_state)
	{
		sw_state = next_sw_state;
		sw_time=0;
	}
	else
		sw_time++;
}


// this routine is called every 8*510 cycles (1960.78Hz)
// and it is used to update the clock and colors
SIGNAL(SIG_OVERFLOW0)
{
	static char counter;
	unsigned char update;
	unsigned char m12,h12;
	unsigned char blm,blh;
	
	counter++;
	
	if(!(counter&0x1F))
		dcf77_handler();	// poll the dcf77_handler every 32 cycles
		
	if(!(counter&0x3F))	// poll the button handler every 64 cycles
		handle_sw();			
		
	if(!(counter&0x7F))	// create a "timer" for the clock set blinking
		blink_counter++;	// increase at about 14Hz
	
	update=0;
	
	secpart	+= 51;			// 1960.78Hz * 51 / 100000 = 1Hz	
	if(secpart > 100000)	// thus, this if statement is called at 1Hz in average
	{
		secpart -= 100000;  // remove 100000 cycles instead of lear to keep the rest
		
		if(++global_time.second>59)	//update seconds, check for overflow
		{
			global_time.second = 0;
			if(++global_time.minute>59)	//update minutes, check for overflow
			{
				global_time.minute = 0;
				if(dcf_time_ok)
					dcf_time_ok--;
				
				if(++global_time.hour>23)	//update hours, check for overflow
					global_time.hour = 0;
			}
		}
		update=1;
	}	
		
	if(update || (time_state && !(blink_counter&1)))
	{		
		if(time_state == TIME_S_SHOW_TIME)
		{
			display_minute	= global_time.minute;
			display_hour	= global_time.hour;
		}
	
		m12 = display_minute / 5;									// minutes indicated in 5min steps
		h12 = display_hour<12?display_hour:display_hour-12;		// hours indicated as 12h
		
		blm=(time_state==TIME_S_SET_MINUTE)&&(!(blink_counter&3));// blink minute LED if setting minute
		blh=(time_state==TIME_S_SET_HOUR)&&(!(blink_counter&3));	// blink hour LED if setting hour
		
		if(time_state != TIME_S_SHOW_TIME)
		{
			if(!(blink_counter&3))
				PORTC = 0x1F;
			else
				PORTC = 0x2F;
		}
		
		PWM_M_R = blm?0:colors[m12][RED];
		PWM_M_G = blm?0:colors[m12][GREEN];
		PWM_M_B = blm?0:colors[m12][BLUE];
		
		PWM_H_R = blh?0:colors[h12][RED];
		PWM_H_G = blh?0:colors[h12][GREEN];
		PWM_H_B = blh?0:colors[h12][BLUE];		
	}
	
	if(time_state == TIME_S_SHOW_TIME)
	{
		if(!dcf_time_ok)
			PORTC = !dcf_pin ? 0x0F : 0x1F ;
		else if(dcf_time_ok > 40)
			PORTC = 0x2F;
		else
			PORTC = 0x3F;
	}
}

// Main just sets up all hardware and then is stuck in a loop for ever as
// nothing needs to be done becase the interrupt is taking care of everything

int main( void )
{
	DDRC = 0x30;
	DDRB = 0x0E;
	DDRD = 0x68;

	PORTC = 0x0F;
	PORTB = 0x0E;
	PORTD = 0x6B;

	TCNT0	= 0x00;	// clear timer0
	TIMSK0	= 0x01;	// enable timer0 overflow interrupt
	TCCR0A	= 0xA1;	// phase correct positive pwm on both comparator outputs
	TCCR0B	= 0x02;	// select IOclk/8	

	TCNT1	= 0x0000;	// clear timer1 (16-bit)
	TCCR1A	= 0xA1;	// fast positive 8-bit pwm on both comparator outputs
	TCCR1B	= 0x02;	// select IOclk/8

	TCNT2	= 0x00;	// clear timer2	
	TCCR2A	= 0xA1;	// phase correct positive pwm on both comparator outputs
	TCCR2B	= 0x02;	// select IOclk/8
	
	PWM_M_R = 0;	// set minute to black
	PWM_M_G = 0;
	PWM_M_B = 0;
		
	PWM_H_R = 0;	// set hour to black
	PWM_H_G = 0;
	PWM_H_B = 0;
	
	sei();  		// enable interrupts
	
	for(;;)			// do nothing, everything is done by the timerinterrupts from now on :)
	{
	}
	
}
