/* File 		berlin.c
 * Description	avr-gcc source code for the Berlin Clock replacment circuit from http://www.engcyclopedia.de
 * MCU			ATmega8L-8
 * Fuse Bits	SUT0=CKSEL3=CKSEL2=0, 4MHz internal RC oscillator, slowly rising power, BODEN=0 for brown out detector
 * Author		Stefan Huebner
 * Date			March 29, 2009
 * License 		Creative Commons by-nc-sa
 */


#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/wdt.h>
#include <stdio.h>


volatile uint8_t lastsample = 0;
volatile uint8_t valid = 0;
volatile uint8_t secondsync = 0; // damit das secondflag nur nach einem vollst. Anzeigezyklus gesetzt wird!

volatile uint8_t cycles = 0;
volatile uint8_t secondflag = 0;
volatile uint8_t mpx_column = 0;
volatile uint8_t minutes = 0;
volatile uint8_t hours = 0;

volatile uint8_t buttons = 0;
volatile uint8_t buttontemp = 0;
volatile uint8_t buttonprev = 0;
volatile uint8_t buttonloop = 0;

volatile uint8_t is_set = 0; // für Blinken des gesamten Displays nach dem Einschalten


// 500Hz-Zeitbasis für Multiplexing

ISR (SIG_OVERFLOW0) {
    uint8_t hoursrest, minutesrest;
	uint8_t sample = 0, enable = 1;

	PORTC |= 0x0F;

	// Analogkomparator samplen für 50Hz-Zeitbasis
	// Schema: 2 aufeinanderfolgende identische Abtastungen sind gültig
	
	sample = ( ACSR & 0x20 );
	if( sample == lastsample ) { // kein Flankenwechsel seit letztem Sample
		if( valid < 5 ) {
			valid ++;
		}
		if( valid == 2 ) {		// gültige Anzahl aufeinanderfolgender Samples
			if( ++cycles == 100 ) {
				secondsync = 1;
				cycles = 0;
			}
		}
	}
	else {	// Flankenwechsel seit letztem Sample
		valid = 0;
	}
    lastsample = sample;

	if( ( is_set == 0 ) && ( ( PORTD & 0x40 ) == 0 ) ) {
		enable = 0;
	}

	
	if( mpx_column == 0 ) {
		// Tastenreihe 3 einlesen
		buttontemp |= ( ( PINB & 0x20 ) << 2 );
		buttontemp |= ( ( PINB & 0x08 ) << 3 );
		// buttontemp ist jetzt h|m|0|nAL1|nAL2|set|AL1|AL2
			
		PORTD &= 0xC0;
		if( hours>=5 && enable ) PORTC &= 0x0E;
		if( hours>=10 && enable ) PORTC &= 0x0D;
		if( hours>=15 && enable ) PORTC &= 0x0B;
		if( hours>=20 && enable ) PORTC &= 0x07;
		PORTD |= 1;
		if( buttontemp == buttonprev ) {	// Schleife zum Entprellen
			if( buttonloop == 10 ) {
				buttons = buttontemp;
			}
			if( buttonloop < 255 ) {
				buttonloop ++;
			}
		}
		else {
			buttonloop = 0;
		}
		mpx_column = 1;
	}
	else if( mpx_column == 1 ) {
		hoursrest = hours;
		while( hoursrest > 4 ) { hoursrest -= 5; }
		PORTD &= 0xC0;
		if( hoursrest>=1 && enable ) PORTC &= 0x0E;
		if( hoursrest>=2 && enable ) PORTC &= 0x0D;
		if( hoursrest>=3 && enable ) PORTC &= 0x0B;
		if( hoursrest>=4 && enable ) PORTC &= 0x07;
		PORTD |= 2;
		mpx_column = 2;
	}
	else if( mpx_column == 2 ) {
		PORTD &= 0xC0;
		if( minutes>=45 && enable ) PORTC &= 0x0E;
		if( minutes>=50 && enable ) PORTC &= 0x0D;
		if( minutes>=55 && enable ) PORTC &= 0x0B;
		PORTD |= 4;
		mpx_column = 3;
	}
	else if( mpx_column == 3 ) {
		PORTD &= 0xC0;
		if( minutes>=25 && enable ) PORTC &= 0x0E;
		if( minutes>=30 && enable ) PORTC &= 0x0D;
		if( minutes>=35 && enable ) PORTC &= 0x0B;
		if( minutes>=40 && enable ) PORTC &= 0x07;
		PORTD |= 8;
		mpx_column = 4;
	}
	else if( mpx_column == 4 ) {
		// zuvor war PD3 high -> Tastenreihe 1 einlesen!
		buttonprev = buttontemp;
		buttontemp = ( ( PINB & 0x38 ) >> 3 ); // PB5..3 ausmaskieren und 3x nach rechts schieben
		PORTD &= 0xC0;
		if( minutes>=5 && enable ) PORTC &= 0x0E;
		if( minutes>=10 && enable ) PORTC &= 0x0D;
		if( minutes>=15 && enable ) PORTC &= 0x0B;
		if( minutes>=20 && enable ) PORTC &= 0x07;
		PORTD |= 16;
		mpx_column = 5;
	}
	else if( mpx_column >= 5 ) {
		// Tastenreihe 2 einlesen
		buttontemp |= ( PINB & 0x38 );
		minutesrest = minutes;
		while( minutesrest > 4 ) { minutesrest -= 5; }
		PORTD &= 0xC0;
		if( minutesrest>=1 && enable ) PORTC &= 0x0E;
		if( minutesrest>=2 && enable ) PORTC &= 0x0D;
		if( minutesrest>=3 && enable ) PORTC &= 0x0B;
		if( minutesrest>=4 && enable ) PORTC &= 0x07;
		PORTD |= 32;
		mpx_column = 0;

		if( secondsync ) {
			secondflag = 1;
			secondsync = 0;
		}
	}
}


void init_avr( void ) {
	DDRB = 0x03;
	DDRC = 0x0F;
	DDRD = 0x7F;

	TCCR0 = 0x02;
	TIMSK |= 0x01;

	OCR1A = 142;		// Auflösung Timer 1
	OCR1B = 71; 		// Output Compare Match auf Channel B auf halber Strecke
	TCCR1A = 0x10;
	TCCR1B = 0x0A;

	ACSR = 0x40;
	sei();



}


int main( void ) {
	uint8_t lastbuttons = 0;
	uint8_t adjust = 0;
	uint8_t seconds = 0;
	uint8_t time_minutes = 59;
	uint8_t time_hours = 24;	// Originalverhalten simulieren: alle Felder leuchten 
	uint8_t alm1_minutes = 0;
	uint8_t alm1_hours = 0;
	uint8_t alm2_minutes = 0;
	uint8_t alm2_hours = 0;
	uint8_t alm1_flag = 0;
	uint8_t alm2_flag = 0;
	
	init_avr();
	
	while( 1 ) {
		if( secondflag ) {
		
			if( ( adjust & 0x01 ) == 0 ) { 		// Zeit nur aktualisieren, wenn sie nicht gerade gestellt wird
				if( ( ++seconds == 60 ) && is_set ) {
					seconds = 0;

					if( time_hours == 24 && time_minutes == 59 ) {		// Sonderfall nach dem Einschalten
						time_hours = 0;
						time_minutes = 0;
					}
					else if( time_hours == 24 && time_minutes == 0 ) {	// 24:00 -> 0:01
						time_hours = 0;
						time_minutes = 1;
					}
					else {
						if( ++time_minutes == 60 ) {
							time_minutes = 0;
							time_hours ++;
						}
					}
					
					if( ( PORTB & 0x01 ) && alm1_hours == time_hours && alm1_minutes == time_minutes && adjust == 0 ) {
						alm1_flag = 1;
					}
					
					if( ( PORTB & 0x02 ) && alm2_hours == time_hours && alm2_minutes == time_minutes && adjust == 0 ) {
						alm2_flag = 1;
					}
				}
				if( adjust == 0 ) { 			// und nur anzeigen, wenn gar nichts gestellt wird
					minutes = time_minutes; 	// Anzeige aktualisieren
					hours = time_hours;
				}
				PORTD ^= 0x40;
				
				if( alm1_flag || alm2_flag ) {
					DDRB ^= 0x04;
				}
				else if( DDRB & 0x04 ) {
					DDRB &= 0xFB;
				}
			}
	
			secondflag = 0;
		}

		if( buttons != lastbuttons ) { // Änderung!

			
			if( ( adjust == 0 ) && ( lastbuttons == 0 ) ) {							
				if( buttons == 0x04 ) {	  			// Verhalten des Originals: erst muss set gehalten werden, dann kann gestellt werden
					PORTD &= 0xBF;
					adjust = 1;						// Uhrzeit stellen
					is_set = 1;
				}
				else if( buttons == 0x02 ) {		// Alarm 1 stellen
					minutes = alm1_minutes;
					hours = alm1_hours;
					PORTB |= 0x01;
					adjust = 2;
				}
				else if( buttons == 0x01 ) {		// Alarm 2 stellen
					minutes = alm2_minutes;
					hours = alm2_hours;
					PORTB |= 0x02;
					adjust = 4;
				}
				else if( buttons == 0x10 ) {
					alm1_flag = 0;
					PORTB &= 0xFE;
				}
				else if( buttons == 0x08 ) {
					alm2_flag = 0;
					PORTB &= 0xFD;
				}
			}

			
			// Verlassen des Uhrzeit-Stellmodus - Display-Speicher in Zeitspeichern übernehmen
			
			if( ( ( buttons & 0x04 ) == 0 ) && ( adjust & 0x01 ) ) { // einmal aktiv wenn Zeit-Stelltaste losgelassen wird
				time_minutes = minutes;
				time_hours = hours;
				seconds = 0;
				adjust = 0;
			}

			if( ( ( buttons & 0x02 ) == 0 ) && ( adjust & 0x02 ) ) { // einmal aktiv wenn Alarm1-Stelltaste losgelassen wird
				alm1_minutes = minutes;
				alm1_hours = hours;
				minutes = time_minutes;
				hours = time_hours;
				if( alm1_minutes == 0 && alm1_hours == 0 ) {
					PORTB &= 0xFE;
				}
				adjust = 0;
			}
			
			if( ( ( buttons & 0x01 ) == 0 ) && ( adjust & 0x04 ) ) { // einmal aktiv wenn Alarm2-Stelltaste losgelassen wird
				alm2_minutes = minutes;
				alm2_hours = hours;
				minutes = time_minutes;
				hours = time_hours;
				if( alm2_minutes == 0 && alm2_hours == 0 ) {
					PORTB &= 0xFD;
				}
				adjust = 0;
			}


			// sämtliche Zeiten werden nur im Display gestellt und beim Verlassen des Stellmodus 
			// in den jeweiligen Speicher geschrieben

			if( ( ( buttons & 0xE0 ) == 0x40 ) && ( adjust != 0 ) ) {
				if( minutes < 59 ) {
					minutes ++;
				}
				else {
					minutes = 0;
					hours ++;
				}
				if( ( hours == 24 && minutes > 0 ) || ( hours > 24 ) ) {
					hours = 0;
				}
			}

			if( ( ( buttons & 0xE0 ) == 0x80 ) && ( adjust != 0 ) ) {
				hours ++;
				if( ( hours == 24 && minutes > 0 ) || ( hours > 24 ) ) {
					hours = 0;
				}
			}

			if( ( ( buttons & 0xE0 ) == 0x20 ) && ( adjust != 0 ) ) {
				hours = 0;
				minutes = 0;
			}
			lastbuttons = buttons;
		}
	}
}