Premium Content

New Signal Chain Resources from Texas Instruments:

Take The Guesswork Out Of Debugging

Date Posted: June 25, 2009 12:00 AM
Author: William Wong

In the classic board game Battleship, an adversary arranges a fleet of tiny, plastic combat vessels on a grid that’s hidden from view. After an analogous fleet is set up on a separate grid, the objective is to guess the locations of the opponent’s boats. Likewise, the opponent’s goal is to divine the whereabouts of your miniature ships. The game proceeds with opponents alternately wagering guesses by calling out grid coordinates (C5, A2, etc.).

At first glance, this patently low-tech and old school game may seem to have little in common with embedded systems development. However, many engineers actually develop and debug software using the same trial-and-error tactics required for Battleship. Just as participants in a game of Battleship call out grid coordinates in attempts to locate ships, these engineers painstakingly insert p r i n t f ( ) statements into various locations in their code, hoping to eventually identify the sources of troublesome bugs.

Unfortunately, when p r i n t f ( ) is the only means of gathering helpful information from an embedded system, ridding the code of bugs is a tall order. The continued widespread use of such an outdated technique may help explain why so many engineers find debugging to be the most challenging aspect of their projects. In one recent survey of embedded software developers, a significant portion of the respondents indicated that they spend more than 50% of their time debugging.1

Although such dismal statistics seem to imply that debugging is necessarily an arduous process, many of the difficulties faced by engineers when developing and debugging embedded software could be avoided altogether. Whereas the rules of the game require Battleship players to somewhat blindly guess where opponents’ ships are hidden, today’s software developers needn’t speculate about the inner workings of their embedded systems. By using some helpful tools, engineers can glean valuable information from their embedded systems and quickly identify bugs that might otherwise lead to missed deadlines, excessive costs, and squandered opportunities.

printf()
For the most part, the tools described here don’t incorporate groundbreaking technology. Furthermore, most of these tools are exceedingly easy to use. Why, then, do so many software developers continue to use p r i n t f ( ) to gather information from embedded systems?

Many engineers (including myself) were introduced to software development via a “Hello World” application. Listing 1 shows an example “Hello World” that’s written in C and utilizes printf(), though there are certainly other forms of “Hello World.”

A particularly clever variation that relies on the human eye’s persistence of vision is described in Programming 16-bit Microcontrollers in C: Learning to Fly the PIC24 by Lucio Di Jasio. When a development board running this “Hello World” application is waved back and forth, the board’s rapidly blinking LEDs actually form the image of the word “hello.”

No matter how “Hello World” applications are implemented, they do nothing more than visually indicate that they’re running. Thus, for many engineers, “Hello World” isn’t simply the first piece of code that we write. It also introduces us to instrumented code. In other words, it shows us how printf()and other output routines can help us confirm that our code is running correctly. Consciously or not, much of the embedded community continues to follow the example of “Hello World” by using p r i n t f ( ) to monitor and debug code.

Since p r i n t f ( ) seems to be perfectly appropriate for “Hello World,” the drawbacks of developing and debugging complex applications with the assistance of this function (or with a similar output routine) may not be immediately apparent. However, this debugging strategy is hardly ideal. While you shouldn’t be discouraged from ever invoking p r i n t f ( ), there are plenty of reasons to be wary of this function.

One factor that should be considered before using p r i n t f ( ) in any project is memory usage. Because p r i n t f ( ) is a highly flexible library function capable of processing a wide range of options, it has substantial memory requirements. Notably, p r i n t f ( ) can increase the amount of stack space required by the application. Therefore, when attempting to debug a newly written application with this seemingly innocuous function, new bugs may be brought about via a stack overflow.

Supposing you can avoid any such memory issues, the size of p r i n t f ( ) still poses a potential problem. As a complex function that depends on an I/O device, p r i n t f ( ) typically has a relatively lengthy execution time. Software developers who rely on p r i n t f ( ) then must account for what’s commonly deemed the Heisenberg effect.

A debugging tool that produces the Heisenberg effect actually alters the behavior of the system being debugged. To understand the dangers of this phenomenon, imagine that the code snippet shown in Listing 2 is responsible for writing an initialization sequence to a flash memory controller. Assume that the sequence is 32 bits long and must be written, in two chunks, to a 16-bit register, access to which is provided by a function named Flash_Reg_Wr().

Continue on Page 2

debug | embedded code | microcontrollers
Part Inventory
Go
powered by:
 

 
You must log on before posting a comment.

Are you a new visitor? Register Here
    There are no comments to display. Be the first one!