Optimierung der Datenübertragung per Laser zwischen zwei Arduinos

Der letzte Versuchsaufbau zur Übertragung von Daten via Laserstrahl zwischen zwei Arduinos war ja mehr eine Machbarkeitsstudie, das mit dem Ergebnis endete, dass eine Datenübertragung mit Signallängen mit 10 bzw. 20 ms pro Bit sicher möglich ist. Dabei kam ein ungeschirmter Fotowiderstand zum Einsatz.

Heute wollen wir versuchen, noch ein wenig mehr Geschwindigkeit herauszukitzeln, werden dafür u. a. auch Fotodioden einsetzen und in den Mikrosekundenbereich vorstoßen.


Versuchsaufbau


Der Versuchsaufbau wurde bereits im ursprünglichen Projekt beschrieben. Unter obigem Link kann er nachgelesen und nachgebaut werden. Es wird dann jeweils nur das lichtsensitive Bauteil ausgetauscht.

Noch einmal zur Erinnerung die Warnung: der Laser hat eine Licht-Leistung von 5 Milliwatt und ist kein Spielzeug. Es darf niemals direkt in den Laserstrahl geschaut werden. Das kann zu ernsten Augenschädigungen führen. Wer sicher gehen will, benutzt eine Laserschutzbrille (auf den richtigen Wellenbereich, z. B. 650nm achten). Wir werden der Reihe nach den bereits bekannten Fotowiderstand, eine weiße Standard-LED, eine Fotodiode Everlight ALS-PDIC144 und eine Fotodiode Osram SFH 203 einsetzen und herausfinden, wie hoch wir jeweils die Übertragungsrate schrauben können.

Leider ist das Programmieren von zwei Arduinos mit der Arduino IDE nicht so komfortabel wie sie sein könnte. Die Port-Einstellungen gelten für alle Projektfenster gleichzeitig. Ändere ich also den Sender-Source-Code, muss ich darauf achten, dass hier auch der richtige Port für den Sender-Arduinos einstellt ist, wenn ich diesen hochlade. Die Crux dabei: auch die Ports in allen anderen Projektfenster ändern sich dann auch den eingestellten Port. Dann darf man da wieder nicht vergessen, den Port wieder zurückzuändern. Außerdem schließen sich alle Serial Monitore und Serial Plotter.

Darum habe ich mir als Kniff einfallen lassen, dass ich beide Arduinos in einem Betriebsmodus "Datenrate übertragen" versetzen kann, wobei diese sich beide temporär auf eine sichere Übertragungsrate mit 20 ms kurz und 40 ms lang setzen und ich dann darüber die neue Datenrate des Senders übertrage, auf die sich der Empfänger dann einstellt. So muss ich immer nur den Source-Code von Sender editieren und hochladen (und nicht den Port wechseln), um eine neue Übertragungsrate einzustellen.

Außerdem soll ein weiterer Knopf am Sender die Datenrate jeweils halbieren, so dass ich nur am Ende im Grenzbereich überhaupt etwas editieren muss.

Die Taster haben darum die folgenden Funktionen: Taster Funktion Sender Funktion Emfänger 1 Pulslängen halbieren Ton an/aus 2 Übertragung pausieren/weiter Anzeige Text / max. Analogwert 3 Übertragung Datenrate mit 20/40 Empfang Datenrate mit 20/40 In folgendem Video zeige ich den Versuchsaufbau und die Testkandidaten, also die lichtempfindlichen Bauteile:



Datenübertragungsprotokoll

Das Datenübertragungsprotokoll bleibt wie zuvor und ist so simpel wie möglich: 1. Bit: 0 = kurz = 1 Einheit 1 = lang = 2 Einheiten Pause zwischen Bits der Länge kurz (1 Einh.) ... Wiederholung für 2. bis 8. Bit Byte-Ende-Markierung der Länge 3 * lang (=6 Einheiten)
Typisches Signal der Übertragung mit 20 ms / 40 ms


Typisches Signal einer höheren Übertragungsrate

Man kann an den Signalkurven gut den Unterscheid zwischen kurzen (Null) und langen (Eins) Bits erkennen. Auch ist die Pause zwischen den Bytes gut erkennbar. Das LANG doppelt so lang wie KURZ ist, ist kein Muss, war für mich aber ein guter Schätzwert, um die beiden Zustände sicher auseinander halten zu können.

Fotowiderstand

Beginnen wollen wir mit dem Fotowiderstand. Diesmal habe ich diesen in eine abgeschnittene, schwarze Stiftkappe gesteckt, um ihn gegen von der Seite einwirkenden Licht abzuschirmen. Nachteil ist allerdings auch, dass die Ausrichtung dann nicht mehr so einfach ist. Denn es hat sich herausgestellt, dass es unter Umständen von Vorteil ist, den Fotowiderstand nur teilweise und an bestimmten Stellen zu beleuchten, um eine höhere Datenübertragungsrate zu bekommen.

Im folgenden Video ist zeige ich, wie die Übertragung der Datenraten vonstatten geht und erhöhe schrittweise die Datenrate. Wenn diese zu hoch ist, um auf dem MFS-Segment-Display noch mitgelesen werden zu können, bitte auf das unten rechts eingeblendete Fenster mit dem Serial Monitor schauen.



Ergebnis:

normale, weiße LED

Als nächste kommt eine normale, billige (unter 1 Cent) weiße Standard-LED zum Einsatz. Auf die Idee, diese auch einmal im Rahmen diesen Projektes einzusetzen, bin ich gekommen, als ich bemerkte, dass sie eine Spannung bis zu 2.5 V liefert, wenn man mit einer Taschenlampe in sie hineinleuchtet.

Da stellt sich die Frage, ob eine Standard-LED auch als Fotodiode taugen könnte. Das nachfolgende Video gibt darüber experimentell Aufschluss:



Ergebnis:

Fotodioden

Wir merken uns: Leuchtdidoden sind zum Leuchten da und nicht zum schnellen Lichtmessen. Dafür gibt es Fotodioden. Diese reagieren sehr viel schneller und sind empfindlicher und damit die richtigen Bauteile für unser Vorhaben.

Ich habe mir eine "NoName" - Fotodiode ALS-PDIC144 vom Hersteller Everlight und eine Marken-Fotodiode SFH 203 vom Hersteller Osram besorgt, die mit folgenden Daten aufwarten:

ALS-PDIC144: SFH 203: Das folgende Video zeigt, wie sich diese beiden Fotodioden schlagen:



Ergebnis: Schon bei 1000 µs / lang 2000 µs zeigen sich die ersten Leistungsschwächen des Arduinos bzw. der Software: Ist das Beepen eingeschaltet, werden die Zeitabstände nicht mehr richtig erkannt und damit auch keine Zeichen mehr.

Das Abstellen des Beepen bringt Besserung. Scheinbar führt der Timer, der für das Beepen zuständig ist, zu Verzögerungen, so dass die Software falsche Zeitabstände misst.

Mit der vorliegenden Software ist eine sichere Übertragung bis zu 500 µs / lang 1000 µs möglich. Mehr ist vielleicht mit Interrupt- oder Timer-gesteuerter Software möglich, was evtl. Thema eines der nächsten Projekte sein wird.

Source-Code

Sender: //////////////////////////////////////////////////////// // (C) 2019 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// #define PinLaser 2 // wo ist der Laser KY-008 angeschlossen? #define BUTTON_1 11 #define BUTTON_2 10 #define BUTTON_3 9 //20ms/40ms ist Safe-Mode zur Übertragung der Übertragungsrate #define KURZ_SAFE 20000 // wieviele microseks lang ist ein kurzer Impuls (für 0) ? #define LANG_SAFE 40000 // wieviele microseks ist ein langer Impuls (für 1) ? void setup() { pinMode(PinLaser, OUTPUT); pinMode(BUTTON_1, INPUT_PULLUP); pinMode(BUTTON_2, INPUT_PULLUP); pinMode(BUTTON_3, INPUT_PULLUP); Serial.begin (115200); } void sendChar(char c, unsigned long kurz, unsigned long lang) { unsigned long dur; Serial.print(c); for (byte i=0; i<8;i++) { // 8 bits byte b = c & 128; // oberstes Bit if (b>0) { dur = lang; } else { dur = kurz; } digitalWrite(PinLaser, HIGH); if (dur <= 16383) { // delayMicroseconds hat nur unsigned int als Parameter delayMicroseconds (dur); } else { delay (dur/1000); } digitalWrite(PinLaser, LOW); if (kurz <= 16383) { delayMicroseconds (kurz); // Pause zwischen den Bits } else { delay (kurz/1000); } c = c << 1; // nächstes Bit } if (lang*3 <= 16383) { delayMicroseconds (lang*3); // Pause nach einem Byte } else { delay (lang*3/1000); } } void loop() { String text = "Dies ist ein Uebertragungstext der genau einhundert stellen hat fuer den test 0123456789 0123456789 "; unsigned long kurz = 10000; // Werte können hier angepasst werden unsigned long lang = 20000; char msg[20]; boolean pause=false; /* Test des Lasers digitalWrite(PinLaser,HIGH); delay(500); digitalWrite(PinLaser,LOW); delay(500); */ while (1) { for (int i=0; i < text.length(); i++) { if (pause) { delay (10); i--; // text anhalten } else { sendChar (text[i], kurz, lang); } if (digitalRead(BUTTON_1) == LOW) { // Übertragungsrate verdoppeln while (digitalRead(BUTTON_1) == LOW) delay(10); // auf loslassen warten pause=true; kurz=kurz/2; lang=lang/2; // dran denken: muss noch übertragen werden (Button 3) } if (digitalRead(BUTTON_2) == LOW) { // Übertragung pausieren while (digitalRead(BUTTON_2) == LOW) delay(10); //auf loslassen warten pause = !pause; } if (digitalRead(BUTTON_3) == LOW) { // auf SAFE Übertragungsrate die neue Übertragungsrate übertragen while (digitalRead(BUTTON_3) == LOW) delay(10); //auf loslassen warten sprintf (msg, "S=%ld L=%ld!\0", kurz, lang); //S=20000 L=40000! Serial.print ("\n\n*** Übertragung der neuen Übertragungsrate: "); Serial.print (msg); Serial.println(); for (int i=0; i < 20; i++) { if (msg[i] == 0) break; sendChar (msg[i], KURZ_SAFE, LANG_SAFE); } Serial.println(); } } Serial.println(); delay (3000); } } Empfänger: //////////////////////////////////////////////////////// // (C) 2019 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// #include <TimerOne.h> // für Timings und Interrupts #include <MultiFuncShield.h> // API für das Multi Function Shield #define PinFoto A5 // wo ist Foto-Widerstand/Diode angeschlossen ? //20ms/40ms ist Safe-Mode zur Übertragung der Übertragungsrate #define KURZ_SAFE 20000 // wie lang ist ein kurzer Impuls (für 0) ? #define LANG_SAFE 40000 // wie lang ist ein langer Impuls (für 1) ? #define Schwellwert -950// 1000 // ab wann gilt der Laser als erkannt? void setup() { Timer1.initialize(); MFS.initialize(&Timer1); // initialize multi-function shield library pinMode (PinFoto, INPUT); Serial.begin (115200); } void loop() { boolean laser=false; boolean laserBefore=false; unsigned long laserStart=0; unsigned long laserStop=0; unsigned long laserDur=0; unsigned long laserPause=0; boolean silent=false; char c=32; byte bitpos=0; byte bit[8]; unsigned long dur=0; char seg[4]={32,32,32,32}; byte led[4]={0,0,0,0}; unsigned long KURZ = KURZ_SAFE; unsigned long LANG = LANG_SAFE; int showAnalogWert = false; int maxWert =0; if (Schwellwert < 0) maxWert=1024; boolean empfNeuRate = false; String neuRate = ""; int stelle=0; MFS.beep(1, 5, 2); // bereit while (1) { int wert = analogRead(PinFoto); if (showAnalogWert) { Serial.println (wert); if (Schwellwert >=0) { if (wert > maxWert) { maxWert=wert; MFS.write(maxWert); } } else { if (wert < maxWert) { maxWert=wert; MFS.write(maxWert); } } } int btn= MFS.getButton(); if (btn == BUTTON_1_PRESSED) { digitalWrite(3,HIGH); silent=!silent; } if (btn == BUTTON_2_PRESSED) { // Umschaltung Anzeige Analogwert (zum Einstellen des Schwellwertes) und // Anzeigen des empfangenen Textes showAnalogWert = !showAnalogWert; } if (btn == BUTTON_3_PRESSED) { // Neue Übertragungsrate empfangen MFS.beep(1, 5, 4); // empfangsbereit neuRate = ""; empfNeuRate=true; KURZ = KURZ_SAFE; LANG = LANG_SAFE; } if (Schwellwert >=0) { laser = (wert >= Schwellwert); } else { laser = (wert <= Schwellwert * -1); } // akustische Rückmeldung, Beeper ist Pin 3 // die Arduino Leistung reicht nicht aus, die Flanken zu erkennen UND // den Beep-Timer zu versorgen if (KURZ < 1000) silent=true; // Auto-Leise if (!silent) digitalWrite(3,!laser); // MFS.write(wert); if (!laserBefore && laser) { // Laser AN gegangen laserStart=micros (); laserPause = micros() - laserStop; if (laserPause > LANG*2) { // Zeichen zuende oder keine Übertragung bitpos=0; } } if (laserBefore && !laser) { // Laser AUS gegangen laserStop=micros (); laserDur = laserStop - laserStart; //if (!showAnalogWert) Serial.println(laserDur); if (laserDur > KURZ * 0.8 && laserDur < KURZ * 1.2) { dur = KURZ; bit [bitpos]=0; bitpos++; } else if (laserDur > LANG * 0.8 && laserDur < LANG * 1.2) { dur = LANG; bit [bitpos]=1; bitpos++; } else { dur=0; bitpos=0; } // Bits auf LEDs ausgeben (die letzten 4) led[0]=led[1]; led[1]=led[2]; led[2]=led[3]; if (dur == KURZ) led [3]=0; if (dur == LANG) led [3]=1; MFS.writeLeds(LED_1, led[0]); MFS.writeLeds(LED_2, led[1]); MFS.writeLeds(LED_3, led[2]); MFS.writeLeds(LED_4, led[3]); if (bitpos >= 8) { // Byte ist voll c=0; for (int i=0; i < 8; i++) { //if (!showAnalogWert) Serial.print(bit[i]); c = c << 1; if (bit[i] >0) c = c | 1; } if (!showAnalogWert) { //Serial.println(); Serial.print(c); stelle++; if (stelle > 100) { Serial.println(); stelle=0; } //Serial.println(); } //Buchstabe auf MFS ausgeben, dabei ggf. scrollen seg[0]=seg[1]; seg[1]=seg[2]; seg[2]=seg[3]; seg[3]=c; if (!showAnalogWert) { MFS.write(seg); } if (empfNeuRate) { //S=20000 L=40000! if (c=='!') { // Übertragung fertig S=20000 L=40000! neuRate += c; int p = neuRate.indexOf("S="); if (p<0) { MFS.beep(100); //falsch } else { int p2 = neuRate.indexOf(" "); if (p2<0) { MFS.beep(100); //falsch } else { String strS=neuRate.substring(p+2,p2); KURZ = strS.toInt(); p = neuRate.indexOf("L="); if (p<0) { MFS.beep(100); //falsch } else { p2 = neuRate.indexOf("!"); if (p2<0) { MFS.beep(100); //falsch } else { String strL=neuRate.substring(p+2,p2); LANG = strL.toInt(); MFS.beep(1, 5, 6); // fertig zum Empfang mit neuer Rate } } } } empfNeuRate=false; } else { MFS.beep (1); neuRate += c; } } } } // delayMicroseconds(100); laserBefore=laser; } }