/* * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "platform.h" #include #include #include #include "driver/gpio.h" #include "esp_adc/adc_cali.h" #include "esp_adc/adc_cali_scheme.h" #include "esp_adc/adc_oneshot.h" #include "esp_log.h" #include "esp_rom_sys.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "freertos/semphr.h" #include "platform_battery_internal.h" #include "sdkconfig.h" #if CONFIG_TQ_BATTERY_ENABLE #define BATTERY_ADC_SAMPLE_COUNT 16 #define BATTERY_ADC_TRIM_COUNT 4 #define BATTERY_VALID_MIN_MV 2500 #define BATTERY_VALID_MAX_MV 4500 #define BATTERY_ADC_SAMPLE_DELAY_US 200 static const char *TAG = "platform_battery"; 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 StaticSemaphore_t s_adc_lock_buffer; static SemaphoreHandle_t s_adc_lock; static portMUX_TYPE s_adc_lock_init_guard = portMUX_INITIALIZER_UNLOCKED; static portMUX_TYPE s_state_lock = portMUX_INITIALIZER_UNLOCKED; static uint16_t s_load_ref_count[PLATFORM_BATTERY_LOAD_COUNT]; static int64_t s_cooldown_until_us; static bool lock_adc(void) { if (s_adc_lock == NULL) { portENTER_CRITICAL(&s_adc_lock_init_guard); if (s_adc_lock == NULL) { s_adc_lock = xSemaphoreCreateMutexStatic(&s_adc_lock_buffer); } portEXIT_CRITICAL(&s_adc_lock_init_guard); } return s_adc_lock != NULL && xSemaphoreTake(s_adc_lock, pdMS_TO_TICKS(100)) == pdTRUE; } static void unlock_adc(void) { (void)xSemaphoreGive(s_adc_lock); } static uint64_t gpio_mask(int gpio_num) { return 1ULL << (unsigned)gpio_num; } static void sort_ints(int *values, size_t count) { for (size_t i = 1; i < count; ++i) { int value = values[i]; size_t j = i; while (j > 0U && values[j - 1U] > value) { values[j] = values[j - 1U]; --j; } values[j] = value; } } static esp_err_t create_calibration(adc_unit_t unit, adc_channel_t channel) { #if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED { adc_cali_curve_fitting_config_t config = { .unit_id = unit, .chan = channel, .atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT, }; esp_err_t err = adc_cali_create_scheme_curve_fitting(&config, &s_cali_handle); if (err == ESP_OK) { return ESP_OK; } ESP_LOGW(TAG, "Curve-fitting ADC calibration unavailable: %s", esp_err_to_name(err)); } #endif #if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED { adc_cali_line_fitting_config_t config = { .unit_id = unit, .atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT, .default_vref = 0, }; esp_err_t err = adc_cali_create_scheme_line_fitting(&config, &s_cali_handle); if (err == ESP_OK) { return ESP_OK; } ESP_LOGW(TAG, "Line-fitting ADC calibration unavailable: %s", esp_err_to_name(err)); } #endif return ESP_ERR_NOT_SUPPORTED; } esp_err_t platform_battery_init(void) { if (!lock_adc()) { return ESP_ERR_TIMEOUT; } if (s_initialized) { unlock_adc(); return ESP_OK; } if (CONFIG_TQ_BATTERY_CHARGE_GPIO >= GPIO_NUM_MAX) { 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 TP4057 status GPIO failed: %s", esp_err_to_name(gpio_err)); unlock_adc(); return gpio_err; } } adc_unit_t unit = ADC_UNIT_1; adc_channel_t channel = ADC_CHANNEL_0; esp_err_t err = adc_oneshot_io_to_channel(CONFIG_TQ_BATTERY_ADC_GPIO, &unit, &channel); if (err != ESP_OK || unit != ADC_UNIT_1) { ESP_LOGE(TAG, "Battery ADC GPIO%d is not a supported ADC1 input: %s", CONFIG_TQ_BATTERY_ADC_GPIO, esp_err_to_name(err)); unlock_adc(); return err != ESP_OK ? err : ESP_ERR_NOT_SUPPORTED; } adc_oneshot_unit_init_cfg_t unit_config = { .unit_id = unit, .ulp_mode = ADC_ULP_MODE_DISABLE, }; err = adc_oneshot_new_unit(&unit_config, &s_adc_handle); if (err != ESP_OK) { unlock_adc(); return err; } adc_oneshot_chan_cfg_t channel_config = { .atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT, }; err = adc_oneshot_config_channel(s_adc_handle, channel, &channel_config); if (err != ESP_OK) { (void)adc_oneshot_del_unit(s_adc_handle); s_adc_handle = NULL; unlock_adc(); return err; } err = create_calibration(unit, channel); if (err != ESP_OK) { (void)adc_oneshot_del_unit(s_adc_handle); s_adc_handle = NULL; unlock_adc(); return err; } s_adc_channel = channel; s_initialized = true; ESP_LOGI(TAG, "Battery monitor ready ADC_GPIO=%d divider=%d/%d charge_GPIO=%d", CONFIG_TQ_BATTERY_ADC_GPIO, CONFIG_TQ_BATTERY_DIVIDER_HIGH_OHM, CONFIG_TQ_BATTERY_DIVIDER_LOW_OHM, CONFIG_TQ_BATTERY_CHARGE_GPIO); unlock_adc(); return ESP_OK; } static void snapshot_load_state(bool *out_active, bool *out_cooldown) { bool active = false; int64_t cooldown_until_us = 0; portENTER_CRITICAL(&s_state_lock); for (size_t i = 0; i < PLATFORM_BATTERY_LOAD_COUNT; ++i) { if (s_load_ref_count[i] > 0U) { active = true; break; } } cooldown_until_us = s_cooldown_until_us; portEXIT_CRITICAL(&s_state_lock); *out_active = active; *out_cooldown = !active && esp_timer_get_time() < cooldown_until_us; } esp_err_t platform_battery_read_sample(platform_battery_sample_t *out_sample) { if (out_sample == NULL) { return ESP_ERR_INVALID_ARG; } memset(out_sample, 0, sizeof(*out_sample)); esp_err_t err = platform_battery_init(); if (err != ESP_OK) { return err; } if (!lock_adc()) { return ESP_ERR_TIMEOUT; } int raw = 0; for (size_t i = 0; i < 4U; ++i) { err = adc_oneshot_read(s_adc_handle, s_adc_channel, &raw); if (err != ESP_OK) { unlock_adc(); return err; } esp_rom_delay_us(BATTERY_ADC_SAMPLE_DELAY_US); } int samples_mv[BATTERY_ADC_SAMPLE_COUNT]; for (size_t i = 0; i < BATTERY_ADC_SAMPLE_COUNT; ++i) { err = adc_oneshot_read(s_adc_handle, s_adc_channel, &raw); if (err != ESP_OK) { unlock_adc(); return err; } err = adc_cali_raw_to_voltage(s_cali_handle, raw, &samples_mv[i]); if (err != ESP_OK) { unlock_adc(); return err; } esp_rom_delay_us(BATTERY_ADC_SAMPLE_DELAY_US); } sort_ints(samples_mv, BATTERY_ADC_SAMPLE_COUNT); int64_t sum_mv = 0; for (size_t i = BATTERY_ADC_TRIM_COUNT; i < BATTERY_ADC_SAMPLE_COUNT - BATTERY_ADC_TRIM_COUNT; ++i) { sum_mv += samples_mv[i]; } int adc_mv = (int)(sum_mv / (BATTERY_ADC_SAMPLE_COUNT - 2 * BATTERY_ADC_TRIM_COUNT)); int64_t battery_mv = (int64_t)adc_mv * ((int64_t)CONFIG_TQ_BATTERY_DIVIDER_HIGH_OHM + CONFIG_TQ_BATTERY_DIVIDER_LOW_OHM) / CONFIG_TQ_BATTERY_DIVIDER_LOW_OHM; battery_mv = battery_mv * CONFIG_TQ_BATTERY_CALIBRATION_GAIN_PPM / 1000000LL + CONFIG_TQ_BATTERY_CALIBRATION_OFFSET_MV; out_sample->adc_voltage_mv = adc_mv; out_sample->voltage_mv = battery_mv > INT_MAX ? INT_MAX : (int)battery_mv; out_sample->voltage_valid = out_sample->voltage_mv >= BATTERY_VALID_MIN_MV && 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; } void platform_battery_load_begin(platform_battery_load_source_t source) { if (source < 0 || source >= PLATFORM_BATTERY_LOAD_COUNT) { return; } portENTER_CRITICAL(&s_state_lock); if (s_load_ref_count[source] < UINT16_MAX) { ++s_load_ref_count[source]; } portEXIT_CRITICAL(&s_state_lock); } void platform_battery_load_end(platform_battery_load_source_t source) { if (source < 0 || source >= PLATFORM_BATTERY_LOAD_COUNT) { return; } int64_t cooldown_until_us = esp_timer_get_time() + (int64_t)CONFIG_TQ_BATTERY_LOAD_COOLDOWN_SEC * 1000000LL; portENTER_CRITICAL(&s_state_lock); if (s_load_ref_count[source] > 0U) { --s_load_ref_count[source]; if (cooldown_until_us > s_cooldown_until_us) { s_cooldown_until_us = cooldown_until_us; } } portEXIT_CRITICAL(&s_state_lock); } #else esp_err_t platform_battery_init(void) { return ESP_ERR_NOT_SUPPORTED; } esp_err_t platform_battery_read_sample(platform_battery_sample_t *out_sample) { if (out_sample != NULL) { memset(out_sample, 0, sizeof(*out_sample)); } return ESP_ERR_NOT_SUPPORTED; } void platform_battery_load_begin(platform_battery_load_source_t source) { (void)source; } void platform_battery_load_end(platform_battery_load_source_t source) { (void)source; } #endif