
BermejaSat #4: Cruzando el aire — Telemetría de datos ambientales por radio LoRa a 868 MHz
Introducción
Tras lograr en la entrada anterior que la placa LILYGO leyera el sensor ambiental AM2302 y mostrase los datos en la pantalla OLED local, llegamos al hito fundamental que convierte un circuito electrónico en un satélite real: la telemetría por radio.
Durante el descenso en paracaídas de nuestro CanSat, no habrá ningún cable ni ordenador conectado a la sonda. La única vía para recibir la temperatura, la humedad y la altitud en tiempo real será mediante ondas de radio de largo alcance. En esta sesión hemos establecido el primer enlace inalámbrico punto a punto entre el módulo emisor (Satélite) y un segundo módulo receptor (Estación Terrena o Ground Station).
⚠️ La regla de oro en radiofrecuencia: La antena primero
Antes de alimentar cualquier dispositivo radio, transmitimos al alumnado una norma crítica en ingeniería de telecomunicaciones: jamás se debe encender o conectar a la corriente una placa LoRa sin su antena instalada.
Cuando el chip emite un paquete de radio, genera una energía de radiofrecuencia que debe disiparse a través de la antena. Si la antena no está conectada al puerto U.FL/IPEX de la placa, esa energía no encuentra salida, rebota hacia el amplificador interno de potencia (Power Amplifier) y puede destruir el transceptor LoRa en cuestión de segundos.
🔬 Arquitectura de hardware y alimentación independiente
Para simular las condiciones reales de vuelo, el sistema se ha dividido en dos nodos independientes utilizando la banda europea libre de 868 MHz:
[ NODO 1: SATÉLITE ] [ NODO 2: ESTACIÓN TERRENA ]
AM2302 (GPIO17) ──► ESP32 ESP32 ──► Pantalla OLED local
│ │ ▲
▼ ▼ (SPI / 868 MHz) │ (Lectura RSSI + CSV)
Pantalla OLED Módulo LoRa ═══════════════► Módulo LoRa ──► Monitor Serie USB (PC)
▲
│
[Alimentación Ext. 5.1V / 1.4A]
1. Módulo Emisor (BermejaSat / Satélite)
- Placa: LILYGO LoRa V1.3 con pantalla OLED.
- Sensor: ASAIR AM2302 (GPIO 17).
- Alimentación: Fuente externa independiente (transformador 5,1 V / 1,4 A). Esto independiza al satélite de cualquier conexión física con un ordenador.
- Función: Lee el sensor cada 2 segundos, serializa las variables en una cadena de texto y transmite el paquete por radio.
Código al final
2. Módulo Receptor (Estación Terrena / Ground Station)
- Placa: LILYGO LoRa V1.3 con pantalla OLED.
- Sensores: Ninguno (no los necesita).
- Alimentación y Datos: Cable USB conectado al ordenador del equipo.
- Función: Escucha de forma continua el espectro de 868 MHz, extrae la telemetría, calcula la calidad del enlace radio (RSSI) y muestra los datos en pantalla y consola.
Código al final
💻 Formato de datos y flujo de software
Emisión (Serialización en formato CSV)
Para optimizar el tiempo de transmisión en el aire (Airtime), la información de telemetría no se envía formateada con textos largos, sino empaquetada como una línea de valores separados por comas (CSV):
$$\text{Trama LoRa: } \text{“[ID\_PAQUETE],[TEMPERATURA],[HUMEDAD]”}$$
Ejemplo de paquete transmitido: "14,23.5,58.2"
Recepción y cálculo de cobertura (RSSI)
Al recibir el paquete, la Estación Terrena analiza la cadena, desglosa los valores para presentarlos de forma clara en su propia pantalla OLED y mide el RSSI (Received Signal Strength Indicator), expresado en decibelios-milioctavios ($\text{dBm}$).
El RSSI nos permite monitorizar cuantitativamente la atenuación de la señal a medida que el satélite se aleja de la recepción terrena (valores cercanos a $-30\text{ dBm}$ indican máxima cobertura a corta distancia, mientras que valores inferiores a $-110\text{ dBm}$ señalan el límite de recepción).
📋 Resumen del mapa de pines del sistema completo
| Componente | Línea / Pin | Pin ESP32 (Emisor) | Pin ESP32 (Receptor) |
| Radio LoRa | SCK / MISO / MOSI | GPIO 5 / 19 / 27 | GPIO 5 / 19 / 27 |
| Control (SS / RST / DIO0) | GPIO 18 / 14 / 26 | GPIO 18 / 14 / 26 | |
| Pantalla OLED | SDA / SCL | GPIO 21 / 22 | GPIO 21 / 22 |
| Sensor AM2302 | DATA | GPIO 17 | Libre |
Conclusión pedagógica
El éxito de esta sesión va más allá de ver aparecer números en la pantalla del receptor: el alumnado ha integrado conceptos de arquitectura de redes inalámbricas, atenuación de radiofrecuencia (RSSI) y serialización de datos.
Comprobar cómo el satélite emite alimentado por un simple transformador mientras los datos “vuelan” por el aula hasta el ordenador de la Estación Terrena marca el verdadero punto de inflexión del proyecto BermejaSat.
En la próxima entrada: integraremos el barómetro de precisión BMP280 para sumar la altitud y la presión atmosférica a nuestra trama de telemetría.
Código emisor
#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
// --- Pines Pantalla OLED ---
#define OLED_SDA 21
#define OLED_SCL 22
Adafruit_SSD1306 display(128, 64, &Wire, -1);
// --- Pines Sensor AM2302 ---
#define DHTPIN 17
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// --- Pines Radio LoRa (LILYGO V1.3) ---
#define SCK 5
#define MISO 19
#define MOSI 27
#define SS 18
#define RST 14
#define DIO0 26
#define BAND 868E6 // 868 MHz (Frecuencia para Europa)
int paqueteID = 0;
void setup() {
Serial.begin(115200);
// Inicializar OLED
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("Error OLED");
}
// Inicializar DHT22
dht.begin();
// Inicializar SPI y LoRa
SPI.begin(SCK, MISO, MOSI, SS);
LoRa.setPins(SS, RST, DIO0);
if (!LoRa.begin(BAND)) {
Serial.println("¡Error al iniciar LoRa!");
display.clearDisplay();
display.setCursor(0, 0);
display.setTextColor(SSD1306_WHITE);
display.println("ERROR LORA");
display.display();
while (1);
}
Serial.println("Emisor LoRa listo.");
}
void loop() {
delay(2000);
float temp = dht.readTemperature();
float hum = dht.readHumidity();
if (isnan(temp) || isnan(hum)) {
Serial.println("Error de lectura del AM2302");
return;
}
paqueteID++;
// Crear mensaje formateado: "ID,Temp,Hum"
String mensaje = String(paqueteID) + "," + String(temp, 1) + "," + String(hum, 1);
// --- ENVIAR POR LORA ---
LoRa.beginPacket();
LoRa.print(mensaje);
LoRa.endPacket();
Serial.print("Enviando paquete #");
Serial.print(paqueteID);
Serial.print(" -> ");
Serial.println(mensaje);
// --- MOSTRAR EN PANTALLA LOCAL ---
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("BERMEJASAT - EMISOR");
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
display.setCursor(0, 20);
display.print("Pkt Enviado: #");
display.println(paqueteID);
display.setCursor(0, 35);
display.print("Temp: "); display.print(temp, 1); display.println(" C");
display.setCursor(0, 50);
display.print("Hum: "); display.print(hum, 1); display.println(" %");
display.display();
}
Código receptor
#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
// --- Pines Pantalla OLED ---
#define OLED_SDA 21
#define OLED_SCL 22
Adafruit_SSD1306 display(128, 64, &Wire, -1);
// --- Pines Radio LoRa (LILYGO V1.3) ---
#define SCK 5
#define MISO 19
#define MOSI 27
#define SS 18
#define RST 14
#define DIO0 26
#define BAND 868E6
void setup() {
Serial.begin(115200);
// Inicializar OLED
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("Error OLED");
}
// Inicializar SPI y LoRa
SPI.begin(SCK, MISO, MOSI, SS);
LoRa.setPins(SS, RST, DIO0);
if (!LoRa.begin(BAND)) {
Serial.println("¡Error al iniciar LoRa!");
display.clearDisplay();
display.setCursor(0, 0);
display.setTextColor(SSD1306_WHITE);
display.println("ERROR LORA");
display.display();
while (1);
}
// Mensaje de espera en pantalla
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("ESTACION TERRENA");
display.setCursor(0, 20);
display.println("Esperando senal...");
display.display();
Serial.println("Receptor LoRa a la escucha...");
}
void loop() {
int packetSize = LoRa.parsePacket();
if (packetSize) {
String mensajeRecibido = "";
// Leer el paquete recibido
while (LoRa.available()) {
mensajeRecibido += (char)LoRa.read();
}
// Obtener la potencia de senal (RSSI) en dBm
int rssi = LoRa.packetRssi();
Serial.print("Recibido: ");
Serial.print(mensajeRecibido);
Serial.print(" | RSSI: ");
Serial.println(rssi);
// Trocear la cadena "ID,Temp,Hum"
int primeraComa = mensajeRecibido.indexOf(',');
int segundaComa = mensajeRecibido.indexOf(',', primeraComa + 1);
String idPkt = mensajeRecibido.substring(0, primeraComa);
String temp = mensajeRecibido.substring(primeraComa + 1, segundaComa);
String hum = mensajeRecibido.substring(segundaComa + 1);
// --- MOSTRAR EN PANTALLA RECEPTORA ---
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("ESTACION TERRENA - RX");
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
display.setCursor(0, 18);
display.print("Pkt: #"); display.print(idPkt);
display.print(" | RSSI:"); display.print(rssi); display.println("dBm");
display.setTextSize(1);
display.setCursor(0, 35);
display.print("Temp: "); display.setTextSize(2); display.print(temp); display.println(" C");
display.setTextSize(1);
display.setCursor(0, 52);
display.print("Hum: "); display.print(hum); display.println(" %");
display.display();
}
}
Etiqueta:868MHz, BermejaSat, cansat, ESA, esp32, Estación Terrena, ies monterroso, LILYGO, LoRa, montesteam, RSSI, Telemetría



