01 · La idea
La escalera de estados
Un microcontrolador no está «encendido» o «apagado»: tiene una escalera de estados, cada uno con menos consumo, menos cosas funcionando y un despertar más lento que el anterior. Los nombres cambian según el fabricante, pero la estructura es la misma en casi todos:
Las barras de la figura están en escala logarítmica porque los consumos abarcan más de tres órdenes de magnitud: pasar de activo a standby divide el consumo por 2500. Las cifras de la figura son un ejemplo didáctico, de orden de magnitud típico de un Cortex-M; las de cada chip están en su hoja de datos (y en el tema 36 se miden). Lo que sí es general es la regla de oro de esta escalera:
Para el programador, esto cambia la forma de pensar el firmware: ya no es «un bucle que corre siempre» sino un programa que trabaja a ráfagas entre sueños, y cuyo diseño (cuándo se despierta, qué lo despierta, qué queda hecho antes de dormir) importa tanto como cada línea de código. El tema 35 desarrolla ese diseño; este tema construye las piezas.
02 · El mecanismo
Cómo se duerme en un Cortex-M
Los núcleos Cortex-M tienen dos instrucciones para dormir, y un registro de control que define la profundidad:
| Elemento | Qué hace |
|---|---|
WFI (wait for interrupt) | Detiene el núcleo hasta que llegue una interrupción (o un evento de depuración). Se la usa casi siempre. En C: __WFI() |
WFE (wait for event) | Detiene el núcleo hasta que ocurra un evento: una interrupción, o una señal de evento de otro núcleo o de un periférico. Si el «registro de evento» ya estaba marcado, no duerme y lo borra. Se usa en sistemas multinúcleo y en pilas de radio. En C: __WFE() |
Bit SLEEPDEEP de SCB->SCR | Le dice al sistema de potencia que el próximo WFI o WFE debe ser un sueño profundo. Qué significa «profundo» (qué se apaga) lo define cada fabricante en su controlador de energía |
Bit SLEEPONEXIT | El núcleo vuelve a dormir automáticamente al terminar una ISR, sin volver al programa principal. Permite programas que viven solo de interrupciones, sin bucle principal |
Bit SEVONPEND | Una interrupción que queda pendiente (aunque esté enmascarada) genera un evento que despierta de un WFE |
#define SCB_SCR (*(volatile uint32_t *)0xE000ED10) /* System Control Register */
#define SCR_SLEEPONEXIT (1u << 1)
#define SCR_SLEEPDEEP (1u << 2)
#define SCR_SEVONPEND (1u << 4)
void dormir_ligero(void) { SCB_SCR &= ~SCR_SLEEPDEEP; __WFI(); } /* sleep: solo se detiene el núcleo */
void dormir_profundo(void) {
SCB_SCR |= SCR_SLEEPDEEP; /* el próximo WFI será profundo */
__WFI();
SCB_SCR &= ~SCR_SLEEPDEEP; /* al despertar, volver al estado ligero */
}
Con esto alcanzaría para dormir, pero hay una trampa que hace que el 99 % de los programas funcionen «casi siempre»: la carrera entre comprobar y dormir. El patrón ingenuo es:
/* MAL: si la interrupción llega entre el 'if' y el WFI, el evento se pierde y el equipo duerme sin atenderlo */
volatile bool hay_trabajo; /* la ISR la levanta */
for (;;) {
if (!hay_trabajo) {
/* <--- aquí puede llegar la interrupción: la ISR corre, levanta la bandera... */
__WFI(); /* ...y el equipo se duerme igual, esperando una interrupción que ya pasó */
}
if (hay_trabajo) { hay_trabajo = false; procesar(); }
}
El fallo es de una gravedad desproporcionada: ocurre una vez cada miles de ciclos, solo si la interrupción llega en una ventana de unos pocos ciclos de reloj, y se manifiesta como un equipo que «se queda dormido» hasta el siguiente evento (a veces, para siempre). Es el tipo de error que nunca aparece en la mesa y siempre aparece en el campo. La solución aprovecha una propiedad de los Cortex-M: una interrupción pendiente despierta al procesador de un WFI aunque las interrupciones estén enmascaradas con PRIMASK (la ISR no se ejecuta hasta que se las habilita, pero el procesador sí sale del sueño):
/* BIEN: se enmascaran las interrupciones antes de comprobar; una que llegue queda pendiente y despierta el WFI */
for (;;) {
__disable_irq();
if (!hay_trabajo) {
__WFI(); /* si una interrupción quedó pendiente, WFI vuelve de inmediato */
}
__enable_irq(); /* ahora se ejecuta la ISR pendiente, que levanta la bandera */
if (hay_trabajo) { hay_trabajo = false; procesar(); } /* (en un diseño real, los datos viajan en una cola: tema 16) */
}
La Parte 4 del programa de Python enumera las tres posiciones en que puede llegar la interrupción y muestra que el patrón ingenuo pierde el despertar en una de ellas y el protegido, en ninguna. En AVR, la misma idea se expresa con el orden sleep_enable(); sei(); sleep_cpu();: la instrucción sei hace efecto después de la que le sigue, de modo que sleep_cpu() siempre se ejecuta antes de que corra cualquier ISR pendiente.
03 · Otros nombres para lo mismo
Los modos en cada familia
Cada fabricante bautiza y organiza sus modos a su manera, pero casi todos caben en tres categorías. Conviene reconocer la equivalencia para poder leer cualquier hoja de datos:
| Categoría | STM32 (F1) | AVR (ATmega328P) | ESP32 | nRF52 | RP2040 |
|---|---|---|---|---|---|
| Ligero: se detiene el núcleo; los periféricos siguen | Sleep | Idle | Modem-sleep, light-sleep | System ON, en reposo | Sleep (WFI con relojes habilitados) |
| Profundo con retención: relojes rápidos apagados, RAM conservada | Stop | Power-down, Power-save, Standby | Light-sleep profundo; deep-sleep con memoria RTC | System ON con relojes lentos | Sleep con relojes apagados selectivamente |
| Apagado: casi todo apagado, vuelve como un reinicio | Standby | (no hay; Power-down es lo más profundo) | Deep-sleep, hibernación | System OFF | Dormant |
Algunos detalles de familia que vale la pena conocer, porque determinan el diseño:
- STM32 (F1). Sleep detiene el núcleo y deja todo lo demás. Stop detiene todos los relojes del dominio de 1,2 V y conserva la SRAM y los registros; sale con una interrupción externa (EXTI) o la alarma del RTC, y al salir el reloj del sistema vuelve al oscilador interno, de modo que hay que reconfigurar el PLL. Standby apaga el dominio de 1,2 V: se pierde la SRAM (salvo unos pocos registros de respaldo), y se sale por el pin
WKUP, la alarma del RTC o el watchdog independiente, con un arranque equivalente a un reinicio. - AVR. Seis modos que se eligen con
set_sleep_mode()enavr/sleep.h. En Power-down, todos los osciladores se detienen y solo despiertan las interrupciones externas, el cambio de pin, el reconocimiento de dirección del bus TWI y el watchdog. La RAM y los registros se conservan, y el despertar necesita esperar el tiempo de arranque del oscilador. - ESP32. En deep sleep se apagan los núcleos y casi toda la memoria; se conserva una pequeña memoria RTC (unos 8 KB) y puede quedar activo un coprocesador de ultra bajo consumo (ULP) que vigila sensores sin despertar al resto. Despierta por temporizador, pines o el ULP. La hibernación apaga también la memoria RTC rápida.
- nRF52. No hay un modo «sleep» explícito: System ON con el núcleo detenido por
WFEbaja solo al consumo mínimo que permiten los periféricos activos (los relojes se encienden bajo demanda). System OFF apaga todo menos la configuración de despertar (por GPIO o NFC), con una retención opcional de bloques de RAM. - RP2040. Sleep con
WFIy relojes habilitados selectivamente; Dormant detiene los osciladores y se despierta por un evento en un pin.
Lo que cambia es el vocabulario; la estructura se repite. Frente a un chip nuevo, las cuatro preguntas son: ¿cuántos modos tiene?, ¿qué se conserva en cada uno?, ¿qué lo despierta? y ¿cuánto tarda y consume al despertar?
04 · El precio de cada escalón
Qué se conserva y qué se pierde
Lo primero que se verifica al elegir un modo es qué sobrevive. Conviene pensarlo en capas, de la más externa a la más profunda:
| Elemento | Ligero (sleep) | Profundo con retención (stop) | Apagado (standby) |
|---|---|---|---|
| Registros del núcleo y la pila | Sí | Sí | No: el programa arranca desde el reset |
| SRAM | Sí | Sí (en general) | No, salvo una pequeña zona de respaldo |
| Registros de los periféricos | Sí | Sí (en general; la configuración persiste, aunque el periférico no funcione) | No: hay que reconfigurar todo |
| Estado de los pines | Sí | Sí: las salidas mantienen su nivel | Suelen pasar a alta impedancia (con excepciones configurables) |
| Relojes rápidos y PLL | Siguen | Se apagan: al salir hay que reiniciarlos | Se apagan |
| Periféricos que necesitan el reloj rápido (UART, SPI, ADC, temporizadores comunes) | Siguen funcionando | Se detienen | Se detienen |
| Reloj de tiempo real (RTC) y temporizador de bajo consumo | Sigue | Sigue | Sigue (si se lo alimenta) |
| La flash | Sí (es no volátil) | Sí | Sí |
Dos consecuencias que condicionan el diseño:
- Despertar de un modo apagado es arrancar de nuevo. El programa vuelve a empezar en el vector de reset (tema 8): se limpian las variables, se reinicializa el hardware (tema 9) y el programa no «recuerda» dónde estaba. Si hay que continuar de forma coherente, los datos necesarios se guardan antes de dormir en la zona de respaldo (registros de respaldo del RTC, memoria RTC del ESP32) o en la flash (tema 40), y al arrancar se averigua el motivo: la causa del reinicio (tema 37) distingue un despertar de un encendido. La técnica de
.noinitdel tema 29 no sirve: la SRAM se perdió. - Despertar de un stop obliga a restaurar el reloj. Sale con el oscilador interno lento: si el programa asumía la frecuencia del PLL, los temporizadores, la UART y el tick del sistema quedan desajustados hasta reconfigurarlos. Es un error frecuente: el equipo despierta y «anda raro» (la comunicación manda basura) porque la UART sigue configurada para una frecuencia que ya no es.
Y una pérdida más sutil: los periféricos que se apagan dejan de hacer lo que hacían. Una conversión del ADC en curso, una transmisión por UART a medias, una escritura en flash: si el procesador entra en stop en ese momento, quedan truncadas. Antes de dormir hay que asegurarse de que cada actividad terminó (por ejemplo, esperar a que se vacíe el registro de transmisión de la UART, no solo que se haya escrito el último byte).
05 · Lo que despierta al chip
Fuentes de despertar
Dormir tiene sentido solo si algo puede despertar al chip, y las fuentes disponibles dependen de la profundidad: cuanto más se apaga, menos fuentes quedan. Por eso la elección del modo es, en parte, la elección de qué evento tiene que poder despertar al sistema:
| Fuente | Sleep | Stop / power-down | Standby / apagado | Trampas |
|---|---|---|---|---|
| Pin externo (botón, sensor, señal de otro chip) | Sí | Sí | Solo pines designados | Rebotes; nivel contra flanco |
| RTC / alarma de tiempo | Sí | Sí | Sí | La precisión depende del cristal de 32 kHz |
| Temporizador de bajo consumo / watchdog | Sí | Sí | El watchdog independiente, a veces | El watchdog despierta y puede reiniciar si no se lo atiende |
| Comunicación (UART, I²C, SPI) | Sí | Solo algunos chips (coincidencia de dirección I²C, bit de inicio de la UART) | No | El primer dato puede perderse por la latencia |
| Comparador analógico / vigilancia del ADC | Sí | En chips con periféricos de bajo consumo | No | Consume una corriente propia mientras vigila |
| Radio (paquete recibido) | Sí | En chips con radio integrada | A veces | Despertar por cada paquete ajeno gasta batería |
| USB, depurador | Sí | Algunos | No | Un depurador conectado puede cambiar el consumo (apartado 8) |
Algunos puntos que hay que decidir al configurar un despertar, y en los que se cometen casi todos los errores:
- Flanco o nivel. Una fuente por flanco despierta una vez por cambio de la señal. Una fuente por nivel sigue pendiente mientras el nivel se mantenga: si el botón sigue apretado, el procesador se despierta, atiende, vuelve a dormir y se despierta de nuevo al instante, en un bucle que gasta toda la batería. En un AVR, el modo Power-down solo admite un despertar por nivel en las interrupciones externas. Se resuelve con el flanco (en AVR, con la interrupción por cambio de pin), o enmascarando la fuente hasta que el nivel vuelva a su estado de reposo.
- Rebotes. Un pulsador que rebota genera varios flancos en milisegundos, y cada uno despierta al chip. Se atiende el primero y se ignoran los siguientes durante un periodo (tema 24 trató el antirrebote); no conviene dormir mientras el rebote no terminó.
- Limpiar la bandera. La bandera que quedó marcada por el despertar hay que borrarla antes de volver a dormir. Si no, el
WFIvuelve de inmediato porque «ya hay un evento pendiente», y el sistema no duerme nunca (el síntoma es un consumo alto con el procesador en un bucle de «dormir y despertar»). - Saber quién despertó. Con varias fuentes, la rutina que corre al despertar debe leer qué bandera está marcada. En un modo apagado, el registro de causa de reinicio (tema 37) y las banderas de despertar del controlador de energía lo dicen.
- El primer dato. Una fuente de comunicación que despierta al chip suele perder el primer byte, porque el procesador tarda en estar listo. A 115 200 baudios un byte dura unos 87 µs (tema 2); si el despertar tarda 200 µs, se pierden más de dos bytes. La solución es un protocolo con un preámbulo (un byte de despertar seguido de una pausa) o un pin de despertar dedicado.
06 · Cuánto cuesta despertar
Latencia y amortización del despertar
Dormir más profundo gasta menos mientras se duerme, pero entrar y salir cuesta tiempo y energía: hay que guardar el estado, reiniciar los osciladores (un cristal puede tardar milisegundos en estabilizarse), reencender el regulador, reconfigurar los periféricos, y durante todo ese tiempo el chip consume tanto como activo (o más, por la corriente de arranque de los osciladores). Para esperas cortas, ese costo supera al ahorro, y el modo profundo gasta más que uno ligero.
Con un modelo simple, la carga que se gasta en una espera de duración T en un modo m es:
| Magnitud | Fórmula |
|---|---|
| Carga de la espera en el modo m | Qm(T) = Iactiva · tm + Im · (T − tm), válida si T ≥ tm |
| Punto de equilibrio entre un modo profundo (d) y uno somero (s) | T* = [ Iactiva · (td − ts) − Id · td + Is · ts ] / (Is − Id) |
Acá tm es el tiempo de transición (entrar más salir) durante el cual se consume la corriente activa, e Im la corriente del modo. La Parte 1 del programa de Python aplica el modelo a los valores didácticos de la figura y arma una tabla con la carga de cada modo para esperas de distinta duración:
| Espera | Sleep | Stop | Standby | Mejor |
|---|---|---|---|---|
| 100 µs | 0,15 µC | no llega | no llega | Sleep |
| 1 ms | 1,50 µC | 1,02 µC | no llega | Stop |
| 10 ms | 15 µC | 1,20 µC | 30 µC | Stop |
| 100 ms | 150 µC | 3,00 µC | 30 µC | Stop |
| 1 s | 1500 µC | 21 µC | 32 µC | Stop |
| 1 min | 90 000 µC | 1201 µC | 150 µC | Standby |
| 1 h | 5 400 000 µC | 72 001 µC | 7230 µC | Standby |
Dos lecturas. Primera: el mejor modo cambia con la duración de la espera, y no hay uno que gane siempre. Con 100 µs, ni siquiera da tiempo a entrar en stop; con 10 ms, stop gana por mucho; con un minuto, standby, a pesar de su despertar de 6 ms. Segunda: los puntos de equilibrio (Parte 2): stop gasta menos que sleep a partir de esperas de unos 0,7 ms; standby gasta menos que sleep a partir de unos 20 ms; y standby gasta menos que stop recién a partir de 1611 ms, o sea 1,6 s. Para un sensor que despierta cada 10 s, standby es mejor; para uno que revisa un pulsador cada 500 ms, conviene stop, que además conserva la RAM.
Y el efecto sobre un dispositivo real (Parte 3): un nodo que trabaja 0,12 mA·s cada 10 s tendría, con dos pilas AA y la autodescarga del tema 33:
| Modo | Corriente media | Autonomía |
|---|---|---|
Sleep (WFI) | 1515,30 µA | 0,14 años (unos 51 días) |
| Stop | 37,77 µA | 5,66 años |
| Standby (pierde la RAM) | 22,70 µA | 9,42 años |
La diferencia entre dormir «ligero» y dormir «profundo» es de dos órdenes de magnitud en la autonomía: un nodo con WFI a secas dura semanas; con standby, casi diez años. Y entre stop y standby hay una diferencia menor pero real, a costa de perder la RAM. Cuando el modo más profundo no sirve (porque hay que conservar la RAM, o porque la latencia del despertar es inaceptable), se elige el más profundo que sí sirve, y se compensa con otras técnicas del tema 35.
07 · De la teoría al código
La secuencia de entrada y salida
Dormir bien es un procedimiento con pasos en un orden fijo. Los saltearse deja consumos ocultos o despertares perdidos. La secuencia de entrada:
- Terminar lo que está en curso: una transmisión por UART (esperar el vaciado, no solo la escritura), una conversión del ADC, una escritura en flash.
- Apagar o dejar en reposo los periféricos que no deben funcionar durante el sueño: deshabilitar el reloj (tema 9) del ADC, de las comunicaciones y de los temporizadores que no despiertan al chip.
- Poner los pines en un estado que no consuma: las salidas en el nivel que no excita nada, las entradas con una polarización definida (nunca flotantes; tema 35).
- Configurar y habilitar las fuentes de despertar y borrar sus banderas.
- Suspender el tick del sistema si no es una fuente deseada (el SysTick despertaría al chip cada milisegundo).
- Comprobar atómicamente que no hay trabajo pendiente y dormir (el patrón con las interrupciones enmascaradas del apartado 2).
Y la de salida, que depende del modo: en un sleep ligero basta con volver a lo que se hacía; en un stop hay que restaurar los relojes y los periféricos que dependían de ellos; en un standby se vuelve a arrancar desde cero y el programa se pregunta primero «¿vengo de un despertar?». Dos ejemplos concretos, para un STM32F103 con la HAL de ST y para un AVR:
/* STM32F103 (HAL): stop y standby */
void dormir_stop(void) {
HAL_SuspendTick(); /* el SysTick despertaría al chip cada milisegundo */
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
/* ...aquí se vuelve cuando llega una interrupción EXTI o la alarma del RTC... */
SystemClock_Config(); /* tras stop el reloj es el oscilador interno: reconfigurar el PLL */
HAL_ResumeTick();
}
void dormir_standby(void) {
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1); /* el pin WKUP (PA0) despierta al chip */
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); /* borrar la bandera del despertar anterior */
HAL_PWR_EnterSTANDBYMode(); /* no vuelve: al despertar se ejecuta Reset_Handler */
}
/* Al principio de main(): ¿es un despertar de standby o un encendido? */
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB)) {
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB); /* vengo de standby: restaurar lo guardado en los registros de respaldo */
}
/* AVR (avr-libc): power-down con despertar por la interrupción externa INT0 */
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <avr/power.h>
volatile uint8_t evento;
ISR(INT0_vect) { evento = 1; }
void dormir_hasta_evento(void) {
power_adc_disable(); /* el ADC no se necesita durante el sueño */
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
cli();
if (!evento) {
sleep_enable();
sleep_bod_disable(); /* el detector de baja tensión consume en reposo (si el chip lo permite) */
sei(); /* el 'sei' hace efecto DESPUÉS de la siguiente instrucción... */
sleep_cpu(); /* ...así que esta siempre se ejecuta antes de cualquier ISR pendiente */
sleep_disable();
}
sei();
power_adc_enable();
}
La estructura es la misma en las dos: se prepara, se comprueba sin carrera, se duerme, y al volver se restaura lo que el modo hubiera apagado. Lo que cambia es el vocabulario del fabricante, y es otra razón para encapsular el sueño en un módulo propio (tema 10): energia_dormir(MODO_PROFUNDO), que el resto del firmware llama sin saber qué registros se tocan.
08 · Comprobar que funciona
Verificación y diagnóstico
| Qué verificar | Cómo | Resultado esperado |
|---|---|---|
| Que el equipo realmente duerme | Medir la corriente de la placa durante la espera (tema 36), con el depurador desconectado | Cercana a la de la hoja de datos más los componentes externos; no la de un núcleo activo |
| Que cada fuente de despertar funciona | Provocar cada una (botón, alarma, mensaje) con el equipo dormido, y repetirlo muchas veces | Despierta siempre, y atiende el evento una sola vez |
| Que no hay despertares perdidos | Provocar eventos en ráfagas y con tiempos al azar mientras el equipo entra y sale del sueño | Ninguno se pierde; el equipo nunca queda «dormido» con trabajo pendiente |
| Que lo que se conserva, se conserva | Guardar un patrón en la RAM antes de dormir (en los modos con retención) y comprobarlo al despertar | El patrón está intacto; en standby, se verifica en cambio que lo guardado en la zona de respaldo sobrevive |
| Que los relojes se restauran | Transmitir por la UART o generar una señal con un temporizador justo después de despertar | La velocidad y la frecuencia son las de antes de dormir |
| La latencia del despertar | Medir con un pin y un analizador (tema 18) desde el evento hasta la primera instrucción útil | Dentro del plazo del requisito |
| Que el sueño se amortiza | Medir la corriente media de un ciclo completo, con el modo elegido y con uno más ligero | El modo elegido gasta menos en el ciclo real |
| Síntoma | Causa probable | Qué hacer |
|---|---|---|
El WFI vuelve de inmediato y el equipo nunca duerme | Una interrupción o bandera queda pendiente (sin borrar), o el SysTick despierta cada milisegundo | Borrar las banderas antes de dormir; suspender el tick; revisar qué interrupción despierta |
| El consumo dormido es mucho mayor que el de la hoja de datos | Pines flotantes, un periférico habilitado, un consumidor externo, o un depurador conectado | Medir sin el depurador; revisar los pines y los relojes habilitados; desconectar componentes de a uno (tema 33) |
| El equipo «se queda dormido» y no atiende un evento | La carrera entre comprobar y dormir | Enmascarar las interrupciones antes de comprobar; en AVR, sei() seguido de sleep_cpu() |
| Despierta una y otra vez sin que pase nada | Una fuente por nivel que sigue activa; rebotes | Pasar a flanco; enmascarar hasta que la señal vuelva al reposo |
| Después de despertar, la comunicación manda basura | No se restauraron los relojes tras el stop: la UART tiene otro divisor | Reconfigurar el reloj del sistema y luego los periféricos |
| Se pierden los primeros bytes de un mensaje | La latencia del despertar es mayor que el tiempo del primer byte | Preámbulo en el protocolo; un pin de despertar dedicado; un chip con despertar por UART |
| Tras despertar de standby, las variables tienen valores iniciales | Es lo esperado: standby es un reinicio | Guardar lo necesario en la zona de respaldo o en flash; arrancar preguntando la causa del despertar |
| Un botón despierta varias veces por pulsación | Rebotes | Antirrebote; ignorar los despertares por un período corto |
Un detalle que hace perder días: un depurador conectado cambia el consumo. Para poder depurar, los núcleos Cortex-M mantienen activos parte de sus relojes y del dominio de depuración aun en sleep y stop (por defecto, el chip no «duerme del todo» con la sonda enchufada), y la corriente medida puede ser de cientos de µA a mA en lugar de los pocos µA esperados. Siempre se mide el consumo con el depurador desconectado, y reiniciando el equipo (cortando y volviendo a dar la alimentación) para que el estado de depuración no quede latente.
09 · Analogía con programación
Probar el modelo en Python
El programa tiene cuatro partes y trabaja con valores didácticos de un Cortex-M (5 mA activo, 1,5 mA en sleep, 20 µA en stop y 2 µA en standby, con tiempos de transición de 1 µs, 200 µs y 6 ms): la carga de cada modo para esperas de distinta duración, los puntos de equilibrio entre modos, el efecto en un nodo que despierta cada 10 s (con el perfil y la batería del tema 33) y una simulación de las tres posiciones en que puede llegar una interrupción respecto de la secuencia «comprobar y dormir», con y sin enmascarar las interrupciones.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
# Modos de bajo consumo: cuál conviene según cuánto dura la espera, cuándo se amortiza el despertar y una carrera clásica.
# Valores DIDÁCTICOS (órdenes de magnitud típicos de un Cortex-M): los reales están en la hoja de datos del chip.
I_ACTIVO = 5.0 # mA, ejecutando
MODOS = { # nombre: (corriente mA, tiempo de transición ms, conserva RAM)
"Sleep (WFI)": (1.5, 0.001, True),
"Stop": (0.020, 0.2, True),
"Standby": (0.002, 6.0, False),
}
def carga(modo, espera_ms):
"""Carga (µC = mA*ms) de esperar 'espera_ms' en un modo: la transición se paga con la corriente activa."""
i, t, _ = MODOS[modo]
return None if espera_ms < t else I_ACTIVO * t + i * (espera_ms - t)
print("Parte 1: la mejor opción según cuánto dura la espera")
print(f" {'espera':>8} | " + " | ".join(f"{m:>16}" for m in MODOS) + " | mejor")
for etiqueta, ms in (("100 µs", 0.1), ("1 ms", 1), ("10 ms", 10), ("100 ms", 100), ("1 s", 1000), ("1 min", 60_000), ("1 h", 3_600_000)):
valores = {m: carga(m, ms) for m in MODOS}
celdas = [("no llega" if v is None else (f"{v:.2f} µC" if v < 10 else f"{v:.0f} µC")) for v in valores.values()]
mejor = min((m for m, v in valores.items() if v is not None), key=lambda m: valores[m], default="—")
print(f" {etiqueta:>8} | " + " | ".join(f"{c:>16}" for c in celdas) + f" | {mejor}")
print()
print("Parte 2: cuánto tiene que durar la espera para que un modo más profundo se amortice")
def umbral(profundo, somero):
ms = 0.0
while ms < 3_600_000:
a, b = carga(profundo, ms), carga(somero, ms)
if a is not None and b is not None and a < b: return ms
ms += 0.01 if ms < 100 else 1
return None
for profundo, somero in (("Stop", "Sleep (WFI)"), ("Standby", "Stop"), ("Standby", "Sleep (WFI)")):
u = umbral(profundo, somero)
print(f" {profundo} gasta menos que {somero} desde esperas de {u:.2f} ms")
print()
print("Parte 3: un nodo que despierta cada 10 s, trabaja 0,12 mA*s por ciclo y duerme el resto (2 AA, 1875 mAh útiles, autodescarga 5,70 µA)")
PERIODO_S, TRABAJO_MAS = 10.0, 3.0 * 0.010 + 15.0 * 0.006
for modo, (i, t_ms, ram) in MODOS.items():
espera_ms = PERIODO_S * 1000 - 16
q = TRABAJO_MAS + (I_ACTIVO * t_ms + i * (espera_ms - t_ms)) / 1000 # mA*s por ciclo
media_ua = q / PERIODO_S * 1000 + 5.70
anios = 1875 / (media_ua / 1000) / 8766
print(f" {modo:<12} corriente media {media_ua:9.2f} µA -> autonomía {anios:7.2f} años" + ("" if ram else " (pierde la RAM)"))
print()
print("Parte 4: la carrera entre «comprobar» y «dormir» (el despertar perdido)")
def ejecutar(segura, llegada):
"""llegada: cuándo ocurre la interrupción respecto de la secuencia 'comprobar bandera' -> 'WFI'."""
bandera, pendiente = False, False
if segura: pass # con las interrupciones enmascaradas, la ISR no corre hasta habilitarlas
if llegada == "antes de comprobar":
if segura: pendiente = True # queda pendiente: la ISR aún no corrió
else: bandera = True # la ISR corre al instante y levanta la bandera
decide_dormir = not bandera
if llegada == "entre comprobar y WFI":
if segura: pendiente = True
else: bandera = True # la ISR corre, pero la decisión de dormir ya estaba tomada
if not decide_dormir: return "no duerme, atiende el evento"
if llegada == "durante el sueño" or pendiente: return "se despierta y atiende el evento"
return "DUERME y el evento se pierde" if bandera else "duerme"
for llegada in ("antes de comprobar", "entre comprobar y WFI", "durante el sueño"):
print(f" {llegada:<24} sin proteger: {ejecutar(False, llegada):<36} con las interrupciones enmascaradas: {ejecutar(True, llegada)}")
Resultado, parte 1: para una espera de 100 µs solo cabe sleep (0,15 µC); con 1 ms gana stop (1,02 µC contra 1,50); entre 10 ms y 1 s sigue ganando stop (de 1,20 µC a 21 µC, contra 15 µC a 1500 µC de sleep); con 1 min gana standby (150 µC contra 1201 µC de stop y 90 000 µC de sleep) y con 1 h también (7230 µC contra 72 001 µC y 5 400 000 µC). Parte 2: stop gasta menos que sleep desde esperas de 0,68 ms (resolución de 0,01 ms; el cálculo analítico da 0,67), standby menos que stop desde 1611 ms y standby menos que sleep desde 20,02 ms. Parte 3: el nodo de 10 s tiene una corriente media de 1515,30 µA con sleep (0,14 años), 37,77 µA con stop (5,66 años) y 22,70 µA con standby (9,42 años, perdiendo la RAM). Parte 4: con el patrón ingenuo, la interrupción que llega «entre comprobar y WFI» se pierde (el equipo duerme y no atiende el evento); con las interrupciones enmascaradas, las tres posiciones terminan atendiendo el evento.
Probá cambiar MODOS para describir un chip que conozcas (con las cifras de su hoja de datos) y mirá dónde cambian los puntos de equilibrio, o bajá el tiempo de transición de stop a 20 µs para ver qué pasa con la tabla de esperas cortas. El resultado no es un número: es la forma de la decisión, que se mantiene aunque cambien las cifras.
10 · Exploración interactiva
Elegir el modo
El laboratorio usa los mismos tres modos (con los valores didácticos del Python) para una espera de duración elegida. Para cada modo calcula la carga gastada, la corriente media y cuánto ahorra frente a quedarse despierto, y marca el mejor. Además podés imponer dos restricciones reales: que se conserve la RAM, y una latencia máxima de despertar.
Qué modo conviene para una espera
Elegí una duración de espera.
Recorrido guiado
- Con la espera de 1 s, el mejor modo es Stop (21 µC contra 32 µC de Standby y 1500 µC de Sleep). Fijate en el ahorro frente a quedarse despierto: más del 99 %.
- Subí la espera a 1 minuto: ahora gana Standby (150 µC contra 1201 µC de Stop). El despertar de 6 ms que lo hacía perder con 1 s ya se amortizó.
- Bajá la espera a 100 µs: Stop y Standby aparecen atenuados («la espera es menor que su transición») y queda solo Sleep. Dormir profundo no siempre es posible.
- Volvé a 1 minuto y marcá «El programa necesita conservar la RAM»: Standby queda descartado («pierde la RAM») y el mejor pasa a ser Stop, con ocho veces la carga. Es el precio de conservar el estado.
- Con 1 minuto y sin la restricción de RAM, poné la latencia máxima en 1 ms: Standby (6 ms de despertar) queda descartado por lento y gana Stop. Un requisito de respuesta rápida puede impedir el modo que más ahorra.
- Probá 10 ms y 100 ms sin restricciones: Stop gana por mucho. Y fijate que a 10 ms Standby gasta 30 µC, mucho más que Stop con 1,20 µC: el modo más profundo no es el más económico para esperas cortas.
Desafío: ¿qué modo elegirías para un teclado que debe responder a una tecla en menos de 1 ms y conservar su estado?
Hay tres restricciones: latencia menor a 1 ms, conservar la RAM (el estado de la sesión) y que el despertar lo provoque una tecla (una fuente externa, no un temporizador). Standby queda fuera por la RAM y la latencia (6 ms). Sleep sirve pero consume demasiado si el teclado pasa horas esperando. Stop cumple todo: despierta en unos cientos de µs, conserva la RAM, y una interrupción externa por flanco (con antirrebote) lo despierta. Hay un detalle a resolver en el diseño: el modo Stop apaga el reloj rápido, de modo que la primera tecla despierta al sistema pero hay que reconfigurar los relojes antes de usar la UART o el USB, y esa demora (unos cientos de µs) puede comerse parte del plazo. Se valida midiendo el tiempo real, de la tecla a la primera acción útil (tema 18).
11 · Método de trabajo
Método: dormir bien
- Conocé los modos de tu chip y armá la tabla: nombre, consumo, qué se conserva, qué lo despierta, cuánto tarda y cuánto gasta al despertar. Está en la hoja de datos; si no está medida, se mide (tema 36).
- Mapeá las esperas de tu programa: cuánto dura cada una y qué la termina. Cada espera elige su propio modo, no el producto entero.
- Elegí el modo más profundo que se amortiza (Q = Iact·t + I·(T − t), el punto de equilibrio) y no destruye lo que se necesita (la RAM, la latencia, la fuente de despertar).
- Diseñá la fuente de despertar con cuidado: flanco o nivel, antirrebote, bandera que se borra, causa que se identifica.
- Encapsulá el sueño en un módulo de energía:
energia_dormir(modo)con la secuencia de entrada y de salida adentro, y no esparcida por el programa. - Evitá la carrera: comprobar y dormir de forma atómica, con las interrupciones enmascaradas.
- Restaurá lo que el modo apaga: relojes y periféricos tras un stop; todo desde cero tras un standby, con una verificación de «vengo de un despertar» y datos guardados en la zona de respaldo.
- Terminá lo que está en curso antes de dormir (transmisiones, conversiones, escrituras en flash).
- Medí sin el depurador y con un ciclo completo, y comprobá que el consumo cae a lo esperado. Si no cae, el equipo no está durmiendo como creés.
- Probá los despertares en estrés: ráfagas, eventos simultáneos, tiempos al azar, baja tensión. Un despertar perdido aparece solo con carga.
12 · Comprensión
Confusiones frecuentes
«Cuanto más profundo el modo, más ahorro»
Solo si la espera es lo bastante larga para amortizar el costo de entrar y salir. Con esperas cortas, un modo profundo gasta más que uno ligero, como muestran la Parte 1 y el laboratorio: standby con 10 ms gasta 30 µC, y stop, 1,2. La elección depende de la duración de la espera.
«Poner __WFI() en el bucle principal ya es bajo consumo»
Detiene el núcleo, pero deja funcionando los relojes y los periféricos: es el escalón más superficial. En el nodo de la Parte 3, el equipo con WFI a secas dura unos 51 días; con standby, casi diez años. Y si el SysTick sigue generando una interrupción cada milisegundo, el procesador despierta mil veces por segundo y casi no duerme.
«Al despertar, el programa sigue donde estaba»
Con sleep y stop, sí (los registros y la pila se conservan). Con standby o apagado, no: el programa arranca desde el reset y las variables se reinicializan. Hay que guardar lo importante antes de dormir y preguntar al arrancar cuál fue el motivo.
«Después de Stop, todo sigue configurado, así que anda igual»
Los registros se conservan, pero los relojes no: el sistema vuelve al oscilador interno, de modo que el PLL, la UART, los temporizadores y el SysTick quedan con frecuencias distintas de las que el código supone. Hay que reconfigurar el reloj después de despertar.
«El despertar por nivel es más fiable que por flanco»
Es más fiable para no perder el evento, pero es peligroso: mientras el nivel se mantenga, el procesador despierta, atiende y vuelve a dormir sin parar. Un botón apretado puede agotar la batería en horas. Para eventos puntuales se prefiere el flanco, o se enmascara la fuente hasta que la señal vuelva al reposo.
«Con el depurador conectado, el consumo medido es el real»
No: la sonda mantiene activos relojes y dominios de depuración, y el consumo puede ser decenas o cientos de veces el de un equipo sin depurador. Se mide siempre sin la sonda y tras un ciclo de alimentación.
«Una interrupción enmascarada no puede despertar al procesador»
Sí puede: en un Cortex-M, una interrupción pendiente despierta el WFI aunque PRIMASK las enmascare (la ISR se ejecuta cuando se las habilita). Es lo que hace segura la secuencia «enmascarar, comprobar, WFI, habilitar», y a la vez explica por qué un WFI con una bandera pendiente sin borrar no duerme.
«Si dormí el chip, ya no consume nada»
Consume la corriente del modo, más la de todo lo que lo rodea: el regulador, los sensores, las resistencias de polarización, los pines mal configurados. El consumo que importa es el de la placa completa (tema 33), y las fugas por pines flotantes son una causa clásica de consumos de cientos de µA en un equipo «dormido».
13 · Práctica guiada
Ejercicios
1. Punto de equilibrio
Un modo A consume 0,5 mA y tiene una transición de 0,01 ms. Un modo B consume 0,01 mA y tiene una transición de 1 ms. La corriente activa es de 4 mA. ¿A partir de qué duración de espera conviene el modo B?
Ver solución
Con la fórmula T* = [Iact·(td − ts) − Id·td + Is·ts] / (Is − Id), donde B es el profundo (d) y A el somero (s): numerador = 4 × (1 − 0,01) − 0,01 × 1 + 0,5 × 0,01 = 3,96 − 0,01 + 0,005 = 3,955; denominador = 0,5 − 0,01 = 0,49. T* = 3,955 / 0,49 ≈ 8,07 ms. Para esperas más largas conviene B; para más cortas, A. Verificación en T = 10 ms: QA = 4 × 0,01 + 0,5 × 9,99 = 5,035 µC; QB = 4 × 1 + 0,01 × 9 = 4,09 µC, y B gana.
2. Un nodo cada minuto
Con los valores de la Parte 3 (5 mA activo; stop de 20 µA y 0,2 ms de transición; standby de 2 µA y 6 ms), un nodo despierta cada 60 s y trabaja 0,12 mA·s por ciclo. ¿Qué corriente media tiene con stop y con standby (sin autodescarga)? ¿Qué elegirías si la RAM no hace falta?
Ver solución
Stop: la espera dura 60 000 − 16 = 59 984 ms; su carga es (5 × 0,2 + 0,02 × 59 983,8) / 1000 ≈ 1,2007 mA·s. Total por ciclo: 0,12 + 1,2007 = 1,3207 mA·s, y la media es 1,3207 / 60 ≈ 22,0 µA. Standby: (5 × 6 + 0,002 × 59 978) / 1000 ≈ 0,1500 mA·s; total 0,12 + 0,15 = 0,27 mA·s; media = 4,5 µA. Con la RAM sin importancia, standby: gasta una quinta parte. Si hubiera que conservar la RAM, stop es la opción, y la diferencia se paga en autonomía (en este caso, de cinco veces menos).
3. El primer byte
Un equipo despierta de stop con una latencia de 200 µs cuando recibe un byte por UART a 115 200 baudios. ¿Cuánto dura un byte? ¿Cuántos bytes se pierden? ¿Cómo lo resolverías sin cambiar de chip?
Ver solución
Un byte con inicio, 8 datos y parada son 10 bits: 10 / 115 200 ≈ 86,8 µs. En 200 µs pasan 200 / 86,8 ≈ 2,3 bytes: se pierden dos o tres (y el que llega durante el despertar puede quedar a medias). Soluciones sin cambiar de chip: que el protocolo empiece con un preámbulo (un byte de despertar y una pausa mayor que la latencia antes de enviar el mensaje); un pin de despertar aparte (que el emisor active unos cientos de µs antes de transmitir); o una velocidad menor durante el despertar. Es una exigencia del protocolo, no del firmware solo: hay que acordarla con quien envía.
4. La carrera
Un programa hace if (!listo) __WFI(); con las interrupciones habilitadas, y a veces «se queda dormido» hasta el siguiente evento. Explicá la causa y corregilo.
Ver solución
Si la interrupción que levanta listo llega entre el if y el WFI, la ISR se ejecuta y marca la bandera, pero el programa ya decidió dormir y ejecuta el WFI igual: duerme esperando una interrupción que ya ocurrió. La ventana es de unos pocos ciclos, por eso aparece rara vez. Corrección: __disable_irq(); if (!listo) __WFI(); __enable_irq();. Con PRIMASK en 1, una interrupción que llegue en la ventana queda pendiente y hace que el WFI vuelva de inmediato; al habilitar las interrupciones, la ISR se ejecuta y el programa ve la bandera. En AVR: sleep_enable(); sei(); sleep_cpu();.
5. Elegir el modo
Elegí un modo (sleep, stop o standby) para cada caso: (a) una pausa de 300 µs entre dos muestras de un ADC; (b) un teclado que debe responder en menos de 1 ms y mantener su estado; (c) un sensor que mide cada 15 minutos y guarda sus datos en flash; (d) un juguete que se despierta con un botón después de meses en un cajón.
Ver solución
(a) Sleep: 300 µs es menos que la transición de un stop razonable, y el ADC (que necesita su reloj) sigue funcionando. (b) Stop: despierta en cientos de µs, conserva la RAM y una interrupción externa lo despierta. (c) Standby: esperas de 15 minutos amortizan largamente su despertar de milisegundos; los datos que importan están en flash; el RTC puede despertarlo por alarma. (d) Standby (o el apagado más profundo del chip) con un pin de despertar: el consumo de unos µA o menos es lo que permite la autonomía de meses o años, y el despertar lento es irrelevante para un botón.
6. Consumo con la sonda
Un equipo en stop mide 20 µA con una prueba aislada, pero con el programador conectado mide 3 mA. ¿Qué pasa? ¿Cómo medís el consumo real?
Ver solución
Con el depurador conectado, el núcleo mantiene activos parte de los relojes y el dominio de depuración aunque el programa esté en un modo profundo, para poder seguir comunicándose con la sonda; por eso el consumo se multiplica por muchas veces. El consumo real se mide sin la sonda: se programa el equipo, se desconecta el depurador, se corta y se vuelve a dar la alimentación (para que no quede latente el estado de depuración) y se mide la corriente con un amperímetro de µA o un analizador de consumo en serie con la alimentación, esperando que el equipo se estabilice en el modo de sueño. Si se necesita depurar durante el sueño, se configuran los bits del controlador de depuración que permiten (o no) que los relojes se detengan en cada modo; pero esa configuración no vale para medir.
14 · Síntesis
Ideas para recordar
- Un microcontrolador tiene una escalera de modos: activo, sleep (núcleo detenido), stop o power-down (relojes rápidos apagados, RAM conservada) y standby o apagado (casi todo apagado, vuelve como un reinicio). Cada escalón baja el consumo y sube el costo de despertar.
- En un Cortex-M se duerme con
WFIoWFE; el bitSLEEPDEEPdeSCB->SCRpide un sueño profundo (cuyo significado define el fabricante) ySLEEPONEXITpermite programas que viven solo de interrupciones. - La carrera entre comprobar y dormir pierde despertares: se evita enmascarando las interrupciones antes de comprobar. Una interrupción pendiente despierta el
WFIaun enmascarada. En AVR:sei()seguido desleep_cpu(). - Hay que saber qué se conserva: en stop, la RAM y los registros pero no los relojes (hay que reconfigurarlos); en standby, nada (salvo la zona de respaldo) y el programa arranca de nuevo. Antes de dormir se termina lo que está en curso.
- Las fuentes de despertar se reducen al profundizar: pin, RTC, temporizador de bajo consumo, comunicación. Hay que cuidar flanco contra nivel, rebotes, la bandera pendiente, la identificación de la causa y la pérdida del primer dato.
- Entrar y salir cuesta tiempo y energía: Qm(T) = Iact·tm + Im·(T − tm). El mejor modo cambia con la duración de la espera: con valores típicos, stop gana de 1 ms a 1 s, y standby de 1,6 s en adelante. El más profundo no es siempre el más económico.
- El efecto es enorme: el nodo de 10 s dura 0,14 años con
WFI, 5,7 con stop y 9,4 con standby. Dormir bien es la palanca individual que más autonomía da. - La secuencia es un procedimiento: terminar lo pendiente, apagar periféricos, poner los pines en reposo, configurar el despertar, suspender el tick, comprobar sin carrera, dormir, y al volver restaurar. Se encapsula en un módulo de energía y se mide sin el depurador conectado.
¿Listo para el tema siguiente?
Antes de avanzar, tomá la hoja de datos de un microcontrolador y armá su tabla de modos: nombre, consumo, qué se conserva, fuentes de despertar, tiempo y energía de despertar. Después elegí un firmware tuyo y listá sus esperas: cuánto dura cada una y qué la termina. Para cada una, calculá con el modelo del tema qué modo la amortiza. Si alguna espera larga se resuelve con un bucle de WFI ligero, ahí está la mejora más barata de todo el producto.
En el próximo tema pasamos de las piezas al diseño: firmware de bajo consumo. Cómo se estructura un programa para dormir la mayor parte del tiempo: diseño por eventos, ciclo de trabajo, tick suspendido, periféricos apagados y relojes lentos.