Sim800 Kommunikation Funktioniert nicht

Hallo ich habe e,inen sim800L an meinen Arduino uno angeschlossen und will damit Messdaten von mehreren Sensoren Stündlich an einen Webserver senden.
Wenn ich die Befehle um das zu tun im Seriellen Monitor eingebe klappt das ganze auch, aber ich bekomme es einfach nicht hin das, dass automatisch Funktioniert da egal wie ich das script geschrieben habe ich hier bei dem case 3 immer nur AT+SAPBR=3,1,APN,web.vod gesendet wird und dann mit dem dem nächsten weiter gemacht wird. Ich habe auch bei Dauer schon jegliche Werte ausprobiert. Weiß irgendjemand wie ich dieses Problem beheben kann?

#include <SoftwareSerial.h>
#include <Wire.h>
#define Sim800_TX 8
#define Sim800_RX 9

SoftwareSerial mySerial(Sim800_TX, Sim800_RX);
String apn = "web.vodafone.com";                       //APN
String apn_u = "vodafone";                     //APN-Username
String apn_p = "vodafone";                     //APN-Password
String url = "url";
int Schritt;
unsigned long Dauer;
unsigned long previousMillis;

void setup() {
  Serial.begin(9600);
  while (!Serial);
  mySerial.begin(9600);
  delay(10000);
  Serial.println("Bereit");
}

void loop(){
   unsigned long currentMillis = millis();


  if (  currentMillis  - previousMillis > Dauer )  
  {
     Schritt++;  
     switch (Schritt)
          {
                case 1:
                   mySerial.println("AT");
                   tell();
                   Dauer = 50;
     
               case 2:
                   mySerial.println("AT+SAPBR=3,1,Contype,GPRS");
                   tell();
                   Dauer = 50; 
                   break;

               case 3:
                  mySerial.println("AT+SAPBR=3,1,APN," + apn);
                  tell();
                  Dauer = 50;
                  break;

               case 4:
                  mySerial.println("AT+SAPBR=3,1,USER," + apn_u);
                  tell();
                  Dauer = 50;
                  break;

               case 5:
                  mySerial.println("AT+SAPBR=3,1,PWD," + apn_p);
                  tell();
                  Dauer = 50;
                  break;

               case 6:
                  mySerial.println("AT+SAPBR =1,1");
                  tell();
                  Dauer = 50;
                  break;

               case 7:
                  mySerial.println("AT+SAPBR=2,1");
                  tell();
                  Dauer = 50;
                  break;

             default:
                  Schritt = 0;
                  Dauer = 1;     
          }
          previousMillis = currentMillis; 
  }

}

void tell() {
  if (mySerial.available()) {
    Serial.write(mySerial.read());
  }
}

Du unterschätzt wie so oft wie langsam Serial ist. Bei 9600 Baud braucht ein Zeichen ca. 1ms!

Deshalb senden solche Geräte Endzeichen (wie das CR) auf das man Abfragen kann. Dann weiß man wann eine Übertragung zu Ende ist. Dazu kann man sich einen Zustandsautomaten bauen der zwischen Senden und Empfang umschaltet. Und erst wenn eine Antwort komplett empfangen wurde sendet man das nächste Kommando.

Du hast zwar eine Wartezeit zwischen den Kommandos, aber du liest jedesmal nur ein Zeichen aus. read() liest genau ein Byte aus!

Oke gibt es eine andere methode wie read() um das ganze schneller auszulessen?

Normal ruft man da eine Aulese-Funktion immer wieder auf. Wenn available() 0 ist beendet man diese sofort. Und wenn ein Zeichen da ist, liest man es aus. Wenn es CR ist, ist die Übertragung zu Ende. Ansonsten kann mit dem Zeichen was tun.

Hier z.B. um einen String in ein char Array einzulesen bis ein CR kommt:

const int SERIAL_BUFFER_SIZE = 30;
char serialBuffer[SERIAL_BUFFER_SIZE];

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

void loop()
{
  if (readSerial(Serial))
  {
    Serial.print(F("Read: ")); Serial.println(serialBuffer);
  }

}

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 == '\r')   //Alternativ geht in diesem Fall auch '\n'
    {
      serialBuffer[index] = '\0';
      index = 0;
      return true;
    }
  }
  return false;
}

Lass dich nicht von der while Schleife verwirren. Normal wird da nur ein Zeichen eingelesen und danach beendet sich die Funktion erst mal. Du kannst nicht x Zeichen in einer while Schleife direkt nacheinander einlesen wenn ein Zeichen 1ms dauert

Alternativ kannst du auch jedes Zeichen direkt auf die andere Serielle Schnittstelle umleiten. Ohne den ganzen String zu speichern

Aber man kann auch den Empfangenen String Parsen und bestimmte Zahlen darin auslesen

Darüber brauchst du wie gesagt einen Zustandsautomaten. Je nach Zustand kann man dann entweder Empfangen oder das nächste Kommando senden. Also solange der Zustand "Empfangen" ist ruft man readSerial() auf. Wenn fertig schaltet man um auf "Senden" und sendet das nächste Kommando. Und dann wieder auf "Empfangen"

Nachtrag:
Mal nachgesehen und es ist ein klein wenig komplizierter:

Commands are usually followed by a response that includes.
""

Also wird schon am Anfang der Antwort CR + LF gesendet. Und dann noch mal am Ende. Das ist etwas doof. Aber das kann man auch leicht abfangen wenn man die zählt wie oft serialRead() true liefert. Und erst beim zweiten mal ist die Antwort da.

Mal nachgesehen und es ist ein klein wenig komplizierter:
https://www.elecrow.com/download/SIM800%20Series_AT%20Command%20Manual_V1.09.pdf

Ja das Pdf kenn ich schon da hab ich mir auch die AT Befehle rausgesucht. Der code den du mir geschickt hast ersetzt nur mein void tell() wenn ich das richtig verstanden habe, oder ist das ein Zustandsautomat?

Sorry wenn ich mich hier ein bißchen dumm anstelle aber ich bin noch ziemlich unerfahren mit dem Arduino.

Etwas Verständnis wie ein Programm abläuft brauchst du für sowas schon wenn du das per Hand programmieren willst

Nein, das ersetzt nicht dein direkt tell(). Das kann man nicht nur einmal aufrufen, sondern es muss immer wieder aufgerufen werden bis die Übertragung zu Ende. Deshalb braucht man da etwas Logik außen herum (die ich nicht gezeigt habe!)
1.) dauert die Übertragung einige Zeit
2.) fängt die Antwort schon mit einem CR + LF an wodurch man mitzählen muss wie viele Zeilen man empfangen hat (das erste CR liefert schon true)
3.) werden je nach Kommando evtl. mehrere Zeilen als Antwort empfangen wenn ich das richtig sehe. Da müsste man dann auch mitzählen. Das ist aber nicht schwer

Du kannst es auch ganz naiv machen und wartest nach dem Senden des Kommandos erst mal 50-100ms bis du mit dem Einlesen anfängst. Das geht auch mit millis(). Dann kannst du alles in einem Rutsch einlesen solange die Zeit groß genug ist und es in den seriellen Eingangspuffer passt. Schön und 100%ig zuverlässig ist das aber nicht.

Alternativ Suche nach einer fertigen Library dafür. Da gibt es einiges

Oke das hab ich jetzt so weit glaub ich verstanden.
Kannst du mir noch sagen wo ich die "Serial:println" mit denen ich die AT Befehle senden will unterbringen soll?
Und wie ich da mitzählen kann und das ganze dann auch so auswerten das bei einem OK der nächste Befehl ausgeführt wird und bei einem ERROR der Befehl oder gegebenenfalls das ganze Programm noch mal von vorne abläuft?

Kannst du mir noch sagen wo ich die "Serial:println" mit denen ich die AT Befehle senden will unterbringen soll?

Ich werde dir nicht alles vorkauen. Das ist nicht unbedingt ein Projekt für blutige Anfänger

Zu Zustandsmaschinen (engl. finite state machine) gibt es im Internet unendliche Anleitungen weil das eines der elementarsten Informatikthemen ist. z.B.:
https://www.mikrocontroller.net/articles/Statemachine

Und wie ich da mitzählen kann und das ganze dann auch so auswerten das bei einem OK der nächste Befehl ausgeführt wird und bei einem ERROR der Befehl oder gegebenenfalls das ganze Programm noch mal von vorne abläuft?

Wenn ich das richtig sehe wird jede Antwort entweder durch OK oder ERROR abgeschlossen. Dann ist es am besten man zählt nicht die Anzahl der empfangen Zeilen, sondern liest so lange ein bis eine dieser zwei Antworten kommt. Die Zeilen davor kann man z.B. auf der seriellen Schnittstelle ausgeben

Wie man einen String einliest habe ich oben schon gezeigt. C Strings werden mit strcmp() verglichen:
http://www.cplusplus.com/reference/cstring/strcmp/

readSerial() liefert true wenn eine Zeile eingelesen wurde. Dadurch kannst du einfach sowas machen:

void loop()
{
 if (readSerial(mySerial))
 {
    if (strcmp(serialBuffer, "OK") == 0 )
    {
    }
    else if (strcmp(serialBuffer, "ERROR") == 0 )
    {
    }
    else
    {
        Serial.println(serialBuffer);
    }
  }
}

Außerdem kann man noch einen Timeout einbauen wenn eine bestimmte Zeit lang nichts empfangen wurde und das auch als Fehler betrachten

Wie gesagt, das ist nicht unbedingt ein Anfänger Thema, auch wenn es auf den ersten Blick einfach aussieht. Man muss da einige Sachen beachten und man braucht ein klares Verständnis für den zeitlichen Ablauf des Programms und der Kommunikation

Wenn ich das richtig sehe wird jede Antwort entweder durch OK oder ERROR abgeschlossen.

Nein bei OK kommt bei machnchen Befehlen noch etwas danach aber es ist immer ein OK oder ERROR in der Antwort enthalten.

Ja da hast du wohl recht das es nicht das einfachste Projekt ist, ich es aber brauche da meine Messdaten aus meinem Gewächshaus auf meinen Server kommen müssen, damit ich sie da dann auswerten kann (wo von ich mehr verstehe) und dann dort anzeigen kann. Aber ich denke wenn ich mich einlese schaff ich es das ich das hinbekomme.

Danke das du so geduldig bist.

Ich habe jetzt mit deiner Hilfe und einigen anderen Tipps aus dem Internet geschafft eine Zustandmaschiene zu bauen, welche ich verstehe und hab jetzt nur noch eine Frage.
Wie kann ich das Programm so schreiben das wenn bei case'0' ein "OK" zurückkommt case'1' aufgerufen wird und wenn ein "ERROR" zurück kommt das case nochmal ausgeführt wird? Momentan muss ich ja jeden Befehl nach einander eingeben, mein Ziel ist es das ich nur noch case'0' aufrufen muss und dann alle anderen danach automatisch aufgerufen werden.

#include <SoftwareSerial.h>
#define SIM800_TX 8
#define SIM800_RX 9
SoftwareSerial mySerial(SIM800_TX, SIM800_RX);

int nummer = 0;

#define ZEILENTRENNZEICHEN 13
char* receiveBuffer() {
  static char lineBuffer[40];
  static byte counter = 0;
  char c;
  if (mySerial.available() == 0) return NULL;
  if (counter == 0) memset(lineBuffer, 0, sizeof(lineBuffer));
  c = mySerial.read();
  if (c == ZEILENTRENNZEICHEN)
  {
    counter = 0;
    return lineBuffer;
  }
  else if (c >= 32)
  {
    lineBuffer[counter] = c;
    if (counter < sizeof(lineBuffer) - 2) counter++;
  }
  return NULL;
}

void setup() {

  Serial.begin(9600);
  while (!Serial);

  mySerial.begin(9600);
  while (!mySerial);

}

void loop() {

  nummer = Serial.read();

  switch (nummer) {

    case '0':
      while (!mySerial);
      mySerial.println("AT");
      break;

    case '1':
      while (!mySerial);
      mySerial.println("AT+SAPBR=3,1,Contype,GPRS");
      break;

    case '2':
      while (!mySerial);
      mySerial.println("AT+SAPBR=3,1,APN,web.vodafone.de");
      break;

    case '3':
      while (!mySerial);
      mySerial.println("AT+SAPBR=3,1,USER,vodafone");
      break;

    case '4':
      while (!mySerial);
      mySerial.println("AT+SAPBR=3,1,PWD,vodafone");
      break;

    case '5':
      while (!mySerial);
      mySerial.println("AT+SAPBR=1,1");
      break;

    case '6':
      while (!mySerial);
      mySerial.println("AT+SAPBR=2,1");
      break;

  }

  char* text = receiveBuffer();
  if (strcmp(text, "OK") == 0 ) {
    if (text != NULL) {
      Serial.println(text);
    }
  } else if (strcmp(text, "ERROR") == 0 ) {
    if (text != NULL) {
      Serial.println(text);
    }
  } else {
    if (text != NULL) {
      Serial.println(text);
    }
  }
}

Ich sehe keine Zustandsmaschine. Da fehlen jegliche Zustandsübergänge. Du tippst lediglich Kommandos über die serielle Schnittstelle ein. Das kann als Test sehr hilfreich sein, aber ist nicht die Lösung

Außerdem fehlt immer noch dass du mal zwischen Senden und Empfangen unterscheidest. Das geht im Moment weil nur gesendet wird wenn du per Hand ein Zeichen eingibst. Sobald das automatisiert wird hast du das gleiche Problem wie vorher. Du musst nach dem Absenden eines Kommandos in einen Zustand gehen der die Antwort von mySerial empfängt. Und erst dann wenn diese abgehandelt ist, gehst du in einen Zustand der das nächste Kommando sendet

Am einfachsten geht es mit zwei Zustandsmaschinen. Eine für Senden und Empfangen. Und eine zweite für die verschiedenen Kommandos die du Senden möchtest. Die Kommando-Schrittkette läuft sequentiell durch. Das kann ruhig ein Integer sein der inkrementiert wird, so wie du das in deinem ersten Post hast. Das war nicht verkehrt. Und am Ende kann man den wieder auf 1 setzen damit es von vorne losgeht.
Aber zwischendurch muss immer wieder mal zwischen Senden und Empfang umgeschaltet werden.

(text != NULL)

Ist überflüssig. Wenn strcmp() 0 ist kann text nicht NULL sein

Erstmal danke ich werde jetzt das ganze umsetzen und (text != NULL) ist nur bei den oberen beiden überflüssig bei der unteresten ist es wichtig da sonst der ganze Monitor mit leeren Zeilen überflutet wird.

Hypec:
und (text != NULL) ist nur bei den oberen beiden überflüssig bei der unteresten ist es wichtig da sonst der ganze Monitor mit leeren Zeilen überflutet wird.

Sowie du es hast, ja.

Oder du hättest dich an meine Version gehalten:

  char* text = receiveBuffer();
  
  if (text != NULL)
  {
     if (strcmp(text, "OK") == 0 ) 
     {
     }
     else if (strcmp(text, "ERROR") == 0 ) 
    {
    }
    else 
    {
    }
  }

Das ist logischer. Erst mal abfragen ob überhaupt die Übertragung der aktuellen Zeile zu Ende ist (jurs Funktion liefert da != NULL. Meine liefert true) und erst danach auswerten. Und nicht beides vermischen.

Auch wenn es geht gibt es keinen Grund strcmp() auszuführen wenn du die Zeile noch nicht mal vollständig eingelesen hast

Ja du hast recht aber dann muss man noch in das else Serial.println(text); schreiben da sonst der Befehl auf den die Antwort kommt nicht mehr geschrieben wird was sehr verwirrend ist.

  if (empfangen == true) {
    char* text = receiveBuffer();
    if (text != NULL) {
      if (strcmp(text, "OK") == 0 ) {
        Serial.println(text);
        senden = true;
        empfangen = false;
      } else if (strcmp(text, "ERROR") == 0 ) {
        Serial.println(text);
        senden = true;
      } else {
        Serial.println(text);
      }
    }   
  }

Ich bin noch nicht Fertig mit dem Code aber mir fällt jetzt schon auf das wenn ich erst case 1 wo empfangen ja nicht aktiviert wird öffne und dann case 0 mir dann im Seriellen Monitor erst die Antwort für case 1 angezeigt wird. Weiß jemand wie ich das verhindere und case 1 dann sozusagen ins leere läuft?

#include <SoftwareSerial.h>
#define SIM800_TX 8
#define SIM800_RX 9
SoftwareSerial mySerial(SIM800_TX, SIM800_RX);

int nummer = 0;
boolean senden = true;
boolean empfangen = false;

#define ZEILENTRENNZEICHEN 13
char* receiveBuffer() {
  static char lineBuffer[40];
  static byte counter = 0;
  char c;
  if (mySerial.available() == 0) return NULL;
  if (counter == 0) memset(lineBuffer, 0, sizeof(lineBuffer));
  c = mySerial.read();
  if (c == ZEILENTRENNZEICHEN)
  {
    counter = 0;
    return lineBuffer;
  }
  else if (c >= 32)
  {
    lineBuffer[counter] = c;
    if (counter < sizeof(lineBuffer) - 2) counter++;
  }
  return NULL;
}

void setup() {

  Serial.begin(9600);
  while (!Serial);

  mySerial.begin(9600);
  while (!mySerial);

}

void loop() {

  if (senden == true) {
    nummer = Serial.read();
    switch (nummer) {

      case '0':
        while (!mySerial);
        mySerial.println("AT");
        empfangen = true;
        senden = false;
        break;

      case '1':
        while (!mySerial);
        mySerial.println("AT+SAPBR=3,1,Contype,GPRS");
        break;

      case '2':
        while (!mySerial);
        mySerial.println("AT+SAPBR=3,1,APN,web.vodafone.de");
        break;

      case '3':
        while (!mySerial);
        mySerial.println("AT+SAPBR=3,1,USER,vodafone");
        break;

      case '4':
        while (!mySerial);
        mySerial.println("AT+SAPBR=3,1,PWD,vodafone");
        break;

      case '5':
        while (!mySerial);
        mySerial.println("AT+SAPBR=1,1");
        break;

      case '6':
        while (!mySerial);
        mySerial.println("AT+SAPBR=2,1");
        break;

    }
  }

  if (empfangen == true) {
    char* text = receiveBuffer();
    if (text != NULL) {
      if (strcmp(text, "OK") == 0 ) {
        Serial.println(text);
        senden = true;
        empfangen = false;
      } else if (strcmp(text, "ERROR") == 0 ) {
        Serial.println(text);
        senden = true;
      } else {
        Serial.println(text);
      }
    }   
  }
}

das wenn ich erst case 1 wo empfangen ja nicht aktiviert wird öffne und dann case 0 mir dann im Seriellen Monitor erst die Antwort für case 1 angezeigt wird

Was soll auch sonst passieren? Die serielle Schnittstelle ist Interrupt gesteuert und hat einen 64 Byte Eingangspuffer. Der liest alles im Hintergrund in diesen Puffer. Mit read() entfernst du lediglich die Daten aus dem Puffer.

Alles was das Gerät sendet muss auch aus dem Puffer entfernt werden. Du steuerst lediglich das Auslesen aus dem Puffer. Und nicht was in diesen automatisch eingelesen wird

Und eine Zustandsvariable für zwei (oder mehr) gegensätzliche Zustände reicht völlig aus:

enum ZKom: byte { SENDEN, EMPFANGEN };
ZKom zustandKom = SENDEN;

...


if (zustandKom == SENDEN)
{ 
   ...

    zustandKom = EMPFANGEN;
}

So steht es doch auch in dem Mikrocontroller.net Link

Oke danke wie kann ich es ohne Delay lösen das wenn ein OK empfangen wurde noch 10 sec gewartet wird bevor wir das Programm wieder in den zustand Senden geht?

Warum wird bei diesem script hier jeder Befehl problemlos ausgeführt bis zu dem Befehl "AT+SAPBR=3,1,USER,vodafone" und dann bleibt das Programm hänge?

#include <SoftwareSerial.h>
#define SIM800_TX 8
#define SIM800_RX 9
SoftwareSerial mySerial(SIM800_TX, SIM800_RX);


int start = 0;
int nummer = 0;
int on = 0;
enum ZKom : byte { SENDEN, EMPFANGEN };
ZKom zustandKom = SENDEN;


#define ZEILENTRENNZEICHEN 13
char* receiveBuffer() {
  static char lineBuffer[40];
  static byte counter = 0;
  char c;
  if (mySerial.available() == 0) return NULL;
  if (counter == 0) memset(lineBuffer, 0, sizeof(lineBuffer));
  c = mySerial.read();
  if (c == ZEILENTRENNZEICHEN)
  {
    counter = 0;
    return lineBuffer;
  }
  else if (c >= 32)
  {
    lineBuffer[counter] = c;
    if (counter < sizeof(lineBuffer) - 2) counter++;
  }
  return NULL;
}

void setup() {

  Serial.begin(9600);
  while (!Serial);

  mySerial.begin(9600);
  while (!mySerial);

}

void loop() {

  if (zustandKom == SENDEN) {
    if (start == 0) {
      nummer = Serial.read();
      if (nummer > -1 && nummer < 60) {
        start = 1;
        Serial.println(nummer);
      }
    } else {
      nummer++;
    }
    switch (nummer) {

      case 48:
        while (!mySerial);
        mySerial.println("AT");
        on = 0;
        zustandKom = EMPFANGEN;
        break;

      case 49:
        while (!mySerial);
        mySerial.println("AT+SAPBR=3,1,Contype,GPRS");
        zustandKom = EMPFANGEN;
        break;

      case 50:
        while (!mySerial);
        mySerial.println("AT+SAPBR=3,1,APN,web.vodafone.de");
        zustandKom = EMPFANGEN;
        break;

      case 51:
        while (!mySerial);
        mySerial.println("AT+SAPBR=3,1,USER,vodafone");
        zustandKom = EMPFANGEN;
        break;

      case 52:
        while (!mySerial);
        mySerial.println("AT+SAPBR=3,1,PWD,vodafone");
        zustandKom = EMPFANGEN;
        break;

      case 53:
        while (!mySerial);
        mySerial.println("AT+SAPBR=1,1");
        zustandKom = EMPFANGEN;
        break;

      case 54:
        while (!mySerial);
        mySerial.println("AT+SAPBR=2,1");
        zustandKom = EMPFANGEN;
        start = 0;
        break;

    }
  }

  if (zustandKom == EMPFANGEN) {
    on++;
    switch (on) {
      
      case '1':
        char* text = receiveBuffer();
        if (text != NULL) {
          if (strcmp(text, "OK") == 0 ) {
            Serial.println(text);
            zustandKom = SENDEN;
          } else if (strcmp(text, "ERROR") == 0 ) {
            Serial.println(text);
            zustandKom = SENDEN;
          } else {
            Serial.println(text);

          }
        }
        break;
      default:
        Serial.println("default");
        if (text != NULL) {
          if (strcmp(text, "OK") == 0 ) {
            Serial.println(text);
            zustandKom = SENDEN;
          } else if (strcmp(text, "ERROR") == 0 ) {
            Serial.println(text);
            zustandKom = SENDEN;
          } else {
            Serial.println(text);

          }
        }
    }
  }
}