Help with Programming

Hi masters,

I am one of those who have just stopped using Strings and learning the beauty of array of chars. But I am a beginner.

My problem: Code freezes at line 88 at the second loop. I am sure I am running out of RAM memory because of my bad implementation.

Arduino MKR 1500 + MKR GPS.

These are an example of the values that I get from GPS coordinates and IMEI variables:

ID: 352753095950760
latitude: -31.8883781
longitude: 115.7977524
altitude: 17.40
speed: 0.11
time:1582812078

Code:

#include <TimeLib.h>
#include <MKRNB.h>
#include <Arduino_MKRGPS.h>

float latitude;
float longitude;
float altitude;
float speed;
byte  satellites;

char IMEI[17];

// Libraries Innitialization
NBClient client; 
NB nbAccess;
NBModem modem;

void setup() {

  //initialize //Serial communications and wait for port to open:
  Serial.begin(9600);
  delay(3000);

  //Start modem test
  if (modem.begin()) {
    //Get modem IMEI
    modem.getIMEI().toCharArray(IMEI, 17);
    IMEI[17] = '\0';
    Serial.print(F("IMEI IS: "));Serial.println(IMEI);
  }else{
    Serial.println(F("ERROR Modem."));
  }
  

  if (!GPS.begin()) {
    Serial.println(F("GPS failed."));
    while (1);
  }
  Serial.println(F("GPS is ON."));

}

void loop() {

    
    if (GPS.available()) { 
      latitude   = GPS.latitude();
      longitude  = GPS.longitude();
      altitude   = GPS.altitude();
      speed      = GPS.speed();
      satellites = GPS.satellites(); 
    }
                    
    // print GPS values
    Serial.println("");
    Serial.print(F("Location: ")); Serial.print(latitude, 7); Serial.print(", "); Serial.println(longitude, 7);
    Serial.print(F("Altitude: ")); Serial.print(altitude);Serial.println("m");
    Serial.print(F("Ground speed: ")); Serial.print(speed);Serial.println(" km/h");
    //Serial.print(F("Satellites: "));Serial.println(satellites);

      if(latitude != 0.0000000){   

        Serial.println("");

        unsigned long timeNow = GPS.getTime();
        Serial.print(F("Epoch time: "));Serial.println(timeNow);

        char postData [160];
        char latitudeConverted[13];
        char longitudeConverted[13];
        char altitudeConverted[8];
        char speedConverted[7];
        char timeNowConverted[11];

        dtostrf(latitude, 12, 7, latitudeConverted);
        dtostrf(longitude, 12, 7, longitudeConverted);
        dtostrf(altitude, 7, 2, altitudeConverted);
        dtostrf(speed, 6, 2, speedConverted);
        dtostrf(timeNow, 10, 0, timeNowConverted);
        
        strcat(postData, "{\"ID\": \"");
        strcat(postData, IMEI);
        strcat(postData, "\",\"lat\": \"");
        strcat(postData, latitudeConverted);
        strcat(postData, "\",\"long\": \"");
        strcat(postData, longitudeConverted);
        strcat(postData, "\",\"alt\": \"");
        strcat(postData, altitudeConverted);
        strcat(postData, "\",\"speed\": \"");
        strcat(postData, speedConverted);
        strcat(postData, "\",\"time\": \"");
        strcat(postData, timeNowConverted);       
        strcat(postData, "\"}");
       
        Serial.print(F("POST DATA: ")); Serial.print(postData);   
        postData [0] = '\0';
      }
}

//Function to convert long into char
char * dtostrf(double number, signed char width, unsigned char prec, char *s) {

    if(isnan(number)) {
        strcpy(s, "nan");
        return s;
    }
    if(isinf(number)) {
        strcpy(s, "inf");
        return s;
    }

    if(number > 4294967040.0 || number < -4294967040.0) {
        strcpy(s, "ovf");
        return s;
    }
    char* out = s;
    // Handle negative numbers
    if(number < 0.0) {
        *out = '-';
        ++out;
        number = -number;
    }

    // Round correctly so that print(1.999, 2) prints as "2.00"
    double rounding = 0.5;
    for(uint8_t i = 0; i < prec; ++i)
        rounding /= 10.0;

    number += rounding;

    // Extract the integer part of the number and print it
    unsigned long int_part = (unsigned long) number;
    double remainder = number - (double) int_part;
    out += sprintf(out, "%d", int_part);

    // Print the decimal point, but only if there are digits beyond
    if(prec > 0) {
        *out = '.';
        ++out;
    }

    while(prec-- > 0) {
        remainder *= 10.0;
        if((int)remainder == 0){
                *out = '0';
                 ++out;
        }
    }
    sprintf(out, "%d", (int) remainder);

    return s;
}

What am I doing wrong? How should I clean postData so that the variable doesn’t go beyond its limit?

Thank you so much in advance.

I found the issue. Yes, it was a silly one. Posting here the fixed code for reference:

#include <TimeLib.h>
#include <MKRNB.h>
#include <Arduino_MKRGPS.h>

float latitude;
float longitude;
float altitude;
float speed;
byte  satellites;

char postData [160];
char latitudeConverted[13];
char longitudeConverted[13];
char altitudeConverted[8];
char speedConverted[7];
char timeNowConverted[11];
char IMEI[17];

// Libraries Innitialization
NBClient client; 
NB nbAccess;
NBModem modem;

void setup() {

  //initialize //Serial communications and wait for port to open:
  Serial.begin(9600);
  delay(3000);

  //Start modem test (reset and check response)
  if (modem.begin()) {
    //Get modem IMEI
    modem.getIMEI().toCharArray(IMEI, 17);
    IMEI[17] = '\0';
    Serial.print(F("IMEI IS: "));Serial.println(IMEI);
  }else{
    Serial.println(F("ERROR Modem."));
  }
  

  if (!GPS.begin()) {
    Serial.println(F("GPS failed."));
    while (1);
  }
  Serial.println(F("GPS is ON."));

}

void loop() {

    
    if (GPS.available()) { 
      latitude   = GPS.latitude();
      longitude  = GPS.longitude();
      altitude   = GPS.altitude();
      speed      = GPS.speed();
      satellites = GPS.satellites(); 
    }
                    
    // print GPS values
    Serial.println("");
    Serial.print(F("Location: ")); Serial.print(latitude, 7); Serial.print(", "); Serial.println(longitude, 7);
    Serial.print(F("Altitude: ")); Serial.print(altitude);Serial.println("m");
    Serial.print(F("Ground speed: ")); Serial.print(speed);Serial.println(" km/h");
    //Serial.print(F("Satellites: "));Serial.println(satellites);

      if(latitude != 0.0000000){   

        Serial.println("");

        unsigned long timeNow = GPS.getTime();
        Serial.print(F("Epoch time: "));Serial.println(timeNow);

        
        dtostrf(latitude, 12, 7, latitudeConverted);
        dtostrf(longitude, 12, 7, longitudeConverted);
        dtostrf(altitude, 7, 2, altitudeConverted);
        dtostrf(speed, 6, 2, speedConverted);
        dtostrf(timeNow, 10, 0, timeNowConverted);
        
        strcat(postData, "{\"ID\": \"");
        strcat(postData, IMEI);
        strcat(postData, "\",\"lat\": \"");
        strcat(postData, latitudeConverted);
        strcat(postData, "\",\"long\": \"");
        strcat(postData, longitudeConverted);
        strcat(postData, "\",\"alt\": \"");
        strcat(postData, altitudeConverted);
        strcat(postData, "\",\"speed\": \"");
        strcat(postData, speedConverted);
        strcat(postData, "\",\"time\": \"");
        strcat(postData, timeNowConverted);       
        strcat(postData, "\"}");
       
        Serial.print(F("POST DATA: ")); Serial.print(postData);
        postData [0] = '\0';

      
      }
}

char * dtostrf(double number, signed char width, unsigned char prec, char *s) {

    if(isnan(number)) {
        strcpy(s, "nan");
        return s;
    }
    if(isinf(number)) {
        strcpy(s, "inf");
        return s;
    }

    if(number > 4294967040.0 || number < -4294967040.0) {
        strcpy(s, "ovf");
        return s;
    }
    char* out = s;
    // Handle negative numbers
    if(number < 0.0) {
        *out = '-';
        ++out;
        number = -number;
    }

    // Round correctly so that print(1.999, 2) prints as "2.00"
    double rounding = 0.5;
    for(uint8_t i = 0; i < prec; ++i)
        rounding /= 10.0;

    number += rounding;

    // Extract the integer part of the number and print it
    unsigned long int_part = (unsigned long) number;
    double remainder = number - (double) int_part;
    out += sprintf(out, "%d", int_part);

    // Print the decimal point, but only if there are digits beyond
    if(prec > 0) {
        *out = '.';
        ++out;
    }

    while(prec-- > 0) {
        remainder *= 10.0;
        if((int)remainder == 0){
                *out = '0';
                 ++out;
        }
    }
    sprintf(out, "%d", (int) remainder);

    return s;
}

Administrator please fell free to delete.

not sure you understand why making postData global fixed you're problem

strcat appends a string to the end of an existing string. if a string array is uninitialized, there's no guarantee it's first value is zero (NULL)

It's not obvious to me that defining a variable globally insures that its value is zero.

when you defined postData [] w/in the function the way you did, it's value was not initialized. you can initialize the value of a array or structure as follows

     char postData [160] = {};

this way, strcat will append a new string starting at the beginning of postData

It’s not obvious to me that defining a variable globally insures that its value is zero

crt0 ensures any global or static variable is zeroed, if it is not explicitly initialised, or it is not given a non-zero value, before main() runs.

i know that it use to be true for early editions of Unix. A user's memory was zeroed out because it may have been used by root and may contain passwords.

but at some point in time (mid 80s, IBM and DEC), that was no longer true and felt to be too time consuming. You could no longer assume global variables were initialized to zero.

at least this page suggests the .bss section is initialized to zero.

gcjr: i know that it use to be true for early editions of Unix.

it's part of the C++ specification since C++14 if I remember well.

Another option is to change "strcat()" to "strcpy()" on the first line. The "strcpy()" (String Copy) ignores whatever was in the destination string and copies over it. The "strcat()" (String Concatenate) looks for the end of the destination string (first null/0 character) and adds the new string there.

The Arduino way to do the same thing is:

    Serial.print(F("POST DATA: "));
    Serial.print(F("{\"ID\": \""));
    Serial.print(IMEI);
    Serial.print(F("\",\"lat\": \""));
    Serial.print(latitude, 7);
    Serial.print(F("\",\"long\": \""));
    Serial.print(longitude, 7);
    Serial.print(F("\",\"alt\": \""));
    Serial.print(altitude, 2);
    Serial.print(F("\",\"speed\": \""));
    Serial.print(speed, 2);
    Serial.print(F("\",\"time\": \""));
    Serial.print( timeNow, 0);
    Serial.print(F("\"}"));

J-M-L:
it’s part of the C++ specification since C++14 if I remember well.

so it took nearly ~half a century (42 years) :slight_smile:

Zeroing the .bss segment has been part of the ANSI C specification for as long as I can remember (and I've been using c since the late '70s). I'm pretty sure even K&R specified that, before ANSI. It is typically the startup code (crt0) that actually ensure this happens, not the compiler...

Regards, Ray L.

Yup, K&R first edition.

The only time I’ve ever been bitten is in C modules that don’t have a main(), like dynamically-linked kernel components, device drivers and the like.

:blush: got my memory confused. Indeed Bjarne Stroustrup did specify it early on for C++ as well.

What was added in C++14 was "Zero Initialization" for non-local initialization, static and thread-local variables that aren't constant-initialized --> the note sums it well:

As described in non-local initialization, static and thread-local variables that aren't constant-initialized (since C++14) are zero-initialized before any other initialization takes place. If the definition of a non-class non-local variable has no initializer, then default initialization does nothing, leaving the result of the earlier zero-initialization unmodified.