← Índice del curso

Sistemas Embebidos · Tema 45 · Bloque 10

Depuración con SWD y JTAG

El tema anterior depuraba desde afuera: LEDs, pines, texto. Casi todos los microcontroladores modernos traen además una puerta de servicio dentro del chip: una interfaz de depuración que, con dos o cuatro cables y una sonda de pocos dólares, permite detener el programa, mirar y modificar cualquier registro y cualquier posición de memoria, poner puntos de interrupción, grabar la flash y hasta recibir mensajes sin pasar por una UART. Es la herramienta más potente de todo el bloque, y también la que más conviene entender por dentro: cómo se conecta, qué se puede y qué no se puede hacer con ella, qué la vuelve engañosa en un sistema de tiempo real, y cómo usarla para el caso más típico, el HardFault que cuelga el equipo.

01 · La puerta de servicio

La interfaz de depuración dentro del chip

Un microcontrolador con núcleo ARM Cortex-M incluye, junto a la CPU, un bloque de depuración que funciona de forma independiente del programa. Desde afuera se llega a él por un puerto de dos o cuatro pines, y a través de él se accede a un bus de acceso que ve el mismo mapa de memoria que la CPU (tema 28): la flash, la RAM, los registros de los periféricos y los propios registros del núcleo. Por eso un depurador puede leer y escribir cualquier dirección sin que el programa lo sepa, y puede detener el núcleo para inspeccionarlo con calma.

Cadena de depuración: PC, sonda y microcontrolador De izquierda a derecha: la computadora con el programa de depuración (gdb o el IDE) se conecta por USB a una sonda; la sonda se conecta con dos o cuatro cables (SWD o JTAG) al puerto de depuración del microcontrolador. Dentro del chip, el puerto de acceso (DAP) da acceso al bus donde están la CPU, la flash, la RAM y los periféricos, y a los bloques de depuración (comparadores de puntos de interrupción y de datos, y la traza). ComputadoraIDE · gdb · OpenOCD SondaST-Link, J-Link… Microcontrolador DAPpuerto de acceso CPU Flash RAM Periféricos FPB · DWT · ITMpuntos, datos y traza USBSWD / JTAG2 o 4 hilos

La CPU y el puerto de depuración son dos agentes que comparten el bus. Esa independencia explica casi todo lo que sigue: el depurador puede mirar la memoria con el programa corriendo, puede grabar la flash con el núcleo detenido, y, cuando detiene el núcleo, el resto del chip sigue funcionando: los temporizadores, la UART y el reloj del mundo exterior no se enteran de que el programa está en pausa. Esa última frase tiene consecuencias importantes en la sección 9.

02 · El estándar de los chips

JTAG: cuatro hilos y una máquina de estados

JTAG (nombre del grupo que lo definió, el estándar IEEE 1149.1) nació en los años noventa para probar las conexiones entre chips de una placa (boundary scan) y terminó siendo la interfaz de depuración universal. Usa cuatro señales (y una quinta opcional):

Señales de JTAG.
SeñalSentido (visto desde el chip)Función
TCKEntradaReloj: lo genera la sonda; cada flanco avanza todo un paso
TMSEntradaSelección de modo: en cada flanco de TCK, su valor decide el estado siguiente de la máquina de estados
TDIEntradaDatos hacia el chip
TDOSalidaDatos desde el chip
nTRST (opcional)EntradaReinicio asíncrono de la lógica JTAG

Dentro de cada chip hay un controlador llamado TAP (Test Access Port), una máquina de estados de 16 estados que solo se maneja con TMS: nada de direcciones ni comandos, solo un bit por ciclo de reloj. Hay un camino para cargar una instrucción (el registro IR) y otro para mover datos por el registro que esa instrucción haya elegido (el registro DR): el IDCODE que identifica al chip, el BYPASS de un solo bit, o los registros de acceso al depurador ARM. En el estado Shift cada ciclo empuja un bit desde TDI hacia dentro y otro hacia fuera por TDO; en Update lo cargado se hace efectivo.

/* La máquina de estados del TAP, en forma de tabla: estado -> {siguiente con TMS=0, siguiente con TMS=1} */
typedef enum { TLR, RTI, SEL_DR, CAP_DR, SH_DR, EX1_DR, PAU_DR, EX2_DR, UP_DR,
               SEL_IR, CAP_IR, SH_IR, EX1_IR, PAU_IR, EX2_IR, UP_IR } EstadoTap;

static const EstadoTap SIGUIENTE[16][2] = {
    [TLR]    = { RTI,    TLR    },  [RTI]    = { RTI,    SEL_DR },
    [SEL_DR] = { CAP_DR, SEL_IR },  [CAP_DR] = { SH_DR,  EX1_DR },  [SH_DR]  = { SH_DR,  EX1_DR },
    [EX1_DR] = { PAU_DR, UP_DR  },  [PAU_DR] = { PAU_DR, EX2_DR },  [EX2_DR] = { SH_DR,  UP_DR  },  [UP_DR]  = { RTI, SEL_DR },
    [SEL_IR] = { CAP_IR, TLR    },  [CAP_IR] = { SH_IR,  EX1_IR },  [SH_IR]  = { SH_IR,  EX1_IR },
    [EX1_IR] = { PAU_IR, UP_IR  },  [PAU_IR] = { PAU_IR, EX2_IR },  [EX2_IR] = { SH_IR,  UP_IR  },  [UP_IR]  = { RTI, SEL_DR },
};
/* Cinco ciclos seguidos con TMS=1 llevan a Test-Logic-Reset desde cualquier estado: así arranca toda sonda. */

La Parte 2 del programa de Python construye esa tabla y busca por anchura los caminos más cortos. Para leer el IDCODE de un chip desde el reset hacen falta 4 ciclos (TMS = 0, 1, 0, 0) hasta el estado de desplazamiento, 32 ciclos para sacar los bits y 2 para volver al reposo: 38 ciclos de TCK en total. La virtud más grande de JTAG aparece al conectar varios chips en cadena: la salida TDO de uno va a la entrada TDI del siguiente, y una sola sonda con cuatro hilos los atiende a todos. Con tres chips, leer los tres IDCODE son 4 + 96 + 2 = 102 ciclos; para hablar con uno solo, los otros se ponen en BYPASS y cada uno agrega apenas un bit de retardo al registro.

03 · El hermano de dos hilos

SWD: dos hilos

ARM definió, para los Cortex-M, una alternativa que usa solamente dos pines: SWCLK (reloj, lo genera la sonda) y SWDIO (datos, bidireccional). En un microcontrolador con pocos pines, ahorrar dos de los cuatro de JTAG vale mucho, y por eso SWD es la opción habitual en los Cortex-M. Usa el mismo bloque de acceso de depuración (el DAP) que JTAG, pero con un protocolo de paquetes en lugar de una máquina de estados: cada transacción tiene tres fases, petición, respuesta y datos.

Una transacción SWD de lectura o escritura de 32 bits.
FaseBitsContenido
Petición (la manda la sonda)8Inicio (1), APnDP (¿acceso al puerto de acceso o al de depuración?), RnW (¿lectura o escritura?), A[2], A[3] (la dirección del registro), paridad, parada (0) y park (1)
Giro del bus1El pin SWDIO cambia de dueño
Respuesta (el chip)3ACK: OK, WAIT (ocupado, reintentar) o FAULT
Datos32 + 1La palabra de 32 bits y su bit de paridad; quien los manda depende de si es lectura o escritura (con un giro del bus antes de la escritura)
Giro final1El bus vuelve a la sonda

La Parte 1 del programa arma las peticiones. El paquete para leer el IDCODE del puerto de depuración es el byte 0xA5: inicio 1, APnDP 0, RnW 1, A[3:2] = 00, paridad 1 (para que el número de unos sea par), parada 0 y park 1, enviado desde el bit menos significativo. Escribir el registro TAR del puerto de acceso es 0x8B y leer el DRW, 0x9F. Cada transacción ocupa 8 + 1 + 3 + 32 + 1 + 1 = 46 ciclos de SWCLK, y leer memoria significa repetirlas: se escribe la dirección en TAR una vez y luego se lee DRW con autoincremento, una palabra por transacción.

Tiempo ideal para leer memoria por SWD (resultado de la Parte 1).
Reloj SWCLKLeer 4 KBRendimientoLeer 128 KB
1 MHz47,2 ms87 KB/s1,51 s
4 MHz11,8 ms347 KB/s0,38 s
10 MHz4,7 ms867 KB/s0,15 s

Son mejores casos: en la práctica la sonda, el USB y el programa de la PC agregan demoras, y la grabación de la flash tarda mucho más que la lectura porque la propia flash es lenta de escribir (tema 40). Pero la cuenta da una idea correcta: a 4 MHz el volcado de toda la RAM de un equipo chico lleva una fracción de segundo. Si el reloj es demasiado rápido para el cableado o para el reloj del núcleo, aparecen errores (ACK incorrecto, paridad): bajar la velocidad de SWCLK es el primer remedio ante una conexión inestable.

SWD y JTAG, lado a lado.
AspectoJTAGSWD
Pines4 o 5 (TCK, TMS, TDI, TDO, nTRST)2 (SWCLK, SWDIO)
Varios chips en una cadenaSí, con una sondaUn solo punto de acceso por conexión (con selección por dirección en SWD multipunto)
Núcleos que lo soportanCasi todos: ARM, RISC-V, DSP, FPGASolo ARM (Cortex-M y otros)
Ciclos por acceso a un registro de 32 bitsUnos 40 (más 10 al cambiar de instrucción)46
Boundary scan (probar conexiones de la placa)SíNo
Traza por un pin extraTDO se comparte con SWO en algunos casosPin SWO opcional

Para un Cortex-M suelto y una sola placa, SWD gana por los pines; para una placa con varios chips programables (un microcontrolador, una FPGA y un procesador) o para probar las conexiones en fábrica, JTAG. Casi todas las sondas hablan los dos, y muchos chips arrancan con ambos habilitados, compartiendo pines: SWDIO con TMS y SWCLK con TCK.

04 · Los cables

Conectores, cableado y reset

La conexión física es lo primero que falla, y la causa de la mayoría de los «no encuentra el dispositivo». El conector estándar de ARM para Cortex-M es un encabezado de 10 pines con paso de 1,27 mm (el de 20 pines con paso de 1,27 mm es la versión grande); en placas chicas se usan versiones sin conector, con almohadillas que se contactan con una pinza (Tag-Connect) o los pines cableados a una tira de 4 o 5 pasos de 2,54 mm.

Conector de depuración Cortex de 10 pines (señales principales).
PinSeñalPara qué sirve
1VTrefTensión de referencia: la sonda adapta sus niveles a la tensión del equipo y detecta si está alimentado
2SWDIO / TMSDatos de SWD
4SWCLK / TCKReloj
6SWO / TDOSalida de traza (SWD) o datos de salida (JTAG)
8TDIDatos de entrada (solo JTAG)
10nRESETPermite a la sonda reiniciar el equipo
3, 5, 9GNDMasa; varias, para que el retorno sea corto

Algunas reglas que ahorran horas: masa común y corta (un cable de masa largo es la causa clásica de errores intermitentes); cables cortos (los de menos de 10 a 15 cm admiten velocidades altas; los largos, no); VTref conectado, para que la sonda use los niveles correctos (una placa de 1,8 V con una sonda a 3,3 V puede dañarse); no alimentar la placa desde la sonda más allá de su capacidad (la sonda entrega poca corriente, y menos aún cuando el equipo arranca una radio, tema 36); y el pin de reset conectado, que permite a la sonda recuperar un chip cuyo programa deshabilitó los pines de depuración. Los pines de SWD no se deben reutilizar para otra función durante la etapa de desarrollo, y se debe poder acceder a ellos también en la placa final, aunque solo sea con almohadillas de prueba.

Una trampa conocida: un programa que reconfigura los pines de SWD como GPIO, o que entra en un modo de bajo consumo que apaga el reloj del bloque de depuración (tema 35), deja al chip sin conexión para la sonda. La salida es mantener presionado el reset mientras se conecta («connect under reset»), de manera que la sonda tome el control antes de que el programa llegue a ejecutar esa línea. Conviene saberlo antes de tener sobre la mesa un equipo «bloqueado».

05 · Controlar la ejecución

Puntos de interrupción, watchpoints y paso a paso

Con el núcleo detenido o en marcha, el depurador ofrece cuatro formas de controlar y observar la ejecución:

  • Punto de interrupción (breakpoint): detiene el programa cuando el contador de programa llega a una dirección. Puede ser de hardware (un comparador dentro del chip, la unidad FPB en los Cortex-M3 y M4, que compara la dirección de captura de instrucciones con unas pocas direcciones configuradas, y funciona también con el código en flash) o de software (el depurador escribe en la posición una instrucción BKPT; solo se puede hacer donde se puede escribir, es decir, en RAM, o reprogramando la flash, lo que es lento y desgasta la memoria).
  • Punto de observación de datos (watchpoint): detiene el programa cuando se lee o escribe una dirección de datos. Lo implementa la unidad DWT. Es la herramienta ideal para el error «una variable cambia sola»: se pone un watchpoint de escritura sobre la variable y el programa se detiene en la instrucción que la pisa (tema 44).
  • Paso a paso (step): ejecuta una línea o una instrucción y vuelve a detenerse.
  • Detención a pedido (halt): detiene el núcleo en el punto en que esté, para ver en dónde se colgó. Es lo primero que se hace con un equipo que «no responde».
Recursos de hardware típicos (varían por modelo: confirmar en el manual del núcleo).
NúcleoBreakpoints de hardwareWatchpoints (DWT)Registro de fallas (CFSR)
Cortex-M0 / M0+hasta 4hasta 2No (solo HardFault)
Cortex-M3 / M4hasta 6 de código (más 2 de literales)4 comparadoresSí

La consecuencia práctica es que los breakpoints de hardware son pocos: seis en un M4. Con el código en flash, el depurador agota el cupo rápido; el IDE avisa cuando se pasa y, en el mejor de los casos, se ponen y se quitan sobre la marcha. Un breakpoint condicional («detenerse solo si i == 500») se implementa deteniendo el núcleo en cada paso por ahí y evaluando la condición desde la PC: puede ser miles de veces más lento que el código real, y en una rutina de 1 kHz vuelve imposible la ejecución normal. Para esos casos, el tema anterior ya dio la salida: un pin de prueba, un registro de eventos, o un watchpoint con una condición simple.

06 · Las herramientas

Sondas y programas

Entre la PC y los pines hay una sonda que traduce el USB a SWD o JTAG. La oferta habitual:

Sondas y programas frecuentes.
HerramientaQué esComentario
ST-LinkSonda de ST, integrada en las placas de desarrollo y también sueltaLa más accesible para STM32; muchas placas permiten usarla con otros chips
J-Link (Segger)Sonda profesionalRápida, con soporte para muchos chips, RTT, y programa de pruebas; hay versiones educativas
CMSIS-DAPProtocolo abierto de ARM para sondas de bajo costoMuchas placas lo traen; funciona con OpenOCD y los IDE
OpenOCDPrograma de código abierto que habla con la sonda y ofrece un servidor para gdbEl puente habitual con herramientas abiertas
gdb (y su interfaz en el IDE)El depurador propiamente dichoInterpreta los símbolos del ejecutable (el archivo ELF), mostrando variables y líneas de código

La sonda solo mueve bits; lo que le da sentido a los números es el archivo ELF con la información de depuración (la opción -g del compilador, tema 12): sin él, el depurador ve direcciones y bytes sueltos. Esa información de símbolos no se graba en el microcontrolador, vive en la PC: por eso se puede depurar un equipo con el ELF que corresponda exactamente al firmware grabado; un ELF distinto del binario que corre da una vista engañosa (variables que no coinciden, líneas que no son las que se ejecutan). Al usar versiones (tema 42), se guarda el ELF de cada versión liberada: sin él, un cuelgue en el campo apenas muestra direcciones.

# Dos terminales: OpenOCD habla con la sonda; gdb habla con OpenOCD (sintaxis típica; los nombres de los archivos cambian según la sonda y el chip)
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg

arm-none-eabi-gdb build/firmware.elf
(gdb) target extended-remote :3333
(gdb) monitor reset halt             # reinicia y detiene el núcleo
(gdb) load                           # graba la flash con el ELF
(gdb) break procesar_muestra         # breakpoint (de hardware si está en flash)
(gdb) watch contador                 # watchpoint: se detiene al escribir la variable
(gdb) continue
(gdb) info registers                 # R0-R12, SP, LR, PC, xPSR
(gdb) x/8wx $sp                      # 8 palabras desde el SP
(gdb) bt                             # pila de llamadas

07 · Mirar con el programa corriendo

Depurar sin detener: ITM, SWO y RTT

Los puntos de interrupción detienen el programa y, en un sistema de tiempo real, eso cambia lo que se quiere observar. Hay mecanismos para sacar información con el programa corriendo, con un costo de microsegundos, y el depurador es el canal:

  • ITM / SWO (Cortex-M3 y superiores): el programa escribe a unos puertos de estímulo (registros especiales) y el hardware envía esos bytes por el pin SWO, empaquetados con un encabezado, hacia la sonda, sin intervención de la CPU. El núcleo solo gasta unos ciclos por escritura, y no espera la salida en la mayor parte de los casos. Requiere el pin SWO conectado y la velocidad configurada igual en el chip y en la sonda.
  • RTT (Real-Time Transfer, de Segger): el programa escribe en un buffer circular en RAM que el depurador lee por SWD sin parar el núcleo. No usa un pin extra, solo SWD. El costo es una copia en RAM: microsegundos.
  • Semihosting: la entrada y la salida estándar del programa se redirigen a la PC a través del depurador. Es cómodo, pero cada operación detiene el núcleo con una instrucción BKPT mientras el depurador la atiende, y sin un depurador conectado el programa se cuelga: no se debe dejar en el firmware que se entrega.
  • Traza de instrucciones (ETM) en núcleos más grandes: graba la secuencia completa de instrucciones ejecutadas con un hardware dedicado y varios pines; es la herramienta más completa y la más costosa.
/* ITM: escribir un carácter en el puerto de estímulo 0 (CMSIS: ITM_SendChar) sin esperar un UART */
#include "core_cm4.h"
static void itm_texto(const char *s) { while (*s) ITM_SendChar((uint32_t)*s++); }   /* espera solo si el FIFO está lleno */

/* RTT (biblioteca de Segger): un buffer en RAM que el depurador lee a su ritmo */
#include "SEGGER_RTT.h"
void inicio(void) { SEGGER_RTT_Init(); }
void registrar(int valor) { SEGGER_RTT_printf(0, "valor=%d\n", valor); }               /* copia a RAM; no depende del reloj de un puerto serie */

La Parte 4 del programa compara con la consola por UART del tema anterior. Con SWO a 2 Mbaudios (un valor supuesto: depende de la sonda y del reloj del núcleo), el canal mueve 200 000 B/s (10 bits por byte). Cada byte escrito a un puerto de estímulo viaja con un encabezado, así que un mensaje de 40 caracteres ocupa 80 B si se escribe byte a byte y 50 B si se escribe de a palabras de 4 bytes: 2500 o 4000 mensajes por segundo, contra los 288 de una UART a 115 200 baudios. Y el costo en el núcleo, con un supuesto de 30 ciclos a 48 MHz, son unos 0,62 µs, unas 5555 veces menos que imprimir por la UART bloqueando. Es la técnica del tema 44 llevada al máximo: el registro diferido en un canal rápido, que no necesita ni UART.

08 · El caso típico

Analizar un HardFault

Cuando el procesador intenta algo imposible (leer una dirección que no existe, ejecutar una instrucción inválida, dividir por cero si se lo pidió), entra en una excepción de falla. Si no hay un manejador específico, se escala a HardFault, y el programa suele terminar en un bucle infinito del archivo de arranque, es decir, colgado. El síntoma, desde afuera, es solo «el equipo dejó de responder». Pero el chip guarda las pistas, y el depurador las lee:

  • El cuadro de pila apilado por el hardware. Al entrar en la excepción, el núcleo guarda en la pila ocho palabras: R0, R1, R2, R3, R12, LR, PC, xPSR. El PC apilado es la dirección de la instrucción que estaba en curso al producirse la falla, y es la pista principal: con el archivo .map o el ELF se traduce a una función y una línea.
  • CFSR (Configurable Fault Status Register, solo en M3 y superiores): una palabra de bits que dice qué pasó: acceso a un lugar inexistente, instrucción indefinida, división por cero, acceso no alineado, uso de la FPU sin habilitarla, falla al apilar (desborde de pila).
  • BFAR y MMFAR: la dirección que se intentó acceder, cuando el hardware la conoce (bit de validez en CFSR).
  • HFSR: indica que la falla se escaló a HardFault (FORCED).
Bits de CFSR más útiles (núcleos ARMv7-M) y lo que sugieren.
BitNombreQué sugiere
9 y 15PRECISERR y BFARVALIDAcceso de datos a una dirección inválida, con la dirección en BFAR: puntero o índice mal
10IMPRECISERRError de bus detectado tarde: el PC es aproximado
12STKERRFalló el apilado del cuadro: la pila se desbordó
16UNDEFINSTRInstrucción inexistente: se ejecutan datos (dirección de retorno pisada)
17INVSTATESalto con el bit de Thumb en cero: puntero a función corrupto
19NOCPInstrucción de coprocesador (FPU) sin habilitar
24UNALIGNEDAcceso no alineado (si se activó la trampa)
25DIVBYZERODivisión por cero (si se activó la trampa)

El código para captar esa información en el propio equipo, sin depurador, es lo que convierte un cuelgue en el campo en un dato (tema 37): un manejador de HardFault que guarda el cuadro de pila y los registros en una zona de RAM que sobrevive al reinicio y que, tras reiniciar, se informa:

/* El HardFault_Handler elige la pila en uso (MSP o PSP, según el bit 2 de LR) y llama a una función en C con el puntero al cuadro apilado */
__attribute__((naked)) void HardFault_Handler(void) {
    __asm volatile ("tst lr, #4        \n"
                    "ite eq            \n"
                    "mrseq r0, msp     \n"
                    "mrsne r0, psp     \n"
                    "b hardfault_c     \n");
}

typedef struct { uint32_t pc, lr, sp, cfsr, hfsr, bfar, mmfar; uint32_t sello; } InformeFalla;
static InformeFalla informe __attribute__((section(".noinit")));          /* sobrevive al reinicio (tema 29) */

void hardfault_c(uint32_t *marco) {
    informe.pc    = marco[6];                  /* R0-R3, R12, LR, PC, xPSR: el PC apilado es el elemento 6 */
    informe.lr    = marco[5];
    informe.sp    = (uint32_t)marco;
    informe.cfsr  = SCB->CFSR;  informe.hfsr = SCB->HFSR;
    informe.bfar  = SCB->BFAR;  informe.mmfar = SCB->MMFAR;
    informe.sello = 0xFA11ED00u;               /* marca de que hay un informe válido */
#ifdef DEPURANDO
    __BKPT(0);                                 /* con depurador: detenerse aquí; sin él, nunca en el producto */
#endif
    NVIC_SystemReset();                        /* en el producto: reiniciar y reportar tras arrancar */
}

La Parte 3 del programa arma ocho fallas típicas con sus bits y las decodifica. Una escritura por un puntero a una dirección inexistente deja CFSR = 0x00008200 (PRECISERR y BFARVALID) y BFAR con la dirección culpable, con un PC apilado que, buscado en el mapa, cae en procesar_muestra+0x1A. Una división por cero deja 0x02000000 y apunta a calcular_promedio+0x14. Ejecutar datos deja 0x00010000 con un PC en la RAM (0x20000150): como ninguna función vive allí, se sabe que se ejecutó información en lugar de código, casi seguro una dirección de retorno pisada. Y un desborde de pila deja 0x00001000, con un SP fuera de la RAM. El laboratorio de más abajo permite recorrerlos todos.

09 · Cuando el equipo sale al mundo

El depurador y el producto final

Una puerta de servicio que permite leer y escribir cualquier posición de memoria es, para quien la use sin permiso, una puerta abierta: se lee el firmware y las claves (tema 43), se lo modifica y se instala uno malicioso. Por eso los fabricantes ofrecen formas de cerrarla, y el producto debe usarlas:

Niveles de protección de la interfaz de depuración (los nombres y detalles dependen del fabricante).
NivelEfectoCuándo usarlo
AbiertoSe lee y se escribe todoDesarrollo y primeras pruebas
Protección de lecturaLa sonda no puede leer la flash; borrar total la reabre (borrando el firmware)Productos de serie: protege la propiedad intelectual y las claves
Depuración deshabilitada de forma permanenteYa no se conecta ningún depurador (en algunos chips, irreversible)Productos de alto riesgo; se pierde la posibilidad de analizar fallas en el campo

La decisión no es obvia: cerrar la puerta protege, pero impide analizar un equipo devuelto del campo. Una política razonable es dejar el nivel intermedio en producción y conservar el registro de eventos y el informe de falla del apartado anterior para el diagnóstico remoto (tema 37); y, si hace falta depurar un equipo cerrado, que el procedimiento sea un desbloqueo controlado (con un borrado total) o una puerta de depuración protegida por una clave (debug authentication, que ofrecen algunos fabricantes). El conector físico también se puede dejar sin poblar, aunque la protección verdadera es la del chip y no la del conector.

Dos efectos más del depurador, en el sentido contrario: con la sonda conectada, el microcontrolador consume más y puede no entrar en el modo de bajo consumo profundo, porque el bloque de depuración mantiene relojes encendidos (hay bits para permitir la depuración en reposo, con ese costo). Las medidas de consumo del tema 36 deben hacerse sin la sonda conectada; con ella, se mide otro sistema. Y, como se vio, watchdog y temporizadores se congelan con el núcleo detenido solo si se lo configura.

10 · Analogía con programación

Probar el modelo en Python

El programa reúne cuatro piezas: la construcción de los paquetes de petición de SWD con su paridad y el tiempo ideal de lectura de memoria; la máquina de estados del TAP de JTAG con una búsqueda del camino más corto de TMS; la decodificación de ocho HardFault a partir de los bits de CFSR y el PC apilado, traducido con una tabla de símbolos; y el ancho de banda de la traza por SWO comparado con el de la UART.

Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.

# Depuración con SWD y JTAG: paquetes SWD, máquina de estados del TAP JTAG, decodificación de un HardFault y ancho de banda de la traza.
import bisect

# ---- Parte 1: el paquete de petición de SWD y el costo de leer memoria ----------------------------------------------------------------
def pedido_swd(apndp, rnw, a):                                     # a = dirección de registro (0, 4, 8, 12): bits A[3:2]
    a2, a3 = (a >> 2) & 1, (a >> 3) & 1
    paridad = (apndp + rnw + a2 + a3) % 2                          # paridad par sobre APnDP, RnW, A2 y A3
    bits = [1, apndp, rnw, a2, a3, paridad, 0, 1]                  # inicio, APnDP, RnW, A2, A3, paridad, parada, park (se envía primero el bit 0)
    return sum(b << i for i, b in enumerate(bits))
print("Parte 1: paquetes de petición de SWD (8 bits, se envía primero el menos significativo)")
for nombre, args in (("leer DP IDCODE (reg 0x0)", (0, 1, 0x0)), ("escribir AP TAR (reg 0x4)", (1, 0, 0x4)), ("leer AP DRW (reg 0xC)", (1, 1, 0xC))):
    print(f"  {nombre:<28} -> 0x{pedido_swd(*args):02X}")
CLK_TRANS = 8 + 1 + 3 + 32 + 1 + 1                                 # petición, giro, ACK, datos, paridad, giro
print(f"  una transacción de lectura o escritura de 32 bits ocupa {CLK_TRANS} ciclos de SWCLK (8 + 1 + 3 + 32 + 1 + 1)")
def leer_memoria(n_bytes, mhz):                                    # CSW y TAR se escriben una vez; DRW se lee con autoincremento; +1 lectura final de RDBUFF
    palabras = n_bytes // 4
    ciclos = (2 + palabras + 1) * CLK_TRANS
    return ciclos / (mhz * 1e6)
for mhz in (1, 4, 10):
    t4k, t128k = leer_memoria(4096, mhz), leer_memoria(131072, mhz)
    print(f"  SWCLK {mhz:>2} MHz: leer 4 KB en {t4k * 1000:6.1f} ms ({4096 / t4k / 1000:5.0f} KB/s); 128 KB en {t128k:5.2f} s (mejor caso, sin demoras de la PC ni de la sonda)")

# ---- Parte 2: la máquina de estados del TAP de JTAG ------------------------------------------------------------------------------------
T = {  # estado: (siguiente con TMS=0, siguiente con TMS=1)
 "TLR": ("RTI", "TLR"), "RTI": ("RTI", "SelDR"), "SelDR": ("CapDR", "SelIR"), "CapDR": ("ShDR", "Ex1DR"), "ShDR": ("ShDR", "Ex1DR"),
 "Ex1DR": ("PauDR", "UpDR"), "PauDR": ("PauDR", "Ex2DR"), "Ex2DR": ("ShDR", "UpDR"), "UpDR": ("RTI", "SelDR"),
 "SelIR": ("CapIR", "TLR"), "CapIR": ("ShIR", "Ex1IR"), "ShIR": ("ShIR", "Ex1IR"), "Ex1IR": ("PauIR", "UpIR"), "PauIR": ("PauIR", "Ex2IR"),
 "Ex2IR": ("ShIR", "UpIR"), "UpIR": ("RTI", "SelDR")}
def camino(desde, hasta):                                          # búsqueda en anchura del camino más corto de TMS
    cola, visto = [(desde, [])], {desde}
    while cola:
        e, tms = cola.pop(0)
        if e == hasta: return tms
        for b in (0, 1):
            s = T[e][b]
            if s not in visto: visto.add(s); cola.append((s, tms + [b]))
print()
print(f"Parte 2: el TAP de JTAG tiene {len(T)} estados y se maneja solo con TMS en cada flanco de TCK")
c_idcode = camino("TLR", "ShDR")
print(f"  de Test-Logic-Reset a Shift-DR: TMS = {c_idcode} ({len(c_idcode)} ciclos); tras reset el registro seleccionado es IDCODE")
def leer_idcodes(n):                                               # n dispositivos en cadena, cada uno con su IDCODE de 32 bits
    return len(c_idcode) + 32 * n + len(camino("Ex1DR", "RTI"))
for n in (1, 3):
    print(f"  leer el IDCODE de una cadena de {n} dispositivo(s): {len(c_idcode)} + {32 * n} + 2 = {leer_idcodes(n)} ciclos de TCK")
ir_bits = 4
c_ir = len(camino("RTI", "ShIR")) + ir_bits + len(camino("Ex1IR", "RTI"))
c_dr = len(camino("RTI", "ShDR")) + 35 + len(camino("Ex1DR", "RTI"))
print(f"  acceso al ARM por JTAG: cambiar la instrucción ({ir_bits} bits) cuesta {c_ir} ciclos y un registro de 35 bits (3 de ACK, 32 de datos), {c_dr}; en SWD, {CLK_TRANS}")
cadena = 3
print(f"  en una cadena de {cadena} chips, para hablar con uno, los otros {cadena - 1} se ponen en BYPASS: cada uno agrega 1 bit al registro de datos ({35 + cadena - 1} bits)")
print("  pines: JTAG usa TCK, TMS, TDI, TDO (y, a veces, nTRST) = 4 o 5; SWD usa SWCLK y SWDIO = 2 (más el reset y la masa)")

# ---- Parte 3: decodificar un HardFault a partir de CFSR y del PC apilado -----------------------------------------------------------------
BITS = {0: "IACCVIOL", 1: "DACCVIOL", 3: "MUNSTKERR", 4: "MSTKERR", 7: "MMARVALID", 8: "IBUSERR", 9: "PRECISERR", 10: "IMPRECISERR",
        11: "UNSTKERR", 12: "STKERR", 15: "BFARVALID", 16: "UNDEFINSTR", 17: "INVSTATE", 18: "INVPC", 19: "NOCP", 24: "UNALIGNED", 25: "DIVBYZERO"}
POR_NOMBRE = {v: k for k, v in BITS.items()}
SIMBOLOS = [(0x08000190, "main"), (0x080001F0, "sensor_leer"), (0x08000A80, "procesar_muestra"), (0x08000B10, "calcular_promedio"),
            (0x08000C40, "enviar_trama"), (0x08000CF0, "TIM2_IRQHandler"), (0x08000D80, "_etext")]
def simbolo(pc):
    if not (0x08000000 <= pc < 0x08000D80): return "FUERA DE LA FLASH (¿se ejecutan datos?)"
    i = bisect.bisect_right([a for a, _ in SIMBOLOS], pc) - 1
    return f"{SIMBOLOS[i][1]}+0x{pc - SIMBOLOS[i][0]:X}"
CASOS = [("escritura por un puntero a una dirección inexistente", ["PRECISERR", "BFARVALID"], 0x08000A9A),
         ("división por cero (con DIV_0_TRP activado)", ["DIVBYZERO"], 0x08000B24),
         ("salto a una dirección sin el bit de Thumb", ["INVSTATE"], 0x08000C58),
         ("ejecución de datos: salto a la RAM", ["UNDEFINSTR"], 0x20000150),
         ("acceso no alineado (con UNALIGN_TRP activado)", ["UNALIGNED"], 0x08000AB2),
         ("desborde de pila hasta fuera de la RAM", ["STKERR"], 0x08000A88),
         ("escritura de un búfer, error impreciso", ["IMPRECISERR"], 0x08000D1C),
         ("instrucción de punto flotante con la FPU apagada", ["NOCP"], 0x08000AE0)]
print()
print("Parte 3: de CFSR y del PC apilado a una causa (cada caso arma el registro con sus bits y lo decodifica)")
for nombre, nombres, pc in CASOS:
    cfsr = sum(1 << POR_NOMBRE[n] for n in nombres)
    leidos = [BITS[b] for b in sorted(BITS) if cfsr >> b & 1]
    assert leidos == sorted(nombres, key=lambda n: POR_NOMBRE[n])
    print(f"  {nombre}\n      CFSR = 0x{cfsr:08X} -> {', '.join(leidos)}; PC apilado 0x{pc:08X} = {simbolo(pc)}")
print("  el cuadro apilado por el hardware al entrar a la excepción: R0, R1, R2, R3, R12, LR, PC, xPSR (8 palabras = 32 bytes), en ese orden desde el SP")

# ---- Parte 4: ancho de banda de la traza por SWO (ITM) frente a la UART ------------------------------------------------------------------
SWO_BAUD = 2_000_000                                               # SUPUESTO: SWO asíncrono a 2 Mbaudios (depende de la sonda y del reloj del núcleo)
bytes_s_swo = SWO_BAUD / 10
msg = 40
por_bytes = msg * 2                                                # cada byte escrito al puerto de estímulo viaja con un encabezado: 2 bytes
por_palabras = (msg // 4) * (1 + 4)                                # una palabra de 4 bytes: encabezado + 4 datos
print()
print("Parte 4: salida de texto por ITM/SWO (puerto de estímulo) frente a la UART")
print(f"  SWO a {SWO_BAUD / 1e6:.0f} Mbaudios (SUPUESTO) mueve {bytes_s_swo:,.0f} B/s (10 bits por byte)".replace(",", " "))
print(f"  un mensaje de {msg} caracteres ocupa {por_bytes} B si se escribe byte a byte y {por_palabras} B si se escribe de a 4 bytes")
print(f"  mensajes por segundo: {bytes_s_swo / por_bytes:,.0f} byte a byte; {bytes_s_swo / por_palabras:,.0f} de a 4 bytes; la UART a 115 200 admite {115200 / 10 / msg:.0f}".replace(",", " "))
ciclos = (msg // 4) * 3                                            # SUPUESTO: 3 ciclos por escritura de palabra al registro de estímulo
print(f"  costo en el núcleo (SUPUESTO: {ciclos} ciclos a 48 MHz): {ciclos / 48:.2f} µs, contra 3472 µs de imprimir por la UART bloqueando ({3472 / (ciclos / 48):,.0f} veces más)".replace(",", " "))

Resultado, parte 1: las peticiones SWD son 0xA5 (leer IDCODE), 0x8B (escribir TAR) y 0x9F (leer DRW); cada transacción ocupa 46 ciclos; leer 4 KB lleva 47,2 ms a 1 MHz, 11,8 ms a 4 MHz y 4,7 ms a 10 MHz (347 KB/s a 4 MHz), y 128 KB, 1,51 s, 0,38 s y 0,15 s. Parte 2: el TAP tiene 16 estados; de reset a desplazamiento hay 4 ciclos (TMS 0, 1, 0, 0); leer el IDCODE de 1 chip son 38 ciclos y de 3 chips en cadena, 102; un registro de 35 bits cuesta 40 ciclos y cambiar la instrucción de 4 bits, 10, contra 46 de una transacción SWD; con 3 chips, el registro de datos es de 37 bits. Parte 3: ocho fallas con CFSR 0x00008200, 0x02000000, 0x00020000, 0x00010000, 0x01000000, 0x00001000, 0x00000400 y 0x00080000, y PC apilados que se traducen a procesar_muestra+0x1A, calcular_promedio+0x14, enviar_trama+0x18, «fuera de la flash» (0x20000150), procesar_muestra+0x32, procesar_muestra+0x8, TIM2_IRQHandler+0x2C y procesar_muestra+0x60. Parte 4: SWO a 2 Mbaudios mueve 200 000 B/s: un mensaje de 40 caracteres ocupa 80 B byte a byte o 50 B de a 4 bytes (2500 o 4000 mensajes por segundo contra 288 de la UART); el costo en el núcleo (30 ciclos a 48 MHz, supuesto) es 0,62 µs, 5555 veces menos que imprimir.

Probá cambiar en la Parte 1 el valor de mhz o el tamaño de la lectura, agregar en la Parte 2 una cadena de diez chips y ver cuánto crece la lectura del IDCODE (el registro crece 32 bits por chip), o agregar en la Parte 3 un caso propio con otros bits de CFSR. La lección del programa es que cada registro del depurador es un conjunto de bits con significado, y que una tabla y unas pocas líneas bastan para convertir una palabra hexadecimal en un diagnóstico.

11 · Exploración interactiva

Diagnóstico de un HardFault

El laboratorio es el «depurador» que se abre cuando el equipo se colgó: elegís una de ocho fallas típicas y muestra lo que leerías con la sonda (CFSR y los bits activos, el PC apilado traducido con una tabla de símbolos de ejemplo, el SP y, cuando es válida, la dirección en BFAR), junto con la causa más probable y qué hacer. Los valores son de un firmware imaginario con 20 KB de RAM (0x20000000 a 0x20004FFF) y la flash a partir de 0x08000000.

¿Por qué se colgó?

CFSR—
Bits activos—

Lo que se lee con el depurador

Causa probable

Qué hacer

Elegí una falla.

Recorrido guiado

  1. Con la falla 1 (escritura por un puntero a una dirección inexistente): CFSR = 0x00008200, con PRECISERR y BFARVALID. BFAR vale 0xA0000000, la dirección que se intentó escribir, y el PC apilado cae en procesar_muestra+0x1A. Con la función y la dirección, el puntero culpable está a un paso.
  2. Elegí la falla 2 (división por cero): CFSR = 0x02000000 (DIVBYZERO) y PC en calcular_promedio+0x14. No hay dirección en BFAR: el error no es de un acceso a memoria.
  3. Con la falla 4 (ejecución de datos): CFSR = 0x00010000 (UNDEFINSTR) y el PC es 0x20000150, fuera de la flash. Es la pista más fuerte: el programa está ejecutando información de la RAM. La búsqueda ya no empieza en la función donde se detuvo (no existe) sino en quién pisó la dirección de retorno.
  4. Con la falla 6 (desborde de pila): CFSR = 0x00001000 (STKERR) y el SP vale 0x1FFFFFE0, fuera de la RAM. Acá no hay que fiarse del PC apilado, porque el cuadro no se pudo guardar: se sospecha de la pila antes que del código.
  5. Con la falla 7 (error impreciso): CFSR = 0x00000400 y BFAR no es válido. El PC apilado es aproximado, porque la escritura quedó en un búfer y falló después de que la instrucción terminó. Para volver el error preciso hay que desactivar el almacenamiento intermedio de escrituras en los núcleos que lo permiten.
  6. Recorré las fallas 3, 5 y 8 (INVSTATE, UNALIGNED y NOCP) y mirá que cada bit de CFSR corresponde a una causa distinta, de modo que con un solo registro se descarta la mayor parte del código. Los bits 17, 19 y 24 son un buen ejemplo: puntero a función, FPU sin habilitar y acceso no alineado.
Desafío: tu equipo es un Cortex-M0+, que no tiene CFSR. ¿Cómo se analiza un HardFault?

Sin CFSR no hay un registro que diga qué pasó, pero el cuadro de pila existe en todos los Cortex-M: el hardware apila R0-R3, R12, LR, PC y xPSR igual. Se obtiene el PC apilado con el mismo manejador (el cuadro está en el MSP o en el PSP según el bit 2 del LR de la excepción) y se mira qué instrucción está en esa dirección: desensamblándola con el ELF se ve si es un acceso a memoria, una división o un salto. Con eso, y el contenido de los registros apilados (R0-R3 suelen contener los punteros usados), casi siempre se deduce la causa. También sirve el LR (quién llamó), y el SP: ¿está dentro de la RAM? Los chips sin CFSR tienen menos tipos de falla distintos (por ejemplo, no tienen trampa de división por cero), de modo que los casos posibles son pocos.

12 · Método de trabajo

Método: depurar con el depurador

  1. Dejá el conector y los pines de depuración accesibles desde el diseño de la placa (VTref, SWDIO, SWCLK, GND, nRESET, SWO si se usa). Un diseño sin acceso a SWD pierde la herramienta más potente.
  2. Conectá bien antes de sospechar del chip: masa corta, VTref, reset, cables cortos y, ante un error de conexión, bajar la velocidad del reloj y probar «connect under reset».
  3. Compilá con símbolos (-g) y conservá el ELF de cada versión liberada, junto con su mapa.
  4. Si el equipo se colgó, no lo reinicies: detenelo con el depurador y mirá el PC, el SP y el LR, y el CFSR. Es información que se pierde con el reinicio.
  5. Preferí los watchpoints a los breakpoints condicionales y los registros sin detener (ITM, RTT, registro de eventos) cuando el tiempo importa. Un breakpoint condicional lento cambia el comportamiento.
  6. Congelá los temporizadores y el watchdog cuando el núcleo está detenido por el depurador, y recordá que al reanudar el tiempo salta.
  7. Instalá un manejador de HardFault que guarde el cuadro de pila y los registros de falla en RAM sin inicializar, para tener el mismo diagnóstico en el campo, sin depurador.
  8. Medí el consumo y los tiempos finales sin la sonda, con la configuración de producción.
  9. Cerrá la interfaz de depuración en el producto con el nivel de protección que corresponda (tema 43) y definí cómo se analizarán los equipos devueltos.

13 · Comprensión

Confusiones frecuentes

«Si detengo el programa, se detiene todo el equipo»

Solo el núcleo. Los temporizadores, la UART, el ADC y el watchdog siguen corriendo, y los sensores y la radio siguen en el mundo real. Al detener y reanudar, el programa ve saltar el tiempo y puede recibir datos que se acumularon. Los bits de congelación de la depuración existen por eso.

«SWD y JTAG son dos cosas completamente distintas»

Comparten el mismo bloque de acceso (el DAP) y las mismas funciones; cambia el protocolo de transporte: JTAG, con una máquina de estados y cuatro hilos; SWD, con paquetes y dos hilos. Una sonda moderna habla los dos, y el programa de depuración no nota la diferencia.

«Un breakpoint condicional sirve para cualquier cosa»

Si la condición se evalúa en la PC, el núcleo se detiene cada vez que pasa por ahí, y eso puede hacerlo miles de veces más lento. En una rutina de tiempo real es inutilizable. Para esos casos: un watchpoint, un pin de prueba o un registro de eventos.

«El depurador me muestra el valor real de la variable»

Con optimizaciones activas, el compilador puede mantener una variable solo en un registro, o eliminarla, y el depurador muestra «optimizado» o valores sin sentido. Y el ELF debe corresponder exactamente al binario grabado. Depurar con la optimización del producto es válido, pero hay que interpretar lo que se ve.

«El PC apilado siempre es la instrucción que falló»

Es así en los errores precisos. En un error de bus impreciso es aproximado, y en un desborde de pila (STKERR) puede no ser fiable porque el cuadro no se guardó. Mirar los bits de CFSR dice cuánto confiar en el PC.

«Con la sonda conectada, mido el consumo real»

No: la sonda mantiene relojes del bloque de depuración activos, impide algunos modos de bajo consumo y agrega corriente por sus propias conexiones. Las medidas de consumo se hacen sin la sonda (tema 36).

«Dejar el depurador abierto en producción no es un riesgo»

Permite leer el firmware y las claves, y escribir uno modificado. Se debe cerrar con la protección del chip (tema 43), y decidir de antemano cómo se analizarán las fallas de los equipos devueltos, por ejemplo con un informe de falla en RAM sin inicializar enviado por telemetría.

«Si el chip no responde a la sonda, está roto»

Casi siempre es otra cosa: un cable de masa largo, VTref sin conectar, una velocidad de SWCLK demasiado alta, o un programa que reconfiguró los pines de SWD o apagó el bloque de depuración. «Connect under reset» y una velocidad más baja suelen resolverlo.

14 · Práctica guiada

Ejercicios

1. Tiempo de lectura

¿Cuánto tarda, en el mejor caso, en leerse por SWD, con SWCLK a 2 MHz, una RAM de 16 KB? (Se escribe una vez CSW y TAR; luego una lectura de DRW por palabra y una lectura final de RDBUFF; 46 ciclos por transacción.)

Ver solución

16 KB = 4096 palabras de 4 bytes. Transacciones: 2 (CSW y TAR) + 4096 (DRW) + 1 (RDBUFF) = 4099. Ciclos: 4099 × 46 = 188 554. Tiempo: 188 554 / 2 000 000 = 0,0943 s ≈ 94 ms (unos 174 KB/s). En la práctica, algo más por las demoras de la sonda y del USB.

2. Leer un CFSR

Un equipo colgado muestra CFSR = 0x00008200 y BFAR = 0xA0000000. ¿Qué bits están activos y qué pasó? ¿Se puede confiar en el PC apilado?

Ver solución

0x8200 = 1000 0010 0000 0000 en binario: están activos el bit 15 (BFARVALID) y el bit 9 (PRECISERR). Fue un error de bus preciso en un acceso de datos a la dirección 0xA0000000 (que no corresponde a ninguna memoria): un puntero o un índice mal calculado. Como es preciso, el PC apilado es la instrucción culpable: se busca en el mapa y se mira qué puntero usa.

3. Cadena JTAG

Una placa tiene cuatro dispositivos JTAG en cadena. ¿Cuántos ciclos de TCK lleva leer los cuatro IDCODE desde el reset, y cuánto tiempo a 1 MHz?

Ver solución

4 ciclos (reset a desplazamiento) + 4 × 32 = 128 de desplazamiento + 2 (a reposo) = 134 ciclos. A 1 MHz: 134 µs. Cada dispositivo agrega 32 bits al registro de datos (la cadena se comporta como un solo registro largo).

4. Pocos breakpoints

Un Cortex-M4 con el código en flash tiene seis breakpoints de hardware. Se quiere vigilar ocho puntos distintos. ¿Qué opciones hay?

Ver solución

En flash solo caben seis a la vez. Opciones: activar y desactivar según la etapa (los de la inicialización se quitan al pasarla); reemplazar algunos puntos por pines de prueba o eventos de registro; usar watchpoints (cuatro más) cuando lo que importa es el dato; copiar el código de interés a RAM, donde sí valen los breakpoints de software; o aceptar que el IDE reprograme la flash para poner breakpoints de software (lento y con desgaste: no recomendable). Y, si el comportamiento es de tiempo real, la traza de eventos evita detener el programa.

5. Canal de traza

SWO a 4 Mbaudios (10 bits por byte). Mensajes de 24 caracteres. ¿Cuántos mensajes por segundo caben si se escriben byte a byte y si se escriben de a palabras de 4 bytes? (Cada escritura lleva un encabezado de 1 byte.)

Ver solución

Canal: 4 000 000 / 10 = 400 000 B/s. Byte a byte: 24 × 2 = 48 B por mensaje → 400 000 / 48 ≈ 8333 mensajes/s. De a 4 bytes: 6 palabras × 5 B = 30 B → 400 000 / 30 ≈ 13 333 mensajes/s. Escribir de a palabras es más eficiente: menos encabezados por dato. En ambos casos muy por encima de los 480 mensajes de 24 caracteres por segundo que admite una UART a 115 200 baudios (11 520 / 24).

6. El equipo que se reinicia con el depurador

Al poner un breakpoint en una función y pararse en él, el equipo se reinicia solo unos segundos después. Sin depurador no se reinicia. ¿Por qué ocurre y cómo se arregla?

Ver solución

Con el núcleo detenido, el watchdog sigue contando (tema 38): como nadie lo alimenta, vence y reinicia el equipo. Se arregla activando el bit de congelación del watchdog en depuración del microcontrolador (en la familia STM32, en DBGMCU), de modo que su cuenta se pare cuando el depurador detiene el núcleo. Lo mismo vale para los temporizadores cuyo comportamiento se quiere «parado» durante la pausa. Y hay que recordar quitar esa configuración, o verificarla, en la versión de producción.

15 · Síntesis

Ideas para recordar

  • La interfaz de depuración es un segundo acceso al chip, independiente de la CPU (el DAP): ve el mismo mapa de memoria, puede leer y escribir cualquier dirección y detener el núcleo. Detener el núcleo no detiene los periféricos ni el watchdog, salvo que se configure su congelación.
  • JTAG usa cuatro o cinco hilos y una máquina de estados de 16 estados manejada por TMS (38 ciclos para leer un IDCODE; 102 con tres chips en cadena). SWD usa dos hilos y paquetes de 46 ciclos por palabra de 32 bits (la petición de lectura del IDCODE es 0xA5). Comparten el mismo bloque; SWD gana en pines y JTAG en cadenas y en boundary scan.
  • La conexión física es la causa de la mayoría de los fallos: masa corta, cables cortos, VTref, reset, velocidad de SWCLK moderada. Los pines de depuración deben seguir accesibles en la placa final, y se puede recuperar un chip «bloqueado» con connect under reset.
  • Los breakpoints de hardware son pocos (hasta 6 en un M4, 4 en un M0) y son los únicos válidos en flash; los watchpoints (DWT) detienen el programa al leer o escribir un dato, y son la herramienta para «la variable cambia sola». Un breakpoint condicional es lento y vuelve inutilizable el tiempo real.
  • Para mirar sin detener: ITM/SWO (200 000 B/s a 2 Mbaudios, miles de mensajes por segundo, unos 0,6 µs de costo), RTT (un buffer en RAM leído por SWD) o el registro de eventos; el semihosting detiene el núcleo y no debe quedar en el producto.
  • Un HardFault se analiza con el cuadro apilado (el PC apilado es la pista principal), CFSR (qué pasó), BFAR/MMFAR (qué dirección), el SP y el LR. Un PC fuera de la flash indica que se ejecutan datos; un SP fuera de la RAM, desborde de pila; una falla imprecisa, un PC aproximado. Un manejador que guarde esos valores en RAM sin inicializar da el mismo diagnóstico en el campo.
  • El ELF con símbolos de cada versión liberada es parte del producto: sin él, un cuelgue solo muestra direcciones. Y la interfaz de depuración se cierra en producción con la protección del chip, midiendo el consumo siempre sin la sonda conectada.

¿Listo para el tema siguiente?

Antes de avanzar, revisá un proyecto tuyo y respondé: ¿tiene el conector o los pines de SWD accesibles en la placa final?, ¿guardás el ELF de cada versión que entregás?, ¿qué hace tu HardFault_Handler: un bucle infinito o un informe que sobrevive al reinicio?, ¿el watchdog se congela con el depurador? Si el manejador es un bucle vacío, escribí el de este tema: son veinte líneas que convierten un cuelgue misterioso en un dato.

En el próximo tema usamos todo esto para estudiar los fallos típicos: el HardFault, la pila corrupta y las condiciones de carrera, y cómo reproducirlos y analizarlos con método.