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.