If you met someone in a bar, say, and you got talking, they are likely to ask what you do. You will probably reply that you are an embedded software developer. [Actually, in my experience, this can be a bit of a conversation killer. You might be better saying you are an airline pilot or a brain surgeon.] If they carry on talking with you, they might ask you what that job entails and you would probably answer with something about designing software and writing C/C++ code. Of course, that is not really the case. Most embedded software developers spend the bulk of their time debugging.
This does beg the question: what does debugging actually mean? The answer is less simple than you might expect …
Many years ago, when computers were big and expensive, there were no debugging tools that we would recognize today. Frankly, programmers made less errors. One reason for this was that time on the computer was precious, so the programmer would write the code down on paper first, thinking it through very thoroughly. They would very carefully proof read to avoid syntax errors, as just one slip could waste a computer run. Then they would think through the operation of the code, dry running it on paper to check the logic. Eventually, this carefully scrutinized code would be submitted to the computer. After the run, the results would be studied and, if an error was detected, the code would be modified to include extra printf() [or equivalent] lines to show how values were changing. By using conditional compilation, “debug” and “production” versions of code could be compiled.
So, why not take this approach when writing embedded software today? The first part – writing the code on paper and thinking a lot – might be a very good practice. However, the ready access to computers that cost almost nothing has moved us away from such an approach. The practice of adding printf() calls to log values is still amazingly common, despite it being quite problematic for embedded applications. There are five key issues:
- Most implementations of printf() are quite large. The function needs to address a very wide range of formatting situations and that takes a lot of code. This does not matter in a desktop software context, but memory is rarely in abundance in an embedded system.
- There is the question of where the output of printf() actually goes. Many embedded systems have nothing that looks like a “console” and directing output back to a host computer can be challenging.
- In a multi-threading context – i.e. when using an RTOS – reentrancy is a concern. If two threads try to use printf() at the same time, confusion can result.
- Every time you want to change the variables you are looking at, a complete rebuild and download of the code is required.
- Lastly, of course, formatting and sending the output [somewhere] takes time. If it is a real time application, this overhead can introduce problems.
So, for most systems, using printf() as a debug tool is not viable. Much better is to attach a debugger to the target system using JTAG. This can give ready access to all the target data and addresses the five issues thus:
- There is no extra code on the target.
- Data is displayed on the host computer without effort.
- The debugger needs to be task-aware, but that is well understood.
- Code only needs to be rebuilt to fix bugs.
- A JTAG connection is not totally non-intrusive in the time domain, but the impact is typically quite small.
I am left wondering whether, if developers were rationed in their computer access and, hence, required to write out all code by hand and do some thinking, would better code result and debug time be reduced?
[With thanks to my colleague Rizwan Rasheed, who suggested this topic.]