
🚀 BermejaSat #18: Control Bi-Direccional LoRa, Telecomandos (TC/TM) y Actuación Remota con TTGO LoRa32 y ASAIR AM2302
En las misiones aeroespaciales de la ESA o la NASA, la telemetría no es un canal unidireccional (Simplex). Un satélite o un CanSat no solo debe “hablar” transmitiendo datos a tierra; también debe “escuchar” y ejecutar órdenes enviadas desde el centro de control.
Hasta ahora, en BermejaSat habíamos probado emisores pasivos de telemetría. En esta entrada damos el salto definitivo hacia una arquitectura bi-direccional (Dúplex) basada en Telecomandos (TC) y Telemetría (TM) mediante radio LoRa a 868 MHz, utilizando dos placas TTGO LoRa32, el sensor ASAIR AM2302 (DHT22) y pantallas OLED SSD1306.
🛠️ Materiales y Arquitectura de Red
Para este experimento de mesa no necesitamos soldaduras ni cables complejos:
- Nodo CanSat (TTGO #1):
- Microcontrolador ESP32 + Módulo LoRa SX1276 (868 MHz).
- Sensor de temperatura y humedad ASAIR AM2302 (DHT22) en
GPIO 17. - Pantalla OLED 0.96″ SSD1306 (
SDA=21,SCL=22,RST=16). - Actuador lógico/LED en
GPIO 2.
- Nodo Estación Terrena (TTGO #2):
- Microcontrolador ESP32 + Módulo LoRa SX1276 (868 MHz).
- Pantalla OLED 0.96″ SSD1306 dedicada.
- Interfaz de comandos por consola Serie (PC) a 115200 baudios.
📡 Estructura del Protocolo TC/TM
Establecemos un protocolo ligero de paquetes por radio:
Plaintext
┌──────────────────────────────────────────────┐
│ ESTACIÓN TERRENA (TTGO #2) │
└──────┬────────────────────────────────▲──────┘
│ │
Telecomando │ "LED_ON" / "LED_OFF" │ Telemetría / ACK
(Tierra -> SAT)│ │ (SAT -> Tierra)
▼ │
┌───────────────────────────────────────┴──────┐
│ CANSAT (TTGO #1) │
│ [ AM2302 ] ──► [ OLED ] ──► [ Actuador GPIO ]│
└──────────────────────────────────────────────┘
- Telemetría periódica (TM): Trama transmitida cada 2 segundos con formato:
TM,<Temperatura>,<Humedad>,<Estado_Actuador> - Telecomando activo (TC): Órdenes desde la base (
LED_ON/LED_OFF). - Confirmación (ACK): Tras recibir un TC válido, el CanSat responde de inmediato con
ACK: LED_ON_OKoACK: LED_OFF_OK.
💻 Código 1: Firmware del CanSat (TTGO #1)
Este programa gestiona la lectura del sensor AM2302, actualiza la pantalla OLED con la telemetría y el último comando recibido, atiende las peticiones por radio y conmuta el actuador.
C++
#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
// --- PINES PANTALLA OLED ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_SDA 21
#define OLED_SCL 22
#define OLED_RST 16
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST);
// --- PINES SENSOR ASAIR AM2302 (DHT22) ---
#define DHTPIN 17
#define DHTTYPE DHT22
DHT dht(DHTPIN, DHTTYPE);
// --- PINES LORA SX1276 ---
#define SCK_LORA 5
#define MISO_LORA 19
#define MOSI_LORA 27
#define SS_LORA 18
#define RST_LORA 14
#define DIO0_LORA 26
// --- ACTUADOR / LED ---
#define ACTUATOR_LED 2
// Variables globales
bool statusLED = false;
float temperatura = 0.0;
float humedad = 0.0;
unsigned long ultimoEnvio = 0;
String ultimoComando = "NINGUNO";
void setup() {
Serial.begin(115200);
// Configurar Actuador LED
pinMode(ACTUATOR_LED, OUTPUT);
digitalWrite(ACTUATOR_LED, LOW);
// Inicializar OLED
pinMode(OLED_RST, OUTPUT);
digitalWrite(OLED_RST, LOW);
delay(20);
digitalWrite(OLED_RST, HIGH);
Wire.begin(OLED_SDA, OLED_SCL);
if (display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("BermejaSat #18");
display.println("Iniciando CanSat...");
display.display();
}
// Inicializar AM2302
dht.begin();
// Inicializar LoRa
SPI.begin(SCK_LORA, MISO_LORA, MOSI_LORA, SS_LORA);
LoRa.setPins(SS_LORA, RST_LORA, DIO0_LORA);
if (!LoRa.begin(868E6)) {
Serial.println("❌ Error de inicio LoRa!");
while (1);
}
Serial.println("🚀 CanSat listo con AM2302, OLED y LoRa!");
}
void loop() {
// 1. RECEPCIÓN DE TELECOMANDOS (TC)
int packetSize = LoRa.parsePacket();
if (packetSize) {
String command = "";
while (LoRa.available()) {
command += (char)LoRa.read();
}
command.trim();
ultimoComando = command;
Serial.print("📩 TC Recibido: ");
Serial.println(command);
if (command == "LED_ON") {
statusLED = true;
digitalWrite(ACTUATOR_LED, HIGH);
enviarACK("ACK: LED_ON_OK");
} else if (command == "LED_OFF") {
statusLED = false;
digitalWrite(ACTUATOR_LED, LOW);
enviarACK("ACK: LED_OFF_OK");
}
actualizarOLED();
}
// 2. LECTURA Y ENVÍO PERIÓDICO DE TELEMETRÍA
if (millis() - ultimoEnvio > 2000) {
ultimoEnvio = millis();
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t) && !isnan(h)) {
temperatura = t;
humedad = h;
}
enviarTelemetria();
actualizarOLED();
}
}
void enviarACK(String ackMsg) {
LoRa.beginPacket();
LoRa.print(ackMsg);
LoRa.endPacket();
}
void enviarTelemetria() {
String payload = "TM," + String(temperatura, 1) + "," + String(humedad, 1) + "," + (statusLED ? "1" : "0");
LoRa.beginPacket();
LoRa.print(payload);
LoRa.endPacket();
Serial.print("📡 Telemetría transmitida: ");
Serial.println(payload);
}
void actualizarOLED() {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print("BERMEJASAT CANSAT");
display.drawLine(0, 10, 128, 10, WHITE);
display.setCursor(0, 16);
display.print("Temp: "); display.print(temperatura, 1); display.print(" C");
display.setCursor(0, 28);
display.print("Hum: "); display.print(humedad, 1); display.print(" %");
display.setCursor(0, 42);
display.print("Actuador: "); display.print(statusLED ? "[ON]" : "[OFF]");
display.setCursor(0, 54);
display.print("Cmd: "); display.print(ultimoComando);
display.display();
}
💻 Código 2: Firmware de la Estación Terrena (TTGO #2)
Permite enviar órdenes tecleando 1 o 0 desde el Monitor Serie del ordenador, transmite el comando por LoRa y muestra la telemetría recibida junto con la intensidad de señal (RSSI en dBm).
C++
#include <SPI.h>
#include <LoRa.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_SDA 21
#define OLED_SCL 22
#define OLED_RST 16
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST);
#define SCK_LORA 5
#define MISO_LORA 19
#define MOSI_LORA 27
#define SS_LORA 18
#define RST_LORA 14
#define DIO0_LORA 26
String ultimaTemp = "--";
String ultimaHum = "--";
String estadoLED = "DESCONOCIDO";
int ultimoRSSI = 0;
void setup() {
Serial.begin(115200);
pinMode(OLED_RST, OUTPUT);
digitalWrite(OLED_RST, LOW);
delay(20);
digitalWrite(OLED_RST, HIGH);
Wire.begin(OLED_SDA, OLED_SCL);
if (display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.println("BERMEJASAT BASE");
display.println("Iniciando LoRa 868...");
display.display();
}
SPI.begin(SCK_LORA, MISO_LORA, MOSI_LORA, SS_LORA);
LoRa.setPins(SS_LORA, RST_LORA, DIO0_LORA);
if (!LoRa.begin(868E6)) {
Serial.println("❌ Error al iniciar LoRa Terrena!");
while (1);
}
Serial.println("\n==============================================");
Serial.println("📡 ESTACIÓN TERRENA BERMEJASAT LISTA");
Serial.println("Pulse '1' + Enter -> Enviar LED_ON");
Serial.println("Pulse '0' + Enter -> Enviar LED_OFF");
Serial.println("==============================================\n");
}
void loop() {
// 1. LEER COMANDOS DESDE EL MONITOR SERIE
if (Serial.available()) {
char input = Serial.read();
if (input == '1') {
enviarComando("LED_ON");
} else if (input == '0') {
enviarComando("LED_OFF");
}
}
// 2. RECIBIR PAQUETES LORA
int packetSize = LoRa.parsePacket();
if (packetSize) {
String rxData = "";
while (LoRa.available()) {
rxData += (char)LoRa.read();
}
ultimoRSSI = LoRa.packetRssi();
if (rxData.startsWith("ACK:")) {
Serial.print("✅ [CONFIRMACIÓN CANSAT] ");
Serial.print(rxData);
Serial.print(" | RSSI: "); Serial.print(ultimoRSSI); Serial.println(" dBm");
} else if (rxData.startsWith("TM,")) {
int p1 = rxData.indexOf(',', 3);
int p2 = rxData.indexOf(',', p1 + 1);
if (p1 != -1 && p2 != -1) {
ultimaTemp = rxData.substring(3, p1);
ultimaHum = rxData.substring(p1 + 1, p2);
String bitLED = rxData.substring(p2 + 1);
estadoLED = (bitLED == "1") ? "ENCENDIDO" : "APAGADO";
}
Serial.print("📊 [TM] Temp: "); Serial.print(ultimaTemp); Serial.print(" °C | ");
Serial.print("Hum: "); Serial.print(ultimaHum); Serial.print(" % | ");
Serial.print("Actuador: "); Serial.print(estadoLED); Serial.print(" | ");
Serial.print("RSSI: "); Serial.print(ultimoRSSI); Serial.println(" dBm");
}
actualizarOLEDTerrena();
}
}
void enviarComando(String cmd) {
Serial.print("🚀 Enviando Telecomando: "); Serial.println(cmd);
LoRa.beginPacket();
LoRa.print(cmd);
LoRa.endPacket();
}
void actualizarOLEDTerrena() {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print("BERMEJASAT TERRENA");
display.drawLine(0, 10, 128, 10, WHITE);
display.setCursor(0, 16);
display.print("CanSat T: "); display.print(ultimaTemp); display.print(" C");
display.setCursor(0, 28);
display.print("CanSat H: "); display.print(ultimaHum); display.print(" %");
display.setCursor(0, 40);
display.print("Actuador: "); display.print(estadoLED);
display.setCursor(0, 54);
display.print("RSSI: "); display.print(ultimoRSSI); display.print(" dBm");
display.display();
}
🧪 Resultados y Verificación en Vuelo Simulado
El ensayo concluyó con éxito total en el bucle cerrado:
- Recepción continua: La Estación Terrena muestra en pantalla OLED y consola los datos ambientales actualizados del AM2302 enviados por el CanSat a 868 MHz.
- Envío de órdenes: Al enviar un
1por el Monitor Serie, la TTGO terrena emite el paqueteLED_ON. - Respuesta remota: El CanSat interpreta la instrucción, conmuta la salida digital, refresca su OLED con
Cmd: LED_ON/Actuador: [ON]y devuelve la tramaACK: LED_ON_OK. - Validación en tierra: La consola terrena confirma la recepción del ACK indicando un RSSI estable de -42 dBm.
💡 Importancia para el Proyecto CanSat
Reemplazar un “LED” por un relé, un optoacoplador o un transistor MOSFET nos permite implementar desde la base de control:
- Activación remota del sistema de liberación de paracaídas mediante hilo de nicrom.
- Encendido de balizas sonoras o lumínicas de rescate tras el impacto.
- Cambio dinámico del modo de transmisión (frecuencia de muestreo baja durante el ascenso vs. muestreo rápido en el descenso).
Con este avance, la arquitectura de comunicaciones de BermejaSat queda validada a nivel de software y protocolo. ¡Próxima parada: soldadura de sensores I2C y pruebas de campo! 🛰️ radio-link checked!


