fix heltec v3/4/4.3 0% battery

This commit is contained in:
liquidraver
2026-07-07 21:25:59 +02:00
parent eff498b64c
commit 247ca655a8
5 changed files with 146 additions and 58 deletions
@@ -9,13 +9,14 @@
* LoRa SPI2 pins: SCLK=9, MOSI=10, MISO=11, NSS=8
* LoRa control: DIO1=14, BUSY=13, RESET=12
* Display I2C0: SDA=17, SCL=18 (SSD1306 @ 0x3C)
* Battery ADC: GPIO1 (ADC1 ch0), enable GPIO37, divider 100K/390K
* Battery ADC: GPIO1 = ADC1 ch0 (&adc0), enable GPIO37 active-HIGH, divider 100K/390K
* Vext: GPIO36 (active-LOW, powers OLED) — regulator + vin-supply in overlay
* Button: GPIO0
* LED: GPIO35
*/
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/dt-bindings/adc/adc.h>
/ {
chosen {
@@ -33,22 +34,36 @@
buttons: buttons {
};
/* Battery ADC — ZephCore reads via zephyr,user { io-channels }
/* Battery ADC — ZephCore reads via zephyr,user { io-channels }.
* Upstream vbatt node uses voltage-divider compatible which ZephCore
* doesn't use. Multiplier: 3300mV full-scale * 5.42x = 17886.
* (5.42x is Arduino's empirical divider factor for V3/V4.) */
* doesn't use (deleted below).
*
* IMPORTANT: the battery pin is GPIO1 = ADC unit 1 channel 0. In the
* upstream esp32s3 DTS the node *labelled* adc0 is unit 1 and the node
* labelled adc1 is unit 2 (the label is 0-indexed, the unit is 1-indexed).
* So GPIO1 lives on &adc0. The previous &adc1 (unit 2) channel 0 mapped to
* ADC2_CH0 = GPIO11 = the SX1262 MISO — wrong peripheral, always read 0.
*
* Multiplier: Vref_internal(1100) * divider_ratio(5.2) = 5720, matching the
* Heltec Wireless Tracker on the same 100K/390K divider (was 17886, which
* assumed a 3300mV full-scale that ADC_REF_INTERNAL does not provide). */
zephyr,user {
io-channels = <&adc1 0>;
vbat-mv-multiplier = <17886>;
io-channels = <&adc0 0>;
vbat-mv-multiplier = <5720>;
};
/* Battery ADC enable — GPIO37 (gpio1 pin 5) must be driven to read.
* Upstream has adc_ctrl power-domain-gpio; ZephCore expects a
* regulator-fixed named vbat_enable. */
* Upstream has an adc_ctrl power-domain-gpio on the same GPIO37; it is
* deleted below so this regulator is the sole owner (else the two fight
* over the pin and the per-read enable/disable in ZephyrBoard is not
* deterministic). Active-HIGH on V3.2/V4 (transistor in the enable path);
* the previous GPIO_ACTIVE_LOW never powered the divider so the read was 0.
* ZephyrBoard toggles this per read, so between reads the divider draws
* nothing. (Early V3 boards may need GPIO_ACTIVE_LOW — see GH #50.) */
vbat_enable: vbat-enable {
compatible = "regulator-fixed";
regulator-name = "vbat-enable";
enable-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
enable-gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>;
};
/* Vext — GPIO36, active-LOW enables rail (matches Arduino periph_power / MeshCore
@@ -162,3 +177,37 @@
/* After SSD1306 no longer references vext_ctrl, remove upstream power-domain node. */
/delete-node/ &vext_ctrl;
/* Battery ADC channel configuration on adc0 (ADC unit 1 -> GPIO1).
* adc0 is status="disabled" upstream, so enable it here. Without the channel
* node adc_channel_setup_dt() fails and getBattMilliVolts() returns 0 before it
* ever performs a conversion — the primary cause of the hard "0% (0mV)".
* ADC_GAIN_1 = 0 dB with ADC_REF_INTERNAL (~1100 mV); the divider output for a
* 3.0-4.2 V cell (~580-810 mV) sits well inside range. */
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};
/* Remove the upstream 'vbatt' voltage-divider (io-channels on &adc1 = unit 2 =
* GPIO11 = SX1262 MISO — wrong node) and the adc_ctrl power-domain that also
* claims GPIO37 and fights the vbat_enable regulator above. */
/ {
/delete-node/ vbatt;
};
/delete-node/ &adc_ctrl;
/* ADC unit 2 (label adc1) is unused on this board; keep it disabled so nothing
* can claim GPIO11 (SX1262 MISO) as an ADC pad. */
&adc1 {
status = "disabled";
};
@@ -3,14 +3,14 @@
* Heltec WiFi LoRa 32 V4 — ZephCore overlay
*
* The DTS already defines: SX1262 (lora0), SSD1306 OLED, button, LED, PWM,
* vext_enable, adc_ctrl, fem_power, I2C0, SPI2, BLE, WiFi.
* vext_enable, 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
* Battery ADC: GPIO1 = ADC1_CH0 (&adc0), enable GPIO37 active-HIGH, divider 100K/390K
* Vext: GPIO36 (active-HIGH, powers OLED)
* Button: GPIO0
* LED: GPIO35
@@ -26,17 +26,21 @@
};
/* 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.) */
* GPIO1 = ADC unit 1 = node &adc0 (label is 0-indexed, unit is 1-indexed);
* the old &adc1 was unit 2 = GPIO11 = SX1262 MISO — always read 0.
* Multiplier: Vref_internal(1100) * divider_ratio(5.2) = 5720 on the
* 100K/390K divider (was 17886, which assumed a 3300mV full-scale that
* ADC_REF_INTERNAL does not provide). Channel config is in the DTS. */
zephyr,user {
io-channels = <&adc1 0>;
vbat-mv-multiplier = <17886>;
io-channels = <&adc0 0>;
vbat-mv-multiplier = <5720>;
};
/* 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. */
* GPIO37 (gpio1 pin 5) driven HIGH enables the ADC input path. ZephyrBoard
* toggles this per read (enable, sample, disable), so the divider draws
* nothing between reads. The DTS no longer defines an adc_ctrl power-domain
* on the same GPIO37, so this regulator is the sole owner. */
vbat_enable: vbat-enable {
compatible = "regulator-fixed";
regulator-name = "vbat-enable";
@@ -9,7 +9,7 @@
* Button: GPIO0
* LED: GPIO35 (PWM via LEDC0)
* Vext: GPIO36 active-HIGH (powers OLED and sensors)
* BatADC: GPIO1 (ADC1 ch0), enable GPIO37 active-LOW
* BatADC: GPIO1 = ADC1_CH0 (&adc0), enable GPIO37 active-HIGH (vbat_enable in overlay)
*
* V4.2 (this file) — GC1109 PA, TX_EN on GPIO46.
* V4.3 (KCT8103L PA, CTX on GPIO5) → see heltec_wifi_lora32_v43.
@@ -36,6 +36,7 @@
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/dt-bindings/lora/sx126x.h>
#include <zephyr/dt-bindings/adc/adc.h>
#include <espressif/partitions_0x0_amp_16M.dtsi>
/ {
@@ -78,12 +79,9 @@
};
};
vbatt {
compatible = "voltage-divider";
io-channels = <&adc1 0>; /* GPIO1 = ADC1 ch0 */
output-ohms = <100000>;
full-ohms = <(100000 + 390000)>;
};
/* No 'vbatt' voltage-divider node: ZephCore reads the battery via
* zephyr,user { io-channels } (see board.overlay), and a voltage-divider
* on &adc1 ch0 would target GPIO11 = the SX1262 MISO. See &adc0 below. */
/* Vext — powers OLED and external peripherals (active-HIGH on GPIO36).
* regulator-fixed + SSD1306 vin-supply: powers rail at boot without relying on
@@ -96,14 +94,9 @@
regulator-always-on;
};
/* 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";
};
/* No adc_ctrl power-domain node: it would claim GPIO37 (gpio1 pin 5) with
* the opposite polarity and fight the vbat_enable regulator in board.overlay,
* which is the actual per-read driver for the ADC divider enable. */
/* FEM LDO power supply — GPIO7 powers the GC1109/KCT8103L LDO.
* Must be HIGH before any radio operation. Keep always-on. */
@@ -126,8 +119,30 @@
};
};
&adc1 {
/* Battery ADC on adc0 (ADC unit 1 -> GPIO1 = ADC1_CH0). The node *labelled*
* adc0 is unit 1; the node labelled adc1 is unit 2 (= GPIO11 = SX1262 MISO).
* The channel@0 config is required — without it adc_channel_setup_dt() fails and
* getBattMilliVolts() returns 0 before ever performing a conversion. ADC_GAIN_1
* (0 dB) with ADC_REF_INTERNAL (~1100 mV); the 100K/390K divider output for a
* 3.0-4.2 V cell sits well inside range. */
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};
/* ADC unit 2 (label adc1) is unused; keep it disabled so nothing can claim
* GPIO11 (SX1262 MISO) as an ADC pad. */
&adc1 {
status = "disabled";
};
&uart0 {
@@ -3,14 +3,14 @@
* Heltec WiFi LoRa 32 V4.3 — ZephCore overlay
*
* The DTS already defines: SX1262 (lora0), SSD1306 OLED, button, LED, PWM,
* vext_enable, adc_ctrl, fem_power, I2C0, SPI2, BLE, WiFi.
* vext_enable, 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
* Battery ADC: GPIO1 = ADC1_CH0 (&adc0), enable GPIO37 active-HIGH, divider 100K/390K
* Vext: GPIO36 (active-HIGH, powers OLED)
* Button: GPIO0
* LED: GPIO35
@@ -26,17 +26,21 @@
};
/* 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.) */
* GPIO1 = ADC unit 1 = node &adc0 (label is 0-indexed, unit is 1-indexed);
* the old &adc1 was unit 2 = GPIO11 = SX1262 MISO — always read 0.
* Multiplier: Vref_internal(1100) * divider_ratio(5.2) = 5720 on the
* 100K/390K divider (was 17886, which assumed a 3300mV full-scale that
* ADC_REF_INTERNAL does not provide). Channel config is in the DTS. */
zephyr,user {
io-channels = <&adc1 0>;
vbat-mv-multiplier = <17886>;
io-channels = <&adc0 0>;
vbat-mv-multiplier = <5720>;
};
/* 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. */
* GPIO37 (gpio1 pin 5) driven HIGH enables the ADC input path. ZephyrBoard
* toggles this per read (enable, sample, disable), so the divider draws
* nothing between reads. The DTS no longer defines an adc_ctrl power-domain
* on the same GPIO37, so this regulator is the sole owner. */
vbat_enable: vbat-enable {
compatible = "regulator-fixed";
regulator-name = "vbat-enable";
@@ -9,7 +9,7 @@
* Button: GPIO0
* LED: GPIO35 (PWM via LEDC0)
* Vext: GPIO36 active-HIGH (powers OLED and sensors)
* BatADC: GPIO1 (ADC1 ch0), enable GPIO37 active-LOW
* BatADC: GPIO1 = ADC1_CH0 (&adc0), enable GPIO37 active-HIGH (vbat_enable in overlay)
*
* V4.3 vs V4.2 difference — FEM chip changed:
* V4.2: GC1109 PA — TX_EN on GPIO46 (CPS)
@@ -35,6 +35,7 @@
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>
#include <zephyr/dt-bindings/lora/sx126x.h>
#include <zephyr/dt-bindings/adc/adc.h>
#include <espressif/partitions_0x0_amp_16M.dtsi>
/ {
@@ -77,12 +78,9 @@
};
};
vbatt {
compatible = "voltage-divider";
io-channels = <&adc1 0>; /* GPIO1 = ADC1 ch0 */
output-ohms = <100000>;
full-ohms = <(100000 + 390000)>;
};
/* No 'vbatt' voltage-divider node: ZephCore reads the battery via
* zephyr,user { io-channels } (see board.overlay), and a voltage-divider
* on &adc1 ch0 would target GPIO11 = the SX1262 MISO. See &adc0 below. */
/* Vext — powers OLED and external peripherals (active-HIGH on GPIO36).
* regulator-fixed + SSD1306 vin-supply: powers rail at boot without relying on
@@ -95,13 +93,9 @@
regulator-always-on;
};
/* 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";
};
/* No adc_ctrl power-domain node: it would claim GPIO37 (gpio1 pin 5) with
* the opposite polarity and fight the vbat_enable regulator in board.overlay,
* which is the actual per-read driver for the ADC divider enable. */
/* FEM LDO power supply — GPIO7 powers the KCT8103L LDO.
* Must be HIGH before any radio operation. Keep always-on. */
@@ -124,8 +118,30 @@
};
};
&adc1 {
/* Battery ADC on adc0 (ADC unit 1 -> GPIO1 = ADC1_CH0). The node *labelled*
* adc0 is unit 1; the node labelled adc1 is unit 2 (= GPIO11 = SX1262 MISO).
* The channel@0 config is required — without it adc_channel_setup_dt() fails and
* getBattMilliVolts() returns 0 before ever performing a conversion. ADC_GAIN_1
* (0 dB) with ADC_REF_INTERNAL (~1100 mV); the 100K/390K divider output for a
* 3.0-4.2 V cell sits well inside range. */
&adc0 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
channel@0 {
reg = <0>;
zephyr,gain = "ADC_GAIN_1";
zephyr,reference = "ADC_REF_INTERNAL";
zephyr,acquisition-time = <ADC_ACQ_TIME_DEFAULT>;
zephyr,resolution = <12>;
};
};
/* ADC unit 2 (label adc1) is unused; keep it disabled so nothing can claim
* GPIO11 (SX1262 MISO) as an ADC pad. */
&adc1 {
status = "disabled";
};
&uart0 {