In this final part 6 of the Best Practices and Debug Tips chapter, you will learn more about probably the most valuable skill in your embedded toolbox...debugging your live code in hardware.


Debug Issues Like a Genius
The Simplicity Studio IDE’s most powerful feature is the ability to debug live programs running on your EFM32 device over a JTAG connection. With this tool, you are not limited to simply programming your embedded device and then hoping it works. Instead, you have introspection tools to interrogate the software as it runs as well as artificially alter the value of variables and memory for test coverage or to find hard-to-find bugs.


1. Build the project before running the debugger
Whenever you begin debugging your code, build the code before you go straight to debugging it. The debugger icon in the toolbar will do both steps at once and if your build has errors, the debugger will get launched after the failed build, which then results in confusing errors due to loading a project into the debugger that doesn’t exist.

2. Turn on all debugging features
Make sure that you are using a “Debug” build configuration by selecting the appropriate project from the drop down menu next to the debug icon in the toolbar.



Also ensure that your debug build has no optimizations (the default) and that the debug level is set to -g2 or -g3, as found in the Project > Properties > C/C++ Build Settings menu.

In order to get the maximum benefit out of your debug session, you will want to make sure that the debugger stops and breaks in whenever anything “bad” or unusual happens. Under the dropdown next to the debug button, click on Debug Configurations. In the Debug Configurations window, in the Exceptions tab, click on all of the types of exceptions that you would like the debugger to catch. These are not enabled by default.



3. Unlock debug access when the debugger refuses to load
Sometimes when you develop code for embedded applications, you do some bad things that can lock up your MCU early in the boot sequence. There is no OS or other supervisor in the system to rescue your program. When this happens, the normal flash routine over JTAG fails, and the debugger informs you that your debugger cannot start. You should do all of the obvious things like disconnecting the hardware from your computer and perhaps restarting your computer or the Simplicity Studio IDE, but if that doesn’t fix it, you can try unlocking debug access to your MCU.

The Unlock Debug Access option in the Flash Programmer tile will erase the contents of the flash memory on your MCU and leave the MCU in a state that allows it to be flashed with programming again. Just beware that if you flash it again with bad programming that does something bad early in the boot sequence, you will need to unlock debug access again.

Just remember that you have to make sure that your Detected Hardware is set to Detect Target Part in the main Simplicity Studio home screen, and that your Debug Mode is set to MCU or Out in the Kit Manager in order for the Flash Programmer to display the proper options. See the Connect to your own PCB over JTAG as if it were a Starter Kit section in this chapter for more information on how to connect to your hardware, whether it is a Starter Kit or your own hardware board.



4. Use a blank project to fix debugger launch issues
Whenever you are starting a new project, build a completely empty_project.c first. Then, launch the debugger on that empty file to prove that all of the connections between the host computer and EFM32 hardware platform are working. Sometimes, you will get mismatches between the type of EFM32 part detected versus the type of EFM32 part that is specified in your project. Solve that problem before you try to add your own code and library files to the mix.

Once you have your code up and running, sometimes things just break and your code refuses to load into your hardware. After you have tried to unlock debug access and other hardware tricks in the previous steps, your problem might lie in the project settings. Something could have changed in your environment. Load an empty project and see if you can get it to work again, without any of your own code. If it works, then the problem is buried somewhere in your project settings. You can choose to hunt down the problem in your existing project or create a new project and copy all of your source files into the new project, and then modify the Project Properties settings of the new project to match that of the old project. This process should get you back in business.

5. Write code to help the debugger
When developing code to be used during a debugging session, there are some things that you can do to help make the process easier:

  • Define arrays that you want to examine in watch windows with constants. If the debugger can tell how big an array is at compile time, it can offer you the appropriate number of elements to be able to inspect them. Otherwise, you will have to manually add a range of indices for the debugger watch window every time you launch the debugger.
  • Use a volatile variable to control execution of a block of code that you don’t want the MCU to automatically execute every time. If you are debugging some code that erases a big chunk of flash memory or some other dangerous code right at startup, it will do it every time the MCU boots. You can prevent it from doing that by encapsulating the dangerous code inside block of code that will only execute if you use the debugger features to make it happen. For example:
    volatile int i=0;
    if (i)
    // Do something dangerous, which could reset the system, etc.

    Put a breakpoint on the if statement, then “Move to Line” in the debugger to move to the first line inside the if statement that you want to execute. Then on subsequent resets, the code inside the if statement will never execute. The volatile declaration of i prevents the compiler from optimizing away the block of code from within the if statement.
  • Don’t use identical variable names at multiple scopes within a module. The debugger can get confused and show you the wrong value when you hover your mouse on a variable in a live debugging session. If you have a global variable named foo and a local variable named foo, the debugger doesn’t always show you the correct value of the foo variable based on the scope. Keep variable names unique and the debugger will give you the proper value of your inspected variables.
  • Don’t set breakpoints or play around too much in the IDE while your project is running live and Simplicity Studio is looking for a breakpoint. This can alter the timing of your code and cause it to miss interrupts. Keep your hands off the IDE as much as possible until a breakpoint is reached.
  • If you have trouble setting interrupts on a line of code, that means that either you have too many breakpoints already set in the current project, or the compiler thinks that the code that you are trying to break on is unreachable. For example, an if (0) statement will never execute, and the compiler knows this. Disable all other breakpoints, restart the IDE if necessary, clean the project and rebuild. Then, try setting breakpoints again.

6. Attach to running instances without resetting the MCU
If you are running a project on your own hardware that was loaded with a Debug configuration, you can attach to it and inspect it without resetting the device. This is particularly helpful if your system has mysteriously locked up and you want to get in there to inspect things without starting a new debugging session. Note that sometimes the act of connecting the JTAG cables from the Starter Kit to your custom project can cause a reset. You may be able to mitigate this issue by grounding your custom project to your Starter Kit first with a jumper cable, and then connecting the JTAG cable, or just leave the JTAG cable attached throughout the test process.

In order to attach to a running project, select Run > Attach to menu option and then select the correct project. Note that the version of software running on your target hardware and the project selected from the Attach to menu must be the same version or you will get nonsensical results. Once code compiles and the debugger attaches, you may have to double-click on the entry in the top left window, called Silicon Labs ARM MCU: etc., expand the dropdown icon next to it, and then find the <project>.axf file. Once you find that and highlight it, you can press the pause button or set breakpoints in your code as if you had launched it from the debug tool.


7. Force activity on a configured peripheral
When you are configuring a peripheral for the first time, it can be a struggle to get to that first glimmer of activity that confirms that you have the right configuration of GPIO pins, route, peripheral and that you have properly interpreted all of the necessary instructions to get everything going. In summary, here are the steps necessary to enable any peripheral in an EFM32 device:
1) Enable the GPIO clock
2) Enable the peripheral clock (i.e USART, I2C, DAC, etc.) and other necessary clock sources
3) Configure and enable the peripheral
4) Route the pins used by the peripheral through to the GPIO
5) Configure the pins used by the peripheral in the GPIO (i.e. push pull, input, etc.)

To be sure that the GPIO pins for your chosen peripheral are connected to the appropriate place in your hardware as you expect, you can create an empty project and simply set or clear the GPIO. Verify that you see the change on an oscilloscope or multimeter. Once you are sure that you are connected to the correct GPIO pins, you can resume debug of the peripheral you are trying to program. If you are like me, you forgot to enable the peripheral clock.

8. Don’t use print statements over UART to debug timing issues
Debugging on an MCU is a tricky business because the limited resources means that any attempts to observe the system will impact the performance of the system. The timing changes caused by debug print statements can cause the issue that you are trying to find to disappear. You don’t have the luxury of multiple cores and deep memory resources to handle such debug printing in the background.

You can mitigate the timing effects of debug print statements by utilizing a print buffer with an interrupt-based mechanism. If you were to simply place a character from a print statement onto the UART, then wait for the character to complete before placing another character on the UART, you will slow down execution of your single-threaded embedded application. By utilizing a ring buffer and interrupt to feed the UART, you will lessen the impact your embedded application.

If a UART is not available for debug output or if the time spent servicing the UART print statements are still causing timing issues, you can place all of your debug print statements in a debug buffer and examine the contents of the buffer through the Simplicity Studio debugger. Simply examine the value of your print buffer in the debugger and it will automatically translate your buffer to ASCII text, allowing you to see whatever message was transferred into it before your error occurs.

Finally, toggling different GPIO pins in various places in the code can help illustrate the flow of the program and if things are executing in the order you expect. You can connect those GPIOs to LEDs for events that happen slowly enough to observe or to an oscilloscope, which will provide precise timing information.

I hope that this guide has helped you gain a better understanding about how to set up, develop, and debug your project for success. Don’t get discouraged if you run into a lot of issues. Embedded development is never easy. It requires a serious investment of effort and time to get it right. There are opportunities everywhere to learn something new. Stick with it, and then gloat over your amazing accomplishment!

  • Blog Posts
  • Makers