From ebd0843a72c71721129308f70158dded3544373e Mon Sep 17 00:00:00 2001 From: Omegaconjecture Date: Thu, 30 Jul 2026 06:04:43 -0700 Subject: [PATCH] Add Lierda AM36 Pico (L-LRMAM36-FANN4-PK02) variant: ESP32-S3 + LR2021 --- variants/lierda_am36_pico/LierdaAM36Board.h | 24 +++++ variants/lierda_am36_pico/README.md | 90 ++++++++++++++++++ variants/lierda_am36_pico/platformio.ini | 100 ++++++++++++++++++++ variants/lierda_am36_pico/target.cpp | 34 +++++++ variants/lierda_am36_pico/target.h | 24 +++++ 5 files changed, 272 insertions(+) create mode 100644 variants/lierda_am36_pico/LierdaAM36Board.h create mode 100644 variants/lierda_am36_pico/README.md create mode 100644 variants/lierda_am36_pico/platformio.ini create mode 100644 variants/lierda_am36_pico/target.cpp create mode 100644 variants/lierda_am36_pico/target.h diff --git a/variants/lierda_am36_pico/LierdaAM36Board.h b/variants/lierda_am36_pico/LierdaAM36Board.h new file mode 100644 index 000000000..3677ec81e --- /dev/null +++ b/variants/lierda_am36_pico/LierdaAM36Board.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +// Lierda AM36 Pico dev board (L-LRMAM36-FANN4-PK02). +// The L-LRMAM36-FANN4 module pairs an ESP32-S3 with a Semtech LR2021. +// Nothing exotic at the board level: USB-C power, no battery divider on the +// dev board. The only LED is a hardwired 3V3 power indicator (no status LED). +class LierdaAM36Board : public ESP32Board { +public: + LierdaAM36Board() { } + + void begin() { + ESP32Board::begin(); +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); // KEY, pressed = LOW +#endif + } + + const char* getManufacturerName() const override { + return "Lierda AM36 Pico"; + } +}; diff --git a/variants/lierda_am36_pico/README.md b/variants/lierda_am36_pico/README.md new file mode 100644 index 000000000..0c463bbbc --- /dev/null +++ b/variants/lierda_am36_pico/README.md @@ -0,0 +1,90 @@ +# MeshCore on the Lierda AM36 Pico (L-LRMAM36-FANN4-PK02) + +Community variant for the Lierda AM36 Pico evaluation board. The on-board +L-LRMAM36-FANN4 module is an **ESP32-S3** (8MB flash / 8MB PSRAM) paired with a +**Semtech LR2021** Generation-4 LoRa transceiver. + +> Status: **hardware-validated** (Jul 2026) against a live US915 MeshCore mesh: +> radio init, noise floor sampling (~-119 dBm), BLE pairing with the iOS +> companion app, flood + direct TX with interrupt-driven send-complete, and +> two-way channel messaging through a third-party repeater +> (SNR up 11.5 dB / down 13.0 dB, replies received at SNR 3.25 dB). +> Requires the `CustomLR2021` radio classes from MeshCore PR #2944. + +## Hardware facts used by this variant + +| Signal | ESP32-S3 GPIO | Source | Verified | +|---|---|---|---| +| LR2021 SPI SCK | 40 | `esp32_lora_driver` Kconfig (`LIERDA_BOARD_LRMAM36_FANN4`) | yes (on air) | +| LR2021 SPI MOSI | 41 | same | yes | +| LR2021 SPI MISO | 42 | same | yes | +| LR2021 NSS | 39 | same | yes | +| LR2021 NRST | 38 | same | yes | +| LR2021 BUSY | 17 | same | yes | +| LR2021 IRQ | 21 - **wired to LR2021 DIO7** | Kconfig `LR20XX_DIO7_GPIO` | yes (TX-done + RX interrupts) | +| KEY button | 4 (schematic net `GPIO4/KEY1`) | schematic | **not yet** - left disabled | + +The IRQ DIO matters: the Seeed Wio-LR2021 (which PR #2944 targets) uses DIO8, +so this variant sets `-D LR2021_IRQ_DIO=7`. No external TCXO or RF-switch +GPIOs exist on the MCU side - the module handles RF internally. The board's +only LED is a hardwired 3V3 power indicator, so no `PIN_STATUS_LED` is defined. + +## Build + +1. Install VS Code + the PlatformIO extension. +2. Get the source with the LR2021 driver: + ```bash + git clone https://github.com/meshcore-dev/MeshCore + cd MeshCore + git fetch origin pull/2944/head:lr2021 + git checkout lr2021 + ``` + (Once PR #2944 is merged, plain `dev` works.) +3. Copy this `lierda_am36_pico/` folder into `variants/`. + (PlatformIO auto-discovers `variants/*/platformio.ini`.) +4. Radio parameters: builds inherit MeshCore's defaults; set your region's + frequency/BW/SF/CR from the companion app after first boot (settings are + persisted on the device), or override `LORA_*` flags in a + `platformio.local.ini` for your own builds. The module covers 863-930 MHz. +5. Build: + ```bash + pio run -e Lierda_AM36_Pico_companion_radio_ble + ``` + Other envs: `..._companion_radio_usb`, `..._repeater`. + Note: the iOS companion app connects over BLE only, so iPhone users want + the `_ble` build. + +## Flash + +1. Connect the Type-C port labelled **USB** (NOT the one labelled UART - that + goes to the CP2105 bridge). The USB port is the ESP32-S3's native + USB-JTAG/CDC and is what esptool talks to. +2. ```bash + pio run -e Lierda_AM36_Pico_companion_radio_ble -t upload + ``` +3. If the port doesn't enumerate or upload fails to sync: hold **BOOT**, tap + **RST**, release **BOOT** (forces download mode), then retry the upload. +4. Tap **RST** after flashing completes. + +## Verify + +- Serial console is on the same USB port + (`pio device monitor -e Lierda_AM36_Pico_companion_radio_ble`). Note the + ESP32-S3 re-enumerates USB on reset, so the monitor may need a restart after + tapping RST. A failed radio bring-up prints + `ERROR: LR2021 init failed: ` and halts (no BLE advertising). +- **Attach the LoRa antenna before any TX.** +- Pair from the MeshCore app over Bluetooth (default PIN `123456`, set via + `BLE_PIN_CODE`), set your region's radio parameters in the app, and send a + channel message or advert. + +## Known quirks / notes + +- SPI to the LR2021 sits on GPIO39-42, which are the S3's JTAG pins. That's + fine (flashing/debug uses USB-JTAG), just don't try external JTAG. +- PSRAM is present but left disabled - MeshCore doesn't need it, and octal + PSRAM misconfiguration is a classic S3 boot-loop source. +- The KEY button (schematic net `GPIO4/KEY1`) is left disabled until traced + on hardware; uncomment `PIN_USER_BTN` in `platformio.ini` once confirmed. +- PR #2944's wrapper drops the radio to standby before every `startReceive()` + (LR2021 wedges if re-armed mid-RX) - don't "optimize" that away. diff --git a/variants/lierda_am36_pico/platformio.ini b/variants/lierda_am36_pico/platformio.ini new file mode 100644 index 000000000..47958a973 --- /dev/null +++ b/variants/lierda_am36_pico/platformio.ini @@ -0,0 +1,100 @@ +; Lierda AM36 Pico (L-LRMAM36-FANN4-PK02) +; Module: L-LRMAM36-FANN4 = ESP32-S3 (8MB flash / 8MB PSRAM) + Semtech LR2021 +; +; Pin map taken from lierda-iot/esp32_lora_driver Kconfig defaults for +; LIERDA_BOARD_LRMAM36_FANN4 and verified against the board schematic +; (L-LRMAM36-FANN4-PK02_SCH_V01.pdf): +; LR2021 SPI: SCK=40 MOSI=41 MISO=42 NSS=39 +; LR2021 ctrl: NRST=38 BUSY=17 IRQ=GPIO21 (wired to LR2021 DIO7!) +; KEY button: GPIO4 per schematic net label (not yet verified on hardware) +; LED1: hardwired 3V3 power indicator (not GPIO-controllable) +; +; Requires the CustomLR2021 radio classes from MeshCore PR #2944. +; Flash via the board's "USB" Type-C port (ESP32-S3 native USB), not "UART". + +[Lierda_AM36_Pico] +extends = esp32_base +board = esp32-s3-devkitc-1 +board_build.mcu = esp32s3 +board_build.flash_size = 8MB +board_upload.flash_size = 8MB +build_flags = ${esp32_base.build_flags} + -I variants/lierda_am36_pico + -D LIERDA_AM36_PICO + ; --- native-USB serial console on the "USB" Type-C port --- + -D ARDUINO_USB_MODE=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 + ; --- LR2021 wiring (ESP32-S3 GPIO numbers) --- + -D P_LORA_SCLK=40 + -D P_LORA_MOSI=41 + -D P_LORA_MISO=42 + -D P_LORA_NSS=39 + -D P_LORA_RESET=38 + -D P_LORA_BUSY=17 + -D P_LORA_DIO_1=21 + ; host IRQ line is routed from the LR2021's DIO7 (PR default is DIO8 for + ; the Seeed Wio-LR2021 - this override is essential on the Lierda module) + -D LR2021_IRQ_DIO=7 + ; --- radio driver selection --- + -D RADIO_CLASS=CustomLR2021 + -D WRAPPER_CLASS=CustomLR2021Wrapper + -D LORA_TX_POWER=22 + ; --- board UI --- + ; KEY button is believed to be GPIO4 (schematic net GPIO4/KEY1) but has not + ; been traced/verified on hardware yet; uncomment once confirmed: + ; -D PIN_USER_BTN=4 + ; The board's only LED (LED1) is a hardwired 3V3 power indicator - there is + ; no GPIO-controllable status LED, so PIN_STATUS_LED is not defined. +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/lierda_am36_pico> + + +lib_deps = + ${esp32_base.lib_deps} + +; ---------------- Companion (phone app over the USB port) ---------------- +[env:Lierda_AM36_Pico_companion_radio_usb] +extends = Lierda_AM36_Pico +build_flags = + ${Lierda_AM36_Pico.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Lierda_AM36_Pico.build_src_filter} + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Lierda_AM36_Pico.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; ---------------- Companion over BLE ---------------- +[env:Lierda_AM36_Pico_companion_radio_ble] +extends = Lierda_AM36_Pico +build_flags = + ${Lierda_AM36_Pico.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Lierda_AM36_Pico.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Lierda_AM36_Pico.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; ---------------- Repeater ---------------- +[env:Lierda_AM36_Pico_repeater] +extends = Lierda_AM36_Pico +build_src_filter = ${Lierda_AM36_Pico.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${Lierda_AM36_Pico.build_flags} + -D ADVERT_NAME='"AM36 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +lib_deps = + ${Lierda_AM36_Pico.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/lierda_am36_pico/target.cpp b/variants/lierda_am36_pico/target.cpp new file mode 100644 index 000000000..cce996945 --- /dev/null +++ b/variants/lierda_am36_pico/target.cpp @@ -0,0 +1,34 @@ +#include +#include "target.h" + +#include + +LierdaAM36Board board; + +// LR2021 on dedicated SPI pins (module-internal wiring; see platformio.ini) +static SPIClass spi; +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; // no-op stub - dev board has no on-board sensors + +#ifdef PIN_USER_BTN + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); // KEY, active low +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + + // CustomLR2021::std_init() routes the IRQ to DIO7 (LR2021_IRQ_DIO), + // brings up SPI on P_LORA_SCLK/MISO/MOSI, and applies LORA_* defaults. + return radio.std_init(&spi); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/lierda_am36_pico/target.h b/variants/lierda_am36_pico/target.h new file mode 100644 index 000000000..ec9c48daa --- /dev/null +++ b/variants/lierda_am36_pico/target.h @@ -0,0 +1,24 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +// CustomLR2021 / CustomLR2021Wrapper come from MeshCore PR #2944 +#include +#include +#include +#include +#include "LierdaAM36Board.h" + +extern LierdaAM36Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef PIN_USER_BTN + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +mesh::LocalIdentity radio_new_identity();