mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 20:09:17 +00:00
extract shutdown to it's own helper
This commit is contained in:
@@ -495,10 +495,7 @@ static void action_deep_sleep(void)
|
||||
#ifdef CONFIG_POWEROFF
|
||||
LOG_INF("deep sleep: shutting down...");
|
||||
|
||||
/* 1. Stop LED heartbeat and msg indicator */
|
||||
ui_set_heartbeat_led(false);
|
||||
|
||||
/* 2. Play shutdown melody (blocking wait) */
|
||||
/* Shutdown melody — blocking wait is fine on this terminal path. */
|
||||
#ifdef CONFIG_ZEPHCORE_UI_BUZZER
|
||||
buzzer_play(MELODY_SHUTDOWN);
|
||||
while (buzzer_is_playing()) {
|
||||
@@ -507,78 +504,10 @@ static void action_deep_sleep(void)
|
||||
buzzer_stop();
|
||||
#endif
|
||||
|
||||
/* 3. Turn display off */
|
||||
#ifdef CONFIG_ZEPHCORE_UI_DISPLAY
|
||||
mc_display_off();
|
||||
#endif
|
||||
|
||||
/* 4. Drive power-hungry enable pins LOW.
|
||||
*
|
||||
* nRF52840 GPIO output latches persist across System OFF and reset.
|
||||
* If GPS_EN is HIGH, the GPS module stays powered during "sleep."
|
||||
* Drive known power-enable GPIOs LOW via Zephyr's safe GPIO API.
|
||||
*
|
||||
* DO NOT touch BLE (bt_conn_disconnect / bt_le_adv_stop) — that
|
||||
* corrupts BLE controller state and prevents clean reboot.
|
||||
* DO NOT blank all GPIOs — that bricked the device previously. */
|
||||
gps_power_off_for_shutdown();
|
||||
mesh_disable_power_regulators();
|
||||
|
||||
/* 5. Hold the LoRa radio in hardware reset.
|
||||
*
|
||||
* sys_poweroff() bypasses device PM — the SX126x hardware duty cycle
|
||||
* would otherwise keep cycling autonomously (drawing mA) while the
|
||||
* SoC is in System OFF (~1µA). Drive RESET low; nRF52 GPIO output
|
||||
* latches persist across System OFF so the chip stays in reset (0µA).
|
||||
* On wakeup, the driver's sx126x_init() re-asserts then releases reset. */
|
||||
#if DT_NODE_EXISTS(DT_ALIAS(lora0)) && DT_NODE_HAS_PROP(DT_ALIAS(lora0), reset_gpios)
|
||||
{
|
||||
static const struct gpio_dt_spec lora_reset =
|
||||
GPIO_DT_SPEC_GET(DT_ALIAS(lora0), reset_gpios);
|
||||
gpio_pin_configure_dt(&lora_reset, GPIO_OUTPUT_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 6. Configure GPIO SENSE for button wakeup, then enter System OFF.
|
||||
*
|
||||
* The nRF GPIO driver does not implement GPIO_INT_WAKEUP, so the
|
||||
* wakeup-source DTS property has no effect on nRF52. We must set
|
||||
* SENSE bits directly via the nRF HAL. sys_poweroff() goes straight
|
||||
* to nrf_power_system_off() with no device PM suspend, so these bits
|
||||
* persist into System OFF and trigger a reset on next button press.
|
||||
*
|
||||
* Wait for button release first: if we enter System OFF while the
|
||||
* button is still held (long-press shutdown), DETECT is already
|
||||
* asserted and the chip cannot enter System OFF cleanly. */
|
||||
#if defined(CONFIG_SOC_FAMILY_NORDIC_NRF) && DT_NODE_EXISTS(DT_ALIAS(sw0))
|
||||
{
|
||||
#define _SW0_NODE DT_ALIAS(sw0)
|
||||
#define _SW0_PORT DT_PROP(DT_GPIO_CTLR(_SW0_NODE, gpios), port)
|
||||
#define _SW0_PIN DT_GPIO_PIN(_SW0_NODE, gpios)
|
||||
#define _SW0_FLAGS DT_GPIO_FLAGS(_SW0_NODE, gpios)
|
||||
|
||||
static const struct gpio_dt_spec sw0 =
|
||||
GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);
|
||||
gpio_pin_configure_dt(&sw0, GPIO_INPUT);
|
||||
|
||||
int64_t deadline = k_uptime_get() + 5000;
|
||||
while (gpio_pin_get_dt(&sw0) && k_uptime_get() < deadline) {
|
||||
k_sleep(K_MSEC(10));
|
||||
}
|
||||
|
||||
nrf_gpio_cfg_sense_input(
|
||||
NRF_GPIO_PIN_MAP(_SW0_PORT, _SW0_PIN),
|
||||
(_SW0_FLAGS & GPIO_PULL_UP) ? NRF_GPIO_PIN_PULLUP :
|
||||
(_SW0_FLAGS & GPIO_PULL_DOWN) ? NRF_GPIO_PIN_PULLDOWN :
|
||||
NRF_GPIO_PIN_NOPULL,
|
||||
(_SW0_FLAGS & GPIO_ACTIVE_LOW) ? NRF_GPIO_PIN_SENSE_LOW
|
||||
: NRF_GPIO_PIN_SENSE_HIGH);
|
||||
#undef _SW0_NODE
|
||||
#undef _SW0_PORT
|
||||
#undef _SW0_PIN
|
||||
#undef _SW0_FLAGS
|
||||
}
|
||||
#endif /* CONFIG_SOC_FAMILY_NORDIC_NRF && sw0 */
|
||||
/* Shared peripheral teardown + SENSE config for sw0 wake.
|
||||
* Single source of truth in helpers/ui/ui_common.c so the joystick
|
||||
* UI variant ends up in the same low-power state. */
|
||||
ui_prepare_for_system_off();
|
||||
|
||||
LOG_INF("deep sleep: entering System OFF");
|
||||
sys_poweroff();
|
||||
|
||||
@@ -1394,16 +1394,23 @@ void JoystickUITask::shutdown(bool restart)
|
||||
LOG_INF("joystick UI: shutdown (restart=%d)", restart);
|
||||
#ifdef CONFIG_ZEPHCORE_UI_BUZZER
|
||||
buzzer_play("shutdown:d=16,o=5,b=120:c,p,a4,p,f4");
|
||||
/* Brief wait for melody */
|
||||
/* Brief wait for melody — terminal path either way. */
|
||||
k_sleep(K_MSEC(400));
|
||||
#endif
|
||||
_display.turnOff();
|
||||
|
||||
if (restart) {
|
||||
_display.turnOff();
|
||||
sys_reboot(SYS_REBOOT_COLD);
|
||||
} else {
|
||||
#ifdef CONFIG_POWEROFF
|
||||
/* Full peripheral teardown + SENSE config for sw0 wake. Shared
|
||||
* helper turns off display, GPS, regulators, holds LoRa in reset
|
||||
* and arms the button SENSE so the user can actually wake the
|
||||
* device. Same code path as the button UI's action_deep_sleep. */
|
||||
ui_prepare_for_system_off();
|
||||
sys_poweroff();
|
||||
#else
|
||||
_display.turnOff();
|
||||
sys_reboot(SYS_REBOOT_COLD);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -13,8 +13,20 @@
|
||||
#include "buzzer.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_ZEPHCORE_UI_DISPLAY
|
||||
#include "display.h"
|
||||
#endif
|
||||
|
||||
#include <ZephyrSensorManager.h> /* gps_power_off_for_shutdown */
|
||||
#include "ui_mesh_actions.h" /* mesh_disable_power_regulators (weak) */
|
||||
|
||||
#include <zephyr/drivers/gpio.h>
|
||||
#include <zephyr/kernel.h>
|
||||
|
||||
#if defined(CONFIG_SOC_FAMILY_NORDIC_NRF)
|
||||
#include <hal/nrf_gpio.h>
|
||||
#endif
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(ui_led, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL);
|
||||
|
||||
@@ -234,6 +246,86 @@ void ui_invalidate_battery_cache(void)
|
||||
s_batt_last_read_ms = 0;
|
||||
}
|
||||
|
||||
/* ========== System OFF preparation ==========
|
||||
* Shared by both UI variants. Caller is responsible for any shutdown chime
|
||||
* and the final sys_poweroff() call; this just leaves the SoC + peripherals
|
||||
* in the lowest-power state with a wakeup source armed.
|
||||
*
|
||||
* On nRF52 the SoC enters System OFF (~1 µA) but GPIO output latches and
|
||||
* SENSE bits persist across the transition — so we must explicitly:
|
||||
* - hold every power-enable GPIO LOW so external chips don't keep drawing
|
||||
* - hold the LoRa radio in HW reset (its internal duty cycle would
|
||||
* otherwise keep cycling autonomously, drawing mA)
|
||||
* - configure SENSE on sw0 so a button press wakes the chip (the nRF GPIO
|
||||
* driver doesn't honour the DT wakeup-source property, so the dtsi
|
||||
* marker is inert without this)
|
||||
*
|
||||
* Non-nRF platforms skip the SENSE block; they rely on Zephyr's wakeup-source
|
||||
* DT property which is honoured by their respective GPIO drivers.
|
||||
*/
|
||||
void ui_prepare_for_system_off(void)
|
||||
{
|
||||
/* 1. Stop heartbeat LED cycle (cancels both works). */
|
||||
ui_set_heartbeat_led(false);
|
||||
|
||||
/* 2. Display off — content stays visible on EPD, blanks on OLED. */
|
||||
#ifdef CONFIG_ZEPHCORE_UI_DISPLAY
|
||||
mc_display_off();
|
||||
#endif
|
||||
|
||||
/* 3. Drive power-enable GPIOs LOW so peripherals don't keep drawing.
|
||||
* Do NOT touch BLE here — that corrupts controller state and prevents
|
||||
* clean reboot on wake. */
|
||||
gps_power_off_for_shutdown();
|
||||
mesh_disable_power_regulators(); /* weak-stubbed on non-companion roles */
|
||||
|
||||
/* 4. Hold LoRa radio in HW reset.
|
||||
* SX126x/LR11xx duty-cycle mode would otherwise keep the radio cycling
|
||||
* autonomously (mA) while the SoC is in System OFF. nRF52 output
|
||||
* latches persist across System OFF → chip stays in reset (~0 µA). */
|
||||
#if DT_NODE_EXISTS(DT_ALIAS(lora0)) && DT_NODE_HAS_PROP(DT_ALIAS(lora0), reset_gpios)
|
||||
{
|
||||
static const struct gpio_dt_spec lora_reset =
|
||||
GPIO_DT_SPEC_GET(DT_ALIAS(lora0), reset_gpios);
|
||||
gpio_pin_configure_dt(&lora_reset, GPIO_OUTPUT_ACTIVE);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* 5. Configure GPIO SENSE for sw0 button wakeup, after waiting for the
|
||||
* user to release any held button (otherwise DETECT is already asserted
|
||||
* when we enter System OFF and the chip never sleeps cleanly).
|
||||
* nRF only — other platforms rely on the DT wakeup-source property. */
|
||||
#if defined(CONFIG_SOC_FAMILY_NORDIC_NRF) && DT_NODE_EXISTS(DT_ALIAS(sw0))
|
||||
{
|
||||
#define _SW0_NODE DT_ALIAS(sw0)
|
||||
#define _SW0_PORT DT_PROP(DT_GPIO_CTLR(_SW0_NODE, gpios), port)
|
||||
#define _SW0_PIN DT_GPIO_PIN(_SW0_NODE, gpios)
|
||||
#define _SW0_FLAGS DT_GPIO_FLAGS(_SW0_NODE, gpios)
|
||||
|
||||
static const struct gpio_dt_spec sw0 =
|
||||
GPIO_DT_SPEC_GET(DT_ALIAS(sw0), gpios);
|
||||
gpio_pin_configure_dt(&sw0, GPIO_INPUT);
|
||||
|
||||
int64_t deadline = k_uptime_get() + 5000;
|
||||
while (gpio_pin_get_dt(&sw0) && k_uptime_get() < deadline) {
|
||||
k_sleep(K_MSEC(10));
|
||||
}
|
||||
|
||||
nrf_gpio_cfg_sense_input(
|
||||
NRF_GPIO_PIN_MAP(_SW0_PORT, _SW0_PIN),
|
||||
(_SW0_FLAGS & GPIO_PULL_UP) ? NRF_GPIO_PIN_PULLUP :
|
||||
(_SW0_FLAGS & GPIO_PULL_DOWN) ? NRF_GPIO_PIN_PULLDOWN :
|
||||
NRF_GPIO_PIN_NOPULL,
|
||||
(_SW0_FLAGS & GPIO_ACTIVE_LOW) ? NRF_GPIO_PIN_SENSE_LOW
|
||||
: NRF_GPIO_PIN_SENSE_HIGH);
|
||||
#undef _SW0_NODE
|
||||
#undef _SW0_PORT
|
||||
#undef _SW0_PIN
|
||||
#undef _SW0_FLAGS
|
||||
}
|
||||
#endif /* CONFIG_SOC_FAMILY_NORDIC_NRF && sw0 */
|
||||
}
|
||||
|
||||
/* ========== Shared splash logo ==========
|
||||
* 128×13 ZephCore wordmark, MSB-first row-major (Adafruit XBM/drawBitmap
|
||||
* format). Used by both UI variants' splash renders via mc_display_xbm(). */
|
||||
|
||||
@@ -182,6 +182,17 @@ void ui_set_battery_provider(uint16_t (*provider)(void));
|
||||
*/
|
||||
void ui_refresh_battery(void);
|
||||
|
||||
/**
|
||||
* Prepare the device for sys_poweroff(): stop heartbeat LED, blank the
|
||||
* display, power off GPS + sensor regulators, hold LoRa in HW reset,
|
||||
* configure SENSE on sw0 (nRF only) for button wakeup.
|
||||
*
|
||||
* Caller is responsible for any shutdown chime BEFORE this call and the
|
||||
* final sys_poweroff() AFTER. Both UI variants share this so the System
|
||||
* OFF state is consistent regardless of which UI design is compiled in.
|
||||
*/
|
||||
void ui_prepare_for_system_off(void);
|
||||
|
||||
/**
|
||||
* Drop the battery-refresh freshness timestamp. The next
|
||||
* ui_refresh_battery() call is guaranteed to sample the ADC.
|
||||
|
||||
Reference in New Issue
Block a user