Pfad: Home ==> Mikrobeginner ==> 12. IR-Empfang/Senden     This page in English (external): Flag EN
ATtiny24 ATtiny13

Lektion 12: IR-Empfänger und Sender


Hier mal was richtig Praktisches: mit einem IR-Empfänger Infrarot-Fernbedienungssignale erkunden und mit einem Infrarotsender auch noch selber produzieren. Um die Zahl der Programmvarianten gering zu halten kommt hier in großem Umfang das bedingte Assemblieren zum Einsatz.

12.0 Übersicht

  1. Einführung in Infrarotsignale
  2. Einführung in die bedingte Programmierung
  3. Hardware, Bauteile, Aufbau
  4. Infrarotsignale ausmessen
  5. Ein IR-Sender
  6. Ein IR-Daten-Übertragungssystem
  7. Ein IR-Schalter

12.1 Einführung in Infrarotsignale

Die kleinen formschönen Schächtelchen, die auf dem Wohnzimmertisch herumliegen und auf unseren Tastendruck harren, sind große Unbekannte. Kaum jemand weiß oder versteht genau, wie die es fertigkriegen, den Fernseher oder den CD-Spieler vom Sofa aus zu bedienen. Früher, als es diese Schächtelchen noch nicht gab, musste man die müden Knochen noch erheben und am Gerät selbst Knöpfe drücken. Die Fernbedienung hat es möglich gemacht liegen zu bleiben und von dort aus Sekundärknöpfchen zu drücken.

Infrarot sagt schon, dass die mit Wärmestrahlung arbeiten und wir die Signale gar nicht sehen können. Da die verwendeten Leuchtdioden auch nur maximal 250 mW versenden, merken wir es nicht mal auf der Hautoberfläche. Aber selbst wenn wir die sehen könnten, würden wir nur wenig sehen, denn das spielt sich in wenigen Millisekunden ab, dann ist der Fernseher auf Kanal 15 verstellt.

Damit sich das Infrarotsignal von anderen Infrarotstrahlern, die auch noch im Wohnzimmer herumstrahlen (wie z. B. ein heißer Tee), gehörig unterscheidet, wird es gehörig zerhackt. Das kann der Tee nicht und hilft, den Tee von der Fernbedienung zu unterscheiden. Das Zerhacken geht mit Fledermausfrequenzen um die 35 bis 56 kHz. Im Empfänger wird diese Frequenz aus dem analogen Messsignal herausgefiltert, so dass die Fernsteuerung auch in schwüler Sommerhitze nur den Kanal 15 erkennt. So ein Ausschnitt aus einem IR-Signal sieht folglich so aus.

IR-Signal Während die IR-LED aus ist, wird vom Empfänger (rot) kein Signal erkannt, er geht auf inaktiv (high). Die LED wird mit einer Frequenz von z. B. 40 kHz ein- und ausgeschaltet (12,5 µs an, 12,5 µs aus). Nach einer Einschwingzeit folgt das Empfängersignal, es wird aktiv (low). Bei Datensignalen steckt die Information in dem Zeitraum, über den die LED aus bleibt. Die Anzahl dieser Wartezeiten zwischen zwei aktiven LED-Signalen liegt bei 15 bis 30 für eine binäre Null und bei 50 bis 130 für eine binäre Eins.

Sind vom IR-Empfänger genügend Signale erkannt, schaltet er ein, in Sendepausen wieder aus. Die Dauer der erkannten Signalfolge, die Pausendauer und die Signaldauer, ist entscheidend für die übertragenen Signale.

Um Batteriestrom zu sparen, arbeiten die meisten Hersteller von Fernsteuerungen so: die Pausendauer, nicht die Signaldauer bestimmt über den Inhalt. Die Signaldauer ist kurz, die Pausendauer ist entweder genauso kurz (Null) oder mehr als doppelt so lang (Eins).

Warum hier immer "z. B." steht liegt daran, dass jeder Hersteller sein eigenes Süppchen kocht und es keinerlei Normierung gibt. Nicht nur sind als Frequenzen 30, 33, 36, 36,7, 38, 40 und 56 kHz in Gebrauch, auch die Zusammensetzung und Dauer der Zeichenfolgen sind höchst individuell. Das Ziel der Hersteller ist eher darauf gerichtet, dass mit der Vielfalt jede Verwechslung vermieden wird als danach, ähnliche Geräte mit einer ähnlichen Signalfolge zu steuern. Entsprechend wächst die Anzahl der Schächtelchen auf dem Wohnzimmertisch mit jeder Geräteanschaffung ins Uferlose.

Wir werden uns mit der Abwesenheit von Normen in dieser Lektion noch praktisch befassen.

Home Top IR Bedingt Hardware Messen Senden Empfang Schalter


12.2 Einführung in die bedingte Programmierung

Mit der Version 2 des ATMEL-Assemblers wurde die Möglichkeit eröffnet, mittels Direktiven dem Assembler mitzuteilen, er möge bestimmte Teile des Quellcodes in Abhängigkeit von Bedingungen assemblieren oder nicht. Dies macht dann Sinn, wenn die Grundstruktur eines Programmes die Gleiche bleibt und sich nur begrenzte ausgewählte Teile verändern.

12.2.1 Setzen von Bedingungen

Mit der folgenden Formulierung wird der nachfolgende Programmbestandteil nur dann übersetzt, wenn der Schalter Eins ist:

.equ Schalter = 1 ; definiere den Schalter
; [...]
.if Schalter == 1
	; [assembliere diesen Teil]
	.endif
; [...]

Das doppelte Gleichheitszeichen in der .IF-Bedingung kennzeichnet eine logische Entscheidung, deren Ergebnis nur wahr (Nicht Null) oder falsch (Null) sein kann. Wieder ist es wichtig zu verstehen, dass die Entscheidung nur beim Assemblieren gefällt wird und mit der Prozessortätigkeit nur so viel zu tun hat als sich der erzeugte Maschinencode ändert. Entscheidungen während des Programmlaufs werden mit bedingten Sprungbefehlen wie BRNE, BREQ, BRCC oder BRNC vorgenommen, nicht mit .IF.

Einem .IF MUSS immer auch ein .ENDIF folgen, am Ende des Quellcodes noch offene Bedingungen führen zu einem Fehler.

Falls man das Gegenteil abfragen möchte, kommen zwei Methoden in Frage:

.if Schalter != 1
	; [assembliere diesen Teil, wenn Schalter aus]
	.endif
; [...]

oder selbiges auch

.if Schalter == 0
	; [assembliere diesen Teil]
	.endif
; [...]

Die Bedingung != steht für Ungleich.

12.2.2 Wenn-Dann-Sonst-Alternativen

Falls man mit dem Schalter zwischen zwei Alternativen auswählen möchte kann das mit der .ELSE-Direktive tun. Die folgende Formulierung reagiert auf unterschiedliche Polaritäten eines Eingangspins:

.equ Schalter = 1
; [...]
.if Schalter == 1
	sbic PINB,PINB0 ; ueberspringe wenn Eingangspin Null ist
	.else
	sbis PINB,PINB0 ; ueberspringe wenn Eingangspin Eins ist
	.endif
	rjmp PinbedingungNichtEingehalten

Der Code hinter .ELSE wird dann vom Assembler assembliert, wenn die .IF-Bedingung nicht eingehalten ist. Das kann leicht unübersichtlich werden, weil man hier Assembler-Bedingungen und bedingte Sprünge vermengt. In solchen Fällen kann es hilfreich sein, in das vom Assembler erzeugte Listing zu schauen, um die Verständnisaufgabe um eine Dimension zu reduzieren.

Manchmal kann es sinnvoll sein, mit der .ELSE-Bedingung eine weitere Schalterbedingung zu setzen. Dann macht man das mit .ELIF. Allerdings entsteht dabei die Gefahr, dass keine der beiden Bedingungen gegeben ist, dann wird weder der eine noch der andere Code assembliert. Das muss der Programmierer durchdenken und entscheiden. Ein Beispiel, mit der ein Code für die beiden Typen ATtiny13 und ATtiny24 fit gemacht werden soll:

.equ cTyp == 13 ; kann 13 oder 24 sein
; [...]
.if cTyp == 13
	; Reset- und Int-Vektortabelle ATtiny13
	.elif cTyp == 24
	; Reset- und Int-Vektortabelle ATtiny24
	.endif

Wenn der User jetzt weder 13 noch 24 einträgt, dann gibt es gar keinen Reset- und Interruptvektor im erzeugten Code. Da passieren dann lustige Effekte.

Um dies zu vermeiden, gibt es bei meinem Assembler gavrasm die zusätzliche Direktive .IFDEVICE Typ. Hier kann man explizit formulieren .IFDEVICE "ATtiny13" oder .IFDEVICE ATTINY13.

12.2.3 Andere hilfreiche Direktiven

Um in dem Fall, dass im obigen Beispiel im Kopf weder 13 noch 24 angegeben ist, kann man mit

.if (cTyp != 13) && (cTyp != 24)
	.error "Falscher Typ!"
	.endif

eine Fehlermeldung auslösen. Das kann man auch verwenden, um einen Abbruch zu erzwingen, wenn eine Konstante bestimmte Werte überschreitet (z. B. wenn ein Wert größer als 255 wird).

Soll nur eine Meldung ausgegeben werden, die Assemblierung aber fortgesetzt werden, hilft ein .MESSAGE "Messagetext".

Home Top IR Bedingt Hardware Messen Senden Empfang Schalter


12.3 Hardware, Bauteile und Aufbau

12.3.1 Schaltbilder

Die Schaltbilder für die beiden Typen von IR-Empfangsmodulen sind geringfügig unterschiedlich. Außer den IR-Empfängern ändert sich an der Schaltung sonst nichts.

Schaltbild TSOP4840

Schaltbild TSOP1740

12.3.2 Bauteile: die IR-Empfänger

Die beiden IR-Empfänger sind für unterschiedliche Fernbedienungen erforderlich. TSOP4840 eignet sich eher für ältere, TSOP1740 eher für neuere Fernbedienungen. In der Praxis habe ich keine relevanten Unterschiede festgestellt.

TSOP4840, TSOP1740 Die Anschlussbelegung der beiden Typen sind unterschiedlich. Der Signalausgang ist mit dem PA0-Eingang des ATtiny24 zu verbinden. Das war es schon.

Beide sind Aktiv-Low, das heißt bei aktiv erkanntem Signal gehen die Ausgänge auf Null.

12.3.3 Aufbau

Der Anbau des Bauteils ist trivial.

12.3.4 Alternativer Aufbau

Wer dieses oder die nächsten Experimente lieber auf kompaktere Weise aufbauen möchte und die vielen Zuleitungen zur LCD fest verdrahtet sehen möchte, kann sich das Board hier als gedruckte Platine bauen. Alle Programmbeispiele dieser und der nachfolgenden Lektionen laufen darauf ohne Änderungen.

Home Top IR Bedingt Hardware Messen Senden Empfang


12.4 Messen von IR-Signalen

12.4.1 Aufgabe

Neben der reinen Neugier, was denn das schwarze Schächtelchen so umtreibt, wenn eine Taste gedrückt wird, kann es sinnvoll sein, die Signale vorhandener Fernsteuerungen zu analysieren. Z. B. wenn wir eine Fernsteuerung klonen wollen, damit wir klammheimlich der sich auf dem Sofa lümmelnden jungen Dame ihren Lieblingssender wegzappen möchten, obwohl sie über die unumstrittene Hoheit über die Fernbedienung verfügt. Also machen wir in diesem Teil ein paar Portionen Erkundungssoftware. Die hilft uns auch, den im nächsten Teil gebauten IR-Sender zu testen und zu schauen, was der so macht.

12.4.2 Startsignale

12.4.2.1 Aufgabe

Die erste Analyseaufgabe ist es, die Startsignale von Fernbedienungen sichtbar zu machen.

12.4.2.2 Beispiele für IR-Signalfolgen

Startausgabe 1 So sieht die Software unten nach dem Starten aus. Alle nachfolgende Software gibt ihre Messwerte in hexadezimalem Format aus. Wozu das gut sein soll, wird weiter unten klar. Wer Werte weiterverarbeiten will, kann einen Hex-Taschenrechner oder eine Tabellenkalkulation wie Open Office dazu verwenden, um aus den Krickeln Zahlen zu machen.

Alle Zahlen sind mit 8 malzunehmen, weil der zur Zeitmessung verwendete Teiler mit einem Vorteiler von 8 und einem Prozessortakt von 1 MHz läuft.

Die Software wartet jetzt auf Signale vom IR-Empfangsgerät. Sind 16 Pegelwechsel eingetroffen, werden diese gemessenen Zeiten dargestellt.

Signalstart TV-Fernbedienung Das Signal beginnt mit einer langen, inaktiven Pause, mit 0x1696 * 8 µs Dauer. Dann folgen 0x0232 * 8 µs eine aktive LED, für etwa den gleichen Zeitraum eine inaktive Pause. Danach folgen Pausenperioden von jeweils entweder 0xC8 * 8 µs oder 0x3F * 8 µs Dauer und dazwischen aktive Signale von 0x49 * 8 µs Dauer. Sieht ziemlich einfach aus.

Signale HDR Das hier sieht schon etwas verrückter aus, es stammt von einem HD-DVD-Recorder. Der Signalkopf hat eine lange High-Periode, eine etwas kürzere Low-Periode, eine etwa halb so lange High-Periode und eine kurze Low-Periode. Die Signalpausen (High) liegen alle zwischen 0x0029 und 0x002D, ein High-Signal liegt bei 0x009C. Die aktiven Signale liegen zwischen 0x003A und 0x0041.

Signale Kamera Das hier sind die Signale einer Kamerasteuerung. Sie ähnelt den beiden obigen Beispielen, nur die Dauern sind völlig unterschiedlich.

Tabelle der Signaldauern Das sind die dargestellten Beispieldaten in einer Tabellenkalkulation. Die Köpfe dauern mit 50, 76 bzw. 50 ms am längsten, Datenbits brauchen je nach Fernbedienung 0,7 bis 1,0 ms.

Es ist noch N angegeben. Das wären die Anzahl Timerdurchläufe, um per CTC das Ein- und Ausschalten der LED mit 40 kHz zu bewerkstelligen.

12.4.2.3 Das Programm

Prinzipiell misst das Programm den Zeitraum zwischen ansteigenden und abfallenden sowie die zwischen abfallenden und ansteigenden Flanken. Dazu wird der 16-Bit-Timer TC1 verwendet, der ausgelesen und wieder auf Null gesetzt wird.

Programmstruktur

Das Programm arbeitet in folgenden Stufen:

12.4.2.3 Das Programm

Hier ist das Programm (den Quelltext gibt es hier, kombiniert mit den LCD-Routinen).

;
; ***************************************************
; * IR-Empfaenger mit ATtiny24 und LCD, Langmessung *
; * (C)2016 by http://www.elektronic.net            *
; ***************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ---------- Programmablauf ------------
;
; Der PCINT am PB0 (IR-Sensor) liest den
; Stand des 16-Bit-Timers bei jeder Flanke
; und gibt sie in Hex aus (fallende Flanken
; zuerst, steigende Flanken danach). Die
; gemessenen Werte werden in einem SRAM-
; Puffer abgelegt.
; Ist der Schalter VonHinten Eins, werden
; die Werte im Puffer bei jeder eintreffen-
; den positiven Flanke um vier Positionen
; im SRAM-Puffer nach vorne geschoben, so
; dass immer die zuletzt eingetroffenen
; Flanken dargestellt werden. 
;
; ---------- Schalter ------------------
.equ VonHinten = 0 ; 0: von vorne nach hinten
;                  , 1: von hinten nach vorne
;
; ---------- Ports ---------------------
.equ pIrIn = PINA ; IR-Detektor-Port
.equ bIrIn = 0    ; IR-Detektor-Pin
;
; ---------- Timing --------------------
; Prozessortakt           1.000.000 Hz
; Zeit pro Takt                   1 µs
; Vorteiler TC1                   8
; Zeit pro Timer-Takt             8 µs
; TC-Stand bei 1 ms             125
;
; ---------- Register ------------------
; verwendet: R0 von LCD, Dezimalwandlung
; verwendet: R1 Dezimalwandlung
; frei: R2..R
.def rSreg = R15 ; Sicherung Status
.def rmp   = R16 ; Vielzweckregister
.def rmo   = R17 ; Vielzweckregister
.def rLine = R18 ; LCD-Zeilenzaehler
.def rLese = R19 ; LCD-Register
.def rimp  = R20 ; Interrupt-Vielzweck
.def rFlag = R21 ; Flaggenregister
   .equ bUpd = 0 ; Update-Flagge
   .equ bShf = 1 ; Shift-Rueckwaerts
; frei: R22 .. R25
; verwendet: XH:XL R27:R26 fuer Schieben
; verwendet: YH:YL R29:R28 Zeiger in SRAM
; verwendet: ZH:ZL R31:R30 in LCD-Routinen
;
; ---------- SRAM ----------------------
.DSEG ; Datensegment
.ORG 0x0060 ; Start SRAM
Buffer:
.byte 32 ; Puffer fuer Empfangsdaten
Bufferende:
Letzter:
.byte 4 ; Letzter gemessener Wert
LetzterEnde:
;
; ---------- Reset- und Interrupts -----
.CSEG ; Code-Segment
.ORG 0 ; an den Beginn
	rjmp Start ; Reset-Vektor, Init
	reti ; INT0 External Interrupt Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Interrupt Request 0
	reti ; PCINT1 Pin Change Interrupt Request 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT Timer/Counter1 Capture Event
	reti ; TIM1_COMPA Timer/Counter1 Compare Match A
	reti ; TIM1_COMPB Timer/Counter1 Compare Match B
	reti ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA Timer/Counter0 Compare Match A
	reti ; TIM0_COMPB Timer/Counter0 Compare Match B
.if VonHinten == 1 ; Von hinten einschieben
	rjmp Tc0Isr ; TIM0_OVF TC0 Overflow, Display updaten
	.else
	reti ; TIM0_OVF Timer/Counter0 Overflow
	.endif
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
;
; ---------- Interrupt Service -----
;
; PCINT Interrupt
;   wird von Flanken am IR-Sensor-Eingang ausge-
;   loest
;   Bei negativen Flanken wird der 16-Bit-Timer-
;   stand gelesen (Dauer der inaktiven Pause).
;   Wenn das MSB die Maximaldauer ueberschrei-
;   tet, wird der Zeiger Y auf den Pufferanfang
;   gestellt. Der Zaehlerstand des 16-Bit
;   Timers wird an der Zeigerposition Y (MSB)
;   und Y+1 (LSB) abgelegt.
;   Bei positiven Flanken wird die Ausgabeflag-
;   ge bUpd gesetzt. In der Betriebsart VonHin-
;   ten wird auch die Schiebeflagge gesetzt.
;   Der Zaehlerstand wird an der Zeigerposition
;   Y+2 (MSB) und Y+3 (LSB) abgelegt. Ist Von-
;   Hinten nicht Eins, wird der Pufferzeiger
;   um vier erhoeht.
;   Bei beiden Flanken wird der 16-Bit-Timer
;   auf Null gestellt.
;   
Pci0Isr:
	in rSreg,SREG ; Status sichern
	sbic pIrIn,bIrIn ; ueberspringe bei Null
	rjmp Pci0Isr1 ; Eins
	; Eingang ist Low, war vorher High
	in rimp,TCNT1L ; Low Byte zuerst
	std Y+1,rimp ; LSB in SRAM Byte 1
	in rimp,TCNT1H ; High Byte danach
	st Y,rimp ; MSB in SRAM Byte 0
.if VonHinten == 0 ; nur bei vorwaerts
	cpi rimp,0x05 ; Pause feststellen
	brcs Pci0Isr2 ; keine Pause
	ldi YH,HIGH(Buffer) ; Y auf Anfang Buffer
	ldi YL,LOW(Buffer)
	in rimp,TCNT1L ; Noch mal lesen, Low Byte zuerst
	std Y+1,rimp ; LSB in SRAM Byte 1
	in rimp,TCNT1H ; High Byte danach
	st Y,rimp ; MSB in SRAM Byte 0
	.endif
	rjmp Pci0Isr2 ; fertig
Pci0Isr1:
	; Eingang ist High, war vorher Low
	in rimp,TCNT1L ; Low Byte zuerst
	std Y+3,rimp ; LSB in SRAM Byte 3
	in rimp,TCNT1H ; High Byte danach
	std Y+2,rimp ; MSB in SRAM Byte 2
.if VonHinten == 1 ; nur bei rueckwaerts
	sbr rFlag,1<<bShf ; setze Schiebeflagge
	.else
	adiw YL,4 ; vier Bytes weiter
	cpi YL,0x60+32 ; 32 Bytes = 8 Lo/Hi-Paare
	brcs Pci0Isr2 ; noch nicht erreicht
	sbr rFlag,1<<bUpd ; Update-Flag setzen
	ldi YH,HIGH(Bufferende) ; Y auf Ende Buffer
	ldi YL,LOW(Bufferende)
	.endif
Pci0Isr2:
	ldi rimp,0 ; Neustart Zaehler
	out TCNT1H,rimp ; Zuerst MSB schreiben
	out TCNT1L,rimp ; Danach LSB schreiben
	out SREG,rSreg ; Status wieder herstellen
	reti ; fertig
;
; TC0-Overflow Interrupt Service Routine
;
; Wird beim Ueberlauf des Zaehlers TC0 nach
; 1.024 * 256 = 0,262 s ausgeloest, wenn
; keine Flanken am IR-Eingang eintreten.
; Setzt die Update-Flagge.
;
Tc0Isr:
	in rSreg,SREG ; SREG retten
	sbr rFlag,1<<bUpd ; Update-Flagge setzen
	out SREG,rSreg ; SREG wieder herstellen
	reti
;
; ---------------- Start, Init --------------
Start:
	ldi rmp,LOW(RAMEND) ; Stapel auf Ramende
	out SPL,rmp ; in Stapelzeiger
	; I/O initiieren
	; LCD-Port-Ausgaenge initiieren
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; Kontrollport-Ausgaenge
	clr rmp ; Ausgaenge aus
	out pLcdCO,rmp ; an Kontrollport
	ldi rmp,mLcdDRW ; Datenport-Ausgabemaske, Schreiben
	out pLcdDR,rmp ; auf Richtungsregister Datenausgabeport
	; LCD initiieren
	rcall LcdInit ; Init der LCD
	ldi ZH,HIGH(2*Codezeichen) ; Spezialzeichen
	ldi ZL,LOW(2*Codezeichen)
	rcall LcdChars ; an LCD
	ldi ZH,HIGH(2*LcdStart) ; Start-Text ausgeben
	ldi ZL,LOW(2*LcdStart)
	rcall LcdText
	; Startwert fuer Pufferzeiger
.if VonHinten == 1 ; rueckwaerts
	ldi YH,HIGH(Letzter) ; Hinter Pufferende
	ldi YL,LOW(Letzter)
	.else 
	ldi YH,HIGH(Buffer) ; Y auf Pufferanfang
	ldi YL,LOW(Buffer)
	.endif
	; Startwert fuer Flagge
	clr rFlag
.if VonHinten == 1 ; Nur fuer rueckwaerts
	; Timer 0 initiieren
	ldi rmp,(1<<CS02)|(1<<CS00) ; Prescaler 1024
	out TCCR0B,rmp ; Timer starten
	.endif
	; Timer 1 initiieren, freilaufend durch 8
	ldi rmp,1<<CS11 ; Prescaler durch 8
	out TCCR1B,rmp ; an Kontrollregister B
	; PCINT0 fuer IR-Rx
	ldi rmp,1<<PCINT0 ; Pin 0 Level change INTs
	out PCMSK0,rmo ; in Maske 0
	ldi rmp,1<<PCIE0 ; Interrupt PCINT0 ermoeglichen
	out GIMSK,rmp
	; Sleep Enable
	ldi rmp,1<<SE ; Sleep Mode Idle
	out MCUCR,rmp ; in MCU Kontrollregister
	; Interrupts ermoeglichen
	sei ; I-Flagge Statusregister
Schleife:
	sleep ; schlafen legen
	nop ; aufwachen
	sbrc rFlag,bShf ; Shift-Flagge auswerten
	rcall Shift ; Shift-Flagge behandeln
	sbrc rFlag,bUpd ; Update-Flagge auswerten
	rcall Update ; Flagge behandeln
	rjmp Schleife ; schlafen legen
;
; Schiebe Buffer-Inhalt rueckwaerts
;   Schiebt den Datenpuffer um vier
;   Bytes nach niedrigeren Adressen
; 
Shift:
	cbr rFlag,1<<bShf ; Loesche Flagge
	clr rmp ; TC0 ruecksetzen
	out TCNT0,rmp
	ldi rmp,1<<TOIE0 ; Interrupt einschalten
	out TIMSK0,rmp ; in Interrupt-Maske
	ldi XH,HIGH(Buffer+4) ; Bufferzeiger Herkunft
	ldi XL,LOW(Buffer+4)
	ldi ZH,HIGH(Buffer) ; Bufferzeiger Ziel
	ldi ZL,LOW(Buffer)
Shift1: ; Schiebe Puffer rueckwaerts
	ld rmp,X+ ; Lese Byte aus Herkunft
	st Z+,rmp ; schreibe Byte in Ziel
	cpi ZL,LOW(Bufferende)
	brcs Shift1
	ret
;
; Update-Flagge behandeln
;   bUpd wird gesetzt, wenn
;   a) der PCINT0 eine Flanke erkennt,
;   b) der Timer ueberlaeuft.
;   Gibt gemessene Dauer in Hex aus.
;
Update:
	cbr rFlag,1<<bUpd ; Flagge wieder abschalten
.if VonHinten == 1 ; nur bei rueckwaerts
	clr rmp ; TC0-Interrupt abschalten
	out TIMSK0,rmp ; in Interrupt-Maske
	.endif
	ldi rmp,1 ; Loesche LCD-Anzeige
	rcall LcdC4Byte ; an LCD-Kontrollport
	ldi ZH,HIGH(Buffer) ; Setze Z auf Buffer-Anfang
	ldi ZL,LOW(Buffer)
	clr rLine ; Zeilenzaehler auf Null
Update1:
.if VonHinten == 1 ; Nur bei rueckwaerts
	ldi rmp,'e' ; letztes Wertepaar
	cpi ZL,LOW(Bufferende-4)
	brcc Update2 ; 'e' kennzeichnet letzten
	.else
	ldi rmp,0x02 ; Pausenzeichen bei vorwaerts
	cpi ZL,LOW(Buffer) ; erstes Wort?
	breq Update2 ; Pausenzeichen ausgeben
	.endif
	ldi rmp,0x01 ; Low-Zeichen ausgeben
	sbrc ZL,1 ; Bit 2 Bufferadresse ist Low?
	ldi rmp,0x00 ; nein, High
Update2:
	rcall LcdD4Byte ; Spezialchar ausgeben
	ld rmp,Z+ ; Lese MSB aus Buffer
	rcall Hex2Lcd ; MSB in Hex ausgeben
	ld rmp,Z+ ; Lese LSB aus Buffer
	rcall Hex2Lcd ; LSB in Hex ausgeben
	mov rmp,ZL ; Zeilenende feststellen
	andi rmp,0x07 ; 8 Byte ausgegeben?
	brne Update1 ; nein, weiter
	inc rLine ; naechste Zeile
	cpi rLine,4 ; Ende Anzeige?
	brcc Update3 ; ja, fertig
	mov rmp,rLine ; naechste Zeile
	rcall LcdLine ; an LCD
	rjmp Update1 ; und weiter	
Update3: 
	ldi rmp,0x0C ; Cursor und Blink aus
	rjmp LcdC4Byte ; an LCD
;
; Byte in rmp in Hex an LCD
;   Eingabedaten: Register rmp
;   Ausgabe HH an LCD an aktueller Position
;
Hex2Lcd:
	push rmp ; Byte retten
	swap rmp ; oberes Nibble zuerst
	rcall HexNibble2Lcd
	pop rmp ; rmp wieder herstellen
;
; Gib Nibble in Hex auf LCD aus
HexNibble2Lcd:
	andi rmp,0x0F ; unteres Nibble maskieren
	subi rmp,-'0' ; ASCII-Null dazu addieren
	cpi rmp,'9'+1 ; A bis F?
	brcs HexNibble2Lcd1 ; nein
	subi rmp,-7 ; auf A bis F
HexNibble2Lcd1:
	rjmp LcdD4Byte ; rmp auf LCD ausgeben
;
; Starttext LCD
LcdStart:
.db "IR-Analyse ATtiny24 ",0x0D,0xFF
.db " gsc-elektronic.net ",0x0D,0xFF
.db "Ausgabe Signaldauer ",0x0D,0xFF
.db " in Hex Hi/Lo, 8 us",0xFE
;
; Spezialzeichen
Codezeichen:
.db 64,0,0,0,0,0,4,4,6,0 ; Z = 0, Low-Signal
.db 72,0,0,0,0,0,5,7,5,0 ; Z = 1, High-Signal
.db 80,0,0,0,0,0,0,0,7,0 ; Z = 2, Pausentrennung
.db 0,0 ; Ende der Tabelle
;
; LCD-Routinen einlesen
.include "Lcd4Busy.inc"
;
; Ende Quelltext
;

12.4.3 Endesignale

Um herauszufinden, ob IR-Signale am Ende der gesendeten Sequenz eine abweichende Formatierung aufweisen, müssen wir die Signale am Ende der Sequenz auswerten und anzeigen. Das erfordert einen gewissen Umbau des Programmes. Hier wurde ein anderer Weg gewählt. Mit einem Schalter im Programmkopf

; ---------- Schalter ------------------
.equ VonHinten = 0 ; 0: von vorne nach hinten
;                  , 1: von hinten nach vorne

und mit .IF VonHinten = 1 werden diejenigen Programmteile assembliert, die diese Bedingung erfüllen. Assemblieren und Brennen des Hex-Codes führt jetzt zu einer geänderten Ausführung.

Signale von hinten Das letzte Signal ist mit einem "e" gekennzeichnet. Die Reihe zeigt, dass von hinten keine geänderten Signalfolgen zu beachten sind.

12.4.4 Anzahl Signale

Eine breite Vielfalt an Varianten gibt es auch bei der Anzahl an Hi/Lo-Paaren, aus denen sich die gesendeten Signalfolgen zusammensetzen.

SignalanzahlDiese Software zählt einfach nur die Signale, sortiert sie nach High- und Low-Polarität und zählt die, die länger als 256*8 = 2048 µs sind.

12.4.4.1 Das Programm

Die Software hat einen ganz ähnlichen Aufbau. Das Programm dafür ist nachfolgend aufgelistet (zum Quellcode geht es hier, kombiniert mit den LCD-Routinen).

;
; ****************************************************
; * IR-Empfaenger mit ATtiny24 und LCD, Signalanzahl *
; * (C)2016 by http://www.elektronic.net             *
; ****************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ---------- Programmablauf ------------
;
; Beobachtet den IR-Sensor am PA0-Eingang
; und misst mit TC1 die Dauer von Signalen.
; Die Anzahl der von der Fernsteuerung ge-
; sendeter Datensets, die Anzahl an Kopf-
; signalen und die Anzahl an aktiven Signa-
; len wird gezaehlt und 0,26 Sekunden nach
; dem letzten Signal auf der LCD in Hex
; dargestellt.
;
; ---------- Einstellungen Fernsteuerung
.equ cSet     = 0x10 ; lange Pause zwischen Signalsets
;
; ---------- Ports ---------------------
.equ pIrIn = PINA ; IR-Detektor-Port
.equ bIrIn = 0    ; IR-Detektor-Pin
;
; ---------- Timing --------------------
; Prozessortakt           1.000.000 Hz
; Zeit pro Takt                   1 µs
; Vorteiler TC1                   8
; Zeit pro Timer-Takt             8 µs
;
; ---------- Register ------------------
; verwendet: R0 von LCD, Dezimalwandlung
; verwendet: R1 Dezimalwandlung
.def rHigh = R2 ; Anzahl High-Signale
.def rLow  = R3 ; Anzahl Low-Signale
.def rKopf = R4 ; Anzahl Kopf-Signale
.def rSet  = R5 ; Anzahl Signalsets
; frei: R4..R14
.def rSreg = R15 ; Sicherung Status
.def rmp   = R16 ; Vielzweckregister
.def rmo   = R17 ; Vielzweckregister
.def rLine = R18 ; LCD-Zeilenzaehler
.def rLese = R19 ; LCD-Register
.def rimp  = R20 ; Interrupt-Vielzweck
.def rFlag = R21 ; Flaggenregister
   .equ bUpd = 0 ; nach Pause ausgeben
; frei: R23 .. R29
; verwendet: ZH:ZL R31:R30 in LCD-Routinen
;
; ---------- Reset- und Interrupts -----
.CSEG ; Code-Segment
.ORG 0 ; an den Beginn
	rjmp Start ; Reset-Vektor, Init
	reti ; INT0 External Interrupt Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Interrupt Request 0
	reti ; PCINT1 Pin Change Interrupt Request 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT Timer/Counter1 Capture Event
	reti ; TIM1_COMPA Timer/Counter1 Compare Match A
	reti ; TIM1_COMPB Timer/Counter1 Compare Match B
	reti ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA Timer/Counter0 Compare Match A
	reti ; TIM0_COMPB Timer/Counter0 Compare Match B
	rjmp Tc0Isr ; TIM0_OVF TC0 Overflow, Display updaten
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
;
; ---------- Interrupt Service -----
;
; PCINT Interrupt
; wird von allen Flanken am IR-Sensor aus-
; geloest
; Stellt fest, ob es sich um eine positive
; (aktives IR-Signal beendet) oder um eine
; negative Flanke (Pausensignal) handelt.
;
; Bei negativen Flanken (Pause) wird ge-
; prueft, ob das Signal laenger als 255 us
; war. Wenn ja, wird die Anzahl erkannter
; Kopfsignale in rKopf erhoeht. War das
; Signal laenger als 2,55 ms, wird die
; Anzahl Datensets in rSet erhoeht.
;
; Bei positiven Flanken wird die Anzahl
; an aktiven Bits in rLow erhoeht.
;
; In allen Faellen werden die Zaehler-
; staende in TC0 und TC1 rueckgesetzt
; und Interrupts des Timeout-Zaehlers
; TC0 zugelassen.  
; 
Pci0Isr:
	in rSreg,SREG ; Status sichern
	sbic pIrIn,bIrIn ; ueberspringe bei Null
	rjmp Pci0Isr1
	; Eingang ist Low, war vorher High
	inc rHigh
	in rimp,TCNT1L ; Stand lesen
	in rimp,TCNT1H
	tst rimp ; MSB groesser Null
	breq Pci0Isr2
	inc rKopf ; Anzahl Kopf erhoehen
	cpi rimp,cSet ; Teste auf cSet
	brcs Pci0Isr2
	inc rSet
	rjmp Pci0Isr2
Pci0Isr1:
	inc rLow
Pci0Isr2:
	ldi rimp,0 ; Neustart Zaehler
	out TCNT1H,rimp ; Zuerst MSB schreiben
	out TCNT1L,rimp ; Danach LSB schreiben
	out TCNT0,rimp ; Zaehler ruecksetzen
	ldi rimp,1<<TOIE0 ; TimeOut starten
	out TIMSK0,rimp ; in Int-Maske
	out SREG,rSreg ; Status wieder herstellen
	reti ; fertig
;
; TC0-Overflow Interrupt Service Routine
; Wird ausgefuehrt, wenn der Timer TC0 nach
; 1.024*256 = 0,262 Sekunden ueberlaeuft.
;
; Setzt die Flagge bUpd, die zur LCD-Anzeige
; der gemessenen Signalanzahlen fuehrt.
;
Tc0Isr:
	in rSreg,SREG ; SREG retten
	sbr rFlag,1<<bUpd ; Update-Flagge setzen
	out SREG,rSreg ; SREG wieder herstellen
	reti
;
; ---------------- Start, Init --------------
Start:
	ldi rmp,LOW(RAMEND) ; Stapel auf Ramende
	out SPL,rmp ; in Stapelzeiger
	; I/O initiieren
	; LCD-Port-Ausgaenge initiieren
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; Kontrollport-Ausgaenge
	clr rmp ; Ausgaenge aus
	out pLcdCO,rmp ; an Kontrollport
	ldi rmp,mLcdDRW ; Datenport-Ausgabemaske, Schreiben
	out pLcdDR,rmp ; auf Richtungsregister Datenausgabeport
	; LCD initiieren
	rcall LcdInit ; Init der LCD
	ldi ZH,HIGH(2*Codezeichen) ; Spezialzeichen
	ldi ZL,LOW(2*Codezeichen)
	rcall LcdChars ; an LCD
	ldi ZH,HIGH(2*LcdStart) ; Start-Text ausgeben
	ldi ZL,LOW(2*LcdStart)
	rcall LcdText
	; Startwert fuer Flagge
	clr rFlag
	; Alle Inhalte entleeren
	rcall Neustart ; Neustartwerte setzen
	; Timer 0 initiieren
	ldi rmp,(1<<CS02)|(1<<CS00) ; Prescaler 1024
	out TCCR0B,rmp ; Timer starten
	; Timer 1 initiieren, freilaufend durch 8
	ldi rmp,1<<CS11 ; Prescaler durch 8
	out TCCR1B,rmp ; an Kontrollregister B
	; PCINT0 fuer IR-Rx
	ldi rmp,1<<PCINT0 ; Pin 0 Level change INTs
	out PCMSK0,rmo ; in Maske 0
	ldi rmp,1<<PCIE0 ; Interrupt PCINT0 ermoeglichen
	out GIMSK,rmp
	; Sleep Enable
	ldi rmp,1<<SE ; Sleep Mode Idle
	out MCUCR,rmp ; in MCU Kontrollregister
	; Interrupts ermoeglichen
	sei ; I-Flagge Statusregister
Schleife:
	sleep ; schlafen legen
	nop ; aufwachen
	sbrc rFlag,bUpd ; Update-Flagge auswerten
	rcall Update ; Flagge behandeln
	rjmp Schleife ; schlafen legen
;
; Update-Flagge behandeln
;   bUpd wird gesetzt, wenn der Timer TC0
;   ueberlaeuft.
;   Gibt gemessene Anzahl IR-Signale in
;   Hex auf der LCD aus.
;
Update:
	cbr rFlag,1<<bUpd ; Flagge wieder abschalten
	clr rmp ; Interrupts Timer aus
	out TCNT0,rmp ; Timer auf Null
	out TIMSK0,rmp ; in Interruptmaske
	; Display leer
	ldi rmp,0x01 ; Display loeschen
	rcall LcdC4Byte
	; Highs anzeigen
	ldi ZH,High(2*LcdForm) ; Formvorlage
	ldi ZL,Low(2*LcdForm)
	rcall LcdText
	ldi rmp,0x01
	rcall LcdD4Byte
	mov rmp,rHigh
	rcall Hex2Lcd
	ldi rmp,' '
	rcall LcdD4Byte
	ldi rmp,0x00
	rcall LcdD4Byte
	mov rmp,rLow
	rcall Hex2Lcd
	ldi rmp,' '
	rcall LcdD4Byte
	ldi rmp,'k'
	rcall LcdD4Byte
	mov rmp,rKopf
	rcall Hex2Lcd
	ldi rmp,' '
	rcall LcdD4Byte
	ldi rmp,'S'
	rcall LcdD4Byte
	mov rmp,rSet
	rcall Hex2Lcd
	ldi rmp,0x0C ; Cursor und Blink aus
	rcall LcdC4Byte ; an LCD
;
; Neustart
Neustart:
	clr rKopf
	clr rHigh
	clr rLow
	ret
;
; Byte in rmp in Hex an LCD
;   Eingabedaten: Register rmp
;   Ausgabe HH an LCD an aktueller Position
;
Hex2Lcd:
	push rmp ; Byte retten
	swap rmp ; oberes Nibble zuerst
	rcall HexNibble2Lcd
	pop rmp ; rmp wieder herstellen
; Gib Nibble in Hex auf LCD aus
HexNibble2Lcd:
	andi rmp,0x0F ; unteres Nibble maskieren
	subi rmp,-'0' ; ASCII-Null dazu addieren
	cpi rmp,'9'+1 ; A bis F?
	brcs HexNibble2Lcd1 ; nein
	subi rmp,-7 ; auf A bis F
HexNibble2Lcd1:
	rjmp LcdD4Byte ; rmp auf LCD ausgeben
;
; Starttext LCD
LcdStart:
.db "IR-Analyse ATtiny24 ",0x0D,0xFF
.db " gsc-elektronic.net ",0x0D,0xFF
.db "Messungen IR-Signal ",0x0D,0xFF
.db "   Signalanzahl",0xFE
;
; Ausgabebild
LcdForm:
.db "Anzahl High/Low-Sig-",0x0D,0xFF
.db "nale, Kopfbytes,",0x0D,0xFF
.db "Langwartesignale",0x0D,0xFE
;
; Spezialzeichen
Codezeichen:
.db 64,0,0,0,0,0,4,4,6,0 ; Z = 0, Low-Signal
.db 72,0,0,0,0,0,5,7,5,0 ; Z = 1, High-Signal
.db 0,0 ; Ende der Tabelle
;
; LCD-Routinen einlesen
.include "Lcd4Busy.inc"
;
; Ende Quelltext
;

12.4.4.2 Ergebnisbeispiele

Anzahl TV Mit dieser Software kriegt man heraus, dass die TV-Fernsteuerung 71 Hi/Lo-Wertepaare umfasst, davon drei Kopfsignale länger als 2,048 ms und 67 Datenpaare. Sie ist offensichtlich geringfügig länger als 64 Bits.

Anzahl HDR Diese hier ist ein echtes Rätsel: sie ist mit 146 Hi- und 147 Lo-Werten ein echtes Schwergewicht.

Anzahl Kamera Mit ganzen neun Tasten ausgestattet, liefert die Kamerasteuerung 35 Hi- und 36 Lo-Signale. Mit 32 Bit lassen sich ganze 429 Millionen Kombinationen kodieren. Offensichtlich steht technische Rationalität beim Entwickeln von Fernsteuerungen nicht im Vordergrund.

12.4.5 Signaldauern Datenbits

Wie lange Einsen und Nullen genau definiert sind, erkennt man im Prinzip schon aus der ersten Diagnosesoftware. Um ein paar mehr Signaldauern zu sehen, können wir auf die Ausgabe des MSB verzichten, da bei Datenbytes ohnehin Null ist. So kriegen wir 24 Bytes angezeigt. Wenn wir nur Hi- oder nur Lo-Signale anzeigen lassen und zusätzlich von vorne und von hinten anzeigen, dann haben wir schon ganz viele Datenbits im Blick.

Bit-Dauern kurz So teilt die Software zu Beginn mit, was beim Messen im Folgenden dargestellt wird.

12.4.5.1 Programm

Das Programm ist im Folgenden aufgelistet (der Quelltext ist hier , kombiniert mit den LCD-Routinen).

;
; ***************************************************
; * IR-Empfaenger mit ATtiny24 und LCD, Byteausgabe *
; * (C)2016 by http://www.elektronic.net            *
; ***************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ---------- Programmablauf ------------
;
; Die Dauer von IR-Signalen wird in Viel-
; fachen von 8 us gemessen und auf der
; LCD in Hex ausgegeben. Wahlweise
; - werden nur die High-Signale (Pausen)
;   oder die Low-Signale (Aktive Signale)
;   oder beide nacheinander ausgegeben
;   (Schalter cHigh und cLow),
; - die Signale zu Beginn oder am Ende
;   eines Datensets ausgegeben (Schalter
;   VonHinten).
;
; ---------- Schalter ------------------
.equ VonHinten = 0 ; 0: von vorne nach hinten
;                    1: von hinten nach vorne
.equ cHigh     = 1 ; 1: High-Bytes aufzeichnen
.equ cLow      = 1 ; 1: Low-Bytes aufzeichnen
;
; ---------- Byte-Ausgabe --------------
; .bb.bb.bb.bb.bb.bb ; 6 Byte
; .bb.bb.bb.bb.bb.bb ; 12 Byte
; .bb.bb.bb.bb.bb.bb ; 18 Byte
; .bb.bb.bb.bb.bb.bb ; 24 Byte
;
; ---------- Ports ---------------------
.equ pIrIn = PINA ; IR-Detektor-Port
.equ bIrIn = 0    ; IR-Detektor-Pin
;
; ---------- Timing --------------------
; Prozessortakt           1.000.000 Hz
; Zeit pro Takt                   1 us
; Vorteiler TC1                   8
; Zeit pro Timer-Takt             8 us
; Lang-Signal                30.000 us
; Langsignal-Zaehlerstand, MSB   14
.equ cLang = 14 ; MSB langes Signal
;
; ---------- Register ------------------
; verwendet: R0 von LCD, Dezimalwandlung
; verwendet: R1 Dezimalwandlung
; frei: R2..R
.def rSreg = R15 ; Sicherung Status
.def rmp   = R16 ; Vielzweckregister
.def rmo   = R17 ; Vielzweckregister
.def rLine = R18 ; LCD-Zeilenzaehler
.def rLese = R19 ; LCD-Register
.def rimp  = R20 ; Interrupt-Vielzweck
.def rFlag = R21 ; Flaggenregister
   .equ bUpd = 0 ; Update-Flagge
   .equ bShf = 1 ; Shift-Rueckwaerts
   .equ bLng = 2 ; Langes High-Signal
; frei: R22 .. R25
; verwendet: XH:XL R27:R26 fuer Schieben
; verwendet: YH:YL R29:R28 Zeiger in SRAM
; verwendet: ZH:ZL R31:R30 in LCD-Routinen
;
; ---------- SRAM ----------------------
.DSEG ; Datensegment
.ORG 0x0060 ; Start SRAM
Buffer:
.byte 24 ; Puffer fuer Empfangsdaten
Bufferende:
Letzter:
.byte 1 ; Letzter gemessener Wert
LetzterEnde:
;
; ---------- Reset- und Interrupts -----
.CSEG ; Code-Segment
.ORG 0 ; an den Beginn
	rjmp Start ; Reset-Vektor, Init
	reti ; INT0 External Interrupt Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Interrupt Request 0
	reti ; PCINT1 Pin Change Interrupt Request 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT Timer/Counter1 Capture Event
	reti ; TIM1_COMPA Timer/Counter1 Compare Match A
	reti ; TIM1_COMPB Timer/Counter1 Compare Match B
	reti ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA Timer/Counter0 Compare Match A
	reti ; TIM0_COMPB Timer/Counter0 Compare Match B
	rjmp Tc0Isr ; TIM0_OVF TC0 Overflow, Display updaten
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
;
; ---------- Interrupt Service -----
;
; PCINT-Interrupt
; wird von Flanken des IR-Sensors ausgeloest
;
; War der IR-Eingang vorher High, liegt eine
; negative Flanke vor (Dauer entspricht einer
; Pause) und es wird geprueft, ob das Signal
; laenger als 14*256*8 = 28,7 ms war. Wenn ja
; wird die bLng-Flagge gesetzt und, wenn nicht
; die Richtung VonHinten gewaehlt ist, der
; Pufferzeiger Y auf Anfang gesetzt. Wenn der
; Modus cHigh auf 1 gesetzt ist, wird die
; Dauer noch in den SRAM-Puffer geschrieben.
;
; War der IR-Eingang vorher Low, wird die
; Dauer in den SRAM-Puffer geschrieben (nur
; wenn cLow auf 1 gesetzt ist).
;
; Abschliessend wird Timer TC1 Null gesetzt.
; 
Pci0Isr:
	in rSreg,SREG ; Status sichern
	sbic pIrIn,bIrIn ; ueberspringe bei Null
	rjmp Pci0Isr1 ; Eins
	; Eingang ist Low, war vorher High
.	in rimp,TCNT1L ; Low Byte zuerst
	st Y,rimp ; LSB in SRAM
	in rimp,TCNT1H ; High Byte danach
	cpi rimp,cLang ; Langsignal?
	brcs Pci0IsrNoLong ; kein Langsignal
	sbr rFlag,1<<bLng ; setze Langflagge
.if VonHinten == 0
	ldi YH,HIGH(Buffer) ; Y auf Anfang Buffer
	ldi YL,LOW(Buffer)
	.endif
	rjmp Pci0Isr2 ; Fertig
Pci0IsrNoLong:
.if cHigh == 1 ; nur bei High-Signal auswerten
	tst rimp ; teste MSB
	brne Pci0IsrMsb
.if VonHinten == 1 ; nur bei von hinten
	; High-Signal 
	sbr rFlag,1<<bShf ; Shift-Flagge setzen
	.else
    ; nur bei vorwaerts: Position erhoehen
	cpi YL,LOW(Bufferende) ; Ende Buffer?
	brcc Pci0IsrNoInc ; hinter Buffer 
	adiw YL,1 ; vor Bufferende, erhoehen
Pci0IsrNoInc:
	.endif
Pci0IsrMsb: ; MSB groesser Null
	.endif
	rjmp Pci0Isr2 ; fertig
Pci0Isr1:
	; Eingang ist High, war vorher Low
.if cLow == 1 ; Nur wenn cLow eingeschaltet
	in rimp,TCNT1L ; Low Byte zuerst
	st Y,rimp ; LSB in SRAM
	in rimp,TCNT1H ; High Byte danach
	tst rimp ; wenn Msb nicht Null ist, ignorieren
	brne Pci0IsrMsb1 ; ignorieren
.if VonHinten == 1 ; nur bei rueckwaerts
	sbr rFlag,1<<bShf ; setze Schiebeflagge
	.else
	clr rimp ; stelle Timer 0 zurueck
	out TCNT0,rimp
	cpi YL,LOW(Bufferende) ; oberhalb Buffer
	brcc Pci0IsrNoInc1 ; ja, nicht erhoehen
	adiw YL,1 ; Eingabeposition weiter
Pci0IsrNoInc1:
	.endif
Pci0IsrMsb1:
	.endif
Pci0Isr2:
	ldi rimp,0 ; Neustart Timer 1
	out TCNT1H,rimp ; Zuerst MSB schreiben
	out TCNT1L,rimp ; Danach LSB schreiben
	out SREG,rSreg ; Status wieder herstellen
	reti ; fertig
;
; TC0-Overflow Interrupt Service Routine
; wird vom Ueberlauf des Timeout-Timers TC0
; ausgeloest
;
; Setzt die bUpd-Flagge und loest die Ausgabe
; der Ergebnisse auf der LCD aus.
;
Tc0Isr:
	in rSreg,SREG ; SREG retten
	sbr rFlag,1<<bUpd ; Update-Flagge setzen
	out SREG,rSreg ; SREG wieder herstellen
	reti
;
; ---------------- Start, Init --------------
Start:
	ldi rmp,LOW(RAMEND) ; Stapel auf Ramende
	out SPL,rmp ; in Stapelzeiger
	; I/O initiieren
	; LCD-Port-Ausgaenge initiieren
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; Kontrollport-Ausgaenge
	clr rmp ; Ausgaenge aus
	out pLcdCO,rmp ; an Kontrollport
	ldi rmp,mLcdDRW ; Datenport-Ausgabemaske, Schreiben
	out pLcdDR,rmp ; auf Richtungsregister Datenausgabeport
	; LCD initiieren
	rcall LcdInit ; Init der LCD
	ldi ZH,HIGH(2*Codezeichen) ; Spezialzeichen
	ldi ZL,LOW(2*Codezeichen)
	rcall LcdChars ; an LCD
	ldi ZH,HIGH(2*LcdStart) ; Start-Text ausgeben
	ldi ZL,LOW(2*LcdStart)
	rcall LcdText
	; Startwert fuer Pufferzeiger
.if VonHinten == 1 ; rueckwaerts
	ldi YH,HIGH(Letzter) ; Hinter Pufferende
	ldi YL,LOW(Letzter)
	.else 
	ldi YH,HIGH(Buffer) ; Y auf Pufferanfang
	ldi YL,LOW(Buffer)
	.endif
	; Startwert fuer Flagge
	clr rFlag
	; Timer 0 fuer Timeout initiieren
	ldi rmp,(1<<CS02)|(1<<CS00) ; Prescaler 1024
	out TCCR0B,rmp ; Timer starten
	; Timer 1 initiieren, freilaufend durch 8
	ldi rmp,1<<CS11 ; Prescaler durch 8
	out TCCR1B,rmp ; an Kontrollregister B
	; PCINT0 fuer IR-Rx
	ldi rmp,1<<PCINT0 ; Pin 0 Level change INTs
	out PCMSK0,rmo ; in Maske 0
	ldi rmp,1<<PCIE0 ; Interrupt PCINT0 ermoeglichen
	out GIMSK,rmp
	; Sleep Enable
	ldi rmp,1<<SE ; Sleep Mode Idle
	out MCUCR,rmp ; in MCU Kontrollregister
	; Interrupts ermoeglichen
	sei ; I-Flagge Statusregister
Schleife:
	sleep ; schlafen legen
	nop ; aufwachen
	sbrc rFlag,bShf ; Shift-Flagge auswerten
	rcall Shift ; Shift-Flagge behandeln
	sbrc rFlag,bLng ; Lang-Flagge behandeln
	rcall Lang ; Lang-Flagge auswerten
	sbrc rFlag,bUpd ; Update-Flagge auswerten
	rcall Update ; Update-Flagge behandeln
	rjmp Schleife ; schlafen legen
;
; Langes Wartesignal erkannt
Lang:
	cbr rFlag,1<<bLng ; Lang-Flagge loeschen
	clr rmp ; Timer 0 ruecksetzen
	out TCNT0,rmp
	ldi rmp,1<<TOIE0 ; Overflow-Int
	out TIMSK0,rmp ; in Interrupt-Maske
	ret
;
; Schiebe letzten Wert in Buffer
; wird von gesetzter bShf-Flagge ausgeloest
;
; Schiebt den letzten gemessenen Datensatz
; in den SRAM-Puffer.
;
Shift:
	cbr rFlag,1<<bShf ; Loesche Flagge
	clr rmp ; TC0 ruecksetzen
	out TCNT0,rmp
	ldi rmp,1<<TOIE0 ; Interrupt einschalten
	out TIMSK0,rmp ; in Interrupt-Maske
	ldi XH,HIGH(Buffer) ; Bufferzeiger Anfang
	ldi XL,LOW(Buffer)
	ldi ZH,HIGH(Buffer+1) ; Bufferzeiger Ein Byte hoeher
	ldi ZL,LOW(Buffer+1)
Shift1: ; Schiebe Bytes vorwaerts
	ld rmp,Z+ ; Lese Byte aus Herkunft
	st X+,rmp ; schreibe Byte in Ziel
	cpi ZL,LOW(LetzterEnde)
	brcs Shift1
	ret
;
; Update-Flagge behandeln
; Wird von gesetzter Update-Flagge ausgeloest
;
; Gibt die im SRAM-Puffer gespeicherten
; Dauern von High-/Low- oder von beiden
; Signalen zusammen aus.
;
Update:
	cbr rFlag,1<<bUpd ; Flagge wieder abschalten
	clr rmp ; TC0-Interrupt abschalten
	out TIMSK0,rmp ; in Interrupt-Maske
	ldi rmp,1 ; Loesche LCD-Anzeige
	rcall LcdC4Byte ; an LCD-Kontrollport
	ldi ZH,HIGH(Buffer) ; Setze Z auf Buffer-Anfang
	ldi ZL,LOW(Buffer)
Update1:
	cpi ZL,LOW(Bufferende) ; gesamter Buffer ausgegeben?
	brcc Update5 ; Ferig
.if VonHinten == 1 ; Nur bei rueckwaerts
	ldi rmp,'e' ; letztes Wertepaar
	cpi ZL,LOW(Bufferende-1)
	brcc Update2 ; 'e' kennzeichnet letzten
	.endif
.if cHigh == 1
	; cHigh=1
	.if cLow == 1 ; beide Signale
		; cHigh=1, cLow=1
		ldi rmp,' ' ; Leerzeichen
		.else
		; cHigh=1, cLow=0
		ldi rmp,0x01 ; High-Zeichen setzen
		.endif
	.else
	; cHigh=0
	ldi rmp,0x00 ; Low-Zeichen setzen
	.endif
Update2:
	rcall LcdD4Byte ; Spezialchar ausgeben
	ld rmp,Z+ ; Lese MSB aus Buffer
	rcall Hex2Lcd ; MSB in Hex ausgeben
	cpi ZL,LOW(Buffer+6) ; Zeile 1 voll?
	brne UpDate3 ; Z=6?
	rcall LcdLine2 ; zeile 2
	rjmp Update1 ; weiter
Update3:
	cpi ZL,LOW(Buffer+12) ; Zeile 2 voll?
	brne Update4
	rcall LcdLine3 ; zeile = 3
	rjmp Update1
Update4:
	cpi ZL,LOW(Buffer+18) ; Zeile 3 voll?
	brne Update1 ; nein, weiter
	rcall LcdLine4 ; zeile = 4
	rjmp Update1
Update5:
	ldi rmp,0x0C ; Cursor und Blink aus
	rcall LcdC4Byte ; an LCD
	clr rmp ; loeschen
.if VonHinten == 1
	; Buffer rueckwaerts loeschen
	ldi ZH,HIGH(LetzterEnde) ; auf Ende Puffer
	ldi ZL,LOW(LetzterEnde)
Lang1:
	st -Z,rmp ; loeschen
	cpi ZL,LOW(Buffer+1) ; am Anfang angekommen?
	brcc Lang1
	.else
	; Buffer vorwaerts loeschen
	ldi ZH,HIGH(Buffer) ; auf Anfang Buffer
	ldi ZL,LOW(Buffer)
Lang1:
	st Z+,rmp ; loeschen
	cpi ZL,LOW(LetzterEnde)
	brcs Lang1
	.endif
	ret
;
; Byte in rmp in Hex an LCD
;   Eingabedaten: Register rmp
;   Ausgabe HH an LCD an aktueller Position
;
Hex2Lcd:
	push rmp ; Byte retten
	swap rmp ; oberes Nibble zuerst
	rcall HexNibble2Lcd
	pop rmp ; rmp wieder herstellen
; Gib Nibble in Hex auf LCD aus
HexNibble2Lcd:
	andi rmp,0x0F ; unteres Nibble maskieren
	subi rmp,-'0' ; ASCII-Null dazu addieren
	cpi rmp,'9'+1 ; A bis F?
	brcs HexNibble2Lcd1 ; nein
	subi rmp,-7 ; auf A bis F
HexNibble2Lcd1:
	rjmp LcdD4Byte ; rmp auf LCD ausgeben
;
; Starttext LCD
LcdStart:
.db "IR-Analyse ATtiny24 ",0x0D,0xFF
.db " gsc-elektronic.net ",0x0D,0xFF
.if VonHinten == 1
	.db "Kurzsignale am Ende ",0x0D,0xFF
	.else
	.db "Kurzsignale von vorn",0x0D,0xFF
	.endif
.if cHigh == 1
	.if cLow ==1
	.db "Hex Hi",0x01,"/Lo",0x00," 8 us",0xFE,0xFE
	.else
	.db "Hex High",0x01,", 8 us",0xFE
	.endif
	.else
	.db "Hex Low",0x00,", 8 us",0xFE,0xFE
	.endif
;
; Spezialzeichen
Codezeichen:
.db 64,0,0,0,0,0,4,4,6,0 ; Z = 0, Low-Signal
.db 72,0,0,0,0,0,5,7,5,0 ; Z = 1, High-Signal
.db 80,0,0,0,0,0,0,0,7,0 ; Z = 2, Pausentrennung
.db 0,0 ; Ende der Tabelle
;
; LCD-Routinen einlesen
.include "Lcd4Busy.inc"
;
; Ende Quelltext
;

12.4.5.2 Beispiele

Das führt zu folgenden Ausgaben: Hi/Lo-Signale Mit beiden Schaltern gesetzt, werden sowohl die High- als auch die Low-Dauern ausgegeben. In diesem Fall sind die Dauern mit Leerzeichen getrennt dargestellt.

Hi/Lo-Signale Ende Die gleiche Darstellung vom Signalende her (e signalisiert das letzte Signal).

Hi-Signale Die Software zeigt hier nur High-Signale auf.

Hi-Signale Ende Und hier das Ganze vom Ende her.

12.4.6 Kodierungen

Aus den bisherigen Analysemethoden lassen sich die kodierten Tasten einer Fernsteuerung nur mühsam ermitteln (nur bis 48 Bits). Es gibt daher hier die etwas komfortablere und für mehr Bits ausgelegte Lösung.

12.4.6.1 Das Programm

Das Programm ist hier (zum Quellcode geht es hier, kombiniert mit den LCD-Routinen).

;
; ****************************************************
; * IR-Empfaenger mit ATtiny24 und LCD, Burstanalyse *
; * (C)2016 by http://www.elektronic.net             *
; ****************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ---------- Programmablauf ------------
;
; Misst die Dauer aller Eingangssignale vom
; IR-Sensor in Vielfachen von 8 us, wertet
; sie nach Kopf- und Datenbits getrennt aus
; und schreibt die Ergebnisse in Hex auf
; die LCD.
;
; ---------- Schalter ------------------
.equ cAktivHi = 1 ; 1: High-Signaldauer Bits
;                   0: Low-Signaldauer Bits
;
; ---------- Einstellungen Fernsteuerung
.equ cIRStart = 0x10 ; Pause vor Signal (MSB)
.equ cIRHigh  = 0x50 ; Null/Eins-Schwelle
;
; ---------- Ports ---------------------
.equ pIrIn = PINA ; IR-Detektor-Port
.equ bIrIn = 0    ; IR-Detektor-Pin
;
; ---------- Timing --------------------
; Prozessortakt           1.000.000 Hz
; Zeit pro Takt                   1 µs
; Vorteiler TC1                   8
; Zeit pro Timer-Takt             8 µs
;
; ---------- Register ------------------
; verwendet: R0 von LCD, Dezimalwandlung
; verwendet: R1 Dezimalwandlung
.def rShift0 = R2 ; Schieberegister, Byte 0
.def rShift1 = R3 ; dto., Byte 1
.def rShift2 = R4 ; dto., Byte 2
.def rShift3 = R5 ; dto., Byte 3
.def rShift4 = R6 ; dto., Byte 4
.def rShift5 = R7 ; dto., Byte 5
.def rShift6 = R8 ; dto., Byte 6
.def rShift7 = R9 ; dto., Byte 7
; frei: R8..R14
.def rSreg = R15 ; Sicherung Status
.def rmp   = R16 ; Vielzweckregister
.def rmo   = R17 ; Vielzweckregister
.def rLine = R18 ; LCD-Zeilenzaehler
.def rLese = R19 ; LCD-Register
.def rimp  = R20 ; Interrupt-Vielzweck
.def rFlag = R21 ; Flaggenregister
   .equ bEin = 0 ; Eingangswert vorhanden
   .equ bPol = 1 ; Polaritaet (high/low)
   .equ bUpd = 2 ; nach Pause ausgeben
   .equ bKOf = 3 ; Kopfdatenueberlauf
   .equ bDOf = 4 ; Datenbits-Ueberlauf
.def rKopf = R22 ; Kopfdatenzaehler
.def rData = R23 ; Bitzaehlerregister
; frei: R24 .. R25
; verwendet: XH:XL R27:R26 fuer Schieben
; verwendet: YH:YL R29:R28 Zeiger in SRAM
; verwendet: ZH:ZL R31:R30 in LCD-Routinen
;
; ---------- SRAM ----------------------
.DSEG ; Datensegment
.ORG 0x0060 ; Start SRAM
Kopf:
.byte 6 ; Puffer fuer Kopfdaten, MSB, LSB
Kopfende:
StatistikNull:
.byte 3 ; Summe Dauer Null,MSB/LSB/Anzahl
StatistikEins:
.byte 3 ; Summe Dauer Eins,MSB/LSB/Anzahl
StatistikPause:
.byte 3 ; Summe Dauer Pause,LSB/MSB/Anzahl
Statistikende:
;
; ---------- Reset- und Interrupts -----
.CSEG ; Code-Segment
.ORG 0 ; an den Beginn
	rjmp Start ; Reset-Vektor, Init
	reti ; INT0 External Interrupt Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Int Req 0
	reti ; PCINT1 Pin Change Interrupt Req 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT Timer/Counter1 Capture Event
	reti ; TIM1_COMPA Timer/Counter1 Compare Match A
	reti ; TIM1_COMPB Timer/Counter1 Compare Match B
	reti ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA Timer/Counter0 Compare Match A
	reti ; TIM0_COMPB Timer/Counter0 Compare Match B
	rjmp Tc0Isr ; TIM0_OVF TC0 Overflow, Display updaten
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
;
; ---------- Interrupt Service -----
;
; PCINT-Interrupt
; wird von allen Flanken des IR-Signals
; ausgeloest
;
; Stellt die Polaritaet des Signals fest
; und schreibt sie in die bPol-Flagge.
; Der Stand des TC1-Zaehlers wird in Y
; gelesen, der Zaehler neu gestartet
; und die bEin-Flagge gesetzt, die die
; Auswertung der gemessenen Dauer aus-
; loest.
; 
Pci0Isr:
	in rSreg,SREG ; Status sichern
	cbr rFlag,1<<bPol ; Polaritaets-Flagge loeschen
	sbic pIrIn,bIrIn ; ueberspringe bei Null
	; Eingang ist Low, war vorher High
	sbr rFlag,1<<bPol ; Polaritaets-Flagge setzen
	in YL,TCNT1L ; Low Byte zuerst
	in YH,TCNT1H ; High Byte danach
Pci0Isr2:
	ldi rimp,0 ; Neustart Zaehler
	out TCNT1H,rimp ; Zuerst MSB schreiben
	out TCNT1L,rimp ; Danach LSB schreiben
	sbr rFlag,1<<bEin ; Flagge eingegangenes Signal
	out SREG,rSreg ; Status wieder herstellen
	reti ; fertig
;
; TC0-Overflow Interrupt Service Routine
; wird 0,26 Sekunden nach dem letzten Eingangs-
; signal ausgeloest
;
; Setzt die Update-Flagge bUpd und loest die
; LCD-Ausgabe der gesammelten Daten aus.
;
Tc0Isr:
	in rSreg,SREG ; SREG retten
	sbr rFlag,1<<bUpd ; Update-Flagge setzen
	out SREG,rSreg ; SREG wieder herstellen
	reti
;
; ---------------- Start, Init --------------
Start:
	ldi rmp,LOW(RAMEND) ; Stapel auf Ramende
	out SPL,rmp ; in Stapelzeiger
	; I/O initiieren
	; LCD-Port-Ausgaenge initiieren
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; Kontrollport-Ausgaenge
	clr rmp ; Ausgaenge aus
	out pLcdCO,rmp ; an Kontrollport
	ldi rmp,mLcdDRW ; Datenport-Ausgabemaske, Schreiben
	out pLcdDR,rmp ; auf Richtungsregister Datenausgabeport
	; LCD initiieren
	rcall LcdInit ; Init der LCD
	ldi ZH,HIGH(2*Codezeichen) ; Spezialzeichen
	ldi ZL,LOW(2*Codezeichen)
	rcall LcdChars ; an LCD
	ldi ZH,HIGH(2*LcdStart) ; Start-Text ausgeben
	ldi ZL,LOW(2*LcdStart)
	rcall LcdText
	; Startwert fuer Flagge
	clr rFlag
	; Alle Inhalte entleeren
	rcall Neustart ; Neustartwerte setzen
	; Timer 0 initiieren
	ldi rmp,(1<<CS02)|(1<<CS00) ; Prescaler 1024
	out TCCR0B,rmp ; Timer starten
	; Timer 1 initiieren, freilaufend durch 8
	ldi rmp,1<<CS11 ; Prescaler durch 8
	out TCCR1B,rmp ; an Kontrollregister B
	; PCINT0 fuer IR-Rx
	ldi rmp,1<<PCINT0 ; Pin 0 Level change INTs
	out PCMSK0,rmo ; in Maske 0
	ldi rmp,1<<PCIE0 ; Interrupt PCINT0 ermoeglichen
	out GIMSK,rmp
	; Sleep Enable
	ldi rmp,1<<SE ; Sleep Mode Idle
	out MCUCR,rmp ; in MCU Kontrollregister
	; Interrupts ermoeglichen
	sei ; I-Flagge Statusregister
Schleife:
	sleep ; schlafen legen
	nop ; aufwachen
	sbrc rFlag,bEin ; Eingangs-Flagge auswerten
	rcall Eingang ; Eingangs-Flagge behandeln
	sbrc rFlag,bUpd ; Update-Flagge auswerten
	rcall Update ; Flagge behandeln
	rjmp Schleife ; schlafen legen
;
; Eingangssignal behandeln
; wird von der bEin-Flagge ausgeloest, die Signal-
; dauer steht im Y-Doppelregister
;
; Je nach Signaldauer wird diese im SRAM-Puffer
; im Kopfbereich abgelegt (Adresse in Z) oder
; als Datenbits behandelt.
; Liegen von der Dauer her Datenbits vor, wer-
; den diese als Nullen und Einsen in ein acht
; Byte (64 Bit) breites Register eingerollt.
;
Eingang:
	cbr rFlag,bEin ; Eingangsflagge loeschen
	mov XH,YH ; Zaehlerstand kopieren
	mov XL,YL
	; Timer ruecksetzen und starten
	ldi rmp,0 ; Timer 0 an den Anfang
	out TCNT0,rmp
	ldi rmp,1<<TOIE0 ; Timer-Ints ermoeglichen
	out TIMSK0,rmp
	tst XH ; MSB groesser Null?
	breq Eingang3 ; MSB = Null
	; MSB ist groesser Null
	cpi XH,cIRStart ; MSB groesser Startbedingung
	brcs Eingang1
	rcall Neustart
Eingang1:
	inc rKopf ; naechstes Kopfwort
	cpi rKopf,4 ; mehr als drei Worte?
	brcs Eingang2
	sbr rFlag,1<<bKOf ; Kopf-Ueberlaufflagge
	ret
Eingang2:
	ldi ZH,HIGH(Kopf)
	ldi ZL,LOW(Kopf)
	lsl rKopf
	add ZL,rKopf
	ldi rmp,0
	adc ZH,rmp
	lsr rKopf
	st Z+,XH ; in Kopfspeicher
	st Z,XL
	ret
Eingang3:
.if cAktivHi == 1
	sbrc rFlag,bPol ; Polaritaet Signal
	.else
	sbrs rFlag,bPol ; Umgekehrte Polaritaet
	.endif
	rjmp Eingang5 ; Low-Signal/High-Signal
	; Eingang war aktiv, Datenbits sammeln
	ldi rmp,cIRHigh ; Low-High-Schwelle
	cp rmp,XL ; kl: C clear, gr: C set
	rol rShift0 ; rolle Carry in Register
	rol rShift1
	rol rShift2
	rol rShift3
	rol rShift4
	rol rShift5
	rol rShift6
	rol rShift7
	inc rData
	cpi rData,66 ; 66 Bits zulaessig
	brcs Eingang4
	sbr rFlag,1<<bDOf ; Overflow-Flagge setzen
Eingang4:
	; zu Summenregister addieren
	ldi ZH,HIGH(StatistikNull)
	ldi ZL,LOW(StatistikNull)
	cp rmp,XL ; noch mal Ergebnis ins Carry
	brcc Eingang6
	ldi ZH,HIGH(StatistikEins)
	ldi ZL,LOW(StatistikEins)
	rjmp Eingang6
Eingang5:
	; Eingang ist umgekehrt polar
	ldi ZH,HIGH(Statistikpause)
	ldi ZL,LOW(Statistikpause)
Eingang6:
	ldd rmp,Z+1 ; LSB laden
	add rmp,XL ; LSB addieren
	std Z+1,rmp ; und speichern
	ld rmp,Z ; MSB laden
	adc rmp,XH ; MSB addieren
	st Z,rmp ; und speichern
	adiw ZL,2 ; auf Anzahl
	ld rmp,Z ; Anzahl lesen
	inc rmp ; erhoehen
	st Z,rmp ; und speichern
	ret
;
; Update-Flagge behandeln
; wird von gesetzter bUpd-Flagge ausgeloest
;
; Gibt den SRAM-Puffer-Inhalt auf der LCD
; aus.
;
Update:
	cbr rFlag,1<<bUpd ; Flagge wieder abschalten
	clr rmp ; Interrupts Timer aus
	out TIMSK0,rmp ; in Interruptmaske
	ldi rmp,0x01 ; Display loeschen
	rcall LcdC4Byte
	ldi ZH,HIGH(2*LcdMaske) ; Maske ausgeben
	ldi ZL,LOW(2*LcdMaske)
	rcall LcdText
	; Kopfdaten anzeigen
	ldi ZH,0 ; Kopfdaten ausgeben
	ldi ZL,5
	rcall LcdPos
Update1:
	ldi ZH,HIGH(Kopf)
	ldi ZL,LOW(Kopf)
	clr R0 ; Pegel-Zeichen
Update2:
	ldi rmp,0x01 ; Pegelzeichen umkehren
	eor R0,rmp
	mov rmp,R0
	rcall LcdD4Byte ; auf LCD ausgeben
	ld rmp,Z+ ; Lese MSB
	rcall Hex2Lcd ; an LCD
	ld rmp,Z+ ; Lese LSB
	rcall Hex2Lcd ; an LCD
	cpi ZL,LOW(Kopfende)
	brcs Update2
	sbrs rFlag,bKOf
	ldi ZH,0
	ldi ZL,17
	rcall LcdPos
	ldi rmp,' '
	rcall LcdD4Byte
	ldi rmp,'U'
	rcall LcdD4Byte
	ldi rmp,'!'
	rcall LcdD4Byte
	; Summen und Anzahlen ausgeben
Update3:
	ldi ZH,1 ; Zeile 2
	ldi ZL,2
	rcall LcdPos
	ldi ZH,HIGH(StatistikNull)
	ldi ZL,LOW(StatistikNull)
	ld rmp,Z+
	rcall Hex2Lcd
	ld rmp,Z+
	rcall Hex2Lcd
	ldi rmp,' '
	rcall LcdD4Byte
	ld rmp,Z+
	rcall Hex2Lcd
	ldi rmp,' '
	rcall LcdD4Byte
	ldi rmp,'1'
	rcall LcdD4Byte
	ldi rmp,':'
	rcall LcdD4Byte
	ld rmp,Z+
	rcall Hex2Lcd
	ld rmp,Z+
	rcall Hex2Lcd
	ldi rmp,' '
	rcall LcdD4Byte
	ld rmp,Z+
	rcall Hex2Lcd
	ldi ZH,2
	ldi ZL,2
	rcall LcdPos
	ldi ZH,HIGH(Statistikpause)
	ldi ZL,LOW(Statistikpause)
	ld rmp,Z+
	rcall Hex2Lcd
	ld rmp,Z+
	rcall Hex2Lcd
	ldi rmp,' '
	rcall LcdD4Byte
	ld rmp,Z+
	rcall Hex2Lcd
	; Schieberegister von oben ausgeben
	ldi ZH,3
	ldi ZL,2
	rcall LcdPos
	ldi ZH,0
	ldi ZL,10
Update4:
	ld rmp,-Z ; Byte lesen
	rcall Hex2Lcd ; und in Hex ausgeben
	cpi ZL,3
	brcc Update4
	sbrs rFlag,bDOf ; Overflow?
	rjmp Update5
	ldi rmp,'U'
	rcall LcdD4Byte
	ldi rmp,'!'
	rcall LcdD4Byte
Update5:
	ldi rmp,0x0C ; Cursor und Blink aus
	rcall LcdC4Byte ; an LCD
;
; Neustart
; wird beim Init, bei langen Pausen und im
; Anschluss an die LCD-Ausgabe aufgerufen
;
; Startet die Flaggen und leert den SRAM-
; Puffer.
;
Neustart:
	ser rKopf ; auf Anfangswerte
	clr rData
	cbr rFlag,(1<<bKOf)|(1<<bDOf) ; Flaggen loeschen
	ldi ZH,0 ; Register loeschen
	ldi ZL,2
Neustart1:
	st Z+,rData ; Register loeschen
	cpi ZL,10
	brcs Neustart1
	ldi ZH,HIGH(Kopf) ; SRAM loeschen
	ldi ZL,LOW(Kopf)
Neustart2:
	st Z+,rData
	cpi ZL,LOW(Statistikende)
	brcs Neustart2
	ret
;
; Byte in rmp in Hex an LCD
;   Eingabedaten: Register rmp
;   Ausgabe HH an LCD an aktueller Position
;
Hex2Lcd:
	push rmp ; Byte retten
	swap rmp ; oberes Nibble zuerst
	rcall HexNibble2Lcd
	pop rmp ; rmp wieder herstellen
; Gib Nibble in Hex auf LCD aus
HexNibble2Lcd:
	andi rmp,0x0F ; unteres Nibble maskieren
	subi rmp,-'0' ; ASCII-Null dazu addieren
	cpi rmp,'9'+1 ; A bis F?
	brcs HexNibble2Lcd1 ; nein
	subi rmp,-7 ; auf A bis F
HexNibble2Lcd1:
	rjmp LcdD4Byte ; rmp auf LCD ausgeben
;
; Starttext LCD
LcdStart:
.db "IR-Analyse ATtiny24 ",0x0D,0xFF
.db " gsc-elektronic.net ",0x0D,0xFF
.db "Messungen IR-Signal ",0x0D,0xFF
.db " in Hex mal 8 us   ",0xFE
;
; Ausgabemaske fuer Messdaten
LcdMaske:
.db "Kopf:",0x0D
;         5
.db "0:xxxx nn 1:xxxx nn ",0x0D,0xFF
;      2
.db "P:xxxx nn",0x0D
;      2
.db "D:xxxxxxxxxxxxxxxx ",0xFE
;      2
;
; Spezialzeichen
Codezeichen:
.db 64,0,0,0,0,0,4,4,6,0 ; Z = 0, Low-Signal
.db 72,0,0,0,0,0,5,7,5,0 ; Z = 1, High-Signal
.db 0,0 ; Ende der Tabelle
;
; LCD-Routinen einlesen
.include "Lcd4Busy.inc"
;
; Ende Quelltext
;

Neu ist hier SER Register: setzt alle Bits im Register auf Eins (R16..R31).

12.4.6.2 Beispiele

Bits TV Das hier ist das Ergebnis dieser Software am Beispiel meiner TV-Fernsteuerung. Die produziert mehr als drei Kopfsignale ("U!" in der ersten Zeile rechts (aber dafür wir ja ein anderes Werkzeug. Aus den Summenwerten von Nullen und Einsen hinter "0:" bzw. "1:" in Kombination mit der Anzahl an ihrem Vorkommen (bei den Nullen z. B. 0x24 mal) lassen sich Durchschnitte errechnen.

Noch viel hilfreicher ist die Datenbit-Auswertung hinter "D:". Hier lassen sich die Datenbits der einzelnen Tasten der Fernbedienung in Hexadezimalform ablesen.

Tastentabelle Das hier ist so eine Tabelle mit den Tastenzuordnungen der TV-Fernsteuerung. Was sich der Entwickler dabei gedacht haben mag, erschließt sich dem normalverständigen Bürger nicht so ganz.

Ich wünsche jedenfalls etwas mehr Glück beim Erkunden der eigenen schwarzen Kästchen, alles nötige Handwerkszeug ist jetzt dafür vorhanden.


Home Top IR Bedingt Hardware Messen Senden Schalter Empfang


12.5 Ein IR-Sender

Damit wir nun nicht nur vorhandene Fernsteuerungen nutzen können, sondern auch mal selber Signale erzeugen können, bereichern wir die Fernsteuerwelt mit einer eigenen Steuerung und unserer eigenen Norm. Da wir aus den letzten Experimenten noch einen ATtiny13 haben, machen wir das mit dem.

12.5.1 Schaltbild zum IR-Senden

IR-Sender Das hier ist es schon alles, was wir brauchen: den ATtiny13, einen Taster um das Signal zu starten und eine Infrarot-LED. Wir verzichten darauf, die IR-Sendediode mit voller Leistung (mit 100 mA) anzusteuern, weil wir damit den auch für die ISP-Programmierung verwendeten Pin völlig übersteuern würden. Das senkt zwar die Reichweite, ist aber für das Experiment hinnehmbar. Wer mit voller Power senden möchte, schaltet einen PNP-Transistor als Treiberstufe dazwischen (NPN würde die Polarität umkehren, was erhebliche Konsequenzen für das Programm hätte).

12.5.2 Die IR-LED

IR-LED Das hier ist eine Infrarot-LED. Sie hat eine Durchlassspannung von 1,35 V bei einem Nennstrom von 100 mA. Der Vorwiderstand bei 5 V ergibt sich bei den maximal sinnvollen 73 mA Maximalstrom, die ein ATtiny13-Ausgang in Sink-Schaltung liefert, damit zu

R [Ohm] = 1000 * (5 - 1,35 - 2 [V]) / 73 [mA] = 23 [Ohm]


Wenn man einen Transistortreiber zwischenschaltet, ergibt sich ein Vorwiderstand von

R [ohm] = 1000 * (5 - 1,35 - 0,2 [V]) / 100 [mA] = 35 [Ohm]


12.5.3 68 Ohm-Widerstand

68 Ohm Das ist der 68 Ω-Widerstand. Mit ihm beträgt der IR-LED-Strom

I [mA] = 1000 * (5 - 1,35 - 1,0 [V]) / 68 = 39 [mA]

12.5.4 Aufbau

Aufbau Sender Das hier ist der einfache Aufbau der Hardware.

12.5.4 Das IR-Sendesignal

Für das Senden bietet sich der Timer im ATtiny13 an. Er schaltet bei Erreichen der programmierten Obergrenze die LED am OC0A-Ausgang im Takt ein und aus.

Das gesamte Timing der Software baut sich zentral auf der Erzeugung dieses 40 kHz-IR-Signals auf. Um diese mittels des 8-Bit-Timers im CTC-Modus erzeugen zu können, muss der Ausgang OC0A bei aktivem Sendesignal im 40 kHz-Takt torkeln. Der TIMO-COMPA-Interrupt kann dazu dienen, durch Zählen der erreichten Anzahl Pegelwechseltakte die Dauer der aktiven und inaktiven Signale zu kontrollieren. Da im Falle der langen Kopfsignale eine sehr große Anzahl an Durchläufen zu zählen sind, muss dies ein 16-Bit-Zähler sein.

Bei 1,2 MHz dauert ein An/Aus-Signal von 40 kHz 30 Prozessortakte, für jedes der beiden Signalteile folglich 15 Takte. Mit dem Auslösen des COMPA-Interrupts sind vier Takte erforderlich (Ablegen des Programmzählers auf dem Stapel), für den Sprung aus der Vektortabelle in die Interrupt-Service-Routine sind zwei weitere Takte erforderlich. Die weiteren Schritte in der Service-Routine wären folgende:

TC0CAIsr:
	in R15,SREG ; Retten Status
	sbiw R24,1 ; Zaehler abwaerts
	brne TC0CAIsrRet ; noch nicht null
	; [... weiteres ...]
TC0CAIsrRet:
	out SREG,R15 ; Status herstellen
	reti ; fertig, zurueck

Das Timing dieser Routine stellt sich folgendermaßen dar:

TC0CAIsr: ; 4 fuer Int, 2 fuer Vektorsprung = 6
	in R15,SREG ; +1 = 7
	sbiw R24,1 ; +2 = 9
	brne TC0CAIsrRet ; +2 = 11 bei Sprung
	; [... weiteres ...]
TC0CAIsrRet: ; 11 Takte
	out SREG,R15 ; +1 = 12
	reti ; +4 = 16

Damit dauert die ISR 16 Takte lang. Damit steht bereits der nächste COMPA-Interrupt an, bevor der derzeitige fertig bearbeitet ist. Für irgendetwas anderes steht weder innerhalb noch außerhalb der Service Routine Zeit zur Verfügung. Mit der Default-Taktrate des ATtiny13 funktioniert dies nicht.

Eine Taktrate von 2,4 Mhz, wie sie durch Beschreiben des CLKPR-Ports möglich ist, ist jedoch ausreichend. Selbst wenn in der Service Routine auch noch Flaggen gesetzt werden und die Torkel-Einstellung umgestellt wird (von Torkeln auf Setzen des Ausgangssignals und zurück), bleibt bis 30 Takte noch genug Luft.

12.5.6 Kontrolle der Signaldauer

Diese erfolgt sinnvollerweise mit dem Registerpaar R25:R24, da dies mit SBIW abwärts gezählt aber nicht als Zeiger verwendet werden kann.

Jeder Takt dieses Zählers entspricht bei 40 kHz 12,5 µs. Für ein Wartesignal vor Beginn der Übertragung stehen maximal 819,2 ms zur Verfügung. Die Mikrosekunden sind mit 2 zu multiplizieren und durch 25 zu teilen, da in Assembler keine Fließkommaoperationen sinnvoll und zulässig sind.

Während der Abarbeitung des Kopfes sind im Prinzip beliebig lange Aktiv- und Inaktivdauern möglich. Damit dies möglich ist, muss eine flexible Konstruktion gewählt werden, um die festgelegte Dauer den einzelnen Bytes zuzuordnen. Damit bei acht denkbaren unterschiedlichen Kopfdauern die Abfrage, welche der Dauern jetzt dran ist, nicht allzu umfangreich wird, wird hier ein berechneter Sprung gewählt. Das ermöglicht die Instruktion IJMP. Sie bewirkt, dass die Programmadresse aus dem Z-Registerpaar geladen wird. Das ermöglicht es, Sprungadressen zu berechnen und relativ rasch an die Zieladresse zu verzweigen.

12.5.7 Programm zum Senden

Das folgende Programm kann als flexible Basis für eigene Formulierungen und Formate verwendet werden. Es lässt sich mit geringem Aufwand an nahezu jeden Bedarf anpassen. Es werden pro Tastendruck jeweils zwei Mal die Sequenz gesendet, um die korrekte Dauer der langen Pause zu Beginn des Kopfes messen zu können.

Beim ISP-Brennen ist die IR-Leuchtdiode oder der Widerstand zeitweise zu entfernen, da das Programmiergerät den hohen Strom nicht schafft und Fehler meldet.

Das Programm ist hier (zum Quelltext geht es hier).

;
; *************************************
; * IR-Sender 40 kHz mit ATtiny13     *
; * (C)2016 by www.gsc-elektronic.net *
; *************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; -------- Programmablauf -------
;
; Sendet ueber die IR-LED eine einstellbare
; Anzahl von Kopfbytes (deren Dauer ein-
; stellbar ist) und danach eine einstellbare
; Anzahl von Datenbits (deren Dauer bei Eins
; oder Null waehlar ist), getrennt durch
; Pausen, deren Dauer ebenfalls einstellbar
; ist.
;
; -------- Register -------------
; frei: R0 .. R5
.def rData0 = R6 ; Senderegister, hoechstes
.def rData1 = R7 ; dto., naechstniedriges
.def rData2 = R8 ; dto., naechstniedriges
.def rData3 = R9 ; dto., naechstniedriges
.def rData4 = R10 ; dto., naechstniedriges
.def rData5 = R11 ; dto., naechstniedriges
.def rData6 = R12 ; dto., naechstniedriges
.def rData7 = R13 ; Senderegister, niedrigstes
.def rEor = R14 ; fuer Polaritaetsumkehr Toggle
.def rSreg = R15 ; Statusregister
.def rmp = R16 ; Vielzweck
.def rimp = R17 ; Vielzweck innerhalb Int
.def rFlag = R18 ; Flaggenregister
	.equ bRun = 0 ; Senden laeuft
	.equ bSta = 1 ; Senden starten
	.equ bTTo = 2 ; Timeout beim Senden
	.equ bRst = 3 ; Zweite Sendung
.def rTOC = R19 ;  Timeout-Zaehler, zaehlt Signale
; frei R20 .. R23
.def rCntL = R24 ; Zaehlt CTC-Compares
.def rCntH = R25
; benutzt: XH:XL R27:R26
; frei: YH:YL R29:R28
; benutzt: ZH:ZL R31:R30 Daten aus Flash
;
; -------- Konstanten und Timing ------
.equ cTakt = 2400000 ; Prozessortakt
.equ cTaktNs = 1000000000 / cTakt ; Takt ns
.equ cIRF = 40000 ; IR-Sendefrequenz Hz
.equ cIRNs = 1000000000 / cIRF / 2 ; IR-TX ns
.equ cCtc = (cIRNs+cTaktNs/2) / cTaktNs - 1
; 9,6 MHz: 120; 4,8 MHz: 60; 2,4 MHz: 30
; 1,2 MHz: 15(!!), schneller als ISR!!
;
; -------- Aufbau IR-Signal -----------
; Alle Zeitangaben in us
; Anzahl Kopfbytes, 1 .. 4
.equ nKopf = 4 ; Anzahl Kopfpaare H/L, 1..4
.equ cKopf = 2*nKopf-1 ; Anzahl Kopf-Signale
; Dauer der Kopfsignale (Dauer in 2*us/25)
.equ cTK1 = 2*50000/25 ; Dauer High 1
.equ cTK2 = 2*5000/25 ; Dauer Low 1
.equ cTK3 = 2*2500/25 ; Dauer High 2
.equ cTK4 = 2*500/25 ; Dauer Low 2
.equ cTK5 = 2*1250/25 ; Dauer High 3
.equ cTK6 = 2*500/25 ; Dauer Low 3
.equ cTK7 = 2*1250/25 ; Dauer High 3
.equ cTK8 = 2*500/25 ; Dauer Low 3
.equ cTK9 = 2*1250/25 ; Dauer High 4
.equ cTK10 = 2*500/25 ; Dauer Low 4
; Anzahl zu sendender Datenbits
.equ cBits = 64 ; Anzahl Bits, 1 .. 64
.equ cSign = cKopf + 2*cBits ; Anzahl Signale
; Dauer von Datenbits und Aktivperiode
.equ cKurz = 2*250/25 ; Dauer Binaer-Null
.equ cLang = 2*750/25 ; Dauer Binaer-Eins
.equ cPaus = 2*250/25 ; Dauer Aktivperiode
;
; -------- Sendeinformationen ------ 
; Testdaten zum Senden
.equ cWort1 = 0xAAAA ; Hoechtwertigst
.equ cWort2 = 0x5555
.equ cWort3 = 0xAAAA
.equ cWort4 = 0x5555 ; Niederwertigst
;
; -------- Reset- und Interruptvektoren ---
.CSEG
.ORG 0
	rjmp Start ; Programm-Init
	rjmp Int0Isr ; INT0 Tasteninterrupt
	reti ; PCINT0 Pin Change Int Request 0
	reti ; TIM0_OVF Timer/Counter Overflow
	reti ; EE_RDY EEPROM Ready
	reti ; ANA_COMP Analog Comparator
	rjmp TC0CAIsr ; TIM0_COMPA TC0 Compare A
	reti ; TIM0_COMPB TC0 Compare Match B
	reti ; WDT Watchdog Time-out
	reti ; ADC Conversion Complete
; -------- Interrupt Service Routinen -----
;
; INT0-Interrupt
; wird von der fallenden Flanke des Tasten-
; drucks ausgeloest.
; Falls der Sendeablauf inaktiv ist (Flagge
; bRun ist Null), werden die Flaggen bRun
; und bSta gesetzt.
;
Int0Isr: ; Tasteninterrupt, startet Senden
	sbrc rFlag,bRun ; laeuft noch nicht
	reti ; doch, nicht neu starten
	in rSreg,SREG ; SREG retten
	sbr rFlag,(1<<bRun)|(1<<bSta) ; Flaggen
	out SREG,rSreg ; SREG wieder herstellen
	reti ; zurueck
;
; CTC-Timeout Timer: IR-LED schalten
; wird vom Compare Match A von TC0 ausgeloest
;
; Zaehlt den 16-Bit-Zaehler R25:R24 abwaerts.
; Ist Null erreicht, wird das Toggle/Clear-Bit
; des Ausganges umgedreht und die bTTO-Flagge
; gesetzt. Damit es schnell geht, erfolgt das
; Umdrehen des Toggle-Zustands statt mit be-
; dingtem Sprung mit einem Exklusiv-Oder mit
; einem Register.
; Die Toggle/Clear-Umschaltung mit den COM0A-
; Bits geht folgendermassen:
;
; Output Mode | COM0A1 | COM0A0  
; ------------+--------+-------
; Toggle OC0A |   0    |   1
; Clear OC0A  |   1    |   1 
;
; Ein bedingter Sprung wuerde so gehen:
;    sbic TCCR0A,COM0A1 ; 1 oder 2 Takte
;    rjmp Gesetzt ; + 2 = 3 Takte
;    sbi TCCR0A,COM0A1 ; + 2 = 4 Takte
;    rjmp Weiter ; + 2 = 6 Takte
;   Gesetzt: ; 3 Takte
;    cbi TCCR0A,COM0A1& ; + 2 = 5 Takte
;   Weiter: ; 5 oder 6 Takte
; Braeuchte also 5 oder 6 Takte.
;
; Legt man hingegen 1<<COM0A1 in ein
; Register ab (hier rEor genannt) und
; Exklusiv-Oder-t TCCR0A mit diesem Register,
; dann torkelt das COM0A1-Bit auch: aus
; Null wird Eins und umgekehrt. Das braucht
; folgende Zeiten:
;    in rimp,TCCR0A ; 1 Takt
;    eor rimp,rEor ; + 1 = 2 Takte
;    out TCCR0A,rimp ; + 1 = 3 Takte
; Das EOR spart also zwei bis drei Takte,
; kostet dafuer aber ein Register (rEor).
; 
TC0CAIsr: ; 6 Takte f. Int und Vektor-rjmp
	in rSreg,SREG ; SREG retten, 7
	sbiw rCntL,1 ; CTC-Zaehler abwaerts, 9
	brne Tc0CAIsrRet ; Nicht Null, 10/11
	sbr rFlag,1<<bTTO ; Flagge setzen, 11
	in rimp,TCCR0A ; Toggle-Zustand, 12
	eor rimp,rEor ; umkehren, 13
	out TCCR0A,rimp ; neuer Toggle, 14
TC0CAIsrRet: ; 11/14
	out SREG,rSreg ; SREG herstellen, 12/15
	reti ; fertig, 16(!!)/19
;
; --------- Programmstart, Init -------
Start:
	; Stapel einrichten
	ldi rmp,LOW(RAMEND)
	out SPL,rmp
	; Takt auf 2,4 MHz hochsetzen
	ldi rmp,1<<CLKPCE ; CLK-Enable
	out CLKPR,rmp
	ldi rmp,1<<CLKPS1 ; Teiler = 4
	out CLKPR,rmp
	; LED-Port initiieren
	sbi PORTB,PORTB0 ; LED aus
	sbi DDRB,DDB0 ; Pin ist Ausgang
	; Tastenport mit Pull-Up
	sbi PORTB,PORTB1 ; Pullup an
	; Startwerte setzen
	clr rFlag ; Flaggen loeschen
	ldi rmp,1<<COM0A1 ; Toggle-Umkehr-Bit
	mov rEor,rmp ; in Exor-Register
	; TC0 als CTC starten
	ldi rmp,cCtc ; Compare A Wert 12,5us
	out OCR0A,rmp
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)
	out TCCR0A,rmp ; Ausgang high, CTC
	ldi rmp,1<<CS00 ; Vorteiler = 1
	out TCCR0B,rmp
	; INT0 und Schlafmodus
	ldi rmp,(1<<SE)|(1<<ISC01) ; Schlafen
	out MCUCR,rmp ; und INT0
	ldi rmp,1<<INT0 ; INT0-Interrupt
	out GIMSK,rmp
	; Interrupts
	sei ; Interrupts ermoeglichen
	; fuer Simulation
	cbi PORTB,PORTB1 ; INT0 ausloesen
	sbi PORTB,PORTB1
;
; -------- Programmschleife ----------
Schleife:
	sleep ; schlafen legen
	nop ; nach Aufwachen
	sbrc rFlag,bTTo ; Timeout?
	rcall Timeout ; Timeout bearbeiten
	sbrc rFlag,bSta ; Startflagge
	rcall StartOut ; Senden starten
	rjmp Schleife ; weiter schlafen
;
; -------- CTC-Timeout --------------- 
;
; Routine Timeout
; wird von der bTTo-Flagge ausgeloest
; (Senden beendet)
;
; Stellt zunaechst fest, ob noch Kopfdaten
; gesendet werden muessen oder schon Daten-
; bits dran sind.
; Sind Kopfdaten zu senden (rTOC ist kleiner
; als cKopf), wird die Dauer des zu senden-
; den Kopfsignals durch Sprung in die ent-
; sprechende Setzroutine mit IJMP geladen.
; Sind Datenbits zu senden, wird geprueft,
; ob schon alle Datenbits und Pausen ge-
; sendet sind. Wenn nein, wird geprueft
; ob zuletzt ein Daten- oder ein Pausenbit
; gesendet wurde. War es ein Datenbit,
; wird die Standard-Pausendauer cPaus
; geladen. War es eine Pause wird durch
; Schieben und Rotieren das naechste zu
; sendende Bit in das Carry-Flag geladen.
; Daraus wird ermittelt ob ein langes oder
; ein kurzes Signal zu senden ist.
; Sind alle Bits gesendet, wird der Ablauf
; neu gestartet (wenn das bRst-Bit noch
; nicht gesetzt war). Ist es gesetzt,
; wird alles abgeschaltet und auf einen
; Neustart durch den Tastendruck gewartet.
; 
TimeOut: ; Flagge war gesetzt
	cbr rFlag,1<<bTTo ; Flagge loeschen, 1
	inc rTOC ; naechstes Zeichen, 2
	cpi rTOC,cKopf ; noch Kopf senden?, 3
	brcc TimeOut1 ; nein, Datenbits, 4/5
	ldi ZH,HIGH(TOK2) ; MSB Adresse Kopf, 6
	ldi ZL,LOW(TOK2) ; dto, LSB, 7
	mov rmp,rTOC ; 8
	lsl rmp ; mal zwei, 9
	add rmp,rTOC ; mal drei, 10
	add ZL,rmp ; zu Adresse, 11
	ldi rmp,0 ; Ueberlauf, 12
	adc ZH,rmp ; addieren, 13
	ijmp ; Sprung nach Z, 15
TimeOut1: ; 5
	cpi rTOC,cSign ; schon alle gesendet?, 6
	brcc TimeOutEnd ; ja, beenden, 7/8
	in rmp,TCCR0A ; Toggle/Set lesen, 8
	sbrc rmp,COM0A1 ; Toggle ein pruefen, 9/10
	rjmp TimeOut2 ; Toggle ist aus, 11
	; Toggle ist an
	ldi rCntH,High(cPaus) ; lade Pausendauer, 11
	ldi rCntL,Low(cPaus) ; 12
	ret ; fertig, 16
TimeOut2: ; 11
	ldi rCntH,High(cKurz) ; lade kurzes Bit, 12
	ldi rCntL,Low(cKurz) ; 13
	lsl rData7 ; schiebe Bits, 14
	rol rData6 ; 15
	rol rData5 ; 16
	rol rData4 ; 17
	rol rData3 ; 18
	rol rData2 ; 19
	rol rData1 ; 20
	rol rData0 ; 21
	brcc TimeOut3 ; Bit ist Null, 22/23
	ldi rCntH,High(cLang) ; Dauer Eins, 23
	ldi rCntL,Low(cLang) ; 24
TimeOut3: ; 23/24
	ret ; fertig, 23/24
TimeOutEnd: ; 8
	sbrs rFlag,bRst ; Restart-Flagge?, 9/10
	rjmp Restart ; noch einmal beginnen, 11
	clr rmp ; Timer-Int aus, 11
	out TIMSK0,rmp ; 12
	; Ausgang OCR0A auf Eins 
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01) ; 13
	out TCCR0A,rmp ; 14
	cbr rFlag,(1<<bRun)|(1<<bRst) ; Flaggen loeschen, 15 
	ret ; fertig, 19
;
; Timeout Kopf, Dauer einstellen
; (Alle Routinen muessen drei Worte lang sein, da
;  die Zieladresse berechnet wird!)
;
TOK2: ; 15
	ldi rCntH,High(cTK2) ; 16
	ldi rCntL,Low(cTK2) ; 17
	ret ; 21
;
	ldi rCntH,High(cTK3)
	ldi rCntL,Low(cTK3)
	ret
;
	ldi rCntH,High(cTK4)
	ldi rCntL,Low(cTK4)
	ret
;
	ldi rCntH,High(cTK5)
	ldi rCntL,Low(cTK5)
	ret
;
	ldi rCntH,High(cTK6)
	ldi rCntL,Low(cTK6)
	ret
;
	ldi rCntH,High(cTK7)
	ldi rCntL,Low(cTK7)
	ret
;
	ldi rCntH,High(cTK8)
	ldi rCntL,Low(cTK8)
	ret
;
; Restart: Lauf noch einmal starten
; wird nach Abschluss des ersten Durchgangs
; aufgerufen und startet die Ausgabe neu
; 
Restart: ; 11
	clr rmp ; Timer-Int ausschalten, 12
	out TIMSK0,rmp ; 13
	sbr rFlag,1<<bRst ; Flagge setzen, 14
	rjmp StartOut1 ; 16
;
; StartOut-Routine
; wird von der Flagge bRst ausgeloest und,
; ohne Flaggen-Loeschen, bei einem Restart
; angesprungen.
;
; Kopiert die Datentabelle aus dem Flash-
; Speicher in umgekehrter Richtung in die
; Senderegister von R14 abwaerts.
; Setzt den den Zaehler rToc auf 255 und
; laedt die Dauer der ersten Kopfpause
; (IR-LED aus) in das Doppelregister in
; R25:R24. Schaltet dann den Timer-Compare
; A Interrupt an.
;
StartOut:
	cbr rFlag,(1<<bSta)|(1<<bRst) ; Flagge loeschen
StartOut1:
	ldi ZH,High(2*Datentabelle)
	ldi ZL,Low(2*Datentabelle)
	ldi XH,0 ; in Register 13 abwaerts
	ldi XL,14
StartOut2:
	lpm rmp,Z+ ; Tabellenbyte lesen
	st -X,rmp ; in Register
	cpi XL,7 ; schon bei 6 angekommen?
	brcc StartOut2 ; nein, weiter
	ser rToC ; Zaehler auf 0xFF
	ldi rCntH,High(cTK1) ; erste Wartedauer
	ldi rCntL,Low(cTK1)
	ldi rmp,1<<OCIE0A ; Timer-Int ein
	out TIMSK0,rmp
	ret
;
; Datentabelle
Datentabelle:
.dw cWort4,cWort3,cWort2,cWort1

12.5.8 Analyse der gesendeten Signale

Diagnose Sendung Das hier ist, was der ATtiny45 von den Signalen so mitbekommen hat. Die beiden ersten Kopfsignale sind mit 0x1901 und 0x0288 (51.208 und 5.184 µs) etwas zu lang. Dass die Anzahl der Kopfsignale bei der Senderoutine extrem groß ist, ist mit einem U! vermerkt.

Die Datensignale bei Nullen haben eine Summe von 0x036A (=874) für 32 Signale, für 32 Einsen von 0x0CC3 ergeben. Das entspricht durchschnittlich 219 µs für Nullen und 769 µs für Einsen. Kurze Nullsignale werden daher als etwas zu kurz, lange Eins-Signale und auch die sehr langen Kopfsignale als etwas zu lang gemessen.

Die Abweichungen sind aber zu verschmerzen. Die gesamte Paaranzahl wurde mit 68 gemessen.

Alle acht Datenbytes sind korrekt erkannt.


Home Top IR Bedingt Hardware Messen Senden Empfang Schalter


12.5.9 Simulation der Sendesignale

Im folgenden werden die IR-Aussendungen simuliert mit avr_sim.

Senden: Prescaler Nach dem Init des Stapelzeigers erhöhen die folgenden Instruktionen den Prozessortakt:
	; Takt auf 2,4 MHz hochsetzen
	ldi rmp,1<<CLKPCE ; CLK-Enable
	out CLKPR,rmp
	ldi rmp,1<<CLKPS1 ; Teiler = 4
	out CLKPR,rmp
Die Änderung ist beim Simulator angekommen und der Prozessortakt steht jetzt bei 2,4 MHz.

Senden: Port-Init Die Instruktionen
	; LED-Port initiieren
	sbi PORTB,PORTB0 ; LED aus
	sbi DDRB,DDB0 ; Pin ist Ausgang
haben den IR-LED-Ausgang auf High geschaltet (LED aus) und ihn als Ausgang konfiguriert. Dann hat die Instruktion
	; Tastenport mit Pull-Up
	sbi PORTB,PORTB1 ; Pullup an
den Pullup-Widerstand am Tasteneingang eingeschaltet. Ferner haben die Instruktionen
	; INT0 und Schlafmodus
	ldi rmp,(1<<SE)|(1<<ISC01) ; Schlafen
	out MCUCR,rmp ; und INT0
	ldi rmp,1<<INT0 ; INT0-Interrupt
	out GIMSK,rmp
den INT0-Interrupt bei fallenden Flanken am INT0-Eingang (beim Drücken der Taste) aktiviert. Nebenbei ist noch der Schlafmodus Idle (mit Aufwachen der CPU beim Int) eingestellt.

Senden: TC0-Init Der Timer/Counter 0 wurde als CTC (Clear TC bei Compare Match) mit 12,5µs Wiederholungszeit konfigueriert, natürlich mit einem Vorteilerwert von 1. Der PB0-Ausgang wird bei Compare Match auf Eins gesetzt, womit die IR-LED vorerst Aus bleibt. Die Befehlsfolge dazu lautete:
	; TC0 als CTC starten
	ldi rmp,cCtc ; Compare A Wert 12,5us (=30)
	out OCR0A,rmp
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)
	out TCCR0A,rmp ; Ausgang high, CTC
	ldi rmp,1<<CS00 ; Vorteiler = 1
	out TCCR0B,rmp


Senden: INT0 activiert Durch Klicken auf INT0 in der Portdarstellung wird ein Tastendruck simuliert und der Tasteninterrupt wird angefordert.

Senden: INT0 wird ausgeführt Mit der nächsten Instruktion wird die INT0-Service-Routine ausgeführt.

Senden: Flagge gesetzt Mit der Instruktion
	sbrc rFlag,bRun ; laeuft noch nicht
	reti ; doch, nicht neu starten
wird zunächst geprüft, ob der Ablauf schon begonnen hat. Wenn ja, kehrt der INT0 zurück. Wenn nein, dann setzen die folgenden Instruktionen
	in rSreg,SREG ; SREG retten
	sbr rFlag,(1<<bRun)|(1<<bSta) ; Flaggen
nach der Rettung des Statusregisters (das nachfolgende sbr verändert Flaggen!) beide Flaggen, bRun und bSta, im Flaggenregister rFlag in R18 gemeinsam. Die weitere Behandlung der Flaggen erfolgt nach dem sleep, denn der Prozessor ist jetzt um den Schlaf gebracht.

Senden: Senderegister kopiert Die Instruktionen
	ldi ZH,High(2*Datentabelle)
	ldi ZL,Low(2*Datentabelle)
	ldi XH,0 ; in Register 13 abwaerts
	ldi XL,14
StartOut2:
	lpm rmp,Z+ ; Tabellenbyte lesen
	st -X,rmp ; in Register
	cpi XL,7 ; schon bei 6 angekommen?
	brcc StartOut2 ; nein, weiter
	ser rToC ; Zaehler auf 0xFF
kopieren die Datentabelle aus dem Flash-Memory
Datentabelle:
.dw cWort4,cWort3,cWort2,cWort1
rückwärts in die Register R13 bis R6.

Senden: TC0-Interrupts einschalten
	ldi rCntL,Low(cTK1)
	ldi rmp,1<<OCIE0A ; Timer-Int ein
	out TIMSK0,rmp
hat den Compare-Match A Interrupt des Timers eingeschaltet. Noch ist aber der Ausgang PB0 inaktiv (High) und die IR-LED aus.

Der Interruptzähler wurde auf die inaktive Dauer des Kopfes gesetzt, was laut gavrasm-Symbol-Listing 4.000 ist:
	ldi rCntH,High(cTK1) ; erste Wartedauer
	ldi rCntL,Low(cTK1)
List of symbols:
Type nDef nUsed Decimalval  Hexvalue Name
  C     1     2        4000      FA0 CTK1


Senden: der erste TC0-Interrupt Das ist der Zähler, wenn der erste Interrupt ansteht. Der Zähler hat bereits weitergezählt, was durch die nötigen Schritte beim Interrupt verursacht ist (Ablegen der Ausführungsadresse auf den Stapel, Sprung zur Vektoradresse).

Senden: Zähler abwärts Die Instruktionen
	sbiw rCntL,1 ; CTC-Zaehler abwaerts, 9 Takte
	brne Tc0CAIsrRet ; Nicht Null, 10/11 Takte
zählen erst mal den Zähler in R25:R24 abwaerts und prüfen, ob der schon Null ist (Z-Flagge im SREG).

Senden: Interrupt-Wiederholzeit Wie vorhergeplant dauert die Zeit zwischen zwei Interrupts 12,5 µs.

Senden: PB0 torkelt Wäre das Torkeln schon eingeschaltet, was es aber noch nicht ist, würde beim Compare-Match-A der Ausgang PB0 getorkelt und die IR-LED für 12,5 µs lang angeschaltet. So macht man mit Prozessors 40 kHz und das ohne Zählschleifen und bei 2,4 MHz Takt.

Senden: Zeit bis zum ersten Timeout Das ist die Zeit, zu der der erste Timeout kommt: der Zähler hat Null erreicht und 50 ms sind vergangen. Die LED wäre bis dahin 2.000 mal ein- und 2.000 Mal ausgeschaltet worden, wenn denn der Ausgang aktiv geschaltet wäre.

Senden: zweite Phase Das ist nun die zweite Phase: eine gepulste LED für 5 ms lang (400 mal an, 400 mal aus).

Sende: dritte Phase In der dritten Phase bleibt die IR-LED für 2,5 ms aus.

Sende: vierte Phase In der vierten Phase wird die IR-LED wieder gepulst, und zwar für ca. 500 µs lang.

So geht es nun weiter bis alle Kopfbits und alle 64 Datenbits gesendet sind. Interessant ist noch, wie die einzelnen Datenbits ermittelt und gesendet werden.

Wenn alle Kopfbits gesendet sind, kommen die Datenbits dran.

Senden: Lade Kurz Zunächst laden die Instruktionen
	ldi rCntH,High(cKurz) ; lade kurzes Bit, 12 Takte
	ldi rCntL,Low(cKurz) ; 13
die Dauer einer kurzen Periode in den Zähler R25:R24.

Senden: Shift des letzten Datenbytes Dann schiebt
	lsl rData7 ; schiebe Bits, 14 Takte
rData4 in R14 um eine Position nach links, aus 0xAA wird 0x54. Das herausgeschobene linkeste Bit, eine Eins, landet in der Carry-Flagge im SREG (in diesem Fall eine Eins).
Senden: Rotieren der sieben Datenbytes Dann werden mit sieben Rotate-Left-Instruktionen
	rol rData6 ; 15 Takte
;       ...
	rol rData0 ; 21 Takte
jeweils alle Bytes nacheinander um eine Position nach links rotiert, wobei das Carry aus dem SREG jeweils an die Position 0 des neuen Bytes rotiert und Bit 7 des alten Bytes ins Carry wandert.

Sind alle sieben Bytes rotiert, befindet sich im Carry das niedrigste Bit aus rData0 (hier eine Null). Wäre das eine Eins, müsste der Zählerstand auf eine lange Dauer gesetzt werden (was aber bei diesem Durchlauf nicht so ist).

Man beachte, dass aus den ursprünglich im Zähler R25:R24 gesetzten 0x14 im Laufe der Schieberei schon 0x13 geworden sind, weil zwischendurch schon ein Compare-Match-A-Interrupt zugeschlagen hat.

Wer das alles linear und ohne Interrupts programmieren möchte, wird schon nach kurzer Zeit mit ganz grauen Haaren aufgeben. Und nie wieder so was anfassen.


Home Top IR Bedingt Hardware Messen Senden Empfang Schalter


12.6 Ein IR-Daten-Übertragungssystem

Das System besteht aus zwei Teilen:
  1. Einem IR-Sender mit einem ATtiny13, an den ein Potentiometer angeschlossen ist. Ändert sich der eingestellte Wert oder ist ein voreinstellbarer Zeitraum abgelaufen, dann sendet der ATtiny das 10-Bit-Messergebnis des ADC in einem 16-Bit-IR-Burst.
  2. Einem IR-Empfänger mit einem ATtiny24, an den eine LCD angeschlossen ist. Eingegangene IR-Bursts werden analysiert und bei korrektem Empfang das 10-Bit-Ergebnis in Prozent ausgegeben. Treten Fehler auf, werden entsprechende Fehlermeldungen ausgegeben.

12.6.1 Schaltbild des IR-Datensenders

Schaltbild Analogsender Das hier generiert die Sendesignale. Die IR-LED an OC0A produziert das Sendesignal, eine zusätzliche rote LED ist an, solange die LED sendet. Mit dem Taster lässt sich der Sendevorgang manuell auslösen. Am Potentiometer ist noch ein Folienkondesator mit 22 nF angeschlossen, weil die kleinen Spannungsschwankungen am ADC-Eingang sonst zu viele Sendevorgänge auslösen würden.

12.6.2 Bauteile

Kondensator 22 nF Das ist der Folienkondensator.

12.6.3 Aufbau

Aufbau IR-Sender Der Aufbau ist nicht sehr aufwändig.

12.6.4 Software

Die Software ist auf der obigen Vorlage aufgebaut, nicht benötigte Teile wurden entfernt. Sie ist im Folgenden aufgelistet (Zum Quellcode geht es hier). Es gibt sinnvoll nur die Zeit für die Auslösung des autarken Sendestart-Zeitraums zu verstellen (cAdc10min), diese Konstante steht jetzt auf 10 Sekunden und kann bis auf mehr als 3 Stunden erhöht werden.

;
; ****************************************
; * IR-Sender 40 kHz ATtiny13 Analogwert *
; * (C)2016 by www.gsc-elektronic.net    *
; ****************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; -------- Programmablauf -------
;
; Das Programm misst den Analogwert am
; Potieingang. Veraendert sich dieses
; um mehr als zwei Digits, sind mehr als
; 10 Minuten Wartezeit um oder wird die
; Taste gedrueckt, werden drei Kopfsig-
; nale und 16 Datenbitsignale ueber die
; IR-LED gesendet. Die unteren 10 Bit
; stammen aus dem AD-Wandler, die oberen
; 6 Bits stammen aus einer voreinge-
; stellten Kennung.
;
; -------- Register -------------
; frei: R0 .. R8
.def r10mL = R9 ; 10-Minuten-Zaehler
.def r10mM = R10
.def r10mH = R11
.def rData0 = R12 ; Senderegister, hoechstes
.def rData1 = R13 ; Senderegister, niedrigstes
.def rEor = R14 ; fuer Polaritaetsumkehr Toggle
.def rSreg = R15 ; Statusregister
.def rmp = R16 ; Vielzweck
.def rimp = R17 ; Vielzweck innerhalb Int
.def rFlag = R18 ; Flaggenregister
	.equ bRun = 0 ; Senden laeuft
	.equ bSta = 1 ; Senden starten
	.equ bTTo = 2 ; Timeout beim Senden
	.equ bRst = 3 ; Restart fuer zweiten Lauf
	.equ bAdc = 4 ; Analogwert fertig
.def rTOC = R19 ;  Timeout-Zaehler, zaehlt Signale
.def rAnaL = R20 ; Aktueller Analogwert
.def rAnaH = R21
.def rSntL = R22 ; letzter gesendeter Analogwert
.def rSntH = R23
.def rCntL = R24 ; Zaehlt CTC-Compares
.def rCntH = R25
; benutzt: XH:XL R27:R26
; frei: YH:YL R29:R28
; benutzt: ZH:ZL R31:R30
;
; -------- Ports und Pins -------------
.equ pOut = PORTB ; Ausgabe
.equ pDir = DDRB ; Richtung
.equ pIn  = PINB ; Eingang
.equ bIrO = PORTB0 ; IR-LED
.equ bIrD = DDB0
.equ bIrI = PINB0
.equ bLdO = PORTB2 ; Rote LED
.equ bLdD = DDB2
.equ bKyO = PORTB1 ; Taste, INT0
;
; -------- Konstanten und Timing ------
.equ cTakt = 2400000 ; Prozessortakt
.equ cTaktNs = 1000000000 / cTakt ; Takt ns
.equ cIRF = 40000 ; IR-Sendefrequenz Hz
.equ cIRNs = 1000000000 / cIRF / 2 ; IR-TX ns
.equ cCtc = (cIRNs+cTaktNs/2) / cTaktNs - 1
; 2,4 MHz: 30
;
; -------- Auto-Senden mit ADC --------
; Takt                2400000 Hz
; ADC-Prescaler           128
; ADC-Messzyklen           13
; ADC-Messfreqenz        1442 Hz
; Sekunden pro 10 Minuten 600
;.equ cAdc10min = 1442*600 ; drei Byte
.equ cAdc10min=10*1442
;
; -------- Aufbau IR-Signal -----------
; Alle Zeitangaben in us
; Anzahl Kopfbytes, 1 .. 4
.equ nKopf = 2 ; Anzahl Kopfpaare H/L
.equ cKopf = 2*nKopf-1 ; Anzahl Kopf-Signale
; Dauer der Kopfsignale (Dauer in 2*us/25)
.equ cTK1 = 2*50000/25 ; Dauer High 1
.equ cTK2 = 2*5000/25 ; Dauer Low 1
.equ cTK3 = 2*2500/25 ; Dauer High 2
.equ cTK4 = 2*500/25 ; Dauer Low 2
; Anzahl zu sendender Datenbits
.equ cBits = 16 ; Anzahl Bits
.equ cSign = cKopf + 2*cBits ; Anzahl Signale
; Dauer von Datenbits und Aktivperiode
.equ cKurz = 2*250/25 ; Dauer Binaer-Null
.equ cLang = 2*750/25 ; Dauer Binaer-Eins
.equ cPaus = 2*250/25 ; Dauer Aktivperiode
;
;  --- --- --- --- --- --- --- --- ... --- --- --- ... ---
; |TK1|TK2|TK3|TK4|B16|P16|B15|P15|...|B00|P00|TK1|...|P00|
;  --- --- --- --- --- --- --- --- ... --- --- --- ... ---
;IR Aus An Aus  An Aus  An Aus  An ... Aus  An Aus ...
; Millisekunden, alle Datenbits Null:
; 50,0    57,5    58,25   58,75    ...73,25   123,5
;     55,0    58,0    58,5     59,0       73,5     ... 147
; Millisekunden, alle Datenbits Eins:
; 50,0    57,5    58,75   59,75    ...88,75   139
;     55,0    58,0    59,0     60,0       89,0     ... 178
; Duty cycle = 2*(5 + 0,5 + 16*0,25)/2 = 9,5 ms, 6 bzw. 5%
; Stromverbr. LED: I = 39 mA, E = 39*9,5/1000/3600 = 0,11uAh
;                  I = 100mA, E = 100*9,5/1000/3600= 0,26uAh
;
; -------- Kennung ------ 
.equ cQuelle  = 0 ; Geraetenummer
.equ cZiel    = 0 ; Zielgeraet
.equ cKennung = (cQuelle<<5)|(cZiel<<3)
;
; -------- Reset- und Interruptvektoren ---
.CSEG
.ORG 0
	rjmp Start ; Programm-Init
	rjmp Int0Isr ; INT0 Tasteninterrupt
	reti ; PCINT0 Pin Change Int Request 0
	reti ; TIM0_OVF Timer/Counter Overflow
	reti ; EE_RDY EEPROM Ready
	reti ; ANA_COMP Analog Comparator
	rjmp TC0CAIsr ; TIM0_COMPA TC0 Compare A
	reti ; TIM0_COMPB TC0 Compare Match B
	reti ; WDT Watchdog Time-out
	rjmp AdcIsr ; ADC Conversion Complete
;
; -------- Interrupt Service Routinen -----
;
; INT0 Interrupt
; wird von negativen Flanken am Tastereingang
; ausgeloest
;
; Falls derzeit kein Sendevorgang aktiv ist
; werden die Flaggen bRun und bSta gesetzt.
;
Int0Isr: ; Tasteninterrupt, startet Senden
	sbrc rFlag,bRun ; laeuft noch nicht
	reti ; doch, nicht neu starten
	in rSreg,SREG ; SREG retten
	sbr rFlag,(1<<bRun)|(1<<bSta) ; Flaggen
;	cbi PORTB,PORTB2
	out SREG,rSreg ; SREG wieder herstellen
	reti ; zurueck
;
; CTC-Timeout Timer, IR-LED schalten
;
; CTC-Timeout Timer, IR-LED schalten
; wird von TC0 beim Compare Match A (CTC) aus-
; geloest
;
; Zaehlt den 16-Bit-Zaehler R25:R24 abwaerts.
; Erreicht es Null, wird die bTTO-Flagge ge-
; setzt und das Toggle-Bit des TC0 umgekehrt.
;
TC0CAIsr: ; 6 Takte f. Int und Vektor-rjmp
	in rSreg,SREG ; SREG retten, 7
	sbiw rCntL,1 ; CTC-Zaehler abwaerts, 9
	brne Tc0CAIsrRet ; Nicht Null, 10/11
	sbr rFlag,1<<bTTO ; Flagge setzen, 11
	in rimp,TCCR0A ; Toggle-Zustand, 12
	eor rimp,rEor ; umkehren, 13
	out TCCR0A,rimp ; neuer Toggle, 14
TC0CAIsrRet: ; 11/14
	out SREG,rSreg ; SREG herstellen, 12/15
	reti ; fertig, 16(!!)/19
;
; ADC Ready Interrupt
; wird nach abgeschlossener AD-Wandlung ausge-
; loest
;
; Liest das Resultat in zwei Register ein, setzt
; die bAdc-Flagge. Ist das Senden nicht aktiviert
; wird der AD-Wandler neu gestartet.
; 
AdcIsr:
	in rSreg,SREG ; Status retten
	in rAnaL,ADCL ; Ergebnis lesen
	in rAnaH,ADCH
	sbr rFlag,1<<bAdc ; Flagge setzen
	ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	sbrs rFlag,bRun ; nicht neu starten, wenn Flagge
	out ADCSRA,rimp ; neu starten
	out SREG,rSreg ; Status wiederherstellen
	reti
;
; --------- Programmstart, Init -------
Start:
	; Stapel einrichten
	ldi rmp,LOW(RAMEND)
	out SPL,rmp
	; Takt auf 2,4 MHz hochsetzen
	ldi rmp,1<<CLKPCE ; CLK-Enable
	out CLKPR,rmp
	ldi rmp,1<<CLKPS1 ; Teiler = 4
	out CLKPR,rmp
	; IR-LED-Port initiieren
	sbi pOut,bIrO ; LED aus
	sbi pDir,bIrD ; Pin ist Ausgang
	; Rote LED initiieren
	sbi pOut,bLdO ; LED aus
	sbi pDir,bLdD ; Pin ist Ausgang
	; Tastenport mit Pull-Up
	sbi pOut,bKyO ; Pullup an
	; Startwerte setzen
	clr rFlag ; Flaggen loeschen
	ldi rmp,1<<COM0A1 ; Toggle-Umkehr-Bit
	mov rEor,rmp ; in Exor-Register
	; TC0 als CTC starten
	ldi rmp,cCtc ; Compare A Wert 12,5us
	out OCR0A,rmp
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01)
	out TCCR0A,rmp ; Ausgang high, CTC
	ldi rmp,1<<CS00 ; Vorteiler = 1
	out TCCR0B,rmp
	; ADC starten
	ldi rmp,1<<MUX1 ; ADC2 zu messender Eingang
	out ADMUX,rmp
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; neu starten
	; INT0 und Schlafmodus
	ldi rmp,(1<<SE)|(1<<ISC01) ; Schlafen
	out MCUCR,rmp ; und INT0
	ldi rmp,1<<INT0 ; INT0-Interrupt
	out GIMSK,rmp
	; Interrupts
	sei ; Interrupts ermoeglichen
;
; -------- Programmschleife ----------
Schleife:
	sleep ; schlafen legen
	nop ; nach Aufwachen, 20
	sbrc rFlag,bTTo ; Timeout?, 21/22
	rcall Timeout ; Timeout bearbeiten, 24
	sbrc rFlag,bSta ; Startflagge
	rcall StartOut ; Senden starten
	sbrc rFlag,bAdc ; ADC-Flagge
	rcall AdcRdy ; ADC-Wert auswerten
	rjmp Schleife ; weiter schlafen
;
; -------- ADC-Wert auswerten --------
;
; Routine AdcRdy wertet das ADC-Ergebnis aus
; wird von der bAdc-Flagge ausgeloest
;
; Unterscheidet sich der eingelesene Wert vom
; vorherigen nur um +/- 2, wird kein Sendevor-
; gang ausgeloest. In diesem Fall wird der 10-
; Minuten-Zaehler erhoeht. Erreicht er den vor-
; eingestellten Wert von 10 Minuten wird der
; Sendevorgang gestartet.
;
AdcRdy:
	cbr rFlag,1<<bAdc ; ADC-Flagge loeschen
	mov ZH,rAnaH ; aktuellen Wert laden
	mov ZL,rAnaL
	adiw ZL,2 ; Toleranz +/- 2
	sub ZL,rSntL
	sbc ZH,rSntH
	brcs AdcRdyUngl ; starte senden
	cpi ZL,5 ; oberhalb Toleranz?
	brcc AdcRdyUngl ; starte senden
	; erhoehe Zehnminuten-Zaehler
	inc r10mL
	brne AdcRdy10m ; kein Ueberlauf
	inc r10mM
	brne AdcRdy10m ; kein Ueberlauf
	inc r10mH
AdcRdy10m:
	mov rmp,r10mL ; Zeit abgelaufen?
	cpi rmp,BYTE1(cAdc10min)
	brne AdcRdyRet
	mov rmp,r10mM
	cpi rmp,BYTE2(cAdc10min)
	brne AdcRdyRet
	mov rmp,r10mH
	cpi rmp,BYTE3(cAdc10min)
	brne AdcRdyRet
	; 10 Minuten abgelaufen
	clr r10mL ; Zaehler wieder loeschen
	clr r10mM
	clr r10mH
AdcRdyUngl:
	ldi rmp,(1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; ADC-Interrupts aus
	nop
	cbr rFlag,1<<bAdc ; Flagge loeschen
	sbr rFlag,(1<<bRun) ; Startflagge
	rjmp StartOut
AdcRdyRet:
	ret
;
; -------- CTC-Timeout --------------- 
;
; Routine Timeout
; wird von der Flagge bTTo ausgeloest
;
; Sendet nacheinander Kopf- und Datensignale
;
TimeOut: ; Flagge war gesetzt, 24
 	cbr rFlag,1<<bTTo ; Flagge loeschen, 25
	inc rTOC ; naechstes Zeichen, 26
	cpi rTOC,cKopf ; noch Kopf senden?, 27
	brcc TimeOut1 ; nein, Datenbits, 28/29
	ldi ZH,HIGH(TOK2) ; MSB Adresse Kopf, 29
	ldi ZL,LOW(TOK2) ; dto, LSB, 30
	mov rmp,rTOC ; 31
	lsl rmp ; mal zwei, 32
	add rmp,rTOC ; mal drei, 33
	add ZL,rmp ; zu Adresse, 34
	ldi rmp,0 ; Ueberlauf, 35
	adc ZH,rmp ; addieren, 36
	ijmp ; Sprung nach Z, 38
TimeOut1: ; 29
	cpi rTOC,cSign ; schon alle gesendet?, 30
	brcc TimeOutEnd ; ja, beenden, 31/32
	in rmp,TCCR0A ; Toggle/Set lesen, 32
	sbrc rmp,COM0A1 ; Toggle ein pruefen, 33/34
	rjmp TimeOut2 ; Toggle ist aus, 35
	; Toggle ist an
	ldi rCntH,High(cPaus) ; lade Pausendauer, 35
	ldi rCntL,Low(cPaus) ; 36
	ret ; fertig, 40
TimeOut2: ; 35
	ldi rCntH,High(cKurz) ; lade kurzes Bit, 36
	ldi rCntL,Low(cKurz) ; 37
	lsl rData1 ; Bit herausschieben, 38
	rol rData0 ; 39
	brcc TimeOut3 ; Bit ist Null, 40/41
	ldi rCntH,High(cLang) ; Dauer Eins, 41
	ldi rCntL,Low(cLang) ; 42
TimeOut3: ; 41
	ret ; fertig, 45/46
TimeOutEnd: ; 32
	sbrs rFlag,bRst ; Restart-Flagge gesetzt?, 33/34
	rjmp Restart ; nein, starte neu, 35
	clr rmp ; Timer-Int aus, 35
	out TIMSK0,rmp ; 36
	; Ausgang OCR0A auf Eins 
	ldi rmp,(1<<COM0A1)|(1<<COM0A0)|(1<<WGM01) ; 37
	out TCCR0A,rmp ; 38
	; gesendeten Wert aktualisieren
	mov rSntH,rAnaH ; nach Snt-Register kopieren, 39
	mov rSntL,rAnaL ; 40
	; Run-Flagge ausschalten
	cbr rFlag,(1<<bRun)|(1<<bRst) ; Flagge loeschen, 41
	; ADC wieder starten
	ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
	out ADCSRA,rmp ; neu starten, 43
	sbi pOut,bLdO ; rote LED aus, 45
	ret ; fertig, 49
;
; Timeout Kopf, Dauer einstellen
TOK2: ; 38
	ldi rCntH,High(cTK2) ; 39
	ldi rCntL,Low(cTK2) ; 40
	ret ; 44
;
	ldi rCntH,High(cTK3)
	ldi rCntL,Low(cTK3)
	ret
;
	ldi rCntH,High(cTK4)
	ldi rCntL,Low(cTK4)
	ret
;
; Erneuter Start, zweiter Burst
; wird nach dem Ende des Sendens ausgeloest
;
; Startet die Ausgabe erneut.
;
Restart:
	clr rmp ; Timer-Int ausschalten, 12
	out TIMSK0,rmp ; 13
	sbr rFlag,1<<bRst ; Restart-Flagge setzen, 14
	rjmp StartOut1 ; 16
;
; Senden starten
; wird von der gesetzten bSta-Flagge ausgeloest
;
; Kopiert den letzten Analogwert, verodert ihn
; mit der Kennung und schreibt ihn in die
; Senderegister rData1 und rData0.
; Setzt den rToC-Zaehler, laedt die erste in-
; aktive Pausendauer des ersten Kopfes und
; ermoeglicht den Compare-Match-A-Interrupt.
;
StartOut:
	cbr rFlag,(1<<bSta)|(1<<bRst) ; Start-Flagge loeschen, 15
	mov rSntH,rAnaH
	mov rSntL,rAnaL
Startout1:
	mov rData1,rAnaL ; LSB Analogwert senden, 16/17
	ldi rmp,cKennung ; Kennung zu MSB ; 17/18
	or rmp,rAnaH ; Verodern, 18/19
	mov rData0,rmp ; in Senderegister, 19/20
	ser rToC ; Zaehler auf 0xFF, 20/21
	ldi rCntH,High(cTK1) ; erste Wartedauer, 21/22
	ldi rCntL,Low(cTK1) ; 22/23
	ldi rmp,1<<OCIE0A ; Timer-Int ein, 23/24
	out TIMSK0,rmp ; 24/25
	cbi pOut,bLdO ; rote LED an, 25/26
	ret ; 29/30
;
; Ende Quellcode
;

12.6.5 Simulation der Software mit dem Studio

Simulation erster Kopf Das Senden der langen Kopfphase im Simulator dauert etwa die vorberechnete Anzahl Rechenzyklen und liegt innerhalb von Rundungsfehlern.

Simulation Kopf2 Die aktive Phase des ersten Kopfsignals liegt ebenfalls innerhalb von Rundungsfehlern, eine Phase wurde verpasst.

Simulation Kopf3 Die inaktive Phase des zweiten Kopfsignals ist ebenfalls um eine Phase länger als vorberechnet.

Simulation Kopf4 Wie aus der Analyse der Ausführungszeiten hervorgeht, ist auch bei diesem Signal die Dauer um eine Phase länger als berechnet. Wer es noch genauer haben will, zieht von den Kopfdaten eine Phase ab.

Simulation Einer-Bit Das Inaktiv-Signal eines langen Einer-Bits ist ebenfalls eine Phase länger als vorgesehen.

Simulation Null-Bit Auch beim Null-Bit ist eine Phase mehr als berechnet.

Simulation Aktive Phase Die Dauer des aktiven Signals ist ebenfalls geringfügig länger.

12.6.6 Hardware des IR-Datenempfängers

Der Empfang der Daten erfolgt mit der gleichen Hardware (ATtiny24, LCD, IR-Empfängermodul) wie die bisher durchgeführten Analysen.

12.6,7 Software IR-Datenempfänger Die Software (zum Quellcode geht es hier, kombiniert mit den LCD-Routinen) ist auf die Signalfolge des Senders eingestellt. Zum Teil ergaben sich erhebliche Abweichungen der Zeiten, die das Empfangsmodul liefert, von denen in der Sendersoftware. Es wurden daher Programmteile zur Diagnose hinzugefügt, die der Analyse dienen können (Schalter "Diagnose" mit den Einstellungen "1" oder "2").

;
; *************************************************
; * IR-Empfaenger mit ATtiny24 und LCD, Analog-RX *
; * (C)2016 by http://www.elektronic.net          *
; *************************************************
;
.NOLIST
.INCLUDE "tn24def.inc"
.LIST
;
; ---------- Programmablauf ------------
;
; Empfaengt die vom IR-Sender gesendeten Analog-
; daten (Kennung und Potentiometerstellung) und
; stellt sie auf der LCD dar.
;
; In den Diagnoseeinstellungen 1 und 2 werden
; die eingehenden Rohdaten des IR-Sensors in Hex
; dargestellt.
;
; ---------- Schalter ------------------
.equ Diagnose = 0 ; 0: Keine Diagnose
;                   1: Datenbytes in hex listen
;                   2: Alle Worte in hex auflisten
;
; ---------- Einstellungen Fernsteuerung
.equ cLang = 20000 ; Lange Pause
.equ cKopf = 2700 ; Dauer High-Signal Kopf us
.equ cNull = 512 ; Dauer Null-Byte us
.equ cEins = 1064 ; Dauer Eins-Byte us
;
; ---------- Timing --------------------
; Prozessortakt           1.000.000 Hz
; Zeit pro Takt                   1 µs
; Vorteiler TC1                   8
; Zeit pro TC1-Timer-Takt         8 µs
; Ueberlauf TC1 bei          65.536
; Zeit bis TC1 Ueberlauf    524,288 ms
.equ cPresc = 8 ; Timer 1 Prescaler
;
; ---------- Signaldauern und Toleranzen ---------
.equ nLang = cLang/cPresc ; Mindestanzahl Langsignalpausen
.equ nKopf = cKopf/cPresc ; Anzahl Zaehlsignale Kopf
.equ nNull = cNull/cPresc ; Anzahl Zaehlsignale Null 
.equ nEins = cEins/cPresc ; Anzahl Zaehlsignale Eins
.equ cToleranz = 20 ; Toleranzbreite in +/- %
.equ nKopfMin = nKopf-(nKopf*cToleranz+50)/100
.equ nKopfMax = nKopf+(nKopf*cToleranz+50)/100+1
.equ nNullMin = nNull-(nNull*cToleranz+50)/100
.equ nNullMax = nNull+(nNull*cToleranz+50)/100+1
.equ nEinsMin = nEins-(nEins*cToleranz+50)/100
.equ nEinsMax = nEins+(nEins*cToleranz+50)/100+1
;
; Alle Datenkonstanten ueberpruefen
.if nNullMin > 255
	.error "nNullMin zu gross!"
	.endif
.if nNullMax > 255
	.error "nNullMax zu gross!"
	.endif
.if nEinsMin > 255 
	.error "nEinsMin zu gross!"
	.endif
.if nEinsMax > 255
	.error "nEinsMax zu gross!"
	.endif
;
; ---------- Ports ---------------------
.equ pIrIn = PINA ; IR-Detektor-Port
.equ bIrIn = 0    ; IR-Detektor-Pin
;
; ---------- Register ------------------
; verwendet: R0 von LCD, Dezimalwandlung
; verwendet: R1 Dezimalwandlung
; frei: R2..R5
.def rErrL = R6
.def rErrH = R7
.def rMul1 = R8 ; Multiplikation
.def rMul2 = R9
.def rMul3 = R10
.def rMulH = R11
.def rFlags = R12 ; Flaggenspeicher
.def rDataL = R13 ; Empfangsdaten
.def rDataH = R14
.def rSreg = R15 ; Sicherung Status
.def rmp   = R16 ; Vielzweckregister
.def rmo   = R17 ; Vielzweckregister
.def rLine = R18 ; LCD-Zeilenzaehler
.def rLese = R19 ; LCD-Register
.def rimp  = R20 ; Interrupt-Vielzweck
.def rFlag = R21 ; Flaggenregister
	.equ bSta = 0 ; Startflagge
	.equ bKpf = 1 ; Kopf korrekt
	.equ bKKu = 2 ; Kopf zu kurz
	.equ bKKl = 3 ; Kopf zu lang
	.equ bDKu = 4 ; Datenbit zu kurz
	.equ bDMi = 5 ; Datenbit mittellang
	.equ bDLa = 6 ; Datenbit zu lang
	.equ bDOv = 7 ; Anzahl Datenbits falsch
.def rDCtr = R22 ; Datenbitzaehler
; frei: R23 .. R25
; verwendet: XH:XL R27:R26 diverse Verwendungen
; verwendet: YH:YL R29:R28 Zaehler Signaldauer
; verwendet: ZH:ZL R31:R30 diverse Verwendungen
;
; ---------- Diagnose-SRAM-Puffer ------
.DSEG
.ORG 0x0060
.if Diagnose == 1
	Buffer:
	.byte 16
	Bufferende:
	.endif
.if Diagnose == 2
	Buffer:
	.byte 36
	Bufferende:
	.endif
;
; ---------- Reset- und Interrupts -----
.CSEG ; Code-Segment
.ORG 0 ; an den Beginn
	rjmp Start ; Reset-Vektor, Init
	reti ; INT0 External Interrupt Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Int Req 0
	reti ; PCINT1 Pin Change Interrupt Req 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT Timer/Counter1 Capture Event
	reti ; TIM1_COMPA Timer/Counter1 Compare Match A
	reti ; TIM1_COMPB Timer/Counter1 Compare Match B
	rjmp Tc1Isr ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA Timer/Counter0 Compare Match A
	reti ; TIM0_COMPB Timer/Counter0 Compare Match B
	reti ; TIM0_OVF TC0 Overflow
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
;
; ---------- Interrupt Service -----
;
; PCINT Interrupt
;   wird von allen Pegelwechseln des IR-Sensors
;   ausgeloest
;
; Liest bei aktiven IR-Signalen den Zaehlerstand
; des TC1 aus und setzt diesen auf Null zurueck.
; Ueberschreitet die Dauer eine Obergrenze, wird
; die Flagge bSta gesetzt und dadurch ein Neu-
; start ausgeloest (langes Kopfsignal).
; Ist ein Kopfsignal eingegangen und ist das
; Signal
; a) kuerzer als die Bitdauer fuer die Unter-
;    grenze von Null (nNullMin), wird die bDKu-
;    Flagge gesetzt,
; b) laenger als die Maximalgrenze fuer Nullen
;    (nNullMax), aber kuerzer als die Minimal-
;    grenze fuer Einsen (nEinsMin), wird die
;    bDMi-Flagge gesetzt,
; c) laenger als die Maximalgrenze fuer Einsen
;    nEinsMax), wird die bDLa-Flagge gesetzt,
; d) innerhalb der Grenzen fuer eine Null, wird
;    eine Null in das Ergebnis einrotiert,
; e) innerhalb der Grenzen fuer eine Eins wird
;    eine Eins in das Ergebnis einrotiert.
; Sind mehr als 16 Datenbits empfangen, wird die
; bDOv-Flagge gesetzt.
; Sind Diagnosemodi gewaehlt, werden die Daten
; im SRAM-Puffer abgelegt.
;
Pci0Isr:
	sbis pIrIn,bIrIn ; ueberspringe bei Eins, 1/2
	reti ; 5
	; Eingang ist Low, war vorher High
	in YL,TCNT1L ; Zaehlerstand lesen,3
	in YH,TCNT1H ; 4
	ldi rimp,0 ; Zaehlerstand ruecksetzen, 5
	out TCNT1H,rimp ; 6
	out TCNT1L,rimp ; 7
	ldi rimp,1<<TOIE1 ; Zaehler-Int starten
	out TIMSK1,rimp
	in rSreg,SREG ; Status sichern
	cpi YH,High(nLang) ; Lange Pause?
	brcs Pci0Isr1 ; Signal war kuerzer
	ldi rFlag,1<<bSta ; Startflagge setzen
.if diagnose != 0
	ldi XH,High(Buffer) , Neustart Puffer
	ldi XL,Low(Buffer)
	.if diagnose == 2
		st X+,YH ; in SRAM
		st X+,YL
		.endif
	.endif
	rjmp Pci0IsrRet
Pci0Isr1: ; Signal nicht langer Kopf
	sbrs rFlag,bSta ; Startflagge?
	rjmp Pci0IsrRet ; Warten bis Start
	sbrc rFlag,bKpf ; Kopfflagge?
	rjmp Pci0Isr2 ; Schon korrekt
.if diagnose == 2
	st X+,YH ; in SRAM
	st X+,YL
	.endif
	subi YL,LOW(nKopfMin) ; - min Kopfdauer
	sbci YH,HIGH(nKopfMin)
	brcs Pci0IsrErrKk ; Kopf zu kurz
	subi YL,LOW(nKopfMax) ; - max Kopfdauer
	sbci YH,HIGH(nKopfMax)
	brcc Pci0IsrErrKl ; Kopf zu lang
	sbr rFlag,1<<bKpf ; Kopf ist ok
	clr rDCtr ; Datenbitzaehler starten
	clr rDataL ; Daten leer
	clr rDataH
	rjmp Pci0IsrRet
Pci0Isr2: ; Kopf war korrekt, pruefe Bits
.if Diagnose == 1
	tst YH ; MSB Null?
	breq Pci0Isr2d ; ja
	ldi YL,0xFF ; signalisiere Ueberlauf
Pci0Isr2d:
	st X+,YL ; in SRAM
	rjmp Pci0IsrRet
	.endif
.if Diagnose == 2
	st X+,YH ; MSB und
	st X+,YL ; LSB in SRAM
	rjmp Pci0IsrRet
	.endif
	cpi YL,LOW(nNullMin) ; min Nulldauer
	brcs Pci0IsrErrDKu ; kuerzer als Null
	cpi YL,LOW(nNullMax) ; max Nulldauer
	brcc Pci0Isr3 ; auf Eins pruefen
	clc ; Carry Null
	rjmp Pci0Isr4 ; in Ergebnis schieben
Pci0Isr3:
	cpi YL,Low(nEinsMin) ; min Einsdauer
	brcs Pci0IsrErrDMi ; mittellang
	cpi YL,Low(nEinsMax) ; max Einsdauer
	brcc Pci0IsrErrDLa ; zu lang
Pci0Isr4:
	rol rDataL ; in Ergebnis rollen 
	rol rDataH
	inc rDCtr ; Anzahl Bits
	cpi rDCtr,17 ; Datenbits ok?
	brcs Pci0IsrRet ; ja
	sbr rFlag,1<<bDOv ; Zu viele Bits 
	rjmp Pci0IsrErrData ; Fehler
Pci0IsrErrKk:
	sbr rFlag,(1<<bKpf)|(1<<bKKu) ; Kopf zu kurz
	ldi rmp,Low(nKopfMin) ; Original herstellen
	add YL,rmp
	ldi rmp,High(nKopfMin)
	adc YH,rmp
	rjmp Pci0IsrErrData ; Fehler
Pci0IsrErrKl:
	sbr rFlag,(1<<bKpf)|(1<<bKKl) ; Kopf zu lang
	ldi rmp,Low(nKopfMin+nKopfMax) ; Original
	add YL,rmp ; wieder herstellen
	ldi rmp,High(nKopfMin+nKopfMax)
	adc YH,rmp
	rjmp Pci0IsrErrData ; Fehler
Pci0IsrErrDKu:
	sbr rFlag,1<<bDKu ; Datenbit zu kurz
	rjmp Pci0IsrErrData ; Fehler
Pci0IsrErrDMi:
	sbr rFlag,1<<bDMi ; Datenbit mittellang
	rjmp Pci0IsrErrData ; Fehler
Pci0IsrErrDLa:
	sbr rFlag,1<<bDLa ; Datenbit zu lang
Pci0IsrErrData:
	lsl YL ; N mal 8
	rol YH
	lsl YL
	rol YH
	lsl YL
	rol YH
	mov rErrL,YL ; in Fehlerregister
	mov rErrH,YH
Pci0IsrRet:
	out SREG,rSreg ; Status wieder herstellen
	reti ; fertig
;
; TC1-Overflow Interrupt Service Routine
;   wird von Ueberlaeufen des TC1 ausge-
;   loest
;
; Ueberlauf erfolgt nach 8*65.536 = 0,52
; Sekunden nach dem letzten aktiven IR-
; Sensor-Signal.
; Falls ein aktives Kopfsignal empfangen
; wurde, wird die Ausgabeflagge T im SREG
; gesetzt.
;
Tc1Isr:
	sbrs rFlag,bSta ; pruefe Startbit
	rjmp Tc1Isr1
	sbrc rFlag,bKpf ; pruefe Kopfbit
	set ; setze Ausgabeflagge
Tc1Isr1:
	reti
;
; ---------------- Start, Init --------------
Start:
	ldi rmp,LOW(RAMEND) ; Stapel auf Ramende
	out SPL,rmp ; in Stapelzeiger
	; I/O initiieren
	; LCD-Port-Ausgaenge initiieren
	ldi rmp,(1<<bLcdCRE)|(1<<bLcdCRRS)|(1<<bLcdCRRW)
	out pLcdCR,rmp ; Kontrollport-Ausgaenge
	clr rmp ; Ausgaenge aus
	out pLcdCO,rmp ; an Kontrollport
	ldi rmp,mLcdDRW ; Datenport-Ausgabemaske, Schreiben
	out pLcdDR,rmp ; auf Richtungsregister Datenausgabeport
	; LCD initiieren
	rcall LcdInit ; Init der LCD
	ldi ZH,HIGH(2*LcdStart) ; Start-Text ausgeben
	ldi ZL,LOW(2*LcdStart)
	rcall LcdText
	; Startwert fuer Flaggen
	clr rFlag
	clt
	; Timer 1 initiieren, freilaufend durch 8
	ldi rmp,1<<CS11 ; Prescaler durch 8
	out TCCR1B,rmp ; an Kontrollregister B
	; PCINT0 fuer IR-Rx
	ldi rmp,1<<PCINT0 ; Pin 0 Level change INTs
	out PCMSK0,rmo ; in Maske 0
	ldi rmp,1<<PCIE0 ; Interrupt PCINT0 ermoeglichen
	out GIMSK,rmp
	; Sleep Enable
	ldi rmp,1<<SE ; Sleep Mode Idle
	out MCUCR,rmp ; in MCU Kontrollregister
	; Interrupts ermoeglichen
	sei ; I-Flagge Statusregister
Schleife:
	sleep ; schlafen legen
	nop ; aufwachen
	brtc Schleife ; Update-Flagge auswerten
	rcall Update ; Flagge behandeln
	rjmp Schleife ; schlafen legen
;
; Routine UpDate
;   wird von einem gesetzten T-Flag im
;   SREG ausgeloest und gibt die Ergeb-
;   nisse und eventuelle Fehlermeldun-
;   gen auf der LCD aus. Bei den Diag-
;   semodi werden die im SRAM gespei-
;   cherten Empfangsdaten ausgegeben.
;
Update:
	clt ; Flagge wieder abschalten
	clr rmp ; Interrupts Timer aus
	out TIMSK1,rmp ; in Interruptmaske
	mov rmp,rFlag ; Fehlerflaggen
	andi rmp,0xFC ; isolieren
	mov rFlags,rmp ; zwischenspeichern
	ldi rFlag,0 ; Flaggen fuer Neustart
.if Diagnose == 1
	rjmp Diag ; gib Diagnose aus
	.endif
.if Diagnose == 2
	rjmp DiagW ; gib Diagnose aus
	.endif
	brne UpdateErr ; Fehlerflaggen
	cpi rDCtr,16 ; 16 Bit empfangen?
	breq UpdateKorrekt ; ja, Ausgabe
	rjmp UpdateZuWenigBits ; Ausgabe Fehler
UpdateKorrekt:
	ldi ZH,2 ; Position LCD setzen
	clr ZL
	rcall LcdPos
	ldi rmp,'R' ; Ausgabe R:
	rcall LcdD4Byte
	ldi rmp,':'
	rcall LcdD4Byte
	ldi ZH,2 ; Position LCD setzen
	ldi ZL,2
	rcall LcdPos
	rcall Multi ; Ergebnis multiplizieren
	rjmp Prozent ; als Prozentzahl ausgeben
UpdateErr:
	rcall LcdLine3 ; ab Zeile 3 loeschen
	ldi ZH,HIGH(2*LcdAusgabe)
	ldi ZL,LOW(2*LcdAusgabe)
	rcall LcdTextC
	rcall LcdLine3
	ldi rmp,'E' ; E: ausgeben
	rcall LcdD4Byte
	ldi rmp,':'
	rcall LcdD4Byte
	ldi ZH,HIGH(2*ErrKKu) ; Fehlerausgaben
	ldi ZL,LOW(2*ErrKKu)
	mov rmp,rFlags ; Fehlerflaggen
	sbrc rmp,bKKu
	rjmp Update1
	ldi ZH,HIGH(2*ErrKLa)
	ldi ZL,LOW(2*ErrKLa)
	sbrc rmp,bKKl
	rjmp Update1
	ldi ZH,HIGH(2*ErrDKu)
	ldi ZL,LOW(2*ErrDKu)
	sbrc rmp,bDKu
	rjmp Update1
	ldi ZH,HIGH(2*ErrDMi)
	ldi ZL,LOW(2*ErrDMi)
	sbrc rmp,bDMi
	rjmp Update1
	ldi ZH,HIGH(2*ErrDLa)
	ldi ZL,LOW(2*ErrDLa)
	sbrc rmp,bDLa
	rjmp Update1
	ldi ZH,HIGH(2*ErrDBi)
	ldi ZL,LOW(2*ErrDBi)
Update1:
	rcall LcdTextC ; Fehlertext ausgeben
	ldi rmp,'='
	rcall LcdD4Byte
	rjmp DezimalY ; Y dezimal ausgeben
;
UpdateZuWenigBits: ; Anzahl Bits ausgeben
	rcall LcdLine4
	ldi rmp,'B' ; B: ausgeben
	rcall LcdD4Byte
	ldi rmp,':'
	rcall LcdD4Byte
	rcall DezimalAus ; Anzahl Bits dezimal
	ldi rmp,' '
	rcall LcdD4Byte
	ldi rmp,'b'
	rcall LcdD4Byte
	ldi rmp,'i'
	rcall LcdD4Byte
	ldi rmp,'t'
	rjmp LcdD4Byte
;
; Zahl in rDataH:rDataL in % umwandeln
.equ cMulti = 1000*256/1023 ; mal 1000/1023
;
; Multi nimmt die empfangene 10-Bit-Zahl mit
; 256 * 1000 / 1023 = 250 mal.
;
Multi:
	mov rmp,rDataH ; oberste Bits loeschen
	andi rmp,0x03 ; Ziel/Quelle loeschen
	mov rDataH,rmp ; und zurueckschreiben
	clr rMul1 ; Ergebnis loeschen
	clr rMul2
	clr rMul3
	clr rMulH ; Hilfsregister loeschen
	ldi rmp,cMulti ; Multiplikator in rmp
Multi1:
	lsr rmp ; rmp rechts schieben
	brcc Multi2 ; kein Carry, nicht addieren
	add rMul1,rDataL ; addieren
	adc rMul2,rDataH
	adc rMul3,rMulH
Multi2:
	lsl rDataL ; Multiplikator links schieben
	rol rDataH
	rol rMulH
	tst rmp ; Ende Multiplikation?
	brne Multi1 ; weiter multiplizieren
	ret
;
; Zahl in rMul3:rMul2 in Dezimal auf LCD ausgeben
; Prozent wandelt das Ergebnis in Dezimal-
; format um und gibt es im Format nnn,n% auf
; der LCD aus.
;
Prozent:
	ldi ZH,HIGH(2*Dezimal) ; Dezimaltabelle
	ldi ZL,LOW(2*Dezimal)
	clt ; fuehrende Nullen unterdruecken
Prozent1:
	lpm XL,Z+ ; Dezimalzahl laden
	lpm XH,Z+
	tst XL ; Ende Tabelle feststellen
	breq Prozent5 ; letzte Stelle
	clr rmp ; rmp ist Zaehler
Prozent2:
	sub rMul2,XL ; Dezimalzahl abziehen
	sbc rMul3,XH
	brcs Prozent3 ; Carry aufgetreten
	inc rmp ; hoch zaehlen
	rjmp Prozent2 ; weiter abziehen
Prozent3:
	add rMul2,XL ; Abziehen rueckgaengig
	adc rMul3,XH
	tst rmp ; Null?
	brne Prozent4 ; nein, ausgeben
	brts Prozent4 ; Fuehrende Nullen aus
	cpi XL,10 ; Dezimaltrennstelle?
	brne Prozent1 ; nein, weiter
	set ; Fuehrende Nullen ausgeben
Prozent4:
	subi rmp,-'0' ; in Dezimal ASCII
	rcall LcdD4Byte
	cpi XL,10 ; Dezimaltrennstelle?
	brne Prozent1 ; nein
	ldi rmp,',' ; Dezimaltrennstelle
	rcall LcdD4Byte
	rjmp Prozent1 ; weiter
Prozent5:
	clt ; T-Flagge loeschen
	mov rmp,rMul2 ; letzte Dezimalstelle
	subi rmp,-'0' ; in ASCII
	rcall LcdD4Byte
	ldi rmp,'%' ; Prozentzeichen
	rcall LcdD4Byte
	ldi rmp,' '
	rjmp Lcd4Byte
;
; Gibt die Anzahl empfangener Datenbits rDCtr
; als Byte in dezimal aus
;
DezimalAus:
	ldi ZH,HIGH(2*Dezimal100) ; Hunderter
	ldi ZL,LOW(2*Dezimal100)
	clt ; Fuehrende Nullen unterdruecken
DezimalAus1:
	lpm XL,Z+ ; Dezimalzahl laden
	lpm XH,Z+
	tst XL ; Ende der Tabelle?
	breq DezimalAus5 ; ja, letzte Stelle
	clr rmp ; rmp ist Zaehler
DezimalAus2:
	sub rDCtr,XL ; Dezimalzahl abziehen
	brcs DezimalAus3 ; carry, fertig
	inc rmp ; naechste Subtraktion
	rjmp DezimalAus2 ; und weiter abziehen
DezimalAus3:
	add rDCtr,XL ; letzte Abziehen rueckgaengig
	tst rmp ; Fuehrende Null?
	brne DezimalAus4 ; nein, ausgeben
	brts DezimalAus4 ; fuehrende Null ist aus
	rjmp DezimalAus1 ; weiter umwandeln
DezimalAus4:
	subi rmp,-'0' ; in Dezimal-ASCII
	rcall LcdD4Byte
	set ; Fuehrende Nullen ausgeben
	rjmp DezimalAus1 ; und weiter
DezimalAus5:
	clt ; T-Flagge loeschen
	mov rmp,rDCtr ; Letzte Stelle
	subi rmp,-'0' ; in ASCII
	rjmp LcdD4Byte
;
; Gibt die Dauer des letzten fehlerhaft empfan-
; genen Datenbits in rErrH:rErrL in Mikrosekun-
; den dezimal aus
;
DezimalY:
	ldi ZH,HIGH(2*Dezimal) ; Z auf Tabelle
	ldi ZL,LOW(2*Dezimal)
	clt ; Fuehrende Nullen unterdruecken
DezimalY2:
	lpm XL,Z+ ; Dezimalzahl aus Tab holen
	lpm XH,Z+
	tst XL ; Ende Tabelle?
	breq DezimalY6 ; ja, letzte Stelle
	clr rmp ; rmp ist Zaehler
DezimalY3:
	sub rErrL,XL ; Error-Y subtrahieren
	sbc rErrH,XH
	brcs DezimalY4 ; carry, fertig
	inc rmp ; noch mal subtrahieren
	rjmp DezimalY3 ; weiter subtrahieren
DezimalY4:
	add rErrL,XL ; Subtraktion rueckgaengig
	adc rErrH,XH
	tst rmp ; Ergebnis Null?
	brne DezimalY5 ; nein
	brts DezimalY5 ; fuehrende Nullen aus?
	rjmp DezimalY2 ; weiter
DezimalY5:
	set ; fuehrende Nullen nicht unterdruecken
	subi rmp,-'0' ; in ASCII
	rcall LcdD4Byte
	rjmp DezimalY2 ; weiter umwandeln
DezimalY6:
	clt ; Loesche T-Flagge
	mov rmp,rErrL ; letzte Dezimalstelle
	subi rmp,-'0' ; in ASCII
	rcall LcdD4Byte
	ldi rmp,' '
	rjmp LcdD4Byte
;
; Dezimaltabelle
Dezimal:
.dw 10000
.dw 1000
Dezimal100:
.dw 100
.dw 10
.dw 0
;
; Starttext LCD
LcdStart:
.db "IR-Datenempfang tn24",0x0D,0xFF
.db " gsc-elektronic.net ",0x0D,0xFF
LcdAusgabe:
.db "                    ",0x0D,0xFF
.db "                    ",0xFE,0xFF
;
; Fehlertexte
ErrKKu:
.db "Kopf zu kurz",0xFE,0xFE
ErrKLa:
.db "Kopf zu lang",0xFE,0xFE
ErrDKu:
.db "DBit zu kurz",0xFE,0xFE
ErrDMi:
.db "DBit Mittel",0xFE
ErrDLa:
.db "DBit zu lang",0xFE,0xFE
ErrDBi:
.db "DBits > 16",0xFE,0xFE
;
.if Diagnose == 1 ; Datenbyte Ausgabe
Diag:
	ldi rmp,0x01 ; Display loeschen
	rcall LcdC4Byte
	ldi XH,High(Buffer) ; Anfang Puffer
	ldi XL,Low(Buffer)
Diag1:
	ld rmp,X+ ; Pufferbyte
	rcall HexOut ; in Hex ausgeben
	ldi rmp,' '
	rcall LcdD4Byte
	cpi XL,Low(Buffer+6) ; Zeile 2?
	brne Diag1a
	rcall LcdLine2 ; Zeile 2
	rjmp Diag2
Diag1a:
	cpi XL,Low(Buffer+12) ; Zeile 3?
	brne Diag2
	rcall LcdLine3 ; Zeile 3
Diag2:
	cpi XL,Low(Bufferende) ; fertig?
	brne Diag1 ; nein, weiter
	ret
	.endif
;
.if Diagnose != 0 ; Hexziffern ausgeben
HexOut:
	push rmp ; rmp retten
	swap rmp ; oberes und unteres Nibble
	rcall HexOutNibble ; Nibble ausgeben
	pop rmp ; rmp wieder herstellen
HexOutNibble:
	andi rmp,0x0F ; unteres Nibble isolieren
	subi rmp,-'0' ; in ASCII
	cpi rmp,'9'+1 ; A..F?
	brcs HexOutNibble1 ; nein
	subi rmp,-7 ; in A..F
HexOutNibble1:
	rjmp LcdD4Byte ; Zeichen ausgeben
	.endif
;
.if Diagnose == 2 ; Woerter ausgeben
DiagW:
	ldi rmp,0x01 ; LCD loeschen
	rcall LcdC4Byte
	ldi XH,HIGH(Buffer) ; X auf Puffer
	ldi XL,LOW(Buffer)
	ldi ZH,0 ; Zeilenzaehler
	ldi ZL,0 ; Spaltenzaehler
DiagW1:
	ld rmp,X+ ; MSB lesen
	tst rmp ; MSB Null?
	brne DiagWW ; nein, als Wort
	cpi ZL,19 ; noch Platz in der Zeile?
	brcs DiagW2 ; ja, gib aus
	rcall NextLine ; naechste Zeile
DiagW2:
	ld rmp,X+ ; lese LSB
	rcall HexOut ; gib in Hex aus
	subi ZL,-2 ; zwei Zeichen addieren
	rjmp DiagW3 ; weiter
DiagWW:
	cpi ZL,16 ; noch Platz in der Zeile?
	brcs DiagWW1 ; ja
	rcall NextLine ; naechste Zeile
DiagWW1:
	rcall HexOut ; gib MSB aus
	ld rmp,X+ ; lese LSB
	rcall HexOut ; gib LSB aus
	subi ZL,-4 ; vier Zeichen addieren
DiagW3:
	cpi ZL,20 ; Ende der Zeile?
	brcc DiagW4 ; ja
	ldi rmp,' ' ; Leerzeichen ausgeben
	rcall LcdD4Byte
	subi ZL,-1 ; ein Zeichen addieren
DiagW4:
	cpi XL,Low(Bufferende) ; Ende Puffer?
	brcs DiagW1 ; nein, weiter
	ret
;
NextLine: ; Zeilenvorschub LCD
	push rmp ; rmp erhalten
	inc ZH ; naechste Zeile
	clr ZL ; Zeilenfang
	rcall LcdPos
	pop rmp ; rmp wieder herstellen
	ret
	.endif
;
; LCD-Routinen einlesen
.include "Lcd4Busy.inc"
;
; Ende Quelltext
;

Neue Instruktion hier ist:

12.6.8 Anwendung

Datenempfang So sieht der Empfang aus.


Home Top IR Bedingt Hardware Messen Senden Empfang Schalter


12.7 Ein Dreikanal-IR-Empfänger mit Schaltern

12.7.1 Aufgabe

Wer wünscht sich das nicht: mit der IR-Fernsteuerung Geräte ein- und ausschalten? Die Zeit des Spezialistentums, wo der Experte sich erst mal tagelang mit den Codes seiner Fernsteuerung beschäftigt, sind vorbei. Diese folgende Fernsteuerung lernt selbstständig, welche Codes zum Schalten verwendet werden sollen. Dazu wird sie einmal trainiert, speichert ihre gelernten Codes im internen EEPROM und ruft sie daraus ab. Bis zur nächsten Umprogrammierung.

12.7.2 Schaltbilder Dreikanalempfänger

Die Hardware gibt es in zwei Versionen:

Aktiv-Low-Schaltung Zunächst die Version mit dem ATtiny13 mit 5V-Relais-Schaltstufen. Verbraucht das Relais weniger als 50 mA Schaltstrom, kann auch auf den Treibertransistor verzichtet werden. Der 1k-Widerstand dient dazu, den TSOP vom Programmierpin der ISP-Schnittstelle zu entkoppeln. Der Taster an PB3 dient beim Einschalten dazu, den Lern- oder Adjust-Modus anzuwerfen.

Aktiv-High-Schaltung Selbige Schaltung, nun mit Aktiv-High-Ausgangstreiber. Der kann mit beliebigen Relaisspannungen arbeiten.

Tiny24-Schaltstufe Die Platzierung der Ausgangskanäle ist erkennbar. PA1 dient beim Programmstart auch zur Abfrage des Tasters, ob die Neuprogrammierung der Erkennungswerte erfolgen soll. Beim Design der Treiberstufe für diesen Kanal ist zu beachten, dass der interne Pull-Up nur etwa 50 kΩ beträgt und die LED und der Basisstrom den Eingang nicht zu stark auf Low aussteuern dürfen, weil sonst die Schaltung beim Starten immer in den Lern- oder Adjust-Modus gehen würde.

12.7.3 Bauteile

Der TSOP-IR-Empfänger wurde bereits oben beschrieben. Der 1k-Widerstand hat die Ringe braun-schwarz-rot-gold.

Die Bauteile der Treiberstufen sind nicht in der Hardwareliste angegeben, da sie zu eng mit dem Verwendungszweck des Geräts zu tun haben.

12.7.4 Aufbau

Aufbau tn13 Das ist der Aufbau der Schaltung mit dem ATtiny13. Angeschlossen sind drei farbige LED, um den Erfolg zu kontrollieren.

Aufbau tn24 Das gleiche mit dem ATtiny24.

12.7.5 Besonderheiten und Struktur des Programmes

Das Programm tut weitgehend dasselbe, egal ob es auf einem ATtiny13 oder einem ATtiny24 läuft. Auf einem Atiny24 mit angeschlossener LCD wurde die entsprechenden Ausgaberoutinen daher mit einem Schalter eingefügt. Auch die Reset- und Interruptvektoren sind natürlich typspezifisch. Da der ATtiny13 keinen 16-Bit-Zähler hat, wird die Ermittlung der Signaldauer mit dem 8-Bit-Timer TC0 durchgeführt. Für lange Signale wurde noch ein überlaufgesteuerter MSB-Zähler hinzugefügt.

Im Unterschied zu den anderen Experimenten in dieser Lektion arbeitet der Zähler in dieser Version mit einem Vorteiler durch 64, so dass jeder Timer-Tick beim Tiny13 53, beim Tiny24 64 µs entspricht. Diese Auflösung erwies sich als hinreichend geeignet.

Um die Genauigkeit bei der Erkennung von Nullen und Einsen noch zu erhöhen, wird die Dauer von jeweils 16 Signalen gemittelt. Dazu werden diese in einem Puffer im SRAM gespeichert, nach Ende des Bursts aufsummiert und durch 16 geteilt. Der jeweils letzte ermittelte Wert wird verwendet und zusammen mit den erkannten Tastencodes gespeichert.

Die Erkennung der Tasten erfolgt mittels der letzten übertragenen 16 Bits. Das hat sich ebenfalls als hinreichend erwiesen.

Die Messung und Erkennung des IR-Signals erfolgt nahezu vollständig in der INT0- (ATtiny13) bzw. der PCINT0- (ATtiny24) Interrupt-Service-Routine. Die ist bis auf zwei Instruktionen zu Beginn für beide gleich. Der Ablauf der ISR ist wie folgt: Die Ermittlung, wann alle Datenbits übertragen sind, erfolgt nach einem einstellbaren Zeitraum, nachdem der Timer nicht mehr von übertragenen IR-Signalen zurückgesetzt wird und das MSB des Timers den eingestellten Wert überschreitet. Erst dann wird eine Flagge gesetzt.

Die weitere Verarbeitung der empfangenen Bitkombinationen erfolgt im Hauptprogramm. Ist der Lernmodus aktiv, dann Im normalen Betriebsmodus werden eingehende Bitkombinationen auf Übereinstimmung mit den gespeicherten Kombinationen verglichen und, bei Übereinstimmung, der betreffende Kanal ein- oder ausgeschaltet.

Folgende Bedingungen führen dazu, dass das Programm zu Beginn in den Lernmodus geht: Beim EEPROM ist noch zu beachten, dass durch das Erase beim Programmieren des Maschinencodes auch das EEPROM geleert (mit 0xFF überschrieben) wird.

EESave-Fuse Dieses Verhalten kann man ändern, indem man die EESave-Fuse setzt. Dann wird beim Erase nicht der Inhalt des EEPROM überschrieben.

12.7.6 Gemessene IR-Fernsteuercodes

Der Inhalt des EEPROMs kann mit den Programmiertools des Studios jederzeit ausgelesen werden. Man erhält dann Dateien mit der Endung .hex, die folgendermaßen aussehen:

:100000001108F5C8B5881528754895A80DFF7FFF1C
:10001000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0
:10002000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE0
:10003000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFD0
:10004000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC0
:10005000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB0
:10006000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA0
:10007000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF90
:00000001FF

Die Bytes des EEPROM beginnen mit "1108...", das letzte Byte ("1C") ist eine Prüfsumme. (in Hex)
GerätRotGelbGrünNull/
Eins
EinAusEinAusEinAus
HDR0805C8C5888528254528A8A511
TV20DF10EFA05F906F609F50AF12
Kamera41BE619E817EE11EC13EA15E19

12.7.7 Diagnosen mit der ATtiny24-Version mit LCD

Diagnose-Ausgabe Dargestellt werden in Hexadezimal-Format:
Mit den Schaltern am Kopf des Quellcodes können zwei weitere Diagnosen durchgeführt werden.

Diagnose Bytes Mit dem Schalter cDataB werden 16 gespeicherte Datenbytes sowie die daraus ermittelte Null/Eins-Schwelle dargestellt.

Diagnose Worte Mit dem Schalter cDataW kann man sich das Ergebnis wortweise ausgeben lassen.

12.7.8 Programm

Das Programm für diesen lernenden Fernsteuerempfänger im im Folgenden gelistet (den Quellcode im asm-Format gibt es hier, wenn der ATtiny24 mit angeschlossener LCD verwendet werden soll, muss noch die Include-Datei mit dazu).

Vor dem Assemblieren müssen die Schalter im Kopf dem eigenen Bedarf angepasst werden.

;
; *************************************
; * IR-Empfaenger 3-Kanal mit tn13/24 *
; * (C)2016 by www.gsc-elektronic.net *
; *************************************
;
; -------- Programmablauf -------
;
; Das Programm laeuft wahlweise auf ei-
; nem ATtiny13 oder auf einem ATtiny24
; (mit angeschlossener 4-zeiliger LCD,
; gibt die gewuenschten Empfangsdaten
; auf LCD aus).
;
; Schaltet drei Kanaele ein und aus,
; wenn im EEPROM gespeicherte Fernsteuer-
; signale ueber den IR-Sensor eintreffen.
; Die Fernsteuercodes koennen im Selbst-
; lernmodus einmalig eingestellt werden.
;
; Startet im Selbstlernmodus, wenn
; a) beim Programmstart der Jumper ge-
;    setzt ist, oder wenn
; b) die Daten im EEPROM fehlerhaft sind.
;
; Im Selbstlernmodus wartet das Programm
; auf IR-Signalfolgen. Sind zwei Signal-
; folgen eingegangen, deren letzte 16 Bits
; gleich waren, wird dies als Einschalt-
; code fuer den Kanal gespeichert. Die
; folgende korrekte Signalfolge wird als
; Ausschaltcode fuer diesen Kanal gespei-
; chert und dann der naechste Kanal ein-
; gestellt. Sind alle drei Kanaele mit
; ihren Ein- und Ausschaltcodes fertig
; eingestellt, werden die Daten ins EEPROM
; geschrieben und der Selbstlernmodus be-
; endet.
;
; Im Schaltmodus wartet das Programm auf
; eingehende Signalfolgen und vergleicht
; deren letzte 16 Bits mit den gespeicher-
; ten sechs Kanalkennungen. Bei Gleichheit
; mit einer der Kennungen wird die zuge-
; hoerige Aktion (an- oder ausschalten)
; ausgefuehrt.
;
; -------- Schalter -------------
.equ cAvrTyp = 13 ; Zieltyp, ATtn13 oder 24
.equ cAktivH = 1 ; 1 = Ausgaenge Aktiv High
.equ cLcd    = 0 ; 1 = LCD angeschlossen
.equ cInput  = 0 ; 1 = Eingabe erzwingen
.equ cDataB  = 0 ; 1 = Anzeige Bytes Rohdaten
.equ cDataW  = 0 ; 1 = Anzeige Words Rohdaten
;
.if (cAvrTyp != 13) && (cAvrTyp != 24)
	.error "Falscher Typ"
	.endif
.if (cLcd == 1) && (cAvrTyp == 13)
	.error "LCD passt nicht zum Typ"
	.endif
;
.NOLIST
.if cAvrTyp == 13
	.INCLUDE "tn13def.inc"
	.else
	.INCLUDE "tn24def.inc"
	.endif
.LIST
;
.if cAvrTyp == 13
; Hardware: ATtiny13
;                _________
;               /         |
;   +5V/10k o--|Reset  VCC|--o +5V
;              |          |
;    Taster o--|PB3    PB2|--o Ausgang ge
;              |          |
;Ausgang gn o--|PB4    PB1|--o TSOP1740
;              |          |
;        0V o--|GND    PB0|--o Ausgang rt
;              |__________|
;
	.else
; Hardware: ATtiny24
;                _________
;               /         |
;       +5V o--|VCC    GND|--o 0V
;              |          |
;    LCD-RS o--|PB0    PA0|--o TSOP1740
;              |          |
;   LCD-R/W o--|PB1    PA1|--o LED rt
;              |          |
;     RESET o--|RES    PA2|--o LED ge
;              |          |
;     LCD-E o--|PB2    PA3|--o LED gn
;              |          |
;    LCD-D7 o--|PA7    PA4|--o LCD-D4/SCK
;              |          |
;MOSI/LCD-D6o--|PA6    PA5|--o LCD-D5/MISO
;              |__________|
;
	.endif
;
; -------- Ports, Portpins ----------
.if cAvrTyp == 13
	; Ports
	.equ pOut = PORTB ; Ausgabeport tn13
	.equ pDir = DDRB ; Richtungsport
	.equ pIn  = PINB ; Eingabeport
	; Portbits
	.equ bIrO = PORTB1 ; IR-Empfaenger Ausg.
	.equ bIrD = DDB1 ; IR-Empfaenger Richtung
	.equ bIrI = PINB1 ; IR-Empfaenger Eingang
	.equ bRtO = PORTB0 ; Schaltausgang rot
	.equ bRtD = DDB0 ; Richtung rot
	.equ bGeO = PORTB2 ; Schaltausgang gelb
	.equ bGeD = DDB2 ; Richtung gelb
	.equ bGnO = PORTB4 ; Schaltausgang gruen
	.equ bGnD = DDB4 ; Richtung gruen
	.equ bTaO = PORTB3 ; Taste Ausg. Pullup
	.equ bTaI = PINB3 ; Taste Eingang
	.else
	; Ports ATtiny24
	.equ pOut = PORTA ; Ausgabeport tn24 
	.equ pDir = DDRA ; Richtungsport
	.equ pIn  = PINA ; Eingabeport
	; Portpins
	.equ bIrO = PORTA0 ; IR-Empfaenger Ausg. 
	.equ bIrD = DDA0 ; IR-Empfaenger Richtung
	.equ bIrI = PINA0 ; IR-Empfaenger Eingang
	.equ bRtO = PORTA1 ; Schaltausgang rot
	.equ bRtD = DDA1 ; Richtung rot
	.equ bGeO = PORTA2 ; Schaltausgang gelb
	.equ bGeD = DDA2 ; Richtung gelb
	.equ bGnO = PORTA3 ; Schaltausgang gruen
	.equ bGnD = DDA3 ; Richtung gruen
	.equ bTaO = PORTA1 ; Taste Ausg. Pullup
	.equ bTaD = DDA1 ; Taste Richtung
	.equ bTaI = PINA1 ; Taste Eingang
	.endif
;
; -------- Timing, IR-Signale -------
.if cAvrTyp == 13
	; Takt:                1200000 Hz
	; TC0-Prescaler             64
	; TC0-Tick             18.750 Hz  
	;                          53,33 us 
	; TC0-Overflow         13.563 us
	.equ cTcTick = 53
	;
	.else ; ATtiny24
	; Takt:                1000000 Hz
	; TC0-Prescaler             64
	; TC0-Tick              15.625 Hz
	;                           64 us
	; TC0-Overflow          16.384 us
	; Pause bis Auswertung  30.000 us
	; Mittelwert Nullen        448 us
	; Mittelwert Einsen      1.288 us
	; Null/Eins-Schwelle       868 us
	.equ cTcTick = 64
	.endif
; Null/Eins-Schwelle:
.equ cEins = 868/cTcTick ; tn13:16; tn24:13
; Kopf/Daten-Schwelle
.equ cKopf = 4*cEins ; tn13:48; tn24:39 
; Auswertepause nach letztem Bit
.equ cAusw = 1
; Halbe Sekunde, MSB
.equ cSekH = 500000/cTcTick / 256 ; 36/30
; 
; -------- Register -----------------
; benutzt: R0 von LCD-Routine (nur LCD)
.def rBits= R5 ; Empfangene Bits
.def rBitsO=R6 ; Empfangene Bits Anzeige
.def rEins= R7 ; Null/Eins-Schwelle
.def rI1L = R8 ; vorletzter Bitburst, LSB
.def rI1H = R9 ; dto., MSB
.def rI0L = R10 ; letzter Bitburst, LSB
.def rI0H = R11 ; dto., MSB
.def rIRL = R12 ; aktueller Bitburst, LSB
.def rIRH = R13 ; dto., MSB
.def rCntH= R14 ; MSB TC0-Zaehler
.def rSreg= R15 ; SREG Status
.def rmp  = R16 ; Vielzweckregister
.def rimp = R17 ; dto., Interrupts
.def rFlag= R18 ; Flaggen
	.equ bOvf = 0 ; Ueberlauf, Signal ausw.
	.equ bAdj = 1 ; Lernmodus
	.equ bSek = 2 ; Halbe Sekunde zu Ende
	.equ bLng = 3 ; Lange Bestaetigung
	.equ bZwo = 4 ; Zweite Messung
	.equ bEqu = 5 ; Gleichheit Burst 1 und 2
	.equ bDop = 6 ; Doppel empfangen
	.equ bErr = 7 ; Keine Uebereinstimmung
.def rSel = R19 ; 0: Rt Ein, 1: Rt Aus
; 2: Ge Ein, 3: Ge Aus, 4: Gn Ein, 5: Gn Aus
.def rCtr = R20 ; Zaehler EEPROM/Input
.def rmo = R21 ; fuer LCD und LED-Steuerung
.if cLcd == 1
	.def rLine = R22
	.def rLese = R23
	.endif
; frei: R24 .. R25
; verwendet: X fuer Zeigeroperationen
; verwendet: Y fuer Speichern im SRAM
; verwendet: Z fuer diverse Zwecke
;
; -------- SRAM -------------
.DSEG
.ORG 0x0060
sBuffer: ; Datenspeicher fuer Mittelwert-
.Byte 16 ; ermittlung Nullen und Einsen
sBufferEnde:
;
sCodes:
.Byte 4 ; Erkennungscodes Rot, Ein/Aus
.Byte 4 ; dto., Gelb, Ein/Aus
.Byte 4 ; dto., Gruen, Ein/Aus
sCodesTastenEnde:
sCodesEins:
.Byte 1 ; Null/Eins-Schwelle
sCodesEnde:
;
; -------- Reset- und Int-Vektoren ---
.CSEG
.ORG 0x0000
.if cAvrTyp == 13
	rjmp Start ; RESET-Vektor
	rjmp Int0Isr ; INT0 Ext. Int Request 0
	reti ; PCINT0 Pin Change Int Request 0
	rjmp TC0OIsr ; TIM0_OVF TC0 Overflow
	reti ; EE_RDY EEPROM Ready
	reti ; ANA_COMP Analog Comparator
	reti ; TIM0_COMPA TC0 Compare Match A
	reti ; TIM0_COMPB TC0 Compare Match B
	reti ; WDT Watchdog Time-out
	reti ; ADC ADC Conversion Complete
	.else
	rjmp Start ; Reset-Vektor, Init
	reti ; INT0 External Int Request 0
	rjmp Pci0Isr ; PCINT0 Pin Change Int 0
	reti ; PCINT1 Pin Change Int Request 1
	reti ; WDT Watchdog Time-out
	reti ; TIM1_CAPT TC1 Capture
	reti ; TIM1_COMPA TC1 Comp Match A
	reti ; TIM1_COMPB TC1 Compare Match B
	reti ; TIM1_OVF Timer/Counter1 Overflow
	reti ; TIM0_COMPA TC0 Compare Match A
	reti ; TIM0_COMPB TC0 Compare Match B
	rjmp Tc0OIsr ; TC0_OVF, MSB Timer
	reti ; ANA_COMP Analog Comparator
	reti ; ADC ADC Conversion Complete
	reti ; EE_RDY EEPROM Ready
	reti ; USI_STR USI START
	reti ; USI_OVF USI Overflow
	.endif
;
; -------- Int Service ------
;
; INT0 (ATtiny13) bzw. PCINT (ATtiny24) Interrupt
;   wird aufgerufen bei
;   a) Low-High Pegelwechseln beim ATtiny13, oder
;   b) jedem Pegelwechsel beim ATtiny24.
;
; Aktive Pulslaenge vom IR-Empfaenger auswerten.
;
.if cAvrTyp == 24
Pci0Isr: ; Bei ATtiny24 PCINT0-Interrupt
	sbic pIn,bIRI ; ignoriere die
	reti ; Low-Phasen vom IR-Empfaenger
	.else
Int0Isr: ; Bei ATtiny13 INT0-Interrupt
	.endif
	in rSreg,SREG ; SREG sichern
.if cDataW == 1
	;
	; wird ausgefuehrt wenn die Roh-
	; daten auf LCD ausgegeben werden
	; sollen, schreibt aktive Pulsdauer
	; in SRAM-Puffer
	;    
	st Y+,rCntH
	in rimp,TCNT0
	st Y+,rimp
	clr rimp
	out TCNT0,rimp
	mov rCntH,rimp
	cpi YL,Low(sBufferEnde)
	brcs Int0IsrWRet
	sbr rFlag,1<<bOvf
	ldi YH,High(sBuffer)
	ldi YL,Low(sBuffer)
Int0IsrWRet:
	out SREG,rSreg
	reti
	.endif
	tst rCntH ; Langes Signal
	brne Int0IsrLang
	in rimp,TCNT0
	cpi rimp,cKopf ; laenger als Kopf
	brcs Int0IsrKurz
	; Kopfsignal empfangen, Neustart
Int0IsrLang:
	clr rimp ; starte Zaehler neu
	out TCNT0,rimp
	clr rCntH ; loesche MSB Zaehlerbyte
	mov rBitsO,rBits
	clr rBits ; Anzahl empfangene Bits auf 0
	clr rIRL ; loesche Bitsammel-Register
	clr rIRH
	out SREG,rSreg ; fertig
	reti
Int0IsrKurz:
	; Kurzes Datensignal
	in rimp,TCNT0 ; lese Zaehlerstand
	st Y+,rimp ; speichere im SRAM
	cp rEins,rimp ; vergleiche Dauer Null/Eins
	rol rIRL ; schiebe Bit in Sammelregister
	rol rIRH
	inc rBits ; erhoehe Anzahl Bits
	cpi YL,Low(sBufferende) ; Zeigerende?
	brne Int0IsrRet ; nicht oberhalb
	ldi YH,High(sBuffer) ; Neubeginn,
	ldi YL,Low(sBuffer) ; wieder an Anfang
Int0IsrRet:
	; Rueckkehr kurzes Signal
	clr rimp ; loesche Zaehler
	out TCNT0,rimp
	out SREG,rSreg ; SREG herstellen
	reti
;
; TC0-Overflow Interrupt
;   wird vom TC0 ausgeloest
;   wertet Zaehler-Ueberlaeufe aus
;
; Erhoeht das MSB des Zaehlers, stellt fest
; ob eine lange Pause mit inaktivem Signal-
; eingang vorlag, stellt fest, ob eine hal-
; be Sekunde um ist (setzt bSek-Flagge wenn
; ja), wertet Bitdauer aus (setzt bOvf-Flag-
; ge wenn Rohdatenausgabe gewaehlt ist).
;
TC0OIsr:
	in rSreg,SREG ; SREG sichern
	inc rCntH ; Ueberlaeufe zaehlen
	mov rimp,rCntH ; laengere Pause?
	cpi rimp,cSekH ; halbe Sekunde um?
	brne TC0OIsr1
	sbr rFlag,1<<bSek ; Sekundenflagge
	clr rCntH
	rjmp TC0OIsrRet
TC0OIsr1:
	cpi rimp,cAusw ; Auswerten?
	brne TC0OIsrRet
	tst rBits
	breq TC0OIsrRet
;.if cDataW != 1
	sbr rFlag,1<<bOvf ; Auswerteflagge
;	.endif
	mov rI0H,rIRH ; empfangene Bits
	mov rI0L,rIRL ; in Null-Speicher
	mov rBitsO,rBits ; Kop. Anzahl Bits
	clr rBits
TC0OIsrRet:
	out SREG,rSreg ; SREG herstellen
	reti
;
; -------- Start, Init ------
Start:
	; Stapel initiieren
	ldi rmp,LOW(RAMEND)
	out SPL,rmp
	; Init Port-Ausgaenge Richtung
	sbi pDir,bRtD ; Ausgaenge, Richtung
	sbi pDir,bGeD
	sbi pDir,bGnD
	; Port-Ausgaenge inaktiv
.if cAktivH == 1
	cbi pOut,bRtO ; Ausgaenge, Startwert
	cbi pOut,bGeO
	cbi pOut,bGnO
	.else
	sbi pOut,bRtO
	sbi pOut,bGeO
	sbi pOut,bGnO
	.endif
	; Init Taste und IR-Emfaengereingang
	sbi pOut,bIrO ; Pullup IR-Empfaenger
	; Flaggen loeschen
	clr rFlag
	; Voreinstellung Null/Eins-Erkennung
	ldi rmp,cEins
	mov rEins,rmp
	; Bufferzeiger fuer Werteaufzeichnung
	ldi YH,High(sBuffer)
	ldi YL,Low(sBuffer)
	; Lesen Erkennungswerte aus EEPROM
	rcall LeseCodes
	lds rEins,sCodesEins
	; Wenn Taste aktiv dann Adjust
.if cAvrTyp == 24
	cbi pDir,bTaD ; Zeitweise als Eingang
	sbi pOut,bTaO ; Pullup einschalten
	.else
	sbi pOut,bTaO ; Pullup einschalten
	.endif
	nop ; etwas abwarten
	nop
	sbis pIn,bTaI ; wenn Tasteneingang
	sbr rFlag,1<<bAdj ; in Lern-Modus
.if cAvrTyp == 24
	sbi pDir,bTaD ; Wieder als Ausgang
	.endif
	; Wenn Force-Input alles loeschen
.if cInput == 1
	rcall StartInput ; Input vorbereiten
	.else
	sbrc rFlag,bAdj ; Lern-Modus?
	rcall StartInput ; Input vorbereiten
	.endif
	; Wenn LCD angeschlossen, Init LCD
.if cLcd == 1
    ; LCD-Kontroll-Ports initiieren
	cbi pLcdCO,bLcdCOE ; Ausgang E Null
	cbi pLcdCO,bLcdCORS ; Ausgang RS Null
	cbi pLcdCO,bLcdCORW ; Ausgang RW Null
	sbi pLcdCR,bLcdCRE ; Ausgang E Richtung
	sbi pLcdCR,bLcdCRRS ; Ausgang RS Richtung
	sbi pLcdCR,bLcdCRRW ; Ausgang RW Richtung
	; Datenausgangsport LCD
	rcall LcdStart ; Init und Textausgabe
	.endif
	; Starten Timer TC0
	ldi rmp,(1<<CS01)|(1<<CS00) ; Prescaler auf 64
	out TCCR0B,rmp
	ldi rmp,1<<TOIE0 ; Overflow Int
	out TIMSK0,rmp
	; Sleep mode idle, INT0/PCINT0 IR-Signale
.if cAvrTyp == 13
	ldi rmp,(1<<SE)|(1<<ISC01)
	out MCUCR,rmp
	ldi rmp,1<<INT0 ; INT0-Interrupts
	out GIMSK,rmp
	.else
	ldi rmp,1<<SE ; Sleep-Mode Idle
	out MCUCR,rmp
	ldi rmp,1<<PCINT0 ; Pin 0 Lvl chg INTs
	out PCMSK0,rmp ; in Maske 0
	ldi rmp,1<<PCIE0 ; Int PCINT0 enable
	out GIMSK,rmp ; in Int-Maske
	.endif
	; Interrupts enable
	sei
Schleife:
	sleep ; Schlafen
	nop ; Wecken
	sbrc rFlag,bOvf ; Auswerten?
	rcall Overflow ; ja
	sbrc rFlag,bSek ; Sekundentakt?
	rcall Sekunde ; ja
	rjmp Schleife
;
; Routine Sekunde
;   wird ausgeloest von der Flagge bSek
;   zu jeder halben Sekunde
;
; Stellt fest, ob der Zustand Lernen aktiv
; ist. Wenn ja, prueft die Routine ob ein
; IR-Burst eingegangen ist. Wenn nein,
; wird die LED an und aus geschaltet. Wenn
; ja, wird geprueft ob schon 5 Sekunden
; vorbei sind. Wenn nein wird weiter ge-
; wartet. Wenn ja, wird die aktuelle LED
; ausgeschaltet und der aktuelle Eingabeka-
; nal erhoeht. Sind alle Kanaele fertig
; eingestellt, werden die Codes in das
; EEPROM geschrieben und der Lernmodus
; beendet.
;
Sekunde:
	; Halbe Sekunde um
	cbr rFlag,1<<bSek
	; Nur im Adjust-Modus
	sbrs rFlag,bAdj
	ret ; nicht adjust
	; Lange An-Phase?
	sbrs rFlag,bLng ; Bestaetigung?
	rjmp LedBlink ; Nein
	; Lange An-Phase zu Ende?
	dec rCtr ; Fuenf-Sekunden zaehlen
	brne SekundeRet ; Nein
	; Lange An-Phase zu Ende
	cbr rFlag,1<<bLng ; Flagge clear
	; Naechste Eingabeposition
	rcall LedOff ; aktuelle LED aus
	subi rSel,-2 ; zwei dazu
	cpi rSel,6 ; bis rSel = 6
	brcs LedBlink ; noch nicht
	brne SekundeEnde ; alle eingestellt
	ldi rSel,1 ; Weiter mit Aus-Codes
	rjmp LedBlink ; blinken
SekundeEnde:
	; alle Kanaele eingestellt
	rcall SchreibeCodes ; EEPROM-Write
	clr rSel ; Neustart mit Kanal Null
	cbr rFlag,1<<bAdj ; Adjust clear
	; alle Ausgaenge Aus
.if cAktivH == 1
	cbi pOut,bRtO ; Ausgang Null
	cbi pOut,bGeO
	cbi pOut,bGnO
	.else
	sbi pOut,bRtO ; Ausgang High
	sbi pOut,bGeO
	sbi pOut,bGnO
	.endif
SekundeRet:
	ret
;
; Routine LedBlink
;   wird bei aktivem Einlesen in jeder
;   halben Sekunde aufgerufen und schaltet
;   die LED im aktuellen Eingabekanal
;   in rSel an und aus
;
LedBlink:
	rcall GetOutPin ; Aktiv-Pin in rmp
	in rmo,pOut ; Pins lesen
	eor rmp,rmo ; Polaritaet umkehren
	out pOut,rmp ; Pins schreiben
	ret
;
; Routine LedOn
;   schaltet die LED des aktuellen Eingabe-
;   kanals rSel an
;
; Wertet aus, ob die LED aktiv high oder low
; angeschlossen ist
;
LedOn:
	rcall GetOutPin ; Aktiv-Pin in rmp
	in rmo,pOut ; Pins lesen
.if cAktivH == 1
	or rmp,rmo ; Pin aktivieren
	.else
	com rmp ; umkehren
	and rmp,rmo ; Pin auf Null
	.endif
	out pOut,rmp ; Pins schreiben
	ret
;
; Routine LedOff
;   schaltet die LED des aktuellen Eingabe-
;   kanals rSel aus
;
; Wertet aus, ob die LED aktiv high oder low
; angeschlossen ist
;
LedOff:
	rcall GetOutPin ; Aktiv-Pin in rmp
	in rmo,pOut ; Pins lesen
.if cAktivH == 1
	com rmp ; umkehren
	and rmp,rmo ; Pin auf Null
	.else
	or rmp,rmo ; Pin auf Eins
	.endif
	out pOut,rmp ; Pins schreiben
	ret
;
; Routine GetOutPin
;   Holt aktuellen Outputpin in das Register
;   rmp
;
; Input: rSel gibt aktuellen Eingabekanal an
; Output: rmp gibt Maske des zugehoerigen
;         LED-Pins zurueck
;
GetOutPin:
	mov rmp,rSel ; aktuelle Position
	lsr rmp ; Ein/Aus-Bit weg schieben
	cpi rmp,1 ; LED gelb aktiv?
	brcs GetOutPinRt ; nein, rot
	brne GetOutPinGn ; nein, gruen
	ldi rmp,1<<bGeO ; gelb
	ret
GetOutPinRt:
	ldi rmp,1<<bRtO ; rot
	ret
GetOutPinGn:
	ldi rmp,1<<bGnO ; gruen
	ret
;
; Routine Overflow
;   ausgeloest durch Ueberlauf des Timers
;   nach Empfang des letzten IR-Signals
;   Ergebnis der empfangenen Signale wird
;   ausgewertet
;
Overflow:
	cbr rFlag,1<<bOvf ; Flagge clear
.if cLcd == 1
  .if cDataW == 1
	ldi rmp,0x01 ; LCD loeschen
	rcall LcdC4Byte
	ldi XH,High(sBuffer) ; X Zeiger
	ldi XL,Low(sBuffer) ; auf Buffer
	clr rLine ; in Zeile 0
	RohdatenW1:
	ld rmp,X+
	rcall LcdHex
	ld rmp,X+
	rcall LcdHex
	ldi rmp,' '
	rcall LcdD4Byte
	mov rmp,XL
	andi rmp,0x07
	brne RohdatenW1
	inc rLine
	cpi rline,4
	brcc RohdatenWRet
	mov ZH,rLine
	clr ZL
	rcall LcdPos
	rjmp RohdatenW1
	ldi YH,High(sBuffer)
	ldi YL,Low(sBuffer)
	ldi rmp,1<<PCIE0
;	out GIMSK,rmp
	RohdatenWRet:
	ret
	.endif
  .endif
	; pruefen ob Lernmodus
	sbrs rFlag,bAdj
	rjmp OverflowCheck ; nein
	; Ab hier Lernmodus
OverflowCalc:
	clr rmp ; Ints aus
	out GIMSK,rmp
	out TIMSK0,rmp
	; Durchschnittsdauer der letzten
	; 16 empfangenen Datenbits
	; berechnen
	ldi ZH,High(sBuffer) ; Anfang
	ldi ZL,Low(sBuffer) ; Buffer
	clr XH ; X ist Summe
	clr XL
OverflowSum:
	ld rmp,Z+ ; Byte aus Buffer
	add XL,rmp ; addieren
	ldi rmp,0 ; Ueberlauf behandeln
	adc XH,rmp
	cpi ZL,LOW(sBufferende) ; Ende?
	brne OverflowSum ; nein, weiter
	lsr XH ; durch 2
	ror XL
	lsr XH ; durch 4
	ror XL
	lsr XH ; durch 8
	ror XL
	lsr XH ; durch 16
	ror XL
	mov rEins,XL ; in Erkennungsreg.
	sts sCodesEins,XL ; in SRAM
	; Zwei Messungen vergleichen
	sbrc rFlag,bZwo ; Zweiter Burst?
	rjmp OverflowZwei ; beide vergl.
	sbr rFlag,1<<bZwo ; Flagge setzen
	mov rI1L,rI0L ; Messwert kopieren
	mov rI1H,rI0H
	rjmp OverflowNormal
OverflowZwei:
	; Zweiten Burst vergleichen
	cbr rFlag,(1<<bZwo)|(1<<bEqu)
	cp rI1L,rI0L ; LSB vergleichen
	brne OverflowNormal ; ungleich
	cp rI1H,rI0H ; MSB vergleichen
	brne OverflowNormal ; ungleich
	; Gleichheit, pruefe auf Doppel
	sbr rFlag,1<<bEqu ; Gleich-Flagge
	ldi XH,High(sCodes) ; X auf Codes
	ldi XL,Low(sCodes) ; im SRAM
OverflowDoppel:
	cpi XL,Low(sCodesTastenEnde)
	breq OverflowTasteOk ; Ende Vergl.
	ld rmp,X+ ; Lese LSB
	cp rmp,rI1L ; vergleiche LSB
	ld rmp,X+ ; Lese MSB
	brne OverflowDoppel ; Doppel gef.
	cp rmp,rI1H ; vergleiche MSB
	brne OverflowDoppel ; 
	sbr rFlag,1<<bDop ; Doppelflagge
	rjmp OverflowNormal
	; speichere Code, starte lange Pause
OverflowTasteOk:
	ldi XH,High(sCodes) ; Code Start
	ldi XL,Low(sCodes)
	mov rmp,rSel ; aktive Taste
	lsl rmp ; mal zwei
	add XL,rmp ; Zu Zeiger addieren
	ldi rmp,0 ; Ueberlauf behandeln
	adc XH,rmp
	st X+,rI1L ; Wert ablegen
	st X,rI1H
	rcall LedOn ; LED einschalten
	ldi rCtr,5 ; lange Pause
	sbr rFlag,(1<<bEqu)|(1<<bLng)
	rjmp OverflowNormal
OverflowCheck:
	; Empfangenen Datensatz auswerten
	ldi XH,High(sCodes) ; Zeiger Codes
	ldi XL,Low(sCodes)
	ser rSel ; mit 0xFF beginnen
OverflowCheck1:
	inc rSel ; naechster Wert
	cpi rSel,6 ; Ende erreicht?
	brcc OverflowNf ; ja
	ld rmp,X+ ; Wert aus SRAM lesen
	cp rmp,rI0L ; LSB vergleichen
	ld rmp,X+ ; MSB lesen
	brne OverflowCheck1 ; ungleich
	cp rmp,rI0H ; MSB vergleichen
	brne OverflowCheck1 ; ungleich
	; Korrekte Taste erkannt
	cbr rFlag,(1<<bErr)|(1<<bDop)
	sbrs rSel,0 ; Pin Einschalten?
	rcall LedOn ; Pin einschalten
	sbrc rSel,0 ; Pin ausschalten?
	rcall LedOff ; Pin ausschalten
	mov rI1H,rI0H ; Wert kopieren
	mov rI1L,rI0L
	rjmp OverflowNormal
OverflowNf:
	; Keine Uebereinstimmung gefunden
	clr rSel ; Null setzen
	sbr rFlag,1<<bErr ; Flagge setzen
OverflowNormal:
; Wenn LCD angeschlossen: Ausgabe
.if cLcd == 1
	;
	; Code wird ausgefuehrt, wenn
	; a) ein ATtiny24 mit LCD ange-
	;    schlossen ist, und
	; b) wenn die Rohdaten-Ausgabe
	;    eingeschaltet ist.
	;
	.if cDataB == 1
		ldi rmp,0x01 ; LCD loeschen
		rcall LcdC4Byte
		ldi XH,High(sBuffer) ; X Zeiger
		ldi XL,Low(sBuffer) ; auf Buffer
		clr rLine ; in Zeile 0
	Rohdaten1:
		ld rmp,X+ ; Byte lesen
		rcall LcdHex ; in Hex ausgeben
		ldi rmp,' ' ; Leerzeichen
		rcall LcdD4Byte
		cpi XL,Low(sBufferende) ; fertig?
		brcc RohdatenEnde
		mov rmp,XL ; vier Bytes pro Zeile
		andi rmp,0x03 ; naechste Zeile?
		brne Rohdaten1 ; nein
		inc rLine ; naechste Zeile
		mov ZH,rLine
		clr ZL
		rcall LcdPos
		rjmp Rohdaten1 ; weiter
	RohdatenEnde:
		mov rmp,rEins ; Null/Eins-
		rcall LcdHex ; Schwelle ausgeben
		.else
		; LCD angeschlossen, diverse Daten
		ldi ZH,1 ; Anzahl Bits in Zeile 2
		ldi ZL,5
		rcall LcdPos
		mov rmp,rBitsO
		rcall LcdHex
		ldi ZH,1 ; rSel in Zeile 2
		ldi ZL,12
		rcall LcdPos
		mov rmp,rSel
		andi rmp,0x07
		subi rmp,-'0'
		rcall LcdD4Byte
		ldi ZH,1 ; Flaggen in Zeile 2
		ldi ZL,18
		rcall LcdPos
		ldi rmp,' '
		sbrc rFlag,bAdj
		ldi rmp,'A'
		sbrc rFlag,bErr
		ldi rmp,'E'
		rcall LcdD4Byte
		ldi ZH,2 ; Letzte zwei Messwerte
		ldi ZL,4 ; in Zeile 3
		rcall LcdPos
		mov rmp,rI0H
		rcall LcdHex
		mov rmp,rI0L
		rcall LcdHex
		ldi ZH,2
		ldi ZL,13
		rcall LcdPos
		mov rmp,rI1H
		rcall LcdHex
		mov rmp,rI1L
		rcall LcdHex
		ldi ZH,3 ; Null/Eins-Schwelle
		ldi ZL,7 ; in Zeile 3
		rcall LcdPos
		mov rmp,rEins
		rcall LcdHex
		ldi ZH,3 ; Gleich/Ungleich/Doppel-
		ldi ZL,18 ; Flagge in Zeile 4
		rcall LcdPos
		ldi rmp,'U' ; Ungleich
		sbrc rFlag,bEqu ; Gleich
		ldi rmp,'G'
		sbrc rFlag,bDop ; Doppel?
		ldi rmp,'D'
		sbrc rFlag,bErr ; Error?
		ldi rmp,'E'
		rcall LcdD4Byte
		.endif
	.endif
	; Interrupts wieder einschalten
	clr rCntH
	ldi rmp,1<<TOIE0 ; Timer Int
	out TIMSK0,rmp
.if cAvrTyp == 13
	ldi rmp,1<<INT0 ; INT0-Interrupts
	out GIMSK,rmp
	.else
	ldi rmp,1<<PCIE0 ; Int PCINT0 enable
	out GIMSK,rmp ; in Int-Maske
	.endif
	ret
;
; Lese Codes aus dem EEPROM
;   liest den Inhalt des EEPROMs in das SRAM
;   wird aufgerufen beim Init
;
; Anzahl zu lesender Bytes: 13
; Prueft, ob die Null/Eins-Schwelle auf 0xFF
; steht. Falls ja (EEPROM ist leer), wird der
; Lernmodus gestartet.
;
LeseCodes:
	ldi ZH,0 ; Zeiger auf EEPROM-Adresse
	ldi ZL,0
	ldi XH,High(sCodes) ; Zeiger auf SRAM
	ldi XL,Low(sCodes)
	ldi rmp,sCodesEnde-sCodes ; Anzahl
	mov rCtr,rmp
LeseCodes1:
	sbic EECR,EEPE ; warte bis bereit
	rjmp LeseCodes1
	out EEARL,ZL ; Adresszaehler
	sbi EECR,EERE ; Read enable
	in rmp,EEDR ; Byte lesen
	st X+,rmp ; in SRAM speichern
	adiw ZL,1 ; naechste Adresse
	dec rCtr ; Zaehler
	brne LeseCodes1 ; weiter lesen
	cpi rmp,0x00 ; Null/Eins auf Null?
	breq LeseCodes2 ; ja, Daten korrupt
	cpi rmp,0xFF ; Null/Eins auf FF?
	brne LeseCodes3 ; ja, Daten korrupt
LeseCodes2:
	rjmp StartInput ; Daten korrupt, neu
LeseCodes3: ; Daten ok
	ret
;
; Schreibe Codes ins EEPROM
;   schreibt alle gelernten Codes in das EEPROM
;   wird aufgerufen wenn alle Codes eingelesen
;   sind
;
; Anzahl zu schreibender Bytes: 13
;
SchreibeCodes:
	ldi ZH,0 ; Zeiger auf EEPROM-Adresse
	ldi ZL,0
	ldi XH,High(sCodes) ; Zeiger auf SRAM
	ldi XL,Low(sCodes)
	ldi rmp,sCodesEnde-sCodes
	mov rCtr,rmp
SchreibeCodes1:
	sbic EECR,EEPE ; Warte bis bereit
	rjmp SchreibeCodes1 ; weiter warten
	clr rmp ; Erase und Schreiben
	out EECR,rmp
	out EEARL,ZL ; Adresse ausgeben
	ld rmp,X+ ; Byte aus SRAM lesen
	out EEDR,rmp ; in Datenregister
	cli ; disable ints wg. Timeout
	sbi EECR, EEMPE ; Master Program
	sbi EECR, EEPE ; Program enable
	sei ; enable Interrupts
	adiw ZL,1 ; Adresse erhoehen
	dec rCtr ; Bytezaehler
	brne SchreibeCodes1 ; weiter
	ret
;
; Routine StartInput
;   initiiert einen Neustart des Codelernens,
;   wird aufgerufen
;   a) beim Init, wenn noch keine Codes ge-
;      setzt sind,
;   b) beim Init, wenn der Jumper geschlos-
;      sen ist,
;   c) beim Init wenn beim EEPROM-Lesen Feh-
;      ler festgestellt werden
;
; Fuert folgende Schritte aus:
; 1. Loeschen aller Codes durch Ueberschrei-
;    ben mit Nullen
; 2. Setzen eines Default Null/Eins-Schwel-
;    lenwertes
; 3. Trainingskanal auf Null
; 4. Setzen der bAdj-Flagge
;
StartInput:
	ldi ZH,High(sCodes) ; Zeiger
	ldi ZL,Low(sCodes)
	clr rmp ; Nullen
StartInput1:
	st Z+,rmp ; Codes loeschen
	cpi ZL,Low(sCodesEnde) ; Ende?
	brne StartInput1 ; weiter
	ldi rmp,cEins ; Def Null/Eins
	sts sCodesEins,rmp ; in SRAM
	mov rEins,rmp ; und in Register
	clr rSel ; Neustart
	sbr rFlag,1<<bAdj ; Lernmodus
	ret
;
.if cLcd == 1
;
; Routine LCDStart
; wird nur benoetigt, wenn der ATtiny24 mit
; angeschlossener LCD verwendet wird,
; wird beim Initiieren aufgerufen, um die
; LCD zu initiieren
;
; Laedt die LCD-Include, initiiert die LCD
; und gibt eine Startmeldung aus.
;
	; LCD-Include laden
	.include "Lcd4Busy.inc"
	; LCD starten
	LcdStart:
		rcall LcdInit ; Init LCD
		ldi ZH,High(2*Ausgabetext)
		ldi ZL,Low(2*Ausgabetext)
		rjmp LcdText ; Text ausgeben
	; LCD-Ausgabetext
	Ausgabetext:
	.db "IR-Rx-Schalter tn24",0x0D
	.db "Bits=xx Sel=x Flg=x",0x0D
	;         5      12    18
	.db "0 = xxxx 1 = xxxx  ",0x0D
	;        4        13
	.db "Eins = xx Vergl = x",0xFE
	;           7          18
	;
	; Routine LcdHex
	; gibt ein Byte in rmp in Hex auf LCD aus
	; wird von der Ausgabe der Rohdaten aufgerufen
	;
	LcdHex:
		push rmp ; Byte retten
		swap rmp ; oberes Nibble zuerst
		rcall LcdHexN ; Nibble ausgeben
		pop rmp ; rmp wieder herstellen
		; Gib Nibble in Hex auf LCD aus
	LcdHexN:
		andi rmp,0x0F ; unteres Nibble maskieren
		subi rmp,-'0' ; ASCII-Null dazu addieren
		cpi rmp,'9'+1 ; A bis F?
		brcs LcdHexN1 ; nein
		subi rmp,-7 ; auf A bis F
	LcdHexN1:
		rjmp LcdD4Byte ; rmp auf LCD ausgeben
	.endif
;
; Vorbelegung von Tasten
; (Beispieleinstellung TV-Fernsteuerung)
;
; Wird nur benoetigt, wenn man
; a) seine Fernbedienung schon genau kennt, und
; b) die Einstellungen nicht durch Training son-
;    dern durch die Voreinstellung einstellen
;    moechte.
;
.ESEG
.ORG 0x00
EeAnf:
 ; rot, Taste 1 ein, Taste 4 aus
  .DW 0x0835,0xC8F5
 ; gelb, Taste 2 ein, Taste 5 aus
  .DW 0x88B5,0x2815
 ; gruen, Taste 3 ein, Taste 6 aus
  .DW 0x4875,0xA895
  ; Null/Eins-Schwelle
  .DB cEins
EeEnde:
;
; Ende Quellcode
;

Der Code belegt beim ATtiny13 57% des Flash-Memories und 45% des SRAMs.

ATtiny13 memory use summary [bytes]:
Segment   Begin    End      Code   Data   Used    Size   Use%
---------------------------------------------------------------
[.cseg] 0x000000 0x000248    584      0    584    1024  57.0%
[.dseg] 0x000060 0x00007d      0     29     29      64  45.3%
[.eseg] 0x000000 0x00000d      0     13     13      64  20.3%

Es ist also noch jede Menge Platz frei für Weiteres.

Home Top IR Bedingt Hardware Messen Senden Empfang Schalter


©2016-2018 by http://www.gsc-elektronic.net