ESP32/main/battery_monitor.c
2026-06-24 21:01:33 +08:00

195 lines
5.2 KiB
C

#include "battery_monitor.h"
#include <stdbool.h>
#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 3000
#define BATTERY_FULL_MV 4120
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;
}