String or not to String (or how the hell do i not use String)

Hi all,

I have been using Arduinos for quite a while and love them. I have pump controllers, temp monitors and a camera mount all controlled by them. that said, I am NOT a C programmer, VB/VBS/Powershell and a bit of PHP is about it, so most of the code is fairly simple stuff.

However, having done a simple temp monitor to monitor the air and lake water temp, i thought i would replace my old weather station with an Arduino. Its been a fun thing to do and its grown that i now use an EtherMega.

Ok, to the problem. I have been monitoring the Arduino using the serial monitor, but there are things that run at midnight that i dont want to leave my laptop on over night so i can scroll back in the morning. To resolve this, as i already have code in the project that writes data to the SD card, i thought i would create a function that i can pass a String to and have it write that string to the serial monitor if available and also to a text file on the SD card. That works, but that is also when odd stuff would happen. I would see the Arduino stop responding to web requests and also see odd data appear on the serial monitor.

I have read lots of comments about String being bad, but I am not sure what or how to use something else to do what i need.

Below is the function. Is there a better way to do this? i call it with lines like this.

logEvent("Current Temp: "+String(Celcius1)+" C");

In the function below you will see that the write to the SD card file is currently commented out. This is me trying to workout where the issue is. So far without the write to the file, it seems to be stable again.

//---------------------------------------------------------------
// Function to to systemlog
//---------------------------------------------------------------
void logEvent(String eventText) {

  char dt[16];
  char tm[16];

  if (Serial) {
    Serial.println(eventText);
  }
/* ******
  DateTime now = rtc.now();
  sprintf(dt, "%02d/%02d/%02d", now.year(),now.month(),now.day());
  sprintf(tm, "%02d:%02d:%02d", now.hour(),now.minute(),now.second());

  String timeString = String(dt) + " " + String(tm) + " -- ";    

  // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.
  File dataFile = SD.open(logFileName, FILE_WRITE);
  // if the file is available, write to it:
  if (dataFile) {
    dataFile.println(timeString + eventText);
    dataFile.close();
  }
  // if the file isn't open, pop up an error:
  else {
    Serial.print("logEvent: error opening file ");
    Serial.println(logFileName);
  }
***** */
//  timeString="";
  eventText = "";
  
}

The function is called from all over the place, in the setup, in the main loop and also inside other functions. I have been careful to make sure that i dont call it while i currently have a file open.

Can anyone shed any light on a, why is this function causing the issue, b, what should i do instead of using String?

Thanks very much

Ian...

What the hell are these?
Srting, Sting.

I recommend to read this The Evils of Arduino Strings | Majenko Technologies

To get away from Strings, you need to use sprintf() like you do inside your log function

// global
char eventText[50]; // make sure it is large enough to hold your biggest message

// ...
  // may need to adjust based on the type of variable Celcius1
  sprintf(eventText, "CurrentTemp: %02d C", Celcius1);
  logEvent("Current Temp: "+String(Celcius1)+" C");
// ...

//---------------------------------------------------------------
// Function to to systemlog
//---------------------------------------------------------------
void logEvent(char *eventText) {

  char dt[16];
  char tm[16];

  if (Serial) {
    Serial.println(eventText);
  }
    DateTime now = rtc.now();
    sprintf(dt, "%02d/%02d/%02d", now.year(),now.month(),now.day());
    sprintf(tm, "%02d:%02d:%02d", now.hour(),now.minute(),now.second());

    //String timeString = String(dt) + " " + String(tm) + " -- ";

    // open the file. note that only one file can be open at a time,
    // so you have to close this one before opening another.
    File dataFile = SD.open(logFileName, FILE_WRITE);
    // if the file is available, write to it:
    if (dataFile) {
      //dataFile.println(timeString + eventText);
      dataFile.print(dt);
      dataFile.print(" ");
      datafile.print(tm);
      datafile.print(" -- ");
      datafile.print(eventText)
      dataFile.close();
    }
    // if the file isn't open, pop up an error:
    else {
      Serial.print("logEvent: error opening file ");
      Serial.println(logFileName);
    }
  ***** */
  //  timeString="";
  //eventText = "";
}

Note: if Celcius1 is a float, the arduino library version of sprintf() does not support floating point conversion by default. You will have to be a bit creative. Read this

The String class can fragment your memory. A C string is built from a simply char array. Virtually anything you can do with a String method can be down with a string function. A good starting list of the functions can be found here. In many cases, C strings will also use less memory.

Ok, thanks, thats made it a little clearer.

One thing i had read and have used if the F() function to store text strings in program memory rather than SRAM (i think that right).

I have been changing the logEvent to use char instead of Strings, but i have found that i cant use F(). Am i doing something wrong in trying to do this

   // logEvent(F("Starting 8 hour time sync from NTP"));
   //replace with

    strcpy(eventText, "Starting 8 hour time sync from NTP");
    logEvent2(eventText);

but that seems to use more SRAM. Is there a way to do this?

    strcpy(eventText, F("Starting 8 hour time sync from NTP"));
    logEvent2(eventText);

Thanks again
Ian..

wskisoft:

   // logEvent(F("Starting 8 hour time sync from NTP"));

//replace with

strcpy(eventText, "Starting 8 hour time sync from NTP");
    logEvent2(eventText);




but that seems to use more SRAM. Is there a way to do this?



strcpy(eventText, F("Starting 8 hour time sync from NTP"));
    logEvent2(eventText);





Thanks again
Ian..

What you want is to allow your logEvent() function to take a char * --or-- a pointer to program memory (e.g. of type 'const __FlashStringHelper*'

This is C++ so you can have the same function name but with different arguments and the compiler will figure out which one is correct.

void logEvent( const char *msg )
{
  Serial.println(msg);
}

void logEvent (const __FlashStringHelper* msg )
{
  char buffer[50];
  strcpy_P(buffer, (char *)pgm_read_word(&(msg)));  // Necessary casts and dereferencing, just copy.
  logEvent(buffer);
}

strcpy_P(buffer, (const char*) msg); is good enough

or

void logEvent (const __FlashStringHelper* msg )
{
Serial.println(msg);
}