Cuando un modulo Bluetooth pierde conexiones en el campo, consume 3 mA por encima de la especificacion en reposo, o falla la certificacion en el segundo canal del barrido DTM de 40 canales, la diferencia entre una solucion de un dia y un laberinto de dos semanas es si instrumento el modulo correctamente durante el desarrollo. Este articulo cubre las interfaces fisicas de depuracion, metodos de trazado en tiempo real, rutas de verificacion RF y herramientas de post-mortem que deben disenarse desde el primer dia del esquematico, no anadirse despues del primer fallo en campo.
## 1. Panorama de Interfaces de Depuracion: JTAG vs SWD vs cJTAG
La mayoria de los SoC BLE modernos (nRF52/53, CC2640, ESP32-C3/H2, DA1469x, BGMxx) exponen un JTAG completo de 5 pines (TCK, TMS, TDI, TDO, nTRST) o el SWD de 2 pines especifico de ARM (SWCLK, SWDIO) mas nRESET. La tabla siguiente resume las compensaciones para el diseno de modulos BLE:
| Parametro | JTAG (5 pines) | SWD (2 pines + RESET) | cJTAG (2 pines) |
|———–|————-|———————|—————-|
| Numero de pines | 5 (o 4 sin nTRST) | 2-3 | 2 |
| Reloj maximo | 10-50 MHz | 1-50 MHz | hasta 50 MHz |
| Soporte de traza | ETM (bus traza 4 pines) | ITM (SWO, 1 pin) | ITM (SWO) |
| Velocidad descarga flash (256 KB) | ~8-12 s | ~6-10 s | ~6-10 s |
| Limite longitud cable | ~15 cm (sin buffer) | ~30 cm (sin buffer) | ~20 cm |
| Soporte SoC | Universal (ARM + RISC-V) | ARM Cortex-M solo | ARM Cortex-M33/55 |
| Corriente tipica (depuracion activa) | 2-5 mA | 1-3 mA | 1-3 mA |
Para modulos BLE donde el area de PCB se mide en milimetros cuadrados, SWD es la opcion por defecto. La serie nRF52/53 incluso permite SWD en los mismos pines que GPIO P0.00/P0.01 durante el desarrollo. Se enrutan a un micro-header de paso 0.05 pulgadas (Samtec FTSH-105 o equivalente) y se puebla para desarrollo o se deja como pads de PCB desnudos para produccion. El enfoque de solo pads es preferible porque un header poblado en un modulo de produccion invita acceso no autorizado.
### Consideraciones de Integridad de Senal SWD
SWDIO es bidireccional y drenaje abierto con pull-up (tipicamente 10 kΩ interno, 4.7 kΩ externo). El valor del pull-up importa mas de lo que la mayoria de ingenieros espera:
| Pull-up | Tiempo subida SWDIO (tr) | SWCLK max | Notas |
|———|———————|———–|——-|
| 100 kΩ | 220 ns | 1 MHz | Marginal — perdida de sincronia ocasional |
| 47 kΩ | 104 ns | 4 MHz | OK para cables cortos (<15 cm) |
| 10 kΩ | 22 ns | 25 MHz | Fiable para la mayoria de configuraciones |
| 4.7 kΩ | 10 ns | 50 MHz | Preferido para J-Link a alta velocidad |
| 1 kΩ | 2.2 ns | >50 MHz | Excesivo, desperdicia 3.3 mA en bajo |
La formula es simplemente retardo RC: tr ≈ 2.2 × R × C, donde C es la capacitancia total de traza + sonda (tipicamente 10-20 pF en un modulo con cable plano de 15 cm). Si ve errores intermitentes «SWD DP read failed» en su J-Link, lo primero que debe verificar es si falta el pull-up externo y el interno de 10 kΩ esta luchando contra un cable largo.
## 2. Consola de Depuracion UART: Diseno para Depuracion del Mundo Real
Una consola de depuracion UART es la interfaz de depuracion mas valiosa en firmware de produccion, pero se implementa rutinariamente de forma deficiente. Los errores comunes:
1. **Baud rate demasiado alto para la prioridad de la pila BLE.** A 1 Mbps UART con FIFO de 16 bytes, la interrupcion se dispara cada 128 μs. Si la pila BLE mantiene interrupciones durante 150 μs en la preparacion del evento de conexion, se pierden caracteres. Solucion: usar 460800 bps o 921600 bps con DMA TX y buffer circular para RX.
2. **printf bloqueando el bucle principal.** Un `printf` bloqueante que escribe 200 bytes a 115200 bps tarda 17.4 ms — mas que un intervalo de conexion BLE de 7.5 ms. Use un buffer circular libre de bloqueos con descarga DMA en idle:
«`c
// Buffer circular UART libre de bloqueos (productor unico, consumidor unico)
#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 lleno, descartar caracter
dbg_buf[dbg_head] = tmp[i];
dbg_head = next;
}
// Trigger descarga DMA si TX idle
if (!(UART0->STAT & UART_STAT_TXBUSY)) {
uart_dma_flush();
}
}
«`
3. **Sin filtrado de nivel de log.** Un modulo de produccion emitiendo logs de nivel VERBOSE a 460800 bps durante un evento de conexion causara jitter de temporizacion. Implemente filtrado en tiempo de compilacion y ejecucion:
| Nivel | Flag compilacion | Runtime | Uso tipico |
|——-|————-|———|————-|
| ERROR | `DBG_ERROR` | Siempre activo | Hard faults, fallos de asercion |
| WARN | `DBG_WARN` | Siempre activo | Reintentos, rendimiento degradado |
| INFO | `DBG_INFO` | Configurable | Conexion/desconexion, cambios de estado |
| DEBUG | `DBG_DEBUG` | Off en produccion | Temporizacion por evento, volcados de paquete |
| VERBOSE | `DBG_VERBOSE` | Off en produccion | Planificacion por slot, RSSI por evento |
4. **Sin capacidad de volcado de fallos binario.** Cuando el modulo tiene un hard fault, UART es su unico salvavidas. Pre-asigne un manejador de volcado de fallos de 1 KB que vuelque los registros apilados (R0-R3, R12, LR, PC, xPSR) y las primeras 16 palabras de la pila antes del reset del watchdog:
«`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); // Esperar watchdog
}
```
### Seleccion de Pines UART en Modulos BLE
Evite P0.00/P0.01 (reservados para SWD o cristal de 32 kHz), P0.02/P0.03 (a menudo ADC o analogico), y cualquier pin usado por la pila BLE para temporizacion de radio. Pines UART seguros en SoC comunes:
| SoC | TX seguro | RX seguro | Evitar |
|-----|---------|---------|-------|
| 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. Trazado en Tiempo Real: RTT vs ITM/SWO vs ETM
La depuracion UART basada en printf es buena para eventos de baja frecuencia, pero el analisis de temporizacion de la pila BLE requiere resolucion sub-microsegundo. Existen tres opciones, cada una con diferentes compensaciones:
### Segger RTT (Real-Time Transfer)
RTT usa un buffer circular en RAM del objetivo que el depurador sondea via SWD a alta velocidad. Requiere tiempo cero de CPU por entrada de log (el depurador lee RAM directamente), haciendolo ideal para codigo sensible al tiempo:
| Metrica | UART @ 460800 | RTT | ITM/SWO @ 2 MHz |
|--------|---------------|-----|-----------------|
| Tiempo CPU por log 100 caracteres | ~2.2 ms | ~5 us | ~50 us |
| Throughput | 46 KB/s | ~500 KB/s | ~200 KB/s |
| Latencia (log → depurador) | 2.2 ms | ~10 us | ~25 us |
| Pines requeridos | 2 (TX/RX) | 0 (usa SWD) | 1 (SWO) + SWD |
| Buffer en RAM | 0 | 1-4 KB | 0 (FIFO hardware) |
| Soporte depurador | Cualquier terminal | Solo J-Link | J-Link, ST-Link, CMSIS-DAP |
La principal ventaja de RTT es que funciona a traves de la interfaz SWD existente sin pines adicionales. Su principal limitacion es que requiere un Segger J-Link. Para desarrollo, esto es aceptable; para depuracion en campo, probablemente no tenga un J-Link.
RTT tambien soporta comunicacion bidireccional — el depurador puede inyectar comandos en el objetivo sin usar un pin UART:
```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] = ‘