fix(battery): detect TP4056 charging and full states on new board
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user