Pfad:
Home ==>
Mikrobeginner ==> 12. IR-Empfang/Senden
This page in English (external):
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
- Einführung in Infrarotsignale
- Einführung in die bedingte Programmierung
- Hardware, Bauteile, Aufbau
- Infrarotsignale ausmessen
- Ein IR-Sender
- Ein IR-Daten-Übertragungssystem
- Ein IR-Schalter
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.
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.
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".
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.
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.
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.
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
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.
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.
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.
Das hier sind die Signale einer Kamerasteuerung. Sie ähnelt den beiden obigen
Beispielen, nur die Dauern sind völlig unterschiedlich.
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:
- Zuerst wird wie immer der Stapel eingerichtet, die LCD-Ports initiiert, die LCD im
4-Bit-Busy-Modus initiiert, die Spezialzeichen in die LCD gebracht und der Starttext
dargestellt. Der Zeiger Y wird auf den Pufferanfang für empfangene Signale im
SRAM gesetzt. Der Timer 1 wird im Normalmodus mit einem Vorteiler von 8 gestartet, er
dient der Zeitmessung. Am Messeingang mit dem IR-Empfänger werden Pin Change
Interrupts ermöglicht.
- Tritt ein Level Change ein, wird zunächst seine Polaritätät
festgestellt: ist der Eingang auf Null, dann war der IR-Empfänger vor dem Pin
Change inaktiv. Um zusammengehörige High/Low-Signale zusammen im SRAM abzulegen,
wird hier ein Trick angewendet: mit dem versetzten Speichern mit
STD Y+N,Register erfolgt die Ablage der gemessenen Zeiten nur an
vorgewählten Plätzen im SRAM. Die Ablagereihenfolge ist dabei: MSB-Inaktiv,
LSB-Inaktiv, MSB Aktiv, LSB Aktiv. Der Trick ist deshalb nötig, weil bei einem
16-Bit-Timer IMMER das LSB zuerst gelesen werden muss, weil mit dem Lesen auch das
zugehörige MSB in einen Zwischenspeicher abgelegt wird. Würde nach dem
nächsten Takt das MSB vielleicht gar nicht mehr zu dem gelesenen LSB passen, weil
der Timer schon weiter und möglicherweise übergelaufen ist. Um die
Anpasserei des Zeigers zu vermeiden, wenn das LSB zuerst zu speichern ist, ist STD ganz
hilfreich. Erst wenn alle vier Bytes abgelegt sind, wird Y wieder um vier Bytes nach
oben verschoben und die nächsten vier Messwerte abgeholt. Ist der Puffer mit
Messwerten gefüllt, wird die Ausgabeflagge gesetzt. Am Ende der Service
Routine wird der TC0-Zähler rückgesetzt.
- Die Ausgaberoutine gibt die im Puffer liegenden Daten formatiert aus.
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.
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.
Diese
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
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.
Diese hier ist ein echtes Rätsel: sie ist mit 146 Hi- und 147 Lo-Werten ein echtes
Schwergewicht.
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.
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:
Mit beiden Schaltern gesetzt, werden sowohl die High- als auch die Low-Dauern ausgegeben.
In diesem Fall sind die Dauern mit Leerzeichen getrennt dargestellt.
Die gleiche Darstellung vom Signalende her (e signalisiert das letzte Signal).
Die Software zeigt hier nur High-Signale auf.
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
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.
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.
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
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
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
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
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
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.
12.5.9 Simulation der Sendesignale
Im folgenden werden die IR-Aussendungen simuliert mit
avr_sim.
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.
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.
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
Durch Klicken auf INT0 in der Portdarstellung wird ein Tastendruck
simuliert und der Tasteninterrupt wird angefordert.
Mit der nächsten Instruktion wird die INT0-Service-Routine
ausgeführt.
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.
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.
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
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).
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).
Wie vorhergeplant dauert die Zeit zwischen zwei Interrupts
12,5 µs.
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.
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.
Das ist nun die zweite Phase: eine gepulste LED für
5 ms lang (400 mal an, 400 mal aus).
In der dritten Phase bleibt die IR-LED für 2,5 ms
aus.
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.
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.
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).
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.
Das System besteht aus zwei Teilen:
- 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.
- 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
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
Das ist der Folienkondensator.
12.6.3 Aufbau
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
Das Senden der langen Kopfphase im Simulator dauert etwa die vorberechnete Anzahl Rechenzyklen
und liegt innerhalb von Rundungsfehlern.
Die aktive Phase des ersten Kopfsignals liegt ebenfalls innerhalb von Rundungsfehlern, eine
Phase wurde verpasst.
Die inaktive Phase des zweiten Kopfsignals ist ebenfalls um eine Phase länger als
vorberechnet.
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.
Das Inaktiv-Signal eines langen Einer-Bits ist ebenfalls eine Phase länger als vorgesehen.
Auch beim Null-Bit ist eine Phase mehr als berechnet.
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:
- SBCI Register,Konstante: zieht die Konstante plus das Übertragsbit vom Register ab.
12.6.8 Anwendung
So sieht der Empfang aus.
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:
- Einfach, anwenderfreundlich und sparsam mit einem ATtiny13, und
- in der Komfortversion mit LCD-Anzeige für den Forscher und Entwickler, der
herausfinden möchte, warum seine besondere Fernsteuerung vom ATtiny13 nicht
erkannt wird.
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.
Selbige Schaltung, nun mit Aktiv-High-Ausgangstreiber. Der kann mit beliebigen Relaisspannungen
arbeiten.
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
Das ist der Aufbau der Schaltung mit dem ATtiny13. Angeschlossen sind drei farbige LED, um den
Erfolg zu kontrollieren.
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:
- Handelt es sich um ein langes Signal (Kopfsignal), wird ein Neustart eingeleitet
(Rücksetzen der Anzahl empfangener Bits und der beiden Schieberegister).
- Ist das Signal kurz, wird es mit dem Vergleichswert (Null/Eins-Schwelle) verglichen
und jeweils eine Null oder eine Eins in die Schieberegister eingeschoben.
- In allen beiden Fällen wird der Timer neu gestartet.
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
- blinkt diejenige LED, die als nächstes mit der Tastenzuordnung dran ist, im
Sekundentakt,
- die jeweils beiden letzten eingegangenen Bitkombinationen werden auf Gleichheit
überprüft,
- bei vorliegender Gleichheit wird geprüft, ob die betreffende Bitkombination
bereits für eine andere Taste gespeichert ist, ist das der Fall, wird weiter
geblinkt,
- ist das nicht der Fall, wird diese Bitkombination an der zutreffenden Position
im SRAM gespeichert und die nächste Taste angesteuert. Zum Quittieren bleibt
die LED im programmierten Kanal für fünf Sekunden an.
- Zuerst werden die drei Kanäle mit denjenigen Tasten programmiert, die zum
Einschalten führen sollen. Danach folgen die drei Ausschalttasten.
- Sind alle sechs Tasten programmiert, werden die Tastenzuordnungen im EEPROM
abgelegt und der Lernmodus wird verlassen.
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:
- Die dafür vorgesehene Taste ist beim Start gedrückt, oder
- beim Assemblieren wurde der Schalter cInput auf 1 gesetzt, oder
- beim Lesen der gespeicherten Bitkombinationen aus dem EEPROM liefert die
Null/Eins-Schwelle eine Null oder 0xFF (natives EEPROM).
Beim EEPROM ist noch zu beachten, dass durch das Erase beim Programmieren des
Maschinencodes auch das EEPROM geleert (mit 0xFF überschrieben) wird.
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ät | Rot | Gelb | Grün | Null/ Eins |
Ein | Aus | Ein | Aus | Ein | Aus |
HDR | 0805 | C8C5 | 8885 | 2825 | 4528 | A8A5 | 11 |
TV | 20DF | 10EF | A05F | 906F | 609F | 50AF | 12 |
Kamera | 41BE | 619E | 817E | E11E | C13E | A15E | 19 |
12.7.7 Diagnosen mit der ATtiny24-Version mit LCD
Dargestellt werden in Hexadezimal-Format:
- die Anzahl empfangener Bits,
- die Nummer der gerade programmierten oder erkannten Taste (gerade: Einschalten, ungerade:
Ausschalten),
- Flaggen (A: Lernmodus, D: Doppel, E: Error),
- 0: letzte empfangene Bitkombination, 1: vorletzte empfangene Bitkombination,
- Null/Eins-Schwelle in Timerticks,
- Vergleichsergebnis (nur im Lernmodus).
Mit den Schaltern am Kopf des Quellcodes können zwei weitere Diagnosen durchgeführt
werden.
Mit dem Schalter cDataB werden 16 gespeicherte Datenbytes sowie die daraus ermittelte
Null/Eins-Schwelle dargestellt.
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.
©2016-2018 by http://www.gsc-elektronic.net