Random Nerd Tutorials
Shares

Better Debugging for Arduino IDE using Software Debugger (Part 2)

Shares
This tutorial was written by João Lopes and edited by Sara Santos.

The SerialDebug library created by João Lopes allows you to improve debugging for the Arduino IDE. In this article he’ll show you how to use the simple software debugger of the SerialDebug library that has most functionalities of an hardware debugger.

This is part 2 of a series of articles about the SerialDebug library. You can check all the articles on the links below.

Simple Software Debugger

When I (João) was developing in ESP-IDF (ESP32 native SDK), I used an hardware debugger, using external hardware compatible with JTAG, GDB server and Eclipse CDT. This is a great solution for debugging because I can see the value of variables, set breakpoints (up to 2 for ESP32), run code step by step, and more.

However, until now Arduino IDE doesn’t support hardware debugger. If you’d like to use hardware debugger, you need:

  • An external hardware (JTAG, Atmel-Ice, etc)
  • Another IDE (Eclipse, Atmel studio, etc)
  • Skills to configure and use it

There are simpler solutions, but these are not free, like the MicroStudio. That’s why there are such few people using debugger in Arduino IDE. Most of all debugging in Arduino IDE is done with Serial.print commands.

When I was developing SerialDebug library, I thought how to bring some hardware debugger features to Arduino without the need for extra hardware and skills. The SerialDebug library has a simple software debugger. Let’s take a closer look at its features:

  • Simple: It’s a simple, but functional debugger. It doesn’t have all features of a real hardware debugger (like the ability to run code step by step).
  • Software: It’s implemented in software, not in hardware as a real hardware debugger. But it is optimized to reduce memory and overhead of processing. For this reason, this feature in SerialDebug library starts disabled until receiving the command “dbg”.
  • Debugger: It is a debugger, you can send commands in the Serial Monitor such as:
    • Call a function (works if debugger is enabled or disabled)
    • Show and change values of global variables (works only if debugger is enabled)
    • Add or change watches for global variables (works only if debugger is enabled)

Note: due to program memory limitations, the simple software debugger doesn’t run in low memory boards such as Arduino Uno. But you can try the debugger with this board by disabling DEBUG_MINIMUM mode, just comment line 64 of SerialDebug.h.

How to use Simple Software Debugger

First, follow the steps in Part 1 of this series to install SerialDebug library. Then, open the advanced example. In the File menu, select Examples > SerialDebug > SerialDebug_Advanced.

Then select Avr for Arduino with AVR arch, like Uno, Leonardo and Mega. Or Others for Arduino as Due, MKR, Teensy, Esp8266 and Esp32. For this post, we use ESP32 board and the example in the “Others” directory.

The following code should open:

Setup Codes for the Debugger

To call functions via Serial Monitor, or SerialDebugApp (see it in Part 3), you have to set the debugger in the setup() function of your sketch. For example:

// Add functions that can be called from SerialDebug

if (debugAddFunctionVoid("benchInt", &benchInt) >= 0) {
  debugSetLastFunctionDescription("To run a benchmark of integers");
}

if (debugAddFunctionVoid("benchFloat", &benchFloat) >= 0) {
  debugSetLastFunctionDescription("To run a benchmark of float");
}

if (debugAddFunctionVoid("benchGpio", &benchGpio) >= 0) {
  debugSetLastFunctionDescription("To run a benchmark of Gpio operations");
}

if (debugAddFunctionVoid("benchAll", &benchAll) >= 0) {
  debugSetLastFunctionDescription("To run all benchmarks");
}

if (debugAddFunctionVoid("benchSerialPrints", &benchSerialPrint) >= 0) {
  debugSetLastFunctionDescription("To benchmarks standard Serial debug");
}

if (debugAddFunctionVoid("benchSerialDebug", &benchSerialDebug) >= 0) {
  debugSetLastFunctionDescription("To benchmarks SerialDebug");
}
if (debugAddFunctionVoid("benchSerialDbgPr", &benchSerialDbgPr) >= 0) {
debugSetLastFunctionDescription("To benchmarks SerialDebug print macros");
}

if (debugAddFunctionVoid("benchSerialAll", &benchSerialAll) >= 0) {
  debugSetLastFunctionDescription("To benchmarks all Serial");
}

if (debugAddFunctionStr("funcArgStr", &funcArgStr) >= 0) {
  debugSetLastFunctionDescription("To run with String arg");
}

if (debugAddFunctionChar("funcArgChar", &funcArgChar) >= 0) {
  debugSetLastFunctionDescription("To run with Character arg");
}

if (debugAddFunctionInt("funcArgInt", &funcArgInt) >= 0) {
  debugSetLastFunctionDescription("To run with Integer arg");
}
cloud-download

You can set it in a shorter way with no descriptions:

debugAddFunctionVoid("benchInt", &benchInt);

Note: in the current version, it only supports functions with the following requirements:

  • Without any argument (void)
  • Or one argument of type int, char or String

Tip: you can create a generic function with one argument for debugging purpose. Then, you can call that function and pass different arguments to test it.

The previous code sets functions that can be called with an “f” command:

Note 1: command “f” without arguments lists all available functions.

Note 2: for this post, we use the ESP32 with the full features of SerialDebug, because it has enough memory.

To call the first function, just type the command: f 1

To call a function that requires an argument, you can write a command as follows “f 11 123“. In this case, the function 11 has received the value 123 as argument.

Show/Change Global Variables Values

To use global variables in the debugger, you need to set them in the setup() function:

// Add global variables that can showed/changed from SerialDebug
// Note: Only global, if pass local for SerialDebug, can be dangerous

if (debugAddGlobalUInt8_t("mRunSeconds", &mRunSeconds) >= 0) {
  debugSetLastGlobalDescription("Seconds of run time");
}
if (debugAddGlobalUInt8_t("mRunMinutes", &mRunMinutes) >= 0) {
  debugSetLastGlobalDescription("Minutes of run time");
}
if (debugAddGlobalUInt8_t("mRunHours", &mRunHours) >= 0) {
  debugSetLastGlobalDescription("Hours of run time");
}

// Note: easy way, no descriptions ....

debugAddGlobalBoolean("mBoolean", &mBoolean);
debugAddGlobalChar("mChar", &mChar);
debugAddGlobalByte("mByte", &mByte);
debugAddGlobalInt("mInt", &mInt);
debugAddGlobalUInt("mUInt", &mUInt);
debugAddGlobalLong("mLong", &mLong);
debugAddGlobalULong("mULong", &mULong);
debugAddGlobalFloat("mFloat", &mFloat);
debugAddGlobalDouble("mDouble", &mDouble);

debugAddGlobalString("mString", &mString);

// Note: For char arrays, not use the '&'

debugAddGlobalCharArray("mCharArray", mCharArray);

// Note, here inform to show only 20 characteres of this string or char array

debugAddGlobalString("mStringLarge", &mStringLarge, 20);

debugAddGlobalCharArray("mCharArrayLarge", mCharArrayLarge, 20);

Here’s some things you need to keep in mind when using the debugger show/change global variables values functionality:

  • It only works with global variables, defined out of any function, generally before all function definitions.
  • Don’t use this for local variables, because this will always be accessed, and if it is a local variable, it may not exist anymore, which would lead to invalid memory access.
  • You should have a function for each type of value, for example: debugAddGlobalString()
  • To help you migrating your code to use SerialDebug, there is a converter that reads your code and generates the setup() code for global variables automatically: SerialDebugConverter

List Global Variables

To list all global variables: just use command “g“. First, type “dbg” to activate the debugger and then use the “g” command  .

Note: to avoid overheads of processing of debugger, it starts always disabled. If enabled, the debugger processes watches, as you can see in the following figure:

By default, the debugger will stop for watches. If you don’t want this to happen, use the command “wa ns“:

Now, use the command “g” to list all global variables:

To change any global variable, e.g. the global number 07 – mInt(int), just type the command “g 7 = 10“:

Note: the debugger will ask a confirmation to change the value of the variable. You just need to append “y” in command: “g 7 = 10 y

You can use command “g ?“, to show help of commands for global variables.

Add/Change Watches for Global Variables

A watch is a good feature of any debugger. It observes a certain variable and shows a message if the variable has changed, or reached a pre-established condition. For example, imagine the following scenario:

  • We have a global variable mControl, that can’t be set to zero.
  • However, during the tests, it has received the value of zero.
  • To help detect this bug, we just add a watch with condition “mControl == 0“.
  • When it occurs, a watch is triggered, and we can see it in the Serial Monitor.
  • Analyzing the debug outputs before the watch allows us to determine the possible source of this bug.

Watches can be set in 2 ways:

  • In the setup() function (this watch isn’t lost if the device turns off or resets)
  • In the Serial Monitor or SerialDebugApp

Setting watches in setup() function

For the example SerialDebug_Advanced, we have the following:

// Watch -> mBoolean when changed (put 0 on value)
debugAddWatchBoolean("mBoolean", DEBUG_WATCH_CHANGED, 0);

// Watch -> mRunSeconds == 10
debugAddWatchInt("mRunSeconds", DEBUG_WATCH_EQUAL, 10);

// Watch -> mRunMinutes > 3// Watch -> mRunMinutes > 3
debugAddWatchUInt8_t("mRunMinutes", DEBUG_WATCH_GREAT, 3);

// Watch -> mRunMinutes == mRunSeconds (just for test)
debugAddWatchCross("mRunMinutes", DEBUG_WATCH_EQUAL, F("mRunSeconds"));

Note that the watch can be set for a specific condition or for when  (global) variable is changed.

Here’s the types of watches that can be used:

  • DEBUG_WATCH_CHANGED -> Changed value ?
  • DEBUG_WATCH_EQUAL -> Equal (==)
  • DEBUG_WATCH_DIFF -> Different (!=)
  • DEBUG_WATCH_LESS -> Less (<=)
  • DEBUG_WATCH_GREAT -> Greater (>)
  • DEBUG_WATCH_LESS_EQ -> Less or equal (<=)
  • DEBUG_WATCH_GREAT_EQ -> Greater or equal (>=)

To list running watches, use “wa“:

To show help for watch commands, use “wa ?” command:

Debug Macros

In Part 1, we’ve seen the print macros for SerialDebug library. It is like a standard Serial.print(). We can also use the debug macros with the powerful printf formatting, regardless of whether the board has Serial.printf native or not. As far as I know, only Espressif boards have Serial.printf native.

For example, this snippet:

Serial.print("*** Example - varA = ");
Serial.print(varA);
Serial.print(" varB = ");
Serial.print(varB);
Serial.print(" varC = ");
Serial.print(varC);
Serial.println();

Can be converted to a single command:

debugD("*** Example - varA = %d varB = %d varC = %d", varA, varB, varC);

And you can add more format parameters:

debugD("*** Example - varA = %02d varB = %02d varC = %02d", varA, varB, varC);

The”%02″ means: minimum of 2 digits, lead of zero, if needed (e.g 2 is shown as 02).

Comparing the different types of debug outputs:

  • print macros is easier to migrate, and there isn’t overhead (it is a simple macro for Serial.print command)
  • debug macros is more powerful, but it isn’t so easy to migrate, and it has an extra overhead (about 1% more)

It’s up to you, you can either use the simple print macro or the powerful printf of debug macro.

Wrapping Up

In this second article you’ve learned how to use the Simple software debugger commands of the SerialDebug library in the Arduino IDE to list and run functions, list and change global variables and set watches.

In Part 3, you can discover how to take the most out of these features using the SerialDebugApp.

Continue ReadingBetter Debugging for Arduino IDE: SerialDebugApp (Part 3)

Help me bring a better debug to the Arduino IDE using this library. Visit the GitHub page https://github.com/JoaoLopesF/SerialDebug, for more information, post issues and suggestions. Also, you can use the gitter chat room to share your feedback.

Thanks to Random Nerd Tutorials for the possibility of doing a post about SerialDebug library.

João Lopes

Random Nerd Tutorials has more than 200 free electronics projects and tutorials. Check them all in the next link: 200+ Electronics Projects and Tutorials

Learn ESP32 with Arduino IDE

This our complete guide to program the ESP32 with Arduino IDE, including projects, tips, and tricks! The registrations are open, so SIGN UP NOW »

Recommended Resources

Home Automation using ESP8266 »
Build IoT projects and home automation gadgets with the ESP8266 Wi-Fi module.

Build a Home Automation System »
Learn how to build a automation system using open-source hardware and software from scratch.

Arduino Step-by-Step Projects »
Build 25 cool Arduino projects with our course even with no prior experience!

Leave a Comment:

Grab our Electronics and Programming Courses