#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 #define BATTERY_EMPTY_MV 2500 #define BATTERY_FULL_MV 4200 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; 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 <= BATTERY_EMPTY_MV) { return 0; } if (mv >= BATTERY_FULL_MV) { return 100; } const int range_mv = BATTERY_FULL_MV - BATTERY_EMPTY_MV; return clamp_percent(((mv - BATTERY_EMPTY_MV) * 100 + range_mv / 2) / range_mv); } 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; }