diff --git a/zephcore/CMakeLists.txt b/zephcore/CMakeLists.txt index cfde60e..8f9e23a 100644 --- a/zephcore/CMakeLists.txt +++ b/zephcore/CMakeLists.txt @@ -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") diff --git a/zephcore/Kconfig b/zephcore/Kconfig index 0cdf5a7..ea9e055 100644 --- a/zephcore/Kconfig +++ b/zephcore/Kconfig @@ -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" diff --git a/zephcore/adapters/radio/SX126xRadio.cpp b/zephcore/adapters/radio/SX126xRadio.cpp index 3ef5e1d..5ab404e 100644 --- a/zephcore/adapters/radio/SX126xRadio.cpp +++ b/zephcore/adapters/radio/SX126xRadio.cpp @@ -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 ──────────────────────────────────────────────── */ diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/Kconfig b/zephcore/boards/esp32/heltec_wifi_lora32_v4/Kconfig new file mode 100644 index 0000000..959ef82 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/Kconfig @@ -0,0 +1,3 @@ +config HEAP_MEM_POOL_ADD_SIZE_BOARD + int + default 4096 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/Kconfig.heltec_wifi_lora32_v4 b/zephcore/boards/esp32/heltec_wifi_lora32_v4/Kconfig.heltec_wifi_lora32_v4 new file mode 100644 index 0000000..ea7548a --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/Kconfig.heltec_wifi_lora32_v4 @@ -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 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.cmake b/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.cmake new file mode 100644 index 0000000..f3c3ac1 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.cmake @@ -0,0 +1,7 @@ +if(NOT "${OPENOCD}" MATCHES "^${ESPRESSIF_TOOLCHAIN_PATH}/.*") + set(OPENOCD OPENOCD-NOTFOUND) +endif() +find_program(OPENOCD openocd PATHS ${ESPRESSIF_TOOLCHAIN_PATH}/openocd-esp32/bin NO_DEFAULT_PATH) + +include(${ZEPHYR_BASE}/boards/common/esp32.board.cmake) +include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.conf b/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.conf new file mode 100644 index 0000000..a5618bd --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.conf @@ -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 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.overlay b/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.overlay new file mode 100644 index 0000000..38871d8 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.overlay @@ -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 + +/ { + 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 = ; + short-codes = ; + long-codes = ; + 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 = ; + tap-codes = ; + 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" diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.yml b/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.yml new file mode 100644 index 0000000..b802895 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/board.yml @@ -0,0 +1,6 @@ +board: + name: heltec_wifi_lora32_v4 + full_name: Heltec WiFi LoRa 32 V4 + vendor: heltec + socs: + - name: esp32s3 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4-pinctrl.dtsi b/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4-pinctrl.dtsi new file mode 100644 index 0000000..eee6688 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4-pinctrl.dtsi @@ -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 +#include +#include + +&pinctrl { + uart0_default: uart0_default { + group1 { + pinmux = ; + output-high; + }; + group2 { + pinmux = ; + bias-pull-up; + }; + }; + + i2c0_default: i2c0_default { + group1 { + pinmux = , + ; + drive-open-drain; + }; + }; + + spim2_default: spim2_default { + group1 { + pinmux = , + ; + }; + group2 { + pinmux = ; + output-low; + }; + }; + + ledc0_default: ledc0_default { + group1 { + pinmux = ; + output-enable; + }; + }; +}; diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4_procpu.dts b/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4_procpu.dts new file mode 100644 index 0000000..f000835 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4_procpu.dts @@ -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 +#include "heltec_wifi_lora32_v4-pinctrl.dtsi" +#include +#include +#include +#include + +/ { + 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 = ; + }; + }; + + 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 = ; +}; + +&cpu1 { + clock-frequency = ; +}; + +&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 = ; + 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 = ; + 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"; }; diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4_procpu_defconfig b/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4_procpu_defconfig new file mode 100644 index 0000000..873f25a --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v4/heltec_wifi_lora32_v4_procpu_defconfig @@ -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 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/Kconfig b/zephcore/boards/esp32/heltec_wifi_lora32_v43/Kconfig new file mode 100644 index 0000000..959ef82 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/Kconfig @@ -0,0 +1,3 @@ +config HEAP_MEM_POOL_ADD_SIZE_BOARD + int + default 4096 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/Kconfig.heltec_wifi_lora32_v43 b/zephcore/boards/esp32/heltec_wifi_lora32_v43/Kconfig.heltec_wifi_lora32_v43 new file mode 100644 index 0000000..74d98b5 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/Kconfig.heltec_wifi_lora32_v43 @@ -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 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.cmake b/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.cmake new file mode 100644 index 0000000..f3c3ac1 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.cmake @@ -0,0 +1,7 @@ +if(NOT "${OPENOCD}" MATCHES "^${ESPRESSIF_TOOLCHAIN_PATH}/.*") + set(OPENOCD OPENOCD-NOTFOUND) +endif() +find_program(OPENOCD openocd PATHS ${ESPRESSIF_TOOLCHAIN_PATH}/openocd-esp32/bin NO_DEFAULT_PATH) + +include(${ZEPHYR_BASE}/boards/common/esp32.board.cmake) +include(${ZEPHYR_BASE}/boards/common/openocd.board.cmake) diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.conf b/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.conf new file mode 100644 index 0000000..3c0cd6b --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.conf @@ -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 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.overlay b/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.overlay new file mode 100644 index 0000000..5852e0d --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.overlay @@ -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 + +/ { + 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 = ; + short-codes = ; + long-codes = ; + 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 = ; + tap-codes = ; + 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" diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.yml b/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.yml new file mode 100644 index 0000000..31063db --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/board.yml @@ -0,0 +1,6 @@ +board: + name: heltec_wifi_lora32_v43 + full_name: Heltec WiFi LoRa 32 V4.3 + vendor: heltec + socs: + - name: esp32s3 diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43-pinctrl.dtsi b/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43-pinctrl.dtsi new file mode 100644 index 0000000..6a2352e --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43-pinctrl.dtsi @@ -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 +#include +#include + +&pinctrl { + uart0_default: uart0_default { + group1 { + pinmux = ; + output-high; + }; + group2 { + pinmux = ; + bias-pull-up; + }; + }; + + i2c0_default: i2c0_default { + group1 { + pinmux = , + ; + drive-open-drain; + }; + }; + + spim2_default: spim2_default { + group1 { + pinmux = , + ; + }; + group2 { + pinmux = ; + output-low; + }; + }; + + ledc0_default: ledc0_default { + group1 { + pinmux = ; + output-enable; + }; + }; +}; diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43_procpu.dts b/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43_procpu.dts new file mode 100644 index 0000000..1fcb772 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43_procpu.dts @@ -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 +#include "heltec_wifi_lora32_v43-pinctrl.dtsi" +#include +#include +#include +#include + +/ { + 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 = ; + }; + }; + + 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 = ; +}; + +&cpu1 { + clock-frequency = ; +}; + +&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 = ; + 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 = ; + 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"; }; diff --git a/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43_procpu_defconfig b/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43_procpu_defconfig new file mode 100644 index 0000000..4818fe8 --- /dev/null +++ b/zephcore/boards/esp32/heltec_wifi_lora32_v43/heltec_wifi_lora32_v43_procpu_defconfig @@ -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 diff --git a/zephcore/boards/example_board/README.md b/zephcore/boards/example_board/README.md index cb97721..beba719 100644 --- a/zephcore/boards/example_board/README.md +++ b/zephcore/boards/example_board/README.md @@ -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. diff --git a/zephcore/patches/zephyr-new/drivers/lora/native/sx126x/sx126x_ext.h b/zephcore/patches/zephyr-new/drivers/lora/native/sx126x/sx126x_ext.h index 10a376f..56abd59 100644 --- a/zephcore/patches/zephyr-new/drivers/lora/native/sx126x/sx126x_ext.h +++ b/zephcore/patches/zephyr-new/drivers/lora/native/sx126x/sx126x_ext.h @@ -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 * diff --git a/zephcore/patches/zephyr/0003-lora-sx126x-native.patch b/zephcore/patches/zephyr/0003-lora-sx126x-native.patch index 3a70acc..397771f 100644 --- a/zephcore/patches/zephyr/0003-lora-sx126x-native.patch +++ b/zephcore/patches/zephyr/0003-lora-sx126x-native.patch @@ -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);