From f23d7059f2430d4455169cb0b34364b06e990a90 Mon Sep 17 00:00:00 2001 From: FoxExe Date: Mon, 20 Jul 2026 15:43:54 +0500 Subject: [PATCH 1/2] LR1121 wrappers --- src/helpers/radiolib/CustomLR1121.h | 113 +++++++++++++++++++++ src/helpers/radiolib/CustomLR1121Wrapper.h | 63 ++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/helpers/radiolib/CustomLR1121.h create mode 100644 src/helpers/radiolib/CustomLR1121Wrapper.h diff --git a/src/helpers/radiolib/CustomLR1121.h b/src/helpers/radiolib/CustomLR1121.h new file mode 100644 index 0000000000..9e67d373df --- /dev/null +++ b/src/helpers/radiolib/CustomLR1121.h @@ -0,0 +1,113 @@ +#pragma once + +#include + + +#ifndef LR11X0_DIO3_TCXO_VOLTAGE +#define LR11X0_DIO3_TCXO_VOLTAGE 3.0 +#endif + +#ifndef LORA_CR +#define LORA_CR 5 +#endif + +class CustomLR1121 : public LR1121 { +private: + bool _rx_boosted = false; + +public: + CustomLR1121(Module* mod) : LR1121(mod) {} + +#ifdef RP2040_PLATFORM + bool std_init(SPIClassRP2040* spi = NULL) +#else + bool std_init(SPIClass* spi = NULL) +#endif + { + float tcxo = LR11X0_DIO3_TCXO_VOLTAGE; + uint8_t cr = LORA_CR; + +#if defined(P_LORA_SCLK) +#ifdef NRF52_PLATFORM + if (spi) { spi->setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); spi->begin(); } +#elif defined(RP2040_PLATFORM) + if (spi) { + spi->setMISO(P_LORA_MISO); + //spi->setCS(P_LORA_NSS); // Setting CS results in freeze + spi->setSCK(P_LORA_SCLK); + spi->setMOSI(P_LORA_MOSI); + spi->begin(); + } +#else + if (spi) spi->begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); +#endif +#endif + int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, 10, 16, tcxo); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + // Waveshare Core1121 (LR1121-HF) RFSwitch Table + static const uint32_t rfswitch_dio_pins[] = { + RADIOLIB_LR11X0_DIO5, + RADIOLIB_LR11X0_DIO6, + RADIOLIB_NC, // DIO7 NC + RADIOLIB_NC, // DIO8 NC + RADIOLIB_NC // DIO9 NC + }; + + static const Module::RfSwitchMode_t rfswitch_table[] = { + { LR11x0::MODE_STBY, { LOW, LOW } }, + { LR11x0::MODE_RX, { HIGH, LOW } }, + { LR11x0::MODE_TX, { LOW, HIGH } }, + { LR11x0::MODE_TX_HP, { LOW, HIGH } }, + { LR11x0::MODE_TX_HF, { LOW, LOW } }, + { LR11x0::MODE_GNSS, { LOW, LOW } }, + { LR11x0::MODE_WIFI, { LOW, LOW } }, + END_OF_MODE_TABLE + }; + + setRfSwitchTable(rfswitch_dio_pins, rfswitch_table); + + setCRC(2); + explicitHeader(); + + return true; // success + } + + size_t getPacketLength(bool update) override { + size_t len = LR1121::getPacketLength(update); + if (len == 0 && getIrqStatus() & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { + // we've just received a corrupted packet + // this may have triggered a bug causing subsequent packets to be shifted + // call standby() to return radio to known-good state + // recvRaw will call startReceive() to restart rx + MESH_DEBUG_PRINTLN("LR1121: got header err, calling standby()"); + standby(); + } + return len; + } + + bool isReceiving() { + uint16_t irq = getIrqFlags(); + bool detected = (irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED); + return detected; + } + + int16_t setRxBoostedGainMode(bool en) { + _rx_boosted = en; + return LR1121::setRxBoostedGainMode(en); + } + + bool getRxBoostedGainMode() const { + return _rx_boosted; + } + + int16_t getSpreadingFactor() const { + return spreadingFactor; + } + + float getFreqMHz() const { return freqMHz; } +}; \ No newline at end of file diff --git a/src/helpers/radiolib/CustomLR1121Wrapper.h b/src/helpers/radiolib/CustomLR1121Wrapper.h new file mode 100644 index 0000000000..1e560d997e --- /dev/null +++ b/src/helpers/radiolib/CustomLR1121Wrapper.h @@ -0,0 +1,63 @@ +#pragma once + +#include "RadioLibWrappers.h" +#include "CustomLR1121.h" +#include "LR11x0Reset.h" + +#ifndef USE_LR1121 +#define USE_LR1121 +#endif + +class CustomLR1121Wrapper : public RadioLibWrapper { +public: + CustomLR1121Wrapper(CustomLR1121& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) {} + + void setParams(float freq, float bw, uint8_t sf, uint8_t cr) override { + ((CustomLR1121*)_radio)->setFrequency(freq); + ((CustomLR1121*)_radio)->setSpreadingFactor(sf); + ((CustomLR1121*)_radio)->setBandwidth(bw); + ((CustomLR1121*)_radio)->setCodingRate(cr); + updatePreamble(sf); + } + + void doResetAGC() override { + lr11x0ResetAGC((LR11x0*)_radio, ((CustomLR1121*)_radio)->getFreqMHz()); + } + + bool isReceivingPacket() override { + return ((CustomLR1121*)_radio)->isReceiving(); + } + + float getCurrentRSSI() override { + float rssi = -110; + ((CustomLR1121*)_radio)->getRssiInst(&rssi); + return rssi; + } + + void onSendFinished() override { + RadioLibWrapper::onSendFinished(); + ((CustomLR1121*)_radio)->explicitHeader(); + _radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); + } + + float getLastRSSI() const override { + return ((CustomLR1121*)_radio)->getRSSI(); + } + + float getLastSNR() const override { + return ((CustomLR1121*)_radio)->getSNR(); + } + + uint8_t getSpreadingFactor() const override { + return ((CustomLR1121*)_radio)->getSpreadingFactor(); + } + + void setRxBoostedGainMode(bool en) override { + ((CustomLR1121*)_radio)->setRxBoostedGainMode(en); + } + + bool getRxBoostedGainMode() const override { + return ((CustomLR1121*)_radio)->getRxBoostedGainMode(); + } + +}; From be76ef041b7f96284f93549f456a4110cc4f8172 Mon Sep 17 00:00:00 2001 From: FoxExe Date: Mon, 20 Jul 2026 15:46:42 +0500 Subject: [PATCH 2/2] ESP32-S3 Supermini + Waveshare Core1121 (LR1121-HF) + WeActStuido 2.9" epaper display --- variants/ESP32_S3_Supermini/SuperminiBoard.h | 63 ++++++ variants/ESP32_S3_Supermini/platformio.ini | 219 +++++++++++++++++++ variants/ESP32_S3_Supermini/target.cpp | 47 ++++ variants/ESP32_S3_Supermini/target.h | 53 +++++ 4 files changed, 382 insertions(+) create mode 100644 variants/ESP32_S3_Supermini/SuperminiBoard.h create mode 100644 variants/ESP32_S3_Supermini/platformio.ini create mode 100644 variants/ESP32_S3_Supermini/target.cpp create mode 100644 variants/ESP32_S3_Supermini/target.h diff --git a/variants/ESP32_S3_Supermini/SuperminiBoard.h b/variants/ESP32_S3_Supermini/SuperminiBoard.h new file mode 100644 index 0000000000..fe5e675f94 --- /dev/null +++ b/variants/ESP32_S3_Supermini/SuperminiBoard.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include "target.h" +#include +#include +#include + + +class SuperminiBoard : public ESP32Board { +private: + float adc_mult = ADC_MULTIPLIER; +public: + SuperminiBoard() {} + + void begin() { + ESP32Board::begin(); + + pinMode(PIN_VBAT_READ, INPUT); + pinMode(BUTTON_PIN, INPUT_PULLUP); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } + else { + esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + esp_deep_sleep_start(); + } + + void powerOff() override { + enterDeepSleep(0); + } + + const char* getManufacturerName() const override { + return "ESP32 Supermini"; + } +}; \ No newline at end of file diff --git a/variants/ESP32_S3_Supermini/platformio.ini b/variants/ESP32_S3_Supermini/platformio.ini new file mode 100644 index 0000000000..9e9ef67cfb --- /dev/null +++ b/variants/ESP32_S3_Supermini/platformio.ini @@ -0,0 +1,219 @@ +[ESP32_S3_Supermini] +extends=esp32_base +board=esp32-s3-zero +board_build.partitions = huge_app.csv ; FW weight: ~1.7MB, but default partition is only ~1.3MB. Remove OTA and raise APP partition upto 3MB +;build_type = debug +;debug_tool = esp-builtin +monitor_echo = yes +;monitor_filters = esp32_exception_decoder + +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/ESP32_S3_Supermini + -D ESP32_CPU_FREQ=80 + -D BOARD_HAS_PSRAM + + ; SPI-0 / LoRa (LR1121-HF) + -D PIN_SPI_MISO=11 + -D PIN_SPI_MOSI=10 + -D PIN_SPI_SCK=9 + -D PIN_SPI_NSS=8 + + -D P_LORA_MISO=PIN_SPI_MISO + -D P_LORA_MOSI=PIN_SPI_MOSI + -D P_LORA_SCLK=PIN_SPI_SCK + -D P_LORA_NSS=PIN_SPI_NSS + -D P_LORA_DIO_1=1 ; IRQ + -D P_LORA_RESET=12 + -D P_LORA_BUSY=13 + + -D LORA_TX_POWER=22 ; Max 14 for 2.4GHz or 22 for <900MHz + -D LR11X0_DIO3_TCXO_VOLTAGE=3.0 + -D LR11X0_DIO_AS_RF_SWITCH=1 + -D RX_BOOSTED_GAIN=1 + + ; Wire/i2c + -D PIN_BOARD_SDA=4 + -D PIN_BOARD_SCL=5 + ; 2nd wire for sensors + ;-D ENV_PIN_SDA=4 + ;-D ENV_PIN_SCL=5 + + ; Serial / UART (For GPS) + -D ENV_INCLUDE_GPS=0 ; Disable GPS support + ;-D PIN_GPS_TX=6 + ;-D PIN_GPS_RX=7 + ;-D PIN_GPS_EN=-1 + ;-D GPS_BAUD_RATE=9600 + ;-D ENV_SKIP_GPS_DETECT + ;-D FORCE_GPS_ALIVE + + ; Builtin LEDs + -D LED_PIN=48 + -D LED_STATE_ON=0 + -D PIN_LED=LED_PIN + -D LED_RED=LED_PIN + -D LED_BLUE=-1 + + ; Button + -D PIN_BUTTON1=0 + -D BUTTON_PIN=PIN_BUTTON1 + -D PIN_USER_BTN=PIN_BUTTON1 + + ; Battery ADC + -D PIN_VBAT_READ=16 + -D ADC_MULTIPLIER=2.085 ; BAT- -> 1M -> ADC -> 1M -> BAT+ + + ; SPI-1 / Display + -D PIN_SPI1_SCK=45 + -D PIN_SPI1_MISO=47 ; NC + -D PIN_SPI1_MOSI=46 + + -D UI_RECENT_LIST_SIZE=4 + ;-D UI_SENSORS_PAGE=1 + -D AUTO_OFF_MILLIS=0 ; Disable display off (eink wont need it) + -D BOOT_SCREEN_MILLIS=1000 ; Reduce boot time + + -D DISPLAY_CLASS=GxEPDDisplay + -D EINK_DISPLAY_MODEL=GxEPD2_290_BS + -D EINK_LOGICAL_W=128 + -D EINK_LOGICAL_H=296 + -D EINK_SCALE_X=1.0f + -D EINK_SCALE_Y=1.0f + -D EINK_X_OFFSET=0 + -D EINK_Y_OFFSET=0 + + -D PIN_DISPLAY_SCLK=PIN_SPI1_SCK + -D PIN_DISPLAY_MISO=PIN_SPI1_MISO + -D PIN_DISPLAY_MOSI=PIN_SPI1_MOSI + -D PIN_DISPLAY_CS=42 + -D PIN_DISPLAY_DC=41 + -D PIN_DISPLAY_RST=40 + -D PIN_DISPLAY_BUSY=39 + + ; Logging + -D DISABLE_DIAGNOSTIC_OUTPUT ; GxEPD2 debug off + ;-D BLE_DEBUG_LOGGING=1 + ;-D MESH_PACKET_LOGGING=1 + ;-D MESH_DEBUG=1 + ;-D RADIOLIB_DEBUG_BASIC=1 + ;-D RADIOLIB_DEBUG_PROTOCOL=1 + ;-D RADIOLIB_DEBUG_SPI=0 + ;-D RADIOLIB_VERBOSE_ASSERT=1 + ;-D CORE_DEBUG_LEVEL=3 + ;-D ARDUINO_USB_CDC_ON_BOOT=1 + ;-D ARDUINO_USB_MODE=1 + +build_src_filter=${esp32_base.build_src_filter} + +<../variants/ESP32_S3_Supermini> + + + ; Display + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + bakercp/CRC32 + ;densaugeo/base64 + ;end2endzone/NonBlockingRTTTL + ;zinggjm/GxEPD2 ; ePaper displays drivers + GxEPD2 + +[env:ESP32_S3_Supermini_repeater] +extends=ESP32_S3_Supermini +build_flags = + ${ESP32_S3_Supermini.build_flags} + -D ADVERT_NAME='"Meshcore Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +build_src_filter=${ESP32_S3_Supermini.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${ESP32_S3_Supermini.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:ESP32_S3_Supermini_bridge_rs232] +extends=ESP32_S3_Supermini +build_flags = + ${ESP32_S3_Supermini.build_flags} + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + + -D WITH_RS232_BRIDGE=Serial2 ; TODO + -D WITH_RS232_BRIDGE_RX=5 + -D WITH_RS232_BRIDGE_TX=6 + +build_src_filter=${ESP32_S3_Supermini.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${ESP32_S3_Supermini.lib_deps} + ${esp32_ota.lib_deps} + +[env:ESP32_S3_Supermini_room_server] +extends=ESP32_S3_Supermini +build_flags = + ${ESP32_S3_Supermini.build_flags} + -D ADVERT_NAME='"Room server"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter=${ESP32_S3_Supermini.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${ESP32_S3_Supermini.lib_deps} + ${esp32_ota.lib_deps} + +[env:ESP32_S3_Supermini_terminal_chat] +extends=ESP32_S3_Supermini +build_flags = + ${ESP32_S3_Supermini.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +build_src_filter=${ESP32_S3_Supermini.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${ESP32_S3_Supermini.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ESP32_S3_Supermini_sensor] +extends=ESP32_S3_Supermini +build_flags = + ${ESP32_S3_Supermini.build_flags} + -D ADVERT_NAME='"Meshcore Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +build_src_filter=${ESP32_S3_Supermini.build_src_filter} + +<../examples/simple_sensor> +lib_deps = + ${ESP32_S3_Supermini.lib_deps} + ${esp32_ota.lib_deps} + +[env:ESP32_S3_Supermini_companion_radio_uni] +extends=ESP32_S3_Supermini +build_flags = + ${ESP32_S3_Supermini.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WITH_COMPANION_CLI + -D WITH_WIFI_SWITCHING +build_src_filter=${ESP32_S3_Supermini.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ESP32_S3_Supermini.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/ESP32_S3_Supermini/target.cpp b/variants/ESP32_S3_Supermini/target.cpp new file mode 100644 index 0000000000..5d6958a946 --- /dev/null +++ b/variants/ESP32_S3_Supermini/target.cpp @@ -0,0 +1,47 @@ +#pragma once + +#include "target.h" + + +static SPIClass spi(HSPI); // FSPI = SPI-1 (Display), HSPI = SPI-2 (LoRa) + +CustomLR1121 radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); + +CustomLR1121Wrapper radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +GxEPDDisplay display; +// (pin, long_press_mills, reverse, pulldownup, multiclick) +MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true, true); + +#if ENV_INCLUDE_GPS +#include +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors; +#endif + +SuperminiBoard board; + + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + + bool result = radio.std_init(&spi); + + if (result) { +#ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif + } + return result; +}; + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +}; diff --git a/variants/ESP32_S3_Supermini/target.h b/variants/ESP32_S3_Supermini/target.h new file mode 100644 index 0000000000..c1b843a1ce --- /dev/null +++ b/variants/ESP32_S3_Supermini/target.h @@ -0,0 +1,53 @@ +#pragma once + +/* ESP32-S3 Supermini + Waveshare Core1121 (LR1121-HF) + WeAct ePaper 2.9" + * + * _____[:::::]_____ + * IN/OUT - 5v - | [] [] | - ?? - UART-0 Tx + * GND - | B+ B- | - ?? - UART-0 Rx + * OUT - 3.3v - | --- | - 01 - LORA_IRQ + * LORA_BUSY - 13 - | BOOST | - 02 - + * LORA_RESET - 12 - | | - 03 - + * LORA_MISO - 11 - | | - 04 - I2C_SDA + * LORA_MOSI - 10 - | | - 05 - I2C_SCL + * LORA_SCK - 09 - | | - 06 - GPS_Rx / Serial1_Tx + * LORA_CS - 08 - | | - 07 - GPS_Tx / Serial1_Rx + * ***************** + * + * Additional pins on board: + * + * 34 38 LED - 48 NOTE: Regular LED and WS2818 RGB led IN at same pin! + * 33 37 EINK_MISO - 47 + * 21 36 EINK_MOSI - 46 + * 18 35 EINK_SCK - 45 + * 17 EINK_CS - 42 + * BAT_ADC - 16 EINK_DC - 41 + * 15 EINK_RST - 40 + * 14 EINK_BUSY - 39 + * +*/ + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include "SuperminiBoard.h" + +// Display +#include +#include + +extern GxEPDDisplay display; +extern MomentaryButton user_btn; + +// Board +extern SuperminiBoard board; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +// Radio / LoRa module +extern CustomLR1121Wrapper radio_driver; +bool radio_init(); +mesh::LocalIdentity radio_new_identity();