SerialDebug Library for Arduino
Improved serial debugging to Arduino, with with debug levels and simple software debugger, to see/change global variables, to add watch for these variables, or call a function, in runtime, using serial monitor or SerialDebugApp.
Note: This image is from the tutorial for this library at randomnerdtutorials.com - Image by Sara Santos
Contents
- About
- News
- Beta version
- Github
- Benefits
- SerialDebugApp
- How it looks
- Commands
- Install
- Usage
- Options
- Watches
- Install
- Converter
- Khow issues
- Releases
- Links
- Thanks
About
Generally debugs messages in Arduino is done by Serial.print*, and show in this monitor serial, or another serial tool, as screen, pyserial, coolterm, etc.
The Arduino official IDE, not have debugger. Only way, until now, is using a another IDE, that can be paid, and a hardware debugger, as JTAG.
SerialDebug is a library to Improved serial debugging and simple software debugger to Arduino.
Yes, now we can use one debugger, simple but functional, and not need a extra hardware to do it.
News
2018-11-16
- Now the SerialDebug print macros support the second argument of Serial.print. Thanks to @wjwieland to open a issue about this. E.g.: printlnA(10, HEX);
2018-10-26
-
Now the SerialDebugApp show debugger elements on screen, please update to 0.9.2 to see it in action
-
In library version >= 0.9.75, have a new minimum mode, to limit to only output debugs, no debugger, no printf, no extra functions or processing on library. And by default, low memory boards, as Uno, is in minimum mode.
Beta version
This is a beta version. Not yet fully tested, optimized, and documented.
This is a previous documentation. It will be better documented before first RC version.
Github
Contribute to this library development by creating an account on GitHub.
Please give a star, if you find this library usefull, this help a another people, discover it too.
Please add a issue for problems or suggestion.
And please join in the Gitter chat room (SerialDebug on Gitter).
Benefits
SerialDebug is bether than Arduino default serial debugging:
-
This is optimized for features that it have
The initial status of SerialDebug is inactive, where no normal debug outputs, and no CPU waste time for debugs. Well, there may not be anyone seeing it. It is good for not always USB connected project, as one powered by battery or external power supply.
Only messages that are processed and displayed, are of type Error or Always (important ones).
After first command received, SerialDebug will become active
All routines to show debug is a C/C++ precompiler macros, so no extra functions calls, only Serial.print*
Except when use printf formatter (debug* macros), for boards that no have it native.
For simple software debugger, have memory optimizations:
-
No fixed arrays, is used C++ Vector to dynamic arrays
-
Is used void* pointer to store values, when it is need. Is more complicate, but it dramatically reduces use of memory, compared to store 17 variables for support 17 kinds of this.
Note: due a extra overhead in processing simple software debugger, it starts disabled. You can enable when you need (dbg command)
Now (>= 0.9.5) SerialDebug have a new performance, compared with standard print, no difference for print* macros and about slower only 1% with debug* macros (due printf processing). Boards tested: Uno, Mega, Due, Esp8266 and Esp32.
To reproduce it, just upload the advanced example, and call function 8 (just command: f 8) to run all benchmarks of serial.
Now (>= 0.9.74) SerialDebug have a mode mininum, for boards with low memory to program, as Arduino UNO, if this mode is set, SerialDebug only show messages, no extra functions or processing
In future versions, the SerialDebug will more otimized, for CPU and memory
-
-
It is good for any Arduino
Is good for new boards, that have good CPU and memory, like Espressif (ESP8266 and ESP32) and ARM arch (Due, Teensy, etc.).
But it runs in older Arduino, as UNO, Leonardo, Mega, ...
In UNO or similar (e.g. Leonardo), some features as Watches in debugger is not implemented, due full library is huge for it (more than 5k lines of code, without comments).
For the Mega, some features are reduced, but have watches.
If debugger is disabled in code, or in mode mininum (default for low memory boards), SerialDebug in Uno, consumes only about 50 bytes of memory. And it not fully otimized yet.
The default speed of serial is 250000, for Espressif, ARM or Mega boards and 115200 for UNO, Leonardo, etc.
Only exception is boards with Tiny* AVR MCU, due it not have CPU and memory to this library.
-
Have debug levels
During the development, we can put a lot of debug messages...
But with SerialDebug, we can put a level in each one.
For all messages (except any (debug*A) or error (debug*E)), the message only is processed and showed, if debug level is equal or higher than it level
SerialDebug have 7 debug levels, in order of priority:
-
Alway showed:
- Error: Critical errors
- Always: Important messages
-
No debug:
- None: No debug output
-
Another levels (showed if level is equal or higher that actual one):
- Warning: Error conditions but not critical
- Info: Information messages
- Debug: Extra information
- Verbose: More information than the usual
So We can change the level to Verbose, to see all messages. Or to Debug to see only debug or higher level, etc.
Is very good to reduce a quantity of messages that a project can generate, in serial monitor.
-
-
It is easy to migrate
From the 0.9.5 version, SerialDebug have a new macros to help migrate more easy
For example: the code extracted from www.ladyada.net:
Serial.println("Here is some math: "); Serial.print("a = "); Serial.println(a); Serial.print("b = "); Serial.println(b); Serial.print("c = "); Serial.println(c); Serial.print("a + b = "); // add Serial.println(a + b); Serial.print("a * c = "); // multiply Serial.println(a * c); Serial.print("c / b = "); // divide Serial.println(c / b); Serial.print("b - c = "); // subtract Serial.println(b - c);
Only replace Serial.println to printlnD and replace Serial.print to printD Note: this order of replaces is important. Note: D is to debug level, can be another, as V to verbose
printlnD("Here is some math: "); printD("a = "); printlnD(a); printD("b = "); printlnD(b); printD("c = "); printlnD(c); printD("a + b = "); // add printlnD(a + b); printD("a * c = "); // multiply printlnD(a * c); printD("c / b = "); // divide printlnD(c / b); printD("b - c = "); // subtract printlnD(b - c);
SerialDebug has a converter to help migrate your Arduino codes, from Serial.prints to this library.
-
Have printf support to serial
Regardless of whether the board has it native or not. That I know, only Espressif boards have it native
For example:
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);
Improving the example of previous topic, w/ debug with printf formatter:
debugD("Here is some math: "); debugD("a = %d", a); debugD("b = %d", b); debugD("c = %d", c); debugD("a + b = %d", (a + b)); // add debugD("a * c = %d", (a * c)); // multiply debugD("c / b = %d", (c / b)); // divide debugD("b - c = %d", (b - c)); // subtract
Note: 50% less code
Note: With debug* macros, SerialDebug follows the same concept, that modern debug/logging messages model, as ESP-IDF, Android, iOS, etc. In these, each call generates a formatted output line.
Note: this is not for low memory boards as Arquino UNO
-
Have auto function name and simple profiler
A simple debug:
debugV("* Run time: %02u:%02u:%02u (VERBOSE)", mRunHours, mRunMinutes, mRunSeconds);
Can generate this output in serial monitor:
(V p:^3065 loop C1) * Run time: 00:41:23 (VERBOSE)
Where: V: is the level p: is a profiler time, elased, between this and previous debug loop: is a function name, that executed this debug C1: is a core that executed this debug (and a function of this) (only for ESP32) The remaining is the message formatted (printf)
Note how printf is powerfull, %02u means a unsigned integer with minimum lenght of 2, and leading by zeros
For ESP32, the core id in each debug is very good to optimizer multicore programming.
-
Have commands to execute from serial monitor
SerialDebug takes care of inputs from serial, and process predefined commands
For example:
-
Show help (?)
-
Change the level of debug (v,d,i,w,e,n), to show less or more messages.
-
See memory (m)
-
Reset the board (reset)
See about SerialDebug commands below.
-
-
Have a simple software debugger
This starts disabled, to avoid extra overhead.
If enabled (dbg command), you can command in serial monitor:
- Show and change values of global variables
- Call a function
- Add or change watches for global variables
It not have some features than a real hardware debugger, but is good features, for when yet have none of this ... It is for when not have a real hardware debugger, e.g. GDB w/ JTAG, or not have skill on it.
It can be disabled (no compile), if not want it.
See about this below.
Note: this is not for low memory boards as Arquino UNO
-
Ready for production (release compiler))
For release your device, just uncomment DEBUG_DISABLED in your project Done this, and no more serial messages, or debug things. A And better for DEBUG_DISABLED, SerialDebug have ZERO overhead, due is nothing of this is compiled
SerialDebugApp
SerialDebugApp is a freeware desktop app, companion for this library.
Note: this library is not depending on this software, you can use default serial monitor of Arduino IDE, or another program.
Why this app is an freeware and not open source ?
This app is based in my another commercial projects, through the serial port, control devices with the Arduino.
SerialDebugApp is a serial monitor, made for this library:
-
Show debug messages with different colors, depending on each level.
-
Have buttons to most of commands of SerialDebug
-
And more, filter, converter, auto-disconnection, etc.
SerialDebugApp page: SerialDebugApp
How it looks
SerialDebug in Arduino serial monitor:
SerialDebug in SerialDebugApp:
SerialDebugApp: new version, with simple software debugger on screen:
Commands
SerialDebug takes care of inputs from serial, and process predefined commands as:
? or help -> display these help of commands
m -> show free memory
n -> set debug level to none
v -> set debug level to verbose
d -> set debug level to debug
i -> set debug level to info
w -> set debug level to warning
e -> set debug level to errors
s -> silence (Not to show anything else, good for analysis)
p -> profiler:
p -> show time between actual and last message (in millis)
p min -> show only if time is this minimal
r -> repeat last command (in each debugHandle)
r ? -> to show more help
reset -> reset the Arduino board
f -> call the function
f ? -> to show more help
dbg [on|off] -> enable/disable the simple software debugger
Only if debugger is enabled:
g -> see/change global variables
g ? -> to show more help
wa -> see/change watches for global variables
wa ? -> to show more help
Not yet implemented:
gpio -> see/control gpio
For simple software debugger:
- For functions:
- To show help: f ?
- To show: f [name|num]
- To search with start of name (case insensitive): f name
- To call it, just command: f [name|number] [arg]
- Enable/disable the debugger:
- To enable: dbg on
- To disable: dbg off
Note: the debugger starts disabled, to avoid extra overhead to processing it
You can enable it when need
- For global variables:
- To show help: g ?
- To show: g [name|num]
- To search by start of name: g name
- To change global variable, g [name|number] = value [y]
note: y is to confirm it (without confirm message)
- For watches:
- To show help: wa ?
- To show: wa [num]
- To add: wa a {global [name|number]} [==|!=|<|>|<=|>=|change] [value] [as]
notes: change not need a value, and as -> set watch to stop always
- To add cross (2 globals): wa ac {global [name|number]} [=|!=|<|>|<=|>=] {global [name|number]} [as]
- To change: wa u {global [name|number]} [==|!=|<|>|<=|>=|change] [value] [as]
- To change cross (not yet implemented)
- To disable: wa d [num|all]
- To enable: wa e [num|all]
- To nonstop on watches: wa ns
- To stop on watches: wa s
Notes:
- watches is not for low memory boards, as Uno.
- memory and reset, yet implemented only to AVR, Espressif, Teensy, and ARM arch (e.g. Arduino Due).
Install
Just download or clone this repository.
Or for Arduino IDE, you can use the library manager to install and update the library.
For install help, please click on this:
For another IDE, or not using the library manager of Arduino IDE, I suggest you use a Github Desktop app to clone,it help to keep updated.
Usage
Note: In some boards, after upload if you see only dirty characteres in serial monitor, please reset the board. There is possibly some glitch in the serial monitor of Arduino
examples
Please open the examples to see it working:
- Basic -> for basic usage, without debugger
- Advanced/Avr -> for Arduino AVR arch. uses F() to save memory
- Advanded/Others -> for new boards, with enough memory,
not use F(), due RAM is more faster than Flash memory
- Disabled -> example of how disable features, or entire SerialDebug
Note: for low memory boards, as UNO, please open only the basic example.
To add SerialDebug to your Arduino project:
include
Place it, in top of code:
#include "SerialDebug.h" //https://github.com/JoaoLopesF/SerialDebug
setup
Setup code is only necessary for debugger elements. As this library not uses a hardware debugger, this codes are necessary to add this elements, into "simple software debugger" of SerialDebug.
For example, for functions:
// Add functions that can 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("benchSerial", &benchSerial) >= 0) {
debugSetLastFunctionDescription("To benchmarks standard Serial debug");
}
if (debugAddFunctionVoid("benchSerialDebug", &benchSerialDebug) >= 0) {
debugSetLastFunctionDescription("To benchmarks SerialDebug");
}
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");
}
Or short use, if you not want descriptions:
// Add functions that can called from SerialDebug
debugAddFunctionVoid("benchInt", &benchInt);
debugAddFunctionVoid("benchFloat", &benchFloat);
debugAddFunctionVoid("benchGpio", &benchGpio);
debugAddFunctionVoid("benchAll", &benchAll);
debugAddFunctionVoid("benchSerial", &benchSerial);
debugAddFunctionVoid("benchSerialDebug", &benchSerialDebug);
debugAddFunctionStr("funcArgStr", &funcArgStr);
debugAddFunctionChar("funcArgChar", &funcArgChar);
debugAddFunctionInt("funcArgInt", &funcArgInt);
debugSetLastFunctionDescription("To run with Integer arg");
Note: If it is for old boards, as UNO, Leornardo, etc. You must use F() to save memory:
// Add functions that can called from SerialDebug
debugAddFunctionVoid(F("benchInt"), &benchInt);
Notes: It is too for all examples showed below
For global variables (note: only global ones):
// Add global variables that can showed/changed from SerialDebug
// Note: Only globlal, 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);
// For arrays, need add for each item (not use loop for it, due the name can not by a variable)
// Notes: Is good added arrays in last order, to help see another variables
// In next versions, we can have a helper to do it in one command
debugAddGlobalInt("mIntArray[0]", &mIntArray[0]);
debugAddGlobalInt("mIntArray[1]", &mIntArray[1]);
debugAddGlobalInt("mIntArray[2]", &mIntArray[2]);
debugAddGlobalInt("mIntArray[3]", &mIntArray[3]);
debugAddGlobalInt("mIntArray[4]", &mIntArray[4]);
And for watches (not for low memory boards, as UNO):
// Add watches for some global variables
// Note: watches can be added/changed in serial monitor too
// Watch -> mBoolean when changed (put 0 on value)
debugAddWatchBoolean("mBoolean", DEBUG_WATCH_CHANGED, 0);
// Watch -> mRunSeconds == 10
debugAddWatchUInt8_t("mRunSeconds", DEBUG_WATCH_EQUAL, 10);
// Watch -> mRunMinutes > 3
debugAddWatchUInt8_t("mRunMinutes", DEBUG_WATCH_GREAT, 3);
// Watch -> mRunMinutes == mRunSeconds (just for test)
debugAddWatchCross("mRunMinutes", DEBUG_WATCH_EQUAL, "mRunSeconds");
loop
- In the begin of loop function
// SerialDebug handle
// NOTE: if in inactive mode (until receive anything from serial),
// it show only messages of always or errors level type
// And the overhead during inactive mode is very much low
debugHandle();
converter
Note: Has a converter to do it for You: SerialDebugConverter
How use SerialDebug macros
Instead Serial.print*, use print* or debug* macros:
print*: easy to migrate and use, just replace each Serial.print for this
println*: same, but add a new line on output
debug*: w/ powerfull printf formatter, one command generate one serial line
See example of how convert it, in Benefits topic below.
Using macros to show debug:
- For always show (for example in setup, here the debug output is disabled)
debugA("**** Setup: initialized.");
// or
printlnA("**** Setup: initialized.");
- For errors:
debugE("* This is a message of debug level ERROR");
// or
printlnE("* This is a message of debug level ERROR");
- For another levels:
debugV("* This is a message of debug level VERBOSE");
debugD("* This is a message of debug level DEBUG");
debugI("* This is a message of debug level INFO");
debugW("* This is a message of debug level WARNING");
// or
printlnV("* This is a message of debug level VERBOSE");
printlnD("* This is a message of debug level DEBUG");
printlnI("* This is a message of debug level INFO");
printlnW("* This is a message of debug level WARNING");
printf formatting (for debug* macros)
SerialDebug use prinf native (for Espressif boards), or implements it in depugPrintf function.
For Example:
debugA("This is a always - var %02d", var);
debugV("This is a verbose - var %02d", var);
debugD("This is a debug - var %02d", var);
debugI("This is a information - var %02d", var);
debugW("This is a warning - var %02d", var);
debugE("This is a error - var %02d", var);
See more about printf formatting: printf reference
Notes:
-
SeriaDebug use the standard printf of Arduino
For String variables, you must use the c_str() method:
debugA("*** called with arg.: %s", str.c_str());
For AVR MCUs, as UNO, Leonardo, Mega and Esp8266, no have support to %f (format floats)
If you need this, use:
#ifndef ARDUINO_ARCH_AVR // Native float printf support
debugV("mFloat = %0.3f", mFloat);
#else // For AVR and ESP8266, it is not supported, using String instead
debugV("mFloat = %s", String(mFloat).c_str());
#endif
(in future versions of SerialDebug, can be have a better solution)
Note: this is only for debug* macros , thats uses printf
For _print* macros, no need extra codes:
printV("*** called with arg.: ");
printlnV(str);
printV("mFloat = ");
printlnV(mFloat);
Watches
Watches is usefull to warning when the content of global variable, is changed or reaches a certain condition.
How this works, without a real hardware debugger? :
-
If have any watch (enabled),
-
Is verified this content is changed,
-
And is verified the watch, And trigger it, if value is changed, or the operation is true
This is done before each debug* show messages or in debugHandle function.
Tutorial
Have a nice tutorial about SerialDebug in randomnerdtutorials.com:
- Better Debugging for Arduino IDE: SerialDebug Library (Part 1): access part 1
- Better Debugging for Arduino IDE using Software Debugger (Part 2): access part 2
- Better Debugging for Arduino IDE: SerialDebugApp (Part 3): access part 3
Please access this tutorial, to give more information about how use SerialDebug and SerialDebugApp.
Khow issues
- Error on use debug* macros with F(). workaround for now: print* macros is ok for it.
Releases
0.9.82 - 2018-11-25
- corrected bug on debugHandleEvent
0.9.81 - 2018-11-16
- print macros now support second arg, e.g.: printlnA(10, HEX);
thanks to @wjwieland to open a issue about this.
0.9.80 - 2018-11-15
- Few adjustments in header files
0.9.79 - 2018-11-06
- Examples and README update
0.9.78 - 2018-10-28
- Examples update
0.9.77 - 2018-10-26
- Adjustments for minimum mode
- #include stdarg, to avoid error in Arduino IDE 1.8.5 and Linux - thanks to @wd5gnr
0.9.76 - 2018-10-26
- #includes for Arduino.h corrected to work in Linux (case sensitive F.S.) - thanks @wd5gnr
0.9.75 - 2018-10-25
- Few Adjustments (bug on declare prototype)
0.9.74 - 2018-10-25
- Adjustments to SerialDebugApp show debugger info in App
- Now low memory boards have debugger disabled by default, but enabled commands (debug level, help ...)
- Create an mode minimum to low memory boards - only debug output enabled to save memory
0.9.73 - 2018-10-24
- Adjustments to SerialDebugApp show debugger panel in App
0.9.72 - 2018-10-21
- Corrected bug on basic example
0.9.71 - 2018-10-19
- Just for new release, due problems on library.proprierties
0.9.7 - 2018-10-18
- Checking if debugger is enabled
0.9.6 - 2018-10-09
- New debug format output
0.9.5 - 2018-10-07
- New print macros
- Optimization on debugPrintf logic
0.9.4 - 2018-10-04
- Now debugger starts disabled
0.9.3 - 2018-10-01
- Few adjustments
0.9.2 - 2018-09-29
- Few adjustments
0.9.1 - 2018-09-28
- Few adjustments
0.9.0 - 2018-09-26
- First beta
Links
If you need a remote debug for ESP8266 or ESP32, see my other library -> RemoteDebug library
This library is made on Slober - the Eclipse for Arduino See it in -> Eclipse for Arduino
Thanks
Special thanks to:
- Arduino, for bring open hardware to us.
- Good people, that work hard, to bring to us excellent open source,
as libraries, examples, etc..
That inspire me to make new libraries, as SerialDebug, RemoteDebug, etc.
- Makers people, that works together as a big family.