← Índice del curso

Sistemas Embebidos · Tema 34 · Bloque 7

Modos de bajo consumo

En el tema anterior vimos que un dispositivo a batería pasa casi todo su tiempo esperando, y que la autonomía depende de cuánto consume mientras espera. La herramienta que más reduce ese consumo está en el propio microcontrolador: los modos de bajo consumo, estados en los que el procesador, los relojes y partes enteras del chip se apagan hasta que algo los despierta. Este tema explica qué modos existen, cuánto se pierde al entrar en cada uno, qué puede despertar al chip, cuánto cuesta despertar y cómo se escribe el código para dormir sin perder eventos.

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:

La escalera de estados de bajo consumo Cuatro barras horizontales en escala logarítmica, de arriba abajo: activo con unos 5 miliamperios, sleep con 1,5 miliamperios, stop con 20 microamperios y standby con 2 microamperios. A la derecha de cada barra, qué se mantiene encendido y cuánto tarda en despertar: inmediato, de microsegundos, de cientos de microsegundos, y de milisegundos con pérdida de la RAM. ACTIVO · 5 mASLEEP · 1,5 mASTOP · 20 µASTANDBY · 2 µA el núcleo ejecuta; todo el reloj activodespertar: no hay núcleo detenido; periféricos y RAM siguendespertar: unos ciclos de reloj (µs) relojes rápidos apagados; la RAM se conservadespertar: cientos de µs (reloj y regulador) casi todo apagado; la RAM se pierdedespertar: ms, equivale a un reinicio Escala logarítmica: cada escalón de la barra es una división del consumo por un factor de entre 3 y 75.

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:

Instrucciones y bits de control del sueño en un Cortex-M.
ElementoQué 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->SCRLe 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 SLEEPONEXITEl 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 SEVONPENDUna 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:

Equivalencia aproximada entre los modos de bajo consumo de distintas familias. Los detalles (qué se conserva, cuánto consume) varían por chip y están en su hoja de datos.
CategoríaSTM32 (F1)AVR (ATmega328P)ESP32nRF52RP2040
Ligero: se detiene el núcleo; los periféricos siguenSleepIdleModem-sleep, light-sleepSystem ON, en reposoSleep (WFI con relojes habilitados)
Profundo con retención: relojes rápidos apagados, RAM conservadaStopPower-down, Power-save, StandbyLight-sleep profundo; deep-sleep con memoria RTCSystem ON con relojes lentosSleep con relojes apagados selectivamente
Apagado: casi todo apagado, vuelve como un reinicioStandby(no hay; Power-down es lo más profundo)Deep-sleep, hibernaciónSystem OFFDormant

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() en avr/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 WFE baja 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 WFI y 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:

Qué se conserva en cada nivel de profundidad (orientativo: depende del chip).
ElementoLigero (sleep)Profundo con retención (stop)Apagado (standby)
Registros del núcleo y la pilaSíSíNo: el programa arranca desde el reset
SRAMSíSí (en general)No, salvo una pequeña zona de respaldo
Registros de los periféricosSíSí (en general; la configuración persiste, aunque el periférico no funcione)No: hay que reconfigurar todo
Estado de los pinesSíSí: las salidas mantienen su nivelSuelen pasar a alta impedancia (con excepciones configurables)
Relojes rápidos y PLLSiguenSe apagan: al salir hay que reiniciarlosSe apagan
Periféricos que necesitan el reloj rápido (UART, SPI, ADC, temporizadores comunes)Siguen funcionandoSe detienenSe detienen
Reloj de tiempo real (RTC) y temporizador de bajo consumoSigueSigueSigue (si se lo alimenta)
La flashSí (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 .noinit del 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:

Fuentes de despertar típicas y en qué modos están disponibles (orientativo).
FuenteSleepStop / power-downStandby / apagadoTrampas
Pin externo (botón, sensor, señal de otro chip)SíSíSolo pines designadosRebotes; nivel contra flanco
RTC / alarma de tiempoSíSíSíLa precisión depende del cristal de 32 kHz
Temporizador de bajo consumo / watchdogSíSíEl watchdog independiente, a vecesEl 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)NoEl primer dato puede perderse por la latencia
Comparador analógico / vigilancia del ADCSíEn chips con periféricos de bajo consumoNoConsume una corriente propia mientras vigila
Radio (paquete recibido)SíEn chips con radio integradaA vecesDespertar por cada paquete ajeno gasta batería
USB, depuradorSíAlgunosNoUn 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 WFI vuelve 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:

Modelo de la carga de una espera en un modo de bajo consumo.
MagnitudFórmula
Carga de la espera en el modo mQm(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:

Carga gastada en una espera según el modo (resultado de la Parte 1; valores didácticos).
EsperaSleepStopStandbyMejor
100 µs0,15 µCno llegano llegaSleep
1 ms1,50 µC1,02 µCno llegaStop
10 ms15 µC1,20 µC30 µCStop
100 ms150 µC3,00 µC30 µCStop
1 s1500 µC21 µC32 µCStop
1 min90 000 µC1201 µC150 µCStandby
1 h5 400 000 µC72 001 µC7230 µCStandby

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:

Un nodo que despierta cada 10 s, según el modo en que espera (resultado de la Parte 3).
ModoCorriente mediaAutonomía
Sleep (WFI)1515,30 µA0,14 años (unos 51 días)
Stop37,77 µA5,66 años
Standby (pierde la RAM)22,70 µA9,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:

  1. 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.
  2. 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.
  3. 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).
  4. Configurar y habilitar las fuentes de despertar y borrar sus banderas.
  5. Suspender el tick del sistema si no es una fuente deseada (el SysTick despertaría al chip cada milisegundo).
  6. 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

Comprobaciones al implementar modos de bajo consumo.
Qué verificarCómoResultado esperado
Que el equipo realmente duermeMedir la corriente de la placa durante la espera (tema 36), con el depurador desconectadoCercana a la de la hoja de datos más los componentes externos; no la de un núcleo activo
Que cada fuente de despertar funcionaProvocar cada una (botón, alarma, mensaje) con el equipo dormido, y repetirlo muchas vecesDespierta siempre, y atiende el evento una sola vez
Que no hay despertares perdidosProvocar eventos en ráfagas y con tiempos al azar mientras el equipo entra y sale del sueñoNinguno se pierde; el equipo nunca queda «dormido» con trabajo pendiente
Que lo que se conserva, se conservaGuardar un patrón en la RAM antes de dormir (en los modos con retención) y comprobarlo al despertarEl patrón está intacto; en standby, se verifica en cambio que lo guardado en la zona de respaldo sobrevive
Que los relojes se restauranTransmitir por la UART o generar una señal con un temporizador justo después de despertarLa velocidad y la frecuencia son las de antes de dormir
La latencia del despertarMedir con un pin y un analizador (tema 18) desde el evento hasta la primera instrucción útilDentro del plazo del requisito
Que el sueño se amortizaMedir la corriente media de un ciclo completo, con el modo elegido y con uno más ligeroEl modo elegido gasta menos en el ciclo real
Síntomas frecuentes y qué revisar.
SíntomaCausa probableQué hacer
El WFI vuelve de inmediato y el equipo nunca duermeUna interrupción o bandera queda pendiente (sin borrar), o el SysTick despierta cada milisegundoBorrar 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 datosPines flotantes, un periférico habilitado, un consumidor externo, o un depurador conectadoMedir 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 eventoLa carrera entre comprobar y dormirEnmascarar las interrupciones antes de comprobar; en AVR, sei() seguido de sleep_cpu()
Despierta una y otra vez sin que pase nadaUna fuente por nivel que sigue activa; rebotesPasar a flanco; enmascarar hasta que la señal vuelva al reposo
Después de despertar, la comunicación manda basuraNo se restauraron los relojes tras el stop: la UART tiene otro divisorReconfigurar el reloj del sistema y luego los periféricos
Se pierden los primeros bytes de un mensajeLa latencia del despertar es mayor que el tiempo del primer bytePreámbulo en el protocolo; un pin de despertar dedicado; un chip con despertar por UART
Tras despertar de standby, las variables tienen valores inicialesEs lo esperado: standby es un reinicioGuardar lo necesario en la zona de respaldo o en flash; arrancar preguntando la causa del despertar
Un botón despierta varias veces por pulsaciónRebotesAntirrebote; 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

Modo elegido—
Quedarse despierto (5 mA)—

Elegí una duración de espera.

Recorrido guiado

  1. 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 %.
  2. 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ó.
  3. 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.
  4. 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.
  5. 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.
  6. 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

  1. 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).
  2. 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.
  3. 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).
  4. Diseñá la fuente de despertar con cuidado: flanco o nivel, antirrebote, bandera que se borra, causa que se identifica.
  5. 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.
  6. Evitá la carrera: comprobar y dormir de forma atómica, con las interrupciones enmascaradas.
  7. 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.
  8. Terminá lo que está en curso antes de dormir (transmisiones, conversiones, escrituras en flash).
  9. 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.
  10. 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 WFI o WFE; el bit SLEEPDEEP de SCB->SCR pide un sueño profundo (cuyo significado define el fabricante) y SLEEPONEXIT permite 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 WFI aun enmascarada. En AVR: sei() seguido de sleep_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.