;+++++++++++++++++++++++++++++++++++++++++++++++++++
;IR-REMOTE FOR AVR-RISC AT90S2313 (4MHZ)
;by Johannes Ferber July 2000
;+++++++++++++++++++++++++++++++++++++++++++++++++++
.list

.include "2313def.inc"
.device AT90S2313

;Definitions

.equ ir_input=2
.equ tast_input=3
.equ red=5
.equ yellow=0
.equ clkkeyb=6
.equ datakeyb=7
.equ clkpc=3
.equ datapc=4
.equ flash_enable=1				;Bit 0 wird maskiert
.equ flash_en=0					;für Sprungbedingung
.equ do_transmit=2				;Bit 1 wird maskiert
.equ transmit_done=1			;für Sprungbedingung
.equ learn_mode=2				;für Sprungbedingung

.def S=r0
.def ref1=r1
.def ref2=r2
.def bitcnt=r16
.def timer=r17
.def system=r18
.def command=r19
.def keyb_code_1=r20
.def keyb_code_2=r21
.def var_wait=r22
.def modus=r23
.def temp=r24
.def transmit=r25
.def watchdog_count=r26
.def byte_count=r27
.def flash=r28
.def modus_save=r29

.cseg

.org 0
	rjmp reset

.org int0addr
	rjmp ir_rec

.org int1addr
	rjmp touch

.org OC1addr
	rjmp compare_int

;.org OVF1addr
;	rjmp tim1_int

.org OVF0addr
	rjmp tim0_int




;INITIALISIERUNG
;******************
reset:
	ldi temp, low(RAMEND)		;Stack-Pointer hochsetzen
	out SPL, temp

	clr temp
	out OCR1AH, temp
	ldi temp, 180
	out OCR1AL, temp		;Lade Compare-register mit 180 (=45us)

	ldi temp, 5			;Timer0: Takt=Clock/1024
	out TCCR0, temp

	wdr
	ldi temp, 0b00001010		;Watchdog ist an und muß alle 60ms zurückgesetzt werden
	out WDTCR, temp

	ldi temp, 1<<ACD
	out ACSR, temp			;Analog Comparator aus
					;Timer 1 ist aus
					;UART ist aus

	ldi temp, 0b00000000		;alle Input
	out DDRB, temp
	ldi temp, 0b11111111		;alle Pullup
	out PORTB, temp


	ldi temp, 0b00100001		;PB0+PB5 Output, der Rest Input
	out DDRD, temp
	ldi temp, 0b11111111		;alle auf Pullup oder High
	out PORTD, temp

	ldi temp, 0b00000000		;fallende Flanke an INT0 und INT1 für Interruptrequest
	out MCUCR, temp
	ldi temp, 0b11000000		;Interrupt enable für INT0 und INT1
	out GIMSK, temp
	ldi temp, 0b01000010 		;Timer0 Overflow + Timer1 Output Compare Interrupt enable
	out TIMSK, temp

	sei				;global Interrupt enable

	clr modus			;Modus-Register gelöscht
	clr watchdog_count
;*****************


end:
	inc watchdog_count
	cpi watchdog_count, 100		;alle 100 Schleifen Watchdog-Reset
	breq clear_watchdog

	cpi timer, 2			;ca. 0,1 s nach letztem Low-Pegel wieder Aktivierung des IR-Empfangs
	breq ir_enable


;LEITUNGSEMULATION:
;****************************
clk_keyb:
	sbic  DDRB, clkkeyb		;Ist clkkeyb Ausgang?
	rjmp data_keyb			;dann Pin-Abfrage überspringen
	sbic PINB, clkkeyb		;Clock-Leitung Keyboard auf Low?
	rjmp clk_keyb_2
	clr temp			;Interrupt disable für INT0 und INT1
	out GIMSK, temp
	clr timer
	cbi PORTB, clkpc		;clkpc auf low(Tristate)
	sbi DDRB, clkpc			;clkpc als Output
data_keyb:
	sbic  DDRB, datakeyb		;Ist datakeyb Ausgang?
	rjmp clk_pc			;dann Pin-Abfrage überspringen
	sbic PINB, datakeyb		;Daten-Leitung Keyboard auf Low?
	rjmp data_keyb_2
	cbi PORTB, datapc		;datapc auf low(Tristate)
	sbi DDRB, datapc		;datapc als Output
clk_pc:
	sbic  DDRB, clkpc		;Ist clkpc Ausgang?
	rjmp data_pc			;dann Pin-Abfrage überspringen
	sbic PINB, clkpc		;Clock-Leitung PC auf Low?
	rjmp clk_pc_2
	clr temp			;Interrupt disable für INT0 und INT1
	out GIMSK, temp
	clr timer
	cbi PORTB, clkkeyb		;clkkeyb auf low(Tristate)
	sbi DDRB, clkkeyb		;clkkeyb als Output
data_pc:
	sbic  DDRB, datapc		;Ist datapc Ausgang?
	rjmp end			;dann Pin-Abfrage überspringen
	sbic PINB, datapc		;Daten-Leitung PC auf Low?
	rjmp data_pc_2
	cbi PORTB, datakeyb		;datakeyb auf low(Tristate)
	sbi DDRB, datakeyb		;datakeyb als Output


	rjmp end			;Endlosschleife auf der Stelle


clk_keyb_2:
	cbi DDRB, clkpc			;clkpc als Input(Tristate)
	sbi PORTB, clkpc		;clkpc auf High(Pullup)
	rjmp data_keyb


data_keyb_2:
	cbi DDRB, datapc		;datapc als Input(Tristate)
	sbi PORTB, datapc		;datapc auf High(Pullup)
	rjmp clk_pc

clk_pc_2:
	cbi DDRB, clkkeyb
	sbi PORTB, clkkeyb
	rjmp data_pc

data_pc_2:
	cbi DDRB, datakeyb
	sbi PORTB, datakeyb
	rjmp end
;***************************




clear_watchdog:
	wdr				;Watchdog zurücksetzen
	clr watchdog_count
	rjmp clk_keyb


ir_enable:
	ldi temp, 0b11000000		;Interrupt enable für INT0 und INT1
	out GIMSK, temp
	ldi timer, 3
	rjmp clk_keyb








;IR-EMPFANG
;++++++++++++++++++++++++
ir_rec:
	in S, SREG			;Statusregister retten

	clr watchdog_count		;Schleifen-Zähler rückgesetzt, jedoch ohne "wdr", d.h. zwischen zwei
					;Interruptroutinen müssen immer min. 100 Schleifen liegen, sonst Reset
	clr temp			;externe Interrupts deaktivieren
	out GIMSK, temp
	sei				;globalen Interrupt aktivieren (wird von Hardware deaktiviert)

	ldi temp, 1			;Timer0: Takt=Clock
	out TCCR0, temp

	clr timer
	mov modus_save, modus
	sbr modus, flash_enable		;LED-Blinken aus

start1:
	cpi timer, 17			;Startbit länger als 1,1ms?
	brsh fault			;dann Fehler
	sbis PIND, ir_input
	rjmp start1

	mov temp, timer			;halbe Dauer des Startbits wird gespeichert
	clr timer

	mov ref1, temp
	lsr ref1			;halbe Bitdauer/2 = viertel Bitdauer
	mov ref2, ref1
	add ref1, temp			;ref1 = 3/4 Bitdauer
	lsl temp
	add ref2, temp			;ref2 = 5/4 Bitdauer

start2:
	cp timer, ref1			;High-Period von Startbit2 länger als 3/4 Bitdauer
	brsh fault			;dann Fehler

	sbic PIND, ir_input
	rjmp start2

	clr timer
	ldi bitcnt, 12
	clr command
	clr system

sample:
	cp timer, ref1
	brlo sample			;Warte 3/4 Bitdauer (Low-Period Startbit2+ 1/4 Bitdauer)

	sbic PIND, ir_input		;Wenn 0, dann zu bit_is_a_0
	rjmp bit_is_a_1

bit_is_a_0:
	clc				;"0" speichern
	rol command
	rol system			;wenn "command" voll, rollen die als erstes empfangenen
					;Bits ins "system"-Register
bit_is_a_0a:
	cp timer, ref2
	brsh fault
	sbis PIND, ir_input
	rjmp bit_is_a_0a

	clr timer
	rjmp nextbit


bit_is_a_1:
	sec				;"1" speichern
	rol command
	rol system

bit_is_a_1a:
	cp timer, ref2
	brsh fault
	sbic PIND, ir_input
	rjmp bit_is_a_1a

	clr timer

nextbit:
	dec bitcnt			;wenn bitcnt>1
	brne sample			;empfange nächstes Bit
	rjmp ready


;FEHLERBEHANDLUNG
;*******************
fault:
	rjmp irrec_end
;******************


ready:
	mov temp, command
	rol temp			;in "command" befinden sich 8 Bit, davon 2 Systembits
	rol system			;diese werden ins "system"-Register gerollt
	rol temp
	rol system

	bst system, 5			;das "control"-Bit wird als Bit6 des "command"-Registers
	bld command, 6			;gespeichert

	andi command, 0b00111111
	andi system, 0b00011111



;EMPFANGSBESTÄTIGUNG
;*******
	ldi transmit, 0b00100000	;rote LED ändert ihren Zustand bei jedem Durchlauf
	in temp, PORTD
	eor temp, transmit		;transmit wird als Hilfsvariable ausgeliehen (gerade nicht benutzt)
	out PORTD, temp
;*******

	sbrs modus, learn_mode		;Programmiermodus?
	rjmp translate
	rcall learning
	rjmp irrec_end



translate:
	rcall decode
	cpi keyb_code_1, 0		;Ist überhaupt eine gültige Taste gedrückt?
	breq irrec_end
	cpi keyb_code_2, 0		;Spezialtaste oder Tastenkombination? (wenn ja, dann command ungleich 0)
	breq normal
	cpi keyb_code_1, $E0		;Spezialtaste?
	breq special

kombi:
	mov command, keyb_code_1
	rcall make
	mov command, keyb_code_2
	rcall make
	rcall break_pre
	mov command, keyb_code_1
	rcall break
	rcall break_pre
	mov command, keyb_code_2
	rcall break

	rjmp irrec_end

special:
	mov command, keyb_code_1
	rcall make
	mov command, keyb_code_2
	rcall make
	mov command, keyb_code_1
	rcall break
	rcall break_pre
	mov command, keyb_code_2
	rcall break

	rjmp irrec_end

normal:
	mov command, keyb_code_1
	rcall make
	rcall break_pre
	rcall break



irrec_end:
	mov modus, modus_save
	ldi temp, 5			;Timer0: Takt=Clock/1024
	out TCCR0, temp
	ser temp
	out GIFR, temp			;External Interrupt Flag löschen
	out GIMSK, temp			;External Interrupts aktivieren
	out sreg, S			;Statusregister wiederherstellen
reti
;+++++++++++++++++++++++++










;TIMER-ÜBERLAUF (alle 65ms, wenn Takt=4Mhz)
;+++++++++++++++++
tim0_int:
	in S, sreg

	inc timer
	dec var_wait

	sbrc modus, flash_en		;LED-Blinken ausgeschaltet?
	rjmp tim0_int_end		;dann LED-Blinken überspringen

	inc flash
	cpi flash, 8			;LED ändert ihren Zustand alle 8*65ms=500ms d.h. f=2Hz
	breq led_toggle
	rjmp tim0_int_end

led_toggle:
	clr flash
	ldi transmit, 0b00000001	;gelbe LED ändert ihren Zustand bei jedem Durchlauf
	in temp, PORTD
	eor temp, transmit		;transmit wird als Hilfsvariable ausgeliehen (gerade nicht benutzt)
	out PORTD, temp

tim0_int_end:
	out sreg, S
	reti
;+++++++++++++++++







;AUSGABE EINES BYTES AN DEN PC
;+++++++++++++++++++++++++++++++++++
compare_int:
	in S, sreg			;Statusregister retten, Interrupts sind inaktiv

	sbis PINB, clkpc		;Taktleitung High?
	rjmp setClk


clrClk:
	rcall clk_low
	rjmp compare_int_end

;******************
setClk:
	rcall clk_high			;dann auf High setzen
	ldi var_wait, 4			;4*5us = 20us
	rcall waitshort

	cpi bitcnt, 8			;alle 8 Bit gesendet?
	breq paritybit			;dann Paritätsbit senden
	cpi bitcnt, 9			;auch Paritätsbit gesendet?
	breq stopbit			;dann Stoppbit senden
	cpi bitcnt, 10			;auch Stoppbit gesendet?
	breq transmit_end		;dann Schluß

	ror transmit
	brcs setData

		clrData:
			rcall data_low
			rjmp nextbit1

		setData:
			rcall data_high
			rjmp nextbit1

		paritybit:				;Paritätsroutine
			brtc parity_low			;Parity-Bit low?

			rcall data_high
			rjmp nextbit1

			parity_low:			;dann springen
				rcall data_low
				rjmp nextbit1

		stopbit:
			rcall data_high
			rjmp nextbit1

		transmit_end:
			cbr modus, do_transmit		;Transmission end
			rjmp compare_int_end


	nextbit1:
		inc bitcnt				;wieder ein Bit gesendet
;*******************


compare_int_end:
	out sreg, S
	reti
;++++++++++++++++++++++++++++++++







;WAITROUTINE (DAUER ABHÄNGIG VON CLOCKVORTEILER UND var_wait)
;+++++++++++++++++++++
wait:
	wdr
	cpi var_wait, 0
	breq wait_end
	rjmp wait

wait_end:
	ret
;+++++++++++++++++++++






;WARTESCHLEIFE 20us
;+++++++++++++++++++++
waitshort:
	cpi var_wait, 0			;1 Cycle
	breq waitshort_end		;1 Cycle if false, 2 if true
	nop				;13*1 Cycle
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	nop
	dec var_wait			;1 Cycle
	rjmp waitshort			;2 Cycle

waitshort_end:
	ret

;eine Schleife dauert 18 Cycle, d.h. knapp 5us. Die fehlende Cycle haben ihren
;Grund in der zusätzlichen Verzögerung von ca. 1us, die beim Ein- und Ausspringen aus der Routine
;entsteht, sowie in den Verzögerungen im Programmablauf der aufrufenden Routinen.
;+++++++++++++++++++++






data_low:
	cbi PORTB, datapc		;datapc auf low(Tristate)
	sbi DDRB, datapc		;datapc als Output
ret



data_high:
	cbi DDRB, datapc		;datapc als Input(Tristate)
	sbi PORTB, datapc		;datapc auf High(Pullup)
ret


clk_low:
	cbi PORTB, clkpc		;clkpc auf low(Tristate)
	sbi DDRB, clkpc			;clkpc als Output
ret




clk_high:
	cbi DDRB, clkpc			;clkpc als Input(Tristate)
	sbi PORTB, clkpc		;clkpc auf High(Pullup)
ret









;PC-KOMMUNIKATION:
;++++++++++++++++++++++++++++++++++

break:
make:
	rcall find_parity
	mov transmit, command		;lade command-Byte in Sendepuffer
	rjmp start




;BREAK-CODE SENDEN
;*******************
break_pre:
	ldi transmit, $F0		;lade Breakpräfix in Sendepuffer
	set				;Parity-Bit high
;*******************




start:
	sbr modus, do_transmit
	clr bitcnt

	sbis PINB, clkpc		;Ist der PC bereit?
	rjmp send_to_keyb_end		;wenn nicht, dann beenden
	sbis PINB, datapc
	rjmp send_to_keyb_end

	rcall data_low			;Datenleitung auf Low

	ldi var_wait, 4			;4*5us = 20us
	rcall waitshort

	rcall clk_low			;Taktleitung auf Low

	ldi temp, 0b00001001		;Timer1 Start, Clear Timer on Compare Match
	out TCCR1B, temp
complete:
	sbrc modus, transmit_done	;warten, bis Übertragung zu Ende
	rjmp complete



	;COMPARE-FUNKTION NEU INITIALISIEREN FÜR KORREKTES TIMING
	;**********************
	ldi temp, 0b00000010
	out TIMSK, temp			;Interrupt Compare Match disabled
	clr temp
	out TCCR1B, temp		;Timer1 anhalten
	clr temp
	out TCNT1H, temp
	clr temp
	out TCNT1L, temp		;Timer1 auf 0 setzen
	ldi temp, 0b01000010		;Interrupt enable
	out TIMSK, temp
	;*********************


	ldi var_wait, 20		;1,3ms warten, dann nächstes Byte (für Tastaturpuffer im PC)
	rcall wait


send_to_keyb_end:
	ret
;++++++++++++++++++++++++






;PARITÄT TESTEN
;****************
find_parity:
	mov transmit, command
	clr bitcnt
	clr temp
	clc

find_parity_1:
	cpi bitcnt, 8			;Schleife 8mal durchlaufen?
	breq find_parity_2		;dann springen
	rol transmit			;Command durch C rollen
	brcc next			;bei jedem positiven Bit

	inc temp			;temp erhöhen

		next:
			inc bitcnt
			rjmp find_parity_1

find_parity_2:
	cpi temp, 1			;ist temp ungerade (1,3,5,7)?
	breq odd
	cpi temp, 3
	breq odd
	cpi temp, 5
	breq odd
	cpi temp, 7
	breq odd

	set
	ret

odd:
	clt				;dann Parität löschen
	ret
;*******************






;TASTENDRUCKBEHANDLUNGSROUTINE:
;++++++++++++++++++++++++++++++++++++++
touch:
	in S, sreg
	clr temp			;externe Interrupts deaktivieren
	out GIMSK, temp

	ldi temp, 1			;Timer0: Takt=Clock
	out TCCR0, temp

	sei				;globalen Interrupt aktivieren (wird von Hardware deaktiviert)

	ldi var_wait, 78		;5ms warten
	rcall wait

	sbic PIND, tast_input		;springe, wenn High, zum Ende
	rjmp touch_end

	ldi temp, 0b00000100		;Toggle Programmiermodus
	eor modus, temp

	sbrc modus, learn_mode		;Programmiermodus?
	rjmp set_flash

	cbr modus, flash_enable
	rjmp tremble


set_flash:
	sbr modus, flash_enable		;LED-Blinken aus
	cbi PORTD, yellow		;gelbe LED dauerhaft an



tremble:
	wdr
	sbis PIND, tast_input
	rjmp tremble

	ldi var_wait, 78		;5ms warten
	rcall wait

	sbis PIND, tast_input
	rjmp tremble

touch_end:
	ldi temp, 5			;Timer0: Takt=Clock/1024
	out TCCR0, temp
	ser temp
	out GIFR, temp			;External Interrupt Flag löschen
	ldi temp, 0b11000000
	out GIMSK, temp			;External Interrupts aktivieren
	out sreg, S
	reti
;+++++++++++++++++++++++++++++++++++++










;PROGRAMMIERMODUS;
;+++++++++++++++++++++++++++++++++++++++++++++++
learning:
	sbic EECR, EEWE			;warte, bis EEPROM bereit
	rjmp learning
	ldi byte_count, 1

wait_for_touch:
	wdr
	sbic PINB, datakeyb
	rjmp wait_for_touch

	clr keyb_code_1
	clr bitcnt


real_query:
	sbic PINB, clkkeyb
	rjmp real_query

;*************

clock_rise:				;warte auf positive Taktflanke
	sbis PINB, clkkeyb
	rjmp clock_rise

clock_fall:
	sbic PINB, clkkeyb		;warte auf negative Taktflanke
	rjmp clock_fall

	sbis PINB, datakeyb		;Ist die Datenleitung auf Low?
	rjmp bit_is_0

bit_is_1:
	sec
	ror keyb_code_1
	rjmp nextb

bit_is_0:
	clc
	ror keyb_code_1


nextb:
	inc bitcnt
	cpi bitcnt, 8
	breq parity

	clr timer
	rjmp clock_rise
;*************


parity:
	push command
	mov command, keyb_code_1	;die Paritätsroutine funktioniert nur mit command
	rcall find_parity		;erwartetes Paritätsbit ermitteln und in T speichern
	pop command

par_rise:				;warte auf positive Taktflanke
	sbis PINB, clkkeyb
	rjmp par_rise

par_fall:

	sbic PINB, clkkeyb		;warte auf negative Taktflanke
	rjmp par_fall

	sbis PINB, datakeyb		;Ist die Datenleitung, d.h. das Paritätsbit Low?
	rjmp parity_is_0

parity_is_1:
	brts rec_from_keyb_end		;wenn erwartete Parität mit empfangener übereinstimmt, dann Ende
	rjmp keyb_fault

parity_is_0:
	brtc rec_from_keyb_end


keyb_fault:
	ldi var_wait,5			;warte Stopbit ab
	rcall wait
	ret



rec_from_keyb_end:
	cpi byte_count, 2
	breq byte2

byte1:
	mov keyb_code_2, keyb_code_1	;lege eine Kopie in keyb_code_2 ab

	lsl command			;command verdoppeln
	rcall write_EEPROM		;ins EEPROM schreiben

	ldi var_wait,5			;warte Stopbit ab
	rcall wait
	inc byte_count
	rjmp wait_for_touch		;wieder zum Anfang springen und zweites Byte lesen

byte2:
	ldi transmit, 0b00100000	;rote LED ändert ihren Zustand bei jedem Durchlauf
	in temp, PORTD
	eor temp, transmit		;transmit wird als Hilfsvariable ausgeliehen
	out PORTD, temp

	cpi keyb_code_1, $F0		;ist das zweite Byte nur das Breakpräfix?
	breq clear_EEPROM		;dann schreibe 0 in die zweite EEPROM-Adresse
	cp keyb_code_1, keyb_code_2	;ist das zweite Byte dasselbe wie das erste?
	breq clear_EEPROM		;dann schreibe 0 in die zweite EEPROM-Adresse

write_byte2:
	sbic EECR, EEWE			;warte, bis EEPROM bereit
	rjmp write_byte2
	inc command
	rcall write_EEPROM

	ldi temp, 4			;Timer0: Takt=Clock/256
	out TCCR0, temp
	ldi var_wait, 12		;warte 200ms
	rcall wait
	ldi temp, 1			;Timer0: Takt=Clock
	out TCCR0, temp
ret



clear_EEPROM:
	clr keyb_code_1
	rjmp write_byte2
;++++++++++++++++++++++++++++++++++++++++++++++++







;EEPROM BESCHREIBEN (Addresse in command, Daten in command1)
;++++++++++
write_EEPROM:
	out EEAR, command		;ins EEPROM-Adreßregister schreiben
	out EEDR, keyb_code_1		;schreibe das empfangene Byte ins EEPROM-Datenregister
	cli				;global Interrupt disable
	sbi EECR, EEMWE			;master write enable
	sbi EECR, EEWE			;EEPROM write strobe
	sei				;global Interrupt enable
ret
;+++++++++







;++++++++++++++++++++
decode:
	sbic EECR, EEWE			;warte, bis bereit
	rjmp decode

	lsl command			;verdopple command
	out EEAR, command		;und beschreibe damit das Adreßregister
	sbi EECR, EERE			;EEPROM read strobe
	in keyb_code_1, EEDR		;lade keyb_code_1 mit dem Datenregister

decode_2:
	sbic EECR, EEWE			;nochmal dasselbe
	rjmp decode_2

	inc command
	out EEAR, command
	sbi EECR, EERE
	in keyb_code_2, EEDR

	ret
;++++++++++++++++++++