18

I guess it's a bad thing to try to debug a microcontroller-based project using printf().

I can understand that you have no predefined place to output to, and that it could consume valuable pins. At the same time I've seen people consume a UART TX pin for outputting to the IDE terminal with a custom DEBUG_PRINT() macro.

Null
  • 7,448
  • 17
  • 36
  • 48
tarabyte
  • 3,112
  • 10
  • 43
  • 67
  • 14
    Who told you that it is bad? "Usually not the best" is not the same as an unqualified "bad". – Spehro Pefhany Apr 03 '14 at 20:57
  • 6
    All this talk about how much overhead there is, if all you need to do is output "I'm here" messages, you don't need printf at all, just a routine to send a string to a UART. That, plus the code to initialize the UART is probably under 100 bytes of code. Adding the ability to output a couple of hex values won't increase it all that much. – tcrosley Apr 04 '14 at 01:15
  • 1
    Takes up memory due to inclusion of that's why it is bad. – Chetan Bhargava Apr 04 '14 at 04:10
  • 8
    @ChetanBhargava - C header files don't usually add code to the executable. They contain declarations; if the rest of the code doesn't use the things that are declared, the code for those things doesn't get linked in. If you use `printf`, of course, all the code that's needed to implement `printf` gets linked in to the executable. But that's because the code used it, not because of the header. – Pete Becker Apr 04 '14 at 12:13
  • 2
    @ChetanBhargava You don't need to even include if you roll your own simple routine to output a string like I described (output characters to the UART until you see a '\0')' – tcrosley Apr 04 '14 at 12:23
  • @tcrosley The question was specifically about printf() not cout or something else. – Spehro Pefhany Apr 04 '14 at 13:06
  • 2
    @tcrosley I think this advice is probably moot if you have a good modern compiler, if you use printf in the simple case without a format string gcc and most others replace it with a more efficient simple call which does much as you describe. – Vality Apr 04 '14 at 14:19
  • @Vality -- I didn't know that. I'll have to check the disassembly output of our $995 PIC32 optimizing compiler and see if it does that. (Based on gcc with further optimizations by Microchip.) – tcrosley Apr 04 '14 at 15:08
  • 2
    @tcrosley, Vality: Or you could just use `puts()`. And then you don't need to even provide a format string. – Ben Voigt Apr 04 '14 at 16:37
  • @Vality: What is this "use printf() in the simple case without a format string"? The first argument is always a format string. Passing your variable directly in that argument is a BAD idea. The only case the toolchain could optimize is when the format string is a literal `"%s"`. – Ben Voigt Apr 04 '14 at 16:39
  • @BenVoigt Why is passing the variable directly into there a bad idea? It is fine with any char* as long as it does not contain any substitution chars? Often you also just want a simple literal in there. printf("foo"); is fine, as is char foo[] = 'bar'; printf(foo); – Vality Apr 04 '14 at 16:52
  • @BenVoigt Essentially I was proposing that one could write their own puts() very easily if they didn't want to use any part of the stdio library. – tcrosley Apr 04 '14 at 16:54
  • 1
    @Vality: Because it's unmaintainable. You write `char foo[] = "bar";`, and your coworker changes it to `char foo[] = "% bar";` and now you have a problem. Also, you're asking `printf` to do extra work looking for format codes. Just use `puts()` and avoid these issues. – Ben Voigt Apr 04 '14 at 16:54
  • @BenVoigt Perhaps you have a good point on the first comment. I am not advocating it's use, just trying to explain with a good compiler it need not be slower, my previous comment was all about how in the simple case gcc can simplify printf on a string it has determined does not contain % at compile time. – Vality Apr 04 '14 at 17:42

8 Answers8

26

I can come up with a few disadvantages of using printf(). Keep in mind that "embedded system" can range from something with a few hundred bytes of program memory to a full-blown rack-mount QNX RTOS-driven system with gigabytes of RAM and terabytes of nonvolatile memory.

  • It requires someplace to send the data. Maybe you already have a debug or programming port on the system, maybe you don't. If you don't (or the one you have is not working) it's not very handy.

  • It's not a lightweight function in all contexts. This could be a big deal if you have a microcontroller with only a few K of memory, because linking in printf might eat up 4K all by itself. If you have a 32K or 256K microcontroller, it's probably not an issue, let alone if you have a big embedded system.

  • It's of little or no use for finding certain kinds of problems related to memory allocation or interrupts, and can change the behavior of the program when statements are included or not.

  • It's pretty useless for dealing with timing-sensitive stuff. You'd be better off with a logic analyzer and an oscilloscope or a protocol analyzer, or even a simulator.

  • If you have a big program and you have to re-compile many times as you change printf statements around and change them you could waste a lot of time.

What it's good for- it is a quick way to output data in a preformatted way that every C programmer knows how to use- zero learning curve. If you need to spit out a matrix for the Kalman filter you're debugging, it might be nice to spit it out in a format that MATLAB could read in. Certainly better than looking at RAM locations one at a time in a debugger or emulator.

I don't think it's a useless arrow in the quiver, but it should be used sparingly, along with gdb or other debuggers, emulators, logic analyzers, oscilloscopes, static code analysis tools, code coverage tools and so on.

Spehro Pefhany
  • 376,485
  • 21
  • 320
  • 842
  • 3
    Most `printf()` implementation are not thread-safe (i.e., non-re-entrant) which isn't a deal killer, but something to keep in mind when using it in a multi-threaded environment. – JRobert Apr 03 '14 at 21:49
  • 1
    @JRobert brings up a good point.. and even in an environment without an operating system, it's difficult to do much useful direct debugging of ISRs. Of course if you're doing printf() or floating point math in an ISR the approach is probably off. – Spehro Pefhany Apr 04 '14 at 03:20
  • @JRobert What debugging tools do software developers working in a multi-threaded environment (in a hardware setting where the use of Logic Analyzers and oscilloscopes isn't practical) have? – Minh Tran Jan 21 '17 at 04:20
  • 1
    In the past I've rolled my own thread-safe printf(); used barefoot puts() or putchar() equivalents to spit out very concise data to a terminal; stored binary data in an array that I dumped & interpreted after the test-run; used an I/O port to blink an LED or to generate pulses to make timing measurements with an oscilloscope; spit out a number to a D/A & measured with VOM... The list is as long as your imagination and inversely as big as your budget! :) – JRobert Jan 21 '17 at 18:35
20

In addition to some other fine answers, the act of sending data to a port at serial baud rates can just be downright slow with respect to your loop time, and have an impact on the way the remainder of your program functions (as can ANY debug process).

As other folks have been telling you, there's nothing "bad" about using this technique, but it does, like many other debug techniques, have its limitations. As long as you know and can deal with these limitations, it can be an extremely convenient affordance to help you get your code correct.

Embedded systems have a certain opacity that, in general, makes debugging a bit of an issue.

Scott Seidman
  • 29,274
  • 4
  • 44
  • 109
  • 9
    +1 for "embedded systems have a certain opacity". Though I fear this statement may only be comprehensible by those who have decent experience working with embedded, it does make for a nice, concise summary of the situation. It is close to a definition of "embedded," actually. – njahnke Apr 04 '14 at 14:14
6

There are two main problems you will run into trying to use printf on a microcontroller.

First, it can be a pain to pipe the output to the correct port. Not always. But some platforms are more difficult than others. Some of the configuration files can be poorly documented and a lot of experimentation may be necessary.

The second is memory. A full blown printf library can be BIG. Sometimes you don't need all of the format specifiers though and specialized versions can be available. For instance, the stdio.h provided by AVR contains three different printf's of varying sizes and functionality.

Since the full implementation of all the mentioned features becomes fairly large, three different flavours of vfprintf() can be selected using linker options. The default vfprintf() implements all the mentioned functionality except floating point conversions. A minimized version of vfprintf() is available that only implements the very basic integer and string conversion facilities, but only the # additional option can be specified using conversion flags (these flags are parsed correctly from the format specification, but then simply ignored).

I had an instance where no library was available and I had minimal memory. So I had no choice but to use a custom macro. But the use of printf or not is really one of what will fit your requirements.

embedded.kyle
  • 8,411
  • 2
  • 26
  • 44
4

To add to what Spehro Pefhany was saying about "timing-sensitive stuff": let's take an example. Let's say you have a gyroscope from which your embedded system is taking 1,000 measurements per second. You want to debug these measurements, so you need to print them out. Problem: printing them out causes the system to be too busy to read 1,000 measurements per second, which causes the gyroscope's buffer to overflow, which causes corrupt data to be read (and printed). And so, by printing the data, you have corrupted the data, making you think there is a bug in reading the data when maybe there actually is not. A so-called heisenbug.

njahnke
  • 163
  • 5
  • lol! Is "heisenbug" really a technical term? I guess it has to do with the measurement of particles state and Heisenburg's Principle... – Zeta.Investigator Dec 26 '17 at 07:58
3

The larger reason for not debugging with printf() is that it is usually inefficient, inadequate, and unnecessary.

Inefficient: printf() and kin use a lot of flash and RAM relative to what's available on a small microcontroller, but the bigger inefficiency is in the actual debugging. Changing what's being logged requires recompiling and reprogramming the target, which slows down the process. It also uses up a UART that you could otherwise be using to do useful work.

Inadequate: There's only so much detail you can output over a serial link. If the program hangs, you don't know exactly where, just the last output that completed.

Unnecessary: Many microcontrollers can be remotely debugged. JTAG or proprietary protocols can be used to pause the processor, peek at registers and RAM, and even alter the state of the running processor without having to recompile. This is why debuggers are generally a better way of debugging than print statements, even on a PC with tons of space and power.

It's unfortunate that the most common microcontroller platform for newbies, Arduino, doesn't have a debugger. The AVR supports remote debugging, but Atmel's debugWIRE protocol is proprietary and undocumented. You can use an official dev board to debug with GDB, but if you have that you're probably not too worried about Arduino anymore.

Theran
  • 3,432
  • 1
  • 19
  • 21
3

printf() doesn't work on its own. It calls many other functions, and if you have little stack space you may not be able to use it at all to debug issues close to your stack limit. Depending on the compiler and microcontroller the format string may also be placed in memory, rather than referenced from flash. This can add up significantly if you pepper your code with printf statements. This is a big problem in the Arduino environment - beginners using dozens or hundreds of printf statements suddenly running into seemingly random problems because they're overwriting their heap with their stack.

Adam Davis
  • 20,339
  • 7
  • 59
  • 95
  • 2
    While I appreciate the feedback the downvote itself provides, it would be more helpful to me and others if those that disagree explained the problems with this answer. We're all here to learn and share knowledge, consider sharing yours. – Adam Davis Apr 04 '14 at 14:32
3

Even if one wants to spit data out to some form of logging console, the printf function is generally not a very good way of doing that, since it needs to examine the passed format string and parse it at runtime; even if code never uses any format specifier other than %04X, the controller will generally need to include all the code that would be required to parse arbitrary format strings. Depending upon the exact controller one is using, it may be much more efficient to use code something like:

void log_string(const char *st)
{
  int ch;
  do
  {
    ch = *st++;
    if (ch==0) break;
    log_char(ch);
  } while(1);
}
void log_hexdigit(unsigned char d)
{
  d&=15;
  if (d > 9) d+=7;
  log_char(d+'0');
}
void log_hexbyte(unsigned char b)
{ log_hexdigit(b >> 4); log_hexdigit(b); }
void log_hexi16(uint16_t s)
{ log_hexbyte(s >> 8); log_hexbyte(s); }
void log_hexi32(uint32_t i)
{ log_hexbyte(i >> 24); log_hexbyte(i >> 16); log_hexbyte(i >> 8); log_hexbyte(i); }
void log_hexi32p(uint32_t *p) // On a platform where pointers are less than 32 bits
{ log_hexi32(*p); }

On some PIC microcontrollers, log_hexi32(l) would likely take 9 instructions and might take 17 (if l is in the second bank), while log_hexi32p(&l) would take 2. The log_hexi32p function itself could be written to be about 14 instructions long, so it would pay for itself if called twice.

supercat
  • 45,939
  • 2
  • 84
  • 143
2

One point that none of the other answers have mentioned: In a basic micro (I.E there is only the main() loop and perhaps a couple of ISR's running at any time, not a multi-threaded OS) if it crashes / stops / gets stuck in a loop, your print function will simply not happen.

Also, people have said "don't use printf" or "stdio.h takes a lot of space" but not given much alternative - embedded.kyle makes mention of simplified alternatives, and that is exactly the sort of thing you should probably be doing as a matter of course on a basic embedded system. A basic routine to squirt a few characters out of the UART could be a few bytes of code.

John U
  • 7,041
  • 2
  • 21
  • 34
  • If your printf doesn't happen, you've learned plenty about where your codeis problematic. – Scott Seidman Apr 05 '14 at 00:28
  • Assuming you only have one printf that might happen, yes. But interrupts can fire hundreds of times in the time it takes a printf() call to get anything out of the UART – John U Apr 07 '14 at 09:09