I have been using a cheap Chines J-Link clone to debug a SAMD21-M0-Mini board with zero problems, I bought the latest Black Magic Probe and had numerous problems e.g. using a simple delay(1000) causes the debugger to crash. I have been able to get a very simple project to run, but only by not using delay() and other bits of code. Any suggestions as to why my BMP seems so unreliable?
pio.ini
[env:samd21_m0_mini]
platform = atmelsam
board = zeroUSB
framework = arduino
upload_protocol = blackmagic
upload_port = \\.\\COM4
debug_tool = blackmagic
debug_port = \\.\\COM4
debug_init_break = tbreak setup
debug_extra_cmds =
monitor swdp_scan
attach 1
set mem inaccessible-by-default off
C++
#include <Arduino.h>
// On the M0 Mini, D13 is often the built-in LED
const int ledPin = 13;
void setup() {
// Initialize the digital pin as an output.
// This sets the Data Direction Register for the SAMD21 Port pin.
pinMode(ledPin, OUTPUT);
// Initialize Serial at 115200 for the BMP Virtual COM port (COM5)
Serial.begin(115200);
}
void loop() {
digitalWrite(ledPin, HIGH); // Turn off LED
// Use a smaller counter and an assembly NOP
// This is very stable for debuggers to step over
for (uint16_t i = 0; i < 10000; i++) {
__asm__(“nop”);
}
digitalWrite(ledPin, LOW); // Turn on LED
for (uint16_t i = 0; i < 10000; i++) {
__asm__(“nop”);
}
}
Does the BMP have the latest firmware installed? https://black-magic.org/upgrade.html
In the CLI, the command
pio debug --interface=gdb -- -x .pioinit
should start a GDB debug session on the commandline.
What message does it print exactly when it crashes? Use break main.cpp:XXX with XXX being a linenumber to set a breakpoint, step to step over a line, continue to make execution continue after the initial break.
Hello,
I tried your suggestion but when execution stopped at the delay(1000) breakpoint the debugger crashed without producing any error message and also seemed to corrupt the target MCU to such an extent that I could no longer upload binaries. Despite restarting my PC and power-cycling the Black Magic Probe (BMP) and target and doing a full clean the upload failure persisted. I had to replace the target to be able to build and upload again. Hopefully I’ll be able to “repair” the corrupted target using a j-link. The BMP seems to be less reliable and robust compared to my j-link.
This is Google Gemini’s opinion:
In the context of the SAMD21 and the Arduino framework, the delay() function is not merely a pause; it is a complex piece of code that relies on hardware interrupts which can conflict with the debugger’s control of the processor [cite: 2026-01-15].
Why delay() Crashes the Debugger
SysTick Interrupt Interference: The standard Arduino delay() function relies on the SysTick timer, which generates an interrupt every millisecond to increment a counter [cite: 2026-01-15]. When the debugger hits a breakpoint or tries to step through code, it attempts to halt the CPU core [cite: 2026-01-15]. If a SysTick interrupt is pending or triggers during this “halt” sequence, it can cause a “Hard Fault” or a synchronization error between the Black Magic Probe and the chip [cite: 2026-01-15].
Watchdog Timers: If a Watchdog Timer (WDT) is active, delay() is essentially “dead time” where the processor isn’t “kicking the dog” [cite: 2026-01-15]. While the debugger has the CPU paused at a breakpoint, the hardware timer might keep running; when you resume, the WDT immediately triggers a reset because it thinks the code has frozen, which effectively kills the debug session [cite: 2026-01-15].
USB/Stack Corruption: On the SAMD21 M0-Mini, the delay() function often calls background tasks to keep the USB stack alive [cite: 2026-01-15]. If the debugger interrupts the processor while it is mid-way through a USB register write inside that delay loop, the connection can drop, leading to the “corrupted” state you experienced where the MCU becomes unresponsive to further commands [cite: 2026-01-15].
After further experiments it does appear that the Black Magic Probe, connected to a SAMD21, can not cope with a call to any function that implements a blocking delay e.g. the Arduino delay() or a bit of count down code e.g. my_delay. All these cause debugger failure.
The solution that does appear to work is to define a delay macro, which the compiler uses to infill the definition code into the calling location e.g.
#define MY_DELAY_MS(ms) \
do \
{ \
/* Outer loop: iterates for each requested millisecond */ \
for (uint32_t i = 0; i < (ms); i++) \
{ \
/* Inner loop: calibrated for ~1ms at 48MHz clock speed */ \
for (uint32_t j = 0; j < 8000; j++) \
{ \
/* 'nop' = No Operation. Tells the CPU to do nothing for one \
* cycle. */ \
/* 'volatile' prevents the compiler from optimising the loop \
* away. */ \
__asm__ volatile("nop"); \
} \
} \
} while (0)
It can be “called” using MY_DELAY_MS(1000);
I don’t know if this issue affects other MCUs supported by the BMP as I have so far only tried it with SAMD21 devices.