Als zusätzliches Feature manches Mikrocontroller-Projektes bietet es sich an, dieses noch fernsteuerbar auszulegen. Als Protokoll möchte ich das recht verbreitete RC5 verwenden, da dazu sehr viele Informationen im Netz verfügbar sind. Auch verwenden viele moderne Applikationen dieses Protokoll, so dass auch das Zweckentfremden so mancher Fernbedienung möglich ist. Dem Controller ist es ja prinzipiell relativ egal, woher er das Signal erhält, man muss ihn nur daraufhin "trainieren", je nach Signal eine bestimmte Aktion auszuführen oder auch einfach nicht.
Zum Senden eines Signal dient in meinem Beispiel eine normale Infrarot-LED, welche über einen Transistor, welcher mit deinem MC verbunden ist, angesteuert wird. Als Empfänger dient ein TSOP 17xx. Die beiden 'x' stehen für die Empfangsfrequenz in kHz. In meinem Beispiel wird ein TSOP 1736 verwendet. Prinzipiell ist es zwar auch möglich, ein 38kHz Signal damit zu dekodieren, jedoch sinken dabei Reichweite und Störsicherheit ab. Der TSOP 1736 enthält intern einen Demodulator, welcher das Nutzsignal wieder von der Trägerfrequenz trennt und vorverstärkt. Dieses kann dann direkt abgegriffen werden.
Zunächst soll der Aufbau eines RC5-Signals betrachtet werden. Eine
Signalfolge besteht laut Spezifikation aus 14 Bits. Diese sind auf einen
entsprechenden Träger aufmoduliert. Hierbei hat jedes Bit eine feste Zuordnung.
Die ersten zwei Bits sind die so genannten Startbits. Sie sind im Normalfall
immer gleich 1. Danach folgt ein Toggelbit. Dieses wechselt bei jedem
Tastendruck seinen logischen Zustand. Wird eine Taste dauerhaft gedrückt bleibt
es konstant. Danach folgen 5 Bits welche für die Geräteadresse stehen sowie 6
Bits, welche den eigentlichen Befehl enthalten.
Selbstverständlich ist auch die zeitliche Abfolge des Sendevorganges festgelegt.
Die gesamte Signalzeit beträgt etwa 114ms. Die Dauer eines einzelnen Bits
1,778ms. So erhält man eine Sendezeit von 1,778ms*14=25ms für das eigentliche
Signal sowie 89ms Pause.
Alle Bits werden biphasencodiert gesendet. D.h., dass immer in der Mitte der
Bitzeit der logische Zustand des Signals wechselt. Ein Wechsel von Low auf High
bedeutet hierbei eine logische 0 sowie ein Wechsel von High auf Low eine
logische 1. Das klingt zwar vielleicht unlogisch, liegt jedoch im invertierten
Signal des Empfängers begründet.
Die Darstellung der Empfängerschaltung ist zwar eigentlich überflüssig weil trivial, soll hier nun aber doch erfolgen. Sie kann alternativ auch dem Datenblatt des Infrarotempfängers entnommen werden.
Nachdem der Empfang des Signal geklärt ist, soll es nun
um dessen Erzeugung gehen, für den Fall das keine RC5-Fernbedienung jedoch ein
MC verfügbar ist.
Die Frequenzerzeugung kann mittels PWM oder einfachen Warteschleifen erfolgen.
Hierzu reicht eigentlich ein sehr kleiner Mikrocontroller. Sämtliche
ATMega-Modelle sind hierfür reichlich überdimensioniert, ein ATTiny sollte mehr
als ausreichend sein. Dieser befindet sich ständig im Sleep-Mode und wacht nur
auf Tastendruck auf, sendet das Signal ein paar mal und kehrt danach in den
Sleep-Mode zurück. So sollte ein Batteriebetrieb über eine längere Dauer ohne
Probleme möglich sein. Da sich jedoch die meisten Projekte dieser Seite auf
ATMegas beziehen habe ich, da die Sache nur Bastelzwecken dient, auch hierfür
einen ATMega8 verwendet.
Da eine Infrarot-Diode Ströme von etwa 100mA (je nach Modell) benötigt, sollte
von einer direkten Ansteuerung abgesehen werden, da ansonsten die Lebensdauer
des MC drastisch sinken würde. Deshalb wird als Treiber ein npn-Transistor
eingesetzt. Dessen Basis kann über einen Vorwiderstand direkt an den MC
angeschlossen werden. An den Kollektor wird dann die IR-LED geklemmt,
gegebenenfalls sollte noch ein Widerstand in Serie geschalten werden, um die LED
für Überlastungen zu schützen. Dieser errechnet sich recht einfach nach dem
ohmschen Gesetz: Rv = (Us - Uce - Uf)/If = (5V-0,7V-1,5V)/0,13A = 21,5Ω.
Die genauen Werte für Flussspannung und Flussstrom können dem Datenblatt der
IR-LED entnommen werden, die Kollektor-Emitter-Spannung dem Datenblatt des
Transistors
Die Schaltung könnte beispielsweise die folgende Form haben.
Bevor ich mich auf das RC5-Protokoll gestürzt habe, habe ich zunächst ein paar
einfach Testalgorithmen implementiert, welche für einfach Fernsteuerungsaufgaben
schon durchaus ausreichend sein können. Wenn es lediglich um die Fernsteuerung
eines Roboters geht, reichen unter Umständen bereits 10 voneinander
unterscheidbare Signale aus, welche als 10 verschiedene Befehle interpretiert
werden können.
Die Basis der Signale bildet prinzipiell ein 36kHz Trägersignal. Dieses muss
zwangläufig als erstes bereitgestellt werden. Ein beispielhafte Implementierung
der Trägererzeugung ist nachfolgend angegeben.
//36kHz Träger
TCCR1A = (1<<COM1A1) | (1<<COM1A0) | (1<<WGM11);
TCCR1B = (1<<WGM12) | (1<<WGM13) | (1<<CS10);
OCR1A = 111;
ICR1 = 222;
Das Beispiel gilt nur für eine Taktfrequenz von 8MHz. Es wird der auf Seite 97 im Datenblatt des ATMega8 aufgelistete Modus 14 verwendet. ICR1 definiert die obere Grenze des Zählers. Es wird ein Prescaler von 1 verwendet, d.h. der Counter läuft mit Systemtakt. Es ergibt sich nun eine Frequenz von 8MHz/1/222=36,036kHz. Setzt man im Datenrichtungsregister für PORTB nun noch das zu PB1 korrespondierende Bit, so liegt am Pin ein 36kHz Signal an. Mittels OCR1A kann die Pulsweite variiert werden, in diesem Fall wurde ein Verhältnis von 1:1 gewählt.
Auf den so gewonnen Träger kann nun ein
"Nutzsignal" aufmoduliert werden. Zu beachten ist, dass dessen Frequenz
logischerweise ein ganzes Stück geringer sein muss als die des Trägers.
Hier ist ein Beispiel, wie man einen Timer für den Sender bzw. Empfänger
konfigurieren könnte. Der Empfänger-Interrupt wird 8fach häufiger aufgerufen als
der Sender-Interrupt, um eine gute Abtastung des Signals gewährleisten zu
können.
//Timer-Interrupt alle 2,048ms zur
Signalerzeugung --> 1/(8MHz/256/64)
TCCR0 = (1<<CS01) | (1<<CS00);
TIMSK |= (1<<TOIE0);
//Abtastung realisieren
//Timer-Interrupt alle 0,256ms zum Signalempfang --> 1/(8MHz/256/8)
TCCR0 |= (1<<CS01);
TIMSK |= (1<<TOIE0);
Die ISR für den Sender muss nun für eine bestimmte
Zeitdauer den Träger auf den Sender geben. Die Dauer wird durch die Variable
pulse festgelegt. Die Variable empty dient der Sendefunktion als Indikator
dafür, ob ein Sendevorgang noch am laufen ist oder bereits abgeschlossen wurde.
So wird vermieden, dass pulse während eines Sendevorgang verändert wird.
volatile unsigned char empty = 0;
volatile unsigned char pulse = 0;
SIGNAL (SIG_OVERFLOW0) {
if (pulse) {
DDRB |= (1<<PB1);
empty = 1;
pulse--;
}
if (!pulse) {
empty = 0;
DDRB &= ~(1<<PB1);
}
}
void ir_send (unsigned char byte) {
if (!empty)
pulse = byte;
}
Die ISR für den Empfänger muss nun darauf warten, dass am Ausgang des TSOP Low-Pegel anliegt. Ist dies der Fall, kann begonnen werden, die Pulslänge auszuzählen. Je nach Pulslänge kann dann das Signal als jeweiliger Befehl interpretiert werden. Die Auswertung kann jederzeit um weitere Pulslängen erweitert werden, der Übersichtlichkeit halber sind hier nur zwei Varianten angegeben.
volatile unsigned short timecount = 0;
volatile unsigned char empty = 0;
volatile unsigned char rec_byte = 0;
SIGNAL (SIG_OVERFLOW0) {
//-----<allgemein>-------
if (!(PINB & (1<<PB0))) { //Low-Pegel = log. 1 am Sender
timecount++;
empty = 1;
}
else
empty = 0;
if ((!empty) && (timecount)) {
//-----<Auswertung>------ //8fache
Abtastung
if ((timecount > 70) && (timecount <
90)) { //pulse war 10
rec_byte =
10;
}
if ((timecount > 150) && (timecount <
170)) { //pulse war 20
rec_byte =
20;
}
timecount = 0;
}
}
unsigned char ir_receive (void) {
//Empfangsroutine
return rec_byte;
}
Ein Testfunktion in der das alles vereint ist, kann hier oder unter der Rubrik Programme angesehen werden. In ihr wird oben per #define festgelegt, ob es sich um Sender oder Empfänger handeln soll. Man hätte das Programm auch auf zwei Dateien verteilen können. Diese Variante fand ich aber noch unübersichtlicher beim debuggen als die verwendete.
Eine komplette RC5-Infrarotroutine wird in Kürze nachgereicht, sie funktioniert zwar bereits, aber zum Teil gibt es leider noch Timing-Probleme. Sobald meine Lösung frei von Bugs ist, werde ich sie samt Erklärung hier vorstellen.