Stabile Serielle Kommunikation

Hmm… also irgendwie liegt mir das Thema noch immer etwas schwer im Magen.

Ich habe in der Vergangenheit bereits eine Bluetooth-Kommunikation von einem Android Handy zu einem HC-05 BT-Module hinbekommen, welches wiederrum mit dem Arduino kommuniziert.

Da ich mit einem UNO angefangen habe und das HardwareSerial aus Bequemlichkeit freihalten wollte, habe ich immer das SoftwareSerial benutzt.

Bei meinen ProMini Clonen funktioniert das leider extrem instabil.
Auf den Hinweis hin, es evtl. mal mit der AltSoftSerial Library zu versuchen, bin ich gleich auf das hardware Serial gegangen um wirklich die Grenzen ausloten zu können.

Leider läuft das auch kein Stück stabil und ich denke, dass ich da wohl einiges grundlegend falsch machen muss.

Da mein Code insgesamt ca. 18KB groß ist, poste ich hier nur einen Teil, der hoffentlich alles wichtige enthält:

//Serial Monitor  
#include <SerialCommand.h>

//SoftwareSerial
#include <SoftwareSerial.h>// import the serial library
SoftwareSerial softSerial(10, 11); // RX, TX


#include <string.h>


// FastSPI WS2812B  
#include <FastSPI_LED2.h>
#define NUM_LEDS 50
#define DATA_PIN 13
struct CRGB leds[NUM_LEDS];

unsigned long previousMillis = 0; // speichert wie viele Sekunden seit derletzten Änderung vergangen sind
boolean gelesen = false;

int ledMode = 0;
int ledModeVorher = 0;

static unsigned long int* receivedParameter;

// Diese Funktion liest die komplette Zeichenkette die sie vom BT-Modul erhält ein und erwartet am Anfang zwei Zahlen
// Diese geben die Länge der restlichen Zeichen an
// Beispiel Zeichenkette: 012 
// Bedeutung die Restzeichenkette hat eine länge von 01 Zeichen und ist 2.
// 101,0xFF00AA  bedeuted 10 Zeichen kommen. Restzeichenkette ist 1,0xFF00AA  (in diesem Fall ist das Komma eine Trennung der Parameter. receivedParameter[0] ist 1 => Modus ist 1 (also Farbe aus ColorWheel übernehmen), receivedParameter[1] ist 0xFF00AA => HEX Farbwert für die LEDs.
char* getText()
{
  static char charArray[61];  
  char c;
  unsigned int numberOfInts;
  int stringLen;
  
  if (Serial.available())
  {
    memset(charArray,0,sizeof(charArray));// Puffer vor Benutzung löschen
    //die ersten beiden Bytes übergeben die maximale Länge der Zeichenkette
    //da der Übertragungs-string maximal 63 Zeichen lang sein kann, reichen 2 Bytes
    //einstellige Zahlen, werden mit vorangestellter 0 übertragen 
    //(bei 7 Zeichen ="071,345,7 bei 17 Zeichen = "171,345,789,123,567")
    static char stringLenAsChar[2];  
  
  
    //  while (Serial.available() == 0) {
          // nichts machen, bis die nächsten bytes angekommen sind um sicher zu gehen, dass nichts vergessen wird
    // }
    
    
    //die ersten beiden Bytes einlesen
    //Hier müsste man allerdings sicherstellen, dass das Arduino auch wirklich beide Bytes einliest
    //mittels delay(5); wäre das wohl wieder zu primitiv oder?
    Serial.readBytes(stringLenAsChar, 2);

    //das eingelesene charArray in Zahlen wandeln
    stringLen = atol(stringLenAsChar);   
  
    //und die verbleibenden Zeichen des strings einlesen
    Serial.readBytes(charArray, stringLen);


    //und dann hier zurück geben
    if(stringLen>0)
    {
      stringLen=0;
      return charArray;
    }
    else
    {  
      return NULL;
    }
  } 
}

// Die erhaltene Zeichenkette "auseinander schneiden" und in einzelene Parameter (array) speichern
unsigned long int* cutText(char* text, int* numberOfInts)
{ 
  const int maxIntParam = 32;
  static unsigned long int intArray[maxIntParam];
  memset(intArray, 0, maxIntParam * sizeof(long)); //eventuell unnötig "wenn ich nicht über das array iteriere"
  int count = 0;
  char* pch = strtok(text, ",");
  while(pch != NULL && count < maxIntParam)
  {
     intArray[count++] = strtol(pch, NULL, 0);
     pch = strtok(NULL, ",");
  }
  *numberOfInts = count;
  return intArray;
}


void setup()  
{
  
   // SoftSerial initiierung
   softSerial.begin(57600);
   softSerial.println("Bluetooth an und verbunden. Warte auf Eingabe...");
   
   //Serial initiierung
   Serial.begin(57600);    
   LEDS.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
}




void loop()
{
  
 while (Serial.available() > 0) 
  {
    
    
  char* serialText = getText();
  
  if(serialText != NULL)
  {
    
    Serial.print("Gesamter Text:  ");
    Serial.println(serialText);
   
    int numberOfInts;
    receivedParameter = cutText(serialText, &numberOfInts);
    
    
    for (int i =0; i < numberOfInts ; i++)
    {
      
       Serial.print("Parameter ");
       Serial.print(i);
       Serial.print(": ");
       Serial.println(receivedParameter[i]);
      
    }
    
     Serial.print("anzahl der Parameter: ");
     Serial.println(numberOfInts);
         

 }
}      
}

Ich weiß nicht ob die geschweiften Klammern jetzt stimmen, da es doch etwas viel ist, was ich da kopiert habe.

Es sollte aber das wichtige komplett dabei sein, damit ihr sehen könnt, wie ich was genau mache.

Ich habe mich über den Seriellen Monitor aus der Arduino Entwicklungsumgebung mit dem Arduino verbunden (bei einer Baudrate von 57600) und habe die Zeichenketten manuell über diesen eingegeben.

Häufig wird dabei der “serialText”, also die bereits empfangene und verarbeitete Rest-Zeichenkette, falsch angezeigt.
Falls ihr es nicht gelesen habt (in den Kommentaren im Code):
Ich sende eine Zeichenkette seriell an das Arduino. Die ersten beiden Zeichen werden direkt eingelesen und geben dann die Anzahl der folgenden Zeichen an. (bei einstelligen Zahlen haben diese eine vorangestellte Null)
Grund dafür war, dass in der Vergangenheit SoftSerial.readBytes am stabilsten und schnellsten lief und ich einfach nichts anderes hinbekommen habe…

Der gesamte Code verzichtet (bis auf das setup) komplett auf delay() und er sollte daher eigentlich keine echten Aufhänger haben.

Zum debuggen gebe ich das Empfangene auch wieder per Serial aus, damit ich es am SM prüfen kann.
Vielleicht entsteht hier nochmal zusätzlich eine Instabilität, allerdings ist das was ich bisher getestet habe ernüchternd…
Sobald ich was “etwas schneller” per SM eingebe verschluckt sich die Kommunikation und es kommt teilweise nichts an.
Doch auch wenn ich warte, kommt es teils zu Fehlern, wie bei einer Zeichenkette, die eigentlich so aussehen sollte “9,23” die dann nur nich “9,” ausgibt.

Das Ganze sollte aber wirklich stabil laufen - und dabei möglichst schnell.

Es sollen später bis zu insgesamt 12 stellige Zeichenketten schnell hintereinander per BT übertragen werden (das geht auch) und dann seriell an das Arduino geschickt werden.
Momentan ist mir egal ob per SoftSerial, AltSoftSerial oder echtem hardware Serial. Hauptsache stabil und schnell.
Die Zeichenketten sehen dann etwa so aus : 101,0xFF9900 (10 Zeichen folgen welche “1,0xFF9900” lauten. Das bedeutet Modus 1: Alle LEDs mit der HEX-Farbe 0xFF9900 leuchten lassen).

Die schnelle Übertragung brauche ich, weil die App einen Farbkreis hat, über den man mit dem Finger streifen kann und die LEDs in dieser Farbe leuchten.
Das klappt auch im großen und Ganzen, aber eben nicht stabil.
Beim Benutzen des Farbkreises fällt das nicht auf.
Besonders ärgerlich ist es aber, weil man die Farbwerte dann mittels “Speichern”-Button in der App in das EEPROM speichern lassen kann (können sollte), um sie dann vom Arduino später wieder aufzurufen.

Wenn jetzt die Zeichenkette 012 ( 01 Zeichen, Modus 2: Speichern) fehlerhaft ankommt, kann man als Benutzer nicht überprüfen ob es wirklich gespeichert wurde.
Eine art Handshakeprotokoll übersteigt meine Möglichkeiten bei weitem - vor allem was das grausige Android programmieren angeht. Zumal das die Geschwindigkeit wieder um einiges senken würde.

Ich hoffe daher mal wieder auf die tatkräftige Unterstützung von uwe, stefan, jurs, serenifly und die vielen anderen Profis die sich hier so tummeln :wink:

Vielen Dank und liebe Grüße!

Da steht aber SoftSerial. Hast du jetzt das oder HardwareSerial?

readBytes() ist blockierend. Darauf würde ich verzichten und die Bytes per Hand einlesen. Das sollte zwar grundlegend funktionieren (in dem Sinne, dass alles eingelesen werden sollte), aber was du da machst ist nach dem ersten Byte warten bis alle Bytes da (oder bis die Timeout Zeit vorbei ist) sind und während dessen wird nichts gemacht. Dadurch kann man während der Serial Übertragung sonst nichts machen.

Wenn man das wirklich nicht-blockierend machen wollte, müsste man es aber auch so ändern dass er zwischendurch mal nach loop() zurück kehrt. Wobei es vielleicht auch ok ist, wenn es blockierend ist. Das kommt auf die Anwendung an.

Ansonsten sieht der Code ok aus.

Da kommt aber so oder so ein anderes Synchronisations-Problem auf dich zu:

Im Moment machst du kein show() mit FastSPI. Wenn das später aber mal drin ist, wird du wieder Ärger bekommen, denn die FastSPI Library deaktiviert während sie Daten sendet die Interrupts komplett. Dadurch werden sowohl HardwareSerial als auch AltSoftSerial gestört.

Das normale SoftSerial funktioniert glaube ich mit Polling, aber das ist wie gesagt erstens generell schlecht. Außerdem kann das wohl auch nichts abfragen, wenn gerade FastSPI Daten schickt.

softSerial.begin(57600);

Die emulierten Schnittstellen funktionieren so schnell nicht. Grüße Uwe

Also das SoftSerial steht soweit ich das sehen kann nur im setup. In dem Programm wird es sonst nicht verwendet.

Bist du dir da übrigends sicher uwe? Ich meine in früheren Tests bewiesen zu haben, dass diese Geschwindigkeit keine Probleme macht. Allerdings bin ich über ein Jahr später nicht mehr sicher inwieweit ich das wirklich geprüft habe.

So oder so ist momentan allerdings das Problem, dass ich sowohl per SoftSerial, also auch per (hard) Serial nicht fehlerfrei Kommunizieren kann.

Es ist vollkommen in Ordnung, wenn die getText()-Funktion, durch Verwendung von readBytes() wartet/blockiert bis alle Zeichen da sind. Das ist eigntlich sogar gewollt.

Der Hinweis mit der FastLED library ist sehr gut gewesen. Ich habe mal geprüft inwieweit dies bei mir zutrifft.

Dazu müsst ihr wissen, das ich einmal global den aktuell aktiven modus speichere. Ein statischer Modus ist der modus 1, bei dem nur einmal der hex-Farbwert übergeben wird und die LEDs entsprechend angesteuert werden. Ein flag prüft, ob die LEDs auch wirklich mit der neuen Farbe leuchten.

das Ganze sieht dann etwa so aus:

int ledMode = 0;
boolean esWurdeWasEmpfangen = false;
long int ledFarbe = 0;


textEmpfangenFunktion()
{
  [...]

   ledMode = 1;
   ledFarbe = XXX;
   esWurdeWasEmpfangen = true; 
}


  if (ledMode == 1 && esWurdeWasEmpfangen == true) 
  {
    statischeFarbe(ledFarbe);         //hier werden die LEDs dann anders beleuchtet...
    esWurdeWasEmpfangen = false;
  }

Somit wird FastLED nur einmal aktiv.

Bei dynamischen Modi sieht das allerdings anders aus. Als Beispiel nehme ich den RegenbogenEffekt. Dieser ist der Modus Nummer 2 (ledMode = 2) und er wird im loop immer einmal aufgerufen:

if (ledMode == 2) 
  {  
    regenbogenEffekt(globalPause, globalHelligkeit);
  }

ein Zähler in regenbogenEffekt() ändert sich bei jedem Aufruf und somit ändern sich die farben langsam. Die Pausen in solchen Funktionen habe ich mit

if (millis() - previousMillis > pause)

statt mit delay() realisiert.

Es ist klar, dass eine solche Funktion häufig aufgerufen wird und damit auch oft die FastLED library zum Einsatz kommt. Im Normalfall werden also alle 200ms 50LEDs beleuchtet (die Geschwindigkeit kann allerdings auch variiert werden, aber wir gehen erstmal von diesem Wert aus).

Wenn ein statischer Modus aktiv ist, läuft die serielle Kommunikation stabil (soweit ich bisher getestet habe).

Wenn ein dynamischer Modus aktiv ist, werden oft Zeichen verschluckt.

Das Seltsame ist, dass sogar innerhalb der getText()- Funktion Zeichen verschluckt werden. Ich habe mir zum debuggen an wichtigen Stellen ausgeben lassen, was Inhalt der Variablen ist.

char* getSerialText()
{
  static char charArray[61];  
  char c;
  unsigned int numberOfInts;
  int stringLen;
  
  if (Serial.available())  // das hier habe ich mal mit einem while ersetzt in der hoffnung, dass hier solange geblieben wird, bis alles angekommen ist... klappte aber leider nicht
  {

    static char stringLenAsChar[2];  
  
  
    
    
    Serial.readBytes(stringLenAsChar, 2);

    //das eingelesene charArray in Zahlen wandeln
    stringLen = atol(stringLenAsChar);   
    
    Serial.print("Die ersten beiden Zeichen: ");
    Serial.print(stringLenAsChar[0]);
    Serial.println(stringLenAsChar[1]);
  
  
    Serial.print("Umgewandelt als Zahl:  ");
    Serial.println(stringLen);
     
     
    Serial.readBytes(charArray, stringLen);

    if(stringLen>0)
    {
      stringLen=0;
      return charArray;
    }
    else
    {  
      return NULL;
    }
  } 
}



void loop()
{
  while (Serial.available() > 0) 
  {

    char* serialText = getText();
  
    if(serialText != NULL)
    {
    
      Serial.print("Gesamter Text:  ");
      Serial.println(serialText);


[....]

Wenn der modus regenbogen aktiv ist und ich jetzt diese Zeichenkette eingebe "039,3". Dann bekomme ich manchmal als "Die ersten beiden Zeichen" nicht 03 zurück. Viel häufiger ist allerdings, dass diese zwar als 03 eingelesen und auch richtig in die Zahl 3 (=stringLen) umgewandelt werden, aber die Funktion

Serial.readBytes(charArray, stringLen);

danach nicht die verbleibenden "9,3" sondern z.b. nur "9," wiedergibt oder komplett leer ist.

Ich müsste also irgendwie hinbekommen, dass innerhalb der getText()-Funktion wirklich gewartet wird bis alles angekommen ist - auch wenn dadurch der rest (für ein paar ms) blockiert ist. Dies am besten mit einem Timeout, damit das Programm nicht komplett hängen bleibt. Ich weiß nicht ob es etwas ändern würde, aber würde eine Vergrößerung des read buffers hier weiterhelfen?

Ich danke für eure Unterstützung!

Wenn du warten willst bis eine bestimmte Zeichen-Kette da ist, mach noch ein Endzeichen hinten dran und liest solange ein bis das da ist.

Da kannst du auch mal readBytesUntil() versuchen wenn du es nicht per Hand machen willst.

Würde readBytesUntil() denn etwas ändern?

Ich gebe momentan doch vor, dass 3 bytes gelesen werden sollen, aber er liest nicht "9,3" sondern etwas anderes. readBytesUntil() hat zwar noch ein bestimmtes Zeichen als marker, aber ich wüsste nicht, wie ich das gut einbinden könnte.

Leon333: Würde readBytesUntil() denn etwas ändern?

Die ganzen blockierenden und damit unsauberen Drecksfunktionen von Serial kannst Du komplett vergessen.

Außer Serial.begin() zum Initialisieren brauchst Du nur diese Funktionen: - Serial.available() ==> Zum Prüfen ob ein Zeichen vorhanden ist - Serial.read() ==> Zum Lesen eines Zeichens von der Schnittstelle - Serial.print() bzw. Serial.write() ==> Zum Ausgaben von Zeichen auf der Schnittstelle

Alle anderen Serial-Funktionen sind unsauberer Dreck, den man niemandem empfehlen kann, in ein ernsthaftes Programm einzubauen.

Synchronisierung von empfangenen Zeilen/Datensätzen bzw. Werten/Daten programmierst Du Dir besser selbst. Und zwar in interaktiven Programmen oder wenn hohe Ausführungsgeschwindigkeiten bei fehlerfreier Frunktion verlangt sind "blockierungsfrei" ohne das Programm zu blockieren, während Daten unterwegs sind.

Außerdem mußt Du Dir darüber klarwerden: Wenn Du WS2812B LEDs ansteuerst, dann blockieren alle zur Ansteuerung solcher LEDs in Frage kommenden Arduino-Libraries sämtliche Interrupts, während die LEDs angesteuert werden. Also auch die "Serial" Interrupts zum Senden und Empfangen von Zeichen, was bedeutet: Alle Zeichen, die an der seriellen Schnittstelle eintreffen, während Du WS2812 LEDs ansteuerst, können entweder verstümmelt/fehlerhaft sein oder komplett verlorengehen.

Wenn Du "Serial-Empfangen" und "WS2812-Ansteuern" im selben Sketch benötigst, mußt Du beides miteinander synchronisieren und dabei sicherstellen, dass jeweils nur eines von beidem zur Zeit fehlerfrei ablaufen kann.

Ich weiß nicht woran der Fehler liegt. Vor allem nicht wenn es auch mit HardwareSerial nicht geht.

Und es kommt wohl wirklich auf das gleiche raus. readBytes(..., 3) wartet bis 3 Bytes oder die TimeOut Zeit vorbei sind. readBytesUntil() wartet bis das Endzeichen da ist, oder die TimeOut Zeit vorbei ist. Dürfte dann doch den gleichen Effekt haben.

Es ist vielleicht möglich dass du generell zu viel sendest. Ich hatte ein ähnliches Problem mit einem GUI auf dem PC, wo ich die Werte von Slidern übertragen wollte. Einfach bei jedem Event Daten an den Arduino zu senden hat dazu geführt, dass vieles irgendwie verschluckt wurde. Oder es kam an, aber verzögert, da davor erst mal andere Schritte übertragen wurden. Was da genau schief lief weiß ich nicht, aber es war sehr unzuverlässig.

Ich habe es dann so gelöst, dass die Slider Events nur eine Variable setzen ob sich was geändert habe. Dann frage ich mit Timer diese Variable alle 100-200ms ab und erst dann werden die aktuelle Slider Position gesendet. So filtert man einige Zwischenwerte heraus. Wenn man die Zeit entsprechend einstellt macht bekommt man immer noch Zwischenschritte mit, aber nur in einem bestimmten Rahmen. Hätte man auch so lösen können dass nur ein neuer Datensatz gesendet wird, wenn die Abweichung zum vorherigen Wert eine bestimmte Schwelle übersteigt.

Geht das ganze ohne die Ansteuerung der LED Strips?

Ob der Rest ohne Ansteuerung der LEDs funktioniert werde ich leider erst nach Weihnachten testen können. Hatte gehofft etwas damit morgen unter den Baum legen zu können, aber leider wurde daraus ja nichts.

Das werde ich aber auf jeden Fall testen. Übrigens habe ich in meiner App aus slider (Seekbars) und ein Farbkreis, bei dem SEHR schnell jeweils 13 Zeichen hintereinander gesendet werden. Davon ging bisher noch nie etwas spürbar verloren oder ruckelte.

Es scheint daher also wirklich an der Library FastLEDs zu liegen. Zusätlich bin ich heute vom Arduino UNO auf ein ProMini Umgestiegen, damit ich zumindest etwas in der hand habe (später soll ein ProMini verwendet werden). Leider versagt das ProMini komplett - also vor allem bei der Darstellung der Effekte. Die Kommunikation läuft genauso wie beim UNO. Ein RegenbogenEffekt mit gleich bleibender Farbe ist jedoch etwas witzlos :slightly_frowning_face:

Naja... kann nur besser werden. Ich kann jetzt leider erstmal nicht weiter machen. Kommt aber noch!

So ich melde mich mal zurück.

Das Problem mit dem ProMini hat sich geklärt. Ich weiß nicht genau was es war, aber es war sicherlich nichts allgemeingültiges. Bei mir werden verschiedene Sachen im EEPROM gespeichert und ich hab so viel getestet, dass ich entweder einen Codefehler eingearbeitet hatte oder ein Wert im EEPROM falsch war.

Ich habe leider jetzt keine Zeit mehr mich weiter mit diesem Projekt zu beschäftigen und muss es also notgedrungen so fertig machen. Als Zusammenfassung kann ich festhalten, dass es mit dem echten hardware Serial besser funktioniert - vor allem ist das bei den ProMinis spürbar und die FastLED library das lesen am Serial blockiert/behindert.

Wenn ich statische Farben ausgewählt habe funktioniert alles ohne Probleme. Sobald die Effekte laufen und die FastLED library die LEDs befeuert gibt es Probleme. Diese sind allerdings seltener, wenn der Abstand der Befehlseingabe größer ist. Wenn ein Fehler auftritt, dann gehen die LEDs aus. Dann kann man allerdings den Effekt wieder Starten und dann nochmal versuchen die parameter zu verändern.

Es ist keine schöne Lösung und weit von stabil entfernt, aber immerhin funktioniert es. Und weiß ich leider keine Zeit habe und die Steuerung benötigt wird, wird erstmal diese BETA hier genutzt, bis ich weiß, wie ich es besser lösen kann.

Eine Möglichkeit die mir zum debuggen gerade einfällt, wäre mittels einem Mega die Daten auf einem hardware Serial zu empfangen und an dem Serial0 über USB am PC auszugeben und ALLES anzeigen zu lassen was erhalten wurde. Dann sieht man wo der Fehler war.

Mein Problem momentan ist nämlich, dass ich nicht sicher weiß wo genau der Fehler entsteht. Die SerielleKommunikation ist bei mir absichtlich blockierend, da ich lieber alle Zeichen sicher empfange, als für diesen Zeitraum (ein paar ms) einen Effekt zu haben. Diese Pause wird man eh nicht wahrnehmen - und selbst wenn, dann wäre dass nicht schlimm. Seltsamerweise werden aber immer wieder Teile des gesendeten "Codes" verschluckt.

Seltsamerweise werden aber immer wieder Teile des gesendeten "Codes" verschluckt.

Aus dem Grunde hat man vor langen Jahren (30? oder waren es 50?) mal das Handshake erfunden. Erst das Hardwarehandshake und danach das Software Handshake. Leider ist das wieder in Vergessenheit geraten. Zumindest die Arduinowelt meint darauf verzichten zu können......

Auch könnten Prüfsummen, CRC usw. die Kommunikation absichern.

Also ich muss hier nun doch nochmal was zu schreiben.

Den "Fehler" konnte ich reproduzierbar eingrenzen. Bei allen LED-Effekten, bei denen sich die LEDs dauernd ändern (Regenbogen-Fade), gibt es Probleme.

Das Problem ensteht beim einlesen des Strings, den das Bluetooth-Modul empfängt und an das Arduino schickt.

Die Funktion sieht folgendermaßen aus:

char* getSerialText() { static char charArray[61]; char c; int stringLen;

if (Serial.available()) { // Puffer vor Benutzung löschen memset(charArray,0,sizeof(charArray));

// array anlegen um die ersten beiden ankommenden Zeichen zu speichern static char stringLenAsChar[2];

// die ersten beiden Zeichen einlesen Serial.readBytes(stringLenAsChar, 2);

// das eingelesene charArray in Zahlen wandeln stringLen = atol(stringLenAsChar);

// und die verbleibenden Zeichen des Strings einlesen Serial.readBytes(charArray, stringLen);

// HIER PASSIERT DER FEHLER

// den String zurück geben if(stringLen>0) { stringLen=0; return charArray; } else { return NULL; } } }

Ich wollte flexibel sein und habe es so festgelegt, das die ersten beiden Zeichen des ankommenden Strings die Anzahl der verbleibenden Zeichen angeben. Da über Bluetooth eh maximal nur 61 Zeichen sauber übertragen werden, reicht eine zweistellige Zahl dafür aus.

Das CharArray wandel ich mit atol() in eine Zahl um. Anschließend sollen die verbleibenden Zeichen in ein CharArray gespeichert werden. Dazu lese ich so aus: Serial.readBytes(charArray, stringLen);

Der Fehler tritt nicht immer auf, aber wenn er auftritt dann immer so, dass die ersten beiden Zeichen der verbleibenden Zeichenkette verschluckt werden.

Angenommen ich schicke "063,9,12" (ohne Anführungsstriche). Dann werden die ersten beiden Bytes eingelesen "06" (ohne Anführungsstriche) und als Zahl umgewandelt. Anschließend sollen 6 weitere Bytes eingelesen werden und es sollte "3,9,12" (ohne Anführungsstriche) eingelesen werden. Es ist aber im Fehlerfall immer nur "9,12". Die ersten beiden Zeichen "3," werden also nicht eingelesen.

Kann es sein, dass die Funktion atol() zu langsam ist und die ersten beiden Zeichen deswegen verschluckt werden?

static char stringLenAsChar[3]; Versuch es mal damit!

Ja, guter Tip von sschultewolter.

Ausserdem solltest du die tatsächlich gelesene Länge mit auswerten.

strlen = Serial.readBytes(charArray, strlen);

Warum sollte static char stringLenAsChar[3]; funktionieren? Es werden doch eh nur die ersten beiden Zeichen des über Serial empfangenen Strings genommen. Meinst du wegen eventuell vorhandener \n oder \r ?

Das wird hier alles komplett als eine Zeichenkette übertragen, daher verstehe ich nicht, auf welcher Idee dieser Lösungsvorschlag aufbaut.

Ich habe die Idee von dir michael mal eingebaut:

    Serial.readBytes(stringLenAsChar, 2);

    //das eingelesene charArray in Zahlen wandeln
    stringLen = atol(stringLenAsChar);  
   
    //DEBUG
    #ifdef DEBUG 
    SOFT_DEBUG_PRINTLN("String laenge: ");
    SOFT_DEBUG_PRINTLN(stringLen);
    #endif  

    int echteStrLen = Serial.readBytes(charArray, stringLen);
    
    //DEBUG
    #ifdef DEBUG 
    SOFT_DEBUG_PRINTLN("echte String laenge: ");
    SOFT_DEBUG_PRINTLN(echteStrLen);
    #endif  

    //DEBUG
    #ifdef DEBUG
    SOFT_DEBUG_PRINTLN("empfangenert Text: ");
    SOFT_DEBUG_PRINTLN(charArray);
    #endif

Als ausgabe erhalte ich dann im Fehlerfall bei Eingabe von "039,0:

String laenge: 3 echte String laenge: 1 empfangenert Text: 0

Es bleibt also dabei, dass die 2 Zeichen verschluckt werden

C Strings müssen Null-terminiert sein! atol() (hier würde es übrigens auch atoi() tun), erwartet zwar nicht zwangsläufig einen richtig terminierten String, aber es muss ein Zeichen am Ende stehen das keine Zahl ist.

Und wenn du ein lokales Array als static deklarierst wird das automatisch 0-initalisiert. Also steht im letzten Index sicher NULL wenn du ihn nicht überschreibst.

Achso! Darauf baut also die Idee von Stefan auf.

Ok, also wenn ich das Array mit einer größe von 3 erstelle, dann ist der 3. immer NULL. Das ist nun klar. Danke.

Allerdings ändert das auch nichts an dem Problem, dass nach dem einlesen der ersten beiden Bytes manchmal die nachfolgenden beiden Bytes nicht eingelesen werden.

Ich habe auch mal das stringLen = atol(stringLenAsChar); (werde ich dann noch in atoi() ändern) komplett raus genommen und stringLen = 3; gesetzt. Es liegt nicht daran, das die funktion zu lange braucht. Irgendwie werden einfach 2 Bytes weg geschnitten, wenn die FastLED library schnell feuert.

Dabei ist wichtig, dass KEINE ansteuerung der LEDs mehr stattfindet, sobald Serial.available() ist. Das einlesen blockiert also (gewollt) die ansteuerung - eigentlich desshalb, damit alles ohne Fehler eingelesen werden kann :(

Ja, mir ging es um das terminierende Zeichen '\0'.

Wie sieht deine Ansteuerung (Sketch) zu den FastLeds aus? Ich denke, dass es hier zu einem Problem kommt.

Wenn ein String vollständig empfangen wurde, dann darfst du nur ein FastLed.show() machen. Wenn nun aber FastLed noch nicht fertig ist, und neue Anweisungen über die Serielle Schnittstelle kommen, dann werden diese noch nicht abgearbeitet und verfallen, da FastLed die Interrupts sperren muss. Das sperren der Interrupts kann unter umständen auch den millis Timer ausbremsen.

Also den ganzen Sketch erspare ich euch mal (das sind 14460 Bytes).

Die FastLED ansteuerung wird beim auslesen allerdings überhaupt nicht verwendet.

Mein Loop sieht mit ein paar weiteren Modi so aus:

void loop()
{

  char* serialText = getSerialText();
  
  if(serialText != NULL)
  { 
    int numberOfInts;
    receivedParameter = cutText(serialText, &numberOfInts);
    
    
    if ((receivedParameter[0] == 1)
    {      

      ledMode = receivedParameter[0]; // den neuen Modus merken
      ledHex = receivedParameter[1]; // Farbwert merken

      changeMode=true;
    }
    else
    {
       ledMode = receivedParameter[0]; // den neuen Modus merken
       changeMode=true;  

    }
    
  }




  // ------------------------------------------------ Alle LEDs ausschalten ------------------------------------------------
  // Code: 010
  if (ledMode == 0  && changeMode == true) 
  {    

    one_color_all(0,0,0);
    changeMode = false;  // speichern, dass die Änderung übernommen wurde 

  }  

  // ------------------------------------------------ Alle LEDs mit der Farbe des ColorWheels (in der App) leuchten lassen ------------------------------------------------
  // Code: 101,0xRRGGBB
  // RRGGBB = Hex-Farbwert
  if (ledMode == 1 && changeMode == true) 
  {

    
    colorWheel();
    changeMode = false;     // speichern, dass die Änderung übernommen wurde 
    
    
  }

Die funktion getSerialText() sieht so aus:

char* getSerialText()
{
  static char charArray[61];  
  char c;
  int stringLen;
  
  if (Serial.available())
  {
    memset(charArray,0,sizeof(charArray));// Puffer vor Benutzung löschen
    static char stringLenAsChar[3];  
  
    Serial.readBytes(stringLenAsChar, 2);
    
    //DEBUG
    #ifdef DEBUG 
    SOFT_DEBUG_PRINT("echte StringLenAsChar: ");
    SOFT_DEBUG_PRINTLN(stringLenAsChar);
    #endif 

    //das eingelesene charArray in Zahlen wandeln
    //stringLen = atol(stringLenAsChar);  
    stringLen = 3;  
   
    //DEBUG
    #ifdef DEBUG 
    SOFT_DEBUG_PRINT("String laenge: ");
    SOFT_DEBUG_PRINTLN(stringLen);
    #endif  



    //und die verbleibenden Zeichen des strings einlesen
    int echteStrLen = Serial.readBytes(charArray, stringLen);
    
    //DEBUG
    #ifdef DEBUG 
    SOFT_DEBUG_PRINT("echte String laenge: ");
    SOFT_DEBUG_PRINTLN(echteStrLen);
    #endif  
    
    
    //DEBUG
    #ifdef DEBUG
    SOFT_DEBUG_PRINT("echter empfangenert Text: ");
    SOFT_DEBUG_PRINTLN(charArray);
    #endif 


      // wenn das charArray nicht leer ist, dann soll es übergeben werden
      if(stringLen>0)
      {
        stringLen=0;
        return charArray;
      }
      else
      {  
        return NULL;
      } 
  }
}

man sieht also, dass in dieser Funktion kein LEDS.show(); Aufruf stattfindet.

ich habe jetzt eine Art workaround gemacht, indem ich in der getSerialText()-Funktion so hier:

  if (charArray[stringLen-1] == NULL)
    {
  
      //DEBUG
      #ifdef DEBUG 
      SOFT_DEBUG_PRINTLN("FEHLER!! Die Zeichenkette wurde nicht komplett erfasst."); 
      #endif 
      
      Serial.write("Übermittlungsfehler");
  
      boolean warten = true;
      previousMillis = millis(); 
      
      while (warten) 
      {
        // Sobald der nochmals gesendete String kommt noch einmal die Daten verarbeiten
        if (Serial.available())
        {
          warten = false;
          Serial.write("kam an");
          
          return getSerialText();
        }

        // Für den Fall, das nichts ankommt, nach einem Timeout von maximal 3 Sekunden die Warteschleife beenden.
        if ( (millis() - previousMillis) > 3000)
        {
           warten = false; 
           Serial.write("timeout");
          //Optisch anzeigen, dass das Timeout abgelaufen ist
           one_color_all(0,0,0);
           delay(80); 
           one_color_all(0,0,60); 
           delay(150);
           one_color_all(0,0,0); 
           delay(80);
           one_color_all(0,0,60); 
           delay(150);
           one_color_all(0,0,0);       
        }
      }  
    }
    }

Ergänzt habe um prüfenzu lassen, ob die Anzahl der Folgezeichen (wie es in den ersten beiden zeichen angekündigt wurde) auch wirklich ankommt.

Die Nachricht "Übermittlungsfehler" geht dann an mein Handy zurück, dass daraufhin nochmal den letzten Befehl sendet. Es ist also eine Art "handshake", allerdings auf simpler basis und ohne Prüfsummer oder so. Es funktioniert zwar so stabil, aber natürlich deutlich langsamer, als wenn es direkt ankommt. Ziemlich doof alles.

Es ist immernoch so, dass Zeichen 3 und 4 manchmal nicht vom Arduino erfasst werden. Falls jemand mir sagen kann, wie ich die beknackten Zeichen 3 und 4 richtig empfange, dann immer her damit.