Xbee: Senden/Empfangen mehrerer Analogwerte

Guten Abend,

ich versuche mittels zwei Xbee´s Analogwerte zu übertragen. Das funktioniert auch sehr gut.

Um die “ASCII” Werte in “int” umzurechnen, habe ich mich folgendem Code bedient.

Serial.print("<");
Serial.print(heading);
Serial.print(">");
char inData[10];
int index;
boolean started = false;
boolean ended = false;

void loop()
{
  while(Serial.available() > 0)
  {
      char aChar = Serial.read();
      if(aChar == '>')
      {
          started = true;
          index = 0;
          inData[index] = '\0';
      }
      else if(aChar == '>')
      {
          ended = true;
      }
      else if(started)
      {
          inData[index] = aChar;
          index++;
          inData[index] = '\0';
      }
  }

  if(started && ended)
  {
[i]      // Convert the string to an integer
      int inInt = atoi(inData);
         Serial.print("Analogwert 1:");
         Serial.println(inInt);[/i]

      // Get ready for the next time
      started = false;
      ended = false;

      index = 0;
      inData[index] = '\0';
  }
}

Quelle: http://forum.arduino.cc/index.php/topic,39609.0.html#2

Auch das funktioniert sehr gut (ob es bessere Methoden gibt?!).

Nun möchte ich einen zweiten Analogen Wert auf die selbe Weise umrechen, was auch noch klappt. Dieser zweite - korrekte - Wert überschreibt nun aber den ersten.

char inData2[10];
int index2;
boolean started2 = false;
boolean ended2 = false;

....

  {
      char aChar2 = Serial.read();
      if(aChar2 == '>')
      {
          started2 = true;
          index2 = 0;
          inData2[index2] = '\0';
      }
      else if(aChar2 == '>')
      {
          ended2 = true;
      }
      else if(started2)
      {
          inData2[index2] = aChar2;
          index2++;
          inData2[index2] = '\0';
      }
  }

  if(started2 && ended2)
  {
[i]      // Convert the string to an integer
      int inInt2 = atoi(inData);
         Serial.print("Analogwert 2:");
         Serial.println(inInt2);[/i]

      // Get ready for the next time
      started2 = false;
      ended2 = false;

      index2 = 0;
      inData2[index2] = '\0';
  }
}

Wo liegt der Fehler, bzw. ist es auf diese Weise überhaupt realisierbar?

Gruß

Wenn ich es richtig verstehe, wird der Wert im Empfänger überschrieben.

Du musst den Wert mit einem Index versehen und anhand des Index den empfangenen Wert im Empfänger auswerten.

Oder die beiden Werte durch Komma getrennt zusammensetzen senden und im Empfänger wieder trennen.

Konnte leider nicht weiter machen in den letzten Tagen.

Aber warum muss ich dem Wert mit einem Index versehen, es sind ja zwei verschiedene Werte (inInt2 / inInt1)? Wie genau soll ich da noch einen Index einbauen?

LG

sxon: Konnte leider nicht weiter machen in den letzten Tagen.

Aber warum muss ich dem Wert mit einem Index versehen, es sind ja zwei verschiedene Werte (inInt2 / inInt1)? Wie genau soll ich da noch einen Index einbauen?

Damit der Arduino des Empfängers das unterscheiden und anhand des Index diese wieder trennen kann.

So wie ich es verstanden habe, überschreibt der zweite Wert den ersten. Was du schreibst, sind die Variablennamen, die werden nicht mit übertragen, nur der Inhalt.

Du kannst aber auch meinen zweiten Vorschlag mit dem Komma oder einen beliebigen anderen Trenner verwenden.

Genau, der erste Wert überschreibt den zweiten. Ich weiß leider nicht wie ich das mit dem index bzw. die Methode mit dem zusammenfassen umsetzen soll. Der index ist mir bis jetzt nur aus den array bekannt - aber wie gesagt weiß ich nicht wie ich das umsetzen muss.

Hier einfach ein Linefeed/Newline als Endzeichen senden (sonst geht es nicht!). Die Werte mit einem Komma oder Strichpunkt abgetrennt.

Also z.B: “123,456\n”

const int SERIAL_BUFFER_SIZE = 20;
const int MAX_VALUES = 2;

char serialBuffer[SERIAL_BUFFER_SIZE];
int values[MAX_VALUES];

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

void loop()
{
  if (readSerial(Serial) == true)
  {
    parseSerial();

    for (int i = 0; i < MAX_VALUES; i++)
      Serial.println(values[i]);
    Serial.println("---");
  }
}

bool readSerial(Stream& stream)
{
  static byte index;

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

    if (c >= 32 && index < SERIAL_BUFFER_SIZE - 1)
    {
      serialBuffer[index++] = c;
    }
    else if (c == '\n' && index > 0)
    {
      serialBuffer[index] = '\0';
      index = 0;
      return true;
    }
  }
  return false;
}

void parseSerial()
{
  char* ptr = strtok(serialBuffer, ",;");
  int index = 0;

  while (ptr != NULL && index < MAX_VALUES)
  {
    values[index++] = atoi(ptr);
    ptr = strtok(NULL, ",;");
  }
}

Alternativ kann man sich auch das Speichern des Strings sparen (wobei mit Strings letztlich flexibler ist, da man den Code leichter anpassen kann). Hier wie du ‘<’ und ‘>’ als Endzeichen verwenden.

const int MAX_VALUES = 2;

int values[MAX_VALUES];

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

void loop()
{
  if (readSerial(Serial) == true)
  {
    for (int i = 0; i < MAX_VALUES; i++)
      Serial.println(values[i]);
    Serial.println("---");
  }
}

bool readSerial(Stream& stream)
{
  static byte index;

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

    if (c == '<')
    {
      index = 0;
      values[0] = 0;
    }
    else if (c == '>')
    {
      return true;
    }
    else if (c == ',' || c == ';')
    {
      index++;
      if (index < MAX_VALUES)
        values[index] = 0;
    }
    else if (c >= '0' && c <= '9')
    {
      if (index < MAX_VALUES)
      {
        values[index] *= 10;
        values[index] += (c - '0');
      }
    }
  }

  return false;
}

Für negative Zahlen kann man das noch leicht anpassen. Wenn man aber z.B. mal Floats oder Hex-Werte übertragen will ist man mit der String Variante viel, viel einfacher dran.

Bis zum heutigen Tag hatte ich leider keine Zeit mehr mich diesem Problem zu widmen. Nun soll es aber weiter gehen.

Zunächst einmal vielen Dank an euch.

Ich habe es eben getestet, und siehe da, es funktioniert. Allerdings möchte ich die Daten lieber im ‘String’ senden.

Ich habe jedoch Verständnisprobleme mit der Funktion (siehe Code). Könnt ihr mir erklären was genau in welcher Zeile passiert?

bool readSerial(Stream& stream)
{
  static byte index;

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

    if (c >= 32 && index < SERIAL_BUFFER_SIZE - 1)
    {
      serialBuffer[index++] = c;
    }
    else if (c == '\n' && index > 0)
    {
      serialBuffer[index] = '\0';
      index = 0;
      return true;
    }
  }
  return false;
}

void parseSerial()
{
  char* ptr = strtok(serialBuffer, ",;");
  int index = 0;

  while (ptr != NULL && index < MAX_VALUES)
  {
    values[index++] = atoi(ptr);
    ptr = strtok(NULL, ",;");
  }
}
bool readSerial(Stream& stream)
{
  static byte index;    //Laufindex

  while (stream.available())   //nachschauen ob Daten da sind
  {
    char c = stream.read();    //Zeichen einlesen

    if (c >= 32 && index < SERIAL_BUFFER_SIZE - 1)    //wenn druckbares Zeichen und noch Platz im Puffer
    {
      serialBuffer[index++] = c;    //Zeichen abspeichern und Index inkrementieren
    }
    else if (c == '\n' && index > 0)   //wenn LF eingelesen und Länge > 0
    {
      serialBuffer[index] = '\0';   //String terminieren
      index = 0;    //Index zurücksetzen
      return true;     //melden dass fertig
    }
  }
  return false;   //noch nicht fertig
}

readSerial() wird dann ständig aufgerufen (nicht nur einmal!) und man fragt ob was es zurück liefert. Bei false ist entweder nichts da oder das Einlesen ist noch nicht fertig. Bei true weiß man dass der String komplett da ist.

void parseSerial()
{
 char* ptr = strtok(serialBuffer, ",;");    //strtok() initialisieren. Zeiger auf Anfang
 int index = 0;

 while (ptr != NULL && index < MAX_VALUES)   //wenn Ende nicht erreicht und noch Platz im Array
 {
   values[index++] = atoi(ptr);   //Teil-String in Integer konvertieren
   ptr = strtok(NULL, ",;");     //Zeiger auf nächsten Teil-String
 }
}

strtok() ist eine Standard Funktion in C (steht für string tokenizer):
http://www.cplusplus.com/reference/cstring/strtok/

Beim ersten Aufruf übergibt man den Puffer als ersten Parameter. Dann sucht die Funktion nach dem Delimiter und ersetzt ihn durch NULL. Zurück kommt ein Zeiger auf den Anfang des Strings. Intern wird ein Zeiger auf den nächsten Teil-String gesetzt. Wiederholte Aufrufe mit NULL als Parameter, bedeuten dass man danach jeweils einen Zeiger auf das nächste Token erhält. Oder NULL wenn man am Ende des Strings ist.

Man ruft das alles immer wieder auf, solange bis man entweder NULL erhält (Ende) oder kein Platz mehr im Array ist.

Auch ja, noch was:
Wenn du per Serial Daten zum PC sendest, kannst du andere Geräte nicht an Serial anschließen! Wenn du keinen Prozessor mit mehreren Hardware Schnittstellen hast (z.B. Mega), dann kannst du dir eine weitere Schnittstelle mit SoftwareSerial emulieren. Oder AltSoftSerial:
https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html

Eine Instanz davon kannst du auch an readSerial() übergeben

Danke Serenifly für deine Erklärung! Werde jetzt versuchen es in meinem Projekt umzusetzen.

Ich habe wohl weiterhin einen Fehler:

char serialSEND[] = "1023,21\n";
char serialSEND1[] = "213,424\n";

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

void loop() {

Serial.println(serialSEND);
Serial.println(serialSEND1);

delay(800);
}

Wenn ich diese Beispielwerte schreibe, kommt keine Übertragung zustande, aber warum?

println() sendet automatisch ein CR + LF. Das muss man als nicht extra angeben

Wie ist der Empfänger angeschlossen? Ich weise nochmal auf den letzten Absatz in #7 hin. Du kannst nicht gleichzeitig von einem anderen Gerät auf Serial empfangen und gleichzeitig per USB Buchse am PC hängen. Das ist die gleiche Schnittstelle!

Ich habe den Fehler gefunden, die Xbee Module Sender/Empfänger waren vertauscht ::slight_smile:

Jetzt werden die Daten wie gewollt übertragen.

  {
    for (int i = 0; i < MAX_VALUES; i++)
      if (i == 1)
      {
       Serial.print("Temperatur: ");
       Serial.print(values[i] /= 100);
       Serial.println("C");
      }
      else if (i != 2){
        Serial.println(values[i]);
        if (values[i] < 400)
        {        digitalWrite(ledPinL, HIGH);
      }
      else{
        digitalWrite(ledPinL, LOW);
      }

      }
      
    Serial.println("---");

  }

Jetzt würde ich den Werten gerne noch einen Namen zuordnen (siehe oben). In der Variante <…> funktionierte das auch bereits einwandfrei, mit der String Variante leider nicht mehr.

Außerdem wird irgendwann die Serial Verbindung unterbrochen, woran liegt das, bzw. wie kann ich die Serial Verbindung neu initialisieren?

sxon:
Jetzt würde ich den Werten gerne noch einen Namen zuordnen (siehe oben). In der Variante <…> funktion Werte das auch bereits einwandfrei, mit der String Variante leider nicht mehr.

Natürlich geht das. Du musst nur den Parser anpassen. Das ist das schöne an der Variante. Einlesen und Parsen sind zwei getrennte Dinge.

Am einfachsten geht es so:

float var1;    //statt dem Array einzelne Variablen
int var2;

...

void loop()
{
  if (readSerial(Serial) == true)
  {
    parseSerial();

    Serial.println(var1);
    Serial.println(var2);
    Serial.println("---");
  }
}

void parseSerial()
{
   var1 = atof(strtok(serialBuffer, ","));  //atof() = ascii to float. Für Integer atoi() nehmen!!
   var2 = atoi(strtok(NULL, ","));
}

Dann kannst du das senden:

Serial.print(var1); Serial.print(','); Serial.println(var2);

Und es wird direkt in Variablen geschrieben statt einem Array

Hier mal mit verschiedenen Datentypen. Einmal float und einmal int. Du kannst die Temperatur natürlich auch als int in Zehntel oder Hunderstel Grad senden und dann /10.0 oder /100.0 machen

Die Definition des char Arrays und readSerial() habe ich mal weggelassen. Ebenso setup(). Das brauchst du natürlich weiterhin!

Oder man kann verschiedene Sachen auch in getrennten Strings sendet und einen Bezeichner davor schreiben. Also du sendest es so:

Serial.print("var1:"); Serial.println(var1);
Serial.print("var2:"); Serial.println(var2);

Dann kann man die Werte getrennt behandeln. So kann man auch nur eine Variable senden. D.h. man kann z.B. auf dem Sensor Arduino eine Variable alle 1 Sekunden senden und eine andere nur alle 30 Sekunden.
Außerdem muss der Puffer nicht so groß sein! Es muss nur Platz für einen String sein. Also würden vielleicht auch 16 Byte reichen

float var1;
int var2;

void loop()
{
  if (readSerial(Serial) == true)
  {
    parseSerial();

    Serial.println(var1);
    Serial.println(var2);
    Serial.println("---");
  }
}
void parseSerial()
{
  if (strncasecmp(serialBuffer, "var1:", 5) == 0)   //5 = wieviele Zeichen am Anfang verglichen werden
    var1 = atof(serialBuffer + 5);      //+5 damit der Zeiger auf das 5. Zeichen zeigt (nach dem Doppelpunkt)
  else if (strncasecmp(serialBuffer, "var2:", 5) == 0)
    var2 = atoi(serialBuffer + 5);
}

Wenn du den Vergleichs-String änderst musst du auch den Index anpassen!! Hier ist es 5 weil “var1:” 5 Zeichen hat

strncasemp() ist eine Variante von strncmp(). Das vergleicht die ersten N Zeichen und ignoriert Groß-/Kleinschreibung. Bei Gleichheit liefert es 0

Oder wenn du es doch in einem String willst vielleicht so:

void parseSerial()
{
  char* ptr = strtok(serialBuffer, ",");

  while (ptr != NULL)
  {
    if (strncasecmp(ptr, "var1:", 5) == 0)
      var1 = atof(ptr + 5);
    else if (strncasecmp(ptr, "var2:", 5) == 0)
      var2 = atoi(ptr + 5);

    ptr = strtok(NULL, ",");
  }
}

Dann kannst du das senden:

Serial.print("var1:"); Serial.print(var1); Serial.print(','); Serial.print("var2"); Serial.println(var2);

Oder auch anders herum mit var2 zuerst.

Bei der Version musst du aber wahrscheinlich das Puffer Array etwas größer machen!

Da gibt es auch noch andere Optionen. Noch einfacher geht es wenn man nur einen Buchstaben zur Identifikation nimmt (z.B. ‘T’ für Temperatur und ‘H’ für Luftfeuchte), dann kann man switch/case aus serialBuffer[0] machen

Danke Serenifly für die unterschiedlichen Varianten und die gute Erklärung, ich werde es direkt umsetzen.