From 40ae0902587d7c627bc1ad6118ed64ccaaf9ef71 Mon Sep 17 00:00:00 2001 From: Matthew Burton <19491784+matthew73210@users.noreply.github.com> Date: Fri, 3 Jul 2026 21:13:52 +0200 Subject: [PATCH 1/3] Reworked the screen pages --- zephcore/helpers/ui-button/ui_pages.c | 95 ++++++++++++++++++++++----- zephcore/helpers/ui-button/ui_pages.h | 1 + zephcore/helpers/ui-button/ui_task.c | 2 +- 3 files changed, 82 insertions(+), 16 deletions(-) diff --git a/zephcore/helpers/ui-button/ui_pages.c b/zephcore/helpers/ui-button/ui_pages.c index 6b0c336..723d6cd 100644 --- a/zephcore/helpers/ui-button/ui_pages.c +++ b/zephcore/helpers/ui-button/ui_pages.c @@ -58,18 +58,20 @@ LOG_MODULE_REGISTER(ui_pages, CONFIG_ZEPHCORE_BOARD_LOG_LEVEL); /* Compact RGB565 palette for small color TFTs. These are used only through * mc_display_has_color(); monochrome displays keep the existing CFB path. */ -#define UI_COLOR_HEADER_BG 0x018c /* deep teal/blue */ -#define UI_COLOR_TITLE 0x5dff /* readable cyan */ -#define UI_COLOR_LABEL 0x8c71 /* muted blue-gray */ +#define UI_COLOR_BG MC_COLOR_BLACK +#define UI_COLOR_HEADER_BG 0x2104 /* dark neutral panel */ +#define UI_COLOR_TITLE 0xffde /* warm white */ +#define UI_COLOR_LABEL 0x8410 /* muted gray */ #define UI_COLOR_VALUE MC_COLOR_WHITE +#define UI_COLOR_SECONDARY 0xbdf7 /* soft gray-white */ #define UI_COLOR_OK 0x07e0 -#define UI_COLOR_ACTIVE 0x05ff +#define UI_COLOR_ACTIVE UI_COLOR_OK #define UI_COLOR_WARN 0xffa0 #define UI_COLOR_ERROR MC_COLOR_RED #define UI_COLOR_DIM 0x4208 #define UI_COLOR_DISABLED MC_COLOR_GRAY #define UI_COLOR_TX MC_COLOR_ORANGE -#define UI_COLOR_RX UI_COLOR_ACTIVE +#define UI_COLOR_RX UI_COLOR_OK #define COLOR_FONT_W 6 #define COLOR_FONT_H 8 @@ -119,6 +121,7 @@ static const enum ui_page active_pages[] = { UI_PAGE_MESSAGES, UI_PAGE_RECENT, UI_PAGE_RADIO, + UI_PAGE_TRAFFIC, UI_PAGE_BLUETOOTH, UI_PAGE_ADVERT, UI_PAGE_GPS, @@ -434,7 +437,7 @@ static void draw_activity_graph(int x, int y, int w, int h) sample_activity_graph(); - mc_display_color_fill_rect(x, y, w, h, MC_COLOR_BLACK); + mc_display_color_fill_rect(x, y, w, h, UI_COLOR_BG); mc_display_color_fill_rect(x, mid, w, 1, UI_COLOR_DIM); for (int i = 0; i < ACTIVITY_GRAPH_SAMPLES; i++) { @@ -487,17 +490,21 @@ static void render_messages(void) int y = CONTENT_Y; uint16_t ble_color = state.ble_connected ? UI_COLOR_OK : UI_COLOR_WARN; const char *ble = state.ble_connected ? "BLE OK" : "BLE ADV"; - int graph_h = 16; - int graph_y = DISP_H - graph_h - 2; + const char *radio = state.lora_tx_active ? "TX" : + (state.lora_in_rx ? "RX" : + (state.lora_radio_ready ? "RDY" : "WAIT")); + uint16_t radio_color = state.lora_tx_active ? UI_COLOR_TX : + state.lora_in_rx ? UI_COLOR_RX : + state.lora_radio_ready ? UI_COLOR_OK + : UI_COLOR_WARN; draw_badge(0, y, ble, ble_color); - draw_badge(DISP_W - color_text_width(state.offgrid_enabled ? "GRID" : "LOCAL") - 4, - y, state.offgrid_enabled ? "GRID" : "LOCAL", - state.offgrid_enabled ? UI_COLOR_ACTIVE : UI_COLOR_DISABLED); + draw_badge(DISP_W - color_text_width(radio) - 4, y, radio, + radio_color); y += LINE_H + 2; - mc_display_color_text(0, y, "MESSAGES", UI_COLOR_LABEL); - snprintf(buf, sizeof(buf), "%u", state.msg_count); + mc_display_color_text(0, y, "COMPANION", UI_COLOR_LABEL); + snprintf(buf, sizeof(buf), "MSG %u", state.msg_count); mc_display_color_text(DISP_W - color_text_width(buf), y, buf, state.msg_count ? UI_COLOR_WARN : UI_COLOR_VALUE); y += LINE_H; @@ -505,7 +512,7 @@ static void render_messages(void) if (state.ble_connected) { draw_centered_color(y, "Connected", UI_COLOR_OK); } else { - mc_display_color_text(34, y, "Waiting for app", UI_COLOR_WARN); + mc_display_color_text(30, y, "Waiting for app", UI_COLOR_WARN); } y += LINE_H; @@ -514,7 +521,6 @@ static void render_messages(void) draw_centered_color(y, buf, state.offgrid_enabled ? UI_COLOR_ACTIVE : UI_COLOR_DISABLED); - draw_activity_graph(2, graph_y, DISP_W - 4, graph_h); return; } @@ -748,6 +754,64 @@ static void render_radio(void) mc_display_text(0, y, buf, false); } +static void render_traffic(void) +{ + char rx_count[6]; + char tx_count[6]; + char err_count[6]; + char buf[32]; + int y = CONTENT_Y; + + fmt_compact_count(rx_count, sizeof(rx_count), state.lora_packets_rx); + fmt_compact_count(tx_count, sizeof(tx_count), state.lora_packets_tx); + fmt_compact_count(err_count, sizeof(err_count), state.lora_packets_err); + + if (mc_display_has_color() && DISP_W >= 120 && DISP_H >= 64) { + int graph_y; + int graph_h; + int x; + + mc_display_color_fill_rect(0, y - 1, DISP_W, COLOR_FONT_H + 2, + UI_COLOR_HEADER_BG); + mc_display_color_text(2, y, "TRAFFIC", UI_COLOR_TITLE); + if (state.lora_packets_err > 0) { + snprintf(buf, sizeof(buf), "E %s", err_count); + mc_display_color_text(DISP_W - color_text_width(buf), y, buf, + UI_COLOR_ERROR); + } + y += LINE_H; + + x = 0; + mc_display_color_text(x, y, "RX ", UI_COLOR_LABEL); + x += 3 * COLOR_FONT_W; + mc_display_color_text(x, y, rx_count, UI_COLOR_RX); + x += color_text_width(rx_count) + 10; + mc_display_color_text(x, y, "TX ", UI_COLOR_LABEL); + x += 3 * COLOR_FONT_W; + mc_display_color_text(x, y, tx_count, UI_COLOR_TX); + + graph_y = y + LINE_H + 2; + graph_h = (int)DISP_H - graph_y - 2; + if (graph_h < 16) { + graph_h = 16; + } + draw_activity_graph(2, graph_y, DISP_W - 4, graph_h); + return; + } + + draw_centered(y, "Traffic"); + y += LINE_H; + + snprintf(buf, sizeof(buf), "RX:%s TX:%s", rx_count, tx_count); + mc_display_text(0, y, buf, false); + y += LINE_H; + + if (state.lora_packets_err > 0) { + snprintf(buf, sizeof(buf), "ERR:%s", err_count); + mc_display_text(0, y, buf, false); + } +} + static void render_bluetooth(void) { if (mc_display_has_color()) { @@ -1251,6 +1315,7 @@ static const page_render_fn renderers[] = { [UI_PAGE_MESSAGES] = render_messages, [UI_PAGE_RECENT] = render_recent, [UI_PAGE_RADIO] = render_radio, + [UI_PAGE_TRAFFIC] = render_traffic, [UI_PAGE_BLUETOOTH] = render_bluetooth, [UI_PAGE_ADVERT] = render_advert, [UI_PAGE_GPS] = render_gps, diff --git a/zephcore/helpers/ui-button/ui_pages.h b/zephcore/helpers/ui-button/ui_pages.h index ee5992f..a161f48 100644 --- a/zephcore/helpers/ui-button/ui_pages.h +++ b/zephcore/helpers/ui-button/ui_pages.h @@ -23,6 +23,7 @@ enum ui_page { UI_PAGE_MESSAGES = 0, /* MSG count + connection status */ UI_PAGE_RECENT, /* Recently heard contacts */ UI_PAGE_RADIO, /* LoRa params (freq, SF, BW, CR, power, noise) */ + UI_PAGE_TRAFFIC, /* TX/RX packet activity graph */ UI_PAGE_BLUETOOTH, /* BLE toggle */ UI_PAGE_ADVERT, /* Send broadcast advert */ UI_PAGE_GPS, /* GPS status / position */ diff --git a/zephcore/helpers/ui-button/ui_task.c b/zephcore/helpers/ui-button/ui_task.c index ee41a6d..9f587ae 100644 --- a/zephcore/helpers/ui-button/ui_task.c +++ b/zephcore/helpers/ui-button/ui_task.c @@ -927,7 +927,7 @@ void ui_set_radio_stats(uint32_t packets_rx, uint32_t packets_tx, #ifdef CONFIG_ZEPHCORE_UI_DISPLAY if (ui_initialized && activity_changed && mc_display_has_color() && - ui_pages_current() == UI_PAGE_MESSAGES) { + ui_pages_current() == UI_PAGE_TRAFFIC) { schedule_render(); } #endif From 955acca8b0a09c244a5f3ed0a69cf6e9a36fc871 Mon Sep 17 00:00:00 2001 From: Matthew Burton <19491784+matthew73210@users.noreply.github.com> Date: Sat, 4 Jul 2026 07:28:17 +0200 Subject: [PATCH 2/3] Added the WTv2 --- README.md | 3 +- build.sh | 1 + .../esp32/heltec_wireless_tracker_v2/Kconfig | 3 + .../Kconfig.heltec_wireless_tracker_v2 | 3 + .../heltec_wireless_tracker_v2/README.md | 79 +++++ .../heltec_wireless_tracker_v2/board.cmake | 7 + .../heltec_wireless_tracker_v2/board.conf | 25 ++ .../heltec_wireless_tracker_v2/board.overlay | 98 ++++++ .../heltec_wireless_tracker_v2/board.yml | 6 + .../heltec_wireless_tracker_v2-pinctrl.dtsi | 75 +++++ .../heltec_wireless_tracker_v2_procpu.dts | 284 ++++++++++++++++++ ...eltec_wireless_tracker_v2_procpu_defconfig | 13 + zephcore/boards/example_board/README.md | 8 +- zephcore/boards/supported_boards.md | 5 + 14 files changed, 608 insertions(+), 2 deletions(-) create mode 100644 zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig create mode 100644 zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig.heltec_wireless_tracker_v2 create mode 100644 zephcore/boards/esp32/heltec_wireless_tracker_v2/README.md create mode 100644 zephcore/boards/esp32/heltec_wireless_tracker_v2/board.cmake create mode 100644 zephcore/boards/esp32/heltec_wireless_tracker_v2/board.conf create mode 100644 zephcore/boards/esp32/heltec_wireless_tracker_v2/board.overlay create mode 100644 zephcore/boards/esp32/heltec_wireless_tracker_v2/board.yml create mode 100644 zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2-pinctrl.dtsi create mode 100644 zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts create mode 100644 zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu_defconfig diff --git a/README.md b/README.md index 6152ccf..879d028 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ Other benefits: | **Heltec V3** | ESP32-S3 | SX1262 | OLED (SSD1306), 8MB flash | | **Heltec V4.2** | ESP32-S3 | SX1262 + GC1109 PA | OLED (SSD1306), 16MB flash, 2MB PSRAM | | **Heltec V4.3** | ESP32-S3 | SX1262 + KCT8103L PA | OLED (SSD1306), 16MB flash, 2MB PSRAM | -| **Heltec Wireless Tracker** | ESP32-S3 | SX1262 | ST7735R 160×80 TFT, UC6580 GPS | +| **Heltec Wireless Tracker V1.1** | ESP32-S3 | SX1262 | ST7735R 160×80 TFT, UC6580 GPS | +| **Heltec Wireless Tracker V2** | ESP32-S3FN8 | SX1262 + KCT8103L PA/FEM | ST7735R 160x80 TFT, UC6580 GNSS, battery ADC | | **LilyGo T-Beam v1.2** | ESP32 (PICO-D4) | SX1262 | AXP2101 PMU, GNSS, USB-UART CLI | ### Other diff --git a/build.sh b/build.sh index d89ac12..632f551 100644 --- a/build.sh +++ b/build.sh @@ -42,6 +42,7 @@ ESP32_boards=( heltec_wifi_lora32_v4/esp32s3/procpu heltec_wifi_lora32_v43/esp32s3/procpu heltec_wireless_tracker/esp32s3/procpu + heltec_wireless_tracker_v2/esp32s3/procpu ttgo_tbeam/esp32/procpu ) diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig b/zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig new file mode 100644 index 0000000..959ef82 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig @@ -0,0 +1,3 @@ +config HEAP_MEM_POOL_ADD_SIZE_BOARD + int + default 4096 diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig.heltec_wireless_tracker_v2 b/zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig.heltec_wireless_tracker_v2 new file mode 100644 index 0000000..a70d3df --- /dev/null +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/Kconfig.heltec_wireless_tracker_v2 @@ -0,0 +1,3 @@ +config BOARD_HELTEC_WIRELESS_TRACKER_V2 + select SOC_ESP32S3_FN8 + select SOC_ESP32S3_PROCPU if BOARD_HELTEC_WIRELESS_TRACKER_V2_ESP32S3_PROCPU diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/README.md b/zephcore/boards/esp32/heltec_wireless_tracker_v2/README.md new file mode 100644 index 0000000..12ce363 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/README.md @@ -0,0 +1,79 @@ +# Heltec Wireless Tracker V2 + +ZephCore board string: + +```bash +west build -b heltec_wireless_tracker_v2/esp32s3/procpu zephcore --pristine --sysbuild +``` + +Build-verified ZephCore roles: + +- Companion: default build +- Repeater: `-DEXTRA_CONF_FILE=boards/common/repeater.conf` +- Room server: `-DEXTRA_CONF_FILE=boards/common/room_server.conf` +- Observer: `-DEXTRA_CONF_FILE=boards/common/observer.conf` +- Repeater uplink: `-DEXTRA_CONF_FILE="boards/common/repeater.conf;boards/common/repeater_uplink.conf"` + +Flash with the ESP32 runner after a sysbuild: + +```bash +west flash --build-dir build --esp-device /dev/ttyACM0 +``` + +On macOS, replace the device with the board's `/dev/cu.usbmodem*` path. If the +ROM bootloader does not auto-enter download mode, connect USB-C, hold `USER`, +tap `RST`, then release `USER`. + +The equivalent manual sysbuild flash layout is: + +```bash +esptool.py --chip esp32s3 --port /dev/ttyACM0 write_flash \ + 0x00000 build/mcuboot/zephyr/zephyr.bin \ + 0x20000 build/zephcore/zephyr/zephyr.signed.bin +``` + +## Hardware + +| Function | ESP32-S3 GPIO | ZephCore node/property | Source | +| --- | --- | --- | --- | +| LoRa NSS | GPIO8 | `&spi2 cs-gpios` | Heltec schematic, MeshCore | +| LoRa SCK/MOSI/MISO | GPIO9/GPIO10/GPIO11 | `&spi2` pinctrl | Heltec schematic, MeshCore | +| LoRa RST/BUSY/DIO1 | GPIO12/GPIO13/GPIO14 | `reset-gpios`, `busy-gpios`, `dio1-gpios` | Heltec schematic, MeshCore | +| SX1262 DIO2 RF switch | SX1262 DIO2 | `dio2-tx-enable` | MeshCore | +| SX1262 DIO3 TCXO | SX1262 DIO3 | `dio3-tcxo-voltage = 1.8V` | MeshCore | +| VFEM_Ctrl | GPIO7 | `fem_power` regulator | Heltec schematic, MeshCore | +| PA_CSD | GPIO4 | `antenna-enable-gpios` | Heltec schematic, MeshCore | +| PA_CTX | GPIO5 | `tx-enable-gpios` | Heltec schematic, MeshCore | +| PA_CPS | GPIO46 | Unused/unclaimed | Heltec schematic/pin map | +| UC6580 GNSS UART | RX GPIO33, TX GPIO34 | `&uart2`, `gnss-nmea-generic` | Heltec schematic, MeshCore | +| GNSS_RST | GPIO35 | `gps-enable` alias, active-high run | Heltec schematic, MeshCore | +| GNSS PPS | GPIO36 | Unused input | Heltec schematic | +| TFT CS/SCLK/MOSI | GPIO38/GPIO41/GPIO42 | `&spi3` pinctrl | Heltec schematic, MeshCore | +| TFT DC/RST | GPIO40/GPIO39 | MIPI-DBI `dc-gpios`, `reset-gpios` | Heltec schematic, MeshCore | +| TFT backlight | GPIO21 | `disp_pwr_enable` regulator | Heltec schematic, MeshCore | +| Vext rail | GPIO3 | `vext_enable`/`tft_pwr_enable` regulator | Heltec schematic, MeshCore | +| Battery ADC | GPIO1 | `zephyr,user io-channels = <&adc0 0>` | Heltec schematic, MeshCore | +| Battery ADC enable | GPIO2 | `vbat_enable` regulator | Heltec schematic, MeshCore | +| Status/TX LED | GPIO18 | `led0`, `lora-tx-led` | Heltec schematic, MeshCore | +| User button | GPIO0 | `sw0` | Heltec schematic, MeshCore | +| I2C header | SDA GPIO6, SCL GPIO17 | `&i2c0`, sensors auto-detect | MeshCore | + +## Notes + +- This is a separate first-class board from Zephyr's upstream + `heltec_wireless_tracker` target, which models the V1.1 board. +- Build validation covers the ESP32-S3FN8 CPU/flash layout, native USB + serial/JTAG console selection, SX1262 SPI/control devicetree, UC6580 UART + wiring, status LED, user button, battery ADC node, ST7735R display node, and + the ZephCore role link paths listed above. +- Hardware has not been exercised in this workspace. Treat LoRa RF switching, + GNSS runtime behavior, battery calibration, TFT orientation/backlight, USB + flashing auto-reset, and LED/button polarity as untested until checked on a + physical Wireless Tracker V2. +- Battery ADC is on ESP32-S3 ADC unit 1 (`&adc0`) channel 0, GPIO1. ADC unit 2 + channel 0 is GPIO11 and must stay disabled because GPIO11 is LoRa MISO. +- MeshCore's V2 target drives `PA_CSD` and `PA_CTX` but does not drive + `PA_CPS`/GPIO46. ZephCore leaves GPIO46 unclaimed pending hardware validation + of that KCT8103L control path. +- `GNSS_RST` is exposed through ZephCore's `gps-enable` alias as an active-high + logical "run" line: high releases reset, low holds UC6580 off. diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.cmake b/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.cmake new file mode 100644 index 0000000..f3c3ac1 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.cmake @@ -0,0 +1,7 @@ +if(NOT "${OPENOCD}" MATCHES "^${ESPRESSIF_TOOLCHAIN_PATH}/.*") + set(OPENOCD OPENOCD-NOTFOUND) +endif() +find_program(OPENOCD openocd PATHS ${ESPRESSIF_TOOLCHAIN_PATH}/openocd-esp32/bin NO_DEFAULT_PATH) + +include(${ZEPHYR_BASE}/boards/common/esp32.board.cmake) +include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.conf b/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.conf new file mode 100644 index 0000000..8d62d4e --- /dev/null +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.conf @@ -0,0 +1,25 @@ +# Heltec Wireless Tracker V2 — ESP32-S3FN8, SX1262, UC6580 GNSS +# +# Hardware: +# SX1262 + KCT8103L PA/FEM +# ST7735R 160x80 TFT on SPI3 +# UC6580 GNSS on UART2 +# User button on GPIO0, status/TX LED on GPIO18 +# +# Pin sources: Heltec Wireless Tracker V2.3 schematic/datasheet and MeshCore +# variants/heltec_tracker_v2. + +CONFIG_ZEPHCORE_BOARD_NAME="Heltec Tracker V2" +CONFIG_BT_DIS_MODEL_NUMBER_STR="Heltec Wireless Tracker V2" + +# ESP32-S3FN8 has 8 MB flash. Override esp32_common.conf's 4 MB default. +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y + +# MeshCore enables the Heltec SX126x register patch for this variant. +CONFIG_ZEPHCORE_SX126X_HELTEC_REG_PATCH=y + +# SX1262 input drive into the KCT8103L PA. +# MeshCore's V2 default is 9 dBm, yielding roughly 22 dBm at the antenna with +# the PA enabled. The module is physically capable of higher output; start +# conservative to avoid overdriving the front end. +CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=9 diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.overlay b/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.overlay new file mode 100644 index 0000000..9039034 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.overlay @@ -0,0 +1,98 @@ +/* + * SPDX-License-Identifier: MIT + * Heltec Wireless Tracker V2 — ZephCore overlay + * + * The full DTS defines: SX1262 (lora0), ST7735R 160x80 TFT, UC6580 GNSS, + * button, status LED, battery ADC, Vext, FEM power, I2C0, SPI2/3, BLE, WiFi. + * This overlay adds ZephCore-specific integration on top. + */ + +#include + +/ { + chosen { + /* Native USB serial/JTAG for ZephCore console and CLI. */ + zephyr,console = &usb_serial; + zephyr,shell-uart = &usb_serial; + + /* Mono-TFT wrapper: presents PIXEL_FORMAT_MONO01 to CFB and converts + * each row to RGB565 for the ST7735R colour TFT. */ + zephyr,display = &mono_tft; + }; + + mono_tft: mono-tft { + compatible = "zephcore,mono-tft"; + tft-dev = <&st7735r_160x80>; + width = <160>; + height = <80>; + }; + + /* Button chain: short tap scrolls pages, double tap goes back, long press enters. */ + user_btn_longpress { + compatible = "zephyr,input-longpress"; + input = <&buttons>; + input-codes = ; + short-codes = ; + long-codes = ; + long-delay-ms = <1000>; + }; + + page_btn_multitap { + compatible = "zephcore,input-multi-tap"; + input-codes = ; + tap-codes = ; + tap-delay-ms = <400>; + }; +}; + +/* I2C sensors — auto-detected at runtime. Header pins per MeshCore: + * SDA=GPIO6, SCL=GPIO17. */ +&i2c0 { + #include "../../common/sensors-i2c.dtsi" +}; + +/* + * Flash partitions — ZephCore 8 MB layout matching the ESP32-S3-FN8 tracker + * class. Delete unused lpcore slots, storage, scratch, and coredump partitions. + * Place LittleFS at 0x7A0000 - 0x800000 (384 KB). + */ +/delete-node/ &slot0_lpcore_partition; +/delete-node/ &slot1_lpcore_partition; +/delete-node/ &storage_partition; +/delete-node/ &scratch_partition; +/delete-node/ &coredump_partition; +/delete-node/ &slot0_partition; +/delete-node/ &slot1_partition; + +/ { + chosen { + zephyr,settings-partition = &storage_partition; + }; +}; + +&flash0 { + partitions { + slot0_partition: partition@20000 { + label = "image-0"; + reg = <0x20000 0x3BE000>; + }; + slot1_partition: partition@3de000 { + label = "image-1"; + reg = <0x3DE000 0x3BE000>; + }; + storage_partition: partition@79c000 { + label = "storage"; + reg = <0x79C000 0x4000>; + }; + lfs_partition: partition@7a0000 { + label = "lfs"; + reg = <0x7A0000 0x60000>; + }; + }; +}; + +/* LittleFS auto-mount — standard /lfs mount point */ +#include "../../common/filesystem.dtsi" + +/* USB CDC-ACM companion transport when built with boards/common/esp32s3_usb.conf. */ +#include "../../common/esp32s3_usb_otg.dtsi" diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.yml b/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.yml new file mode 100644 index 0000000..55ef79c --- /dev/null +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/board.yml @@ -0,0 +1,6 @@ +board: + name: heltec_wireless_tracker_v2 + full_name: Heltec Wireless Tracker V2 + vendor: heltec + socs: + - name: esp32s3 diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2-pinctrl.dtsi b/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2-pinctrl.dtsi new file mode 100644 index 0000000..15c912d --- /dev/null +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2-pinctrl.dtsi @@ -0,0 +1,75 @@ +/* + * SPDX-License-Identifier: MIT + * Heltec Wireless Tracker V2 — pin control definitions + * + * Confirmed by Heltec V2.3 schematic/datasheet and MeshCore + * variants/heltec_tracker_v2. + * + * UART0: TX=GPIO43, RX=GPIO44 + * UART2/GNSS: MCU TX=GPIO34, MCU RX=GPIO33 + * I2C0 header: SDA=GPIO6, SCL=GPIO17 + * SPI2 LoRa: SCLK=GPIO9, MOSI=GPIO10, MISO=GPIO11, NSS=GPIO8 via GPIO CS + * SPI3 TFT: SCLK=GPIO41, MOSI=GPIO42, CS=GPIO38 + */ + +#include +#include +#include + +&pinctrl { + uart0_default: uart0_default { + group1 { + pinmux = ; + output-high; + }; + + group2 { + pinmux = ; + bias-pull-up; + }; + }; + + uart2_default: uart2_default { + group1 { + pinmux = ; + output-high; + }; + + group2 { + pinmux = ; + bias-pull-up; + }; + }; + + i2c0_default: i2c0_default { + group1 { + pinmux = , + ; + drive-open-drain; + }; + }; + + spim2_default: spim2_default { + group1 { + pinmux = , + ; + }; + + group2 { + pinmux = ; + output-low; + }; + }; + + spim3_default: spim3_default { + group1 { + pinmux = , + ; + }; + + group2 { + pinmux = ; + output-low; + }; + }; +}; diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts b/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts new file mode 100644 index 0000000..489e299 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu.dts @@ -0,0 +1,284 @@ +/* + * SPDX-License-Identifier: MIT + * Heltec Wireless Tracker V2 — ZephCore procpu DTS + * + * Hardware: + * MCU: ESP32-S3FN8, 8 MB flash + * Radio: SX1262 on SPI2, KCT8103L external PA/FEM + * GNSS: UC6580 on UART2 + * TFT: ST7735R-compatible 0.96" 160x80 on SPI3 + * Button: GPIO0 + * LED: GPIO18 + * + * Pin mappings are verified against: + * - Heltec Wireless Tracker V2.3 schematic and V2 datasheet + * - MeshCore variants/heltec_tracker_v2 + * + * PA_CPS appears on GPIO46 in the Heltec pin map/schematic but MeshCore's V2 + * variant does not drive it. ZephCore leaves GPIO46 unclaimed until the exact + * KCT8103L CPS behavior for this revision is confirmed. + */ + +/dts-v1/; + +#include +#include +#include +#include +#include +#include "heltec_wireless_tracker_v2-pinctrl.dtsi" + +/ { + model = "Heltec Wireless Tracker V2 PROCPU"; + compatible = "heltec,wireless-tracker-v2", "espressif,esp32s3"; + + aliases { + led0 = &status_led; + lora-tx-led = &status_led; + uart-0 = &uart0; + uart-2 = &uart2; + i2c-0 = &i2c0; + lora0 = &lora0; + sw0 = &button0; + watchdog0 = &wdt0; + gnss = &gnss; + gps-enable = &gps_enable_pin; + display0 = &st7735r_160x80; + }; + + chosen { + zephyr,sram = &sram1; + zephyr,console = &usb_serial; + zephyr,shell-uart = &usb_serial; + zephyr,flash = &flash0; + zephyr,code-partition = &slot0_partition; + zephyr,bt-hci = &esp32_bt_hci; + zephyr,display = &st7735r_160x80; + }; + + leds { + compatible = "gpio-leds"; + + status_led: led_0 { + gpios = <&gpio0 18 GPIO_ACTIVE_HIGH>; + label = "Status LED"; + }; + }; + + buttons: buttons { + compatible = "gpio-keys"; + + button0: button_0 { + gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>; + label = "USER SW"; + zephyr,code = ; + }; + }; + + /* Vext powers the TFT and UC6580 GNSS rail. Keep it on; ZephCore gates + * GNSS by holding/releasing the GNSS reset line and gates only the TFT + * backlight at runtime. */ + vext_enable: tft_pwr_enable: vext-enable { + compatible = "regulator-fixed"; + regulator-name = "vext"; + enable-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>; + regulator-boot-on; + regulator-always-on; + startup-delay-us = <10000>; + }; + + /* GPIO21 drives the TFT backlight switch. */ + disp_pwr_enable: disp-pwr-enable { + compatible = "regulator-fixed"; + regulator-name = "disp-pwr-enable"; + enable-gpios = <&gpio0 21 GPIO_ACTIVE_HIGH>; + }; + + /* GPIO35 is the UC6580 reset/run line. HIGH releases reset/runs GNSS. */ + gps_en: gps-enable { + compatible = "gpio-leds"; + + gps_enable_pin: gps_enable { + gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>; + label = "GPS Enable"; + }; + }; + + /* FEM LDO power supply: VFEM_Ctrl = GPIO7. */ + fem_power: fem-power { + compatible = "regulator-fixed"; + regulator-name = "fem-power"; + enable-gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>; + regulator-boot-on; + regulator-always-on; + startup-delay-us = <1000>; + }; + + /* ADC_Ctrl = GPIO2. ZephCore enables it only while sampling battery. */ + vbat_enable: vbat-enable { + compatible = "regulator-fixed"; + regulator-name = "vbat-enable"; + enable-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; + }; + + zephyr,user { + io-channels = <&adc0 0>; + /* MeshCore V2 uses a 5.42x empirical battery scale. With ESP32-S3 + * ADC_REF_INTERNAL (1100 mV), raw-to-battery multiplier is 5962 mV. */ + vbat-mv-multiplier = <5962>; + }; + + mipi_dbi_st7735r_160x80 { + compatible = "zephyr,mipi-dbi-spi"; + spi-dev = <&spi3>; + dc-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>; /* GPIO40 */ + reset-gpios = <&gpio1 7 GPIO_ACTIVE_LOW>; /* GPIO39 */ + write-only; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + st7735r_160x80: st7735r@0 { + compatible = "sitronix,st7735r"; + mipi-max-frequency = <24000000>; + mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE"; + reg = <0>; + width = <160>; + height = <80>; + inversion-on; + x-offset = <1>; + y-offset = <26>; + madctl = <0xa8>; + colmod = <0x05>; + invctr = <7>; + vmctr1 = <0x0e>; + pwctr1 = [a2 02 84]; + pwctr2 = [c1]; + pwctr3 = [0a 00]; + pwctr4 = [8a 2a]; + pwctr5 = [8a ee]; + frmctr1 = [01 26 2e]; + frmctr2 = [01 26 2e]; + frmctr3 = [01 26 2e 01 26 2e]; + gamctrp1 = [0f 1a 0f 18 2f 28 20 22 1f 1b 23 37 00 07 02 10]; + gamctrn1 = [0f 1b 0f 17 33 2c 29 2e 30 2e 30 3b 00 07 03 10]; + caset = [00 01 00 a0]; + raset = [00 1a 00 69]; + }; + }; +}; + +&adc0 { + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@0 { + reg = <0>; + zephyr,gain = "ADC_GAIN_1"; + zephyr,reference = "ADC_REF_INTERNAL"; + zephyr,acquisition-time = ; + zephyr,resolution = <12>; + }; +}; + +/* ADC unit 2 channel 0 maps to GPIO11 on ESP32-S3, which is LoRa MISO here. */ +&adc1 { + status = "disabled"; +}; + +&uart0 { + status = "okay"; + current-speed = <115200>; + pinctrl-0 = <&uart0_default>; + pinctrl-names = "default"; +}; + +&uart2 { + status = "okay"; + current-speed = <115200>; + pinctrl-0 = <&uart2_default>; + pinctrl-names = "default"; + + gnss: gnss-nmea-generic { + compatible = "gnss-nmea-generic"; + status = "okay"; + }; +}; + +&gpio0 { + status = "okay"; +}; + +&gpio1 { + status = "okay"; +}; + +&cpu0 { + clock-frequency = ; +}; + +&cpu1 { + clock-frequency = ; +}; + +&i2c0 { + status = "okay"; + clock-frequency = ; + pinctrl-0 = <&i2c0_default>; + pinctrl-names = "default"; +}; + +&spi2 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + pinctrl-0 = <&spim2_default>; + pinctrl-names = "default"; + cs-gpios = <&gpio0 8 GPIO_ACTIVE_LOW>; + + lora0: lora@0 { + compatible = "semtech,sx1262"; + reg = <0>; + reset-gpios = <&gpio0 12 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; + busy-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; + dio1-gpios = <&gpio0 14 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; + + /* KCT8103L control per MeshCore: + * - PA_CSD/GPIO4: active-HIGH FEM enable + * - PA_CTX/GPIO5: HIGH=TX, LOW=RX/LNA + * - SX1262 DIO2: internal RF switch TX select */ + antenna-enable-gpios = <&gpio0 4 GPIO_ACTIVE_HIGH>; + tx-enable-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>; + dio2-tx-enable; + + dio3-tcxo-voltage = ; + tcxo-power-startup-delay-ms = <5>; + rx-boosted; + spi-max-frequency = <4000000>; + status = "okay"; + }; +}; + +&spi3 { + #address-cells = <1>; + #size-cells = <0>; + status = "okay"; + pinctrl-0 = <&spim3_default>; + pinctrl-names = "default"; + cs-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>; /* GPIO38 */ + use-iomux; +}; + +&timer0 { status = "okay"; }; +&timer1 { status = "okay"; }; +&timer2 { status = "okay"; }; +&timer3 { status = "okay"; }; + +&wdt0 { status = "okay"; }; +&trng0 { status = "okay"; }; + +&esp32_bt_hci { status = "okay"; }; +&wifi { status = "okay"; }; +&usb_serial { status = "okay"; }; +&st7735r_160x80 { status = "okay"; }; diff --git a/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu_defconfig b/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu_defconfig new file mode 100644 index 0000000..1a2dc6d --- /dev/null +++ b/zephcore/boards/esp32/heltec_wireless_tracker_v2/heltec_wireless_tracker_v2_procpu_defconfig @@ -0,0 +1,13 @@ +# Heltec Wireless Tracker V2 — minimal Zephyr defconfig (procpu) +CONFIG_CLOCK_CONTROL=y +CONFIG_CONSOLE=y +CONFIG_SERIAL=y +CONFIG_UART_CONSOLE=y +CONFIG_GPIO=y +CONFIG_UART_INTERRUPT_DRIVEN=y +CONFIG_PINCTRL=y +CONFIG_SPI=y +CONFIG_I2C=y +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_NVS=y diff --git a/zephcore/boards/example_board/README.md b/zephcore/boards/example_board/README.md index d7c1a53..fa9c0e2 100644 --- a/zephcore/boards/example_board/README.md +++ b/zephcore/boards/example_board/README.md @@ -43,7 +43,8 @@ SWD flash: `west flash` (requires J-Link, pyocd, or nrfjprog connected). | Heltec V3 | `west build -b heltec_wifi_lora32_v3/esp32s3/procpu zephcore` | `west flash` | | Heltec V4.2 (GC1109 PA) | `west build -b heltec_wifi_lora32_v4/esp32s3/procpu zephcore` | `west flash` | | Heltec V4.3 (KCT8103L PA) | `west build -b heltec_wifi_lora32_v43/esp32s3/procpu zephcore` | `west flash` | -| Heltec Wireless Tracker | `west build -b heltec_wireless_tracker/esp32s3/procpu zephcore` | `west flash` | +| Heltec Wireless Tracker V1.1 | `west build -b heltec_wireless_tracker/esp32s3/procpu zephcore` | `west flash` | +| Heltec Wireless Tracker V2 | `west build -b heltec_wireless_tracker_v2/esp32s3/procpu zephcore` | `west flash` | | LilyGo T-Beam v1.2 | `west build -b ttgo_tbeam/esp32/procpu zephcore` | `west flash` | **Heltec V3 console:** ZephCore routes console/shell to `uart0` on V3. Use the UART serial port for boot logs and CLI. @@ -53,6 +54,11 @@ unclear, check GPIO2's default pull: the V4.2 GC1109 PA has an internal pull-dow (GPIO2 reads LOW at boot), while the V4.3 KCT8103L PA has an internal pull-up (GPIO2 reads HIGH). Only difference in firmware: TX control pin GPIO46→GPIO5. +**Heltec Wireless Tracker V1.1 vs V2:** use `heltec_wireless_tracker` for the +upstream Zephyr V1.1 target and `heltec_wireless_tracker_v2` for the ESP32-S3FN8 +V2 board with KCT8103L PA/FEM. The V2 pin map is documented in +`boards/esp32/heltec_wireless_tracker_v2/README.md`. + **LilyGo T-Beam v1.2:** Classic ESP32 (PICO-D4) board — several caveats apply: - Upstream Zephyr DTS models the SX1276 variant; `board.overlay` overrides the radio node to SX1262 on the same SPI3 wiring. - PICO-D4 rev 1.0 bootloops when the bootloader tries to enable QIO flash mode. `board.conf` forces DIO (`CONFIG_ESPTOOLPY_FLASHMODE_DIO=y`). Any new classic ESP32 board with PICO-D4 needs this. diff --git a/zephcore/boards/supported_boards.md b/zephcore/boards/supported_boards.md index 53931ce..3e717ff 100644 --- a/zephcore/boards/supported_boards.md +++ b/zephcore/boards/supported_boards.md @@ -41,6 +41,7 @@ heltec_wifi_lora32_v3/esp32s3/procpu heltec_wifi_lora32_v4/esp32s3/procpu heltec_wifi_lora32_v43/esp32s3/procpu heltec_wireless_tracker/esp32s3/procpu +heltec_wireless_tracker_v2/esp32s3/procpu ttgo_tbeam/esp32/procpu ``` @@ -51,6 +52,10 @@ ttgo_tbeam/esp32/procpu > **Heltec Wireless Tracker** (`heltec_wireless_tracker/esp32s3/procpu`): V1.1 > ESP32-S3-FN8 companion with SX1262, ST7735R 160x80 TFT, and UC6580 GPS. > +> **Heltec Wireless Tracker V2** (`heltec_wireless_tracker_v2/esp32s3/procpu`): +> ESP32-S3FN8 companion with SX1262 + KCT8103L PA/FEM, ST7735R 160x80 TFT, +> UC6580 GNSS, battery ADC, and USB-C native serial/JTAG. +> > **LilyGo T-Beam** (`ttgo_tbeam/esp32/procpu`): classic ESP32 (PICO-D4) with > SX1262, AXP2101 PMU, and GNSS. Use this for the **v1.2 SX1262** variant — the > upstream Zephyr DTS models the SX1276 radio, which ZephCore overrides to From d5efd3351a57d395d0744977400185ef93b7cecd Mon Sep 17 00:00:00 2001 From: Matthew Burton <19491784+matthew73210@users.noreply.github.com> Date: Sat, 4 Jul 2026 07:42:10 +0200 Subject: [PATCH 3/3] fixed apc text on screen --- zephcore/helpers/ui/ui_headless_stubs.c | 2 + zephcore/helpers/ui/ui_task.h | 6 ++ zephcore/src/main_repeater.cpp | 73 +++++++++++++++++++------ 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/zephcore/helpers/ui/ui_headless_stubs.c b/zephcore/helpers/ui/ui_headless_stubs.c index c11a495..8112e62 100644 --- a/zephcore/helpers/ui/ui_headless_stubs.c +++ b/zephcore/helpers/ui/ui_headless_stubs.c @@ -68,6 +68,8 @@ WEAK void ui_set_radio_stats(uint32_t packets_rx, uint32_t packets_tx, ARG_UNUSED(packets_rx); ARG_UNUSED(packets_tx); ARG_UNUSED(packets_err); } +WEAK void ui_refresh_display(void) { } + WEAK void ui_set_gps_data(bool has_fix, uint8_t sats, int32_t lat_mdeg, int32_t lon_mdeg, int32_t alt_mm) { diff --git a/zephcore/helpers/ui/ui_task.h b/zephcore/helpers/ui/ui_task.h index be98a0a..334cfca 100644 --- a/zephcore/helpers/ui/ui_task.h +++ b/zephcore/helpers/ui/ui_task.h @@ -99,6 +99,12 @@ void ui_set_radio_runtime(int8_t effective_tx_power, bool apc_enabled, void ui_set_radio_stats(uint32_t packets_rx, uint32_t packets_tx, uint32_t packets_err); +/** + * Request an immediate display redraw for the current UI state. + * No-op on headless builds. + */ +void ui_refresh_display(void); + /** * Update GPS data for display. */ diff --git a/zephcore/src/main_repeater.cpp b/zephcore/src/main_repeater.cpp index f7af7f7..266e36b 100644 --- a/zephcore/src/main_repeater.cpp +++ b/zephcore/src/main_repeater.cpp @@ -141,6 +141,7 @@ K_TIMER_DEFINE(housekeeping_timer, housekeeping_timer_fn, NULL); /* Forward declarations */ #ifdef ZEPHCORE_LORA static RepeaterMesh *repeater_mesh_ptr; +static void refresh_repeater_ui_radio_state(bool redraw); #endif /* Print string to USB serial */ @@ -233,6 +234,7 @@ static void process_cli_commands(void) while (repeater_mesh_ptr && k_msgq_get(&cli_cmd_queue, &c, K_NO_WAIT) == 0) { cli_reply_buf[0] = '\0'; repeater_mesh_ptr->handleCommand(0, c.buf, cli_reply_buf); + refresh_repeater_ui_radio_state(true); if (cli_reply_buf[0] != '\0') { cli_print("\r\n -> "); cli_print(cli_reply_buf); @@ -358,6 +360,55 @@ static mesh::SimpleMeshTables mesh_tables; /* RepeaterMesh requires: board, radio, ms_clock, rng, rtc, tables */ static RepeaterMesh repeater_mesh(zephyr_board, lora_radio, ms_clock, zephyr_rng, rtc_clock, mesh_tables); + +static void refresh_repeater_ui_radio_state(bool redraw) +{ + if (!repeater_mesh_ptr) { + return; + } + + ui_set_radio_params( + lora_radio.getActiveFrequencyHz(), + lora_radio.getActiveSpreadingFactor(), + lora_radio.getActiveBandwidthKHzX10(), + lora_radio.getActiveCodingRate(), + lora_radio.getConfiguredTxPower(), + lora_radio.getNoiseFloor()); + + bool apc_enabled = false; + int8_t apc_reduction = 0; + int16_t apc_margin_x10 = 0; + uint8_t apc_target = repeater_mesh_ptr->getNodePrefs()->apc_margin; + +#ifdef CONFIG_ZEPHCORE_APC + apc_enabled = repeater_mesh_ptr->isAPCEnabled(); + apc_reduction = repeater_mesh_ptr->getAPCReduction(); + apc_margin_x10 = (int16_t)(repeater_mesh_ptr->getAPCMargin() * 10.0f); + apc_target = repeater_mesh_ptr->getAPCTargetMargin(); +#endif + + ui_set_radio_runtime( + lora_radio.getEffectiveTxPower(), + apc_enabled, + apc_reduction, + apc_margin_x10, + apc_target, + lora_radio.getActiveSyncWord(), + lora_radio.getActivePreambleLength(), + lora_radio.isRxDutyCycleEnabled(), + lora_radio.isRadioReady(), + lora_radio.isInRecvMode(), + lora_radio.isTxActive()); + + ui_set_radio_stats( + lora_radio.getPacketsRecv(), + lora_radio.getPacketsSent(), + lora_radio.getPacketsRecvErrors()); + + if (redraw) { + ui_refresh_display(); + } +} #endif /* Repeater event loop */ @@ -425,17 +476,9 @@ static void repeater_event_loop(void) ui_set_clock(rtc_clock.getCurrentTime()); #ifdef ZEPHCORE_LORA - /* Refresh radio params (noise floor changes from calibration) */ - if (repeater_mesh_ptr) { - NodePrefs *p = repeater_mesh_ptr->getNodePrefs(); - ui_set_radio_params( - (uint32_t)(p->freq * 1000000.0f + 0.5f), - p->sf, - (uint16_t)(p->bw * 10.0f + 0.5f), - p->cr, - p->tx_power_dbm, - lora_radio.getNoiseFloor()); - } + /* Refresh live radio/APC state (noise floor, TX power + * reduction, RX/TX mode, packet counters). */ + refresh_repeater_ui_radio_state(true); /* Battery is now refreshed lazily from ui_pages_render() with * a 30 s freshness guard — no periodic ADC fire here. */ @@ -588,13 +631,7 @@ int main(void) /* Feed initial UI state from loaded prefs */ ui_set_node_name(prefs->node_name); - ui_set_radio_params( - (uint32_t)(prefs->freq * 1000000.0f + 0.5f), /* MHz → Hz */ - prefs->sf, - (uint16_t)(prefs->bw * 10.0f + 0.5f), /* kHz → 0.1 kHz */ - prefs->cr, - prefs->tx_power_dbm, - lora_radio.getNoiseFloor()); + refresh_repeater_ui_radio_state(false); ui_set_battery_provider(get_battery_mv); ui_set_battery(zephyr_board.getBattMilliVolts(), 0); ui_set_gps_available(gps_is_available());