A Better Serial.print() For Arduino

In a previous article I described how to add the old-fashioned print() function to Arduino to improve debugging – after all, it gets tedious to use a separate Serial.print() function for each type – and inserting information into a string is printf’s specialty.

However, while I found they did the job, they weren’t quite what I wanted. for one thing, the memory they used for the format string is in RAM, which means it contributes to ‘eating up’ the 2k memory that the ATMega328 on the Arduino has to use. Any string does this, of course, but prints can eat up string space quickly, especially if you add a lot of debugging code.

Another problem when I mentioned the code to others seemed to be the waste of space for the buffer character array. However, this wasn’t a big issue, since the buffer only existed for the time the function was around – and in tight memory situations, it could be shrunk by editing the length.

Still another issue: I didn’t like the way it handled the Serial object, since it made it hard to reuse – for example, if I used a software serial device, or I programmed on the Mega (which has 4 hardware serial ports).

For these and other reasons, I decided to improve the code, and did a search on the Internet. The result is one function and two macros, which save memory while giving you more flexibility:

#include <avr/pgmspace.h>

int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

void StreamPrint_progmem(Print &out,PGM_P format,...)
{
  // program memory version of printf - copy of format string and result share a buffer
  // so as to avoid too much memory use
  char formatString[128], *ptr;
  strncpy_P( formatString, format, sizeof(formatString) ); // copy in from program mem
  // null terminate - leave last char since we might need it in worst case for result's \0
  formatString[ sizeof(formatString)-2 ]='\0'; 
  ptr=&formatString[ strlen(formatString)+1 ]; // our result buffer...
  va_list args;
  va_start (args,format);
  vsnprintf(ptr, sizeof(formatString)-1-strlen(formatString), formatString, args );
  va_end (args);
  formatString[ sizeof(formatString)-1 ]='\0'; 
  out.print(ptr);
}

#define Serialprint(format, ...) StreamPrint_progmem(Serial,PSTR(format),##__VA_ARGS__)
#define Streamprint(stream,format, ...) StreamPrint_progmem(stream,PSTR(format),##__VA_ARGS__)

void setup() 
{
  Serial.begin(9600);
  Serial.print("12345678901234567890123456789012345678901234567890");
  Serialprint("");
  Serialprint(" memory available: %d bytes\n",freeRam());
}
void loop() 
{
}

The relevant code here is the function StreamPrint_progmem(), and the macros Serialprint() and Streamprint(). In code, you’d only use the macros; so for instance you’d replace:

Serial.print("hiya!");

With

Serialprint("hiya!");

Notice you only have to remove the period – it’s the reason for the function name, to make it easy to switch over.

But of course it becomes really handy with those long entries:

Serial.print("Count: ");
Serial.print(count);
Serial.print(", Data: ");
Serial.print(data);

Can become (assuming both items are numbers):

Serialprint("Count %d, Data: %d",count,data);

And as a bonus, the strings do not go into the 2k the computer needs to run.

In fact, you can prove it – run the above sketch and note the size, which is the available memory of the 2k. then, swap the Serial.print() with the Serialprint() – and notice the size change by 50 bytes, the size of the string. And that’s 50 bytes you won’t have to look for as you program gets tighter and tighter!

There are some notes about this, however:

  • There is no println() version – instead, add \n or \r to your strings:
    Serialprint("Count %d, Data: %d\n",count,data);
  • The Streamprint() is the more flexible option – you can use it for other serial items, of even the NewSoftSerial object. You just pass the Serial object of your choice as the first parameter – for example, here we’ll use Serial, in effect, doing the same as Serialprint();
    Streamprint(Serial,"Count %d, Data: %d\n",count,data);
  • To save buffer space, I combined two buffers into one (temporary format string storage, and the print result). Because of this, the current size of 128 characters is a bit misleading – you’ll only have ‘about’ 64, since half will be the temporary format string, and half will be the final output. I felt this was better than using two larger buffers, and risking not enough room for either. If you find your text is getting truncated, just increase the buffer size (remember, it only exists while the function runs).

Oh, and by the way, the ‘Better’ in the title is subjective – I like it better. whether you’ll find it better is up to you decide – but I hope it comes in handy.

16 thoughts on “A Better Serial.print() For Arduino

  1. Works as advertised, so thanks a for this!

    I suppose this will work with Ethernet library’s Client class, too.

  2. I’d imagine so – ultimately, the StreamPrint_progmem() function passes a string to the output routine, which can be anything, or even multiple calls to multiple devices…

  3. Thankyou for this great work.
    It decreased my RAM usage from 856 down to 634 !
    I plan to also use it agains GSM_Shield library, it use NewSoftSerial with alot of “static text”. Although I’m still not sure how to do it.

    Question : Is there any way to use it where “%d” data is a “char array” ?

  4. Great routine and helping me with a space problem. However, I can’t get floating decimal fields to format. I’m using
    Serialprint(” %12s Temp = %f C, %f F, Min = %d, Max = %dn”,
    sensorList[ix].ds_Name,
    sensorList[ix].ds_celsius,
    sensorList[ix].ds_fahrenheit,
    sensorList[ix].ds_min_Temp,
    sensorList[ix].ds_max_Temp);
    All prints fine except the %f fields just print question marks. Needless to say they print fine with Serial.print(…..); Any suggestions? Thanks

    • Thanks for the quick reply. I’ve converted all my “debugging” messages and saved about 600 bytes – a big deal in this particular application. I do have a flag that will eliminate them completely for production, but for now this code has helped immensely. I’ll simply massage the floats to ints for the trace – not a big deal – precision not important in the message.

  5. Excellent work and thanks for sharing. I was having a lot of trouble with the standard serial.print but i used your code and it works like a champ. i haven’t looked in detail to figure out what is happening, i am just glad that this saved a lot of time for me.

    Hari

  6. Hi there,

    Thanks for this awesome code! I hope it can help me fitting my project in an Arduino Pro Mini. The project is now just hitting the limits of the mini…

    My project is using multiple *.cpp/*.h files, could you elaborate on how to make this Serialprint() function available throughout the whole project?

    Thanks in advance!

  7. You can Serial.print() strings without copying them to RAM by using the F() macro, like in

    Serial.println(F(“This string lives in Flash.”));

    And if you like printf-style formatting, why not use printf() itself? Or rather printf_P() to keep the formatting string in flash. You will have to setup stdout, but it’s not hard.

  8. Could you please change “instead, add n or r to your strings” to “instead, add \n or \n to your strings”? Thank you.