From a50cbff5ebd8b89914591d92de9474d440f69d58 Mon Sep 17 00:00:00 2001 From: TJ Downes <273720+tjdownes@users.noreply.github.com> Date: Sat, 25 Jul 2026 12:38:55 -0700 Subject: [PATCH] Add Muzi Base board support (Duo + Uno + SuperIO) One nRF52840 variant shared across both radios, picked per env: Duo runs the LR1121, Uno the SX1262. Same PCB, only the radio and its DIO1 pin change. SuperIO adds the SH1107 OLED, GPS, buzzer and joystick. GPS is driven by the 3-position mode switch (Mode 2 = on), polled live the way thinknode_m1 does it. Consolidates #2054 and andyshinn's shared-base/uno work, rebased on dev. Co-authored-by: lbibass Co-authored-by: Andy Shinn --- boards/muzi_base.json | 72 +++++ src/helpers/radiolib/CustomLR1121.h | 41 +++ src/helpers/radiolib/CustomLR1121Wrapper.h | 50 ++++ src/helpers/ui/SH1107Display.cpp | 113 +++++++ src/helpers/ui/SH1107Display.h | 43 +++ variants/muzi_base/muzi_baseBoard.cpp | 52 ++++ variants/muzi_base/muzi_baseBoard.h | 48 +++ variants/muzi_base/platformio.ini | 326 +++++++++++++++++++++ variants/muzi_base/target.cpp | 131 +++++++++ variants/muzi_base/target.h | 58 ++++ variants/muzi_base/variant.cpp | 90 ++++++ variants/muzi_base/variant.h | 164 +++++++++++ 12 files changed, 1188 insertions(+) create mode 100644 boards/muzi_base.json create mode 100644 src/helpers/radiolib/CustomLR1121.h create mode 100644 src/helpers/radiolib/CustomLR1121Wrapper.h create mode 100644 src/helpers/ui/SH1107Display.cpp create mode 100644 src/helpers/ui/SH1107Display.h create mode 100644 variants/muzi_base/muzi_baseBoard.cpp create mode 100644 variants/muzi_base/muzi_baseBoard.h create mode 100644 variants/muzi_base/platformio.ini create mode 100644 variants/muzi_base/target.cpp create mode 100644 variants/muzi_base/target.h create mode 100644 variants/muzi_base/variant.cpp create mode 100644 variants/muzi_base/variant.h diff --git a/boards/muzi_base.json b/boards/muzi_base.json new file mode 100644 index 0000000000..1b2cf7df12 --- /dev/null +++ b/boards/muzi_base.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_MUZI_BASE -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x4405" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ] + ], + "usb_product": "muzi_base", + "mcu": "nrf52840", + "variant": "MUZI_BASE", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": [ + "jlink" + ], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "Muzi Base", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ] + }, + "url": "https://github.com/muzi-works", + "vendor": "MuziWorks" +} diff --git a/src/helpers/radiolib/CustomLR1121.h b/src/helpers/radiolib/CustomLR1121.h new file mode 100644 index 0000000000..873bfabc4e --- /dev/null +++ b/src/helpers/radiolib/CustomLR1121.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include "MeshCore.h" + +class CustomLR1121 : public LR1121 { + bool _rx_boosted = false; + + public: + CustomLR1121(Module *mod) : LR1121(mod) { } + + 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; + } + + float getFreqMHz() const { return freqMHz; } + + int16_t setRxBoostedGainMode(bool en) { + _rx_boosted = en; + return LR1121::setRxBoostedGainMode(en); + } + + bool getRxBoostedGainMode() const { return _rx_boosted; } + + bool isReceiving() { + uint16_t irq = getIrqStatus(); + bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); + return detected; + } + uint8_t getSpreadingFactor() const { return spreadingFactor; } + +}; \ 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..791ca79f2b --- /dev/null +++ b/src/helpers/radiolib/CustomLR1121Wrapper.h @@ -0,0 +1,50 @@ +#pragma once + +#include "CustomLR1121.h" +#include "RadioLibWrappers.h" +#include "LR11x0Reset.h" + +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(); + _radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts + } + + uint32_t getEstAirtimeFor(int len_bytes) override { + auto airtime = RadioLibWrapper::getEstAirtimeFor(len_bytes); + return airtime < 200 ? 200 : airtime; // at least 200 millis + } + + 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(); } + + bool setRxBoostedGainMode(bool en) override { + return ((CustomLR1121 *)_radio)->setRxBoostedGainMode(en) == RADIOLIB_ERR_NONE; + } + bool getRxBoostedGainMode() const override { + return ((CustomLR1121 *)_radio)->getRxBoostedGainMode(); + } +}; diff --git a/src/helpers/ui/SH1107Display.cpp b/src/helpers/ui/SH1107Display.cpp new file mode 100644 index 0000000000..09bf424f84 --- /dev/null +++ b/src/helpers/ui/SH1107Display.cpp @@ -0,0 +1,113 @@ +#include "SH1107Display.h" +#include +#include "Adafruit_SH110X.h" + +#ifndef DISPLAY_ROTATION +#define DISPLAY_ROTATION 0 +#endif + +ColorVal UIColor::window_bkg = SH110X_BLACK; +ColorVal UIColor::title_bkg = SH110X_BLACK; +ColorVal UIColor::title_txt = SH110X_WHITE; +ColorVal UIColor::primary_txt = SH110X_WHITE; +ColorVal UIColor::secondary_txt = SH110X_WHITE; +ColorVal UIColor::warning_txt = SH110X_WHITE; +ColorVal UIColor::popup_bkg = SH110X_BLACK; +ColorVal UIColor::popup_txt = SH110X_WHITE; +ColorVal UIColor::corp_blue = SH110X_WHITE; + +bool SH1107Display::i2c_probe(TwoWire &wire, uint8_t addr) +{ + wire.beginTransmission(addr); + uint8_t error = wire.endTransmission(); + return (error == 0); +} + +bool SH1107Display::begin() +{ + bool result = display.begin(DISPLAY_ADDRESS, true) && i2c_probe(Wire, DISPLAY_ADDRESS); + if (result) { + display.setRotation(DISPLAY_ROTATION); + } + return result; +} + +void SH1107Display::turnOn() +{ + display.oled_command(SH110X_DISPLAYON); + uint8_t cmd[] = {0xD5, 0xF0}; + display.oled_commandList(cmd, 2); + _isOn = true; +} + +void SH1107Display::turnOff() +{ + display.oled_command(SH110X_DISPLAYOFF); + _isOn = false; +} + +void SH1107Display::clear() +{ + display.clearDisplay(); + display.display(); +} + +void SH1107Display::startFrame(ColorVal bkg) +{ + display.clearDisplay(); // TODO: apply 'bkg' + display.setContrast(120); // 0-127. default setting was causing some flickering. + // display.SH110X_SETPRECHARGE(255); + _color = SH110X_WHITE; + display.setTextColor(_color); + display.setTextSize(1); + display.cp437(true); // Use full 256 char 'Code Page 437' font +} + +void SH1107Display::setTextSize(int sz) +{ + display.setTextSize(sz); +} + +void SH1107Display::setColor(ColorVal c) +{ + _color = (c != 0) ? SH110X_WHITE : SH110X_BLACK; + display.setTextColor(_color); +} + +void SH1107Display::setCursor(int x, int y) +{ + display.setCursor(x, y); +} + +void SH1107Display::print(const char *str) +{ + display.print(str); +} + +void SH1107Display::fillRect(int x, int y, int w, int h) +{ + display.fillRect(x, y, w, h, _color); +} + +void SH1107Display::drawRect(int x, int y, int w, int h) +{ + display.drawRect(x, y, w, h, _color); +} + +void SH1107Display::drawXbm(int x, int y, const uint8_t *bits, int w, int h) +{ + display.drawBitmap(x, y, bits, w, h, SH110X_WHITE); +} + +uint16_t SH1107Display::getTextWidth(const char *str) +{ + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + return w; +} + +void SH1107Display::endFrame() +{ + display.display(); +} diff --git a/src/helpers/ui/SH1107Display.h b/src/helpers/ui/SH1107Display.h new file mode 100644 index 0000000000..417184c29c --- /dev/null +++ b/src/helpers/ui/SH1107Display.h @@ -0,0 +1,43 @@ +#pragma once + +#include "DisplayDriver.h" +#include +#include +#define SH110X_NO_SPLASH +#include + +#ifndef PIN_OLED_RESET +#define PIN_OLED_RESET -1 +#endif + +#ifndef DISPLAY_ADDRESS +#define DISPLAY_ADDRESS 0x3c +#endif + +class SH1107Display : public DisplayDriver +{ + Adafruit_SH1107 display; + bool _isOn; + uint8_t _color; + + bool i2c_probe(TwoWire &wire, uint8_t addr); + +public: + SH1107Display() : DisplayDriver(128, 128), display(128, 128, &Wire, PIN_OLED_RESET) { _isOn = false; } + bool begin(); + + bool isOn() override { return _isOn; } + void turnOn() override; + void turnOff() override; + void clear() override; + void startFrame(ColorVal bkg = UIColor::window_bkg) override; + void setTextSize(int sz) override; + void setColor(ColorVal 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; +}; diff --git a/variants/muzi_base/muzi_baseBoard.cpp b/variants/muzi_base/muzi_baseBoard.cpp new file mode 100644 index 0000000000..ed713fa07a --- /dev/null +++ b/variants/muzi_base/muzi_baseBoard.cpp @@ -0,0 +1,52 @@ +#include +#include + +#include "muzi_baseBoard.h" + +#ifdef NRF52_POWER_MANAGEMENT +const PowerMgtConfig power_config = { + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, + .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK +}; + +void muzi_baseBoard::initiateShutdown(uint8_t reason) { + // Disable LoRa module power before shutdown + if (reason == SHUTDOWN_REASON_LOW_VOLTAGE || + reason == SHUTDOWN_REASON_BOOT_PROTECT) { + configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + } + + enterSystemOff(reason); +} +#endif // NRF52_POWER_MANAGEMENT + +void muzi_baseBoard::begin() { + NRF52BoardDCDC::begin(); + pinMode(PIN_VBAT_READ, INPUT); +#ifdef muzi_base_superIO + // 12V rail is only needed for the superIO display + pinMode(SCREEN_12V_ENABLE, OUTPUT); + digitalWrite(SCREEN_12V_ENABLE, HIGH); // Enable 12V power for SH1107 display + delay(250); +#endif + Wire.begin(); + // delay(1000); // wait for display to initialize. otherwise it doesn't come up on boot. + +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + pinMode(PIN_BUTTON1, INPUT_PULLUP); + pinMode(PIN_BUTTON2, INPUT_PULLUP); + pinMode(PIN_BUTTON3, INPUT_PULLUP); + pinMode(PIN_BUTTON4, INPUT_PULLUP); + pinMode(PIN_BUTTON5, INPUT_PULLUP); + pinMode(PIN_BUTTON6, INPUT_PULLUP); + +// #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) +// Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); +// #endif +#ifdef NRF52_POWER_MANAGEMENT + checkBootVoltage(&power_config); +#endif +} diff --git a/variants/muzi_base/muzi_baseBoard.h b/variants/muzi_base/muzi_baseBoard.h new file mode 100644 index 0000000000..e5412b6a83 --- /dev/null +++ b/variants/muzi_base/muzi_baseBoard.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +// The Muzi Base PCB ships in two radio flavors. Keep the user-facing identity +// (OTA/DFU name and reported manufacturer) distinct per radio even though the +// board logic is shared. +#if defined(USE_SX1262) + #define MUZI_BASE_OTA_NAME "MUZI_BASE_UNO_OTA" + #define MUZI_BASE_MFR_NAME "Muzi Base Uno" +#else + #define MUZI_BASE_OTA_NAME "MUZI_BASE_DUO_OTA" + #define MUZI_BASE_MFR_NAME "Muzi Base Duo" +#endif + +class muzi_baseBoard : public NRF52BoardDCDC { +protected: +#ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + +public: + muzi_baseBoard() : NRF52Board(MUZI_BASE_OTA_NAME) {} + void begin(); + + #define BATTERY_SAMPLES 8 + + uint16_t getBattMilliVolts() override { + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(1); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + + // ADC_MULTIPLIER is the voltage divider ratio + return (raw * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char* getManufacturerName() const override { + return MUZI_BASE_MFR_NAME; + } +}; diff --git a/variants/muzi_base/platformio.ini b/variants/muzi_base/platformio.ini new file mode 100644 index 0000000000..eb35d5696b --- /dev/null +++ b/variants/muzi_base/platformio.ini @@ -0,0 +1,326 @@ +; ============================================================================ +; Muzi Base (MuziWorks) +; +; One nRF52840 board, shipped in two radio flavors. The radio is selected per +; environment; all board logic lives in variants/muzi_base and is shared: +; muzi_base_duo_* -> LR1121 (USE_LR1121) +; muzi_base_uno_* -> SX1262 (USE_SX1262) +; ============================================================================ + +[muzi_base_common] +extends = nrf52_base +board = muzi_base +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -I variants/muzi_base + -I src/helpers/ui + -D muzi_base + -D NRF52_POWER_MANAGEMENT + -D PIN_USER_BTN=PIN_BUTTON1 + -D USER_BTN_PRESSED=LOW + -D PIN_STATUS_LED=35 + -D LORA_TX_POWER=22 + -D P_LORA_BUSY=LORA_BUSY + -D P_LORA_SCLK=LORA_SCLK + -D P_LORA_NSS=LORA_NSS + -D P_LORA_DIO_1=LORA_DIO_1 + -D P_LORA_MISO=LORA_MISO + -D P_LORA_MOSI=LORA_MOSI + -D P_LORA_RESET=LORA_RESET + -D QSPIFLASH=1 +build_src_filter = ${nrf52_base.build_src_filter} + + + + + +<../variants/muzi_base> +debug_tool = jlink +upload_protocol = nrfutil +lib_deps = + ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} + +; --------------------------------------------------------------------------- +; Radio selection (Duo = LR1121, Uno = SX1262). The matching radio pins / TCXO +; / RF-switch defines are picked up in variant.h via USE_LR1121 / USE_SX1262. +; --------------------------------------------------------------------------- +[muzi_base_duo] +extends = muzi_base_common +build_flags = ${muzi_base_common.build_flags} + -D USE_LR1121 + -D RADIO_CLASS=CustomLR1121 + -D WRAPPER_CLASS=CustomLR1121Wrapper + -D RF_SWITCH_TABLE + -D RX_BOOSTED_GAIN=true + -D LR11X0_DIO_AS_RF_SWITCH=true + -D LR11X0_DIO3_TCXO_VOLTAGE=3.0 + +[muzi_base_uno] +extends = muzi_base_common +build_flags = ${muzi_base_common.build_flags} + -D USE_SX1262 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_DIO2_AS_RF_SWITCH=1 + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + +; =========================================================================== +; Duo (LR1121) environments +; =========================================================================== +[env:muzi_base_duo_repeater] +extends = muzi_base_duo +build_flags = ${muzi_base_duo.build_flags} + -I examples/companion_radio/ui-new + -D ADVERT_NAME='"Muzi Base Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${muzi_base_duo.build_src_filter} + +<../examples/simple_repeater> +lib_deps = ${muzi_base_duo.lib_deps} + +[env:muzi_base_duo_room_server] +extends = muzi_base_duo +build_flags = ${muzi_base_duo.build_flags} + -I examples/companion_radio/ui-new + -D ADVERT_NAME='"Muzi Base Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${muzi_base_duo.build_src_filter} + +<../examples/simple_room_server> +lib_deps = ${muzi_base_duo.lib_deps} + +[env:muzi_base_duo_companion_radio_usb] +extends = muzi_base_duo +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${muzi_base_duo.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=500 ;can increase number of contacts since we have a ton of extra flash. + -D MAX_GROUP_CHANNELS=40 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=NullDisplayDriver +build_src_filter = ${muzi_base_duo.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${muzi_base_duo.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:muzi_base_duo_companion_radio_ble] +extends = muzi_base_duo +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${muzi_base_duo.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=500 ;can increase number of contacts since we have a ton of extra flash. + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_TX_POWER=0 + -D QSPIFLASH=1 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=NullDisplayDriver + ; -D ADVERT_NAME='"@@MAC"' +build_src_filter = ${muzi_base_duo.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${muzi_base_duo.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[muzi_base_duo_superIO] +extends = muzi_base_duo +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${muzi_base_duo.build_flags} + -D muzi_base_superIO + -D UI_HAS_JOYSTICK=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SH1107Display + -D DISPLAY_ROTATION=2 + -D ENV_INCLUDE_GPS=1 + -D ENV_SKIP_GPS_DETECT + -D PIN_BUZZER=22 +build_src_filter = ${muzi_base_duo.build_src_filter} + + + + + + + + + + +lib_deps = ${muzi_base_duo.lib_deps} + densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 + adafruit/Adafruit SH110X@^2.1.14 + artronshop/ArtronShop_RX8130CE@1.0.0 + adafruit/Adafruit GFX Library @ ^1.12.1 +debug_tool = jlink +upload_protocol = nrfutil + +[env:muzi_base_duo_companion_radio_ble_superIO] +extends = muzi_base_duo_superIO +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${muzi_base_duo_superIO.build_flags} + -D MAX_CONTACTS=500 ; can increase number of contacts since we have a ton of extra flash. + -D MAX_GROUP_CHANNELS=40 + -I examples/companion_radio/ui-new + -D BLE_PIN_CODE=123456 + -D BLE_TX_POWER=0 + -D QSPIFLASH=1 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${muzi_base_duo_superIO.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${muzi_base_duo_superIO.lib_deps} + +; =========================================================================== +; Uno (SX1262) environments +; =========================================================================== +[env:muzi_base_uno_repeater] +extends = muzi_base_uno +build_flags = ${muzi_base_uno.build_flags} + -I examples/companion_radio/ui-new + -D ADVERT_NAME='"Muzi Base Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${muzi_base_uno.build_src_filter} + +<../examples/simple_repeater> +lib_deps = ${muzi_base_uno.lib_deps} + +[env:muzi_base_uno_room_server] +extends = muzi_base_uno +build_flags = ${muzi_base_uno.build_flags} + -I examples/companion_radio/ui-new + -D ADVERT_NAME='"Muzi Base Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${muzi_base_uno.build_src_filter} + +<../examples/simple_room_server> +lib_deps = ${muzi_base_uno.lib_deps} + +[env:muzi_base_uno_companion_radio_usb] +extends = muzi_base_uno +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${muzi_base_uno.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=500 ;can increase number of contacts since we have a ton of extra flash. + -D MAX_GROUP_CHANNELS=40 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=NullDisplayDriver +build_src_filter = ${muzi_base_uno.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${muzi_base_uno.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:muzi_base_uno_companion_radio_ble] +extends = muzi_base_uno +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${muzi_base_uno.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=500 ;can increase number of contacts since we have a ton of extra flash. + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_TX_POWER=0 + -D QSPIFLASH=1 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=NullDisplayDriver + ; -D ADVERT_NAME='"@@MAC"' +build_src_filter = ${muzi_base_uno.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${muzi_base_uno.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[muzi_base_uno_superIO] +extends = muzi_base_uno +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${muzi_base_uno.build_flags} + -D muzi_base_superIO + -D UI_HAS_JOYSTICK=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SH1107Display + -D DISPLAY_ROTATION=2 + -D ENV_INCLUDE_GPS=1 + -D ENV_SKIP_GPS_DETECT + -D PIN_BUZZER=22 +build_src_filter = ${muzi_base_uno.build_src_filter} + + + + + + + + + + +lib_deps = ${muzi_base_uno.lib_deps} + densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 + adafruit/Adafruit SH110X@^2.1.14 + artronshop/ArtronShop_RX8130CE@1.0.0 + adafruit/Adafruit GFX Library @ ^1.12.1 +debug_tool = jlink +upload_protocol = nrfutil + +[env:muzi_base_uno_companion_radio_ble_superIO] +extends = muzi_base_uno_superIO +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${muzi_base_uno_superIO.build_flags} + -D MAX_CONTACTS=500 ; can increase number of contacts since we have a ton of extra flash. + -D MAX_GROUP_CHANNELS=40 + -I examples/companion_radio/ui-new + -D BLE_PIN_CODE=123456 + -D BLE_TX_POWER=0 + -D QSPIFLASH=1 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${muzi_base_uno_superIO.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${muzi_base_uno_superIO.lib_deps} diff --git a/variants/muzi_base/target.cpp b/variants/muzi_base/target.cpp new file mode 100644 index 0000000000..cb476e583a --- /dev/null +++ b/variants/muzi_base/target.cpp @@ -0,0 +1,131 @@ +#include +#include "target.h" +#include "variant.h" + +muzi_baseBoard board; + +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); +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + MuziBaseSensorManager sensors = MuziBaseSensorManager(nmea); + + bool MuziBaseSensorManager::begin() { + pinMode(PIN_GPS_SWITCH, INPUT); + bool ok = EnvironmentSensorManager::begin(); + _last_gps_sw = digitalRead(PIN_GPS_SWITCH); // initial state from the switch + if (_last_gps_sw == HIGH) start_gps(); else stop_gps(); + return ok; + } + + void MuziBaseSensorManager::loop() { + if (millis() > _next_sw_check) { // check the switch ~once a sec + _next_sw_check = millis() + 1000; + int sw = digitalRead(PIN_GPS_SWITCH); + if (sw != _last_gps_sw) { + _last_gps_sw = sw; + if (sw == HIGH) start_gps(); else stop_gps(); + } + } + EnvironmentSensorManager::loop(); + } +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, false, false); + MomentaryButton joystick_left(JOYSTICK_LEFT, 1000, true, false, false); + MomentaryButton joystick_right(JOYSTICK_RIGHT, 1000, true, false, false); + MomentaryButton back_btn(PIN_BACK_BTN, 1000, true, false, true); +#endif + +#if defined(USE_LR1121) + #ifndef LORA_CR + #define LORA_CR 5 + #endif + + #ifdef RF_SWITCH_TABLE + static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = { + RADIOLIB_LR11X0_DIO5, + RADIOLIB_LR11X0_DIO6, + RADIOLIB_NC + }; + + static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + { 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, + }; + #endif +#endif // USE_LR1121 + +bool radio_init() { + //rtc_clock.begin(Wire); + +#if defined(USE_LR1121) + #ifdef LR11X0_DIO3_TCXO_VOLTAGE + float tcxo = LR11X0_DIO3_TCXO_VOLTAGE; + #else + float tcxo = 1.6f; + #endif + + SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); + SPI.begin(); + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + radio.setCRC(2); + radio.explicitHeader(); + + #ifdef RF_SWITCH_TABLE + radio.setRfSwitchTable(rfswitch_dios, rfswitch_table); + #endif + #ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); + #endif + + return true; // success +#else // USE_SX1262 + // CustomSX1262::std_init() configures the SPI pins, runs begin() with the + // TCXO-voltage fallback, sets CRC, current limit, the DIO2 RF switch, and + // RX boosted gain from the SX126X_* build flags. + return radio.std_init(&SPI); +#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(int8_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/muzi_base/target.h b/variants/muzi_base/target.h new file mode 100644 index 0000000000..eda9dc12f1 --- /dev/null +++ b/variants/muzi_base/target.h @@ -0,0 +1,58 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include "muzi_baseBoard.h" +#if defined(USE_LR1121) + #include +#elif defined(USE_SX1262) + #include +#else + #error "muzi_base: no radio selected (define USE_LR1121 or USE_SX1262)" +#endif +#include +#include +#include +#include +#include // Added: Include for EnvironmentSensorManager +#include + + +#ifdef muzi_base_superIO + #include + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; + extern MomentaryButton joystick_left; + extern MomentaryButton joystick_right; + extern MomentaryButton back_btn; +#elif defined(DISPLAY_CLASS) + #include "helpers/ui/NullDisplayDriver.h" + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +extern muzi_baseBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; + +#if ENV_INCLUDE_GPS +// mode switch drives gps on/off, polled in loop() (see thinknode_m1) +class MuziBaseSensorManager : public EnvironmentSensorManager { + int _last_gps_sw = -1; + unsigned long _next_sw_check = 0; +public: + MuziBaseSensorManager(LocationProvider& location) : EnvironmentSensorManager(location) {} + bool begin() override; + void loop() override; +}; +extern MuziBaseSensorManager sensors; +#else +extern EnvironmentSensorManager sensors; +#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(int8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/muzi_base/variant.cpp b/variants/muzi_base/variant.cpp new file mode 100644 index 0000000000..b179af8ac7 --- /dev/null +++ b/variants/muzi_base/variant.cpp @@ -0,0 +1,90 @@ +/* + * variant.cpp + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[PINS_COUNT + 1] = +{ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 24, + 25, + 26, + 27, + 28, + 29, + 30, + 31, +// P1 pins. + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 46, + 47, +}; + +void initVariant() +{ + // All pins output HIGH by default. + // https://github.com/Seeed-Studio/Adafruit_nRF52_Arduino/blob/fab7d30a997a1dfeef9d1d59bfb549adda73815a/cores/nRF5/wiring.c#L65-L69 + + pinMode(PIN_VBAT_READ, INPUT); + pinMode(PIN_BATTERY_CHARGING, INPUT); + pinMode(PIN_CHARGER_FAULT, INPUT); + pinMode(PIN_BUTTON1, INPUT); + pinMode(PIN_BUTTON2, INPUT); + pinMode(PIN_BUTTON3, INPUT); + pinMode(PIN_BUTTON4, INPUT); + pinMode(PIN_BUTTON5, INPUT); + pinMode(PIN_BUTTON6, INPUT); + pinMode(LED_PIN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_PIN, LOW); + digitalWrite(LED_BLUE, LOW); + pinMode(BUZZER_PIN, OUTPUT); + digitalWrite(BUZZER_PIN, LOW); // turn off buzzer at start. don't leave it high. + // gps power is driven by the sensor manager (mode switch). off to start. + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, LOW); + + pinMode(SCREEN_12V_ENABLE, OUTPUT); + digitalWrite(SCREEN_12V_ENABLE, LOW); // disable 12V power for SH1107 display for now. +} diff --git a/variants/muzi_base/variant.h b/variants/muzi_base/variant.h new file mode 100644 index 0000000000..0f4ea5e5d5 --- /dev/null +++ b/variants/muzi_base/variant.h @@ -0,0 +1,164 @@ +/* + * variant.h + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#pragma once + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) +// #define USE_LFRC // 32.768 kHz RC oscillator + +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define PIN_VBAT_READ (31) // P0.31 +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.537 +#define ADC_RESOLUTION 14 +#define PIN_BATTERY_CHARGING (32+2) // P1.02 STAT2 +#define PIN_CHARGER_FAULT (27) // P0.27 STAT1 this pin is disabled on meshtastic. +// BQ25185 has 2 status pins: STAT1 and STAT2. Both are high when not charging. STAT1 high, STAT2 low: charging. Recoverable fault: STAT1 low, STAT2 high. Unrecoverable fault: both low. +// We only need to detect charging vs not charging, but someone else can use the fault pin to log when the battery gets too hot or cold. + +// Power management boot protection threshold (millivolts) +#define PWRMGT_VOLTAGE_BOOTLOCK 3100 // Won't boot below this voltage (mV). BB15 battery min voltage is 3v, 3100mV is minimum batt in meshtastic code. + +// LPCOMP wake configuration (voltage recovery from SYSTEMOFF) +#define PWRMGT_LPCOMP_AIN 7 // AIN7 = P0.31 = PIN_VBAT_READ +#define PWRMGT_LPCOMP_REFSEL 4 // 5/8 VDD (~3.13-3.44V) was the default on RAK4631. should still apply here. + +// Other pins +#define PIN_AREF (-1) +#define SCREEN_12V_ENABLE (23) // SH1107 OLED controller has a pin that needs to be enabled to turn on the screen. + +static const uint8_t AREF = (PIN_AREF); // not used + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX (19) // P0.19 used for GPS RX +#define PIN_SERIAL1_TX (20) // P0.20 used for GPS TX + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define HAS_WIRE (1) +#define WIRE_INTERFACES_COUNT (2) + +#define PIN_WIRE1_SDA (4) // P0.4 +#define PIN_WIRE1_SCL (6) // P0.6 +#define PIN_WIRE_SDA (24) // P0.24 OLED I2C +#define PIN_WIRE_SCL (25) // P0.25 OLED I2C +#define I2C_NO_RESCAN +// #define I2C_NO_RESCAN +// #define HAS_QMA6100P +// #define QMA_6100P_INT_PIN (34) // P1.2 + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (32+15) // internally connected to p1.15 +#define PIN_SPI_MOSI (32+14) // internally connected to p1.14 +#define PIN_SPI_SCK (32+13) // internally connected to p1.13 +#define PIN_SPI_NSS (32+12) // internally connected to p1.12 + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define LED_BUILTIN (35) +#define LED_BLUE (-1) // P1.04 turned off, because the blue LED was annoying. +// #define LED_GREEN (35) // P1.03 +#define LED_PIN LED_BUILTIN + +#define LED_STATE_ON LOW + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons +#define PIN_BUTTON1 (10) // P0.10 Menu / User Button | on superIO, this is in the center of the "D-Pad", but it's also the button on the Uno/Duo. +#define PIN_BUTTON2 (21) // Joystick Up +#define PIN_BUTTON3 (17) // Joystick Down +#define PIN_BUTTON4 (37) // Joystick Left +#define PIN_BUTTON5 (16) // Joystick Right +#define PIN_BUTTON6 (15) // Back / Cancel Button. +#define JOYSTICK_PRESS PIN_BUTTON1 +#define JOYSTICK_UP PIN_BUTTON2 +#define JOYSTICK_DOWN PIN_BUTTON3 +#define JOYSTICK_LEFT PIN_BUTTON4 +#define JOYSTICK_RIGHT PIN_BUTTON5 +#define PIN_BACK_BTN PIN_BUTTON6 + +//////////////////////////////////////////////////////////////////////////////// +// LoRa radio +// +// The Muzi Base PCB is populated with one of two radios: +// * Base Uno -> SX1262 (USE_SX1262) +// * Base Duo -> LR1121 (USE_LR1121) +// Both share NSS/SCLK/MISO/MOSI/RESET/BUSY. The only pin difference is the +// radio IRQ (DIO1): the SX1262 routes it to P1.06, the LR1121 to P1.08. +// (see Meshtastic muzi_base variant, which defines both radios on this board) + +#define LORA_NSS (PIN_SPI_NSS) // P1.12 +#define LORA_RESET (32+10) // P1.10 +#define LORA_BUSY (32+11) // P1.11 +#define LORA_SCLK (PIN_SPI_SCK) // P1.13 +#define LORA_MISO (PIN_SPI_MISO) // P1.15 +#define LORA_MOSI (PIN_SPI_MOSI) // P1.14 + +// only the IRQ pin differs per radio. rf-switch/tcxo defines are in platformio.ini +#if defined(USE_LR1121) + #define LORA_DIO_1 (32+8) // P1.08 LR1121 IRQ/DIO1 +#elif defined(USE_SX1262) + #define LORA_DIO_1 (32+6) // P1.06 SX1262 IRQ/DIO1 +#else + #error "muzi_base: no radio selected (define USE_LR1121 or USE_SX1262)" +#endif + +//////////////////////////////////////////////////////////////////////////////// +// QSPI Flash +#define PIN_QSPI_SCK (0 + 3) +#define PIN_QSPI_CS (0 + 26) +#define PIN_QSPI_IO0 (0 + 30) +#define PIN_QSPI_IO1 (0 + 29) +#define PIN_QSPI_IO2 (0 + 28) +#define PIN_QSPI_IO3 (0 + 2) + +#define EXTERNAL_FLASH_DEVICES W25Q128JVPQ +#define EXTERNAL_FLASH_USE_QSPI + +//////////////////////////////////////////////////////////////////////////////// +// GPS +#define HAS_GPS 1 +#define PIN_GPS_RX PIN_SERIAL1_RX +#define PIN_GPS_TX PIN_SERIAL1_TX +#define PIN_GPS_EN (32+1) // P1.01 PWR_IO2 on schematic. gps power, driven by the sensor manager. + +// superIO 3-position mode switch (pins from the meshtastic muzi_base variant). +// "Mode 2" reads HIGH on PIN_GPS_SWITCH and turns the gps on. +#define SWITCH_MODE1 (32+9) // P1.09 +#define SWITCH_MODE2 (12) // P0.12 +#define PIN_GPS_SWITCH SWITCH_MODE2 + +//////////////////////////////////////////////////////////////////////////////// +// Buzzer + +#define BUZZER_PIN (22) // P0.22 same load switch design as GPS_EN.