Probleme bei serieller Datenübertragung

Guten Morgen zusammen :slight_smile:

Ich habe folgendes Problem.
Ich möchte gerne meinen DHT22 an einem Mega auslesen, was auch funktioniert. Die Werte sind plausibel.
Dann möchte ich diese gerne an einen UNO seriell senden, da sie dort im Fortgang des Projektes weiterverarbeitet werden sollen.

Momentan arbeite ich mich durch die Grundlagen von C und habe jedoch mit dem Thema Zeichenketten Probleme.

Mir ist klar dass ich am Sender ein Array mit den beiden Variablen mit der Länge 2 erzeugen muss.
Dies schicke ich dann über den HardwareSerial Port 2 (der Mega hat das) seriell an den UNO.

Auf der Empfängerseite müsste doch dann das Empfangene Array wieder zerlegt werden oder?

Jedenfalls habe ich das so gemacht und ich empfange nur willkürliche Zahlen.
Kann sich das bitte jemand ansehen.
Haut aber bitte nicht direkt druff wenn es trivial ist, bin mit den Zeichenketten noch nicht so weit >:(

#include "DHT.h" //DHT Bibliothek einbinden
#define DHTPIN 2  //DHT22 auf Pin 2
#define DHTTYPE DHT22  //Typ DHT22
DHT dht(DHTPIN, DHTTYPE);

int h_as_int; //Werte sollen als INT übertragen werden und später am Empfänger zur Dezimalzahl gemacht
int t_as_int;
int Messwerte[2]={ h_as_int, t_as_int};// Array der Länge 2 erzeugen und mit den beiden Variablen füllen

void setup() {
 Serial.begin(9600);  //Start des seriellen Monitors
 Serial2.begin(9600);  //Start der Hardware Seriellen Schnittstelle 2
 dht.begin(); //Start des Sensors DHT22
 delay(3000);  // "3 Sekunden warten bis Sensor hochgefahren ist"
}

void loop() {
 float h = dht.readHumidity();  //Feuchte auslesen
 float t = dht.readTemperature();  //Temperatur auslesen
 int h_as_int = round(h);  // ordentlich runden und nicht abschneiden, als INT speichern
 int t_as_int = round(t * 10);  // Komma verschieben und runden 
 Serial.print("Luftfeuchte");
 Serial.print(h_as_int);
 Serial.println("Temperatur");
 Serial.print(t_as_int); 
 Serial2.print(Messwerte[2]); //Messwerte über serielle Schnittstelle schicken
 delay(5000); //fünf Sekunden warten
}

Empfänger

int laenge=2;
char empfangeneDaten[2]; //Array der Länge 2

void setup(){
  Serial.begin(9600);}//Start der seriellen Schnittstelle

void loop() {
int i=0;

 if ( Serial.available())  //Wenn serielle Daten vorhanden...
 {
   empfangeneDaten[i++] = Serial.read();
   delay(2);
   Serial.print(char(empfangeneDaten[0])); //Wert1 Feuchte
   Serial.print(char(empfangeneDaten[1])); //Wert 2 Temp    
   }
 ClearArray(); //Array wieder freimachen für den nächsten Datensatz
   
}
void ClearArray()
{
  for (int x=0; x<laenge; x++)
  {
empfangeneDaten[x]=NULL;
  }
}

DHT22 seriell.pdf (640 KB)

Da sind mehrere Haken und Ösen drin :wink:
Der UNO hat nur eine HW-serielle, und die wird bereits für die USB Schnittstelle genutzt. Du musst also für die Verbindung zum Mega SoftwareSerial verwenden.

Das

int Messwerte[2]={ h_as_int, t_as_int};// Array der Länge 2 erzeugen und mit den beiden Variablen füllen

funktioniert nicht. Du musst die Werte im loop explizit eintragen ( könntest dann aber auch gleich deine beide int-Werte versenden )

Auch das

Serial2.print(Messwerte[2]); //Messwerte über serielle Schnittstelle schicken

macht nicht dass was Du willst. Du sendest hier einen Integer, der hinter deinem Array steht - also was vollkommen undefiniertes. Bei einem Array der Länge 2 gibt es nur die Inidizes 0 und 1. Und die musst Du einzeln verschicken.

Beim Empfänger kommt die Zahl als ASCII-String an. Damit Du das Ende erkennen kannst solltest Du sie deshalb als println schicken, sonst ist das nur eine Ziffernwurst. Und dann musst Du beim Empfanger dementsprechend auch auf das Zeilenende abfragen. Um Temperatur und Feuchte unterscheiden zu können, empfiehlt sich gegebenenfalls noch eine entsprechende Startkennung, z.B. 'T' imd 'F'

Und hier:
char empfangeneDaten[2]; //Array der Länge 2brauchst Du ein char-Array, das die komplette empfangene Zeile aufnehmen kann. Und die musst Du dann auch noch entsprechend interpretieren ( Kennung für Feuchte/Temperatur auswerten, und Ziffern wieder in Integer wandeln ( mit atoi() ),

Es geht also nicht ganz so einfach, wie Du dir das vorgestellt hast :wink:

Hallo zusammen,

vielen Dank schonmal. Ich werde mich jetzt da durcharbeiten. Wenn ich soweit bin werde ich den Code überarbeiten.

Das heißt dann also ich erstelle am Sender gar kein Array und sende beide INT - Werte über die Funktion Serial2.println hintereinander.
Mit Software Serial am UNO zu arbeiten ist auch sinnvoll. Sonst kollidieren die Datensätze mit dem USB - Port. Es ist nicht so einfach :o

Schöne Grüße

Stefan

Generell: print() sendet ASCII. write() sendet Binär

Entscheide dich dafür was du übertragen willst. Du redest was von Zeichenketten, aber dann behandelst du das auf dem Empfänger nicht so. Dazu muss bis zu einem Endzeichen (meistens ein Linefeed einlesen). Und man muss mit NULL terminieren.
Wozu dann auch das Array groß genug sein muss. Ein char Array der Länge 2 hat eigentlich nur Platz für ein Zeichen

Siehe hier:
https://forum.arduino.cc/index.php?topic=634159.msg4293510#msg4293510

Strings mit verschiedenem Inhalt voneinander unterscheiden geht hier sehr schön mit switch/case:

void setup()
{
  Serial.begin(9600);

  char t[] = "T25.3";
  char f[] = "F29";

  parseLine(t);
  parseLine(f);
}

void loop()
{
}

void parseLine(char* str)
{
  switch (str[0])
  {
  case 'T':
  case 't':
    Serial.println(atof(str + 1));
    break;
  case 'F':
  case 'f':
    Serial.println(atoi(str + 1));
    break;
  }
}

Einen Buchstaben voranstellen. Auf den kann man abfragen. Und dann bei der Konvertierung +1 machen damit die Funktion mit dem zweiten Buchstaben anfängt

Hallo zusammen,

ich kann einen Erfolg verzeichnen. Die Werte kommen sauber an. Im Buch von Herrn Brühlmann ist ein gutes Beispiel.
Jetzt habe ich nur noch ein Problem: Wie bekomme ich den String so zerlegt dass ich wie im vorherigen Beitrag mit der Switch Case Bedingung die Werte in Temp und Hum unterscheiden kann?

Im seriellen Monitor des Empfängers sehe ich jetzt F50 ; T223 Das entspricht 50%Feuchte und 22,3°C

Die beiden Werte möchte ich jetzt in zwei Variablen haben

#include "DHT.h" //DHT Bibliothek einbinden
#define DHTPIN 2  //DHT22 auf Pin 2
#define DHTTYPE DHT22  //Typ DHT22
DHT dht(DHTPIN, DHTTYPE);

int h_as_int; //Werte sollen als INT übertragen werden und später am Empfänger zur Dezimalzahl gemacht
int t_as_int;


void setup() {
 Serial.begin(9600);  //Start des seriellen Monitors
 Serial2.begin(9600);  //Start der Hardware Seriellen Schnittstelle 2
 dht.begin(); //Start des Sensors DHT22
 delay(3000);  // "3 Sekunden warten bis Sensor hochgefahren ist"
}

void loop() {
 float h = dht.readHumidity();  //Feuchte auslesen
 float t = dht.readTemperature();  //Temperatur auslesen
 int h_as_int = round(h);  // ordentlich runden und nicht abschneiden, als INT speichern
 int t_as_int = round(t * 10);  // Komma verschieben und runden 
 Serial.print("Luftfeuchte");
 Serial.print(h_as_int);
 Serial.println("Temperatur");
 Serial.print(t_as_int); 
 Serial2.print("F");
 Serial2.print(h_as_int);
 Serial2.print(" ; "); 
 Serial2.print("T");
 Serial2.println(t_as_int);
 //Messwerte über serielle Schnittstelle schicken
 delay(5000); //fünf Sekunden warten
}
#include <SoftwareSerial.h> // Software Serial Bibliothek einbinden

SoftwareSerial mySerial(2, 3); //PIN2 ist RX, PIN 3 ist TX
char empfangeneDaten[255];
int laenge=0;
void setup()

{
  Serial.begin(9600); //Start der seriellen Schnittstelle, an der der USB angeschlossen ist
  mySerial.begin(9600); //Start der seriellen Schnittstelle, an der Datensätze empfangen werden sollen
}

void loop() {
  int i=0;
// Wenn serielle Daten vorhanden sind
if (mySerial.available() > 0) {
  while (mySerial.available()) {
    empfangeneDaten[i++] = mySerial.read();
    delay(2);
  }
  Serial.print(empfangeneDaten);
 
}
ClearArray();
}

void ClearArray()
{
  for (int x=0; x<laenge; x++)
  {
    empfangeneDaten[x] = NULL;
  }
}

Schau mal hier, da habe ich ein Bespiel beschrieben.

Gruß Tommy

Hi

Du liest die Zeichen einzeln ein.
Wenn Du ein F erkennst, leerst Du die variable für die Feuchte - Alles, was ab nun kommt, landet da drin.
Erkennst Du ein T, Gleiches mit der Temperatur.

Ok, Alles war gelogen - wir prüfen auf >='0' && <='9' - wenn also ein Zahlzeichen übergeben wurde, dann

  1. alter_Wert*=10; //alten Wert x 10
  2. alter_Wert+=Zeichen; //Das Zeichen addieren
  3. alter_Wert-=0x30; //'0' -> 0x30, wenn wir '0' addieren und 0x30 abziehen, haben wir +0 gerechnet
    ... klappt auch bei den Zahlziffern 1-9, nur bleibt Da halt 'mehr' über.

Als Trennzeichen verwendest Du ; bzw. das Zeilenende CR/LF.
Wenn ein Trennzeichen gefunden wurde, wird mit der Erkennung/Erweiterung des Wert aufgehört.

Verständlich?

MfG

Den Null-Terminator unterschlägst du immer noch...So wird das nicht. Außerdem überprüfst du nicht ob du über Array Grenzen schreibst (Bei 255 Byte wird das nicht passieren, aber man muss nicht gleich übertreiben). Und bei höheren Baudraten bekommst du so auch Probleme.

In dem Link von mir ist eine fertige Einlese-Funktion die das alles berücksichtigt. Die fragt immer wieder ob was im Eingangspuffer ist. Wenn ja wird das Zeichen eingelesen und NULL zurückgegeben. Wenn ein LF eingelesen wurde bekommt man einen Zeiger auf den Puffer und kann was damit machen.
Das geht alles nicht-blockierend. Man kann dann dazwischen andere Sachen machen aber die Funktion muss ständig in loop() aufgerufen werden. Nicht nur einmal.

Wenn du ein 'F' oder 'T' voranstellst kannst du zwei getrennte Zeilen senden. println() hängt CR + LF an. Auf das LF kann man dann abfragen und weiß wenn eine Zeile fertig ist. Wie man dann von da auch die Werte kommt steht in Beitrag #4

Alternativ kann man auch beides in einer Zeile senden (ohne Buchstaben) und mit strtrok() trennen. Aber entscheide dich für eine Variante und vermische das nicht wie du es oben gemacht hast!

So, so langsam ist das Ziel in Sicht. Habs jetzt mit der zweiten Variante gemacht. Beide Werte ohne Buchstaben in eine Zeile geschrieben und mit Semikolon getrennt. Diese kommen dann auch so an.
Den komischen Nullterminator brauche ich dann wohl auch nicht.

Wenn ich die zerlegten Strings auf dem seriellen Monitor ausgebe zeigt dieser mir dafür aber null an. Hat jmd. eine Ahnung wo dran das liegt. Kann ja nicht mehr viel sein :slight_smile: :slight_smile: :slight_smile:
Ich habe mich in die Funktion strtok() eingelesen und "denke" das ist wohl so richtig. Über die Atoi Befehle hole ich mir den Integer des Wertes. Aber warum kommt immer null?

#include "DHT.h" //DHT Bibliothek einbinden
#define DHTPIN 2  //DHT22 auf Pin 2
#define DHTTYPE DHT22  //Typ DHT22
DHT dht(DHTPIN, DHTTYPE);

int h_as_int; //Werte sollen als INT übertragen werden und später am Empfänger zur Dezimalzahl gemacht
int t_as_int;


void setup() {
 Serial.begin(9600);  //Start des seriellen Monitors
 Serial2.begin(9600);  //Start der Hardware Seriellen Schnittstelle 2
 dht.begin(); //Start des Sensors DHT22
 delay(3000);  // "3 Sekunden warten bis Sensor hochgefahren ist"
}

void loop() {
 float h = dht.readHumidity();  //Feuchte auslesen
 float t = dht.readTemperature();  //Temperatur auslesen
 int h_as_int = round(h);  // ordentlich runden und nicht abschneiden, als INT speichern
 int t_as_int = round(t * 10);  // Komma verschieben und runden 
 Serial.print("Luftfeuchte");
 Serial.print(h_as_int);
 Serial.println("Temperatur");
 Serial.print(t_as_int); 
 Serial2.print(h_as_int);
 Serial2.print(";"); 
 Serial2.println(t_as_int);
 //Messwerte über serielle Schnittstelle schicken
 delay(5000); //fünf Sekunden warten
}
#include <SoftwareSerial.h> // Software Serial Bibliothek einbinden

SoftwareSerial mySerial(2, 3); //PIN2 ist RX, PIN 3 ist TX
char empfangeneDaten[255];
int laenge=0;
char str[]=";";//Trennzeichen sind Semikolon
int value1;//Luftfeuchte
int value2;//Lufttemperatur

void setup()


{
  Serial.begin(9600); //Start der seriellen Schnittstelle, an der der USB angeschlossen ist
  mySerial.begin(9600); //Start der seriellen Schnittstelle, an der Datensätze empfangen werden sollen
}

void loop() {
  int i=0;
// Wenn serielle Daten vorhanden sind
if (mySerial.available() > 0) {
  while (mySerial.available()) {
    empfangeneDaten[i++] = mySerial.read();//Daten von der seriellen Schnittstelle einlesen
    int value1 = atoi(strtok(str, ";"));//Wert 1 Feuchte
    int value2 = atoi(strtok(NULL, ";"));//Wert 2 Temperatur
   
    delay(2);
  }
  Serial.println(empfangeneDaten);
  Serial.print(value1);
  Serial.println(value2);
}
ClearArray();
}

void ClearArray()
{
  for (int x=0; x<laenge; x++)
  {
    empfangeneDaten[x] = NULL;
  }
}

Hi

Dein Arduino ist ein startendes Flugzeug.
Deine seriellen Daten sind die Rentner am Rollator.
Nun überlege, wie viele Rentner Du mit Deinem Flugzeug 'deuten' kannst, bevor Du weit weit weg bist.

Du musst SAMMELN, bis die Daten alle da sind - und DANN ERST auswerten.

Momentan prüfst Du, 'ob Was Da ist'.
Egal Was ... Hauptsache: Es ist Was da.
Dann versuchst Du, Dieses 'egal Was' in zwei Happen einzulesen - ob hier überhaupt schon der Erste überhaupt angefangen hat ... ich möchte Behaupten: Nö
Dann gibst Du die soeben erlangten (gar nicht vorhandenen) Daten aus.
Dein 'Glück' besteht noch darin, daß atoi wohl Null zurück gibt, wenn Nix kam - sonst hättest Du irgend welche Hausnummern, da der Speicherplatz, wo Du Deine INTs anlegst, zuvor undefiniert war.

Also: Gerne zügig, aber auf manches muß man auch warten können (Warten nun aber nicht mit delay() gleich setzen)

MfG

Dein ganzes Prinzip ist Murks. Du brauchst ein Endzeichen! Das hast du mit dem Linefeed von println(). Im Moment geht das durch das delay() nach jedem Zeichen, aber schön ist das nicht. Bei dieser Anwendung wird es zwar keine Rolle spielen, aber wenn man sonst noch was machen will stört das

Den komischen Nullterminator brauche ich dann wohl auch nicht.

Falsch! Bitte verlasse sich dich nicht darauf dass Code zufällig das macht was du erwartest wenn du nicht verstehst was da eigentlich abläuft. atoi() geht in bestimmten Situationen auch ohne Terminator, da es abbricht wenn es auf etwas trifft das keine Ziffer ist. Die meisten anderen String Funktionen wie strtok() oder auch println() erwarten einen richtig terminierten String. Wie sollen die sonst merken dass sie Abbrechen sollen?

Wenn man das richtig macht braucht man auch kein clearArray(). Dadurch dass du ganze Array immer auf 0 setzt hat du eine Terminierung drin. Es reicht aber wenn man einfach das letzte Zeichen auf 0 setzt.
Und wenn man wirklich ein ganzes Array beschreiben will nimmt man normal memset()

Dann kannst du nicht ein Zeichen einlesen und sofort Auswerten. Das ist der Hauptfehler. Du musst bis zum Ende einlesen. Und da kommt wieder das Linefeed in Spiel

So macht man es richtig:
https://forum.arduino.cc/index.php?topic=634159.msg4293510#msg4293510

Es geht auch mit anderen Varianten aber dann hat man Dinge wie Blockierungen und Timeouts drin. Und deren minimal nötige Zeiten hängen von der Baudrate ab.

Da kommt wieder null. Ich glaube aber dass es wohl eine Hausnummer zu groß ist und so kein Sinn ergibt.
Ist zwar schade, wollte mir eine Wetterstation aufbauen und habe es schon geschafft mit dem Arduino über Modbus Profisensoren auszulesen aber hier klappts irgedwie nicht.

#include <SoftwareSerial.h> // Software Serial Bibliothek einbinden

SoftwareSerial mySerial(2, 3); //PIN2 ist RX, PIN 3 ist TX
const unsigned int READ_BUFFER_SIZE = 17;    //16 Zeichen + Terminator
 char* txt ;
 int text;
void setup()


{
  Serial.begin(9600); //Start der seriellen Schnittstelle, an der der USB angeschlossen ist
  mySerial.begin(9600); //Start der seriellen Schnittstelle, an der Datensätze empfangen werden sollen
}




void loop()
{
  const unsigned int READ_BUFFER_SIZE = 17;    //16 Zeichen + Terminator
  char* txt = readLine(mySerial);
  if (txt != nullptr)
  {
    Serial.print("Empfangen: ");
    Serial.println(text);
  }
}

char* readLine(Stream& stream)
{
  static byte index;
  static char buffer[READ_BUFFER_SIZE];

  while (stream.available())
  {
    char c = stream.read();

    if (c == '\n')          //wenn LF eingelesen
    {
      buffer[index] = '\0';   //String terminieren
      index = 0;
      return buffer;        //melden dass String fertig eingelesen wurde
    }
    else if (c >= 32 && index < READ_BUFFER_SIZE - 1)   //solange noch Platz im Puffer ist
    {
      buffer[index++] = c;    //Zeichen abspeichern und Index inkrementieren
    }
  }
  return nullptr;           //noch nicht fertig
}

READ_BUFFER_SIZE ist eine globale Variable. Das nochmal lokal zu machen bringt nichts. txt reicht als lokale Variable

Sendest du beim Sender ein Linefeed am Ende? Auf print() reagiert das nicht. Da muss println() stehen (das sendet CR + LF und das CR wird ignoriert).
Den Unterschied merkst du auch wenn du das einfach mal mit Serial und dem seriellen Monitor ausprobierst

Guten Abend zusammen,

ich habe jetzt die Zeit gefunden den Code anzupassen und es funktioniert. Vielen lieben Dank dafür, war ne schwierige Kiste...

#include <SoftwareSerial.h> // Software Serial Bibliothek einbinden
SoftwareSerial mySerial(2, 3); //PIN2 ist RX, PIN 3 ist TX

void setup (){
  Serial.begin(9600);
  mySerial.begin(9600); //Start der definierten seriellen Schnittstelle
  Serial.println(F("Format\nFeuchte;Temperatur"));
}

void loop()
{
  const byte SERIALBUFFERSIZE = 30;
  const byte ENDOFLINE = 13;
  static char lineBuffer[SERIALBUFFERSIZE];
  static byte lineCounter;
  static byte lineState = 0;
  //einlesen
  if (mySerial.available() > 0)//Wenn Daten auf der seriellen Schnittstelle vorhanden, dann....
  {
    byte inChar;
    inChar = mySerial.read();
    if (inChar == ENDOFLINE)
    {
      lineBuffer[lineCounter] = '\0';
      lineState = 1;
    }
    else if (inChar >= SERIALBUFFERSIZE)
    {
      lineBuffer[lineCounter] = inChar;
      if (lineCounter < SERIALBUFFERSIZE - 2) lineCounter++;
    }
  }
  //auswerten
  if (lineState)
  {
    // zerteilen
    int value0 = atoi(strtok(lineBuffer, ";"));//Wert  Feuchte zu einem INT wandeln
    int value1 = atoi(strtok(NULL, ";"));//Wert  Temperatur zu einem INT wandeln
    float Takt = value1/10.0;// Temperatur in Dezimalformat wandeln
   
    Serial.print(F("value0=")); Serial.println(value0);//Ausgabe der Daten auf dem seriellen Monitor
    Serial.print(F("value1=")); Serial.println(value1);
    Serial.print(Takt);
    // status zurücksetzen
    lineState = 0;    // RESET
    lineCounter = 0;  // RESET
  } 
}

"ne schwierige Kiste..." mit humoristischen Einlagen.

if (inChar >= SERIALBUFFERSIZE)

@noiasca:
Warum schreibst du nicht

const byte SERIALBUFFERSIZE = ' ';

:wink: