From ee6b5d0255a33fd6e3914ee76f51f29d4ef02ff4 Mon Sep 17 00:00:00 2001 From: snarfattack Date: Sun, 29 Mar 2026 15:06:41 -0500 Subject: [PATCH 1/4] Add Lesson 7 Heltec V3 GPS and SD Card example --- .../Lesson_7_Heltec_V3_U-BloxGPS_SD-card.ino | 187 ++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Heltec_V3_U-BloxGPS_SD-card.ino diff --git a/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Heltec_V3_U-BloxGPS_SD-card.ino b/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Heltec_V3_U-BloxGPS_SD-card.ino new file mode 100644 index 0000000..02217cc --- /dev/null +++ b/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Heltec_V3_U-BloxGPS_SD-card.ino @@ -0,0 +1,187 @@ +#include +#include // Mikal Hart v1.0.3 +#include +#include +#include +#include +#include + +// Heltec V3 Internal Pins +#define VEXT_PIN 36 +#define OLED_RST 21 +#define LORA_NSS 8 +#define LORA_DIO1 14 +#define LORA_NRST 12 +#define LORA_BUSY 13 + +// SD Card Pins - Using separate HSPI bus +#define SD_CS 7 +#define SD_SCK 4 +#define SD_MISO 5 +#define SD_MOSI 6 + +SPIClass hspi(HSPI); +SX1262 lora = new Module(LORA_NSS, LORA_DIO1, LORA_NRST, LORA_BUSY); +TinyGPSPlus gps; +HardwareSerial SerialGPS(2); +Adafruit_SSD1306 display(128, 64, &Wire, OLED_RST); + +volatile bool txBusy = false; +unsigned long lastActionTime = 0; +bool sdEnabled = false; + +// Interrupt for non-blocking LoRa transmission +void onTxDone(void) { + txBusy = false; +} + +// Function to handle SD Mounting and Recovery +void trySDInit() { + if (SD.begin(SD_CS, hspi, 4000000)) { + sdEnabled = true; + File file = SD.open("/flight.csv", FILE_APPEND); + if(file) { + file.println("--- LOG SESSION START ---"); + file.println("DATE,TIME,LAT,LON,ALT_FT,SATS"); + file.close(); + } + } else { + sdEnabled = false; + } +} + +void setup() { + Serial.begin(115200); + + // 1. Hardware Power/Reset + pinMode(VEXT_PIN, OUTPUT); + digitalWrite(VEXT_PIN, LOW); + pinMode(OLED_RST, OUTPUT); + digitalWrite(OLED_RST, LOW); delay(50); digitalWrite(OLED_RST, HIGH); + + // 2. Initialize OLED + Wire.begin(17, 18); + display.begin(SSD1306_SWITCHCAPVCC, 0x3C); + display.clearDisplay(); + display.setTextColor(WHITE); + display.setCursor(0,0); + display.println("BALLOON SYSTEM BOOT"); + display.display(); + + // 3. Initialize SD (HSPI Bus) + hspi.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS); + trySDInit(); + display.println(sdEnabled ? "SD: READY" : "SD: NOT FOUND"); + display.display(); + + // 4. Initialize GPS + SerialGPS.begin(9600, SERIAL_8N1, 45, 46); + // UBX Airborne < 1g Mode Command + byte packet[] = {0xB5, 0x62, 0x06, 0x24, 0x24, 0x00, 0xFF, 0xFF, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10, 0x27, 0x00, 0x00, 0x05, 0x00, 0xFA, 0x00, 0xFA, 0x00, 0x64, 0x00, 0x2C, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0xDC}; + SerialGPS.write(packet, sizeof(packet)); + display.println("GPS: AIRBORNE SET"); + display.display(); + + // 5. Initialize LoRa + // freq: 915.0, bw: 125.0, sf: 7, cr: 5, sync: 0x12, pwr: 10, preamble: 8, tcxo: 1.6V, LDO: false (DC-DC) + int status = lora.begin(915.0, 125.0, 7, 5, 0x12, 10, 8, 1.6, false); + if (status == RADIOLIB_ERR_NONE) { + display.print("LORA PING... "); + display.display(); + + // Blocking startup ping + int txStatus = lora.transmit("BALLOON_ONLINE_STARTUP"); + + if(txStatus == RADIOLIB_ERR_NONE) display.println("SENT"); + else display.println("FAIL"); + + lora.setPacketSentAction(onTxDone); + } else { + display.printf("LORA ERR: %d\n", status); + } + display.display(); + delay(2000); +} + +void loop() { + // Constant GPS Feeding + while (SerialGPS.available() > 0) { + gps.encode(SerialGPS.read()); + } + + // Action Cycle: 10 Seconds + if (millis() - lastActionTime > 10000) { + + // A. SD Recovery Check + if (!sdEnabled) { + trySDInit(); + } + + // B. Build Data String + char dataBuf[128]; + snprintf(dataBuf, sizeof(dataBuf), "%02d/%02d/%d,%02d:%02d:%02d,%.6f,%.6f,%.1f,%d", + gps.date.month(), gps.date.day(), gps.date.year(), + gps.time.hour(), gps.time.minute(), gps.time.second(), + gps.location.lat(), gps.location.lng(), gps.altitude.feet(), gps.satellites.value()); + + // C. Write to SD + if (sdEnabled) { + File dataFile = SD.open("/flight.csv", FILE_APPEND); + if (dataFile) { + dataFile.println(dataBuf); + dataFile.close(); + } else { + sdEnabled = false; // Trigger recovery on next loop + } + } + + // D. Transmit LoRa + if (!txBusy) { + txBusy = true; + lora.startTransmit(dataBuf); + } + + lastActionTime = millis(); + } + + updateDisplay(); +} + +void updateDisplay() { + display.clearDisplay(); + display.setCursor(0, 0); + + // Date and Time (Always attempt to show) + if (gps.date.isValid() && gps.time.isValid()) { + display.printf("%02d/%02d/%02d %02d:%02d:%02d\n", + gps.date.month(), gps.date.day(), gps.date.year() % 100, + gps.time.hour(), gps.time.minute(), gps.time.second()); + } else { + display.printf("SATS: %d Wait Time...\n", gps.satellites.value()); + } + + display.println("--------------------"); + + // GPS Position Data + if (gps.location.isValid()) { + display.printf("ALT: %.0f ft\n", gps.altitude.feet()); + display.printf("LAT: %.5f\n", gps.location.lat()); + display.printf("LON: %.5f\n", gps.location.lng()); + } else { + display.println("GPS: SEARCHING..."); + display.printf("Visible Satellites: %d\n", gps.satellites.value()); + if (gps.satellites.value() > 2) display.println("Wait for lock..."); + } + + // SD Status Bar + display.setCursor(0, 56); + if (!sdEnabled) { + display.setTextColor(BLACK, WHITE); + display.print(" SD: ERROR (RETRYING) "); + display.setTextColor(WHITE, BLACK); + } else { + display.print("SD: LOGGING OK"); + } + + display.display(); +} From d0fa5c1cfaef8ac3befa7f70ab10eb726e03b2e1 Mon Sep 17 00:00:00 2001 From: snarfattack Date: Sun, 29 Mar 2026 15:07:17 -0500 Subject: [PATCH 2/4] Refactor LoRa setup and improve data display Refactored LoRa initialization and packet handling. Updated serial baud rate and improved screen display for received data. --- .../c/lesson-7/Lesson_7_Receiver_Code.ino | 86 +++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Receiver_Code.ino b/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Receiver_Code.ino index dd5a74b..5f0962f 100644 --- a/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Receiver_Code.ino +++ b/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Receiver_Code.ino @@ -1,26 +1,49 @@ #include "HT_st7735.h" #include +// Heltec Tracker v1.1/1.2 LoRa Pins #define LORA_NSS 8 #define LORA_DIO1 14 #define LORA_NRST 12 #define LORA_BUSY 13 +#define VEXT_CTRL 3 // GPIO 3 powers LoRa and Screen on Tracker v1.2 HT_st7735 screen; - -// initalize lora radio and make an interrupt function for when packets arrive SX1262 lora = new Module(LORA_NSS, LORA_DIO1, LORA_NRST, LORA_BUSY); + volatile bool packetAvailable = false; -ICACHE_RAM_ATTR // puts onReceive in RAM for better memory access + +// Interrupt function void onReceive(void) { packetAvailable = true; } void setup() { - Serial.begin(115000); + Serial.begin(115200); + + // 1. Power on the onboard LoRa and Screen + pinMode(VEXT_CTRL, OUTPUT); + digitalWrite(VEXT_CTRL, LOW); + delay(100); + + // 2. Initialize Screen screen.st7735_init(); screen.st7735_fill_screen(ST7735_BLACK); - loraInit(); + screen.st7735_write_str(0, 0, "BALLOON RECV", Font_7x10, ST7735_YELLOW, ST7735_BLACK); + + // 3. Initialize LoRa + // MUST MATCH TRANSMITTER EXACTLY: + // freq: 915.0, bw: 125.0, sf: 7, cr: 5, sync: 0x12, pwr: 10, preamble: 8, tcxo: 1.6V, LDO: false (DC-DC) + int status = lora.begin(915.0, 125.0, 7, 5, 0x12, 10, 8, 1.6, false); + + if (status == RADIOLIB_ERR_NONE) { + screen.st7735_write_str(0, 15, "LORA: OK (915MHz)", Font_7x10, ST7735_GREEN, ST7735_BLACK); + lora.setPacketReceivedAction(onReceive); + lora.startReceive(); + } else { + screen.st7735_write_str(0, 15, "LORA: FAIL", Font_7x10, ST7735_RED, ST7735_BLACK); + screen.st7735_write_str(0, 25, "ERR: " + String(status), Font_7x10, ST7735_WHITE, ST7735_BLACK); + } } void loop() { @@ -28,50 +51,27 @@ void loop() { readPacket(); } } - -int loraInit(){ - screen.st7735_write_str(0, 0, "LORA init...", Font_7x10, ST7735_WHITE, ST7735_BLACK); - int status1 = lora.begin(); - lora.setPacketReceivedAction(onReceive); // tells lora to call the interrupt function when a packet arrives - int status2 = lora.startReceive(); - - if (status1 == RADIOLIB_ERR_NONE && status2 == RADIOLIB_ERR_NONE) { - screen.st7735_write_str(90, 0, "OK", Font_7x10, ST7735_GREEN, ST7735_BLACK); - } else { - screen.st7735_write_str(90, 0, "ERR", Font_7x10, ST7735_RED, ST7735_BLACK); - screen.st7735_write_str(120, 0, (String)status1, Font_7x10, ST7735_WHITE, ST7735_BLACK); - screen.st7735_write_str(140, 0, (String)status2, Font_7x10, ST7735_WHITE, ST7735_BLACK); - } - return status1 + status2; // can be used for status checking: will be 0 if no errors -} void readPacket() { String data; - int status = lora.readData(data); // data is stored into "data" and status is stored is "status" + int status = lora.readData(data); + if (status == RADIOLIB_ERR_NONE) { - screen.st7735_write_str(0, 12, "Data: ", Font_7x10, ST7735_WHITE, ST7735_BLACK); - screen.st7735_write_str(64, 12, data, Font_7x10, ST7735_WHITE, ST7735_BLACK); + // Clear a portion of the screen for new data + screen.st7735_fill_screen(ST7735_BLACK); + screen.st7735_write_str(0, 0, "RECV SUCCESS:", Font_7x10, ST7735_YELLOW, ST7735_BLACK); + + // Display the payload (Time, Lat, Lon, Alt, Sats) + screen.st7735_write_str(0, 20, data, Font_7x10, ST7735_WHITE, ST7735_BLACK); - String rssi = (String)lora.getRSSI(); - screen.st7735_write_str(0, 24, "RSSI: dBm", Font_7x10, ST7735_WHITE, ST7735_BLACK); - screen.st7735_write_str(64, 24, rssi, Font_7x10, ST7735_WHITE, ST7735_BLACK); + // Signal Quality Stats + String stats = "R:" + String(lora.getRSSI()) + " S:" + String(lora.getSNR()); + screen.st7735_write_str(0, 110, stats, Font_7x10, ST7735_CYAN, ST7735_BLACK); - String snr = (String)lora.getSNR(); - screen.st7735_write_str(0, 36, "SNR: dB", Font_7x10, ST7735_WHITE, ST7735_BLACK); - screen.st7735_write_str(64, 36, snr, Font_7x10, ST7735_WHITE, ST7735_BLACK); - - String freqErr = (String)lora.getFrequencyError(); - screen.st7735_write_str(0, 48, "Freq Err: Hz", Font_7x10, ST7735_WHITE, ST7735_BLACK); - screen.st7735_write_str(64, 48, freqErr, Font_7x10, ST7735_WHITE, ST7735_BLACK); - - } else if (status == RADIOLIB_ERR_CRC_MISMATCH) { - screen.st7735_write_str(0, 12, "CRC Error", Font_7x10, ST7735_RED, ST7735_BLACK); - screen.st7735_write_str(0, 24, "Malformed Packet", Font_7x10, ST7735_WHITE, ST7735_BLACK); } else { - String err = (String)status; - screen.st7735_write_str(0, 12, "Rx Error:", Font_7x10, ST7735_RED, ST7735_BLACK); - screen.st7735_write_str(80, 12, err, Font_7x10, ST7735_WHITE, ST7735_BLACK); + screen.st7735_write_str(0, 50, "RX Error: " + String(status), Font_7x10, ST7735_RED, ST7735_BLACK); } - packetAvailable = false; // reset flag -} + packetAvailable = false; + lora.startReceive(); // Re-enable receive mode +} From 18134a9b74de11090799fb0a105ebac5db3d3708 Mon Sep 17 00:00:00 2001 From: snarfattack Date: Sun, 29 Mar 2026 17:15:41 -0500 Subject: [PATCH 3/4] Refactor GPS and LoRa functionality in transmitter code Updated GPS integration and LoRa transmission logic. Enhanced screen updates for GPS data and added new functions for data transmission. --- .../c/lesson-7/Lesson_7_Transmitter_Code.ino | 153 +++++++++++++----- 1 file changed, 112 insertions(+), 41 deletions(-) diff --git a/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Transmitter_Code.ino b/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Transmitter_Code.ino index 58a051b..30e383c 100644 --- a/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Transmitter_Code.ino +++ b/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Transmitter_Code.ino @@ -1,67 +1,138 @@ -#include "HT_st7735.h" +#include "HT_st7735.h" +#include "HT_TinyGPS++.h" #include -#define LORA_NSS 8 -#define LORA_DIO1 14 -#define LORA_NRST 12 -#define LORA_BUSY 13 +// --- Tracker v1.2 Internal GPS Pins --- +#define GPS_RX_PIN 33 // ESP32 RX <- GPS TX +#define GPS_TX_PIN 34 // ESP32 TX -> GPS RX +#define GPS_RESET 35 // GPS Reset Pin +#define VEXT_CTRL 3 // Power Control (LOW = ON) -HT_st7735 screen; +// --- LoRa Pins --- +#define LORA_NSS 8 +#define LORA_DIO1 14 +#define LORA_NRST 12 +#define LORA_BUSY 13 -// initalize lora radio and make an interrupt function for when packets finish sending +// Objects +HT_st7735 screen; SX1262 lora = new Module(LORA_NSS, LORA_DIO1, LORA_NRST, LORA_BUSY); +TinyGPSPlus gps; +HardwareSerial SerialGPS(1); + volatile bool txBusy = false; -ICACHE_RAM_ATTR // puts onReceive in RAM for better memory access +unsigned long lastActionTime = 0; +bool lastLockStatus = false; + void onTxDone(void) { txBusy = false; } -int transmissionStatus = RADIOLIB_ERR_NONE; // initial status is no errors -int packetCount = 0; void setup() { - Serial.begin(115000); + Serial.begin(115200); + + // 1. Power on hardware + pinMode(VEXT_CTRL, OUTPUT); + digitalWrite(VEXT_CTRL, LOW); + pinMode(GPS_RESET, OUTPUT); + digitalWrite(GPS_RESET, HIGH); + delay(500); + + // 2. Initialize Screen screen.st7735_init(); screen.st7735_fill_screen(ST7735_BLACK); - loraInit(); + screen.st7735_write_str(0, 0, "TRACKER V1.2", Font_7x10, ST7735_YELLOW, ST7735_BLACK); + + // 3. Initialize Internal GPS (RX=33, TX=34 @ 115200) + SerialGPS.begin(115200, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN); + + // 4. Initialize LoRa + int status = lora.begin(915.0, 125.0, 7, 5, 0x12, 10, 8, 1.6, false); + if (status == RADIOLIB_ERR_NONE) { + lora.setPacketSentAction(onTxDone); + lora.transmit("TRACKER_ONLINE"); + } + + delay(2000); + screen.st7735_fill_screen(ST7735_BLACK); } void loop() { - if(!txBusy){ - sendPacket(); + while (SerialGPS.available() > 0) { + gps.encode(SerialGPS.read()); + } + + // Detect transition from "No Lock" to "Lock" to wipe screen once to clear old text + if (gps.location.isValid() != lastLockStatus) { + lastLockStatus = gps.location.isValid(); + screen.st7735_fill_screen(ST7735_BLACK); + } + + if (millis() - lastActionTime > 10000) { + if (!txBusy && gps.location.isValid()) { + transmitData(); + } + lastActionTime = millis(); } - delay(1000); + + updateScreen(); } -int loraInit(){ - screen.st7735_write_str(0, 0, "LORA init...", Font_7x10, ST7735_WHITE, ST7735_BLACK); +void updateScreen() { + // --- ROW 1: DATE --- + if (gps.date.isValid()) { + char dStr[24]; + sprintf(dStr, "DATE: %02d/%02d/%04d", gps.date.month(), gps.date.day(), gps.date.year()); + screen.st7735_write_str(0, 0, dStr, Font_7x10, ST7735_YELLOW, ST7735_BLACK); + } else { + screen.st7735_write_str(0, 0, "DATE: SYNCING... ", Font_7x10, ST7735_WHITE, ST7735_BLACK); + } - int status = lora.begin(); - if (status == RADIOLIB_ERR_NONE) { - screen.st7735_write_str(90, 0, "OK", Font_7x10, ST7735_GREEN, ST7735_BLACK); + // --- ROW 2: TIME --- + if (gps.time.isValid()) { + char tStr[24]; + sprintf(tStr, "TIME: %02d:%02d:%02d ", gps.time.hour(), gps.time.minute(), gps.time.second()); + screen.st7735_write_str(0, 12, tStr, Font_7x10, ST7735_YELLOW, ST7735_BLACK); } else { - screen.st7735_write_str(90, 0, "ERR", Font_7x10, ST7735_RED, ST7735_BLACK); - screen.st7735_write_str(100, 0, (String)status, Font_7x10, ST7735_RED, ST7735_BLACK); + screen.st7735_write_str(0, 12, "TIME: SYNCING... ", Font_7x10, ST7735_WHITE, ST7735_BLACK); + } + + // --- GPS DATA BLOCK (Shifted Up) --- + if (gps.location.isValid()) { + char satStr[24], latStr[24], lonStr[24], altStr[24]; + + // Using %- pads the string with spaces to overwrite old characters + sprintf(satStr, "SATS: %-2d ", gps.satellites.value()); + sprintf(latStr, "LAT: %-10.5f ", gps.location.lat()); + sprintf(lonStr, "LON: %-10.5f ", gps.location.lng()); + sprintf(altStr, "ALT: %-7.0fft", gps.altitude.feet()); // Removed space before 'ft' to save width + + screen.st7735_write_str(0, 28, satStr, Font_7x10, ST7735_GREEN, ST7735_BLACK); + screen.st7735_write_str(0, 40, latStr, Font_7x10, ST7735_WHITE, ST7735_BLACK); + screen.st7735_write_str(0, 52, lonStr, Font_7x10, ST7735_WHITE, ST7735_BLACK); + screen.st7735_write_str(0, 64, altStr, Font_7x10, ST7735_CYAN, ST7735_BLACK); + } + else { + screen.st7735_write_str(0, 28, "STATUS: NO LOCK ", Font_7x10, ST7735_RED, ST7735_BLACK); + + char sCount[24]; + sprintf(sCount, "SATS VISIBLE: %-2d", gps.satellites.value()); + screen.st7735_write_str(0, 40, sCount, Font_7x10, ST7735_WHITE, ST7735_BLACK); + + // Clear the unused data lines below searching status + screen.st7735_write_str(0, 52, " ", Font_7x10, ST7735_BLACK, ST7735_BLACK); + screen.st7735_write_str(0, 64, " ", Font_7x10, ST7735_BLACK, ST7735_BLACK); } - lora.setPacketSentAction(onTxDone); - delay(2000); - return status; } -void sendPacket(){ +void transmitData() { txBusy = true; - String str = "Packet# " + String(packetCount); - packetCount++; - screen.st7735_write_str(0, 10, "Sending: ", Font_7x10, ST7735_WHITE, ST7735_BLACK); - screen.st7735_write_str(56, 10, str, Font_7x10, ST7735_WHITE, ST7735_BLACK); - transmissionStatus = lora.startTransmit(str); - delay(1000); - lora.finishTransmit(); - - if (transmissionStatus == RADIOLIB_ERR_NONE) { - String msg = "OK"; - screen.st7735_write_str(140, 10, msg, Font_7x10, ST7735_GREEN, ST7735_BLACK); - } else { - String msg = "ERR "+transmissionStatus; - screen.st7735_write_str(140, 10, msg, Font_7x10, ST7735_RED, ST7735_BLACK); - } + char payload[120]; + sprintf(payload, "%02d/%02d %02d:%02d,%.5f,%.5f,%.0f,%d", + gps.date.month(), gps.date.day(), + gps.time.hour(), gps.time.minute(), + gps.location.lat(), gps.location.lng(), + gps.altitude.feet(), gps.satellites.value()); + + lora.startTransmit(payload); } From d7c827a335cda418233f51b2d05f342ffe1f3ec6 Mon Sep 17 00:00:00 2001 From: snarfattack Date: Sun, 29 Mar 2026 17:39:30 -0500 Subject: [PATCH 4/4] Refactor receiver code for improved readability --- .../c/lesson-7/Lesson_7_Receiver_Code.ino | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Receiver_Code.ino b/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Receiver_Code.ino index 5f0962f..3304013 100644 --- a/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Receiver_Code.ino +++ b/telemetry/esp32-heltec-tracker/c/lesson-7/Lesson_7_Receiver_Code.ino @@ -1,19 +1,17 @@ #include "HT_st7735.h" #include -// Heltec Tracker v1.1/1.2 LoRa Pins #define LORA_NSS 8 #define LORA_DIO1 14 #define LORA_NRST 12 #define LORA_BUSY 13 -#define VEXT_CTRL 3 // GPIO 3 powers LoRa and Screen on Tracker v1.2 +#define VEXT_CTRL 3 HT_st7735 screen; SX1262 lora = new Module(LORA_NSS, LORA_DIO1, LORA_NRST, LORA_BUSY); volatile bool packetAvailable = false; -// Interrupt function void onReceive(void) { packetAvailable = true; } @@ -21,28 +19,21 @@ void onReceive(void) { void setup() { Serial.begin(115200); - // 1. Power on the onboard LoRa and Screen pinMode(VEXT_CTRL, OUTPUT); digitalWrite(VEXT_CTRL, LOW); delay(100); - // 2. Initialize Screen screen.st7735_init(); screen.st7735_fill_screen(ST7735_BLACK); - screen.st7735_write_str(0, 0, "BALLOON RECV", Font_7x10, ST7735_YELLOW, ST7735_BLACK); + screen.st7735_write_str(0, 0, "RECEIVER READY", Font_7x10, ST7735_YELLOW, ST7735_BLACK); - // 3. Initialize LoRa - // MUST MATCH TRANSMITTER EXACTLY: - // freq: 915.0, bw: 125.0, sf: 7, cr: 5, sync: 0x12, pwr: 10, preamble: 8, tcxo: 1.6V, LDO: false (DC-DC) + // Frequency: 915.0 MHz, TCXO: 1.6V, Regulator: DC-DC int status = lora.begin(915.0, 125.0, 7, 5, 0x12, 10, 8, 1.6, false); if (status == RADIOLIB_ERR_NONE) { - screen.st7735_write_str(0, 15, "LORA: OK (915MHz)", Font_7x10, ST7735_GREEN, ST7735_BLACK); + screen.st7735_write_str(0, 15, "LORA: OK", Font_7x10, ST7735_GREEN, ST7735_BLACK); lora.setPacketReceivedAction(onReceive); lora.startReceive(); - } else { - screen.st7735_write_str(0, 15, "LORA: FAIL", Font_7x10, ST7735_RED, ST7735_BLACK); - screen.st7735_write_str(0, 25, "ERR: " + String(status), Font_7x10, ST7735_WHITE, ST7735_BLACK); } } @@ -57,21 +48,36 @@ void readPacket() { int status = lora.readData(data); if (status == RADIOLIB_ERR_NONE) { - // Clear a portion of the screen for new data screen.st7735_fill_screen(ST7735_BLACK); - screen.st7735_write_str(0, 0, "RECV SUCCESS:", Font_7x10, ST7735_YELLOW, ST7735_BLACK); - // Display the payload (Time, Lat, Lon, Alt, Sats) - screen.st7735_write_str(0, 20, data, Font_7x10, ST7735_WHITE, ST7735_BLACK); + // --- ROW 1: HEADER --- + screen.st7735_write_str(0, 0, "PAYLOAD:", Font_7x10, ST7735_YELLOW, ST7735_BLACK); + + // --- ROW 2: DATA (Starts at 12, allows wrapping to ~34) --- + screen.st7735_write_str(0, 12, data, Font_7x10, ST7735_WHITE, ST7735_BLACK); + + // --- ROW 3: RADIO STATS --- + // Lowered to Y=40 to clear potential 2nd line of payload text + char radioStr[40]; + sprintf(radioStr, "RSSI:%-4.0f SNR:%-3.1f", lora.getRSSI(), lora.getSNR()); + screen.st7735_write_str(0, 40, radioStr, Font_7x10, ST7735_CYAN, ST7735_BLACK); - // Signal Quality Stats - String stats = "R:" + String(lora.getRSSI()) + " S:" + String(lora.getSNR()); - screen.st7735_write_str(0, 110, stats, Font_7x10, ST7735_CYAN, ST7735_BLACK); + // --- ROW 4: FREQUENCY ERROR --- + char feStr[40]; + sprintf(feStr, "FE: %-5.0f Hz", lora.getFrequencyError()); + screen.st7735_write_str(0, 53, feStr, Font_7x10, ST7735_WHITE, ST7735_BLACK); } else { - screen.st7735_write_str(0, 50, "RX Error: " + String(status), Font_7x10, ST7735_RED, ST7735_BLACK); + // ERROR HANDLING (Screen turns red text on failure) + screen.st7735_fill_screen(ST7735_BLACK); + screen.st7735_write_str(0, 0, "RX ERROR!", Font_7x10, ST7735_RED, ST7735_BLACK); + if(status == RADIOLIB_ERR_CRC_MISMATCH) { + screen.st7735_write_str(0, 15, "CRC CORRUPTION", Font_7x10, ST7735_RED, ST7735_BLACK); + } else { + screen.st7735_write_str(0, 15, "CODE: " + String(status), Font_7x10, ST7735_WHITE, ST7735_BLACK); + } } packetAvailable = false; - lora.startReceive(); // Re-enable receive mode + lora.startReceive(); }