heltec t1

This commit is contained in:
liquidraver
2026-09-14 09:14:46 +02:00
parent 8c21a1609b
commit 16518e2196
14 changed files with 946 additions and 2 deletions
+1
View File
@@ -23,6 +23,7 @@ nRF_boards=(
promicro_sx1262
heltec_t114
heltec_t096
heltec_t1
gat562_30s
muziworks_r1neo
)
+3
View File
@@ -22,6 +22,7 @@ promicro_sx1262
promicro_lr2021
heltec_t114
heltec_t096
heltec_t1
gat562_30s
muziworks_r1neo
lilygo_timpulse_plus
@@ -35,6 +36,8 @@ lilygo_timpulse_plus
>
> **Heltec Mesh Node T096** (`heltec_t096`): nRF52840 with SX1262 + KCT8103L PA/FEM, UC6580 GNSS, and ST7735S 160x80 TFT companion display. The external SPI flash footprint is documented in the board notes but left disabled until the device parameters are confirmed.
>
> **Heltec Mesh Node T1** (`heltec_t1`): nRF52840 + SX1262 handheld tracker card with UC6580 L1+L5 GNSS, ST7735S 160x80 TFT, PAM8904-driven piezo buzzer, two buttons and a 1850 mAh battery. No PA/FEM — the SX1262 drives a uPG2179 antenna switch from DIO2. The onboard ICM-42607-P/MMC5983MA IMU bus is left powered down (no ZephCore consumer). Untested on hardware; the GNSS UART direction and the display offsets are the two things to check first, both flagged in the board README.
>
> **LilyGo T-Impulse Plus** (`lilygo_timpulse_plus`): nRF52840 wristband/tracker with SX1262 (GPIO antenna switch, not DIO2), SSD1315 64x32 OLED, u-blox MIA-M10Q GPS, 4 MB QSPI flash (`/ext`), touch button, haptic motor. Builds and ships in releases, but several parameters are inferred from vendor sources and not bench-verified (TCXO voltage, battery divider ratio, OLED offsets) — see the board README. Early hardware testing also showed occasional spontaneous reboots, untriaged.
>
> **ProMicro LR2021** (`promicro_lr2021`): nRF52840 SuperMini + Semtech LR2021, the first ZephCore board bring-up for that radio. Source-only — **not in `build.sh`, the release workflow, or the Mesh America catalog**, so no release carries a binary for it. The bring-up module was destroyed by an accidental 5V feed (LR2021 VBAT max ~3.7V); build it yourself if you want to continue testing on a fresh module.
+3 -1
View File
@@ -84,6 +84,7 @@ MAKER_BY_DEVICE = {
"ProMicro nrf52 (faketec)": "promicro",
"Heltec T114": "heltec",
"Heltec Mesh Node T096": "heltec",
"Heltec Mesh Node T1": "heltec",
"Heltec v3": "heltec",
"Heltec v4": "heltec",
"Heltec v4.3": "heltec",
@@ -171,6 +172,7 @@ BOARDS = [
dict(stem="heltec_t114", kind="nrf", device="Heltec T114"),
dict(stem="heltec_t114", kind="nrf", device="Heltec T114",
variant="noscreen", subtitle="No screen"),
dict(stem="heltec_t1", kind="nrf", device="Heltec Mesh Node T1"),
dict(stem="gat562_30s", kind="nrf", device="GAT-IoT GAT562 30s"),
dict(stem="muziworks_r1neo", kind="nrf", device="muzi works R1 Neo"),
@@ -264,7 +266,7 @@ SOFTDEVICE = {
"rak4631": 6, "rak3401_1watt": 6, "thinknode_m1": 6, "thinknode_m3": 6,
"thinknode_m6": 6, "rak_wismesh_tag": 6, "lilygo_techo": 6,
"lilygo_timpulse_plus": 6, "promicro_sx1262": 6, "heltec_t114": 6,
"heltec_t096": 6, "gat562_30s": 6, "muziworks_r1neo": 6,
"heltec_t096": 6, "heltec_t1": 6, "gat562_30s": 6, "muziworks_r1neo": 6,
"wio_tracker_l1": 7, "t1000_e": 7, "ikoka_nano_30dbm": 7,
"sensecap_solar": 7, "xiao_nrf52840": 7, "meshtracker_x1": 7,
}
+1 -1
View File
@@ -319,7 +319,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 "rak_wismesh_tag" OR BOARD MATCHES "rak3401_1watt" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "meshtracker_x1" OR BOARD MATCHES "thinknode_m1" OR BOARD MATCHES "thinknode_m3" OR BOARD MATCHES "thinknode_m6" OR BOARD MATCHES "promicro_lr2021" OR BOARD MATCHES "promicro_sx1262" OR BOARD MATCHES "sensecap_solar" OR BOARD MATCHES "xiao_nrf52840" OR BOARD MATCHES "lilygo_techo" OR BOARD MATCHES "lilygo_timpulse_plus" OR BOARD MATCHES "heltec_t114" OR BOARD MATCHES "heltec_t096" OR BOARD MATCHES "gat562" OR BOARD MATCHES "muziworks_r1neo")
if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "rak_wismesh_tag" OR BOARD MATCHES "rak3401_1watt" OR BOARD MATCHES "wio_tracker" OR BOARD MATCHES "ikoka_nano" OR BOARD MATCHES "t1000_e" OR BOARD MATCHES "meshtracker_x1" OR BOARD MATCHES "thinknode_m1" OR BOARD MATCHES "thinknode_m3" OR BOARD MATCHES "thinknode_m6" OR BOARD MATCHES "promicro_lr2021" OR BOARD MATCHES "promicro_sx1262" OR BOARD MATCHES "sensecap_solar" OR BOARD MATCHES "xiao_nrf52840" OR BOARD MATCHES "lilygo_techo" OR BOARD MATCHES "lilygo_timpulse_plus" OR BOARD MATCHES "heltec_t114" OR BOARD MATCHES "heltec_t096" OR BOARD MATCHES "heltec_t1" OR BOARD MATCHES "gat562" OR BOARD MATCHES "muziworks_r1neo")
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")
+1
View File
@@ -27,6 +27,7 @@ Supported Boards
| LilyGo T-Impulse Plus | `west build -b lilygo_timpulse_plus zephcore` | UF2 drag-drop or `west flash` |
| Heltec T114 | `west build -b heltec_t114 zephcore` | UF2 drag-drop or `west flash` |
| Heltec Mesh Node T096 | `west build -b heltec_t096 zephcore` | UF2 drag-drop or `west flash` |
| Heltec Mesh Node T1 | `west build -b heltec_t1 zephcore` | UF2 drag-drop or `west flash` |
| muzi works R1 Neo | `west build -b muziworks_r1neo zephcore` | UF2 drag-drop or `west flash` |
**Heltec T114 screenless:** append `boards/nrf52840/heltec_t114/no_display.conf` to `EXTRA_CONF_FILE` for units without the TFT module.
@@ -0,0 +1,6 @@
# Copyright (c) 2026 ZephCore
#
# SPDX-License-Identifier: MIT
config BOARD_HELTEC_T1
select SOC_NRF52840_QIAA
@@ -0,0 +1,229 @@
# Heltec Mesh Node T1
Custom ZephCore board target for the Heltec Mesh Node T1.
Build:
```bash
west build -b heltec_t1 zephcore --pristine
```
Repeater/observer roles build the same way with the usual `EXTRA_CONF_FILE`
overlays; the T1 is a handheld tracker, so companion is the expected role.
## Hardware
- MCU: nRF52840 (1 MB flash, 256 kB RAM), Adafruit UF2 bootloader, SoftDevice
s140 v6.1.1 layout (app @ `0x26000`, bootloader @ `0xF4000`)
- Radio: SX1262, 32 MHz TCXO on DIO3 @ 1.8 V, no PA/FEM
- RF path: separate RFO (TX) and RFI_P/RFI_N (RX) matched networks into a
uPG2179 SPDT switch driven by SX1262 DIO2. Datasheet table 3.3: 21 ±1 dBm at
the antenna in 863870 and 902928 MHz.
- GNSS: UC6580 (L1+L5, GPS/BDS/GLONASS/Galileo/QZSS/NavIC/SBAS), ATR2652 LNA
and HDDL1L5RSS-B8 diplexer, 26 MHz TCXO, 32.768 kHz RTC crystal
- Display: ST7735S 0.96" TFT, 80×160 native / 160×80 landscape
- Buzzer: piezo element driven differentially by a PAM8904 charge-pump driver
- IMU: ICM-42607-P (accel + gyro) and MMC5983MA (magnetometer) — present but
unused, see "Not enabled"
- Battery: 1850 mAh Li-ion, CN3165 charger (red LED is hardware-only)
- No external SPI/QSPI flash — storage is internal `/lfs` only
- IP65, 85 × 55 × 9.5 mm, 53 g
## Confirmed pinout
Source: official Heltec `Mesh_Node_T1_V1.0` schematic — the pin table on the
sheet, cross-checked against the U10/U11/U14/U18 symbols. Component behaviour
comes from the parts' own datasheets (ST7735S panel spec
`N096-1608TBBIG09-C08`, Diodes PAM8904, Semtech SX1262).
| Function | nRF52840 pin | Schematic net | ZephCore mapping |
| --- | --- | --- | --- |
| LoRa SCK | P1.13 | LOAR_SCK | `spi2` SCK |
| LoRa MOSI | P1.14 | LOAR_MOSI | `spi2` MOSI |
| LoRa MISO | P0.03 | LOAR_MISO | `spi2` MISO |
| LoRa NSS / CS | P1.11 | LOAR_NSS | `cs-gpios` |
| LoRa reset | P0.02 | LOAR_NRSET | `reset-gpios` |
| LoRa busy | P0.29 | LORA_BUSY | `busy-gpios` |
| LoRa DIO1 | P0.31 | DIO1 | `dio1-gpios` |
| Antenna switch | SX1262 DIO2 | ANT_SW_CTRL | `dio2-tx-enable` |
| GNSS UART TX from MCU | P0.07 | GNSS_RX | `uart0` TX |
| GNSS UART RX into MCU | P0.08 | GNSS_TX | `uart0` RX |
| GNSS reset | P0.26 | GNSS_RST | `gps-reset` alias, active-low |
| GNSS rail enable | P0.04 | VGNSS_Ctrl | regulator `gps_power`, active-low |
| GNSS PPS | P1.09 | PPS | documented, not consumed |
| Display SCK | P1.00 | SCLK | `spi3` SCK |
| Display MOSI | P0.24 | SDIN | `spi3` MOSI |
| Display CS | P0.12 | CS | `spi3` `cs-gpios` |
| Display DC | P0.22 | RS | MIPI-DBI `dc-gpios` |
| Display reset | P0.20 | RES | MIPI-DBI `reset-gpios` |
| Display backlight | P0.15 | LED_K | regulator `disp_pwr_enable`, active-low |
| Display panel rail | P0.13 | VScreen_Ctrl | regulator `tft_pwr_enable`, active-low |
| Buzzer tone | P0.09 | DIN | `pwm0` channel 0, `buzzer` alias |
| Buzzer 2× select | P1.02 | EN1 | unclaimed (10K pull-up) |
| Buzzer 3× select | P1.05 | EN2 | unclaimed (10K pull-up) |
| White LED | P0.16 | LED | `led0`, `lora-tx-led`, active-low |
| User button | P1.10 | Button | `sw0`, active-low |
| Function button | P0.14 | User | `KEY_LEFT`, active-low |
| Battery ADC | P0.05 / AIN3 | BAT_ADC | ADC channel 3 |
| Battery ADC enable | P0.11 | ADC_Ctrl | regulator `vbat_enable`, active-high |
| Sensor rail enable | P1.06 | VSensor_Ctrl | regulator `sensor_power`, active-low, held off |
| Sensor I2C SDA | P1.03 | Sensor_SDA | not enabled |
| Sensor I2C SCL | P0.10 | Sensor_SCL | not enabled |
| IMU INT1 / INT2 | P1.01 / P1.07 | INT1 / INT2 | not consumed |
The power button (S1) is not visible to firmware. It feeds the EC190708 latch
that gates the 3V3 LDO enable and, through Q7/Q4, pulls `nRF_RST` low. Per the
datasheet: 3 s hold = on/off, short press = reboot, double-click = DFU.
## Two Heltec document errors, corrected here
**The white LED is on P0.16, not P0.09.** Datasheet §2.2 says "White Function
LED, connected to P0.09". P0.09 is the PAM8904 `DIN` (buzzer). The schematic
puts the LED on P0.16, and Heltec's own nRF52 Arduino variant
(`HT-mesh-node-t1`) agrees.
**The GNSS UART aliases in the schematic's pin table are inverted.** The table
prints `nRF_RX` beside P0.07 and `nRF_TX` beside P0.08. The `GNSS_RX`/`GNSS_TX`
halves of the same rows are correct, and they are the ones to trust:
- `GNSS_RX` lands on UC6580 pin 18, whose symbol pin name is `RX` — an input.
Nothing else drives that node, so P0.07 can only be the nRF's **transmit**
line. Two inputs tied together with no driver is not a circuit.
- `GNSS_TX` lands on UC6580 pin 19 (`TX`, an output), which must feed an MCU
input, so P0.08 is the nRF's **receive** line.
Heltec's own Arduino core (`HelTecAutomation/Heltec_nRF52`,
`variants/HT-mesh-node-t1/variant.h`) sets `PIN_SERIAL1_TX = P0.07` and
`PIN_SERIAL1_RX = P0.08` — the same way round — and it is what their shipped
`GPS_To_Serial` test firmware for this board is built against. Meshtastic's
variant is a copy of it and agrees.
Arduino MeshCore's `variants/heltec_t1/variant.h` has these two swapped
(`PIN_SERIAL1_RX = P0.07`), still on both `main` and `dev`. Their T1 GPS
cannot be receiving.
If a first hardware run shows no NMEA, swapping the two `psels` in
`heltec_t1_nrf52840-pinctrl.dtsi` is the one-line test.
## Display
- Controller: ST7735S, driven by Zephyr's `sitronix,st7735r` driver.
- Panel: Unvision `N096-1608TBBIG09-C08`, 0.96", 80(H)RGB × 160(V), dot pitch
0.135 mm, active area 10.8 × 21.7 mm, normally black, 4-line SPI, one white
LED backlight, 20…+70 °C.
- Bus: SPI3 through MIPI-DBI, **write-only** — the 8-pin FFC has no data-out
line.
- UI path: `zephcore,mono-tft` wraps the colour TFT as a 1bpp display for
ZephCore's CFB UI. At 160×80 with the 6×8 font that is 26 columns × 10 rows,
identical to the T096 and the Heltec Wireless Tracker.
- Orientation: landscape, rotated 180° from the T096 mounting of the same panel
class — `madctl = 0x68` (MX | MV | BGR) against `0xa8` (MY | MV | BGR) there.
Offsets are `x-offset = 0`, `y-offset = 24`, matching the "red tab 160×80"
column-start of 24 / row-start of 0 transposed by MV.
**To check on first boot:** mirroring an 80-column window that sits at GRAM
offset 24 inside the ST7735S's 132 columns should geometrically land it at
28, yet both mainstream Arduino ST7735 drivers keep the same offsets across
rotations 1 and 3. If the image is shifted with a black band on one edge, the
mirrored alternative is `x-offset = 2`, `y-offset = 28`,
`caset = [00 02 00 a1]`, `raset = [00 1c 00 6b]`.
- Power: the panel rail (`VScreen_Ctrl`) is boot-enabled so the ST7735 init
sequence reaches a powered panel. The backlight FET's source is `VDD_3V3`,
**not** the panel rail, so it has to be switched off in its own right —
`disp_pwr_enable` does that and follows ZephCore display on/off.
## Buzzer
The piezo is driven by a PAM8904 charge pump, not a bare transistor. From the
MCU's side it is still one tone input: PWM0 channel 0 on P0.09.
Its charge-pump multiplier is selected by EN1 (P1.02) and EN2 (P1.05), which
both carry 10K pull-ups to VDD_3V3. Per the PAM8904 datasheet that is 3× mode —
7.29 V out, the loudest setting — with no firmware involvement, and the part
sits under 1 µA whenever `DIN` is low, auto-sleeping 42 ms after the tone
stops. So both pins are deliberately left unclaimed. Driving them is only
worth doing if a quieter mode is ever wanted:
| DIN | EN1 | EN2 | Mode |
| --- | --- | --- | --- |
| 0 | × | × | shutdown |
| 1 | 0 | 0 | shutdown |
| 1 | 0 | 1 | 1× (2.83 V) |
| 1 | 1 | 0 | 2× (5.26 V) |
| 1 | 1 | 1 | 3× (7.29 V) |
## Buttons
Two buttons, so unlike the single-button Heltec boards the T1 can page
backwards.
| Button | Pin | Gesture | Action |
| --- | --- | --- | --- |
| User (S3) | P1.10 | 1 tap | page next |
| | | 2 taps | LED heartbeat toggle |
| | | 3 taps | notification mode |
| | | 4 taps | GPS on/off |
| | | 5 taps | flood advert |
| | | hold 1 s | page enter |
| Function (S2) | P0.14 | press | page previous |
The datasheet calls P1.10 "Button1 / USER key" and P0.14 "Button2 / Function
key"; the schematic silkscreen labels are the other way round (S2 = "User1" on
P0.14, S3 = "User2" on P1.10). Pins are what matter.
## NFC pins used as GPIO
P0.09 (buzzer `DIN`) and P0.10 (`Sensor_SCL`) are the nRF52840's NFC antenna
pins, so `UICR.NFCPINS` has to select GPIO. The DTS sets `nfct-pins-as-gpios`
on `&uicr`.
Stock boards already have this programmed by the factory Adafruit UF2
bootloader, so a normal UF2 install needs nothing extra. A board that has been
fully erased over SWD does, and that property is what puts it back.
## Not enabled
- **Sensor I2C bus.** The ICM-42607-P and MMC5983MA sit on `Sensor_SDA`
(P1.03) / `Sensor_SCL` (P0.10) behind the `VSensor_Ctrl` rail, which also
powers the bus's 4.7K pull-ups. ZephCore has no consumer for either part —
there is no IMU support, and the MMC5983MA has no Zephyr driver at all — and
the board is sealed with no external sensor connector, so the bus stays down
and the `sensor_power` regulator node exists purely to hold P1.06 inactive.
The datasheet's 11 µA sleep figure assumes that rail is off.
To bring it up: enable `&i2c0` with a pinctrl pair for those two pins and add
`regulator-boot-on` to `sensor_power`.
- **U6**, an 8-pin pressure-sensor footprint on the same bus, is marked NC on
the schematic, and so is `R25`, its SDO/address strap — the address would
float even if a part were fitted.
- **External flash.** There is none on this board, so `&qspi` is disabled and
`/ext` is unavailable. Contacts and channels live in internal `/lfs`.
## Heltec license page overlap
Heltec's own Arduino variant reserves `0xF3000``0xF4000` (the last flash page
before the bootloader) for a hardware-version byte and a license blob
(`HARD_VERSION_ADDR` / `HT_LICENSE_ADDR`). That page falls inside ZephCore's
`/lfs` partition, so running ZephCore will eventually erase it, the same way it
does on the other nRF52 boards that overlap an Adafruit-era filesystem region.
It only matters if you intend to go back to Heltec's own F&T / SnapEmu
firmware, which is what reads that blob; MeshCore and Meshtastic do not.
## Hardware smoke test plan
1. Flash UF2 by double-clicking the power button and drag-dropping.
2. Confirm USB CDC/logging appears.
3. Confirm the white LED goes out after boot (it is lit from power-up until
firmware drives P0.16 high).
4. Confirm BLE advertises and the MeshCore app pairs.
5. Confirm the display lights and shows the companion UI. **Check the image
fills the panel edge to edge** — see the offsets note above.
6. Confirm the backlight and panel both go dark on display timeout.
7. Confirm both buttons: page next / page previous.
8. Confirm SX1262 init with no reset, busy or DIO1 errors, then RX.
9. Test TX at low power first, then step up — the antenna switch is chip-driven
via DIO2, so a wrong `dio2-tx-enable` shows up as very poor range rather
than an error.
10. Confirm the buzzer sounds.
11. Confirm GNSS last: `get gps` should show sentences arriving. **If nothing
arrives, swap the UART psels** — see the GNSS UART note above.
@@ -0,0 +1,14 @@
# Copyright (c) 2026 ZephCore
#
# SPDX-License-Identifier: MIT
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)
@@ -0,0 +1,37 @@
# Heltec Mesh Node T1 - nRF52840 + SX1262 + UC6580 GNSS
# Board-specific configuration
#
# Hardware (Heltec Mesh_Node_T1_V1.0 schematic + datasheet Rev 1.0.0):
# - nRF52840 SoC, SX1262 LoRa, no FEM (uPG2179 SPDT switch driven by DIO2)
# - UC6580 GNSS on UART0 at 115200 baud (L1+L5, ATR2652 LNA front end)
# - ST7735S 0.96" 160x80 TFT (SPI3, MIPI-DBI, mono-tft wrapper)
# - PAM8904 piezo buzzer driver, tone input on PWM0/P0.09
# - Battery ADC on AIN3 (P0.05), 390K/100K divider, enable P0.11
# - LED: P0.16 (active-LOW)
# - Buttons: P1.10 (user, active-LOW) and P0.14 (function, active-LOW)
# - ICM-42607-P + MMC5983MA on a gated I2C bus; no ZephCore consumer, so the
# bus and its rail are left off
# - No external flash: storage is internal /lfs only
#
# Not set here because devicetree already decides them:
# - CONFIG_PWM / CONFIG_ZEPHCORE_UI_BUZZER come from the "buzzer" nodelabel
# - CONFIG_ZEPHCORE_UI_DISPLAY comes from chosen { zephyr,display }
# - CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM stays at the 22 dBm default: there is
# no PA to overdrive, and datasheet table 3.3 rates the board at 21 +/- 1 dBm
# in both the 863-870 and 902-928 MHz bands.
# Board identification. "Heltec Mesh Node T1" is the exact device name the
# MeshCore flasher catalogue uses, so the Mesh America configurator folds this
# into the existing device tile instead of creating a ZephCore-only one.
CONFIG_ZEPHCORE_BOARD_NAME="Heltec Mesh Node T1"
# Device Information Service model name
CONFIG_BT_DIS_MODEL_NUMBER_STR="Heltec Mesh Node T1"
# SoftDevice firmware ID (s140 v6.1.1)
CONFIG_ZEPHCORE_SD_FWID=0x00B6
# ========== RAM budget ==========
# CFB mono-tft framebuffer = 160x80/8 = 1600 bytes, allocated from k_malloc.
# Default heap (2048) is sized tightly for SSD1306 128x64 (1024 bytes).
CONFIG_HEAP_MEM_POOL_SIZE=4096
@@ -0,0 +1,10 @@
# Copyright (c) 2026 ZephCore
#
# SPDX-License-Identifier: MIT
board:
name: heltec_t1
full_name: Heltec Mesh Node T1
vendor: heltec
socs:
- name: nrf52840
@@ -0,0 +1,96 @@
/*
* Heltec Mesh Node T1 pin control definitions
* Copyright (c) 2026 ZephCore
*
* SPDX-License-Identifier: MIT
*
* Sources:
* - Heltec "Mesh_Node_T1_V1.0" schematic (resource.heltec.cn), pin table and
* the U10/U11/U14/U18 symbols
* - Heltec "Mesh Node T1 Wireless Position Tag" datasheet Rev 1.0.0
*
* Schematic nets:
* - SPI2 (SX1262 LoRa): SCK=P1.13 (LOAR_SCK), MOSI=P1.14 (LOAR_MOSI),
* MISO=P0.03 (LOAR_MISO)
* - SPI3 (ST7735S display): SCK=P1.00 (SCLK), MOSI=P0.24 (SDIN); the FFC has
* no data-out line, so the bus is write-only
* - UART0 (UC6580 GNSS): TX=P0.07 -> UC6580 pin 18 (RX),
* RX=P0.08 <- UC6580 pin 19 (TX)
* - PWM0 (PAM8904 buzzer): OUT0=P0.09 (DIN)
*
* The schematic's pin table prints "nRF_RX" beside P0.07 and "nRF_TX" beside
* P0.08. Those two aliases are inverted: P0.07 is wired to the UC6580's RX
* input, which no other part drives, so it can only be the nRF's transmit
* line. The GNSS_RX/GNSS_TX halves of the same table are correct, and match
* Heltec's own nRF52 Arduino variant (HT-mesh-node-t1) which sets
* PIN_SERIAL1_TX = P0.07 and PIN_SERIAL1_RX = P0.08.
*/
&pinctrl {
/* SX1262 LoRa radio */
spi2_default: spi2_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 13)>,
<NRF_PSEL(SPIM_MOSI, 1, 14)>,
<NRF_PSEL(SPIM_MISO, 0, 3)>;
};
};
spi2_sleep: spi2_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 13)>,
<NRF_PSEL(SPIM_MOSI, 1, 14)>,
<NRF_PSEL(SPIM_MISO, 0, 3)>;
low-power-enable;
};
};
/* ST7735S TFT — write-only, no MISO on the 8-pin FFC */
spi3_default: spi3_default {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 0)>,
<NRF_PSEL(SPIM_MOSI, 0, 24)>;
};
};
spi3_sleep: spi3_sleep {
group1 {
psels = <NRF_PSEL(SPIM_SCK, 1, 0)>,
<NRF_PSEL(SPIM_MOSI, 0, 24)>;
low-power-enable;
};
};
/* UC6580 GNSS */
uart0_default: uart0_default {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 7)>;
};
group2 {
psels = <NRF_PSEL(UART_RX, 0, 8)>;
bias-pull-up;
};
};
uart0_sleep: uart0_sleep {
group1 {
psels = <NRF_PSEL(UART_TX, 0, 7)>,
<NRF_PSEL(UART_RX, 0, 8)>;
low-power-enable;
};
};
/* PAM8904 piezo driver tone input */
pwm0_default: pwm0_default {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 9)>;
};
};
pwm0_sleep: pwm0_sleep {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 9)>;
low-power-enable;
};
};
};
@@ -0,0 +1,504 @@
/*
* Heltec Mesh Node T1 - nRF52840 + SX1262 + UC6580 GNSS
* Copyright (c) 2026 ZephCore
*
* SPDX-License-Identifier: MIT
*
* Every net named below is taken from the official Heltec
* "Mesh_Node_T1_V1.0" schematic (resource.heltec.cn), cross-checked against
* the "Mesh Node T1 Wireless Position Tag" datasheet Rev 1.0.0 and the
* component datasheets for the parts the nets land on (ST7735S panel spec
* N096-1608TBBIG09-C08, Diodes PAM8904, Semtech SX1262). Where the two
* Heltec documents disagree the schematic wins; the two places where that
* happens are called out at the nodes concerned.
*/
/dts-v1/;
#include <nordic/nrf52840_qiaa.dtsi>
#include <zephyr/dt-bindings/lora/sx126x.h>
#include <zephyr/dt-bindings/adc/nrf-saadc.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
#include "heltec_t1_nrf52840-pinctrl.dtsi"
/ {
model = "Heltec Mesh Node T1";
compatible = "heltec,t1";
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 = &mono_tft;
zephcore,gps-power = &gps_power;
};
aliases {
led0 = &led_white;
lora-tx-led = &led_white;
sw0 = &user_button;
lora0 = &lora;
watchdog0 = &wdt0;
gps-reset = &gps_reset_pin;
display0 = &tft;
buzzer = &buzzer;
};
leds {
compatible = "gpio-leds";
/*
* Schematic net LED: P0.16 -> R56 1K -> gate of Q10 (AO3401A,
* P-channel, source on VDD_3V3), drain -> R30 330R -> LED2
* (white) -> GND. Pulling the gate low turns the FET on, so
* the LED is active-LOW.
*
* The datasheet's "2.2 LED" table instead says the white LED is
* on P0.09. That is wrong - P0.09 is the PAM8904 DIN (buzzer);
* the schematic, and every vendor firmware, put the LED on
* P0.16.
*
* Caveat for anyone chasing a boot-time glow: unlike the
* backlight FET, Q10's gate has no pull-up to its source, only
* R57 10M to ground. While P0.16 is still an input the gate
* sits near 0 V and the LED is lit. gpio-leds drives it
* inactive at init, which closes the window but does not remove
* it.
*/
led_white: led_0 {
gpios = <&gpio0 16 GPIO_ACTIVE_LOW>;
label = "White LED";
};
};
buttons: buttons {
compatible = "gpio-keys";
/*
* Schematic S3 (silkscreen "User2"), net Button: P1.10 to GND,
* R8 10K pull-up to VDD_nRF, C8 0.1uF debounce. The datasheet
* calls this one "Button1 / USER key".
*/
user_button: button_0 {
gpios = <&gpio1 10 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
zephyr,code = <INPUT_KEY_0>;
label = "User Button";
};
/*
* Schematic S2 (silkscreen "User1"), net User: P0.14 to GND,
* R20 10K pull-up to VDD_3V3, C15 0.1uF debounce. The
* datasheet calls this one "Button2 / Function key".
*
* Wired straight to KEY_LEFT: with two buttons the T1 can do
* page-previous, which the single-button Heltec boards cannot.
*/
page_prev_button: button_1 {
gpios = <&gpio0 14 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
zephyr,code = <INPUT_KEY_LEFT>;
label = "Function Button";
};
};
/* Input filter: user button long-press detection (1000ms) */
user_btn_longpress: 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>;
};
/*
* Input filter: user button multi-tap detection. Stock ladder - the
* T1 has a real buzzer, so the 3-tap notification-mode slot is live
* here (unlike T096, where it was compiled out and had to be dropped).
*
* 1 tap (400ms wait) → KEY_1 = page next
* 2 taps (400ms wait) → KEY_B = LED heartbeat toggle
* 3 taps (400ms wait) → KEY_D = notification mode
* 4 taps (400ms wait) → KEY_C = GPS on/off
* 5 taps (immediate) → KEY_E = flood advert
*/
user_btn_multitap {
compatible = "zephcore,input-multi-tap";
/* No 'input' phandle - KEY_A comes from the longpress
* pseudo-device, not from &buttons. */
input-codes = <INPUT_KEY_A>;
tap-codes = <INPUT_KEY_1 INPUT_KEY_B INPUT_KEY_D INPUT_KEY_C INPUT_KEY_E>;
tap-delay-ms = <400>;
};
/*
* Schematic net DIN: P0.09 -> R24 0R -> PAM8904 (U3) pin 3.
*
* U3 is a charge-pump piezo driver, not a plain transistor: it drives
* the element differentially from VO1/VO2 through R14/R18 100R. From
* the MCU's side it is still just a tone input - PAM8904 datasheet
* p.5 has the part idle at under 1uA whenever DIN is low, and
* auto-sleep 42ms after DIN stops toggling, so no enable sequencing is
* needed.
*
* P0.09 is NFC1. See the &uicr node at the bottom of this file.
*
* The charge-pump multiplier pins EN1 (P1.02) and EN2 (P1.05) are
* deliberately left unconfigured: both carry 10K pull-ups to VDD_3V3,
* which selects 3x mode (7.2-9V out, the loudest setting) with no
* firmware involvement. Driving them would only ever make the buzzer
* quieter, so they are documented in the README as a future volume
* control rather than claimed here.
*/
pwmbuzzer {
compatible = "pwm-leds";
buzzer: buzzer {
pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
};
};
/*
* Schematic net VGNSS_Ctrl: P0.04 -> R32 1K -> gate of Q5 (AO3401A,
* P-channel, source on VDD_3V3, R54 10K gate pull-up), drain feeds
* VDD_GNSS. Active-LOW, and off at reset while the pin is an input.
*/
gps_power: gps-power {
compatible = "regulator-fixed";
regulator-name = "gps-power";
enable-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>;
regulator-boot-on;
/* Documents the module's requirement, but do not rely on it:
* regulator-boot-on sends regulator_common_init() down the
* refcount-only branch, so regulator_delay() never runs and this
* value is inert. Harmless here because gnss-nmea-generic never
* probes the receiver — it attaches a UART and parses whatever
* arrives, so an unpowered module means no sentences yet, not a
* failed init. ZephyrGPSManager owns the rail at runtime via
* chosen { zephcore,gps-power }. */
startup-delay-us = <10000>;
};
/* Schematic net GNSS_RST: P0.26 -> UC6580 pin 17 (RESETN), active-LOW,
* R37 10K pull-up to VDD_GNSS and C90 0.1uF. */
gps_rst: gps-reset {
compatible = "gpio-leds";
gps_reset_pin: gps_reset {
gpios = <&gpio0 26 GPIO_ACTIVE_LOW>;
label = "GNSS Reset";
};
};
/*
* Schematic net VSensor_Ctrl: P1.06 -> R31 1K -> gate of Q11 (AO3401A,
* P-channel, source on VDD_3V3, R23 10K gate pull-up) -> VDD_Sensor.
* Active-LOW.
*
* VDD_Sensor powers the ICM-42607-P, the MMC5983MA, the unpopulated U6
* pressure-sensor footprint, and the 4.7K pull-ups on the Sensor_SDA /
* Sensor_SCL bus. ZephCore consumes none of those, so this node exists
* only to hold the rail off: with no consumer and no regulator-boot-on,
* regulator-fixed init drives P1.06 inactive and the rail stays down.
* That is what the datasheet's 11uA sleep figure assumes.
*/
sensor_power: sensor-power {
compatible = "regulator-fixed";
regulator-name = "sensor-power";
enable-gpios = <&gpio1 6 GPIO_ACTIVE_LOW>;
};
/*
* Schematic net ADC_Ctrl: P0.11 -> R7 1K -> base of Q2 (S9013 NPN,
* R12 10K base pulldown); Q2's collector pulls the gate of Q1
* (AO3401A, source on VBAT, R5 10K gate pull-up) low, switching VBAT
* into the R11 390K / R16 100K divider. Active-HIGH.
*/
vbat_enable: vbat-enable {
compatible = "regulator-fixed";
regulator-name = "vbat-enable";
enable-gpios = <&gpio0 11 GPIO_ACTIVE_HIGH>;
};
/*
* Schematic net VScreen_Ctrl: P0.13 -> R26 1K -> gate of Q6 (AO3401A,
* P-channel, source on VDD_3V3, R52 10K gate pull-up) -> VDD_Screen,
* which is FFC pin 7 (VDD). Active-LOW.
*
* Meshtastic's variant.h comments this net as "Active HIGH". That
* comment is wrong - Q6 is a P-channel FET with its gate pulled to its
* own source - and Heltec's nRF52 Arduino variant agrees with the
* schematic (TFT_VDD_ENABLE = 0).
*/
tft_pwr_enable: tft-pwr-enable {
compatible = "regulator-fixed";
regulator-name = "tft-pwr-enable";
enable-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
regulator-boot-on;
/* Documents the panel's requirement, but do not rely on it:
* regulator-boot-on sends regulator_common_init() down the
* refcount-only branch, so regulator_delay() never runs and this
* value is inert. The real settle time comes from the ST7735R
* driver's own mipi_dbi_reset() + exit-sleep delay, at
* DISPLAY_INIT_PRIORITY 85 against this rail's 75. */
startup-delay-us = <10000>;
};
/*
* Schematic net LED_K: P0.15 -> R28 1K -> gate of Q9 (AO3401A,
* P-channel, source on VDD_3V3, R27 10K gate pull-up), drain ->
* R55 22R -> FFC pin 1 (LEDA). Active-LOW, off at reset.
*
* The net name is a misnomer inherited from other Heltec boards: the
* N096-1608TBBIG09-C08 only brings the backlight *anode* out to the
* FFC (panel spec section 3), so Q9 switches the high side.
*
* Note the FET source is VDD_3V3, not VDD_Screen - the backlight does
* not follow the panel rail and has to be switched off in its own
* right.
*/
disp_pwr_enable: disp-pwr-enable {
compatible = "regulator-fixed";
regulator-name = "disp-pwr-enable";
enable-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
};
/*
* Monochrome wrapper around the ST7735S-compatible TFT.
* Reports PIXEL_FORMAT_MONO01 to CFB and converts rows to RGB565.
*/
mono_tft: mono-tft {
compatible = "zephcore,mono-tft";
tft-dev = <&tft>;
width = <160>;
height = <80>;
};
/*
* MIPI-DBI SPI bus for the 0.96" TFT.
*
* Panel is an Unvision N096-1608TBBIG09-C08: 80(H)RGB x 160(V),
* 10.8 x 21.7 mm active area, normally black, 4-line SPI, ST7735S
* driver IC, single white LED backlight (panel spec sections 1.2
* and 3).
*
* Schematic nets, all on the 8-pin FFC U18:
* - CS -> P0.12, FFC pin 8
* - RS -> P0.22, FFC pin 4, data/command
* - SDIN -> P0.24, FFC pin 5 (SDA)
* - SCLK -> P1.00, FFC pin 6 (SCL)
* - RES -> P0.20, FFC pin 3, active-LOW reset
*
* The FFC carries no data-out line, hence write-only.
*/
mipi_dbi {
compatible = "zephyr,mipi-dbi-spi";
spi-dev = <&spi3>;
dc-gpios = <&gpio0 22 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio0 20 GPIO_ACTIVE_LOW>;
write-only;
#address-cells = <1>;
#size-cells = <0>;
tft: st7735s@0 {
compatible = "sitronix,st7735r";
mipi-max-frequency = <20000000>;
mipi-mode = "MIPI_DBI_MODE_SPI_4WIRE";
reg = <0>;
width = <160>;
height = <80>;
/* No inversion-on: the mono_tft wrapper writes 0xFFFF for a lit
* pixel and 0x0000 for background, so INV_ON paints a light
* background with dark text. The UI is drawn light-on-dark. */
/*
* Landscape, rotated 180 degrees from the T096 mounting
* of the same panel class. MADCTL 0x68 = MX | MV | BGR,
* against 0xa8 = MY | MV | BGR there. The offsets are
* the pair that belongs to this glass bonding (the
* "red tab 160x80" column start of 24, row start of 0,
* transposed by MV).
*
* If the first hardware run shows the image shifted with
* a black band on one edge, the alternative offsets for
* the mirrored orientation are x-offset 2 / y-offset 28,
* caset [00 02 00 a1], raset [00 1c 00 6b].
*/
x-offset = <0>;
y-offset = <24>;
madctl = <0x68>;
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 00 00 9f];
raset = [00 18 00 67];
};
};
zephyr,user {
io-channels = <&adc 3>;
/*
* Schematic: BAT_ADC on P0.05/AIN3, divider R11=390K high side
* and R16=100K low side -> 4.9:1. With ADC_GAIN_1_6 and the
* internal 0.6V reference, full-scale is 3600 mV, so the
* nominal multiplier is 3600 * 4.9 = 17640; the value below
* carries the same +0.5% nRF SAADC gain-error correction used
* on the other ZephCore nRF52 boards.
*/
vbat-mv-multiplier = <17728>;
};
};
&reg0 {
status = "okay";
};
&reg1 {
regulator-initial-mode = <NRF5X_REG_MODE_DCDC>;
};
&adc {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@3 {
reg = <3>;
zephyr,gain = "ADC_GAIN_1_6";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME(ADC_ACQ_TIME_MICROSECONDS, 10)>;
zephyr,input-positive = <NRF_SAADC_AIN3>;
zephyr,resolution = <12>;
};
};
/*
* P0.09 (PAM8904 DIN) and P0.10 (Sensor_SCL) are the nRF52840's NFC antenna
* pins. UICR.NFCPINS has to select GPIO or neither pin can be driven.
*
* Stock boards already have this programmed by the factory Adafruit UF2
* bootloader, so a UF2 install needs nothing extra; a board that has been
* fully erased over SWD does, and this property is what puts it back.
*/
&uicr {
nfct-pins-as-gpios;
};
&gpiote {
status = "okay";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
&pwm0 {
status = "okay";
pinctrl-0 = <&pwm0_default>;
pinctrl-1 = <&pwm0_sleep>;
pinctrl-names = "default", "sleep";
};
/*
* The Sensor_SDA (P1.03) / Sensor_SCL (P0.10) bus is left disabled on
* purpose - see the sensor_power node above. To bring it up, enable
* &i2c0 with a pinctrl pair for those two pins and give sensor_power a
* regulator-boot-on; without the rail the 4.7K pull-ups are unpowered and
* the bus cannot work.
*/
/* UC6580 GNSS - 115200 baud, plain NMEA stream, no command handshake. */
&uart0 {
compatible = "nordic,nrf-uarte";
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-1 = <&uart0_sleep>;
pinctrl-names = "default", "sleep";
gnss: gnss {
compatible = "gnss-nmea-generic";
};
};
/* SPI3 - ST7735S 0.96" 160x80 TFT display */
&spi3 {
compatible = "nordic,nrf-spim";
status = "okay";
pinctrl-0 = <&spi3_default>;
pinctrl-1 = <&spi3_sleep>;
pinctrl-names = "default", "sleep";
cs-gpios = <&gpio0 12 GPIO_ACTIVE_LOW>; /* Schematic net CS */
};
/* SPI2 - SX1262 LoRa radio */
&spi2 {
compatible = "nordic,nrf-spim";
status = "okay";
cs-gpios = <&gpio1 11 GPIO_ACTIVE_LOW>; /* Schematic net LOAR_NSS */
pinctrl-0 = <&spi2_default>;
pinctrl-1 = <&spi2_sleep>;
pinctrl-names = "default", "sleep";
lora: lora@0 {
compatible = "semtech,sx1262";
reg = <0>;
spi-max-frequency = <8000000>;
reset-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>; /* LOAR_NRSET */
busy-gpios = <&gpio0 29 GPIO_ACTIVE_HIGH>; /* LORA_BUSY */
dio1-gpios = <&gpio0 31 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; /* DIO1 */
/*
* There is no FEM on this board. The SX1262's RFO (pin 23) and
* RFI_P/RFI_N (pins 21/22) run as separate matched paths into a
* uPG2179 SPDT switch (U13), whose Vcont1 is driven straight
* from schematic net ANT_SW_CTRL = SX1262 DIO2 (pin 12) and
* whose Vcont2 is the complement produced by Q8 (2SC3356). So
* the chip switches its own antenna path and no MCU GPIO is
* involved: dio2-tx-enable, and no antenna-enable-gpios or
* lna-bypass-gpios.
*/
dio2-tx-enable;
/*
* X3 is a 32 MHz TCXO whose supply comes from DIO3 through
* FB2 (10R@100MHz). 1.8V per Heltec's own Arduino variant
* (SX126X_DIO3_TCXO_VOLTAGE 1.8).
*/
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_1V8>;
tcxo-power-startup-delay-ms = <5>;
rx-boosted;
};
};
/* No external SPI/QSPI flash on this board - storage is internal only. */
&qspi {
status = "disabled";
};
zephyr_udc0: &usbd {
compatible = "nordic,nrf-usbd";
status = "okay";
cdc_acm_uart: cdc_acm_uart {
compatible = "zephyr,cdc-acm-uart";
};
};
/* SoftDevice s140 v6.1.1 flash layout (app@0x26000, bootloader@0xF4000) */
#include "../../common/nrf52_partitions_sdv6.dtsi"
#include "../../common/nrf52_wakeup.dtsi"
@@ -0,0 +1,20 @@
identifier: heltec_t1/nrf52840
name: Heltec Mesh Node T1
type: mcu
arch: arm
toolchain:
- zephyr
- gnuarmemb
- xtools
ram: 256
flash: 1024
supported:
- adc
- ble
- gpio
- pwm
- spi
- usb_device
- lora
- gnss
vendor: heltec
@@ -0,0 +1,21 @@
# Copyright (c) 2026 ZephCore
# SPDX-License-Identifier: MIT
# Enable MPU
CONFIG_ARM_MPU=y
# Enable hardware stack protection
CONFIG_HW_STACK_PROTECTION=y
# Enable GPIO
CONFIG_GPIO=y
# Enable UART driver
CONFIG_SERIAL=y
# Enable console
CONFIG_CONSOLE=y
# Build UF2 by default
CONFIG_BUILD_OUTPUT_UF2=y
CONFIG_USE_DT_CODE_PARTITION=y