← Índice del curso

Microcontroladores · Tema 13

Reset, arranque y protección

Un microcontrolador no tiene a nadie que lo reinicie cuando falla. Cuatro mecanismos de hardware le dan arranque limpio, defensa contra la alimentación inestable y recuperación si el programa se cuelga. Conocerlos distingue un prototipo de un dispositivo que funciona meses sin atención.

01 · Punto de partida

Qué pasa cuando algo falla

En un servidor, un proceso que se cuelga lo reinicia el sistema operativo o un supervisor. En un microcontrolador no hay sistema operativo ni supervisor externo: el programa lo es todo. Si se queda esperando un evento que no llega, entra en un bucle infinito por un error de lógica, o la tensión cae por un instante, el dispositivo puede quedar inutilizable hasta que alguien desconecte la alimentación.

Para eso el chip incorpora mecanismos de hardware que actúan aunque el programa esté perdido: una lógica que reinicia el sistema cuando se enciende, otra que lo retiene mientras la tensión es insuficiente y un temporizador que lo reinicia si el programa deja de dar señales de vida.

Retomamos el pin RESET y los fuses del tema 4 y del tema 12, el registro de estado del tema 9 y el código de arranque del tema 5.

02 · Cuatro caminos

Las fuentes de reset

Un reset detiene la CPU, devuelve todos los registros de E/S a su valor inicial y hace que la ejecución comience de nuevo en la dirección 0x0000, el vector de reset. El ATmega328P tiene cuatro fuentes:

Fuentes de reset del ATmega328P y su bandera en MCUSR.
FuenteCuándo se produceBandera
Encendido (power-on reset)La tensión de alimentación sube desde cero hasta superar un umbral bajoPORF
Reset externoEl pin RESET se mantiene en bajo durante un tiempo mínimo (unos pocos microsegundos)EXTRF
Caída de tensión (brown-out)La alimentación baja del umbral configurado y el detector está habilitadoBORF
Vigilante (watchdog)Vence su temporizador sin que el programa lo haya reiniciadoWDRF

Los cuatro desembocan en el mismo lugar, pero no son iguales: dos son del entorno (encendido y reset externo), uno es un problema eléctrico (caída de tensión) y uno es una falla del software (vigilante). Distinguirlos permite diagnosticar.

03 · De la energía al main()

La secuencia de arranque

Entre el instante en que llega la energía y la primera línea de tu programa ocurren varias cosas, todas importantes para el comportamiento del sistema:

  1. La tensión sube. Mientras esté por debajo del umbral de encendido, el circuito de power-on reset mantiene al chip detenido.
  2. Se supera el umbral. El chip empieza a contar un tiempo de arranque (determinado por los fuses de reloj, tema 12) para que la fuente de reloj y la alimentación se estabilicen. Con los fuses de fábrica son decenas de milisegundos.
  3. Se libera el reset. La CPU carga el contador de programa con 0x0000 (o con la sección de arranque, si el fuse BOOTRST está programado, como en la placa Uno).
  4. Código de inicio. Antes de main(), el enlazador inserta código que coloca el puntero de pila en el final de la SRAM, pone en cero .bss y copia a la SRAM los valores iniciales de .data desde la Flash.
  5. Empieza main(). Los periféricos están en su estado inicial: pines como entradas, temporizadores detenidos, interrupciones deshabilitadas.

Dos consecuencias prácticas. La primera: un programa no debe asumir que la alimentación ya es estable en su primera instrucción; si un sensor tarda 100 ms en estar listo, hay que esperarlo. La segunda: el estado de la SRAM tras un reset que no sea de encendido no está garantizado, aunque a menudo se conserve; ningún programa debe contar con eso, salvo una zona declarada expresamente para ello (se ve más adelante).

04 · Un cable sensible

El pin de reset

El pin RESET (pin 1 del chip, tema 4) es de lógica negativa: un nivel bajo reinicia el chip. Para que no se reinicie por ruido, tiene una resistencia de pull-up interna (de decenas de kiloohmios), y un montaje fiable añade una externa de 10 kΩ a VCC. El botón de reset conecta el pin a masa.

  • Un capacitor de 100 nF entre RESET y masa filtra picos breves, pero con un botón y un cable largo puede ralentizar la subida; en la Uno hay uno entre DTR y el pin para generar el reset automático al programar.
  • Los cables largos hacia el pin RESET captan interferencias. Reinicios inexplicables en un entorno con motores o relés suelen venir de allí.
  • El pin RESET es también la entrada del programador ISP, que lo usa para poner al chip en modo de programación.

05 · La caja negra

MCUSR: por qué se reinició

El registro MCUSR (MCU Status Register) guarda la causa del último reset en cuatro banderas:

MCUSR (dirección de datos 0x54). Los bits 7 a 4 son reservados.
Bit3210
NombreWDRFBORFEXTRFPORF
CausaVigilanteCaída de tensiónPin de resetEncendido

Dos reglas importantes para usarlo:

  • Hay que leerlo temprano y borrarlo. Las banderas no se borran solas: si no las limpiás, se acumulan y después de varios resets no sabés cuál fue el último. Se lee al principio del programa y se pone en 0.
  • Se borran escribiendo un 0, al contrario de las banderas de eventos del tema 9. MCUSR = 0; basta. Es una excepción que conviene recordar.
uint8_t causa_reset __attribute__((section(".noinit")));   // en .noinit: el código de inicio no la pone a cero

void guardar_causa(void) __attribute__((naked, section(".init3")));
void guardar_causa(void) {
    causa_reset = MCUSR;       // copiar la causa
    MCUSR = 0;                 // y borrar las banderas
    wdt_disable();             // el vigilante se desactiva (ver sección 7)
}

La rutina se coloca en la sección .init3, que se ejecuta antes de main(), y la variable en .noinit, una zona que el código de inicio no pone a cero. Así main() puede consultar causa_reset y, por ejemplo, registrar o mostrar el motivo del reinicio. Para un dispositivo en campo, saber que hubo veinte reinicios por vigilante en una semana es información valiosísima.

06 · Cuidar la tensión

Detector de caída de tensión

Cuando la alimentación baja de la tensión mínima, el chip no se apaga: sigue ejecutando, pero los transistores ya no conmutan con la rapidez del reloj y empieza a cometer errores. Peor aún: puede escribir basura en la EEPROM o ejecutar una instrucción mal interpretada. Es un fallo que no deja huella.

El detector de caída de tensión (brown-out detection, BOD) compara continuamente la alimentación con un umbral. Si cae por debajo, mantiene al chip en reset hasta que se recupere. Se configura con los fuses BODLEVEL2:0:

Umbrales del detector (valores típicos de la hoja de datos).
BODLEVEL2:0UmbralCuándo conviene
111DeshabilitadoSe prioriza el consumo mínimo y la alimentación es estable
1101,8 VFuncionamiento a baja tensión y poca velocidad
1012,7 VRelojes de hasta 10 MHz; es el valor de la placa Uno
1004,3 VAlimentación de 5 V con relojes altos

La elección sale del tema 12: el umbral debe ser cercano a la tensión mínima que necesita el reloj elegido. A 16 MHz hacen falta 4,5 V; un umbral de 2,7 V deja un tramo amplio (de 2,7 a 4,5 V) en que el chip funciona sin garantías y sin reiniciarse. Por eso, con relojes altos, el nivel más cercano disponible es 4,3 V.

El detector consume una pequeña corriente continua (del orden de decenas de microamperios), relevante en aplicaciones a batería. En los modos de bajo consumo se puede desactivar por software temporalmente, tema que veremos en el bloque de energía.

07 · El perro guardián

El vigilante (watchdog)

El vigilante es un temporizador independiente, con su propio oscilador interno de unos 128 kHz, que cuenta hacia el vencimiento. Si el programa no lo reinicia a tiempo con la instrucción wdt_reset() (WDR), vence y reinicia el microcontrolador. Funciona como un «hombre muerto»: mientras el programa está sano, lo alimenta; si se cuelga, deja de hacerlo.

Tiempos de vencimiento del vigilante (nominales a 5 V).
WDP3:0TiempoMacro
0000 a 001116, 32, 64 y 125 msWDTO_15MS, WDTO_30MS, WDTO_60MS, WDTO_120MS
0100 a 0110250, 500 ms y 1 sWDTO_250MS, WDTO_500MS, WDTO_1S
0111 a 10012, 4 y 8 sWDTO_2S, WDTO_4S, WDTO_8S

Su registro, WDTCSR, permite tres modos: reset (WDE = 1), interrupción (WDIE = 1) y ambos (la primera expiración dispara una interrupción y la siguiente reinicia). Como un cambio accidental sería peligroso, su configuración se protege con la misma idea que CLKPR: hay que abrir una ventana de cuatro ciclos escribiendo WDCE y WDE en 1 y luego escribir el nuevo valor. La biblioteca avr-libc lo resuelve con wdt_enable(), wdt_reset() y wdt_disable().

Cómo usarlo bien

  • Alimentarlo solo cuando todo está bien. Se llama a wdt_reset() en un único lugar del bucle principal, después de comprobar que las tareas importantes avanzaron. Si se alimenta desde una interrupción de temporizador, el vigilante seguirá contento aunque el bucle principal esté colgado: pierde su sentido.
  • Elegir el tiempo con margen. El peor caso de una vuelta del bucle principal debe ser bastante menor que el tiempo del vigilante.
  • Registrar los vencimientos. Guardar la causa de reset (MCUSR) permite saber si el dispositivo se está reiniciando en campo.
  • No esperar que arregle el error. Reinicia el sistema; no corrige la causa del cuelgue. Si el error es determinista, el chip entrará en un ciclo de reinicios.

08 · Configuración permanente

Fuses de arranque y seguridad

Además de los de reloj (tema 12), el chip tiene fuses que controlan el arranque y la protección. Recordá que en un fuse 0 significa «programado»:

Byte alto y byte extendido de fuses del ATmega328P.
FuseQué controlaCuidado
RSTDISBLUsa el pin de reset como E/S (PC6)Impide reprogramar por ISP
DWENHabilita la depuración por debugWIREUsa el pin de reset para depurar
SPIENHabilita la programación serie (ISP)Desprogramarlo bloquea el ISP; no se puede hacer por ISP
WDTONMantiene el vigilante encendido siempreEl programa no puede apagarlo
EESAVEConserva la EEPROM al borrar el chipÚtil para no perder calibraciones al reprogramar
BOOTSZ1:0Tamaño de la sección de arranqueReserva Flash para el cargador
BOOTRSTEmpezar en la sección de arranque tras un resetEn una Uno está programado para que arranque el cargador
BODLEVEL2:0Umbral del detector de caída de tensiónVer la sección 6

Los valores de fábrica son 0xD9 para el byte alto y 0xFF para el extendido (sin detector). Además existen los bits de bloqueo (lock bits), que impiden leer o modificar la Flash y la EEPROM desde fuera, una protección básica del programa contra copias. Una vez puestos, solo se quitan borrando el chip entero.

La regla de prudencia es anotar los valores de fábrica antes de cambiar nada, y modificar un fuse por vez, verificando con una lectura. Los programas como avrdude muestran los fuses en hexadecimal; una calculadora en línea de fuses ayuda a traducirlos a nombres, y un error en este paso puede costarte el chip.

09 · Analogía con programación

Interpretar un reset y medir lo que salva el vigilante

Este programa tiene dos partes. La primera decodifica los cuatro bits de MCUSR en causas. La segunda simula un firmware con un error determinista que lo cuelga a los 2 segundos de funcionar, y compara cuánto tiempo está realmente disponible sin vigilante y con vigilantes de distintos tiempos.

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

def causa_reset(mcusr):
    """Interpreta las cuatro banderas de MCUSR (bits 3 a 0)."""
    nombres = [(0, "encendido (PORF)"), (1, "pin de reset (EXTRF)"),
               (2, "caída de tensión (BORF)"), (3, "vigilante (WDRF)")]
    causas = [texto for bit, texto in nombres if mcusr & (1 << bit)]
    return causas or ["ninguna registrada"]


for valor in (0b0001, 0b0010, 0b1000, 0b1010):
    print(f"MCUSR = {valor:04b}  ->  {', '.join(causa_reset(valor))}")

print()


def simular(vigilante_ms, falla_tras_ms=2000, arranque_ms=50, total_ms=60_000):
    """Un firmware con un error que lo cuelga tras `falla_tras_ms` de funcionar.
    Con vigilante, el chip se reinicia al vencer; sin vigilante, queda colgado."""
    t, funcionando, reinicios = 0, 0, 0
    while t < total_ms:
        corre = min(falla_tras_ms, total_ms - t)       # funciona hasta que se cuelga
        funcionando += corre
        t += corre
        if t >= total_ms:
            break
        if vigilante_ms is None:                        # nadie lo reinicia
            break
        t += vigilante_ms + arranque_ms                 # espera el vencimiento y vuelve a arrancar
        reinicios += 1
    return funcionando / total_ms * 100, reinicios


for etiqueta, tiempo in (("sin vigilante", None), ("vigilante 1 s", 1000), ("vigilante 250 ms", 250), ("vigilante 15 ms", 15)):
    disponibilidad, reinicios = simular(tiempo)
    print(f"{etiqueta:<18} disponibilidad {disponibilidad:5.1f} %   reinicios: {reinicios}")

Resultado: sin vigilante, el dispositivo trabaja solo 2 de 60 segundos (3,3 %); con vigilante de 1 s, 66,7 %; con 15 ms, 96,9 %. Cuanto más corto el tiempo, mayor la disponibilidad, pero a costa de exigir que el bucle principal sea más rápido. Probá cambiar falla_tras_ms a 500 y observá cuántos reinicios ocurren.

El modelo deja claro que el vigilante es una red de seguridad, no una solución. El dispositivo sigue fallando cada 2 segundos; lo único que cambia es que se recupera. En la práctica, cada vencimiento debe registrarse y analizarse, no ignorarse. El modelo también omite que el tiempo de arranque real incluye la espera del cargador y la inicialización de los periféricos.

10 · Exploración interactiva

Causa de reset, vigilante y detector de tensión

Tres herramientas pequeñas: un intérprete de MCUSR con consejos de diagnóstico, un configurador del vigilante que calcula WDTCSR y genera el código, y un simulador del detector de caída de tensión frente a la velocidad del reloj.

Banco de reset y protección

1. ¿Por qué se reinició?

MCUSR00000000
Causa—

2. Configurar el vigilante

WDTCSR a escribir—
Campos—

3. Tensión, reloj y detector de caída de tensión

Estado del chip—

La herramienta 3 usa los mismos grados de velocidad por tensión del tema 12 (4 MHz desde 1,8 V; 10 MHz desde 2,7 V; 20 MHz desde 4,5 V) y no modela la histéresis del detector (unas decenas de milivoltios) ni el tiempo que tarda en reaccionar.

Recorrido guiado

  1. En la herramienta 1 marcá PORF: es el arranque normal. Marcá WDRF solo: el programa se colgó. Marcá EXTRF y WDRF juntos y leé la advertencia sobre las banderas acumuladas.
  2. En la herramienta 2, con 2 s y modo reset, copiá el valor 0x0F. Cambiá a 8 s: aparece WDP3 y el valor es 0x29. Elegí «Interrupción»: queda 0x40, solo WDIE.
  3. En la herramienta 3 dejá 2,7 V, 16 MHz y 5 V: todo en regla. Bajá a 3,5 V: el chip sigue ejecutando fuera de especificación, porque el umbral no lo protege.
  4. Elegí el umbral de 4,3 V con 4,4 V de alimentación: sigue habiendo una pequeña zona sin protección (por debajo de los 4,5 V que necesitan 16 MHz).
  5. Pasá el reloj a 8 MHz con el umbral de 2,7 V y 5 V de alimentación: ahora el umbral coincide con lo que el reloj necesita.
Desafío: elegir el tiempo del vigilante

El bucle principal de un termostato lee un sensor (50 ms), actualiza una pantalla (hasta 200 ms en el peor caso) y tiene una espera de 100 ms. El peor caso es de 350 ms. ¿Qué tiempo elegís? Con 500 ms hay poco margen (350 de 500); con 1 s hay buen margen y la recuperación sigue siendo rápida. Evitá 250 ms, que vencería en un camino lento.

11 · Método de trabajo

Cómo diseñar un arranque robusto

  1. Al principio de todo (sección .init3): copiar MCUSR, borrarlo y deshabilitar el vigilante.
  2. Esperar a que el hardware esté listo: alimentación estable, sensores que tardan en arrancar, cristal estabilizado.
  3. Configurar el detector de caída de tensión con un umbral acorde al reloj y a la tensión de trabajo.
  4. Inicializar periféricos y estados con valores seguros: salidas en estado inocuo (motores apagados, relés abiertos) antes de habilitar nada.
  5. Activar el vigilante con un tiempo que tenga margen sobre el peor caso del bucle, y alimentarlo desde un único punto del bucle principal.
  6. Registrar la causa de los reinicios y usar la información para detectar problemas en campo.

En la mesa de trabajo, el osciloscopio con el disparo en el pin RESET y otro canal en VCC muestra la secuencia de encendido real: cuánto tarda la alimentación en subir, cuándo se libera el reset y cuándo aparece el primer pulso en un pin. Para provocar un reinicio por caída de tensión, una fuente regulable permite bajarla despacio y ver a qué valor se reinicia el chip.

12 · Comprensión

Confusiones frecuentes

«El vigilante arregla los programas que se cuelgan»

Solo los reinicia. Si el error es determinista, el dispositivo vuelve a colgarse. El vigilante es una red de seguridad y la causa debe corregirse.

«Puedo alimentar el vigilante desde una interrupción para que nunca falle»

Así el vigilante deja de detectar que el bucle principal está colgado, porque la interrupción sigue funcionando. Hay que alimentarlo desde el flujo principal, tras comprobar que todo avanza.

«Con la tensión justo por debajo del mínimo el chip se apaga»

Sin detector de caída de tensión, el chip sigue ejecutando con errores hasta que ya no puede. Esa zona intermedia es peligrosa porque los fallos son erráticos y no dejan rastro.

«Al reiniciarse, todas las variables vuelven a cero»

Solo si el código de inicio las inicializa, y eso afecta a .data y .bss. Una variable en la zona .noinit, o el contenido de la SRAM antes de que el código de inicio la toque, puede conservarse tras un reset sin pérdida de energía, pero sin garantías.

«Para ahorrar un pin puedo usar RSTDISBL sin problema»

Si se programa por ISP, deja de ser posible reprogramar el chip por ese medio. Solo se recupera con un programador de alta tensión. Hay que pensarlo dos veces y tener otra vía de programación.

13 · Práctica guiada

Predecí antes de comprobar

1. Reiniciar sin parar

Un programa habilita el vigilante de 2 s en main() y se cuelga. Tras el reinicio el chip se reinicia cada 16 ms sin llegar a ejecutar nada. ¿Por qué?

Ver solución razonada

Tras un reset por vigilante, el AVR deja el vigilante activo con el tiempo mínimo (16 ms) y la bandera WDRF encendida. Si el programa tarda más de 16 ms en llegar a main() y reconfigurarlo, vuelve a vencer. La solución es limpiar MCUSR y llamar a wdt_disable() en la sección .init3, antes del resto del arranque.

2. Elegir el tiempo

El peor caso de una vuelta del bucle principal es de 1,2 s. ¿Cuáles de los tiempos del vigilante sirven y cuál elegirías?

Ver solución razonada

Sirven solo los mayores que 1,2 s: 2, 4 y 8 s. Con la regla de dejar el doble de margen, 2 s se queda corto (1,2 de 2) y 4 s es una elección prudente. Otra opción es acortar el peor caso del bucle.

3. Leer MCUSR

Un dispositivo a batería registra MCUSR = 0b00000100 cada vez que arranca el motor. ¿Qué significa y por dónde empezarías a buscar?

Ver solución razonada

El bit 2 es BORF: caída de tensión. El arranque del motor produce un pico de corriente que hace caer la alimentación por debajo del umbral. Hay que mejorar la fuente y el desacople (capacitores cerca del chip y del motor), separar las alimentaciones o alimentar el motor desde otra fuente.

4. Un fuse peligroso

Programás el fuse RSTDISBL por ISP para ganar un pin. ¿Qué pasa después y cómo se recupera?

Ver solución

El pin de reset pasa a ser PC6. El programador ISP necesita RESET para poner al chip en modo de programación, de modo que ya no puede reprogramarlo por ISP. Solo se recupera con un programador de alta tensión que fuerza la restauración del fuse.

5. ¿Dónde alimentar al vigilante?

Un programa llama a wdt_reset() dentro de la interrupción de un temporizador de 10 ms. El bucle principal se cuelga y el vigilante no actúa. ¿Por qué?

Ver solución razonada

La interrupción sigue ejecutándose aunque el bucle principal esté detenido, y sigue alimentando al vigilante. Este nunca vence, y el cuelgue pasa inadvertido. La llamada debe estar en el flujo principal, en un punto que solo se alcanza si las tareas avanzaron.

14 · Síntesis

Ideas para recordar

  • Hay cuatro fuentes de reset: encendido, pin externo, caída de tensión y vigilante; todas llevan al vector 0x0000.
  • Antes de main() corre un código de inicio: configura la pila, pone .bss en cero y copia .data.
  • MCUSR guarda la causa; se lee al arrancar y se borra escribiendo 0, o las banderas se acumulan.
  • El detector de caída de tensión retiene el reset por debajo de un umbral; debe ser cercano a la tensión que exige el reloj.
  • El vigilante reinicia el sistema si no se lo alimenta a tiempo; se alimenta una sola vez en el bucle principal, nunca desde una interrupción.
  • Tras un reset por vigilante, hay que limpiar MCUSR y deshabilitarlo pronto para evitar reinicios en cadena.
  • Los fuses son permanentes y peligrosos: RSTDISBL y SPIEN pueden dejar el chip inaccesible por ISP.

¿Listo para el tema 14?

Antes de avanzar, enumerá las cuatro fuentes de reset, explicá por qué el vigilante no debe alimentarse desde una interrupción y decí cómo elegirías el umbral del detector para un reloj de 16 MHz. Si podés justificar las tres respuestas, entendés cómo arranca y se protege un microcontrolador.

En el próximo tema hacemos un taller de medición de ciclos y tiempos de ejecución: contamos instrucciones, usamos un pin como marca y verificamos con el osciloscopio cuánto tarda de verdad cada parte del programa.