Troubleshooting real-time software issues using a logic analyzer
Debug macros for printing
The use of macros to standardize print statement debugging is both useful to know and represents the basis for logic analyzer debugging macros.
Listing 1: Typical 'print statement' debugging.
void myfunc(void) {
int x,y;
printf("Start of myfunc\n");
x = read();
printf("x = %d\n",x);
y = f(x);
printf("f(x) = %d\n",y);
etc.
printf("End of myfunc\n");
}
Consider the procedure when using print statements to debug code. A make-shift code segment is shown in Listing 1, with print statements added to trace the flow of the code. This would produce the following output (with assumed values for x and y):Start of myfunc x = 10 f(x) = 24 End of myfuncWhile such code will help trace the internals of
myfunc(), it's rather inefficient to type in all those print statements, and the amount of information provided in each statement is minimal. What if these lines of code were just a handful out of thousands of debug statements? Tracing back to the original source code could also be time consuming.Listing 2: Macros to standardize print statement debugging
#define DEBUG_WHERE() printf("[%s/%u] %s()\n",__FILE__,__LINE__,__FUNCTION__)
#define DEBUG_INT(_var) printf("[%s/%u] %s = %d\n",__FILE__,__LINE__,#_var,_var)
A simple solution is to abstract print statement debugging into macros. Listing 2 shows two macros that we can use. With these macros, the debug code can be changed to that shown in Listing 3. Listing 3: Using debug macros.
void myfunc(void) {
int x,y;
DEBUG_WHERE();
x = read();
DEBUG_INT(x);
y = f(x);
DEBUG_INT(y);
etc.
DEBUG_WHERE();
}
The resulting output would be:[file.c/3] myfunc() [file.c/5] x = 10 [file.c/7] y = 24 [file.c/9] myfunc()When such code is intermixed with thousands of debug statements, it's still possible to easily follow the code, as filenames, function names, line numbers, and values of variables are each clearly marked. Because macros are used, it's also easier to use shortcuts or cut-and-paste to add the debug statements to your code, thus also making it more efficient.
Suppose we want to print the variables in hex instead of decimal. We can create alternate macros, as shown in Listing 4.
Listing 4: Defining additional print statement debug macros.
#define DEBUG_HEX8(_var) printf("[%s/%u] %s = 0x%02X\n",__FILE__,__LINE__,#_var,_var)
#define DEBUG_HEX16(_var) printf("[%s/%u] %s = 0x%04X\n",__FILE__,__LINE__,#_var,_var)
If we replace lines 5 and 7 with DEBUG_HEX8() and DEBUG_HEX16() macros respectively, it results in this change to the outputs:[file.c/5] x = 0x0A [file.c/7] y = 0x0018There are many different ways that macros can be used to standardize debug print statements, but I won't cover them here, since our focus is on debugging with the logic analyzer.
The constraint with the print-statement method is that you might be able to add maybe 50 to 100 debug statements per second to your code before it breaks functionality, or maybe just a handful per second before it affects real-time performance. Using the logic analyzer, it's possible to get thousands of debug statements per second with negligible impact on the system's real-time performance. Furthermore, the data is time-stamped and can be visualized as timing diagrams, enabling rapidly identifying anomalies in the code. The concept of logic-analyzer debugging is similar to using these
DEBUG macros, except that the macros will be defined to send codes to a logic analyzer instead of print statements to a console. Before going into detail on logic-analyzer macros, I'll describe the logic analyzer and hardware setup needed to support this type of debugging.


Loading comments... Write a comment