In the last three tutorials, we learned how to use the GPIO output/input and ADC peripheral modules in our microcontroller. But before we continue to learn about new modules, there’s something we’re missing — we have no way of debugging our code! It’s pretty frustrating to program without being able to add a quick printf() call to check the status of a variable — or use more sophisticated debugging tools like backtraces and assertions — so let’s take a quick look at using the debugger.
Before we get started, let’s make sure the “Halt at Main” debugger feature is enabled — when we run the debugger, it’ll wait before running the program. In MPLABX, go to Tools > Options. Click the “Embedded” menu, then the “Generic Settings” tab. Near the bottom there’s a “Debug startup” option, make sure it’s set to “Halt at Main”. We’re all set to start debugging.
Debugging a project
Let’s open up the program that we used for the ADC tutorial. Instead of the usual make-and-program button, let’s click the debug button. After a few seconds we’ll get a “Target Halted” message and a green bar will appear, showing us the current line of code that we’re on. We also see that a bunch of new debugging icons have appeared at the top of the screen.
These are fairly straightforward. The green “play” button runs your code, the red “stop” button quits the debugger, and the blue “reset” button starts over. The next few icons are for stepping through our code, which is very useful, and for jumping around in our program.
Next, let’s view our variables. Go to Window > Debugging > Variables, and a view on the bottom of the GUI shows us the name, data type, memory location, and value of all variables in the current scope. Press the “Step Over” icon to step through our code line-by-line and notice that when we pass the function call to readADC(), the value of adcval is updated with a value corresponding to the potentiometer shaft position (values that just updated are shown in red).
If we wanted to see the value stored in a SFR instead of a variable, we can go to Window > Debugging > Watches and type in the name of our register (ex. if we wanted to see the ADC result registers, we’d create watches for ADRESH and ADRESL). We can also create watches for variables, and we can expand watches for registers to see the values of their bits.
Breakpoints
We can also use a breakpoint to make our debugger stop every time it reaches a given line. We set a breakpoint by clicking on the line number we wish to break at. Click on the line number corresponding to the line “if(adcval < 341)” — the line immediately after the call to readADC() — and a red square should appear over the number. Now press the “play” button, and the debugger will run through the program continuously until that line is reached. The program will stop running and we can see the current value of adcval. Now turn the potentiometer to another position, then hit the play button again. The debugger will run until the breakpoint again, and we can see that the value of adcval has updated.
This breakpoint gives us some useful information about our potentiometer-reading program — click on the “play” button a few times without touching the potentiometer. Notice how adcval still changes a little bit? We can reduce the effects of this imperfection by reading from the ADC several times and averaging the result:
unsigned long adcval; ... for(int i=0; i<8; i++) adcval += readADC(); adcval /= 8; //if we're being slick, we'd do adcval >>= 3 instead, which is faster but gives the same result
Now set the breakpoint and run the debugger again, clicking the “play” button repeatedly. Notice how the value of adcval is much more consistent.
Wrapping up
We’ve learned how to use the basic functionality of the debugger in just a few minutes, and it’ll save us lots of headache going forward. Excellent. Now let’s move forward and learn how to use the timer module.
Not-so-advanced debugging
Many experience programmers are familiar with the use of a stack trace or backtrace, which lets us view the current history of function calls. We can do this in MPLAB, although its stack viewer tool is not great — it doesn’t show us the arguments of the function calls. Go to Window > Target Memory Views > Hardware Stack to see the current function call history. This isn’t very useful for a small program like the one we’re debugging, but if we had lots of functions that called other functions, and we needed to know the sequence of calls that got us to where we are, this would be invaluable.
I mentioned assert() above — I love assert. In a future advanced tutorial, I’ll go over how we can write an assert() implementation in which the microcontroller will send diagnostic information to our PC in the event of an assertion failure.