Infrarot Fernbedienung - Empfänger
Bei Infrarot Fernbedienungen funktioniert die Datenübertragung mittels einem Lichtsignals. Lichtpulse werden von einer LED (der Fernbedienung) ausgesendet und von einem Fototransistor (Bsp. TV Gerät) empfangen. Wie der Name schon sagt passiert dies nicht mit einer für uns sichtbaren Wellenlänge, sondern im Infrarotbereich.
Da normale IR Detektoren (Fototransistor in Form einer LED) auf sämtliche Infrarotstrahlung und damit auch Wärmestrahlung der Umgebung reagieren, verwendet man für die Fernbedienung IR Detektoren mit einer integrierten Elektronik (dreibeinige IR Empfänger). Diese Empfänger reagieren nur auf Signale die mit einer Trägerfrequenz (Carrier Frequency) von 38KHz gesendet werden. Andere Frequenzen werden ausgefiltert.
Wie in der Abbildung dargestellt sendet die LED eine Frequenz von 38Khz (Puls Burst) oder sie sendet nichts (Pause). Am Ausgang des IR Empfängers liegt im Leerlauf dauerhaft +5V an. Empfängt er ein 38KHz Signal zieht er den Ausgang, solange das Signal empfangen wird, auf Masse. Die empfangenen Pulse und Pausen werden mit einem bestimmten Protokoll gesendet. Bei unserer Fernbedienung von ELEGOO ist dies nach Datenblatt das Protokoll von NEC. Für jede gedrückte Taste wird in Form des vereinbarten Protokolls eine tastenspezifische Zahl übertragen. Dies kann Binär, Hexadezimal oder Dezimal dargestellt oder in einem Mikrocontroller weiter verarbeitet werden.
| Bsp. für empfangene Bitfolge | |
|---|---|
| BIN | 11111111 00110000 11001111 |
| HEX | FF30CF |
| DEC | 16724175 |
Auswertung mit der Bibliothek IRemote.h
Mit der Bibliothek IRemote.h lassen sich die Tastencodes einfach auswerten. Das Programm erkennt dabei selbständig das verwendete Übertragungsprotokoll und wendet es entsprechend an. Zur Information können wir uns das Protokoll auch anzeigen lassen.
#include "IRremote.h"
// Anschlusspin
int IN = 2;
IRrecv myIR(IN); // Instanz von IRrecv
decode_results ergebnisse; // Instanz von Ergebnisse
unsigned long taste = 0; // aufgrund er 32 Bit Länge
void setup() {
Serial.begin(9600);
Serial.println("Tool zum Decodieren");
myIR.enableIRIn();
}
void loop() {
// wenn etwas empfangen wurde wir die If-Abfrage ausgeführt
if (myIR.decode(&ergebnisse)) {
// Ergebnis wird in der Variablen 'taste' gespeichert
taste = ergebnisse.value;
// ... und als HEX, BIN, oder DEC ausgegeben
Serial.println(taste, HEX);
// Zusatzfunktion um das verwendete Protokoll anzuzeigen
protocolFinder();
// Zusatzfunktion um die Tastencode zu übersetzten
translateToELEGOO();
myIR.resume();
}
}
Funktion: protocolFinder()
void protocolFinder() {
Serial.print("Übertragungsprotokoll: ");
switch (ergebnisse.decode_type) {
case NEC:
Serial.println("NEC");
break;
case SONY:
Serial.println("SONY");
break;
case RC5:
Serial.println("RC5");
break;
case RC6:
Serial.println("RC6");
break;
case DISH:
Serial.println("DISH");
break;
case SHARP:
Serial.println("SHARP");
break;
case JVC:
Serial.println("JVC");
break;
case SANYO:
Serial.println("SANYO");
break;
case MITSUBISHI:
Serial.println("MITSUBISHI");
break;
case SAMSUNG:
Serial.println("SAMSUNG");
break;
case LG:
Serial.println("LG");
break ;
default:
Serial.println("UNKNOWN");
break ;
}
}
Funktion: translateToELEGOO()
void translateToELEGOO() {
Serial.print("Taste: ");
switch(ergebnisse.value) {
case 0xFFA25D: Serial.println("POWER"); break;
case 0xFFE21D: Serial.println("FUNC/STOP"); break;
case 0xFF629D: Serial.println("VOL+"); break;
case 0xFF22DD: Serial.println("FAST BACK"); break;
case 0xFF02FD: Serial.println("PAUSE"); break;
case 0xFFC23D: Serial.println("FAST FORWARD"); break;
case 0xFFE01F: Serial.println("DOWN"); break;
case 0xFFA857: Serial.println("VOL-"); break;
case 0xFF906F: Serial.println("UP"); break;
case 0xFF9867: Serial.println("EQ"); break;
case 0xFFB04F: Serial.println("ST/REPT"); break;
case 0xFF6897: Serial.println("0"); break;
case 0xFF30CF: Serial.println("1"); break;
case 0xFF18E7: Serial.println("2"); break;
case 0xFF7A85: Serial.println("3"); break;
case 0xFF10EF: Serial.println("4"); break;
case 0xFF38C7: Serial.println("5"); break;
case 0xFF5AA5: Serial.println("6"); break;
case 0xFF42BD: Serial.println("7"); break;
case 0xFF4AB5: Serial.println("8"); break;
case 0xFF52AD: Serial.println("9"); break;
case 0xFFFFFFFF: Serial.println(" REPEAT");break;
default: Serial.println(" other button ");
}
delay(500);
}
Auswerten ohne Bibliothek
Schauen wir uns das hier verwendete Übertragungsprotokoll von NEC an. Zur Unterscheidung von (0) und (1) wird die Pulsweite unterschieden. Der Sender sendet einen immer gleichen Puls-Burst von 560 µs (zusätzlich überlagert von den 38kHz). Diese Puls wird aufgrund der umgedrehten Logik des IR Empfängers zu einer Pause (LOW) mit gleicher Länge. Abhängig der Zeit bis der Sender den nächsten Puls sendet (und der Empfänger dies wieder als LOW ausgibt) wird entschieden ob eine (0) oder eine (1) übertragen wurde. Bei einer (1) liegt der HIGH Pegel 1690µs an, bei einer (0) nur 560µs.
Zusätzlich zu den 32 Bit wird nach dem Tastendruck zusätzlich zuerst ein Startpuls von 9ms Länge gesendet. Danach folgt eine Pause von 4.5ms und danach werden die 32 Bit Daten gesendet. Ganz am Ende wird zum Abschluss noch mal ein Puls von 560µs übertragen.
Noch wichtig für die spätere Auswertung bzw. Weiterverwendung ist, dass sich das Signal nur in 16 Bit voneinander unterscheidet. Die zweiten 16Bit ist eine Inverse Darstellung um Übertragungsfehler zu erkennen.
Hier in tabellarischer Form die mit der oben abgebildeten Fernbedienung empfangenen Daten beim Drücken einer Taste. In Zeile 1 sind die LOW Zeiten. Immer ein Burst mit rund 550-600µs. In Zeile 2 folgt die sich anschließende Pause zwischen den Burst. Rund 600µs für eine logische (0) und rund 1650s für eine logische (1). In Zeile 3 ist die Auswertung in die entsprechende Bitfolge und in Zeile 4 die Einteilung von je 8 Bit in ein Byte. Adresse, inverse Adresse, Daten und die Daten invers.
| LOW[µs] | 9200 | 550 | 600 | 600 | 600 | 550 | 550 | 550 | 550 | 550 | 550 | 600 | 550 | 600 | 550 | 550 | 550 | 600 | 550 | 550 | 550 | 550 | 550 | 550 | 550 | 550 | 550 | 600 | 550 | 600 | 550 | 550 | 550 | 550 |
| HIGH[µs] | 4450 | 600 | 550 | 550 | 600 | 600 | 600 | 600 | 600 | 1700 | 1650 | 1650 | 1650 | 1650 | 1650 | 1700 | 1650 | 1650 | 600 | 1700 | 600 | 600 | 600 | 1700 | 600 | 600 | 1650 | 600 | 1650 | 1650 | 1650 | 600 | 1700 | 50000 |
| SB | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | 0 | EB | |
| Byte | Adresse | Adresse invers | Daten | Daten invers | ||||||||||||||||||||||||||||||
Programm
Die grundsätzliche Funktionsweise des hier angezeigten Scan-Programms ist in der Rubrik Software / Sniffer Tool am Beispiel eines Temperatursensors detailliert beschrieben. Angepasst auf unseren Anwendungsfall der IR Übertragung sieht es folgendermaßen aus:
const int timeSegment = 50; // Auflösung in µs (Messzyklus in dessen Abstand der Eingang geprüft wird)
const int maxCount = 1000; // Maximale Länge des Pulses oder der Pause (maxCount x timeSegment)
unsigned int pulseCounter; // Anzahl der Puls-/Pausenpaare
unsigned int pulseWidthCounter; // Pulsbreite und Pausenbreite (Segmentbreite x Anzahl)
bool completed;
unsigned int empfangeneDaten[100][2]; // maximale Anzahl der übertragenen Puls-/Pausenpaare (Bits)
void setup() {
Serial.begin(9600);
Serial.println("Sniffer-Tool zum Scannen eines unbekannten Datenstroms");
}
void loop() {
pulseCounter = 0;
if(!(PIND & (1<<PD2))){ // wenn Pin 2 LOW
completed = false;
while(!completed){
// LOW Bestimmen (welches als LOW Signal übertragen wird)
pulseWidthCounter = 0;
while(!(PIND & (1<<PD2)) && (pulseWidthCounter < maxCount)){
pulseWidthCounter++;
delayMicroseconds(timeSegment);
}
empfangeneDaten[pulseCounter][0] = pulseWidthCounter;
// LOW Bestimmen (welches als HIGH Signal übertragen wird)
pulseWidthCounter = 0;
while((PIND & (1<<PD2)) && (pulseWidthCounter < maxCount)) {
pulseWidthCounter++;
delayMicroseconds(timeSegment);
}
empfangeneDaten[pulseCounter][1] = pulseWidthCounter;
// Vorbereiten um das nächste Puls-/Pausenpaar zu speichern
pulseCounter++;
// wenn eine Schleife über maxCount liegt und daher nicht mehr ausgeführt wird,
// ... wird die Abfrage der Puls-/Pausenpaare hier beendet.
if(pulseWidthCounter >= maxCount) completed = true;
}
ergebnisAusgeben();
}
}
Funktion: ergebnisAusgeben()
void ergebnisAusgeben() {
Serial.println();
// Darstellen der Puls|Pause Paare
Serial.print(" LOW[µs] ");
for (int i = 0; i < pulseCounter; i++) {
Serial.print(empfangeneDaten[i][0]*timeSegment);
Serial.print(" "); // oder Tab \t
}
Serial.println();
Serial.print("HIGH[µs] ");
for (int i = 0; i < pulseCounter; i++) {
Serial.print(empfangeneDaten[i][1]*timeSegment);
Serial.print("\t"); // oder Tab \t
}
Serial.println();
Serial.println();
// Dekodieren der Zeiten und darstellen als Bitfolge
// ... evt. Acknowledge-Bits und Schluss-Bits abschneiden
for (int i = 1; i < pulseCounter-1; i++) {
if (i==9 || i==17 || i==25 || i==33) Serial.print(" ");
// Festlegen der Grenzen ob '1' oder '0'
if (empfangeneDaten[i][1]*timeSegment < 1000) Serial.print(1);
else Serial.print(0);
}
Serial.println();
}