Getting Started with MCU Projects Using MPLAB® Code Configurator (MCC) Melody

Debugging an MCC Melody Project

Last modified by Microchip on 2024/06/13 21:18

   Editing an Existing MCC Melody Project 

In this section, you will cover the fundamentals of debugging in MPLAB® X IDE using the MPLAB Code Configurator (MCC) Melody project and the associated Curiosity Nano Board

Debugging with the Data Visualizer

There are a few strategies at your disposal to debug your application's code and ensure its seamless performance. Let’s examine one method that utilizes the integrated Data Visualizer plugin data visualizer.

data visualizer

The PIC18F56Q71 Basic Universal Asynchronous Receiver/Transmitter (UART) Comms project example can be used to demonstrate this. The UART2_Write() function is used to transmit the character 'S' to the Data Visualizer's serial monitor, and from the serial monitor to toggle the LED when the character ‘T’ was received by the PIC18F56Q71. Essentially, this is a form of debugging, confirming that the pushbutton activation or specific character sent from the Serial Monitor triggered certain actions within the application. This technique can be employed to monitor the execution of specific code segments or the initiation of certain subroutines.

In these instances, a fundamental UART write functionality can be used. For more verbose messages, you may want to enable printf messages in the UART easy view inside of Melody. Once enabled you need to re-generate the code. Now you can modify your code to send a message such as "entry into x", or by sending the value of a related peripheral register to verify its update.
adding printf statement to code
UART easy view
Click image to enlarge.
The Data Visualizer also provides the added feature of integrating dashboard widgets. This allows for the inclusion of push buttons, sliders, and a range of data visualization instruments such as a 7-segment display, table, progress bar, or graph.
integrating dashboard widgets
Click image to enlarge.

Now that you've seen how printf can be used, disable printf capabilities and regenerate the Melody code to conserve memory space.

If you would like to learn more about the Data Visualizer, please check out the Visual Debugging with MPLAB Data Visualizer class on Microchip University.

Back to Top

MCU Debug Interface

To debug a microcontroller, debugging hardware is needed. You can use a standalone programmer/debugger (e.g.: MPLAB PICkit™ 5), or use an on-board programmer/debugger included on many of Microchip's evaluation boards (e.g.: PIC18F56Q71 Curiosity Nano Demo Board.

If this evaluation board is plugged into your computer, the Kit Window in the MPLAB X IDE will provide links to the Users Guide, board schematics, and other useful information.
Kit Window
Click image to enlarge.

Opening the Users Guide will show you the interface features of the board:

  • A debugger interface that facilitates programming and debugging of the PIC18F56Q71 within the MPLAB X IDE. 
  • A mass storage device that enables drag-and-drop programming for the PIC18F56Q71.  
  • A virtual serial port Communications Device Class, or CDC for short, interface connected to a UART on the PIC18F56Q71 to communicate with the target application via terminal software. This was used to transmit the character from the Discover example used in previous sections of this course to visualize in the Data Visualizer tool.  
  • A Data Gateway Interface (DGI) specifically designed for use within the Data Visualizer tool to display code instrumentation with logic analyzer channels to visualize the program's flow.  

Debugging a microcontroller using these methods will require the debugger tool interface with specific pins on the target device. 

The datasheet for the PIC18F56Q71 defines these pins. Using the button on the left side of the project’s dashboard, selecting the HTML Browser Download from Microchip Website radio button and then clicking OK will open the datasheet directly.
open datasheet from project dashboard
Click image to enlarge.
On the PIC18F56Q71 device used in this demonstration, these pins are part of In-Circuit Serial Programming™ (ICSP™). If you are using a different type of device like an AVR® or PIC24 MCU you will see different technologies used but you will find that information in their respective datasheets similar to how they are presented here.
ICSP interface from datasheet
Click image to enlarge.

The ICSP circuitry is embedded within the PIC18 device used and aids in both programming and debugging. Scrolling to the ICSP section of the datasheet, you can see that three pins are used for debugging: a data line (SDL), a clock line (SCL), and a master clear line (MCLR).

Only the data and clock lines are needed when purely programming the microcontroller.

ICSP schematic
Click image to enlarge.
Referring to the Curiosity Nano Schematic document linked to from the kit window, you can see that these connections are already established using the integrated programmer/debugger on the PIC18F56Q71 Curiosity Nano boards and only a USB connection is needed to interface debugging capabilities with MPLAB X IDE. 
ICSP schematic
Click image to enlarge.

Making sure that the Curiosity Nano board is connected to an available USB port on the computer, let’s go ahead and configure MPLAB X IDE for debugging.  

Back to Top

Building for Debug vs Building for Production

Earlier in this course, you used the Make and Program device button program-target-project.png, found at the top of MPLAB X IDE, to program the PIC18F56Q71 device.

However, to debug the project, you will need to build it specifically for debugging purposes. Debug Build mode is used during the developmental and testing phases of the project. This mode includes additional debugging information in the output file, enabling the use of debugging tools such as breakpoints, watch variables, stepping into/over functions, and more which will be discussed shortly in greater detail.  

To build the project and program the device for debugging, use the Debug Project button debug.

Back to Top

Basic Debugging with Breakpoints

Each device integrates a variety of debugging features. To keep things simple in this course, our focus will be on a handful of fundamental debugging functions. If you are interested in more sophisticated debugging features, a comprehensive description can be found in the MPLAB X User’s Guide which can be accessed using the Help dropdown menu in the MPLAB X IDE then selecting Tool Help Contents and then MPLAB X IDE WebHelp to open the online documentation.
MPLAB X IDE webhelp
Click image to enlarge.

Let’s go ahead and set up this project for basic debugging. Like the Make and Program Device button, you can also build our project for debugging and start a debugging session by clicking on the Debug Project button debug located at the top of the MPLAB X IDE. Let’s go ahead and do that.

The project will undergo a build process tailored for debugging, as previously mentioned, and the PIC18F56Q71 microcontroller will be programmed.  Once programming is completed, the application will be running on the microcontroller.

MPLAB X IDE will now display new buttons associated with the debugging process in the toolbar. This section of the toolbar, like any other section, can be moved around to make it easier to access by grabbing the dotted vertical separator next to the section and dragging where it works best for your needs. We’ll discuss each of these buttons shortly. 

debug toolbar

But first let’s look at a feature called breakpoints.  In the example project used in this class, you know that an if statement is executed whenever data is transmitted from the data visualizer via USB to the Curiosity Nano board.
There is a function called UART2_IsRxReady() that gets checked as part of the if statement condition check. A breakpoint can be used to visualize and verify this function call. Go ahead and navigate to the function code to do this.
while 1 code
Click image to enlarge.
Holding the CTRL key and right-clicking on the UART2_IsRxReady() parameter in the if statement will take us to the associated function inside of the uart2.c file.
while 1 code
Click image to enlarge.

If you click in the margin next to the line of code that returns the value of the RXBE bit, or Receive Buffer Empty status, inside of the UART2 FIFO register, a little red box appears indicating that you've set a breakpoint.
A breakpoint is a debugging tool used to temporarily stop the execution of a program at a specific point. Think of it as a trap that you set; when the program hits this trap, it pauses, allowing you to examine the current state of the program, check the values of variables, and understand how the program is running up to that point. This helps in finding and fixing errors or understanding the program's behavior.

Notice that since the debugger is running it immediately stops at that line of code highlighting it in green and a green arrow indicates code execution has stopped at this line. The reason it stops immediately is that line of code is executed each time through that while(1) loop in our main().
breakpoint added
Click image to enlarge.

More on this in a moment. For now, select the Dashboard tab for the project, which is situated next to the Navigator window, and locate the Debug Resources information.

What the window says here is that a breakpoint has been used and there are two left that can be used as either program or data breakpoint types. Program breakpoints halt the execution of a microcontroller's code when a specific line is reached, while data breakpoints trigger a stop when a certain condition, like a specific value, is met in a microcontroller's register. Both are hardware breakpoints that rely on the microcontroller's built-in mechanisms to monitor and respond to these events. You will only use program hardware breakpoints in this class.

Navigator window
Click image to enlarge.

A third type of breakpoint called a software breakpoint has been disabled by default. These types are different than hardware breakpoints in that they can interrupt program execution at a specified point without the need for dedicated hardware resources. Software breakpoints are implemented by altering the program code itself, typically by inserting a specific instruction that causes the program to halt.

This allows for a greater number of breakpoints compared to hardware breakpoints, which are limited by the microcontroller's debugging capabilities. While software breakpoints are versatile, they are also not covered in this class. If you’re interested in learning more about data hardware or software breakpoints, further information can be obtained from the docs folder inside of the MPLAB X IDE installation directory or through more in-depth courses available through Microchip University.
At the time of this class’s development the PIC18F56Q71 Curiosity Nano board doesn’t support software breakpoints at all and are disabled. 

SW breakpoints
Click image to enlarge.
The properties of a breakpoint can be examined by right-clicking on the breakpoint, choosing the (MPLAB X) Breakpoint option, and then properties.
breakpoint properties
Click image to enlarge.
The properties dialog reveals a few details, including the specific line number at which the breakpoint will be triggered and a pass count feature. This feature allows us to configure the breakpoint to activate either upon every occurrence of a condition or only after the line has been executed a predetermined number of times, which can range from zero to 255. For our purposes here, you will set the breakpoint to activate consistently at this line and confirm by clicking OK.
breakpoint properties
Click image to enlarge.

You can restart program execution by clicking on the Continue button continue in the debugger toolbar.

As mentioned earlier, code execution should stop again at that line where the breakpoint is since this function is called every time through the while loop as the UART RX buffer is checked or in other words polled.

Remove the breakpoint by clicking on it in the left side margin and return to main() inside of the main.c source file.

Inside of the if statement that checks the UART2_IsRxReady(), again hold CTRL and click on the UART2_Read() to go to the definition inside of uart2.h. Another way to find how various macros and functions are used is to search the project. Right-click on the UART2_Read to the right and select Find Usages which will open the Find Usages dialog to search all instances of the UART2_Read macro inside of the project when Find is clicked. 

uart2_read function

uart2.h code
Click image to enlarge.
This will open a Search Results window at the bottom of the screen. The Search Results will show every instance or mention of UART2_Read in the entire project excluding comments but you can search those as well by enabling here.
find usages output
Click image to enlarge.
At the bottom of the IDE you can see something that looks like a function inside of the uart2.c file. Go ahead and double-click on that to go directly to that line of code inside of the uart2.c source file. What this function does is simply return the contents of the UART2 Receive Buffer register U2RXB. Add a breakpoint next to the return noting that program execution doesn’t stop.
uart2_read defined
Click image to enlarge.

Return to Data Visualizer now and type anything into the terminal window. The debugger will halt program execution and bring me right to uart2.c file so you can confirm that the return is about to be executed.

Clicking on a button inside of the debugger toolbar called Step Into step into will execute the return line and then stop execution. The green arrow moves to the next line in code which is the curly brace to indicate what bit of code is going to be executed next.

green arrow moves to next line
Click image to enlarge.

Clicking the step into button again moves to the next line of code to be executed which is the next if statement inside of the main function. The debugger will display the file that the current line of code being executed is located in.

I’ll click on the continue button and enter another character into the data visualizer terminal. Program execution stops again at the breakpoint.

Back to Top

Viewing Variable and Register Values with the Watches Window

Now I’m going to use a debugging feature called the Watches window. This window opened automatically when this debug session was started. Otherwise, you could also open the window by selecting Window > Debugging > Watches.
Click image to enlarge.

The Watches window lets me monitor and evaluate the values of variables, registers, or expressions in real time as the program runs. It is used to see how data changes, enables the modification of variable values on the fly, and assists in testing program behavior under various conditions without altering the source code. Again, a complete discussion is in the resources mentioned earlier should you like to do more elaborate debugging.

Here it will be used to confirm the character typed in the terminal is what the microcontroller received. Since UART2_Read() returns the value in the U2RXB register look for that in the Watches window. Here it is second from the top and referring over here on the right you can see that one of the ways the data inside of the register is relayed is as a character which is the same character typed into the Data Visualizer.
Click image to enlarge.

Looking at some of the other buttons in the debugger toolbar, next to the Continue button continue is the Reset button reset which is used to Reset the program back to the beginning of code execution which is the state the Microcontroller will be in when it is either powered on or a RESET condition has occurred. The reset condition is outside of the scope of this class, but a detailed discussion can be found in the device datasheet.

Next to the RESET button is the Pause button pause which stops code should you wish to see what line is currently executing.

Now, the way this demo is configured is that when the UART receives an upper-case letter ‘T’, then a conditional statement calls a macro which will Toggle the LED on the Curiosity Nano board ON/OFF by driving the associated pin LOW or HIGH.

Click on the Continue button continue to begin program execution once again.

Inside the Data Visualizer enter an upper case ‘T’. Again, code execution is halted at the return in the UART2_Read().
Data Visualizer
Click image to enlarge.
Click on the Step Into button step into until you are back in the main(). You can see here in the Watches window that the Microcontroller has indeed received the upper case ‘T’. Clicking on the step into button again you can see that since the condition is true the LED_Toggle() macro is called and the LED on the Curiosity Nano board toggles ON or OFF depending on the state it was in previously.
arrow in while 1 loop
Click image to enlarge.

Next, check the if statement that handles what happens when the switch is pressed. As previously mentioned, when the switch is pressed, the voltage level on the associated pin changes from a HIGH (3.3V) to LOW (0V). When this happens the UART2_Write() is called and an upper case ‘S’ is sent.

To visualize this, first get rid of the breakpoint in the UART2_Read() and set a breakpoint in the main() next to the UART2_Write().

breakpoint moved
Click image to enlarge.
Clicking on the Continue button continue again to continue code execution, press the switch on the Curiosity Nano board once more to halt code execution at the new breakpoint.
arrow at breakpoint
Click image to enlarge.
Hitting the Step Into button step into jumps code execution to the uart2.c file to the UART2_Write(). Clicking the step into button a couple more times loads the UART2 Transmit Buffer (U2TXB) with the value sent as an argument in the function, the upper case ‘S’. You can confirm this again in the Watches window in the Char column of the U2TXB register instance.
watches window
Click image to enlarge.

Now, you will notice that the upper case ‘S’ does not appear in the Data Visualizer terminal. This is because the character is transmitted over the UART and subsequently the USB both of which have timing requirements. Obviously, clicking the step into button will break those requirements.

Clicking the Step Into button step into again will take program execution into the DELAY_milliseconds() routine inside of the delay.c file.
This code will execute a while loop that decrements the milliseconds variable by 1 each time through the loop executing a macro called __delay_ms(1) until it is zero at which point it will exit the loop and return.

Now you could hit the step into button 349 more times if you wanted to. However, in the interest of time I’m just going to click on the Step Out button step out which will continue executing the program until the current function completes and returns to the calling function in main() or in software terms allow me to quickly exit the current scope and resume execution at the point where the function was called. This is a way to exit a large function should you only wish to see some of it’s execution.

next step
Click image to enlarge.
There’s also a Step Over button step over here. RESET the debugger and then press the switch again. You can hit the Step Over button twice which basically allows me to jump over function calls executing the code in the function without actually having to go into the function code.
green arrow at uart2_write function
Click image to enlarge.

The toolbar also includes advanced features such as Run to Cursor run to cursor, Set Program Counter at Cursor set program counter at cursor, and Focus Cursor at Program Counter Focus Cursor at Program Counter. While these functions extend beyond elementary debugging, comprehensive explanations of their capabilities can be found in the MPLAB X IDE documentation. 

Back to Top

Common Debugging Problems

There is such a thing as a broken breakpoint. So, for example, if you go to the main.c source file and try and create a breakpoint at the while loop here. You’ll notice that the breakpoint icon in the left margin is broken.

broken breakpoint

The reason for this is that breakpoints are applied to the associated machine code instruction which is the very low-level language that any microcontroller understands. Programs like C and Assembly essentially translate human language into machine code. As part of the translation process there is additional information created as part of the C language to make it more readable to us humans which doesn’t really translate directly into a specific machine code instruction. A while statement is one of those translation mismatches. To overcome this situation, a common practice is to use what’s called a NOP instruction.

This is a machine language instruction that basically does nothing. However, you can place a NOP instruction right after the line of code generating the broken breakpoint as a workaround. Note that the project needs to be rebuilt for debug as this line of code needs to be added into hardware’s program memory.  

use a nop to fix a broken breakpoint

Another issue can occur with regards to software breakpoints. If using a tool such as the Curiosity Nano board here, that doesn’t support software breakpoints, and if you try to enable software breakpoints you will get an error when you try and debug the project. Here you could also unintentionally enable software breakpoints. An example of this would be that I’ve added more breakpoints into the current debug session than supported by the tool being used. A dialog will open giving the option to enable software breakpoints.
SW breakpoint preference dialog
Click image to enlarge.
If you click Yes, software breakpoints would then be enabled and the next time you press the Continue button the debug communication would be ended and the project exits debug mode.
SW breakpoint preference dialog
Click image to enlarge.

To fix this, you would then need to go into the project properties by clicking the Project Properties button Project properties in the Dashboard and disable software breakpoints.

In this case by selecting the PKOB listing under the Configuration listing in the Project Properties dialog, then Debug Options from the Option Categories drop-down and deselecting the Use Software Breakpoints radio button and then clicking Apply. Keep in mind to continue debugging, you will need to disable the extra breakpoints.

disable sw breakpoints
Click image to enlarge.
Another common error for not being able to debug is not having setup the device configuration bits properly. An example of this could be the clock source for the application has been configured to use an external clock source when you may be expecting to use an internal clock source, perhaps Brown-out Reset has been enabled and the reset voltage is greater than the device’s actual supply voltage and other conditions. Be aware that some devices may default to these conditions so Configuration bit settings or even clock control settings should always be checked in situations where failure to debug the application isn’t immediately apparent.
configuration bits
Click image to enlarge.