Probleme mit Serieller Verbindung / AT-Befehle GSM

Hallo,

Ich bin absolut neu hier im Forum und auch erst seit kurzem dabei mit Arduino umzugehen. Ich besitze und beziehe mich dabei auf einen Arduino Mega 2560.

Ich habe folgendes Problem:

Auf diversen Seiten im Web (z.B. SUPER IR ANDROID APPLICATIONS - ARDUINO PROJECTS) habe ich herausgefunden, dass man ältere Handys quasi als GSM-Shield nutzen und über die serielle Verbindung mit dem AT-Befehlssatz steuern kann (Anrufe tätigen, SMS lesen, SMS senden usw.). Ich habe mir daraufhin ein Sony Ericsson K700i mitsamt Datenkabel besorgt und die Verbindung hergestellt (TX Handy -> RX1 Arduino, RX Handy -> TX1 Arduino, GND -> GND). Wohlgemerkt beherscht das SE K700i nur den PDU-Modus, was mich jetzt aber weniger stört. Hierfür gibt es genügend Converter (z.B. http://twit88.com/home/utility/sms-pdu-encode-decode).
Soweit so gut, das funktioniert auch einwandfrei. Sende ich dem Handy Befehle bekomme ich Antworten, ich kann Anrufe tätigen und SMS versenden. Das Problem ist also nicht die Verbindung.
Mein Problem besteht jetzt vielmehr darin empfangene SMS auf dem Handy auszulesen. Anwendungszweck z.B. ich sende eine SMS mit "led an" an das Handy, Arduino liest dies aus und schaltet die LED an.

Ich habe dazu folgenden Sketch geschrieben (es geht mir erstmal nur darum die SMS überhaupt auszulesen):

void setup(){
  
Serial.begin(9600);
Serial1.begin(9600);
delay(3000); 
Serial1.println("AT+CPMS=\"ME\",\"ME\",\"ME\""); //Auf internen Telefonspeicher umschalten
delay(3000);
}

void loop(){

 
Serial1.println("AT+CMGL=4"); //Alle Nachrichten auslesen
Serial.println("Befehl gesendet: AT+CMGL=4");

Serial.println("Antwort:");

while(Serial1.available() > 0){
Serial.write(Serial1.read());
}

Serial.println("");
Serial.println("");
delay(5000); 

}

(das K700i ist beim Arduino Mega an Serial1 angeschlossen!)

Auf dem Serial Monitor erhalte ich nun folgendes (Ausschnitt):

Befehl gesendet: AT+CMGL=4
Antwort:
+CMGL=4
+CMGL: 1,1,,26
0791947106005034240D91947177104149F30

(Handynummer verfälscht)

eigentlich müsste ich aber das erhalten:

Befehl gesendet: AT+CMGL=4
Antwort:
+CMGL=4
+CMGL: 1,1,,26
0791947106005034240D91947177104149F300003170327135628006EC3219147603

Ich habe nun schon mehrere Stunden herumprobiert und es scheint mir als ob Arduino den Output des Handys irgendwie "verschluckt" oder abschneidet, denn wenn ich folgenden Code (einfach ohne das delay(5000)) verwende:

void setup(){
  
Serial.begin(9600);
Serial1.begin(9600);
delay(3000);
Serial1.println("AT+CPMS=\"ME\",\"ME\",\"ME\"");
delay(3000);
}

void loop(){

 
Serial1.println("AT+CMGL=4");
Serial.println("Befehl gesendet: AT+CMGL=4");

Serial.println("Antwort:");

while(Serial1.available() > 0){
Serial.write(Serial1.read());
}

Serial.println("");
Serial.println("");

}

erhalte ich (Ausschnitt):

Befehl gesendet: AT+CMGL=4
Antwort:
AT
Befehl gesendet: AT+CMGL=4
Antwort:
Befehl gesendet: AT+CMGL=4
Antwort:
+CMGL=4
+CMGL: 1,1,,26
0791947106005034240D91947177104149F300003170327135628006EC3219147603
OK
Befehl gesendet: AT+CMGL=4
Antwort:
AT
Befehl gesendet: AT+CMGL=4
Antwort:
+CMGL=4
+CMGL: 1,1,,26
0791947106005034240D91947177104149F300003170327135628006EC3219147603
OK
Befehl gesendet: AT+CMGL=4
Antwort:
AT
Befehl gesendet: AT+CMGL=4
Antwort:
Befehl gesendet: AT+CMGL=4
Antwort:
+CMGL=4
+CMGL: 1,1,,26
0791947106005034240D91947177104149F300003170327135628006EC3219147603

Hier taucht also auf einmal der zuvor vermeintlich abgeschnittene Teil auf, allerdings scheint sich Arduino hier zu überschlagen oder dergleichen weil der Output auf dem Serial Monitor nicht immer gleich ist....es scheint als ob Arduino hier einfach immer einen willkürlichen Teil aufschnappt und dann ausgibt.

Wie kann ich es am nun am besten lösen dass Arduino mir einfach den kompletten Output des Handys ausgibt ohne dabei etwas abzuschneiden oder durcheinander zu werfen? Am besten wäre es wenn ich den Output des Handys in einer Variable speichern könnte um diese später mit den "Soll-PDU"-Daten zu vergleichen um dann die LED zu steuern...

Das Problem taucht auch nur beim SMS auslesen auf. Ich vermute das liegt daran dass der Output des Handys hierbei wesentlich länger ist als bei all den anderen Befehlen. Sende ich dem Handy andere Befehle wie (ATD12345678, AT, AT+CBC) funktioniert das einwandfrei...ich bekomme einen gleichbleibenden kompletten Output des Handys angezeigt.

Der Beispielcode (SUPER IR ANDROID APPLICATIONS - ARDUINO PROJECTS) von oben genannter Website gibt mir den gleichen Output wie mein erster Sketch, also unvollständig.

Ich habe diesbezüglich noch keine vergleichbaren Probleme gefunden und hoffe dass mir hier geholfen werden kann. Ich probiere nun wirklich schon Ewigkeiten herum und komme nicht voran. Ebenso hoffe ich mal dass mein erster Post so in Ordnung war :wink:

Besten Dank schonmal.

PhiRo:
Ich habe diesbezüglich noch keine vergleichbaren Probleme gefunden und hoffe dass mir hier geholfen werden kann. Ich probiere nun wirklich schon Ewigkeiten herum und komme nicht voran. Ebenso hoffe ich mal dass mein erster Post so in Ordnung war :wink:

Willkommen im Forum!

Ich reiße die Problemfelder in Deinem Sketch mal an:

  1. Befehle senden und verarbeiten
    Ein Handy verarbeitet keine Befehle in Nullzeit und es kommt in Nullzeit auch keine Antwort. Du mußt daher nach dem Absenden eines Befehls abwarten, dass das Handy den Befehl verarbeitet und seine Antwort zurücksendet. Erst dann kannst Du im Eingangspuffer etwas auslesen.

  2. Du kannst bei einem einmaligen Auslesen des Eingangspuffers maximal 63 Zeichen auf einmal auslesen, weil mehr Zeichen nicht in den Eingangspuffer des Arduino hineinpassen. Wenn mehr als 63 Zeichen empfangen werden sollen, muss das Einlesen immer in mehreren Durchgängen erfolgen.

  3. Serielle Schnittstellen sind langsam, bei 9600 Baud geht 1 Zeichen pro Millisekunde über die Leitung. Und trotzdem: Der Eingangspuffer einer seriellen Schnittstelle im Arduino ist 63 Zeichen groß und kann daher innerhalb von 63 Millisekunden volllaufen. Danach eintreffende Zeichen gehen verloren. D.h. Du darfst nirgendwo die Programmausführung für mehr als 63 Millisekunden anhalten (bei 9600 Baudrate). Eine Blockierung von 5000 Millisekunden für ein solches Programm ist ein völliges No-Go, das geht gar nicht!

Der erste Schritt wäre nun mal, den Sketch so zu ändern, dass

  • kein delay im Programmcode vorkommt und
  • trotzdem nicht ständig neue Befehle mit der Loop-Drehzahl gesendet werden

Probier das mal, dieser Code sendet alle 10 Sekunden einen Befehl (das erst mal 10 Sekunden nach Programmstart) und liest zwischendurch ständig die eintreffenden Zeichen aus:

void setup(){
  
Serial.begin(9600);
Serial1.begin(9600);
delay(3000); 
Serial1.println("AT+CPMS=\"ME\",\"ME\",\"ME\""); //Auf internen Telefonspeicher umschalten
delay(3000);
}

void SendeAlle10Sekunden()
{
  static unsigned long lastMillis;
  if (millis()-lastMillis>10000)
  {
    lastMillis=millis(); 
    Serial1.println("AT+CMGL=4"); //Alle Nachrichten auslesen
    Serial.println("Befehl gesendet: AT+CMGL=4");
    Serial.println("Antwort:");
  }
}

void loop()
{
  SendeAlle10Sekunden();
  while(Serial1.available() > 0)
  {
    Serial.write(Serial1.read());
  }
}

Und wenn das hinhaut, kannst Du im nächsten Schritt die eintreffenden Zeichen in einen eigenen Eingangspuffer als Variable zur weiteren Verarbeitung einlesen.

OK, das dachte ich mir schon irgendwie dass da eine Zeichenbegrenzung (63) mit im Spiel ist.

Mit dem Code von dir klappt es jetzt aber ohne abschneiden und/oder verschlucken! Vielen Dank dafür!

Zum speichern in einer Variable habe ich jetzt aber auch noch eine Frage...

Wie kann ich am besten von dem Output des Handys (bzw. vom Serial Monitor):

+CMGL=4
+CMGL: 1,1,,26
0791947106005034240D91947177104149F300003170327135628006EC3219147603
OK

nur die dritte Zeile, also den "PDU-Code" in eine Variable packen bzw. wie kann man die anderen ausnehmen?

Danke!

//EDIT:

Ich habe jetzt mit folgendem Code das ganze gleich als Variable gespeichert (habe mich dabei an SUPER IR ANDROID APPLICATIONS - ARDUINO PROJECTS orientiert):

String readString;
char c;

void setup(){
  
Serial.begin(9600);
Serial1.begin(9600);
delay(3000); 
Serial1.println("AT+CPMS=\"ME\",\"ME\",\"ME\""); //Auf internen Telefonspeicher umschalten
delay(3000);
}

void SendeAlle10Sekunden()
{
  static unsigned long lastMillis;
  if (millis()-lastMillis > 5000)
  {
    lastMillis=millis(); 
    Serial1.println("AT+CMGL=4"); //Alle Nachrichten auslesen
    Serial.println("");
    Serial.println("");
    Serial.println("Befehl gesendet: AT+CMGL=4");
    Serial.println("Antwort:");
  }
}

void loop()
{
  
  readString=0;  
  SendeAlle10Sekunden();
  
  while(Serial1.available() > 0)
  {
    c=Serial1.read();
    readString+=c;
  }
  
Serial.print(readString);

}

Jetzt weiß ich nur nicht wie ich den "PDU-Code" allein betrachten bzw. extrahieren kann...

  1. Du kannst bei einem einmaligen Auslesen des Eingangspuffers maximal 63 Zeichen auf einmal auslesen, weil mehr Zeichen nicht in den Eingangspuffer des Arduino hineinpassen. Wenn mehr als 63 Zeichen empfangen werden sollen, muss das Einlesen immer in mehreren Durchgängen erfolgen.

Das stimmt nicht ganz, denn der Puffer wird im Hintergrund mittels Interrupt wieder gefüllt. Man kann also immer lesen, solange noch Zeichen im Puffer sind. Wichtiger ist, dass der Puffer früh genug ausgelesen wird, sonst gehen Zeichen verloren, man sollte also nie warten, bis der Puffer voll ist (wie jurs weiter unten ausführt), sondern so oft wie möglich auslesen und ggfs. in einen Programmspeicher abspeichern, bis man es weiterverwertet.

PhiRo:
Zum speichern in einer Variable habe ich jetzt aber auch noch eine Frage…

Wie kann ich am besten von dem Output des Handys (bzw. vom Serial Monitor):

nur die dritte Zeile, also den “PDU-Code” in eine Variable packen bzw. wie kann man die anderen ausnehmen? Und welchen Variablentyp verwende ich dafür am besten? Ich vermute char?

Indem Du erstmal jede empfangene Zeilen in eine Variable einliest und beim Einlesen der Zeileninhalte auch die Anzahl der empfangenen Zeilen mitzählst, z.B. (teste mal):

void setup(){
  
Serial.begin(9600);
Serial1.begin(9600);
delay(3000); 
Serial1.println("AT+CPMS=\"ME\",\"ME\",\"ME\""); //Auf internen Telefonspeicher umschalten
delay(3000);
}

boolean SendeAlle10Sekunden()
// Rückgabewert 
// false: Es wurde nichts gesendet
// true: Es wurde nach Zeitablauf ein Befehl gesendet
{
  static unsigned long lastMillis;
  if (millis()-lastMillis>10000)
  {
    lastMillis=millis(); 
    Serial1.println("AT+CMGL=4"); //Alle Nachrichten auslesen
    Serial.println("Befehl gesendet: AT+CMGL=4");
    Serial.println("Antwort:");
    return true;
  }
  else return false;
}


char linebuf[81];
char charcounter=0;
byte linecounter=1;

void loop()
{
  char c;
  if (SendeAlle10Sekunden())
  {
    memset(linebuf,0,sizeof(linebuf)); // Zeilenpuffer löschen
    charcounter=0; // Zeichenzähler auf 0
    linecounter=1; // Zeilenzähler auf 1
  }
  while(Serial1.available() > 0)
  {
    c=Serial1.read(); // Zeichen auslesen
    if (c>=32) // ASCII-Code Leerzeichen oder höher
    {
      linebuf[charcounter]=c;  // Zeichen in den Zeilenpuffer einfügen
      if (charcounter<sizeof(linebuf)-1) charcounter++;
    }
    else if (c==13) // CR Zeilenende ("Carriage Return")
    { // Jetzt ist eine Zeile komplett empfangen worden
      Serial.print("Zeile ");
      Serial.print(linecounter); // Zeilennummer
      Serial.print(": ");
      Serial.println(linebuf);  // Inhalt der empfangenen Zeile
      memset(linebuf,0,sizeof(linebuf)); // Zeilenpuffer löschen
      charcounter=0; // Zeichenzähler auf 0
      linecounter++; // Zeilenzähler erhöhen
    }
    else ; // Sonstige Steuerzeichen ignorieren
  }
}

Die geeignete Datenstruktur zum Speichern von Zeichenketten ist bei C immer ein Char-Array. Ich habe mal eins von 81 Zeichen Länge reserviert, das würde für einen String von max. 80 Zeichen Zeilenlänge ausreichen.

Und wenn Du eine bestimmte Zeile haben möchtest, dann machst Du statt:
Serial.print("Zeile “);
Serial.print(linecounter); // Zeilennummer
Serial.print(”: ");
Serial.println(linebuf); // Inhalt der empfangenen Zeile
eben sowas wie
if (linecounter==3) … verarbeite den Inhalt von linebuf weiter

OK, das sieht dann so aus:

Befehl gesendet: AT+CMGL=4
Antwort:
Zeile 1: AT+CMGL=4
Zeile 2:
Zeile 3: +CMGL: 1,1,,26
Zeile 4: 0791947106005034240D91947177104149F300003170327135628006EC3219147603
Zeile 5:
Zeile 6: OK

Ich denke ich bekomme das jetzt hin...falls nicht melde ich mich nochmal.

Vielen Dank! Ich hätte ehrlich gesagt nicht gedacht, dass mir hier so schnell und gut geholfen wird! :slight_smile: