← Índice del curso

Sistemas Embebidos · Tema 37 · Bloque 8

Fallas y causas de reinicio

Un equipo embebido que se reinicia sin que nadie sepa por qué es un equipo con un error que todavía no se encontró. Los reinicios no son todos iguales: algunos son normales (alguien lo encendió), otros son una defensa que funcionó (el watchdog rescató un programa colgado) y otros son el síntoma de un problema serio (una batería que no aguanta un pico, una falla de memoria, un contador que da la vuelta). Este tema abre el bloque de confiabilidad con la pregunta que lo ordena todo: cuando el equipo se reinicia, ¿puede decir por qué? Veremos las causas de un reinicio, cómo se lee el registro que las guarda, qué es y cómo se evita el brown-out, cómo se pide bien un reinicio por software y cómo se arma una «caja negra» para investigar después de la falla.

01 · Dos caras de lo mismo

El reinicio como mecanismo y como síntoma

En un sistema embebido, el reinicio es la última línea de defensa. Cuando el programa se cuelga, la memoria se corrompe o el hardware queda en un estado imposible, la forma más segura de volver a un estado conocido es reiniciar: borrar todo y empezar de cero (tema 8). Muchos equipos que funcionan años en el campo lo hacen porque, de vez en cuando, algo los reinicia y los rescata. El reinicio es, entonces, un mecanismo de recuperación.

Pero el reinicio es también un síntoma. Un equipo bien diseñado debería reiniciarse poco; si lo hace, algo lo causó, y esa causa merece investigarse. El problema es que un reinicio, por naturaleza, borra la evidencia: el estado del programa, las variables, la pila, todo desaparece. Sin un registro de qué ocurrió, un equipo que «se reinicia a veces» es casi imposible de depurar, porque el fallo no se puede reproducir en la mesa y cada reinicio en el campo destruye las pistas.

Por eso el diseño de la confiabilidad se apoya en tres capacidades que este bloque desarrolla:

  • Reiniciarse cuando hace falta, y solo entonces: detectar una falla y reaccionar (temas 38 y 39).
  • Saber por qué se reinició: distinguir un encendido de un brown-out, un watchdog o un reinicio pedido por software (este tema).
  • Conservar evidencia de la falla más allá del reinicio, para poder analizarla después (este tema y el tema 39).
De las fuentes de reinicio al diagnóstico Seis fuentes de reinicio a la izquierda: encendido, brown-out, pin de reset, watchdog, reinicio por software y falla grave. Todas llegan a la lógica de reset del chip, que marca una bandera en el registro de causa y reinicia el procesador. El código de arranque lee ese registro, lo interpreta junto con el motivo guardado en memoria que sobrevive al reinicio, y registra el diagnóstico. Encendido (POR)Brown-out (BOR)Pin de reset WatchdogReinicio por softwareFalla grave (lockup) Lógica dereset del chip Registro de causabanderas que persisten (pegajosas) Memoria que sobreviveel motivo que anotó el firmware (.noinit) Arranque: diagnósticolee las dos fuentes, registra y decide Las banderas dicen QUÉ mecanismo reinició; el motivo guardado dice POR QUÉ el firmware lo pidió.Con las dos fuentes, un reinicio se explica; con una sola, se adivina.

02 · Un catálogo

Las causas de un reinicio

Casi todos los microcontroladores distinguen las mismas cinco o seis causas de reinicio, aunque cada familia las llame de otro modo. Conviene conocerlas, porque cada una apunta a un tipo distinto de problema:

Causas de reinicio, qué las dispara y qué sugieren cuando son inesperadas.
CausaQué la disparaNormal cuando…Si es inesperada, sugiere…
Encendido (power-on reset, POR)La alimentación sube desde cero hasta el umbral de arranqueAlguien conectó la batería o la fuenteUn corte de alimentación total: un contacto flojo, un conector, la batería agotada del todo
Brown-out (BOR)La tensión de alimentación cae por debajo de un umbral (sin llegar a cero) y se recuperaCasi nunca; es una condición de diseño a evitarUna batería que no aguanta los picos, una fuente débil, un cableado de resistencia alta (apartado 4)
Pin de reset (externo)Una señal baja en el pin de reset: un botón, un depurador o un supervisor externoDurante el desarrollo, con el programador; con un botón de reinicioRuido en el pin de reset (falta de resistencia o condensador), un supervisor externo que actúa
Watchdog (perro guardián)Un temporizador que el programa debía «alimentar» y no lo hizo a tiempoCasi nunca; es la defensa contra un cuelgueEl programa se colgó, o una tarea no completó su ciclo: una espera infinita, un bucle, un interbloqueo (tema 38)
SoftwareEl propio programa pide el reinicio (NVIC_SystemReset, esp_restart)Tras una actualización, un cambio de configuración, o una falla que el programa detectó y decidió no resolver (aserción, HardFault)Un camino de error que reinicia en lugar de recuperarse: el motivo anotado dice cuál
Falla grave (lockup)El núcleo entra en un estado irrecuperable (una falla dentro del manejador de fallas); en algunos chips genera un reinicioNuncaUn error de memoria grave (tema 31): pila corrupta, tabla de vectores dañada
Salida de un modo apagado (por ejemplo, standby)Un despertar desde el sueño más profundo, que se parece a un reinicio (tema 34)Cada vez que el equipo despierta de un sueño profundo, si se diseñó asíUn despertar que no debía ocurrir: una fuente por nivel que no se desactivó

Cuatro observaciones que ordenan el análisis. Primera: las causas se agrupan en «esperadas» y «de defensa». El encendido, el pin con un programador y el software tras una actualización son esperados; el watchdog, el brown-out y el lockup son defensas o fallas, y en un equipo sano no ocurren. Segunda: cada causa apunta a un lugar distinto donde buscar: el brown-out, a la alimentación; el watchdog, al programa colgado; el pin de reset, al hardware del reset; la falla grave, a la memoria. Tercera: la frecuencia y el momento cuentan (apartado 6 y Parte 3 del programa): reinicios que ocurren siempre al mismo tiempo de funcionamiento delatan una causa determinista; los dispersos, una ambiental. Y cuarta: un reinicio por software no es una causa, es una consecuencia: siempre hay un motivo anterior (una falla detectada, una actualización), y ese motivo hay que anotarlo.

03 · La primera pregunta del arranque

Leer la causa: el registro de reinicio

La lógica de reset de casi todo chip deja una bandera marcada en un registro especial que indica qué mecanismo provocó el reinicio. Es la fuente de verdad sobre el hardware: el programa no puede saber por sí mismo qué lo reinició, porque empezó de cero. Los nombres cambian por familia, pero el concepto es idéntico:

Dónde está la causa de reinicio en cada familia.
FamiliaRegistro o funciónBanderas o valoresCómo se borran
AVR (ATmega)MCUSRPORF, EXTRF, BORF, WDRFEscribiendo MCUSR = 0
STM32 (F1 y similares)RCC->CSRPINRSTF, PORRSTF, SFTRSTF, IWDGRSTF, WWDGRSTF, LPWRRSTF (y BORRSTF en las familias con BOR)Bit RMVF de RCC->CSR
nRF52NRF_POWER->RESETREASRESETPIN, DOG, SREQ, LOCKUP, OFF (despertar desde System OFF) y otrasEscribiendo un 1 en cada bit
ESP32esp_reset_reason()ESP_RST_POWERON, ESP_RST_BROWNOUT, ESP_RST_SW, ESP_RST_PANIC, ESP_RST_INT_WDT, ESP_RST_TASK_WDT, ESP_RST_DEEPSLEEP, ESP_RST_EXTLa biblioteca lo maneja

La lectura se hace lo primero en el arranque, antes de que otro código pueda alterar el registro. En un AVR, hay una receta clásica (de la documentación de avr-libc) que lo hace antes de main, en la sección de inicialización .init3, y de paso resuelve un problema peligroso: si el reinicio fue por watchdog, el watchdog sigue activo con el menor tiempo de espera, y reiniciaría al equipo otra vez en cuanto el programa tardara en configurarlo:

/* AVR: guardar y borrar MCUSR antes de main(); apagar el watchdog que pudo quedar activo */
#include <avr/io.h>
#include <avr/wdt.h>

uint8_t causa_reinicio __attribute__((section(".noinit")));      /* no se inicializa: conserva lo que se escriba */

void leer_causa(void) __attribute__((naked, used, section(".init3")));
void leer_causa(void) {
    causa_reinicio = MCUSR;                                     /* guardar las banderas */
    MCUSR = 0;                                                  /* borrarlas para el próximo reinicio */
    wdt_disable();                                              /* si venía de un watchdog, está activo con el timeout mínimo */
}

/* Más adelante, en la aplicación: */
if      (causa_reinicio & (1 << PORF))  registrar(CAUSA_ENCENDIDO);
else if (causa_reinicio & (1 << BORF))  registrar(CAUSA_BROWNOUT);
else if (causa_reinicio & (1 << WDRF))  registrar(CAUSA_WATCHDOG);
else if (causa_reinicio & (1 << EXTRF)) registrar(CAUSA_PIN);
/* STM32 (F1 y similares): leer RCC->CSR y borrar las banderas al terminar */
Causa leer_causa(void) {
    uint32_t csr = RCC->CSR;
    RCC->CSR |= RCC_CSR_RMVF;                          /* borrar las banderas YA: las del próximo reinicio no se mezclarán con estas */

    if (csr & RCC_CSR_PORRSTF)                        return CAUSA_ENCENDIDO;      /* el encendido marca también PINRSTF: se mira primero */
    if (csr & (RCC_CSR_IWDGRSTF | RCC_CSR_WWDGRSTF))  return CAUSA_WATCHDOG;
    if (csr & RCC_CSR_SFTRSTF)                        return CAUSA_SOFTWARE;
    if (csr & RCC_CSR_LPWRRSTF)                       return CAUSA_BAJO_CONSUMO;   /* despertar de standby, tema 34 */
    if (csr & RCC_CSR_PINRSTF)                        return CAUSA_PIN;
    return CAUSA_DESCONOCIDA;
}

La trampa más importante es que las banderas son pegajosas (sticky): no se borran solas con un reinicio, y cada nuevo reinicio agrega su bandera a las que ya estaban. Solo el encendido las borra. Si el firmware lee el registro pero no lo limpia, después de un watchdog y un reinicio por software el registro dirá «watchdog y software»: no se sabe cuál fue el último. La Parte 1 del programa de Python muestra la diferencia con una secuencia de reinicios:

Un firmware que no limpia las banderas y uno que sí (resultado de la Parte 1).
Reinicio porFirmware A (lee y no borra)Firmware B (lee y borra)
EncendidoPIN + PORPIN + POR
WatchdogPIN + POR + WDTWDT
SoftwarePIN + POR + SW + WDTSW
Brown-outBOR + PIN + POR + SW + WDTBOR
Pin de resetBOR + PIN + POR + SW + WDTPIN

El firmware A acumula banderas de todo lo que alguna vez ocurrió y se vuelve ciego: después de cinco reinicios de causas distintas, ve todas las banderas marcadas. El B ve siempre una sola, la de su último reinicio. Tres reglas se desprenden: leer lo primero, borrar enseguida y decidir con una prioridad cuando haya varias (en el encendido de un STM32, por ejemplo, el pin de reset también queda marcado, por eso el encendido se mira primero). Y otra complicación que conviene conocer: en un reinicio por watchdog, la bandera dice que fue el watchdog, pero no dice qué lo hizo vencer. Para eso hace falta evidencia que el propio programa dejó antes de colgarse (apartado 6).

04 · Cuando la tensión no alcanza

El brown-out a fondo

El brown-out («apagón parcial») ocurre cuando la tensión de alimentación cae por debajo del mínimo que el chip necesita para funcionar bien, sin llegar a cero. Es peligroso porque entre «funciona perfectamente» y «apagado» existe una zona intermedia en que la lógica interna sigue ejecutando, pero mal: las puertas son más lentas, la flash puede leer datos equivocados, un registro puede cambiar de valor, una escritura a la memoria no volátil puede quedar corrupta. Un chip en esa zona no se detiene: hace cosas incorrectas.

Por eso los chips incluyen un detector de baja tensión (brown-out detector, BOD o BOR): un circuito que vigila la tensión y, si cae por debajo de un umbral configurable, mantiene el chip en reset hasta que se recupere. Es una barrera de seguridad: es mejor un reinicio ordenado que una ejecución incorrecta. Tiene un costo (consume unos µA, tema 35) y requiere una decisión de diseño, el umbral:

  • Un umbral demasiado bajo deja pasar tensiones en las que el chip no funciona correctamente a su frecuencia. Los chips tienen una zona de operación segura en la que la frecuencia máxima depende de la tensión: en un ATmega328P, los puntos de la hoja de datos son unos 4 MHz a 1,8 V, 10 MHz a 2,7 V y 20 MHz a 4,5 V. Un chip a 16 MHz con un BOR de 1,8 V funciona fuera de lo garantizado en todo el tramo de 1,8 V a unos 4 V.
  • Un umbral demasiado alto reinicia al equipo cuando la batería todavía tiene energía útil, o ante un bajón momentáneo inocuo. Y puede crear un bucle de reinicios (apartado 7).

En el AVR, el umbral se elige con los fuses BODLEVEL (típicamente 1,8 V, 2,7 V o 4,3 V); en los STM32 con bytes de opción (en las familias con BOR) o con un PVD (programmable voltage detector) que no reinicia sino que avisa con una interrupción. Esa segunda opción es la que permite anticiparse: con un umbral de aviso por encima del de reinicio, el programa tiene unos milisegundos para guardar lo importante antes de caer.

/* STM32 (HAL): aviso de baja tensión por interrupción, antes de que llegue al reinicio. Los niveles exactos están en la hoja de datos. */
void pvd_iniciar(void) {
    PWR_PVDTypeDef cfg = { .PVDLevel = PWR_PVDLEVEL_7,              /* el nivel más alto disponible (del orden de 2,9 V) */
                           .Mode     = PWR_PVD_MODE_IT_RISING };    /* interrupción cuando VDD baja del umbral */
    HAL_PWR_ConfigPVD(&cfg);
    HAL_PWR_EnablePVD();
    HAL_NVIC_EnableIRQ(PVD_IRQn);
}

void HAL_PWR_PVDCallback(void) {                                    /* la tensión está bajando: hay pocos milisegundos */
    radio_apagar();                                                 /* la radio es lo que más corriente pide */
    guardar_estado_critico();                                       /* solo lo imprescindible y breve: la tensión sigue cayendo */
    caja.motivo = MOTIVO_TENSION_BAJA;                              /* para que el próximo arranque lo sepa (apartado 6) */
}

De dónde viene un brown-out real

En un equipo a batería, el brown-out casi nunca es «la batería se agotó» (eso es un apagón completo, sin vuelta): es un pico de corriente sobre una batería con resistencia interna. Como vimos en los temas 33 y 36, la tensión en bornes durante un pico es Vpico = Vvacío − I · Rinterna. Con pilas usadas, la resistencia interna sube, y el pico de la radio puede hundir la tensión por debajo del umbral aunque la batería tenga el 40 % de su carga. Es el brown-out «misterioso» típico: el equipo funciona bien mes tras mes, y empieza a reiniciarse cuando la batería envejece o hace frío. La Parte 2 del programa lo calcula con pilas de 2,9 V en vacío y un pico de 15 mA:

¿Reinicia el pico de transmisión? Tensión en vacío 2,9 V, pico de 15 mA (resultado de la Parte 2).
Resistencia internaTensión durante el picoBOR de 1,8 VBOR de 2,7 V
5 Ω (pila fresca)2,825 Vsiguesigue
20 Ω2,600 Vsiguereinicia
50 Ω (pila gastada o fría)2,150 Vsiguereinicia

El mismo equipo y la misma batería se comportan distinto según el umbral elegido, y la elección «correcta» no es evidente: el umbral bajo (1,8 V) evita los reinicios pero deja al chip operar en una zona en la que, a su frecuencia, no está garantizado; el alto (2,7 V) protege la ejecución pero convierte la batería gastada en un equipo que se reinicia en cada transmisión. La solución no es elegir un umbral, sino atacar la causa: reducir el pico (transmisión más corta o de menor potencia), aportarlo desde un condensador (tema 36: 90 µC sobre 470 µF son 0,19 V de caída), bajar la frecuencia del chip a una que esté garantizada con la tensión más baja, o usar una química con menor resistencia interna.

Otras causas de brown-out, para completar el catálogo: una fuente débil o un regulador que no alcanza (se hunde cuando la carga arranca: motores, radio, pantalla); cableado de resistencia alta (un conector oxidado, un cable largo o delgado); una rampa de encendido lenta (la tensión sube tan despacio que el chip arranca antes de que sea suficiente); y el ruido de alimentación (una conmutación cercana que hunde la tensión durante microsegundos). Una regla de diseño: el peor caso de tensión (batería usada, en frío, durante el pico máximo) tiene que quedar por encima del umbral con margen.

05 · Reiniciar a propósito

El reinicio por software bien hecho

Hay situaciones en que el propio programa debe reiniciar al equipo: tras instalar una actualización de firmware (tema 42), cuando cambia una configuración que solo se aplica al arrancar, cuando detecta una falla de la que no sabe recuperarse (un assert que falla, un HardFault: tema 31) o cuando recibe una orden de reinicio remoto. Es una herramienta legítima, y se la usa mal con frecuencia: un reinicio sin cuidado destruye evidencia y puede dejar el hardware en un estado peligroso.

En un Cortex-M, el reinicio se pide escribiendo el registro AIRCR (Application Interrupt and Reset Control Register, en 0xE000ED0C) con la clave 0x05FA en los 16 bits altos y el bit SYSRESETREQ (bit 2) en 1; CMSIS lo envuelve en la función NVIC_SystemReset(). En un AVR, que no tiene una instrucción equivalente, la técnica clásica es activar el watchdog con el tiempo de espera mínimo y esperar en un bucle. En el ESP32, esp_restart(). Lo que importa es la secuencia alrededor:

/* Un reinicio por software con las precauciones del caso */
void __attribute__((noreturn)) sistema_reiniciar(Motivo motivo) {
    __disable_irq();                               /* nada debe interrumpir la secuencia */

    caja.motivo = motivo;                          /* 1. anotar POR QUÉ, en memoria que sobrevive (apartado 6) */
    caja.crc = caja_crc(&caja);

    apagar_salidas_peligrosas();                   /* 2. dejar el hardware en un estado seguro: solenoide, motor, calefactor (tema 27) */
    flush_registro_critico();                      /* 3. si hay un registro pendiente de escribir y es rápido, terminarlo */

    __DSB();                                       /* 4. asegurar que las escrituras anteriores se completaron */
    NVIC_SystemReset();                            /* 5. pedir el reinicio: escribe AIRCR con VECTKEY y SYSRESETREQ */
    for (;;) { }                                   /* no se vuelve de aquí */
}

/* Un manejador de HardFault bien hecho termina igual (tema 31):
   void hardfault_c(uint32_t *marco) { guardar_pc_lr_cfsr(marco); sistema_reiniciar(MOTIVO_HARDFAULT); } */

Cada paso evita un problema concreto:

  • Anotar el motivo antes de reiniciar es lo que convierte un reinicio por software en información. La bandera del hardware dirá «software» (se pidió un reinicio), pero no por qué; solo el motivo escrito por el programa lo dice, y se guarda en memoria que sobrevive (.noinit, tema 29) con una marca de validez y una suma de verificación, como todo dato que vive en RAM sin inicializar.
  • Dejar las salidas en un estado seguro evita, por ejemplo, que un reinicio deje la cerradura destrabada o el calefactor encendido durante los milisegundos del reinicio. Las salidas de un microcontrolador, al reiniciar, pasan a alta impedancia (entradas flotantes) y pueden hacer cualquier cosa hasta que el programa las configure; el circuito externo debe estar diseñado para que ese estado sea seguro (tema 27, apartado de falla de energía).
  • Esperar las escrituras en curso. Un reinicio en medio de una escritura en flash o en una EEPROM corrompe el dato (tema 28): si hay una en marcha, se la deja terminar antes de pedir el reinicio.
  • La barrera __DSB() garantiza que las escrituras previas (al registro de la caja negra, por ejemplo) llegaron a la memoria antes de que el reinicio ocurra.

Y una regla de uso: un reinicio por software es el último recurso, no el primero. Reiniciar para «arreglar» un problema sin entenderlo es esconderlo: el equipo parece funcionar, pero lo hace con un error que se repetirá. Si el reinicio es la respuesta a una falla detectada, la falla debe registrarse (con el motivo), contarse y, si se repite, escalarse (apartado 7).

06 · Dejar evidencia

La caja negra: diagnóstico posterior

Un avión tiene una caja negra para poder investigar el accidente después. Un firmware necesita lo mismo: un bloque de memoria donde dejar, antes de que se pierda, la información para entender por qué falló. No hace falta mucha: unas pocas decenas de bytes bastan para convertir un «se reinició» en un diagnóstico. Un buen diseño guarda:

Qué guardar en la caja negra y para qué sirve cada dato.
DatoPara qué
Marca de validez y suma de verificación (CRC)Distinguir un bloque válido de basura (tras un encendido la RAM contiene valores aleatorios)
Motivo del último reinicio pedidoExplicar los reinicios por software: «HardFault», «actualización», «aserción en tal módulo»
Contador de reinicios totales y seguidosDetectar un equipo que reinicia demasiado y el bucle de reinicios (apartado 7)
Tiempo de funcionamiento antes del reinicioReconocer patrones: «siempre a las 49,7 días», «cada hora»
Datos de la última falla (PC, LR, CFSR, direcciones)Saber dónde falló el programa: con el .map, la línea de código exacta (tema 31)
Hito de ejecución (breadcrumb)Saber en qué parte del programa estaba cuando se colgó, para los reinicios por watchdog que no dejan ningún motivo
Tensión de la batería y temperaturaCorrelacionar los reinicios con un brown-out o con condiciones ambientales
#define MARCA  0xCAFEF00Du
#define MAX_SEGUIDOS  3

typedef struct {
    uint32_t marca;                    /* MARCA si el bloque es válido */
    uint16_t motivo;                   /* por qué se pidió el último reinicio por software (MOTIVO_*) */
    uint16_t hito;                     /* última etapa del programa que se alcanzó (para los cuelgues) */
    uint16_t seguidos;                 /* reinicios seguidos sin funcionar un minuto completo */
    uint16_t _relleno;
    uint32_t total;                    /* reinicios desde el último encendido */
    uint32_t uptime_s;                 /* segundos que funcionó antes del reinicio */
    uint32_t pc, lr, cfsr;             /* de la última falla (tema 31) */
    uint32_t crc;
} CajaNegra;

static CajaNegra caja __attribute__((section(".noinit")));          /* sobrevive a los reinicios (no a un encendido) */

void diagnostico_arranque(Causa causa) {
    bool valida = (caja.marca == MARCA) && (caja_crc(&caja) == caja.crc);
    if (causa == CAUSA_ENCENDIDO || !valida) {                       /* tras un encendido la RAM no sirve: se empieza de cero */
        memset(&caja, 0, sizeof caja);
        caja.marca = MARCA;
    } else {
        caja.total++;
        caja.seguidos++;
    }
    telemetria_encolar(causa, caja.motivo, caja.hito, caja.uptime_s, caja.seguidos);   /* para que el servidor lo vea */
    if (caja.seguidos >= MAX_SEGUIDOS) modo_seguro();                /* apartado 7 */
    caja.motivo = 0;                                                 /* el motivo se consume: el próximo reinicio traerá el suyo */
    caja.crc = caja_crc(&caja);
}

/* Cada minuto de funcionamiento normal */
void tick_de_un_minuto(void) { caja.seguidos = 0; caja.uptime_s += 60; caja.crc = caja_crc(&caja); }

/* En los puntos clave del programa: dónde estoy, por si me cuelgo */
#define HITO(n)  do { caja.hito = (n); } while (0)

Cuatro detalles de diseño hacen que esto funcione en la práctica:

  • La caja negra usa RAM sin inicializar (.noinit, tema 29), que sobrevive a reinicios de todo tipo salvo a un encendido completo o a un brown-out profundo. Por eso lleva marca y CRC: nunca se confía en su contenido sin validarlo. Para los datos que deben sobrevivir al encendido (el total de reinicios de toda la vida, por ejemplo), se usa memoria no volátil, con las precauciones del desgaste (tema 40): se escribe solo cuando cambia algo importante, no en cada ciclo.
  • El hito de ejecución resuelve el caso más difícil, el reinicio por watchdog: el programa estaba colgado, así que no pudo anotar ningún motivo. Pero si, a medida que avanza, va dejando una marca en cada etapa (HITO(3) al entrar a la transmisión, HITO(4) al esperar la respuesta), la última marca que quedó dice dónde se colgó. Es barato (una escritura de 16 bits) y su valor es enorme.
  • Algunos chips ofrecen una interrupción de aviso previo del watchdog (en el STM32, la «interrupción de despertar temprano» del watchdog de ventana), que permite guardar el PC y el estado unos instantes antes de que el reinicio ocurra. El tema 38 la usa.
  • La información tiene que salir del equipo. Una caja negra que nadie lee no sirve. Se encola un registro de telemetría con cada arranque (causa, motivo, hito, uptime, contadores) y se envía por el canal de comunicación disponible, o se lo guarda en un buffer circular en memoria no volátil (tema 41) para recuperarlo en servicio.

Con esos datos, la Parte 3 del programa analiza una flota de equipos: para cada causa calcula la antigüedad mediana y la dispersión del tiempo de funcionamiento al reiniciar. Cuando el tiempo es casi idéntico en todos (40 reinicios por watchdog a los 49,71 días, con una dispersión del 0,0 %), la causa es determinista: un contador de milisegundos de 32 bits da la vuelta a los 232 ms = 49,71 días, y algún código se rompe cuando ocurre. Cuando es muy dispersa (el brown-out, con una dispersión del 70,7 %), la causa es ambiental o aleatoria: el frío, un pico, una batería. Esa distinción orienta la investigación: lo primero se arregla leyendo el código; lo segundo, midiendo el hardware.

07 · El peligro de reiniciar en círculos

Bucles de reinicio y modo seguro

Una de las fallas más feas de un equipo embebido es el bucle de reinicios (boot loop): el equipo arranca, falla, se reinicia, falla de nuevo, y así sin parar. Es especialmente peligroso porque cada ciclo consume energía (vacía la batería en horas), puede desgastar la memoria no volátil (si cada arranque escribe algo), satura el canal de comunicación con mensajes de arranque, y mantiene al equipo fuera de servicio sin que nadie sepa cómo salir. Casi siempre tiene una de estas causas:

  • Un brown-out autoinfligido: el arranque consume una corriente que hunde la tensión, el chip se reinicia, vuelve a arrancar, hunde de nuevo la tensión. En la Parte 2, con una pila de 20 Ω y un BOR de 2,7 V, el equipo reinicia en cada intento de transmitir: con un arranque de 50 ms, son 200 reinicios en 10 segundos.
  • Un dato de configuración corrupto que el arranque lee, interpreta mal y provoca una falla.
  • Un error del programa en el arranque que ocurre siempre (una actualización defectuosa, por ejemplo).
  • Un watchdog que vence siempre porque el arranque tarda más que su tiempo de espera.

La defensa estándar es una combinación de contar y escalar. La caja negra cuenta los reinicios seguidos (los que ocurren sin un período de funcionamiento normal entre uno y el siguiente). Si el contador llega a un tope (tres, por ejemplo), el equipo ya no repite lo que falló: entra en un modo seguro (tema 39) con un comportamiento reducido y conservador:

Escalado de la respuesta ante reinicios repetidos.
Reinicios seguidosRespuestaPor qué
1Arrancar normalmente y registrar la causaUn reinicio aislado puede ser un episodio pasajero (un bajón, una interferencia)
2Arrancar normalmente, con registro detallado y esperando un poco antes de las actividades pesadas (la radio)Si es un brown-out, dar tiempo a que la tensión se recupere
3 o másModo seguro: sin radio, con los periféricos mínimos, esperando un tiempo largo entre intentos, avisando si puedeCortar el círculo: no volver a ejecutar lo que falló; preservar la batería y la evidencia
Un minuto de funcionamiento normalContador de seguidos a cero; salir del modo seguroEl equipo demostró que arranca y funciona
Muchos reinicios acumuladosRestaurar la configuración de fábrica o volver al firmware anterior (tema 42)Si el problema está en el dato o en la actualización, la salida es volver atrás

Algunas medidas complementarias evitan que el bucle empiece: demorar las actividades de mucha corriente tras un reinicio (esperar unos cientos de milisegundos antes de encender la radio, para que la tensión se estabilice), usar histéresis en el brown-out (los detectores reinician con un umbral y liberan con otro, un poco más alto) y limitar la frecuencia de los reintentos con un tiempo de espera creciente entre cada uno (backoff). Y una regla: nunca escribir en memoria no volátil en cada arranque. Un equipo en bucle de reinicios que escribe un registro en cada arranque agota la endurancia de su flash en horas (tema 28).

08 · Comprobar que funciona

Verificación y diagnóstico

Cómo verificar que cada causa se detecta y se registra bien.
CausaCómo provocarlaResultado esperado
EncendidoCortar y dar la alimentaciónCausa «encendido»; la caja negra se inicializa en cero
Pin de resetPulsar el botón de resetCausa «pin»; la caja negra conserva su contenido (marca y CRC válidos)
SoftwareLlamar a sistema_reiniciar(MOTIVO_PRUEBA)Causa «software» y el motivo «prueba» en el primer registro del arranque
WatchdogColgar el programa a propósito con un bucle infinitoCausa «watchdog»; el último hito indica el punto donde se colgó
HardFaultEscribir en una dirección inválida a propósito (tema 31)Causa «software» con motivo «HardFault» y el PC, LR y CFSR de la falla
Brown-outBajar lentamente la tensión de una fuente de laboratorioReinicio al llegar al umbral configurado; se anota la tensión en que ocurre; el equipo no ejecuta mal antes
Bucle de reiniciosHacer fallar el arranque a propósito (un dato inválido en la configuración)Después de tres reinicios seguidos, modo seguro; sin escrituras repetidas en flash
Persistencia de la caja negraReiniciar por software, por watchdog y por pin; luego cortar la alimentaciónSobrevive a los tres primeros; tras el corte, marca inválida y bloque reinicializado
Síntomas frecuentes y qué revisar.
SíntomaCausa probableQué hacer
El diagnóstico dice siempre «encendido» (o varias causas a la vez)No se borran las banderas tras leerlasLeer lo primero y limpiar enseguida (MCUSR = 0, RMVF); decidir por prioridad
Un AVR se reinicia en bucle tras un watchdogEl watchdog sigue activo con el timeout mínimo y el arranque tarda másApagar el watchdog al principio (wdt_disable() en .init3)
Reinicios por brown-out con pilas que «todavía tienen carga»La resistencia interna crece con el uso y el frío: el pico hunde la tensiónCondensador de reserva; picos más cortos; medir la tensión en el chip durante el pico (tema 36)
La caja negra tiene valores absurdos tras un encendidoNo se valida con marca y CRC: se confía en RAM aleatoriaValidar siempre; reinicializar si no es válida
Se pierde el motivo de un reinicio por softwareNo se anotó antes, o se anotó pero no se completó la escritura antes del reinicioAnotar primero; __DSB() antes de reiniciar
Un reinicio por watchdog sin ninguna pistaNo hay hitos de ejecución ni aviso previo del watchdogAgregar hitos (HITO(n)); usar la interrupción de aviso previo si el chip la tiene
Reinicios a un tiempo fijo de funcionamiento (49,7 días, 24 horas, 1 hora)Una causa determinista: un contador que da la vuelta, una fuga de memoria, un temporizadorMirar las variables de tiempo de 32 bits y las comparaciones; usar aritmética de diferencias sin signo; revisar el heap y la pila (temas 29 y 30)
El equipo reinicia sin parar tras una actualizaciónUn error del arranque nuevo, o un dato incompatibleContador de reinicios seguidos, modo seguro y retorno al firmware anterior (tema 42)

Un detalle sobre la aritmética del tiempo, causante de uno de los fallos deterministas más clásicos: un contador de milisegundos de 32 bits (uint32_t) da la vuelta a los 49,71 días, y el código que compara if (ahora > limite) deja de funcionar cuando ahora vuelve a cero. La forma correcta compara diferencias: if ((int32_t)(ahora - limite) >= 0), que sigue siendo correcta aunque el contador dé la vuelta, mientras el intervalo sea menor que la mitad del rango. Es una de las pruebas que conviene hacer a propósito, adelantando el contador a un valor cercano al desborde.

09 · Analogía con programación

Probar el modelo en Python

El programa tiene tres partes: la simulación de las banderas pegajosas de un registro de reinicio con un firmware que no las borra y uno que sí; el cálculo de si el pico de transmisión provoca un brown-out según la resistencia interna de la batería y el umbral del detector, con el efecto de un bucle de reinicios; y el análisis de 100 reinicios registrados de una flota de equipos (datos sintéticos), donde la dispersión del tiempo de funcionamiento distingue una causa determinista de una ambiental.

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

# Causas de reinicio: banderas persistentes, brown-out con una batería gastada y análisis de una flota de equipos.
import random, statistics

# ---- Parte 1: las banderas de causa de reinicio son «pegajosas» ------------------------------------------------
def evento(banderas, causa):
    """Un reinicio por 'causa' sobre el registro de banderas. El encendido borra el resto y marca POR y PIN (como en varios STM32)."""
    if causa == "POR": return {"POR", "PIN"}
    return set(banderas) | {causa}

def decodificar(banderas):
    return " + ".join(sorted(banderas)) if banderas else "(ninguna)"

SECUENCIA = ["POR", "WDT", "SW", "BOR", "PIN"]
print("Parte 1: banderas de causa de reinicio, con y sin limpiarlas al leer")
sucio, limpio = set(), set()
for causa in SECUENCIA:
    sucio = evento(sucio, causa)                    # el firmware A lee las banderas y no las borra
    limpio = evento(limpio, causa)                  # el firmware B las lee y las borra al terminar de leer
    print(f"  reinicio por {causa:<3}: firmware A ve [{decodificar(sucio):<22}] | firmware B ve [{decodificar(limpio)}]")
    limpio = set()                                  # B borra después de leer

# ---- Parte 2: brown-out con una batería gastada ---------------------------------------------------------------
V_OC, I_TX = 2.9, 0.015                              # tensión en vacío de las pilas usadas y pico de la radio (15 mA)
print()
print(f"Parte 2: ¿reinicia el pico de transmisión? Tensión en vacío {V_OC} V, pico de {I_TX * 1000:.0f} mA")
print(f"  {'R interna':>10} | {'tensión en el pico':>18} | " + " | ".join(f"BOR {u} V" for u in (1.8, 2.7)))
for r in (5, 20, 50):
    v = V_OC - I_TX * r
    celdas = [("REINICIA" if v < u else "ok") for u in (1.8, 2.7)]
    print(f"  {r:>8} Ω | {v:>16.3f} V | " + " | ".join(f"{c:>8}" for c in celdas))
ARRANQUE_S, VENTANA_S = 0.05, 10.0                   # tarda 50 ms en llegar a transmitir; ventana de 10 s
v = V_OC - I_TX * 20
reinicios = int(VENTANA_S / ARRANQUE_S) if v < 2.7 else 0
print(f"  con R = 20 Ω y BOR de 2,7 V: el equipo reinicia en cada intento -> {reinicios} reinicios en {VENTANA_S:.0f} s (un bucle de reinicios)")
print("  zona de operación segura de un ATmega328P (puntos de la hoja de datos): 1,8 V -> 4 MHz, 2,7 V -> 10 MHz, 4,5 V -> 20 MHz")
print(f"  un BOR de 1,8 V con el chip a 16 MHz deja pasar tensiones donde esa frecuencia no está garantizada (de 1,8 V a 4,5 V)")

# ---- Parte 3: una flota de equipos, 100 reinicios registrados (datos sintéticos) --------------------------------
MS_32_BITS = 2 ** 32
dias_desborde = MS_32_BITS / 86_400_000
print()
print(f"Parte 3: análisis de reinicios de una flota (un contador de milisegundos de 32 bits da la vuelta a los {dias_desborde:.2f} días)")
rnd = random.Random(5)
registro = [("WDT", rnd.gauss(dias_desborde, 0.02)) for _ in range(40)]            # reinicios por watchdog con la misma antigüedad
registro += [("BOR", rnd.uniform(1, 200)) for _ in range(35)]                      # brown-out: en cualquier momento
registro += [("SW", rnd.uniform(0.5, 90)) for _ in range(15)]                      # reinicios por software
registro += [("PIN", rnd.uniform(0.1, 30)) for _ in range(10)]                     # alguien apretó el botón de reset
for causa in ("WDT", "BOR", "SW", "PIN"):
    dias = [d for c, d in registro if c == causa]
    med, dispersion = statistics.median(dias), statistics.pstdev(dias) / statistics.mean(dias)
    pista = "casi idéntica en todos: causa DETERMINISTA (un contador, una fuga)" if dispersion < 0.05 else "muy dispersa: causa AMBIENTAL o aleatoria"
    print(f"  {causa}: {len(dias):>2} reinicios, antigüedad mediana {med:6.2f} días, dispersión {dispersion * 100:5.1f} % -> {pista}")

Resultado, parte 1: tras cinco reinicios (encendido, watchdog, software, brown-out y pin), el firmware A, que no borra las banderas, termina viendo «BOR + PIN + POR + SW + WDT» en los dos últimos y no puede decir cuál fue la causa; el firmware B, que las borra, ve una sola en cada caso (WDT, SW, BOR, PIN; y «PIN + POR» tras el encendido). Parte 2: con 2,9 V en vacío y un pico de 15 mA, la tensión durante el pico es de 2,825 V con 5 Ω, 2,600 V con 20 Ω y 2,150 V con 50 Ω; con un BOR de 1,8 V el equipo sigue en los tres casos, y con uno de 2,7 V reinicia con 20 Ω y con 50 Ω; con 20 Ω y 2,7 V, un arranque de 50 ms produce 200 reinicios en 10 s. Parte 3: un contador de milisegundos de 32 bits da la vuelta a los 49,71 días; en la flota, los 40 reinicios por watchdog tienen una antigüedad mediana de 49,71 días con una dispersión del 0,0 % (causa determinista), mientras que el brown-out (35 reinicios, mediana de 72,88 días, dispersión del 70,7 %), el software (15, 55,17 días, 55,7 %) y el pin (10, 7,42 días, 82,8 %) son muy dispersos (causa ambiental o aleatoria).

Probá cambiar V_OC a 3,2 V para simular pilas más frescas y mirá desde qué resistencia interna reinicia cada umbral, o cambiá en la Parte 3 la dispersión de los reinicios por watchdog (rnd.gauss(dias_desborde, 0.02)) a 20 días para ver cómo pierde la firma de causa determinista. La idea que enseña el programa es doble: una causa se reconoce por su patrón, no por un reinicio aislado, y para ver patrones hace falta haber registrado cada reinicio.

10 · Exploración interactiva

Causas de reinicio

El laboratorio simula un microcontrolador con un registro de banderas de causa de reinicio y una caja negra en memoria que sobrevive a los reinicios (menos al encendido). Provocás distintos reinicios y mirás qué ve el firmware: las banderas, el diagnóstico que saca de ellas, la causa real, el motivo guardado y los contadores. Con la casilla «el firmware borra las banderas tras leerlas» comparás los dos firmwares de la Parte 1, y con tres reinicios seguidos se activa el modo seguro.

Provocar reinicios y leer la causa

Banderas leídas al arrancar—
Diagnóstico del firmware—
Causa real—
Motivo guardado en la caja negra—
Reinicios—
Modo—

Registro de arranques (últimos 10)

—

Elegí un evento. Empezá con «Encender».

Recorrido guiado

  1. Con la casilla sin marcar (el firmware A), apretá «Encender»: las banderas son «PIN + POR» y el diagnóstico es «encendido». La caja negra estaba con basura y se inicializó.
  2. Apretá «Vence el watchdog»: ahora hay tres banderas (PIN + POR + WDT) y el firmware, que mira primero el encendido, diagnostica mal («encendido») aunque la causa real fue el watchdog: el laboratorio lo marca en rojo. Probá con «Pulsar reset»: sigue diagnosticando «encendido». El firmware se volvió ciego porque nunca borra las banderas, y como cree que todo es un encendido, tampoco cuenta los reinicios seguidos (el contador queda en 0).
  3. Marcá «El firmware borra las banderas tras leerlas» y apretá «Encender» otra vez. Ahora repetí watchdog, software, brown-out y pin: cada vez ves una sola bandera y el diagnóstico coincide con la causa real.
  4. Apretá «HardFault»: el manejador anota el motivo «HARDFAULT» en la caja negra y pide un reinicio por software. Fijate que la bandera dice «SW» (software) y que el motivo guardado dice «HARDFAULT»: las banderas dicen qué mecanismo reinició, y el motivo dice por qué.
  5. Apretá «Vence el watchdog» y mirá el motivo guardado: sigue siendo el del reinicio anterior («HARDFAULT»), obsoleto. El watchdog no pudo anotar nada, porque el programa estaba colgado. Solo la bandera dice la verdad; el motivo es de otro reinicio. Es la razón de los hitos de ejecución del apartado 6.
  6. Seguí provocando reinicios sin apretar «Funciona un minuto»: al tercer reinicio seguido el modo pasa a MODO SEGURO y queda marcado en rojo, y el registro lo anota. Apretá «Funciona un minuto» y el contador vuelve a cero. Y apretá «Encender»: el encendido también reinicia el contador y la caja negra (la RAM contenía basura).
Desafío: ¿cómo distinguirías un reinicio por watchdog «inocente» de uno que delata un error serio?

Con los datos de la caja negra y un poco de estadística. Un watchdog inocente es aislado: ocurre una vez, sin patrón, tras una interferencia o un pico raro, y el equipo vuelve a funcionar y no se repite en semanas. Uno que delata un error se reconoce por la repetición: el mismo hito de ejecución en cada reinicio (el programa siempre se cuelga en el mismo lugar), una antigüedad parecida al reiniciar (49,7 días, o siempre tras una cierta cantidad de transmisiones: señal de una fuga de memoria o de un contador), o una racha de reinicios seguidos. La tasa también cuenta: un reinicio por año por equipo en una flota de diez mil son diez al día, y eso es una falla de diseño aunque cada equipo individual parezca sano. Por eso el diagnóstico se envía al servidor y se analiza en conjunto: un reinicio aislado no dice nada, pero cien reinicios registrados dicen casi todo.

11 · Método de trabajo

Método: un reinicio que se explica

  1. Lo primero que hace el arranque es leer la causa de reinicio y borrarla (en AVR, antes de main, y apagando el watchdog si quedó activo). Decidí con una prioridad cuando haya varias banderas.
  2. Armá una caja negra en memoria sin inicializar con marca y CRC: motivo, hito, contadores, tiempo de funcionamiento y datos de la última falla.
  3. Anotá siempre el motivo antes de reiniciar por software, con la secuencia completa: salidas seguras, escrituras terminadas, barrera de memoria.
  4. Marcá hitos de ejecución en los puntos clave del programa para poder ubicar un cuelgue que termine en watchdog.
  5. Elegí el umbral de brown-out con el peor caso: la tensión mínima en el chip (batería usada, en frío, durante el pico) debe quedar por encima del umbral con margen, y el umbral por encima de la mínima que garantiza la frecuencia de trabajo. Usá un aviso de baja tensión (PVD) para guardar lo esencial antes de caer.
  6. Atacá la causa del brown-out, no el umbral: picos más cortos, condensadores, química adecuada, frecuencia más baja.
  7. Protegé contra los bucles de reinicio: contador de reinicios seguidos, modo seguro al tercero, demora de las actividades de mucha corriente tras un reinicio, y nunca escribir en flash en cada arranque.
  8. Sacá el diagnóstico del equipo: telemetría con cada arranque, para analizar la flota en conjunto y buscar patrones (antigüedad, hito, causa).
  9. Provocá cada causa a propósito y verificá que se detecta y se registra bien. Una caja negra que nunca se probó no se sabe si funciona.
  10. Tratá cada reinicio inesperado como un error pendiente, no como una anécdota: si se reinició, hay una causa que entender.

12 · Comprensión

Confusiones frecuentes

«Si el equipo se reinicia y sigue funcionando, no hay problema»

Hay un problema que el reinicio está ocultando. Un equipo que se reinicia una vez por semana funciona, en apariencia, perfecto, pero tiene un error (un cuelgue, una fuga, un pico de batería) que otra circunstancia convertirá en una falla permanente. El reinicio es una defensa, no una solución.

«La bandera de reinicio me dice todo lo que necesito»

Dice qué mecanismo reinició, no por qué. Un reinicio por software puede ser una actualización o un HardFault; uno por watchdog no dice qué hizo vencer al perro. Para el porqué hace falta lo que el programa anotó antes: el motivo, el hito, los datos de la falla.

«Las banderas se borran solas con cada reinicio»

No: son pegajosas. Cada reinicio agrega su bandera a las que ya estaban, y solo el encendido las borra. Si el firmware no las borra después de leerlas, acumula causas viejas y deja de distinguir la actual.

«El brown-out es cuando la batería se agota»

Una batería agotada produce un apagado completo. El brown-out típico ocurre con batería que todavía tiene carga: un pico de corriente que, por la resistencia interna (alta con el uso o el frío), hunde la tensión por debajo del umbral durante unos milisegundos. Por eso aparece con el envejecimiento y el clima, y no con el nivel de carga.

«Se arregla bajando el umbral del detector de brown-out»

Se evita el reinicio, pero se deja al chip ejecutar con una tensión en la que su frecuencia no está garantizada: se cambia un reinicio visible por una ejecución incorrecta invisible, que es peor. La solución es atacar la causa de la caída de tensión.

«Un reinicio por software es una forma limpia de resolver un error»

Es una forma limpia de esconderlo. Si se reinicia para recuperarse de una falla, la falla debe registrarse, contarse y, si se repite, escalarse. Un equipo que se reinicia cada vez que algo sale mal, sin registrar nada, nunca se arregla.

«La RAM conserva sus valores tras un reinicio, así que mi caja negra siempre está bien»

La conserva tras la mayoría de los reinicios, no tras un encendido ni, a veces, tras un brown-out profundo. Y puede estar corrompida por el mismo fallo que causó el reinicio. Por eso la caja negra lleva marca y CRC, y nunca se confía en su contenido sin validarlo.

«Un bucle de reinicios se resuelve con más reinicios»

Un bucle de reinicios es lo contrario: reiniciar repetidamente lo que falla en el arranque. Se corta contando los reinicios seguidos y entrando en un modo seguro, sin repetir lo que falló, y con una salida (configuración de fábrica o firmware anterior). Y se evita empeorarlo: sin escrituras en flash en cada arranque, sin actividades de mucha corriente apenas arranca.

13 · Práctica guiada

Ejercicios

1. Leer las banderas

Un AVR arranca y MCUSR vale 0x0A (en binario, 0000 1010). Los bits son: PORF (bit 0), EXTRF (bit 1), BORF (bit 2), WDRF (bit 3). ¿Qué banderas están marcadas? ¿Qué se puede concluir y qué no?

Ver solución

0x0A = 0b1010: están marcados el bit 1 (EXTRF, reinicio por pin externo) y el bit 3 (WDRF, reinicio por watchdog). No se puede concluir cuál fue el último: puede haber sido un reinicio por watchdog después de uno por pin que nadie borró, o un reinicio por pin después de uno por watchdog. Es el problema de las banderas pegajosas: si el firmware borrara MCUSR en cada arranque, vería una sola. Lo único seguro es que no hubo un encendido (PORF habría borrado el resto y marcaría el bit 0) y que no hubo brown-out desde la última limpieza.

2. El umbral de un ATmega328P

Un ATmega328P trabaja a 8 MHz alimentado con dos pilas AA (de 3,2 V frescas a 2,4 V casi gastadas). Con los puntos de la zona de operación segura (1,8 V a 4 MHz, 2,7 V a 10 MHz, 4,5 V a 20 MHz), ¿qué umbral de BOR conviene (1,8 V, 2,7 V o 4,3 V)? ¿Y si trabajara a 16 MHz con 5 V?

Ver solución

A 8 MHz, interpolando entre los puntos de 1,8 V (4 MHz) y 2,7 V (10 MHz), la zona segura exige unos 2,4 V. El umbral de 2,7 V garantiza que, mientras el chip funcione, esté dentro de esa zona; pero con pilas que bajan hasta 2,4 V, el chip entra en reset por debajo de 2,7 V y no aprovecha la última parte de la batería. Un umbral de 1,8 V aprovecharía más la batería, pero dejaría operar al chip entre 1,8 V y 2,4 V sin garantía para 8 MHz. La alternativa que aprovecha toda la batería es bajar la frecuencia a 4 MHz y usar un BOR de 1,8 V, que permite funcionar hasta 1,8 V con la frecuencia garantizada. A 16 MHz con 5 V el umbral de 4,3 V es el adecuado (la zona segura a 16 MHz exige más de unos 3,8 V, que se obtiene interpolando entre los puntos de 2,7 V y 4,5 V, aproximadamente); los umbrales de 1,8 V y 2,7 V dejarían operar al chip en una zona no garantizada para esa frecuencia.

3. El pico y el condensador

Una pila de 3,0 V en vacío con resistencia interna de 30 Ω alimenta un nodo con picos de 15 mA y un BOR de 2,7 V. ¿Reinicia? ¿Cuánto tiene que valer un condensador para que el pico (90 µC) no hunda la tensión más de 0,2 V?

Ver solución

Tensión en el pico: 3,0 − 0,015 × 30 = 3,0 − 0,45 = 2,55 V, menor que 2,7 V: reinicia. Con un condensador que aporte el pico, la caída de su tensión es ΔV = Q / C. Para ΔV ≤ 0,2 V con Q = 90 µC: C ≥ 90 µC / 0,2 V = 450 µF; un condensador de 470 µF queda en 0,19 V de caída (una cota superior, porque la pila también aporta algo durante el pulso). La tensión en el chip queda en unos 2,8 V, por encima de los 2,7 V. Tiene que ser cerámico o de película, o un electrolítico de baja fuga, para no sumar corriente de fuga al reposo (tema 36), y el condensador se recarga entre picos a través de la resistencia interna: con 30 Ω y 470 µF, la constante de tiempo es de 14 ms, mucho menor que el período entre transmisiones.

4. Cuántos reinicios hasta el modo seguro

Con un tope de 3 reinicios seguidos, la secuencia es: encendido, watchdog, watchdog, watchdog. ¿En qué reinicio se activa el modo seguro? ¿Qué pasa si, entre el segundo y el tercer watchdog, el equipo funciona un minuto completo?

Ver solución

Tras el encendido, el contador de seguidos vale 0 (el encendido lo borra). Primer watchdog: 1. Segundo: 2. Tercer watchdog: 3, se activa el modo seguro. Si entre el segundo y el tercero el equipo funciona un minuto, el contador vuelve a cero con el minuto de funcionamiento, y el tercer watchdog queda como el primero de una nueva racha (contador 1): no se activa el modo seguro. La lógica distingue «tres reinicios sin respirar» (un bucle) de «tres reinicios aislados a lo largo del día» (un equipo con un problema ocasional, que se registra pero no necesita el modo seguro).

5. El contador de 49,7 días

Calculá cuántos días tarda en dar la vuelta un contador de milisegundos de 32 bits. Un programa tiene if (ahora > limite) con limite = ahora + 100. ¿Qué pasa cuando ahora está cerca del desborde? ¿Cómo se escribe correctamente?

Ver solución

232 ms = 4 294 967 296 ms; dividido por 86 400 000 ms por día son 49,71 días. Si ahora vale 4 294 967 250 y limite = ahora + 100, el cálculo desborda y limite vale 54: ahora > limite es verdadero de inmediato, y la condición «ya pasaron 100 ms» se cumple cuando no pasó nada (y, del otro lado, cuando ahora también da la vuelta, ciertos plazos se cumplen mal). La forma correcta compara la diferencia con signo: if ((int32_t)(ahora - limite) >= 0). Con 4 294 967 250 − 54 = 4 294 967 196 (que como entero de 32 bits con signo es −100): negativo, no venció. Cuando ahora llega a 54, la diferencia es 0: venció. Funciona siempre que el plazo sea menor que 231 ms (24,8 días).

6. Un reinicio sin pistas

Un equipo se reinicia por watchdog una vez cada tantos días y la caja negra solo muestra el motivo del reinicio anterior, que no tiene relación. ¿Qué agregarías para saber qué lo cuelga? ¿Por qué el motivo guardado engaña?

Ver solución

El motivo guardado engaña porque el watchdog no deja motivo: el programa estaba colgado y no pudo anotar nada, así que en la caja negra queda el de un reinicio anterior (obsoleto). Hay que distinguir lo que escribió el programa de lo que dice el hardware: la bandera de watchdog es la verdad. Para saber qué lo cuelga se agregan: hitos de ejecución (HITO(n) en cada etapa; el último valor dice dónde se detuvo); el consumo del motivo al arrancar (borrarlo tras leerlo, para que un motivo viejo no se confunda con uno nuevo); el tiempo de funcionamiento antes del reinicio (para ver si hay un patrón); y, si el chip lo ofrece, la interrupción de aviso previo del watchdog, que corre unos instantes antes del reinicio y permite guardar el PC y la pila. Con el hito repetido en los reinicios se identifica el lugar, y con el .map la función.

14 · Síntesis

Ideas para recordar

  • El reinicio es a la vez un mecanismo de recuperación (la última defensa) y un síntoma. Pero borra la evidencia: sin un registro de por qué ocurrió, un equipo que «se reinicia a veces» es casi imposible de depurar. Un reinicio inexplicado es un error pendiente.
  • Las causas son: encendido, brown-out, pin de reset, watchdog, software, falla grave y despertar de un modo apagado. Cada una apunta a un lugar distinto donde buscar, y un reinicio por software siempre tiene un motivo anterior que anotar.
  • El registro de causa (MCUSR, RCC->CSR, RESETREAS, esp_reset_reason()) se lee lo primero y se borra enseguida: sus banderas son pegajosas y se acumulan. Cuando hay varias se decide por prioridad. En un AVR, además, se apaga el watchdog que pudo quedar activo.
  • El brown-out es una tensión insuficiente en la que el chip ejecuta mal; el detector (BOR) lo mantiene en reset. El umbral debe estar por encima de la tensión que garantiza la frecuencia de trabajo y por debajo de la tensión mínima real en el peor caso. El brown-out típico es un pico sobre una batería con resistencia interna alta: se ataca la causa (condensadores, picos más cortos, química), no solo el umbral. Un PVD avisa antes de caer.
  • Un reinicio por software bien hecho anota el motivo, deja las salidas en estado seguro, termina las escrituras en curso y usa una barrera antes de pedirlo (NVIC_SystemReset). Es el último recurso, no el primero.
  • La caja negra (RAM sin inicializar con marca y CRC) guarda motivo, hito de ejecución, contadores, tiempo de funcionamiento y datos de la última falla. Los hitos resuelven el reinicio por watchdog, que no deja motivo. Y la información tiene que salir del equipo para analizarla en conjunto.
  • Los patrones delatan la causa: reinicios a un tiempo casi idéntico (como 49,71 días, el desborde de un contador de milisegundos de 32 bits) son deterministas; los dispersos son ambientales. Los contadores de tiempo se comparan por diferencias, no por magnitud.
  • Los bucles de reinicio se cortan contando los reinicios seguidos y entrando en modo seguro al tercero, con una salida (configuración de fábrica o firmware anterior), demorando las actividades de mucha corriente y sin escribir en flash en cada arranque.

¿Listo para el tema siguiente?

Antes de avanzar, mirá un firmware tuyo y respondé: ¿lee la causa de reinicio al arrancar?, ¿la borra?, ¿guarda en algún lado el motivo de los reinicios por software?, ¿sabría decir qué reinició al equipo la última vez? Después provocá a propósito cada causa (encendido, pin, software, watchdog) y comprobá qué dice tu programa. Si alguna respuesta es «no sé», ahí está el próximo reinicio misterioso.

En el próximo tema vemos la defensa más importante contra el cuelgue: el watchdog. El temporizador perro guardián, cómo alimentarlo bien (y mal), los watchdogs de ventana, el watchdog externo, y cómo se combina con la caja negra de este tema para convertir cada rescate en información.