merge fix/llcc68-long-message-failure into develop
CI / build-debian-src (push) Skipped
CI / package-pio-deps-native-tft (push) Skipped
CI / test-native (push) Skipped
CI / setup (push) Canceled after 0s
CI / version (push) Canceled after 0s
Build Windows Binary / build-Windows (push) Canceled after 0s
Build PortDuino WASM / Build PortDuino WASM (push) Canceled after 0s
CI / check (push) Canceled after 0s
CI / build (push) Canceled after 0s
CI / ci-gate (push) Canceled after 0s
CI / gather-artifacts (esp32) (push) Canceled after 0s
CI / gather-artifacts (esp32c3) (push) Canceled after 0s
CI / gather-artifacts (esp32c6) (push) Canceled after 0s
CI / gather-artifacts (esp32s3) (push) Canceled after 0s
CI / gather-artifacts (nrf52840) (push) Canceled after 0s
CI / gather-artifacts (rp2040) (push) Canceled after 0s
CI / gather-artifacts (rp2350) (push) Canceled after 0s
CI / gather-artifacts (stm32) (push) Canceled after 0s
CI / firmware-size-report (push) Canceled after 0s
CI / size-budget-gate (push) Canceled after 0s
CI / release-artifacts (push) Canceled after 0s
CI / release-firmware (esp32) (push) Canceled after 0s
CI / release-firmware (esp32c3) (push) Canceled after 0s
CI / release-firmware (esp32c6) (push) Canceled after 0s
CI / release-firmware (esp32s3) (push) Canceled after 0s
CI / release-firmware (nrf52840) (push) Canceled after 0s
CI / release-firmware (rp2040) (push) Canceled after 0s
CI / release-firmware (rp2350) (push) Canceled after 0s
CI / release-firmware (stm32) (push) Canceled after 0s
CI / publish-firmware (push) Canceled after 0s
CI / publish-nightly (push) Canceled after 0s
CI / MacOS (15) (push) Canceled after 0s
CI / MacOS (26) (push) Canceled after 0s
Build MacOS Binary / build-MacOS (push) Canceled after 0s
CI / Windows (2025) (push) Canceled after 0s
CI / build-wasm (push) Canceled after 0s
CI / docker (alpine, native-tft, linux/arm64) (push) Canceled after 0s
CI / docker (debian, native-tft, linux/arm64) (push) Canceled after 0s
Build Docker / docker-build (push) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-manifest (release) Canceled after 0s
Package for OpenSUSE Build Service / package-obs (release) Canceled after 0s
Trigger COPR build / build-copr-hook (release) Canceled after 0s
Trigger release workflows upon Publish / publish-release-notes (release) Canceled after 0s
Trigger release workflows upon Publish / bump-version (release) Canceled after 0s
Trigger release workflows upon Publish / build-docker (release) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-debian-amd64 (release) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-debian-arm64 (release) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-debian-armv7 (release) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-debian-riscv64 (release) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-alpine-amd64 (release) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-alpine-arm64 (release) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-alpine-armv7 (release) Canceled after 0s
Build Docker Multi-Arch Manifest / docker-alpine-riscv64 (release) Canceled after 0s
Trigger release workflows upon Publish / package-ppa (jammy) (release) Canceled after 0s
Trigger release workflows upon Publish / package-ppa (noble) (release) Canceled after 0s
Trigger release workflows upon Publish / package-ppa (resolute) (release) Canceled after 0s
Trigger release workflows upon Publish / package-ppa (stonking) (release) Canceled after 0s
Package for Launchpad PPA / build-debian-src (release) Canceled after 0s
Package for Launchpad PPA / package-ppa (release) Canceled after 0s
Trigger release workflows upon Publish / package-obs (release) Canceled after 0s
Package for OpenSUSE Build Service / build-debian-src (release) Canceled after 0s
Trigger release workflows upon Publish / hook-copr (release) Canceled after 0s
Build Docker / docker-build (release) Canceled after 0s
Build Debian Source Package / build-debian-src (release) Canceled after 0s

This commit is contained in:
2026-08-24 11:08:50 +08:00
8 files changed
+538 -30

No files matched your search

+20 -3
View File
@@ -3,6 +3,7 @@
#include "configuration.h"
#include "error.h"
#include "mesh/NodeDB.h"
#include "meshUtils.h" // for pow_of_2
#ifdef ARCH_PORTDUINO
#include "PortduinoGlue.h"
#endif
@@ -197,12 +198,28 @@ template <typename T> bool SX126xInterface<T>::reconfigure()
// configure publicly accessible settings
int err = lora.setSpreadingFactor(sf);
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
if (err != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX126X setSpreadingFactor(%d) at BW %.1f kHz rejected (%s%d), attempting fallback", sf, bw, radioLibErr, err);
for (uint8_t fallbackSf = sf - 1; fallbackSf >= 5; fallbackSf--) {
err = lora.setSpreadingFactor(fallbackSf);
if (err == RADIOLIB_ERR_NONE) {
LOG_WARN("SX126X fell back to SF%d at BW %.1f kHz (requested SF%d), change modem preset to match", fallbackSf, bw,
sf);
this->sf = fallbackSf;
preambleTimeMsec = preambleLength * (pow_of_2(sf) / bw);
slotTimeMsec = computeSlotTimeMsec();
break;
}
}
if (err != RADIOLIB_ERR_NONE)
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
}
err = lora.setBandwidth(bw);
if (err != RADIOLIB_ERR_NONE)
if (err != RADIOLIB_ERR_NONE) {
LOG_ERROR("SX126X setBandwidth(%.1f) rejected (%s%d)", bw, radioLibErr, err);
RECORD_CRITICALERROR(meshtastic_CriticalErrorCode_INVALID_RADIO_SETTING);
}
err = lora.setCodingRate(cr);
if (err != RADIOLIB_ERR_NONE)
@@ -8,4 +8,5 @@ build_flags =
-D PRIVATE_HW
-D ARDUINO_USB_MODE=1
-D ARDUINO_USB_CDC_ON_BOOT=1
-D USERPREFS_LORACONFIG_MODEM_PRESET=meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW
-I variants/esp32c3/diy/moonshine_ESP_C3
+42 -16
View File
@@ -24,9 +24,9 @@ commenting/uncommenting the `#define` lines at the top of `variant.h`:
Only one module should be enabled at a time.
| Module | Chip | Power | `USE_*` | `TX_GAIN_LORA` | `SX126X_MAX_POWER` | Total output |
| ------------ | ------ | ------------ | ------------ | -------------- | ------------------ | --------------- |
| E220-400M30S | LLCC68 | 30 dBm (1 W) | `USE_LLCC68` | 8 | 22 | 22 + 8 = 30 dBm |
| Module | Chip | Power | `USE_*` | `TX_GAIN_LORA` | `SX126X_MAX_POWER` | Total output |
| ------------ | ------ | ------------ | ------------ | -------------- | ------------------ | ---------------- |
| E220-400M30S | LLCC68 | 30 dBm (1 W) | `USE_LLCC68` | 8 | 22 | 22 + 8 = 30 dBm |
| E220-400M33S | LLCC68 | 33 dBm (2 W) | `USE_LLCC68` | 11 | 22 | 22 + 11 = 33 dBm |
| E22-400M33S | SX1268 | 33 dBm (2 W) | `USE_SX1268` | 25 | 8 | 8 + 25 = 33 dBm |
@@ -42,21 +42,47 @@ See `variants/nrf52840/diy/moonshine_NRF52/variant.h` for the same module
wired with a more conservative power profile (`SX126X_MAX_POWER = 2`,
~27 dBm output).
### LLCC68 modem preset limitation
The E220-400M30S and E220-400M33S use the LLCC68 chip, which does **not**
support all spreading-factor/bandwidth combinations:
| Bandwidth | Max SF |
| --------- | ------ |
| 125 kHz | SF9 |
| 250 kHz | SF10 |
| 500 kHz | SF11 |
SF12 is never supported on the LLCC68.
The Meshtastic default preset **LongFast** (BW 250 kHz / SF11) is invalid
for the LLCC68. This variant therefore ships with **MediumSlow**
(BW 250 kHz / SF10) as the default preset via
`USERPREFS_LORACONFIG_MODEM_PRESET`.
If you switch to the E22-400M33S (SX1268), you can safely change the preset
back to LongFast or any other - the SX1268 supports the full SF/BW range.
**Note**: All nodes in a mesh must use the same modem preset to communicate.
If other nodes in your network are on LongFast, either switch them to
MediumSlow as well, or use LongTurbo (BW 500 kHz / SF11) which the LLCC68
also supports.
## Pin map
| Function | GPIO | Notes |
| ------------- | ---- | -------------------------------------- |
| BUTTON | 9 | BOOT button |
| LED | 0 | Active high (`LED_STATE_ON = 1`) |
| BATTERY_ADC | 1 | 0.5 divider (`ADC_MULTIPLIER = 2.0`) |
| LORA_RXEN | 2 | RF switch RX enable |
| LORA_DIO1 | 3 | IRQ |
| LORA_BUSY | 4 | |
| LORA_RESET | 5 | |
| LORA_MISO | 6 | |
| LORA_MOSI | 7 | |
| LORA_CS | 8 | |
| LORA_SCK | 10 | |
| Function | GPIO | Notes |
| ----------- | ---- | ------------------------------------ |
| BUTTON | 9 | BOOT button |
| LED | 0 | Active high (`LED_STATE_ON = 1`) |
| BATTERY_ADC | 1 | 0.5 divider (`ADC_MULTIPLIER = 2.0`) |
| LORA_RXEN | 2 | RF switch RX enable |
| LORA_DIO1 | 3 | IRQ |
| LORA_BUSY | 4 | |
| LORA_RESET | 5 | |
| LORA_MISO | 6 | |
| LORA_MOSI | 7 | |
| LORA_CS | 8 | |
| LORA_SCK | 10 | |
DIO2 drives the module's TXEN pin internally (`SX126X_DIO2_AS_RF_SWITCH`), so
`SX126X_TXEN` is left as `RADIOLIB_NC`. `SX126X_RXEN` is driven by GPIO2.
@@ -67,11 +67,11 @@ MOONSHINE NRF52 EBYTE PIN ASSIGNMENT (Ebyte nRF52840 + E22-400M33S)
// LED - Bluetooth pairing status indicator
#define LED_PAIRING (0 + 15) // P0.15
#define LED_STATE_ON 1 // State when LED is lit (active high)
#define LED_STATE_ON 1 // State when LED is lit (active high)
// Charge detection (P0.13, active high = charging)
// Charge detection (P0.13, active high = charging, needs internal pull-down)
#define EXT_CHRG_DETECT (0 + 13)
#define EXT_CHRG_DETECT_VALUE HIGH
#define EXT_CHRG_DETECT_MODE INPUT_PULLDOWN
// Buzzer
#define PIN_BUZZER (32 + 11) // P1.11
@@ -95,8 +95,8 @@ MOONSHINE NRF52 EBYTE PIN ASSIGNMENT (Ebyte nRF52840 + E22-400M33S)
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_MISO (32 + 0) // P1.00
#define PIN_SPI_MOSI (0 + 22) // P0.22
#define PIN_SPI_SCK (0 + 20) // P0.20
#define PIN_SPI_MOSI (0 + 22) // P0.22
#define PIN_SPI_SCK (0 + 20) // P0.20
#define LORA_MISO PIN_SPI_MISO
#define LORA_MOSI PIN_SPI_MOSI
@@ -113,14 +113,14 @@ MOONSHINE NRF52 EBYTE PIN ASSIGNMENT (Ebyte nRF52840 + E22-400M33S)
#define TX_GAIN_LORA 12
// SX126X CONFIG
#define SX126X_CS (0 + 17) // P0.17 FIXME - we really should define LORA_CS instead
#define SX126X_DIO1 (32 + 10) // P1.10 IRQ
#define SX126X_CS (0 + 17) // P0.17 FIXME - we really should define LORA_CS instead
#define SX126X_DIO1 (32 + 10) // P1.10 IRQ
#define SX126X_DIO2_AS_RF_SWITCH // Note for E22 modules: DIO2 is not attached internally to TXEN for automatic TX/RX switching,
// so it needs connecting externally if it is used in this way
#define SX126X_BUSY (0 + 28) // P0.28 (AIN4)
#define SX126X_RESET (32 + 13) // P1.13
#define SX126X_RXEN (32 + 9) // P1.09 - E22 RXEN, MCU controls RX/TX RF switch path
#define SX126X_TXEN RADIOLIB_NC // DIO2 controls TXEN directly
#define SX126X_BUSY (0 + 28) // P0.28 (AIN4)
#define SX126X_RESET (32 + 13) // P1.13
#define SX126X_RXEN (32 + 9) // P1.09 - E22 RXEN, MCU controls RX/TX RF switch path
#define SX126X_TXEN RADIOLIB_NC // DIO2 controls TXEN directly
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL
@@ -0,0 +1,22 @@
; Moonshine NRF travelers core - DIY variant (Ebyte nRF52840 + E22-400M33S)
; Uses S140 v6 SoftDevice (matches nice!nano / Adafruit Feather factory bootloader).
; Inherits the default v6 ldscript from [nrf52840_base] (app start at 0x26000).
[env:moonshine_NRF_travelers_core]
extends = nrf52840_base
board = adafruit_feather_nrf52840
build_flags = ${nrf52840_base.build_flags}
-I variants/nrf52840/diy/moonshine_NRF_travelers_core
-D PRIVATE_HW
-D CONFIG_GPIO_AS_PINRESET
-D MESHTASTIC_EXCLUDE_I2C=1
-D MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR=1
-D MESHTASTIC_EXCLUDE_INPUTBROKER=1
-D MESHTASTIC_EXCLUDE_ACCELEROMETER=1
-D MESHTASTIC_EXCLUDE_MAGNETOMETER=1
-D MESHTASTIC_EXCLUDE_GPS=1
-D MESHTASTIC_EXCLUDE_SCREEN=1
build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/moonshine_NRF_travelers_core>
debug_tool = jlink
; Upload via USB: pio run -e moonshine_NRF_travelers_core -t upload
; Uses 1200bps touch to enter DFU bootloader, then nrfutil serial DFU.
; Alternative: double-tap reset -> drag .uf2 to USB mass storage
@@ -0,0 +1,147 @@
# Moonshine NRF travelers core
DIY Meshtastic node variant based on an Ebyte nRF52840 MCU module and E22-400M33S LoRa module.
## Hardware
- **MCU module**: Ebyte nRF52840 (512 KB flash / 128 KB RAM)
- **LF clock**: 32.768 kHz crystal (`USE_LFXO`)
- **LoRa**: E22-400M33S (SX1268 + 33dBm PA, 2W, 433MHz) - `SX126X_MAX_POWER=21`,
`TX_GAIN_LORA=12`. `SX126X_RXEN` (P0.02) is MCU-controlled by RadioLib; TXEN is
driven by DIO2 (`SX126X_DIO2_AS_RF_SWITCH`). TCXO voltage 2.2V with `TCXO_OPTIONAL`
fallback to XTAL.
- **LED**: Bluetooth pairing status on P0.15 (`LED_PAIRING`). Solid on = BLE
connected, slow blink = unpaired, fast blink = pairing, off after 30s idle.
- **Battery ADC**: 0.5 voltage divider (equal resistors)
- **Power button (P1.09) + self-hold latch (P0.05)**: the button supplies power
while pressed. Hold it 2s to power on - firmware then drives P0.05 HIGH to
latch the board on. Hold it 2s again to power off - the firmware saves the
NodeDB first, then drops P0.05 LOW; the board keeps running on button power
until the button is released. Low-battery cutoff (~3.2V) also drops the latch.
Implemented in a dedicated FreeRTOS task (`powerButtonTask` in variant.cpp) -
the nrf52 platform's weak `variant_shutdown`/`variant_nrf52LoopHook` hooks are
inlined away by LTO and cannot be used (see the `noinline` note in
`src/platform/nrf52/main-nrf52.cpp`).
- **USB CDC serial**: enabled by default (nRF52840 built-in USB-ACM). Debug output
and Meshtastic CLI communication go through the USB serial port.
- **I2C**: SDA/SCL pins wired (P0.07/P0.12) but no I2C devices connected.
## Pin map
| Function | Pin | Notes |
| ----------- | ----- | ---------------------------------- |
| LED | P0.15 | Bluetooth pairing status |
| CHARGE_DET | P0.13 | Active high (charging) |
| BUZZER | P1.11 | PWM buzzer |
| BATTERY_ADC | P0.03 | AIN1, 0.5 divider |
| POWER_BTN | P1.09 | Power button, active low (to GND) |
| POWER_LATCH | P0.05 | HIGH = keep board powered |
| IIC_SDA | P0.07 | Wired, no device |
| IIC_SCL | P0.12 | Wired, no device |
| SPI_MISO | P0.26 | |
| SPI_MOSI | P0.06 | |
| SPI_SCK | P0.08 | |
| LORA_CS | P0.04 | |
| LORA_DIO1 | P1.13 | IRQ |
| LORA_BUSY | P0.28 | AIN4 (AI4) |
| LORA_RESET | P1.10 | |
| LORA_RXEN | P0.02 | MCU-controlled RXEN; TXEN via DIO2 |
## Power button
- **Power on**: press and hold the button ~2s. The `powerButtonTask` FreeRTOS
task detects P1.09 LOW for 2s, then drives P0.05 HIGH to latch the board on.
- **Power off**: hold the button ~2s while running. The task saves the NodeDB
(`nodeDB->saveToDisk()`), then drops P0.05 LOW. The board keeps running on
button power until the button is released; releasing it cuts power. The
normal shutdown sequence (shutdown melody / SystemOff) is intentionally not
used - the task saves first and then just cuts the latch.
- A release is required between power-on and arming power-off, so holding the
button through the power-on latch cannot shut the board down again.
- If the board resets while latched (button released), P1.09 reads HIGH at boot
and `initVariant` re-latches P0.05 immediately - the board stays on through
crashes and soft resets.
- **Low battery cutoff**: the task checks the battery every 10s and, if it reads
below 3.2V for 3 consecutive samples (and no USB power), saves the NodeDB and
drops the latch. The threshold is above the firmware's own 3.1V SDS trigger so
the latch is released before SystemOff could leave it engaged (SystemOff
retains the latch and would drain the battery).
## Build & Flash
### Build
```sh
pio run -e moonshine_NRF_travelers_core
```
Uses `PRIVATE_HW` (HardwareModel 255) - no protobuf or `architecture.h` changes
required.
The build links against `nrf52840_s140_v6.ld`, so the application is linked to
start at `0x26000` and requires the S140 SoftDevice v6.x to be present on the
chip at `0x0`-`0x25FFF`. The build output (`firmware-*.hex` / `.uf2`) contains
**only the application**, not the SoftDevice.
### Flash via USB (nice!nano)
The nice!nano comes pre-flashed with the S140 v6 SoftDevice and the Adafruit
UF2 bootloader. No SWD probe is needed - firmware can be uploaded over USB.
**Method 1: PlatformIO serial DFU (recommended)**
```sh
pio run -e moonshine_NRF_travelers_core -t upload
```
PlatformIO opens the USB CDC serial port at 1200 baud to trigger the DFU
bootloader, then uploads via `nrfutil` serial DFU. The board automatically
resets into the application after upload.
**Method 2: UF2 drag-and-drop**
1. Double-tap the Reset button to enter UF2 bootloader mode. A USB mass storage
volume will appear (e.g. `NRF52BOOT`).
2. Copy the `.uf2` file from `.pio/build/moonshine_NRF_travelers_core/firmware-*.uf2`
to the USB volume.
3. The board automatically resets into the application after the file transfer
completes.
### Flash via SWD (alternative)
If the SoftDevice or bootloader is missing or corrupted, flash via SWD with
OpenOCD (CMSIS-DAP / J-Link / ST-Link):
```sh
# Full flow: recover (erase all + unlock) -> SoftDevice -> app -> reset
openocd -f interface/cmsis-dap.cfg -f target/nrf52.cfg \
-c "init; nrf52_recover; \
program bin/s140_nrf52_7.3.0_softdevice.hex verify; \
program .pio/build/moonshine_NRF_travelers_core/firmware-*.hex verify; \
reset run; exit"
```
When the SoftDevice is already on the chip and only the application changed,
skip `nrf52_recover` and the SoftDevice line:
```sh
openocd -f interface/cmsis-dap.cfg -f target/nrf52.cfg \
-c "init; \
program .pio/build/moonshine_NRF_travelers_core/firmware-*.hex verify; \
reset run; exit"
```
## Notes
- The firmware hex filename contains a commit hash (e.g. `2.8.0.421838f`); use
the actual file produced by `pio run` or a shell glob.
- USB CDC serial is available immediately after boot for debug output and the
Meshtastic Python CLI (`meshtastic --noproto` / `meshtastic --info`).
- During DFU flashing (bootloader reset), P0.05 is not driven, so the latch
relies on button power or the latch circuit's hold capacitance - hold the
power button during USB DFU if the board loses power mid-flash.
- `MESHTASTIC_EXCLUDE_I2C=1` is set because no I2C devices are connected. Remove
this flag from `platformio.ini` if an I2C display or sensor is added later.
- No GPS, screen, accelerometer, magnetometer, or button is configured. Remove
the corresponding `MESHTASTIC_EXCLUDE_*` flags from `platformio.ini` if hardware
is added.
@@ -0,0 +1,156 @@
/*
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
Copyright (c) 2016 Sandeep Mistry All right reserved.
Copyright (c) 2018, Adafruit Industries (adafruit.com)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "variant.h"
#include "PowerStatus.h" // powerStatus (battery state)
#include "configuration.h" // millis, MESHTASTIC_LOG_LEVEL_INFO, console
#include "freertosinc.h" // FreeRTOS (xTaskCreate, vTaskDelay)
#include "mesh/NodeDB.h" // nodeDB
#include "mesh/Throttle.h" // Throttle
#include "nrf.h"
#include "wiring_constants.h"
#include "wiring_digital.h"
const uint32_t g_ADigitalPinMap[] = {
// P0
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
32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47};
// Power button state machine (see variant.h). Runs in its own FreeRTOS task
// because the nrf52 weak loop hooks are inlined away by LTO.
static bool powerLatched = false;
static void logPowerButton(const char *msg)
{
if (console)
console->log(MESHTASTIC_LOG_LEVEL_INFO, msg);
}
static void powerButtonPowerOff()
{
// Save the DB first, then cut the latch. The board keeps running on button
// power until the button is released (no SystemOff in this flow).
nodeDB->saveToDisk();
if (console)
console->flush();
nrf_gpio_pin_write(POWER_LATCH_PIN, 0);
powerLatched = false;
}
static void powerButtonTask(void *arg)
{
bool releaseSeen = false; // P1.09 must go HIGH once before arming power-off
bool powerOffRequested = false;
uint32_t lowSinceMs = 0;
uint32_t lastBattCheck = 0;
uint8_t lowBattCount = 0;
while (true) {
bool pressed = !nrf_gpio_pin_read(POWER_BUTTON_PIN); // LOW = pressed
if (!powerOffRequested) {
if (pressed) {
if (lowSinceMs == 0)
lowSinceMs = millis();
else if ((uint32_t)(millis() - lowSinceMs) >= 2000) {
if (!powerLatched) {
// Power-on: hold the button 2s to latch the board on
nrf_gpio_pin_write(POWER_LATCH_PIN, 1);
powerLatched = true;
releaseSeen = false;
logPowerButton("Power button: latched on");
} else if (releaseSeen) {
// Power-off: hold the button 2s, save the DB, cut power
logPowerButton("Power button: powering off");
powerOffRequested = true;
powerButtonPowerOff();
}
lowSinceMs = 0;
}
} else {
releaseSeen = true;
lowSinceMs = 0;
}
}
// Low-battery cutoff: fires above the firmware's 3100mV SDS threshold
// so the latch is dropped before SystemOff would leave it engaged.
if (!powerOffRequested && !Throttle::isWithinTimespanMs(lastBattCheck, 10000)) {
lastBattCheck = millis();
if (powerStatus && powerStatus->getHasBattery() && !powerStatus->getHasUSB() &&
powerStatus->getBatteryVoltageMv() > 0 && powerStatus->getBatteryVoltageMv() < 3200) {
if (++lowBattCount >= 3) {
logPowerButton("Power button: low battery cutoff");
powerOffRequested = true;
powerButtonPowerOff();
}
} else {
lowBattCount = 0;
}
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void initVariant()
{
// Configure P0.18 as RESET pin (UICR->PSELRESET).
// nrf52_recover (ERASEALL) clears UICR to 0xFFFFFFFF, disabling reset.
// SystemInit() already handles this via CONFIG_GPIO_AS_PINRESET; this is a
// safety net that runs before SoftDevice is enabled, so NVMC is accessible.
if ((NRF_UICR->PSELRESET[0] != 18) || (NRF_UICR->PSELRESET[1] != 18)) {
NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen << NVMC_CONFIG_WEN_Pos;
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {
}
NRF_UICR->PSELRESET[0] = 18;
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {
}
NRF_UICR->PSELRESET[1] = 18;
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {
}
NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren << NVMC_CONFIG_WEN_Pos;
while (NRF_NVMC->READY == NVMC_READY_READY_Busy) {
}
NVIC_SystemReset(); // Reboot to apply PSELRESET (hardware reconfigures P0.18)
}
// Power button + latch: button pulls P1.09 to GND, P0.05 HIGH keeps power on
pinMode(POWER_LATCH_PIN, OUTPUT);
digitalWrite(POWER_LATCH_PIN, LOW);
pinMode(POWER_BUTTON_PIN, INPUT_PULLUP);
// If the button is not pressed at boot, power must already be latched
// (reboot/crash while running on the latch) - re-latch immediately so the
// board survives the reset glitch.
if (digitalRead(POWER_BUTTON_PIN) == HIGH) {
digitalWrite(POWER_LATCH_PIN, HIGH);
powerLatched = true;
}
// Initialize pairing LED pin (StatusLEDModule only calls digitalWrite, not pinMode)
pinMode(LED_PAIRING, OUTPUT);
digitalWrite(LED_PAIRING, LED_STATE_ON ^ 1); // OFF
// Power button state machine (created before the scheduler, runs at boot)
xTaskCreate(powerButtonTask, "powerBtn", 2048, NULL, 10, NULL);
}
@@ -0,0 +1,139 @@
#ifndef _VARIANT_MOONSHINE_NRF_TRAVELERS_CORE_
#define _VARIANT_MOONSHINE_NRF_TRAVELERS_CORE_
/** Master clock frequency */
#define VARIANT_MCK (64000000ul)
#define USE_LFXO // Board uses 32khz crystal for LF
/*----------------------------------------------------------------------------
* Headers
*----------------------------------------------------------------------------*/
#include "WVariant.h"
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
/*
MOONSHINE NRF TRAVELERS CORE PIN ASSIGNMENT (Ebyte nRF52840 + E22-400M33S)
| Pin | Function | | Pin | Function |
| ----- | ----------- | ------ | ----- | ----------- |
| P0.02 | LORA_RXEN | | P0.15 | LED |
| P0.03 | VBAT_ADC | | P0.26 | SPI_MISO |
| P0.04 | LORA_CS | | P0.28 | LORA_BUSY |
| P0.05 | POWER_LATCH | | P1.09 | POWER_BTN |
| P0.06 | SPI_MOSI | | P1.10 | LORA_RESET |
| P0.07 | IIC_SDA | | P1.11 | BUZZER |
| P0.08 | SPI_SCK | | P1.13 | LORA_DIO1 |
| P0.12 | IIC_SCL | | | |
| P0.13 | CHAG_DET | | | |
*/
// Number of pins defined in PinDescription array
#define PINS_COUNT (48)
#define NUM_DIGITAL_PINS (48)
#define NUM_ANALOG_INPUTS (1)
#define NUM_ANALOG_OUTPUTS (0)
// No dedicated 3V3 enable pin on this board
#define PIN_3V3_EN (-1)
// Analog pins
#define BATTERY_PIN (0 + 3) // P0.03 / AIN1 Battery ADC
#define ADC_RESOLUTION 14
#define BATTERY_SENSE_RESOLUTION_BITS 12
#define BATTERY_SENSE_RESOLUTION 4096.0
// Definition of milliVolt per LSB => 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096
#define VBAT_MV_PER_LSB (0.73242188F)
// Voltage divider value => equal resistors divider = 0.5
#define VBAT_DIVIDER (0.5F)
// Compensation factor for the VBAT divider
#define VBAT_DIVIDER_COMP (2.0F)
// Fixed calculation of milliVolt from compensation value
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
#undef AREF_VOLTAGE
#define AREF_VOLTAGE 3.0
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
#define ADC_MULTIPLIER VBAT_DIVIDER_COMP // REAL_VBAT_MV_PER_LSB
#define VBAT_RAW_TO_SCALED(x) (REAL_VBAT_MV_PER_LSB * x)
// WIRE IC AND IIC PINS
#define WIRE_INTERFACES_COUNT 1
#define PIN_WIRE_SDA (0 + 7) // P0.07
#define PIN_WIRE_SCL (0 + 12) // P0.12
// LED - Bluetooth pairing status indicator
#define LED_PAIRING (0 + 15) // P0.15
#define LED_STATE_ON 1 // State when LED is lit (active high)
// Charge detection (P0.13, active high = charging)
#define EXT_CHRG_DETECT (0 + 13)
#define EXT_CHRG_DETECT_VALUE HIGH
// Buzzer
#define PIN_BUZZER (32 + 11) // P1.11
// Power button + self-hold latch: the button (pulled to GND) supplies power while
// pressed; firmware drives P0.05 HIGH after a 2s hold to keep the board powered.
#define POWER_BUTTON_PIN (32 + 9) // P1.09 - LOW = pressed
#define POWER_LATCH_PIN (0 + 5) // P0.05 - HIGH = keep power on
// UART interfaces - no physical serial ports wired
#define PIN_SERIAL1_RX (-1)
#define PIN_SERIAL1_TX (-1)
#define PIN_SERIAL2_RX (-1)
#define PIN_SERIAL2_TX (-1)
// GPS - no GPS hardware
#define GPS_RX_PIN (-1)
#define GPS_TX_PIN (-1)
#define PIN_GPS_EN (-1)
#define PIN_GPS_PPS (-1)
// Serial interfaces
#define SPI_INTERFACES_COUNT 1
#define PIN_SPI_MISO (0 + 26) // P0.26
#define PIN_SPI_MOSI (0 + 6) // P0.06
#define PIN_SPI_SCK (0 + 8) // P0.08
#define LORA_MISO PIN_SPI_MISO
#define LORA_MOSI PIN_SPI_MOSI
#define LORA_SCK PIN_SPI_SCK
#define LORA_CS (0 + 4) // P0.04
// LORA MODULES
#define USE_SX1268 // E22-400M33S uses SX1268 (433MHz)
// E22-400M33S: 33dBm/2W module, PA gain varies with power (non-linear)
// Per E22-M UserManual V1.2 power curve: chip 21dBm -> module 33.5dBm (gain ~12.5dB)
// TX_GAIN_LORA=12 at max power point; SX126X_MAX_POWER + TX_GAIN_LORA = 33dBm
#define SX126X_MAX_POWER 21
#define TX_GAIN_LORA 12
// SX126X CONFIG
#define SX126X_CS (0 + 4) // P0.04 FIXME - we really should define LORA_CS instead
#define SX126X_DIO1 (32 + 13) // P1.13 IRQ
#define SX126X_DIO2_AS_RF_SWITCH // Note for E22 modules: DIO2 is not attached internally to TXEN for automatic TX/RX switching,
// so it needs connecting externally if it is used in this way
#define SX126X_BUSY (0 + 28) // P0.28 (AIN4)
#define SX126X_RESET (32 + 10) // P1.10
#define SX126X_RXEN (0 + 2) // P0.02 - E22 RXEN, MCU controls RX/TX RF switch path
#define SX126X_TXEN RADIOLIB_NC // DIO2 controls TXEN directly
#define SX126X_DIO3_TCXO_VOLTAGE 2.2
#define TCXO_OPTIONAL // make it so that the firmware can try both TCXO and XTAL
#ifdef __cplusplus
}
#endif
/*----------------------------------------------------------------------------
* Arduino objects - C++ only
*----------------------------------------------------------------------------*/
#endif