From ce4e559c01dbd20d492189d9d3e2ab7851334cb1 Mon Sep 17 00:00:00 2001 From: Alexander Begoon Date: Thu, 7 Aug 2025 22:30:33 +0200 Subject: [PATCH 01/11] Add support for Xiao ESP32C6 with external antenna configuration --- src/helpers/esp32/XiaoC6Board.h | 28 ++++++++++++++++++++++++++++ variants/xiao_c6/platformio.ini | 1 + variants/xiao_c6/target.cpp | 2 +- variants/xiao_c6/target.h | 3 ++- 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/helpers/esp32/XiaoC6Board.h diff --git a/src/helpers/esp32/XiaoC6Board.h b/src/helpers/esp32/XiaoC6Board.h new file mode 100644 index 00000000..86c3475c --- /dev/null +++ b/src/helpers/esp32/XiaoC6Board.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +class XiaoC6Board : public ESP32Board { +public: + void begin() { + ESP32Board::begin(); + +#ifdef USE_XIAO_ESP32C6_EXTERNAL_ANTENNA +// Connect an external antenna to your XIAO ESP32C6 otherwise, it may be damaged! + pinMode(3, OUTPUT); + digitalWrite(3, LOW); // Activate RF switch control + + delay(100); + + pinMode(14, OUTPUT); + digitalWrite(14, HIGH); // Use external antenna +#endif + } + + const char* getManufacturerName() const override { + return "Xiao C6"; + } +}; + + diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index 9ad07aef..c27df55e 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -87,6 +87,7 @@ build_flags = -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D USE_XIAO_ESP32C6_EXTERNAL_ANTENNA=1 [env:Meshimi_Repeater] extends = Meshimi diff --git a/variants/xiao_c6/target.cpp b/variants/xiao_c6/target.cpp index caca57bc..ff77474a 100644 --- a/variants/xiao_c6/target.cpp +++ b/variants/xiao_c6/target.cpp @@ -1,7 +1,7 @@ #include #include "target.h" -ESP32Board board; +XiaoC6Board board; #if defined(P_LORA_SCLK) static SPIClass spi(0); diff --git a/variants/xiao_c6/target.h b/variants/xiao_c6/target.h index c26d5958..e1469228 100644 --- a/variants/xiao_c6/target.h +++ b/variants/xiao_c6/target.h @@ -4,11 +4,12 @@ #include #include #include +#include #include #include #include -extern ESP32Board board; +extern XiaoC6Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; From 6902dd81fa8eb8276c2582a0c9a561609f8e5c9a Mon Sep 17 00:00:00 2001 From: Alexander Begoon Date: Fri, 8 Aug 2025 23:04:14 +0200 Subject: [PATCH 02/11] Move variant specific code --- variants/xiao_c6/{target.cpp => XiaoC6Board.cpp} | 2 ++ {src/helpers/esp32 => variants/xiao_c6}/XiaoC6Board.h | 0 variants/xiao_c6/platformio.ini | 1 + variants/xiao_c6/target.h | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) rename variants/xiao_c6/{target.cpp => XiaoC6Board.cpp} (99%) rename {src/helpers/esp32 => variants/xiao_c6}/XiaoC6Board.h (100%) diff --git a/variants/xiao_c6/target.cpp b/variants/xiao_c6/XiaoC6Board.cpp similarity index 99% rename from variants/xiao_c6/target.cpp rename to variants/xiao_c6/XiaoC6Board.cpp index ff77474a..555fed62 100644 --- a/variants/xiao_c6/target.cpp +++ b/variants/xiao_c6/XiaoC6Board.cpp @@ -47,3 +47,5 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } + + diff --git a/src/helpers/esp32/XiaoC6Board.h b/variants/xiao_c6/XiaoC6Board.h similarity index 100% rename from src/helpers/esp32/XiaoC6Board.h rename to variants/xiao_c6/XiaoC6Board.h diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index c27df55e..95dede08 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -28,6 +28,7 @@ build_flags = -D DISABLE_WIFI_OTA=1 build_src_filter = ${esp32c6_base.build_src_filter} +<../variants/xiao_c6> + + [env:Xiao_C6_Repeater] extends = Xiao_C6 diff --git a/variants/xiao_c6/target.h b/variants/xiao_c6/target.h index e1469228..0fbb0bb2 100644 --- a/variants/xiao_c6/target.h +++ b/variants/xiao_c6/target.h @@ -2,9 +2,9 @@ #define RADIOLIB_STATIC_ONLY 1 #include +#include #include #include -#include #include #include #include From 6e26a6a78cea7f95c461cc47586348e061c5307f Mon Sep 17 00:00:00 2001 From: Tomas Gabor Date: Sat, 9 Aug 2025 21:28:31 +0200 Subject: [PATCH 03/11] Added room option to T-Beam SX1276 --- variants/lilygo_tbeam_SX1276/platformio.ini | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index b0f6c001..72040eab 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -65,6 +65,22 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_SX1276.lib_deps} + ${esp32_ota.lib_deps} + +[env:Tbeam_SX1276_room] +extends = LilyGo_TBeam_SX1276 +build_flags = + ${LilyGo_TBeam_SX1276.build_flags} + -D ADVERT_NAME='"Tbeam Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} + +<../examples/simple_room_server> lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} ${esp32_ota.lib_deps} \ No newline at end of file From af7db5593baf6ef24670d0e936b4f1acb3eaf107 Mon Sep 17 00:00:00 2001 From: gumbero Date: Sun, 10 Aug 2025 14:25:43 +0200 Subject: [PATCH 04/11] Update platformio.ini --- variants/lilygo_tbeam_SX1276/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index 72040eab..8febe7b3 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -69,7 +69,7 @@ lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} ${esp32_ota.lib_deps} -[env:Tbeam_SX1276_room] +[env:Tbeam_SX1276_room_server] extends = LilyGo_TBeam_SX1276 build_flags = ${LilyGo_TBeam_SX1276.build_flags} @@ -83,4 +83,4 @@ build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} +<../examples/simple_room_server> lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} From bed311313ad3e384554b9c46c258e4262bc0c55c Mon Sep 17 00:00:00 2001 From: kelsey hudson Date: Sun, 10 Aug 2025 10:34:47 -0700 Subject: [PATCH 05/11] Adding support for Ikoka Stick with Seeed Xiao nRF54 baseboard. Adds a new variant 'ikoka_stick_nrf' and associated support files. This is based on the Xiao nRF54 code with pin numbers and functions changed to suit the differences in hardware between the WIO SX1262 board and the Ikoka Stick. Sets the default LoRa transmit power to 9dBm to avoid burning up the frontend in Ikoka Sticks equipped with the Ebyte 33dBm S22 module on first boot. Adds support for an SSD1306 display connected to the display header. Note the display pinout is the same as the RAK4631 display header so make sure to use a display wired accordingly (aliexpress etc. SSD1306s typically have Vcc & GND reversed from what this board expects). Adds support for display rotation to SSD1306Display via a platformIO define. This support was added following the same paradigms found elsewhere in the code for rotating a display. --- src/helpers/ui/SSD1306Display.cpp | 3 + .../ikoka_stick_nrf/ikoka_stick_nrf_board.cpp | 99 ++++++++++++ .../ikoka_stick_nrf/ikoka_stick_nrf_board.h | 66 ++++++++ variants/ikoka_stick_nrf/platformio.ini | 133 ++++++++++++++++ variants/ikoka_stick_nrf/target.cpp | 43 +++++ variants/ikoka_stick_nrf/target.h | 26 +++ variants/ikoka_stick_nrf/variant.cpp | 86 ++++++++++ variants/ikoka_stick_nrf/variant.h | 149 ++++++++++++++++++ 8 files changed, 605 insertions(+) create mode 100644 variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp create mode 100644 variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h create mode 100644 variants/ikoka_stick_nrf/platformio.ini create mode 100644 variants/ikoka_stick_nrf/target.cpp create mode 100644 variants/ikoka_stick_nrf/target.h create mode 100644 variants/ikoka_stick_nrf/variant.cpp create mode 100644 variants/ikoka_stick_nrf/variant.h diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 8d977db0..c9da0cf8 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,6 +7,9 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { + #ifdef DISPLAY_ROTATION + display.setRotation(DISPLAY_ROTATION); + #endif return display.begin(SSD1306_SWITCHCAPVCC, DISPLAY_ADDRESS, true, false) && i2c_probe(Wire, DISPLAY_ADDRESS); } diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp new file mode 100644 index 00000000..8634cda1 --- /dev/null +++ b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp @@ -0,0 +1,99 @@ +#ifdef XIAO_NRF52 + +#include +#include "ikoka_stick_nrf_board.h" + +#include +#include + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) +{ + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) +{ + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void ikoka_stick_nrf_board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + pinMode(PIN_VBAT, INPUT); + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, HIGH); + +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, HIGH); +#endif + +// pinMode(SX126X_POWER_EN, OUTPUT); +// digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up +} + +bool ikoka_stick_nrf_board::startOTAUpdate(const char* id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("XIAO_NRF52_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; + + + return false; +} + +#endif diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h new file mode 100644 index 00000000..a5f33b01 --- /dev/null +++ b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +#ifdef XIAO_NRF52 + +// redefine lora pins if using the S3 variant of SX1262 board +#ifdef SX1262_XIAO_S3_VARIANT + #undef P_LORA_DIO_1 + #undef P_LORA_BUSY + #undef P_LORA_RESET + #undef P_LORA_NSS + #undef SX126X_RXEN + #define P_LORA_DIO_1 D0 + #define P_LORA_BUSY D1 + #define P_LORA_RESET D2 + #define P_LORA_NSS D3 + #define SX126X_RXEN D4 +#endif + +class ikoka_stick_nrf_board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off + } +#endif + + uint16_t getBattMilliVolts() override { + // Please read befor going further ;) + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + + // We can't drive VBAT_ENABLE to HIGH as long + // as we don't know wether we are charging or not ... + // this is a 3mA loss (4/1500) + digitalWrite(VBAT_ENABLE, LOW); + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(PIN_VBAT); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char* getManufacturerName() const override { + return "Ikoka Stick (Xiao-nrf52)"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; + +#endif diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini new file mode 100644 index 00000000..3e62b5e7 --- /dev/null +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -0,0 +1,133 @@ +[nrf52840_xiao] +extends = nrf52_base +platform_packages = + toolchain-gccarmnoneeabi@~1.100301.0 + framework-arduinoadafruitnrf52 +board = seeed-xiao-afruitnrf52-nrf52840 +board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52_base.build_flags} + -D NRF52_PLATFORM -D XIAO_NRF52 + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 +lib_ignore = + BluetoothOTA + lvgl + lib5b4 +lib_deps = + ${nrf52_base.lib_deps} + rweather/Crypto @ ^0.4.0 + adafruit/Adafruit INA3221 Library @ ^1.0.1 + adafruit/Adafruit INA219 @ ^1.2.3 + adafruit/Adafruit AHTX0 @ ^2.0.5 + adafruit/Adafruit BME280 Library @ ^2.3.0 + adafruit/Adafruit SSD1306 @ ^2.5.13 + + +[ikoka_stick_nrf] +extends = nrf52840_xiao +;board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52840_xiao.build_flags} + -D P_LORA_TX_LED=11 + -I variants/ikoka_stick_nrf + -I src/helpers/nrf52 + -D DISPLAY_CLASS=SSD1306Display + -D DISPLAY_ROTATION=2 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=9 + -D P_LORA_DIO_1=D1 + -D P_LORA_RESET=D2 + -D P_LORA_BUSY=D3 + -D P_LORA_NSS=D4 + -D SX126X_RXEN=D5 + -D SX126X_TXEN=RADIOLIB_NC + -D SX126X_DIO2_AS_RF_SWITCH=1 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_USER_BTN=0 + -D PIN_WIRE_SCL=7 + -D PIN_WIRE_SDA=6 + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 +build_src_filter = ${nrf52840_xiao.build_src_filter} + + + + + + + +<../variants/ikoka_stick_nrf> +debug_tool = jlink +upload_protocol = nrfutil + +[env:ikoka_stick_nrf_companion_radio_ble] +extends = ikoka_stick_nrf +build_flags = + ${ikoka_stick_nrf.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + + + +<../examples/companion_radio> +lib_deps = + ${ikoka_stick_nrf.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ikoka_stick_nrf_companion_radio_usb] +extends = ikoka_stick_nrf +build_flags = + ${ikoka_stick_nrf.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + + + +<../examples/companion_radio> +lib_deps = + ${ikoka_stick_nrf.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ikoka_stick_nrf_alt_pinout_companion_radio_ble] +extends = env:ikoka_stick_nrf_companion_radio_ble +build_flags = + ${env:ikoka_stick_nrf_companion_radio_ble.build_flags} + -D SX1262_XIAO_S3_VARIANT + +[env:ikoka_stick_nrf_repeater] +extends = ikoka_stick_nrf +build_flags = + ${ikoka_stick_nrf.build_flags} + -D ADVERT_NAME='"Ikoka Stick Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + +<../examples/simple_repeater/main.cpp> + +[env:ikoka_stick_nrf_alt_pinout_repeater] +extends = env:ikoka_stick_nrf_repeater +build_flags = + ${env:ikoka_stick_nrf_repeater.build_flags} + -D SX1262_XIAO_S3_VARIANT + +[env:ikoka_stick_nrf_room_server] +extends = ikoka_stick_nrf +build_flags = + ${ikoka_stick_nrf.build_flags} + -D ADVERT_NAME='"Ikoka Stick Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_stick_nrf.build_src_filter} + +<../examples/simple_room_server/main.cpp> diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp new file mode 100644 index 00000000..e50150eb --- /dev/null +++ b/variants/ikoka_stick_nrf/target.cpp @@ -0,0 +1,43 @@ +#include +#include "target.h" +#include + +ikoka_stick_nrf_board board; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + +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); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +EnvironmentSensorManager sensors; + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/ikoka_stick_nrf/target.h b/variants/ikoka_stick_nrf/target.h new file mode 100644 index 00000000..758cd019 --- /dev/null +++ b/variants/ikoka_stick_nrf/target.h @@ -0,0 +1,26 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS + #include + extern DISPLAY_CLASS display; +#endif + +extern ikoka_stick_nrf_board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/ikoka_stick_nrf/variant.cpp b/variants/ikoka_stick_nrf/variant.cpp new file mode 100644 index 00000000..16542e27 --- /dev/null +++ b/variants/ikoka_stick_nrf/variant.cpp @@ -0,0 +1,86 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // D0 .. D10 + 2, // D0 is P0.02 (A0) + 3, // D1 is P0.03 (A1) + 28, // D2 is P0.28 (A2) + 29, // D3 is P0.29 (A3) + 4, // D4 is P0.04 (A4,SDA) + 5, // D5 is P0.05 (A5,SCL) + 43, // D6 is P1.11 (TX) + 44, // D7 is P1.12 (RX) + 45, // D8 is P1.13 (SCK) + 46, // D9 is P1.14 (MISO) + 47, // D10 is P1.15 (MOSI) + + // LEDs + 26, // D11 is P0.26 (LED RED) + 6, // D12 is P0.06 (LED BLUE) + 30, // D13 is P0.30 (LED GREEN) + 14, // D14 is P0.14 (READ_BAT) + + // LSM6DS3TR + 40, // D15 is P1.08 (6D_PWR) + 27, // D16 is P0.27 (6D_I2C_SCL) + 7, // D17 is P0.07 (6D_I2C_SDA) + 11, // D18 is P0.11 (6D_INT1) + + // MIC + 42, // D19 is P1.10 (MIC_PWR) + 32, // D20 is P1.00 (PDM_CLK) + 16, // D21 is P0.16 (PDM_DATA) + + // BQ25100 + 13, // D22 is P0.13 (HICHG) + 17, // D23 is P0.17 (~CHG) + + // + 21, // D24 is P0.21 (QSPI_SCK) + 25, // D25 is P0.25 (QSPI_CSN) + 20, // D26 is P0.20 (QSPI_SIO_0 DI) + 24, // D27 is P0.24 (QSPI_SIO_1 DO) + 22, // D28 is P0.22 (QSPI_SIO_2 WP) + 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) + + // NFC + 9, // D30 is P0.09 (NFC1) + 10, // D31 is P0.10 (NFC2) + + // VBAT + 31, // D32 is P0.31 (VBAT) +}; + +void initVariant() +{ + // Disable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + pinMode(VBAT_ENABLE, OUTPUT); + //digitalWrite(VBAT_ENABLE, HIGH); + // This was taken from Seeed github butis not coherent with the doc, + // VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V + // This induces a 3mA current in the resistors :( but it's better than burning the nrf + digitalWrite(VBAT_ENABLE, LOW); + + // Low charging current (50mA) + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + //pinMode(PIN_CHARGING_CURRENT, INPUT); + + // High charging current (100mA) + pinMode(PIN_CHARGING_CURRENT, OUTPUT); + digitalWrite(PIN_CHARGING_CURRENT, LOW); + + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); + + pinMode(LED_RED, OUTPUT); + digitalWrite(LED_RED, HIGH); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); +} diff --git a/variants/ikoka_stick_nrf/variant.h b/variants/ikoka_stick_nrf/variant.h new file mode 100644 index 00000000..f94ebe49 --- /dev/null +++ b/variants/ikoka_stick_nrf/variant.h @@ -0,0 +1,149 @@ +#ifndef _IKOKA_STICK_NRF_H_ +#define _IKOKA_STICK_NRF_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +//#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define PINS_COUNT (33) +#define NUM_DIGITAL_PINS (33) +#define NUM_ANALOG_INPUTS (8) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (LED_RED) +#define LED_PWR (PINS_COUNT) +#define PIN_NEOPIXEL (PINS_COUNT) +#define NEOPIXEL_NUM (0) + +#define LED_BUILTIN (PIN_LED) + +#define LED_RED (11) +#define LED_GREEN (13) +#define LED_BLUE (12) + +#define LED_STATE_ON (1) // State when LED is litted + +// Buttons +#define PIN_BUTTON1 (PINS_COUNT) + +// Digital PINs +static const uint8_t D0 = 0 ; +static const uint8_t D1 = 1 ; +static const uint8_t D2 = 2 ; +static const uint8_t D3 = 3 ; +static const uint8_t D4 = 4 ; +static const uint8_t D5 = 5 ; +static const uint8_t D6 = 6 ; +static const uint8_t D7 = 7 ; +static const uint8_t D8 = 8 ; +static const uint8_t D9 = 9 ; +static const uint8_t D10 = 10; + +#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define PIN_CHARGING_CURRENT (22) // Battery Charging current + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + +// Analog pins +#define PIN_A0 (0) +#define PIN_A1 (1) +#define PIN_A2 (2) +#define PIN_A3 (3) +#define PIN_A4 (4) +#define PIN_A5 (5) +#define PIN_VBAT (32) // Read the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define BAT_NOT_CHARGING (23) // LOW when charging + +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; + +#define ADC_RESOLUTION (12) + +// Other pins +#define PIN_NFC1 (30) +#define PIN_NFC2 (31) + +// Serial interfaces +#define PIN_SERIAL1_RX (7) +#define PIN_SERIAL1_TX (6) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (9) +#define PIN_SPI_MOSI (10) +#define PIN_SPI_SCK (8) + +#define PIN_SPI1_MISO (25) +#define PIN_SPI1_MOSI (26) +#define PIN_SPI1_SCK (29) + +// Lora SPI is on SPI0 +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT (1) + +// #define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 ! +// #define PIN_WIRE_SCL (16) // use WIRE1_SDA + +static const uint8_t SDA = PIN_WIRE_SDA; +static const uint8_t SCL = PIN_WIRE_SCL; + +//#define PIN_WIRE1_SDA (17) +//#define PIN_WIRE1_SCL (16) +#define PIN_LSM6DS3TR_C_POWER (15) +#define PIN_LSM6DS3TR_C_INT1 (18) + +// PDM Interfaces +#define PIN_PDM_PWR (19) +#define PIN_PDM_CLK (20) +#define PIN_PDM_DIN (21) + +// QSPI Pins +#define PIN_QSPI_SCK (24) +#define PIN_QSPI_CS (25) +#define PIN_QSPI_IO0 (26) +#define PIN_QSPI_IO1 (27) +#define PIN_QSPI_IO2 (28) +#define PIN_QSPI_IO3 (29) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES (P25Q16H) +#define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From 86ec82fd06768a8d4afc5a35edb7b12af3640d36 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 12 Aug 2025 20:56:59 +1000 Subject: [PATCH 06/11] * Heltec CT62: sensor role (with 2 channel relay support + 1 digital input) --- variants/heltec_ct62/HT-CT62Board.h | 30 +++++++++++++++++++++++++++-- variants/heltec_ct62/platformio.ini | 21 ++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/variants/heltec_ct62/HT-CT62Board.h b/variants/heltec_ct62/HT-CT62Board.h index e5a627b8..d26e7b26 100644 --- a/variants/heltec_ct62/HT-CT62Board.h +++ b/variants/heltec_ct62/HT-CT62Board.h @@ -8,9 +8,35 @@ #include class Heltec_CT62_Board : public ESP32Board { -public: + uint32_t gpio_state = 0; -uint16_t getBattMilliVolts() override { +public: + void begin() { + ESP32Board::begin(); +#if defined(PIN_BOARD_RELAY_CH1) && defined(PIN_BOARD_RELAY_CH2) + pinMode(PIN_BOARD_RELAY_CH1, OUTPUT); + pinMode(PIN_BOARD_RELAY_CH2, OUTPUT); +#endif +#if defined(PIN_BOARD_DIGITAL_IN) + pinMode(PIN_BOARD_DIGITAL_IN, INPUT); +#endif + } + uint32_t getGpio() override { +#if defined(PIN_BOARD_DIGITAL_IN) + return gpio_state | (digitalRead(PIN_BOARD_DIGITAL_IN) ? 1 : 0); +#else + return 0; +#endif + } + void setGpio(uint32_t values) override { +#if defined(PIN_BOARD_RELAY_CH1) && defined(PIN_BOARD_RELAY_CH2) + gpio_state = values; + digitalWrite(PIN_BOARD_RELAY_CH1, values & 2); + digitalWrite(PIN_BOARD_RELAY_CH2, values & 4); +#endif + } + + uint16_t getBattMilliVolts() override { #ifdef PIN_VBAT_READ analogReadResolution(12); // ESP32-C3 ADC is 12-bit - 3.3/4096 (ref voltage/max counts) uint32_t raw = 0; diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index ba23a5a6..ec24cdcd 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -86,3 +86,24 @@ lib_deps = ${Heltec_ct62.lib_deps} ${esp32_ota.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Heltec_ct62_sensor] +extends = Heltec_ct62 +build_flags = + ${Heltec_ct62.build_flags} + -D ADVERT_NAME='"HT-CT62 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D PIN_BOARD_SDA=-1 + -D PIN_BOARD_SCL=-1 + -D PIN_BOARD_RELAY_CH1=0 + -D PIN_BOARD_RELAY_CH2=1 + -D PIN_BOARD_DIGITAL_IN=19 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_ct62.build_src_filter} + +<../examples/simple_sensor> +lib_deps = + ${Heltec_ct62.lib_deps} + ${esp32_ota.lib_deps} From be243a2663a6a2d462228e11ed4210bb5bf5e0e7 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Wed, 13 Aug 2025 18:12:48 +0800 Subject: [PATCH 07/11] Add heltec_vision_master_e213 board. --- boards/heltec_vision_master_e213.json | 44 +++++++ src/helpers/RefCountedDigitalPin.h | 10 +- src/helpers/ui/E213Display.cpp | 121 +++++++++++++++--- src/helpers/ui/E213Display.h | 13 ++ .../HeltecE213Board.cpp | 69 ++++++++++ .../HeltecE213Board.h | 30 +++++ .../heltec_vision_master_e213/pins_arduino.h | 61 +++++++++ .../heltec_vision_master_e213/platformio.ini | 84 ++++++++++++ variants/heltec_vision_master_e213/target.cpp | 53 ++++++++ variants/heltec_vision_master_e213/target.h | 27 ++++ variants/heltec_wireless_paper/platformio.ini | 2 +- 11 files changed, 493 insertions(+), 21 deletions(-) create mode 100644 boards/heltec_vision_master_e213.json create mode 100644 variants/heltec_vision_master_e213/HeltecE213Board.cpp create mode 100644 variants/heltec_vision_master_e213/HeltecE213Board.h create mode 100644 variants/heltec_vision_master_e213/pins_arduino.h create mode 100644 variants/heltec_vision_master_e213/platformio.ini create mode 100644 variants/heltec_vision_master_e213/target.cpp create mode 100644 variants/heltec_vision_master_e213/target.h diff --git a/boards/heltec_vision_master_e213.json b/boards/heltec_vision_master_e213.json new file mode 100644 index 00000000..9bc6c389 --- /dev/null +++ b/boards/heltec_vision_master_e213.json @@ -0,0 +1,44 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [ + ["0x303A", "0x1001"], + ["0x303A", "0x0002"] + ], + "mcu": "esp32s3", + "variant": "heltec_vision_master_e213" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Vision Master E213", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 8388608, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/project/vision-master-e213/", + "vendor": "Heltec" +} diff --git a/src/helpers/RefCountedDigitalPin.h b/src/helpers/RefCountedDigitalPin.h index 14b67fb1..753f6c30 100644 --- a/src/helpers/RefCountedDigitalPin.h +++ b/src/helpers/RefCountedDigitalPin.h @@ -5,25 +5,25 @@ class RefCountedDigitalPin { uint8_t _pin; int8_t _claims = 0; - + uint8_t _active = 0; public: - RefCountedDigitalPin(uint8_t pin): _pin(pin) { } + RefCountedDigitalPin(uint8_t pin,uint8_t active=HIGH): _pin(pin), _active(active) { } void begin() { pinMode(_pin, OUTPUT); - digitalWrite(_pin, LOW); // initial state + digitalWrite(_pin, !_active); // initial state } void claim() { _claims++; if (_claims > 0) { - digitalWrite(_pin, HIGH); + digitalWrite(_pin, _active); } } void release() { _claims--; if (_claims == 0) { - digitalWrite(_pin, LOW); + digitalWrite(_pin, !_active); } } }; diff --git a/src/helpers/ui/E213Display.cpp b/src/helpers/ui/E213Display.cpp index 92bf37fb..0cb3dbf6 100644 --- a/src/helpers/ui/E213Display.cpp +++ b/src/helpers/ui/E213Display.cpp @@ -2,20 +2,59 @@ #include "../../MeshCore.h" +EInkDetectionResult E213Display::detectEInk() +{ + // Test 1: Logic of BUSY pin + + // Determines controller IC manufacturer + // Fitipower: busy when LOW + // Solomon Systech: busy when HIGH + + // Force display BUSY by holding reset pin active + pinMode(DISP_RST, OUTPUT); + digitalWrite(DISP_RST, LOW); + + delay(10); + + // Read whether pin is HIGH or LOW while busy + pinMode(DISP_BUSY, INPUT); + bool busyLogic = digitalRead(DISP_BUSY); + + // Test complete. Release pin + pinMode(DISP_RST, INPUT); + + if (busyLogic == LOW) + return V_LCMEN213EFC1; + else // busy HIGH + return V_E0213A367; +} + + bool E213Display::begin() { if (_init) return true; powerOn(); - display.begin(); + _version = detectEInk(); + if(_version==V_LCMEN213EFC1) { + display.begin(); + // Set to landscape mode rotated 180 degrees + display.setRotation(3); + } else{ + display1.begin(); + // Set to landscape mode rotated 180 degrees + display1.setRotation(3); + } - // Set to landscape mode rotated 180 degrees - display.setRotation(3); _init = true; _isOn = true; clear(); - display.fastmodeOn(); // Enable fast mode for quicker (partial) updates + if(_version==V_LCMEN213EFC1) { + display.fastmodeOn(); // Enable fast mode for quicker (partial) updates + } else{ + display1.fastmodeOn(); // Enable fast mode for quicker (partial) updates + } return true; } @@ -23,15 +62,23 @@ bool E213Display::begin() { void E213Display::powerOn() { #ifdef PIN_VEXT_EN pinMode(PIN_VEXT_EN, OUTPUT); +#ifdef PIN_VEXT_EN_ACTIVE + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); +#else digitalWrite(PIN_VEXT_EN, LOW); // Active low +#endif delay(50); // Allow power to stabilize #endif } void E213Display::powerOff() { #ifdef PIN_VEXT_EN +#ifdef PIN_VEXT_EN_ACTIVE + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); +#else digitalWrite(PIN_VEXT_EN, HIGH); // Turn off power #endif +#endif } void E213Display::turnOn() { @@ -46,21 +93,37 @@ void E213Display::turnOff() { } void E213Display::clear() { - display.clear(); + if(_version==V_LCMEN213EFC1) { + display.clear(); + } else{ + display1.clear(); + } } void E213Display::startFrame(Color bkg) { // Fill screen with white first to ensure clean background - display.fillRect(0, 0, width(), height(), WHITE); + if(_version==V_LCMEN213EFC1) { + display.fillRect(0, 0, width(), height(), WHITE); + } else{ + display1.fillRect(0, 0, width(), height(), WHITE); + } if (bkg == LIGHT) { // Fill with black if light background requested (inverted for e-ink) - display.fillRect(0, 0, width(), height(), BLACK); + if(_version==V_LCMEN213EFC1) { + display.fillRect(0, 0, width(), height(), BLACK); + } else{ + display1.fillRect(0, 0, width(), height(), BLACK); + } } } void E213Display::setTextSize(int sz) { // The library handles text size internally - display.setTextSize(sz); + if(_version==V_LCMEN213EFC1) { + display.setTextSize(sz); + } else{ + display1.setTextSize(sz); + } } void E213Display::setColor(Color c) { @@ -68,19 +131,35 @@ void E213Display::setColor(Color c) { } void E213Display::setCursor(int x, int y) { - display.setCursor(x, y); + if(_version==V_LCMEN213EFC1) { + display.setCursor(x, y); + } else{ + display1.setCursor(x, y); + } } void E213Display::print(const char *str) { - display.print(str); + if(_version==V_LCMEN213EFC1) { + display.print(str); + } else { + display1.print(str); + } } void E213Display::fillRect(int x, int y, int w, int h) { - display.fillRect(x, y, w, h, BLACK); + if(_version==V_LCMEN213EFC1) { + display.fillRect(x, y, w, h, BLACK); + } else { + display1.fillRect(x, y, w, h, BLACK); + } } void E213Display::drawRect(int x, int y, int w, int h) { - display.drawRect(x, y, w, h, BLACK); + if(_version==V_LCMEN213EFC1) { + display.drawRect(x, y, w, h, BLACK); + } else { + display1.drawRect(x, y, w, h, BLACK); + } } void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { @@ -98,7 +177,11 @@ void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { // If the bit is set, draw the pixel if (bitSet) { - display.drawPixel(x + bx, y + by, BLACK); + if(_version==V_LCMEN213EFC1) { + display.drawPixel(x + bx, y + by, BLACK); + } else { + display1.drawPixel(x + bx, y + by, BLACK); + } } } } @@ -107,10 +190,18 @@ void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { uint16_t E213Display::getTextWidth(const char *str) { int16_t x1, y1; uint16_t w, h; - display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + if(_version==V_LCMEN213EFC1) { + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + } else { + display1.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + } return w; } void E213Display::endFrame() { - display.update(); + if(_version==V_LCMEN213EFC1) { + display.update(); + } else { + display1.update(); + } } diff --git a/src/helpers/ui/E213Display.h b/src/helpers/ui/E213Display.h index 330a2b6d..46d13d0f 100644 --- a/src/helpers/ui/E213Display.h +++ b/src/helpers/ui/E213Display.h @@ -6,9 +6,21 @@ #include #include +enum EInkDetectionResult { + V_LCMEN213EFC1 = 0, // Initial version + V_E0213A367 = 1, // E213 PCB marked V1.1 (Mid 2025) +}; + // Display driver for E213 e-ink display class E213Display : public DisplayDriver { +#ifdef VISION_MASTER_E213 EInkDisplay_VisionMasterE213 display; + EInkDisplay_VisionMasterE213V1_1 display1; +#else + EInkDisplay_WirelessPaperV1_1 display; + EInkDisplay_WirelessPaperV1_1_1 display1; +#endif + EInkDetectionResult _version =V_LCMEN213EFC1; bool _init = false; bool _isOn = false; @@ -32,6 +44,7 @@ public: void endFrame() override; private: + EInkDetectionResult detectEInk(); void powerOn(); void powerOff(); }; \ No newline at end of file diff --git a/variants/heltec_vision_master_e213/HeltecE213Board.cpp b/variants/heltec_vision_master_e213/HeltecE213Board.cpp new file mode 100644 index 00000000..d32d274e --- /dev/null +++ b/variants/heltec_vision_master_e213/HeltecE213Board.cpp @@ -0,0 +1,69 @@ +#include "HeltecE213Board.h" + +void HeltecE213Board::begin() { + ESP32Board::begin(); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + periph_power.begin(); + + 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 HeltecE213Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + 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); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void HeltecE213Board::powerOff() { + // TODO: re-enable this when there is a definite wake-up source pin: + // enterDeepSleep(0); + } + + uint16_t HeltecE213Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecE213Board::getManufacturerName() const { + return "Heltec E213"; + } + diff --git a/variants/heltec_vision_master_e213/HeltecE213Board.h b/variants/heltec_vision_master_e213/HeltecE213Board.h new file mode 100644 index 00000000..dd622064 --- /dev/null +++ b/variants/heltec_vision_master_e213/HeltecE213Board.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +// LoRa radio module pins for heltec_vision_master_e213 +#define P_LORA_DIO_1 14 +#define P_LORA_NSS 8 +#define P_LORA_RESET 12 +#define P_LORA_BUSY 13 +#define P_LORA_SCLK 9 +#define P_LORA_MISO 11 +#define P_LORA_MOSI 10 + +class HeltecE213Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecE213Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_vision_master_e213/pins_arduino.h b/variants/heltec_vision_master_e213/pins_arduino.h new file mode 100644 index 00000000..56f5ef15 --- /dev/null +++ b/variants/heltec_vision_master_e213/pins_arduino.h @@ -0,0 +1,61 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant +#define BUILTIN_LED LED_BUILTIN // Backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 39; +static const uint8_t SCL = 38; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO1 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e213/platformio.ini b/variants/heltec_vision_master_e213/platformio.ini new file mode 100644 index 00000000..29611dfa --- /dev/null +++ b/variants/heltec_vision_master_e213/platformio.ini @@ -0,0 +1,84 @@ +[Heltec_Vision_Master_E213_base] +extends = esp32_base +board = heltec_vision_master_e213 +build_flags = + ${esp32_base.build_flags} + -I variants/heltec_vision_master_e213 + -D HELTEC_VISION_MASTER_E213 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=45 + -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=18 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VBAT_READ=7 + -D PIN_ADC_CTRL=46 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_BOARD_SDA=39 + -D PIN_BOARD_SCL=38 + -D DISP_CS=5 + -D DISP_BUSY=1 + -D DISP_DC=2 + -D DISP_RST=3 + -D DISP_SCLK=4 + -D DISP_MOSI=6 + -D Vision_Master_E213 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_vision_master_e213> +lib_deps = + ${esp32_base.lib_deps} + https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip + +[env:Heltec_Vision_Master_E213_radio_ble] +extends = Heltec_Vision_Master_E213_base +build_flags = + ${Heltec_Vision_Master_E213_base.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=E213Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter} + + + + + +<../examples/companion_radio> +lib_deps = + ${Heltec_Vision_Master_E213_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_Vision_Master_E213_repeater] +extends = Heltec_Vision_Master_E213_base +build_flags = + ${Heltec_Vision_Master_E213_base.build_flags} + -D DISPLAY_CLASS=E213Display + -D ADVERT_NAME='"Heltec E213 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 +build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_Vision_Master_E213_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:Heltec_Vision_Master_E213_room_server] +extends = Heltec_Vision_Master_E213_base +build_flags = + ${Heltec_Vision_Master_E213_base.build_flags} + -D DISPLAY_CLASS=E213Display + -D ADVERT_NAME='"Heltec E213 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${Heltec_Vision_Master_E213_base.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${Heltec_Vision_Master_E213_base.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_vision_master_e213/target.cpp b/variants/heltec_vision_master_e213/target.cpp new file mode 100644 index 00000000..dfba0103 --- /dev/null +++ b/variants/heltec_vision_master_e213/target.cpp @@ -0,0 +1,53 @@ +#include "target.h" +#include + +HeltecE213Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi(FSPI); + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +SensorManager sensors; + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/heltec_vision_master_e213/target.h b/variants/heltec_vision_master_e213/target.h new file mode 100644 index 00000000..ec113879 --- /dev/null +++ b/variants/heltec_vision_master_e213/target.h @@ -0,0 +1,27 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS +#include +#endif + +extern HeltecE213Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 513ba4b9..b0c9ed1d 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -31,7 +31,7 @@ build_src_filter = ${esp32_base.build_src_filter} +<../variants/heltec_wireless_paper> lib_deps = ${esp32_base.lib_deps} - todd-herbert/heltec-eink-modules @ 4.5.0 + https://github.com/todd-herbert/heltec-eink-modules/archive/9207eb6ab2b96f66298e0488740218c17b006af7.zip [env:Heltec_Wireless_Paper_companion_radio_ble] extends = Heltec_Wireless_Paper_base From dc9b4f8e846ea5a40029beeca2924d1c417c4d0e Mon Sep 17 00:00:00 2001 From: 446564 Date: Wed, 13 Aug 2025 09:47:01 -0700 Subject: [PATCH 08/11] add nano g2 usb companion --- variants/nano_g2_ultra/platformio.ini | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index 511c0ae7..25871fc9 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -55,3 +55,27 @@ lib_deps = adafruit/Adafruit GFX Library @ ^1.12.1 stevemarple/MicroNMEA @ ^2.0.6 end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:Nano_G2_Ultra_companion_radio_usb] +extends = Nano_G2_Ultra +build_flags = + ${Nano_G2_Ultra.build_flags} + -I src/helpers/ui + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SH1106Display + -D PIN_BUZZER=4 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Nano_G2_Ultra.build_src_filter} + + + + + +<../examples/companion_radio> +lib_deps = + ${Nano_G2_Ultra.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit SH110X @ ~2.1.13 + adafruit/Adafruit GFX Library @ ^1.12.1 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 From fad4a7fb5170a85202e55b7d4d079d0e77b5424c Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 14 Aug 2025 10:26:26 +0800 Subject: [PATCH 09/11] Modify the flash size to 16MB. --- boards/heltec_vision_master_e213.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/boards/heltec_vision_master_e213.json b/boards/heltec_vision_master_e213.json index 9bc6c389..81efd8f5 100644 --- a/boards/heltec_vision_master_e213.json +++ b/boards/heltec_vision_master_e213.json @@ -2,7 +2,7 @@ "build": { "arduino": { "ldscript": "esp32s3_out.ld", - "partitions": "default_8MB.csv", + "partitions": "default_16MB.csv", "memory_type": "qio_opi" }, "core": "esp32", @@ -31,9 +31,9 @@ "frameworks": ["arduino", "espidf"], "name": "Heltec Vision Master E213", "upload": { - "flash_size": "8MB", + "flash_size": "16MB", "maximum_ram_size": 8388608, - "maximum_size": 8388608, + "maximum_size": 16777216, "use_1200bps_touch": true, "wait_for_upload_port": true, "require_upload_port": true, From 6d18e2c57b1806e1e6aca1c2cf2faad2f4f2e54e Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 14 Aug 2025 10:30:27 +0800 Subject: [PATCH 10/11] Add heltec_vision_master_e290 board. --- boards/heltec_vision_master_e290.json | 44 +++++++ src/helpers/ui/E290Display.cpp | 116 ++++++++++++++++++ src/helpers/ui/E290Display.h | 37 ++++++ .../HeltecE290Board.cpp | 69 +++++++++++ .../HeltecE290Board.h | 30 +++++ .../heltec_vision_master_e290/pins_arduino.h | 61 +++++++++ .../heltec_vision_master_e290/platformio.ini | 78 ++++++++++++ variants/heltec_vision_master_e290/target.cpp | 53 ++++++++ variants/heltec_vision_master_e290/target.h | 27 ++++ 9 files changed, 515 insertions(+) create mode 100644 boards/heltec_vision_master_e290.json create mode 100644 src/helpers/ui/E290Display.cpp create mode 100644 src/helpers/ui/E290Display.h create mode 100644 variants/heltec_vision_master_e290/HeltecE290Board.cpp create mode 100644 variants/heltec_vision_master_e290/HeltecE290Board.h create mode 100644 variants/heltec_vision_master_e290/pins_arduino.h create mode 100644 variants/heltec_vision_master_e290/platformio.ini create mode 100644 variants/heltec_vision_master_e290/target.cpp create mode 100644 variants/heltec_vision_master_e290/target.h diff --git a/boards/heltec_vision_master_e290.json b/boards/heltec_vision_master_e290.json new file mode 100644 index 00000000..07577557 --- /dev/null +++ b/boards/heltec_vision_master_e290.json @@ -0,0 +1,44 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [ + ["0x303A", "0x1001"], + ["0x303A", "0x0002"] + ], + "mcu": "esp32s3", + "variant": "heltec_vision_master_e290" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec Vision Master E290", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 8388608, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/project/vision-master-e290/", + "vendor": "Heltec" +} diff --git a/src/helpers/ui/E290Display.cpp b/src/helpers/ui/E290Display.cpp new file mode 100644 index 00000000..23ff2d95 --- /dev/null +++ b/src/helpers/ui/E290Display.cpp @@ -0,0 +1,116 @@ +#include "E290Display.h" + +#include "../../MeshCore.h" + +bool E290Display::begin() { + if (_init) return true; + + powerOn(); + display.begin(); + + // Set to landscape mode rotated 180 degrees + display.setRotation(3); + + _init = true; + _isOn = true; + + clear(); + display.fastmodeOn(); // Enable fast mode for quicker (partial) updates + + return true; +} + +void E290Display::powerOn() { +#ifdef PIN_VEXT_EN + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); + delay(50); // Allow power to stabilize +#endif +} + +void E290Display::powerOff() { +#ifdef PIN_VEXT_EN + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // Turn off power +#endif +} + +void E290Display::turnOn() { + if (!_init) begin(); + powerOn(); + _isOn = true; +} + +void E290Display::turnOff() { + powerOff(); + _isOn = false; +} + +void E290Display::clear() { + display.clear(); +} + +void E290Display::startFrame(Color bkg) { + // Fill screen with white first to ensure clean background + display.fillRect(0, 0, width(), height(), WHITE); + if (bkg == LIGHT) { + // Fill with black if light background requested (inverted for e-ink) + display.fillRect(0, 0, width(), height(), BLACK); + } +} + +void E290Display::setTextSize(int sz) { + // The library handles text size internally + display.setTextSize(sz); +} + +void E290Display::setColor(Color c) { + // implemented in individual display methods +} + +void E290Display::setCursor(int x, int y) { + display.setCursor(x, y); +} + +void E290Display::print(const char *str) { + display.print(str); +} + +void E290Display::fillRect(int x, int y, int w, int h) { + display.fillRect(x, y, w, h, BLACK); +} + +void E290Display::drawRect(int x, int y, int w, int h) { + display.drawRect(x, y, w, h, BLACK); +} + +void E290Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { + // Width in bytes for bitmap processing + uint16_t widthInBytes = (w + 7) / 8; + + // Process the bitmap row by row + for (int by = 0; by < h; by++) { + // Scan across the row bit by bit + for (int bx = 0; bx < w; bx++) { + // Get the current bit using MSB ordering (like GxEPDDisplay) + uint16_t byteOffset = (by * widthInBytes) + (bx / 8); + uint8_t bitMask = 0x80 >> (bx & 7); + bool bitSet = bits[byteOffset] & bitMask; + + // If the bit is set, draw the pixel + if (bitSet) { + display.drawPixel(x + bx, y + by, BLACK); + } + } + } +} + +uint16_t E290Display::getTextWidth(const char *str) { + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + return w; +} + +void E290Display::endFrame() { + display.update(); +} diff --git a/src/helpers/ui/E290Display.h b/src/helpers/ui/E290Display.h new file mode 100644 index 00000000..16f45382 --- /dev/null +++ b/src/helpers/ui/E290Display.h @@ -0,0 +1,37 @@ +#pragma once + +#include "DisplayDriver.h" + +#include +#include +#include + +// Display driver for E290 e-ink display +class E290Display : public DisplayDriver { + EInkDisplay_VisionMasterE290 display; + bool _init = false; + bool _isOn = false; + +public: + E290Display() : DisplayDriver(296, 128) {} + + bool begin(); + bool isOn() override { return _isOn; } + void turnOn() override; + void turnOff() override; + void clear() override; + void startFrame(Color bkg = DARK) override; + void setTextSize(int sz) override; + void setColor(Color c) override; + void setCursor(int x, int y) override; + void print(const char *str) override; + void fillRect(int x, int y, int w, int h) override; + void drawRect(int x, int y, int w, int h) override; + void drawXbm(int x, int y, const uint8_t *bits, int w, int h) override; + uint16_t getTextWidth(const char *str) override; + void endFrame() override; + +private: + void powerOn(); + void powerOff(); +}; \ No newline at end of file diff --git a/variants/heltec_vision_master_e290/HeltecE290Board.cpp b/variants/heltec_vision_master_e290/HeltecE290Board.cpp new file mode 100644 index 00000000..7d8c654d --- /dev/null +++ b/variants/heltec_vision_master_e290/HeltecE290Board.cpp @@ -0,0 +1,69 @@ +#include "HeltecE290Board.h" + +void HeltecE290Board::begin() { + ESP32Board::begin(); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + periph_power.begin(); + + 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 HeltecE290Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + 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); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void HeltecE290Board::powerOff() { + // TODO: re-enable this when there is a definite wake-up source pin: + // enterDeepSleep(0); + } + + uint16_t HeltecE290Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecE290Board::getManufacturerName() const { + return "Heltec E290"; + } + diff --git a/variants/heltec_vision_master_e290/HeltecE290Board.h b/variants/heltec_vision_master_e290/HeltecE290Board.h new file mode 100644 index 00000000..95f8c03e --- /dev/null +++ b/variants/heltec_vision_master_e290/HeltecE290Board.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +// LoRa radio module pins for heltec_vision_master_e290 +#define P_LORA_DIO_1 14 +#define P_LORA_NSS 8 +#define P_LORA_RESET 12 +#define P_LORA_BUSY 13 +#define P_LORA_SCLK 9 +#define P_LORA_MISO 11 +#define P_LORA_MOSI 10 + +class HeltecE290Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecE290Board() : periph_power(PIN_VEXT_EN) { } + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_vision_master_e290/pins_arduino.h b/variants/heltec_vision_master_e290/pins_arduino.h new file mode 100644 index 00000000..56f5ef15 --- /dev/null +++ b/variants/heltec_vision_master_e290/pins_arduino.h @@ -0,0 +1,61 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +static const uint8_t LED_BUILTIN = 45; // LED is not populated on earliest board variant +#define BUILTIN_LED LED_BUILTIN // Backward compatibility +#define LED_BUILTIN LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 39; +static const uint8_t SCL = 38; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO1 = 14; + +#endif /* Pins_Arduino_h */ diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/heltec_vision_master_e290/platformio.ini new file mode 100644 index 00000000..b3ba33be --- /dev/null +++ b/variants/heltec_vision_master_e290/platformio.ini @@ -0,0 +1,78 @@ +[Heltec_Vision_Master_E290_base] +extends = esp32_base +board = heltec_vision_master_e290 +build_flags = + ${esp32_base.build_flags} + -I variants/heltec_vision_master_e290 + -D HELTEC_VISION_MASTER_E290 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=45 + -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=18 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VBAT_READ=7 + -D PIN_ADC_CTRL=46 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_BOARD_SDA=39 + -D PIN_BOARD_SCL=38 + -D Vision_Master_E290 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_vision_master_e290> +lib_deps = + ${esp32_base.lib_deps} + https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip + +[env:Heltec_Vision_Master_E290_radio_ble] +extends = Heltec_Vision_Master_E290_base +build_flags = + ${Heltec_Vision_Master_E290_base.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=E290Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter} + + + + + +<../examples/companion_radio> +lib_deps = + ${Heltec_Vision_Master_E290_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_Vision_Master_E290_repeater] +extends = Heltec_Vision_Master_E290_base +build_flags = + ${Heltec_Vision_Master_E290_base.build_flags} + -D DISPLAY_CLASS=E290Display + -D ADVERT_NAME='"Heltec E290 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 +build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_Vision_Master_E290_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:Heltec_Vision_Master_E290_room_server] +extends = Heltec_Vision_Master_E290_base +build_flags = + ${Heltec_Vision_Master_E290_base.build_flags} + -D DISPLAY_CLASS=E290Display + -D ADVERT_NAME='"Heltec E290 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${Heltec_Vision_Master_E290_base.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${Heltec_Vision_Master_E290_base.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_vision_master_e290/target.cpp b/variants/heltec_vision_master_e290/target.cpp new file mode 100644 index 00000000..2e897e49 --- /dev/null +++ b/variants/heltec_vision_master_e290/target.cpp @@ -0,0 +1,53 @@ +#include "target.h" +#include + +HeltecE290Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi(FSPI); + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +SensorManager sensors; + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/heltec_vision_master_e290/target.h b/variants/heltec_vision_master_e290/target.h new file mode 100644 index 00000000..00b27e54 --- /dev/null +++ b/variants/heltec_vision_master_e290/target.h @@ -0,0 +1,27 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS +#include +#endif + +extern HeltecE290Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file From aa7f9d8df634f1c377cb252aeb54437f9353b88d Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 14 Aug 2025 17:43:46 +0800 Subject: [PATCH 11/11] Use the base class to optimize screen display code. --- src/helpers/ui/E213Display.cpp | 109 ++++++++++----------------------- src/helpers/ui/E213Display.h | 22 +++---- 2 files changed, 40 insertions(+), 91 deletions(-) diff --git a/src/helpers/ui/E213Display.cpp b/src/helpers/ui/E213Display.cpp index 0cb3dbf6..a0e71f31 100644 --- a/src/helpers/ui/E213Display.cpp +++ b/src/helpers/ui/E213Display.cpp @@ -2,7 +2,7 @@ #include "../../MeshCore.h" -EInkDetectionResult E213Display::detectEInk() +BaseDisplay* E213Display::detectEInk() { // Test 1: Logic of BUSY pin @@ -23,38 +23,37 @@ EInkDetectionResult E213Display::detectEInk() // Test complete. Release pin pinMode(DISP_RST, INPUT); - if (busyLogic == LOW) - return V_LCMEN213EFC1; - else // busy HIGH - return V_E0213A367; + if (busyLogic == LOW) { +#ifdef VISION_MASTER_E213 + return new EInkDisplay_VisionMasterE213 ; +#else + return new EInkDisplay_WirelessPaperV1_1 ; +#endif + } else {// busy HIGH +#ifdef VISION_MASTER_E213 + return new EInkDisplay_VisionMasterE213V1_1 ; +#else + return new EInkDisplay_WirelessPaperV1_1_1 ; +#endif + } } - bool E213Display::begin() { if (_init) return true; powerOn(); - _version = detectEInk(); - if(_version==V_LCMEN213EFC1) { - display.begin(); - // Set to landscape mode rotated 180 degrees - display.setRotation(3); - } else{ - display1.begin(); - // Set to landscape mode rotated 180 degrees - display1.setRotation(3); + if(display==NULL) { + display = detectEInk(); } - + display->begin(); + // Set to landscape mode rotated 180 degrees + display->setRotation(3); _init = true; _isOn = true; clear(); - if(_version==V_LCMEN213EFC1) { - display.fastmodeOn(); // Enable fast mode for quicker (partial) updates - } else{ - display1.fastmodeOn(); // Enable fast mode for quicker (partial) updates - } + display->fastmodeOn(); // Enable fast mode for quicker (partial) updates return true; } @@ -93,37 +92,23 @@ void E213Display::turnOff() { } void E213Display::clear() { - if(_version==V_LCMEN213EFC1) { - display.clear(); - } else{ - display1.clear(); - } + display->clear(); + } void E213Display::startFrame(Color bkg) { // Fill screen with white first to ensure clean background - if(_version==V_LCMEN213EFC1) { - display.fillRect(0, 0, width(), height(), WHITE); - } else{ - display1.fillRect(0, 0, width(), height(), WHITE); - } + display->fillRect(0, 0, width(), height(), WHITE); + if (bkg == LIGHT) { // Fill with black if light background requested (inverted for e-ink) - if(_version==V_LCMEN213EFC1) { - display.fillRect(0, 0, width(), height(), BLACK); - } else{ - display1.fillRect(0, 0, width(), height(), BLACK); - } + display->fillRect(0, 0, width(), height(), BLACK); } } void E213Display::setTextSize(int sz) { // The library handles text size internally - if(_version==V_LCMEN213EFC1) { - display.setTextSize(sz); - } else{ - display1.setTextSize(sz); - } + display->setTextSize(sz); } void E213Display::setColor(Color c) { @@ -131,35 +116,19 @@ void E213Display::setColor(Color c) { } void E213Display::setCursor(int x, int y) { - if(_version==V_LCMEN213EFC1) { - display.setCursor(x, y); - } else{ - display1.setCursor(x, y); - } + display->setCursor(x, y); } void E213Display::print(const char *str) { - if(_version==V_LCMEN213EFC1) { - display.print(str); - } else { - display1.print(str); - } + display->print(str); } void E213Display::fillRect(int x, int y, int w, int h) { - if(_version==V_LCMEN213EFC1) { - display.fillRect(x, y, w, h, BLACK); - } else { - display1.fillRect(x, y, w, h, BLACK); - } + display->fillRect(x, y, w, h, BLACK); } void E213Display::drawRect(int x, int y, int w, int h) { - if(_version==V_LCMEN213EFC1) { - display.drawRect(x, y, w, h, BLACK); - } else { - display1.drawRect(x, y, w, h, BLACK); - } + display->drawRect(x, y, w, h, BLACK); } void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { @@ -177,11 +146,7 @@ void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { // If the bit is set, draw the pixel if (bitSet) { - if(_version==V_LCMEN213EFC1) { - display.drawPixel(x + bx, y + by, BLACK); - } else { - display1.drawPixel(x + bx, y + by, BLACK); - } + display->drawPixel(x + bx, y + by, BLACK); } } } @@ -190,18 +155,10 @@ void E213Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) { uint16_t E213Display::getTextWidth(const char *str) { int16_t x1, y1; uint16_t w, h; - if(_version==V_LCMEN213EFC1) { - display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); - } else { - display1.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); - } + display->getTextBounds(str, 0, 0, &x1, &y1, &w, &h); return w; } void E213Display::endFrame() { - if(_version==V_LCMEN213EFC1) { - display.update(); - } else { - display1.update(); - } + display->update(); } diff --git a/src/helpers/ui/E213Display.h b/src/helpers/ui/E213Display.h index 46d13d0f..657bfb4c 100644 --- a/src/helpers/ui/E213Display.h +++ b/src/helpers/ui/E213Display.h @@ -6,27 +6,19 @@ #include #include -enum EInkDetectionResult { - V_LCMEN213EFC1 = 0, // Initial version - V_E0213A367 = 1, // E213 PCB marked V1.1 (Mid 2025) -}; - // Display driver for E213 e-ink display class E213Display : public DisplayDriver { -#ifdef VISION_MASTER_E213 - EInkDisplay_VisionMasterE213 display; - EInkDisplay_VisionMasterE213V1_1 display1; -#else - EInkDisplay_WirelessPaperV1_1 display; - EInkDisplay_WirelessPaperV1_1_1 display1; -#endif - EInkDetectionResult _version =V_LCMEN213EFC1; + BaseDisplay* display=NULL; bool _init = false; bool _isOn = false; public: E213Display() : DisplayDriver(250, 122) {} - + ~E213Display(){ + if(display!=NULL) { + delete display; + } + } bool begin(); bool isOn() override { return _isOn; } void turnOn() override; @@ -44,7 +36,7 @@ public: void endFrame() override; private: - EInkDetectionResult detectEInk(); + BaseDisplay* detectEInk(); void powerOn(); void powerOff(); }; \ No newline at end of file