diff --git a/zephcore/boards/common/nrf52_wakeup.dtsi b/zephcore/boards/common/nrf52_wakeup.dtsi new file mode 100644 index 0000000..1d9455a --- /dev/null +++ b/zephcore/boards/common/nrf52_wakeup.dtsi @@ -0,0 +1,18 @@ +/* + * Common: configure the 'buttons' gpio-keys node as a wakeup source. + * + * On nRF52840, sys_poweroff() enters System OFF (~1µA). Waking via the user + * button requires GPIO SENSE bits to be set before poweroff. Zephyr's gpio-keys + * driver adds GPIO_INT_WAKEUP to the interrupt flags when wakeup-source is set, + * which causes the nRF52 GPIO driver to call nrf_gpio_cfg_sense_set(). Those + * SENSE bits persist through System OFF and trigger a reset on button press. + * + * Include this in any nRF52 board DTS that defines a 'buttons' gpio-keys node. + * Boards without buttons (e.g. ikoka_nano, rak4631) should NOT include this. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&buttons { + wakeup-source; +}; diff --git a/zephcore/boards/example_board/README.md b/zephcore/boards/example_board/README.md index 0b5d28a..a8256c5 100644 --- a/zephcore/boards/example_board/README.md +++ b/zephcore/boards/example_board/README.md @@ -157,6 +157,21 @@ If you write a fully custom DTS that includes neither, add it yourself: }; +**nRF52 boards with a user button: button wakeup from System OFF** + +`sys_poweroff()` on nRF52840 enters System OFF (~1µA). To allow waking via +button press (instead of only via USB/charger), GPIO SENSE bits must be set +before poweroff. This is done via the `wakeup-source` property on the +`gpio-keys` node, which the nRF52 GPIO driver handles automatically. + +Boards that define a `buttons:` gpio-keys node must add at the END of their DTS: + + #include "../../common/nrf52_wakeup.dtsi" + +Boards WITHOUT a `buttons` label (ikoka_nano, rak4631) must NOT include it — +referencing an undefined `&buttons` label is a hard build error. + + What Goes in board.conf ----------------------- diff --git a/zephcore/boards/example_board/board.overlay b/zephcore/boards/example_board/board.overlay index 53d6671..6b4ec1a 100644 --- a/zephcore/boards/example_board/board.overlay +++ b/zephcore/boards/example_board/board.overlay @@ -659,6 +659,8 @@ * / { * buttons: buttons { * compatible = "gpio-keys"; + * /* nRF52 only: omit wakeup-source here; use the common include below instead. + * * Other platforms: add wakeup-source; directly if the driver supports it. */ * user_button: button_0 { * gpios = <&gpio0 8 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; * zephyr,code = ; @@ -669,18 +671,29 @@ * compatible = "zephyr,input-longpress"; * input = <&buttons>; * input-codes = ; - * short-codes = ; - * long-codes = ; + * short-codes = ; /* short press → multi-tap input */ + * long-codes = ; /* long press (≥1s) → deep sleep */ * long-delay-ms = <1000>; * }; * + * /* Multi-tap action mapping (standard ZephCore assignment): + * * 1 tap (400ms wait) → KEY_1 = page next + * * 2 taps (400ms wait) → KEY_B = flood advert + * * 3 taps (400ms wait) → KEY_D = buzzer mute toggle + * * 4 taps (400ms wait) → KEY_C = GPS on/off + * * 5 taps (immediate) → KEY_E = LED heartbeat toggle */ * user_btn_multitap { * compatible = "zephcore,input-multi-tap"; * input-codes = ; - * tap-codes = ; + * tap-codes = ; * tap-delay-ms = <400>; * }; * }; + * + * /* nRF52 boards: add this at the END of the DTS file so the button GPIO + * * is configured for System OFF wakeup (press button to boot after shutdown). + * * Do NOT add for boards without a 'buttons' label (ikoka_nano, rak4631). */ + * #include "../../common/nrf52_wakeup.dtsi" */ /* --- GPS on UART (nRF52840 typically on &uart0) --- diff --git a/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts b/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts index a33da9b..069d0f9 100644 --- a/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts +++ b/zephcore/boards/nrf52840/promicro_lr2021/promicro_lr2021.dts @@ -203,3 +203,4 @@ zephyr_udc0: &usbd { /* ---- Flash partitions (SoftDevice v6, app@0x26000) ---- */ #include "../../common/nrf52_partitions_sdv6.dtsi" +#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/boards/nrf52840/rak_wismesh_tag/rak_wismesh_tag.dts b/zephcore/boards/nrf52840/rak_wismesh_tag/rak_wismesh_tag.dts index f60be7a..b031944 100644 --- a/zephcore/boards/nrf52840/rak_wismesh_tag/rak_wismesh_tag.dts +++ b/zephcore/boards/nrf52840/rak_wismesh_tag/rak_wismesh_tag.dts @@ -95,11 +95,12 @@ * 1 tap → KEY_1 = (reserved / next page if display added) * 2 taps → KEY_B = flood advert * 3 taps → KEY_D = buzzer mute toggle - * 4 taps → KEY_C = GPS on/off */ + * 4 taps → KEY_C = GPS on/off + * 5 taps → KEY_E = LED heartbeat toggle */ user_btn_multitap { compatible = "zephcore,input-multi-tap"; input-codes = ; - tap-codes = ; + tap-codes = ; tap-delay-ms = <400>; }; @@ -277,3 +278,4 @@ zephyr_udc0: &usbd { /* ---- Flash partitions (SoftDevice v6, app@0x26000) ---- */ #include "../../common/nrf52_partitions_sdv6.dtsi" +#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/boards/nrf52840/t1000_e/t1000_e_nrf52840.dts b/zephcore/boards/nrf52840/t1000_e/t1000_e_nrf52840.dts index de70158..412fa29 100644 --- a/zephcore/boards/nrf52840/t1000_e/t1000_e_nrf52840.dts +++ b/zephcore/boards/nrf52840/t1000_e/t1000_e_nrf52840.dts @@ -76,14 +76,15 @@ * 1 tap (400ms wait) → KEY_1 = page next (no-op without display) * 2 taps (400ms wait) → KEY_B = flood advert * 3 taps (400ms wait) → KEY_D = buzzer mute toggle - * 4 taps (immediate) → KEY_C = GPS on/off */ + * 4 taps (400ms wait) → KEY_C = GPS on/off + * 5 taps (immediate) → KEY_E = LED heartbeat toggle */ user_btn_multitap { compatible = "zephcore,input-multi-tap"; /* No 'input' phandle — listen to ALL devices. * KEY_A is emitted by the longpress pseudo-device, * not by &buttons, so we must not filter by device. */ input-codes = ; - tap-codes = ; + tap-codes = ; tap-delay-ms = <400>; }; @@ -279,3 +280,4 @@ zephyr_udc0: &usbd { /* Arduino MeshCore compatible partition layout (SoftDevice v7) */ #include "../../common/nrf52_partitions_sdv7.dtsi" +#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts b/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts index d66fb14..4d8fa62 100644 --- a/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts +++ b/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts @@ -389,3 +389,4 @@ zephyr_udc0: &usbd { /* ---- External QSPI /ext automount ---- */ #include "../../common/qspi-ext.dtsi" +#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/boards/nrf52840/thinknode_m3/thinknode_m3.dts b/zephcore/boards/nrf52840/thinknode_m3/thinknode_m3.dts index 25466d5..64922e4 100644 --- a/zephcore/boards/nrf52840/thinknode_m3/thinknode_m3.dts +++ b/zephcore/boards/nrf52840/thinknode_m3/thinknode_m3.dts @@ -82,11 +82,12 @@ * 1 tap → KEY_1 = page next * 2 taps → KEY_B = flood advert * 3 taps → KEY_D = buzzer mute toggle - * 4 taps → KEY_C = GPS on/off */ + * 4 taps → KEY_C = GPS on/off + * 5 taps → KEY_E = LED heartbeat toggle */ user_btn_multitap { compatible = "zephcore,input-multi-tap"; input-codes = ; - tap-codes = ; + tap-codes = ; tap-delay-ms = <400>; }; @@ -255,3 +256,4 @@ zephyr_udc0: &usbd { /* SoftDevice v6 partition layout (S140 v6 — Adafruit nRF52 bootloader) */ #include "../../common/nrf52_partitions_sdv6.dtsi" +#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/boards/nrf52840/thinknode_m6/thinknode_m6.dts b/zephcore/boards/nrf52840/thinknode_m6/thinknode_m6.dts index 08a0026..6a75627 100644 --- a/zephcore/boards/nrf52840/thinknode_m6/thinknode_m6.dts +++ b/zephcore/boards/nrf52840/thinknode_m6/thinknode_m6.dts @@ -81,11 +81,12 @@ * 1 tap → KEY_1 = page next * 2 taps → KEY_B = flood advert * 3 taps → KEY_D = buzzer mute toggle - * 4 taps → KEY_C = GPS on/off */ + * 4 taps → KEY_C = GPS on/off + * 5 taps → KEY_E = LED heartbeat toggle */ user_btn_multitap { compatible = "zephcore,input-multi-tap"; input-codes = ; - tap-codes = ; + tap-codes = ; tap-delay-ms = <400>; }; @@ -323,3 +324,4 @@ zephyr_udc0: &usbd { /* External QSPI /ext automount */ #include "../../common/qspi-ext.dtsi" +#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.dts b/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.dts index 8bc0c13..ceb634b 100644 --- a/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.dts +++ b/zephcore/boards/nrf52840/wio_tracker_l1/wio_tracker_l1_nrf52840.dts @@ -103,14 +103,15 @@ * 1 tap (400ms wait) → KEY_1 = page next on OLED * 2 taps (400ms wait) → KEY_B = flood advert * 3 taps (400ms wait) → KEY_D = buzzer mute toggle - * 4 taps (immediate) → KEY_C = GPS on/off */ + * 4 taps (400ms wait) → KEY_C = GPS on/off + * 5 taps (immediate) → KEY_E = LED heartbeat toggle */ user_btn_multitap { compatible = "zephcore,input-multi-tap"; /* No 'input' phandle — listen to ALL devices. * KEY_A is emitted by the longpress pseudo-device, * not by &buttons, so we must not filter by device. */ input-codes = ; - tap-codes = ; + tap-codes = ; tap-delay-ms = <400>; }; @@ -340,3 +341,4 @@ zephyr_udc0: &usbd { }; }; }; +#include "../../common/nrf52_wakeup.dtsi" diff --git a/zephcore/helpers/ui/input_multi_tap.c b/zephcore/helpers/ui/input_multi_tap.c index 4cc99ef..d1b5e0f 100644 --- a/zephcore/helpers/ui/input_multi_tap.c +++ b/zephcore/helpers/ui/input_multi_tap.c @@ -133,8 +133,8 @@ static int __maybe_unused multi_tap_init(const struct device *dev) "input-codes must have at least 1 entry"); \ BUILD_ASSERT(DT_INST_PROP_LEN(inst, tap_codes) >= 1, \ "tap-codes must have at least 1 entry"); \ - BUILD_ASSERT(DT_INST_PROP_LEN(inst, tap_codes) <= 4, \ - "tap-codes must have at most 4 entries"); \ + BUILD_ASSERT(DT_INST_PROP_LEN(inst, tap_codes) <= 5, \ + "tap-codes must have at most 5 entries"); \ \ INPUT_CALLBACK_DEFINE_NAMED( \ COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, input), \ diff --git a/zephcore/helpers/ui/ui_task.c b/zephcore/helpers/ui/ui_task.c index 326594b..0e1afb9 100644 --- a/zephcore/helpers/ui/ui_task.c +++ b/zephcore/helpers/ui/ui_task.c @@ -76,6 +76,9 @@ LOG_MODULE_REGISTER(ui_task, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL); #define MELODY_GPS_ON "gon:d=16,o=7,b=200:c,p,c,p,c,p,c,p,p,8e" #define MELODY_GPS_OFF "gof:d=16,o=7,b=200:c,p,c,p,c,p,c,p,p,8g5" +#define MELODY_LED_ON "lon:d=16,o=7,b=200:c,p,c,p,c,p,c,p,c,p,p,8e" +#define MELODY_LED_OFF "lof:d=16,o=7,b=200:c,p,c,p,c,p,c,p,c,p,p,8g5" + /* ========== LED Heartbeat ========== */ /* Match Arduino: 4s cycle, 20ms pulse (normal) or 200ms (unread messages). * Uses led0 or led1 alias — whichever exists in the board's DTS. @@ -475,6 +478,9 @@ static void action_leds_toggle(void) s->leds_disabled = new_disabled; ui_set_heartbeat_led(!new_disabled); mesh_set_leds_disabled(new_disabled); +#ifdef CONFIG_ZEPHCORE_UI_BUZZER + buzzer_play(new_disabled ? MELODY_LED_OFF : MELODY_LED_ON); +#endif LOG_INF("LEDs %s (user toggle)", new_disabled ? "disabled" : "enabled"); schedule_render(); } @@ -698,10 +704,15 @@ static void ui_input_cb(struct input_event *evt, void *user_data) break; case INPUT_KEY_C: - /* Quadruple tap (immediate): toggle GPS */ + /* Quadruple tap (400ms delayed): toggle GPS */ action_gps_toggle(); break; + case INPUT_KEY_E: + /* Quintuple tap (immediate): toggle LED heartbeat */ + action_leds_toggle(); + break; + /* ===== Longpress output ===== */ case INPUT_KEY_POWER: case INPUT_KEY_F: