Über diesen Beitrag
In den letzten Beiträgen habe ich vorgestellt, wie man mittels geeigneter Bibliotheken Funkmodule und Funksteckdosen mit dem Arduino oder anderen Microcontrollern ansteuert. Aber geht 433 MHz Funk auch ohne Bibliothek? Ist es kompliziert, eigene (einfache) Funkprotokolle zu entwickeln? Wenn man ein paar Dinge beachtet, ist es nicht sonderlich schwierig und macht darüber hinaus auch viel Spaß. Ich stelle hier zwei Konzepte vor. Dabei benutze ich einfache 433 MHz Transmitter- und Receivermodule wie in den vorherigen Beiträgen. Bitte nicht vergessen Antennen an die Module zu basteln. Ein Anschlussschema für den Arduino muss ich wohl nicht noch einmal abbilden: GND an GND, VCC an 5 V und den Datapin des Moduls gemäß Vorschrift im jeweiligen Sketch. Für die Transmitterseite habe ich einen Arduino Nano eingesetzt, auf der Receiverseite einen UNO.
Schnelligkeit ist gefragt
Bevor es losgeht, muss ich noch ein kleines bisschen ausholen. Funktechnik ist so schnell, dass die Geschwindigkeit, in der ein Pinstatus (HIGH oder LOW) abgefragt werden kann, durchaus relevant wird. Normalerweise verwendet man den digitalRead Befehl, hier z.B. zum Abfragen des Pin 5:
pinStatus = digitalRead(5);
Gleichbedeutend, wesentlich schneller, aber auch viel kryptischer ist die „C“ Schreibweise:
pinStatus = PIND & (1<<PD5);
Ich werde vielleicht in einem anderen Beitrag auf das Thema „C“ näher eingehen, denn es gibt auch viele andere Gelegenheiten, wo die „C“ Schreibweise sinnvoll ist. Hier werde ich nur kurz erklären, was das bedeutet. „PD5“ ist eine vordefinierte Konstante mit dem Wert 5 und steht für den Pin 5 am PORTD des ATmega 328. Dieser Pin ist zufälligerweise der Digitalpin 5 am Arduino. Der Pin „PB5″ ist beispielsweise Digitalpin 13 am Arduino. „1<<PD5“ ist eine Binäroperation und bedeutet: verschiebe die 1 um fünf Stellen nach links. Also wird aus der 1, sprich der 00000001, wird eine 00100000.
PIND ist das Statusregister des PORTD. Sind alle Pins „LOW“ steht im PIND Register „00000000“. Ist nur PD5 „HIGH“, steht dort „00100000“. „&“ ist ein binäres „UND“. Es vergleicht bitweise beide Werte links und rechts von sich. Sind beide gleich, ist das Ergebnis „TRUE“ bzw. „1“. Sind beide ungleich, ist das Ergebnis „FALSE“ bzw. „0“. Und damit führen beide oben genannten Befehle zum selben Ergebnis.
Wieviel schneller die „C“ Schreibweise ist, testet der folgende Sketch. Ich habe den Status des Pin 5 mit beiden Befehlen 100000 Male auslesen lassen und die dafür benötigte Zeit in Millisekunden messen lassen. Hier der Sketch und das Ergebnis:
unsigned long numberOfReads = 100000; unsigned long startTime = 0; unsigned long readTimeLength = 0; bool pinStatus; void setup() { Serial.begin(9600); pinMode(5,INPUT); } void loop() { startTime = millis(); for(unsigned long i=0; i<numberOfReads; i++){ pinStatus = digitalRead(5); } readTimeLength = millis() - startTime; Serial.print("digitalRead: "); Serial.print(readTimeLength); Serial.print(" ms "); Serial.print(" / "); delay(1000); startTime = millis(); for(unsigned long i=0; i<numberOfReads; i++){ pinStatus = (PIND & (1<<PD5)); } readTimeLength = millis() - startTime; Serial.print("PIND Abfrage: "); Serial.print(readTimeLength); Serial.println(" ms"); delay(5000); }
Demnach ist der „C“ Befehl ca. achtmal schneller. Pro Einzelabfrage ergeben sich 3.45 Mikrosekunden gegenüber 0.44 Mikrosekunden.
Viel Rauschen im Äther
Eigentlich klingt es zunächst einfach: Auf der Transmitterseite führt ein „HIGH“ am Datenpin zum Senden eines Signals. Dadurch wird an der Receiverseite ein „HIGH“ am Datenpin erzeugt und das lässt sich hinsichtlich der Länge auswerten. Wir wenden hier also keine Modulationsverfahren an. Einfach, aber funktioniert.
Der folgende Sketch für die Receiverseite fängt Signale auf und misst deren Länge am Pin 5. Um Zeiten zu messen, verwenden die meisten wohl gerne den „millis()“ oder „micros()“ Befehl. Zum Messen sehr kurzer Zeiträume hat sich eine andere Methode bewährt. In einer Schleife wird die Bedingung für die Messung abgefragt. Solange die Abbruchbedingung nicht erfüllt ist, wird in der Schleife eine kurze Zeit (die Auflösung / Resolution) gewartet und dann ein Zähler inkrementiert. Am Ende ergibt Zähler x Auflösung die Zeit.
const int resolution = 5; unsigned int counter; void setup(){ Serial.begin(9600); DDRD = 0x00; // Arduino Pins D0 - D7 als Input, "C" Schreibweise PORTD = 0x00; // Arduino Pins D0 - D7 sind LOW, "C" Schreibweise } void loop(){ counter = 0; while(!(PIND & (1<<PD5))){/* Wait for High*/} while((PIND & (1<<PD5))){ /* Count High */ delayMicroseconds(resolution); counter++; } Serial.print(counter); Serial.print(", "); }
Lässt man den Sketch laufen, ohne dass man einen Sender eingeschaltet hat, ist man erstaunt, wie viele Signale trotzdem detektiert werden. Wie soll man in dieser Suppe aber nun sein „echtes“ Signal finden?
Das Prinzip
Man kann ein Signal als „echtes“ Signal kennzeichnen, indem man es in ein eindeutig identifizierbares Start- und Endsignal einbettet. In meinem ersten Testsignal sind dies überlange „HIGH“ Dauersignale von 10000 µs. Dazwischen finden sich die „HIGH“ Informationssignale von 400, 600, 800,….., 2000 µs. Diese sind durch „LOW“ Phasen von 600 µs separiert.
Der Sketch dazu sieht folgendermaßen aus und braucht glaube ich keine weitere Erklärung:
int txPin = 10; /* Transmitter Daten Pin */ const int lowLength = 600; /* konstante Low Phase */ void setup(){ pinMode(txPin, OUTPUT); } void loop(){ unsigned int mcs; startSequence(); /*Sende die Startsequenz*/ for(int i=0; i<9; i++){ /* Sende neun Signale; High: 400, 600, .... */ digitalWrite(txPin, HIGH); mcs = i*200 + 400; delayMicroseconds(mcs); digitalWrite(txPin,LOW); delayMicroseconds(lowLength); } endSequence(); /* Sende die Endsequenz */ delay(5000); } void startSequence(){ digitalWrite(txPin, HIGH); delayMicroseconds(10000); digitalWrite(txPin, LOW); delayMicroseconds(lowLength); } void endSequence(){ digitalWrite(txPin, HIGH); delayMicroseconds(10000); digitalWrite(txPin, LOW); }
Kommen wir zum Receiversketch. Der Receiver wartet zunächst auf ein gültiges Startsignal. Ist das gegeben, also eine bestimmte Länge überschritten, werden die Informationssignale analysiert, d.h. im Wechsel werden die Längen der „HIGH“ und „LOW“ Phasen gemessen. Diese Werte werden in einem zweidimensionalen Array „signalLength“ als „lowCounter“ und „highCounter“ gespeichert. Die Länge der Signale ist dann low- bzw. highCounter x Resolution (hier 20 µs). Wenn der „highCounter“ eine definierte Größe überschreitet, so handelt es sich um das Endesignal. Die Sequenz ist damit vollständig übermittelt und wird auf dem seriellen Monitor ausgegeben.
int resolution = 20; void setup(){ Serial.begin(9600); DDRD = 0x00; // Arduino Pins D0 - D7 als Input PORTD = 0x00; // Arduino Pins D0 - D7 sind LOW } void loop(){ waitForSignal(); } void waitForSignal(){ while(!(PIND & (1<<PD5))){/*Warte bis ein Signal kommt*/} if(startSequence()){ /* Ist das Signal die Startsequenz? */ analyseSignal(); /* Wenn ja, dann analysiere das Signal */ } } void analyseSignal(){ int signalCounter = 0; /* Zähler für die High/Low Signalpaare */ int lowCounter = 0; /* Low Signallänge = lowCounter * resolution; */ int highCounter = 0; /* High Signallänge = highCounter * resolution; */ int signalLength[15][2]; /* Die Signale werden in einem zweidimensionalen array gespeichert */ bool msgCompleted = false; while(!msgCompleted){ /* Solange wir uns in einer Sequenz befinden */ lowCounter = 0; highCounter = 0; while(!(PIND & (1<<PD5))){ /* Warten auf High, messen des Low Signals */ delayMicroseconds(resolution); lowCounter++; } while(PIND & (1<<PD5)){ /* Warten auf Low, messen des High Signals */ delayMicroseconds(resolution); highCounter++; } if(highCounter < 200){ /* Ein Low/High paar wurde empfangen */ signalLength[signalCounter][0] = lowCounter; signalLength[signalCounter][1] = highCounter; signalCounter++; } else if(highCounter < 5000){ /* Ende Signal empfangen, das if ist redundant */ msgCompleted = true; } } /* Es folgt die Ausgabe */ Serial.println("low / high"); for(int i=0; i<signalCounter; i++){ Serial.print(signalLength[i][0]); Serial.print(" / "); Serial.println(signalLength[i][1]); } Serial.println("--------"); delay(2000); } bool startSequence(){ int counter = 0; while(PIND & (1<<PD5)){ delayMicroseconds(resolution); counter++; } if(counter >400){ /* Das Startsignal wurde empfangen */ return true; } else{ return false; } }
Und, siehe da, man erhält schöne unterscheidbare Signale. Da die Auflösung 20 µs beträgt und die „LOW“ Signale 600 µs lang sind, müsste der lowCounter 30 betragen. Genau genommen 29, da er bei Null los zählt. Wir messen also um die 100 µs zu viel. Und das, was bei den „LOW“ Signalen zu viel ist, fehlt bei den „HIGH“-Signalen. Wahrscheinlich brauchen die Receivermodule eine gewisse Zeit um bei Signalempfang den Datenpin auf „HIGH“ zu setzen. Auf jeden Fall bekommt man hier guten Eindruck, in welchen Geschwindigkeiten man Daten übertragen kann.
Protokoll 1 – bitweise Übertragung
Die Grundidee
Da wir nun gesehen haben, dass man Informationen über die Signallänge übermitteln kann, ist es nun an der Zeit, eigene Funkprotokolle zu entwickeln. Im ersten Beispiel übertragen wir Bits, die über die „HIGH“ Phasenlänge definiert werden. Eine „0“ ist ein 600 µs Signal und eine „1“ ein 1200 µs Signal. Dazwischen gibt es feste „LOW“ Signale von 600 µs. Nimmt man mal an, dass Nullen und Einsen gleich oft vorkommen, dann ergibt sich eine durchschnittliche Signallänge von 1500 µs, d.h. eine Übertragungsrate von „sagenhaften“ 0,666 kbit/s. Durch das Grundlagenexperiment weiter oben haben wir gesehen, dass auch sehr viel kürzere Signale noch eindeutig unterschieden werden können. Hier geht also noch ordentlich was in Sachen Geschwindigkeit.
Der Transmittersketch
Die zu übermittelnde Information habe ich als String definiert. Einen String kann man zunächst sehr bequem in seine einzelnen Zeichen zerlegen: euerString[ites Zeichen]. Für die Zerlegung der Zeichen in ihre 8 Bits verwende ich eine Binäroperation in der Funktion „sendeByte“, die Ihr – wenn Ihr oben gut aufgepasst habt – verstehen solltet. In Kurzform: Das Byte wird schrittweise nach rechts verschoben. Dann wird mit dem logischen „UND“ geprüft, ob das jeweils erste Bit „1“ oder „0“ ist. Entsprechend wird dann ein kurzes oder langes „HIGH“ in der Funktion sendeSignal() gesendet. Das Ende Signal habe ich auf 5000µs gekürzt.
int txPin = 10; /* Transmitter Data Pin */ void setup() { pinMode(txPin, OUTPUT); Serial.begin(9600); } void loop() { String msgString = "Hallo Welt, wie geht es dir heute?"; sendeString(msgString); delay(5000); } void sendeString(String msg) { int len = msg.length(); initMsg(); /* Sende das Initialisierungssignal */ for (int i = 0; i < len; i++) { /* Byte by byte senden */ sendeByte(byte(msg[i])); } closeMsg(); } void sendeByte(byte msgByte) { /*Bit by bit senden */ bool msgBit = 0; for (int i = 7; i >= 0; i--) { /* "Extrahieren" der bits */ msgBit = (msgByte >> i) & 1; sendeSignal(msgBit); } } void sendeSignal(bool sendBit) { digitalWrite(txPin, HIGH); if (sendBit == false) { delayMicroseconds(600); } else { delayMicroseconds(1200); } digitalWrite(txPin, LOW); delayMicroseconds(1000); } void initMsg() { /* Initialisierung */ digitalWrite(txPin, HIGH); delayMicroseconds(10000); digitalWrite(txPin, LOW); delayMicroseconds(600); } void closeMsg() { /*Abschlusssignal */ digitalWrite(txPin, HIGH); delayMicroseconds(5000); digitalWrite(txPin, LOW); delayMicroseconds(600); }
Der Receiversketch
Der Receiversketch ähnelt sehr dem Receiversketch für das Testsignal. Zunächst wird auf ein gültiges Startsignal gewartet, dann werden die Signallängen ausgewertet und als Bits interpretiert. Die Bits werden über Binäroperationen zu Bytes zusammengesetzt, die Bytes in Characters umgewandelt und aus diesen der String wieder zusammen gesetzt. Da nur ganze Bytes übertragen werden, sollte am Ende kein Bit mehr übrig sein. Und wenn doch, dann ist irgendetwas schief gegangen und ein Error Flag wird gesetzt.
int resolution = 20; /* Auflösung */ int error = 0; /* Fehler flag */ void setup(){ Serial.begin(9600); DDRD = 0x00; PORTD = 0x00; Serial.println("Warte auf Signal..."); } void loop(){ listenToSignal(); } void listenToSignal(){ int lowPulse = 0; int highPulse = 0; byte bitNo = 0; byte incomingByte = 0; String msg = ""; int msgLen = 0; bool msgCompleted = false; error=0; while(!(PIND & (1<<PD5))){/*Wait*/} if(startSequence()){ while(!msgCompleted){ lowPulse = 0; highPulse = 0; while(!(PIND & (1<<PD5))){ delayMicroseconds(resolution); lowPulse++; } while(PIND & (1<<PD5)){ delayMicroseconds(resolution); highPulse++; } if(highPulse < 45){ incomingByte = (incomingByte << 1); } else if(highPulse < 70){ incomingByte = (incomingByte << 1) + 1; } else if(highPulse >= 150){ msgCompleted = true; if(bitNo!=0){ error = 1; } } if(bitNo<7){ bitNo++; } else{ msg = msg + char(incomingByte); bitNo = 0; msgLen++; incomingByte = 0; } } if((!error)&&(msg.length()!=0)){ Serial.println(msg); } // else{ // Serial.println("error"); // } } } bool startSequence(){ int counter = 0; while(PIND & (1<<PD5)){ delayMicroseconds(resolution); counter++; } if(counter > 300){ return true; } else{ return false; } }
Und siehe da – es funktioniert!
Protokoll 2 – Zahlen ziffernweise übertragen
Die Grundidee
Diesen Abschnitt habe ich noch einmal nachträglich in 2024 bearbeitet.
Bei diesem Protokoll werden nur Ganzzahlen übertragen. Dazu zerteilen wir die Zahl in ihre Ziffern und kodieren diese über ihre Länge. Damit wir auch Nullen übertragen können, bekommt jedes Signal noch ein Offset.
Die Transmitterseite
Die zu übertragende Zahl definieren wir als Character Array. Dadurch ist sie einfach zerlegbar. Und wir müssen uns keine Gedanken um die maximale Größe von Integer- oder Long Integer-Werten machen.
int dataPin = 5; /* Transmitter data pin */ const int lowLength = 600; /* low phase is constant */ char message[] = "0123456789"; void setup() { pinMode(dataPin, OUTPUT); } void loop() { unsigned int mcs; sendStartSequence(); for (unsigned int i=0; i < sizeof(message)-1; i++) { /* */ /* Make an integer from the char array element */ char element[1] = {message[i]}; int digit = atoi(element); mcs = digit * 200 + 250; // mcs: signal length in microseconds digitalWrite(dataPin, HIGH); delayMicroseconds(mcs); digitalWrite(dataPin,LOW); delayMicroseconds(lowLength); } sendEndSequence(); /* Send the terminating sequence */ delay(5000); } void sendStartSequence() { digitalWrite(dataPin, HIGH); delayMicroseconds(10000); digitalWrite(dataPin, LOW); delayMicroseconds(lowLength); } void sendEndSequence() { digitalWrite(dataPin, HIGH); delayMicroseconds(10000); digitalWrite(dataPin, LOW); }
Der Receiversketch
Der Receiver wartet zunächst auf das lange Startsignal. Hat er das erhalten, dann werden die eingehenden Signale hinsichtlich ihrer Länge ausgewertet. Diese Werte speichert der Sketch in einem Array ab. Über einen Umrechnungsfaktor werden aus den Längen die Ziffern rekonstruiert. Ggf. müsst ihr den Umrechnungsfaktor bei euch anpassen. Mithilfe des Beispieles der Übertragung 0123456789 sollte gut sichtbar werden, ob der Faktor passt.
int resolution = 15; int dataPin = 5; #define LEN_TO_NUMBER_DIVIDER 11 /* Divider to calculate numbers from signal length */ void setup() { Serial.begin(115200); pinMode(dataPin, INPUT); } void loop() { waitForSignal(); } void waitForSignal() { while (!digitalRead(dataPin)) {/*Wait until data pin receives a signal*/} if (startSignalReceived()) { /* Is the start sequence valid wrt its length? */ Serial.println("Received valid start signal"); analyzeIncomingMessage(); } } void analyzeIncomingMessage(){ int signalCounter = 0; /* Counter for the number of signals */ int signalLength[15]; /* Signal length array*/ bool msgCompleted = false; while (!msgCompleted){ signalLength[signalCounter] = 0; while (!digitalRead(dataPin)) { /* Wait for high signal */ delayMicroseconds(resolution); } while (digitalRead(dataPin)) { /* Measure the length of the high signal */ delayMicroseconds(resolution); signalLength[signalCounter]++; } if (signalLength[signalCounter] < 200) { /* Received message content */ signalCounter++; } else { /* Received the termnating signal */ msgCompleted = true; } } /* Es folgt die Ausgabe */ Serial.println("Signal length: "); for (int i=0; i<signalCounter; i++) { Serial.println(signalLength[i]); } Serial.print("Number received: "); for (int i=0; i<signalCounter; i++) { Serial.print(signalLength[i]/LEN_TO_NUMBER_DIVIDER); } Serial.println(); Serial.println("--------"); delay(1000); } bool startSignalReceived() { int counter = 0; while (digitalRead(dataPin)) { delayMicroseconds(resolution); counter++; } if (counter > 400) { /* Start signal has been received */ return true; } else { return false; } }
Hier noch die Ausgabe, die zeigt, dass es passt:
Die Ausgabe der Signallängen könnt ihr noch herausnehmen.
Und dann könntet ihr noch eigene Protokolle entwickeln. Wie wäre es zum Beispiel mit Morsecode?
Sir one last help how can i use your rf protocol with attiny 814,
Pl guide with simple tx rx code
I have tried RCSwitch sketches and at least they compile when using Spence Konde’s package and choosing the ATtiny814 board. Did you experience a compiler error or did it just not work?
If you want to „translate“ the sketches in this article for MegaTiny MCUs the register programming is a little different. You can find a crash course here:
https://wolles-elektronikkiste.de/en/arduino-nano-every-a-deep-dive#reg_programming_in_c
I wrote this for the Arduino Every (Atmega4808/4809) but it’s the same principle.
Instead of DDRB = 0; you have to write PORTB.DIRSET = 0;
Or DDRB |= (1<<PB2); would be PORTB.DIRSET = PIN2_bm;
To set a pin high, where you are used to using e. g. PORTB |= (1<<PB2); and you would use PORTB.OUTSET = PIN2_bm; instead.
To read a pin level you use e.g. if(PORTB.IN & PIN2_bm) instead of if(PINB & (1<<PB2));
Does this help? Sorry, I do not have the bandwith to write complete sketches.
And have you seen that this article is also available in English? I am just asking because your comments are on the German version.
sir good morning
today i spent 7 hrs working on your attached library.
but i didnt get any results on receiver side.
what could be the reason.
i am using arduino uno for tx as well as Rx part.
please help me.
Hi,
when you say attached library, you mean Radio_WE? Did you use the example sketches that are part of Radio_WE? Did you change anything in the example sketches? At least, you should have changed the pins.
And are sure you want to use that lib? All it can do is transmitting a number consisting of exactly 6 digits. This library is just a concept. It should work, of course, but it’s far away from being a convenient, mature piece of code.
You can send me the sketches you have tried by e-mail (wolfgang.ewald@wolles-elektronikkiste.de). Then I will have look.
Have you checked if your modules work? Do the sketches in this article work?
Regards, Wolfgang
Regards, Wolfgang
Hello,
I tried the Radio_WE now after some years again and indeed it has issues. I am sorry for that. I will simply delete it, since it is not efficient.
But still, I would like to know if this is the lib that you were referring to when you said you used the attached library. Or did you use RadioHead?
Regards, Wolfgang
sir
how can i use rc-swich library for mega tiny core ,
as i am mechanical engineer, doing good in embadded systems, by the help of people like you those are open source teacher for whole world.
any way i want to decode EV1527 code using Mega timy 814, using RC-Switch lib please help .
Thanks in advance.
Hi,
I am not sure how I can help. Here, I have described how to use the RC-Switch lib:
https://wolles-elektronikkiste.de/en/radio-sockets-and-hand-transmitters
For programming Megatiny MCUs, I recommend the board package megaTinyCore by Spence Konde. Here, I have described how to use it:
https://wolles-elektronikkiste.de/en/using-megatinycore
So, with these to articles, you should at least get the RC-Switch lib running on an Megatiny 814.
I had to look what an EV1527 is and learned that it is an OTP Encoder. I have no experience with this, and therefore it’s difficult to give you advice. Maybe you can tell me what the specific problem is you face?
Regards, Wolfgang
Big Thanks for reply.
Spence konde board package doesn’t support rc switch lib.
Any other way or lib that i can use.
Again thanks and God bless you, helping people.
My experience is, that RC-switch runs with megatinycore, as long as you are not using interrupts. With interrupts it is not working any more.
At the moment I’m also looking for an alternative to RC-switch on that type of boards
Hi, just in case there is a simple RC-switch replacement needed for transmitter only, this is what I added to my sketch. Protocols are flexible, actually protocol 1 is chosen. Basis for this I found this in some forum
const int rfPin = 3; // pin 5
const int txRepeat = 15; //
const int txPulselength = 350; // protocol 1
void rfSend(unsigned long code, byte length) {
for (int nRepeat = 0; nRepeat = 0; i–) {
if (code & (1L < 0)
{
delayMicroseconds(txPulselength);
us–;
}
}
void rfTransmit(byte high, byte low) {
digitalWrite(rfPin, HIGH);
delay_pulselengths(high);
digitalWrite(rfPin, LOW);
delay_pulselengths(low);
}
void setup () {
pinMode(rfPin, OUTPUT);
} // end of setup
void loop () {
rfSend(3848390, 24); // toggle, 3848390₁₀ = 3AB8C6₁₆
}
Lieber Hr.Ewald.
Ich möchte mich einfach mal bei Ihnen bedanken,
für die vielen Hinweise und Beispiele, die ich Ihren Seiten bereits mehrfach habe entnehmen können
und die mir vielfach weitergeholfen haben.
Alles Gute und vielen Dank !
MfG
Jürgen Weidemann
Wülfrath
Vielen Dank Herr Weidemann, das ist sehr freundlich und motivierend!
VG, Wolfgang Ewald
Huhu Wolfgang,
ich nehme an , du lernst gerne dazu …, daher mal einige Hinweise:
– msgBit = (msgByte >> i) & 1; //wenig sinnvoll, schau dir mal die shift functions in Cores von Spence Kode an, (dort von mir eingebracht), das variable shiften kosten viel Zeit/Code,
anstatt
void sendeByte(byte msgByte) { /*Bit by bit senden */
bool msgBit = 0;
for (int i = 7; i >= 0; i–) { /* „Extrahieren“ der bits */
msgBit = (msgByte >> i) & 1;
sendeSignal(msgBit);
}
}
//simple (less run time & programm code)
void sendByte(uint8_t msgByte) {
for (uint8_t i=8; i!=0; msgByte>>=1,i–) {
sendSignal(msgByte & 1);
}
}
Hinweis:
Parameter von Funktionen sind immer lokale Kopien, die auf den Stack abgelegt sindUND können direkt genutzt/manipuliert werden. Ich sehe oft (auch hier), dass nochmals lokale Kopien benutzt werden – das ist sinnfrei.
zerlegeZahl() soll offensichtlich binary zu packed bcd Darstellung also bin2bcd() realisieren, dazu gibt es wesentlich effizientere Methoden als die hier verwende, Goggle ist dein Freund!
Allgemein viel Redundanz im Code Style, z.B.
//anstatt
if (sendBit == false) {
delayMicroseconds(600);
}
else {
delayMicroseconds(1200);
}
//übersichtlich
delayMicroseconds(sendBit? 1200 : 600);
Ein Protokoll zu definieren ohne Verwendung von Daten Strukturen (struct) ist selten übersichtlich u. effizient und ohne Zustandsdiagram sind oft/immer unberücksichtigte/unerkannte Zustände im Protokollablauf die dann für Überraschungen/Fehler sorgen. Es ist fehleranfällig State Machines mit if/else anstatt switch/case zu implementieren, da die Übersicht verloren geht.
Gut möglich, wenn ich das Protokoll analysiere (und dazu gehören sämtliche Zeitbedingungen – auch der wd-tmr), dann finde ich unbeachte Zustände!
Ein Protokoll State-Diagramm ist sehr hilfreich und eher Pflicht, weil ohne nur Bastelei.
Das Protokoll beinhaltet keine Fehlererkennung und das ist ganz schlecht.
Hier mal mein einfaches CRC-8 als Anregung, was auch für one-wire Protokolle verwendbr ist.
//test data+crc: int8_t crc_test[] = {0x10,0x50,0xA9,0x0A,0x02,0x08,0x00,0x37};
//*****************************************************************************
uint8_t owi_crc(ds18xx_t *pSensor, const uint8_t num) { //X^8+X^5+X^4+X^0
//*****************************************************************************
uint8_t crc = 0;
for (uint8_t j=0; j!=num; j++) {
crc ^= pSensor->scratchpad[j];
for (uint8_t i=0; i!=8; i++)
crc = (crc>>1) ^ ((crc & 1)? 0x8C : 0);
}
return crc; //value:0 means no error if cal. based on data+crc
}
Eine Preamble (dein Code Wort oder sonst was) ist hier immer notwendig und hat nichts mit Rx Synchronization zu tun (womit die Tx Taktgewinnung im Rx gemeint wird), sondern mit der HW der Rx Filtertechnik. Simple ausgedrückt, damit die analogen Filter einschwingen, sonst hat die Filterantwort ein Phasendelay.
Das gilt auch für IRed Empfänger IC, daher sollte der 38KHz Träger vorab der Datenübertragung immer aktiv geschaltet werden.
Keep going!
Vielen Dank! Ich lerne immer gerne dazu. Heute würde ich wohl insbesondere die Beiträge aus der Anfangszeit wohl auch ein bisschen anders schreiben. So umfassende Dinge bekomme ich aber nicht mehr eingepflegt. Das schaffe ich nicht in meiner One-Man-Show.
Hut ab, wie du hier mit viel Motivation/Liebe die Themen presentierst, darstellst und als Person rüber kommst! Das könnte ich nicht mal ansatzweise. Ich verstehe was du schreibst und denke ähnlich!
Das Arduino Framework ist zwar C++, ABER von den C++ Features ist da fast nichts verwendet. Der Code ist im wesentlichen eher C11 und da gibt es auch einige interessane Feature die du vielleicht noch nicht kennst wie „compound literals“ oder „sequence point innerhalb switch/while/if. Hierzu gibt es 2-4 gute Bookz, z.B. 21st Century C, Klemens oder Moderne C Programmierung, Schellig, die du hier findest http://libgen.rs/. Lese den Klemens, du wirst garantiert ein Level in C aufsteigen und überrascht sein, weil viel Neues jetzt dazu kommt.
Ich programmiere auch in Python/Micropython seit 4 Jahren und denke erst 30% zu verstehen! Ein C Program in Python Syntax (was einfach zu erreichen ist) hat nichts mit Python zu tun. Ich denke, Python reicht für mich für Jahrzehnte und macht mir richtig viel Spass. Es gibt in der MicroPython Community 2-3 People von den kannst du lernen was Python wirklich kann/bedeutet, der eine davon ist der Autor von asyn, Peter Hinch https://github.com/peterhinch, schau dir seinen Code an und du lernst viel dazu. Ich bin immer noch am Anfang von meiner Reise!
Hallo Wolle,
danke für die Anleitung und den Code. Das war wirklich extrem informativ und hilfreich!
Vor einigen Monaten hatte ich beim netten Chinesen aus dem Netz einen Sack voll 433Mhz Transmitter WL102-341 und Empfänger WL101-341 für einen sehr schmalen Taler erstanden. Ziel ist es eine Batteriegestützte Temperatur und Feuchteerfassung für jeden Raum zu bauen. Dabei kommt jeweils ein ATtiny85V, ein HTU21D und ein WL102-341 aufs Board.
Empfängerseite ist ein WL101-341 mit einem ESP8266 (ESP-01) der von allen 433MHz Sendern die Werte annimmt und über WLAN und MQTT die Daten weiter schickt.
Ich habe mich in den letzten 2 Tagen mit der 433MHz Übertragung beschäftigt und bestimmt 15 weitere graue Haare bekommen, denn es gab einige Klippen zu umschiffen.
Der Code der RadioHead Library welchen Du in einem anderen Blogpost über 433MHz erwähnst ist zwar einfach zu nutzen, frisst aber enorm viel Speicher auf meinem kleinen Tiny85. Aus diesem Grund entschied ich mich für Deinen PWM-ähnlichen Code. Leider hatte ich immer wieder Probleme beim Empfang mit der Genauigkeit. Häufig waren Störsignale (kurze 160µs Blibs) zwischen den High-Leveln. Das konnte ich zwar noch etwas eindampfen indem ich die if … else etwas angepasst und die Pegellängen mehr eingegrenzt habe. Leider führte das immer noch zu ca 10% Ausschuss.
Erst ein Blick in den Code von RadioHead brachte die Erleuchtung. Da steht nämlich in der RH_ASK.cpp:
// Initialise the first 8 nibbles of the tx buffer to be the standard
// preamble. We will append messages after that. 0x38, 0x2c is the start symbol before
// 6-bit conversion to RH_ASK_START_SYMBOL
uint8_t preamble[RH_ASK_PREAMBLE_LEN] = {0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x38, 0x2c};
0x2a entspricht Binär 101010 und wird jeder Übertragung vorangestellt. Somit ergibt sich ein alternierendes Signal welches offenbar der Synchronisierung des Empfängers mit dem Sender dient. Ich habe somit Deinen Code etwas erweitert:
void initMsg() { /* Initialisierung */
uint8_t preamble_len = 32;
do {
digitalWrite(txPin, !(PIND & (1 << PIND5)));
delayMicroseconds(500);
} while (–preamble_len);
digitalWrite(txPin, HIGH);
delayMicroseconds(10000);
digitalWrite(txPin, LOW);
delayMicroseconds(600);
}
Tatsächlich werden die störenden Blibs beim Empfang auf ein Minimum reduziert. Eine Anpassung am Code des Empfängers ist nicht nötig, da der ja auf das 10ms Startsignal wartet und somit die vorangehenden Signale ignoriert.
Ein weiterer Härtefall ist der Watchdog Timer im ESP, sowie die Interrupts. Diese sollte man in jedem Fall abschalten während das Signal analysiert wird. Ich habe dafür am Begin der listenToSignal() Routine noch folgendes:
ESP.wdtDisable();
hw_wdt_disable();
noInterrupts();
Natürlich am Ende nicht vergessen wieder einzuschalten 😉
Dann ist mir aufgefallen, daß man die Empfangsroutine noch besser und schneller gestalten kann, indem man statt den while(PIND & (1<= 500 && highPulse <= 700) {
incomingByte = (incomingByte << 1);
…
Als Dritte und letzte Klippe bin ich mit dem Debugging der Pulslängen beim Empfang böhöse ins Straucheln gekommen. Ich hab mich immer gewundert, warum der Empfang der ersten paar Bytes problemlos klappt und dann immer wieder falsche Werte liefert. Ich hatte direkt nach dem Messen der Pulslänge ein serial.print(…) im Code. Und genau das hat mir das ganze Timing verhauen. Der ESP (wie auch der Tiny85) vertrödelt für das Senden der UART-Daten so viel Zeit, daß er die nächsten 1-2 Bits des Senders "verpasst". Also: in Zeitkritischen Routinen niemals Debugging-Code einfügen 😉
Danke für Deinen Anstoß und die Idee der Codierung. Weiter so!
Hallo David,
danke für den reichhaltigen Kommentar! Die Erweiterung um die Initialisierung ist ein hilfreicher Hinweis. Aber auch der Hinweis zu dem ESP Watchdog – das hat mich bei anderen Projekten schon einige Zeit gekostet.
Ich setze mittlerweile meistens HC-12 Module ein. Die sind zuverlässig und einfach anzusteuern. Fast schon langweilig wenn etwas einfach so funktioniert. Und leider sind die Teile etwas teurer.
Viel Spaß noch bei deinen Projekten!
VG, Wolle
Tolle Idee!
Leider habe ich Probleme mit der Umsetzung auf ESP32.
Im letzten Abschnitt der 2. Funklösung Receive-Modul an der Stelle:
while(!messageCompleted || noSignal);
Kann ich noch einen Breakpunkt setzen, beim nächsten Stepp bootet der ESP32 ohne Ende.
Hab mit Hardwaredebugging in VS Code PlatformIO keinen Erfolg.
Gibt es irgend welche Erkenntnisse, die weiterhelfen könnten?
Kann es sein, dass hier der Watchdog Timer des ESP32 zuschlägt? Jedenfalls ist das ein Problem beim ESP8266. Der startet bei solchen Leerschleifen nach 3 oder 8 Sekunden (Hardware / Software Watchdog. Wenn das der Fall ist, müsste man dem ESP32 zwischendurch etwas zu tun geben, den Watchdog Timeout verlängern oder den Watchdog abstellen, wenn möglich. Ich habe leider gerade nicht so viel Zeit, das im Detail zu recherchieren. Ich hoffe das hilft erstmal weiter. Sonst melde dich nochmal.