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ñal TTGO LoRa32 (ESP32) Notas/Detalle
I2C SDA GPIO 21 Conectar a SDA de BME280 y ADS1115
I2C SCL GPIO 22 Conectar a SCL de BME280 y ADS1115
3V3 3V3 Alimentación sensores BME280, ADS1115 y sensor de suelo (si admite 3.3 V)
GND GND Tierra común para todos los módulos
SX1276 NSS (SS) GPIO 18 Interno a la placa; lo fijamos en el código LoRa.setPins(18,14,26)
SX1276 RST GPIO 14 Interno
SX1276 DIO0 GPIO 26 Interno
SPI SCK GPIO 5 Interno al SX1276
SPI MISO GPIO 19 Interno al SX1276
SPI MOSI GPIO 27 Interno al SX1276
ADS1115 dirección I2C 0x48 Por defecto (ADDR a GND)
BME280 dirección I2C 0x76 o 0x77 Según módulo (normalmente 0x76)
Sensor suelo -> ADS1115 A0 ADS1115 A0 Señal analógica del sensor capacitivo
VBAT dividido -> ADS1115 A1 ADS1115 A1 Divisor 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