...
- Requires recompilation every time we want to look at something different - For embedded, this also means reflashing.
- Requires cleanup after the problem is found.
- Requires dump functions for anything more than an integer.
- Printf is huge - For embedded, adding printf support can eat up a lot of RAM and flash.
- Printf is slow - It takes a long time to actually format the string, then transmitting it through your debug output has an additional delay.
- This can actually block the main program for a noticeable amount of time.
- This will wreck havoc with any timing-sensitive operations such as race conditions.
- It requires a debug output - In Module 2: Hello World, we needed a UART-to-USB adapter to see the serial output.
- It can't handle crashes and relies on the debug output to be working.
Of course, like any tool, this has its place. If you're okay with the drawbacks, printf is great for convenient, human-readable continuous logging. We have a set of LOG
macros for human-readable print statements in our HAL, such as the one you used in Module 2: Hello World.
GPIO Debugging
A higher-speed variant of this technique on embedded platforms is toggling GPIOs at certain points and using an oscilloscope or logic analyzer to trace execution. I find this most useful for timing-sensitive debugging.
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#include <stdint.h> #include "soft_timer.h" #include "gpio.h" #include "interrupt.h" #include "log.h" #include "wait.h" #define DEBUG_TEST_TIMEOUT_MS 50 typedef struct DebugTestStorage { GPIOAddressGpioAddress *leds; size_t num_leds; size_t current_led; } DebugTestStorage; static DebugTestStorage s_storage; static void prv_init_leds(DebugTestStorage *storage) { GPIOAddressGpioAddress leds[] = { { .port = GPIO_PORT_C, .pin = 8 }, // { .port = GPIO_PORT_C, .pin = 9 }, // { .port = GPIO_PORT_C, .pin = 6 }, // { .port = GPIO_PORT_C, .pin = 7 }, // }; GPIOSettings led_settings = { .direction = GPIO_DIR_OUT, .state = GPIO_STATE_HIGH }; storage->leds = leds; storage->num_leds = SIZEOF_ARRAY(leds); storage->current_led = 0; for (size_t i = 0; i < SIZEOF_ARRAY(leds); i++) { gpio_init_pin(&leds[i], &led_settings); } } static void prv_timeout_cb(SoftTimerID timer_id, void *context) { DebugTestStorage *storage = context; // Toggle the current LED GPIOAddressGpioAddress *led = &storage->leds[storage->current_led]; LOG_DEBUG("Toggling LED P%d.%x\n", led->port, led->pin); gpio_toggle_state(led); // Next LED storage->current_led = (storage->current_led + 1) % storage->num_leds; soft_timer_start_millis(DEBUG_TEST_TIMEOUT_MS, prv_timeout_cb, storage, NULL); } int main(void) { interrupt_init(); soft_timer_init(); gpio_init(); prv_init_leds(&s_storage); uint32_t buffer[20]; LOG_DEBUG("%ld LEDs set up\n", s_storage.num_leds); soft_timer_start_millis(DEBUG_TEST_TIMEOUT_MS, prv_timeout_cb, &s_storage, NULL); size_t i = 0; while (true) { wait(); buffer[i % SIZEOF_ARRAY(buffer)] += i; i++; } return 0; } |
...