Wie überträgt man Datenpakete über drahtlose Kanäle? Die erste Idee: man setze den Sender an TxD des UART, den Empfänger an RxD, nehme eine möglichst langsame Baudrate, die grade für den Einsatzzweck ausreicht, verwende ein Paritätsbit, und fertig ist die Datenübertragung. Spätestens nach dem ersten Test zeigt sich jedoch, dass das nicht ganz so einfach ist wie gedacht. Diese Seite zeigt, wie man eine kleine Fernbedienung per Funk aufbauen kann, die dann auch tatsächlich funktioniert. | ||
Die Sende- und Empfangshardware |
||
Die erste Frage ist: woher ein paar Funksender/empfänger nehmen?
Die meisten Sender/Empfänger-Kombinationen sind gradezu lächerlich teuer
- nichts zum verbasteln. Selbstbaulösungen sind mangels geeigneter Ausrüstung
nicht machbar, und ausserdem auch noch illegal. Die billigste Lösung, die aufzutreiben
war, bestand aus den Bausätzen für Für 40 DM waren die Sender, für 80 DM die Empfänger zu bekommen. Die HF-Teile waren jeweils auf einer fertig aufgebauten Modulplatine vorhanden, das restliche Bausatzgemüse konnte man der Bastelkiste zuführen. Sender/Empfänger waren allerdings wirklich billig - in jeder Hinsicht. Die 100m Reichweite sind allenfalls Wunschdenken, die angegebenen 20m unter gestörten - also realen - Bedingungen schon eher nachvollziehbar. Der Enable-Pin des Senders ist in Wirklichkeit die Spannungsversorgung. Dies hat zwei negative Nebeneffekte: nachdem man Enable gesetzt hat, muss der Empfänger sich noch ca. 15ms aufladen, einschwingen oder was auch immer, bevor er sendebereit ist. Des weiteren sendet das Modul nach Abschaltung des Enable einige Millisekunden willkürliche Signale, bevor es völlig entladen ist. Das Empfangsmodul begnügt sich mit 5V. Auch das Sendemodul lässt sich mit 5V betreiben, der Maximalwert von 14V ist aber angebrachter, um die Reichweite zu erhöhen. Am einfachsten lässt sich der Sender über Optokoppler wie die 4N25 schalten, da diese grade genug Leistung bieten, um den Sender direkt anschließen zu können, im abgeschalteten Zustand praktisch keinen Strom verbrauchen, und zudem relativ klein sind. |
Zum Einbau des Senders eignet sich eine größere Fernbedienung. Im
Pollin-Katalog fand sich dazu eine Video-TV Fernbedienung von Telefunken
für nur 3 DM.
Mit Batteriefach, ordentlich Platz im Innenraum und 21 Tasten, die über ein Folienkabel angeschlossen sind, ist dieser Typ für den Einbau einer eigenen Elektronik ausgezeichnet geeignet. Der Empfänger zeigt sich ziemlich zickig bei Störungen auf der Eingangsleitung. Nach dem Einbau in ein Gehäuse aus miteinander verlöteten Leiterplatten mit Masseanschluss und der Filterung der Spannungsversorgung mittels Drosseln und Kondensatoren legt sich dieses Problem glücklicherweise. |
|
zurück zum Anfang | ||
Das Software-Konzept |
||
Mit einem regelbaren Funktionsgenerator lässt sich die nutzbare
Bandbreite der Sender/Empfänger-Kombination leicht ausprobieren. Bei den
Conrad-Modulen können noch Pulse mit 0.2ms Länge zuverlässig
unterschieden werden. Ist ein gesendeter Puls länger als 10ms, zeigt
sich der Empfänger irritiert und gibt falsche Werte aus. Somit lassen
sich ohne Probleme 50 Bit mit 0.2ms Bitzeit am Stück übermitteln, ohne
sich Sorgen um deren Inhalt machen zu müssen. Längere Bit-Streams
müssten entweder durch Pausen unterteilt werden, oder durch Bitstuffing
künstlich mit Pausen angereichert werden.
Für die Fernbedienung, die die Grundlage für diese Beschreibung ist, genügen 16 Bit. Funkübertragungen, erst recht auf einem lizenzfreien Band, sind oft starken Störungen unterworfen. |
Daher sind die letzten 8 Bit für eine wirklich zuverlässige
Fehlererkennung reserviert. Warum sich Gedanken machen, wenn eine zuverlässige Lösung längst existiert? Daher wird zur Fehlererkennung der CRC8-Algorithmus in der Implementierung von John Wren, gefunden auf www.8051.com, eingesetzt. Grob vereinfacht funktioniert dieser Algorithmus dadurch, dass die Datenbits durch einen Generatorwert geteilt werden. Der dabei übrigbleibende Rest ist nun die CRC-Prüfsumme. Weil 0 durch einen beliebigen Wert wieder 0 ergibt, und dabei Daten und Prüfsumme identisch wären, ist es angeraten, vor der Prüfsummenberechnung einen willkürlichen Startwert einzustellen. zurück zum Anfang |
|
Senden |
||
Da nun Hardware und Grobkonzept festliegen, steht nun auch der Ablauf des Sendens weitestgehend fest. Dieser sieht so aus: | ||
INCLUDE crc8.inc INCLUDE time24.inc HFOUT EQU P3.1 HFEN EQU P3.0 |
Zunächst werden einige Ersatzvariablen belegt und die CRC8-Libary eingebunden,
desweiteren eine Bibliothek für Warteschleifen. HFOUT: Datenpin, Active Low HFEN: Enable-Pin, Active Low |
|
send_data: push 0 push b push Acc |
Diese Funktion wird zum Senden aufgerufen. Dabei befinden sich die Sendedaten in Acc. | |
clr HFEN setb HFOUT push Acc mov a,#15 call F_wait_m pop Acc |
Hier wird der Sender 15 ms 'vorgeheizt'. Dafür wird eine Funktion in der Warteschleifen - Bibliothek aufgerufen. | |
mov b,#10011101b call crc8_add_calc |
CRC-Zähler mit Startwert initialisieren und Prüfsumme berechnen | |
Radio_out: mov R0,#8 |
Hier beginnt nun die eigentliche Senderoutine. Gesendet werden alle 16 Bytes an Stück, eingeleitet von einem Startbit. Die Inputbytes sind in Acc und B. | |
clr HFOUT call Radio_L3 call Radio_L3 nop |
Startbit ausgeben. Bis zur Ausgabe des 1. Bits sind es 398 Maschinenzyklen. | |
Radio_L2a: rrc a mov HFOUT,c call Radio_L3 call Radio_L3 djnz R0,Radio_L2a |
Erstes Byte ausgeben. Der Durchlauf durch diese Schleife dauert genau 401 Maschinenzyklen. Dabei wird zunächst mit einem Rotationsbefehl das zu sendende Byte Bit für Bit in das Carry-Flag geschoben, und von dort in den Ausgang kopiert. Daraufhin wird 0.2ms gewartet. | |
mov a, b mov R0,#8 |
Byte wechseln und R0 wieder initialisieren. | |
Radio_L2b: rrc a mov HFOUT,c call Radio_L3 call Radio_L3 djnz R0,Radio_L2b |
2. Byte ausgeben | |
setb HFOUT setb HFEN pop Acc pop b pop 0 ret |
Das war's: Sender abstellen und gesicherte Register wiederherstellen. | |
Radio_L3: push psw push 0 mov 0,#92 Radio_L3_labelA: djnz 0,Radio_L3_labelA pop 0 pop psw ret |
Intern verwendete Warteschleife, die 198 Maschinenzyklen verbrät. | |
zurück zum Anfang | ||
Empfangen |
||
Das Empfangen ist nicht ganz so einfach wie das Senden. Im Prinzip könnte man auch beim Empfang
eine Warteschleife durchlaufen lassen, und für jedes Bit einen 0/1-Wert
in der Mitte von dessen Bitzeit abgreifen. Dies funktioniert
bei einer fehlerarmen Übertragung ganz gut, und der Hardware-UART scheint auch genau so zu arbeiten.
Allerdings sieht eine Funk-Übertragung nicht so schön sauber aus wie eine kabelgebundene. In der Illustration, die oben die gesendeten und unten die empfangenen Signale abbildet, kann man einige Störungen im Empfänger erkennen. Zum einen hat die Synchronisation mit dem Startbit nicht ganz geklappt, so sind die empfangenen Signale leicht auf der Zeitachse verschoben. Zum anderen sind die gesendeten Bits von Störungen unterbrochen. Bei nur einer Abfrage des Wertes pro Bit wäre es purer Zufall, ob das gesendete Signal oder eine Störung aufgezeichnet würde. |
Die Lösung des Problems besteht darin, das Eingangssignal möglichst kontinuierlich abzufragen und
die erhaltenen 0/1-Werte aufzusummieren.
Die Summe einer Bitzeit kann dann mit einem Schwellwert
verglichen werden. Ist der aufsummierte Wert größer als eine bestimmte Konstante, so ist das Bit 1,
sonst 0. Das erste zu empfangende Bit ist das Startbit, welches immer 1 beziehungsweise ein gesendeter Puls sein muß, weil sich der Empfänger daran synchronisiert. Er weiß nach dem Erhalt des Startbits, dass noch 16 weitere Bits folgen. Auf den Empfang des Startbits wird in diesem Programmbeispiel in einer Schleife gewartet. Da diese Schleife aber nicht nur verlassen wird, wenn ein Startbit empfangen wird, sondern auch, wenn nur ein gesendeter Störungsimpuls eintrifft, würde der Empfänger daraufhin 16 Bitzeiten sinnlos mit dem Aufzeichnen von falschen Werten vertun. Damit dies nicht geschieht, wird auch das Startbit über dessen Bitzeit aufsummiert. Ist diese Summe kleiner als eine Konstante, so war es kein Startbit, sondern nur eine empfangene Störung; die Suche nach einem gültigen Startbit kann weitergehen. zurück zum Anfang |
|
INCLUDE crc8.inc HFIN EQU P3.2 main: Radio_in: clr a mov R0,#8 mov R1,#98 mov R2,a mov R3,a |
Den Empfang vorbereiten. R0 enthält den Zähler für die äußere Schleife, 8 Durchläufe für 8 Bits. R1 enthält den Zähler für die innere Schleife. Dieser ist so angelegt, dass ein Durchlauf zusammen mit den Befehlen davor und danach genau eine Bitzeit lang dauert. R2 und R3 nehmen das Datenbyte und die Prüfsumme auf. | |
Radio_L0: jnb HFIN,Radio_L0 |
Hier wird auf das Startbit gewartet. Die Schleife wird erst dann verlassen, wenn HFIN 1 ist. | |
Radio_L0a_innerLoop: mov c,HFIN addc a,#0 djnz R1,Radio_L0a_innerLoop |
An dieser Stelle wird das Startbit empfangen. Dazu wird in einer Schleife zunächst der empfangene 0/1-Wert in das C-Flag geschrieben, und dieses dann mittels ADDC zu A addiert. | |
clr c subb a,#60 jc main_error |
Bei einem optimalen Empfang müsste HFIN die ganze Bitzeit lang 1 gewesen sein. Bei 98 Schleifendurchläufen hätte also A den Wert 98. Weil die Übertragung aber alles andere als perfekt ist, wird A mit einer kleineren Konstante verglichen. War das Startbit nicht 1, so wurde nur eine Störung empfangen, es kann zu einer Fehlerbehandlungsroutine verzweigt werden. | |
Radio_L1a: mov R1,#98 clr a |
Da augenscheinlich ein korrektes Startbit eingetroffen ist, wird nun das 1. Byte empfangen. | |
Radio_L1a_innerLoop: mov c,HFIN addc a,#0 djnz R1,Radio_L1a_innerLoop |
Dazu wird wieder das Signal für jedes Bit aufsummiert | |
clr c subb a,#60 |
und mit einer Konstante verglichen. Der Vergleich sieht so aus, dass eine Konstante vom empfangenen Wert abgezogen wird. War der empfangene Wert kleiner dieser Konstante, so trat bei der Subtraktion ein Überlauf auf, und das C-Flag ist gesetzt, anderenfalls gelöscht. | |
mov a,R2 rrc a mov R2,a djnz 0,Radio_L1a |
Das C-Flag wird nun mittels 'Rotate right trough the C-Flag' in ein Byte geschoben. Nach 8 dieser Rotationen ist das Byte komplett und jedes Bit an seiner richtigen Stelle. | |
mov R0,#8 Radio_L1b: mov R1,#98 clr a Radio_L1b_innerLoop: mov c,HFIN addc a,#0 djnz R1,Radio_L1b_innerLoop clr c subb a,#60 mov a,R3 rrc a mov R3,a djnz 0,Radio_L1b |
Das selbe Spielchen wiederholt sich nun für das zweite Byte, das die Prüfsumme enthält. | |
mov a,R2 mov b,#10011101b call crc8_add_calc |
Nachdem nun beide Bytes empfangen wurden, wird aus dem Datenbyte wieder die Prüfsumme errechnet. | |
mov a,R3 xrl a,b jnz main_error ; [Daten verarbeiten] jmp main main_error: ; [Fehlerbehandlung] |
Ist die errechnete Prüfsumme identisch mit der empfangenen, so war diese Übertragung erfolgreich, und das Datenbyte kann verarbeitet werden. Anderenfalls wird zu einer Fehlerbehandlungsroutine verzweigt. Beispielsweise könnte dort ein Fehler-Pieps ausgegeben werden. | |
zurück zum Anfang | ||
Dieser Code wurde mit dem Ziel entwickelt, eine Funk-Übertragung
zuverlässig zu senden und zu empfangen. Prinzipiell eignet er sich jedoch für alle
drahtlosen Übertragungen, da überall das gleiche Maß an Unzuverlässigkeit
zu erwarten ist. Beispielsweise
entstehen bei einer Infrarot-Übertragungsstrecke durch Sonnenlicht, das von
bewegenden Blättern reflektiert, kurze Störimpulse.
zurück zum Anfang |
http://www.epanorama.net/radio.html | Radio Electronics Pages, allgemeines zu Funk |
http://www.epanorama.net/opto.html | Optoelectronics and optics page |
http://www.isocomoptocouplers.com/isocom/4n25.htm | 4N25, Optically Coupled Isolators |
http://www.8052.com/codelib.phtml | 8051 Code Library, CRC-Code |