Wenn ein Bluetooth-Modul im Feld Verbindungen verliert, im Sleep-Modus 3 mA ueber Spezifikation verbraucht oder bei der Zertifizierung auf dem 2. Kanal des 40-Kanal-DTM-Sweeps scheitert, ist der Unterschied zwischen einer einstuendigen Reparatur und einer zweiwoechigen Sackgasse, ob Sie das Modul waehrend der Entwicklung richtig instrumentiert haben. Dieser Artikel behandelt die physischen Debug-Schnittstellen, Echtzeit-Tracing-Methoden, RF-Verifizierungspfade und Post-Mortem-Tools, die ab dem ersten Schaltplan-Tag eingeplant werden sollten — nicht nach dem ersten Feldfehler nachgeruestet.
## 1. Debug-Schnittstellenuebersicht: JTAG vs SWD vs cJTAG
Die meisten modernen BLE-SoCs (nRF52/53, CC2640, ESP32-C3/H2, DA1469x, BGMxx) exponieren entweder ein volles 5-Pin-JTAG (TCK, TMS, TDI, TDO, nTRST) oder das ARM-spezifische 2-Pin-SWD (SWCLK, SWDIO) plus nRESET. Die folgende Tabelle fasst die Kompromisse fuer das BLE-Moduldesign zusammen:
| Parameter | JTAG (5-Pin) | SWD (2-Pin + RESET) | cJTAG (2-Pin) |
|———–|————-|———————|—————-|
| Pin-Anzahl | 5 (oder 4 ohne nTRST) | 2-3 | 2 |
| Max. Takt | 10-50 MHz | 1-50 MHz | bis 50 MHz |
| Trace-Unterstuetzung | ETM (4-Pin-Trace-Bus) | ITM (SWO, 1-Pin) | ITM (SWO) |
| Flash-Download-Geschw. (256 KB) | ~8-12 s | ~6-10 s | ~6-10 s |
| Kabellaengenbegrenzung | ~15 cm (ungepuffert) | ~30 cm (ungepuffert) | ~20 cm |
| SoC-Unterstuetzung | Universell (ARM + RISC-V) | Nur ARM Cortex-M | ARM Cortex-M33/55 |
| Typische Stromaufnahme (aktives Debug) | 2-5 mA | 1-3 mA | 1-3 mA |
Fuer BLE-Module, bei denen die PCB-Flaeche in Quadratmillimetern gemessen wird, ist SWD die Standardwahl. Die nRF52/53-Serie erlaubt SWD sogar auf denselben Pins wie GPIO P0.00/P0.01 waehrend der Entwicklung. Diese werden auf einen 0,05-Zoll-Raster-Mikro-Header (Samtec FTSH-105 oder aequivalent) gefuehrt und entweder fuer die Entwicklung bestueckt oder als nackte PCB-Pads fuer die Produktion belassen. Der Nur-Pad-Ansatz ist vorzuziehen, da ein bestueckter Header auf einem Produktionsmodul unautorisierten Zugriff einlaedt.
### SWD-Signalintegritaetsueberlegungen
SWDIO ist bidirektional und Open-Drain mit Pull-up (typisch 10 kΩ intern, 4,7 kΩ extern). Der Pull-up-Wert ist wichtiger, als die meisten Ingenieure erwarten:
| Pull-up | SWDIO-Anstiegszeit (tr) | Max. SWCLK | Hinweise |
|———|———————|———–|——-|
| 100 kΩ | 220 ns | 1 MHz | Grenzwertig — gelegentlicher Sync-Verlust |
| 47 kΩ | 104 ns | 4 MHz | OK fuer kurze Kabel (<15 cm) |
| 10 kΩ | 22 ns | 25 MHz | Zuverlaessig fuer die meisten Setups |
| 4,7 kΩ | 10 ns | 50 MHz | Bevorzugt fuer J-Link bei hoher Geschwindigkeit |
| 1 kΩ | 2,2 ns | >50 MHz | Uebertrieben, verschwendet 3,3 mA bei Low |
Die Formel ist einfach RC-Verzoegerung: tr ≈ 2.2 × R × C, wobei C die Gesamt-Leiterbahn- + Tastkopfkapazitaet ist (typisch 10-20 pF bei einem Modul mit 15 cm Flachbandkabel). Wenn Sie intermitierende „SWD DP read failed“-Fehler auf Ihrem J-Link sehen, sollten Sie zuerst pruefen, ob der externe Pull-up fehlt und der interne 10 kΩ gegen ein langes Kabel kaempft.
## 2. UART-Debug-Konsole: Design fuer reales Debugging
Eine UART-Debug-Konsole ist die wertvollste Debug-Schnittstelle in Produktionsfirmware, wird aber routinemaessig schlecht implementiert. Die haeufigsten Fehler:
1. **Baudrate zu hoch fuer die BLE-Stack-Prioritaet.** Bei 1 Mbps UART mit 16-Byte-FIFO wird der Interrupt alle 128 μs ausgeloest. Wenn der BLE-Stack Interrupts waehrend 150 μs der Verbindungsereignis-Vorbereitung sperrt, gehen Zeichen verloren. Loesung: 460800 bps oder 921600 bps mit DMA-TX und Ringpuffer fuer RX verwenden.
2. **printf blockiert die Hauptschleife.** Ein blockierendes `printf`, das 200 Bytes bei 115200 bps schreibt, dauert 17,4 ms — laenger als ein 7,5 ms BLE-Verbindungsintervall. Verwenden Sie einen lockfreien Ringpuffer mit DMA-Flush im Idle:
„`c
// Lockfreier UART-Debug-Ringpuffer (einzelner Produzent, einzelner Konsument)
#define DBG_BUF_SIZE 2048
static volatile uint16_t dbg_head = 0, dbg_tail = 0;
static char dbg_buf[DBG_BUF_SIZE];
void dbg_printf(const char *fmt, …) {
va_list ap;
va_start(ap, fmt);
char tmp[256];
int len = vsnprintf(tmp, sizeof(tmp), fmt, ap);
va_end(ap);
for (int i = 0; i < len; i++) {
uint16_t next = (dbg_head + 1) & (DBG_BUF_SIZE - 1);
if (next == dbg_tail) break; // Puffer voll, Zeichen verwerfen
dbg_buf[dbg_head] = tmp[i];
dbg_head = next;
}
// DMA-Flush ausloesen, wenn TX idle
if (!(UART0->STAT & UART_STAT_TXBUSY)) {
uart_dma_flush();
}
}
„`
3. **Keine Log-Level-Filterung.** Ein Produktionsmodul, das VERBOSE-Level-Logs bei 460800 bps waehrend eines Verbindungsereignisses ausgibt, verursacht Timing-Jitter. Implementieren Sie Kompilierzeit- und Laufzeit-Filterung:
| Level | Kompilier-Flag | Runtime | Typische Verwendung |
|——-|————-|———|————-|
| ERROR | `DBG_ERROR` | Immer an | Hard Faults, Assert-Fehler |
| WARN | `DBG_WARN` | Immer an | Wiederversuche, degradierte Leistung |
| INFO | `DBG_INFO` | Konfigurierbar | Verbindung/Trennung, Zustaendsaenderungen |
| DEBUG | `DBG_DEBUG` | Off in Produktion | Pro-Event-Timing, Paket-Dumps |
| VERBOSE | `DBG_VERBOSE` | Off in Produktion | Pro-Slot-Scheduling, RSSI pro Event |
4. **Keine Binaer-Crash-Dump-Faehigkeit.** Wenn das Modul einen Hard Fault hat, ist UART Ihre einzige Lebensader. Reservieren Sie einen 1 KB Crash-Dump-Handler, der die gestapelten Register (R0-R3, R12, LR, PC, xPSR) und die ersten 16 Worte des Stacks vor dem Watchdog-Reset ausgibt:
„`c
void HardFault_Handler(void) {
uint32_t *stk;
__asm volatile(„tst lr, #4
“
„ite eq
“
„mrseq %0, msp
“
„mrsne %0, psp
“ : „=r“(stk));
dbg_printf(“
*** HARD FAULT ***
„);
dbg_printf(„R0: 0x%08X R1: 0x%08X R2: 0x%08X R3: 0x%08X
„,
stk[0], stk[1], stk[2], stk[3]);
dbg_printf(„R12: 0x%08X LR: 0x%08X PC: 0x%08X PSR: 0x%08X
„,
stk[4], stk[5], stk[6], stk[7]);
dbg_printf(„BFAR: 0x%08X CFSR: 0x%08X HFSR: 0x%08X
„,
SCB->BFAR, SCB->CFSR, SCB->HFSR);
for (int i = 0; i < 16; i++) {
dbg_printf("SP+%02d: 0x%08X
", i*4, stk[i]);
}
while(1); // Auf Watchdog warten
}
```
### UART-Pin-Auswahl bei BLE-Modulen
Vermeiden Sie P0.00/P0.01 (fuer SWD oder 32-kHz-Kristall reserviert), P0.02/P0.03 (oft ADC oder Analog) und alle Pins, die vom BLE-Stack fuer Radio-Timing verwendet werden. Sichere UART-Pins bei haeufigen SoCs:
| SoC | Sicheres TX | Sicheres RX | Vermeiden |
|-----|---------|---------|-------|
| nRF52832 | P0.06 | P0.08 | P0.00/01 (XL1/XL2), P0.09 (NFC) |
| nRF52840 | P1.08 | P1.10 | P0.00/01, P0.09/10 (NFC), P0.24-25 (USB) |
| ESP32-C3 | GPIO2 | GPIO3 | GPIO0 (Boot), GPIO2 (Strapping) |
| CC2640R2 | DIO2 | DIO3 | DIO0-1 (Boot), DIO7-8 (JTAG) |
| DA1469x | P0_06 | P0_07 | P0_00 (XTAL32M), P0_01-03 (SWD) |
## 3. Echtzeit-Tracing: RTT vs ITM/SWO vs ETM
printf-basiertes UART-Debug ist fuer niederfrequente Ereignisse geeignet, aber die BLE-Stack-Timing-Analyse erfordert Submikrosekunden-Aufloesung. Drei Optionen mit unterschiedlichen Kompromissen:
### Segger RTT (Real-Time Transfer)
RTT verwendet einen Ringpuffer im Ziel-RAM, den der Debugger ueber SWD mit hoher Geschwindigkeit abfragt. Es erfordert null Ziel-CPU-Zeit pro Log-Eintrag (der Debugger liest RAM direkt), was es ideal fuer timing-kritischen Code macht:
| Metrik | UART @ 460800 | RTT | ITM/SWO @ 2 MHz |
|--------|---------------|-----|-----------------|
| CPU-Zeit pro 100-Zeichen-Log | ~2,2 ms | ~5 us | ~50 us |
| Durchsatz | 46 KB/s | ~500 KB/s | ~200 KB/s |
| Latenz (Log → Debugger) | 2,2 ms | ~10 us | ~25 us |
| Benoetigte Pins | 2 (TX/RX) | 0 (nutzt SWD) | 1 (SWO) + SWD |
| Puffer im RAM | 0 | 1-4 KB | 0 (Hardware-FIFO) |
| Debugger-Unterstuetzung | Beliebiges Terminal | Nur J-Link | J-Link, ST-Link, CMSIS-DAP |
RTTs Hauptvorteil ist, dass es ueber die vorhandene SWD-Schnittstelle ohne zusaetzliche Pins funktioniert. Die Hauptbeschraenkung ist, dass es einen Segger J-Link erfordert. Fuer Entwicklung ist das akzeptabel; fuer Felddiagnose haben Sie wahrscheinlich keinen J-Link.
RTT unterstuetzt auch bidirektionale Kommunikation — der Debugger kann Befehle in das Ziel injizieren, ohne einen UART-Pin zu verwenden:
```c
#include "SEGGER_RTT.h"
void rtt_command_loop(void) {
char cmd[64];
int len = SEGGER_RTT_Read(0, cmd, sizeof(cmd) - 1);
if (len > 0) {
cmd[len] = ‚