When a Bluetooth module drops connections in the field, draws 3 mA above spec in sleep, or fails certification on the 2nd channel of the 40-channel DTM sweep, the difference between a one-day fix and a two-week rabbit hole is whether you instrumented the module correctly during development. This article covers the physical debug interfaces, real-time tracing methods, RF verification paths, and post-mortem tooling that should be designed in from schematic day one — not bolted on after the first field failure.

## 1. Debug Interface Landscape: JTAG vs SWD vs cJTAG

Most modern BLE SoCs (nRF52/53, CC2640, ESP32-C3/H2, DA1469x, BGMxx) expose either a full 5-pin JTAG (TCK, TMS, TDI, TDO, nTRST) or the ARM-specific 2-pin SWD (SWCLK, SWDIO) plus nRESET. The table below summarizes the trade-offs for BLE module design:

| Parameter | JTAG (5-pin) | SWD (2-pin + RESET) | cJTAG (2-pin) |
|———–|————-|———————|—————-|
| Pin count | 5 (or 4 w/o nTRST) | 2-3 | 2 |
| Max clock | 10-50 MHz | 1-50 MHz | up to 50 MHz |
| Trace support | ETM (4-pin trace bus) | ITM (SWO, 1-pin) | ITM (SWO) |
| Flash download speed (256 KB) | ~8-12 s | ~6-10 s | ~6-10 s |
| Wire length limit | ~15 cm (unbuffered) | ~30 cm (unbuffered) | ~20 cm |
| SoC support | Universal (ARM + RISC-V) | ARM Cortex-M only | ARM Cortex-M33/55 |
| Typical current (active debug) | 2-5 mA | 1-3 mA | 1-3 mA |

For BLE modules where PCB real estate is measured in square millimeters, SWD is the default choice. The nRF52/53 series even allows SWD on the same pins as GPIO P0.00/P0.01 during development — you route them to a 0.05-inch pitch micro-header (Samtec FTSH-105 or equivalent) and either populate it for development or leave it as bare PCB pads for production. The pad-only approach is preferable because a populated header on a production module invites unauthorized access.

### SWD Signal Integrity Considerations

SWDIO is bidirectional and open-drain with a pull-up (typically 10 kΩ internal, 4.7 kΩ external). The pull-up value matters more than most engineers expect:

| Pull-up | SWDIO rise time (tr) | Max SWCLK | Notes |
|———|———————|———–|——-|
| 100 kΩ | 220 ns | 1 MHz | Marginal — occasional sync loss |
| 47 kΩ | 104 ns | 4 MHz | OK for short wires (<15 cm) | | 10 kΩ | 22 ns | 25 MHz | Reliable for most setups | | 4.7 kΩ | 10 ns | 50 MHz | Preferred for J-Link at high speed | | 1 kΩ | 2.2 ns | >50 MHz | Overkill, wastes 3.3 mA when low |

The formula is simply RC delay: tr ≈ 2.2 × R × C, where C is the total trace + probe capacitance (typically 10-20 pF on a module with a 15 cm ribbon cable). If you see intermittent “SWD DP read failed” errors on your J-Link, the first thing to check is whether the external pull-up is missing and the internal 10 kΩ is fighting a long cable.

## 2. UART Debug Console: Design for Real-World Debugging

A UART debug console is the single most valuable debug interface in production firmware, yet it is routinely implemented poorly. The common mistakes:

1. **Baud rate too high for the BLE stack priority.** At 1 Mbps UART with a 16-byte FIFO, the interrupt fires every 128 μs. If the BLE stack holds interrupts for 150 μs during connection event preparation, you lose characters. Solution: use 460800 bps or 921600 bps with DMA TX, and ring-buffer RX.

2. **printf blocking the main loop.** A blocking `printf` that writes 200 bytes at 115200 bps takes 17.4 ms — longer than a 7.5 ms BLE connection interval. Use a lock-free ring buffer with DMA flush in idle:

“`c
// Lock-free UART debug ring buffer (single producer, single consumer)
#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; // buffer full, drop char dbg_buf[dbg_head] = tmp[i]; dbg_head = next; } // Trigger DMA flush if TX idle if (!(UART0->STAT & UART_STAT_TXBUSY)) {
uart_dma_flush();
}
}
“`

3. **No log level filtering.** A production module spitting VERBOSE-level logs at 460800 bps during a connection event will cause timing jitter. Implement compile-time and runtime level filtering:

| Level | Compile flag | Runtime | Typical use |
|——-|————-|———|————-|
| ERROR | `DBG_ERROR` | Always on | Hard faults, assertion failures |
| WARN | `DBG_WARN` | Always on | Retries, degraded performance |
| INFO | `DBG_INFO` | Configurable | Connection/disconnection, state changes |
| DEBUG | `DBG_DEBUG` | Off in production | Per-event timing, packet dumps |
| VERBOSE | `DBG_VERBOSE` | Off in production | Per-slot scheduling, RSSI every event |

4. **No binary crash dump capability.** When the module hard-faults, the UART is your only lifeline. Pre-allocate a 1 KB crash dump handler that dumps the stacked registers (R0-R3, R12, LR, PC, xPSR) and the first 16 words of the stack before the watchdog resets:

“`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);
// Dump 16 stack words for context
for (int i = 0; i < 16; i++) { dbg_printf("SP+%02d: 0x%08X ", i*4, stk[i]); } while(1); // Wait for watchdog } ``` ### UART Pin Selection on BLE Modules Avoid P0.00/P0.01 (reserved for SWD or 32 kHz crystal), P0.02/P0.03 (often ADC or analog), and any pins used by the BLE stack for radio timing (NRF52 uses P0.08-P0.11 for DCX/CS/MOSI/MISO in some configurations). Safe UART pins on common SoCs: | SoC | Safe TX | Safe RX | Avoid | |-----|---------|---------|-------| | 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. Real-Time Tracing: RTT vs ITM/SWO vs ETM printf-based UART debug is fine for low-frequency events, but BLE stack timing analysis requires sub-microsecond resolution. Three options exist, each with different trade-offs: ### Segger RTT (Real-Time Transfer) RTT uses a ring buffer in target RAM that the debugger polls via SWD at high speed. It requires zero target CPU time per log entry (the debugger reads RAM directly), making it ideal for timing-sensitive code: | Metric | UART @ 460800 | RTT | ITM/SWO @ 2 MHz | |--------|---------------|-----|-----------------| | CPU time per 100-char log | ~2.2 ms | ~5 μs | ~50 μs | | Throughput | 46 KB/s | ~500 KB/s | ~200 KB/s | | Latency (log → debugger) | 2.2 ms | ~10 μs | ~25 μs | | Pins required | 2 (TX/RX) | 0 (uses SWD) | 1 (SWO) + SWD | | Buffer in RAM | 0 | 1-4 KB | 0 (hardware FIFO) | | Debugger support | Any terminal | J-Link only | J-Link, ST-Link, CMSIS-DAP | RTT's main advantage is that it works through the existing SWD interface with no extra pins. Its main limitation is that it requires a Segger J-Link (or a compatible debug probe running J-Link firmware). For development, this is fine; for field debugging, you likely do not have a J-Link. RTT also supports bidirectional communication — the debugger can inject commands into the target without using a UART pin: ```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] = ‘’;
if (strcmp(cmd, “stats”) == 0) {
print_ble_stats();
} else if (strcmp(cmd, “scan”) == 0) {
start_debug_scan(100); // 100 ms scan window
} else if (strcmp(cmd, “conn”) == 0) {
print_connection_info();
}
}
}
“`

### ITM/SWO Tracing

ITM (Instrumentation Trace Macrocell) is an ARM Cortex-M3/4/7/33 feature that provides 32 stimulus ports for application-level tracing, plus timestamping and exception tracing. The SWO (Serial Wire Output) pin carries ITM data as a single-wire UART at up to the CPU clock speed:

“`c
// ITM stimulus port 0 for debug messages
#define ITM_Port8(n) (*((volatile uint8_t *)(0xE0000000 + 4*n)))
#define ITM_Port32(n) (*((volatile uint32_t *)(0xE0000000 + 4*n)))

void itm_printf(char *s) {
while (*s) {
while (ITM_Port8(0) == 0); // wait for stimulus port ready
ITM_Port8(0) = *s++;
}
}
“`

SWO can be configured in two modes:

| Mode | SWO baud | Clock source | Pros | Cons |
|——|———|————-|——|——|
| UART mode | 2-50 MHz | HCLK / prescaler | Works with any SWO-capable probe | Must match baud exactly |
| Manchester mode | Up to CPU clk | HCLK direct | Self-clocking, no baud matching | J-Link only, not ST-Link |

For BLE timing analysis, ITM excels at logging per-event timestamps. The ITM hardware timestamps each write with a 1-cycle resolution counter, so you can measure connection event timing without any CPU overhead:

“`c
// Log connection event timing on ITM port 1
void on_conn_event(uint16_t handle, int8_t rssi, uint32_t elapsed_us) {
ITM_Port32(1) = handle;
ITM_Port32(1) = (uint32_t)rssi;
ITM_Port32(1) = elapsed_us;
}
“`

The J-Link SWO Viewer or SystemView can then display these events on a timeline with nanosecond-accurate timestamps.

### ETM (Embedded Trace Macrocell)

ETM provides full instruction-level trace via a 4-bit parallel trace bus. It is available on Cortex-M7 (and some M4) but requires 4-7 additional pins and a Trace Only or larger debug connector. For BLE module work, ETM is rarely justified — the pin cost is too high and instruction-level trace produces too much data to be useful for protocol-level debugging. It is occasionally useful for cache miss analysis on dual-core modules (nRF5340), but for most BLE work, ITM/SWO is the better choice.

## 4. Logic Analyzer Techniques for BLE Timing

A logic analyzer is indispensable for debugging BLE timing issues that are invisible to software-based instrumentation. The key use cases:

### 4.1 BLE Stack Event Verification

Most BLE stacks expose a “radio event” or “timer compare” GPIO that toggles at the start and end of each radio activity. On the nRF52, this is the `RADIO_SHORTS` or `TIMER0` compare; on ESP32, it is the `BT_CONTROLLER` event signal. Configuring one debug GPIO to toggle at radio events lets you measure:

| Measurement | Method | What it reveals |
|————-|——–|—————–|
| Connection event start jitter | Measure radio-GPIO to GPIO-PPS offset | Crystal accuracy, sleep timer drift |
| Connection event duration | Pulse width of radio-GPIO | Payload size vs. interval mismatch |
| Scan window timing | Pulse width of scan-GPIO | Actual scan vs. configured scan |
| Advertising interval accuracy | Period measurement | Drift, randomization correctness |
| Inter-event gap | Time between falling and next rising edge | Stack overhead, processing time |

### 4.2 Protocol Decode with SWIRE/HCI Sniffing

Most BLE modules route HCI traffic between the host and controller internally, but some expose it on a UART or SPI interface. Sniffing this traffic with a logic analyzer protocol decoder is the fastest way to debug host-controller interaction issues:

– **UART HCI**: Saleae Logic 2 includes an HCI UART decoder that parses command complete, command status, ACL data, and events. Set the analyzer to the module’s HCI baud rate (typically 115200 for simple modules, up to 3 Mbps for modules with high-throughput profiles).
– **SPI HCI**: Some modules (CC26xx with external host) use 4-wire SPI for HCI. Use a generic SPI decoder with the correct CPOL/CPHA, then export the MOSI stream for post-processing.
– **Proprietary**: Nordic’s proprietary SWIRE is a single-wire bidirectional protocol. Saleae does not decode it natively, but Nordic’s nRF Sniffer for Bluetooth LE can decode it when used with a compatible capture.

### 4.3 Power Profiling with Logic Analyzer + Oscilloscope

The most common BLE debugging scenario: the module draws more current than expected. Connect a logic analyzer to key GPIOs alongside a current measurement (shunt resistor + oscilloscope or Power Profiler Kit) to correlate software events with current spikes:

| GPIO signal | What to look for in current profile |
|————-|————————————-|
| Radio active | 4-15 mA spike, should be 1-5 ms for typical events |
| CPU active | 2-8 mA, should drop to <10 μA between events | | Flash erase/write | 8-15 mA spike lasting 20-50 ms per page | | UART TX | 1-3 mA for duration of transmission | | ADC sample | 0.5-1.5 mA, short spikes at sample rate | | External sensor read (I2C/SPI) | 1-5 mA depending on sensor | A common finding: the BLE stack's `app_timer` module on nRF52 fires a software timer 200 μs before the radio event, waking the CPU for 1.5 ms to prepare. If your application also has a timer scheduled at the same time, the CPU stays awake longer, adding 2-4 mA average current. Logic analyzer correlation makes this immediately visible. ## 5. RF Debug: Direct Test Mode (DTM) DTM is the standardized (BT 4.2+ Vol 6, Part F) method for testing the BLE radio without going through the full stack. It allows you to: 1. Transmit a constant carrier (CW) at a specific frequency and power 2. Transmit a modulated signal with PRBS9/PRBS15 payload 3. Receive and count packets with configurable parameters 4. Measure packet error rate (PER) vs. RSSI ### DTM Access Methods | Method | Interface | Commands | Typical use | |--------|-----------|----------|-------------| | 2-wire UART | RX/TX pins | HCI vendor-specific | Production test, certification | | 3-wire UART | RX/TX/RTS | Same as 2-wire | Flow control for high-rate tests | | HCI over USB | USB | HCI commands | USB dongle testing | | Vendor proprietary | SWD/JTAG | SoC-specific | SoC-level debug (no firmware) | ### DTM Command Structure (2-wire UART) DTM uses a simple 16-bit command word at 19200 bps (8N1): | Bit | Field | Value | Description | |-----|-------|-------|-------------| | 15 | Type | 0 | Packet type | | 14-6 | Length | 0-37 | Number of bytes in packet (RX) or length to TX | | 5-2 | RF Channel | 0-39 | BLE channel 0-39 (2402-2480 MHz) | | 1-0 | Test Type | 0-3 | 0=PRBS9 TX, 1=PRBS15 TX, 2=PRBS9 RX, 3=CW TX | Example: transmit PRBS9 on channel 19 (2440 MHz), 32 bytes: - Command word = 0b0_00001000_010011_00 = 0x044C - Send as 2 bytes: 0x4C, 0x04 (little-endian) ```c // DTM test sequence for RF verification void dtm_verify_rf(void) { // 1. TX PRBS9 on channel 19, 32 bytes dtm_send(0x044C); delay_ms(100); // Let tester capture dtm_send(0x0000); // End test, read event // 2. RX test on channel 19 dtm_send(0x044E); // bits: 0_00001000_010011_10 delay_ms(500); // Collect packets for 500ms dtm_send(0x0000); // End test uint16_t event = dtm_read_event(); // event[14:0] = number of packets received // event[15] = 0 if OK, 1 if error uint16_t pkt_count = event & 0x7FFF; dbg_printf("DTM RX: %d packets in 500ms ", pkt_count); } ``` ### DTM for Certification Pre-compliance Before sending a module to a certification lab, verify the following DTM tests pass: | Test | DTM command | Pass criteria | Equipment needed | |------|------------|---------------|------------------| | TX power (all 40 ch) | CW TX per channel | ±2 dB of spec | Spectrum analyzer or power meter | | Modulation characteristics | PRBS15 TX | Δf1avg = 225-275 kHz, Δf2avg/Δf1avg ≥ 0.8 | Spectrum analyzer + firmware | | Carrier frequency offset | CW TX | ±50 kHz from nominal | Spectrum analyzer (freq counter mode) | | In-band emissions | PRBS9 TX, 1 ch | < -20 dBm at adjacent ch | Spectrum analyzer | | 20 dB bandwidth | PRBS15 TX | < 2.0 MHz | Spectrum analyzer (RBW 100 kHz) | | PER sensitivity | RX test | < 30.8% PER at -93 dBm (LE 1M) | Signal generator + DTM RX | | PER maximum input | RX test | < 30.8% PER at -20 dBm | Signal generator + DTM RX | | Co-channel rejection | RX + interferer | < 30.8% PER at desired/interferer = +3 dB | 2 signal generators | ## 6. Debug GPIO Strategy: From Development to Production A well-designed BLE module should allocate 2-4 GPIOs specifically for debug purposes, with their function changing between development and production builds: ### Debug GPIO Allocation Example (nRF52832 module) | GPIO | Development function | Production function | Notes | |------|---------------------|--------------------|----| | P0.31 | Radio event toggle | Unused (input, pull-down) | Toggle at radio start/stop | | P0.30 | Timer event toggle | Unused (input, pull-down) | Toggle at app timer fire | | P0.29 | BLE state machine state | Unused (input, pull-down) | 3-bit state code (2 GPIOs) | | P0.28 | BLE state machine state | Unused (input, pull-down) | 3-bit state code (2 GPIOs) | The state-machine GPIOs encode the current BLE state as a binary value: | State code | Meaning | Typical duration | |-----------|---------|-----------------| | 000 | IDLE/sleep | 90-99% of time | | 001 | Advertising prep | 50-100 μs | | 010 | Advertising TX | 80-376 μs | | 011 | Advertising RX (scan response) | 100-200 μs | | 100 | Connection prep | 100-200 μs | | 101 | Connection event TX | 80-200 μs | | 110 | Connection event RX | 100-300 μs | | 111 | Flash/processing | Variable | This gives you a real-time state machine view on a logic analyzer without any software overhead — the GPIO writes happen in interrupt context and take 2-3 cycles each. ### Production Debug Pin Strapping In production, you often cannot afford populated debug headers. A common strategy is to use test pads accessible via pogo-pin fixture: | Pad | Signal | Pogo pin diameter | Notes | |-----|--------|-------------------|-------| | 1 | SWDIO | 0.68 mm | 0.05" pitch, 2x5 pattern | | 2 | SWCLK | 0.68 mm | | | 3 | GND | 0.68 mm | 2x GND pads | | 4 | VDD | 0.68 mm | Target power sense | | 5 | nRESET | 0.68 mm | Optional | | 6 | UART TX | 0.68 mm | Production console | | 7 | UART RX | 0.68 mm | Production console | | 8 | RF_OUT | 0.68 mm | Conducted test (if no antenna) | For modules with an integrated antenna, RF testing requires either an RF connector (U.FL or similar) during development or a coupling fixture (TEM cell or near-field probe) in production. The coupling approach adds ±3-5 dB uncertainty, which is acceptable for go/no-go production testing but not for certification. ## 7. Crash Dump and Post-Mortem Analysis When a BLE module crashes in the field, the debug interfaces discussed so far are unavailable. The module needs a self-contained crash dump mechanism that survives a reset: ### 7.1 Retained RAM (No-reset) Crash Dump ARM Cortex-M retains RAM across a soft reset (NVIC_SystemReset). By placing a magic number at a known RAM address, the bootloader can detect a crash and dump the saved context: ```c // Place in a known RAM section that survives soft reset #define CRASH_MAGIC_ADDR 0x20007FF0 // Last 16 bytes of RAM #define CRASH_MAGIC 0xDEAD5F70 typedef struct { uint32_t magic; uint32_t r0, r1, r2, r3, r12, lr, pc, psr; uint32_t cfsr, hfsr, bfar; uint32_t uptime_ms; uint16_t conn_handle; uint8_t ble_state; uint8_t reserved; } crash_dump_t; // In bootloader (runs before main app): crash_dump_t *cd = (crash_dump_t *)CRASH_MAGIC_ADDR; if (cd->magic == CRASH_MAGIC) {
// Previous crash detected, dump via UART or store to flash
uart_dump_crash(cd);
flash_store_crash(cd);
cd->magic = 0; // Clear so we don’t re-dump
}
“`

### 7.2 Flash-Based Crash Log

For modules that may lose power before the crash dump is retrieved, store crash data in a dedicated flash page. Use a circular log with one entry per crash:

| Field | Size | Description |
|——-|——|————-|
| Magic | 4 B | 0xDEAD5F70 |
| Reset cause | 4 B | RCU reset register |
| Stacked regs | 32 B | R0-R3, R12, LR, PC, xPSR |
| CFSR/HFSR/BFAR | 12 B | Fault status registers |
| Uptime | 4 B | ms since boot |
| Conn handle | 2 B | Active connection handle (0xFFFF = none) |
| BLE state | 1 B | State at crash |
| Reserved | 1 B | Alignment |
| CRC16 | 2 B | Integrity check |
| **Total** | **62 B** | Fits in one flash write unit |

With a 4 KB flash page, you can store 65 crash entries before wrapping. In practice, 10-20 entries is sufficient — if a module crashes more than 20 times without being retrieved, the crash pattern is likely systematic and the first few entries will diagnose it.

### 7.3 Watchdog Timeout Analysis

The most frustrating crash type: the watchdog fires, but there is no HardFault, so the crash dump is empty. To diagnose watchdog timeouts:

1. **Timer ISR snapshot**: In the watchdog feed function, save the current call stack (LR value) to a retained RAM location. When the watchdog fires, the last feed location is preserved.

2. **Periodic heartbeat log**: Write a timestamped heartbeat to flash every 30 seconds. After a watchdog reset, the last heartbeat tells you exactly when the module stopped responding.

3. **Watchdog pre-warning**: Configure the watchdog to fire an early warning interrupt 500 ms before the actual reset. In the ISR, dump the current stack pointer and a few frames:

“`c
void WDT_IRQHandler(void) {
// Early warning — 500ms before reset
uint32_t *sp;
__asm volatile(“mrs %0, psp” : “=r”(sp));
crash_dump_t *cd = (crash_dump_t *)CRASH_MAGIC_ADDR;
cd->magic = CRASH_MAGIC;
cd->pc = sp[6]; // Return address from PSP stack frame
cd->lr = sp[5];
cd->cfsr = SCB->CFSR;
cd->uptime_ms = system_uptime();
cd->ble_state = ble_get_state();
// Save to retained RAM; watchdog will reset in 500ms
while(1);
}
“`

## 8. Debug Security: Lock Bits, Secure Debug, and Production Hardening

Debug interfaces are a security liability. A populated SWD header on a production module means anyone with a $10 CMSIS-DAP probe can read your firmware, extract keys, and clone your module. The defense-in-depth approach:

### 8.1 Lock Bit Levels

| Protection level | Mechanism | What it blocks | Recovery |
|—————–|———–|—————|———-|
| Level 0 (open) | None | Nothing | N/A |
| Level 1 (flash locked) | APPROTECT register | SWD read/write of flash/RAM | Full erase to unlock |
| Level 2 (permanent) | Fuse/OTP bit | All debug access, permanently | None — irreversible |

For nRF52, the `APPROTECT` register at `UICR.APPROTECT = 0xFFFFFF00` enables Level 1. The debug probe can still connect but cannot read flash — it can only do a full erase. Level 2 is available on some SoCs (ESP32-C3 burn efuses, STM32 RDP Level 2) and permanently disables debug access.

### 8.2 Secure Debug (Authenticated Debug)

Some SoCs (nRF5340, CC2640R2 with secure bootloader) support authenticated debug, where the debug probe must present a cryptographic challenge-response before debug access is granted:

“`c
// nRF5340 network core: secure debug challenge
// Debugger must sign the challenge with a pre-provisioned key
typedef struct {
uint8_t challenge[16]; // Random nonce from target
uint8_t signature[64]; // ECDSA-P256 signature from debug cert
uint32_t cert_id; // Which debug certificate to use
} secure_debug_auth_t;
“`

This allows debug access in the field (for authorized service personnel) without permanently compromising the module’s security. The trade-off is additional flash for the crypto library (8-12 KB) and a slightly more complex debug setup.

### 8.3 Production Debug Strategy

| Phase | APPROTECT | UART console | Debug GPIOs | DTM |
|——|———–|————-|————-|—–|
| Bring-up (EVT) | Off | On, 460800 | All populated | Via SWD |
| Verification (DVT) | Off | On, 115200 | All populated | Via SWD |
| Pilot (PVT) | Level 1 | On, 115200 (test pad) | 2 signal pads only | Via test fixture |
| Mass production | Level 1 | Off (test pad for failures) | None | Via test fixture only |
| Field return | Level 1 | Rework to enable | Rework | Rework |

The key principle: every interface that is not needed in production should be either depopulated (headers), disabled (UART via compile flag), or protected (SWD via APPROTECT). Field returns should require physical rework (soldering a header) to re-enable debug access — this prevents casual probing while still allowing failure analysis.

## 9. Common Debug Architecture Mistakes

| Mistake | Impact | Fix |
|———|——–|—–|
| No SWD pull-up on SWDIO | Intermittent probe connection, random “DP read failed” | Add 4.7-10 kΩ external pull-up |
| UART TX on same pin as BLE stack DCX | Garbled radio config, random connection drops | Move UART to a safe pin (see Section 2 table) |
| RTT buffer in non-retained RAM | RTT data lost after sleep/wake | Place RTT buffer in retained RAM section |
| printf in radio ISR | 2+ ms ISR latency, missed packets | Use RTT or defer to idle task |
| No crash dump in retained RAM | Post-reset no diagnostic data | Implement Section 7.1 crash dump |
| Debug GPIOs active in production | Extra current, 50-200 μA per floating input | Configure as input with pull-down in production build |
| DTM not accessible without full firmware flash | Cannot RF-test without erasing app | Bootloader checks for DTM pin strap on boot |
| APPROTECT not set on shipping firmware | Firmware extractable with $10 probe | Set UICR.APPROTECT in production flash script |
| UART baud too high for BLE stack | Character loss during connection events | Use DMA TX or limit to 115200 with 16-byte FIFO |
| No watchdog pre-warning ISR | Cannot diagnose WDT timeouts | Enable WDT early warning interrupt 500ms before reset |

## 10. Debug Interface Checklist for New Module Design

Before taping out a BLE module PCB, verify:

– [ ] SWD pads (SWDIO, SWCLK, nRESET, VDD, GND) accessible as 0.05″ micro-header or test pads
– [ ] SWDIO has 4.7-10 kΩ external pull-up (do not rely on internal)
– [ ] UART TX/RX on safe pins (not conflicting with crystal, NFC, or BLE stack)
– [ ] UART DMA configured for TX, ring buffer for RX
– [ ] Compile-time log level flags (DBG_ERROR/WARN/INFO/DEBUG/VERBOSE)
– [ ] 2-4 debug GPIOs allocated, configurable via compile flag
– [ ] HardFault handler dumps stacked registers to retained RAM + UART
– [ ] Crash dump magic number in retained RAM, bootloader checks on boot
– [ ] Flash crash log (circular, 10-20 entries)
– [ ] Watchdog pre-warning ISR with stack snapshot
– [ ] DTM accessible via 2-wire UART (test pads in production)
– [ ] RF test pad or U.FL connector for conducted DTM testing
– [ ] APPROTECT setting in production flash script
– [ ] Production firmware compiles with DBG_INFO only (no DEBUG/VERBOSE)
– [ ] Debug GPIOs configured as input-pulldown in production build

## Summary

Debug interface design is not an afterthought — it is a first-class engineering discipline that determines how quickly you can diagnose field failures, pass certification, and iterate on firmware. The investment is modest: 2-4 extra GPIOs, a 5-pad SWD footprint, a retained-RAM crash dump handler, and a DTM boot mode. The return is measured in weeks saved during bring-up and thousands of dollars saved avoiding field-return guesswork. On your next Bluetooth module design, budget these interfaces from the schematic review, not from the first bug report.