c++arduinoprintfsamd21

Arduino very randomly freezes on printing float to string via String and sprintf


TLDR: I am getting consistent freezes when printing floats to a String via arduinos String class. Previously I was getting the same freeze with sprintf and %f. I've solved the problem using the PString class, but I would like to understand the issue.

FULL STORY: I have a rather large c++ codebase for arduino SAMD architecture (a MKRZero), that started freezing recently on a line of code I hadn't touched in a long time. This was a call to sprintf with %f, which was oddly working as expected previously. After some research on SO and google, I realized that float formatting via sprintf isn't supported on Arduino, and after ruling out dtostr (due to not being on AVR), I tried with Arduinos String class. This temporarily solved the problem, but the freeze resurfaced after testing the system with some different external periphicals (I2C slaves that can be connected and disconnected). So the exact same code, but some differences in the parts of it being exersized due to the different periphical.

The codebase is quite large (several 1000s lines), and I haven't been able to reproduce with a simple example. So unfortunately without much context, these are the lines that fail:

for (int fieldIndex = 0; fieldIndex < totalFullDataPoints; fieldIndex++) {
      char buffer[14];
      Serial.println("foo");
      // String floatString = String((float)data[fieldIndex], 2); // causes system to freeze
      // String floatString = String((float)1024.46, 2); // causes system to freeze
      String floatString = String((float)1024.46); // causes system to freeze      
      // String floatString = String("1024.46"); // works
      Serial.println("bar"); // freezes before this
}

The bug is extremely unstable in that I can cause it to not trigger by modifying unrelated stuff other places in the code or disconnecting a sensor (I2C slave) from my arduino. But when it's present it's consistent in that it happens every run. I even had a version of my code that worked - but removing there three lines would cause it to freeze again:

String floatString = "14123.123";
Serial.println("Float String: ");
Serial.println(floatString);

I'm quite certain it's not a memory problem, and as far as I can tell it's not a case of pointers or non-terminated strings exploding.

I ended up using PStrings (https://github.com/boseji/PString-Arduino-lib) due to this post https://forum.arduino.cc/t/use-pstring-to-avoid-crashes-due-to-string-sprintf-or-dtostrf-float-issues/230946 but I'm frustrated and curious as to why it freezes in such a seemingly random fashion when creating floats via String is supposed to be supported.


Solution

  • After a lot of debugging, it seems that the issue indeed does have to do with ability of Arduino to print floats to string, and not with pointers or non-terminated string issues in my code.

    It seems to be stalling in the dtostrf() call of the String constructor:

    // ~/.platformio/packages/framework-arduino-samd/cores/arduino/api/String.cpp
    String::String(float value, unsigned char decimalPlaces)
    {
        static size_t const FLOAT_BUF_SIZE = FLT_MAX_10_EXP + FLT_MAX_DECIMAL_PLACES + 1 /* '-' */ + 1 /* '.' */ + 1 /* '\0' */;
        init();
        char buf[FLOAT_BUF_SIZE];
        decimalPlaces = min(decimalPlaces, FLT_MAX_DECIMAL_PLACES);
        *this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);     // <-- HERE
    }
    

    and it seems to be related to the assembler call to _print_float within the dtostrf() function:

    // ~/.platformio/packages/framework-arduino-samd/cores/arduino/api/deprecated-avr-comp/avr/dtostrf.c.impl
    char *dtostrf (double val, signed char width, unsigned char prec, char *sout) {
      asm(".global _printf_float");   // If this line is uncommented, the stall wont happen
      char fmt[20];
      sprintf(fmt, "%%%d.%df", width, prec);
      sprintf(sout, fmt, val);   // If the above line is not commented out, the system will freeze here
      return sout;
    }
    

    I realize this might be a pretty unsatisfactory answer for anyone coming across this thread... But for what it's worth, the solution for us will be to use PString (https://github.com/boseji/PString-Arduino-lib) as they at least seem stable so far.

    I will follow up on this thread if the issue ever surfaces again despite using PStrings