%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#16A085', 'secondaryColor': '#E67E22', 'tertiaryColor': '#ECF0F1'}}}%%
flowchart TD
START["Debug Issue"] --> Q1{"Type of<br/>problem?"}
Q1 -->|"Logic bug"| UNIT["Unit Tests +<br/>Serial Debug"]
Q1 -->|"Timing issue"| LOGIC["Logic Analyzer"]
Q1 -->|"Memory/crash"| JTAG["JTAG Debugger"]
Q1 -->|"Field failure"| REMOTE["Remote Logging"]
Q1 -->|"Protocol error"| WIRE["Wireshark +<br/>Logic Analyzer"]
style START fill:#E67E22,stroke:#2C3E50,color:#fff
style UNIT fill:#16A085,stroke:#2C3E50,color:#fff
style LOGIC fill:#16A085,stroke:#2C3E50,color:#fff
style JTAG fill:#16A085,stroke:#2C3E50,color:#fff
style REMOTE fill:#16A085,stroke:#2C3E50,color:#fff
style WIRE fill:#16A085,stroke:#2C3E50,color:#fff
1540 Software Prototyping: Testing and Debugging
1540.1 Learning Objectives
By the end of this chapter, you will be able to:
- Write Unit Tests: Create testable firmware functions using Unity and PlatformIO
- Debug with Serial Output: Implement structured logging with debug levels
- Use Hardware Debuggers: Set breakpoints and inspect variables with JTAG/SWD
- Debug Remotely: Monitor deployed devices via OTA logging and telnet
1540.2 Prerequisites
Before diving into this chapter, you should be familiar with:
- Development Environments: PlatformIO setup and configuration
- Software Architecture Patterns: Modular code organization
1540.3 Unit Testing
PlatformIO Unit Tests:
#include <Arduino.h>
#include <unity.h>
void test_temperature_reading() {
float temp = readTemperature();
TEST_ASSERT_TRUE(temp > -40.0 && temp < 85.0);
}
void test_sensor_initialization() {
bool initialized = initSensor();
TEST_ASSERT_TRUE(initialized);
}
void setup() {
UNITY_BEGIN();
RUN_TEST(test_temperature_reading);
RUN_TEST(test_sensor_initialization);
UNITY_END();
}
void loop() {
// Tests run once in setup
}Test Directory Structure:
test/
βββ test_sensor_logic/
β βββ test_main.cpp # Tests on HOST (your PC)
βββ test_embedded/
βββ test_main.cpp # Tests on TARGET (ESP32)
Running Tests:
# Run tests on host machine (fast, no hardware)
pio test -e native
# Run tests on actual hardware
pio test -e esp32dev
# Run specific test folder
pio test -e native -f test_sensor_logic1540.4 Serial Debugging
Debug Macros:
#define DEBUG 1
#if DEBUG
#define DEBUG_PRINT(x) Serial.print(x)
#define DEBUG_PRINTLN(x) Serial.println(x)
#else
#define DEBUG_PRINT(x)
#define DEBUG_PRINTLN(x)
#endif
void loop() {
float temp = readSensor();
DEBUG_PRINT("Temperature: ");
DEBUG_PRINTLN(temp);
}Structured Logging:
enum LogLevel {
LOG_ERROR,
LOG_WARN,
LOG_INFO,
LOG_DEBUG
};
LogLevel currentLevel = LOG_INFO;
void log(LogLevel level, const char* message) {
if (level > currentLevel) return;
const char* levelStr[] = {"ERROR", "WARN", "INFO", "DEBUG"};
Serial.print("[");
Serial.print(millis());
Serial.print("] [");
Serial.print(levelStr[level]);
Serial.print("] ");
Serial.println(message);
}
// Usage
log(LOG_INFO, "Sensor initialized");
log(LOG_ERROR, "Wi-Fi connection failed");
log(LOG_DEBUG, "Temperature: 25.5");Formatted Logging:
void logf(LogLevel level, const char* format, ...) {
if (level > currentLevel) return;
char buffer[256];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
log(level, buffer);
}
// Usage
logf(LOG_INFO, "Temperature: %.2f C, Humidity: %.1f%%", temp, humidity);1540.5 Hardware Debugging
JTAG/SWD Debugging: - Set breakpoints in code - Step through execution - Inspect variables - View call stack - Supported by professional IDEs (STM32CubeIDE, PlatformIO with debugger)
PlatformIO Debug Configuration:
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
; Debug configuration
debug_tool = esp-builtin ; For ESP32-S3/C3
; debug_tool = esp-prog ; For ESP32 with ESP-PROG
debug_init_break = tbreak setup
debug_speed = 5000
build_type = debugCommon GDB Commands:
# In PlatformIO Debug Console
info registers # Show CPU registers
print variable_name # Print variable value
watch variable_name # Break when variable changes
bt # Backtrace (call stack)
list # Show source code
continue # Resume execution
Logic Analyzer Debugging: - Capture I2C, SPI, UART communication - Verify timing and protocols - Identify bus conflicts or errors - Decode protocol messages automatically
LED Debugging:
// Blink patterns indicate status
void indicateError(int errorCode) {
for(int i = 0; i < errorCode; i++) {
digitalWrite(LED_PIN, HIGH);
delay(200);
digitalWrite(LED_PIN, LOW);
delay(200);
}
delay(2000);
}
// Error codes
// 1 blink: Sensor error
// 2 blinks: Wi-Fi error
// 3 blinks: MQTT error
// 4 blinks: OTA error1540.6 Remote Debugging
OTA Logging (MQTT):
void logToCloud(String message) {
if (WiFi.status() == WL_CONNECTED) {
client.publish("device/logs", message.c_str());
}
}
// Usage
logToCloud("Boot complete, firmware v1.2.3");
logToCloud("Sensor error: NaN reading");Telnet Debugging:
#include <TelnetStream.h>
void setup() {
Serial.begin(115200);
TelnetStream.begin();
}
void loop() {
// Output goes to both Serial and Telnet
TelnetStream.println("Debug message over telnet");
Serial.println("Debug message over serial");
// Read Telnet commands
if (TelnetStream.available()) {
char cmd = TelnetStream.read();
handleCommand(cmd);
}
}Remote Console:
#include <RemoteDebug.h>
RemoteDebug Debug;
void setup() {
Debug.begin("esp32-device");
Debug.setResetCmdEnabled(true);
}
void loop() {
Debug.handle();
// Debug levels: verbose, debug, info, warning, error
debugV("Verbose message");
debugD("Debug message");
debugI("Info message");
debugW("Warning message");
debugE("Error message");
}1540.7 Debugging Tools Comparison
| Tool | Best For | Advantages | Limitations |
|---|---|---|---|
| Serial.print() | Quick debugging | Simple, universal | Adds timing overhead |
| JTAG/SWD | Complex bugs | Real-time, non-intrusive | Requires hardware probe |
| Logic Analyzer | Protocol issues | Shows timing, decodes protocols | External equipment |
| OTA Logging | Deployed devices | Remote monitoring | Requires connectivity |
| Unit Tests | Regression prevention | Automated, fast | Canβt test hardware interaction |
1540.8 Debugging Technique Selection
1540.9 Knowledge Check
1540.10 Whatβs Next
The next section covers Best Practices and Common Pitfalls, where youβll learn code organization, configuration management, power optimization, error handling, and how to avoid the most common mistakes in IoT firmware development.