#include "battery_monitor.h" #include #include "esp_adc/adc_cali.h" #include "esp_adc/adc_cali_scheme.h" #include "esp_adc/adc_oneshot.h" #include "esp_check.h" #include "esp_err.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/portmacro.h" #include "freertos/task.h" static const char *TAG = "battery"; #define BATTERY_ADC_UNIT ADC_UNIT_1 #define BATTERY_ADC_CHANNEL ADC_CHANNEL_3 #define BATTERY_ADC_ATTEN ADC_ATTEN_DB_12 #define BATTERY_DIVIDER 3 static adc_oneshot_unit_handle_t s_adc_handle; static adc_cali_handle_t s_cali_handle; static bool s_cali_enabled; static portMUX_TYPE s_battery_mux = portMUX_INITIALIZER_UNLOCKED; static int s_latest_percent; static int s_latest_millivolts; static bool s_has_latest; typedef struct { int millivolts; int raw_avg; int pin_millivolts; int samples; } battery_reading_t; typedef struct { int millivolts; int percent; } battery_curve_point_t; static const battery_curve_point_t s_battery_curve[] = { { 4200, 100 }, { 4160, 95 }, { 4110, 90 }, { 4080, 85 }, { 4020, 80 }, { 3980, 75 }, { 3950, 70 }, { 3910, 65 }, { 3870, 60 }, { 3850, 55 }, { 3830, 50 }, { 3810, 45 }, { 3790, 40 }, { 3770, 35 }, { 3750, 30 }, { 3730, 25 }, { 3710, 20 }, { 3690, 15 }, { 3650, 10 }, { 3570, 5 }, { 3300, 0 }, }; static int clamp_percent(int value) { if (value < 0) { return 0; } if (value > 100) { return 100; } return value; } static int battery_percent_from_mv(int mv) { if (mv <= 0) { return 0; } const int count = sizeof(s_battery_curve) / sizeof(s_battery_curve[0]); if (mv >= s_battery_curve[0].millivolts) { return s_battery_curve[0].percent; } for (int i = 1; i < count; i++) { const battery_curve_point_t high = s_battery_curve[i - 1]; const battery_curve_point_t low = s_battery_curve[i]; if (mv >= low.millivolts) { int mv_range = high.millivolts - low.millivolts; int percent_range = high.percent - low.percent; int percent = low.percent; if (mv_range > 0) { percent += ((mv - low.millivolts) * percent_range + mv_range / 2) / mv_range; } return clamp_percent(percent); } } return s_battery_curve[count - 1].percent; } static esp_err_t battery_adc_init(void) { adc_oneshot_unit_init_cfg_t unit_config = { .unit_id = BATTERY_ADC_UNIT, .clk_src = ADC_RTC_CLK_SRC_DEFAULT, .ulp_mode = ADC_ULP_MODE_DISABLE, }; ESP_RETURN_ON_ERROR(adc_oneshot_new_unit(&unit_config, &s_adc_handle), TAG, "adc unit init failed"); adc_oneshot_chan_cfg_t channel_config = { .atten = BATTERY_ADC_ATTEN, .bitwidth = ADC_BITWIDTH_DEFAULT, }; ESP_RETURN_ON_ERROR(adc_oneshot_config_channel(s_adc_handle, BATTERY_ADC_CHANNEL, &channel_config), TAG, "adc channel config failed"); #if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED adc_cali_curve_fitting_config_t cali_config = { .unit_id = BATTERY_ADC_UNIT, .chan = BATTERY_ADC_CHANNEL, .atten = BATTERY_ADC_ATTEN, .bitwidth = ADC_BITWIDTH_DEFAULT, }; esp_err_t cali_ret = adc_cali_create_scheme_curve_fitting(&cali_config, &s_cali_handle); s_cali_enabled = (cali_ret == ESP_OK); if (!s_cali_enabled) { ESP_LOGW(TAG, "ADC calibration unavailable: %s", esp_err_to_name(cali_ret)); } #endif return ESP_OK; } static battery_reading_t read_battery(void) { int raw_sum = 0; int mv_sum = 0; int samples = 0; battery_reading_t reading = { 0 }; for (int i = 0; i < 8; i++) { int raw = 0; if (adc_oneshot_read(s_adc_handle, BATTERY_ADC_CHANNEL, &raw) != ESP_OK) { continue; } raw_sum += raw; samples++; if (s_cali_enabled) { int pin_mv = 0; if (adc_cali_raw_to_voltage(s_cali_handle, raw, &pin_mv) == ESP_OK) { mv_sum += pin_mv; } } } if (samples == 0) { return reading; } reading.samples = samples; reading.raw_avg = raw_sum / samples; if (s_cali_enabled && mv_sum > 0) { reading.pin_millivolts = mv_sum / samples; reading.millivolts = reading.pin_millivolts * BATTERY_DIVIDER; return reading; } reading.pin_millivolts = (reading.raw_avg * 3100) / 4095; reading.millivolts = reading.pin_millivolts * BATTERY_DIVIDER; return reading; } static void battery_task(void *arg) { (void)arg; int log_ticks = 0; while (1) { battery_reading_t reading = read_battery(); int percent = battery_percent_from_mv(reading.millivolts); portENTER_CRITICAL(&s_battery_mux); s_latest_percent = percent; s_latest_millivolts = reading.millivolts; s_has_latest = true; portEXIT_CRITICAL(&s_battery_mux); if (log_ticks < 6 || (log_ticks % 6) == 0) { ESP_LOGI(TAG, "adc raw=%d pin_mv=%d battery_mv=%d percent=%d cali=%d samples=%d", reading.raw_avg, reading.pin_millivolts, reading.millivolts, percent, s_cali_enabled ? 1 : 0, reading.samples); } log_ticks++; vTaskDelay(pdMS_TO_TICKS(10000)); } } void battery_monitor_start(void) { esp_err_t ret = battery_adc_init(); if (ret != ESP_OK) { ESP_LOGW(TAG, "battery monitor disabled: %s", esp_err_to_name(ret)); portENTER_CRITICAL(&s_battery_mux); s_latest_percent = 0; s_latest_millivolts = 0; s_has_latest = true; portEXIT_CRITICAL(&s_battery_mux); return; } xTaskCreate(battery_task, "battery", 4096, NULL, 2, NULL); } bool battery_monitor_take_latest(int *percent, int *millivolts) { if (percent == NULL || millivolts == NULL) { return false; } bool has_latest = false; portENTER_CRITICAL(&s_battery_mux); if (s_has_latest) { *percent = s_latest_percent; *millivolts = s_latest_millivolts; s_has_latest = false; has_latest = true; } portEXIT_CRITICAL(&s_battery_mux); return has_latest; }