Hallo zusammen. Ich habe zwar schon gegoogelt, aber keine passende Lösung gefunden, deshalb hoffe ich hier auf Erleuchtung. Ich möchte mit einem Arduino 3 Messwerte einlesen. Die sollen dann über RS485 versendet werden und an einem zweiten Arduino über ein Display ausgegeben werden. Die Kommunikation über RS485 läuft so einigermaßen. Ich habe serial.write und serial.read verwendet. Die Werte treffen nicht gleichmäßig ein, als würden Pakete verpasst. Ist das normal? Die Hauptfrage ist, wie ich die Werte versenden kann und am Enpfanger wieder auseinander nehme. Ich denke, ein Array würde da passen. Wie lässt sich ein Array seriell versenden?
Über RS485 werden einzelne Zeichen gesendet und empfangen. Mehrere Zeichen sind ein String. Die Werte gibst du entweder dezimal oder hexadezimal aus. Hexadezimal ist platzsparend und hat den Vorteil einer konstanten Länge der Werte. Zwischen den Werten empfehlen sich Leerzeichen oder andere Trennzeichen. Dass nur bestimmte Zeichen (0-9,A-F) zulässig sind, ist schon ein bisschen Schutz gegen Übertragungsfehler. Besser ist aber noch eine Checksumme anzuhängen und zu überprüfen.
:
Bearbeitet durch User
Jobst Q. schrieb: > Mehrere Zeichen sind ein String. Die Werte gibst du entweder dezimal > oder hexadezimal aus. Also nacheinander: Ich müsste aus dem Array erstmal einen String machen und zwischen die Werte ein Trennungszeigen setzen, hab ich das so richtig verstanden?
Daniel O. schrieb: > Ich müsste aus dem Array erstmal einen String machen und zwischen die > Werte ein Trennungszeigen setzen, hab ich das so richtig verstanden? Ja, ist so zu empfehlen.
Alles klar, das probiere ich ersteinmal. Habe mich mit sowas noch nie beschäftigt. ^^
Daniel O. schrieb: > Also nacheinander: > Ich müsste aus dem Array erstmal einen String machen und zwischen die > Werte ein Trennungszeigen setzen, hab ich das so richtig verstanden? Das KANN man so machen, ist aber bei drei Meßwerten nicht wirklich nötig oder sinnvoll. Es reicht hier wahrscheinlich, die drei Meßwerte direkt binär zu senden, ggf. mit einer einfachen Prüfsumme an Schluß. Der Trick liegt im Empfänger, der die Zeichen empfangen und richtig zuordnen muss. Dabei muss vor allem berücksichtigt werden, daß irgendwie die Übertragung gestört sein kann. D.h. der Empfänger muss immer wieder den Anfang des Datenpakets erkennen. Bei der einfache Situation hier reicht ein timeout. D.h. wenn ein Zeichen empfangen wird, wird ein Timeout von ??ms gestartet. Treffen innerhalb dieser Zeit nicht alle Zeichen ein, liegt ein Übertragungsfehler vor und die Empfangsroutine wartet erneut auf den Anfang des Datenpakets.
Hallo RS485 ist in der Regel nicht biderecktional. Du must hardwertechnisch zwischen senden und empfangen umschalten. Gruesse
Ich würde ein Startzeichen (1 Byte), einen Telegrammzählerwert (1 Byte), die 3 Datenbytes und eine Prüfsumme (1 Byte) übertragen. Es werden pro Telegramm 6 Bytes übertragen. Es können so Übertragungsfehler und fehlende Telegramme erkannt werden.
Gerald K. schrieb: > Ich würde ein Startzeichen (1 Byte), einen Telegrammzählerwert (1 Byte), > die 3 Datenbytes und eine Prüfsumme (1 Byte) übertragen. Wie kommst Du darauf, dass ein Messwert ein Byte ist. @Daniel O.: Welchen Datentyp haben denn Deine Werte? Wie sieht dein aktueller Code für's Senden aus?
Wenn du es gerne standardisiert haben willst, dann schau dir Modbus RTU an, macht genau das was du erreichen willst und ist sehr einfach gehalten.
mm schrieb: > Wie sieht dein aktueller Code für's Senden aus? Da ich von Anfang an begonnen habe, um keine Fehler fort zu führen, so:
1 | #include <OneWire.h> |
2 | #include <DallasTemperature.h> |
3 | |
4 | #define ONE_WIRE_BUS 12 |
5 | #define TEMPERATURE_PRECISION 9 |
6 | |
7 | OneWire oneWire(ONE_WIRE_BUS); |
8 | DallasTemperature sensors(&oneWire); |
9 | |
10 | DeviceAddress Thermometer1 = { 0x28, 0xFF, 0x64, 0x1E, 0x83, 0x44, 0xA8, 0x20 }; |
11 | DeviceAddress Thermometer2 = { 0x28, 0xFF, 0x64, 0x1E, 0x83, 0x4F, 0x2D, 0xB0 }; |
12 | |
13 | float tempC; //Wert zum speichern der DS18B20-Fühler |
14 | |
15 | |
16 | void setup(void) |
17 | { |
18 | Serial.begin(9600); |
19 | sensors.begin(); |
20 | sensors.setResolution(Thermometer1, TEMPERATURE_PRECISION); |
21 | sensors.setResolution(Thermometer2, TEMPERATURE_PRECISION); |
22 | } |
23 | |
24 | |
25 | |
26 | void loop(void) |
27 | { |
28 | sensors.requestTemperatures(); |
29 | Serial.print("Temp 1: "); |
30 | Serial.print(sensors.getTempC(Thermometer1)); |
31 | Serial.print(", "); |
32 | Serial.print("Temp 2: "); |
33 | Serial.print(sensors.getTempC(Thermometer2)); |
34 | Serial.println(); |
35 | } |
Christian K. schrieb: >schau dir Modbus RTU an Das werd ich gleich mal machen.
Klaus H. schrieb: > Hallo > > RS485 ist in der Regel nicht biderecktional. Genau anders herum. RS485 wird MEISTENS bidirektional genutzt. RS422 ist nur unidirektional. Was du meinst ist Halbduplex. Da gibt es nur ein Signalpaar, auf dem die Daten in beide Richtungen gehen. Im Gegensatz zu Vollduplex, da gibt es für jede Richtung ein Signalpaar, ähnlich RS232, nur daß das nicht differnetiell arbeitet. > Du must hardwertechnisch zwischen senden und empfangen umschalten. Nö, denn der OP will ja nur unidirektional Daten senden. Vielleicht klappt's ja mit sinnvollen Beträgen beim nächsten Mal.
Daniel O. schrieb: >>schau dir Modbus RTU > an > > Das werd ich gleich mal machen. Naja, wenn gleich man da einiges lernen kann, ist das für den Anfang eher etwas zuviel.
mm schrieb: > Wie kommst Du darauf, dass ein Messwert ein Byte ist. Gerald K. schrieb: > die 3 Datenbytes
Das Verpacken in binäre strukturen plus Start/Stopp/Checksumme ist üblich. Vor allem weil es die Programmierfähigkeiten herausfordert und man da zeigen kann, was man kann. Es ist aber wesentlich einfacher, gerade für einen Beginner, Einfache "Strings zu senden. D.H. * mit sprintf oder ähnlichem einen String zu erzeugen, z.B. "M1=23456" * Den inclusive der terminierenden 0 zu übertragen und/oder "\n". * Auf der Empfangsseite das ganze mit scanf oder ähnlichem wieder in eine Zahl zu zerlegen. Der Vorteil: * Egal welche Endian ein System hat oder welche sizeof(int) * Mitlesen der Daten funktioniert im Klartext, man muss nicht extra ein Tool dafür basteln. * Für die Sicherheit reicht es oft, Parity einzuschalten, Framing-Errors auszuwerten und daraufhin ganze Strings zu verwerfen. Für Anfänger die was erreichen wollen, die beste Wahl. Für Anfänger die auf die lernen wollen, gerne fancy Protokolle
A. S. schrieb: > mm schrieb: >> Wie kommst Du darauf, dass ein Messwert ein Byte ist. > > Gerald K. schrieb: >> die 3 Datenbytes Äääh eine Behauptung mit der selben Behauptung zu "belegen" spricht jetzt nicht unbedingt von Kompetenz... Wie der Code zeigt, ist es nicht nur ein Byte. Aber er zeigt halt auch, dass da Arduinotypisch irgendwas zusammengehackt wurde, ohne genau zu wissen, was man da macht.
1 | struct HeaderType |
2 | {
|
3 | uint8_t Data1; |
4 | uint8_t Data2 |
5 | uint8_t Data3; |
6 | uint8_t crc8; //Pruefsumme |
7 | } __attribute__ ((packed)); |
8 | |
9 | union HeaderUnion |
10 | {
|
11 | struct HeaderType Header; |
12 | uint8_t Byte[4]; |
13 | };
|
> Vor allem weil es die Programmierfähigkeiten herausfordert und > man da zeigen kann, was man kann. Wenn solche Banalitaeten schon ausreichen um zu zeigen "was man kann", dann fange ich an mir Sorgen um die Programmierer zu machen. .-) Olaf
Daniel O. schrieb: > void loop(void) > { > sensors.requestTemperatures(); > Serial.print("Temp 1: "); > Serial.print(sensors.getTempC(Thermometer1)); > Serial.print(", "); > Serial.print("Temp 2: "); > Serial.print(sensors.getTempC(Thermometer2)); > Serial.println(); > } denke dein Programm überholt sich, du brauchst für die Ausgabe ca. 30msec oder dein Buffer läuft voll. Man kann leider nicht erkennen ob bei serial.print gewartet wird bis alle Zeichen gesendet sind
Angsthase schrieb: > denke dein Programm überholt sich, du brauchst für die Ausgabe ca. > 30msec oder dein Buffer läuft voll. Man kann leider nicht erkennen ob > bei serial.print gewartet wird bis alle Zeichen gesendet sind Kann man, denn der interne FIFO ist nicht unendlich groß. Wenn zu wenig Platz ist, muss serial.print() warten. Aber egal, man ballert keine Meßwerte ohne Pause so schnell raus, das ist sinnlos und kontraproduktiov. Man sollte eine sinnvolle Übertragungsfrequenz festlegen, erst recht bei sehr langsamen Temperatursensoren.
Falk B. schrieb: > Man sollte eine sinnvolle Übertragungsfrequenz > festlegen, erst recht bei sehr langsamen Temperatursensoren. Dies in der Kombination mit Olafs Vorschlag dürfte zum Ziel führen. In Arduinosprache könnte es so aussehen.
1 | struct
|
2 | {
|
3 | float temp1; |
4 | float temp2; |
5 | float dritterWert; |
6 | uint8_t crc; //Pruefsumme |
7 | } txData __attribute__ ((packed)); |
8 | |
9 | ...
|
10 | |
11 | void loop(void) |
12 | {
|
13 | sensors.requestTemperatures(); |
14 | txData.temp1 = sensors.getTempC(Thermometer1); |
15 | txData.temp2 = sensors.getTempC(Thermometer2); |
16 | txData.dritterWert = wasAuchImmer(); |
17 | crc8.restart(); |
18 | crc8.add(txData, sizeof(txData) - sizeof(txData.crc)); |
19 | txData.crc = crc8.getCRC(); |
20 | serial.write((char *) &txData, sizeof(txData)); |
21 | delay(1000); |
22 | }
|
Durch die Pause (die durchaus auch noch länger sein dürfte) muss man sich empfängerseitig auch nicht so viel Gedanken machen. Falls man zufällig mittendrin anfängt zu hören (mit Timeout), bekommt man nicht genug Daten, also werden sie einfach verworfen und nochmal gehört, bis man dann mal genug auf einmal bekommt. Ist zwar nicht schön, aber für eine Arduino-Bastelei sollte es reichen. OT: Kaum wirft man einen Blick in die Arduino-Referenz fallen einem schon drei Dinge auf, die "suboptimal" (bzw. fehlerträchtig) sind. Ich mag das Zeug wohl zurecht nicht.
Also dem Teilmit der Checksumme widme ich mich später. Ich habe schon drüber nachgedacht, ob es einfacher wäre, das I2C-Display mit Extendern zu betreiben, aber das ist ja nicht die Lösung. Aktuell gibt das Programm die Werte seriell in einer Zeile aus. Diese Werte müsste ich jetzt in einen String bekommen?
Daniel O. schrieb: > Also dem Teilmit der Checksumme widme ich mich später. Ich habe schon > drüber nachgedacht, ob es einfacher wäre, das I2C-Display mit Extendern > zu betreiben, Sicher, denn so eine Anzeige macht ein Mikrocontroller mit links nebenbei. Dafür 2 Controller zu verwenden ist meistens nicht nötig. Einzig dann, wenn der Abstand mehrere Meter beträgt, dann ist I2C eher nicht gut geeignet (wenn gleich möglich). > aber das ist ja nicht die Lösung. Warum nicht? > Aktuell gibt das Programm die Werte seriell in einer Zeile aus. Diese > Werte müsste ich jetzt in einen String bekommen? Das ist schon einer ;-)
Daniel O. schrieb: > Aktuell gibt das Programm die Werte seriell in einer Zeile aus. Diese > Werte müsste ich jetzt in einen String bekommen? Deine Zeile ist doch schon ein lesbarer Text-String. Aber "Human-Readable" macht halt die Empfangsseite nicht unbedingt einfacher. Wenn ich von Maschine zu Maschine (µC zu µC) kommuniziere, besteht keine Notwendikeit, es für den Menschen lesbar zu machen. Die Daten roh (binär) zu schicken verringert den Aufwand enorm. Wenn die Daten in einem packed struct gespeichert sind, liegen sie netterweise schon genau so im Speicher, wie man sie senden kann. Auf der anderen Seite einfach in den Speicher geschrieben können sie wieder als das gleiche struct ausgewertet werden. Daniel O. schrieb: > ob es einfacher wäre, das I2C-Display mit Extendern > zu betreiben Einfacher natürlich... Um welche Strecke geht es denn?
mm schrieb: > Daniel O. schrieb: >> Aktuell gibt das Programm die Werte seriell in einer Zeile aus. Diese >> Werte müsste ich jetzt in einen String bekommen? > > Deine Zeile ist doch schon ein lesbarer Text-String. Aber > "Human-Readable" macht halt die Empfangsseite nicht unbedingt einfacher. > > Wenn ich von Maschine zu Maschine (µC zu µC) kommuniziere, besteht keine > Notwendikeit, es für den Menschen lesbar zu machen. Die Daten roh > (binär) zu schicken verringert den Aufwand enorm. > Wenn die Daten in einem packed struct gespeichert sind, liegen sie > netterweise schon genau so im Speicher, wie man sie senden kann. Auf der > anderen Seite einfach in den Speicher geschrieben können sie wieder als > das gleiche struct ausgewertet werden. > > > Daniel O. schrieb: >> ob es einfacher wäre, das I2C-Display mit Extendern >> zu betreiben > > Einfacher natürlich... Um welche Strecke geht es denn? Es geht um ca. 30m. Also müsste ich die Daten z.B. als "Wert1,Wert2,Wert3" versenden?
Daniel O. schrieb: > Es geht um ca. 30m. > Also müsste ich die Daten z.B. als "Wert1,Wert2,Wert3" versenden? BINGO!
Jobst Q. schrieb: > Hexadezimal ist platzsparend und hat den Vorteil einer konstanten Länge > der Werte. Bei hexadezimaler Übertragung müssen pro Byte zwei Byte, ggf. zzgl. Trennzeichen übertragen werden. Was ist daran platzsparend. In welchem Wertebereich liegen die zu übertragenden Werte?
Daniel O. schrieb: > Es geht um ca. 30m. Also nix mit I²C direkt. > Also müsste ich die Daten z.B. als "Wert1,Wert2,Wert3" versenden? Natürlich, wenn Du Dir das Leben unnötig schwer machen willst. Einmal ist Wert1 "23.4" ein anderes Mal "34.56" oder "45.678". Du musst also entweder jedesmal den Text aufwändig interpretieren oder eine feste Anzahl an Nachkommastellen verwenden oder Dich von "float" verabschieden. Du kannst aber auch ganz einfach Deine Werte als Rohdaten übertragen wie oben demonstriert.
Da war doch die Sache mit der Synchronität ... Bit-Synchronität kann der UART von Haus aus. Wenn man ihm sagt, mit wie viel BPS die Daten reintröpfeln, synchronisiert er anhand der Signalwechsel seinen internen Takt so, dass das empfangene Signal in Bitmitte abgetastet wird. Auch die Wort-Synchronität stellt der UART sicher, indem er anhand von Start- und Stopp-Bits - richtige Konfiguration vorausgesetzt - den Anfang eines Zeichens (7- bzw. 8-Bit) findet. Was du die vorgenommen ist die Übertragung von Telegrammen/Paketen, bestehend aus mehreren Zeichen. Wie will der Empfänger entscheiden, welches Zeichen das Erste im Telegramm ist - wenn du es ihm nicht sagst? Üblicherweise definiert man Frames mit Start-Zeichen, ev. Stopp-Zeichen und/oder auch Prüfsummen, ev, auch eine fixe Frame-Länge - eben um es dem Empfänger einfach zu machen, auf die Frames zu synchronisieren -> Frame-Synchronität. Ohne jetzt das Fass "ISO/OSI-Referenzmodell" aufzumachen (man rutscht hier schon deutlich in Layer 2 / data link layer) rein, braucht man sich das nicht aus den Fingern zu saugen. Es ist durchaus legitim, bei den "Anderen" abzukucken. Als Starpunkt bietet sich z.B. https://en.wikipedia.org/wiki/Data_link_layer#Protocol_examples an.
Wolfgang schrieb: > Jobst Q. schrieb: >> Hexadezimal ist platzsparend und hat den Vorteil einer konstanten Länge >> der Werte. > > Bei hexadezimaler Übertragung müssen pro Byte zwei Byte, ggf. zzgl. > Trennzeichen übertragen werden. Was ist daran platzsparend. Es ist platzsparend gegenüber dem Dezimalformat, die konstante Länge ist aber bedeutsamer. Wenn man Dezimalzahlen durch führende Nullen eine konstante Länge gibt, tappt man leicht in die Oktalfalle. Binär ist zwar platzsparender als Text, ist aber nur bei großen Datenmengen von Vorteil. Gerade beim Experimentieren von Anfängern erleichtert die menschenlesbare Textform deutlich die Fehlersuche.
Ich habe jetzt etwas gefunden, was meinen Anforderungen genügt. Ich sende seriell "<abc,11.11,22.22,33.33,44.44,55.55>" und auf der Empfängerseite kommt es richtig in den Variabeln an. Das Original hat auch die Möglichkeit, Text als erste aAriabel zu senden. Ich bekomme den Teil des "Auf-Text-Warten" aber nicht aus dem Programm bei "parseData" raus, vielleicht hat jemand einen Tipp. Hier der Code:
1 | // Empfänger |
2 | |
3 | const byte numChars = 32; |
4 | char receivedChars[numChars]; |
5 | char tempChars[numChars]; // temporary array for use when parsing |
6 | |
7 | // variables to hold the parsed data |
8 | char messageFromPC[numChars] = {0}; |
9 | float integerFromPC = 0.0; |
10 | float wert1 = 0.0; |
11 | float wert2 = 0.0; |
12 | float wert3 = 0.0; |
13 | float wert4 = 0.0; |
14 | float wert5 = 0.0; |
15 | |
16 | boolean newData = false; |
17 | |
18 | //============ |
19 | |
20 | void setup() { |
21 | Serial.begin(9600); |
22 | Serial.println("bereit"); |
23 | Serial.println(); |
24 | } |
25 | |
26 | //============ |
27 | |
28 | void loop() { |
29 | recvWithStartEndMarkers(); |
30 | if (newData == true) { |
31 | strcpy(tempChars, receivedChars); |
32 | // this temporary copy is necessary to protect the original data |
33 | // because strtok() used in parseData() replaces the commas with \0 |
34 | parseData(); |
35 | showParsedData(); |
36 | newData = false; |
37 | } |
38 | } |
39 | |
40 | //============ |
41 | |
42 | void recvWithStartEndMarkers() { |
43 | static boolean recvInProgress = false; |
44 | static byte ndx = 0; |
45 | char startMarker = '<'; |
46 | char endMarker = '>'; |
47 | char rc; |
48 | |
49 | while (Serial.available() > 0 && newData == false) { |
50 | rc = Serial.read(); |
51 | |
52 | if (recvInProgress == true) { |
53 | if (rc != endMarker) { |
54 | receivedChars[ndx] = rc; |
55 | ndx++; |
56 | if (ndx >= numChars) { |
57 | ndx = numChars - 1; |
58 | } |
59 | } |
60 | else { |
61 | receivedChars[ndx] = '\0'; // terminate the string |
62 | recvInProgress = false; |
63 | ndx = 0; |
64 | newData = true; |
65 | } |
66 | } |
67 | |
68 | else if (rc == startMarker) { |
69 | recvInProgress = true; |
70 | } |
71 | } |
72 | } |
73 | |
74 | //============ |
75 | |
76 | void parseData() { // split the data into its parts |
77 | |
78 | char * strtokIndx; // this is used by strtok() as an index |
79 | |
80 | strtokIndx = strtok(tempChars, ","); // get the first part - the string |
81 | strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC |
82 | |
83 | strtokIndx = strtok(NULL, ","); |
84 | wert1 = atof(strtokIndx); |
85 | |
86 | strtokIndx = strtok(NULL, ","); |
87 | wert2 = atof(strtokIndx); |
88 | |
89 | strtokIndx = strtok(NULL, ","); |
90 | wert3 = atof(strtokIndx); |
91 | |
92 | strtokIndx = strtok(NULL, ","); |
93 | wert4 = atof(strtokIndx); |
94 | |
95 | strtokIndx = strtok(NULL, ","); |
96 | wert5 = atof(strtokIndx); |
97 | |
98 | } |
99 | |
100 | //============ |
101 | |
102 | void showParsedData() { |
103 | Serial.print("Text: "); |
104 | Serial.println(messageFromPC); |
105 | Serial.print("Wert 1: "); |
106 | Serial.println(wert1); |
107 | Serial.print("Wert 2: "); |
108 | Serial.println(wert2); |
109 | Serial.print("Wert 3: "); |
110 | Serial.println(wert3); |
111 | Serial.print("Wert 4: "); |
112 | Serial.println(wert4); |
113 | Serial.print("Wert 5: "); |
114 | Serial.println(wert5); |
115 | } |
Daniel O. schrieb: > Hier der Code: > ... Was mag wohl der Hinweis "Wichtige Regeln - erst lesen, dann posten! ... Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang" bedeuten? Schon Mal drüber nachgedacht?
Man möge es verzeihen, habe nicht dran gedacht. Hier also als Datei.
Daniel O. schrieb: > Ich bekomme den Teil des "Auf-Text-Warten" aber nicht aus dem > Programm bei "parseData" raus, vielleicht hat jemand einen Tipp. Du rufst in der Loop quasi im Vorbeigehen eine Funktion auf, die jedes Byte in einen Puffer schreibt, wenn ein Byte empfangen wurde. Dabei prüft sie, ob ein Zeilenende erkannt wird und erst dann übergibt sie den Puffer dem Parser. Auch prüft sie auf Pufferüberlauf/-unterlauf. Zwischen diesen Aufrufen kann die Loop beliebig viele andere Sachen machen. Wie lange es dauert, eine Zeile zu empfangen, spielt dann keine Rolle mehr.
1 | void receive_command(void) // receive from UART |
2 | {
|
3 | static char cbuf[CMD_MAX_LEN]; // command buffer |
4 | static uint8_t idx = 0; |
5 | uint8_t ch; |
6 | |
7 | if (ukbhit0() == 0) |
8 | return; |
9 | ch = ugetchar0(); |
10 | switch (ch) |
11 | {
|
12 | default:
|
13 | cbuf[idx] = ch; |
14 | if (idx < sizeof(cbuf) - 1) |
15 | idx++; |
16 | return; |
17 | case '\b': |
18 | if (idx) |
19 | idx--; // remove last char (interactive mode) |
20 | return; |
21 | case '\n': |
22 | case '\r': |
23 | if (idx == 0) // do nothing on empty commands |
24 | return; |
25 | cbuf[idx] = 0; |
26 | idx = 0; |
27 | execute(cbuf); |
28 | uputs0(cbuf); // send answer |
29 | }
|
30 | }
|
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.