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.

SOME DEALS YOU MIGHT ENJOY
Perfect High Quality New NRF24L01 2.4GHz Wireless Transceiver Module Arduino
$2.29
End Date: Sunday May-13-2012 22:17:43 PDT
Buy It Now for only: $2.29
Buy It Now | Add to watch list
Duemilanove 2009 ( FT232RL / ATmega 328 ) Board For ARDUINO's IDE
$15.00
End Date: Thursday Jun-7-2012 4:54:32 PDT
Buy It Now for only: $15.00
Buy It Now | Add to watch list
NEW SainSmart Mega2560+LCD Keypad+Prototype Shield V3 Kit 4 Arduino ATMEL ATMEGA
$27.66 (12 Bids)
End Date: Thursday May-10-2012 18:47:12 PDT
Bid now | Add to watch list
! Photosensitive detection switch light sensor module For Arduino Robot
$3.95
End Date: Saturday May-19-2012 19:58:28 PDT
Buy It Now for only: $3.95
Buy It Now | Add to watch list
SainSmart UNO+LCD Keypad+Prototype Shield+HC-SR04 Distance Sensor Kit 4 arduino
$29.25 (19 Bids)
End Date: Thursday May-10-2012 18:45:40 PDT
Bid now | Add to watch list


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

  1. 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…

  2. 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” ?

  3. 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 = %d\n”,
    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.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>