My serial read from SIM900 GPRS shield is dropping characters

Dear Forumites

I'm trying to read a SIM phone book with a GPRS Sim900 shield on a Uno R3. As is evident from my coding, I'm new at this.

I want to use the Simcard to store messages and settings. When I query the phonebook, this is all I get:
AT+CPBF
+CPBF: 1,"0812036824",161,"Arthur-Master,E
"
+CPBF: 2,"0815713157",161,"Arthur-Spare,H*"
+CPBF: 6,"0812036824",161,"Troubleshoot,6*"
+CPBF: 7,"0819999999",161,"Buitekant-Str-16*"
+CPBF: 8,"0819999999",161,"PANIC-Send-HELP*"
+CPBF: 9,"0819999999",161,"Alarm-Solar-Pump1*"
+CPBF: 10,"0819999999",161,"99h99*"
OK
*PSNWID: "649","01", "MTC Namibia", 0, "MTC Namibia", 0
*PSUTTZ: 2024, 8, 25, 21, 19, 15, "+8", 1
DST: 1

When I use my short code below, I get what I expect:

*PANIC-Send-HELP

Arthur-Master
Buitekant-Str-16
0812036824
Alarm-Solar-Pump1

Arthur-Master
Buitekant-Str-16
0812036824
All ready to go.*

But when I put the same code snippet into a larger program, it all goes to pieces. The problem seems to be that it's not reading every character on serial read, especially is looks like the first few characters are dropped, instead of
1,"0812036824",161,"Arthur-Master,E*"
I get
6824",161,"Arthur-Master,E*

The rest of the reads are good, but without the first part I'm lost. Sometimes it works as expected. Mostly it doesn't.

Can someone please point me in the right direction? The function that's applicable is "_getSimCard()"

#include <SoftwareSerial.h>
#include <avr/wdt.h>

// Define I/O pins
#define SIM800_Rx      7  //SIM800 Serial port Rx
#define SIM800_Tx      8  //SIM800 serial port Tx
#define SIM800_Reset   2  //SIM800 Reset pin

// Create an instance of software serial port
SoftwareSerial SSerial(SIM800_Tx, SIM800_Rx); // RX, TX

boolean    Connected       = false;
int        i               = 0;
int        k               = 0;
char       PhBkAll[49];
char       PhBkName[10][18];
char       PhBkNum[6][11];
char       PhBkAct[6][2];
char       PhBkLine[3];
char       PhBkLineNum;
char       Serialread[] = "1";
const int  TIMEOUT_AT = 8000;
long int   Start      = millis();
char       SmsMsg[2][160];


//###############################################################################################
// SETUP

void setup() {

  // Start serial ports
  //-------------------
  SSerial.begin(4800);
  Serial.begin(4800);
  delay(2000);

  //Initialise SIM800
  InitGSM();

  // Get contacts on simcard
  _getSimCard();

  Serial.println("All ready to go.");
}

//###############################################################################################
// MAIN LOOP
void loop() {

}

//###############################################################################################
// Init GSM Module
void InitGSM() {

  Connected = false;

  // Setup I/Os
  pinMode(SIM800_Reset, OUTPUT);

  // Reboot SIM800
  digitalWrite(SIM800_Reset, LOW);
  delay(1000);
  digitalWrite(SIM800_Reset, HIGH);
  delay(5000);

  //Scan for GSM Module
  SSerial.print(F("AT\r\n"));
  WaitOK();

  // Set device to read SMS if available and print to serial
  SSerial.print(F("AT+CNMI=1,2,0,0,0\r\n"));
  WaitOK();

  //Delete old SMS
  SSerial.print(F("AT+CMGD=1,4\r\n"));
  WaitOK();

  // Set SMS mode to ASCII
  SSerial.print(F("AT+CMGF=1\r\n"));
  WaitOK();

  while (!SSerial.available()) {
  }
  delay(1000); //wait for time to be set
  while (SSerial.available()) {
    char c1 = char(SSerial.read());
    c1 = c1;
  }

}

//###############################################################################################
// Wait for the OK response from software serial port

void WaitOK() {
  char c1 = ' ';
  char c2 = ' ';
  boolean OK = false;
  while (!SSerial.available()) {
  }
  while (!OK) {
    while (SSerial.available()) {
      c1 = c2;
      c2 = char(SSerial.read());
      if ( (c1 == 'O') and (c2 == 'K') ) {
        OK = true;
      }
    }
  }
}

//###############################################################################################
// Read contacts from simcard


void _getSimCard() {

  int NewLn = 0;
  int SwitchTest = 0;
  char LineNum[2];
  int Type = 0;

  _clearArray(PhBkNum[0], 10);
  _clearArray(PhBkNum[1], 10);
  _clearArray(PhBkNum[2], 10);
  _clearArray(PhBkNum[3], 10);
  _clearArray(PhBkNum[4], 10);
  _clearArray(PhBkNum[5], 10);

  _clearArray(PhBkName[0], 18);
  _clearArray(PhBkName[1], 18);
  _clearArray(PhBkName[2], 18);
  _clearArray(PhBkName[3], 18);
  _clearArray(PhBkName[4], 18);
  _clearArray(PhBkName[5], 18);
  _clearArray(PhBkName[6], 18);
  _clearArray(PhBkName[7], 18);
  _clearArray(PhBkName[8], 18);
  _clearArray(PhBkName[9], 18);

  _clearArray(PhBkAct[0], 2);
  _clearArray(PhBkAct[1], 2);
  _clearArray(PhBkAct[2], 2);
  _clearArray(PhBkAct[3], 2);
  _clearArray(PhBkAct[4], 2);
  _clearArray(PhBkAct[5], 2);

  _clearArray(SmsMsg[0], 160);
  _clearArray(SmsMsg[1], 160);

  SSerial.println("AT+CPBF");
  Start = millis();
  k = 0;

  while ((millis() - Start) < TIMEOUT_AT) {

    while (SSerial.available()) {

      Serialread[0] = char(SSerial.read());
      Serialread[1] = '\0';
      if (k <= 49) {
        strcat (PhBkAll, Serialread);
      }

      if ( k > 4 ) {
        if (PhBkAll [k - 5] == 'C' && PhBkAll [k - 4] == 'P' && PhBkAll [k - 3] == 'B' && PhBkAll [k - 2] == 'F' && PhBkAll [k - 1] == ':' && PhBkAll [k] == ' ') {
          NewLn = 1;
          _clearArray(PhBkAll, 49);
          k = -1;
        }
      }

      if (Serialread[0] == '*' && NewLn == 1 ) {

        //Serial.print ("NewString: ");
        //Serial.println (PhBkAll);

        if ( PhBkAll[1] != ',' ) {                                  
          strncpy ( PhBkLine, &PhBkAll[0], 1);
          strncat ( PhBkLine, &PhBkAll[1], 1);
        }
        else {
          strncpy ( PhBkLine, &PhBkAll[0], 1);
        }

        SwitchTest = atoi(PhBkLine);
        SwitchTest--;

        switch (SwitchTest) {
          case 0 ... 5: {
              strncpy ( PhBkNum[SwitchTest], &PhBkAll[3], 10);
              strncpy ( PhBkName[SwitchTest], &PhBkAll[20], k - 22);
              strncpy ( PhBkAct[SwitchTest], &PhBkAll[k - 1], 1);
              break;
            }
          case 6 ... 8: {
              strncpy ( PhBkName[SwitchTest], &PhBkAll[20], k - 20);
              break;
            }
          case 9: {
              strncpy ( PhBkName[SwitchTest], &PhBkAll[21], k - 21);
              break;
            }
          default: {
              break;
            }
        }

        _clearArray(PhBkLine, 3);
        _clearArray(PhBkAll, 49);
        NewLn = 0;
        k = -1;
      }
      k++;
    }
  }

  switch (Type) {
    
    case 1: {                                                   
      for (int m = 0; m != 5 ; m++) {
        itoa(m+1, LineNum, 10);
        if (strlen(PhBkName[m]) != 0) {
          if (m==0) {
            strcpy(SmsMsg[0], LineNum);  
          }
          else {  
            strcat(SmsMsg[0], LineNum);
          }
          strcat(SmsMsg[0], ":");
          strcat(SmsMsg[0], PhBkAct[m]);
          strcat(SmsMsg[0], ",");
          strcat(SmsMsg[0], PhBkName[m]);
          strcat(SmsMsg[0], ",");
          strcat(SmsMsg[0], PhBkNum[m]);
          if ( m != 5 ) {
            SmsMsg[0][strlen(SmsMsg[0])] = '\n';
            SmsMsg[0][strlen(SmsMsg[0])+1] = '\0';
          }
        }
      }  
      break;
    }

    case 2: {                                                         //This is a sms to master for all the text settings
      for (int m = 5; m != 10 ; m++) {
        itoa(m+1, LineNum, 10);
        if (strlen(PhBkName[m]) != 0) {
          if (m==5) {
            strcpy(SmsMsg[1], LineNum);
            strcat(SmsMsg[1], ":"); 
            strcat(SmsMsg[1], PhBkAct[5]);
            strcat(SmsMsg[1], ",");
            strcat(SmsMsg[1], PhBkName[5]);
            strcat(SmsMsg[1], ",");
            strcat(SmsMsg[1], PhBkNum[5]);
          }
          else {  
            strcat(SmsMsg[1], LineNum);
            strcat(SmsMsg[1], ":");
            strcat(SmsMsg[1], PhBkName[m]);
          }
          if ( m != 9 ) {
            SmsMsg[1][strlen(SmsMsg[1])] = '\n';
            SmsMsg[1][strlen(SmsMsg[1])+1] = '\0';
          }
        }
      }
      //Serial.println(SmsMsg[1]);
      break;
    }
    default:{
      strcpy(SmsMsg[0], PhBkName[7]);                                        //Panic Message
      SmsMsg[0][strlen(SmsMsg[0])] = '\n';
      SmsMsg[0][strlen(SmsMsg[0])] = '\n';
      SmsMsg[0][strlen(SmsMsg[0])+1] = '\0';
      strcat(SmsMsg[0], PhBkName[0]);
      SmsMsg[0][strlen(SmsMsg[0])] = '\n';
      SmsMsg[0][strlen(SmsMsg[0])+1] = '\0';
      strcat(SmsMsg[0], PhBkName[6]);
      SmsMsg[0][strlen(SmsMsg[0])] = '\n';
      SmsMsg[0][strlen(SmsMsg[0])+1] = '\0';
      strcat(SmsMsg[0], PhBkNum[0]);  
      Serial.println(SmsMsg[0]); 

      strcpy(SmsMsg[1], PhBkName[8]);                                        //Alarm Message
      SmsMsg[1][strlen(SmsMsg[1])] = '\n';
      SmsMsg[1][strlen(SmsMsg[1])] = '\n';
      SmsMsg[1][strlen(SmsMsg[1])+1] = '\0';
      strcat(SmsMsg[1], PhBkName[0]);
      SmsMsg[1][strlen(SmsMsg[1])] = '\n';
      SmsMsg[1][strlen(SmsMsg[1])+1] = '\0';
      strcat(SmsMsg[1], PhBkName[6]);
      SmsMsg[1][strlen(SmsMsg[1])] = '\n';
      SmsMsg[1][strlen(SmsMsg[1])+1] = '\0';
      strcat(SmsMsg[1], PhBkNum[0]);  
      Serial.println(SmsMsg[1]); 
      break;  
    }
  }
}  

//###############################################################################################
//Clear entire character array
void _clearArray(char* array, int size) {
  for (int p = 0; p < size; p++) {
    array[p] = '\0';  // Set each element to the null character
  }
}

Best regards

NewSoftSerial instead of SoftwareSerial may be the answer, give it a try.

@tigger_na
sorry, but your code is not good. It is too long and complex for so simple task and looks as completely nonsense for me.

For example, your WaitOK() function doesn't do that could be expected by its name - it doesn't wait to "ok" string. The function stops on any pause in the Serial output regardless either the Ok answer was received or not - and continue the main program even not informs the user about its result.

Thank you for trying. This according to the NewSoftSerial | Arduiniana page:

The latest version of NewSoftSerial is available here: NewSoftSerial12.zip. Note: don’t download this if you have Arduino 1.0 or later. As of 1.0, NewSoftSerial is included in the Arduino core (named SoftwareSerial).

Does your arduino has an hardware serial?
Use that for a similar ( important ) task.
If it has not use a different board with an hardware serial ( it won't let you down even in complex sketches )

1 Like

The code is hard to read, sorry.

BTW what I would try:

  1. create a class FakeSerial whcih implements 2 mehods: available() and read(). This class will simulate a SSerial by providing letters from a big string (char []) of your example output on each FakeSerial::read(). The FakeSerial::available() should return "true" if there is something left.

If it will work as expected, then the problem is in Serial interface. If not - then it is a bug in your getSim code.

  1. Adding a debug printfs at every branch in your code (at least at "if" statements) also helps alot.

  2. Try to read a whole message at once in a buffer and then work with this buffer. At least you can check if this buffer contains all the expected characters before processing

1 Like

I know my code isn't good. If I had a penny for every time I heard or realized that my work isn't good, I'd be a millionaire. Well, I got a dollar for every time I did improve on it, so I'm not starving.

I replaced the icky WaitOK function with

//###################################################################
// Wait for sepcified response from software serial port
// 
//###################################################################
void WaitOK(String WaitFor) {
  
  unsigned long startTime = millis();
  okReceived = false;
  //Serial.print("Waiting for ");
  //Serial.println(WaitFor);
    
  while (millis() - startTime < 5000) {  // Wait up to 5 seconds
    if (SSerial.available()) {
      String response = SSerial.readStringUntil('\n');
      //Serial.print("Sim800L response: ");
      //Serial.println(response);
      
      if (response.indexOf(WaitFor) != -1) {
        okReceived = true;
        //Serial.println("OK now received");
        break;
      }
    }
  }

  //if (!okReceived) {
  //  Serial.println("OK not received within timeout.");
  //}
}

In my bigger sketch I'm at 74% of SRAM, so I'm using as few Serial.println's as possible, which somewhat limits my possible interaction interaction.

I replaced the GPRS shield with the SIM900 with a SIM800L, and now the code works as expected every time. Maybe it'll even stay stable.

Your new function is better than the old one, but the problems are almost the same. The function can finish because the required response has arrived or by timeout. However, your function is again declared as a void - that is, it does not inform the main program whether "OK" has been received or not.
If you do not analyze the result - why do you need this function at all? - you can simply add a delay to the code for the time of response arrival, as bad programmers do.

Correct operation of the modem is not just sending the necessary commands - but also analyzing the responses to them and making decisions in accordance with the response. If you received OK - you can give a new command, and if not - most likely there is an error in the modem. Perhaps you need to repeat the previous command. Or give another one altogether

However, your function is again declared as a void - that is, it does not inform the main program whether "OK" has been received or not.
[/quote]

Thank you. I come from a (also shaky) VB background, so C++ is a bit of dark magic for me. I declared the "okReceived" boolean globally, so I can deal with corrective action in the setup and main loops, like rebooting the SIM800L when needed. It works, that's all I need for the moment.

My original problem is back though. When booting up the exended program, of which this is but a snippet, the SIM phonebook now reads perfectly. But: When the SIM800l receives a SMS, it may have to edit the phonebook. If it does get edited, the user needs a confirmation SMS to see whether his changes are correct. This being stored on the phonebook means I have to query it again, and that time around it again starts disregarding the first few characters of the first phonebook entry. Might be a memory leak, for all I know. Or the fact that I'm using 3/4 of dynamic memory already. Using the hardware serial is not an option for now, I need the serial monitor to see what I'm doing, and I can't get a different board with two serial ports so fast.

(A bit more background, seeing that this solution could be a lifesaver where I live, I just want to get the basic functionality right, and then I intend passing it on to someone who I can pay to streamline the code. The rest of my life is way too short to try and become a perfect, even usable, C++ programmer.)

Use the print "F" macro it will save you a lot of RAM. example serial.print("Hello World"); to serial.print(F("Hello World")); saves about 7 bytes of RAM.Happy Printing

1 Like

I may have found the problem. I should have cleared the PhBkAll array before using it. Up to now it's holding up.

  SSerial.println("AT+CPBF");
  Start = millis();
  k = 0;
  _clearArray(PhBkAll, 49);

  while ((millis() - Start) < TIMEOUT_AT) {