From f256499ca3d89849e8ece7e96c63134b2e69bb14 Mon Sep 17 00:00:00 2001 From: kevin Date: Sun, 2 Aug 2026 12:32:15 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BC=80=E6=9C=BA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../moonshine_NRF_travelers_core/readme.md | 34 +++++++ .../moonshine_NRF_travelers_core/variant.cpp | 98 +++++++++++++++++++ .../moonshine_NRF_travelers_core/variant.h | 6 ++ 3 files changed, 138 insertions(+) diff --git a/variants/nrf52840/diy/moonshine_NRF_travelers_core/readme.md b/variants/nrf52840/diy/moonshine_NRF_travelers_core/readme.md index e53dff2c2..618b6b53a 100644 --- a/variants/nrf52840/diy/moonshine_NRF_travelers_core/readme.md +++ b/variants/nrf52840/diy/moonshine_NRF_travelers_core/readme.md @@ -13,6 +13,15 @@ DIY Meshtastic node variant based on an Ebyte nRF52840 MCU module and E22-400M33 - **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. @@ -25,6 +34,8 @@ DIY Meshtastic node variant based on an Ebyte nRF52840 MCU module and E22-400M33 | 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 | | @@ -36,6 +47,26 @@ DIY Meshtastic node variant based on an Ebyte nRF52840 MCU module and E22-400M33 | 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 @@ -106,6 +137,9 @@ openocd -f interface/cmsis-dap.cfg -f target/nrf52.cfg \ 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 diff --git a/variants/nrf52840/diy/moonshine_NRF_travelers_core/variant.cpp b/variants/nrf52840/diy/moonshine_NRF_travelers_core/variant.cpp index 21b8ff5c9..6280d5f26 100644 --- a/variants/nrf52840/diy/moonshine_NRF_travelers_core/variant.cpp +++ b/variants/nrf52840/diy/moonshine_NRF_travelers_core/variant.cpp @@ -19,6 +19,11 @@ */ #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" @@ -30,6 +35,83 @@ const uint32_t g_ADigitalPinMap[] = { // 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). @@ -52,7 +134,23 @@ void initVariant() 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); } diff --git a/variants/nrf52840/diy/moonshine_NRF_travelers_core/variant.h b/variants/nrf52840/diy/moonshine_NRF_travelers_core/variant.h index 5a9f94f25..e2febeb6f 100644 --- a/variants/nrf52840/diy/moonshine_NRF_travelers_core/variant.h +++ b/variants/nrf52840/diy/moonshine_NRF_travelers_core/variant.h @@ -24,6 +24,7 @@ MOONSHINE NRF TRAVELERS CORE PIN ASSIGNMENT (Ebyte nRF52840 + E22-400M33S) | 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 | @@ -76,6 +77,11 @@ MOONSHINE NRF TRAVELERS CORE PIN ASSIGNMENT (Ebyte nRF52840 + E22-400M33S) // 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)