Merge pull request #47 from matthew73210/master

Reworked the screen pages
This commit is contained in:
liquidraver
2026-07-04 12:51:15 +02:00
committed by GitHub
20 changed files with 753 additions and 36 deletions
+2 -1
View File
@@ -48,7 +48,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 |
| **TTGO LoRa32** | ESP32 (PICO-D4) | **SX1276** (loramac-node backend) | SX127x reference board, USB-UART CLI |
+1
View File
@@ -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
)
@@ -0,0 +1,3 @@
config HEAP_MEM_POOL_ADD_SIZE_BOARD
int
default 4096
@@ -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
@@ -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.
@@ -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)
@@ -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
@@ -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 <zephyr/dt-bindings/input/input-event-codes.h>
/ {
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 = <INPUT_KEY_0>;
short-codes = <INPUT_KEY_A>;
long-codes = <INPUT_KEY_ENTER>;
long-delay-ms = <1000>;
};
page_btn_multitap {
compatible = "zephcore,input-multi-tap";
input-codes = <INPUT_KEY_A>;
tap-codes = <INPUT_KEY_1 INPUT_KEY_LEFT>;
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"
@@ -0,0 +1,6 @@
board:
name: heltec_wireless_tracker_v2
full_name: Heltec Wireless Tracker V2
vendor: heltec
socs:
- name: esp32s3
@@ -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 <zephyr/dt-bindings/pinctrl/esp-pinctrl-common.h>
#include <zephyr/dt-bindings/pinctrl/esp32s3-pinctrl.h>
#include <zephyr/dt-bindings/pinctrl/esp32s3-gpio-sigmap.h>
&pinctrl {
uart0_default: uart0_default {
group1 {
pinmux = <UART0_TX_GPIO43>;
output-high;
};
group2 {
pinmux = <UART0_RX_GPIO44>;
bias-pull-up;
};
};
uart2_default: uart2_default {
group1 {
pinmux = <UART2_TX_GPIO34>;
output-high;
};
group2 {
pinmux = <UART2_RX_GPIO33>;
bias-pull-up;
};
};
i2c0_default: i2c0_default {
group1 {
pinmux = <I2C0_SDA_GPIO6>,
<I2C0_SCL_GPIO17>;
drive-open-drain;
};
};
spim2_default: spim2_default {
group1 {
pinmux = <SPIM2_MISO_GPIO11>,
<SPIM2_SCLK_GPIO9>;
};
group2 {
pinmux = <SPIM2_MOSI_GPIO10>;
output-low;
};
};
spim3_default: spim3_default {
group1 {
pinmux = <SPIM3_SCLK_GPIO41>,
<SPIM3_CSEL_GPIO38>;
};
group2 {
pinmux = <SPIM3_MOSI_GPIO42>;
output-low;
};
};
};
@@ -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 <espressif/esp32s3/esp32s3_fn8.dtsi>
#include <espressif/partitions_0x0_amp_8M.dtsi>
#include <zephyr/dt-bindings/adc/adc.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/dt-bindings/lora/sx126x.h>
#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 = <INPUT_KEY_0>;
};
};
/* 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 = <ADC_ACQ_TIME_DEFAULT>;
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 = <DT_FREQ_M(160)>;
};
&cpu1 {
clock-frequency = <DT_FREQ_M(160)>;
};
&i2c0 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
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 = <SX126X_DIO3_TCXO_1V8>;
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"; };
@@ -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
+7 -1
View File
@@ -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.
+5
View File
@@ -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
ttgo_lora32/esp32/procpu
```
@@ -52,6 +53,10 @@ ttgo_lora32/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
+80 -15
View File
@@ -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,
@@ -435,7 +438,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++) {
@@ -488,17 +491,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;
@@ -506,7 +513,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;
@@ -515,7 +522,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;
}
@@ -749,6 +755,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()) {
@@ -1252,6 +1316,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,
+1
View File
@@ -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 */
+1 -1
View File
@@ -925,7 +925,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
+2
View File
@@ -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)
{
+6
View File
@@ -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.
*/
+55 -18
View File
@@ -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. */
@@ -594,13 +637,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());