← Índice del curso

Comunicación entre Dispositivos · Tema 40 · Taller

Taller: sensor inalámbrico de bajo consumo

En los temas anteriores se estudió la radio: el espectro, el presupuesto de enlace, BLE, LoRa y sus parientes. Acá se construye con ellas un dispositivo completo y se lo mide con la regla que decide si un sensor inalámbrico sirve o no: cuánto dura la batería. El proyecto es un nodo de temperatura y humedad que duerme casi todo el tiempo, despierta cada cinco minutos, mide con un BME280, mide también la tensión de su propia batería, publica una lectura de 10 bytes por BLE (con la variante por LoRa) y vuelve a dormir. Parece poco, y es la clase de proyecto donde los números mandan: un ESP32 en una placa de desarrollo común dura dos semanas con una batería de 2000 mAh, y el mismo chip bien montado dura años. La diferencia no está en el código del sensor sino en las decisiones: qué consume mientras duerme, cuánto tarda en despertar, cuánto dura la radio y qué cuesta medir la batería. Se diseña el presupuesto de energía antes de programar, se define un mensaje binario con número de secuencia para medir las pérdidas sin tener confirmaciones, se escribe el programa, se escribe un receptor en Python y se verifica cada cifra con instrumentos.

01 · Objetivo

Qué vamos a construir

El proyecto es un nodo ambiental con batería, con los requisitos de un dispositivo real:

  • Medir la temperatura y la humedad con un BME280 (el sensor I²C de los temas 16 a 20), en modo forzado para que se apague solo al terminar.
  • Medir su propia batería con un divisor de tensión y el conversor del ESP32, sin comerse el presupuesto de energía.
  • Publicar cada lectura en un mensaje binario de 10 bytes, con versión, número de nodo y número de secuencia, como datos del fabricante de un paquete de publicidad BLE (sin conexión, como una baliza del tema 38).
  • Dormir en sueño profundo entre ciclos, con un período de 300 s configurable, conservando el contador de secuencia en la memoria del reloj de tiempo real.
  • Declarar la falta de dato: si el sensor no responde, el mensaje se envía igual con el valor «sin dato» (0x8000, como en la característica de temperatura del tema 38).
  • Recibir en una computadora con Python (bleak), validar el mensaje, descartar repeticiones y contar los mensajes perdidos por saltos en la secuencia.
  • Cumplir un objetivo de duración: más de dos años con una batería de litio de 2000 mAh.

Lo importante no son las líneas de código sino las decisiones de diseño. Una: dividir la vida del nodo en un ciclo breve y un sueño largo, y calcular la carga de cada fase. Dos: que el mensaje sea autodescriptivo y mida su propia calidad de enlace. Tres: atacar primero lo que más pesa en la cuenta, que casi nunca es lo que parece. Es la integración de todo lo visto en esta parte: el enlace (temas 34 y 39), BLE (38), el ahorro de energía de Wi-Fi (36) y el diseño de mensajes que se verá en profundidad en los temas 41 a 46.

02 · Materiales y cableado

Materiales y cableado

Cableado del nodo Una batería de litio alimenta un regulador de baja corriente de reposo, que entrega 3,3 voltios al módulo ESP32 y al sensor BME280. El sensor se conecta al ESP32 por I2C, con SDA en el pin 21 y SCL en el pin 22. La batería también llega a un divisor de dos resistencias de 1 megaohm, cuyo punto medio, con un condensador de 100 nanofaradios a masa, va al pin 34 del ESP32, una entrada del conversor analógico. Batería de litio3,0 a 4,2 V · 2000 mAh Regulador 3,3 Vreposo de pocos µA Módulo ESP32sin chip USB ni LED BME280dirección 0x76 3,3 V SDASCL GPIO 21y GPIO 22 R1 1 MΩ R2 1 MΩ masa 100 nF GPIO 34 El divisor está siempre conectado: por eso las resistencias son de 1 MΩ. El condensador permite que el conversor lea con una fuente de impedancia tan alta.
Materiales.
CantidadComponenteNota
1Módulo ESP32 (por ejemplo, ESP32-WROOM-32 en una placa mínima)Sin chip USB ni LED de alimentación. Para programarlo se usa un adaptador USB-serie externo que se retira después
1Regulador de 3,3 V de baja corriente de reposo (MCP1700-3302, HT7333 o similar)El AMS1117 de las placas comunes gasta unos 5 mA solo por existir
1Celda de litio de 3,7 V y 2000 mAh (con protección)O dos pilas AA con otro regulador; las pilas de botón no soportan el pico de la radio
1BME280 (placa con la dirección 0x76)Funciona con 3,3 V; en modo forzado duerme con menos de 1 µA
2Resistencias de 1 MΩDivisor para medir la batería
3Condensadores: 100 nF (en el conversor), 10 µF y 100 µF (cerca del módulo)Los picos de la radio llegan a 100 mA y sin condensador hunden la tensión
1Un segundo ESP32 o una computadora con Bluetooth y Python 3El receptor, con la biblioteca bleak
1Multímetro con escala de microamperios (y, opcional, un analizador de corriente)Para medir el sueño y la carga del ciclo
OpcionalMódulo SX1276 (RFM95) y antenaPara la variante por LoRa (tema 39)
Conexiones (con un ESP32 clásico; en otras variantes, usá los pines I²C y del conversor correspondientes).
DesdeHastaFunción
Batería (+)Entrada del regulador y R1Alimentación y medición
Salida del regulador (3,3 V)3V3 del módulo y VCC del BME280Alimentación
GPIO 21SDA del BME280Datos I²C (la placa del BME280 trae sus resistencias de elevación)
GPIO 22SCL del BME280Reloj I²C
Punto medio R1 – R2 (con 100 nF a masa)GPIO 34Entrada del conversor (ADC1; solo entrada)
R2 y el condensadorMasaReferencia
GND de todoGND comúnMasa

Una advertencia sobre el módulo: la placa de desarrollo del ESP32 (la más común, con el conversor USB y un regulador lineal) no sirve para este proyecto. Ni siquiera con el sueño profundo activado baja de 5 a 10 mA, por el chip USB y el regulador, y con 2000 mAh duraría unos 13 días (herramienta 1). Se la usa para programar y depurar, y para el nodo definitivo se pasa a un módulo desnudo o a una placa pensada para baterías, con el regulador de la tabla.

03 · Diseñar antes de programar

El presupuesto de energía

Corriente de un ciclo del nodo Gráfico de la corriente en el tiempo, sin escala. Un ciclo comienza con el arranque del procesador, de 60 milisegundos y 30 miliamperios, sigue con la medición, de 25 milisegundos y 25 miliamperios, y termina con la radio, de 250 milisegundos y 70 miliamperios. Después hay un sueño de casi 5 minutos con 15 microamperios, y el ciclo se repite. La radio es el rectángulo más alto y ancho y concentra la mayor parte de la carga del ciclo. corriente tiempo (sin escala) arranqueradio: 250 ms · 70 mA 60 ms · 30 mAmedición 25 mssueño: 15 µA durante casi 5 minutos 70 mA30 mA0 y se repite cada 300 s El área bajo la corriente es la carga: cada fase pesa por su corriente y por su duración.

Antes de escribir una línea hay que decidir si el proyecto es viable, y para eso alcanza una cuenta: la carga de cada fase (corriente por tiempo, en milicoulombs si las unidades son miliamperios y segundos) más la del sueño, repartida en el período, da la corriente media; la capacidad útil de la batería dividida por esa corriente da la duración. Es el mismo cálculo del tema 34 y del 38, ahora con tres fases despiertas. La herramienta 1 del laboratorio la hace con los valores que siguen:

Fases de un ciclo del nodo con un ESP32 y publicidad BLE (valores de un diseño típico).
FaseQué ocurreTiempoCorrienteCarga
ArranqueEl procesador sale del sueño profundo y arranca el programa desde el principio60 ms30 mA1,80 mC
MediciónEl BME280 mide en modo forzado y el conversor lee la batería25 ms25 mA0,63 mC
RadioSe inicializa BLE, se publica en tres o cuatro eventos y se apaga250 ms70 mA17,50 mC
SueñoProcesador en sueño profundo, regulador, divisor y sensor en reposocasi 300 s15 µA4,50 mC
Total por período de 300 s24,4 mC

La corriente media es 24,4 mC / 300 s = 81,4 µA. Con una batería de 2000 mAh y el 80 % utilizable son 1600 mAh: 1600 / 0,0814 ≈ 19 650 h, unos 2,2 años. Cumple el objetivo, pero con poco margen, y la tabla enseña dónde está el costo: la radio pesa el 72 % de la energía (y no por la transmisión misma, que dura apenas milisegundos, sino por inicializar BLE y mantenerlo encendido), el sueño pesa el 18 % y el arranque y la medición, el 10 %. Dos conclusiones prácticas:

  • Lo que más ayuda es acortar la radio. Reducir la fase de radio de 250 a 60 ms (con menos eventos de publicidad y una inicialización más corta) baja la media a 37 µA y la duración a 4,9 años: con la herramienta 1 se ve de inmediato.
  • El sueño no es despreciable. Cada 15 µA de más valen lo mismo que casi 20 mC por ciclo de radio; y con la placa de desarrollo (5 mA), el sueño se come el 99 % de todo y la duración cae a 13 días. En el otro extremo, con un período de una hora, el sueño pasa a ser el 73 % del total: a períodos largos, lo que manda es lo que consume el nodo dormido.

La cuenta inversa es igual de útil: dada la duración que se pide, la herramienta 5 calcula cada cuánto se puede medir. Para dos años con 2000 mAh y 19,9 mC por ciclo, el período mínimo es de 261 s: los 300 s elegidos cumplen. Para cinco años, 15 minutos.

04 · El formato de los datos

El mensaje

Un nodo que publica sin conexión no puede preguntar nada: el mensaje tiene que explicarse solo. El diseño sigue las reglas del tema 38 y anticipa las de los temas 41 y 46: tamaño fijo, enteros de 16 bits, little-endian, unidad entera y un valor reservado para «sin dato».

Mensaje de 10 bytes.
ByteCampoTipoContenido
0Versiónuint81. Permite cambiar el formato sin romper a los receptores viejos
1Nodouint8Identificador del emisor (0 a 255)
2 y 3Secuenciauint16Sube de a uno por cada medición y da la vuelta en 65 535
4 y 5Temperaturaint16Centésimas de grado (23,50 °C = 2350); 0x8000 es «sin dato»
6 y 7Humedaduint16Centésimas de por ciento; 0xFFFF es «sin dato»
8 y 9Bateríauint16Milivolts

Con 23,5 °C, 55 % de humedad, 3800 mV, la secuencia 1234 y el nodo 1, el mensaje es 01 01 D2 04 2E 09 7C 15 D8 0E: 1234 es 0x04D2 (D2 04), 2350 es 0x092E (2E 09), 5500 es 0x157C (7C 15) y 3800 es 0x0ED8 (D8 0E). Va como datos del fabricante (tipo 0xFF) con el identificador de empresa 0xFFFF, el valor que la Bluetooth SIG reserva para pruebas: el paquete completo es 02 01 06 0D FF FF FF seguido del mensaje, 17 de los 31 bytes y 264 µs por paquete en el aire (herramienta 2). Quedan 14 bytes: lugar para una segunda lectura o para un nombre.

El número de secuencia es la pieza más barata y más útil del formato. Como el nodo no recibe confirmaciones, el receptor mide la calidad del enlace por los saltos del contador: una diferencia de 1 con el último mensaje es lo normal, de 0 es una repetición (la publicidad repite el mismo paquete en varios eventos y canales) y de más de 1 son mensajes perdidos. La resta se hace módulo 65 536, de modo que el contador puede dar la vuelta sin problemas (herramienta 4). Los datos no llevan un CRC propio porque BLE ya trae uno de 24 bits en cada paquete: un mensaje corrupto no llega a la aplicación. En un enlace sin esa protección (un UART sin más, tema 44) habría que agregarlo.

Medir la batería

La tensión de una celda de litio llega a 4,2 V y el conversor del ESP32 admite unos 3,1 V, de modo que se necesita un divisor. Y como queda conectado a la batería para siempre, su corriente es parte del sueño: con 100 kΩ y 100 kΩ gasta 19 µA, más que el procesador dormido; con 1 MΩ y 1 MΩ, menos de 2 µA. El precio de una resistencia grande es que el conversor necesita una fuente de baja impedancia: se agrega un condensador de 100 nF en el pin y se promedian varias lecturas (el programa lee ocho). La herramienta 3 convierte la lectura del pin en tensión, estima el porcentaje y calcula la fuga. El porcentaje es una estimación grosera: la tensión de una celda de litio es plana en el medio de la descarga y depende de la temperatura y de la carga, de modo que conviene enviar los milivolts y decidir en el receptor.

05 · La estructura del programa

Un programa que nunca se queda esperando

Ciclo de vida del nodo Cinco pasos en una fila: despertar, medir, armar el mensaje, publicar y dormir. Una flecha de retorno desde dormir hasta despertar indica que un temporizador del reloj de tiempo real despierta al procesador tras 300 segundos, y que el programa arranca de nuevo desde el principio. Debajo, la secuencia se guarda en la memoria del reloj de tiempo real, que sobrevive al sueño. DespertarMedirArmarPublicarDormir 60 ms25 ms1 ms250 ms≈ 300 s el temporizador despierta al procesador y el programa vuelve a empezar desde setup() memoria RTC: la secuencia se conserva No hay bucle: cada despertar es un arranque nuevo que termina durmiendo.

El nodo no tiene un loop() que haga algo: cada despertar es un arranque completo. Al salir del sueño profundo, el ESP32 pierde toda la memoria de trabajo, reinicia el programa desde el principio (setup()) y solo conserva una pequeña zona de memoria de reloj de tiempo real, marcada con RTC_DATA_ATTR. El programa, entonces, es una receta lineal que termina en esp_deep_sleep_start(), que no retorna:

  1. Despertar: incrementar el contador de secuencia (que está en la memoria RTC).
  2. Medir: iniciar el BME280 en modo forzado, esperar el resultado y leerlo; leer la batería promediando ocho muestras. Si el sensor no responde, usar los valores de «sin dato».
  3. Armar el mensaje de 10 bytes con enteros de tamaño fijo.
  4. Publicar: inicializar BLE, cargar el paquete, publicitar unos 100 ms (varios eventos, porque no hay confirmación) y apagar.
  5. Dormir: programar el despertar por temporizador y entrar en sueño profundo.

La falta de un bucle y de esperas es una decisión de consumo: cada milisegundo despierto cuesta, y por eso el programa no usa delay() salvo cuando espera algo imprescindible (los 100 ms de publicidad). Tampoco abre el puerto serie en producción (Serial.begin alarga el arranque), no inicializa Wi-Fi y apaga lo que enciende. El número de secuencia hace de única memoria: cualquier otro estado (como la última lectura, para enviar solo si cambia) tendría que vivir también en la memoria RTC.

06 · Lo que pasa mientras no pasa nada

Dormir de verdad

«Sueño profundo» es una promesa del procesador, no del montaje. El ESP32 solo, en ese modo, consume unos 10 µA (el reloj de tiempo real y la memoria RTC encendidos); todo lo demás de la placa suma lo suyo. La lista de los culpables habituales es la parte más valiosa del taller:

Qué consume mientras el procesador duerme.
ElementoConsumo típicoQué hacer
Chip USB-serie de la placa de desarrolloAlgunos mAMódulo desnudo o placa pensada para baterías
Regulador lineal común (AMS1117)5 a 10 mA de reposoRegulador con corriente de reposo de µA (MCP1700, HT7333, TPS7A02)
LED de alimentación1 a 5 mAQuitarlo o cortar su pista
Divisor de la bateríaBatería / (R1 + R2): 19 µA con 100 kΩ, 1,9 µA con 1 MΩResistencias de 1 MΩ y condensador (herramienta 3), o un transistor que lo conecte solo al medir
SensorBME280 forzado: < 1 µA; otros sensores, de 10 µA a mAElegir un sensor con modo de reposo, o alimentarlo desde un pin del procesador y apagarlo
Resistencias de elevación de I²C3,3 V / 10 kΩ = 330 µA si una línea queda a nivel bajoDejar las líneas en alto antes de dormir y desconectar el sensor si hace falta
Pines flotantesFugas de µA en entradas sin definirFijar el nivel de los pines que quedan conectados (gpio_hold)

El método para encontrar un culpable es medir, no adivinar: se mide la corriente de sueño del montaje real (sección 7) y, si es mayor que los 10 a 20 µA esperados, se desconectan los componentes de a uno hasta que baje. La herramienta 1 muestra cuánto vale cada microamperio: con 15 µA, el sueño es el 18 % del consumo a 5 minutos y el 73 % a una hora.

Otras particularidades del sueño profundo

  • El reloj interno no es exacto. El temporizador del sueño usa el oscilador RC del chip (unos 150 kHz), con un error de hasta el 5 % y dependiente de la temperatura. Un período de «300 s» es de 285 a 315 s. Para una lectura ambiental no importa; para sincronizar nodos o poner la hora, sí (hace falta un cristal de 32 kHz o sincronizar con el receptor).
  • millis() vuelve a cero en cada despertar, porque es un arranque nuevo. Cualquier cuenta de tiempo larga se hace con el contador de secuencia o con el reloj de tiempo real.
  • El pico de la radio (más de 100 mA durante milisegundos) hunde la tensión de una batería vieja, de una pila pequeña o de un regulador sin condensador, y el ESP32 se reinicia con el detector de bajón de tensión. Condensadores de 10 y 100 µF cerca del módulo y una batería que entregue ese pico son parte del diseño.
  • Hay otras fuentes de despertar: un pin (un botón, un sensor de movimiento, una puerta), el tacto o un coprocesador de ultra bajo consumo. Un nodo que despierta por un evento en lugar de por tiempo consume lo mismo dormido y no gasta ciclos en vano.

07 · Poner a andar el nodo

Plan de pruebas

Se prueba de a una capa, de abajo hacia arriba, y con cada paso se compara con una cifra del presupuesto:

  1. El sensor, en el banco. Con la placa de desarrollo y el puerto serie, comprobar que bme.begin(0x76) responde y que las lecturas son plausibles (la temperatura ambiente, la humedad entre 20 y 80 %). Si no responde, el programa debe seguir y publicar «sin dato»: se prueba desconectando el sensor.
  2. El mensaje, con una aplicación. Con nRF Connect (tema 38), buscar el dispositivo: aparece sin nombre, con Manufacturer data de la empresa 0xFFFF y los 10 bytes. Verificar los bytes con la herramienta 2 y comprobar que la secuencia sube de a uno.
  3. El receptor. Con el programa de Python, comprobar que imprime las lecturas, que descarta las repeticiones (el mismo número de secuencia llega más de una vez) y que cuenta cero pérdidas a un metro.
  4. La corriente de sueño. Alimentar el nodo definitivo desde la batería, con el multímetro en la escala de µA en serie con la alimentación, sin el adaptador USB conectado. Esperar que el nodo duerma (unos segundos). Esperado: 10 a 20 µA; si da miliamperios, hay un culpable de la tabla anterior.
  5. La carga de un ciclo. Medir la corriente en el tiempo con un analizador (o con una resistencia de 10 Ω en serie y un osciloscopio: 70 mA son 0,7 V) y comparar el área con los 19,9 mC del presupuesto. Los tiempos de cada fase salen del gráfico, y a menudo difieren de lo supuesto (la inicialización de BLE puede tardar el doble).
  6. El alcance. Alejar el nodo del receptor y mirar la tasa de pérdidas por la secuencia (herramienta 4) y el RSSI: es la medición de campo del tema 43, con una tasa de pérdida real. Con la publicidad sin confirmación, una pérdida de pocos por ciento es normal en interiores.
  7. La batería. Comparar el valor enviado con el de un multímetro en la batería: si difieren más de unos 50 mV, se calibra (la lectura del conversor del ESP32 no es lineal y varía de un chip a otro).
  8. La duración. No se espera dos años: se mide la corriente media durante unas horas y se calcula. Si coincide con el presupuesto (± 20 %), el diseño está validado.
Qué medir y qué esperar.
MediciónInstrumentoValor esperadoSi da distinto
Corriente de sueñoMultímetro en µA, en serie10 a 20 µABuscar el elemento de la tabla de la sección 6
Tiempo despiertoOsciloscopio con una resistencia en serie, o el analizadorUnos 300 msQuitar el puerto serie, acortar la inicialización de la radio
Carga por cicloAnalizador de corriente (o el área en el osciloscopio)Cerca de 20 mCMedir cada fase por separado
Paquetes por ciclonRF Connect o el receptor3 a 4 eventos de publicidadAjustar el tiempo de publicidad
PérdidasReceptor con secuenciaMenos del 5 % a pocos metrosAntena, orientación, repetir el envío

08 · Con el microcontrolador

El código completo

El programa es el que sigue. Usa las bibliotecas Adafruit BME280 (con Adafruit Unified Sensor) y el soporte de BLE del paquete de placas ESP32 (núcleo 3.x; en el 2.x, algunas funciones usan std::string en lugar de String).

#include <Wire.h>
#include <Adafruit_BME280.h>
#include <BLEDevice.h>
#include <BLEAdvertising.h>
#include <esp_sleep.h>

// ---------- Configuración ----------
const uint8_t  NODO = 1;
const uint32_t PERIODO_S = 300;                                // segundos de sueño entre mediciones
const uint8_t  PIN_BAT = 34;                                   // punto medio del divisor (ADC1)
const float    DIVISOR = 2.0;                                  // R1 = R2: la batería es el doble de lo que lee el pin
const uint16_t TEMP_SIN_DATO = 0x8000;
const uint16_t HUM_SIN_DATO = 0xFFFF;
#define DEPURAR 1                                              // 0 en producción: el puerto serie alarga el arranque

RTC_DATA_ATTR uint16_t secuencia = 0;                          // sobrevive al sueño profundo (la memoria común no)

// Formato del mensaje: el mismo que lee el receptor
struct __attribute__((packed)) Mensaje {
    uint8_t  version;
    uint8_t  nodo;
    uint16_t secuencia;
    uint16_t temperatura;                                      // centésimas de grado, entero de 16 bits con signo
    uint16_t humedad;                                          // centésimas de por ciento
    uint16_t bateria_mv;
};

Adafruit_BME280 bme;

// ---------- Medición ----------
void medir(Mensaje &m) {
    m.temperatura = TEMP_SIN_DATO;                             // si el sensor falla, el mensaje sale igual
    m.humedad = HUM_SIN_DATO;
    if (bme.begin(0x76)) {
        bme.setSampling(Adafruit_BME280::MODE_FORCED,         // mide una vez y se duerme solo
                        Adafruit_BME280::SAMPLING_X1,
                        Adafruit_BME280::SAMPLING_NONE,       // sin presión
                        Adafruit_BME280::SAMPLING_X1,
                        Adafruit_BME280::FILTER_OFF);
        if (bme.takeForcedMeasurement()) {
            m.temperatura = (uint16_t)(int16_t)lroundf(bme.readTemperature() * 100);
            m.humedad = (uint16_t)lroundf(bme.readHumidity() * 100);
        }
    }
    uint32_t suma = 0;
    for (int i = 0; i < 8; i++) suma += analogReadMilliVolts(PIN_BAT);   // el promedio compensa la impedancia alta del divisor
    m.bateria_mv = (uint16_t)(suma / 8 * DIVISOR);
}

// ---------- Publicación por BLE ----------
void publicar(const Mensaje &m) {
    BLEDevice::init("");                                       // sin nombre: ahorra bytes en el paquete
    BLEAdvertising *pub = BLEDevice::getAdvertising();
    BLEAdvertisementData datos;
    datos.setFlags(0x06);                                      // descubrible, sin Bluetooth clásico
    const uint8_t empresa[2] = { 0xFF, 0xFF };                 // identificador reservado para pruebas
    String fabricante;
    fabricante.concat((const char *)empresa, 2);
    fabricante.concat((const char *)&m, sizeof(m));            // concat con largo: acepta bytes en cero
    datos.setManufacturerData(fabricante);
    pub->setAdvertisementData(datos);
    pub->setAdvertisementType(ADV_TYPE_NONCONN_IND);           // solo difunde: no admite conexión
    pub->start();
    delay(100);                                                // con intervalos de 20 a 40 ms, tres o cuatro eventos
    pub->stop();
}

// ---------- Programa ----------
void setup() {
#if DEPURAR
    Serial.begin(115200);
#endif
    Mensaje m = { 1, NODO, secuencia++, 0, 0, 0 };
    Wire.begin();                                              // I²C en los pines 21 y 22
    medir(m);
    publicar(m);
#if DEPURAR
    Serial.printf("n.º %u: %d centésimas de grado, %u de humedad, %u mV\n", m.secuencia, (int16_t)m.temperatura, m.humedad, m.bateria_mv);
    Serial.flush();
#endif
    esp_sleep_enable_timer_wakeup(PERIODO_S * 1000000ULL);
    esp_deep_sleep_start();                                    // no retorna: al despertar, el programa empieza otra vez
}

void loop() {}                                                 // nunca se ejecuta

Qué hay que notar:

  • Un solo flujo, sin bucle. setup() hace todo y termina en esp_deep_sleep_start(). El contador secuencia se incrementa antes de armar el mensaje y sobrevive al sueño gracias a RTC_DATA_ATTR. Tras un reinicio completo (se quitó la batería) vuelve a cero, y el receptor lo ve como un salto: es normal.
  • El mensaje es una estructura empaquetada (packed): sus 10 bytes son exactamente los de la tabla y en el orden del ESP32 (little-endian). La temperatura, un entero con signo, se guarda en un campo sin signo para copiar los bits tal cual; el receptor la reinterpreta con signo.
  • La falta de dato no detiene nada: si el BME280 no responde, el mensaje sale con los valores reservados. Un nodo que se queda mudo es mucho más difícil de diagnosticar que uno que avisa que su sensor falló.
  • El modo forzado mide una vez y devuelve el sensor al reposo: no hay que apagarlo. SAMPLING_NONE en la presión ahorra tiempo.
  • Se publica 100 ms con un tipo no conectable. Como no hay confirmación, se envía en varios eventos (cada uno en tres canales) para tener varias oportunidades de llegar. El mismo número de secuencia llega repetido, y es el receptor quien lo descarta.
  • Con DEPURAR en 0 desaparecen el puerto serie y su demora: es una de las primeras cosas que se hacen al medir consumo. Serial.flush() evita que el sueño corte el último mensaje.
  • La API de BLE cambia entre versiones del núcleo ESP32 (el tipo de cadena de setManufacturerData, entre otros). Si no compila, revisá los ejemplos de la biblioteca o usá NimBLE-Arduino, que gasta menos memoria y arranca más rápido.

La variante por LoRa

Para más alcance, la función publicar se reemplaza por una que use un módulo SX1276 con la biblioteca RadioLib (tema 39). El resto del programa (medición, mensaje, sueño) no cambia, porque el mensaje es el mismo:

#include <RadioLib.h>

SX1276 radio = new Module(5, 26, 14, 33);                      // CS, DIO0, RST, DIO1 (según el cableado)

void publicar(const Mensaje &m) {
    // 868,0 MHz en Europa; 915,0 en América. SF9, 125 kHz, codificación 4/5, 14 dBm
    int estado = radio.begin(868.0, 125.0, 9, 5, RADIOLIB_SX127X_SYNC_WORD, 14);
    if (estado == RADIOLIB_ERR_NONE) {
        radio.transmit((uint8_t *)&m, sizeof(m));              // 10 bytes: unos 144 ms en el aire con SF9
    }
    radio.sleep();                                             // el módulo duerme con menos de 1 µA
}

Con SF9 y 10 bytes, el mensaje dura unos 144 ms en el aire (tema 39), y con 35 mA son unos 5,4 mC: menos que los 17,5 mC de la radio de BLE en el ESP32, porque BLE gasta la mayor parte en inicializarse. Conviene recordar la otra cara: el alcance es de kilómetros, pero no hay teléfono que lo reciba, y el receptor necesita su propio módulo LoRa (o una puerta de enlace). Con el 1 % de ciclo de trabajo de Europa, un mensaje de 144 ms permite uno cada 14,4 s como mínimo: de sobra para uno cada 5 minutos.

09 · Ver lo que pasa

El receptor y la medición

El receptor es un programa de Python que escucha la publicidad con la biblioteca bleak (pip install bleak). Aplica lo del taller: valida el formato (empresa, largo y versión), descarta las repeticiones, cuenta las pérdidas por la secuencia y muestra el RSSI (la potencia recibida), que da una idea del margen del enlace.

import asyncio
import struct
from bleak import BleakScanner

ID_EMPRESA = 0xFFFF
ultimo = {}                                                    # última secuencia recibida por nodo
perdidos = {}


def procesar(dispositivo, anuncio):
    datos = anuncio.manufacturer_data.get(ID_EMPRESA)
    if datos is None or len(datos) != 10 or datos[0] != 1:     # lo que llega por el aire no es confiable
        return
    version, nodo, secuencia, t, h, bateria = struct.unpack("<BBHhHH", datos)
    if nodo in ultimo:
        salto = (secuencia - ultimo[nodo]) & 0xFFFF               # el contador es de 16 bits y da la vuelta
        if salto == 0 or salto >= 0x8000:
            return                                             # repetición: el mismo paquete llega en varios eventos
        perdidos[nodo] = perdidos.get(nodo, 0) + salto - 1
    ultimo[nodo] = secuencia
    temp = "sin dato" if t == -32768 else f"{t / 100:.2f} °C"
    hum = "sin dato" if h == 0xFFFF else f"{h / 100:.2f} %"
    print(f"nodo {nodo}, n.º {secuencia}: {temp}, {hum}, {bateria} mV, {anuncio.rssi} dBm, perdidos {perdidos.get(nodo, 0)}")


async def main():
    async with BleakScanner(detection_callback=procesar):
        await asyncio.sleep(3600)                              # escuchar una hora


asyncio.run(main())

La secuencia hace posible algo que parecía imposible con un nodo que solo habla: medir la calidad del enlace sin ningún tráfico de vuelta. Con ese dato se decide si hace falta repetir cada lectura, acercar el receptor o cambiar de tecnología. En un sistema real, el receptor sería una puerta de enlace que reenvía cada lectura por MQTT (tema 32) a una base de datos.

Instrumentos de medición

Cómo medir el consumo de un nodo que duerme.
InstrumentoQué mide bienLimitaciones
Multímetro en µALa corriente de sueño (estable)No ve los picos (promedia), tiene una resistencia en serie que hunde la tensión del nodo, y en la escala de mA se pasa de rango y reinicia el nodo
Resistencia de 10 Ω y osciloscopioLa forma del ciclo: tiempos y picosNo ve el sueño (10 µA son 100 µV) y la resistencia también cae tensión
Analizador de corriente (Nordic PPK2, Joulescope o similar)Todo: del nanoamperio al amperio, con integración de la cargaCuesta dinero, pero es la herramienta que transforma el trabajo
Medidor USBNada útil para este proyectoResolución de mA y muestreo lento: no ve ni el sueño ni los picos

Con el multímetro, un método útil es medir la energía de un ciclo largo: poner un condensador grande como fuente, o mirar cuánto cae la tensión de la batería de prueba en un día. Pero en la práctica, un analizador de corriente es la inversión que más rinde cuando el proyecto es de bajo consumo: se ve en un gráfico qué fase pesa y se corrige en el acto.

10 · Analogía con programación

El nodo en Python

La lógica del nodo (el presupuesto, el mensaje, la batería, las pérdidas y el período) se prueba sin hardware. El modelo siguiente reproduce las reglas del programa y de la página, y es la base del diseño: se ajustan los números hasta que el presupuesto cierra, y recién entonces se conecta el sensor.

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

import struct

I_ARRANQUE, I_MEDICION = 30, 25                              # mA


def presupuesto(periodo_s, arranque_ms, medicion_ms, radio_ms, i_radio_ma, sueno_ua, capacidad_mah, util=0.8):
    activo_ms = arranque_ms + medicion_ms + radio_ms
    q = (arranque_ms * I_ARRANQUE + medicion_ms * I_MEDICION + radio_ms * i_radio_ma) / 1000    # mC por ciclo
    q_sueno = sueno_ua / 1000 * (periodo_s - activo_ms / 1000)
    media_ma = (q + q_sueno) / periodo_s
    return q, media_ma * 1000, capacidad_mah * util / media_ma / 24    # mC, µA, días


def empaquetar(version, nodo, secuencia, temperatura, humedad, bateria_mv):
    return struct.pack("<BBHhHH", version, nodo, secuencia, round(temperatura * 100), round(humedad * 100), bateria_mv)


def desempaquetar(datos):
    version, nodo, secuencia, t, h, bateria = struct.unpack("<BBHhHH", datos)
    return {"version": version, "nodo": nodo, "secuencia": secuencia, "temperatura": t / 100, "humedad": h / 100, "bateria_mv": bateria}


def publicidad(cuerpo):
    return bytes([0x02, 0x01, 0x06, len(cuerpo) + 3, 0xFF, 0xFF, 0xFF]) + cuerpo     # banderas y datos del fabricante (ID 0xFFFF)


def bateria_mv(pin_mv, r1_k, r2_k):
    return pin_mv * (r1_k + r2_k) / r2_k


def fuga_ua(bat_mv, r1_k, r2_k):
    return bat_mv / (r1_k + r2_k)                           # mV sobre kΩ son µA


def contar_perdidas(secuencias):
    ultimo, perdidos, repetidos = secuencias[0], 0, 0
    for s in secuencias[1:]:
        salto = (s - ultimo) & 0xFFFF                        # el contador es de 16 bits y da la vuelta
        if salto == 0 or salto >= 0x8000:
            repetidos += 1
            continue
        perdidos += salto - 1
        ultimo = s
    return perdidos, repetidos


def periodo_minimo_s(capacidad_mah, util, anios, q_mc, sueno_ua):
    i_max_ma = capacidad_mah * util / (anios * 8766)
    return q_mc / (i_max_ma - sueno_ua / 1000)


for periodo in (300, 60, 3600):
    q, ua, dias = presupuesto(periodo, 60, 25, 250, 70, 15, 2000)
    print(f"cada {periodo:>4} s: {q:.2f} mC por ciclo, {ua:6.1f} µA de media, {dias:6.0f} días ({dias / 365:.1f} años)")
q, ua, dias = presupuesto(300, 60, 25, 60, 70, 15, 2000)
print(f"radio de 60 ms: {q:.2f} mC, {ua:.1f} µA, {dias / 365:.1f} años")
q, ua, dias = presupuesto(300, 60, 25, 250, 70, 5000, 2000)
print(f"sueño de 5 mA (placa de desarrollo): {ua:.0f} µA, {dias:.1f} días")

cuerpo = empaquetar(1, 1, 1234, 23.5, 55.0, 3800)
print("mensaje:", cuerpo.hex(" ").upper(), len(cuerpo), "bytes")
print("paquete:", publicidad(cuerpo).hex(" ").upper(), len(publicidad(cuerpo)), "de 31 bytes")
print("al leerlo:", desempaquetar(cuerpo))
print("-5,25 °C:", empaquetar(1, 1, 65535, -5.25, 55.0, 3800).hex(" ").upper())

print("batería con 1900 mV en el pin (1 MΩ y 1 MΩ):", bateria_mv(1900, 1000, 1000), "mV, fuga", round(fuga_ua(3800, 1000, 1000), 2), "µA")
print("con 100 kΩ y 100 kΩ, fuga", round(fuga_ua(3800, 100, 100), 1), "µA")

lecturas = [65530, 65531, 65531, 65533, 65534, 65535, 0, 1, 1, 5, 6]
print("perdidos y repetidos:", contar_perdidas(lecturas))

print(f"período mínimo para 2 años con 2000 mAh: {periodo_minimo_s(2000, 0.8, 2, 19.9, 15):.0f} s")
print(f"para 5 años: {periodo_minimo_s(2000, 0.8, 5, 19.9, 15) / 60:.1f} min")

Resultado: con un ciclo de 60 ms de arranque, 25 de medición y 250 de radio a 70 mA, 15 µA de sueño y 2000 mAh (80 % útil), cada ciclo gasta 19,93 mC y la corriente media es de 81,4 µA a 300 s (819 días, 2,2 años), 347,0 µA a 60 s (192 días, 0,5 años) y 20,5 µA a 3600 s (3247 días, 8,9 años). Con la radio en 60 ms, 6,62 mC, 37,1 µA y 4,9 años. Con 5 mA de sueño (la placa de desarrollo), 5061 µA y 13,2 días. El mensaje es 01 01 D2 04 2E 09 7C 15 D8 0E (10 bytes), el paquete de publicidad, 02 01 06 0D FF FF FF 01 01 D2 04 2E 09 7C 15 D8 0E (17 de 31 bytes), y al leerlo se recupera nodo 1, secuencia 1234, 23,5 °C, 55,0 % y 3800 mV; con −5,25 °C y secuencia 65 535, 01 01 FF FF F3 FD 7C 15 D8 0E. Con 1900 mV en el pin y dos resistencias de 1 MΩ la batería es de 3800 mV y la fuga, 1,9 µA; con 100 kΩ y 100 kΩ, 19,0 µA. La lista de secuencias 65530, 65531, 65531, 65533, 65534, 65535, 0, 1, 1, 5, 6 tiene 4 mensajes perdidos y 2 repetidos. El período mínimo para 2 años con 2000 mAh es de 261 s y para 5 años, de 15,4 minutos.

La lección que sale del modelo es la que el taller repite: la tecnología de radio decide menos que el montaje. Pasar de la placa de desarrollo a un módulo con un regulador adecuado cambia la duración de 13 días a años; pasar de BLE a LoRa cambia la carga de radio en un factor de tres; pasar de un divisor de 100 kΩ a uno de 1 MΩ ahorra 17 µA. Nada de eso se ve en el código del sensor.

11 · Exploración interactiva

Laboratorio: ciclo, mensaje, batería, pérdidas y período

El laboratorio tiene cinco herramientas. La primera calcula el presupuesto de energía de un ciclo y la duración de la batería. La segunda arma el mensaje de 10 bytes y su paquete de publicidad. La tercera convierte la lectura del divisor de la batería y calcula lo que gasta. La cuarta cuenta las pérdidas a partir de una lista de números de secuencia. La quinta calcula, al revés, el período mínimo para una duración deseada.

1. Presupuesto de energía

Carga por ciclo—
Corriente media—
Duración—
Reparto de la energía—
Veredicto—

Elegí los tiempos y la corriente de cada fase.

2. Mensaje y paquete de publicidad

Mensaje—
Paquete de publicidad—
Tiempo en el aire (1 Mbit/s)—
El receptor lo lee así—
Veredicto—

Escribí una lectura.

3. Divisor para medir la batería

Tensión de la batería—
Carga estimada—
Rango del pin—
Corriente del divisor—
Veredicto—

Elegí las resistencias y la lectura.

4. Pérdidas por número de secuencia

Mensajes recibidos—
Repeticiones—
Perdidos—
Tasa de pérdida—
Mayor racha perdida—
Veredicto—

Escribí los números de secuencia.

5. Período mínimo para una duración objetivo

Corriente media permitida—
Sueño—
Período mínimo—
Mensajes por día—
Veredicto—

Elegí la batería y la duración.

En la herramienta 1, el arranque se calcula a 30 mA y la medición a 25 mA, y la corriente de sueño es la de todo el nodo (procesador, regulador, divisor y sensor). En la 2, el tiempo en el aire cuenta el preámbulo, la dirección de acceso, la cabecera, la dirección del dispositivo y el CRC a 1 Mbit/s. En la 3, el pin admite hasta unos 3100 mV. En la 4, el contador da la vuelta en 65 535. La 5 usa 8766 horas por año.

Recorrido guiado

  1. En la herramienta 1, con los valores iniciales: 19,93 mC por ciclo (1,80 + 0,63 + 17,50) con 335 ms despierto, 81,4 µA de media y 2,2 años; la radio pesa el 71,7 %. Con 60 s de período: 347 µA y 192 días (en rojo). Con 3600: 20,5 µA y 8,9 años, y manda el sueño (73 %). Con la radio en 60 ms: 6,63 mC, 37,1 µA y 4,9 años.
  2. Con un sueño de 5000 µA (la placa de desarrollo): 5061 µA, 13,2 días y el sueño es el 98,7 % del total. Con 3000 ms de arranque y 3000 de medición (un programa con esperas con delay): 182,5 mC por ciclo, 623 µA y 107 días: el ciclo despierto vuelve a mandar. El período mínimo que admite la herramienta es de 10 s.
  3. En la herramienta 2, con los valores iniciales: 01 01 D2 04 2E 09 7C 15 D8 0E, el paquete 02 01 06 0D FF FF FF … de 17 de 31 bytes, 264 µs por paquete (792 µs en los tres canales) y la lectura exacta. Con −5,25 °C y la secuencia 65535: FF FF y F3 FD. Con 23.456: el receptor lee 23,46 °C («se recupera con la resolución de 0,01»). Con humedad 101: valor inválido.
  4. En la herramienta 3, con 1000 kΩ, 1000 kΩ y 1900 mV: 3800 mV, 55 %, y una fuga de 1,90 µA (el 0,2 de lo que gasta el ESP32 dormido). Con 100 y 100 kΩ: la misma lectura, pero 19 µA de fuga (1,9 veces el sueño del ESP32) en rojo. Con 1650 mV y 1 MΩ: 3300 mV y 3 %, la batería casi agotada.
  5. En la herramienta 4, con la lista inicial: 9 mensajes distintos, 2 repeticiones, 4 perdidos, 30,8 % de pérdida y una racha de 3. Con 1 2 3 4 5: sin pérdidas. Con 10 11 14 20 21 22 23 24 25 30: 11 perdidos y 52,4 %. Con un solo número: falta información.
  6. En la herramienta 5, con los valores iniciales (2000 mAh, 80 %, 2 años, 19,9 mC, 15 µA): corriente media permitida de 91,3 µA y un período mínimo de 4,3 min (331 mensajes por día). Con 5 años: 36,5 µA y 15,4 min. Con un sueño de 400 µA: «imposible». Con una carga de 4 mC (la radio en 60 ms): 52 s. Con 220 mAh (una pila pequeña) y 1 año: 20,1 µA y 1,1 h entre mensajes.
Desafío: la estación meteorológica del techo

Un cliente quiere cinco nodos como este en el techo de una casa, que reporten cada 5 minutos a un receptor ubicado en el interior (a unos 15 m, con una losa de por medio) y que no se les cambie la batería en dos años. Con la herramienta 1: los valores iniciales dan 81,4 µA y 2,2 años con 2000 mAh y 80 % útil, pero (1) el frío reduce la capacidad de una celda de litio a casi la mitad en invierno (a −10 °C), así que el 80 % es optimista: con el 50 % la duración baja a 1,4 años. (2) La temperatura del sueño también influye: las fugas de los reguladores aumentan con el calor. (3) El enlace: con 15 m y una losa, la publicidad de BLE llega con poco margen; hay que medir el RSSI y la pérdida (herramienta 4), y si es mala, repetir cada envío o acercar el receptor. (4) Cinco nodos comparten el medio: con 335 ms cada 300 s, la probabilidad de que dos se pisen es pequeña (menos del 1 %), y la secuencia por nodo permite medirla. (5) La hora: como cada nodo mide cada «300 s ± 5 %», los instantes de medición no están sincronizados: el receptor pone el sello de tiempo al recibir. Soluciones realistas: una celda mayor con una protección contra el frío, baja potencia de los reguladores, un receptor mejor ubicado y, si el techo queda lejos, pasar a LoRa para cubrir con margen (tema 39).

12 · Qué viene después

Ampliaciones y buenas prácticas

  • Repetir el envío. Si la pérdida medida es alta, publicar más tiempo o enviar cada lectura dos veces con la misma secuencia: cuesta energía, pero sube la probabilidad de llegada.
  • Enviar solo si cambia. Guardar la última lectura en la memoria RTC y publicar solo cuando la diferencia supere un umbral, con un envío periódico de «sigo vivo» (latido): una forma de ahorro muy eficaz en magnitudes que cambian poco.
  • Medir más seguido que enviar. Medir cuesta unos 0,6 mC y enviar, 17 mC. Un nodo puede guardar varias lecturas en la memoria RTC y mandarlas juntas: con 12 lecturas por hora se envía una vez, y el costo de la radio se reparte (el tamaño de un paquete de publicidad, 31 bytes, limita cuántas entran; con conexión o con publicidad extendida se pueden enviar más).
  • Cifrado y autenticación. La publicidad es pública. Si los datos importan, se cifran en la aplicación (por ejemplo, AES con el contador de secuencia como parte del vector inicial), y el receptor rechaza las repeticiones; es el contenido del tema 49.
  • Un modo de configuración. Publicar de forma conectable durante unos segundos tras un reinicio (con un botón) para cambiar el período o el número de nodo con una característica GATT (tema 38), y guardar el valor en la memoria no volátil.
  • Perro guardián. Un reinicio por tiempo si el programa se cuelga: en un dispositivo desatendido, un cuelgue con el procesador despierto agota la batería en días.
  • Más sensores. Presión y gas del propio BME280 (SAMPLING_X1 en lugar de NONE, a costa de unos milisegundos), luz, movimiento por despertar de pin. Cada sensor suma al sueño: se elige el que tenga modo de reposo.
  • Un SoC de BLE de bajo consumo. Con un nRF52 o un EFR32, la fase de radio baja a unos 3 ms a 8 mA (24 µC, lo que se vio en el tema 38): el mismo diseño dura diez años con una pila de botón. Es la ampliación que más cambia el resultado.
  • Energía del ambiente. Una celda solar pequeña y un supercondensador o una batería recargable: con 20 µA de media, una celda de pocos centímetros cuadrados alcanza.
  • Gestión de la flota. Con muchos nodos, la tensión de la batería y la tasa de pérdida de cada uno, publicadas por el receptor por MQTT, avisan cuál hay que atender antes de que muera.

Con este taller termina la parte de comunicación inalámbrica. Lo que sigue en el curso es lo que viaja dentro de cualquiera de estos enlaces: cómo se diseña un protocolo propio, cómo se direcciona un paquete, cómo se detectan y corrigen los errores. El mensaje de 10 bytes con versión, nodo y secuencia de este taller es un primer ejemplo, y los temas 41 a 47 lo convierten en un método.

13 · Comprensión

Confusiones frecuentes

«Con deep sleep, mi placa de desarrollo consume microamperios»

El procesador sí, la placa no. El chip USB-serie, el regulador y el LED gastan miliamperios y no se apagan con el sueño del procesador. Con 5 mA de reposo, 2000 mAh duran menos de dos semanas. Para medir bien hace falta un módulo desnudo o una placa pensada para baterías.

«La batería dura su capacidad dividida por la corriente»

Eso es el máximo teórico. Solo se aprovecha una parte (80 % o menos, y mucho menos con frío o con picos altos), la autodescarga actúa todo el tiempo y la capacidad envejece. La cuenta se hace con la capacidad útil, y se valida midiendo.

«Lo que más gasta es la transmisión»

Casi nunca la transmisión en sí: es inicializar la radio, esperar a que el procesador arranque y mantener todo encendido. En este nodo, los 250 ms de radio son el 72 % de la energía, y el paquete tarda menos de 1 ms por canal. Se mide cada fase antes de optimizar.

«Medir la batería no cuesta nada»

El divisor está siempre conectado: con 100 kΩ y 100 kΩ gasta 19 µA, el doble que el ESP32 dormido. Se usan resistencias de 1 MΩ y un condensador, o un transistor que conecte el divisor solo al medir.

«El multímetro me da la corriente media del nodo»

En µA mide bien el sueño, pero no sigue los picos, mete una resistencia en serie que hunde la tensión de alimentación y, si el nodo despierta y la escala es de µA, puede protegerse con un fusible o reiniciar el nodo. Para el ciclo hace falta un analizador de corriente o un osciloscopio.

«Si no hay confirmación, no puedo saber si llegaron los datos»

Sí se puede: con un número de secuencia, el receptor detecta los saltos y mide la tasa de pérdida sin ningún tráfico de vuelta. Lo que no puede hacer el nodo es saberlo él mismo; si necesita saberlo, hace falta un canal de retorno (un ACK, como en el tema 45).

«Para no perder datos, envío más seguido»

Cada envío cuesta 20 mC: duplicar la frecuencia casi duplica el consumo y acorta la vida a la mitad. Para no perder datos se repite el envío de la misma lectura, se guarda un histórico o se mejora el enlace; no se miden más lecturas de las que hacen falta.

«El temporizador del sueño es exacto»

El oscilador interno tiene un error de hasta el 5 % y depende de la temperatura: un período de 300 s es de 285 a 315 s. Si se necesita una hora real, se sincroniza con el receptor o se usa un cristal de 32 kHz.

«Los datos que viajan por publicidad son seguros porque nadie conoce el formato»

Cualquiera con un teléfono ve los bytes, y el formato se deduce en minutos. La publicidad es pública: si el contenido es sensible se cifra, y si el receptor actúa sobre él se autentica y se protege contra la repetición (tema 49).

«LoRa gasta menos que BLE»

Depende del chip y del tiempo despierto, no del protocolo. En este diseño, LoRa con un SX1276 gasta menos carga de radio (unos 5,4 mC contra 17,5 mC) porque el ESP32 pasa mucho tiempo inicializando BLE. Un SoC de BLE de bajo consumo gasta menos que los dos (24 µC).

14 · Práctica guiada

Predecí antes de comprobar

1. Presupuesto

Un nodo tiene 60 ms de arranque a 30 mA, 25 ms de medición a 25 mA y 100 ms de radio a 70 mA, con 15 µA de sueño, mide cada 600 s y usa una batería de 2000 mAh con el 80 % útil. ¿Cuál es la carga por ciclo, la corriente media y la duración?

Ver solución

Arranque: 1,8 mC; medición: 0,625 mC; radio: 7,0 mC; total 9,43 mC. Sueño: 0,015 mA · (600 − 0,185) s ≈ 9,0 mC. Total por período: 18,4 mC; media: 18,4 / 600 = 30,7 µA. Duración: 1600 / 0,0307 ≈ 52 100 h, unos 2171 días (5,9 años). Es casi tres veces más que con 250 ms de radio y 300 s.

2. Un mensaje

Armá los 10 bytes del nodo 3, secuencia 258, 30,00 °C, 40,00 % y 4100 mV.

Ver solución

Versión 1 y nodo 3: 01 03. La secuencia 258 es 0x0102: 02 01. La temperatura 3000 es 0x0BB8: B8 0B. La humedad 4000 es 0x0FA0: A0 0F. La batería 4100 es 0x1004: 04 10. El mensaje: 01 03 02 01 B8 0B A0 0F 04 10.

3. El divisor

Con resistencias de 470 kΩ y 470 kΩ y una lectura de 2000 mV en el pin, ¿cuál es la tensión de la batería, qué porcentaje es y cuánto gasta el divisor?

Ver solución

Batería = 2000 · (470 + 470) / 470 = 4000 mV, un 82 % según la curva de la herramienta. El divisor gasta 4000 mV / 940 kΩ ≈ 4,3 µA: menos que el sueño del ESP32, pero no despreciable (un tercio del presupuesto de sueño de 15 µA). Con 1 MΩ serían 2 µA.

4. Pérdidas

El receptor ve las secuencias 100, 101, 103, 103, 104, 107. ¿Cuántos mensajes llegaron, cuántos se perdieron y cuál es la tasa?

Ver solución

Llegaron 100, 101, 103, 104 y 107: 5 distintos, con un 103 repetido. Se perdieron el 102, el 105 y el 106: 3. La tasa es 3 de 8: 37,5 %, con una racha máxima de 2.

5. Período mínimo

Con 2000 mAh (80 % útil), 3 años de duración, 9,4 mC por ciclo y 15 µA de sueño, ¿cada cuánto se puede medir?

Ver solución

Corriente media permitida: 1600 / (3 · 8766) ≈ 0,0608 mA = 60,8 µA. Quedan 60,8 − 15 = 45,8 µA para los ciclos: período = 9,4 mC / 0,0458 mA ≈ 205 s, unos 3,4 minutos entre mensajes como mínimo.

6. Un diagnóstico

Un nodo medido con el multímetro da 8 mA dormido. Con 2000 mAh y 80 % útil, ¿cuánto dura y qué se hace?

Ver solución

1600 / 8 = 200 h: 8,3 días. Algo más que el procesador está encendido: 8 mA es típico del chip USB-serie, el regulador lineal y el LED de una placa de desarrollo. Se pasa a un módulo desnudo con un regulador de bajo consumo, se quita el LED y se vuelve a medir; el objetivo son 10 a 20 µA.

7. BLE o LoRa

Para el mismo nodo, ¿cuánta carga gasta la fase de radio con LoRa (SF9, 10 bytes, con unos 10 ms de preparación del módulo a 35 mA) y cómo se compara con los 17,5 mC de BLE? ¿Qué otra condición hay que mirar?

Ver solución

Tiempo en el aire con SF9 y 10 bytes: (12,25 + 23) · 4,096 ms ≈ 144 ms; con la preparación, unos 154 ms a 35 mA: 5,4 mC, un tercio de los 17,5 mC de BLE en el ESP32. A cambio, el receptor tiene que ser otro módulo LoRa (o una puerta de enlace) y no sirve un teléfono, y hay que respetar el ciclo de trabajo (un mensaje de 144 ms con el 1 % obliga a esperar 14,4 s, que sobran con 300 s de período). La elección depende de quién recibe y a qué distancia, no solo de la energía.

15 · Síntesis

Ideas para recordar

  • Un sensor inalámbrico con batería es un ciclo breve y un sueño largo. Se diseña con un presupuesto: la carga de cada fase (corriente por tiempo) más la del sueño, repartida en el período, da la corriente media, y la capacidad útil dividida por ella da la duración. Se calcula antes de programar.
  • En el nodo del taller (arranque de 60 ms a 30 mA, medición de 25 ms, radio de 250 ms a 70 mA y 15 µA de sueño, cada 300 s), la corriente media es de 81,4 µA y la duración, de 2,2 años con 2000 mAh. La radio es el 72 % de la energía, por inicializarse y no por transmitir.
  • El sueño profundo es una propiedad del montaje, no del procesador: una placa de desarrollo (chip USB, regulador lineal, LED) consume miliamperios y reduce la vida a días. Se usa un módulo desnudo, un regulador de reposo de µA, un sensor con modo de reposo y se miden los pines flotantes.
  • Medir la batería cuesta: un divisor de 100 kΩ gasta 19 µA, más que el procesador dormido. Con resistencias de 1 MΩ y un condensador de 100 nF baja a menos de 2 µA. El porcentaje de carga es una estimación; mejor enviar los milivolts.
  • El mensaje es de tamaño fijo, con enteros de 16 bits en little-endian, una versión, un identificador de nodo, un valor reservado para «sin dato» y un número de secuencia. El mensaje de 10 bytes cabe en 17 de los 31 bytes de un paquete de publicidad.
  • El número de secuencia permite medir las pérdidas sin ningún tráfico de vuelta: una diferencia (módulo 65 536) de 0 es una repetición, de 1 es lo normal y de más son mensajes perdidos. Con publicidad sin confirmación es la única forma de conocer la calidad del enlace.
  • El programa no tiene bucle: cada despertar es un arranque nuevo, mide, publica y duerme. Solo la memoria RTC (RTC_DATA_ATTR) conserva el contador. El reloj interno tiene un error de hasta 5 %, millis() vuelve a cero y los picos de la radio exigen condensadores y una batería que los entregue.
  • Con LoRa, el resto del programa no cambia: solo la función de publicar. Con SF9 y 10 bytes, la radio gasta unos 5,4 mC contra los 17,5 de BLE en un ESP32, pero se necesita otro módulo LoRa como receptor. Con un SoC de BLE de bajo consumo, la radio baja a unos 24 µC.
  • Se verifica con instrumentos: multímetro en µA para el sueño, analizador de corriente u osciloscopio para el ciclo, nRF Connect y un receptor con secuencia para el mensaje y las pérdidas. La cuenta inversa dice cada cuánto se puede medir para una duración dada: 261 s para dos años y 15 minutos para cinco.

¿Listo para el siguiente tema?

Antes de avanzar, explicá por qué una placa de desarrollo no sirve para un nodo con batería aunque use el sueño profundo; calculá la corriente media de un nodo con 20 mC por ciclo, 15 µA de sueño y un período de 10 minutos; y decidí qué campo agregarías al mensaje para que el receptor detecte las pérdidas y cómo lo calcularía con un contador que da la vuelta. Si podés justificar las tres respuestas, entendés cómo se diseña un sensor de bajo consumo.

En el próximo tema se abre una nueva parte del curso: el diseño de protocolos propios. El mensaje de este taller (versión, nodo, secuencia, datos) es un primer ejemplo; ahora se estudia el método: delimitadores, campos de longitud y tipo de mensaje, la máquina de estados de recepción y los criterios para que un formato sobreviva al paso del tiempo.