01 · El enemigo
Del bucle ocupado a las ráfagas
Los primeros programas de todo aprendiz de firmware tienen la misma forma: un bucle infinito que lee un botón, lee un sensor, actualiza un LED y espera un poco con un retardo. Funciona perfectamente y es lo peor que se le puede hacer a una batería: la CPU ejecuta instrucciones sin parar, aun cuando no tiene nada útil que hacer, y el consumo es el de un núcleo activo (miliamperios) todo el tiempo. El objetivo de este tema es el contrario: que la CPU esté dormida casi siempre y despierte solo para un trabajo concreto, que termina lo antes posible.
Casi todos los consumos evitables de un firmware tienen una de seis formas:
| Patrón que gasta | Por qué gasta | Reemplazo |
|---|---|---|
Retardo por conteo (for (...) ;, delay_ms() con bucle) | La CPU cuenta en vacío, al 100 %, durante todo el retardo | Un temporizador de hardware que despierte al terminar, con la CPU dormida |
| Encuesta (polling) de un botón, un pin o una bandera | La CPU pregunta una y otra vez por algo que casi nunca cambia | Una interrupción por flanco: el hardware avisa y la CPU duerme mientras tanto |
Espera activa a un periférico (while (!listo); con el ADC, la UART, el SPI) | La CPU espera con el reloj completo lo que el periférico hace solo | Interrupción de fin de operación, o WFI hasta que termine, o DMA |
| Tick periódico a 1 kHz «por si acaso» | Despierta a la CPU mil veces por segundo aunque no haya trabajo | Un sistema sin tick (apartado 4): se despierta solo cuando vence algo |
| Periféricos y sensores siempre encendidos | El ADC, el sensor y las comunicaciones consumen aunque no se usen | Encender, usar y apagar (apartado 5) |
| Trabajo de más: muestrear, calcular o transmitir más seguido que lo necesario | Cada ciclo cuesta un costo fijo de despertar, medir y transmitir | Menos ciclos: informar por excepción, agrupar (apartados 3 y 7) |
02 · La arquitectura
Diseño por eventos para poder dormir
La arquitectura que permite dormir es la orientada a eventos del tema 26, con una vuelta de tuerca: cuando la cola de eventos está vacía, el procesador duerme. Las interrupciones (de un temporizador, un pin, un periférico) publican eventos y despiertan al procesador; el bucle principal los procesa; y cuando no queda ninguno, vuelve a dormir. El bucle principal deja de ser un bucle que gira y pasa a ser un despachador que se detiene en el WFI:
typedef enum { EV_LECTURA_VENCIDA, EV_BOTON, EV_RX_LISTO, EV_FIN_TX } Evento;
/* La ISR hace lo mínimo: publica un evento (tema 16 y 26) y vuelve */
void EXTI0_IRQHandler(void) { borrar_bandera_exti0(); cola_poner(EV_BOTON); }
void RTC_Alarm_IRQHandler(void) { borrar_bandera_rtc(); cola_poner(EV_LECTURA_VENCIDA); }
int main(void) {
iniciar_hardware();
iniciar_modulos();
for (;;) {
Evento e;
__disable_irq(); /* comprobar y dormir sin carrera (tema 34) */
bool vacia = !cola_sacar(&e);
if (vacia) energia_esperar(); /* WFI (sleep o stop) con las interrupciones aún enmascaradas */
__enable_irq(); /* aquí corre la ISR pendiente, que publica el evento */
if (!vacia) despachar(e); /* el trabajo se hace FUERA de la sección crítica */
}
}
static void despachar(Evento e) {
switch (e) {
case EV_LECTURA_VENCIDA: medir_y_guardar(); programar_proxima_lectura(); break;
case EV_BOTON: atender_boton(); break;
case EV_RX_LISTO: procesar_mensaje(); break;
case EV_FIN_TX: radio_apagar(); break;
}
}
Observá cómo se aplican los apartados de los temas anteriores: la cola es la de los temas 16 y 26, la comprobación atómica es la del tema 34, y energia_esperar() es el módulo que oculta qué modo se usa. Pero hay una propiedad nueva que el diseño debe cumplir: ninguna tarea puede esperar activamente. Cada vez que el código necesita «esperar a que algo termine», la espera se convierte en un evento:
| Diseño que bloquea | Diseño por eventos |
|---|---|
adc_iniciar(); while (!adc_listo()); v = adc_leer(); | adc_iniciar(); y salir; la ISR de fin de conversión publica EV_ADC_LISTO; el manejador del evento lee el valor |
uart_enviar(buf, n); while (!uart_vacia()); | Envío por interrupción o DMA; el fin de la transmisión publica EV_FIN_TX; recién entonces se apaga el periférico |
delay_ms(5); para esperar que un sensor se estabilice | Programar un temporizador de 5 ms y salir; su vencimiento publica el evento que sigue con la medición |
| Consultar cada 10 ms si el botón se apretó | Interrupción por flanco del pin, con antirrebote por temporizador de una sola vez (tema 24) |
El costo de este estilo es de diseño: el código de una operación larga (medir, transmitir, esperar la respuesta) se parte en varios manejadores encadenados por eventos. Es exactamente la máquina de estados del bloque 5: cada estado espera un evento, y el sistema duerme entre ellos. Un firmware de bajo consumo suele ser, en el fondo, una máquina de estados jerárquica con un WFI en el medio.
Existe una variante más extrema para programas simples que viven solo de interrupciones: con el bit SLEEPONEXIT (tema 34), el procesador vuelve a dormir automáticamente al terminar cada ISR, sin volver nunca al programa principal. Es la forma más económica (no hay bucle principal ni cambio de contexto), pero sirve solo cuando todo el trabajo cabe dentro de las interrupciones, con las limitaciones de tiempo y de datos compartidos que eso implica (tema 16).
03 · Trabajar a ráfagas
El ciclo de trabajo
Si el firmware trabaja a ráfagas, su consumo queda determinado por el ciclo de trabajo (duty cycle): la fracción del tiempo que pasa activo. Con una corriente activa Ia, una de reposo Ir y un ciclo de trabajo D = tactivo / T, la corriente media es:
| Magnitud | Fórmula | Ejemplo |
|---|---|---|
| Ciclo de trabajo | D = tactivo / T | 12 ms activos por cada 1 s: D = 1,2 % |
| Corriente media | Imedia = D · Ia + (1 − D) · Ir | 0,012 · 6 mA + 0,988 · 3 µA ≈ 75 µA |
En el ejemplo, el 96 % de la corriente media viene de la parte activa, que ocupa el 1,2 % del tiempo. La fórmula dice que hay dos formas de bajar la media, y las dos son de diseño, no de optimización de instrucciones:
- Hacer cada ráfaga más corta (menos tactivo): terminar el trabajo rápido y volver a dormir. Se logra con una frecuencia de reloj que acorte la tarea (tema 33), con menos cálculo, con hardware que trabaje solo (DMA), y evitando esperas dentro de la ráfaga.
- Hacer menos ráfagas (más T): muestrear, calcular y transmitir lo mínimo que el requisito permite. Es la palanca más poderosa: la mejor operación es la que no se hace. Duplicar el período casi duplica la autonomía cuando domina la parte activa.
Además de la duración, cada ráfaga tiene un costo fijo: despertar (tema 34), encender el sensor, arrancar y sincronizar la radio. Ese costo no depende de cuánto se haga en la ráfaga, sino de que haya una. Eso lleva a una de las técnicas de mayor rendimiento: agrupar. En lugar de transmitir cada lectura, se acumulan N lecturas y se transmiten juntas, pagando el costo fijo de la radio una vez cada N. Con un costo fijo de 60 µC por transmisión y 30 µC por lectura transmitida (valores didácticos), el costo por lectura es 60 / N + 30:
| Lecturas por transmisión (N) | Costo por lectura | Ahorro frente a N = 1 | Lo que se paga |
|---|---|---|---|
| 1 | 90 µC | — | Nada: cada dato sale al instante |
| 4 | 45 µC | 50 % | Un dato puede esperar hasta 3 períodos |
| 10 | 36 µC | 60 % | Hasta 9 períodos de demora; memoria para 10 lecturas |
| 30 | 32 µC | 64 % | Hasta 29 períodos de demora; el ahorro ya casi no crece |
El ahorro tiene un límite (el costo variable de 30 µC no se elimina) y un precio: la latencia (un dato llega tarde) y la memoria para acumularlos (tema 30). Es una decisión de requisitos: si el usuario necesita saber de inmediato cuando algo cambia, se combina con el envío por excepción (apartado 7), que envía al instante lo importante y agrupa el resto.
04 · El latido que no deja dormir
El tick y el firmware sin tick
La mayoría de los firmware con RTOS o con un planificador (temas 17 a 20) tienen un tick: una interrupción periódica, casi siempre del SysTick y casi siempre de 1 ms, que mantiene la hora del sistema y despierta al planificador. Es cómodo, y es un enemigo del bajo consumo por dos motivos distintos:
- Despierta a la CPU mil veces por segundo, aunque no haya nada que hacer. Cada despertar es breve (unos microsegundos), pero gasta energía de entrada y salida, y mil por segundo suman. Con valores didácticos (10 µs activos a 5 mA por despertar), la Parte 2 del programa de Python da 50 µA solo por el tick de 1 kHz, más que todo el consumo de un nodo bien diseñado.
- El SysTick usa el reloj del núcleo, que se apaga en los sueños profundos. Con un tick de 1 kHz el procesador no puede entrar en stop ni en standby: se queda en el escalón de sleep ligero, con una corriente cientos de veces mayor. Es la causa más común de que un firmware con RTOS «no baje de un miliamperio».
| Frecuencia del tick | Despertares por segundo | Corriente media por despertar |
|---|---|---|
| 1000 Hz | 1000 | 50 µA |
| 100 Hz | 100 | 5 µA |
| 10 Hz | 10 | 0,5 µA |
| 1 Hz | 1 | 0,05 µA |
La solución es el firmware sin tick (tickless): en lugar de despertar cada milisegundo para ver si venció algo, el sistema calcula cuándo vence lo próximo, programa un temporizador de bajo consumo (el RTC o un temporizador que sigue corriendo dormido) para ese instante, y duerme profundo hasta entonces. Al despertar, corrige la hora del sistema con el tiempo que realmente pasó. Los RTOS lo ofrecen incorporado: en FreeRTOS se activa con configUSE_TICKLESS_IDLE, y cuando la tarea inactiva detecta que no habrá trabajo por al menos configEXPECTED_IDLE_TIME_BEFORE_SLEEP ticks, llama a la macro portSUPPRESS_TICKS_AND_SLEEP(), que el puerto implementa con el temporizador de bajo consumo y el sueño del chip, y a la vuelta ajusta la cuenta de ticks con vTaskStepTick(). Los ganchos configPRE_SLEEP_PROCESSING y configPOST_SLEEP_PROCESSING permiten apagar y volver a encender relojes y periféricos alrededor del sueño.
En un firmware sin RTOS, la misma idea se programa en pocas líneas: la hora sale del contador del RTC (que corre dormido) y el planificador duerme hasta la próxima tarea que vence:
/* Un planificador cooperativo sin tick: duerme hasta la próxima tarea que vence */
uint32_t ahora_ms(void); /* lee el contador del RTC: sigue corriendo con el chip dormido */
void rtc_alarma_en(uint32_t ms); /* programa la alarma del RTC para dentro de 'ms' */
void planificador_esperar(void) {
uint32_t t = ahora_ms(), siguiente = UINT32_MAX;
for (int i = 0; i < N_TAREAS; i++) {
uint32_t resta = tareas[i].proxima - t; /* aritmética sin signo: sigue bien aunque el contador dé la vuelta */
if ((int32_t)resta < 0) resta = 0; /* ya venció */
if (resta < siguiente) siguiente = resta;
}
if (siguiente > MARGEN_MINIMO_MS) { /* solo vale la pena dormir profundo si la espera amortiza el despertar */
rtc_alarma_en(siguiente);
energia_dormir_profundo(); /* stop o standby: el tema 34 dice cuál */
} /* si falta muy poco, se queda despierto o duerme ligero */
}
Dos cuidados. Primero, el MARGEN_MINIMO_MS: dormir profundo solo se amortiza si la espera supera el punto de equilibrio del tema 34; para esperas más cortas, un sueño ligero o seguir despierto cuesta menos. Segundo, la precisión del tiempo: el RTC con un cristal de 32,768 kHz es preciso; con un oscilador RC interno la hora se desvía, y las tareas con plazos exactos deben tenerlo en cuenta.
05 · Encender, usar y apagar
Periféricos apagados y relojes lentos
Un periférico con el reloj habilitado consume, lo use o no (tema 33). La regla de oro es el ciclo encender, usar, apagar, aplicada a tres niveles: el reloj del periférico dentro del chip, el periférico mismo, y el circuito externo al que sirve (un sensor, una memoria, una radio). El caso típico, una medición con alimentación del sensor controlada por un pin:
uint16_t medir_temperatura(void) {
gpio_escribir(PIN_SENSOR_ALIM, 1); /* alimentar el sensor desde un pin: sin medir, no consume */
esperar_ms_dormido(5); /* estabilización que pide la hoja de datos: con un temporizador y la CPU dormida */
adc_encender(); /* habilita el reloj del ADC y lo activa */
uint16_t v = adc_convertir(CANAL_TEMP); /* conversión por interrupción: la CPU duerme mientras convierte */
adc_apagar(); /* apaga el ADC y su reloj */
gpio_escribir(PIN_SENSOR_ALIM, 0); /* quitar la alimentación al sensor */
return v;
}
Observá esperar_ms_dormido: la espera del sensor es uno de los puntos donde un retardo por conteo desperdiciaría los 5 ms con la CPU activa. Y la alimentación desde un pin sirve mientras la corriente del sensor sea menor que lo que el pin entrega (algunos mA); para cargas mayores se usa un transistor o un interruptor de carga controlado por el pin. El ahorro de este patrón es grande: en la Parte 1 del programa, un sensor siempre alimentado (150 µA de reposo en el ejemplo) pasaba de dominar el consumo a no aparecer en la cuenta.
El segundo recurso son los relojes. Un chip suele tener varias fuentes de reloj, con compromisos de precisión, consumo y tiempo de arranque que conviene conocer:
| Fuente | Frecuencia típica | Precisión | Consumo y arranque | Se usa para |
|---|---|---|---|---|
| Oscilador RC interno rápido (HSI) | Algunos MHz a decenas de MHz | Moderada (del orden del 1 %) | Bajo; arranca en microsegundos | Reloj de arranque y de trabajo cuando no hay comunicaciones exigentes; reloj al salir de stop |
| Cristal externo rápido (HSE) | Pocos MHz a 25 MHz | Alta | Mayor; milisegundos en estabilizarse | USB, radio, comunicaciones precisas |
| PLL | Decenas a cientos de MHz | La del reloj de entrada | Alto; decenas a cientos de µs en engancharse | Máxima velocidad; se apaga cuando no hace falta |
| Cristal de 32,768 kHz (LSE) | 32 768 Hz | Alta | Del orden de unos µA o menos | RTC y despertares precisos mientras todo lo demás duerme |
| Oscilador RC interno lento (LSI) | Unas decenas de kHz | Baja | Muy bajo | Watchdog independiente; temporizadores sin precisión |
Las prácticas que se derivan:
- Para el tiempo mientras se duerme, el cristal de 32,768 kHz. Consume muy poco y da una base precisa para el RTC y los despertares. Un RC lento ahorra el cristal, pero la hora se desvía y hay que sobredimensionar los márgenes.
- El reloj rápido solo mientras trabaja. Una estrategia común: despertar con el oscilador interno (que arranca enseguida), hacer el trabajo, y subir el PLL y el cristal solo si hace falta una comunicación precisa o un cálculo pesado. Si el trabajo corto no necesita más, no se sube.
- La frecuencia como parámetro de diseño. Como vimos en el tema 33 (Parte 5), correr más rápido y dormir antes puede gastar menos energía por tarea si el consumo fijo es apreciable, pero no siempre. Para una carga continua y liviana que no permite dormir (un control de lazo cerrado de 1 kHz, por ejemplo), conviene en cambio la frecuencia más baja que cumple el plazo. Se decide midiendo.
- Al cambiar la frecuencia, reconfigurar lo que depende de ella: los divisores de la UART, los preescalares de los temporizadores y los estados de espera de la flash. Un error clásico del tema 34: despertar con otro reloj y que la comunicación salga como basura.
- Apagar los relojes de los módulos que no se usan (tema 9): en los STM32,
__HAL_RCC_xxx_CLK_DISABLE(); en los AVR,power_xxx_disable(). Muchos chips los dejan encendidos por defecto o los encienden la HAL de ST al inicializar.
06 · El consumo invisible
Pines que no gastan
Los pines son una fuente de consumo traicionera, porque no aparecen en ninguna línea del programa que «haga» algo. Un pin configurado como entrada flotante (sin nada que fije su nivel) tiene una tensión que deriva por el aire; el buffer de entrada CMOS, al quedar en la zona intermedia, conduce una corriente a través de sus dos transistores, y la fuga puede ser de decenas o cientos de µA por pin según el chip y las condiciones. Un nodo con diez pines sin configurar puede gastar más dormido que durante toda una transmisión. La regla es que todos los pines tienen que estar en un estado definido antes de dormir:
| Situación del pin | Configuración recomendada | Por qué |
|---|---|---|
| Sin conectar a nada | Modo analógico (en STM32) o salida a nivel bajo | El modo analógico desconecta el buffer de entrada: el consumo mínimo |
| Entrada con un nivel externo siempre definido (otro chip lo maneja) | Entrada sin resistencia interna | El nivel está fijado desde afuera |
| Entrada que puede quedar flotante (un conector, un sensor desconectable) | Entrada con resistencia interna hacia un nivel (arriba o abajo) | Fija el nivel y evita la deriva |
| Pulsador con resistencia hacia arriba | Entrada con pull-up, despertar por flanco | Consume solo mientras está apretado: 3,3 V / 40 kΩ ≈ 82 µA durante el tiempo de pulsación |
| Salida sin carga | Al nivel que el circuito externo necesite, nunca flotante | No consume, pero un nivel incorrecto puede excitar algo |
| Salida que excita una carga (LED, sensor) | En el nivel que apaga la carga antes de dormir | La corriente de la carga se suma al consumo dormido |
| Pines de depuración (SWD) | Mantenerlos mientras se depura; liberarlos o fijarlos en el producto final | Ponerlos en analógico corta la comunicación con el depurador |
/* Antes de dormir: todos los pines en modo analógico (consumo mínimo), y después se vuelven a configurar solo los que SÍ se usan */
void pines_modo_sueno(void) {
GPIO_InitTypeDef g = { .Pin = GPIO_PIN_All, .Mode = GPIO_MODE_ANALOG, .Pull = GPIO_NOPULL };
HAL_GPIO_Init(GPIOA, &g); HAL_GPIO_Init(GPIOB, &g); HAL_GPIO_Init(GPIOC, &g);
/* Los que sí tienen una función durante el sueño: */
gpio_configurar_entrada_con_pull_up(PIN_BOTON); /* despertar por flanco */
gpio_configurar_salida(PIN_SENSOR_ALIM, NIVEL_BAJO); /* el sensor apagado */
/* En desarrollo se excluyen PA13 y PA14 (SWD): en analógico se pierde la conexión con el depurador */
}
El paso complementario es revisar el esquema electrónico con la hoja de datos del chip abierta: qué pines tiene el chip, a qué está conectado cada uno, y en qué estado queda. Es un trabajo de una hora que suele recuperar decenas de µA; la Parte 1 del programa de Python muestra que 30 µA de pines flotantes, en un nodo con 32 µA, duplican el consumo.
07 · El mayor consumidor
Hablar poco: la comunicación
En casi todo dispositivo conectado, el módulo de comunicación inalámbrica (Wi-Fi, Bluetooth, LoRa, celular) es el mayor consumidor de energía por mucho: decenas a cientos de miliamperios al transmitir, y también al escuchar. En el tema 33 vimos que acortar a la mitad el tiempo de radio rendía casi seis veces más que bajar el reposo. Por eso el diseño de la comunicación es la palanca principal en cualquier equipo a batería, y tiene cuatro principios:
- Transmitir lo menos posible. Cada mensaje cuesta lo mismo, lleve poco o mucho. Antes de optimizar cómo se envía, la pregunta es si hace falta enviarlo.
- Agrupar (apartado 3): acumular y enviar juntos reparte el costo fijo de cada transmisión.
- Mensajes cortos. Menos bytes, menos tiempo de radio encendida: codificar en binario en lugar de texto, enviar diferencias en lugar de valores completos, descartar campos que el receptor puede deducir.
- Escuchar es caro. Un receptor encendido consume tanto como (o más que) uno que transmite. Los protocolos de bajo consumo evitan la escucha continua con ventanas de recepción programadas: el dispositivo abre el receptor solo en momentos acordados, justo después de transmitir.
Informar por excepción
Cuando la magnitud medida cambia lentamente (una temperatura, un nivel de depósito, un estado), la técnica de mayor rendimiento es transmitir solo cuando hay un cambio significativo, más un latido periódico que confirma que el dispositivo sigue vivo. El receptor asume que, si no llegó nada, el valor no cambió más que el umbral. La Parte 3 del programa lo simula con 24 horas de lecturas de temperatura cada 10 s (una caminata al azar):
| Política | Mensajes por día | Respecto del periódico | Error máximo del último valor informado |
|---|---|---|---|
| Periódico, cada 60 s | 1440 | 100 % | — |
| Por excepción, umbral 0,10 °C | 569 | 39,5 % | 0,10 °C |
| Por excepción, umbral 0,25 °C | 104 | 7,2 % | 0,25 °C |
| Por excepción, umbral 0,50 °C | 39 | 2,7 % | 0,50 °C |
| Por excepción, umbral 1,00 °C | 25 | 1,7 % | 0,99 °C |
Con un umbral de 0,5 °C (un error que muchos usos toleran), se transmite el 2,7 % de los mensajes. Con 90 µC por mensaje (valores didácticos), el costo de radio pasa de 1,50 µA de media a 0,04 µA. El precio es doble: el error acotado por el umbral (el receptor sabe el valor con esa tolerancia, no exacto) y la necesidad del latido, sin el cual el receptor no puede distinguir «no hay cambios» de «el dispositivo murió». Tampoco sirve cuando el receptor necesita el historial completo de las lecturas.
08 · El resto de los detalles
Otros hábitos que cuestan o ahorran
| Hábito | Efecto | Qué hacer |
|---|---|---|
| Mensajes de depuración por la UART en el producto | Mantienen la UART y el procesador activos; formatear texto cuesta ciclos y flash | Compilarlos fuera del producto con una macro; registros compactos por códigos (tema 32) |
| Depurador conectado | Mantiene relojes y dominios de depuración activos (tema 34) | Medir sin él; en el producto final, desactivar la depuración si la seguridad lo pide |
| Watchdog independiente | Sigue corriendo dormido con su reloj lento; consume un poco y exige despertar para atenderlo | Elegir el período del watchdog en función del ciclo de sueño (tema 38); un despertar de mantenimiento solo para el perro es un costo real |
| Detector de baja tensión (BOR) | Consume unos µA, siempre | Dejar habilitado el nivel que la batería necesita (tema 37); no deshabilitarlo sin análisis: protege los datos |
| Retención de RAM | En chips que permiten retener solo parte de la RAM en el sueño, cada bloque retenido consume | Retener solo lo que hace falta, y sacar el resto |
| Esperas de la flash a alta frecuencia | Ejecutar a la frecuencia máxima exige ciclos de espera y consume más por ciclo | Elegir la frecuencia que cumple el plazo con el menor consumo (se mide) |
| Bibliotecas y HAL genéricas | Suelen dejar periféricos y relojes habilitados «por si acaso» | Revisar qué deja encendido cada inicialización, y apagar lo que sobra |
| Corrientes de polarización externas | Divisores, pull-ups y LED de estado de la placa (tema 33) | Cambiar valores, desconectar con un pin o quitarlos del diseño |
El patrón común: casi todos estos consumos son decisiones por defecto que nadie tomó a conciencia. Los chips y las bibliotecas vienen configurados para que todo funcione, no para que consuma poco, y el trabajo de un firmware de bajo consumo es revisar cada valor por defecto y preguntar si hace falta.
09 · Comprobar que funciona
Verificación y diagnóstico
| Qué verificar | Cómo | Resultado esperado |
|---|---|---|
| Que la CPU duerme casi todo el tiempo | Sacar un pin a nivel alto al despertar y a bajo al dormir; medirlo con un analizador (tema 18) | El pin está en alto una fracción pequeña del ciclo (el ciclo de trabajo esperado) |
| El perfil de corriente | Registrar la corriente de la placa durante varios ciclos con un analizador de consumo (tema 36) | Espigas del tamaño y la duración del presupuesto; una línea base igual al reposo calculado |
| Ausencia de espera activa | Buscar en el código while (!...) sobre periféricos y retardos por conteo | Ninguno en la ruta normal; los que queden, justificados y acotados |
| Ausencia de despertares espurios | Contar los despertares por minuto en un contador de depuración | Coincide con los eventos esperados; el tick no despierta en el sueño profundo |
| Estado de los pines al dormir | Comparar la corriente dormido con los pines en un estado y en otro | Sin diferencias de decenas de µA entre configuraciones razonables |
| Periféricos y relojes habilitados | Leer los registros de habilitación de relojes con el depurador antes de dormir | Solo los que se necesitan para despertar |
| Número de transmisiones por día | Contarlas en el firmware y compararlas con el diseño | El esperado según la política (periódica, por excepción, agrupada) |
| La autonomía | Repetir el presupuesto del tema 33 con las corrientes medidas | Cumple el requisito con margen |
| Síntoma | Causa probable | Qué hacer |
|---|---|---|
| El consumo dormido es del orden de 1 mA con un RTOS | El tick del SysTick impide los sueños profundos | Activar el modo sin tick del RTOS; verificar los ganchos de sueño y los relojes |
| El consumo dormido es de cientos de µA en un equipo sin RTOS | Pines flotantes, periféricos con el reloj habilitado, un sensor siempre alimentado | Recorrer la tabla del apartado 6 y la de los consumidores del tema 33 |
| El equipo despierta muchas más veces de las esperadas | Una fuente por nivel, rebotes, una bandera que no se borra, interrupciones de periféricos sin usar | Contar las causas de despertar; borrar banderas; deshabilitar lo que no se usa |
| La CPU está activa más tiempo que el diseño | Esperas activas escondidas (en bibliotecas, en el controlador de un periférico) | Medir cuánto dura cada ráfaga; revisar con un analizador qué la alarga |
| La medición con el sensor da valores raros tras apagarlo y encenderlo | No se respetó el tiempo de estabilización | Esperar lo que pide la hoja de datos (dormido, con un temporizador) |
| Se perdió un evento mientras el equipo dormía | La carrera entre comprobar y dormir (tema 34), o una cola sin protección | Enmascarar las interrupciones al comprobar; revisar la cola |
| La hora del sistema se desvía | Se usa un oscilador RC lento para el RTC, o no se corrige el tick al despertar | Cristal de 32,768 kHz; corregir la hora con el tiempo medido por el RTC |
| El consumo medio supera al calculado pese a todo | Eventos raros (reintentos de radio, reconexiones) que el presupuesto no incluyó | Medir durante un período largo, con los eventos de falla incluidos |
10 · Analogía con programación
Probar el modelo en Python
El programa rediseña paso a paso el mismo nodo del tema 33 (una lectura cada 10 s, con dos pilas AA), sumando una decisión de diseño por vez, y tiene tres partes: la escalera de rediseño con la corriente media y la autonomía de cada versión, el costo de distintos ticks periódicos, y la simulación de 24 horas de lecturas para comparar el envío periódico con el envío por excepción. Los valores eléctricos son didácticos; lo que se aprende es el orden de magnitud de cada decisión y cuál rinde más.
Python en tu navegador. Los bloques son editables: cambiá los valores y presioná «Ejecutar Python». La primera vez tarda unos segundos en cargar.
# Firmware de bajo consumo: la misma tarea, rediseñada paso a paso. Valores DIDÁCTICOS (órdenes de magnitud de un Cortex-M).
import random
HORAS_ANIO = 8766
CAPACIDAD_UTIL_MAH, AUTODESCARGA_UA = 2500 * 0.75, 5.70 # 2 AA alcalinas del tema 33
T = 10.0 # una lectura cada 10 s
IDLE = { # (corriente de espera en µA, carga extra por despertar en µC)
"bucle con retardos (CPU activa)": (5000.0, 0.0),
"WFI con tick de 1 kHz": (1500.0 + 1000 * 0.05 * 1000 / 1000, 0.0), # 1500 µA de sleep + 1000 despertares/s de 10 µs a 5 mA = 50 µA
"stop con despertar por RTC (sin tick)": (20.0, 5.0 * 0.2), # 20 µA; cada despertar cuesta 5 mA * 0,2 ms = 1 µC
"standby con despertar por RTC": (2.0, 5.0 * 6.0), # 2 µA; cada despertar cuesta 5 mA * 6 ms = 30 µC
}
def corriente_media(idle, sensor_apagado, pines_ok, n_por_paquete):
i_idle, q_despertar = IDLE[idle]
q_medida = 3.0 * 10.0 # 3 mA * 10 ms = 30 µC por lectura
q_radio = (60.0 + 30.0 * n_por_paquete) / n_por_paquete # 60 µC fijos por paquete + 30 µC por lectura transmitida, repartidos
i_sensor = 0.0 if sensor_apagado else 150.0 # un sensor siempre alimentado
i_pines = 0.0 if pines_ok else 30.0 # pines flotantes
return i_idle + (q_despertar + q_medida + q_radio) / T + i_sensor + i_pines
def anios(i_ua):
return CAPACIDAD_UTIL_MAH / ((i_ua + AUTODESCARGA_UA) / 1000) / HORAS_ANIO
print("Parte 1: el mismo nodo (una lectura cada 10 s), rediseñado paso a paso")
escalera = [
("diseño ingenuo: bucle con retardos, sensor siempre alimentado, pines sin configurar", ("bucle con retardos (CPU activa)", False, False, 1)),
("+ por eventos con WFI y tick de 1 kHz", ("WFI con tick de 1 kHz", False, False, 1)),
("+ sin tick: stop con despertar por RTC", ("stop con despertar por RTC (sin tick)", False, False, 1)),
("+ sensor alimentado solo al medir", ("stop con despertar por RTC (sin tick)", True, False, 1)),
("+ pines sin usar en modo analógico", ("stop con despertar por RTC (sin tick)", True, True, 1)),
("+ una transmisión cada 10 lecturas", ("stop con despertar por RTC (sin tick)", True, True, 10)),
("+ standby en lugar de stop (la RAM no hace falta)", ("standby con despertar por RTC", True, True, 10)),
]
anterior = None
for etiqueta, args in escalera:
i = corriente_media(*args)
mejora = "" if anterior is None else f" (÷ {anterior / i:.1f})"
print(f" {etiqueta:<82} {i:9.1f} µA -> {anios(i):8.2f} años{mejora}")
anterior = i
print()
print("Parte 2: el costo de un tick periódico (cada despertar: 10 µs activos a 5 mA = 0,05 µC)")
for f in (1000, 100, 10, 1):
print(f" tick de {f:>4} Hz: {f * 0.05:6.2f} µA de media solo por despertar al procesador")
print(f" un stop con RTC cuesta 20 µA de base; con un tick de 1 kHz el SysTick ni siquiera funciona en stop (su reloj se detiene)")
print()
print("Parte 3: informar solo cuando cambia (24 h, una lectura cada 10 s, caminata al azar)")
rnd = random.Random(3)
valor, lecturas = 20.0, []
for _ in range(8640):
valor += rnd.gauss(0.0, 0.03)
lecturas.append(valor)
def simular(umbral, latido_s):
enviados, ultimo, desde = 0, None, 0
peor_error = 0.0
for lectura in lecturas:
desde += 10
if ultimo is None or abs(lectura - ultimo) >= umbral or desde >= latido_s:
ultimo, desde = lectura, 0
enviados += 1
peor_error = max(peor_error, abs(lectura - ultimo))
return enviados, peor_error
print(f" periódico cada 60 s : {86400 // 60:5d} mensajes en el día")
for umbral in (0.1, 0.25, 0.5, 1.0):
n, err = simular(umbral, 3600)
print(f" por excepción (umbral {umbral:4.2f} °C, latido de 1 h): {n:5d} mensajes ({n / 1440 * 100:5.1f} % del periódico), error máximo del último valor informado {err:4.2f} °C")
Resultado, parte 1: el diseño ingenuo (bucle con retardos, sensor siempre alimentado y pines sin configurar) consume 5192 µA y dura 0,04 años (unos 15 días). Cada rediseño suma una mejora: pasar a eventos con WFI y tick de 1 kHz baja a 1742 µA (÷3,0, 0,12 años); quitar el tick y usar stop con despertar por RTC, a 212,1 µA (÷8,2, 0,98 años); alimentar el sensor solo al medir, a 62,1 µA (÷3,4, 3,15 años); poner los pines en analógico, a 32,1 µA (÷1,9, 5,66 años); agrupar una transmisión cada 10 lecturas, a 26,7 µA (÷1,2, 6,60 años); y pasar a standby (si la RAM no hace falta), a 11,6 µA (÷2,3, 12,36 años). El total es una mejora de unas 450 veces en el consumo y de unas 300 veces en la autonomía, sin cambiar ni una sola de las funciones del producto. Parte 2: un tick de 1000 Hz cuesta 50 µA, uno de 100 Hz 5 µA, uno de 10 Hz 0,5 µA y uno de 1 Hz 0,05 µA, y el SysTick no funciona en stop. Parte 3: el envío periódico cada minuto son 1440 mensajes por día; por excepción con un latido de una hora, el umbral de 0,10 °C da 569 (39,5 %), el de 0,25 °C da 104 (7,2 %), el de 0,50 °C da 39 (2,7 %) y el de 1,00 °C da 25 (1,7 %), con un error máximo igual al umbral.
Probá cambiar el orden de la escalera (por ejemplo, empezar por agrupar la radio) y mirá que las mejoras no se suman, se multiplican en cadena, y que cada una vale más o menos según lo que ya se arregló antes: arreglar los pines vale 30 µA, que importan poco en un diseño de 5000 µA y mucho en uno de 62. Por eso se ataca siempre primero lo que más pesa.
11 · Exploración interactiva
Rediseñar el nodo
El laboratorio usa el modelo de la Parte 1: un nodo que mide cada T segundos y transmite, con dos pilas AA. Elegís cómo espera (bucle con retardos, WFI con tick, stop o standby), si el sensor se alimenta solo al medir, si los pines están configurados y cuántas lecturas se agrupan por transmisión. Ves la corriente media (que incluye los 5,7 µA de autodescarga de las pilas), la autonomía y qué componente pesa más en cada versión.
De un firmware ingenuo a uno de bajo consumo
Reparto de la corriente
Elegí un diseño.
Recorrido guiado
- Con el diseño ingenuo (los valores iniciales), la corriente media es de unos 5200 µA y el nodo dura unos 15 días. La barra «Espera» ocupa casi todo: la CPU nunca duerme.
- Apretá «Por eventos con WFI y tick»: baja a unos 1750 µA (÷3). Ahora la CPU duerme, pero el tick y el escalón ligero del sueño siguen pesando.
- Cambiá la espera a Stop con despertar por RTC: la corriente cae a unos 218 µA (÷8). Quitar el tick y dormir profundo es el salto más grande de toda la escalera. Ahora el mayor consumidor es el sensor siempre alimentado (150 µA).
- Marcá «El sensor se alimenta solo al medir»: unos 68 µA. Y luego «Pines sin usar en modo analógico»: unos 38 µA, y la autonomía pasa de unos 3 años a unos 5,7.
- Subí las lecturas por transmisión a 10: baja a unos 32 µA. Es una mejora chica (÷1,2) porque lo que queda es sobre todo el reposo del stop y la autodescarga. Fijate que las barras de «Radio» y «Medida» son ahora pequeñas.
- Cambiá la espera a Standby (si la RAM no hace falta): unos 17 µA (11,6 del equipo y 5,7 de autodescarga de las pilas) y más de 12 años. Y probá el período de 10 minutos: la radio y la medida casi desaparecen del reparto y manda la espera; entonces importa mucho más el modo de sueño que el resto.
Desafío: ¿qué cambiaría si el nodo tuviera que reaccionar en menos de 1 ms a un evento externo?
La espera dejaría de poder ser standby, cuyo despertar tarda milisegundos (tema 34), y probablemente de poder ser stop si su latencia (cientos de µs en el ejemplo) más la restauración del reloj compromete el plazo. Se podría usar sleep ligero con un reloj activo, pagando una corriente de espera cientos de veces mayor, o buscar hardware que haga la parte urgente sin la CPU: un comparador analógico o un periférico de captura que reaccione al evento por sí mismo (cambiando una salida, disparando un DMA) y que despierte a la CPU solo para el trabajo no urgente. El diseño de bajo consumo, en estos casos, baja la responsabilidad a un hardware que gasta microamperios en lugar de dejar despierta a una CPU que gasta miliamperios. Es el motivo por el que los chips modernos de bajo consumo tienen periféricos que operan en los modos profundos.
12 · Método de trabajo
Método: diseñar para dormir
- Empezá por el presupuesto (tema 33): el requisito de autonomía, la batería y el perfil de consumo esperado. Dice cuánta corriente media se puede gastar, y entonces qué técnicas hacen falta.
- Listá lo que despierta al sistema: temporizadores, pines, comunicaciones. Para cada evento, ¿qué hardware lo espera? Ninguna espera se hace con la CPU.
- Estructurá el programa por eventos con un único lugar donde se duerme, comprobando de forma atómica que no hay trabajo pendiente.
- Eliminá el tick o reducilo al mínimo: un temporizador de bajo consumo que despierte solo cuando vence algo.
- Aplicá el ciclo «encender, usar, apagar» a cada periférico y a cada circuito externo, incluida la alimentación de los sensores.
- Definí el estado de cada pin para dormir, revisando el esquema con la hoja de datos.
- Elegí los relojes: cristal de 32,768 kHz para el tiempo mientras duerme; reloj rápido solo mientras se trabaja; la frecuencia más baja que cumpla los plazos en cargas continuas.
- Diseñá la comunicación primero para no usarla: informar por excepción, agrupar, mensajes cortos, ventanas de escucha.
- Atacá en el orden en que pesa: medí, mirá la barra más grande del reparto, arreglala y repetí. Un cambio mejora más o menos según lo que ya se arregló.
- Medí el producto real (tema 36) sin el depurador y con eventos raros incluidos. Un firmware de bajo consumo no se declara así: se mide así.
13 · Comprensión
Confusiones frecuentes
«Con ponerle un __WFI() al bucle principal, el firmware es de bajo consumo»
Lo es solo si no hay nada más que despierte al chip todo el tiempo. Un tick de 1 kHz lo despierta mil veces por segundo, un periférico habilitado sigue consumiendo, un sensor sigue encendido. En el escalón de la Parte 1, WFI con tick baja el consumo a un tercio; el resto de la mejora viene de otras decisiones.
«Un retardo de pocos milisegundos no importa»
Si se repite en cada ciclo, es tiempo activo puro. Un delay_ms(5) en un ciclo de 10 s mantiene la CPU a plena potencia durante el 0,05 % del tiempo, pero a 5 mA eso son 25 µC por ciclo, comparable al costo de toda la medición del ejemplo. Se hace con un temporizador y la CPU dormida.
«El tick de 1 ms es inofensivo: son unos pocos ciclos»
Mil veces por segundo. Con valores razonables suma decenas de µA, más que todo el consumo de un nodo bien diseñado, y además impide los sueños profundos porque el SysTick usa un reloj que se apaga. Para un dispositivo a batería, el tick periódico es lo primero que se elimina.
«Los pines que no uso no gastan nada»
Un pin sin configurar es una entrada flotante, y su buffer puede conducir una corriente de decenas o cientos de µA. La configuración de los pines sin usar (analógico o nivel definido) es una de las mejoras más baratas del diseño.
«Agrupar mensajes siempre ahorra»
Ahorra el costo fijo, pero el ahorro tiene un tope (el costo variable no desaparece), agrega latencia (los datos llegan tarde) y obliga a acumular en memoria. Si el requisito exige la inmediatez, se combina con el envío por excepción, o directamente no se agrupa.
«Informar por excepción pierde información»
Pierde la que cambió menos que el umbral, que por diseño no interesaba. El receptor conoce el valor con una tolerancia acotada. Lo que sí hace falta es un latido para distinguir «sin cambios» de «dispositivo caído». Si el uso requiere todas las lecturas, no es la técnica adecuada.
«Optimizar el firmware se hace al final, cuando todo funciona»
Las decisiones de este tema son de arquitectura: si el firmware nació con retardos por conteo, sondeos y un tick, hay que reestructurarlo, no ajustarlo. El bajo consumo se diseña desde el principio, con el presupuesto del tema 33 a la vista, y se mide durante todo el desarrollo.
«Un firmware de bajo consumo es un firmware lento»
Al contrario: suele ser más reactivo, porque está dormido esperando eventos en lugar de ocupado haciendo otra cosa. Lo que se sacrifica es la comodidad de «pensar en bucle»; lo que se gana es un sistema donde todo es una respuesta a algo que pasó, el estilo que ya vimos en el bloque de máquinas de estados.
14 · Práctica guiada
Ejercicios
1. El costo de un tick
Un sistema tiene un tick de 100 Hz. Cada despertar dura 20 µs a 8 mA. ¿Qué corriente media agrega solo por el tick? ¿Y si el tick fuera de 1 kHz?
Ver solución
Cada despertar gasta 8 mA × 20 µs = 0,16 µC. A 100 Hz: 100 × 0,16 µC = 16 µC por segundo = 16 µA. A 1 kHz: 1000 × 0,16 = 160 µC por segundo = 160 µA. Para un nodo cuyo objetivo es consumir unas decenas de µA en total, ni el de 100 Hz es aceptable: hay que eliminarlo o pasar a un sistema sin tick.
2. Agrupar
Una transmisión cuesta 80 µC fijos más 20 µC por cada lectura incluida. Se mide cada 30 s. Calculá el costo por lectura y la corriente media de radio con N = 1 y con N = 8. ¿Cuánto puede demorarse un dato con N = 8?
Ver solución
N = 1: 80 + 20 = 100 µC por lectura; media = 100 / 30 s = 3,33 µA. N = 8: 80 / 8 + 20 = 30 µC por lectura; media = 30 / 30 = 1,0 µA. El ahorro es del 70 %. La primera lectura de cada paquete espera a las otras siete: hasta 7 × 30 s = 210 s (3,5 minutos). Si ese plazo es aceptable, la técnica es muy buena; si el requisito dice «avisar en menos de 1 minuto», no.
3. Ciclo de trabajo
Un equipo trabaja 12 ms a 6 mA cada 1 s y duerme a 3 µA el resto. Calculá el ciclo de trabajo, la corriente media y qué parte aporta la ráfaga. ¿Qué pasa con la media si se mide cada 500 ms en lugar de cada 1 s?
Ver solución
D = 12 ms / 1000 ms = 1,2 %. Media = 0,012 × 6 mA + 0,988 × 0,003 mA = 0,072 + 0,00296 ≈ 0,0750 mA = 75 µA. La ráfaga aporta 72 de 75 µA: el 96 %. Si se mide cada 500 ms, el ciclo de trabajo se duplica a 2,4 % y la media casi se duplica: unos 147 µA (0,024 × 6 + 0,976 × 0,003 = 0,147 mA). Es la prueba de que, con el ciclo de trabajo chico, la autonomía es casi inversamente proporcional a la frecuencia de las mediciones.
4. El pulsador y la resistencia
Un pulsador usa el pull-up interno de 40 kΩ a 3,3 V. ¿Qué corriente consume mientras está apretado? Si el usuario lo aprieta 10 % del día, ¿cuánto es la media?
Ver solución
I = 3,3 V / 40 kΩ = 82,5 µA mientras está apretado. Con el 10 % del día apretado, la media es 0,10 × 82,5 = 8,25 µA: para un nodo de 30 µA, un 27 % más. Soluciones: un pull-up de mayor valor (el despertar por flanco no necesita una corriente alta), o activar el pull-up solo mientras se muestrea la entrada, o un diseño en que el pulsador cierre contra una resistencia que solo conduzca al apretar.
5. Despertares por minuto
Un sistema con eventos reales una vez por segundo usa un tick de 1 ms. ¿Cuántos despertares por minuto hace? ¿Y con un sistema sin tick? ¿Cuántas veces menos?
Ver solución
Con tick de 1 ms: 1000 despertares por segundo × 60 = 60 000 por minuto. Sin tick, solo despierta por los eventos: 60 por minuto. Son 1000 veces menos. Con el costo por despertar del ejercicio 1 (0,16 µC), son 9600 µC por minuto contra 9,6 µC. Y, más importante todavía, sin tick el procesador puede usar el sueño profundo entre eventos.
6. Informar por excepción
Con los resultados de la Parte 3, un mensaje cuesta 90 µC. Calculá el costo de radio diario (en µC) y la corriente media del periódico (1440 mensajes) y del de umbral 0,5 °C (39 mensajes). ¿Qué hay que agregar para que el receptor confíe en el sistema?
Ver solución
Periódico: 1440 × 90 = 129 600 µC por día; en 86 400 s son 1,50 µA de media. Por excepción: 39 × 90 = 3510 µC; 0,041 µA. Es una reducción de 37 veces. Para que el receptor confíe hace falta un latido (en la simulación, uno por hora, ya incluido en los 39 mensajes) para distinguir «sin cambios» de «dispositivo sin batería o sin cobertura», y que el receptor sepa y acepte la tolerancia de 0,5 °C. Si hay que detectar el fallo en menos de una hora, el latido tiene que ser más frecuente, y la reducción, menor.
15 · Síntesis
Ideas para recordar
- El bajo consumo es una propiedad de la arquitectura: el firmware tiene que estar dormido casi siempre y trabajar a ráfagas. Los enemigos son los retardos por conteo, la encuesta, la espera activa, el tick periódico, los periféricos siempre encendidos y el trabajo de más.
- La arquitectura que permite dormir es la orientada a eventos: las ISR publican eventos, el bucle principal los despacha y, con la cola vacía, duerme (comprobando y durmiendo sin carrera). Ninguna tarea espera activamente: cada espera se convierte en un evento.
- El ciclo de trabajo D = tactivo / T da la corriente media: Imedia = D·Ia + (1 − D)·Ir. Se baja haciendo cada ráfaga más corta o haciendo menos ráfagas, y agrupar reparte el costo fijo de cada ráfaga.
- El tick periódico despierta mil veces por segundo (50 µA con valores típicos) e impide los sueños profundos. El firmware sin tick programa un temporizador de bajo consumo para el próximo vencimiento (en FreeRTOS,
configUSE_TICKLESS_IDLE). - Cada periférico y circuito externo sigue el ciclo encender, usar, apagar, incluida la alimentación del sensor desde un pin. Los relojes: cristal de 32,768 kHz para dormir con precisión, reloj rápido solo mientras se trabaja.
- Todos los pines deben estar en un estado definido antes de dormir (analógico si no se usan): una entrada flotante puede consumir decenas o cientos de µA.
- La comunicación es el mayor consumidor: transmitir poco, agrupar, mensajes cortos, ventanas de escucha e informar por excepción (con latido): en la simulación, el 2,7 % de los mensajes con un error máximo de 0,5 °C.
- Las mejoras se encadenan: en el ejemplo, de 5192 µA (15 días) a 11,6 µA (12 años) sin quitar una función del producto. Se ataca siempre lo que más pesa, se mide con el producto real y se vuelve a medir.
¿Listo para el tema siguiente?
Antes de avanzar, tomá un firmware tuyo y buscá cinco cosas: ¿hay retardos por conteo?, ¿hay while (!...) sobre periféricos?, ¿hay un tick, y de qué frecuencia?, ¿qué periféricos y relojes están habilitados al dormir?, ¿qué pines están sin configurar? Cada «sí» es una mejora concreta. Y calculá cuánto vale la más grande con el modelo de este tema.
En el próximo tema cerramos el bloque con un taller: medir y estimar la duración de la batería. Cómo se mide la corriente de un dispositivo real, cómo se arma su perfil de consumo, y cómo se compara con el presupuesto para llegar a una autonomía en la que confiar.