237 lines
6.2 KiB
C
237 lines
6.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
|
|
|
|
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;
|
|
}
|