Zeitliche Abfrage Bluetooth Read

Hey Leute,

hab ein Uno mit Hc05.
App schickt Daten rüber wenn man Buttons drückt.

Folgendes
Habe wie sich gehört schon die Bluetooth Read schleife usw hier :

char bufferspeicher[40];
char IncomingString = '0';
int i = 0;
byte CounterIncomingString = 0;



while (bt.available() > 0 ){
      delayMicroseconds(500); // warte kurz dass das programm die daten empfangen kann
      IncomingString = bt.read();

      if (IncomingString > 34 && IncomingString < 58 && IncomingString != 36
          && IncomingString != 38 && IncomingString != 39 && IncomingString != 40
          && IncomingString != 41 && IncomingString != 42 && IncomingString != 43
          && IncomingString != 44 && IncomingString != 45 && IncomingString != 46
          && IncomingString != 47 ) // Nur Zeichen mit ASCII-Code die ich brauche... # % 0-9
      {

        bufferspeicher[i] = IncomingString; // array mit den zeichen vom empfang füllen
        i++;
        CounterIncomingString++; // Zeichen zählen die reinkommen
      }
    }

Funktioniert alles super gut.

Mein Problem nun :

Es passiert manchmal dass jemand auf der App hastig Taster drückt, und der Arduino nicht mehr nachkommt mit dem Lesen da es Strings mit 30+ Zeichen sind.

Ich würde nun gerne was programmieren, dass diese While bt available Schleife nur jeweils für ein Datenpaket offen lässt, dann ein Paar Ms Pause lässt und dann wieder “freigibt”. In dieser Pause soll nichts gelesen werden können
Damit erhoffe ich mir kommende Lesefehler vom zu schnellem Tippen zu eliminieren.

Könnt ihr mir da helfen ? Oder hat jemand ne einfachere / bessere Methode für sowas ?

Würd mich freuen,

Franz

Wieso soll der Empfang besser werden wenn sich der Arduino zeitweise taub stellen soll?

Ohne den gesamten Sketch können wir Dir nichts sagen.

Grüße Uwe

Wie praktisch immer mit delay() ist das die völlig falsche Vorgehensweise. Und deine “Lösung” ist viel zu kompliziert.

Dabei geht es ganz einfach: String Übertragungen schließt man mit einem Linefeed ab. Dann kann man das Ende automatisch erkennen. Dann schaut man ständig nach ob was im Eingangspuffer ist. Wenn nicht läuft loop() einfach weiter. Wenn ja liest man das Zeichen aus. Man schaut ob es das Endzeichen ist. Wenn ja terminiert man den String (das fehlt bei dir auch). Wenn nicht überprüft man ob noch Platz im Puffer ist (das fehlt auch) und ob das Zeichen im gewünschten Bereich ist. Erst dann speichert man es ab.

Für den Bereich reicht es eigentlich aus die ASCII Steuerzeichen zu unterdrücken. Dadurch schließt man das CR aus (da oft CR + LF am Ende kommt). Alles sichtbare kann abgespeichert werden.
Hier kann man übrigens auch mit > und < auf Bereiche abfragen!

Siehe hier:

Das läuft unabhängig von der Baudrate und geht auch mit 500.000 Baud zuverlässig

Hey danke für die hilfreiche Antwort und den Link :slight_smile:

Hab das jetzt miteingebet, scheint zu klappen was sagst du?

Der Sketch soll können : Verbindung zu einer App aufbauen, die eine Datenpaket sendet angefangen mit #
Dann kommen 7 Zahlenwerte, mit % getrennt. Diese Wandle ich zu Int um und lasse Sie Seriell ausgeben.

Macht er auch. Aber wie sehr das so korrekt ist weiß ich nicht :smiley:

#include <SoftwareSerial.h>
#include <Adafruit_NeoPixel.h>
#define rxPin 2
#define txPin 4
#define BtPin 8

SoftwareSerial bt (txPin, rxPin); // tx , rx

boolean FlagCounterIncomingString = false;
byte CounterIncomingString = 0;
const unsigned int READ_BUFFER_SIZE = 41;

void setup() {

  pinMode(BtPin, INPUT);
  bt.begin(9600);
  Serial.begin(9600);
  Serial.println("Arduino bereit...");
  Serial.println("Bluetooth bereit...");
  Serial.print("Sketch:   ");   Serial.println(__FILE__);
  Serial.print("Uploaded: ");   Serial.println(__DATE__);

}

void loop() {
  BluetoothDatenLesenUndVerarbeiten();
}

void BluetoothDatenLesenUndVerarbeiten() {

  static char buffer[READ_BUFFER_SIZE];
  char IncomingString = '0';
  int index = 0;
  int var1, var2, var3, var4, var5, var6, var7;
  memset(buffer, 0, sizeof(buffer));

  while (bt.available() > 0 ) {
    delayMicroseconds(500); // warte kurz dass das programm die daten empfangen und in den speicher schieben kann
    IncomingString = bt.read();

    if (IncomingString == '\n')          //wenn LF eingelesen
    {
      buffer[index] = '\0';   //String terminieren
      index = 0;
      return buffer;        //melden dass String fertig eingelesen wurde
    }

    else if (IncomingString > 34 && IncomingString < 58 && IncomingString != 36
             && IncomingString != 38 && IncomingString != 39 && IncomingString != 40
             && IncomingString != 41 && IncomingString != 42 && IncomingString != 43
             && IncomingString != 44 && IncomingString != 45 && IncomingString != 46
             && IncomingString != 47 && index < READ_BUFFER_SIZE - 1 ) // Nur Zeichen mit ASCII-Code die ich brauche... # % 0-9
    {

      buffer[index++] = IncomingString; // array mit den zeichen vom empfang füllen
      //i++;
      CounterIncomingString++;
    }


    if (CounterIncomingString < 25) { // INCOMING STRING ZÄHLER IST IMMER GRÖßER ALS 25
      char IncomingString = '0';
    }
  }

  if (strstr(buffer, "#") == buffer && CounterIncomingString >= 25) {

    Serial.print("String erkannt: ");
    Serial.println(buffer);
    Serial.print("CounterIncomingString= ");
    Serial.println(CounterIncomingString);


    var1 = getIntFromString(buffer, 1);
    Serial.print("Wert 1 : "); Serial.println(var1);

    var2 = getIntFromString(buffer, 2);
    Serial.print("Wert 2: "); Serial.println(var2);

    var3 = getIntFromString(buffer, 3);
    Serial.print("Wert 3 : "); Serial.println(var3);

    var4 = getIntFromString(buffer, 4);
    Serial.print("Wert 4 : "); Serial.println(var4);

    var5 = getIntFromString(buffer, 5);
    Serial.print("Nr 5: "); Serial.println(var5);

    var6 = getIntFromString(buffer, 6);
    Serial.print("Nr 6: "); Serial.println(var6);

    var7 = getIntFromString(buffer, 7);
    Serial.print("Nr7: "); Serial.println(var7);

  }
}

int getIntFromString (char *stringWithInt, byte num) {
  char *tail;
  while (num > 0) {
    num--;
    // skip non-digits
    while ((!isdigit (*stringWithInt)) && (*stringWithInt != 0)) stringWithInt++;
    tail = stringWithInt;
    // find digits
    while ((isdigit(*tail)) && (*tail != 0)) tail++;
    if (num > 0) stringWithInt = tail; // new search string is the string after that number
  }
  return (strtol(stringWithInt, &tail, 0));
}

Kann man da noch was anders oder geschickter lösen?

Gruss Franz

Schau Dir mal strtok an. Infos hier.

Gruß Tommy

Hi Tommy,

danke für den Link sehr hilfreiche Doku :slight_smile:

Wie das zeitlich abläuft hast du noch nicht verstanden. Die denkst immer noch in “Warten” und “Schleifen”. Wie in dem anderen Thread gesagt:

Noch eine Anmerkung: die serielle Schnittstelle ist im Vergleich zum Prozessor sehr langsam. Selbst mit hohen Baudraten wird i.d.R. nur ein Zeichen pro Funktionsaufruf eingelesen. Du darfst dich da nicht von dem while() verwirren lassen. Deshalb muss man die Funktion ständig aufrufen und abfragen ob sie fertig ist

Ein Zeichen braucht 1 / Baudrate * 10 Sekunden! Bei deinen 9600 Baud kommt da nur alle 1ms ein Zeichen! Solange will man doch nicht auf das nächste Zeichen warten. Oder kann es nicht wenn man versucht das alles auf einmal in einer while()-Schleife einzulesen.

Pro Aufruf von readLine() wird normal nur ein Zeichen eingelesen. Dann ruft man einfach die Funktion solange auf bis das Endzeichen da ist. Wenn nichts zum Einlesen da ist hört die Funktion gleich wieder auf. Während der Zeit kann man dann in loop() andere Dinge tun. So muss man auf nichts Warten und hängt nie in irgendwelchen Schleifen.

Die while()-Schleife in meinem Code ist nur für den Fall da dass das Programm an anderer Stelle mal länger braucht. z.B. Sensoren mit Busy Waiting auslesen. Dann kann sein dass mal mehrere Zeichen da sind. Außerdem kann man die Funktion auch für SD Karten nehmen. Dann geht alles in einem Stück

Schau dir mal genau den Funktionsaufruf an:

void loop()
{
  char* txt = readLine(Serial);
  if (txt != nullptr)
  {
    Serial.print("Empfangen: ");
    Serial.println(text);
  }
}

readLine() wird ständig aufgerufen. Die Funktion liefert nullptr wenn das LF noch nicht eingetroffen ist. Wenn sie fertig ist kommt ein Zeiger auf das interne Array. Das ist natürlich ungleich NULL und damit kann man dann die Daten auswerten.

Es ist nicht so dass die Funktion einmal aufgerufen wird und man hat gleich das Ergebnis. Das wäre ja wieder Warten und damit Zeitverschwendung

Bis auf die Steuerzeichen sehe auch keine großen Sinn daran Zeichen herauszufiltern. Wenn der Sender Zeichen sendet die man nicht erwartet stimmt was am Übertragungsprotokoll nicht

Generell musst du etwas mehr darüber nachdenken was du tust und wenn du was änderst musst du den Code auch verstehen. Du kannst z.B. nicht “return buffer” machen wenn deine Funktionen keinen Rückgabe-Wert hat.
Der Code den ich dir gegeben habe ist so wie er ist funktionsfähig. Da musst du eigentlich nichts anpassen (Startzeichen kann man noch hinzufügen, ist aber nicht zwingend nötig, da man ein Endzeichen hat). Sondern du musst eine separate Parser Funktion schreiben die dann in loop() aufgerufen wird wenn ein String da ist. Das ist auch ein Vorteil dabei. Auslesen und Auswerten sind getrennt. Das beides in einer Funktion zu machen ist keine gute Idee.

z.B. um zwei nach Kommas getrennte Zahlen in Integer zu splitten:

void loop()
{
  char* txt = readLine(Serial);
  if (txt != nullptr)
  {
    parseResult(txt);
  }
}

void parseResult(char* str)
{
   int val1 = atoi(strtok(str, ","));
   int val2 = atoi(strtok(NULL, ","));

   Serial.println(val1);
   Serial.println(val2);
}

Wenn der Parser anders ist muss man nur eine Funktion ändern. Das Einlesen bleibt immer gleich.

Hi Serenifly

vielen lieben dank für die Erklärungen und die Beispiele.

Du meinst also die Einlese Funktion soll ich so lassen und eine separate (hier parseResult) zum Integer splitten benutzen?

Zum parseResult, was bedeutet dass "NULL" und wie muss ich weitermachen will ich zb 3 Werte?

So ?

int val1 = atoi(strtok(str, "%"));
  int val2 = atoi(strtok(NULL, "%"));
  int val3 = atoi(strtok(NULL, "%"));

Du meinst also die Einlese Funktion soll ich so lassen und eine separate (hier parseResult) zum Integer splitten benutzen?

Ja. Wenn du weißt was du tust kannst du kleine Änderungen beim Einlesen machen, aber halte dich an das Prinzip. Das ist kein Zufall. Der Code ist so ausgelegt dass man zusammen mit dem Rest ein gutes Zeitverhalten hat. Wenn man dann ansonsten weitgehend nicht-blockierend programmiert kann man mehrere Dinge quasi-gleichzeitig tun.

Und durch getrennte Funktionen hast du mehr Flexibilität und Übersicht. Du kannst das z.B. so einfach in ein anderes Programm kopieren und nur einen neuen Parser schreiben.

Ein weiterer Vorteil:
Die Quelle von der man liest wird als Parameter übergeben. Daher geht das mit allen Klassen die von Stream abgeleitet sind. Also alle Varianten von Hardware Serial und Software Serial. Aber eben auch die File Klasse. Oder glaube ich auch Klassen für TCP oder UDP. Man kann von verschiedenen Quellen lesen ohne was an der Funktion zu ändern.

Zum parseResult, was bedeutet dass "NULL" und wie muss ich weitermachen will ich zb 3 Werte?

Schau dir Doku zu strtok() an. Da ist das erklärt. Die Verwendung ist etwas ungewöhnlich. Genau wie meine readLine() Funktion mit den static Variablen verwaltet die intern ihren Zustand von Aufruf zu Aufruf. Beim ersten Aufruf übergibt man den String. Alle weiteren Aufrufe haben statt dessen NULL/nullptr als Parameter damit es mit dem nächsten Token weitergeht. Und nicht wieder von vorne.

strtok() terminiert jeweils die Teil-Strings und liefert mit jedem Aufruf einen Zeiger auf den nächsten Teil-String zurück. Wenn der String zu Ende ist kommt NULL.

Wenn die Anzahl der Tokens bekannt ist (z.B. man hat immer 7 Werte) kann man es so wie oben machen und es einfach so oft hintereinander aufrufen wie man Tokens erwartet.
Bei einer unbekannten Anzahl kann man es in einer while-Schleife solange aufrufen bis NULL kommt. So wird es in den Beispielen bei den Dokus und Tutorials meistens gemacht. Das eignet sich aber eher zum Einlesen in ein Array und nicht um die Werte verschiedenen Variablen zuzuweisen

Danke für die Erklärungen :slight_smile:

Okay alles klar nutze dann die readLine als Funktion wie sie ist, rufe die im loop andauernd auf.

Die parseResult genauso.

Noch was.

oid parseResult(char* str)
{
  int val1 = atoi(strtok(str, "%"));
  int val2 = atoi(strtok(NULL, "%"));
  int val3 = atoi(strtok(NULL, "%"));
  int val4 = atoi(strtok(NULL, "%"));
  int val5 = atoi(strtok(NULL, "%"));
  int val6 = atoi(strtok(NULL, "%"));
  int val7 = atoi(strtok(NULL, "%"));
}

darin Werte ich ja jetzt 7 int Werte aus die mit % getrennt sind und schreibe die in die val1-7

Soweit so verstanden.

Ich würde dann gerne mit diesen Werten schleifen aufrufen. Zb eine Funktion "Cycle" die solange laufen soll, solange zb. die val1 == 1 ist.

Schreibe ich dann das mit while(val1 == 1) {Code...} ? Weil dann bin ich ja in dieser While gefangen und kann doch nebenbei nicht mehr Daten lesen richtig ? Muss dann erst die schleife brechen und kann dann erst wieder Lesen?

Beschreibe doch mal genauer was das werden soll. Dann werden sich mit Sicherheit auch sinnvolle Lösungen finden.

Gruß Tommy

Hay Tommy

Das soll ein Programm werden, das Daten von einer App lesen kann ( 123%123%122%12%33%67%12 ) so in der Art sieht das Kommende Paket von der App aus.

Diese Zeichen sollen dann in var1 bis var7 getrennt werden, Trennzeichen ist das %.

Diese Daten enthalten Werte, mit denen ich RGB Leds ansteuere. Dafür ist der erste Wert im Paket zb der Farbwert für "Rot", dann "Blau" usw.
Die anderen Werte nutze ich dann um in Schleifen zu springen, die programmierete Animationen ausführen sollen, eben solange wie besagter Wert aktiv ist. Zb While (var7 == 12) { do This loop }

Das wärs auch schon mit dem ganzen Mist.

Gruss Franz

Evtl. könntest Du die Werte auch in ein Array einlesen.
Aber bei 7 Werten, die sich aus 2 Mal RGB und einen Aufruf verteilen lohnt sich das wohl nicht.

Gruß Tommy

Du hast in loop() schon eine Schleife. Es heißt ja auch so. Damit kann man theoretisch alle weiteren Schleifen zu if-Abfragen auflösen (im Fach-Jargon heißt das "unrolling" oder abrollen). Wenn eine Schleife gleich fertig ist muss das natürlich nicht sein (z.B. über ein Array iterieren), aber sobald es dauert bis ein Ereignis eintritt nimmt man loop() dafür. Das ist eine Vorgehensweise und Denkweise du die verinnerlichen solltest, weil sehr, sehr viele Programme so aufgebaut sind.

Auch hier muss man soweit wie möglich auf lange Blockierungen verzichten. Das heißt auch die Animationen abrollen. Verzögerungen zwischen Animations-Schritten mit millis()/BlinkWithoutDelay. So dass loop() immer dran kommt und man direkt auf die serielle Schnittstelle reagieren kann. Das Problem kam hier schon oft und dafür gibt es auch viel Code. Meistens geht es eher darum auf Taster zu reagieren, aber die Lösung ist sie selbe.

Pseudocode:

Zustand = mach nichts
alte Zeit = 0

loop()
{
   if (serielle Eingabe == Start)
      Zustand = tu was
   else if (serielle Eingabe == Stop)
      Zustand = mach nichts

   if (Zustand == tu was) 
   {
     if (aktuelle Zeit - alte Zeit > Intervall)
     {
         alte Zeit = aktuelle Zeit

         hier etwas tun, z.B. nächster Animations-Schritt
     }
   }
}

Und statt zwei Zustände gehen auch natürlich auch mehrere. Bei den Animationen musst du dir auch merken wo du gerade bist
Außerdem sollte man auch das schön in Funktionen packen, so dass in loop() nicht so viel Code steht

Stichworte: Zustandsmaschine, endlicher Automat. Und "Blink Without Delay" für die Zeit Geschichte

Hey Serenifly,

also die Funktionen alle habe ich schon passend unf funktionierend auf Millis umgebaut, da benutzt keine einzige mehr Delay();

Das würde ich sagen ist schon die halbe miete. Dann probiere ich mal ob ich mithilfe deinem Pseudocode mal ein Programm schreiben kann, dass auf While wenn möglich verzichtet.

Vielen Dank
Gruss Franz