//--------------------------------------------------------------------------------------------------
//	PROGRAM : PSD regulator main source
//	VERSION : 1.0
//	AUTHOR  : Bc. Pazour Zbyněk
//	DATE	: 07.09.2009
//--------------------------------------------------------------------------------------------------




#include <avr/io.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <avr/iom32.h>			// Definice hlavičkových souborů
#include <avr/sfr_defs.h>
#include <avr/interrupt.h>		// Preruseni pro casovac
#include "typedefs.h"			// Hlavičkový soubor pro přetypování proměnných
#include <avr/eeprom.h>			// Hlavičkový soubor pro použití vnitřní eeprom
#include <util/twi.h>			// Hlavičkový soubor pro I2C komunikaci
#include "lcd_h.h"				// Hlavickovy soubor pro LCD
#include "lcd_c.c"				// Knihovna pro praci s LCD
#include "delay.c"				// Knihovna pro zpoždění
#define F_OSC 16000000			// Frekvence krystalu
#include "i2c.h"				// Hlavičkový soubor komunikace I2C
#include "i2c.c"				// Knihovna pro I2c komunikaci
#include <avr/wdt.h>			// Hlavickovy soubor pro watchdog
#include "menu.h"				// Hlavičkový soubor pro zobrazení MENU
#include "menu.c"				// Knihovna pro zobrazení MENU
#include "uart.h"				// Hlavičkový soubor pro zobrazení MENU
#include "uart.c"				// Knihovna pro zobrazení MENU

				 
volatile		 uint16_t 	 teplota_A,teplota_B,zadana,omezeni_rozbehu;
volatile		 uint16_t     teplota,tep1,tep2,tep3,teplota1,teplota1_prumer,teplota2,teplota2_prumer;
static			 uint16_t	 perioda_m,perioda_s,perioda_pwm,perioda_vzorkovani;
volatile		 int32_t	 q0,q1,q2;
volatile		 int16_t     e_0,e_1,e_2;
volatile	   	 int16_t	 ukt,teplota_desetiny,eepromco;
volatile		 int16_t	 smyc=0,zasah=0;
volatile		 uint8_t	 uart_zasah[4],uart_tepA[6],uart_tepB[6],uart_e[4],uart_u[6],uart_u1[6];
volatile		 uint16_t    ukt2,del=1000;

#define	OUT_ON	PORTD &=~_BV(PD5)	// Jako výstup regulátoru je použit port PD5
#define	OUT_OFF	PORTD |=_BV(PD5)
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


#ifndef F_CPU
#define F_CPU 16000000UL
#endif	
	
#define UART_BAUD_RATE      9600



//************************************************************************************************************
///////***	UKLÁDÁNÍ HODNOT NA EXTERNÍ EEPROM	****
//************************************************************************************************************


//***	Při prvním uložení se ukládá i čas a datum	***

void zapis_firstEEPROM (void)
{
u8  tepA,tepB;									// Definice pomocných proměnných

	tepA=teplota_A/10;							// Jednotky teploty A
	tepB=teplota_B/10;							// Jednotky teploty B
	tepA=(teplota_A-(tepA*10));					
	tepB=(teplota_B-(tepB*10));
	teplota_desetiny=((tepA<<4)|(tepB&0x0F));	// Nižší čtyři bity obsahují desetiny teploty B, vyšší desetiny teploty A  

	eeadr=eeprom_read_word(alok_eeadr);         // Načtení poslední uložené adresy
	nacti_RTC();								// Načtení obvodu I2C

	twi_start();								// Inicializační sekvence komunikace s externí eeprom
	hotovo();
	kontrola(TW_START);

	vysli(0xAA); 								// Adresa paměti eeprom pro zápis je 0xAA -hexadecimálně
	hotovo();
	kontrola(TW_MT_SLA_ACK);

	vysli(eeadr>>8);
	hotovo();
	kontrola(TW_MT_DATA_ACK);	

	vysli(eeadr&0xff);
	hotovo();
	kontrola(TW_MT_DATA_ACK);

	vysli(0xFF);								// K určení výpadku elektrické energie slouží první řádek jenž obsahuje 0xFF
	hotovo();
	kontrola(TW_MT_DATA_ACK);	
	
	vysli(citac);								// Perioda ukládání
	hotovo();
	kontrola(TW_MT_DATA_ACK);

	vysli(year);								// Uložení roku
	hotovo();
	kontrola(TW_MT_DATA_ACK);

	vysli(month);								// Uložení měsíce
	hotovo();
	kontrola(TW_MT_DATA_ACK);

	vysli(day);									// Uložení dnu
	hotovo();
	kontrola(TW_MT_DATA_ACK);

	vysli(hour);								// Uložení hodiny
	hotovo();
	kontrola(TW_MT_DATA_ACK);
	
	vysli(min);									// Uložení minuty
	hotovo();
	kontrola(TW_MT_DATA_ACK);

	vysli(sec);									// Uložení sekundy
	hotovo();
	kontrola(TW_MT_DATA_ACK);

	vysli(teplota_A/10);						// Uložení desítek teploty A
	hotovo();
	kontrola(TW_MT_DATA_ACK);
	
	vysli(teplota_B/10);						// Uložení desítek teploty B
	hotovo();
	kontrola(TW_MT_DATA_ACK);
	
	vysli(teplota_desetiny);					// Uložení desetin obou teplot
	hotovo();
	kontrola(TW_MT_DATA_ACK);

	twi_stop();	

eeadr+=11;
eeprom_write_word(alok_eeadr,eeadr);			// Uložení adresy paměti na kterou se má příště ukládat
}



//***	Při následujících uložení se ukládá pouze teplota	***

void zapis_nextEEPROM (void)
{
	u8 tepA,tepB;


	tepA=teplota_A/10;
	tepB=teplota_B/10;
	tepA=(teplota_A-(tepA*10));
	tepB=(teplota_B-(tepB*10));
	teplota_desetiny=((tepA<<4)|(tepB&0x0F));

	eeadr=eeprom_read_word(alok_eeadr);

	twi_start();
	hotovo();
	kontrola(TW_START);

	vysli(0xAA);
	hotovo();
	kontrola(TW_MT_SLA_ACK);

	vysli(eeadr>>8);
	hotovo();
	kontrola(TW_MT_DATA_ACK);	

	vysli(eeadr&0xff);
	hotovo();
	kontrola(TW_MT_DATA_ACK);

	vysli(teplota_A/10);
	hotovo();
	kontrola(TW_MT_DATA_ACK);
	
	vysli(teplota_B/10);
	hotovo();
	kontrola(TW_MT_DATA_ACK);
	
	vysli(teplota_desetiny);
	hotovo();
	kontrola(TW_MT_DATA_ACK);
	
	twi_stop();	
	
eeadr+=3;
eeprom_write_word(alok_eeadr,eeadr);					//	Uložení následující pozice ukládání
}



//************************************************************************************************************
///////***	NAVZORKOVANI TEPLOT	****
//************************************************************************************************************

void vzorkovani (void)
{
switch (vyber_cidel)
	{
//***	VYBER CIDEL PT100	***

		
		case 0:
		{
		ADMUX  =0xC0;				//nastavení AD převodníku na AD0
		ADCSRA |= (1<<ADSC);				//spusteni AD prevodu 
		loop_until_bit_is_set(ADCSRA,ADIF);	//cekam dokud prevod neskonci
		teplota1 = ADCW;					//Hodnota převodu uložena do teplotaPT1 (10 bitově)	
		ADCSRA &=~ (1<<ADIF);				//Vynulování příznaku dokončení AD převodu	
		cidlo=0;
		teplota1+=kalibrace1;
		teplota1_prumer=((teplota1_prumer+teplota1)/2);
		teplota=teplota1_prumer;
		vypocetPT ();
		

///////////////////////////////////////////////////////////////////////////

	
		ADMUX  = 0xC1;				//nastaveni AD prevodniku na AD1
		ADCSRA |= (1<<ADSC);				//spusteni AD prevodu 
		loop_until_bit_is_set(ADCSRA,ADIF);	//cekam dokud prevod neskonci
		teplota2 = ADCW;					//Hodnota převodu uložena do teplotaPT1 (10 bitově)
		ADCSRA &=~ (1<<ADIF);
		cidlo=1;
		teplota2+=kalibrace2;
		teplota2_prumer=((teplota2_prumer+teplota2)/2);
		teplota=teplota2_prumer;
		vypocetPT ();
		}break;


//***	VÝBĚR ČIDEL KTY 81/210	***
		
		case 1:
		{
		ADMUX = 0xC2;
		ADCSRA |= (1<<ADSC);				//spusteni AD prevodu 
		loop_until_bit_is_set(ADCSRA,ADIF);	//cekam dokud prevod neskonci
		teplota1 = ADCW;
		ADCSRA &=~ (1<<ADIF);	
		cidlo=0;
		teplota1+=kalibrace1;
		teplota1_prumer=((teplota1_prumer+teplota1)/2);
		teplota=teplota1_prumer;
		vypocetKT ();	

//////////////////////////////////////////////////////////////////////////

		ADMUX = 0xC3;
		ADCSRA |= (1<<ADSC);				//spusteni AD prevodu 
		loop_until_bit_is_set(ADCSRA,ADIF);	//cekam dokud prevod neskonci
		teplota2 = ADCW;
		ADCSRA &=~ (1<<ADIF);
		cidlo=1;
		teplota2+=kalibrace2;
		teplota2_prumer=((teplota2_prumer+teplota2)/2);
		teplota=teplota2_prumer;
		vypocetKT ();
		}break;
	}


}
//************************************************************************************************************
///////***	PŘEPOČET DEKADICKÉ HODNOTY AD PŘEVODNÍKU NA TEPLOTU VE °C PRO ČIDLA PT100	****
//************************************************************************************************************


void vypocetPT (void)
{

if (teplota<5) teplota=0;							// Osetreni nulove teploty
		tep1=((17*teplota)-95);						// Konverze cisla na teplotu teplota=0,171x - 0,957
		tep2=(((1*teplota)-7)/10);					// z důvodů používání celých čísel rozděleno
		tep3=(tep1+tep2);
		tep1=tep3/100;
		tep3=(tep3-(tep1*100));
		tep2=tep3/10;	
			if (tep3>50)							// Algoritmus pro zaokrouhleni na desetiny stupně
	 		{
	 		if (tep3>89)
				{
				tep1++;
				tep2=0;
				}
			else tep2++;
			}
if (perioda_s==per)preved_teplotu ();							// převod teploty na vhodnou formu pro zobrazení
}

//************************************************************************************************************
///////***	PŘEPOČET DEKADICKÉ HODNOTY AD PŘEVODNÍKU NA TEPLOTU VE °C PRO ČIDLA KTY81/210	****
//************************************************************************************************************

void vypocetKT (void)
{
uint8_t rovnice;	


if (teplota<5) teplota=0;							// Osetreni nulove teploty
	if (teplota<=199) rovnice=0;					// Pro linearizaci závislosti teplot čidel KTY
	if (teplota>199) rovnice=1;						// byly zvoleny čtyři rovnice, jenž se začínají
	if (teplota>=449) rovnice=2;					// požívat od určité teploty dané z grafu
	if (teplota>=774) rovnice=3;					//

		switch (rovnice)
			{
			case 0:
				{
				tep1=((18*teplota)+20);				// Konverze cisla na teplotu teplota=0,185x + 0,207
				tep2=(((5*teplota)+7)/10);			// z důvodů používání celých čísel rozděleno
				tep3=(tep1+tep2);
				tep1=tep3/100;
				tep3=(tep3-(tep1*100));
				tep2=tep3/10;	
					if (tep3>50)					// Algoritmus pro zaokrouhleni na desetiny stupně
			 		{
			 		if (tep3>89)
						{
						tep1++;
						tep2=0;
						}
					else tep2++;
					}
				}break;

			case 1:
				{
				tep1=((16*teplota)+486);			// Konverze cisla na teplotu teplota=0,161x + 4,866
				tep2=(((1*teplota)+6)/10);			// z důvodů používání celých čísel rozděleno
				tep3=(tep1+tep2);
				tep1=tep3/100;
				tep3=(tep3-(tep1*100));
				tep2=tep3/10;	
					if (tep3>50)					// Algoritmus pro zaokrouhleni na desetiny stupně
			 		{
			 		if (tep3>89)
						{
						tep1++;
						tep2=0;
						}
					else tep2++;
					}
				}break;

			case 2:
				{
				tep1=((14*teplota)+1190);			// Konverze cisla na teplotu teplota=0,145x + 11,90
				tep2=((5*teplota)/10);				// z důvodů používání celých čísel rozděleno
				tep3=(tep1+tep2);
				tep1=tep3/100;
				tep3=(tep3-(tep1*100));
				tep2=tep3/10;	
					if (tep3>50)					// Algoritmus pro zaokrouhleni na desetiny stupně
			 		{
			 		if (tep3>89)
						{
						tep1++;
						tep2=0;
						}
					else tep2++;
					}
				}break;

			case 3:
				{
				tep3=((20*teplota)-3133);			// Konverze cisla na teplotu teplota=0,200x + 31,33
				tep1=tep3/100;						// z důvodů používání celých čísel rozděleno
				tep3=(tep3-(tep1*100));
				tep2=tep3/10;	
					if (tep3>50)					// Algoritmus pro zaokrouhleni na desetiny stupně
			 		{
			 		if (tep3>89)
						{
						tep1++;
						tep2=0;
						}
					else tep2++;
					}
				}break;	


			}//switch
if (perioda_s==per)preved_teplotu ();							// převod teploty na vhodnou formu pro zobrazení
}
//************************************************************************************************************
///////***	PŘEVOD TEPLOTY NA DANÉ POUŽITELNÉ TVARY PRO ZOBRAZENÍ NA LCD A K PRO VÝPOČET PSD	****
//************************************************************************************************************

void preved_teplotu (void)
{
	if (cidlo==0)
	{
	teplota_A=((tep1*10)+tep2);						// teplota ve tvaru xxy (reálné celé číslo)
	sprintf (teppt1,"%3d.%1d",tep1,tep2);				// teplota ve tvaru xx.y °C Výsledná
//	sprintf (uart_tepA,"%3d,%1d|",tep1,tep2);
	}

	else
	{
	teplota_B=((tep1*10)+tep2);						// teplota ve tvaru xxy (reálné celé číslo)
	sprintf (teppt2,"%3d.%1d",tep1,tep2);				// teplota ve tvaru xx.y °C Výsledná
//	sprintf (uart_tepB,"%3d,%1d|",tep1,tep2);
	}
}



//************************************************************************************************************
///////***	DEFINICE TLACITEK	****
//************************************************************************************************************


void displej (void)
{
	switch (zobrazeni)
	{
		case 0:							// Volba zobrazení hodin a data
		{
		nacti_RTC();					// Načtu čas a datum
		zobraz_cas();					// zobrazím ho na displeji
		}break;
	
		case 1:							// Volba zobrazení teplot
		{
		lcd_gotoxy(0,0);
		lcd_puts("T1-");
		lcd_puts(teppt1);				// Na první řádek displeje zobrazím teplotu z čidla 1
		lcd_gotoxy (0,1);
		lcd_puts("T2-");
		lcd_puts(teppt2);				// Na druhý řádek displeje zobrazím teplotu z čidla 2
		}break;
		
		case 2:							// Volba úsporného režimu
		{
		DIS_OFF;						// Vypnu displej
		}
	}
}

//************************************************************************************************************
///////***	VYPOCET KONSTANT REGULATORU PSD	****
//************************************************************************************************************

void vypocet_konstPSD (void)
{
	uint16_t a,b;


	a= ((Td*100)/per);	
	b= ((per*100)/(2*Ti));

	q0=(Kr)*(100+(a+b));
	q1=(((-1)*(Kr))*(100+(2*a)-b));
	q2=((Kr)*(a));

	omezeni_rozbehu=(1000/per);						//Minimalni doba rozbehu cerpadla je rovna 1 sekunde
	if (omezeni_rozbehu<=10)omezeni_rozbehu=1;		// Zde zjistuji jak moc velkz zasah teto hodnote odpovida v zavislosti na vzorkovaci periode
	else omezeni_rozbehu=omezeni_rozbehu/10;

}

//************************************************************************************************************
///////***	ZJISTENI STAVU PODSVICENI A JEHO REALIZACE	****
//************************************************************************************************************

void init_LED (void)
{
if (led==0) SVIT_ON;
else SVIT_OFF;

}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
void uloz_teploty (void)
{
		
		wdt_disable();
		if (eepromco==0) 										//prvni ulozeni teplot
			{
			zapis_firstEEPROM();
			eepromco=1;
			}
		else
			{
			zapis_nextEEPROM();									//nasledujici ulozeni teplot
			} 
		wdt_enable(WDTO_1S);
		perioda_s=0;
		perioda_pwm=0;
		sei();

}

//************************************************************************************************************
///////***	OBSLUHA PRERUSENI	****
//************************************************************************************************************

//---------------------přerušení od RTC pro uložení teplot----------------------------------------------------

ISR (INT0_vect)
{
cli();
nul_citac();
uloz_teploty();
sei();
}
//-------------------- přerušení od generátoru vzorkovací periody---------------------------------------------
ISR (TIMER1_COMPA_vect)
{
perioda_pwm++;
perioda_m++;
if (perioda_m==10)
	{
	perioda_s++;
	perioda_m=0;
	}
}
//************************************************************************************************************
///////***	INICIALIZACE MIKROPROCESORU	****
//************************************************************************************************************

void init_CPU (void)
{
DDRB = 0x00;													 // portB 0-3 vstupní
DDRC = 0xFF;													 // portC vystupni
PORTC = 0x03;													 // zapnut pull-up rezistor pro I2C komunikaci
DDRD = 0b11100000;
DDRA = 0x00;
ADCSRA |= (1<<ADEN)|(1<<ADSC)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);	 // Zapnutí A/D převodníku, Předdělička 128												 // Komparační hodnota 156 ==10ms
MCUCR  |=(1<<ISC10); 											 // Vstup reaguje na náběžnou hranu 
TCCR1A |=0x00;													 // Nastaveni citace/casovace1
TCCR1B |=0x0B;													 // predelicka 64
TIMSK  |=0x10;													 // Preruseni pri shode s komparacnim registrem OCR1A
GICR |= (1<<INT0);											     // Povolení přerušení na vstupu INT0 (perioda ukládání dat na eeprom)
OCR1A=25000;											    	 // Komparator nastaven na 10 milisekund

}

//************************************************************************************************************
///////***	HLAVNI FUBNKCE	****
//************************************************************************************************************

int main (void)
{
eepromco=0;
init_CPU();														// Inicializace vstupů,výstupů,AD převodníku a přerušení
OUT_OFF;
lcd_init();														// Inicializace LCD displeje
lcd_clrscr();													// mazu displej
i2c_init();														// Inicializace I2C
init_RTC();														// Inicializace obvodu RTC
nacti_par_pid();												// Načtení konstant regulátoru z vnitřní EEPROM
vypocet_konstPSD();												// Výpočet konstant PSD regulátoru
init_LED();														// Inicializace podsvícení LCD displeje
//uart_init( UART_BAUD_SELECT(UART_BAUD_RATE,F_CPU) );
sei();															// Povolení globálního přerušení
wdt_enable(WDTO_500MS);											// Zapnu watchdog s periodou 500ms

while (1)
{
 	
	if (teplota_A<1200)
		{
		if (teplota_A<1200)
			{
			wdt_reset();	
			}
		}														// Vynuluji watchdog
//////////////////////////////////////////////////////////////////	

	if (menu())													// Pokud bylo zmáčknuto tlačítko MENU
		{
		cli ();
		OUT_OFF;
		delay_ms(100);	
		dispmenu();												// Nabídka MENU
		vypocet_konstPSD();										// V MENU mohlo dojít ke změně parametrů PSD,Proto znovu vypočtu konstanty PSD regulátoru
		perioda_s=0;
		perioda_vzorkovani=0;
		perioda_pwm=0;
		sei();
		}	


	if (perioda_s==perioda_vzorkovani)
		{
		perioda_vzorkovani++;
		vzorkovani();
		}

	
							
//////////////////////////////////////////////////////////////////

if ((teplota_A+(Dif/2))>teplota_B)
{

	if (perioda_s==per)											// Při dosažení vzorkovací periody
		{
		perioda_s=0;
		perioda_vzorkovani=0;
		perioda_pwm=0;	
		e_0=e_1;												// Minulou vypočítanou odchylku uložím do předminulé
		e_1=e_2;
	    e_2=((((teplota_A/10)-(Dif/10))-teplota_B/10));						// Vypočtu aktuální odchylku
		
		ukt=(((q0*e_2)+(q1*e_1)+(q2*e_0)));
		ukt/=100;
		ukt/=10;

		if (e_2>0)
		{
		if ((e_1-e_2)>=2)ukt=0;
		}

		zasah=(zasah+ukt);										// Zjistím velikost zásahu zasah=ukt(t-1)+ukt(t)
	
		if (zasah>=100) zasah=100;
		if (zasah<=0) zasah=0;
	
		
	if (zasah>omezeni_rozbehu)
		{
		if ((teplota_B+Dif)<teplota_A)
			{
			smyc=0;
			if (perioda_pwm==0) OUT_ON;								// Pri dasažení velikosti zásahu vypnu výstup
			}
		}



	if (zasah==((perioda_pwm*10)/per))OUT_OFF;
	}



	displej();													// Zjištění stavu zobrazení na LCD displeji	


}

}//while
return 0;
}






