← Back to Studying Paths

Debugging & Testing

Debugging embedded systems is uniquely challenging—you're dealing with real-time constraints, hardware interactions, and limited visibility. Mastering debugging tools and testing methodologies is essential for shipping reliable products.

[Diagram: Debug flow - Problem → Hypothesis → Instrument → Analyze → Fix → Verify]

Debug Interfaces

Modern microcontrollers provide dedicated hardware interfaces for debugging:

JTAG (Joint Test Action Group)

SWD (Serial Wire Debug)

💡 Pro Tip: SWD is preferred for Cortex-M projects due to fewer pins. Reserve JTAG for multi-core debugging or when boundary scan is needed.

Debug Probes & Tools

Hardware tools that connect your PC to the target MCU:

Popular Debug Probes

Software Debugging Techniques

1. Breakpoints

Pause execution at specific lines of code to examine program state.

2. Watch Expressions

Monitor variable values in real-time without breaking execution.

// Variables to watch volatile uint32_t adc_reading; volatile int32_t motor_position; volatile uint8_t error_flags;

⚠️ Important: Mark watched variables as volatile to prevent compiler optimization from hiding their values during debugging!

3. Printf Debugging (UART/SWO)

Classic technique using serial output for runtime logging:

// Retarget printf to UART int _write(int file, char *ptr, int len) { HAL_UART_Transmit(&huart2, (uint8_t*)ptr, len, HAL_MAX_DELAY); return len; } // Debug output printf("[DEBUG] Sensor value: %d, State: %s\n", value, state_names[state]);

4. SWO/ITM Trace

ARM's Instrumentation Trace Macrocell allows printf over the debug probe—no extra UART needed!

// Send character via ITM (ARM Cortex-M) int _write(int file, char *ptr, int len) { for (int i = 0; i < len; i++) { ITM_SendChar(*ptr++); } return len; }

Logic Analyzers & Oscilloscopes

Essential tools for debugging hardware-related issues:

Logic Analyzer

[Diagram: Logic analyzer capture of I2C communication]

Oscilloscope

Testing Methodologies

Unit Testing

Test individual functions in isolation, often on host PC:

// Using Unity test framework void test_calculate_checksum(void) { uint8_t data[] = {0x01, 0x02, 0x03, 0x04}; uint8_t expected = 0x0A; TEST_ASSERT_EQUAL(expected, calculate_checksum(data, 4)); }

Popular embedded testing frameworks:

Unity CppUTest Google Test Ceedling

Integration Testing

Hardware-in-the-Loop (HIL) Testing

Simulate the physical environment while testing real firmware:

[Diagram: HIL setup with simulator, DUT, and test automation]

Common Debugging Scenarios

Hard Fault Debugging (ARM Cortex-M)

// Hard Fault Handler - capture fault info void HardFault_Handler(void) { __asm volatile ( "TST LR, #4 \n" "ITE EQ \n" "MRSEQ R0, MSP \n" "MRSNE R0, PSP \n" "B hard_fault_handler_c \n" ); } void hard_fault_handler_c(uint32_t *stack) { volatile uint32_t r0 = stack[0]; volatile uint32_t r1 = stack[1]; volatile uint32_t pc = stack[6]; // Program counter at fault volatile uint32_t lr = stack[5]; // Link register // Inspect these in debugger or log them while(1); // Halt for debugging }

Timing Issues

// Measure execution time using DWT (ARM Cortex-M) CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; uint32_t start = DWT->CYCCNT; // ... code to measure ... uint32_t cycles = DWT->CYCCNT - start; float time_us = cycles / (SystemCoreClock / 1000000.0f);

Static Analysis & Code Quality

🎯 Key Takeaway: Effective debugging requires the right mindset (be systematic!), the right tools, and experience. Build a debugging toolkit and document your troubleshooting steps!