Über den Beitrag
In diesem Beitrag geht es mal wieder um Funk, und zwar um die LoRa Technologie am Beispiel der Ebyte E220-, E22- und E32-Modul-Serien. Die Beispielsketche sind für die E220-Module geschrieben, lassen sich aber relativ leicht übertragen, zumal ich die wesentlichen anzupassenden Punkte aufführe. Ich bitte mir nachzusehen, dass ich nicht jeden Sketch in drei Versionen verfasst habe.
Hier ein Überblick über den Beitrag:
- Rechtliches – bitte beachten
- Was ist LoRa, LoRaWAN und LPWAN?
- LoRA Module
- Ebyte Lora Module
- Bibliotheken für die Ebyte E220-, E22- und E32-Serie
- Schaltung für den Arduino Nano
- Einstellungen
- Response Container und Response Status
- Transparent Transmission Mode
- Strukturen senden und empfangen
- Fixed Transmission Mode
- RSSI Received Signal Strength Indicator
- Wake On Radio (WOR) nutzen
- Mit WOR die MCU wecken
- Reichweitentest
- Anhänge – Transceiversketche mit Einstellungen für E220, E22, E32
Rechtliches – bitte beachten!
Wichtig zu wissen:
- Funk ist in fast allen Ländern dieser Welt rechtlich geregelt, z.B. hinsichtlich erlaubter Frequenzen, der Sendeleistung oder dem Duty-Cycle (prozentualer Anteil tatsächlicher Sendezeit).
- Nicht alles, was man kaufen kann, darf man auch ohne Einschränkungen betreiben – manches auch gar nicht!
In Deutschland werden die Regeln für den Funk durch die Bundesnetzagentur festgelegt. Die relevanten Details findet ihr hier im Abschnitt „Funkanlagen mit geringer Reichweite“. Von Hobbyisten werden vor allem die Frequenzen um 433 MHz, 868 MHz und 2.4 GHz genutzt – aber nicht frei von Einschränkungen. Ihr seid selbst dafür verantwortlich, die gültigen Vorschriften in eurem Land einzuhalten.
Was ist LoRa, LoRaWAN und LPWAN?
Wenn ihr euch mit LoRa beschäftigt, dann trefft ihr auch auf die Begriffe LoRaWAN und LPWAN. LoRa steht für Long Range, d. h. es handelt sich eine Funktechnologie für vergleichsweise große Strecken. LoRa (wie auch Sigfox) gehört zu der Gruppe der LPWAN-Funktechnologien, was wiederum die Abkürzung für Low Power Wide Area Network ist. In dieser Bezeichnung steckt damit ein zweites Charakteristikum dieser Technik, nämlich der niedrige Energiebedarf. Während sich LoRa auf die spezielle Funktechnologie bezieht, ist LoRaWAN ein darauf basierendes Kommunikationsprotokoll.
Ich bin weder Physiker noch Funktechniker. Deshalb halte ich mich mit Erklärungen, wie LoRa genau funktioniert, lieber zurück. Nur so viel: Es handelt sich um eine spezielle Modulationstechnik, mit der die große Reichweite und der niedrige Energieverbrauch erreicht wird. Die Datenübertragungsraten sind vergleichsweise gering.
LoRa Module
In den gängigen Online-Shops wie Amazon oder AliExpress werden viele verschiedene LoRa Module angeboten. Darunter befinden sich auch fertige Lösungen, z. B. auf ESP32 basierende Boards mit OLED Display. Ursprünglich hatte ich geplant, einen breiteren Überblick über gängige Module und Bibliotheken zu geben. Dann musste ich allerdings feststellen, dass das Thema schlicht zu umfangreich ist, um alles in einem Beitrag zu behandeln. Ich beschränke mich deshalb zunächst auf die E220-, E22- und die E32-Reihen von Ebyte. Sie sind nahe Verwandte, die fast gleich zu programmieren sind. Weitere Module werde ich evtl. in einem späteren Beitrag betrachten.
Ebyte LoRa Module
Übersicht
Die Firma Ebyte, genau gesagt Chengdu Ebyte Electronic Technology Co. Ltd., bietet eine Vielzahl verschiedener LoRa Module an. Wenn ihr nach bestimmten Modellen sucht, dann schaut hier. Die wichtigsten Unterschiede der Modulreihen habe ich hier zusammengefasst:
Die Module besitzen alle dasselbe Pinout und sind in der Bedienung sehr ähnlich.
Eine Übersicht über die Chips SX1262, SX1276 und SX1278 findet ihr hier. Alle Chips bzw. Module haben ihre Vor- und Nachteile.
Namensgebung
Die Namensgebung der Module erfolgt nach dem Schema Eaaa–bbbTccd, z.B. E220-900T22D. Dabei ist:
- aaa: der „Familienname“,
- bbb: kodiert die Funkfrequenz,
- cc: kodiert die Signalstärke in dBm,
- d: kodiert die Bauform (DIP o. SMD).
Hier, als Beispiel, eine Übersicht über einige Vertreter der E220-Reihe:
Technische Daten am Beispiel E220-900T22D
Datenblätter bzw. Manuals findet ihr beispielsweise auf den Seiten von Chengdu Ebyte. Als Beispiel habe ich hier für euch die technischen Daten des Modells E220-900T22D:
An anderer Stelle habe ich ein Datenblatt für dasselbe Modul gefunden, aber mit etwas unterschiedlichen Angaben zur optimalen Spannungsversorgung:
Probiert im Zweifelsfall aus, ob ihr mit 5 Volt bessere Ergebnisse erzielt. Wichtig ist aber, dass nicht alle Pins 5 Volt vertragen. Bei 5 Volt MCUs solltet ihr also Levelshifter oder Spannungsteiler nutzen.
Pinout LoRa E220-, E22-, E32-Serie
- GND / VCC: Spannungsversorgung
- AUX: Zeigt den Status der Datenpuffer zum Senden und Empfangen an und wird für den Selbstcheck genutzt.
- RX / TX: Serielle Kommunikation
- M0 / M1 (E220): Steuerung der vier Betriebsmodi:
- M0 = LOW, M1 = LOW: Normaler Modus
- M0 = HIGH, M1 = LOW: WOR Transmitter
- M0 = LOW, M1 = HIGH: WOR Receiver
- M0 = HIGH, M1 = HIGH: Deep Sleep
- M0 /M1 (E22): Steuerung der vier Betriebsmodi:
- M0 = LOW, M1 = LOW: Normaler Modus
- M0 = HIGH, M1 = LOW: WOR Mode (Transmitter/Receiver)
- M0 = LOW, M1 = HIGH: Configuration Mode
- M0 = HIGH, M1 = HIGH: Deep Sleep
- M0 / M1 (E32): Steuerung der vier Betriebsmodi:
- M0 = LOW, M1 = LOW: Normaler Modus
- M0 = HIGH, M1 = LOW: Wake-Up Modus (Transmitter)
- M0 = LOW, M1 = HIGH: Power-Saving
- M0 = HIGH, M1 = HIGH: Sleep
Bibliotheken für die Ebyte E220-, E22- und E32-Serie
Für die Ansteuerung der Module habe ich die Bibliotheken von Renzo Mischianti ausgewählt. Ihr könnt sie hier von GitHub herunterladen:
… oder über den Bibliotheksmanager der Arduino IDE installieren.
Zusätzlich hat der fleißige Renzo Mischianti zu jeder der Modulreihen noch ein mehrteiliges Tutorial erstellt. Hier die Links zum jeweiligen ersten Teil: E220-Tutorial, E22-Tutorial, E32-Tutorial.
Meine Motivation einen eigenen Beitrag zu schreiben war es, die Dinge etwas kompakter zusammenzufassen und die Modulserien gegenüberzustellen. Es lohnt sich aber auf jeden Fall, auch in die Tutorials schauen. Sie bieten viele zusätzliche Informationen, so z. B. Beispielsketche und Schaltpläne für andere Boards.
Die Tutorials und die Bibliotheken für die verschiedenen Reihen sind nach demselben Schema aufgebaut. So findet ihr euch schnell zurecht, wenn ihr mal die Modulreihe wechselt.
Schaltung für den Arduino Nano
Ich verwende in diesem Beitrag den klassischen Arduino Nano als steuerndes Board. Folgendermaßen habe ich ihn mit dem E220-Modul verbunden (identisch für E22 und E32):
Ein paar Anmerkungen dazu:
- Wenn ihr die WOR- (Wake On Radio) oder Power-Down-Modi nicht verwenden wollt, dann könnt ihr M0 und M1 mit GND verbinden.
- Auch AUX könnt ihr im Prinzip unverbunden lassen. Dann weiß der steuernde Mikrocontroller zwar nicht, wann die Datenübertragung abgeschlossen ist, aber die Bibliothek fügt eine ausreichende Wartezeit ein.
- Bei Verwendung eines 5 Volt Boards müsst ihr zwischen M0, M1, RX und den Board-Pins einen Levelshifter oder einen Spannungsteiler (z.B. 2 kΩ / 1 kΩ) setzen.
- AUX, RX und TX benötigen einen Pull-up-Widerstand.
- Beispielschaltungen für andere MCU-Boards findet ihr in den Tutorials von Renzo Mischianti.
- Als Antenne für die von mir getesteten Module E220-900T22D und E32-433T30D kamen Standantennen für 868 MHz bzw. 433 MHz zum Einsatz. Diese gibt es für < 10 € in Online-Shops.
So sah mein Aufbau auf dem Breadboard aus:
Einstellungen
Einstellungssketch am Beispiel der E220-Modulreihe
Die Bibliotheken sind mit separaten Beispielsketchen für die Einstellung und den Betrieb der Module ausgestattet. Mit setConfiguration.ino nehmt ihr die Einstellungen vor und mit getConfiguration.ino fragt ihr sie ab. Diese Einstellungen bleiben auch bei Trennung von der Stromversorgung erhalten, sofern ihr beim Speichern der Konfiguration den Parameter WRITE_CFG_PWR_DWN_SAVE wählt (Zeile 62 im nächsten Sketch).
Wenn ihr setConfiguration.ino öffnet, dann seid ihr vielleicht von seinem Umfang erschlagen. Der größte Teil ist aber auskommentiert und die viele Dinge wiederholen sich. So finden sich im oberen Teil verschiedene Beispiele zur Objekterstellung für verschiedene Boards. Im mittleren Teil gibt es vorgefertigte Konfigurationsbeispiele, die ihr bequem entkommentieren könnt. Am Ende finden sich die Funktionen zum Ausgeben der Einstellungen.
Ich habe setConfiguration.ino (E220-Modul) auf das Wesentliche für die Verwendung auf einem Arduino Nano reduziert. Einige Zeilen habe ich aber auch zugefügt:
- Die Zeilen 1 bis 5 dienen der Ausgabe der richtigen Frequenz. Entkommentiert die Zeile mit dem richtigen Frequenzbereich.
- Die Zeilen 6 und 7 sind für die Auswahl der Signalstärke relevant.
- Zeile 8 sorgt dafür, dass die Einstellungen detailliert ausgegeben werden.
- Die Zeilen 57 und 58 erlauben es euch, eure Nachrichten individuell zu verschlüsseln.
// #define FREQUENCY_433 // default value without set // #define FREQUENCY_170 // #define FREQUENCY_470 #define FREQUENCY_868 // #define FREQUENCY_915 // #define E220_22 // default value without set // #define E220_30 // uncomment in case you use an E220...T30D or E220...T30S #define LoRa_E220_DEBUG // for printing the settings #include "LoRa_E220.h" SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 // LoRa_E220 e220ttl(4, 5, 3, 7, 6); // alternative function to create the LoRa_E220 object void printParameters(struct Configuration configuration); void printModuleInformation(struct ModuleInformation moduleInformation); void setup() { Serial.begin(9600); while(!Serial){}; delay(500); Serial.println(); // Startup all pins and UART e220ttl.begin(); ResponseStructContainer c; c = e220ttl.getConfiguration(); // It's important get configuration pointer before all other operation Configuration configuration = *(Configuration*) c.data; Serial.println(c.status.getResponseDescription()); Serial.println(c.status.code); printParameters(configuration); // ----------------------- DEFAULT TRANSPARENT ----------------------- configuration.ADDL = 0x02; // Low byte of address configuration.ADDH = 0x00; // High byte of address configuration.CHAN = 18; // 868 MHz for Exxx-900 modules, choose 23 for Exxx-400 to set 433 MHz configuration.SPED.uartBaudRate = UART_BPS_9600; // Serial baud rate configuration.SPED.airDataRate = AIR_DATA_RATE_010_24; // Air baud rate configuration.SPED.uartParity = MODE_00_8N1; // Parity bit configuration.OPTION.subPacketSetting = SPS_200_00; // Packet size configuration.OPTION.RSSIAmbientNoise = RSSI_AMBIENT_NOISE_DISABLED; // Need to send special command configuration.OPTION.transmissionPower = POWER_22; // Device power configuration.TRANSMISSION_MODE.enableRSSI = RSSI_DISABLED; // Enable RSSI info configuration.TRANSMISSION_MODE.fixedTransmission = FT_TRANSPARENT_TRANSMISSION; // Transmission mode configuration.TRANSMISSION_MODE.enableLBT = LBT_DISABLED; // Check interference configuration.TRANSMISSION_MODE.WORPeriod = WOR_2000_011; // WOR timing configuration.CRYPT.CRYPT_H = 0x00; // encryption high byte, default: 0x00 configuration.CRYPT.CRYPT_L = 0x00; // encryption low byte, default: 0x00 /* Set configuration changed and set to hold the configuration; chose WRITE_CFG_PWR_DWN_LOSE to not save the configuration permanently */ ResponseStatus rs = e220ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE); Serial.println(rs.getResponseDescription()); Serial.println(rs.code); c = e220ttl.getConfiguration(); // It's important get configuration pointer before all other operation configuration = *(Configuration*) c.data; Serial.println(c.status.getResponseDescription()); Serial.println(c.status.code); printParameters(configuration); c.close(); } void loop() { } void printParameters(struct Configuration configuration) { DEBUG_PRINTLN("----------------------------------------"); DEBUG_PRINT(F("HEAD : ")); DEBUG_PRINT(configuration.COMMAND, HEX);DEBUG_PRINT(" ");DEBUG_PRINT(configuration.STARTING_ADDRESS, HEX);DEBUG_PRINT(" ");DEBUG_PRINTLN(configuration.LENGHT, HEX); DEBUG_PRINTLN(F(" ")); DEBUG_PRINT(F("AddH : ")); DEBUG_PRINTLN(configuration.ADDH, HEX); DEBUG_PRINT(F("AddL : ")); DEBUG_PRINTLN(configuration.ADDL, HEX); DEBUG_PRINTLN(F(" ")); DEBUG_PRINT(F("Chan : ")); DEBUG_PRINT(configuration.CHAN, DEC); DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.getChannelDescription()); DEBUG_PRINTLN(F(" ")); DEBUG_PRINT(F("SpeedParityBit : ")); DEBUG_PRINT(configuration.SPED.uartParity, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.SPED.getUARTParityDescription()); DEBUG_PRINT(F("SpeedUARTDatte : ")); DEBUG_PRINT(configuration.SPED.uartBaudRate, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.SPED.getUARTBaudRateDescription()); DEBUG_PRINT(F("SpeedAirDataRate : ")); DEBUG_PRINT(configuration.SPED.airDataRate, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.SPED.getAirDataRateDescription()); DEBUG_PRINTLN(F(" ")); DEBUG_PRINT(F("OptionSubPacketSett: ")); DEBUG_PRINT(configuration.OPTION.subPacketSetting, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.OPTION.getSubPacketSetting()); DEBUG_PRINT(F("OptionTranPower : ")); DEBUG_PRINT(configuration.OPTION.transmissionPower, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.OPTION.getTransmissionPowerDescription()); DEBUG_PRINT(F("OptionRSSIAmbientNo: ")); DEBUG_PRINT(configuration.OPTION.RSSIAmbientNoise, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.OPTION.getRSSIAmbientNoiseEnable()); DEBUG_PRINTLN(F(" ")); DEBUG_PRINT(F("TransModeWORPeriod : ")); DEBUG_PRINT(configuration.TRANSMISSION_MODE.WORPeriod, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getWORPeriodByParamsDescription()); DEBUG_PRINT(F("TransModeEnableLBT : ")); DEBUG_PRINT(configuration.TRANSMISSION_MODE.enableLBT, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getLBTEnableByteDescription()); DEBUG_PRINT(F("TransModeEnableRSSI: ")); DEBUG_PRINT(configuration.TRANSMISSION_MODE.enableRSSI, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getRSSIEnableByteDescription()); DEBUG_PRINT(F("TransModeFixedTrans: ")); DEBUG_PRINT(configuration.TRANSMISSION_MODE.fixedTransmission, BIN);DEBUG_PRINT(" -> "); DEBUG_PRINTLN(configuration.TRANSMISSION_MODE.getFixedTransmissionDescription()); DEBUG_PRINTLN("----------------------------------------"); } void printModuleInformation(struct ModuleInformation moduleInformation) { Serial.println("----------------------------------------"); DEBUG_PRINT(F("HEAD: ")); DEBUG_PRINT(moduleInformation.COMMAND, HEX);DEBUG_PRINT(" ");DEBUG_PRINT(moduleInformation.STARTING_ADDRESS, HEX);DEBUG_PRINT(" ");DEBUG_PRINTLN(moduleInformation.LENGHT, DEC); Serial.print(F("Model no.: ")); Serial.println(moduleInformation.model, HEX); Serial.print(F("Version : ")); Serial.println(moduleInformation.version, HEX); Serial.print(F("Features : ")); Serial.println(moduleInformation.features, HEX); Serial.println("----------------------------------------"); }
Beim Herumprobieren kann es etwas nerven, die Einstellungen über einen separaten Sketch einzustellen, auch wenn das ressourcensparend ist. Vielleicht möchtet ihr die Einstellungen lieber direkt in den Sketchen vornehmen, die ihr auch zur Ansteuerung benutzt? Und vielleicht möchtet ihr den Überblick über alle Einstellungsoptionen haben? Dann schaut für die E220-Reihe in den Anhang 1.
Einstellungssketche für die E22- und die E32-Reihe
Im Anhang 2 und Anhang 3 findet ihr die Pendants zum Anhang 1 für die E22- bzw. E32-Reihe. Die Sketche geben auch einen guten Überblick über die Unterschiede der verschiedenen Modulreihen.
Einstellungen im Detail
Adresse und Kanal (configuration)
Jedes LoRa Modul hat eine Adresse, die sich aus dem höheren Byte ADDH
und dem niedrigeren Byte ADDL
zusammensetzt. Damit könnt ihr 65536 Adressen einstellen. Die Zuweisung der Adresse erfolgt über configuration.ADDL = ...
bzw. configuration.ADDH = ...
.
Die von mir getesteten Module decken die Frequenzen 850 – 930 MHz bzw. 410 bis 493 MHz ab. Die Feineinstellung erfolgt über den Kanal. Die Formel für die Frequenz ν lautet:
Bei den Exxx-900 Modulen stellt ihr also mit configuration.CHAN = 18
die Frequenz 868.125 MHz ein. Bei den Exxx-400 Modellen bekommt ihr 433.125 MHz mit configuration.CHAN = 23
.
Hier besteht die Gefahr, dass ihr durch die falsche Auswahl auf einer in eurem Land unzulässigen Frequenz sendet. Ich sag’s ja nur.
Eine Überprüfung mit einem tinySA Spectrum Analyzer ergab, im Rahmen der Genauigkeit dieses Gerätes, eine gute Übereinstimmung mit der Zielfrequenz. Hier das Ergebnis für Kanal 0, 18 und 80 eines E220-900T22D Moduls:
Das Prinzip der Kanaleinstellung ist für alle Module identisch.
Übertragungsrate und -modalitäten (configuration.SPED)
Folgende Parameter könnt ihr für die UART Kommunikation und die Datenübertragung über die Luft einstellen (Beispiel E220):
- uartBaudRate: Baudrate UART_BPS_xxx
- xxx = 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200
- airDataRate: Datenrate bei Funkübertragung AIR_DATA_RATE_xxx
- AIR_DATA_RATE_010_24: 2.4 kbit/s (default)
- AIR_DATA_RATE_011_48: 4.8 kbit/s
- AIR_DATA_RATE_100_96: 9.6 kbit/s
- AIR_DATA_RATE_101_192: 19.2 kbit/s
- AIR_DATA_RATE_110_384: 38.4 kbit/s
- AIR_DATA_RATE_111_625: 62.5 kbit/s
- uartParity: Parität MODE_xxx;
- MODE_00_8N1: none (default)
- MODE_01_8O1: odd
- MODE_10_8E1: even
Für die E22- und 32-Reihe weichen die Optionen für die airDataRate ab. Siehe Anhang 2 und 3.
Zur Datenrate über Funk ist noch zu sagen: je kleiner die Datenrate, desto höher die maximale Reichweite.
Weitere Übertragungsoptionen (configuration.OPTION)
Weitere Optionen (Beispiel E220):
- subPacketSetting: Maximale Datenpaketgröße, d. h. die maximale Länge eurer Nachricht, die ihr in einem Stück ununterbrochen versenden könnt.
- SPS_200_00: 200 Byte
- SPS_128_01: 128 Byte
- SPS_064_10: 64 Byte
- SPS_032_11: 32 Byte
- RSSIAmbientNoise: RSSI (Received Signal Strength Indicator) Ambient Noise enable
- RSSI_AMBIENT_NOISE_DISABLED: Funktion ist abgeschaltet
- RSSI_AMBIENT_NOISE_ENABLED: Funktion ist angeschaltet
- transmissionPower: Sendeleistung POWER_xx mit xx = Leistung in dbm. Die zulässigen Einstellungen sind modellabhängig.
- xx für E220….22D: 22, 17, 13, 10
- xx für E220….30D: 30, 27, 24, 21
Die Einstellungen subPacketSetting und transmissionPower weichen bei der E22- und E32-Reihe ab (siehe Anhang 2 und 3). RSSIAmbientNoise ist auf dem E32-Modul nicht verfügbar.
Modus-Einstellungen (configuration.TRANSMISSION_MODE)
Auf einige dieser Einstellungen komme ich später zurück. Hier die Optionen für die E220-Reihe:
- enableRSSI: Signalstärkeninformation (Received Signal Strength Indication)
- RSSI_DISABLED: deaktiviert
- RSSI_ENABLED: aktiviert
- fixedTransmission: Übertragungsmodus
- FT_FIXED_TRANSMISSION: Senden an eine bestimmte Adresse / Kanal.
- FT_TRANSPARENT_TRANSMISSION: Senden an alle Module mit identischer Adresse und Kanal.
- enableLBT: LBT (Listen Before Talk) ist eine Funktion, die bewirkt, dass das Modul mit dem Senden bis zu zwei Sekunden abwartet, dass ein günstiger Moment (mit geringen Interferenzen) gekommen ist.
- LBT_DISABLED: Funktion deaktiviert
- LBT_ENABLED: Funktion aktiviert
- WORPeriod: WOR (Wake On Radio) Periode WOR_xxx_yyy mit xxx = Aufwachperiode in Millisekunden
- xxx_yyy = 500_000, 1000_001, 1500_010, 2000_011, 2500_100, 3000_101, 3500_110, 4000_111
Der Parameter WORPeriod braucht noch ein wenig Erklärung. Es handelt sich dabei um die Periode, nach der der Receiver jeweils aufwacht, um zu prüfen, ob eine Nachricht kommt. Der Sender weiß natürlich nicht, wann der Empfänger wach ist und sendet deshalb eine „Präambel“ mit der Länge der Aufwachperiode. Der WOR Receiver bleibt dann so lange wach, bis die eigentliche Nachricht kommt. Aus diesem Grund müssen der WOR Receiver und der WOR Transmitter dieselbe WORPeriod eingestellt haben.
Die WORPeriod heißt bei der E32-Serie wirelessWakeUpTime. Auch die auswählbaren Zeiten sind abweichend. Die Einstellungen enableRSSI und enableLBT stehen auf den E32-Modulen gar nicht zur Verfügung.
Verschlüsselung (configuration.CRYPT)
Alle Module verschlüsseln die Nachrichten. Allerdings lassen nur die Module der Reihen E22 und E220 eine individuelle Verschlüsselung zu. Dazu legt ihr Werte für die beiden Bytes CRYPT_H und CRYPT_L fest. Die Einstellung muss beim Transmitter und beim Empfänger natürlich identisch sein. Auf dem E32 wird mit einer fest eingestellten Verschlüsselung gearbeitet.
Response Container und Response Status
Bevor es endlich losgeht, muss ich noch zwei in der Bibliothek definierte Strukturen erklären, und zwar ResponseContainer und ResponseStatus. Sie beinhalten Informationen über die ein- und ausgehenden Nachrichten.
Der ResponseContainer enthält die eigentliche Nachricht data
, den Signalstärkenwert rssi
, und den ResponseStatus status
.
struct ResponseContainer { String data; byte rssi; // only E22 and E220 series! ResponseStatus status; };
Der ResponseStatus ist auch wieder eine Struktur. Sie beinhaltet den Fehlercode code
und die Funktion getResponseDescription()
, die einen String zurückgibt, der den Code verständlich übersetzt. Der Fehlercode 1 (E220_SUCCESS) bedeutet beispielsweise „Success“, also Erfolg.
struct ResponseStatus { Status code; String getResponseDescription() { return getResponseDescriptionByParams(this->code); } };
Transparent Transmission Mode
Nun aber zum ersten Sketch. Dabei lassen wir mehrere (mind. zwei) Module im „Transparent Transmission Modus“ miteinander kommunizieren. In diesem Modus könnt ihr alle LoRa Module erreichen, die dieselbe Adresse und denselben Kanal eingestellt haben:
Um den transparenten Modus auszuprobieren, solltet ihr an den Einstellungen normalerweise keine Änderungen vornehmen müssen. Falls ihr aber Probleme haben solltet, dann bringt die Module mit setConfiguration_modified.ino in den richtigen Zustand.
Dann ladet den Sketch lora_transparent.ino auf die Mikrocontrollerboards. Ggf. müsst ihr für eure Boards die Zeilen 3 und 4 anpassen. Schaut in die Originalbeispielsketche der Bibliothek – da gibt es viele vorgefertigte Einstellungen für verschiedene Boards.
Bei den meisten Beispielen brauchen wir für jedes Board einen eigenen seriellen Monitor. Mit der alten Arduino IDE 1.x war das recht simpel, indem man die IDE einfach mehrfach aufrief, also mehrere Instanzen schuf und für jede Instanz einen eigenen seriellen Port auswählte. Die IDE 2.x macht das nicht so ohne Weiteres. Hier könnt ihr euch behelfen, indem ihr den Sketch unter verschiedenen Namen speichert, die Versionen öffnet und dann für jeden Sketch einen eigenen Port einstellt.
#include "LoRa_E220.h" SoftwareSerial mySerial(4,5); LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 void setup() { Serial.begin(9600); delay(500); Serial.println("Hi, I'm going to send message!"); // Startup all pins and UARTD e220ttl.begin(); // Send message ResponseStatus rs = e220ttl.sendMessage("Hello, world?"); // Check if there is some problem of successfully send Serial.println(rs.getResponseDescription()); } void loop() { // If something available if (e220ttl.available()>1) { // read the String message ResponseContainer rc = e220ttl.receiveMessage(); // Is something goes wrong print error if (rc.status.code!=1){ Serial.println(rc.status.getResponseDescription()); }else{ // Print the data received Serial.println(rc.data); } } if (Serial.available()) { String input = Serial.readString(); e220ttl.sendMessage(input); } }
Abgesehen von der einzubindenden Bibliothek und der Objekterstellung (Zeile 4) funktioniert der Sketch genauso mit den E22- und E32-Boards.
Erläuterungen zu lora_transparent.ino
Zunächst erzeugt ihr das Objekt e220ttl und initialisiert euer Modul mit e220ttl.begin()
.
Ihr versendet Nachrichten mit der Funktion sendMessage()
. Wie ihr seht, gebt ihr im transparenten Modus keine Adresse und keinen Kanal an. Das Modul sendet automatisch an die Module mit denselben Einstellungen. Der Rückgabewert der Funktion sendMessage()
ist eine Struktur vom Typ ResponseStatus
, die ich weiter oben erläutert habe. Mittels getResponseDescription()
bekommt ihr eine lesbare Übersetzung des Status Codes. Im Idealfall ist das „Success“. „Success“ bedeutet aber nur, dass die Nachricht erfolgreich rausging und nicht, dass sie auch empfangen wurde.
Ob eine Nachricht eingegangen ist, prüft ihr auf der Receiverseite mit e220ttl.available()
. Ihr lest die Nachricht mit receiveMessage()
. Genauer gesagt gibt receiveMessage()
eine Struktur vom Typ ResponseContainer
zurück, die die eigentliche Nachricht im Element data
enthält.
Der ResponseContainer
„rc“ enthält wiederum den ResponseStatus
„rc.status“. Ihr prüft mit
if (rc.status.code != 1) {....
ob ein Fehler aufgetreten ist. Vielleicht wäre die Variante
if (rc.status.getResponseDescription() = "Success") {...
etwas verständlicher, aber ich wollte nah an den Originalsketchen der Bibliotheken bleiben.
Wenn alles OK ist, wird die Nachricht ausgegeben, falls nicht, erscheint die entsprechende Fehlermeldung.
Mit if (Serial.available(){...
prüft ihr, ob eine Eingabe über den seriellen Monitor erfolgt ist. Ist das der Fall, wird die Eingabe gelesen und versendet.
Ausgabe lora_transparent.ino
Ich habe drei Module verwendet. Unten seht ihr die Ausgabe des zuerst aktivierten Moduls. Die zwei „Hello, world?“ Nachrichten wurden über das Setup der anderen Module versendet. Die zwei anderen Nachrichten habe ich „manuell“ von den anderen Modulen mittels Eingabe im seriellen Monitor versendet.
Strukturen senden und empfangen
In den meisten Fällen wollt ihr wahrscheinlich keine Zeichenketten übermitteln, sondern Daten, wie z. B. die einer Wetterstation. Im folgenden Beispiel senden wir alle fünf Sekunden die Luftfeuchte (integer), die Temperatur (float) den Regenstatus (bool). Um diese Daten als „Paket“ zu versenden, definieren wir die Struktur weatherData
.
Für dieses Beispiel bleiben wir noch im transparenten Modus. Die beiden Sketche für den Transmitter und Receiver müssen für die E22- und E32-Module nur hinsichtlich der einzubindenden Bibliothek und der Objekterstellung geändert werden.
Transmitter
Der Sketch für den Transmitter sollte weitgehend selbsterklärend sein:
#include "LoRa_E220.h" SoftwareSerial mySerial(4,5); LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 struct weatherData { int humidity; float temperature; bool rain; }; weatherData currentWeather = {50, 20.0, false}; void setup() { Serial.begin(9600); delay(500); Serial.println("Hi, I'm going to send message!"); // Startup all pins and UARTD e220ttl.begin(); // Send message ResponseStatus rs = e220ttl.sendMessage("Hello, world?"); // Check if there is some problem of successfully send Serial.println(rs.getResponseDescription()); } void loop() { static unsigned long lastSend = millis(); if(millis() - lastSend > 5000){ currentWeather.humidity = 30; currentWeather.temperature = 23.7; currentWeather.rain = false; e220ttl.sendMessage(¤tWeather, sizeof(currentWeather)); lastSend = millis(); } }
In Bezug auf den Sendevorgang ist der einzige Unterschied zum vorherigen Sketch, dass wir sendMessage()
keinen String, sondern eine Struktur (als Referenz mit „&“) und die Größe der Struktur übergeben.
Receiver
Um die zu empfangende Struktur verarbeiten zu können, verwenden wir anstelle des ResponseContainer
die Struktur ResponseStructContainer
, die folgendermaßen definiert ist:
struct ResponseStructContainer { void *data; byte rssi; ResponseStatus status; void close() { free(this->data); } };
Der Datentyp void*
ist ein untypisierter Zeiger, der vielen vielleicht nicht geläufig ist. Im Gegensatz zu den gewohnten Zeigern wie etwa int*
muss ihm seine Bedeutung, also der Datentyp, erst zugewiesen werden.
Hier erst einmal der Sketch:
#include "LoRa_E220.h" SoftwareSerial mySerial(4,5); LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 struct weatherData { int humidity; float temperature; bool rain; }; void setup() { Serial.begin(9600); delay(500); Serial.println("Hi, waiting for weather data..."); // Startup all pins and UARTD e220ttl.begin(); } void loop() { if (e220ttl.available()>1) { // read the String message ResponseStructContainer rsc = e220ttl.receiveMessage(sizeof(weatherData)); weatherData currentWeather = *(weatherData*) rsc.data; Serial.print("Humidity [%]: "); Serial.println(currentWeather.humidity); Serial.print("Temperature [°C]: "); Serial.println(currentWeather.temperature); Serial.print("Rain : "); if(currentWeather.rain){ Serial.println("yes"); } else{ Serial.println("no"); } Serial.println(); } }
Die Zeile:
ResponseStructContainer rsc = e220ttl.receiveMessage(sizeof(weatherData));
liest die empfangene Nachricht und speichert sie im ResponseStructContainer
„rsc“. Zu beachten ist, dass wir receiveMessage()
die Größe des erwarteten Datenpaketes übergeben müssen.
Die eigentlichen Daten sind als Datentyp void*
in rsc.data
abgelegt. Um damit etwas anfangen zu können, wandeln wir den Datentyp void*
mit (weatherData*)
in einen Zeiger um, der auf die Struktur vom Typ weatherData
zeigt. Um die Daten in der Struktur currentWeather
zu speichern, müssen wir den Zeiger mit einem weiteren *
dereferenzieren. Also:
weatherData currentWeather = *(weatherData*) rsc.data;
Fehlt noch die Ausgabe. Sie ist natürlich etwas langweilig, weil wir immer dieselben Daten senden. Aber es geht ja hier nur um das Prinzip.
Fixed Transmission Mode
Ihr stellt den Fixed Transmission Mode ein, indem ihr im Sketch setConfiguration_modified.ino die Zeile
configuration.TRANSMISSION_MODE.fixedTransmission = FT_TRANSPARENT_TRANSMISSION;
abändert in:
configuration.TRANSMISSION_MODE.fixedTransmission = FT_FIXED_TRANSMISSION;
Im Fixed Transmission Mode gibt es zwei Optionen für das Versenden von Nachrichten:
- Exklusives Senden an die Module mit einer bestimmten Adresse auf einem bestimmten Kanal:
- Funktion:
sendFixedMessage(ADDH, ADDL, channel, message);
- Das sendende Modul selbst darf eine abweichende Adresse und einen anderen Kanal eingestellt haben.
- Funktion:
- Senden an alle Module auf einem bestimmten Kanal, unabhängig von ihrer Adresse („Broadcasting“):
- Funktion:
sendBroadcastFixedMessage(channel, message)
- das entspricht:
sendFixedMessage(0xFF, 0xFF, channel, message)
, da 0xFFFF die Broadcasting-Adresse ist.
- das entspricht:
- Auch hier darf das sendende Modul einen abweichenden Kanal eingestellt haben.
- Funktion:
Zu Verdeutlichung habe ich zwei Schemata dazu. Hier zunächst für das Senden an eine bestimmte Adresse:
Und hier das Schema für das Broadcasting im Fixed Transmission Mode:
Beispielsketch Fixed Transmission Mode
Um den Fixed Transmission Mode auszuprobieren, habe ich drei Module mit den folgenden Einstellungen verwendet:
- ADDH = 0x00, ADDL = 0x01, Channel = 18
- ADDH = 0x00, ADDL = 0x02, Channel = 18
- ADDH = 0x00, ADDL = 0x03, Channel = 18
Auf die drei steuernden Arduinos habe ich den folgenden Sketch hochgeladen, wobei ich lediglich die Begrüßungsformel in Zeile 16 und die Switch-Konstruktion ab Zeile 38 angepasst habe.
Der Sketch erlaubt es, über den seriellen Monitor eingegebene Nachrichten an bestimmte Adressen zu versenden (Eingabe: „x,Nachricht“ mit x = 1,2,3) oder per Broadcasting an alle Module (Eingabe: „18,Nachricht“).
Der Sketch muss für die Verwendung auf E22- und E32-Modulen nur hinsichtlich der Bibliothek und der Objekterstellung angepasst werden.
#include "LoRa_E220.h" SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 void setup() { Serial.begin(9600); delay(500); Serial.println("Hi, I'm going to send message!"); // Startup all pins and UART e220ttl.begin(); // Send message ResponseStatus rs = e220ttl.sendBroadcastFixedMessage(18,"Hi to all receivers! This is no. 1"); // adjust // Check If there is some problem of successfully send Serial.println(rs.getResponseDescription()); } void loop() { // If something available if (e220ttl.available()>1) { // read the String message ResponseContainer rc = e220ttl.receiveMessage(); // Is something goes wrong print error if (rc.status.code!=1){ Serial.println(rc.status.getResponseDescription()); }else{ // Print the data received Serial.println(rc.data); } } if (Serial.available()) { int addr = Serial.parseInt(); String input = Serial.readString(); input = input.substring(input.indexOf(",")+1); switch(addr){ case 2: e220ttl.sendFixedMessage(0,2,18,input); break; case 3: e220ttl.sendFixedMessage(0,3,18,input); break; case 18: e220ttl.sendBroadcastFixedMessage(18,input); break; default: e220ttl.sendBroadcastFixedMessage(18,input); } } }
Hier die Ausgabe von Modul 3 (ADDL = 3). Um die Begrüßungen („Hi to all Receivers! …“) der anderen Module zu erhalten, musste es vor ihnen initialisiert werden.
Natürlich lassen sich auch im Fixed Transmission Mode Strukturen versenden. Ich versuche aber meine Beispiele einfach zu halten und auf das gerade relevante Thema zu beschränken.
RSSI Received Signal Strength Indicator
Da die RSSI Funktion auf den E32-Modulen nicht zur Verfügung steht, gilt dieser Abschnitt nur für die E22- und E220-Module.
Der RSSI ist eine dimensionslose Zahl vom Typ byte, die euch mitteilt, wie stark das empfangene Signal war. Die Zahl lässt sich nicht direkt in eine Signalstärke in dBm übersetzen. Um den RSSI auslesen zu können, müsst ihr lediglich auf der Receiverseite receiveMessage()
durch ReceiveMessageRSSI()
ersetzen, also beispielsweise:
ResponseContainer rc = e220ttl.receiveMessageRSSI();
Hier ein Beispielsketch:
#include "LoRa_E220.h" SoftwareSerial mySerial(4,5); LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 void setup() { Serial.begin(9600); delay(500); Serial.println("Hi, I'm going to send message!"); // Startup all pins and UART e220ttl.begin(); // Send message ResponseStatus rs = e220ttl.sendFixedMessage(0,2,18,"Hello, world?"); // Check If there is some problem of successfully send Serial.println(rs.getResponseDescription()); } void loop() { // If something available if (e220ttl.available()>1) { // read the String message ResponseContainer rc = e220ttl.receiveMessageRSSI(); // Is something goes wrong print error if (rc.status.code!=1){ Serial.println(rc.status.getResponseDescription()); }else{ // Print the data received String output = rc.data; byte rssiVal = rc.rssi; Serial.println(rc.data); Serial.print("RSSI: "); Serial.println(rc.rssi); } } if (Serial.available()) { String input = Serial.readString(); e220ttl.sendFixedMessage(0,2,18,input); } }
Für die folgende Receiver-Ausgabe habe ich zwei Module auf meinem Schreibtisch platziert und zwei Nachrichten versendet. Bei der ersten Nachricht habe ich bei beiden Modulen eine kleine Standantenne verwendet. Vor dem Versenden der zweiten Nachricht habe ich die Antenne auf der Receiverseite entfernt. Wie man sieht, ist die empfangene Signalstärke entsprechend heruntergegangen. Wie ihr auch seht, hat der RSSI nur bedingt etwas mit der eigentlichen Signalstärke zu tun, denn die ist ja in beiden Fällen gleich.
Wake On Radio (WOR) nutzen
Der Stromverbrauch der LoRa Modulserien E220, E22 und E32 kann erheblich reduziert werden, wenn man sie im WOR-Modus („Wake On Radio“) betreibt. Wie zuvor erwähnt müssen Transmitter und Receiver dieselbe WOR Periode eingestellt haben.
WOR-Transmitter
Das Einzige, was ihr auf der Transmitterseite zusätzlich einstellen müsst, ist der WOR-Transmitter Modus:
e220ttl.setMode(MODE_1_WOR_TRANSMITTER);
Das macht ihr direkt in eurem Anwendungssketch, d. h. nicht über den Einstellungssketch. Trotz seiner Rolle als WOR-Transmitter kann das Modul auch Nachrichten empfangen.
Hier der Sketch für den WOR-Transmitter:
#include "LoRa_E220.h" SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 void setup() { Serial.begin(9600); delay(500); Serial.println("Hi, I'm going to send a WOR message!"); e220ttl.begin(); e220ttl.setMode(MODE_1_WOR_TRANSMITTER); // Send message ResponseStatus rs = e220ttl.sendFixedMessage(0,2,18,"Hello, world? WOR!"); // Check If there is some problem of successfully send Serial.println(rs.getResponseDescription()); } void loop() { // If something available if (e220ttl.available()>1) { // read the String message ResponseContainer rc = e220ttl.receiveMessage(); // Is something goes wrong print error if (rc.status.code!=1){ Serial.println(rc.status.getResponseDescription()); }else{ // Print the data received Serial.println(rc.data); } } if (Serial.available()) { String input = Serial.readString(); e220ttl.sendFixedMessage(0,2,18,input); } }
Fehlt noch die Ausgabe. Wie ihr unten seht, bekommt der Transmitter eine Empfangsbestätigung. Das ist keine Eigenschaft des WOR-Modus, sondern ein Feature des Receiversketches, zu dem wir gleich kommen.
Anstelle von setMode(MODE_1_WOR_TRANSMITTER)
verwendet ihr bei den E32-Modulen setMode(MODE_1_WAKE_UP)
. Bei den E22-Modulen stellt ihr den Modus MODE_1_WOR
im Sketch ein und legt die Rolle als WOR Transmitter über den Einstellungssketch fest.
WOR-Receiver
Den WOR-Receivermodus stellt ihr folgendermaßen ein:
e220ttl.setMode(MODE_2_WOR_RECEIVER);
Um die schon erwähnte Empfangsbestätigung zurückschicken zu können, müsst ihr den Receiver zwischenzeitlich mit e220ttl.setMode(MODE_0_NORMAL);
in den normalen Modus bringen.
Den hier eingefügten Interrupt, der durch die fallende Flanke des AUX-Pins ausgelöst wird, braucht ihr nicht unbedingt. Er wird lediglich genutzt, um die Ausgabe der eingegangenen Nachricht über den TX-Pin anzukündigen. Hier das Schema für das Verhalten von AUX-Pin und TX-Pin:
#include "LoRa_E220.h" #define AUX_PIN 3 volatile bool interruptExecuted = false; SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX LoRa_E220 e220ttl(&mySerial, AUX_PIN, 7, 6); // AUX M0 M1 void wakeUp() { interruptExecuted = true; //detachInterrupt(digitalPinToInterrupt(AUX_PIN)); } void setup() { Serial.begin(9600); delay(500); e220ttl.begin(); e220ttl.setMode(MODE_2_WOR_RECEIVER); Serial.println("Start sleep!"); delay(100); attachInterrupt(digitalPinToInterrupt(AUX_PIN), wakeUp, FALLING); } void loop() { // If something available if (e220ttl.available()>1) { Serial.println("Message arrived"); // read the String message ResponseContainer rc = e220ttl.receiveMessage(); String message = rc.data; //Serial.println(rc.status.getResponseDescription()); Serial.println(message); e220ttl.setMode(MODE_0_NORMAL); // change to normal mode delay(1000); e220ttl.sendFixedMessage(0, 1, 18, "We have received the message!"); e220ttl.setMode(MODE_2_WOR_RECEIVER); // change back to WOR receiver mode interruptExecuted = false; } if(interruptExecuted) { Serial.println("WakeUp Callback, AUX pin go LOW and start receive message!"); Serial.flush(); //attachInterrupt(digitalPinToInterrupt(AUX_PIN), wakeUp, FALLING); interruptExecuted = false; } }
Und hier die Ausgabe:
Anstelle von setMode(MODE_2_WOR_RECEIVER)
verwendet ihr bei den E32-Modulen setMode(MODE_2_POWER_SAVING)
. Bei den E22-Modulen stellt ihr den Modus MODE_1_WOR
im Sketch ein und legt die Rolle als WOR Receiver als permanente Einstellung über setConfiguration.ino fest.
Mit WOR die MCU wecken
Da der AUX-Pin nach Empfang der Nachricht und vor der Ausgabe an den Mikrocontroller auf LOW geht, kann man dieses Signal nutzen, um nicht nur das LoRa Modul, sondern auch einen schlafenden Mikrocontroller über einen externen Interrupt zu wecken.
Leider ist die Vorlaufzeit zwischen fallender Flanke des AUX-Pin und Beginn der Übertragung über den TX-Pin nicht einstellbar (jedenfalls habe ich keine Möglichkeit gefunden). Zwei bis drei Millisekunden sind eventuell zu wenig Zeit, etwa um einen ESP32 aus dem Deep Sleep zurückzuholen. Auf dem Arduino Nano habe ich festgestellt, dass mindestens das erste Zeichen der übertragenen Nachricht verschluckt wurde. Eine pragmatische Lösung wäre, zumindest für meine Arduino Nano Konfiguration, der Nachricht ein paar Dummy-Zeichen voranzustellen.
Ich habe in meinem Beispiel eine andere Lösung gewählt, und zwar senden wir zunächst einen „Wake-Up-Call“. Bevor dann die eigentliche Nachricht hinausgeht, lassen wir dem Transmitter noch eine Bestätigung zukommen, dass der Receiver wach ist.
Hier der Transmittersketch:
Transmitterseite
#include "LoRa_E220.h" SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 void setup() { Serial.begin(9600); delay(500); Serial.println("Hello, starting now. Type in a message."); e220ttl.begin(); e220ttl.setMode(MODE_1_WOR_TRANSMITTER); } void loop() { if (Serial.available()) { String input = Serial.readString(); e220ttl.sendFixedMessage(0,2,18,"......wake up!!"); // wake up call e220ttl.setMode(MODE_0_NORMAL); // change to normal mode delay(100); // give time for the receiver to wake up while(e220ttl.available()<= 1); // wait for confirmation ResponseContainer rc = e220ttl.receiveMessage(); // receive message rc.status.getResponseDescription(); Serial.println(rc.data); e220ttl.sendFixedMessage(0,2,18,input); // send the actual message e220ttl.setMode(MODE_1_WOR_TRANSMITTER); // change back to WOR transmitter mode } }
Und hier die wenig überraschende Ausgabe:
Auch hier müsst ihr – abgesehen von den allgemeinen Anpassungen – für die E32-Module anstelle von setMode(MODE_1_WOR_TRANSMITTER)
die Einstellung setMode(MODE_1_WAKE_UP)
vornehmen. Bei den E22-Modulen stellt ihr den Modus MODE_1_WOR
im Sketch ein und legt die Rolle als WOR Transmitter als permanente Einstellung fest.
Receiverseite
Die „Schlafgewohnheiten“ der Mikrocontroller sind hardwarespezifisch. Über die Schlafmodi der AVR-Mikrocontroller habe ich hier etwas geschrieben. Wenn ihr keinen AVR-basiertes Mikrocontrollerboard verwendet, müsst ihr den Sketch entsprechend anpassen.
#include "LoRa_E220.h" #include <avr/sleep.h> #define AUX_PIN 3 SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX LoRa_E220 e220ttl(&mySerial, AUX_PIN, 7, 6); // AUX M0 M1 void wakeUp() { delay(0); // add code if you want } void setup() { Serial.begin(9600); delay(500); // Startup all pins and UART e220ttl.begin(); e220ttl.setMode(MODE_2_WOR_RECEIVER); Serial.println("Start sleep!"); delay(100); attachInterrupt(digitalPinToInterrupt(AUX_PIN), wakeUp, FALLING); set_sleep_mode(SLEEP_MODE_PWR_DOWN); // define sleep mode } void loop() { sleep_mode(); // set MCU (Arduino Nano) to sleep delay(10); if (e220ttl.available()>1) { // wait for wake up ResponseContainer rc = e220ttl.receiveMessage(); String message = rc.data; Serial.println(message); e220ttl.setMode(MODE_0_NORMAL); delay(200); e220ttl.sendFixedMessage(0, 1, 18, "Receiver woke up!"); delay(100); while(e220ttl.available()<= 1); // wait for second message rc = e220ttl.receiveMessage(); message = rc.data; Serial.println(message); Serial.flush(); e220ttl.setMode(MODE_2_WOR_RECEIVER); } }
Hier die Ausgabe:
Wenn ihr die WORPeriod ändert, dann müsst ihr die Delays in den Sketchen ändern, damit das Wechselspiel funktioniert.
Für E32-Module ist, unter anderem, setMode(MODE_2_WOR_RECEIVER)
durch setMode(MODE_2_POWER_SAVING)
zu ersetzen. Bei den E22-Modulen stellt ihr den Modus MODE_1_WOR
im Sketch ein und legt die Rolle als WOR Receiver als permanente Einstellung fest.
Reichweitentest
Dann habe ich mit dem E220-900T22D Modul noch einen Reichweitentest durchgeführt. Die Reichweite beträgt laut Datenblatt bis zu 5 km, allerdings gilt das für eine hindernislose Funkstrecke, die man bei dieser Entfernung ja eher selten hat.
Ich habe mit 22 dBm die höchste Sendeleistung gewählt und die niedrigste Datenrate, weil das die höchste Reichweite liefern sollte. Als Antenne kam eine 868 MHz Standantenne zum Einsatz. Der Arduino wurde über einen 9 V Lithium-Akku mit Strom versorgt. Maßnahmen zur Spannungsstabilisierung wie zusätzliche Kondensatoren kamen nicht zu Einsatz.
Hier die Receivereinheit:
Der Transmitter hat alle 5 Sekunden eine Nachricht gesendet:
#include "LoRa_E220.h" SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 void setup() { Serial.begin(9600); delay(500); Serial.println("Hi, I'm going to send message!"); // Startup all pins and UART e220ttl.begin(); // Send message ResponseStatus rs = e220ttl.sendFixedMessage(0,2,18,"Hi to receiver!"); // adjust // Check If there is some problem of successfully send Serial.println(rs.getResponseDescription()); delay(2000); } void loop() { // If something available static unsigned long lastSend = 0; if (millis() - lastSend > 5000) { e220ttl.sendFixedMessage(0,2,18, "Hi Receiver, did you get this message?"); lastSend = millis(); } }
Der Receiver hat die Nachricht entgegengenommen, den Inhalt überprüft und bei Richtigkeit eine LED an Pin 8 fünfmal kurz leuchten lassen.
#include "LoRa_E220.h" #define LED_PIN 8 SoftwareSerial mySerial(4, 5); // Arduino RX <-- e220 TX, Arduino TX --> e220 RX LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 void setup() { pinMode(LED_PIN, OUTPUT); Serial.begin(9600); delay(500); Serial.println("Hi, waiting for messages!"); // Startup all pins and UART e220ttl.begin(); delay(2000); } void loop() { // If something available if (e220ttl.available()>1) { ResponseContainer rc = e220ttl.receiveMessage(); //Serial.println(rc.data); if(rc.data = "Hi Receiver, did you get this message?"){ for(int i=0; i<5; i++){ digitalWrite(LED_PIN, HIGH); delay(100); digitalWrite(LED_PIN, LOW); delay(100); } } } }
Der Transmitter stand bei mir zu Hause auf dem Schreibtisch. Ich bin mit der Receivereinheit auf die Felder gegangen und habe geprüft, bis zu welcher Entfernung ich Nachrichten empfangen konnte. Das Funksignal musste erst einmal meine eigene Hauswand durchdringen, dann einen schmalen Waldstreifen und drei bis vier Wohnhäuser, bis es dann über das freie Feld ging. Das Gelände war flach. Damit kam ich immerhin auf 1.34 km Reichweite.
Anhänge – Transceiversketche mit Einstellungen für E220, E22, E32
Anhang 1 – E220 Transceiver Sketch mit Einstellungen
Sketch mit eingebetteter Konfiguration für E220-Module. Ihr könnt die Funktion setConfiguration()
herauskopieren und in anderen Sketchen verwenden. Der Sketch wurde mit zwei E220-900T22D Modulen getestet.
// #define FREQUENCY_433 // default value without set // #define FREQUENCY_170 // #define FREQUENCY_470 #define FREQUENCY_868 // #define FREQUENCY_915 // #define E220_22 // default value without set // #define E220_30 // uncomment in case you use an E220...T30D or E220...T30S #include "LoRa_E220.h" SoftwareSerial mySerial(4,5); LoRa_E220 e220ttl(&mySerial, 3, 7, 6); // AUX M0 M1 void setup() { Serial.begin(9600); delay(500); Serial.println("Beginning configuration..."); e220ttl.begin(); setConfiguration(); Serial.println("Hi, I'm going to send message!"); ResponseStatus rs = e220ttl.sendBroadcastFixedMessage(18, "Hello, world?"); Serial.println(rs.getResponseDescription()); } void loop() { // If something available if (e220ttl.available()>1) { // read the String message ResponseContainer rc = e220ttl.receiveMessage(); // Is something goes wrong print error if (rc.status.code!=1){ Serial.println(rc.status.getResponseDescription()); }else{ // Print the data received Serial.println(rc.data); } } if (Serial.available()) { String input = Serial.readString(); e220ttl.sendFixedMessage(0, 2, 18, input); } } void setConfiguration(){ ResponseStructContainer c; c = e220ttl.getConfiguration(); // It's important get configuration pointer before all other operation Configuration configuration = *(Configuration*) c.data; Serial.println(c.status.getResponseDescription()); Serial.println(c.status.code); configuration.ADDL = 0x03; // Low byte of address configuration.ADDH = 0x00; // High byte of address configuration.CHAN = 18; // Communication channel --> 868 MHz /* UART_BPS_xxx with xxx = Baudrate xxx = 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 */ configuration.SPED.uartBaudRate = UART_BPS_9600; // Serial baud rate /* AIR_DATA_RATE_000_24 2.4 kb/s AIR_DATA_RATE_001_24 2.4 kb/s AIR_DATA_RATE_010_24 2.4 kb/s AIR_DATA_RATE_011_48 4.8 kb/s AIR_DATA_RATE_100_96 9.6 kb/s AIR_DATA_RATE_101_192 19.2 kb/s AIR_DATA_RATE_110_384 38.4 kb/s AIR_DATA_RATE_111_625 62.5 kb/s */ configuration.SPED.airDataRate = AIR_DATA_RATE_010_24; // Air baud rate /* MODE_00_8N1 none MODE_01_8O1 odd MODE_10_8E1 even MODE_11_8N1 none */ configuration.SPED.uartParity = MODE_00_8N1; // Parity bit /* SPS_200_00 200 SPS_128_01 128 SPS_064_10 64 SPS_032_11 32 */ configuration.OPTION.subPacketSetting = SPS_200_00; // Packet size /* RSSI_AMBIENT_NOISE_DISABLED RSSI_AMBIENT_NOISE_ENABLED */ configuration.OPTION.RSSIAmbientNoise = RSSI_AMBIENT_NOISE_DISABLED; // Need to send special command /* POWER_xxx with xxx = power in dBm E220...T22D/S: xxx = 22, 17, 13, 10 E220...T30D/S: xxx = 30, 27, 24, 21 */ configuration.OPTION.transmissionPower = POWER_22; // Device power /* RSSI_DISABLED RSSI_DISABLED */ configuration.TRANSMISSION_MODE.enableRSSI = RSSI_DISABLED; // Enable RSSI info /* FT_FIXED_TRANSMISSION FT_TRANSPARENT_RANSMISSION */ configuration.TRANSMISSION_MODE.fixedTransmission = FT_FIXED_TRANSMISSION; // Transmission mode /* LBT_DISABLED LBT_ENABLED */ configuration.TRANSMISSION_MODE.enableLBT = LBT_DISABLED; // Check interference /* WOR_xxx_yyy with xxx = WOR Period xxx_yyy = 500_000, 1000_001, 1500_010, 2000_011, 2500_100, 3000_101, 3500_110, 4000_111 */ configuration.TRANSMISSION_MODE.WORPeriod = WOR_2000_011; // WOR timing configuration.CRYPT.CRYPT_H = 0x00; // encryption high byte, default: 0x00 configuration.CRYPT.CRYPT_L = 0x00; // encryption low byte, default: 0x00 // Set configuration changed and set to hold the configuration ResponseStatus rs = e220ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE); // WRITE_CFG_PWR_DWN_LOSE to not permanently save Serial.println(rs.getResponseDescription()); Serial.println(rs.code); c.close(); }
Anhang 2 – E22 Transceiver Sketch mit Einstellungen
Sketch mit eingebetteter Konfiguration für E22-Module. Ihr könnt die Funktion setConfiguration()
herauskopieren und in anderen Sketchen verwenden. Der Sketch wurde mit zwei E22-400T22D Modulen getestet.
// #define FREQUENCY_433 // default value without set // #define FREQUENCY_170 // #define FREQUENCY_470 // #define FREQUENCY_868 // #define FREQUENCY_915 // #define E22_22 // default value without set // #define E22_30 // uncomment in case you use an E22...T30D or E22...T30S #include "LoRa_E22.h" SoftwareSerial mySerial(4,5); LoRa_E22 e22ttl(&mySerial, 3, 7, 6); // AUX M0 M1 void setup() { Serial.begin(9600); delay(500); Serial.println("Beginning configuration..."); e22ttl.begin(); setConfiguration(); Serial.println("Hi, I'm going to send message!"); ResponseStatus rs = e22ttl.sendBroadcastFixedMessage(23, "Hello, world?"); Serial.println(rs.getResponseDescription()); } void loop() { // If something available if (e22ttl.available()>1) { // read the String message ResponseContainer rc = e22ttl.receiveMessage(); // Is something goes wrong print error if (rc.status.code!=1){ Serial.println(rc.status.getResponseDescription()); }else{ // Print the data received Serial.println(rc.data); } } if (Serial.available()) { String input = Serial.readString(); e22ttl.sendFixedMessage(0, 2, 23, input); } } void setConfiguration(){ ResponseStructContainer c; c = e22ttl.getConfiguration(); // It's important get configuration pointer before all other operation Configuration configuration = *(Configuration*) c.data; Serial.println(c.status.getResponseDescription()); Serial.println(c.status.code); configuration.ADDL = 0x03; // Low byte of address configuration.ADDH = 0x00; // High byte of address configuration.NETID = 0x00; // used for repeater function configuration.CHAN = 23; // Communication channel --> 433 MHz /* UART_BPS_xxx with xxx = Baudrate xxx = 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 */ configuration.SPED.uartBaudRate = UART_BPS_9600; // Serial baud rate /* AIR_DATA_RATE_000_03 0.3 kb/s AIR_DATA_RATE_001_12 1.2 kb/s AIR_DATA_RATE_010_24 2.4 kb/s AIR_DATA_RATE_011_48 4.8 kb/s AIR_DATA_RATE_100_96 9.6 kb/s AIR_DATA_RATE_101_192 19.2 kb/s AIR_DATA_RATE_110_384 38.4 kb/s AIR_DATA_RATE_111_625 62.5 kb/s */ configuration.SPED.airDataRate = AIR_DATA_RATE_010_24; // Air baud rate /* MODE_00_8N1 none MODE_01_8O1 odd MODE_10_8E1 even MODE_11_8N1 none */ configuration.SPED.uartParity = MODE_00_8N1; // Parity bit /* SPS_240_00 200 SPS_128_01 128 SPS_064_10 64 SPS_032_11 32 */ configuration.OPTION.subPacketSetting = SPS_240_00; // Packet size /* RSSI_AMBIENT_NOISE_DISABLED RSSI_AMBIENT_NOISE_ENABLED */ configuration.OPTION.RSSIAmbientNoise = RSSI_AMBIENT_NOISE_DISABLED; // Need to send special command /* POWER_xxx with xxx = power in dBm E22...T22D/S: xxx = 22, 17, 13, 10 E22...T30D/S: xxx = 30, 27, 24, 21 */ configuration.OPTION.transmissionPower = POWER_22; // Device power /* RSSI_DISABLED RSSI_DISABLED */ configuration.TRANSMISSION_MODE.enableRSSI = RSSI_DISABLED; // Enable RSSI info /* FT_FIXED_TRANSMISSION FT_TRANSPARENT_RANSMISSION */ configuration.TRANSMISSION_MODE.fixedTransmission = FT_FIXED_TRANSMISSION; // Transmission mode /* In the repeater mode, ADDH/ADDL is no longer used as the module address, it is used as a NETID to pair and forwarding. If the reperater receive the data from a network, then it will forward the data to the other network. The network ID of the repeater itself is invalid in this case. The repeater module cannot transmit and receive data, and cannot perform low-power operation. REPEATER_ENABLED REPEATER_DISABLED */ configuration.TRANSMISSION_MODE.enableRepeater = REPEATER_DISABLED; /* LBT_DISABLED LBT_ENABLED */ configuration.TRANSMISSION_MODE.enableLBT = LBT_DISABLED; // Check interference /* WOR_TRANSMITTER WOR_RECEIVER */ configuration.TRANSMISSION_MODE.WORTransceiverControl = WOR_RECEIVER; /* WOR_xxx_yyy with xxx = WOR Period in ms xxx_yyy = 500_000, 1000_001, 1500_010, 2000_011, 2500_100, 3000_101, 3500_110, 4000_111 */ configuration.TRANSMISSION_MODE.WORPeriod = WOR_2000_011; // WOR timing configuration.CRYPT.CRYPT_H = 0x00; // encryption high byte, default: 0x00 configuration.CRYPT.CRYPT_L = 0x00; // encryption low byte, default: 0x00 // Set configuration changed and set to not hold the configuration ResponseStatus rs = e22ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE); // WRITE_CFG_PWR_DWN_LOSE to not permanently save Serial.println(rs.getResponseDescription()); Serial.println(rs.code); c.close(); }
Anhang 3 – E32 Transceiver Sketch mit Einstellungen
Sketch mit eingebetteter Konfiguration für E32-Module. Ihr könnt die Funktion setConfiguration()
herauskopieren und in anderen Sketchen verwenden. Der Sketch wurde mit zwei E32-433T30D Modulen getestet.
// #define FREQUENCY_433 // default value without set // #define FREQUENCY_170 // #define FREQUENCY_470 // #define FREQUENCY_868 // #define FREQUENCY_915/* Choose your module */ // #define E32_TTL_100 // #define E32_TTL_500 #define E32_TTL_1W // E32-TTL-1W, E32-433T30S/D, E32-868T30S/D, E32-915T30S/D // #define E32_TTL_2W #include "LoRa_E32.h" SoftwareSerial mySerial(4,5); LoRa_E32 e32ttl(&mySerial, 3, 7, 6); // AUX M0 M1 void setup() { Serial.begin(9600); delay(500); Serial.println("Beginning configuration..."); e32ttl.begin(); setConfiguration(); Serial.println("Hi, I'm going to send message!"); ResponseStatus rs = e32ttl.sendBroadcastFixedMessage(23, "Hello, world?"); Serial.println(rs.getResponseDescription()); } void loop() { // If something available if (e32ttl.available()>1) { // read the String message ResponseContainer rc = e32ttl.receiveMessage(); // Is something goes wrong print error if (rc.status.code!=1){ Serial.println(rc.status.getResponseDescription()); }else{ // Print the data received Serial.println(rc.data); } } if (Serial.available()) { String input = Serial.readString(); e32ttl.sendFixedMessage(0, 2, 23, input); } } void setConfiguration(){ ResponseStructContainer c; c = e32ttl.getConfiguration(); // It's important get configuration pointer before all other operation Configuration configuration = *(Configuration*) c.data; Serial.println(c.status.getResponseDescription()); Serial.println(c.status.code); configuration.ADDL = 0x03; // Low byte of address configuration.ADDH = 0x00; // High byte of address configuration.CHAN = 23; // Communication channel --> 433 MHz /* After turning off FEC, the actual data transmission rate increases while anti-interference ability decreases. Also, the transmission distance is relatively short, and both communication parties must keep on the same pages about turn-on or turn-off FEC. FEC_0_OFF FEC_1_ON */ configuration.OPTION.fec = FEC_1_ON; /* FT_FIXED_TRANSMISSION FT_TRANSPARENT_RANSMISSION */ configuration.OPTION.fixedTransmission = FT_FIXED_TRANSMISSION; // Transmission mode /* Using internal pull-up resistors may make external redundant IO_D_MODE_OPEN_COLLECTOR IO_D_MODE_PUSH_PULLS_PULL_UPS */ configuration.OPTION.ioDriveMode = IO_D_MODE_PUSH_PULLS_PULL_UPS; /* for E32_TTL_1W POWER_xxx with xxx = power in dBm E32_TTL_100: xxx = 20, 17, 14, 10 E32_TTL_500: xxx = 27, 24, 21, 18 E32_TTL_1W : xxx = 30, 27, 24, 21 E32_TTL_2W : xxx = 33, 30, 27, 24 */ configuration.OPTION.transmissionPower = POWER_30; // Device power /* WOR period is called WAKE_UP time here WAKE_UP_xxx with xxx = Wake-Up Period xxx = 250, 500. 750, 1000, 1250, 1500, 1750, 2000 */ configuration.OPTION.wirelessWakeupTime = WAKE_UP_1250; /* AIR_DATA_RATE_000_03 0.3 kb/s AIR_DATA_RATE_001_12 1.2 kb/s AIR_DATA_RATE_010_24 2.4 kb/s AIR_DATA_RATE_011_48 4.8 kb/s AIR_DATA_RATE_100_96 9.6 kb/s AIR_DATA_RATE_101_192 19.2 kb/s AIR_DATA_RATE_110_192 19.2 kb/s AIR_DATA_RATE_111_192 19.2 kb/s */ configuration.SPED.airDataRate = AIR_DATA_RATE_010_24; // Air baud rate /* UART_BPS_RATE_xxx with xxx = Baudrate xxx = 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 */ configuration.SPED.uartBaudRate = UART_BPS_9600; // Serial baud rate // Set configuration changed and set to hold the configuration ResponseStatus rs = e32ttl.setConfiguration(configuration, WRITE_CFG_PWR_DWN_SAVE); // WRITE_CFG_PWR_DWN_LOSE to not save permanently Serial.println(rs.getResponseDescription()); Serial.println(rs.code); c.close(); }
Danksagung
Renzo Mischianti möchte ich für die wunderbaren Bibliotheken und die ausführlichen Tutorials danken.
Hallo Wolfgang,
vielen Dank für deinen tollen Beitrag.
Das ist hier fast die Bibel für IoT Selbstbau geworden … 😉
Ich hätte da mal eine Frage….
Die Module gibt es ja mit seriellen Interface und auch mit SPI Interface.
Mir ist nicht ganz klar wo die Vor- und Nachteile bei den Interfacetypen liegen.
Vielleicht kannst du da Licht in die Sache bringen…
Herzliche Grüße aus Dessau
Alain
Hi Alain,
die Module von EByte haben alle ein serielles Interface. Ich denke, mit den SPI-Modulen meinst du die Teile, die als LoRa SX126x oder LoRA SX127x angeboten werden? Die EByte-Module basieren auf denselben SX12xy Chips der Fa. SemTech. Insofern sind die technischen Eigenschaften (z.B. Übertragungsraten / Reichweiten) vergleichbar. Als ich über LoRa Module berichten wollte, stand ich genau vor der Frage, welche Teile ich nehmen sollte. Nachdem ich ein paar Bibliotheken angeschaut hatte, fand ich die von Renzo Mischianti am besten verständlich und am besten dokumentiert. Die anderen Teile habe ich bisher schlicht nicht ausprobiert. Werde ich irgendwann mal tun. SPI ist schneller als seriell, aber bei den langsamen Übertragungsraten der LoRa Technologie ist das nicht der Flaschenhals.
VG, Wolfgang
Moin, sag mal, ist der „Transparent Transmission Modus“ eigentlich ein propritäres Protokoll von EByte? Bzw das jeder Hersteller irgendwie implementiert? Ich habe nämlich einen Ai-Thinker RA-08 Board, für welches es auch eine „LoRaWAN Transparent Transmission Firmware Application“ https://www.youtube.com/watch?v=UulXDdYWaSk gibt. Allerdings bekomme ich das RA-08 Board (noch) nicht mit meinem E22-900T22U USB-Dongle zum Laufen.
EByte ist nur der Modulhersteller. Die zugrundeliegenden LoRa Chips wie etwa der SX1262 oder SX1268 wurden von der Semtech Corporation entwickelt. Was jetzt spezifisch von EByte, Semtech oder anderen ist und inwiefern das alles in den LoRa-/LoRaWan Standards festgelegt ist, kann ich dir leider nicht sagen. Mit dem Ai-Thinker RA-08 Board habe ich keine Erfahrung.
Ja, die Basics versuche ich auch gerade zu verstehen. Mittlerweile spricht wenigstens mein RA-08 im Transparent Mode mit einem Lora OpenMQTTGateway auf einem ttgo-Lora Board.
Allerdings ist mir nicht klar, ob es möglich ist die EByte Module in diesem propritären Transparent Modus mit ADDRL/H und Air Rate/airDataRate auch mit normalen Lora Modulen sprechen zu lassen. Schaut man sich Arduino Beispiel Code der gängigen Module an, findet man folgende Kommunikations-Parameter:
LORA_BANDWIDTH 0: 125 kHz, 1: 250 kHz,2: 500 kHz
LORA_SPREADING_FACTOR 7 // [SF7..SF12]
LORA_CODINGRATE [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
LORA_PREAMBLE_LENGTH 8
LORA_SYMBOL_TIMEOUT 0
LORA_FIX_LENGTH_PAYLOAD_ON
LORA_IQ_INVERSION_ON false
Nice work – here is Wolfie form Zaruchane vilage.
Hallo Wolfgang,
Danke für den Beitrag. Wieder einmal hast Du komplexe Materie in verständliche Form gebracht.
@Stefan Kluge: es gibt bereits ein Fernsteuer-Protokoll, das LoRa nutzt: ELRS. Es ist open-source und da kann man auch Sender und Empfänger selber bauen. Wobei die gekauften extrem leicht sind. Habe hier einen Rx, der 0,47 Gramm wiegt und den man per WLAN konfigurieren kann. Weiterer Vorteil: Es gibt Sendemodule mit bis zu 1 Watt Leistung. Damit werden Reichweiten von 30 km erreicht.
Ganz großes Kino, wie immer verständlich erklärt. Mal schauen ob man damit nicht eine einfache Fernsteuerung realisieren kann. THX
Herzlichen Dank. Für eine Fernsteuerung würde ich LoRa-Module aber nur nehmen, wenn es die Entfernung wirklich erfordert. Viel Spaß und Erfolg!
Hallo Wolfgang,
seit Monaten will ich einen Kommentar unter Deinen Beiträgen verfassen und habe es immer wieder auf die lange Bank geschoben. Jetzt ist es soweit. Auch, wenn ich noch keine Projekte von Dir nachgebaut habe, sind sie immer eine Bereicherung für mich und lassen meine Liste mit Ideen und Experimenten immer weiter anwachsen. Ich möchte mich an dieser Stelle für Dein Engagement und Deinen zeitlichen Invest bedanken, den du in diesen Blog und die Projekte steckst. Immer verständlich aufgebaut, gut erklärt… 1.000 Dank… Mach bitte weiter so… Irgendwann im Herbst lege ich los und habe bestimmt zum einen oder anderen Projekt eine Detailfrage oder schicke Dir meinen Spagetticode zur Fehleranalyse… 😉
Grüße aus Niedersachsen
Ulf
Vielen Dank und viel Erfolg für deine Pojekte!
Sehr interessant und wie immer: gute Erklärung mit viel Hintergrund.
DANKE