fix(battery): detect TP4056 charging and full states on new board

This commit is contained in:
chenjintao
2026-10-23 16:23:39 +08:00
parent 415c75b0e6
commit 6116a67d5f
13 changed files with 617 additions and 249 deletions
@@ -7,8 +7,6 @@
#include "control_plane_internal.h"
#include "domain.h"
#include "driver/gpio.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -18,7 +16,7 @@
/*
* 电池后台策略层:domain 负责把原始电压稳定为状态,本任务负责提示音、充满确认和受控关机。
* GPIO ISR 只唤醒/通知,不读取 ADC 或播放音频;所有慢操作都在任务上下文完成。
* 两路充电输入固定100ms轮询;ADC按独立截止时间采样,信号抖动不加速电量和关机确认。
* 关机一旦进入独立任务即视为不可逆流程,以免普通采样任务重复触发并争用电机/音频资源。
*/
@@ -26,11 +24,8 @@
#define BATTERY_WORKER_PRIORITY 2
#define BATTERY_SUPPRESSED_RETRY_MS 1000
#define BATTERY_STOP_POLL_MS 10
#define BATTERY_CHARGE_DEBOUNCE_MS 30
#define BATTERY_FULL_CONFIRM_RETRY_MS 1000
#define BATTERY_FULL_CONFIRM_TIME_MS \
((uint32_t)CONFIG_TQ_BATTERY_FULL_CONFIRM_SEC * 1000U)
#define BATTERY_FULL_CONFIRM_SAMPLES 3U
#define BATTERY_CHARGE_POLL_MS 100
#define BATTERY_CONFIRM_RETRY_MS 1000
#define BATTERY_SHUTDOWN_CONFIRM_SAMPLES 3U
#define BATTERY_EMERGENCY_CONFIRM_SAMPLES 2U
#define BATTERY_SHUTDOWN_RETRY_MS 1000
@@ -40,7 +35,7 @@
/*
* 多次确认用于排除负载突变导致的瞬时低压;紧急阈值只需两次以缩短保护延迟。
* 轻待机下采样间隔显著拉长,充电 GPIO 中断仍可及时唤醒状态更新。
* 轻待机下采样间隔显著拉长,充电输入仍保持100ms轮询。
*/
#define BATTERY_LOW_PROMPT_FILE "audio_prompts/battery_low_20.opus"
#define BATTERY_FULL_PROMPT_FILE "audio_prompts/charge_complete.opus"
@@ -69,7 +64,6 @@ static const char *TAG = "cp_battery";
static TaskHandle_t s_battery_task;
static bool s_stop_requested;
static bool s_starting;
static bool s_charge_gpio_isr_registered;
static bool s_light_standby;
static TaskHandle_t s_shutdown_task;
static uint8_t s_shutdown_confirmations;
@@ -84,58 +78,6 @@ typedef enum {
static battery_power_state_t s_power_state = BATTERY_POWER_STATE_RUNNING;
static portMUX_TYPE s_battery_lock = portMUX_INITIALIZER_UNLOCKED;
static void IRAM_ATTR battery_charge_gpio_isr(void *arg)
{
(void)arg;
TaskHandle_t task = s_battery_task;
if (task == NULL) {
return;
}
BaseType_t higher_priority_task_woken = pdFALSE;
vTaskNotifyGiveFromISR(task, &higher_priority_task_woken);
if (higher_priority_task_woken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
static esp_err_t battery_charge_interrupt_start(void)
{
if (CONFIG_TQ_BATTERY_CHARGE_GPIO < 0) {
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t err = gpio_set_intr_type((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO,
GPIO_INTR_ANYEDGE);
if (err != ESP_OK) {
return err;
}
err = gpio_isr_handler_add((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO,
battery_charge_gpio_isr,
NULL);
if (err != ESP_OK) {
(void)gpio_set_intr_type((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO,
GPIO_INTR_DISABLE);
return err;
}
s_charge_gpio_isr_registered = true;
return ESP_OK;
}
static void battery_charge_interrupt_stop(void)
{
if (!s_charge_gpio_isr_registered || CONFIG_TQ_BATTERY_CHARGE_GPIO < 0) {
return;
}
(void)gpio_intr_disable((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO);
(void)gpio_isr_handler_remove((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO);
(void)gpio_set_intr_type((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO,
GPIO_INTR_DISABLE);
s_charge_gpio_isr_registered = false;
}
static const char *charge_state_name(domain_battery_charge_state_t state)
{
switch (state) {
@@ -143,6 +85,10 @@ static const char *charge_state_name(domain_battery_charge_state_t state)
return "charging";
case DOMAIN_BATTERY_CHARGE_NOT_CHARGING:
return "not_charging";
case DOMAIN_BATTERY_CHARGE_FULL:
return "full";
case DOMAIN_BATTERY_CHARGE_FAULT:
return "fault";
default:
return "unknown";
}
@@ -342,7 +288,7 @@ static void control_plane_battery_low_hook(
}
}
static void control_plane_battery_full_hint_hook(
static void control_plane_battery_full_hook(
const domain_battery_status_t *status)
{
if (status == NULL) {
@@ -350,7 +296,7 @@ static void control_plane_battery_full_hint_hook(
}
ESP_LOGI(TAG,
"Inferred charge-complete prompt start voltage=%dmV level=%u%% file=%s",
"TP4056 charge-complete prompt start voltage=%dmV level=%u%% file=%s",
status->voltage_mv,
(unsigned)status->percentage,
BATTERY_FULL_PROMPT_FILE);
@@ -369,9 +315,10 @@ static void battery_worker(void *arg)
(void)ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
domain_battery_level_state_t previous_level = DOMAIN_BATTERY_LEVEL_UNKNOWN;
domain_battery_charge_state_t previous_charge = DOMAIN_BATTERY_CHARGE_UNKNOWN;
bool full_hint_candidate = false;
TickType_t full_hint_started_at = 0;
uint8_t full_hint_confirmations = 0U;
/* 本次运行先观察到充电才允许报满;充满后拔插或输入毛刺不能反复播放。 */
bool full_prompt_armed = false;
TickType_t next_voltage_sample = xTaskGetTickCount();
bool previous_standby = battery_light_standby();
esp_err_t init_err = domain_battery_init();
if (init_err != ESP_OK) {
@@ -381,21 +328,20 @@ static void battery_worker(void *arg)
return;
}
esp_err_t interrupt_err = battery_charge_interrupt_start();
if (interrupt_err == ESP_OK) {
ESP_LOGI(TAG,
"TP4057 charge-state interrupt ready gpio=%d debounce=%dms",
CONFIG_TQ_BATTERY_CHARGE_GPIO,
BATTERY_CHARGE_DEBOUNCE_MS);
} else if (interrupt_err != ESP_ERR_NOT_SUPPORTED) {
ESP_LOGW(TAG,
"TP4057 charge-state interrupt unavailable; using polling: %s",
esp_err_to_name(interrupt_err));
}
ESP_LOGI(TAG, "TP4056 status polling CHRG=%d FULL=%d interval=100ms stable=1000ms",
CONFIG_TQ_BATTERY_CHARGE_GPIO, CONFIG_TQ_BATTERY_FULL_GPIO);
ESP_LOGI(TAG, "Battery monitoring worker started");
while (!battery_stop_requested()) {
esp_err_t sample_err = domain_battery_sample();
esp_err_t charge_err = domain_battery_poll_charge();
TickType_t now = xTaskGetTickCount();
bool standby = battery_light_standby();
if (standby != previous_standby) {
next_voltage_sample = now;
previous_standby = standby;
}
bool voltage_due = (int32_t)(now - next_voltage_sample) >= 0;
esp_err_t sample_err = voltage_due ? domain_battery_sample() : ESP_OK;
domain_battery_status_t status = {0};
esp_err_t status_err = domain_battery_get_status(&status);
@@ -403,12 +349,14 @@ static void battery_worker(void *arg)
ESP_LOGW(TAG, "Battery sample rejected: %s", esp_err_to_name(sample_err));
}
if (status_err == ESP_OK) {
esp_err_t overlay_err = domain_runtime_update_battery_overlay(&status);
bool charge_changed = status.charge_state != previous_charge;
esp_err_t overlay_err = (voltage_due || charge_changed)
? domain_runtime_update_battery_overlay(&status) : ESP_OK;
if (overlay_err != ESP_OK && overlay_err != ESP_ERR_NOT_SUPPORTED) {
ESP_LOGD(TAG, "Battery LCD overlay update skipped: %s", esp_err_to_name(overlay_err));
}
if (status.valid && !status.measurement_suppressed) {
if (voltage_due && status.valid && !status.measurement_suppressed) {
ESP_LOGI(TAG,
"Battery adc=%dmV voltage=%dmV level=%u%% charge=%s state=%s",
status.latest_adc_mv,
@@ -416,7 +364,7 @@ static void battery_worker(void *arg)
(unsigned)status.percentage,
charge_state_name(status.charge_state),
level_state_name(status.level_state));
} else if (status.measurement_suppressed) {
} else if (voltage_due && status.measurement_suppressed) {
ESP_LOGD(TAG,
"Battery SOC update suppressed adc=%dmV raw=%dmV valid=%d load=%d cooldown=%d",
status.latest_adc_mv,
@@ -443,76 +391,55 @@ static void battery_worker(void *arg)
"Battery charge state changed: %s -> %s",
charge_state_name(previous_charge),
charge_state_name(status.charge_state));
if (previous_charge == DOMAIN_BATTERY_CHARGE_CHARGING &&
status.charge_state == DOMAIN_BATTERY_CHARGE_NOT_CHARGING) {
/*
* TP4057 exposes the same high level for charge termination
* and charger removal. Voltage can only make this a useful
* UI/audio hint, not a rigorous full-charge measurement.
* Never use this inference for safety, shutdown, or power
* switching decisions.
*/
full_hint_candidate = true;
full_hint_started_at = xTaskGetTickCount();
full_hint_confirmations = 0U;
} else if (status.charge_state != DOMAIN_BATTERY_CHARGE_NOT_CHARGING) {
full_hint_candidate = false;
full_hint_confirmations = 0U;
ESP_LOGI(TAG, "TP4056 inputs valid=%d charge_active=%d full_active=%d error=%s",
status.charge_inputs_valid, status.charge_signal_active,
status.full_signal_active, esp_err_to_name(charge_err));
if (status.charge_state == DOMAIN_BATTERY_CHARGE_CHARGING) {
full_prompt_armed = true;
} else if (status.charge_state == DOMAIN_BATTERY_CHARGE_FULL) {
bool play_full_prompt = full_prompt_armed;
full_prompt_armed = false;
if (play_full_prompt) {
control_plane_battery_full_hook(&status);
}
} else {
/* 拔线、异常或读取失败都打断本轮,不能把旧充电历史当作完成事件。 */
full_prompt_armed = false;
}
previous_charge = status.charge_state;
}
if (full_hint_candidate && status.valid &&
!status.measurement_suppressed) {
if (status.charge_state != DOMAIN_BATTERY_CHARGE_NOT_CHARGING ||
status.voltage_mv < CONFIG_TQ_BATTERY_FULL_MIN_VOLTAGE_MV ||
status.percentage < CONFIG_TQ_BATTERY_FULL_MIN_PERCENT) {
ESP_LOGI(TAG,
"Charge-complete inference cancelled charge=%s voltage=%dmV level=%u%%",
charge_state_name(status.charge_state),
status.voltage_mv,
(unsigned)status.percentage);
full_hint_candidate = false;
full_hint_confirmations = 0U;
} else {
if (full_hint_confirmations < UINT8_MAX) {
++full_hint_confirmations;
}
TickType_t elapsed_ticks =
xTaskGetTickCount() - full_hint_started_at;
if (full_hint_confirmations >= BATTERY_FULL_CONFIRM_SAMPLES &&
elapsed_ticks >= pdMS_TO_TICKS(BATTERY_FULL_CONFIRM_TIME_MS)) {
control_plane_battery_full_hint_hook(&status);
full_hint_candidate = false;
full_hint_confirmations = 0U;
}
}
/* 检测到充电立即打断低压确认,不能把插拔前后的样本拼成连续低压。 */
if (status.charge_state == DOMAIN_BATTERY_CHARGE_CHARGING) {
s_shutdown_confirmations = 0U;
s_emergency_confirmations = 0U;
}
/* 安全计数只消费新的ADC样本;100ms输入轮询不得重复累计旧电压。 */
if (voltage_due && sample_err == ESP_OK) {
battery_consider_controlled_shutdown(&status);
} else if (voltage_due) {
s_shutdown_confirmations = 0U;
s_emergency_confirmations = 0U;
}
battery_consider_controlled_shutdown(&status);
}
uint32_t delay_ms;
if (status_err == ESP_OK && status.measurement_suppressed) {
delay_ms = BATTERY_SUPPRESSED_RETRY_MS;
} else if (full_hint_candidate || s_shutdown_confirmations > 0U ||
s_emergency_confirmations > 0U) {
delay_ms = BATTERY_FULL_CONFIRM_RETRY_MS;
} else if (battery_light_standby()) {
delay_ms = (uint32_t)CONFIG_TQ_LIGHT_STANDBY_BATTERY_INTERVAL_SEC * 1000U;
} else {
delay_ms = (uint32_t)CONFIG_TQ_BATTERY_SAMPLE_INTERVAL_SEC * 1000U;
}
uint32_t notifications = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(delay_ms));
if (notifications > 0U && !battery_stop_requested()) {
vTaskDelay(pdMS_TO_TICKS(BATTERY_CHARGE_DEBOUNCE_MS));
/* Collapse contact bounce into one fresh read of the final level. */
(void)ulTaskNotifyTake(pdTRUE, 0U);
if (voltage_due) {
uint32_t interval_ms;
if (status_err != ESP_OK || sample_err != ESP_OK || status.measurement_suppressed) {
interval_ms = BATTERY_SUPPRESSED_RETRY_MS;
} else if (s_shutdown_confirmations > 0U || s_emergency_confirmations > 0U) {
interval_ms = BATTERY_CONFIRM_RETRY_MS;
} else if (battery_light_standby()) {
interval_ms = (uint32_t)CONFIG_TQ_LIGHT_STANDBY_BATTERY_INTERVAL_SEC * 1000U;
} else {
interval_ms = (uint32_t)CONFIG_TQ_BATTERY_SAMPLE_INTERVAL_SEC * 1000U;
}
next_voltage_sample = xTaskGetTickCount() + pdMS_TO_TICKS(interval_ms);
}
/* 通知只用于停止/待机切换;不注册GPIO ISR,持续边沿也不会造成中断风暴。 */
(void)ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(BATTERY_CHARGE_POLL_MS));
}
battery_charge_interrupt_stop();
ESP_LOGI(TAG, "Battery monitoring worker stopped");
battery_worker_finish();
vTaskDelete(NULL);
}
@@ -524,6 +451,11 @@ esp_err_t control_plane_battery_apply_boot_guard(void)
return ESP_OK;
}
/* 深睡唤醒时先给两路状态一个有界确认窗口;不能因启动时尚未去抖就再次睡眠。 */
for (unsigned i = 0; i < 12U; ++i) {
(void)domain_battery_poll_charge();
vTaskDelay(pdMS_TO_TICKS(BATTERY_CHARGE_POLL_MS));
}
esp_err_t sample_err = domain_battery_sample();
if (sample_err != ESP_OK) {
return sample_err;
@@ -540,6 +472,7 @@ esp_err_t control_plane_battery_apply_boot_guard(void)
charge_state_name(status.charge_state),
status.valid);
if (status.charge_state == DOMAIN_BATTERY_CHARGE_CHARGING ||
status.charge_state == DOMAIN_BATTERY_CHARGE_FULL ||
(status.valid &&
status.voltage_mv >= CONFIG_TQ_BATTERY_WAKE_RECOVERY_VOLTAGE_MV)) {
domain_battery_complete_wakeup();
@@ -623,7 +556,6 @@ esp_err_t control_plane_battery_service_stop(int timeout_ms)
if (task != NULL) {
ESP_LOGW(TAG, "Battery worker stop timeout; forcing teardown");
vTaskDelete(task);
battery_charge_interrupt_stop();
return ESP_ERR_TIMEOUT;
}
return ESP_OK;
+10
View File
@@ -52,6 +52,8 @@ typedef enum {
DOMAIN_BATTERY_CHARGE_UNKNOWN = 0,
DOMAIN_BATTERY_CHARGE_NOT_CHARGING,
DOMAIN_BATTERY_CHARGE_CHARGING,
DOMAIN_BATTERY_CHARGE_FULL,
DOMAIN_BATTERY_CHARGE_FAULT,
} domain_battery_charge_state_t;
typedef enum {
@@ -67,6 +69,9 @@ typedef struct {
int latest_sample_mv;
uint8_t percentage;
domain_battery_charge_state_t charge_state;
bool charge_inputs_valid;
bool charge_signal_active;
bool full_signal_active;
domain_battery_level_state_t level_state;
bool valid;
bool latest_sample_valid;
@@ -83,6 +88,11 @@ esp_err_t domain_battery_init(void);
/** 获取并处理一次电池采样,不执行产品策略。 / Process one battery sample without product policy. */
esp_err_t domain_battery_sample(void);
/** 每100ms调用;两路输入连续采样稳定1秒后更新充电状态,间隔>250ms重新确认。
* 不访问ADC;两把锁各最多等待100ms,失败不会后台继续。并发调用安全。
*/
esp_err_t domain_battery_poll_charge(void);
/** 复制最新电池 SSOT 快照。 / Copy the latest battery SSOT snapshot into `out_status`. */
esp_err_t domain_battery_get_status(domain_battery_status_t *out_status);
@@ -100,17 +100,66 @@ static void unlock_state(void)
(void)xSemaphoreGive(s_lock);
}
static domain_battery_charge_state_t map_charge_state(
platform_battery_charge_state_t state)
/* 只有领域层解释 TP4056 CHRG#/STDBY#。两个低电平同时出现视为异常,
* 两个高电平只表示未充电,不能证明 USB 是否接入或电池是否存在。 */
static domain_battery_charge_state_t s_charge_candidate;
static int64_t s_charge_candidate_since_ms;
static int64_t s_charge_last_poll_ms;
static bool s_charge_candidate_valid;
static int64_t s_charge_last_confirmed_ms;
static domain_battery_charge_state_t decode_charge_inputs(bool charging, bool full)
{
switch (state) {
case PLATFORM_BATTERY_CHARGE_CHARGING:
return DOMAIN_BATTERY_CHARGE_CHARGING;
case PLATFORM_BATTERY_CHARGE_NOT_CHARGING:
return DOMAIN_BATTERY_CHARGE_NOT_CHARGING;
default:
return DOMAIN_BATTERY_CHARGE_UNKNOWN;
if (charging && full) {
return DOMAIN_BATTERY_CHARGE_FAULT;
}
if (charging) {
return DOMAIN_BATTERY_CHARGE_CHARGING;
}
return full ? DOMAIN_BATTERY_CHARGE_FULL : DOMAIN_BATTERY_CHARGE_NOT_CHARGING;
}
esp_err_t domain_battery_poll_charge(void)
{
/* 在领域锁内读取原始输入,保证并发采样不会倒序发布状态。
* 数字输入独立于ADC:ADC失败或电机负载抑制不影响充电状态更新。 */
if (!lock_state()) {
return ESP_ERR_TIMEOUT;
}
platform_battery_charge_inputs_t inputs = {0};
esp_err_t err = platform_battery_read_charge_inputs(&inputs);
int64_t now_ms = esp_timer_get_time() / 1000;
if (err != ESP_OK || !inputs.valid) {
s_charge_candidate_valid = false;
s_status.charge_inputs_valid = false;
s_status.charge_state = DOMAIN_BATTERY_CHARGE_UNKNOWN;
unlock_state();
return err != ESP_OK ? err : ESP_ERR_INVALID_RESPONSE;
}
s_status.charge_inputs_valid = true;
s_status.charge_signal_active = inputs.charge_level == CONFIG_TQ_BATTERY_CHARGE_ACTIVE_LEVEL;
s_status.full_signal_active = inputs.full_level == CONFIG_TQ_BATTERY_FULL_ACTIVE_LEVEL;
domain_battery_charge_state_t candidate = decode_charge_inputs(
s_status.charge_signal_active, s_status.full_signal_active);
if (!s_charge_candidate_valid || candidate != s_charge_candidate ||
now_ms - s_charge_last_poll_ms > 250 || now_ms < s_charge_last_poll_ms) {
if (s_charge_candidate_valid && now_ms - s_charge_last_poll_ms > 250) {
s_status.charge_state = DOMAIN_BATTERY_CHARGE_UNKNOWN;
}
s_charge_candidate = candidate;
s_charge_candidate_since_ms = now_ms;
s_charge_candidate_valid = true;
}
s_charge_last_poll_ms = now_ms;
if (now_ms - s_charge_candidate_since_ms >= 1000) {
s_status.charge_state = candidate;
s_charge_last_confirmed_ms = now_ms;
} else if (now_ms - s_charge_last_confirmed_ms >= 2000) {
/* 持续抖动不能无限沿用旧的“充电中”,否则可能长期屏蔽低压保护。 */
s_status.charge_state = DOMAIN_BATTERY_CHARGE_UNKNOWN;
}
unlock_state();
return ESP_OK;
}
static uint8_t voltage_to_percentage(int voltage_mv)
@@ -221,14 +270,8 @@ esp_err_t domain_battery_init(void)
return ESP_OK;
}
esp_err_t err = platform_battery_init();
if (err != ESP_OK) {
unlock_state();
return err;
}
memset(&s_status, 0, sizeof(s_status));
s_status.charge_state = DOMAIN_BATTERY_CHARGE_UNKNOWN;
/* ADC由电压采样路径幂等初始化;失败时充电输入仍能独立工作。 */
/* 允许启动保护在 ADC 初始化前独立轮询充电输入,不能清掉已确认状态。 */
s_status.level_state = DOMAIN_BATTERY_LEVEL_UNKNOWN;
s_initialized = true;
unlock_state();
@@ -264,7 +307,6 @@ esp_err_t domain_battery_sample(void)
s_status.latest_adc_mv = sample.adc_voltage_mv;
s_status.latest_sample_mv = sample.voltage_mv;
s_status.latest_sample_valid = sample.voltage_valid;
s_status.charge_state = map_charge_state(sample.charge_state);
s_status.high_load_active = sample.high_load_active;
s_status.cooldown_active = sample.cooldown_active;
s_status.measurement_suppressed = sample.high_load_active || sample.cooldown_active;
@@ -385,6 +427,11 @@ esp_err_t domain_battery_init(void)
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t domain_battery_poll_charge(void)
{
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t domain_battery_sample(void)
{
return ESP_ERR_NOT_SUPPORTED;
+10 -7
View File
@@ -403,22 +403,25 @@ void platform_power_release_deep_sleep_holds(void);
esp_err_t platform_power_enter_deep_sleep(int charger_gpio, int button_gpio);
/* platform 只报告原始充电和电压质量,电量等级/SOC 语义由 domain 层计算。 */
typedef enum {
PLATFORM_BATTERY_CHARGE_UNKNOWN = 0,
PLATFORM_BATTERY_CHARGE_NOT_CHARGING,
PLATFORM_BATTERY_CHARGE_CHARGING,
} platform_battery_charge_state_t;
/* 板级数字输入原始电平;电平组合含义及去抖由 domain 唯一解释。 */
typedef struct {
int charge_level;
int full_level;
bool valid;
} platform_battery_charge_inputs_t;
/** 读取两路原始电平;无 ADC 访问,互斥等待最多100ms。 */
esp_err_t platform_battery_read_charge_inputs(platform_battery_charge_inputs_t *out_inputs);
typedef struct {
int adc_voltage_mv;
int voltage_mv;
platform_battery_charge_state_t charge_state;
bool voltage_valid;
bool high_load_active;
bool cooldown_active;
} platform_battery_sample_t;
/** 幂等初始化电池 ADC 和 TP4057 状态输入。 / Idempotently initialize battery sensing. */
/** 幂等初始化电池 ADC 和 TP4056 两路状态输入。 / Idempotently initialize battery sensing. */
esp_err_t platform_battery_init(void);
/**
@@ -41,6 +41,7 @@ static adc_oneshot_unit_handle_t s_adc_handle;
static adc_cali_handle_t s_cali_handle;
static adc_channel_t s_adc_channel;
static bool s_initialized;
static bool s_inputs_initialized;
static StaticSemaphore_t s_adc_lock_buffer;
static SemaphoreHandle_t s_adc_lock;
static portMUX_TYPE s_adc_lock_init_guard = portMUX_INITIALIZER_UNLOCKED;
@@ -123,6 +124,53 @@ static esp_err_t create_calibration(adc_unit_t unit, adc_channel_t channel)
return ESP_ERR_NOT_SUPPORTED;
}
/* 调用者持有 ADC/输入互斥锁;数字输入可独立初始化,不依赖 ADC 校准成功。 */
static esp_err_t configure_charge_inputs_locked(void)
{
if (s_inputs_initialized) {
return ESP_OK;
}
if (!GPIO_IS_VALID_GPIO(CONFIG_TQ_BATTERY_CHARGE_GPIO) ||
!GPIO_IS_VALID_GPIO(CONFIG_TQ_BATTERY_FULL_GPIO) ||
CONFIG_TQ_BATTERY_CHARGE_GPIO == CONFIG_TQ_BATTERY_FULL_GPIO ||
CONFIG_TQ_BATTERY_ADC_GPIO == CONFIG_TQ_BATTERY_CHARGE_GPIO ||
CONFIG_TQ_BATTERY_ADC_GPIO == CONFIG_TQ_BATTERY_FULL_GPIO) {
return ESP_ERR_INVALID_ARG;
}
gpio_config_t config = {
.pin_bit_mask = gpio_mask(CONFIG_TQ_BATTERY_CHARGE_GPIO) |
gpio_mask(CONFIG_TQ_BATTERY_FULL_GPIO),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
esp_err_t err = gpio_config(&config);
if (err == ESP_OK) {
s_inputs_initialized = true;
}
return err;
}
esp_err_t platform_battery_read_charge_inputs(platform_battery_charge_inputs_t *out_inputs)
{
if (out_inputs == NULL) {
return ESP_ERR_INVALID_ARG;
}
memset(out_inputs, 0, sizeof(*out_inputs));
if (!lock_adc()) {
return ESP_ERR_TIMEOUT;
}
esp_err_t err = configure_charge_inputs_locked();
if (err == ESP_OK) {
out_inputs->charge_level = gpio_get_level(CONFIG_TQ_BATTERY_CHARGE_GPIO);
out_inputs->full_level = gpio_get_level(CONFIG_TQ_BATTERY_FULL_GPIO);
out_inputs->valid = true;
}
unlock_adc();
return err;
}
esp_err_t platform_battery_init(void)
{
if (!lock_adc()) {
@@ -133,24 +181,10 @@ esp_err_t platform_battery_init(void)
return ESP_OK;
}
if (CONFIG_TQ_BATTERY_CHARGE_GPIO >= GPIO_NUM_MAX) {
esp_err_t input_err = configure_charge_inputs_locked();
if (input_err != ESP_OK) {
unlock_adc();
return ESP_ERR_INVALID_ARG;
}
if (CONFIG_TQ_BATTERY_CHARGE_GPIO >= 0) {
gpio_config_t charge_config = {
.pin_bit_mask = gpio_mask(CONFIG_TQ_BATTERY_CHARGE_GPIO),
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
esp_err_t gpio_err = gpio_config(&charge_config);
if (gpio_err != ESP_OK) {
ESP_LOGE(TAG, "Configure charger status GPIO failed: %s", esp_err_to_name(gpio_err));
unlock_adc();
return gpio_err;
}
return input_err;
}
adc_unit_t unit = ADC_UNIT_1;
@@ -295,14 +329,6 @@ esp_err_t platform_battery_read_sample(platform_battery_sample_t *out_sample)
out_sample->voltage_mv <= BATTERY_VALID_MAX_MV;
snapshot_load_state(&out_sample->high_load_active, &out_sample->cooldown_active);
if (CONFIG_TQ_BATTERY_CHARGE_GPIO < 0) {
out_sample->charge_state = PLATFORM_BATTERY_CHARGE_UNKNOWN;
} else {
int level = gpio_get_level((gpio_num_t)CONFIG_TQ_BATTERY_CHARGE_GPIO);
out_sample->charge_state = level == CONFIG_TQ_BATTERY_CHARGE_ACTIVE_LEVEL
? PLATFORM_BATTERY_CHARGE_CHARGING
: PLATFORM_BATTERY_CHARGE_NOT_CHARGING;
}
unlock_adc();
return ESP_OK;
}
@@ -341,6 +367,14 @@ void platform_battery_load_end(platform_battery_load_source_t source)
#else
esp_err_t platform_battery_read_charge_inputs(platform_battery_charge_inputs_t *out_inputs)
{
if (out_inputs != NULL) {
memset(out_inputs, 0, sizeof(*out_inputs));
}
return ESP_ERR_NOT_SUPPORTED;
}
esp_err_t platform_battery_init(void)
{
return ESP_ERR_NOT_SUPPORTED;