/*
 *      watch.c
 *      
 *      Copyright 2010 Brian Starkey
 *      
 *      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., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 *
 */

/* 	avrdude command to set fuses, 32kHz external crystal, 
 * 	with internal capacitors.
 *	!WARNING! This will DISABLE programming WITHOUT the crystal!
 *
 *	avrdude -p attiny26l -c usbasp -U lfuse:w:0xa9:m -U hfuse:w:0x17:m 
 *
 */

#define F_CPU 32768

#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>
#include <avr/eeprom.h>

/*======== Global Variables ========*/
char hbuffer[7]; 		// Used to store the frame information.
int h = 0,m = 0,s = 0;	// Hours, minutes and seconds.
int dectime[6];			// String buffer to store the time digits.
static char time[27];	// Buffer to store bytes to be displayed.
static char buf[3];		// Used to load font from PROGMEM.
char show = 0;			// Used to decide which mode we're in.

/*======== Character Set ========*/
char zero[] PROGMEM = {0x1F,0x11,0x1F};
char one[] PROGMEM = {0x12,0x1F,0x10};
char two[] PROGMEM = {0x1D,0x15,0x17};
char three[] PROGMEM = {0x11,0x15,0x1F};
char four[] PROGMEM = {0x7,0x4,0x1F};
char five[] PROGMEM = {0x17,0x15,0x1D};
char six[] PROGMEM = {0x1F,0x15,0x1D};
char seven[] PROGMEM = {0x1,0x1,0x1F};
char eight[] PROGMEM = {0x1F,0x15,0x1F};
char nine[] PROGMEM = {0x7,0x5,0x1F};	
PGM_P numbers[] PROGMEM = {
		zero,
		one,
		two,
		three,
		four,
		five,
		six,
		seven,
		eight,
		nine
};

/*======== Function Prototypes ========*/
static void tick (void); 				// Gets called every second.
void hframe (char * display, int time); // Display a frame.
void hscroll (char * banner, int length, int speed); // Scroll a string right to left.
char * loadnumber (int num); 			// Load character data from PROGMEM.
void makedectime (void); 				// Make a time string from dectime[].
void calctime(void);					// Turn h, m, s into chars in dectime[].
void set(void);							// Set the time.



/* void hframe (char * display, int time)
 * 	Display the data from 'display' for 'time'.
 * 	
 * 	display:
 * 		Should be an array of 7 chars, the least significant 5 bits
 * 		are the pixel data.
 * 
 * 	time:
 * 		Time to display the frame for. (Arbitrary measurement)
 *
 * 	PORTA:
 * 		anodes (*7)
 * 
 * 	PORTB:
 * 		cathodes (*5)
 */
void hframe (char * display, int time) {
	int count = 0;
	char col, row, offset;
		for(count = 0; count < time; count++) {
			offset = 0;			
			for (col = 64; col >= 1; col = col >> 1) {
				PORTA = col | 0x80;					
				for (row = 1; row <= 16; row = row << 1) {
					PORTB = ~(row & display[offset]);	// Sink cathode.
					PORTA = ((PORTB << 3) & 0x80) | col;// Drive anode. 					
					PORTB = 0xFF;						// All off.
				}		
				PORTA = 0x80;	// Internal pull-up for button.
				offset++;			
			};
		};
}

/* void hscroll(char * banner, int length, int speed)
 * 	Scroll the data from 'banner' from left to right at 'speed'
 * 	
 * 	banner:
 * 		Should be an array of chars, the least significant 5 bits
 * 		of each are the pixel data.
 * 
 * 	length:
 * 		The overall length of 'banner', i.e. how many columns.
 *
 * 	speed:
 * 		How fast to scroll, sets 'time' for hframe() calls.
 */
void hscroll (char * banner, int length, int speed) {
	int offset, i, pos;
	// Start with -7 to make the banner start at right hand edge.
	for(offset = -7; offset < length; offset++) {
		for(i = 0; i < 7; i++) {
			pos = i + offset;
			if((pos < 0) || (pos >= length)) {
				hbuffer[i] = 0;	// Ensure blank columns			
			}
			else{
				hbuffer[i] = banner[pos];
			}
		};		
		hframe(hbuffer, speed);
	};
}

/* char * loadnumber (int num)
 * 	Load a number from the character table in PROGMEM, returns a
 *  pointer to 'buf' which will contain the character data.
 * 
 * 	num:
 * 		Number (0-9) to load.
 * 
 * 	returns:
 * 		Pointer to string containing character data.
 */
char * loadnumber (int num) {
	int i;	
	PGM_P p;
	memcpy_P(&p, &numbers[num], sizeof(PGM_P));
	for (i = 0; i < 3; i++) {	
		buf[i] = pgm_read_byte(p+i);
	};
	return buf;
}

/* void makedectime (void)
 * 	Turns the dectime digits into a string to display using hscroll.
 * 	Puts the string into time[] in the form hh:mm:ss.
 */ 
void makedectime (void) {
	PORTA = 0x00;
	PORTB = 0xFF; // Ensures blank screen.
	char * temp;
	char i, j, offset = 0;
	for (j = 0; j <= 6; j++) {
		temp = loadnumber(dectime[j]);	
		for (i = 0; i < 3; i++) {	
			time[offset] = *(temp + i);
			offset++;
		};
		time[offset++] = 0x00;		// Space between digits.
		if ((j%2) & (j<6)) {		// Puts a colon every 2 digits.
			time[offset++] = 0x0A; 
			time[offset++] = 0x00;
		};			
	};
}

/* void calctime (void)
 *  Turn h, m and s into individual digits for use by makedectime()
 * 	and (ultimately) loadnumber().
 */
void calctime (void) {
	dectime[5] =s % 10;				// 'Units' of seconds.
	dectime[4] = (int) (s / 10);	// 'Tens' of seconds.
	dectime[3] = m % 10;
	dectime[2] = (int) (m / 10);
	dectime[1] = h % 10;
	dectime[0] = (int) (h / 10);
}

/* void tick (void)
 *	Gets called every second by TIMER1_OVF1 interrupt service
 * 	routine.
 * 	Carries out the necessary updates of variables and buffers.
 */
static void tick (void) {
	PORTA = 0x00;
	s++;
	if(s > 59) {		
		m++;
		s = 0;
		if(m > 59) {
			m = 0;
			h++;
			if(h > 23) {
				h = 0;
			}
		}
	}
	calctime();
	makedectime();
}

/* void set (void)
 * 	Enter time setting mode, stays in this function until the
 *  setting process is completed.
 */
void set (void) {
	TCCR1B = 0;	
	calctime();			
	hscroll(time,27,2);
	char * temp;
	int i, offset;	
	do {
		if (show == 3) {
			offset = 0;
		}
		else {
			offset = 2;
		}
		temp = loadnumber(dectime[offset]);	
		for (i = 0; i < 3; i++) {	
				hbuffer[i] = *(temp+i);
		}
		temp = loadnumber(dectime[offset+1]);	
		for (i = 0; i < 3; i++) {	
			hbuffer[i+4] = *(temp+i);
		}
			hframe(hbuffer,17);
			_delay_ms(333);
	} while (show >= 3);
	s = 0;
	makedectime();
	TCCR1B |= (1 << CS13);	
}

//PORTB are cathodes (5)
//PORTA are anodes   (7)
int main (void)
{
	DDRA = 0xFF;    // Configure PORTA as output.
	DDRB = 0x1F;    // Configure PORTB as output. (Low Z).
	PORTB = 0xFF;	// Turn all rows off.
	TCCR1B |= (1 << CS13);	
	TIMSK |= ((1 << TOIE1) | (1 << TOIE0));  // Setting up the timer
	TCNT1 = 0;
	TCNT0 = 0;
	MCUCR |= (1 << ISC01);
	GIMSK |= (1 << INT0);
	sei();	
	makedectime();
	_delay_ms(1);
	for(;;) {
		if (show == 2) {
			hscroll(time,27,2);
			show = 0;
		}
		else if (show >= 3) {
			set();
		}
		if (show == 0) {
			asm("SLEEP");
		}
	}
}

/* TIMER1_OVF1_vect
 * 	Overflows every second.
 */
ISR(TIMER1_OVF1_vect) {
	tick();
}

/* TIMER0_OVF0_vect
 * 	Used to detect prolonged button presses (~2 Seconds)
 */
ISR(TIMER0_OVF0_vect) {
	MCUCR &= ~(1 << ISC00);
	GIMSK &= ~(1 << INT0);
	TCCR0 = 0;
	TCNT0 = 0;	
	if (show < 3) {
		show = 3;
	}
	else if (show == 3) {
		show++;
	}
	else {
	show = 2;
	}	
	GIMSK |= (1 << INT0);
}

/* INT0_vect
 * 	External interrupt 0, detects button presses
 */
ISR(INT0_vect) {	
	if (MCUCR & (1 << ISC00)) {
		TCCR0 = 0;
		MCUCR &= ~(1 << ISC00);		
		switch (show) {
				case 0:
					show = 2;					
					break;
				case 3:
					h++;
					if (h > 23) h = 0;
					break;
				case 4:
					m++;
					if (m > 59) m = 0;
					break;
				default:
					break;
		}
 		TCNT0 = 0;
		calctime();
	}
	else {
		TCCR0 = 4;	
		MCUCR |= (1 << ISC00);		
	}
}	
	

