initial port of heltec v4 and v4.3 boards

This commit is contained in:
liquidraver
2026-04-08 11:21:44 +02:00
parent 5f9c8b0d99
commit 3b3eff7aab
24 changed files with 1043 additions and 4 deletions
+1 -1
View File
@@ -187,7 +187,7 @@ if(BOARD MATCHES ".*nrf52.*" OR BOARD MATCHES "rak4631" OR BOARD MATCHES "rak_wi
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")
elseif(BOARD MATCHES ".*esp32.*" OR BOARD MATCHES "station_g2" OR BOARD MATCHES "lilygo_tlora_c6" OR BOARD MATCHES "heltec_wifi_lora32_v3")
elseif(BOARD MATCHES ".*esp32.*" OR BOARD MATCHES "station_g2" OR BOARD MATCHES "lilygo_tlora_c6" OR BOARD MATCHES "heltec_wifi_lora32_v3" OR BOARD MATCHES "heltec_wifi_lora32_v4")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32_common.conf")
elseif(DEFINED BOARD_QUALIFIERS AND BOARD_QUALIFIERS MATCHES ".*esp32.*")
set(ZEPHCORE_PLATFORM_CONF "${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32_common.conf")
+10
View File
@@ -305,6 +305,16 @@ config ZEPHCORE_MAX_TX_POWER_DBM
above this, protecting external PAs from damage.
Example: Station G2 caps at 19 dBm to stay below PA P1dB (35 dBm).
config ZEPHCORE_SX126X_HELTEC_REG_PATCH
bool "Apply undocumented SX126x register 0x8B5 RX improvement (Heltec V4)"
depends on ZEPHCORE_RADIO_NATIVE
default n
help
Sets the LSB of undocumented register 0x8B5 after the first lora_config().
Described by Heltec engineer @Quency-D in MeshCore PR#1398 — consistently
improves RX reception on boards with GC1109 or KCT8103L PA (Heltec V4/V4.3).
Enable in board.conf for Heltec V4 boards.
endmenu # Radio Configuration
menu "Board Configuration"
+7
View File
@@ -29,6 +29,13 @@ void SX126xRadio::begin()
startTxThread(sx126x_tx_wait_stack,
K_THREAD_STACK_SIZEOF(sx126x_tx_wait_stack));
LoRaRadioBase::begin();
#if IS_ENABLED(CONFIG_ZEPHCORE_SX126X_HELTEC_REG_PATCH)
/* Apply undocumented register 0x8B5 RX improvement (MeshCore PR#1398).
* Must run after lora_config() has been called (via startReceive above). */
sx126x_apply_heltec_reg_patch(_dev);
LOG_INF("Applied Heltec reg 0x8B5 RX patch");
#endif
}
/* ── Hardware primitives ──────────────────────────────────────────────── */
@@ -0,0 +1,3 @@
config HEAP_MEM_POOL_ADD_SIZE_BOARD
int
default 4096
@@ -0,0 +1,3 @@
config BOARD_HELTEC_WIFI_LORA32_V4
select SOC_ESP32S3_WROOM_N16R2
select SOC_ESP32S3_PROCPU if BOARD_HELTEC_WIFI_LORA32_V4_ESP32S3_PROCPU
@@ -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 WiFi LoRa 32 V4 — ESP32-S3, 16 MB flash, 2 MB QSPI PSRAM
#
# Hardware:
# SX1262 + GC1109 external PA (10 dBm in → ~22 dBm out)
# SSD1306 128x64 OLED on I2C0
# User button on GPIO0
#
# Include order: prj.conf → zephcore_common.conf → esp32_common.conf → board.conf
# Board identification
CONFIG_ZEPHCORE_BOARD_NAME="Heltec V4"
CONFIG_BT_DIS_MODEL_NUMBER_STR="Heltec WiFi LoRa 32 V4"
# Flash size — 16 MB (override esp32_common.conf 4 MB default)
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
# Undocumented register 0x8B5 RX improvement (MeshCore PR#1398)
# Described by Heltec engineer @Quency-D — improves RX on GC1109/KCT8103L PA boards.
CONFIG_ZEPHCORE_SX126X_HELTEC_REG_PATCH=y
# TX power — SX1262 input to GC1109 PA.
# 10 dBm → ~22 dBm radiated output (GC1109 ~12 dB gain).
# Do not exceed 10 dBm into the PA to avoid compression.
CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=10
CONFIG_ZEPHCORE_MAX_TX_POWER_DBM=10
@@ -0,0 +1,113 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Heltec WiFi LoRa 32 V4 — ZephCore overlay
*
* The DTS already defines: SX1262 (lora0), SSD1306 OLED, button, LED, PWM,
* vext_ctrl, adc_ctrl, fem_power, I2C0, SPI2, BLE, WiFi.
* This overlay adds ZephCore-specific integration on top.
*
* LoRa SPI2: SCLK=9, MOSI=10, MISO=11, NSS=8
* LoRa ctrl: DIO1=14, BUSY=13, RESET=12
* FEM: POWER=7, GC1109 CSD=2, CPS=46 (V4.2 — for V4.3/KCT8103L see heltec_wifi_lora32_v43)
* Display I2C0: SDA=17, SCL=18, RST=21
* Battery ADC: GPIO1 (ADC1 ch0), enable GPIO37, divider 100K/390K
* Vext: GPIO36 (active-HIGH, powers OLED)
* Button: GPIO0
* LED: GPIO35
*/
#include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
chosen {
/* Native USB serial for ZephCore console/CLI */
zephyr,console = &usb_serial;
zephyr,shell-uart = &usb_serial;
};
/* Battery ADC — ZephCore reads via zephyr,user { io-channels }.
* Multiplier: 3300mV full-scale * 5.42x empirical divider = 17886 mV.
* (Same as V3 — identical resistor divider topology.) */
zephyr,user {
io-channels = <&adc1 0>;
vbat-mv-multiplier = <17886>;
};
/* Battery ADC enable — ZephCore uses vbat_enable regulator alias.
* GPIO37 (gpio1 pin 5) driven HIGH enables the ADC input path.
* The DTS adc_ctrl node is power-domain-gpio which ZephCore doesn't use;
* this regulator-fixed is the actual driver. */
vbat_enable: vbat-enable {
compatible = "regulator-fixed";
regulator-name = "vbat-enable";
enable-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>; /* GPIO37 = gpio1 pin 5 */
};
/* ---- Button input filter chain ----
* Button emits INPUT_KEY_0. Split into:
* short press → KEY_A (consumed by multi-tap)
* long press (1 s) → KEY_POWER (deep sleep) */
user_btn_longpress {
compatible = "zephyr,input-longpress";
input = <&buttons>;
input-codes = <INPUT_KEY_0>;
short-codes = <INPUT_KEY_A>;
long-codes = <INPUT_KEY_POWER>;
long-delay-ms = <1000>;
};
/* Multi-tap decodes short presses:
* 1 tap (400 ms wait) → KEY_1 = page next
* 2 taps (400 ms wait) → KEY_B = flood advert
* 3 taps (400 ms wait) → KEY_D = buzzer mute toggle
* 4 taps (immediate) → KEY_C = GPS on/off */
user_btn_multitap {
compatible = "zephcore,input-multi-tap";
/* No 'input' phandle — listen to ALL devices.
* KEY_A is emitted by the longpress pseudo-device. */
input-codes = <INPUT_KEY_A>;
tap-codes = <INPUT_KEY_1 INPUT_KEY_B INPUT_KEY_D INPUT_KEY_C>;
tap-delay-ms = <400>;
};
};
/* Enable native USB serial for console/CLI */
&usb_serial {
status = "okay";
};
/* Patch LoRa radio: add pull-down on DIO1 and enable RX boosted mode.
* DTS defines antenna-enable, tx-enable, dio2-tx-enable, and TCXO already. */
&lora0 {
dio1-gpios = <&gpio0 14 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
rx-boosted;
};
/* I2C sensors — auto-detected at runtime */
&i2c0 {
#include "../../common/sensors-i2c.dtsi"
};
/*
* Flash partitions — repartition 16 MB layout for ZephCore.
* Delete lpcore slots, storage, scratch, and coredump to reclaim space.
* Place LittleFS at 0xFA0000 - 0x1000000 (384 KB).
*/
/delete-node/ &slot0_lpcore_partition;
/delete-node/ &slot1_lpcore_partition;
/delete-node/ &storage_partition;
/delete-node/ &scratch_partition;
/delete-node/ &coredump_partition;
&flash0 {
partitions {
/* LittleFS 384 KB for DataStore + file-based BLE settings */
lfs_partition: partition@fa0000 {
label = "lfs";
reg = <0xFA0000 0x60000>;
};
};
};
/* LittleFS auto-mount — standard /lfs mount point */
#include "../../common/filesystem.dtsi"
@@ -0,0 +1,6 @@
board:
name: heltec_wifi_lora32_v4
full_name: Heltec WiFi LoRa 32 V4
vendor: heltec
socs:
- name: esp32s3
@@ -0,0 +1,53 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Heltec WiFi LoRa 32 V4 — Pin Control Definitions
*
* Pin assignments (identical to V3):
* UART0: TX=GPIO43, RX=GPIO44
* I2C0: SDA=GPIO17, SCL=GPIO18 (SSD1306 OLED + sensors)
* SPI2: SCLK=GPIO9, MOSI=GPIO10, MISO=GPIO11 (SX1262 LoRa)
* LEDC0: CH0=GPIO35 (PWM LED)
*/
#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;
};
};
i2c0_default: i2c0_default {
group1 {
pinmux = <I2C0_SDA_GPIO17>,
<I2C0_SCL_GPIO18>;
drive-open-drain;
};
};
spim2_default: spim2_default {
group1 {
pinmux = <SPIM2_MISO_GPIO11>,
<SPIM2_SCLK_GPIO9>;
};
group2 {
pinmux = <SPIM2_MOSI_GPIO10>;
output-low;
};
};
ledc0_default: ledc0_default {
group1 {
pinmux = <LEDC_CH0_GPIO35>;
output-enable;
};
};
};
@@ -0,0 +1,238 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Heltec WiFi LoRa 32 V4 — ZephCore procpu DTS
*
* Hardware:
* MCU: ESP32-S3 (16 MB flash, 2 MB QSPI PSRAM)
* Radio: SX1262 on SPI2, GC1109 external PA/FEM (V4.2)
* OLED: SSD1306 128x64 on I2C0
* Button: GPIO0
* LED: GPIO35 (PWM via LEDC0)
* Vext: GPIO36 active-HIGH (powers OLED and sensors)
* BatADC: GPIO1 (ADC1 ch0), enable GPIO37 active-LOW
*
* V4.2 (this file) — GC1109 PA, TX_EN on GPIO46.
* V4.3 (KCT8103L PA, CTX on GPIO5) → see heltec_wifi_lora32_v43.
*
* Key V4 differences vs V3:
* - 16 MB flash + 2 MB QSPI PSRAM (was 8 MB, no PSRAM)
* - External PA/FEM (GC1109):
* GPIO7 = VFEM_Ctrl LDO power (active-HIGH, always-on)
* GPIO2 = GC1109 CSD (chip enable, active-HIGH)
* GPIO46 = GC1109 CPS (TX mode select, active-HIGH)
* - Vext control is active-HIGH (was active-LOW on V3)
* - DIO2 used as RF switch alongside external FEM TX control
*
* LoRa SPI2: SCLK=9, MOSI=10, MISO=11, NSS=8
* LoRa ctrl: DIO1=14, BUSY=13, RESET=12
* FEM: POWER=7, CSD=2, CPS=46
* Display I2C0: SDA=17, SCL=18, RST=21
*/
/dts-v1/;
#include <espressif/esp32s3/esp32s3_wroom_n16r2.dtsi>
#include "heltec_wifi_lora32_v4-pinctrl.dtsi"
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/dt-bindings/lora/sx126x.h>
#include <espressif/partitions_0x0_amp_16M.dtsi>
/ {
model = "Heltec WiFi LoRa 32 V4 PROCPU";
compatible = "heltec,wifi-lora32-v4";
aliases {
led0 = &led0;
pwm-0 = &ledc0;
pwm-led0 = &pwm_led_white;
uart-0 = &uart0;
i2c-0 = &i2c0;
lora0 = &lora0;
sw0 = &button0;
watchdog0 = &wdt0;
};
leds {
compatible = "gpio-leds";
led0: led {
gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>; /* GPIO35 = gpio1 pin 3 */
label = "White LED";
};
};
pwmleds {
compatible = "pwm-leds";
pwm_led_white: pwm_led_gpio35 {
label = "White PWM LED";
pwms = <&ledc0 0 PWM_MSEC(10) PWM_POLARITY_NORMAL>;
};
};
buttons: buttons {
compatible = "gpio-keys";
button0: button_0 {
gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "USER Key";
zephyr,code = <INPUT_KEY_0>;
};
};
vbatt {
compatible = "voltage-divider";
io-channels = <&adc1 0>; /* GPIO1 = ADC1 ch0 */
output-ohms = <100000>;
full-ohms = <(100000 + 390000)>;
};
/* Vext — powers OLED and external peripherals.
* V4 DIFFERENCE: active-HIGH (GPIO36 HIGH = power on).
* V3 was active-LOW on the same GPIO. */
vext_ctrl: vext_ctrl {
compatible = "power-domain-gpio";
enable-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>; /* GPIO36 = gpio1 pin 4 */
#power-domain-cells = <0>;
label = "Vext Control";
};
/* ADC enable — GPIO37 (gpio1 pin 5) must be driven HIGH to read battery.
* ZephCore uses the vbat_enable regulator alias for this. */
adc_ctrl: adc_ctrl {
compatible = "power-domain-gpio";
enable-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>; /* GPIO37 = gpio1 pin 5, active-LOW */
#power-domain-cells = <0>;
label = "ADC Control";
};
/* FEM LDO power supply — GPIO7 powers the GC1109/KCT8103L LDO.
* Must be HIGH before any radio operation. Keep always-on. */
fem_power: fem-power {
compatible = "regulator-fixed";
regulator-name = "fem-power";
enable-gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>; /* VFEM_Ctrl = GPIO7 */
regulator-boot-on;
regulator-always-on;
};
chosen {
zephyr,sram = &sram1;
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
zephyr,bt-hci = &esp32_bt_hci;
zephyr,display = &ssd1306_128x64;
};
};
&adc1 {
status = "okay";
};
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
/* Reduce CPU clock from 240 MHz to 160 MHz — saves power, HAL minimum */
&cpu0 {
clock-frequency = <DT_FREQ_M(160)>;
};
&cpu1 {
clock-frequency = <DT_FREQ_M(160)>;
};
&ledc0 {
pinctrl-0 = <&ledc0_default>;
pinctrl-names = "default";
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel0@0 {
reg = <0x0>;
timer = <0>;
};
};
&i2c0 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
pinctrl-0 = <&i2c0_default>;
pinctrl-names = "default";
ssd1306_128x64: display-controller@3c {
compatible = "solomon,ssd1306";
reg = <0x3c>;
width = <128>;
height = <64>;
segment-offset = <0>;
page-offset = <0>;
display-offset = <0>;
multiplex-ratio = <63>;
segment-remap;
com-invdir;
inversion-on;
prechargep = <0x22>;
reset-gpios = <&gpio0 21 GPIO_ACTIVE_LOW>; /* RST_OLED = GPIO21 */
power-domains = <&vext_ctrl>;
};
};
&spi2 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
pinctrl-0 = <&spim2_default>;
pinctrl-names = "default";
cs-gpios = <&gpio0 8 GPIO_ACTIVE_LOW>; /* NSS = GPIO8 */
lora0: modem@0 {
compatible = "semtech,sx1262";
reg = <0>;
reset-gpios = <&gpio0 12 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; /* RESET = GPIO12 */
busy-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; /* BUSY = GPIO13 */
dio1-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>; /* DIO1 = GPIO14 */
/* External PA/FEM control (GC1109, V4.2):
* GPIO2 = CSD (chip select/enable): driven HIGH during radio, LOW in sleep
* GPIO46 = CPS (TX select): HIGH during TX, LOW during RX
* V4.3 (KCT8103L) uses GPIO5 instead of GPIO46 — see heltec_wifi_lora32_v43. */
antenna-enable-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; /* GC1109 CSD = GPIO2 */
tx-enable-gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>; /* GC1109 CPS = GPIO46 = gpio1 pin 14 */
/* DIO2 drives the SX1262 internal RF switch (TX path select).
* Combined with tx-enable-gpios, this keeps both the internal and
* external PA switch in sync with no software intervention needed. */
dio2-tx-enable;
/* TCXO 1.8V via DIO3 — same as V3 */
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_1V8>;
tcxo-power-startup-delay-ms = <5>;
spi-max-frequency = <4000000>;
};
};
&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"; };
@@ -0,0 +1,12 @@
# Heltec WiFi LoRa 32 V4 — minimal Zephyr defconfig (procpu)
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y
CONFIG_GPIO=y
CONFIG_CLOCK_CONTROL=y
CONFIG_PINCTRL=y
CONFIG_SPI=y
CONFIG_I2C=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
@@ -0,0 +1,3 @@
config HEAP_MEM_POOL_ADD_SIZE_BOARD
int
default 4096
@@ -0,0 +1,3 @@
config BOARD_HELTEC_WIFI_LORA32_V43
select SOC_ESP32S3_WROOM_N16R2
select SOC_ESP32S3_PROCPU if BOARD_HELTEC_WIFI_LORA32_V43_ESP32S3_PROCPU
@@ -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,26 @@
# Heltec WiFi LoRa 32 V4.3 — ESP32-S3, 16 MB flash, 2 MB QSPI PSRAM
#
# Hardware:
# SX1262 + KCT8103L external PA/FEM (GPIO5 CTX instead of GC1109 GPIO46 CPS)
# SSD1306 128x64 OLED on I2C0
# User button on GPIO0
#
# V4.3 vs V4.2: FEM chip changed GC1109 → KCT8103L, TX control pin GPIO46 → GPIO5.
# Include order: prj.conf → zephcore_common.conf → esp32_common.conf → board.conf
# Board identification
CONFIG_ZEPHCORE_BOARD_NAME="Heltec V4.3"
CONFIG_BT_DIS_MODEL_NUMBER_STR="Heltec WiFi LoRa 32 V4.3"
# Flash size — 16 MB (override esp32_common.conf 4 MB default)
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
# Undocumented register 0x8B5 RX improvement (MeshCore PR#1398)
# Applies to both GC1109 and KCT8103L PA boards.
CONFIG_ZEPHCORE_SX126X_HELTEC_REG_PATCH=y
# TX power — SX1262 input to KCT8103L PA.
# 10 dBm → ~22 dBm radiated output (KCT8103L similar gain to GC1109).
# Do not exceed 10 dBm into the PA to avoid compression.
CONFIG_ZEPHCORE_DEFAULT_TX_POWER_DBM=10
CONFIG_ZEPHCORE_MAX_TX_POWER_DBM=10
@@ -0,0 +1,113 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Heltec WiFi LoRa 32 V4.3 — ZephCore overlay
*
* The DTS already defines: SX1262 (lora0), SSD1306 OLED, button, LED, PWM,
* vext_ctrl, adc_ctrl, fem_power, I2C0, SPI2, BLE, WiFi.
* This overlay adds ZephCore-specific integration on top.
*
* LoRa SPI2: SCLK=9, MOSI=10, MISO=11, NSS=8
* LoRa ctrl: DIO1=14, BUSY=13, RESET=12
* FEM: POWER=7, KCT8103L CSD=2, CTX=5
* Display I2C0: SDA=17, SCL=18, RST=21
* Battery ADC: GPIO1 (ADC1 ch0), enable GPIO37, divider 100K/390K
* Vext: GPIO36 (active-HIGH, powers OLED)
* Button: GPIO0
* LED: GPIO35
*/
#include <zephyr/dt-bindings/input/input-event-codes.h>
/ {
chosen {
/* Native USB serial for ZephCore console/CLI */
zephyr,console = &usb_serial;
zephyr,shell-uart = &usb_serial;
};
/* Battery ADC — ZephCore reads via zephyr,user { io-channels }.
* Multiplier: 3300mV full-scale * 5.42x empirical divider = 17886 mV.
* (Same as V3 — identical resistor divider topology.) */
zephyr,user {
io-channels = <&adc1 0>;
vbat-mv-multiplier = <17886>;
};
/* Battery ADC enable — ZephCore uses vbat_enable regulator alias.
* GPIO37 (gpio1 pin 5) driven HIGH enables the ADC input path.
* The DTS adc_ctrl node is power-domain-gpio which ZephCore doesn't use;
* this regulator-fixed is the actual driver. */
vbat_enable: vbat-enable {
compatible = "regulator-fixed";
regulator-name = "vbat-enable";
enable-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>; /* GPIO37 = gpio1 pin 5 */
};
/* ---- Button input filter chain ----
* Button emits INPUT_KEY_0. Split into:
* short press → KEY_A (consumed by multi-tap)
* long press (1 s) → KEY_POWER (deep sleep) */
user_btn_longpress {
compatible = "zephyr,input-longpress";
input = <&buttons>;
input-codes = <INPUT_KEY_0>;
short-codes = <INPUT_KEY_A>;
long-codes = <INPUT_KEY_POWER>;
long-delay-ms = <1000>;
};
/* Multi-tap decodes short presses:
* 1 tap (400 ms wait) → KEY_1 = page next
* 2 taps (400 ms wait) → KEY_B = flood advert
* 3 taps (400 ms wait) → KEY_D = buzzer mute toggle
* 4 taps (immediate) → KEY_C = GPS on/off */
user_btn_multitap {
compatible = "zephcore,input-multi-tap";
/* No 'input' phandle — listen to ALL devices.
* KEY_A is emitted by the longpress pseudo-device. */
input-codes = <INPUT_KEY_A>;
tap-codes = <INPUT_KEY_1 INPUT_KEY_B INPUT_KEY_D INPUT_KEY_C>;
tap-delay-ms = <400>;
};
};
/* Enable native USB serial for console/CLI */
&usb_serial {
status = "okay";
};
/* Patch LoRa radio: add pull-down on DIO1 and enable RX boosted mode.
* DTS defines antenna-enable, tx-enable, dio2-tx-enable, and TCXO already. */
&lora0 {
dio1-gpios = <&gpio0 14 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>;
rx-boosted;
};
/* I2C sensors — auto-detected at runtime */
&i2c0 {
#include "../../common/sensors-i2c.dtsi"
};
/*
* Flash partitions — repartition 16 MB layout for ZephCore.
* Delete lpcore slots, storage, scratch, and coredump to reclaim space.
* Place LittleFS at 0xFA0000 - 0x1000000 (384 KB).
*/
/delete-node/ &slot0_lpcore_partition;
/delete-node/ &slot1_lpcore_partition;
/delete-node/ &storage_partition;
/delete-node/ &scratch_partition;
/delete-node/ &coredump_partition;
&flash0 {
partitions {
/* LittleFS 384 KB for DataStore + file-based BLE settings */
lfs_partition: partition@fa0000 {
label = "lfs";
reg = <0xFA0000 0x60000>;
};
};
};
/* LittleFS auto-mount — standard /lfs mount point */
#include "../../common/filesystem.dtsi"
@@ -0,0 +1,6 @@
board:
name: heltec_wifi_lora32_v43
full_name: Heltec WiFi LoRa 32 V4.3
vendor: heltec
socs:
- name: esp32s3
@@ -0,0 +1,53 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Heltec WiFi LoRa 32 V4.3 — Pin Control Definitions
*
* Pin assignments identical to V4.2 (GPIO5/CTX is plain GPIO, no pinctrl entry needed):
* UART0: TX=GPIO43, RX=GPIO44
* I2C0: SDA=GPIO17, SCL=GPIO18 (SSD1306 OLED + sensors)
* SPI2: SCLK=GPIO9, MOSI=GPIO10, MISO=GPIO11 (SX1262 LoRa)
* LEDC0: CH0=GPIO35 (PWM LED)
*/
#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;
};
};
i2c0_default: i2c0_default {
group1 {
pinmux = <I2C0_SDA_GPIO17>,
<I2C0_SCL_GPIO18>;
drive-open-drain;
};
};
spim2_default: spim2_default {
group1 {
pinmux = <SPIM2_MISO_GPIO11>,
<SPIM2_SCLK_GPIO9>;
};
group2 {
pinmux = <SPIM2_MOSI_GPIO10>;
output-low;
};
};
ledc0_default: ledc0_default {
group1 {
pinmux = <LEDC_CH0_GPIO35>;
output-enable;
};
};
};
@@ -0,0 +1,234 @@
/*
* SPDX-License-Identifier: Apache-2.0
* Heltec WiFi LoRa 32 V4.3 — ZephCore procpu DTS
*
* Hardware:
* MCU: ESP32-S3 (16 MB flash, 2 MB QSPI PSRAM)
* Radio: SX1262 on SPI2, KCT8103L external PA/FEM
* OLED: SSD1306 128x64 on I2C0
* Button: GPIO0
* LED: GPIO35 (PWM via LEDC0)
* Vext: GPIO36 active-HIGH (powers OLED and sensors)
* BatADC: GPIO1 (ADC1 ch0), enable GPIO37 active-LOW
*
* V4.3 vs V4.2 difference — FEM chip changed:
* V4.2: GC1109 PA — TX_EN on GPIO46 (CPS)
* V4.3: KCT8103L PA — CTX on GPIO5 (HIGH=TX, LOW=RX/LNA)
* Detection: GPIO2 default pull differs (GC1109: pull-down, KCT8103L: pull-up)
* (See MeshCore PR#1867, Heltec schematic HTIT-WB32LAF_V4.3)
*
* All other hardware is identical to V4.2:
* GPIO7 = VFEM_Ctrl LDO power (active-HIGH, always-on)
* GPIO2 = KCT8103L CSD (chip enable, active-HIGH)
* GPIO5 = KCT8103L CTX (TX/RX select: HIGH=TX, LOW=RX/LNA) ← V4.3 change
*
* LoRa SPI2: SCLK=9, MOSI=10, MISO=11, NSS=8
* LoRa ctrl: DIO1=14, BUSY=13, RESET=12
* FEM: POWER=7, CSD=2, CTX=5
* Display I2C0: SDA=17, SCL=18, RST=21
*/
/dts-v1/;
#include <espressif/esp32s3/esp32s3_wroom_n16r2.dtsi>
#include "heltec_wifi_lora32_v43-pinctrl.dtsi"
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/dt-bindings/lora/sx126x.h>
#include <espressif/partitions_0x0_amp_16M.dtsi>
/ {
model = "Heltec WiFi LoRa 32 V4.3 PROCPU";
compatible = "heltec,wifi-lora32-v4_3";
aliases {
led0 = &led0;
pwm-0 = &ledc0;
pwm-led0 = &pwm_led_white;
uart-0 = &uart0;
i2c-0 = &i2c0;
lora0 = &lora0;
sw0 = &button0;
watchdog0 = &wdt0;
};
leds {
compatible = "gpio-leds";
led0: led {
gpios = <&gpio1 3 GPIO_ACTIVE_HIGH>; /* GPIO35 = gpio1 pin 3 */
label = "White LED";
};
};
pwmleds {
compatible = "pwm-leds";
pwm_led_white: pwm_led_gpio35 {
label = "White PWM LED";
pwms = <&ledc0 0 PWM_MSEC(10) PWM_POLARITY_NORMAL>;
};
};
buttons: buttons {
compatible = "gpio-keys";
button0: button_0 {
gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
label = "USER Key";
zephyr,code = <INPUT_KEY_0>;
};
};
vbatt {
compatible = "voltage-divider";
io-channels = <&adc1 0>; /* GPIO1 = ADC1 ch0 */
output-ohms = <100000>;
full-ohms = <(100000 + 390000)>;
};
/* Vext — powers OLED and external peripherals (active-HIGH, same as V4.2) */
vext_ctrl: vext_ctrl {
compatible = "power-domain-gpio";
enable-gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>; /* GPIO36 = gpio1 pin 4 */
#power-domain-cells = <0>;
label = "Vext Control";
};
/* ADC enable — GPIO37 (gpio1 pin 5) must be driven HIGH to read battery */
adc_ctrl: adc_ctrl {
compatible = "power-domain-gpio";
enable-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>; /* GPIO37 = gpio1 pin 5, active-LOW */
#power-domain-cells = <0>;
label = "ADC Control";
};
/* FEM LDO power supply — GPIO7 powers the KCT8103L LDO.
* Must be HIGH before any radio operation. Keep always-on. */
fem_power: fem-power {
compatible = "regulator-fixed";
regulator-name = "fem-power";
enable-gpios = <&gpio0 7 GPIO_ACTIVE_HIGH>; /* VFEM_Ctrl = GPIO7 */
regulator-boot-on;
regulator-always-on;
};
chosen {
zephyr,sram = &sram1;
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,flash = &flash0;
zephyr,code-partition = &slot0_partition;
zephyr,bt-hci = &esp32_bt_hci;
zephyr,display = &ssd1306_128x64;
};
};
&adc1 {
status = "okay";
};
&uart0 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart0_default>;
pinctrl-names = "default";
};
&gpio0 {
status = "okay";
};
&gpio1 {
status = "okay";
};
/* Reduce CPU clock from 240 MHz to 160 MHz — saves power, HAL minimum */
&cpu0 {
clock-frequency = <DT_FREQ_M(160)>;
};
&cpu1 {
clock-frequency = <DT_FREQ_M(160)>;
};
&ledc0 {
pinctrl-0 = <&ledc0_default>;
pinctrl-names = "default";
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel0@0 {
reg = <0x0>;
timer = <0>;
};
};
&i2c0 {
status = "okay";
clock-frequency = <I2C_BITRATE_FAST>;
pinctrl-0 = <&i2c0_default>;
pinctrl-names = "default";
ssd1306_128x64: display-controller@3c {
compatible = "solomon,ssd1306";
reg = <0x3c>;
width = <128>;
height = <64>;
segment-offset = <0>;
page-offset = <0>;
display-offset = <0>;
multiplex-ratio = <63>;
segment-remap;
com-invdir;
inversion-on;
prechargep = <0x22>;
reset-gpios = <&gpio0 21 GPIO_ACTIVE_LOW>; /* RST_OLED = GPIO21 */
power-domains = <&vext_ctrl>;
};
};
&spi2 {
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
pinctrl-0 = <&spim2_default>;
pinctrl-names = "default";
cs-gpios = <&gpio0 8 GPIO_ACTIVE_LOW>; /* NSS = GPIO8 */
lora0: modem@0 {
compatible = "semtech,sx1262";
reg = <0>;
reset-gpios = <&gpio0 12 (GPIO_OPEN_DRAIN | GPIO_ACTIVE_LOW)>; /* RESET = GPIO12 */
busy-gpios = <&gpio0 13 GPIO_ACTIVE_HIGH>; /* BUSY = GPIO13 */
dio1-gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>; /* DIO1 = GPIO14 */
/* External PA/FEM control (KCT8103L, V4.3):
* GPIO2 = CSD (chip enable): driven HIGH during radio, LOW in sleep
* GPIO5 = CTX (TX/RX select): HIGH during TX, LOW during RX/LNA
* V4.3 CHANGE: CTX moved from GPIO46 (GC1109 CPS) to GPIO5 (KCT8103L CTX). */
antenna-enable-gpios = <&gpio0 2 GPIO_ACTIVE_HIGH>; /* KCT8103L CSD = GPIO2 */
tx-enable-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>; /* KCT8103L CTX = GPIO5 */
/* DIO2 drives the SX1262 internal RF switch (TX path select).
* Combined with tx-enable-gpios, this keeps both the internal and
* external PA switch in sync with no software intervention needed. */
dio2-tx-enable;
/* TCXO 1.8V via DIO3 — same as V4.2 */
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_1V8>;
tcxo-power-startup-delay-ms = <5>;
spi-max-frequency = <4000000>;
};
};
&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"; };
@@ -0,0 +1,12 @@
# Heltec WiFi LoRa 32 V4.3 — minimal Zephyr defconfig (procpu)
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y
CONFIG_GPIO=y
CONFIG_CLOCK_CONTROL=y
CONFIG_PINCTRL=y
CONFIG_SPI=y
CONFIG_I2C=y
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
+7
View File
@@ -32,6 +32,13 @@ SWD flash: `west flash` (requires J-Link, pyocd, or nrfjprog connected).
| XIAO ESP32-S3 | `west build -b xiao_esp32s3/esp32s3/procpu zephcore` | `west flash` |
| Station G2 | `west build -b station_g2/esp32s3/procpu zephcore` | `west flash` |
| 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 V4.2 vs V4.3:** The hardware revision is printed on the PCB silkscreen. If
unclear, check GPIO2's default pull: the V4.2 GC1109 PA has an internal pull-down
(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.
ESP32 flash uses esptool over USB. Hold BOOT button if device doesn't enter download mode.
@@ -62,6 +62,19 @@ void sx126x_set_rx_boost(const struct device *dev, bool enable);
*/
bool sx126x_is_chip_busy(const struct device *dev);
/**
* @brief Apply undocumented register 0x8B5 RX improvement for Heltec V4
*
* Sets the LSB of register 0x8B5 which consistently improves RX reception
* on boards with GC1109 or KCT8103L PA (Heltec V4/V4.3). Described by
* Heltec engineer @Quency-D in MeshCore PR#1398.
*
* Must be called after the first lora_config() completes.
*
* @param dev LoRa device
*/
void sx126x_apply_heltec_reg_patch(const struct device *dev);
/**
* @brief Reset AGC by performing warm sleep + full recalibration
*
@@ -1,5 +1,74 @@
diff --git a/drivers/lora/CMakeLists.txt b/drivers/lora/CMakeLists.txt
index 9cd035fcf2b..18d04ec099a 100644
--- a/drivers/lora/CMakeLists.txt
+++ b/drivers/lora/CMakeLists.txt
@@ -1,7 +1,11 @@
# SPDX-License-Identifier: Apache-2.0
+# ZephCore patch: adds lr11xx driver subdirectory.
zephyr_sources_ifdef(CONFIG_LORA_SHELL shell.c)
+add_subdirectory_ifdef(CONFIG_LORA_LR11XX lr11xx)
+add_subdirectory_ifdef(CONFIG_LORA_LR20XX lr20xx)
+
# zephyr-keep-sorted-start
add_subdirectory_ifdef(CONFIG_LORA_MODULE_BACKEND_LORAMAC_NODE loramac-node)
add_subdirectory_ifdef(CONFIG_LORA_MODULE_BACKEND_LORA_BASICS_MODEM lora-basics-modem)
diff --git a/drivers/lora/Kconfig b/drivers/lora/Kconfig
index 657bdb9ce28..f8513b37044 100644
--- a/drivers/lora/Kconfig
+++ b/drivers/lora/Kconfig
@@ -5,6 +5,7 @@
#
# Top-level configuration file for LORA drivers.
+# ZephCore patch: adds lr11xx driver Kconfig.
menuconfig LORA
bool "LoRa drivers"
@@ -60,6 +61,8 @@ config LORA_INIT_PRIORITY
rsource "Kconfig.sx12xx"
rsource "Kconfig.rylrxxx"
+rsource "lr11xx/Kconfig"
+rsource "lr20xx/Kconfig"
rsource "lora-basics-modem/Kconfig"
rsource "native/Kconfig"
diff --git a/drivers/lora/loramac-node/sx12xx_common.c b/drivers/lora/loramac-node/sx12xx_common.c
index 17689720dd2..09982dc2e70 100644
--- a/drivers/lora/loramac-node/sx12xx_common.c
+++ b/drivers/lora/loramac-node/sx12xx_common.c
@@ -195,8 +195,15 @@ static void sx12xx_ev_rx_error(void)
/**
* @brief Convert Zephyr bandwidth enum to loramac-node bandwidth index
*
- * The loramac-node library expects bandwidth as an index (0, 1, 2) into its
- * internal Bandwidths[] array, not the actual kHz value.
+ * The loramac-node library expects bandwidth as an index into its internal
+ * Bandwidths[] array (not the actual kHz value). The SX127x hardware
+ * supports sub-125 kHz LoRa bandwidths; index 3 maps to 62.5 kHz (reg BW=6).
+ *
+ * Index mapping (kept in sync with sx1276.c / sx1272.c):
+ * 0 → 125 kHz (reg BW 7)
+ * 1 → 250 kHz (reg BW 8)
+ * 2 → 500 kHz (reg BW 9)
+ * 3 → 62.5 kHz (reg BW 6) — SX127x only
*
* @param bandwidth Zephyr lora_signal_bandwidth enum value
* @param bw_idx Pointer to store the resulting bandwidth index
@@ -215,6 +222,9 @@ static int sx12xx_get_bandwidth_idx(enum lora_signal_bandwidth bandwidth,
case BW_500_KHZ:
*bw_idx = 2;
break;
+ case BW_62_KHZ:
+ *bw_idx = 3;
+ break;
default:
return -EINVAL;
}
diff --git a/drivers/lora/native/sx126x/sx126x.c b/drivers/lora/native/sx126x/sx126x.c
index 30243ba5dc7..0ba41d05da0 100644
index 30243ba5dc7..ddc4ce19660 100644
--- a/drivers/lora/native/sx126x/sx126x.c
+++ b/drivers/lora/native/sx126x/sx126x.c
@@ -10,10 +10,19 @@
@@ -423,7 +492,7 @@ index 30243ba5dc7..0ba41d05da0 100644
/* Set packet parameters */
ret = sx126x_set_packet_params(dev,
@@ -1083,14 +1303,407 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
@@ -1083,14 +1303,423 @@ static int sx126x_lora_test_cw(const struct device *dev, uint32_t frequency,
return 0;
}
@@ -529,6 +598,22 @@ index 30243ba5dc7..0ba41d05da0 100644
+ k_mutex_unlock(&data->lock);
+}
+
+void sx126x_apply_heltec_reg_patch(const struct device *dev)
+{
+ struct sx126x_data *data = dev->data;
+ uint8_t val = 0;
+
+ /* Set LSB of undocumented register 0x8B5 to improve RX performance.
+ * Described by Heltec engineer @Quency-D in MeshCore PR#1398 —
+ * consistently improves reception on boards with GC1109/KCT8103L PA
+ * (Heltec V4/V4.3). Must be called after lora_config(). */
+ k_mutex_lock(&data->lock, K_FOREVER);
+ sx126x_hal_read_regs(dev, 0x08B5, &val, 1);
+ val |= 0x01;
+ sx126x_hal_write_regs(dev, 0x08B5, &val, 1);
+ k_mutex_unlock(&data->lock);
+}
+
+/* ── Driver API: CAD ────────────────────────────────────────────────── */
+
+/* Recommended cad_detect_peak values per SF for 2-symbol CAD.
@@ -838,7 +923,7 @@ index 30243ba5dc7..0ba41d05da0 100644
};
#ifdef CONFIG_PM_DEVICE
@@ -1121,9 +1734,18 @@ static int sx126x_init(const struct device *dev)
@@ -1121,9 +1750,18 @@ static int sx126x_init(const struct device *dev)
k_msgq_init(&data->rx_msgq, (char *)&data->rx_result,
sizeof(struct sx126x_rx_result), 1);
k_work_init(&data->irq_work, sx126x_irq_work_handler);