Verzweiflung mit Serial.available/read

Hallo,
Bin leider schon seit 5h Stunden am verzweifelten suchen des Fehler und ich komme ganz einfach nicht darauf. Hoffe ihr könnt mir hier weiterhelfen :slight_smile:

Ich habe ein GSM Modul schicke ein AT Kommando zum Modul und möchte die Antwort, welche ich auf dem Arduino Mega 2560 zurückbekomme, analysieren. Die Antwort selbst beinhaltet mehr als 64Bytes. Nachdem der Serial Buffer ja Standard auf 64Bytes gestellt ist und ich über 300Bytes als Antwort bekomme speichere ich das ganze in ein Array ab.
Hierbei brauche ich aber nur die ersten 10Bytes, die anderen lese ich zwar ebenfalls aus (damit der Buffer leer ist) speichere den Rest jedoch nicht in das Array ab.

Soweit so gut.

Das Problem besteht nun darin, dass beim ersten Aufruf der Funktion kein Inhalt von der seriellen Schnittstelle in den Buffer gespeichert wird. Anscheinend liegt hier keine Antwort des GSm Moduls an. Auch wenn ich zwischen AT Kommando und auslesen der seriellen Schnittstelle eine delay Funktion einbaue, führt das erste Aufrufen der FUnktion zu keinen Daten. Erst beim zweiten Aufrufen der IsSMSUnread() Funktion bekomme ich eine richtige Antwort.

Hättet ihr hier eine Idee weshalb sich das programm hier so merkwürdig verhält?

#define GSMSerial Serial3
#define MEGASerial Serial 
 
void setup()
{
  GSMSerial.begin(9600);
  MEGASerial.begin(9600);
     
  GSMSerial.write("AT+CMGL=\"REC UNREAD\",1\r");
}
 

void loop()
{

  int x = IsSMSUnread();
  MEGASerial.println(x);

  delay(5000); 

}



int IsSMSUnread()
{
  //If no new SMS is received, only ...OK<CR><LF> is received.
  //In c and C++ we receive OK\r\n where \n is the linefeed character (<LF>).
  char gsmBuffer[10];
  char c = 0;
  char x = 0;
  int i = 0;

  memset(gsmBuffer, '\0', sizeof(gsmBuffer)); //Set all 10 places to zero
  
    while(GSMSerial.available() != 0)
    {
      c = GSMSerial.read(); //Read character by character
      if(i<10)
      {
        gsmBuffer[i] = c; //Write character by character into buffer
      }
      i++;
    }
    
  i = 0;
  c = 0;

//  If there is not SMS, response is (30 chars)
//         AT+CMGL="REC UNREAD",1  --> 22 + 2
//                                 --> 2
//         OK                      --> 2 + 2
//
//     If there is SMS, response is like (>64 chars)
//         AT+CMGL="REC UNREAD",1
//         +CMGL: 9,"REC UNREAD","XXXXXXXXX","","14/10/16,21:40:08+08"
//         Here SMS text.
//         OK  
  

  MEGASerial.print("In Buffer is2: ");
  MEGASerial.println(gsmBuffer);

  

  if(strstr(gsmBuffer, "OK") != 0)
  {
    MEGASerial.println("Keinen neuen Nachrichten Vorhanden!");

    memset(gsmBuffer, '\0', sizeof(gsmBuffer)); //Set all 10 places to zero

  MEGASerial.print("In Buffer is3: ");
  MEGASerial.println(gsmBuffer);
  
    return false;
  }
  else
  {
    MEGASerial.println("Neue Nachrichten Vorhanden!");

    memset(gsmBuffer, '\0', sizeof(gsmBuffer)); //Set all 10 places to zero

  MEGASerial.print("In Buffer is3: ");
  MEGASerial.println(gsmBuffer);
  
    return true;
  }
 
}

Erst beim zweiten Aufrufen der IsSMSUnread() Funktion bekomme ich eine richtige Antwort.

Kein Wunder. Wie viele Leute unterschätzt du völlig wie langsam die serielle Schnittstelle ist. Ein Zeichen braucht 1 / Baudrate * 10 Sekunden. Das auf einmal in einer while-Schleife einzulesen ist also zum Scheitern verurteilt. Da wird dann oft gleich mit delay() gearbeitet um diese Zeit zu verplempern, aber das ist auch unsinnig, da die Zeit von der Baudrate und Länge der Nachricht abhängt

So macht man es richtig:

Erklärung zum Zeitverhalten dazu:

Das speichert auch nur so viel ab wie Platz im Array ist. Dabei darfst du den Null-Terminator nicht vergessen. Ein Array der Größe 10 hat nur 9 sichtbare Zeichen

Da braucht du dann noch eine kleine Zustandsmaschine so dass du das Kommando nur einmal abschickst und danach auf die Antwort "wartest" bevor wieder was gesendet wird. Also zwischen den Zuständen "Kommando senden" und "Antwort einlesen" wechseln

Hey Danke für deine rasche Antwort und für deine Beispiele! Das hilft mir einiges weiter!

Habe mir das ganze Programm einmal durchüberlegt. Soweit verstehe ich das gut u ich merke das das etwas mehr Programmier-Niveau ist als bei mir. Etwas viel mehr.

Verstehe ich das richtig, dass readLine() einmal AUfgerufen wird, alles in buffer gespeichert wird bis das LF kommt und dann wieder zurückgegeben wird in das Hauptprogramm? Danach wird es erneut ausgeführt, wenn Daten vorhanden wieder in Buffer gespeichert, wenn keine vorhanden wird while(stream.available()) übersprungen.

Über Stream habe ich mich bereits informiert.

Was genau ist nullptr? Ist das char welcher Zeichen beinhaltet oder eine allgemeine Funktion wie z.B. if() etc.? Da du ja den nullptr returnst ich aber nicht weiß was genau das ist.

Alles andere ist sehr nachvollziehbar. Danke!

Noch eine kleine Frage bzgl. deinem Programmier Stil, hast du dir diesen selbst beigebracht oder eine entsprechende Ausbildung genoßen? Bin nebenbei auch immer auf der suche nach guter Literatur/Kurse eben beispielsweise für genau so etwas wie dieses Thema hier. Kannst du hier etwas empfehlen?

Thor2018:
Verstehe ich das richtig, dass readLine() einmal AUfgerufen wird, alles in buffer gespeichert wird bis das LF kommt und dann wieder zurückgegeben wird in das Hauptprogramm?

Das eben gerade nicht! Du kannst das nicht auf einmal einlesen. Jedenfalls nicht ohne irgendwo delay() einzubauen, was unsauber ist und je nach Anwendung auch unbrauchbar. Vielleicht will man während der Übertragung ja noch was tun, wie LEDs blinken lassen.
Der Prozessor ist viel schneller als die Übertragung. Die Funktion muss ständig so lange aufgerufen werden bis sie meldet dass sie fertig ist. Das Ende einer Nachricht kann man ja leicht am Linefeed erkennen. Bei manchen Anwendungen wird auch nur ein CR gesendet.

Deshalb darfst du auch nicht schon wieder ein Kommando an das Modul schicken noch bevor du die Antwort eingelesen hast. Bei dir ist das in Moment in setup(). Das ist erst mal ok. Da wird das nur einmal gemacht. Aber in einem richtigen Programm will man vielleicht mal verschiedene Kommandos senden.

Was genau ist nullptr?

https://en.cppreference.com/w/cpp/language/nullptr

Eine besserer, typ-sichere Alternative für NULL. Bräuchte man hier nicht unbedingt, aber ich möchte mir das generell angewöhnen. Wo das wichtig ist, ist z.B. beim Überladen von Funktionen. Da NULL einfach 0 ist, kann man nicht richtig zwischen func(char*) und func(int) unterscheiden wenn man NULL übergibt. Das Problem gibt es auch in der Arduino Software, z.B. in der Wire Klasse

Noch eine kleine Frage bzgl. deinem Programmier Stil, hast du dir diesen selbst beigebracht oder eine entsprechende Ausbildung genoßen?

Beides. Auch wenn man irgendwo was lernt muss man sich vieles selbst beibringen. Gerade was die Details der Programmiersprachen betrifft. Beigebracht bekommt man eher allgemeine Prinizipien

readLine hat die Wartefunktion eingebaut.

Wenn garnichts oder etwas ohne Zeilenende kommt, läuft die Funktion in einen Timeout.

Wenn der Sketch sonst sowieso nichts tun kann oder soll und man sich dieses Verhaltens bewußt ist, kann man readLine Serial.readBytesUntil o.ä. verwenden

Sorry. Das mit readLine war daneben.
Serial.readBytesUntil ('\n', buffer, len) oder ähnliches hatte ich wohl im Kopf

michael_x:
Wenn garnichts oder etwas ohne Zeilenende kommt, läuft die Funktion in einen Timeout.

Häh? Verwechselst du das mit den eingebauten Lesefunktionen der Arduino IDE? Die arbeiten mit einem Timeout.

Meine Funktion kehrt einfach zurück wenn keine Daten da sind oder das Ende noch nicht da war. Dadurch muss man sie mehrmals aufrufen bis eine Zeile komplett da ist

Sorry, ja. Vergesst das bitte

Danke nochmals für deine Hilfe @Serenifly! Habe mir nun neben der Implementierung deiner Funktion einen Zustandsautomaten gebaut welcher bei einem Fehler das Kommando nochmals wiederholt und an das Hauptprogramm die eigentliche Anzahl an ungelesenen SMSen zurückgibt. Bei Fehler Meldungen ebenso diese.

Hier der Code falls noch jemand etwas in der Art gebrauchen könnte :slight_smile:

#define GSMSerial Serial3
#define MEGASerial Serial
 
const unsigned int READ_BUFFER_SIZE = 11;    //10 Zeichen + Terminator
typedef enum { SendCMD, ReadLine } state_t ;

void setup()
{
  GSMSerial.begin(9600);
  MEGASerial.begin(9600);  
}
 

void loop()
{
  int c = 0;
  c = stateMachine_IsSMSAvailable();
  MEGASerial.print("Ausgabe Hauptprog Unread SMS = ");
  MEGASerial.println(c);


  delay(20000); //Um die Funktion alle 20sec aufzurufen, rein für das Testen gedacht!
}



int stateMachine_IsSMSAvailable()
{
  int repeatswitch = 1;
  state_t state = SendCMD;

  
  while(repeatswitch)
  {
  switch( state ) 
  {
    case SendCMD:
      GSMSerial.write("AT+CMGL=\"REC UNREAD\",1\r");
      state = ReadLine;
      repeatswitch = 1;
      break;
 
    case ReadLine:
      char* txt = readLine(GSMSerial);
      char* s;        
      if (txt != nullptr)
      {
        Serial.print("Empfangen: ");
        Serial.println(txt);
        
        if(strstr(txt, "UNDER_VOLTAGE") != 0) 
        {
          repeatswitch = 0;
          return -1;
        }
        else if(strstr(txt, "OVER_VOLTAGE") != 0) 
        {
          repeatswitch = 0;
          return -2;
        }
        else if(strcmp(txt, "OK") == 0) //Wenn Strings ident
        {
          Serial.println("OK ist gekommen");
          repeatswitch = 0;
          return atoi(s+2);
        }
        else if(strstr(txt, "+CMGL:") != 0) //String gefunden im Buffer
        {
          s = strstr(txt, ":"); //Altes s überschreiben, da nur das letzte zählt (höchste Zahl)
          Serial.print("atoi ");
          Serial.println(atoi(s+2));

          state = ReadLine;
          repeatswitch = 1;
        }
        else if(strstr(txt, "ERROR") != 0)  //Falls ERROR ausgegeben wird versuche nochmals CMD zu senden 
        {
          state = SendCMD;
          repeatswitch = 1;
        }
      }
      else
      {
        repeatswitch = 1;
      }
      break;
  }
  }
}





char* readLine(Stream& stream)
{
  /* Typische Antwort auf AT+CMGL="REC UNREAD",1:
   *  17:44:08.902 -> +CMGL: 1,"REC UNREAD","+436606xxxxxx","","2019/10/18 10:34:58+08"
   *  17:44:08.970 -> Neu10
   *  17:44:09.038 -> 
   *  17:44:09.038 -> +CMGL: 2,"REC UNREAD","+436606xxxxxx","","2019/10/18 10:36:52+08"
   *  17:44:09.140 -> Neu11
   *  
   *  Bevor eine neue Zeile anfängt wird noch ein \n gesendet, sprich neue Zeile -
   *  dieses wird im Programm benutzt um das erste buffer Packet an das aufrufende
   *  Programm zu übergeben.
   */
   
  static byte index;
  static char buffer[READ_BUFFER_SIZE];

  while (stream.available())
  {
    char c = stream.read(); //Auslesen der Antwort bis \n kommt, sonst weiter inkrementieren

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