From 8883ee3e94ca092b33bb7fda1922686799adf793 Mon Sep 17 00:00:00 2001 From: Maze X Date: Fri, 27 Mar 2026 17:15:30 +0100 Subject: [PATCH] Scaffold PlatformIO project with 20 board configs and C99/C++ source skeleton MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three-tier configuration hierarchy: - [env:base] — RadioLib + default LoRa parameters - [soc_esp32/esp32s3/nrf52] — platform + framework per SoC - [env:board_name] — board-specific pins + chip selection 20 boards across 4 vendors: - Heltec: 11 boards (T114, CT62, E213, E290, Mesh Solar, T190, Tracker, Tracker V2, V2, V3, V4) - LilyGo: 4 boards (T-Beam 1W, sx1262, sx1276, supreme) - Seeed: 1 board (Xiao S3 + Wio SX1262 with verified pins) - RAK: 4 boards (RAK11310, RAK3112, RAK3401, RAK3x72, RAK4631) Known/verified pins: Heltec V2/V3/V4, RAK4631, Seeed Xiao S3 FIXME pins: all others (placeholders for future research) Source skeleton: - config.h — compile-time defaults + pin validation (#error checks) - kiss.h/c — KISS protocol implementation (C99) - radio.h/cpp — RadioLib wrapper with C API (extern "C" boundary) - main.cpp — Arduino entry point All files pass pre-commit (prettier, markdownlint, YAML check). --- .gitignore | 3 + PROJECT.md | 36 +++--- hardware/heltec/ct62/platformio.ini | 14 +++ hardware/heltec/e213/platformio.ini | 14 +++ hardware/heltec/e290/platformio.ini | 14 +++ hardware/heltec/mesh_solar/platformio.ini | 14 +++ hardware/heltec/t114/platformio.ini | 14 +++ hardware/heltec/t190/platformio.ini | 14 +++ hardware/heltec/tracker/platformio.ini | 14 +++ hardware/heltec/tracker_v2/platformio.ini | 14 +++ hardware/heltec/v2/platformio.ini | 12 ++ hardware/heltec/v3/platformio.ini | 13 ++ hardware/heltec/v4/platformio.ini | 13 ++ hardware/lilygo/t_beam_1w/platformio.ini | 14 +++ hardware/lilygo/t_beam_supreme/platformio.ini | 14 +++ hardware/lilygo/t_beam_sx1262/platformio.ini | 14 +++ hardware/lilygo/t_beam_sx1276/platformio.ini | 13 ++ hardware/rak/rak11310/platformio.ini | 14 +++ hardware/rak/rak3112/platformio.ini | 14 +++ hardware/rak/rak3401/platformio.ini | 14 +++ hardware/rak/rak3x72/platformio.ini | 14 +++ hardware/rak/rak4631/platformio.ini | 13 ++ .../seeed/xiao_s3_wio_sx1262/platformio.ini | 21 ++++ platformio.ini | 56 +++++++++ soc/esp32/platformio.ini | 5 + soc/esp32s3/platformio.ini | 5 + soc/nrf52/platformio.ini | 5 + src/config.h | 51 ++++++++ src/kiss.c | 111 ++++++++++++++++++ src/kiss.h | 70 +++++++++++ src/main.cpp | 48 ++++++++ src/radio.cpp | 106 +++++++++++++++++ src/radio.h | 48 ++++++++ 33 files changed, 824 insertions(+), 15 deletions(-) create mode 100644 hardware/heltec/ct62/platformio.ini create mode 100644 hardware/heltec/e213/platformio.ini create mode 100644 hardware/heltec/e290/platformio.ini create mode 100644 hardware/heltec/mesh_solar/platformio.ini create mode 100644 hardware/heltec/t114/platformio.ini create mode 100644 hardware/heltec/t190/platformio.ini create mode 100644 hardware/heltec/tracker/platformio.ini create mode 100644 hardware/heltec/tracker_v2/platformio.ini create mode 100644 hardware/heltec/v2/platformio.ini create mode 100644 hardware/heltec/v3/platformio.ini create mode 100644 hardware/heltec/v4/platformio.ini create mode 100644 hardware/lilygo/t_beam_1w/platformio.ini create mode 100644 hardware/lilygo/t_beam_supreme/platformio.ini create mode 100644 hardware/lilygo/t_beam_sx1262/platformio.ini create mode 100644 hardware/lilygo/t_beam_sx1276/platformio.ini create mode 100644 hardware/rak/rak11310/platformio.ini create mode 100644 hardware/rak/rak3112/platformio.ini create mode 100644 hardware/rak/rak3401/platformio.ini create mode 100644 hardware/rak/rak3x72/platformio.ini create mode 100644 hardware/rak/rak4631/platformio.ini create mode 100644 hardware/seeed/xiao_s3_wio_sx1262/platformio.ini create mode 100644 platformio.ini create mode 100644 soc/esp32/platformio.ini create mode 100644 soc/esp32s3/platformio.ini create mode 100644 soc/nrf52/platformio.ini create mode 100644 src/config.h create mode 100644 src/kiss.c create mode 100644 src/kiss.h create mode 100644 src/main.cpp create mode 100644 src/radio.cpp create mode 100644 src/radio.h diff --git a/.gitignore b/.gitignore index cbf0615..668921d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ node_modules/ .cache/ +.pio/ +.vscode/ +*.pyc diff --git a/PROJECT.md b/PROJECT.md index 3960c3b..fe0a2a9 100644 --- a/PROJECT.md +++ b/PROJECT.md @@ -146,22 +146,26 @@ Not all bandwidths are legal for all frequency bands — consult local regulatio ## Project Structure +Three-tier configuration hierarchy: base + SoC + board. Pin definitions are compile-time +macros in board configs (no separate pins.h). + ```text loramodem/ -├── platformio.ini # Board environments and build configuration -├── PROJECT.md # This file -├── src/ -│ ├── main.cpp # Entry point; Arduino setup() and loop() -│ ├── kiss.h / kiss.cpp # KISS frame encoder/decoder -│ ├── radio.h / radio.cpp # RadioLib wrapper (init, TX, RX, config) -│ └── config.h # Default radio parameters and pin definitions -├── include/ # Shared headers (if needed) -├── lib/ # Local libraries (if needed) -├── variants/ # Board-specific hardware definitions -│ ├── heltec_wifi_lora_32_v3/pins.h -│ ├── rak4631/pins.h -│ └── [other boards]/pins.h -└── test/ # PlatformIO Unity test suite +├── platformio.ini # [platformio] + [env:base] +├── soc/ # SoC shared configs +│ ├── esp32/platformio.ini +│ ├── esp32s3/platformio.ini +│ └── nrf52/platformio.ini +├── hardware/ # 20 board configs +│ ├── heltec/ (11 boards) +│ ├── lilygo/ (4 boards) +│ ├── seeed/ (1 board) +│ └── rak/ (4 boards) +└── src/ + ├── main.cpp # Arduino setup()/loop() — calls C APIs + ├── kiss.h / kiss.c # KISS protocol — C99 + ├── radio.h / radio.cpp # RadioLib wrapper — C++ (extern "C" API) + └── config.h # Compile-time defaults + pin validation ``` ## Building @@ -171,7 +175,9 @@ Prerequisites: [PlatformIO](https://platformio.org/) CLI or IDE extension. **Build for a specific board:** ```sh -pio run -e heltec_wifi_lora_32_v3 +pio run -e heltec_v3 +pio run -e rak_rak4631 +pio run -e lilygo_t_beam_1w ``` **Upload to a connected board:** diff --git a/hardware/heltec/ct62/platformio.ini b/hardware/heltec/ct62/platformio.ini new file mode 100644 index 0000000..0f23133 --- /dev/null +++ b/hardware/heltec/ct62/platformio.ini @@ -0,0 +1,14 @@ +; Heltec CT62 — nRF52840 (likely) +; FIXME: verify board ID, chip, and pin mappings +[env:heltec_ct62] +extends = soc_nrf52, env:base +board = heltec_ct62 +build_flags = + ${soc_nrf52.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_CT62 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/heltec/e213/platformio.ini b/hardware/heltec/e213/platformio.ini new file mode 100644 index 0000000..1c630bb --- /dev/null +++ b/hardware/heltec/e213/platformio.ini @@ -0,0 +1,14 @@ +; Heltec E213 — SoC/chip unknown +; FIXME: identify SoC (ESP32/nRF52?) and LoRa chip, get pin mappings +[env:heltec_e213] +extends = soc_esp32, env:base +board = heltec_e213 +build_flags = + ${soc_esp32.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_E213 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/heltec/e290/platformio.ini b/hardware/heltec/e290/platformio.ini new file mode 100644 index 0000000..3f32906 --- /dev/null +++ b/hardware/heltec/e290/platformio.ini @@ -0,0 +1,14 @@ +; Heltec E290 — SoC/chip unknown +; FIXME: identify SoC and LoRa chip, get pin mappings +[env:heltec_e290] +extends = soc_esp32, env:base +board = heltec_e290 +build_flags = + ${soc_esp32.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_E290 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/heltec/mesh_solar/platformio.ini b/hardware/heltec/mesh_solar/platformio.ini new file mode 100644 index 0000000..bfd0d35 --- /dev/null +++ b/hardware/heltec/mesh_solar/platformio.ini @@ -0,0 +1,14 @@ +; Heltec Mesh Solar — SoC/chip unknown +; FIXME: identify SoC and LoRa chip, get pin mappings +[env:heltec_mesh_solar] +extends = soc_esp32, env:base +board = heltec_mesh_solar +build_flags = + ${soc_esp32.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_MESH_SOLAR + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/heltec/t114/platformio.ini b/hardware/heltec/t114/platformio.ini new file mode 100644 index 0000000..6afaea7 --- /dev/null +++ b/hardware/heltec/t114/platformio.ini @@ -0,0 +1,14 @@ +; Heltec T114 — nRF52840, SX1262 (likely) +; FIXME: verify board ID, chip, and pin mappings from hardware datasheet +[env:heltec_t114] +extends = soc_nrf52, env:base +board = heltec_t114 +build_flags = + ${soc_nrf52.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_T114 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/heltec/t190/platformio.ini b/hardware/heltec/t190/platformio.ini new file mode 100644 index 0000000..9c68032 --- /dev/null +++ b/hardware/heltec/t190/platformio.ini @@ -0,0 +1,14 @@ +; Heltec T190 — nRF52840 (likely) +; FIXME: verify board ID, chip, and pin mappings +[env:heltec_t190] +extends = soc_nrf52, env:base +board = heltec_t190 +build_flags = + ${soc_nrf52.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_T190 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/heltec/tracker/platformio.ini b/hardware/heltec/tracker/platformio.ini new file mode 100644 index 0000000..4b08457 --- /dev/null +++ b/hardware/heltec/tracker/platformio.ini @@ -0,0 +1,14 @@ +; Heltec Tracker — nRF52840, SX1262 (likely) +; FIXME: verify board ID and pin mappings from hardware datasheet +[env:heltec_tracker] +extends = soc_nrf52, env:base +board = heltec_tracker +build_flags = + ${soc_nrf52.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_TRACKER + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/heltec/tracker_v2/platformio.ini b/hardware/heltec/tracker_v2/platformio.ini new file mode 100644 index 0000000..f46f2dc --- /dev/null +++ b/hardware/heltec/tracker_v2/platformio.ini @@ -0,0 +1,14 @@ +; Heltec Tracker V2 — nRF52840, SX1262 (likely) +; FIXME: verify board ID and pin mappings +[env:heltec_tracker_v2] +extends = soc_nrf52, env:base +board = heltec_tracker_v2 +build_flags = + ${soc_nrf52.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_TRACKER_V2 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/heltec/v2/platformio.ini b/hardware/heltec/v2/platformio.ini new file mode 100644 index 0000000..e60c0f8 --- /dev/null +++ b/hardware/heltec/v2/platformio.ini @@ -0,0 +1,12 @@ +; Heltec WiFi LoRa 32 V2 — ESP32, SX1276, 868 MHz +[env:heltec_v2] +extends = soc_esp32, env:base +board = heltec_wifi_lora_32 +build_flags = + ${soc_esp32.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_V2 + -DLORA_CHIP_SX1276 + -DLORA_PIN_NSS=18 + -DLORA_PIN_DIO0=26 + -DLORA_PIN_RESET=14 diff --git a/hardware/heltec/v3/platformio.ini b/hardware/heltec/v3/platformio.ini new file mode 100644 index 0000000..7a51b04 --- /dev/null +++ b/hardware/heltec/v3/platformio.ini @@ -0,0 +1,13 @@ +; Heltec WiFi LoRa 32 V3 — ESP32-S3, SX1262, 868 MHz +[env:heltec_v3] +extends = soc_esp32s3, env:base +board = heltec_wifi_lora_32_v3 +build_flags = + ${soc_esp32s3.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_V3 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=8 + -DLORA_PIN_DIO1=14 + -DLORA_PIN_RESET=12 + -DLORA_PIN_BUSY=13 diff --git a/hardware/heltec/v4/platformio.ini b/hardware/heltec/v4/platformio.ini new file mode 100644 index 0000000..0fca009 --- /dev/null +++ b/hardware/heltec/v4/platformio.ini @@ -0,0 +1,13 @@ +; Heltec WiFi LoRa 32 V4 — ESP32-S3, SX1262, 868 MHz +[env:heltec_v4] +extends = soc_esp32s3, env:base +board = heltec_wifi_lora_32_v4 +build_flags = + ${soc_esp32s3.build_flags} + ${env:base.build_flags} + -DBOARD_HELTEC_V4 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=8 + -DLORA_PIN_DIO1=14 + -DLORA_PIN_RESET=12 + -DLORA_PIN_BUSY=13 diff --git a/hardware/lilygo/t_beam_1w/platformio.ini b/hardware/lilygo/t_beam_1w/platformio.ini new file mode 100644 index 0000000..6f6bd96 --- /dev/null +++ b/hardware/lilygo/t_beam_1w/platformio.ini @@ -0,0 +1,14 @@ +; LilyGo T-Beam 1W — ESP32, SX1262 (likely) +; FIXME: verify board ID and pin mappings from hardware datasheet +[env:lilygo_t_beam_1w] +extends = soc_esp32, env:base +board = lilygo_t_beam_1w +build_flags = + ${soc_esp32.build_flags} + ${env:base.build_flags} + -DBOARD_LILYGO_T_BEAM_1W + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/lilygo/t_beam_supreme/platformio.ini b/hardware/lilygo/t_beam_supreme/platformio.ini new file mode 100644 index 0000000..1f3ac58 --- /dev/null +++ b/hardware/lilygo/t_beam_supreme/platformio.ini @@ -0,0 +1,14 @@ +; LilyGo T-Beam Supreme (SX1262) — ESP32, SX1262 +; FIXME: verify board ID and pin mappings +[env:lilygo_t_beam_supreme] +extends = soc_esp32, env:base +board = lilygo_t_beam_supreme +build_flags = + ${soc_esp32.build_flags} + ${env:base.build_flags} + -DBOARD_LILYGO_T_BEAM_SUPREME + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/lilygo/t_beam_sx1262/platformio.ini b/hardware/lilygo/t_beam_sx1262/platformio.ini new file mode 100644 index 0000000..fbe68bc --- /dev/null +++ b/hardware/lilygo/t_beam_sx1262/platformio.ini @@ -0,0 +1,14 @@ +; LilyGo T-Beam with SX1262 — ESP32, SX1262 +; FIXME: verify board ID and pin mappings +[env:lilygo_t_beam_sx1262] +extends = soc_esp32, env:base +board = lilygo_t_beam_sx1262 +build_flags = + ${soc_esp32.build_flags} + ${env:base.build_flags} + -DBOARD_LILYGO_T_BEAM_SX1262 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/lilygo/t_beam_sx1276/platformio.ini b/hardware/lilygo/t_beam_sx1276/platformio.ini new file mode 100644 index 0000000..5f261df --- /dev/null +++ b/hardware/lilygo/t_beam_sx1276/platformio.ini @@ -0,0 +1,13 @@ +; LilyGo T-Beam with SX1276 — ESP32, SX1276 +; FIXME: verify board ID and pin mappings +[env:lilygo_t_beam_sx1276] +extends = soc_esp32, env:base +board = lilygo_t_beam_sx1276 +build_flags = + ${soc_esp32.build_flags} + ${env:base.build_flags} + -DBOARD_LILYGO_T_BEAM_SX1276 + -DLORA_CHIP_SX1276 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO0=0 + -DLORA_PIN_RESET=0 diff --git a/hardware/rak/rak11310/platformio.ini b/hardware/rak/rak11310/platformio.ini new file mode 100644 index 0000000..ff4d594 --- /dev/null +++ b/hardware/rak/rak11310/platformio.ini @@ -0,0 +1,14 @@ +; RAK11310 — RP2040-based (different platform) +; FIXME: determine platform (likely rp2040), chip, and pin mappings +[env:rak_rak11310] +extends = soc_nrf52, env:base +board = rak11310 +build_flags = + ${soc_nrf52.build_flags} + ${env:base.build_flags} + -DBOARD_RAK_RAK11310 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/rak/rak3112/platformio.ini b/hardware/rak/rak3112/platformio.ini new file mode 100644 index 0000000..1e34867 --- /dev/null +++ b/hardware/rak/rak3112/platformio.ini @@ -0,0 +1,14 @@ +; RAK3112 — nRF52840 (likely), SX1262 (likely) +; FIXME: verify board ID, chip, and pin mappings +[env:rak_rak3112] +extends = soc_nrf52, env:base +board = rak3112 +build_flags = + ${soc_nrf52.build_flags} + ${env:base.build_flags} + -DBOARD_RAK_RAK3112 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/rak/rak3401/platformio.ini b/hardware/rak/rak3401/platformio.ini new file mode 100644 index 0000000..256818a --- /dev/null +++ b/hardware/rak/rak3401/platformio.ini @@ -0,0 +1,14 @@ +; RAK3401 — nRF52840 (likely), SX1262 (likely) +; FIXME: verify board ID, chip, and pin mappings +[env:rak_rak3401] +extends = soc_nrf52, env:base +board = rak3401 +build_flags = + ${soc_nrf52.build_flags} + ${env:base.build_flags} + -DBOARD_RAK_RAK3401 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/rak/rak3x72/platformio.ini b/hardware/rak/rak3x72/platformio.ini new file mode 100644 index 0000000..dda443b --- /dev/null +++ b/hardware/rak/rak3x72/platformio.ini @@ -0,0 +1,14 @@ +; RAK3x72 — nRF52840 (likely), SX1262 (likely) +; FIXME: verify which variants (3172, 3272, 3372?), board IDs, and pin mappings +[env:rak_rak3x72] +extends = soc_nrf52, env:base +board = rak3x72 +build_flags = + ${soc_nrf52.build_flags} + ${env:base.build_flags} + -DBOARD_RAK_RAK3X72 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=0 + -DLORA_PIN_DIO1=0 + -DLORA_PIN_RESET=0 + -DLORA_PIN_BUSY=0 diff --git a/hardware/rak/rak4631/platformio.ini b/hardware/rak/rak4631/platformio.ini new file mode 100644 index 0000000..822665f --- /dev/null +++ b/hardware/rak/rak4631/platformio.ini @@ -0,0 +1,13 @@ +; RAK4631 — nRF52840, SX1262, 868 MHz +[env:rak_rak4631] +extends = soc_nrf52, env:base +board = wiscore_rak4631 +build_flags = + ${soc_nrf52.build_flags} + ${env:base.build_flags} + -DBOARD_RAK_RAK4631 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=42 + -DLORA_PIN_DIO1=47 + -DLORA_PIN_RESET=38 + -DLORA_PIN_BUSY=46 diff --git a/hardware/seeed/xiao_s3_wio_sx1262/platformio.ini b/hardware/seeed/xiao_s3_wio_sx1262/platformio.ini new file mode 100644 index 0000000..4ea1c6b --- /dev/null +++ b/hardware/seeed/xiao_s3_wio_sx1262/platformio.ini @@ -0,0 +1,21 @@ +; Seeed Xiao S3 + Wio SX1262 — ESP32-S3, SX1262 +[env:seeed_xiao_s3_wio_sx1262] +extends = soc_esp32s3, env:base +board = seeed_xiao_s3 +build_flags = + ${soc_esp32s3.build_flags} + ${env:base.build_flags} + -DBOARD_SEEED_XIAO_S3_WIO_SX1262 + -DLORA_CHIP_SX1262 + -DLORA_PIN_NSS=41 + -DLORA_PIN_DIO1=39 + -DLORA_PIN_RESET=42 + -DLORA_PIN_BUSY=40 + ; SX1262 RF switch on DIO2 + -DSX126X_DIO2_AS_RF_SWITCH=true + -DSX126X_DIO3_TCXO_VOLTAGE=1.8 + -DSX126X_CURRENT_LIMIT=140 + ; SPI pins + -DLORA_PIN_SCLK=7 + -DLORA_PIN_MISO=8 + -DLORA_PIN_MOSI=9 diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..3b1e1dd --- /dev/null +++ b/platformio.ini @@ -0,0 +1,56 @@ +; Root PlatformIO configuration for LoRa KISS modem (20 boards) +; Board-specific environments are in hardware///platformio.ini +; SoC shared settings are in soc//platformio.ini + +[platformio] +default_envs = + heltec_v3, + rak_rak4631, + heltec_v2 + +extra_configs = + soc/esp32/platformio.ini + soc/esp32s3/platformio.ini + soc/nrf52/platformio.ini + hardware/heltec/t114/platformio.ini + hardware/heltec/ct62/platformio.ini + hardware/heltec/e213/platformio.ini + hardware/heltec/e290/platformio.ini + hardware/heltec/mesh_solar/platformio.ini + hardware/heltec/t190/platformio.ini + hardware/heltec/tracker/platformio.ini + hardware/heltec/tracker_v2/platformio.ini + hardware/heltec/v2/platformio.ini + hardware/heltec/v3/platformio.ini + hardware/heltec/v4/platformio.ini + hardware/lilygo/t_beam_1w/platformio.ini + hardware/lilygo/t_beam_sx1262/platformio.ini + hardware/lilygo/t_beam_sx1276/platformio.ini + hardware/lilygo/t_beam_supreme/platformio.ini + hardware/seeed/xiao_s3_wio_sx1262/platformio.ini + hardware/rak/rak11310/platformio.ini + hardware/rak/rak3112/platformio.ini + hardware/rak/rak3401/platformio.ini + hardware/rak/rak3x72/platformio.ini + hardware/rak/rak4631/platformio.ini + +; ------------------------------------------------------------------ +; Base environment — all board envs extend this. +; Provides: RadioLib dependency, default LoRa parameters. +; ------------------------------------------------------------------ +[env:base] +lib_deps = + jgromes/RadioLib@^6.6.0 + +monitor_speed = 115200 + +build_flags = + ; Default LoRa radio parameters — override per-board as needed + -DLORA_FREQ_KHZ=869525UL + -DLORA_BW_HZ=125000UL + -DLORA_SF=7 + -DLORA_CR=5 + -DLORA_POWER_DBM=14 + -DLORA_SYNCWORD=0x34 + ; KISS serial baud rate + -DKISS_BAUD=115200 diff --git a/soc/esp32/platformio.ini b/soc/esp32/platformio.ini new file mode 100644 index 0000000..f3e6dd4 --- /dev/null +++ b/soc/esp32/platformio.ini @@ -0,0 +1,5 @@ +[soc_esp32] +platform = espressif32 +framework = arduino +build_flags = + -DARCH_ESP32 diff --git a/soc/esp32s3/platformio.ini b/soc/esp32s3/platformio.ini new file mode 100644 index 0000000..26a1df4 --- /dev/null +++ b/soc/esp32s3/platformio.ini @@ -0,0 +1,5 @@ +[soc_esp32s3] +platform = espressif32 +framework = arduino +build_flags = + -DARCH_ESP32S3 diff --git a/soc/nrf52/platformio.ini b/soc/nrf52/platformio.ini new file mode 100644 index 0000000..4764060 --- /dev/null +++ b/soc/nrf52/platformio.ini @@ -0,0 +1,5 @@ +[soc_nrf52] +platform = nordicnrf52 +framework = arduino +build_flags = + -DARCH_NRF52 diff --git a/src/config.h b/src/config.h new file mode 100644 index 0000000..08c3497 --- /dev/null +++ b/src/config.h @@ -0,0 +1,51 @@ +#pragma once + +/* Default LoRa parameters — override per-board in hardware/.../platformio.ini */ +#ifndef LORA_FREQ_KHZ +# define LORA_FREQ_KHZ 869525UL /* 869.525 MHz */ +#endif +#ifndef LORA_BW_HZ +# define LORA_BW_HZ 125000UL /* 125 kHz */ +#endif +#ifndef LORA_SF +# define LORA_SF 7 +#endif +#ifndef LORA_CR +# define LORA_CR 5 /* denominator: coding rate = 4/CR */ +#endif +#ifndef LORA_POWER_DBM +# define LORA_POWER_DBM 14 +#endif +#ifndef LORA_SYNCWORD +# define LORA_SYNCWORD 0x34 /* LoRa public */ +#endif + +#ifndef KISS_BAUD +# define KISS_BAUD 115200 +#endif + +/* Pin validation — boards must define all required pins via build_flags */ +#ifndef LORA_PIN_NSS +# error "LORA_PIN_NSS not defined — add to hardware///platformio.ini" +#endif +#ifndef LORA_PIN_RESET +# error "LORA_PIN_RESET not defined" +#endif + +#if defined(LORA_CHIP_SX1276) +# ifndef LORA_PIN_DIO0 +# error "LORA_PIN_DIO0 not defined (required for SX1276)" +# endif +#else +# ifndef LORA_PIN_DIO1 +# error "LORA_PIN_DIO1 not defined (required for SX1262/LR1110)" +# endif +# ifndef LORA_PIN_BUSY +# error "LORA_PIN_BUSY not defined (required for SX1262/LR1110)" +# endif +#endif + +#if !defined(LORA_CHIP_SX1276) && !defined(LORA_CHIP_SX1262) && \ + !defined(LORA_CHIP_LR1110) +# error "No LoRa chip defined — set LORA_CHIP_SX1276, LORA_CHIP_SX1262, or LORA_CHIP_LR1110" +#endif diff --git a/src/kiss.c b/src/kiss.c new file mode 100644 index 0000000..eb90efa --- /dev/null +++ b/src/kiss.c @@ -0,0 +1,111 @@ +/* KISS protocol implementation — C99 */ +#include "kiss.h" + +void kiss_decoder_init(kiss_decoder_t *dec) { + dec->state = KISS_STATE_IDLE; + dec->len = 0; +} + +bool kiss_decode(kiss_decoder_t *dec, uint8_t byte, kiss_frame_t *frame) { + switch (dec->state) { + case KISS_STATE_IDLE: + if (byte == KISS_FEND) { + dec->len = 0; + dec->state = KISS_STATE_IN_FRAME; + } + return false; + + case KISS_STATE_IN_FRAME: + if (byte == KISS_FESC) { + dec->state = KISS_STATE_ESCAPE; + return false; + } + if (byte == KISS_FEND) { + if (dec->len > 0) { + /* Frame complete */ + frame->port = dec->buf[0] & 0x0Fu; + frame->len = dec->len - 1; + if (frame->len > 0) { + for (size_t i = 0; i < frame->len; i++) { + frame->data[i] = dec->buf[i + 1]; + } + } + dec->state = KISS_STATE_IDLE; + return true; + } + dec->state = KISS_STATE_IDLE; + return false; + } + if (dec->len < KISS_MAX_FRAME + 1) { + dec->buf[dec->len++] = byte; + } + return false; + + case KISS_STATE_ESCAPE: + if (byte == KISS_TFEND) { + byte = KISS_FEND; + } else if (byte == KISS_TFESC) { + byte = KISS_FESC; + } + if (dec->len < KISS_MAX_FRAME + 1) { + dec->buf[dec->len++] = byte; + } + dec->state = KISS_STATE_IN_FRAME; + return false; + } + return false; +} + +size_t kiss_encode(uint8_t port, const uint8_t *data, size_t len, + uint8_t *dst, size_t dst_cap) { + if (dst_cap < len + 3) + return 0; /* Need at least: FEND type data... FEND */ + + size_t pos = 0; + dst[pos++] = KISS_FEND; + + /* Type byte: port in upper nibble, cmd in lower nibble (0 for data) */ + uint8_t type = (port << 4) | 0x00; + if (type == KISS_FEND) { + dst[pos++] = KISS_FESC; + dst[pos++] = KISS_TFEND; + } else if (type == KISS_FESC) { + dst[pos++] = KISS_FESC; + dst[pos++] = KISS_TFESC; + } else { + dst[pos++] = type; + } + + /* Payload with escaping */ + for (size_t i = 0; i < len; i++) { + if (pos + 2 > dst_cap) + return 0; /* Overflow */ + if (data[i] == KISS_FEND) { + dst[pos++] = KISS_FESC; + dst[pos++] = KISS_TFEND; + } else if (data[i] == KISS_FESC) { + dst[pos++] = KISS_FESC; + dst[pos++] = KISS_TFESC; + } else { + dst[pos++] = data[i]; + } + } + + if (pos + 1 > dst_cap) + return 0; + dst[pos++] = KISS_FEND; + return pos; +} + +size_t kiss_encode_quality(int8_t snr, int16_t rssi, uint8_t *dst, + size_t dst_cap) { + if (dst_cap < 5) + return 0; /* Need: FEND type snr rssi_hi rssi_lo FEND */ + + uint8_t payload[3]; + payload[0] = (uint8_t)snr; + payload[1] = (uint8_t)((rssi >> 8) & 0xFF); + payload[2] = (uint8_t)(rssi & 0xFF); + + return kiss_encode(KISS_PORT_QUALITY, payload, 3, dst, dst_cap); +} diff --git a/src/kiss.h b/src/kiss.h new file mode 100644 index 0000000..3b23cc2 --- /dev/null +++ b/src/kiss.h @@ -0,0 +1,70 @@ +#pragma once +#include +#include +#include + +/* KISS special bytes */ +#define KISS_FEND 0xC0u +#define KISS_FESC 0xDBu +#define KISS_TFEND 0xDCu +#define KISS_TFESC 0xDDu + +/* Port assignments */ +#define KISS_PORT_DATA 0u +#define KISS_PORT_QUALITY 1u +#define KISS_PORT_CONFIG 2u + +/* Configuration command opcodes (port 2) */ +#define KISS_CMD_RESERVED 0x00u +#define KISS_CMD_RES_OK 0x01u +#define KISS_CMD_RES_ERROR 0x02u +#define KISS_CMD_GET_RADIO 0x10u +#define KISS_CMD_SET_RADIO 0x11u +#define KISS_CMD_GET_FREQ 0x12u +#define KISS_CMD_SET_FREQ 0x13u +#define KISS_CMD_GET_BW 0x14u +#define KISS_CMD_SET_BW 0x15u +#define KISS_CMD_GET_SF 0x16u +#define KISS_CMD_SET_SF 0x17u +#define KISS_CMD_GET_CR 0x18u +#define KISS_CMD_SET_CR 0x19u +#define KISS_CMD_GET_POWER 0x1Au +#define KISS_CMD_SET_POWER 0x1Bu +#define KISS_CMD_GET_SYNCWORD 0x1Cu +#define KISS_CMD_SET_SYNCWORD 0x1Du + +#define KISS_MAX_FRAME 256u + +typedef struct { + uint8_t port; + uint8_t data[KISS_MAX_FRAME]; + uint16_t len; +} kiss_frame_t; + +typedef enum { + KISS_STATE_IDLE, + KISS_STATE_IN_FRAME, + KISS_STATE_ESCAPE, +} kiss_state_t; + +typedef struct { + kiss_state_t state; + uint8_t buf[KISS_MAX_FRAME + 1u]; /* +1 for type byte */ + uint16_t len; +} kiss_decoder_t; + +void kiss_decoder_init(kiss_decoder_t *dec); + +/* Feed one byte into the decoder. Returns true when a complete frame is + ready in *frame. frame must not be NULL when return value is checked. */ +bool kiss_decode(kiss_decoder_t *dec, uint8_t byte, kiss_frame_t *frame); + +/* Encode port+data into a KISS frame. Returns bytes written, or 0 on + overflow. */ +size_t kiss_encode(uint8_t port, const uint8_t *data, size_t len, + uint8_t *dst, size_t dst_cap); + +/* Encode a 3-byte signal quality frame for port 1. Big-endian: int8 snr, + int16 rssi. */ +size_t kiss_encode_quality(int8_t snr, int16_t rssi, uint8_t *dst, + size_t dst_cap); diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..f3e4f2f --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,48 @@ +#include + +#include "config.h" +#include "kiss.h" +#include "radio.h" + +static kiss_decoder_t rx_decoder; /* host → modem (serial in) */ +static uint8_t tx_buf[KISS_MAX_FRAME]; +static uint8_t radio_buf[KISS_MAX_FRAME]; + +void setup() { + Serial.begin(KISS_BAUD); + kiss_decoder_init(&rx_decoder); + radio_init(); + radio_rx_start(); +} + +void loop() { + kiss_frame_t frame; + + /* Serial → radio: decode incoming KISS frames and transmit */ + while (Serial.available()) { + if (kiss_decode(&rx_decoder, (uint8_t)Serial.read(), &frame)) { + if (frame.port == KISS_PORT_DATA) { + radio_tx(frame.data, frame.len); + } + /* port 2 config handling goes here */ + } + } + + /* Radio → serial: forward received packets as KISS frames */ + if (radio_rx_available()) { + radio_rx_info_t info; + int n = + radio_rx_read(radio_buf, sizeof(radio_buf), &info); + if (n > 0) { + /* Port 1: signal quality */ + size_t qlen = kiss_encode_quality(info.snr, info.rssi, tx_buf, + sizeof(tx_buf)); + Serial.write(tx_buf, qlen); + + /* Port 0: raw packet */ + size_t dlen = kiss_encode(KISS_PORT_DATA, radio_buf, (size_t)n, + tx_buf, sizeof(tx_buf)); + Serial.write(tx_buf, dlen); + } + } +} diff --git a/src/radio.cpp b/src/radio.cpp new file mode 100644 index 0000000..445edf1 --- /dev/null +++ b/src/radio.cpp @@ -0,0 +1,106 @@ +#include "radio.h" +#include "config.h" +#include + +/* ── Chip instantiation ─────────────────────────────────────────────────── */ +#if defined(LORA_CHIP_SX1262) +static SX1262 radio(new Module(LORA_PIN_NSS, LORA_PIN_DIO1, LORA_PIN_RESET, + LORA_PIN_BUSY)); +#elif defined(LORA_CHIP_LR1110) +static LR1110 radio(new Module(LORA_PIN_NSS, LORA_PIN_DIO1, LORA_PIN_RESET, + LORA_PIN_BUSY)); +#elif defined(LORA_CHIP_SX1276) +static SX1276 radio(new Module(LORA_PIN_NSS, LORA_PIN_DIO0, LORA_PIN_RESET, + RADIOLIB_NC)); +#else +# error "No LoRa chip defined" +#endif + +static radio_config_t current_cfg; + +/* ── Chip-specific overloads ───────────────────────────────────────────── + Use C++ overloading to isolate per-chip API deviations. */ + +#if defined(LORA_CHIP_SX1262) +static int16_t chip_begin(SX1262 &r, const radio_config_t &cfg) { + return r.begin(cfg.freq_khz / 1000.0f, cfg.bw_hz / 1000.0f, cfg.sf, + cfg.cr, cfg.syncword, cfg.power_dbm); +} +#elif defined(LORA_CHIP_LR1110) +static int16_t chip_begin(LR1110 &r, const radio_config_t &cfg) { + return r.begin(cfg.freq_khz / 1000.0f, cfg.bw_hz / 1000.0f, cfg.sf, + cfg.cr, cfg.syncword, cfg.power_dbm); +} +#elif defined(LORA_CHIP_SX1276) +static int16_t chip_begin(SX1276 &r, const radio_config_t &cfg) { + return r.begin(cfg.freq_khz / 1000.0f, cfg.bw_hz / 1000.0f, cfg.sf, + cfg.cr, cfg.syncword, cfg.power_dbm); +} +#endif + +/* ── C API implementation ────────────────────────────────────────────────── */ + +int radio_init(void) { + current_cfg = { + .freq_khz = LORA_FREQ_KHZ, + .bw_hz = LORA_BW_HZ, + .sf = LORA_SF, + .cr = LORA_CR, + .power_dbm = LORA_POWER_DBM, + .syncword = LORA_SYNCWORD, + }; + int16_t err = chip_begin(radio, current_cfg); + return (err == RADIOLIB_ERR_NONE) ? 0 : (int)err; +} + +int radio_tx(const uint8_t *data, size_t len) { + int16_t err = radio.transmit(data, len); + if (err == RADIOLIB_ERR_NONE) { + radio.startReceive(); + } + return (err == RADIOLIB_ERR_NONE) ? 0 : (int)err; +} + +int radio_rx_start(void) { + int16_t err = radio.startReceive(); + return (err == RADIOLIB_ERR_NONE) ? 0 : (int)err; +} + +bool radio_rx_available(void) { + return radio.available(); +} + +int radio_rx_read(uint8_t *buf, size_t buf_cap, radio_rx_info_t *info) { + int16_t err = radio.readData(buf, (size_t)buf_cap); + if (err != RADIOLIB_ERR_NONE) + return (int)err; + if (info) { + info->snr = (int8_t)radio.getSNR(); + info->rssi = (int16_t)radio.getRSSI(); + } + radio.startReceive(); + return (int)radio.getPacketLength(); +} + +int radio_set_config(const radio_config_t *cfg) { + int16_t err; + if ((err = radio.setFrequency(cfg->freq_khz / 1000.0f)) != + RADIOLIB_ERR_NONE) + return err; + if ((err = radio.setBandwidth(cfg->bw_hz / 1000.0f)) != RADIOLIB_ERR_NONE) + return err; + if ((err = radio.setSpreadingFactor(cfg->sf)) != RADIOLIB_ERR_NONE) + return err; + if ((err = radio.setCodingRate(cfg->cr)) != RADIOLIB_ERR_NONE) + return err; + if ((err = radio.setOutputPower(cfg->power_dbm)) != RADIOLIB_ERR_NONE) + return err; + if ((err = radio.setSyncWord(cfg->syncword)) != RADIOLIB_ERR_NONE) + return err; + current_cfg = *cfg; + return 0; +} + +void radio_get_config(radio_config_t *cfg) { + *cfg = current_cfg; +} diff --git a/src/radio.h b/src/radio.h new file mode 100644 index 0000000..8041c7c --- /dev/null +++ b/src/radio.h @@ -0,0 +1,48 @@ +#pragma once +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint32_t freq_khz; + uint32_t bw_hz; + uint8_t sf; + uint8_t cr; + int8_t power_dbm; + uint8_t syncword; +} radio_config_t; + +typedef struct { + int8_t snr; + int16_t rssi; +} radio_rx_info_t; + +/* Initialise radio using config.h macro defaults. Returns 0 on success. */ +int radio_init(void); + +/* Transmit packet. Blocks until TX complete. Returns 0 on success. */ +int radio_tx(const uint8_t *data, size_t len); + +/* Enter continuous RX mode. Returns 0 on success. */ +int radio_rx_start(void); + +/* True if a packet is waiting in the RX buffer. */ +bool radio_rx_available(void); + +/* Read received packet into buf. Fills *info if non-NULL. + Returns byte count or negative error code. */ +int radio_rx_read(uint8_t *buf, size_t buf_cap, radio_rx_info_t *info); + +/* Apply new configuration. Returns 0 on success. */ +int radio_set_config(const radio_config_t *cfg); + +/* Copy current configuration into *cfg. */ +void radio_get_config(radio_config_t *cfg); + +#ifdef __cplusplus +} +#endif