You dont have javascript enabled! Please enable it!

Caso práctico: Telemetría agro LoRa bajo consumo con ESP32

Caso práctico: Telemetría agro LoRa bajo consumo con ESP32 — hero

Objetivo y caso de uso

Qué construirás: Un nodo sensor de bajo consumo para monitoreo ambiental utilizando TTGO LoRa32, BME280 y ADS1115.

Para qué sirve

  • Monitoreo de temperatura y humedad en invernaderos usando BME280.
  • Medición de niveles de agua en cultivos mediante ADS1115.
  • Transmisión de datos de sensores a través de LoRa para largas distancias.
  • Integración con sistemas de alerta temprana para condiciones ambientales adversas.

Resultado esperado

  • Datos de temperatura y humedad reportados cada 10 minutos.
  • Latencia de transmisión de datos inferior a 5 segundos.
  • Consumo de energía del nodo menor a 50 mA en modo activo.
  • Rango de transmisión efectivo de al menos 5 km en campo abierto.

Público objetivo: Ingenieros y desarrolladores en IoT; Nivel: Avanzado

Arquitectura/flujo: Nodo sensor LoRa -> Gateway LoRa -> Plataforma de monitoreo en la nube.

Nivel: Avanzado

Prerrequisitos

  • Sistemas operativos verificados
  • Windows 11 Pro 23H2 (con privilegios de administrador para instalar drivers)
  • Ubuntu 22.04.4 LTS (usuario en grupo dialout)
  • macOS 14 Sonoma

  • Toolchain exacta (probada)

  • PlatformIO Core 6.1.14 (CLI)
  • Python 3.11.6
  • Git 2.44.0
  • PlatformIO platform espressif32@6.5.0
  • Framework Arduino-ESP32 2.0.14 (resuelto por espressif32@6.5.0)
  • toolchain-xtensa-esp32 gcc 8.4.0 (instalado por la plataforma anterior)
  • esptool.py 4.6 (tool-esptoolpy suministrado por PlatformIO)
  • Librerías Arduino (versiones fijas en este proyecto)

    • sandeepmistry/LoRa@0.8.0
    • adafruit/Adafruit BME280 Library@2.2.4
    • adafruit/Adafruit Unified Sensor@1.1.14
    • adafruit/Adafruit ADS1X15@2.4.0
  • Drivers de USB–Serie

  • La TTGO LoRa32 emplea normalmente CP2102/CP2104 (Silicon Labs). Instalar el driver:
    • Windows/macOS: descargar de Silicon Labs “CP210x Universal Windows/Mac Driver”.
    • Linux: el kernel ya incluye cp210x. Añade tu usuario a dialout: sudo usermod -aG dialout $USER y reinicia sesión.
  • Si tu variante usase CH340/CH34x, instala el driver de WCH.

  • Notas de conformidad LoRa

  • Selecciona banda según tu región y normativa:
    • EU868: 868.1/868.3/868.5 MHz (duty-cycle 1% típico).
    • US915: 903.9–927.5 MHz (canales espaciados 0.2 MHz).
  • En este caso práctico se configura por defecto EU868 (868.1 MHz); puedes cambiarlo en build_flags.

Materiales

  • 1× TTGO LoRa32 (ESP32 + SX1276) con interfaz USB (modelo de LILYGO, también llamado “TTGO LoRa32 v1/v2”).
  • 1× BME280 (módulo I2C, dirección por defecto 0x76/0x77).
  • 1× ADS1115 (módulo I2C, dirección por defecto 0x48).
  • 1× Sensor capacitivo de humedad de suelo 3.3 V (salida analógica, no el resistivo).
  • 1× Batería LiPo 3.7 V (con conector JST-PH compatible con la TTGO LoRa32).
  • Resistencias para divisor de tensión de batería hacia ADS1115 (ejemplo: 100 kΩ + 100 kΩ).
  • Cables Dupont Macho–Hembra/Macho–Macho.
  • Cable USB-C o Micro-USB según tu TTGO LoRa32.
  • Opcional, para validación por radio:
  • 1× segunda TTGO LoRa32 (ESP32 + SX1276) para actuar como receptor simple.

Notas:
– El objetivo “lora-agro-telemetry-lowpower” exige minimizar consumo: usaremos deep sleep del ESP32, el SX1276 en sleep entre tramas, BME280 en modo forced y lecturas single-shot del ADS1115.

Preparación y conexión

La TTGO LoRa32 integra el SX1276 y el bus SPI ya cableado en la propia placa. Los sensores BME280 y ADS1115 se conectan por I2C. Para sensar humedad de suelo usando un sensor capacitivo, su salida analógica se conectará a la entrada A0 del ADS1115. Si se desea telemetría de batería, llevaremos la tensión VBAT a A1 del ADS1115 a través de un divisor 1:2 (por ejemplo, 100 kΩ/100 kΩ), de modo que un máximo de 4.2 V quede por debajo del rango seguro del ADC externo.

Tabla de pines, buses y direcciones

Componente/SeñalTTGO LoRa32 (ESP32)Notas/Detalle
I2C SDAGPIO 21Conectar a SDA de BME280 y ADS1115
I2C SCLGPIO 22Conectar a SCL de BME280 y ADS1115
3V33V3Alimentación sensores BME280, ADS1115 y sensor de suelo (si admite 3.3 V)
GNDGNDTierra común para todos los módulos
SX1276 NSS (SS)GPIO 18Interno a la placa; lo fijamos en el código LoRa.setPins(18,14,26)
SX1276 RSTGPIO 14Interno
SX1276 DIO0GPIO 26Interno
SPI SCKGPIO 5Interno al SX1276
SPI MISOGPIO 19Interno al SX1276
SPI MOSIGPIO 27Interno al SX1276
ADS1115 dirección I2C0x48Por defecto (ADDR a GND)
BME280 dirección I2C0x76 o 0x77Según módulo (normalmente 0x76)
Sensor suelo -> ADS1115 A0ADS1115 A0Señal analógica del sensor capacitivo
VBAT dividido -> ADS1115 A1ADS1115 A1Divisor 100 kΩ/100 kΩ entre VBAT y GND; punto medio a A1

Conexiones paso a paso:
1. Alimentación y bus I2C:
– TTGO 3V3 → 3V3 de BME280, 3V3 de ADS1115 y VCC del sensor de suelo.
– TTGO GND → GND de BME280, GND de ADS1115 y GND del sensor de suelo.
– TTGO GPIO21 (SDA) → SDA de BME280 y SDA de ADS1115.
– TTGO GPIO22 (SCL) → SCL de BME280 y SCL de ADS1115.
2. Entradas analógicas en ADS1115:
– Sensor suelo OUT → ADS1115 A0.
– Punto medio del divisor de VBAT (entre 100 kΩ y 100 kΩ) → ADS1115 A1.
– Extremos del divisor: superior a VBAT (línea de batería de la TTGO, no al 5 V), inferior a GND.
3. Batería:
– Conecta la LiPo al conector JST de la TTGO LoRa32. La placa integra carga/gestión básica.
4. Antena:
– Conecta una antena adecuada a la TTGO (imprescindible para no dañar el SX1276).

Verificación inicial (opcional pero recomendable):
– Con un escáner I2C se deben ver 0x48 (ADS1115) y 0x76/0x77 (BME280).

Código completo

A continuación se muestra un proyecto completo para PlatformIO (framework Arduino). Implementa:
– Lectura de BME280 en modo forced (reduce consumo).
– Lectura single-shot de ADS1115 en A0 (humedad de suelo) y A1 (VBAT/2).
– Trama binaria compacta con CRC16-IBM.
– SX1276 dormido entre tramas; ESP32 en deep sleep con temporizador.
– Frecuencia, SF y TX power configurables por build_flags.
– Desactivación de WiFi/BT.

platformio.ini

Crea el proyecto y pega este contenido en platformio.ini:

; Proyecto: lora-agro-telemetry-lowpower
; Dispositivo: TTGO LoRa32 (ESP32 + SX1276) + BME280 + ADS1115
; Toolchain exacta fijada vía platform y libs

[env:ttgo-lora32-v1]
platform = espressif32@6.5.0
board = ttgo-lora32-v1
framework = arduino

; Paquetes de plataforma (se instalan automáticamente con espressif32@6.5.0)
; - toolchain-xtensa-esp32 (gcc 8.4.0)
; - tool-esptoolpy (esptool.py 4.6)
; - framework-arduinoespressif32 (Arduino-ESP32 2.0.14)

monitor_speed = 115200
monitor_filters = time, esp32_exception_decoder

lib_deps =
  sandeepmistry/LoRa@0.8.0
  adafruit/Adafruit BME280 Library@2.2.4
  adafruit/Adafruit Unified Sensor@1.1.14
  adafruit/Adafruit ADS1X15@2.4.0

; Parámetros de radio y hardware (modificables)
build_flags =
  -D LORA_FREQ_MHZ=868.1
  -D LORA_SF=7
  -D LORA_TX_PWR_DBM=14
  -D I2C_SDA=21
  -D I2C_SCL=22
  -D SOIL_CH=0
  -D VBAT_CH=1
  -D WAKE_INTERVAL_S=900
  -D REGION_EU868

Notas:
– Para US915, cambia LORA_FREQ_MHZ=915.0 y retira REGION_EU868 o define REGION_US915.
– Puedes afinar la SF (7–12) y potencia (2–20 dBm) según normativa y enlace.

src/main.cpp

Crea src/main.cpp con el siguiente código. Está documentado por bloques clave.

#include <Arduino.h>
#include <Wire.h>
#include <SPI.h>
#include <LoRa.h>
#include <WiFi.h>
#include "esp_bt.h"
#include <Adafruit_BME280.h>
#include <Adafruit_ADS1X15.h>

#ifndef LORA_FREQ_MHZ
#define LORA_FREQ_MHZ 868.1
#endif
#ifndef LORA_SF
#define LORA_SF 7
#endif
#ifndef LORA_TX_PWR_DBM
#define LORA_TX_PWR_DBM 14
#endif
#ifndef I2C_SDA
#define I2C_SDA 21
#endif
#ifndef I2C_SCL
#define I2C_SCL 22
#endif
#ifndef WAKE_INTERVAL_S
#define WAKE_INTERVAL_S 900
#endif
#ifndef SOIL_CH
#define SOIL_CH 0
#endif
#ifndef VBAT_CH
#define VBAT_CH 1
#endif

// Mapeo SX1276 en TTGO LoRa32
static constexpr int LORA_SS = 18;
static constexpr int LORA_RST = 14;
static constexpr int LORA_DIO0 = 26;

// SPI (VSPI) ya mapeado a SCK=5, MISO=19, MOSI=27 por defecto en ESP32-ARDUINO

// Sensores I2C
Adafruit_BME280 bme;
Adafruit_ADS1115 ads;

// Contador persistente en deep sleep
RTC_DATA_ATTR uint32_t rtc_seq = 0;

// CRC16-IBM (polinomio 0xA001)
uint16_t crc16_ibm(const uint8_t* data, size_t len) {
  uint16_t crc = 0xFFFF;
  for (size_t i = 0; i < len; ++i) {
    crc ^= data[i];
    for (uint8_t b = 0; b < 8; ++b) {
      if (crc & 1) crc = (crc >> 1) ^ 0xA001;
      else crc >>= 1;
    }
  }
  return crc;
}

// Estructura de trama compacta
#pragma pack(push, 1)
struct FrameV1 {
  uint8_t  pre[2];           // 'A','G'
  uint8_t  ver;              // 0x01
  uint32_t dev;              // 32 bits del MAC (ID)
  uint32_t seq;              // contador
  int16_t  t_c_x100;         // temperatura *100 (°C)
  uint16_t rh_x100;          // humedad *100 (%)
  uint16_t press_hPa_x10;    // presión *10 (hPa)
  uint16_t soil_permille;    // humedad suelo en ‰ (0-1000)
  uint16_t vbat_mV;          // batería en mV
  uint16_t crc;              // CRC16-IBM sobre cabecera+datos
};
#pragma pack(pop)

// Lectura BME280 en modo de baja energía (forced)
bool read_bme280(float &tC, float &rh, float &p_hPa) {
  // Configurar I2C si no está iniciado
  static bool wireBegun = false;
  if (!wireBegun) {
    Wire.begin(I2C_SDA, I2C_SCL);
    wireBegun = true;
  }
  static bool bmeInit = false;
  if (!bmeInit) {
    // Probar 0x76 y 0x77
    if (!bme.begin(0x76) && !bme.begin(0x77)) {
      return false;
    }
    // Configuración mínima: oversampling x1, filtro off, modo FORCED
    bme.setSampling(
      Adafruit_BME280::MODE_FORCED,
      Adafruit_BME280::SAMPLE_X1,   // temp
      Adafruit_BME280::SAMPLE_X1,   // hum
      Adafruit_BME280::SAMPLE_X1,   // pres
      Adafruit_BME280::FILTER_OFF
    );
    bmeInit = true;
  }
  // Disparo de conversión en modo FORCED
  bme.takeForcedMeasurement();
  tC    = bme.readTemperature();          // °C
  rh    = bme.readHumidity();             // %
  p_hPa = bme.readPressure() / 100.0F;    // hPa
  return !(isnan(tC) || isnan(rh) || isnan(p_hPa));
}

// Inicialización ADS1115 en modo lectura single-shot
bool init_ads1115() {
  static bool init = false;
  if (!init) {
    if (!ads.begin(0x48)) return false;
    ads.setGain(GAIN_ONE); // ±4.096 V -> 0.125 mV/LSB
    init = true;
  }
  return true;
}

float read_ads_voltage(uint8_t ch) {
  int16_t counts = ads.readADC_SingleEnded(ch);
  return ads.computeVolts(counts); // Volts referidos a Vdd ADC
}

// Calibración simple para humedad de suelo (ajusta a tu sensor)
uint16_t soil_percent_permille_from_voltage(float v) {
  // Ajusta límites según calibración empírica de tu sonda
  // Ejemplo típico de sensor capacitivo: ~0.5 V seco, ~2.5 V muy húmedo
  const float v_dry = 0.5f;
  const float v_wet = 2.5f;
  float f = (v - v_dry) / (v_wet - v_dry);
  if (f < 0.0f) f = 0.0f;
  if (f > 1.0f) f = 1.0f;
  return (uint16_t)lroundf(f * 1000.0f); // ‰ (0–1000)
}

// ID de dispositivo a partir del MAC
uint32_t device_id32() {
  uint64_t mac = ESP.getEfuseMac();
  return (uint32_t)((mac >> 16) & 0xFFFFFFFF);
}

// Inicialización de la radio LoRa con parámetros de bajo consumo
bool lora_radio_begin(double freqMHz, int sf, int txPower) {
  // Apaga radios del SoC para ahorrar
  WiFi.mode(WIFI_OFF);
  btStop();

  SPI.begin(); // VSPI por defecto
  LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
  if (!LoRa.begin(freqMHz * 1e6)) {
    return false;
  }
  LoRa.setSPIFrequency(8E6);
  LoRa.setTxPower(txPower);          // dBm (máx. 20 con PA_BOOST, cumple normativa local)
  LoRa.setSpreadingFactor(sf);       // 7..12
  LoRa.setSignalBandwidth(125E3);    // 125 kHz (estándar)
  LoRa.setCodingRate4(5);            // 4/5
  LoRa.setPreambleLength(8);         // preámbulo estándar
  LoRa.enableCrc();                  // CRC de payload de la librería (además del nuestro)
  return true;
}

void lora_radio_sleep() {
  LoRa.idle();
  LoRa.sleep(); // SX1276 en sleep (consumo mínimo)
}

// Empaquetado y envío de trama binaria
bool send_frame_once(FrameV1 &frm) {
  // Calcula CRC sobre todo excepto el campo CRC
  frm.crc = 0;
  uint16_t crc = crc16_ibm(reinterpret_cast<const uint8_t*>(&frm), sizeof(frm) - sizeof(frm.crc));
  frm.crc = crc;

  // Emisión
  LoRa.beginPacket();
  LoRa.write(reinterpret_cast<const uint8_t*>(&frm), sizeof(frm));
  int err = LoRa.endPacket(true); // true = TX async (mejor para no bloquear)
  // Espera a que termine
  LoRa.idle();
  return (err == 1);
}

void setup() {
  Serial.begin(115200);
  delay(50);
  Serial.println();
  Serial.println(F("[lora-agro-telemetry-lowpower] Boot"));

  // Lógica de secuencia
  rtc_seq++;

  // I2C
  Wire.begin(I2C_SDA, I2C_SCL);
  if (!init_ads1115()) {
    Serial.println(F("ERR: ADS1115 no detectado en 0x48"));
  }

  // Sensores
  float tC=0, rh=0, p_hPa=0;
  bool bme_ok = read_bme280(tC, rh, p_hPa);
  if (!bme_ok) {
    Serial.println(F("ERR: BME280 no detectado (0x76/0x77)"));
  }

  // Lecturas ADS1115
  float v_soil = read_ads_voltage(SOIL_CH);
  float v_bat_div = read_ads_voltage(VBAT_CH); // miden VBAT/2 por divisor 100k/100k
  float vbat = v_bat_div * 2.0f; // Volts reales

  // Conversión a unidades compactas
  uint16_t soil_permille = soil_percent_permille_from_voltage(v_soil);
  int16_t t_c_x100 = (int16_t)lroundf(tC * 100.0f);
  uint16_t rh_x100 = (uint16_t)lroundf(rh * 100.0f);
  uint16_t press_hPa_x10 = (uint16_t)lroundf(p_hPa * 10.0f);
  uint16_t vbat_mV = (uint16_t)lroundf(vbat * 1000.0f);

  // Construcción de la trama
  FrameV1 frm{};
  frm.pre[0] = 'A'; frm.pre[1] = 'G';
  frm.ver = 0x01;
  frm.dev = device_id32();
  frm.seq = rtc_seq;
  frm.t_c_x100 = t_c_x100;
  frm.rh_x100 = rh_x100;
  frm.press_hPa_x10 = press_hPa_x10;
  frm.soil_permille = soil_permille;
  frm.vbat_mV = vbat_mV;

  Serial.printf("ID=0x%08lX SEQ=%lu T=%.2fC RH=%.2f%% P=%.1fhPa SOIL=%.1f%% VBAT=%.3fV\n",
    (unsigned long)frm.dev, (unsigned long)frm.seq,
    tC, rh, p_hPa, soil_permille/10.0f, vbat);

  // Radio
  if (!lora_radio_begin(LORA_FREQ_MHZ, LORA_SF, LORA_TX_PWR_DBM)) {
    Serial.println(F("ERR: Fallo init LoRa (frecuencia/pines/región)"));
  } else {
    bool ok = send_frame_once(frm);
    Serial.printf("TX %s, len=%u, CRC=0x%04X, F=%.1fMHz SF=%d PWR=%ddBm\n",
      ok ? "OK" : "ERR", (unsigned)sizeof(frm), frm.crc, LORA_FREQ_MHZ, LORA_SF, LORA_TX_PWR_DBM);
  }
  // Duerme la radio
  lora_radio_sleep();

  // Espera corta para drenar UART
  delay(50);

  // Programa deep sleep con temporizador
  esp_sleep_enable_timer_wakeup((uint64_t)WAKE_INTERVAL_S * 1000000ULL);
  Serial.printf("DeepSleep %us...\n", WAKE_INTERVAL_S);
  Serial.flush();
  esp_deep_sleep_start();
}

void loop() {
  // No se usa; el ESP32 nunca entra aquí debido a deep sleep
}

Puntos clave del código:
– Se desconectan WiFi y BT para reducir consumo.
– El BME280 se usa en modo forced, volviendo a sleep tras la medición.
– El ADS1115 realiza lecturas single-shot, que apagan internamente el conversor entre muestras.
– La trama “AG v1” incluye un CRC16-IBM propio y además se habilita el CRC de la librería LoRa.
– Se usa RTC_DATA_ATTR para mantener el contador de secuencia entre ciclos de deep sleep.
– El SX1276 se pone en sleep tras el envío.
– El ESP32 entra en deep sleep durante WAKE_INTERVAL_S (por defecto 900 s = 15 min).

Receptor de validación (opcional, misma TTGO LoRa32)

Para validar por aire, flashea en otra TTGO LoRa32 el siguiente receptor de propósito general. Solo decodifica cabecera y CRC para mostrar los valores.

#include <Arduino.h>
#include <SPI.h>
#include <LoRa.h>

static constexpr int LORA_SS = 18;
static constexpr int LORA_RST = 14;
static constexpr int LORA_DIO0 = 26;

#ifndef LORA_FREQ_MHZ
#define LORA_FREQ_MHZ 868.1
#endif
#ifndef LORA_SF
#define LORA_SF 7
#endif

#pragma pack(push,1)
struct FrameV1 {
  uint8_t  pre[2];
  uint8_t  ver;
  uint32_t dev;
  uint32_t seq;
  int16_t  t_c_x100;
  uint16_t rh_x100;
  uint16_t press_hPa_x10;
  uint16_t soil_permille;
  uint16_t vbat_mV;
  uint16_t crc;
};
#pragma pack(pop)

uint16_t crc16_ibm(const uint8_t* d, size_t n) {
  uint16_t crc=0xFFFF;
  for(size_t i=0;i<n;i++){ crc^=d[i]; for(uint8_t b=0;b<8;b++){ crc=(crc&1)?(crc>>1)^0xA001:(crc>>1);} }
  return crc;
}

void setup() {
  Serial.begin(115200);
  SPI.begin();
  LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
  if (!LoRa.begin(LORA_FREQ_MHZ*1e6)) {
    Serial.println("ERR: LoRa.begin()");
    while(1) delay(1000);
  }
  LoRa.setSpreadingFactor(LORA_SF);
  LoRa.setSignalBandwidth(125E3);
  LoRa.setCodingRate4(5);
  LoRa.enableCrc();
  Serial.println("RX listo");
}

void loop() {
  int packetSize = LoRa.parsePacket();
  if (packetSize == (int)sizeof(FrameV1)) {
    FrameV1 frm{};
    LoRa.readBytes((uint8_t*)&frm, sizeof(frm));
    uint16_t crc = frm.crc; frm.crc = 0;
    uint16_t calc = crc16_ibm((uint8_t*)&frm, sizeof(frm)-2);
    if (crc == calc && frm.pre[0]=='A' && frm.pre[1]=='G' && frm.ver==0x01) {
      float tC = frm.t_c_x100 / 100.0f;
      float rh = frm.rh_x100 / 100.0f;
      float p  = frm.press_hPa_x10 / 10.0f;
      float soilPct = frm.soil_permille / 10.0f;
      float vbat = frm.vbat_mV / 1000.0f;
      Serial.printf("OK dev=0x%08lX seq=%lu T=%.2fC RH=%.2f%% P=%.1fhPa SOIL=%.1f%% VBAT=%.3fV RSSI=%d SNR=%.1f\n",
        (unsigned long)frm.dev, (unsigned long)frm.seq, tC, rh, p, soilPct, vbat, LoRa.packetRssi(), LoRa.packetSnr());
    } else {
      Serial.println("ERR CRC/cabecera");
    }
  }
}

Compilación, flash y ejecución

A continuación los comandos exactos con PlatformIO CLI. Puedes usarlos en cualquier SO con la consola/terminal.

1) Verificar versiones de Python y PlatformIO:

python --version
pio --version

2) Crear el proyecto (en una carpeta nueva):

mkdir -p ~/proyectos/lora-agro-telemetry-lowpower
cd ~/proyectos/lora-agro-telemetry-lowpower
pio project init --board ttgo-lora32-v1

3) Sustituye platformio.ini por el proporcionado en este caso práctico y crea src/main.cpp con el código anterior.

4) Opcional: listar la placa para confirmar:

pio boards | grep -i ttgo

5) Actualizar paquetes y compilar:

pio pkg update
pio run -t clean
pio run

6) Conecta la TTGO LoRa32 por USB. Identifica el puerto:
– Windows: comprueba en el Administrador de dispositivos (COMx).
– Linux: dmesg | grep -i ttyUSB y ls -l /dev/ttyUSB*
– macOS: ls -l /dev/tty.SLAB_USBtoUART

7) Flashear el firmware:

pio run -t upload

8) Abrir monitor serie a 115200 baudios:

pio device monitor --baud 115200 --echo --filter time --filter esp32_exception_decoder

Notas específicas por SO:
– Linux udev: si no tienes permisos, crea /etc/udev/rules.d/99-usb-serial.rules con:
– SUBSYSTEM==»tty», ATTRS{idVendor}==»10c4″, ATTRS{idProduct}==»ea60″, MODE=»0666″, GROUP=»dialout»
– Luego: sudo udevadm control –reload-rules && sudo udevadm trigger
– Windows: si upload falla intermitente, prueba cables USB de calidad y puertos traseros.

Para el receptor opcional, crea otro proyecto (o un environment aparte) y repite los pasos con su main.cpp.

Validación paso a paso

1) Validación de consola del transmisor:
– Tras el reset, deberías ver líneas similares a:
– [lora-agro-telemetry-lowpower] Boot
– ID=0x12AB34CD SEQ=1 T=23.12C RH=45.67% P=1013.2hPa SOIL=56.7% VBAT=4.051V
– TX OK, len=21, CRC=0x5A93, F=868.1MHz SF=7 PWR=14dBm
– DeepSleep 900s…

  • SEQ aumentará en cada despertar (1,2,3…).

2) Validación de sensores:
– Si el BME280 no está o su dirección no coincide, verás ERR: BME280 no detectado.
– Si el ADS1115 no responde, verás ERR: ADS1115 no detectado en 0x48.
– Acerca un dedo al sensor de suelo (si está al aire) o insértalo en tierra húmeda; la lectura SOIL deberá aumentar.

3) Validación de radio con receptor (opcional, segunda TTGO LoRa32):
– En el receptor, al abrir el monitor serie deberías ver:
– RX listo
– OK dev=0x12AB34CD seq=1 T=23.12C RH=45.67% P=1013.2hPa SOIL=56.7% VBAT=4.051V RSSI=-72 SNR=9.8
– Comprueba que el dev (ID) y seq coinciden con el transmisor y que los valores son razonables.

4) Validación de consumo (orientativa):
– Con un medidor USB en línea, observa tres fases:
– Pico al transmitir LoRa (decenas de mA, típico 80–120 mA por milisegundos).
– Activo durante sensado y TX (20–70 mA según placa).
– Deep sleep: debería caer significativamente. En TTGO LoRa32, dependiendo del regulador y del SX1276, es habitual ver cientos de µA a ~1–2 mA. Registra el valor.
– Si el consumo en deep sleep no baja de ~5 mA, revisa:
– Que WiFi/BT estén apagados.
– Que no tengas LEDs siempre encendidos.
– Que el sensor de suelo no consuma de forma continua (puede alimentarse desde un GPIO + MOSFET para desconectarlo entre mediciones en una mejora posterior).

5) Validación de payload/CRC:
– Si alteras el código del receptor para forzar un CRC erróneo, debe reportar ERR CRC/cabecera, verificando que el emisor genera CRC correcto.

6) Verificación de duty-cycle:
– Para EU868, con WAKE_INTERVAL_S=900 (15 min), el duty-cycle es seguro. Si reduces el intervalo, asegúrate de cumplir normativa (1% por sub-banda típica).

Troubleshooting

1) No aparece el puerto serie:
– Instala el driver CP210x adecuado.
– Cambia de cable (evita “solo carga”).
– En Linux, añade usuario a dialout y reabre sesión.
– Prueba otro puerto USB (en PC de sobremesa, los traseros suelen ser más fiables).

2) Fallo al entrar en modo programación (upload):
– Mantén pulsado BOOT (GPIO0 a GND) mientras conectas USB, o pulsa RST manteniendo BOOT.
– En PlatformIO, intenta: pio run -t upload –upload-port /dev/ttyUSB0 (ajusta puerto).
– Desconecta otros dispositivos serie que “secuestren” el puerto.

3) LoRa.begin() falla:
– Verifica antena conectada y banda correcta (LORA_FREQ_MHZ).
– Confirma pines del SX1276 (para TTGO: SS=18, RST=14, DIO0=26). Si usas otro modelo, ajusta.
– No mezcles región (EU868 vs US915) con frecuencias no válidas.

4) No se observan lecturas I2C:
– Confirmar 3V3 y GND a los módulos.
– Verifica que BME280 no sea en realidad BMP280 (sin humedad).
– Comprueba direcciones: muchos BME280 vienen a 0x76; si no responde, prueba 0x77 en el código (el ejemplo ya intenta ambas).
– Cableado SDA/SCL correcto (21/22) y longitud/ruido razonables.

5) ADS1115 saturado o lecturas erróneas:
– Revisa GAIN. Con GAIN_ONE (±4.096 V) y VDD de 3.3 V es adecuado; si la señal es baja, podrías usar GAIN_TWO para mejor resolución.
– Asegura masas comunes y que el sensor de suelo se alimenta a 3.3 V (no 5 V).

6) Deep sleep no reduce consumo:
– Asegura que LoRa.sleep() se llama antes de esp_deep_sleep_start().
– Verifica que no haya periferia externa alimentada sin necesidad (sensores).
– Revisa que WiFi.mode(WIFI_OFF) y btStop() se ejecuten sin errores.

7) Mensajes esporádicos “Brownout detector”:
– Batería descargada o picos de corriente durante TX.
– Usa una LiPo en buen estado y un cable corto; considera un condensador adicional de desacoplo (p. ej. 470 µF cerca del módulo LoRa).

8) Cobertura de radio insuficiente:
– Aumenta SF (ej., LORA_SF=9 o 10) a costa de tiempo en aire.
– Mejora antena, eleva el nodo, evita obstáculos.
– Ajusta potencia respetando límites regulatorios (14 dBm EU, 20 dBm en ciertos casos con restricciones).

Mejoras/variantes

  • Cortar alimentación al sensor de suelo:
  • Controla VCC del sensor con un MOSFET P o un switch de alta/low-side desde un GPIO para apagarlo entre mediciones.
  • Encriptación ligera:
  • Añade cifrado (p. ej., AES-128 CTR) al payload antes de LoRa.write(); intercambia una clave en el receptor.
  • Confirmación y reintentos:
  • Implementa ACK ligero con el receptor enviando un paquete corto de vuelta; reintenta si no hay ACK, con contador limitado para no violar duty-cycle.
  • LoRaWAN (si tu caso requiere):
  • Sustituye LoRa “crudo” por LMIC (MCCI) y gestiona altas en un Network Server (TTN/ChirpStack). Implica más complejidad y consumo.
  • ADR y autoajuste:
  • Cambia SF en función del RSSI/SNR reportado por el receptor a lo largo del tiempo.
  • Medición adicional:
  • Usa ADS1115 canales A2/A3 para sensores de pH/EC aislados o tensiómetros, con cuidado de masas y filtrado.
  • Optimización de tiempo en aire:
  • Compacta más el payload (por ejemplo, delta-encoding y escala fija) y reduce la cadencia en condiciones estables.

Checklist de verificación

  • [ ] He instalado PlatformIO Core 6.1.14 y Python 3.11.6; pio –version responde correctamente.
  • [ ] El driver CP210x está instalado y el puerto serie aparece al conectar la TTGO LoRa32.
  • [ ] He creado el proyecto con board=ttgo-lora32-v1 y he pegado platformio.ini con versiones y build_flags indicados.
  • [ ] He cableado BME280 y ADS1115 a SDA=21 y SCL=22, con 3V3 y GND compartidos.
  • [ ] El sensor de suelo está conectado a A0 del ADS1115; el divisor VBAT (100k/100k) a A1.
  • [ ] El transmisor compila, flashea y muestra por serie la línea de mediciones y “TX OK”.
  • [ ] Puedo ver el incremento de SEQ en cada despertar y cambios en SOIL al humedecer/secar la sonda.
  • [ ] En validación por aire (opcional), el receptor TTGO LoRa32 decodifica tramas “AG v1” con CRC OK.
  • [ ] El consumo cae notablemente en deep sleep tras unos segundos (verificado con medidor).
  • [ ] He ajustado LORA_FREQ_MHZ/SF/PA según mi región y cobertura requerida.

Con este caso práctico tendrás un nodo agrícola de telemetría LoRa de bajo consumo funcionando con el modelo exacto TTGO LoRa32 (ESP32 + SX1276) y sensores BME280 + ADS1115, listo para desplegar en campo y extender con mejoras avanzadas.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Qué versión de Windows es necesaria para instalar drivers según el artículo?




Pregunta 2: ¿Cuál es la herramienta de línea de comandos mencionada en el artículo?




Pregunta 3: ¿Qué versión de Python se requiere para el proyecto?




Pregunta 4: ¿Qué driver es necesario instalar para la TTGO LoRa32 en Windows/macOS?




Pregunta 5: ¿Qué librería se utiliza para el sensor BME280?




Pregunta 6: ¿Qué dirección I2C se utiliza por defecto para el módulo ADS1115?




Pregunta 7: ¿Cuál es la banda de frecuencia configurada por defecto en el caso práctico?




Pregunta 8: ¿Qué comando se debe ejecutar para añadir un usuario al grupo 'dialout' en Linux?




Pregunta 9: ¿Qué herramienta se suministra por PlatformIO para flashear el ESP32?




Pregunta 10: ¿Qué tipo de sensor se menciona específicamente para medir la humedad del suelo?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio