#include "tn13def.inc"
.device ATtiny13

;********************************************************************************************************
;* Rizeni zachodoveho vetraku :)
;*
;*		Popis pinu:
;*			PB3 - vstup (vypinac)
;*			PB4 - vystup (spinani vetraku)
;*			PB2 - vystup (piezo)
;*
;* © 2010 OK2GND
;* ------------------------------------------------------------------------------------------------------
;* Verze 	        : 1.0
;* Datum            : 15-04-2010
;* Autor	        : Pavel Sala, OK2GND
;* Obvod            : ATtiny13 @ 9.6MHz + nastaveny CKDIV8 -> 1.2 MHz
;********************************************************************************************************
.def	tmp			=	r16				; pomocny registr
.def	tmp1		=	r17				; pomocny registr
.def	tmp2		=	r18				; pomocny registr
.def	timer10s	=	r19				; pomocna promenna pro pocitani casu behu vetraku (10tisekundovych useku)
.def	vetrak_time	=	r20				; pro pocitani celkoveho casu behu vetraku
.def	interval	=	r21    			; hodnota delky spusteni vetraku
.def	prog_mode	=	r22				; informace o tom, jestli jsem v programovacim modu
.def	poc_zmen	=	r23				; pocet zmen stavu vypinace
.def	predch_stav	=	r24				; predchozi stavu vypinace
.def	stejny_stav	=	r25				; pocet opakovani stejneho stavu
.def	run_if_on	=	r15				; informace, jestli se vetrak ma tocit i pri zapnutem svetlu

.cseg			
.org			0x0000					; nastavim adresu na zacatek programove pameti
				rjmp reset				; reset
				rjmp ext_int0			; IRQ0 - externi preruseni
				rjmp pc_int0			; PCINT0 - zmena urovne na pinu
				rjmp tim0_ovf			; Timer0 preteceni
				rjmp ee_rdy				; EEPROM pripravena
				rjmp ana_comp			; Analogovy komparator
				rjmp tim0_compA			; Timer0 Comp A
				rjmp tim0_compB			; Timer0 Comp B
				rjmp watchdog			; Watchdog INT
				rjmp ad_conv			; ADC conv

reset:			cli						; zakazu vsechna preruseni
				ldi tmp,low(RAMEND)		; nastaveni STACK pointeru na konec pameti RAM
				out SPL,tmp

eeprom_init:	sbic EECR,EEPE				; cekam na dokonceni predchozi "eeprom-operace"
				rjmp eeprom_init;			; kdyz je bit EEPE = 0(nic neprobiha), tento radek se preskoci
				ldi tmp,0x00				; nastavim adresu pro byte v EEPROM (maximalne 64 B)
				out EEARL,tmp				; zapisu adresu do registru adres EEPROM
				ldi tmp,0x01				; rezim cteni
				out EECR,tmp				; provede cteni
				in tmp,EEDR					; hodnotu z EEPROM presunu do r16
				subi tmp,33					; od hodnoty odectu "33"
				breq eeprom_interval_read	; pokud je vysledek 0 (hodnota v eeprom je 33, prectu nastaveni vetraku)

eeprom_ok_set:	sbic EECR,EEPE			; nastavim informacni BYTE eepromky at poznam, ze hodnota je spravna
				rjmp eeprom_ok_set		
				ldi tmp,0x21			; hodnota 33
				out EEDR,tmp			; do datoveho registru
				rcall write_eeprom_byte

eeprom_interval_set:sbic EECR,EEPE			; nastavim defaultni hodnotu intervalu pro vetrak (1 minuta)
					rjmp eeprom_interval_set
					ldi tmp,0x01
					out EEARL,tmp			; adresa
					ldi tmp,0x06
					out	EEDR,tmp			; defaultni hodnota (1 minuta)
					rcall write_eeprom_byte

eeprom_run_set:		sbic EECR,EEPE			; nastavim defaultni hodnotu intervalu pro vetrak (1 minuta)
					rjmp eeprom_run_set
					ldi tmp,0x02
					out EEARL,tmp			; adresa
					ldi tmp,0x00
					out	EEDR,tmp			; defaultni hodnota (vetrak OFF, kdyz svetlo sviti)
					rcall write_eeprom_byte
				
eeprom_interval_read:	sbic EECR,EEPE			; ctu hodnotu intervalu pro vetrak
						rjmp eeprom_interval_read		
						ldi tmp,0x01			; adresa
						out EEARL,tmp
						ldi tmp,0x01			; cteni
						out EECR,tmp
						in tmp,EEDR				; prectena hodnota
						mov interval,tmp		; hodnotu si ulozim do promenne				

eeprom_run_read:		sbic EECR,EEPE			; ctu hodnotu nastaveni pro vetrak
						rjmp eeprom_run_read		
						ldi tmp,0x02			; adresa
						out EEARL,tmp
						ldi tmp,0x01			; cteni
						out EECR,tmp
						in tmp,EEDR				; prectena hodnota
						mov run_if_on,tmp		; hodnotu si ulozim do promenne				

pin_init:		ldi tmp, 0b00010111		; PB3 = detekce vypinace, PB5 = reset -> vstupni piny /ostatni vystupni
				out DDRB, tmp
; --------------------------------------------------------------------------------------------------------
start:			ldi	poc_zmen,0x00;		; reset poctu zmen stavu
				ldi predch_stav,0x00	; predchozi stav byl VYPNUTO
				ldi stejny_stav,0x00	; stejnych stavu = 0
				ldi timer10s,0x00		; pocitadlo desitek sekund
				ldi vetrak_time,0x00	; pocitadlo celkoveho casu
				ldi prog_mode,0x00		; infomrace - o vstupu do programovaciho modu
				sei						; povolim vsechna preruseni
				
				rcall info_beep			; po zapnuti obvodu mi to vypipa hodnotu

				; Stav vypinace budu kontrolovat 20x za sekundu.
				; Pokud bude zaznamenano vic nez 30stejnych stavu za sebou znamena to, vypinac je "v klidu"..
				; Pokud bude 6 zmen za sebou, prepne se do programovaciho modu.
				
run:			rcall delay_50_ms		; Pauza 50ms = 20 kontrol/s
				rcall read_input_to_tmp	; precte stav na pinu a vysledek da do promenne TMP -> 0(vyp)/1(zap)				

				; Zjisteny stav vypinace vyhodnotim				
				cp predch_stav,tmp	; porovnam predchozi stav s aktualnim
				breq stav_stejny	
stav_jiny:		ldi stejny_stav,0x00; nuluju pocet stejnych stavu
				mov predch_stav,tmp	; poznamenam si aktualni stav vypinace
				rcall update_status	; kontroluju stav vypinace a vetraku a nuluji promenne pro timer
				inc poc_zmen		; prictu dalsi zmenu
				cpi prog_mode,0x00	; jsem v programovacim modu?
				breq normal_mode	; nejsem v normalnim modu
				rjmp run

normal_mode:	cpi poc_zmen,0x06	; bylo 6 zmen nebo vic rychle za sebou? (prepnuti do programovaciho modu)
				brmi run			; jeste nebylo 6 zmen
				ldi prog_mode,0x01	; natavim programovaci mod
				ldi poc_zmen,0x00	; vynuluju pocitadlo zmen
				rcall long_beep		; pipnu at vim, ze jsem v programovacim modu
				rjmp run

stav_stejny:	cpi stejny_stav,0x1E; uz byl 30x stejny stav?
				breq ustaleny_stav
				inc stejny_stav		; stejny stav
				rjmp run			; zvednu pocitadlo a vracim se na test vypinace

ustaleny_stav:	cpi prog_mode,0x01		; jsem v programovacim modu?
				brne normal				; pokud ne, tak jdu dal
				rcall nastav_interval	; nastavi nove zvolenou dobu vetrani
				ldi prog_mode,0x00		; zrusim programovaci mod
normal:			ldi poc_zmen,0x00		; vymazu pocitadlo zmen
				cpi predch_stav,0x01	; zjistuju jaky je stav vypinace (v predch_stav mam ted ulozeny aktualni stav)
				brne switch_off			; pokud neni hodnota 0x01=zapnuto, skoc na "off" sekci
switch_on:		ldi tmp1,0x01			; vypinac je zapnuty, kontroluju jestli ma bezet vetrak, nastavim pomocnoupromennou
				cp run_if_on,tmp1		; chci aby vetrak bezel pri zapnuti?
				brne off_end			; nechci zapnout vetrak -> koncim
				cp run_if_on,tmp1		; chci aby vetrak bezel pri zapnuti?
				breq on_end				; ano chci -> zapnu ho
off_end:		rcall fan_off;			; vypnu vetrak (pro jistotu,kdyby bezel)
				rjmp normal_end;		; skacu na konec
on_end:			rcall fan_on			; zapnu vetrak
				rjmp normal_end
switch_off:		rcall check_fan_state	; zkontroluje jestli vetrak ma bezet, bezi nebo uz bezel				
normal_end:		rjmp run				; zpatky na test vypinace
; --------------------------------------------------------------------------------------------------------
read_input_to_tmp:	ldi tmp1,0x08		
					out DIDR0,tmp1		; u PINU2 vypnu digitalni cast, nebudu ji potrebovat a nebude zrat energii
					ldi tmp1,0x03		; provedu jeden prevod prevodniku, PIN2=ADC3
					out ADMUX,tmp1		; nastavim vstup prevodniku
					ldi tmp1,0b11000110	
					out ADCSRA,tmp1		; spustim jeden prevod, delicka 64
		adc_wait:	sbis ADCSRA,ADIF 	; probiha konverze
					rjmp adc_wait
					in tmp1,ADCL		; prectu hodnotu
					in tmp2,ADCH
					ldi tmp,0b00010000
					out ADCSRA,tmp		; vypnu prevodnik a vyresetuju priznak preruseni
					;	limit pro VYP/ZAP = 4.1V/03-47
					cpi tmp2,0x03
					brne set_off
					cpi tmp1,0x47
					brmi set_off
		 set_on:	ldi tmp,0x01		; ZAPNUTO
					rjmp adc_end
		 set_off:	ldi tmp,0x00		; VYPNUTO
		adc_end:	ret
					
					
update_status:	cpi tmp,0x01			; je vypinac zapnuty?
				brne update_exit		; neni -> nic dalsiho vedet nepotrebuju, koncim
				rcall timer_stop		; je zapnuty -> vypnu casovac (pro jistotu)
				ldi timer10s,0x00		; vynuluju pocitadla pro casovac
				ldi vetrak_time,0x00
				ldi tmp1,0x01
				cp run_if_on,tmp1		; testuju jestli ma vetrak bezet i pri zapnutem vypinaci
				breq update_exit		; chci, aby vetrak bezel pri zapnutem vypinaci -> koncim
				rcall fan_off;			; vypnu vetrak
update_exit:	ret

timer_start:	ldi timer10s,0x00						; vynuluju pomocne promenne
				ldi vetrak_time,0x00
				ldi tmp,1<<TOV0							
				out TIFR0,tmp							; vymazu informaci o preruseni timeru
				ldi tmp,1<<TOIE0						
				out TIMSK0,tmp							; povolim preruseni
				ldi tmp,(1<<CS02)|(0<<CS01)|(1<<CS00)	; preddelicka = 1024, 1clk = (1024/1200000) = 853.3 us
				out TCCR0B,tmp							; spusti timer
				ret

timer_stop:		ldi tmp, 0x00;
				out TCCR0B,tmp			; vypne casovac
				ret

write_eeprom_byte:	ldi tmp,0x04			; zapise do EEPROM hodnotu v datovem registru EEPROM
					out EECR,tmp			; nachystam na zapis	
					ldi tmp,0x06			
					out EECR,tmp			; zapisuju
					ret

fan_on:			in tmp,PORTB
				ori tmp,0b00010000			; vystup na 1
				out PORTB,tmp
				ret

fan_off:		in tmp,PORTB
				andi tmp,0b11101111			; vystup na 0
				out PORTB,tmp
				ret

piezo_change:	push tmp					; hodnotu ulozim do pameti RAM				
				in tmp,PORTB
				sbrc tmp,2					; je na PIEZO vystupu 0?, jestli ano tak preskoc nasledujici radek
				rjmp piezo_off
	piezo_on:	ori tmp,0b00000100			; pin na 1
				out PORTB,tmp
				rjmp piezo_end				; skocim na konec
	piezo_off:	andi tmp,0b11111011			; pin na 0
				out PORTB,tmp
	piezo_end:	pop tmp						; nactu puvodni hodnotu z RAM
				ret

long_beep:		ldi tmp, 0x00							; pouziva TMP a TMP1 pro PIPANI
				out TIMSK0, tmp							; zakaze preruseni pri preteceni casovace
				ldi tmp,1<<TOV0							
				out TIFR0,tmp							; vymazu informaci o preruseni timeru (kdyby tam jeste byla)
					ldi tmp2, 0x80						; zacinam od pulky (128)
					out TCNT0, tmp2						; zapisu hodnotu
				ldi tmp,(0<<CS02)|(1<<CS01)|(0<<CS00)	; preddelicka = 8, 1clk = (8/1200000) = 6.7 us
				out TCCR0B, tmp							; zapne casovac
				ldi tmp,0x00							; ______ vymazu pomocne promenne (potrebuju cislo 3000)
				ldi tmp1,0x00							; ___/
	timer_wait: in tmp2,TIFR0
				sbrs tmp2,TOV0							; kdyz casovac docita, preskoci dalsi radek
				rjmp timer_wait
				ldi tmp2,1<<TOV0							
				out TIFR0,tmp2							; vymazu informaci o preruseni timeru
					ldi tmp2, 0x80						; zacinam od pulky (128)
					out TCNT0, tmp2						; zapisu hodnotu
				rcall piezo_change						; zmenim uroven na piezu
				clc										; nuluji priznak prenosu
				inc tmp									; prictu 1
				cpi tmp,0x00							; bylo preteceni?
				brne low_comp							; kdyz ne jdu na porovnavani
				inc tmp1								; preteceni bylo -> prictu 1 do vyssiho byte
				; testuju jestli uz mam 3000 hodnot (3000 = 0B B8)
	low_comp:	cpi tmp,0xb8
				breq high_comp							; low hodnota je "b8", skocim na porovnani vyssiho byte
				rjmp timer_wait
	high_comp:	cpi tmp1,0x0b
				brne timer_wait							; high hodnota neni "0b", dal cekam
				ldi tmp2,1<<TOV0							
				out TIFR0,tmp2							; vymazu informaci o preruseni timeru
				ldi tmp2,0x00 
				out TCCR0B,tmp2							; vypnu casovac
				ret

nastav_interval:	cpi poc_zmen,0x00					; kontrola jestli je hodnota vetsi nez 0
					brne test_max						; hodnota je jina nez "0"					
					ldi	tmp, 0x01						; zadna zmena -> prepina rezim spusteni vetraku i pri zapnutem svetle
					add	run_if_on, tmp
					ldi tmp2, 0x02
					cp run_if_on, tmp2					; tetsuju jestli hodnota "nepretekla" (interval 0-1)
					brne write_run_info					; hodnota neni vetsi nez dva, zapisu novou hodnotu do pameti eeprom
					ldi tmp, 0x00
					mov run_if_on, tmp					; kdzy je hodnota 2, mela by to byt opet "0"
write_run_info:		sbic EECR,EEPE						; cekam na dokonceni predchozi "eeprom-operace"
					rjmp write_run_info					; kdyz je bit EEPE = 0(nic neprobiha), tento radek se preskoci
					ldi tmp,0x02						; adresa pro hodnotu vetrani
					out EEARL,tmp						; zapisu adresu do registru adres EEPROM
					out EEDR,run_if_on					; hodnota v datovem registru
					rcall write_eeprom_byte				; zapisu novou hodnotu
					rcall info_beep
					ret					
test_max:			cpi poc_zmen,0x0A					; kontrola jestli to neni vic nez "10"
					brmi interval_ok					; hodnota je v intervalu 1-10 -> ok
					ldi poc_zmen,0x0A					; nastavim maximalni cas
interval_ok:		ldi interval,0x00					; vynuluju promenneou
					ldi tmp,0x06						; budu pricitat
	add_int:		add interval,tmp					; prictu 6
					dec poc_zmen
					cpi poc_zmen,0x00					; mam jeste co pricitat?
					brne add_int
write_int_init:		sbic EECR,EEPE						; cekam na dokonceni predchozi "eeprom-operace"
					rjmp write_int_init;				; kdyz je bit EEPE = 0(nic neprobiha), tento radek se preskoci
					ldi tmp,0x01						; adresa pro hodnotu vetrani
					out EEARL,tmp						; zapisu adresu do registru adres EEPROM
					out EEDR,interval					; hodnota v datovem registru
					rcall write_eeprom_byte				; zapisu novou hodnotu
					rcall info_beep
					ret

check_fan_state:	in tmp,TCCR0B						; prectu kontrolni registr a zjistuji, jestli casovac bezi
					andi tmp,0b00000111					; vymaskuju si nastaveni preddelicky (pokud je "000" casovac nebezi)
					cpi tmp,0x00				
					breq timer_off						; hodnota = 0, casovac nebezi, skocim pryc a testuju jestli uz bezel
					rjmp fan_end						; casovac bezi, dal nic neprovadim
timer_off:			cpi vetrak_time,0x00				; zjistuju, jestli casovac neco nacital
					brne fan_end						; "vetrak_time" neni 0, casovac uz pracoval, koncim
					rcall fan_on						; zapnu vetrak
					rcall timer_start					; casovac jeste nepracoval, spustim ho					
fan_end:			ret

info_beep:			mov tmp,interval
beep:				rcall short_beep
					subi tmp,0x06						; odectu 6
					brne beep							; kdyz neni 0 tak pipnu
					ret

short_beep:		push tmp								; hodnotu tmp si schovam
				ldi tmp, 0x00
				out TIMSK0, tmp							; zakaze preruseni pri preteceni casovace
				ldi tmp,1<<TOV0							
				out TIFR0,tmp							; vymazu informaci o preruseni timeru (kdyby tam jeste byla)
					ldi tmp2, 0x80						; zacinam od pulky (128)
					out TCNT0, tmp2						; zapisu hodnotu
				ldi tmp,(0<<CS02)|(1<<CS01)|(0<<CS00)	; preddelicka = 8, 1clk = (8/1200000) = 6.7 us
				out TCCR0B, tmp							; zapne casovac
				ldi tmp,0x00							; ______ vymazu pomocne promenne (potrebuju cislo cca 3000)
				ldi tmp1,0x00							; ___/
				ldi tmp2,0x00
				mov r1,tmp2
sh_timer_wait:	in tmp2,TIFR0
				sbrs tmp2,TOV0							; kdyz casovac docita, preskoci dalsi radek
				rjmp sh_timer_wait
				ldi tmp2,1<<TOV0							
				out TIFR0,tmp2							; vymazu informaci o preruseni timeru
					ldi tmp2, 0x80						; zacinam od pulky (128)
					out TCNT0, tmp2						; zapisu hodnotu
				; testuju jestli uz mam vic jak 800 hodnot (800 = 03 20)
				mov tmp2,r1
				cpi tmp2,0x02							; uz jsem za pulkou, - nenenim uroven na piezu
				breq sh_next
				cpi tmp2,0x01							; vyssi byte uz tam driv byl
				breq sh_test_low
				cpi tmp1,0x03
				breq sh_high_ok							; horni byte odpovida pulce -> poznamenam si to
				rjmp sh_piezo_change					; hodnota je mensi, zmenim uroven na piezu
sh_high_ok:		inc r1				
sh_test_low:	cpi tmp,0x20							; dolni byte odpovida pulce
				brne sh_piezo_change
				inc r1
sh_piezo_change:rcall piezo_change						; zmenim uroven na piezu
sh_next:		inc tmp									; prictu 1
				cpi tmp,0x00							; bylo preteceni?
				brne sh_low_comp						; preteceni nebylo, jdu na test
				inc tmp1								; preteceni bylo -> prictu 1 do vyssiho byte
				; testuju jestli uz mam 1200 hodnot (1200 = 04 B0)
sh_low_comp:	cpi tmp,0xB0
				breq sh_high_comp						; low hodnota je "b8", skocim na porovnani vyssiho byte
				rjmp sh_timer_wait
sh_high_comp:	cpi tmp1,0x04
				brne sh_timer_wait						; high hodnota neni "0b", dal cekam
				ldi tmp2,1<<TOV0							
				out TIFR0,tmp2							; vymazu informaci o preruseni timeru
				ldi tmp2,0x00 
				out TCCR0B, tmp2						; vypnu casovac
				pop tmp									; hodnotu tmp nactu z pameti
				ret

; Zpozdeni cca 50ms (1clk = 1/1200000 = 833 ns -> 60 024 clk = 50 ms) ----------------------------------
;	Inicializace zpozdeni -> 1x rcall(3) + 2x ldi(1) = 5clk
;	Pruchod 1			  -> 1x dec(1) + 1x brne(2) = 3clk
;	Pruchod 0			  -> 1x dec(1) + 1x brne(2) + 1x ldi(1) = 4clk
;		ldi		1 		clk
;		dec		1 		clk
;		brne	1/2		clk
delay_50_ms:	ldi tmp1,0xff
dl_50_ms_inc0:	ldi tmp,0x4e
dl_50_ms_inc1:	dec tmp
				brne dl_50_ms_inc1		; pokud hodnota neni 0 provede skok
				dec tmp1
				brne dl_50_ms_inc0		; pokud hodnota neni 0 provede skok
				ret

; obsluhy preruseni -------------------------------------------------------------------------------------
ext_int0:		reti
pc_int0:		reti

tim0_ovf:		in r1,SREG				; ulozim stav SREG
				; fcpu = 1 200 000 Hz, prescaler = 1024
				; zmena o 1 = 1(1200000/1024) = 1/1171.8 = 853.3 us
				; cely beh casovace t = 853.3 us * 256 = 218.4 ms
				; 10 s -> 10/218.4 ms ~= 48 	  -> jedna pomocna promenna (timer10s)
				;					   6 - 60 -> druha pomocna promenna (vetrak_time)
				inc timer10s
				cpi timer10s, 0x30		; uz mam 48 prubehu?
				brne tim0_end			; jeste neni 48 prubehu -> koncim obsluhu preruseni
				ldi timer10s,0x00		; vynuluju prvni pocitadlo
				inc vetrak_time
				cp vetrak_time,interval	; uz dobehl celkovy interval vetrani?
				brne tim0_end			; jeste neubehl cas -> koncim obsluhu preruseni
				rcall fan_off			; vypnu vetrak
				rcall timer_stop		; vypnu casovac
tim0_end:		out SREG,r1				; vratim puvodni stav SREG
				reti

ee_rdy:			reti
ana_comp:		reti
tim0_compA:		reti
tim0_compB:		reti
watchdog:		reti
ad_conv:		reti
