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:
| Fuente | Cuándo se produce | Bandera |
|---|---|---|
| Encendido (power-on reset) | La tensión de alimentación sube desde cero hasta superar un umbral bajo | PORF |
| Reset externo | El 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á habilitado | BORF |
| Vigilante (watchdog) | Vence su temporizador sin que el programa lo haya reiniciado | WDRF |
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:
- La tensión sube. Mientras esté por debajo del umbral de encendido, el circuito de power-on reset mantiene al chip detenido.
- 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.
- Se libera el reset. La CPU carga el contador de programa con
0x0000(o con la sección de arranque, si el fuseBOOTRSTestá programado, como en la placa Uno). - 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.bssy copia a la SRAM los valores iniciales de.datadesde la Flash. - 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:
| Bit | 3 | 2 | 1 | 0 |
|---|---|---|---|---|
| Nombre | WDRF | BORF | EXTRF | PORF |
| Causa | Vigilante | Caída de tensión | Pin de reset | Encendido |
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:
| BODLEVEL2:0 | Umbral | Cuándo conviene |
|---|---|---|
111 | Deshabilitado | Se prioriza el consumo mínimo y la alimentación es estable |
110 | 1,8 V | Funcionamiento a baja tensión y poca velocidad |
101 | 2,7 V | Relojes de hasta 10 MHz; es el valor de la placa Uno |
100 | 4,3 V | Alimentació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.
| WDP3:0 | Tiempo | Macro |
|---|---|---|
0000 a 0011 | 16, 32, 64 y 125 ms | WDTO_15MS, WDTO_30MS, WDTO_60MS, WDTO_120MS |
0100 a 0110 | 250, 500 ms y 1 s | WDTO_250MS, WDTO_500MS, WDTO_1S |
0111 a 1001 | 2, 4 y 8 s | WDTO_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»:
| Fuse | Qué controla | Cuidado |
|---|---|---|
RSTDISBL | Usa el pin de reset como E/S (PC6) | Impide reprogramar por ISP |
DWEN | Habilita la depuración por debugWIRE | Usa el pin de reset para depurar |
SPIEN | Habilita la programación serie (ISP) | Desprogramarlo bloquea el ISP; no se puede hacer por ISP |
WDTON | Mantiene el vigilante encendido siempre | El programa no puede apagarlo |
EESAVE | Conserva la EEPROM al borrar el chip | Útil para no perder calibraciones al reprogramar |
BOOTSZ1:0 | Tamaño de la sección de arranque | Reserva Flash para el cargador |
BOOTRST | Empezar en la sección de arranque tras un reset | En una Uno está programado para que arranque el cargador |
BODLEVEL2:0 | Umbral del detector de caída de tensión | Ver 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ó?
2. Configurar el vigilante
3. Tensión, reloj y detector de caída de tensión
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
- En la herramienta 1 marcá
PORF: es el arranque normal. MarcáWDRFsolo: el programa se colgó. MarcáEXTRFyWDRFjuntos y leé la advertencia sobre las banderas acumuladas. - En la herramienta 2, con 2 s y modo reset, copiá el valor
0x0F. Cambiá a 8 s: apareceWDP3y el valor es0x29. Elegí «Interrupción»: queda0x40, soloWDIE. - 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.
- 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).
- 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
- Al principio de todo (sección
.init3): copiarMCUSR, borrarlo y deshabilitar el vigilante. - Esperar a que el hardware esté listo: alimentación estable, sensores que tardan en arrancar, cristal estabilizado.
- Configurar el detector de caída de tensión con un umbral acorde al reloj y a la tensión de trabajo.
- Inicializar periféricos y estados con valores seguros: salidas en estado inocuo (motores apagados, relés abiertos) antes de habilitar nada.
- Activar el vigilante con un tiempo que tenga margen sobre el peor caso del bucle, y alimentarlo desde un único punto del bucle principal.
- 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.bssen cero y copia.data. MCUSRguarda 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
MCUSRy deshabilitarlo pronto para evitar reinicios en cadena. - Los fuses son permanentes y peligrosos:
RSTDISBLySPIENpueden 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.