From 7081e19b3d4fb1775afa8ca5d5b9a607b138f0ae Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Sun, 1 Mar 2026 16:20:17 +0100 Subject: [PATCH] add thinknode m1 board + tcxo fixes --- zephcore/CMakeLists.txt | 2 +- .../esp32/lilygo_tlora_c6/board.overlay | 4 +- .../boards/esp32/station_g2/board.overlay | 6 +- .../boards/esp32/xiao_esp32c3/board.overlay | 6 +- .../boards/esp32/xiao_esp32c6/board.overlay | 6 +- zephcore/boards/example_board/board.overlay | 12 +- zephcore/boards/mg24/xiao_mg24/board.overlay | 6 +- .../ikoka_nano_30dbm/ikoka_nano_30dbm.dts | 3 +- .../thinknode_m1/Kconfig.thinknode_m1 | 6 + .../boards/nrf52840/thinknode_m1/board.cmake | 13 + .../boards/nrf52840/thinknode_m1/board.conf | 28 ++ .../boards/nrf52840/thinknode_m1/board.yml | 9 + .../thinknode_m1/thinknode_m1-pinctrl.dtsi | 134 +++++++ .../nrf52840/thinknode_m1/thinknode_m1.dts | 370 ++++++++++++++++++ .../thinknode_m1/thinknode_m1_defconfig | 29 ++ .../boards/nrf54l/xiao_nrf54l15/board.overlay | 6 +- zephcore/helpers/ui/display.c | 10 + zephcore/helpers/ui/display.h | 7 + zephcore/helpers/ui/ui_task.c | 24 +- 19 files changed, 659 insertions(+), 22 deletions(-) create mode 100644 zephcore/boards/nrf52840/thinknode_m1/Kconfig.thinknode_m1 create mode 100644 zephcore/boards/nrf52840/thinknode_m1/board.cmake create mode 100644 zephcore/boards/nrf52840/thinknode_m1/board.conf create mode 100644 zephcore/boards/nrf52840/thinknode_m1/board.yml create mode 100644 zephcore/boards/nrf52840/thinknode_m1/thinknode_m1-pinctrl.dtsi create mode 100644 zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts create mode 100644 zephcore/boards/nrf52840/thinknode_m1/thinknode_m1_defconfig diff --git a/zephcore/CMakeLists.txt b/zephcore/CMakeLists.txt index afef849..cee337d 100644 --- a/zephcore/CMakeLists.txt +++ b/zephcore/CMakeLists.txt @@ -171,7 +171,7 @@ set(ZEPHCORE_COMMON_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/zephcore_com # Detect platform from board name and add platform-specific common config # For boards with qualifiers like "wio_tracker_l1/nrf52840", check both the full BOARD # string and the BOARD_QUALIFIERS which contains just the qualifier (e.g., "nrf52840") -if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e") +if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "thinknode_m1") set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf") elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*nrf52.*") set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/nrf52_common.conf") diff --git a/zephcore/boards/esp32/lilygo_tlora_c6/board.overlay b/zephcore/boards/esp32/lilygo_tlora_c6/board.overlay index 7162516..897dca4 100644 --- a/zephcore/boards/esp32/lilygo_tlora_c6/board.overlay +++ b/zephcore/boards/esp32/lilygo_tlora_c6/board.overlay @@ -12,6 +12,8 @@ * Console: USB-Serial/JTAG (usb_serial) */ +#include + / { aliases { lora0 = &lora; @@ -51,7 +53,7 @@ dio2-tx-enable; /* TCXO 1.8V via DIO3 */ - dio3-tcxo-voltage = <2>; + dio3-tcxo-voltage = ; tcxo-power-startup-delay-ms = <10>; /* RX boosted mode for better sensitivity */ diff --git a/zephcore/boards/esp32/station_g2/board.overlay b/zephcore/boards/esp32/station_g2/board.overlay index c9437d6..8d11f86 100644 --- a/zephcore/boards/esp32/station_g2/board.overlay +++ b/zephcore/boards/esp32/station_g2/board.overlay @@ -12,6 +12,8 @@ * Button: GPIO38 */ +#include + / { aliases { lora0 = &lora; @@ -49,8 +51,8 @@ /* DIO2 controls RF switch for TX */ dio2-tx-enable; - /* TCXO 1.8 V via DIO3 (value 2 = 1.8 V in Zephyr SX126x driver) */ - dio3-tcxo-voltage = <2>; + /* TCXO 1.8V via DIO3 */ + dio3-tcxo-voltage = ; tcxo-power-startup-delay-ms = <10>; rx-boosted; diff --git a/zephcore/boards/esp32/xiao_esp32c3/board.overlay b/zephcore/boards/esp32/xiao_esp32c3/board.overlay index e44bb6a..c9b39b4 100644 --- a/zephcore/boards/esp32/xiao_esp32c3/board.overlay +++ b/zephcore/boards/esp32/xiao_esp32c3/board.overlay @@ -13,6 +13,8 @@ * Console: USB-Serial/JTAG (usb_serial) */ +#include + / { aliases { lora0 = &lora; @@ -43,8 +45,8 @@ rx-enable-gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>; /* D5 = GPIO7 RXEN */ dio2-tx-enable; - /* TCXO 1.8V via DIO3 (SX126X_DIO3_TCXO_1V8 = 0x02) */ - dio3-tcxo-voltage = <2>; + /* TCXO 1.8V via DIO3 */ + dio3-tcxo-voltage = ; tcxo-power-startup-delay-ms = <10>; /* RX boosted mode for better sensitivity */ diff --git a/zephcore/boards/esp32/xiao_esp32c6/board.overlay b/zephcore/boards/esp32/xiao_esp32c6/board.overlay index cb7ef3a..4e701b0 100644 --- a/zephcore/boards/esp32/xiao_esp32c6/board.overlay +++ b/zephcore/boards/esp32/xiao_esp32c6/board.overlay @@ -13,6 +13,8 @@ * Console: USB-Serial/JTAG (usb_serial) */ +#include + / { aliases { lora0 = &lora; @@ -43,8 +45,8 @@ rx-enable-gpios = <&gpio0 23 GPIO_ACTIVE_HIGH>; /* D5 = GPIO23 RXEN */ dio2-tx-enable; - /* TCXO 1.8V via DIO3 (SX126X_DIO3_TCXO_1V8 = 0x02) */ - dio3-tcxo-voltage = <2>; + /* TCXO 1.8V via DIO3 */ + dio3-tcxo-voltage = ; tcxo-power-startup-delay-ms = <10>; /* RX boosted mode for better sensitivity */ diff --git a/zephcore/boards/example_board/board.overlay b/zephcore/boards/example_board/board.overlay index 4e5967a..496bf8d 100644 --- a/zephcore/boards/example_board/board.overlay +++ b/zephcore/boards/example_board/board.overlay @@ -150,10 +150,8 @@ * rx-enable-gpios — Separate RXEN pin (Wio-SX1262 needs both) * * TCXO (if your module has one): - * dio3-tcxo-voltage — DIO3 provides TCXO power - * Values: 0=1.6V, 1=1.7V, 2=1.8V, 3=2.2V, 4=2.4V, 5=2.7V, 6=3.0V, 7=3.3V - * Or use named constants from sx126x.h: - * SX126X_DIO3_TCXO_1V6 .. SX126X_DIO3_TCXO_3V3 + * dio3-tcxo-voltage — DIO3 provides TCXO power (use 1.8V) + * Use: SX126X_DIO3_TCXO_1V8 (from sx126x.h) * * rx-boosted — Better RX sensitivity (slight power increase) * @@ -205,7 +203,7 @@ * dio1-gpios = <&gpio1 5 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; * rx-enable-gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>; * dio2-tx-enable; - * dio3-tcxo-voltage = <6>; + * dio3-tcxo-voltage = ; * tcxo-power-startup-delay-ms = <10>; * rx-boosted; * }; @@ -231,7 +229,7 @@ * dio1-gpios = <&gpioc 1 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; * rx-enable-gpios = <&gpioc 5 GPIO_ACTIVE_HIGH>; * dio2-tx-enable; - * dio3-tcxo-voltage = <6>; + * dio3-tcxo-voltage = ; * tcxo-power-startup-delay-ms = <10>; * rx-boosted; * }; @@ -255,7 +253,7 @@ * dio1-gpios = <&gpio0 3 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; * rx-enable-gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>; * dio2-tx-enable; - * dio3-tcxo-voltage = <6>; + * dio3-tcxo-voltage = ; * tcxo-power-startup-delay-ms = <10>; * rx-boosted; * }; diff --git a/zephcore/boards/mg24/xiao_mg24/board.overlay b/zephcore/boards/mg24/xiao_mg24/board.overlay index c1b696e..fb878b6 100644 --- a/zephcore/boards/mg24/xiao_mg24/board.overlay +++ b/zephcore/boards/mg24/xiao_mg24/board.overlay @@ -14,6 +14,8 @@ * WARNING: Default I2C0 uses PC4/PC5 (D4/D5) — must be disabled for LoRa! */ +#include + / { aliases { lora0 = &lora; @@ -48,8 +50,8 @@ rx-enable-gpios = <&gpioc 5 GPIO_ACTIVE_HIGH>; /* D5 = PC05 RXEN */ dio2-tx-enable; - /* TCXO 1.8V via DIO3 (SX126X_DIO3_TCXO_1V8 = 0x02) */ - dio3-tcxo-voltage = <2>; + /* TCXO 1.8V via DIO3 */ + dio3-tcxo-voltage = ; tcxo-power-startup-delay-ms = <10>; /* RX boosted mode for better sensitivity */ diff --git a/zephcore/boards/nrf52840/ikoka_nano_30dbm/ikoka_nano_30dbm.dts b/zephcore/boards/nrf52840/ikoka_nano_30dbm/ikoka_nano_30dbm.dts index 140d29a..3e375d6 100644 --- a/zephcore/boards/nrf52840/ikoka_nano_30dbm/ikoka_nano_30dbm.dts +++ b/zephcore/boards/nrf52840/ikoka_nano_30dbm/ikoka_nano_30dbm.dts @@ -22,6 +22,7 @@ #include #include "ikoka_nano_30dbm-pinctrl.dtsi" #include +#include / { model = "Ikoka Nano 30dBm"; @@ -152,7 +153,7 @@ dio2-tx-enable; /* TCXO voltage 1.8V */ - dio3-tcxo-voltage = <6>; + dio3-tcxo-voltage = ; tcxo-power-startup-delay-ms = <10>; /* Enable RX boosted mode for better sensitivity */ diff --git a/zephcore/boards/nrf52840/thinknode_m1/Kconfig.thinknode_m1 b/zephcore/boards/nrf52840/thinknode_m1/Kconfig.thinknode_m1 new file mode 100644 index 0000000..7254be0 --- /dev/null +++ b/zephcore/boards/nrf52840/thinknode_m1/Kconfig.thinknode_m1 @@ -0,0 +1,6 @@ +# Elecrow ThinkNode M1 board configuration +# Copyright (c) 2025 ZephCore +# SPDX-License-Identifier: Apache-2.0 + +config BOARD_THINKNODE_M1 + select SOC_NRF52840_QIAA diff --git a/zephcore/boards/nrf52840/thinknode_m1/board.cmake b/zephcore/boards/nrf52840/thinknode_m1/board.cmake new file mode 100644 index 0000000..34bb176 --- /dev/null +++ b/zephcore/boards/nrf52840/thinknode_m1/board.cmake @@ -0,0 +1,13 @@ +# Copyright (c) 2025 ZephCore +# SPDX-License-Identifier: Apache-2.0 + +board_runner_args(jlink "--device=nRF52840_xxAA" "--speed=4000") +board_runner_args(pyocd "--target=nrf52840" "--frequency=4000000") + +set(OPENOCD_NRF5_SUBFAMILY "nrf52") + +include(${ZEPHYR_BASE}/boards/common/jlink.board.cmake) +include(${ZEPHYR_BASE}/boards/common/pyocd.board.cmake) +include(${ZEPHYR_BASE}/boards/common/openocd-nrf5.board.cmake) +include(${ZEPHYR_BASE}/boards/common/nrfjprog.board.cmake) +include(${ZEPHYR_BASE}/boards/common/nrfutil.board.cmake) diff --git a/zephcore/boards/nrf52840/thinknode_m1/board.conf b/zephcore/boards/nrf52840/thinknode_m1/board.conf new file mode 100644 index 0000000..ced8c7f --- /dev/null +++ b/zephcore/boards/nrf52840/thinknode_m1/board.conf @@ -0,0 +1,28 @@ +# Elecrow ThinkNode M1 (nRF52840 + SX1262 + SSD1681 EPD) +# Board-specific configuration +# +# Hardware: +# - nRF52840 SoC, SX1262 LoRa (22dBm), 1.54" SSD1681 e-paper (200x200) +# - GPS module on UART0, MX25R1635F 2MB QSPI flash +# - Battery ADC on AIN2 (P0.04), 150K+150K divider +# - Buzzer on P0.06, two buttons, GPS hardware switch +# - LEDs: GREEN=P1.04, BLUE=P0.14 + +# Board identification (matches Arduino MeshCore variant name) +CONFIG_ZEPHCORE_BOARD_NAME="ThinkNode M1" + +# Device Information Service model name +CONFIG_BT_DIS_MODEL_NUMBER_STR="Elecrow ThinkNode-M1" + +# SoftDevice firmware ID (ThinkNode M1 bootloader uses S140 v6) +CONFIG_ZEPHCORE_SD_FWID=0x00B6 + +# QSPI flash present +CONFIG_NORDIC_QSPI_NOR=y +CONFIG_NORDIC_QSPI_NOR_FLASH_LAYOUT_PAGE_SIZE=4096 + +# Contacts on QSPI (2MB) — override nrf52_common.conf limit +CONFIG_ZEPHCORE_MAX_CONTACTS=510 + +# PWM for buzzer +CONFIG_PWM=y diff --git a/zephcore/boards/nrf52840/thinknode_m1/board.yml b/zephcore/boards/nrf52840/thinknode_m1/board.yml new file mode 100644 index 0000000..f69889a --- /dev/null +++ b/zephcore/boards/nrf52840/thinknode_m1/board.yml @@ -0,0 +1,9 @@ +# Copyright (c) 2025 ZephCore +# SPDX-License-Identifier: Apache-2.0 + +board: + name: thinknode_m1 + full_name: Elecrow ThinkNode M1 (nRF52840 + SX1262 + SSD1681 EPD) + vendor: elecrow + socs: + - name: nrf52840 diff --git a/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1-pinctrl.dtsi b/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1-pinctrl.dtsi new file mode 100644 index 0000000..e99bf0f --- /dev/null +++ b/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1-pinctrl.dtsi @@ -0,0 +1,134 @@ +/* + * Elecrow ThinkNode M1 pin control definitions + * Copyright (c) 2025 ZephCore + * + * SPDX-License-Identifier: Apache-2.0 + * + * Pin mappings (from Arduino MeshCore variant.h): + * + * SPI2 (LoRa SX1262): SCK=P0.19, MISO=P0.23, MOSI=P0.22, CS=P0.24 + * SPI3 (EPD SSD1681): SCK=P0.31, MISO=P1.06, MOSI=P0.29 + * (MISO shared with LED_RED — unused by write-only EPD) + * I2C0 (RTC/sensors): SDA=P0.26, SCL=P0.27 + * UART0 (GPS 9600): TX=P1.08 (→GPS RX), RX=P1.09 (←GPS TX) + * PWM0 (buzzer): P0.06 + * QSPI (MX25R1635F): SCK=P1.14, CS=P1.15, IO0=P1.12, IO1=P1.13, + * IO2=P0.07, IO3=P0.05 + */ + +&pinctrl { + /* UART0 for GPS module at 9600 baud */ + uart0_default: uart0_default { + group1 { + psels = ; /* P1.08 → GPS RX */ + }; + group2 { + psels = ; /* P1.09 ← GPS TX */ + bias-pull-up; + }; + }; + + uart0_sleep: uart0_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + /* I2C0 for external sensors / RTC */ + i2c0_default: i2c0_default { + group1 { + psels = , /* P0.26 */ + ; /* P0.27 */ + }; + }; + + i2c0_sleep: i2c0_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + /* SPI2 for LoRa SX1262 */ + spi2_default: spi2_default { + group1 { + psels = , /* P0.19 */ + , /* P0.22 */ + ; /* P0.23 */ + }; + }; + + spi2_sleep: spi2_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; + + /* SPI3 for E-Paper display SSD1681 + * MISO on P1.06 is shared with LED_RED — e-ink is write-only so + * MISO is never driven, but nRF SPIM requires all three pins. */ + spi3_default: spi3_default { + group1 { + psels = , /* P0.31 */ + , /* P0.29 */ + ; /* P1.06 (shared LED_RED) */ + }; + }; + + spi3_sleep: spi3_sleep { + group1 { + psels = , + , + ; + low-power-enable; + }; + }; + + /* PWM0 for buzzer on P0.06 */ + pwm0_default: pwm0_default { + group1 { + psels = ; + }; + }; + + pwm0_sleep: pwm0_sleep { + group1 { + psels = ; + low-power-enable; + }; + }; + + /* QSPI for MX25R1635F 2MB external flash */ + qspi_default: qspi_default { + group1 { + psels = , + , + , + , + , + ; + }; + }; + + qspi_sleep: qspi_sleep { + group1 { + psels = , + , + , + , + ; + low-power-enable; + }; + group2 { + psels = ; + low-power-enable; + bias-pull-up; + }; + }; +}; diff --git a/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts b/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts new file mode 100644 index 0000000..2075458 --- /dev/null +++ b/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1.dts @@ -0,0 +1,370 @@ +/* + * Elecrow ThinkNode M1 — nRF52840 + SX1262 + SSD1681 1.54" EPD + * Copyright (c) 2025 ZephCore + * + * SPDX-License-Identifier: Apache-2.0 + * + * Hardware: + * - nRF52840 SoC with BLE 5 + * - SX1262 LoRa radio (DIO2 as RF switch, external TCXO 1.8V) + * - SSD1681 1.54" 200x200 monochrome e-paper display (SPI) + * - MX25R1635F 2MB QSPI flash + * - GPS module on UART0 (9600 baud, multi-constellation) + * - 1200 mAh battery with ADC on AIN2 (150K+150K divider) + * - Buzzer on PWM0, two user buttons, GPS hardware switch + * - LEDs: GREEN=P1.04, BLUE=P0.14 (RED=P1.06 shared with display SPI) + * + * Pin conflicts (shared by hardware design): + * - P1.06: LED_RED + display SPI MISO (EPD is write-only, MISO unused) + * - P1.05: LoRa power enable + GPS reset (shared power rail) + * - P1.04: LED_GREEN + GPS PPS (GPS PPS blinks the green LED) + */ + +/dts-v1/; +#include +#include "thinknode_m1-pinctrl.dtsi" +#include +#include +#include +#include + +/ { + model = "Elecrow ThinkNode M1"; + compatible = "elecrow,thinknode-m1"; + + chosen { + zephyr,sram = &sram0; + zephyr,flash = &flash0; + zephyr,code-partition = &code_partition; + zephyr,console = &cdc_acm_uart; + zephyr,shell-uart = &cdc_acm_uart; + zephyr,display = &epd; + }; + + aliases { + lora0 = &lora; + led0 = &led_green; + led1 = &led_blue; + sw0 = &user_button; + watchdog0 = &wdt0; + buzzer = &buzzer; + gps-enable = &gps_enable_pin; + }; + + /* ---- LEDs (active-low) ---- */ + leds { + compatible = "gpio-leds"; + + led_green: led_0 { + gpios = <&gpio1 4 GPIO_ACTIVE_LOW>; + label = "Green LED"; + }; + + led_blue: led_1 { + gpios = <&gpio0 14 GPIO_ACTIVE_LOW>; + label = "Blue LED"; + }; + /* LED_RED (P1.06) omitted — shared with display SPI MISO */ + }; + + /* ---- Buttons ---- */ + buttons: buttons { + compatible = "gpio-keys"; + + user_button: button_0 { + gpios = <&gpio1 10 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "User Button"; + }; + + page_button: button_1 { + gpios = <&gpio0 11 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + zephyr,code = ; + label = "Page Button"; + }; + }; + + /* Input filter: user button long-press (1000ms) */ + user_btn_longpress { + compatible = "zephyr,input-longpress"; + input = <&buttons>; + input-codes = ; + short-codes = ; + long-codes = ; + long-delay-ms = <1000>; + }; + + /* Input filter: user button multi-tap + * 1 tap → KEY_1 = next page on EPD + * 2 taps → KEY_B = flood advert + * 3 taps → KEY_D = buzzer mute toggle + * 4 taps → KEY_C = GPS on/off */ + user_btn_multitap { + compatible = "zephcore,input-multi-tap"; + input-codes = ; + tap-codes = ; + tap-delay-ms = <400>; + }; + + /* ---- Power control ---- */ + + /* Board power enable — must be HIGH for peripherals to function */ + pwr_enable: pwr-enable { + compatible = "regulator-fixed"; + regulator-name = "pwr-enable"; + enable-gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>; + regulator-boot-on; + }; + + /* LoRa radio power enable — must be HIGH before SX1262 can communicate */ + lora_pwr_enable: lora-pwr-enable { + compatible = "regulator-fixed"; + regulator-name = "lora-pwr-enable"; + enable-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>; + regulator-boot-on; + startup-delay-us = <10000>; + }; + + /* External TCXO power enable — SX1262 DIO3 provides reference voltage, + * but the TCXO itself is powered through this separate GPIO */ + tcxo_enable: tcxo-enable { + compatible = "regulator-fixed"; + regulator-name = "tcxo-enable"; + enable-gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>; + regulator-boot-on; + }; + + /* ---- GPS hardware switch (P1.01) ---- + * Physical toggle switch on the board. + * HIGH = GPS ON, LOW = GPS OFF. + * Separate from the buttons node so it doesn't feed + * the longpress/multi-tap filter chain. */ + gps_switch: gps-switch { + compatible = "gpio-keys"; + gps_sw: gps_sw { + gpios = <&gpio1 1 GPIO_ACTIVE_HIGH>; + zephyr,code = ; + label = "GPS Switch"; + }; + }; + + /* GPS power control */ + gps_en: gps-enable { + compatible = "gpio-leds"; + gps_enable_pin: gps_enable { + gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>; + label = "GPS Enable"; + }; + }; + + /* ---- Battery ADC ---- */ + /* 150K+150K voltage divider (2:1) on AIN2 (P0.04) */ + zephyr,user { + io-channels = <&adc 2>; + vbat-mv-multiplier = <7200>; + }; + + /* ---- Buzzer on PWM0 ---- */ + pwmbuzzer { + compatible = "pwm-leds"; + buzzer: buzzer { + pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>; + }; + }; + + /* ---- E-Paper display (MIPI DBI SPI) ---- + * SSD1681 1.54" 200x200 monochrome EPD on SPI3. + * The MIPI DBI node wraps the SPI bus with DC and RST GPIOs. + * Display is auto-detected by ZephCore display.c via the + * zephyr,display chosen node — EPD flag set automatically. */ + mipi_dbi { + compatible = "zephyr,mipi-dbi-spi"; + spi-dev = <&spi3>; + dc-gpios = <&gpio0 28 GPIO_ACTIVE_HIGH>; + reset-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>; + #address-cells = <1>; + #size-cells = <0>; + + epd: ssd16xxfb@0 { + compatible = "solomon,ssd1681"; + mipi-max-frequency = <4000000>; + reg = <0>; + width = <200>; + height = <200>; + busy-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>; + }; + }; +}; + +/* ---- SoC configuration ---- */ + +®0 { + status = "okay"; +}; + +®1 { + regulator-initial-mode = ; +}; + +&adc { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + /* Battery voltage on P0.04 (AIN2) */ + channel@2 { + reg = <2>; + zephyr,gain = "ADC_GAIN_1_6"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,input-positive = ; + zephyr,resolution = <12>; + }; +}; + +&uicr { + nfct-pins-as-gpios; +}; + +&gpiote { + status = "okay"; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +/* ---- PWM0 for buzzer ---- */ +&pwm0 { + status = "okay"; + pinctrl-0 = <&pwm0_default>; + pinctrl-1 = <&pwm0_sleep>; + pinctrl-names = "default", "sleep"; +}; + +/* ---- GPS on UART0 (9600 baud) ---- + * Uses luatos,air530z driver (PCAS/NMEA protocol). + * GPS power controlled via on-off-gpios (P1.02 = GPS_EN). */ +&uart0 { + compatible = "nordic,nrf-uarte"; + status = "okay"; + current-speed = <9600>; + pinctrl-0 = <&uart0_default>; + pinctrl-1 = <&uart0_sleep>; + pinctrl-names = "default", "sleep"; + + gnss: gnss { + compatible = "luatos,air530z"; + on-off-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>; + }; +}; + +/* ---- I2C0 for RTC and external sensors ---- */ +&i2c0 { + compatible = "nordic,nrf-twim"; + status = "okay"; + clock-frequency = ; + pinctrl-0 = <&i2c0_default>; + pinctrl-1 = <&i2c0_sleep>; + pinctrl-names = "default", "sleep"; + + /* All supported environment & power sensors — auto-detected at runtime */ + #include "../../common/sensors-i2c.dtsi" +}; + +/* ---- SPI2 for LoRa SX1262 ---- */ +&spi2 { + compatible = "nordic,nrf-spim"; + status = "okay"; + pinctrl-0 = <&spi2_default>; + pinctrl-1 = <&spi2_sleep>; + pinctrl-names = "default", "sleep"; + cs-gpios = <&gpio0 24 GPIO_ACTIVE_LOW>; /* P0.24 LORA_CS */ + + lora: lora@0 { + compatible = "semtech,sx1262"; + reg = <0>; + spi-max-frequency = <8000000>; + + /* LoRa control pins */ + reset-gpios = <&gpio0 25 GPIO_ACTIVE_LOW>; /* P0.25 */ + busy-gpios = <&gpio0 17 GPIO_ACTIVE_HIGH>; /* P0.17 */ + dio1-gpios = <&gpio0 20 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; /* P0.20 */ + + /* DIO2 drives RF switch for TX */ + dio2-tx-enable; + + /* TCXO voltage 1.8V via DIO3 */ + dio3-tcxo-voltage = ; + tcxo-power-startup-delay-ms = <10>; + + /* RX boosted mode for better sensitivity */ + rx-boosted; + }; +}; + +/* ---- SPI3 for E-Paper display ---- */ +&spi3 { + compatible = "nordic,nrf-spim"; + status = "okay"; + pinctrl-0 = <&spi3_default>; + pinctrl-1 = <&spi3_sleep>; + pinctrl-names = "default", "sleep"; + cs-gpios = <&gpio0 30 GPIO_ACTIVE_LOW>; /* P0.30 DISP_CS */ +}; + +/* ---- QSPI Flash — MX25R1635F 2MB ---- */ +&qspi { + status = "okay"; + pinctrl-0 = <&qspi_default>; + pinctrl-1 = <&qspi_sleep>; + pinctrl-names = "default", "sleep"; + + qspi_flash: qspi_flash@0 { + compatible = "nordic,qspi-nor"; + reg = <0>; + writeoc = "pp"; + readoc = "fastread"; + sck-frequency = <16000000>; + jedec-id = [c2 28 15]; /* MX25R1635F */ + size = <16777216>; /* 2MB = 16Mbit */ + has-dpd; + t-enter-dpd = <10000>; + t-exit-dpd = <35000>; + }; +}; + +/* ---- USB CDC for console/CLI ---- */ +zephyr_udc0: &usbd { + compatible = "nordic,nrf-usbd"; + status = "okay"; + + cdc_acm_uart: cdc_acm_uart { + compatible = "zephyr,cdc-acm-uart"; + }; +}; + +/* ---- Flash partitions (SoftDevice v6, app@0x26000) ---- */ +#include "../../common/nrf52_partitions_sdv6.dtsi" + +/* ---- External QSPI flash partitions ---- */ +&qspi_flash { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + /* Full 2MB for LittleFS storage */ + qspi_storage_partition: partition@0 { + label = "qspi_storage"; + reg = <0x00000000 0x00200000>; + }; + }; +}; + +/* ---- External QSPI /ext automount ---- */ +#include "../../common/qspi-ext.dtsi" diff --git a/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1_defconfig b/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1_defconfig new file mode 100644 index 0000000..ef29475 --- /dev/null +++ b/zephcore/boards/nrf52840/thinknode_m1/thinknode_m1_defconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: Apache-2.0 +# Elecrow ThinkNode M1 default configuration + +# Enable MPU +CONFIG_ARM_MPU=y +CONFIG_HW_STACK_PROTECTION=y + +# Enable GPIO +CONFIG_GPIO=y + +# Enable console on USB CDC +CONFIG_CONSOLE=y +CONFIG_UART_CONSOLE=y +CONFIG_SERIAL=y +CONFIG_UART_LINE_CTRL=y + +# Enable flash/NVS storage +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y + +# Pinctrl +CONFIG_PINCTRL=y + +# SPI for LoRa + EPD display +CONFIG_SPI=y + +# ADC for battery +CONFIG_ADC=y diff --git a/zephcore/boards/nrf54l/xiao_nrf54l15/board.overlay b/zephcore/boards/nrf54l/xiao_nrf54l15/board.overlay index 26d7b96..f5ee864 100644 --- a/zephcore/boards/nrf54l/xiao_nrf54l15/board.overlay +++ b/zephcore/boards/nrf54l/xiao_nrf54l15/board.overlay @@ -14,6 +14,8 @@ * Flash via: SWD through SAMD11 CMSIS-DAP (west flash / openocd) */ +#include + / { aliases { lora0 = &lora; @@ -47,8 +49,8 @@ rx-enable-gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>; /* D5 = P1.11 RXEN */ dio2-tx-enable; - /* TCXO 1.8V via DIO3 (SX126X_DIO3_TCXO_1V8 = 0x02) */ - dio3-tcxo-voltage = <2>; + /* TCXO 1.8V via DIO3 */ + dio3-tcxo-voltage = ; tcxo-power-startup-delay-ms = <10>; /* RX boosted mode for better sensitivity */ diff --git a/zephcore/helpers/ui/display.c b/zephcore/helpers/ui/display.c index 33de625..c1cbfc1 100644 --- a/zephcore/helpers/ui/display.c +++ b/zephcore/helpers/ui/display.c @@ -50,6 +50,11 @@ static void auto_off_handler(struct k_work *work) if (doom_game_is_running()) { return; } + /* E-paper uses zero power when static — blanking wastes a full + * refresh cycle (~2s) and leaves the screen blank for no benefit. */ + if (is_epd) { + return; + } if (disp_on) { mc_display_off(); } @@ -227,6 +232,11 @@ bool mc_display_is_on(void) return disp_on; } +bool mc_display_is_epd(void) +{ + return is_epd; +} + void mc_display_clear(void) { if (!disp_initialized) { diff --git a/zephcore/helpers/ui/display.h b/zephcore/helpers/ui/display.h index 4bc3551..cbdab40 100644 --- a/zephcore/helpers/ui/display.h +++ b/zephcore/helpers/ui/display.h @@ -75,6 +75,13 @@ void mc_display_off(void); */ bool mc_display_is_on(void); +/** + * @return true if the display is an e-paper (EPD) type. + * EPD displays have slow refresh (~2s) and use zero power when static, + * so callers should use longer update intervals and skip blanking. + */ +bool mc_display_is_epd(void); + /** * Clear the framebuffer (fill with black). * Call before rendering a new frame. diff --git a/zephcore/helpers/ui/ui_task.c b/zephcore/helpers/ui/ui_task.c index 77182f4..48cf660 100644 --- a/zephcore/helpers/ui/ui_task.c +++ b/zephcore/helpers/ui/ui_task.c @@ -11,6 +11,7 @@ * KEY_B → action_flood_advert() (2 taps, 400ms delayed) * KEY_D → action_buzzer_toggle() (3 taps, 400ms delayed) * KEY_C → action_gps_toggle() (4 taps, immediate) + * KEY_G → GPS switch on/off (hardware toggle, ThinkNode M1) * KEY_POWER → action_deep_sleep() (long press ≥1s) * KEY_RIGHT → action_page_next() (joystick, Wio Tracker) * KEY_LEFT → action_page_prev() (joystick, Wio Tracker) @@ -109,7 +110,8 @@ static struct ui_state local_ui_state; /* ========== Constants ========== */ #define SPLASH_DURATION_MS 3000 -#define RENDER_DEBOUNCE_MS 50 +#define RENDER_DEBOUNCE_MS 50 +#define RENDER_DEBOUNCE_EPD_MS 2500 /* e-paper: coalesce updates across full refresh (~2s) */ /* ========== State ========== */ static bool ui_initialized; @@ -243,7 +245,9 @@ static void advert_defer_handler(struct k_work *work) static void schedule_render(void) { #ifdef CONFIG_ZEPHCORE_UI_DISPLAY - k_work_reschedule(&render_work, K_MSEC(RENDER_DEBOUNCE_MS)); + uint32_t debounce = mc_display_is_epd() ? RENDER_DEBOUNCE_EPD_MS + : RENDER_DEBOUNCE_MS; + k_work_reschedule(&render_work, K_MSEC(debounce)); #endif } @@ -591,6 +595,22 @@ static void ui_input_cb(struct input_event *evt, void *user_data) } #endif + /* GPS hardware switch (toggle switch, not momentary button). + * Needs both press (ON) and release (OFF) events, + * so handle before the release-event filter below. */ + if (evt->code == INPUT_KEY_G) { + bool gps_on = (evt->value != 0); + LOG_INF("GPS switch → %s", gps_on ? "on" : "off"); + if (gps_is_available()) { + mesh_gps_set_enabled(gps_on); +#ifdef CONFIG_ZEPHCORE_UI_BUZZER + buzzer_play(gps_on ? MELODY_GPS_ON : MELODY_GPS_OFF); +#endif + schedule_render(); + } + return; + } + /* Only handle key press events (value=1), not releases (value=0) */ if (!evt->value) { return;