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.

Five quick user-button presses power the unit off; a press wakes it back
on (arms the button as an nRF52 GPIO SENSE source before SYSTEMOFF).

Consolidates #2054 and andyshinn's shared-base/uno work, rebased on dev.

Co-authored-by: lbibass <ewdries02@gmail.com>
Co-authored-by: Andy Shinn <andys@andyshinn.as>
This commit is contained in:
TJ Downes
2026-09-12 05:41:26 -07:00
committed by Audit Bot
co-authored by lbibass Andy Shinn
parent b599bc5117
commit 21cdd7b319
12 changed files with 1230 additions and 0 deletions
+72
View File
@@ -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"
}
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include <RadioLib.h>
#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; }
};
@@ -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(), getRxBoostedGainMode()); }
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();
}
};
+113
View File
@@ -0,0 +1,113 @@
#include "SH1107Display.h"
#include <Adafruit_GrayOLED.h>
#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();
}
+43
View File
@@ -0,0 +1,43 @@
#pragma once
#include "DisplayDriver.h"
#include <Wire.h>
#include <Adafruit_GFX.h>
#define SH110X_NO_SPLASH
#include <Adafruit_SH110X.h>
#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;
};
+52
View File
@@ -0,0 +1,52 @@
#include <Arduino.h>
#include <Wire.h>
#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
}
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include <MeshCore.h>
#include <Arduino.h>
#include <helpers/NRF52Board.h>
// 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;
}
};
+326
View File
@@ -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}
+<helpers/*.cpp>
+<helpers/sensors>
+<../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}
+<helpers/ui/NullDisplayDriver.cpp>
+<../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}
+<helpers/nrf52/SerialBLEInterface.cpp>
+<helpers/sensors>
+<helpers/ui/NullDisplayDriver.cpp>
+<../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}
+<helpers/nrf52/SerialBLEInterface.cpp>
+<helpers/sensors>
+<helpers/ui/SH1107Display.cpp>
+<helpers/ui/buzzer.cpp>
+<helpers/ui/MomentaryButton.cpp>
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}
+<helpers/nrf52/SerialBLEInterface.cpp>
+<../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}
+<helpers/ui/NullDisplayDriver.cpp>
+<../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}
+<helpers/nrf52/SerialBLEInterface.cpp>
+<helpers/sensors>
+<helpers/ui/NullDisplayDriver.cpp>
+<../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}
+<helpers/nrf52/SerialBLEInterface.cpp>
+<helpers/sensors>
+<helpers/ui/SH1107Display.cpp>
+<helpers/ui/buzzer.cpp>
+<helpers/ui/MomentaryButton.cpp>
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}
+<helpers/nrf52/SerialBLEInterface.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${muzi_base_uno_superIO.lib_deps}
+162
View File
@@ -0,0 +1,162 @@
#include <Arduino.h>
#include <nrf_gpio.h>
#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 <helpers/sensors/MicroNMEALocationProvider.h>
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
MuziBaseSensorManager sensors = MuziBaseSensorManager(nmea);
#else
MuziBaseSensorManager sensors;
#endif
// user button for the N-click power-off. single-click mode (multiclick off) so
// each press is its own CLICK; we count them ourselves. reliable across the
// idle-sleep loop because it's the same MomentaryButton the UI navigates with.
static MomentaryButton pwr_btn(PIN_USER_BTN, 0, true, true, false);
// power off, arming the user button as a wake source first so a later press
// turns it back on. wait for the button to release (the 5th click already fires
// on release, so this returns quickly), then set nRF52 GPIO SENSE (press = LOW);
// it survives into SYSTEMOFF because shutdownPeripherals() doesn't touch this pin.
static void muziPowerOff() {
uint32_t t0 = millis();
while (digitalRead(PIN_USER_BTN) == USER_BTN_PRESSED && (millis() - t0) < 3000) { delay(5); }
delay(50); // settle
nrf_gpio_cfg_sense_input(g_ADigitalPinMap[PIN_USER_BTN], NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
board.powerOff(); // shutdownPeripherals() + SYSTEMOFF
}
bool MuziBaseSensorManager::begin() {
pwr_btn.begin();
bool ok = EnvironmentSensorManager::begin();
#if ENV_INCLUDE_GPS
pinMode(PIN_GPS_SWITCH, INPUT);
_last_gps_sw = digitalRead(PIN_GPS_SWITCH); // initial gps state from the switch
if (_last_gps_sw == HIGH) start_gps(); else stop_gps();
#endif
return ok;
}
void MuziBaseSensorManager::loop() {
// user button: USER_BTN_POWER_OFF_CLICKS quick clicks -> power off
if (pwr_btn.check() == BUTTON_EVENT_CLICK) {
unsigned long t = millis();
if (t - _pwr_last_click > 2000) _pwr_clicks = 0; // restart if too slow
_pwr_last_click = t;
if (++_pwr_clicks >= USER_BTN_POWER_OFF_CLICKS) muziPowerOff();
}
#if ENV_INCLUDE_GPS
unsigned long now = millis();
if (now > _next_sw_check) { // check the mode switch ~once a sec
_next_sw_check = now + 1000;
int sw = digitalRead(PIN_GPS_SWITCH);
if (sw != _last_gps_sw) {
_last_gps_sw = sw;
if (sw == HIGH) start_gps(); else stop_gps();
}
}
#endif
EnvironmentSensorManager::loop();
}
#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
}
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/radiolib/RadioLibWrappers.h>
#include "muzi_baseBoard.h"
#if defined(USE_LR1121)
#include <helpers/radiolib/CustomLR1121Wrapper.h>
#elif defined(USE_SX1262)
#include <helpers/radiolib/CustomSX1262Wrapper.h>
#else
#error "muzi_base: no radio selected (define USE_LR1121 or USE_SX1262)"
#endif
#include <helpers/ArduinoHelpers.h>
#include <helpers/SensorManager.h>
#include <helpers/sensors/LocationProvider.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/sensors/EnvironmentSensorManager.h> // Added: Include for EnvironmentSensorManager
#include <helpers/ui/MomentaryButton.h>
#ifdef muzi_base_superIO
#include <helpers/ui/SH1107Display.h>
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;
// muzi sensor manager, polled from main loop via sensors.loop():
// - user button: USER_BTN_POWER_OFF_CLICKS quick presses -> power off (all builds)
// - gps mode switch on/off (superIO only, see thinknode_m1)
class MuziBaseSensorManager : public EnvironmentSensorManager {
unsigned long _pwr_last_click = 0;
uint8_t _pwr_clicks = 0;
#if ENV_INCLUDE_GPS
int _last_gps_sw = -1;
unsigned long _next_sw_check = 0;
#endif
public:
#if ENV_INCLUDE_GPS
MuziBaseSensorManager(LocationProvider& location) : EnvironmentSensorManager(location) {}
#else
MuziBaseSensorManager() {}
#endif
bool begin() override;
void loop() override;
};
extern MuziBaseSensorManager 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(int8_t dbm);
mesh::LocalIdentity radio_new_identity();
+90
View File
@@ -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.
}
+169
View File
@@ -0,0 +1,169 @@
/*
* 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
// quick user-button presses that power the device off (per Muzi's docs)
#ifndef USER_BTN_POWER_OFF_CLICKS
#define USER_BTN_POWER_OFF_CLICKS 5
#endif
////////////////////////////////////////////////////////////////////////////////
// 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.