/*********************************************************************/
/*  Digitronove hodiny                                               */
/*  Lukas Olivik                                                     */
/*  l.olivik @ centrum.cz                                            */
/*  www.ollie.xf.cz                                                  */
/*                                                                   */
/*  v0.3 - 01.06.2011                                                */
/*  podpora teplotniho cidla, uvodni animace                         */
/*********************************************************************/

#include <avr/io.h>
#include <avr/interrupt.h>

#define F_CPU 4000000UL			// interni oscilator 4 MHz kvuli I2C
#include <util/delay.h>
#include "twimaster.c"
#include "i2cmaster.h"

#define 	USE_DS18B20			// moznost volby kompilace podpory teplotniho cidla,
#ifdef USE_DS18B20				// zabira celkem asi 900 B Flash pameti
#include "myds.c"
#endif

//#define ANIMACE1				// moznost volby kompilace animace po zapnuti
#define ANIMACE2
#define DEL1		100			// zpozdeni pro uvodni animaci

///////////////// Adresy registru v obvodu RTC ////////////////////////////////

#define 	PCF8563		0xA2	// adresa PCF8563
#define 	I2C_READ	1	// PCF8563 cteni
#define  	I2C_WRITE	0	// PCF8563 zapis
#define		RTCctrl1	0x00	// kontrolni registry
#define		RTCctrl2	0x01
#define		RTCsec		0x02	// adresa sekund, BCD
#define		RTCmin		0x03	// minuty
#define		RTChour		0x04	// hodiny
#define		RTCday		0x05	// den
#define		RTCdayw		0x06	// den v tydnu
#define		RTCmonth	0x07	// mesic
#define		RTCyear		0x08	// rok


///////////////// Makra pro obsluhu HW ////////////////////////////////////////

#define anode_off PORTA &=~(0b01110000); PORTC &=~(0b00111000);
#define anode1 PORTC |=(1<<PC5);	// aktivace prislusne anody
#define anode2 PORTC |=(1<<PC3);	// prelozi se spravne jako SBI
#define anode3 PORTC |=(1<<PC4);
#define anode4 PORTA |=(1<<PA6);
#define anode5 PORTA |=(1<<PA4);
#define anode6 PORTA |=(1<<PA5);

#define digits_off PORTA |=0b00001111; // BCD kod 15, dekoder vypne vse

#define nixie_io_init DDRA |=0b01111111; DDRC |=0b00111100; anode_off; digits_off;

// nastaveni Timeru0 pro multiplexni zobrazeni
#define TIM0_init TCCR0 |=(1<<CS01)|(1<<CS00)|(1<<WGM01); TIMSK |=(1<<OCIE0); OCR0 = 63;//124;
// preddelicka 64, CTC rezim, preruseni Compare Match, vysledna frekvence preruseni 500 Hz, obnoveni cislic 83 Hz pro 4 MHz

// puvodni nastaveni - kratka perioda, obsluha preruseni zabirala zbytecne mnoho casu
//#define TIM0_init TCCR0 |=(1<<CS01); TIMSK |=(1<<TOIE0); // nastaveni Timeru0
// preddelicka 8, povolit preruseni, vysledna frekvence preruseni 1953 Hz, obnoveni cislic 325 Hz pro 4 MHz

#define dp_off		PORTC &=~(1<<PC2);	// ovladani desetinnych tecek v digitronkach
#define dp_on		PORTC |= (1<<PC2);

#define led_btn_off	DDRB &=~(0b00001111); PORTB &=~(0b00001111); // nastavit ovladaci piny na vysokou impedanci
#define scan_s3		DDRB |= (1<<PB1); PORTB |= (1<<PB1); asm("nop; \n nop; \n");	// skenovani tlacitek - 
#define scan_s2		DDRB |= (1<<PB2); PORTB |= (1<<PB2); asm("nop; \n nop; \n");	// nastavit prislusny vystup
#define scan_s1		DDRB |= (1<<PB3); PORTB |= (1<<PB3); asm("nop; \n nop; \n");	// a pockat na "ustaleni" signalu
#define led5_on		DDRB |= (1<<PB3);	// rozsvitit LED5 - znamenko minus, podminka scan_s3
#define led6_on		DDRB |= (1<<PB2);	// rozsvitit LED6 - neosazena, 		podminka scan_s1
#define led7_on		DDRB |= (1<<PB1);	// rozsvitit LED7 - neosazena, 		podminka scan_s1

#define KEY_MENU	1					// prirazeni tlacitek
#define KEY_UP		2
#define KEY_OK		3

///////////////// Konstanty ///////////////////////////////////////////////////
static const char digits[11]={
//	hodnoty jsou poskladane podle propojeni na plosnem spoji
//	  ....BCDA
	0b11110001,		// 0 //
	0b11111001,		// 1 //
	0b11110100,		// 2 //
	0b11110000,		// 3 //
	0b11111100,		// 4 //
	0b11110010,		// 5 //
	0b11111101,		// 6 //
	0b11110101,		// 7 //
	0b11110011,		// 8 //
	0b11111000,		// 9 //
	0b11111111};	// 15 - vse vypnuto

///////////////// Globalni promenne
volatile unsigned char disp[6]={10,10,10,10,10,10};		// globalni promenna pro zobrazovaci rutinu, zapsanim 10 se cislice zhasne
volatile unsigned char time[6]={10,10,10,10,10,10};		// druhe pole, pouyite jen pro uvodni animaci
volatile unsigned char dp = 0b00010100;					// desetinne tecky, MSB je LED pro znamenko minus.
volatile unsigned char key = 0;							// cislo prave stisknute klavesy
volatile unsigned char blink = 0;						// promenna udavajici ktere cislice maji blikat, platnych 6 nejnizsich bitu
volatile unsigned int  blink_cnt = 0;					// pomocna promenna, pocitadlo pro blikani
volatile unsigned char timeout = 0;						// pomocna promenna, drzeni tlacitka / timeout pri nastaveni casu
volatile unsigned char i=0, temp=0, result=0, i2cdata=0;// pomocne promenne

#ifdef USE_DS18B20
int teplota = 250, temp3 = 0;
#endif

///////////////// Funkce //////////////////////////////////////////////////////
#ifdef USE_DS18B20
void temp_disp(int teplota)
{
	dp = 0b00000100;
	if (teplota < 0) 
		{
		dp |= 128;			// ovladani LEDky - znamenko minus
		teplota = -teplota;	// odstranit znamenko
		}
	else dp &= 127;
	//if (teplota > 1000)	// zobrazeni teploty nad 99,9 se zatim neresi
	temp3 = teplota / 100;	// desitky stupnu
	disp[0] = (char)(temp3);
	teplota = teplota - (disp[0]*100);
	temp3 = teplota / 10;	// jednotky stupnu
	disp[1] = (char)(temp3);
	teplota = teplota - (disp[1]*10);
	temp3 = teplota;		// desetiny stupnu
	disp[2] = (char)(temp3);

	disp[3] = 10;			// ostatni zhasnout
	disp[4] = 10;
	disp[5] = 10;//ow_detect_presence();// zobrazeni pritomnosti cidla

}
#endif

inline void scan_key(unsigned char num)
{
if ((key == 0) || (key == num))			// testujeme jen pokud bylo stisknuto stejne tl. nebo zadne
	{
	if (PINB & 1) key = num; else key = 0;
	} 
}

void read(unsigned char reg)
{
	i2c_start_wait(PCF8563+I2C_WRITE);  // zapis do PCF8563
	i2c_write(reg);                     // adresa bunky
	i2c_rep_start(PCF8563+I2C_READ);    // cteni z PCF8563
	i2cdata = i2c_readNak();			// ulozit prectenou hodnotu do globalni promenne			
	i2c_stop();		               		// stop
};

void write(unsigned char reg)
{	
	i2c_start_wait(PCF8563+I2C_WRITE);   	  									   	
    i2c_write(reg);	        			// 	vyber adresy( hodiny minuty atd) 											   
	i2c_write(i2cdata);					// 	zapise BCD na vybranou adresu							   
	i2c_stop(); 						//	stop
};

void read_time(char *dest)				// funkce pro vycteni casu z RTC do zvoleneho pole
{
	read(RTCsec);
	dest[5]=(i2cdata & 0x0F);
	dest[4]=((i2cdata >>4)& 0b00000111);
	read(RTCmin);
	dest[3]=(i2cdata & 0x0F);
	dest[2]=((i2cdata >>4)& 0b00000111);
	read(RTChour);
	dest[1]=(i2cdata & 0x0F);
	dest[0]=((i2cdata >>4)& 0b00000011);
	blink = 0;
	dp = 0b00010100;
}

// pro kompatibilitu, toto byla puvodne funkce pro vycteni do pole disp
#define read_disp_time()	read_time(&disp[0]);

///////////////// Preruseni Timer0 - multiplexovani cislic ////////////////////
ISR(TIMER0_COMP_vect )	// pouzito preruseni na Compare Match
//ISR(TIMER0_OVF_vect)
{
static char pos;		// index prave zobrazovane pozice

digits_off;				// zhasnout vsechno
anode_off;
dp_off;
temp = (1<<pos);		// vypocet bitove masky pro soucasnou pozici
led_btn_off;

_delay_us(100);			// male zpozdeni pro odstraneni prosvitani sousednich znaku

switch (pos)			// vybrat anodu, skenovani tlacitek
	{
	case 0: 
		scan_s1;
		scan_key(1);
		if ((temp&dp)||(disp[0]<10)) anode1;	// pokud neni nic zobrazeno, nepripoji se ani anoda,
		break;									// aby neprosvitala sousedni cislice
	case 1: 
		scan_s1;
		scan_key(1);
		if ((temp&dp)||(disp[1]<10)) anode2; 
		break;
	case 2: 
		scan_s2;
		scan_key(2);
		if ((temp&dp)||(disp[2]<10)) anode3;
		break;
	case 3: 
		scan_s2;
		scan_key(2);
		if ((temp&dp)||(disp[3]<10)) anode4;
	 	break;
	case 4: 
		scan_s3;
		scan_key(3);
		if ((temp&dp)||(disp[4]<10)) anode5;
		if (dp & 128)	// pozadujeme rozsvitit znamenko minus
			led5_on; 
		break;
	case 5: 
		scan_s3;
		scan_key(3);
		if ((temp&dp)||(disp[5]<10)) anode6; 	
		if (dp & 128)	// pozadujeme rozsvitit znamenko minus
			led5_on;
		break;
	}// end switch

_delay_us(100);			// male zpozdeni pro odstraneni prosvitani sousednich znaku

if (dp & temp)	dp_on;	// rozsviceni pozadovane desetinne tecky

blink_cnt++;
if (blink_cnt > 1000) blink_cnt = 0;// perioda blikani jedna sekunda	//1952//500

if (blink & temp)		// blikat touto cislici
	{					// doba svitu umyslne delsi nez polovina
	if (blink_cnt < 700) //1200//350
		{
		PORTA &= (digits[disp[pos]]);
		}
	else 
		{				// misto uplneho zhasnuti svitit polovicnim svitem - nefunguje
		//if (blink_cnt & 1) PORTA &= (digits[disp[pos]]);
		}
	}
else{					// trvale sviceni
	PORTA &= (digits[disp[pos]]);	// rozsvitit cislici z globalni promenne
	};
pos++;							// prepnout dalsi cislici
if (pos>5) pos = 0;	
}

///////////////// Hlavni funkce ///////////////////////////////////////////////
int main (void)
{
nixie_io_init;		// nasteveni I/O pinu
TIM0_init;			// nastaveni Timeru pro multiplex digitronu
PORTC |=(1<<PC0);	// zapnout pull-upy na I2C linkach, na DPS nejsou zadne
PORTC |=(1<<PC1);
i2c_init();			// inicializace rozhrani I2C pro komunikaci s RTC obvodem


_delay_ms(50);

i2cdata = 128;
write(0x0D);		// povolit vystup 32,768 KHz z obvodu RTC, stejne nefunguje...?

_delay_ms(50);

read(RTCsec);
if (i2cdata > 127)  // hodiny nenastaveny, nastavit nulovy vychozi cas
	{
	dp |= 128;		// rozsvitit znamenko minus pro signalizaci ztraty casu
	i2cdata = 0;
	write(RTChour);
	i2cdata = 0;
	write(RTCmin);
	i2cdata = 0;
	write(RTCsec);
	}

_delay_ms(50);

sei();

/////////////////// Uvodni animace ////////////////////////////////////////////
#ifdef ANIMACE1

dp = 0;
for (i=0;i<6;i++) disp[i] = 0;
_delay_ms(200);
while(1)
{
read_time(&time[0]);
dp = 0;
if		(time[0] > disp[0]) disp[0]++;
else if (time[1] > disp[1]) disp[1]++;
else if (time[2] > disp[2]) disp[2]++;
else if (time[3] > disp[3]) disp[3]++;
else if (time[4] > disp[4]) disp[4]++;
else if (time[5] > disp[5]) disp[5]++;
else break;
_delay_ms(150);
}
#endif

#ifdef ANIMACE2

dp = 0;
disp[0] = 0;
read_time(&time[0]);
dp = 0;
while(1)
	{_delay_ms(DEL1);
	if (time[0] > disp[0]) disp[0]++;
	else break;}
disp[1] = 0;
read_time(&time[0]);
dp = 0;
while(1)
	{_delay_ms(DEL1);
	if (time[1] > disp[1]) disp[1]++;
	else break;}
disp[2] = 0;
read_time(&time[0]);
dp = 0b00000100;
while(1)
	{_delay_ms(DEL1);
	if (time[2] > disp[2]) disp[2]++;
	else break;}
disp[3] = 0;
read_time(&time[0]);
dp = 0b00000100;
while(1)
	{_delay_ms(DEL1);
	if (time[3] > disp[3]) disp[3]++;
	else break;}
disp[4] = 0;
read_time(&time[0]);
while(1)
	{_delay_ms(DEL1);
	if (time[4] > disp[4]) disp[4]++;
	else break;}
disp[5] = 0;
read_time(&time[0]);
while(1)
	{_delay_ms(DEL1);
	if (time[5] > disp[5]) disp[5]++;
	else break;}

#endif
/////////////////// Konec uvodni animace //////////////////////////////////////

while(1)
{
if (key == KEY_OK)
	{
	timeout++;
	read_disp_time();		// vycteni casu a zobrazeni
	_delay_ms(50);
	if (timeout > 20)	// tl. podrzeno alespon sekundu, chceme nastavit cas
		{
		timeout = 0;
		read_disp_time();
		disp[4] = 0; disp[5] = 0; // vynulovat sekundy
		blink = 0b00000001;		// blikat - desitky hodin
		_delay_ms(20);			//
		while (key != 0);		// pockat na pusteni tlacitka
		while ((key != KEY_OK) && (timeout < 40))	// nastaveni desitek hodin
			{					// opakujeme dokud nepotvrdime tlacitkem OK nebo nevyprsi 10 s
			if (key == KEY_UP)
				{				// zvyseni hodnoty
				timeout = 0;
				disp[0]++;
				if (disp[0] > 2) disp[0] = 0;
				}
			timeout++;
			_delay_ms(250);		// pro drzeni tl. zvysovat 4x za sekundu
			}					// konec nastaveni desitek hodin

		blink = 0b00000010;		// blikat - jednotky hodin
		if (disp[0]==2 && disp[1]>3) disp[1]=3;		// max. 23 hodin
		_delay_ms(20);			//
		while (key != 0);		// pockat na pusteni tlacitka
		while ((key != KEY_OK) && (timeout < 40))	// nastaveni jednotek hodin
			{					// opakujeme dokud nepotvrdime tlacitkem OK nebo nevyprsi 10 s
			if (key == KEY_UP)
				{				// zvyseni hodnoty
				timeout = 0;
				disp[1]++;
				if (disp[0] ==2)// nejde nastavit vic nez 23 hodin
					{
					if (disp[1]>3) disp[1] = 0; 
					}
				else{
					if (disp[1]>9) disp[1] = 0; 
					}
				}
			timeout++;
			_delay_ms(250);		// pro drzeni tl. zvysovat 4x za sekundu
			}					// konec nastaveni jednotek hodin

		blink = 0b00000100;		// blikat - desitky minut
		_delay_ms(20);			//
		while (key != 0);		// pockat na pusteni tlacitka
		while ((key != KEY_OK) && (timeout < 40))	// nastaveni desitek minut
			{					// opakujeme dokud nepotvrdime tlacitkem OK nebo nevyprsi 10 s
			if (key == KEY_UP)
				{				// zvyseni hodnoty
				timeout = 0;
				disp[2]++;
				if (disp[2] > 5) disp[2] = 0;	// max 59 minut
				}
			timeout++;
			_delay_ms(250);		// pro drzeni tl. zvysovat 4x za sekundu
			}					// konec nastaveni desitek minut

		blink = 0b00001000;		// blikat - jednotky minut
		_delay_ms(20);			//
		while (key != 0);		// pockat na pusteni tlacitka
		while ((key != KEY_OK) && (timeout < 40))	// nastaveni jednotek minut
			{					// opakujeme dokud nepotvrdime tlacitkem OK nebo nevyprsi 10 s
			if (key == KEY_UP)
				{				// zvyseni hodnoty
				timeout = 0;
				disp[3]++;
				if (disp[3] > 9) disp[3] = 0;	// max 59 minut
				}
			timeout++;
			_delay_ms(250);		// pro drzeni tl. zvysovat 4x za sekundu
			}					// konec nastaveni jednotek minut

		blink = 0b00110000;		// blikat - cele sekundy
		_delay_ms(20);			//
		while (key != 0);		// pockat na pusteni tlacitka
		while ((key != KEY_OK) && (timeout < 40))	// nastaveni jednotek minut
			{					// opakujeme dokud nepotvrdime tlacitkem OK nebo nevyprsi 10 s
			if (key == KEY_UP)
				{				// pouze vynulovat timeout, sekundy se vzdy nastavuji na 0
				timeout = 0;
				}
			timeout++;
			_delay_ms(250);		// pro drzeni tl. zvysovat 4x za sekundu
			}					// konec nastaveni jednotek minut
		
		// stisknuto OK nebo vyprsel cas
		if (timeout < 40)		// pokud nevyprsel cas
			{
			blink = 0;			// vypnout blikani
			i2cdata = ((disp[0]<<4)+disp[1]);	// slozit BCD hodnotu
			write(RTChour);
			i2cdata = ((disp[2]<<4)+disp[3]);	// slozit BCD hodnotu
			write(RTCmin);
			i2cdata = 0;
			write(RTCsec);
			blink = 0b00111111;
			_delay_ms(20);		//
			while (key != 0);	// pockat na pusteni tlacitka OK
			while ((key != KEY_OK) && (timeout < 40))
				{	// 4 sekundy blika nove nastaveny cas, muzeme preskocit tlacitkem OK
				timeout++;
				_delay_ms(100);		
				}					

			}	// konec blikani nove nastavenym casem
			blink = 0;			// vypnout blikani
		}	// end if (timeout > 20), konec nastaveni casu
	}	// end if (key == KEY_OK)

#ifdef USE_DS18B20
else if (key == KEY_MENU)	// zmereni teploty
	{
	timeout = 10; 			// zobrazeni 7,5 sekundy
	while (timeout > 0)
		{					// mereni trva 750 ms
		timeout--;
		if (key == KEY_UP) timeout = 40; // prodlouzit zobrazeni na 30 s
		if (ds_conv()!=0)  break;// zahajit mereni, jen pokud je detekovano cidlo
		_delay_ms(250);		// cekat 750 ms, moznost odejit stiskem OK
		if (key == KEY_OK) break;
		_delay_ms(250);
		if (key == KEY_OK) break;
		_delay_ms(250);
		if (key == KEY_OK) break;
		teplota = ds_read();// precist teplotu	
		temp_disp(teplota);	// zobrazit teplotu
		}
	timeout = 0;
	}						// konec mereni teploty
#endif

else						// zadne tlacitko nestisknuto, klidovy stav
	{
	timeout = 0;
	read_disp_time();		// vycteni casu a zobrazeni
	
	#ifdef USE_DS18B20		// dvakrat za minutu na okamzik ukazat teplotu
	if ((disp[4]==1 || disp[4]==4) && disp[5]==5)	
		{					
		if (ds_conv()==0)	// zahajit mereni, jen pokud je detekovano cidlo
			{
			_delay_ms(750);
			teplota = ds_read();// precist teplotu	
			temp_disp(teplota);	// zobrazit teplotu
			_delay_ms(2000);	// na dve sekundy
			}
		}
	#endif	

	_delay_ms(50);
	}
}	// end main loop

}	// end main
