Skip to content

Commit 7aca506

Browse files
CaTeIMjgriffiths
authored andcommitted
Fix: Battery stability and USB detection for T-Display/S3
Also fix formatting from previous commits.
1 parent 913ff10 commit 7aca506

File tree

3 files changed

+170
-24
lines changed

3 files changed

+170
-24
lines changed

main/power.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@
2727
#include "power/ip5306.inc"
2828
#elif defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2)
2929
#include "power/wslcdtouch2.inc"
30-
#elif defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAYS3) && defined(CONFIG_HAS_BATTERY)
31-
// ttgo-tdisplays3 can read battery level and charging status if a battery is connected
30+
#elif defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAYS3)
31+
// T-Display S3 can read battery level but lacks hardware to read charging status
3232
#include "power/tdisplays3.inc"
33+
#elif defined(CONFIG_BOARD_TYPE_TTGO_TDISPLAY)
34+
// T-Display can read battery level but lacks hardware to read charging status
35+
#include "power/tdisplay.inc"
3336
#else
3437
// Stubs for other hw boards (ie. no power management)
3538
#include "power/minimal.inc"

main/power/tdisplay.inc

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// T-Display (ESP32) implementation without PMIC (ADC-based)
2+
//
3+
4+
#include <driver/gpio.h>
5+
#include <esp_adc/adc_cali.h>
6+
#include <esp_adc/adc_cali_scheme.h>
7+
#include <esp_adc/adc_oneshot.h>
8+
#include <esp_sleep.h>
9+
10+
#define BATTERY_ADC_CHANNEL ADC_CHANNEL_6
11+
#define BATTERY_ADC_ATTEN ADC_ATTEN_DB_12
12+
#define BATTERY_EMA_ALPHA 0.3f // estimated moving average smoothing factor
13+
14+
static adc_oneshot_unit_handle_t adc1_handle = NULL;
15+
static adc_cali_handle_t adc1_cali_chan0_handle = NULL;
16+
static int ema_voltage = 0;
17+
18+
esp_err_t power_init(void)
19+
{
20+
// Initialise the ADC to measure battery level
21+
adc_oneshot_unit_init_cfg_t init_config1 = {
22+
.unit_id = ADC_UNIT_1,
23+
};
24+
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
25+
JADE_ASSERT(adc1_handle);
26+
// ADC Config
27+
adc_oneshot_chan_cfg_t config = {
28+
.atten = BATTERY_ADC_ATTEN,
29+
.bitwidth = ADC_BITWIDTH_DEFAULT,
30+
};
31+
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, BATTERY_ADC_CHANNEL, &config));
32+
// ESP32 (original) uses Line Fitting, not Curve Fitting like S3
33+
adc_cali_line_fitting_config_t cali_config = {
34+
.unit_id = ADC_UNIT_1,
35+
.atten = BATTERY_ADC_ATTEN,
36+
.bitwidth = ADC_BITWIDTH_DEFAULT,
37+
};
38+
if (adc_cali_create_scheme_line_fitting(&cali_config, &adc1_cali_chan0_handle) != ESP_OK) {
39+
JADE_LOGW("ADC calibration not available, continuing without it");
40+
adc1_cali_chan0_handle = NULL;
41+
}
42+
return ESP_OK;
43+
}
44+
45+
esp_err_t power_shutdown(void)
46+
{
47+
esp_deep_sleep_start();
48+
return ESP_OK;
49+
}
50+
esp_err_t power_screen_on(void) { return ESP_OK; }
51+
esp_err_t power_screen_off(void) { return ESP_OK; }
52+
esp_err_t power_backlight_on(const uint8_t brightness) { return ESP_OK; }
53+
esp_err_t power_backlight_off(void) { return ESP_OK; }
54+
esp_err_t power_camera_on(void) { return ESP_OK; }
55+
esp_err_t power_camera_off(void) { return ESP_OK; }
56+
57+
uint16_t power_get_vbat(void)
58+
{
59+
JADE_ASSERT(adc1_handle);
60+
int raw_adc, voltage;
61+
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, BATTERY_ADC_CHANNEL, &raw_adc));
62+
if (adc1_cali_chan0_handle) {
63+
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_chan0_handle, raw_adc, &voltage));
64+
} else {
65+
// Fallback for ESP32: 3.3V ref approx (less accurate without cali)
66+
voltage = (raw_adc * 3300) / 4095;
67+
}
68+
// Apply Estimated Moving Average (EMA)
69+
const int ema_v = ema_voltage ? ema_voltage : voltage;
70+
ema_voltage = (BATTERY_EMA_ALPHA * voltage) + ((1 - BATTERY_EMA_ALPHA) * ema_v);
71+
// T-Display voltage divider is 2 (100k/100k)
72+
return (uint16_t)(ema_voltage * 2);
73+
}
74+
75+
uint8_t power_get_battery_status(void)
76+
{
77+
const uint16_t vbat = power_get_vbat();
78+
79+
if (vbat > 4000) {
80+
return 5;
81+
} else if (vbat > 3800) {
82+
return 4;
83+
} else if (vbat > 3600) {
84+
return 3;
85+
} else if (vbat > 3400) {
86+
return 2;
87+
} else if (vbat > 3200) {
88+
return 1;
89+
}
90+
return 0;
91+
}
92+
93+
bool power_get_battery_charging(void)
94+
{
95+
// The charging IC STAT pin is not connected to GPIO.
96+
// Return false to avoid showing "Charging" icon permanently when on USB.
97+
return false;
98+
}
99+
100+
uint16_t power_get_ibat_charge(void) { return 0; }
101+
uint16_t power_get_ibat_discharge(void) { return 0; }
102+
uint16_t power_get_vusb(void) { return 0; }
103+
uint16_t power_get_iusb(void) { return 0; }
104+
uint16_t power_get_temp(void) { return 0; }
105+
106+
void disable_usb_host(void) {}
107+
void enable_usb_host(void) {}
108+
109+
bool usb_is_powered(void)
110+
{
111+
// The T-Display (ESP32) does not have native USB (uses CP2104/CH9102 bridge).
112+
// We cannot reliably detect if we are on USB or Battery because the charging IC
113+
// applies voltage to the battery rails even without a battery.
114+
const uint16_t vbat = power_get_vbat();
115+
return (vbat < 1000) || (vbat > 4150);
116+
}

main/power/tdisplays3.inc

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
1-
// tdisplays3 and devices with no power-management but it can measure voltage level of the battery and
2-
// detect charging status when a battery is connected through an analog input
1+
// T-Display S3 implementation without PMIC (ADC-based)
32
//
3+
44
#include <driver/gpio.h>
55
#include <esp_adc/adc_cali.h>
6+
#include <esp_adc/adc_cali_scheme.h>
67
#include <esp_adc/adc_oneshot.h>
8+
#include <esp_sleep.h>
9+
10+
#include "tusb.h"
711

812
#define BATTERY_ADC_CHANNEL ADC_CHANNEL_3
13+
#define BATTERY_ADC_ATTEN ADC_ATTEN_DB_12
14+
#define BATTERY_EMA_ALPHA 0.3f // estimated moving average smoothing factor
915

1016
static adc_oneshot_unit_handle_t adc1_handle = NULL;
11-
static adc_cali_handle_t adc1_cali_handle = NULL;
17+
static adc_cali_handle_t adc1_cali_chan0_handle = NULL;
18+
static int ema_voltage = 0;
1219

1320
esp_err_t power_init(void)
1421
{
@@ -20,22 +27,30 @@ esp_err_t power_init(void)
2027
JADE_ASSERT(adc1_handle);
2128
// ADC Config
2229
adc_oneshot_chan_cfg_t config = {
23-
.atten = ADC_ATTEN_DB_12,
30+
.atten = BATTERY_ADC_ATTEN,
2431
.bitwidth = ADC_BITWIDTH_DEFAULT,
2532
};
2633
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, BATTERY_ADC_CHANNEL, &config));
27-
// Curve fitting calibration
34+
// Use voltage calibration
2835
adc_cali_curve_fitting_config_t cali_config = {
2936
.unit_id = ADC_UNIT_1,
30-
.atten = ADC_ATTEN_DB_12,
37+
.atten = BATTERY_ADC_ATTEN,
3138
.bitwidth = ADC_BITWIDTH_DEFAULT,
3239
};
33-
ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &adc1_cali_handle));
40+
if (adc_cali_create_scheme_curve_fitting(&cali_config, &adc1_cali_chan0_handle) != ESP_OK) {
41+
JADE_LOGW("ADC calibration not available, continuing without it");
42+
adc1_cali_chan0_handle = NULL;
43+
}
3444
return ESP_OK;
3545
}
3646

37-
esp_err_t power_shutdown(void) { return ESP_OK; }
47+
esp_err_t power_shutdown(void)
48+
{
49+
esp_deep_sleep_start();
50+
return ESP_OK;
51+
}
3852
esp_err_t power_screen_on(void) { return ESP_OK; }
53+
esp_err_t power_screen_off(void) { return ESP_OK; }
3954
esp_err_t power_backlight_on(const uint8_t brightness) { return ESP_OK; }
4055
esp_err_t power_backlight_off(void) { return ESP_OK; }
4156
esp_err_t power_camera_on(void) { return ESP_OK; }
@@ -44,12 +59,20 @@ esp_err_t power_camera_off(void) { return ESP_OK; }
4459
uint16_t power_get_vbat(void)
4560
{
4661
JADE_ASSERT(adc1_handle);
47-
int cal_vbat = 0;
48-
int raw_vbat = 0;
49-
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, BATTERY_ADC_CHANNEL, &raw_vbat));
50-
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_handle, raw_vbat, &cal_vbat));
51-
return (uint16_t)(cal_vbat * 2);
62+
int raw_adc, voltage;
63+
ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, BATTERY_ADC_CHANNEL, &raw_adc));
64+
if (adc1_cali_chan0_handle) {
65+
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_chan0_handle, raw_adc, &voltage));
66+
} else {
67+
voltage = (raw_adc * 3300) / 4095;
68+
}
69+
// Apply Estimated Moving Average (EMA)
70+
const int ema_v = ema_voltage ? ema_voltage : voltage;
71+
ema_voltage = (BATTERY_EMA_ALPHA * voltage) + ((1 - BATTERY_EMA_ALPHA) * ema_v);
72+
// T-Display S3 voltage divider is 2 (100k/100k)
73+
return (uint16_t)(ema_voltage * 2);
5274
}
75+
5376
uint8_t power_get_battery_status(void)
5477
{
5578
const uint16_t vbat = power_get_vbat();
@@ -70,11 +93,8 @@ uint8_t power_get_battery_status(void)
7093

7194
bool power_get_battery_charging(void)
7295
{
73-
uint16_t vbat = power_get_vbat();
74-
// If the voltage is greater than 4500 mV and less than 4750 it means its charging
75-
if (vbat > 4500 && vbat < 4750) {
76-
return true;
77-
}
96+
// The charging IC STAT pin is not connected to GPIO, so we can't know for sure.
97+
// Return false to avoid showing "Charging" icon permanently when on USB.
7898
return false;
7999
}
80100

@@ -84,12 +104,19 @@ uint16_t power_get_vusb(void) { return 0; }
84104
uint16_t power_get_iusb(void) { return 0; }
85105
uint16_t power_get_temp(void) { return 0; }
86106

107+
void disable_usb_host(void) {}
108+
void enable_usb_host(void) {}
109+
87110
bool usb_is_powered(void)
88111
{
89-
// If the voltage is greater than 4500 mV it means USB is connected
90-
uint16_t vbat = power_get_vbat();
91-
if (vbat > 4500) {
112+
// Check if USB is mounted and not suspended (active connection)
113+
if (tud_mounted() && !tud_suspended()) {
114+
return true;
115+
}
116+
// Fallback: If voltage is near zero (<1V) but chip is on,
117+
// we must be running on USB power without a battery.
118+
if (power_get_vbat() < 1000) {
92119
return true;
93120
}
94121
return false;
95-
}
122+
}

0 commit comments

Comments
 (0)