From 6dffd936aedaed0a362583316baabf61fde4e83b Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Fri, 30 Jan 2026 17:27:31 -0700 Subject: [PATCH] feat: add Heltec V3 LoRa TX beacon firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PlatformIO project for ESP32-S3 + SX1262 (Heltec WiFi LoRa 32 V3). Transmits periodic LoRa packets matching gr-lora_sdr receiver params: SF7, BW125kHz, CR4/5, 915MHz, sync word 0x12, CRC enabled. Verified end-to-end: beacon TX → RTL-SDR → Docker gr-lora_sdr → decoded "GR-MCP #N" payloads with valid CRC. --- firmware/heltec-lora-beacon/.gitignore | 1 + firmware/heltec-lora-beacon/include/README | 37 ++++++++ firmware/heltec-lora-beacon/platformio.ini | 17 ++++ firmware/heltec-lora-beacon/src/main.cpp | 105 +++++++++++++++++++++ firmware/heltec-lora-beacon/test/README | 11 +++ 5 files changed, 171 insertions(+) create mode 100644 firmware/heltec-lora-beacon/.gitignore create mode 100644 firmware/heltec-lora-beacon/include/README create mode 100644 firmware/heltec-lora-beacon/platformio.ini create mode 100644 firmware/heltec-lora-beacon/src/main.cpp create mode 100644 firmware/heltec-lora-beacon/test/README diff --git a/firmware/heltec-lora-beacon/.gitignore b/firmware/heltec-lora-beacon/.gitignore new file mode 100644 index 0000000..03f4a3c --- /dev/null +++ b/firmware/heltec-lora-beacon/.gitignore @@ -0,0 +1 @@ +.pio diff --git a/firmware/heltec-lora-beacon/include/README b/firmware/heltec-lora-beacon/include/README new file mode 100644 index 0000000..49819c0 --- /dev/null +++ b/firmware/heltec-lora-beacon/include/README @@ -0,0 +1,37 @@ + +This directory is intended for project header files. + +A header file is a file containing C declarations and macro definitions +to be shared between several project source files. You request the use of a +header file in your project source file (C, C++, etc) located in `src` folder +by including it, with the C preprocessing directive `#include'. + +```src/main.c + +#include "header.h" + +int main (void) +{ + ... +} +``` + +Including a header file produces the same results as copying the header file +into each source file that needs it. Such copying would be time-consuming +and error-prone. With a header file, the related declarations appear +in only one place. If they need to be changed, they can be changed in one +place, and programs that include the header file will automatically use the +new version when next recompiled. The header file eliminates the labor of +finding and changing all the copies as well as the risk that a failure to +find one copy will result in inconsistencies within a program. + +In C, the convention is to give header files names that end with `.h'. + +Read more about using header files in official GCC documentation: + +* Include Syntax +* Include Operation +* Once-Only Headers +* Computed Includes + +https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html diff --git a/firmware/heltec-lora-beacon/platformio.ini b/firmware/heltec-lora-beacon/platformio.ini new file mode 100644 index 0000000..2d25135 --- /dev/null +++ b/firmware/heltec-lora-beacon/platformio.ini @@ -0,0 +1,17 @@ +; Heltec V3 LoRa TX Beacon +; Periodically transmits LoRa packets for testing gr-mcp SDR reception + +[env:heltec_wifi_lora_32_V3] +platform = espressif32 +board = heltec_wifi_lora_32_V3 +framework = arduino +monitor_speed = 115200 +upload_port = /dev/ttyUSB0 +monitor_port = /dev/ttyUSB0 + +lib_deps = + jgromes/RadioLib@^7.1.2 + +; Use UART0 (CP2102 on ttyUSB0) for Serial output — simpler than USB CDC +build_flags = + -DARDUINO_USB_CDC_ON_BOOT=0 diff --git a/firmware/heltec-lora-beacon/src/main.cpp b/firmware/heltec-lora-beacon/src/main.cpp new file mode 100644 index 0000000..7a2883e --- /dev/null +++ b/firmware/heltec-lora-beacon/src/main.cpp @@ -0,0 +1,105 @@ +/* + * Heltec V3 LoRa TX Beacon + * + * Transmits periodic LoRa packets for testing gr-mcp SDR reception. + * Parameters match the gr-lora_sdr receiver defaults. + * + * Hardware: Heltec WiFi LoRa 32 V3 (ESP32-S3 + SX1262) + */ + +#include +#include +#include + +// Heltec V3 SX1262 pin mapping +#define LORA_NSS 8 +#define LORA_DIO1 14 +#define LORA_RST 12 +#define LORA_BUSY 13 +#define LORA_SCK 9 +#define LORA_MOSI 10 +#define LORA_MISO 11 + +// Heltec V3 power control — Vext must be LOW to power LoRa + OLED +#define VEXT_CTRL 36 + +// Custom SPI bus for the LoRa radio (not the default Arduino SPI pins) +SPIClass loraSPI(FSPI); + +// SX1262 with custom SPI +SX1262 radio = new Module(LORA_NSS, LORA_DIO1, LORA_RST, LORA_BUSY, loraSPI); + +// LoRa parameters — must match gr-lora_sdr receiver +const float FREQUENCY = 915.0; // MHz (US ISM band) +const float BANDWIDTH = 125.0; // kHz +const uint8_t SPREADING = 7; // SF7 +const uint8_t CODING_RATE = 5; // CR 4/5 +const uint8_t SYNC_WORD = 0x12; // LoRaWAN public sync word +const int8_t TX_POWER = 14; // dBm +const uint16_t PREAMBLE_LEN = 8; // symbols + +uint32_t packet_count = 0; + +void setup() { + Serial.begin(115200); + delay(2000); // wait for USB CDC enumeration + + // Enable Vext to power the LoRa radio + pinMode(VEXT_CTRL, OUTPUT); + digitalWrite(VEXT_CTRL, LOW); + delay(100); + + Serial.println("=== Heltec V3 LoRa TX Beacon ==="); + Serial.printf("Freq: %.1f MHz, SF%d, BW%.0fk, CR4/%d\n", + FREQUENCY, SPREADING, BANDWIDTH, CODING_RATE); + Serial.printf("Sync: 0x%02X, Power: %d dBm\n", SYNC_WORD, TX_POWER); + + // Initialize custom SPI bus with Heltec V3 LoRa pins + loraSPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_NSS); + + Serial.println("SPI initialized on SCK=9 MISO=11 MOSI=10 NSS=8"); + + int state = radio.begin( + FREQUENCY, + BANDWIDTH, + SPREADING, + CODING_RATE, + SYNC_WORD, + TX_POWER, + PREAMBLE_LEN + ); + + if (state != RADIOLIB_ERR_NONE) { + Serial.printf("Radio init FAILED: %d\n", state); + while (true) { delay(1000); } + } + + // SX1262-specific: set DIO2 as RF switch control (required for Heltec V3) + radio.setDio2AsRfSwitch(true); + + // Use explicit header mode (default for LoRa) + radio.explicitHeader(); + + // Enable CRC (gr-lora_sdr expects CRC) + radio.setCRC(true); + + Serial.println("Radio initialized OK, starting TX loop"); +} + +void loop() { + char payload[64]; + snprintf(payload, sizeof(payload), "GR-MCP #%lu", (unsigned long)packet_count); + + Serial.printf("[TX %lu] \"%s\" ... ", (unsigned long)packet_count, payload); + + int state = radio.transmit((uint8_t*)payload, strlen(payload)); + + if (state == RADIOLIB_ERR_NONE) { + Serial.printf("OK (%d dBm)\n", TX_POWER); + } else { + Serial.printf("FAIL: %d\n", state); + } + + packet_count++; + delay(3000); // TX every 3 seconds +} diff --git a/firmware/heltec-lora-beacon/test/README b/firmware/heltec-lora-beacon/test/README new file mode 100644 index 0000000..9b1e87b --- /dev/null +++ b/firmware/heltec-lora-beacon/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html