NullSerial: reducing production code size

There are many Arduino applications which make use of the built-in Serial class to communicate with a computer. A good many of these use serial communications during the development process for debugging, but do not or cannot in the final release product. For these, the interspersing of dozens or hundreds of calls to Serial.println() or Serial.begin() represents so much wasted space and, potentially, program efficiency. One way to excise this waste is to #define a preprocessor symbol that indicates whether the build is a DEBUG or non-DEBUG build, and use #if to logically remove all such serial calls, like this:

#define DEBUG true
...
if (light_led)
{
  digitalWrite(led, HIGH);
#if DEBUG
  Serial.println("The LED was illuminated!");
#endif
}

The problem with this tactic is that it can make the code look very clumsy, especially if you have lots of Serial.println() calls peppered through it, as many complex programs do.
One alternative approach that I have used successfully is to conditionally define a NullSerial class that replaces the Serial class in production releases. NullSerial does nothing and doesn't take up any space! Adding just a little bit of code to the top of one of my recent programs reduced the production release size about 2 kilobytes!

To try this trick, just add the following snippet to the top of your program. When you are ready to release, simply #define DEBUG to be false. For even cleaner code, put the class definition in a separate header file, say NullSerial.h and #include it.

// Change to "false" to disable serial debugging
#define DEBUG true

// This trick makes all embedded serial stuff go away magically
#if !DEBUG
class NullSerialClass // a do-nothing class to replace Serial
{
  public:
  void begin(int speed) {}
  void println(int x, int type=DEC) {}
  void println(const char *p=NULL) {}
  void print(int x, int type=DEC) {}
  void print(const char *p) {}
  int available() {return 0;}
  int read() {return -1;}
  void flush() {}
} NullSerial;
#define Serial NullSerial
#endif

Mikal Hart

I do something a bit similar with the preprocessor:

#define DEBUG

#ifdef DEBUG
#define DEBUG_PRINT(x) Serial.println(x)
#else
#define DEBUG_PRINT(x)
#endif

void setup()
{
   DEBUG_PRINT("here we go!");
}
...

When finished debugging, comment out the #define DEBUG.

-j

That's a very good solution, and even cleaner -- no need to #include NullSerial.h or any funny business like that. Further, using syntax like DEBUG_PRINT makes it very clear to the reader what you are trying to do (as compared to Serial.print).

I had thought to bring up the argument that the NullSerial solution saves more space, because you are never creating the Serial object at all. (In your solution, you presumably still call Serial.begin(), and just making that single call seems to add an additional ~1.4K bytes to your program's memory consumption.) But then I started wondering, in a single-processing environment like Arduino, is there really any reason to worry about program size? I mean, as long as it fits to begin with, there really isn't a compelling reason to shave an additional few bytes off the memory footprint, unless you just feel like exercising disciplined programming, which -- don't get me wrong -- is a very good thing!

Thanks for the feedback.

Mikal

Oops, forgot about Serial.begin() - that will cost some code space, plus some RAM. (I yanked this example from some of my non-embedded programs, where I do IO anyway, just not so much during normal operation.) I'd probably put that inside a "#ifdef DEBUG/#endif pair.

And you're right on program size - it's a go/nogo function. If it fits, who cares?

-j

So if your code has "gone production", why do you care what the code size is, as long as it still fits in the flash space available? The production "quanta" is a single chip; does it matter whether the chip is half full of real code or 3/4 full of real code?

Aside from that; thanks for the hint on the null serial class; not being familiar with C++; it's not a solution I would have thought of, but it might come in handy!

Honestly, I can't think of a great reason to work hard to reduce the size of a binary image that already "fits", unless you just want to practice the discipline of keeping code small and tidy for its own sake. (I guess you could argue that keeping it small to begin with makes it easy to consider adding complicated new features in a subsequent revision.)

But for certain applications the performance benefit of excising the Serial stuff could be important.

Thanks for the feedback.

Mikal

I've got some code I'd like to use with either a serial LCD or a parallel 4bit LCD. With the parallel LCD module(pde) the entire package compiles to 14220 bytes. When I added the serial LCD pde file to the package and then tried using the pre-processor directive to exclude it in the compilation, the code still ballooned by the added pde. Any ideas why?

In SparkFunSerialLCD.pde it starts with:
//#define USE_SERIAL_LCD
#ifdef USE_SERIAL_LCD
.....rest of the file...
#endif

in SparkFun4bitLCD.pde:
#define USE_4BIT_LCD
#ifdef USE_4BIT_LCD
....rest of file....
#endif

Again, before I tried adding the serial LCD module, I was at 14220 but now it's saying 15270 "Sketch too big" so the serial module is still getting compiled or calculated for some reason. I tried #undef USE_SERIAL_LCD but that didn't change anything. Any ideas why?

Might you have #included a header file that was not excluded by the #ifdef USE_SERIAL_LCD?

Might you have #included a header file that was not excluded by the #ifdef USE_SERIAL_LCD?

No, there are only 10 or so lines of comments before the #define and #ifdef directives.

Hmm... I'm stumped. Perhaps if you posted your code we could take a closer look?

I could post the code but all I have is 10 tabs in the Arduino IDE, 3 of which are .h/header files an the others are what I think would be called Sketch's. No magic there. One of the sketch's does all the LCD control with setup() and updateDisplay() functions for handling the SparkFunLCD in 4bit parallel mode. I have another complete copy of all these Sketches where the one display sketch is for doing serial LCD using the SoftSerial library.

I figured, I could copy one LCD sketch to the other directory, add the following to each of the LCD sketches and uncomment the directive definition in the display/file/sketch I want to use.

#define USE_4BIT_LCD // the other defines USE_SERIAL_LCD
#ifdef USE_*******
...the entire contents of the sketch here....
#endif

I guess I could go in and change a bunch of the commenting around so I can just block off everything as a comment using a /* .... */ block.

And I thought that #ifdef was going to be the easy way to do it instead of complete directory and code duplication and all that junk which goes with keeping two identical sets of 6 Sketches in sync.

I went back to only one LCD sketch but left the define/ifdef directives, compiled and tested it worked. All good there. I then commented out the #define directive and, as expected it complained that the setupDisplay and updateDisplay functions were not defined. I then closed Arduino, copied the other LCD sketch into the directory and restarted Arduino IDE and verified the original LCD sketch was still as it was and the other LCD sketch had its define directive enabled. Compile away and bam, it says the sketch is too big.

FYI, I've compiled each with just their unique LCD display sketches.
Parallel/4bit compiles to 1390 bytes
Serial compiles to 14290 bytes
Therefore, I know it's not just one LCD sketch throwing me for a loop here.

I would imagine you could duplicate this with any sketch just by copying one to another name and restarting the IDE. Then try to exclude one using just the #define/#ifdef directives. It'll probably somehow compile or sum up the bytes of the sketch supposedly #ifdef'ed out of the picture.

//FYI, I'm running Arduino 0011 alpha on Linux.
I just updated to Arduino IDE v0012 alpha and it does the same thing.

I found my problem. The #include directives are being parsed and included regardless of any #ifdef/#endif wrapping them.

I found my problem. The #include directives are being parsed and included regardless of any #ifdef/#endif wrapping them.

This might be a side tangent and I'm still new to the Arduino tool chain, but here goes anyway. Maybe the Arduino tools shouldn't be trying to parse the .C files directly to determine linking requirements. It's a cool feature, I understand the reason to include it. But there are too many situations where the mini-parser can disagree with the real C compiler's parser. For this particular problem, I bet the C Preprocessor output could be parsed instead of the original C. If the mini-processor was looking at #line directives for truly included headers (not #includes that might be #ifdef disabled), it could then accurately locate the required libraries to link.

This sounds like a good idea. If anyone wants to take a shot at implementing it, that would be awesome.