195 lines
5.2 KiB
C
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 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;
|
|
}
|