BAT_ADC (GPIO4) sits between two 100k resistors on the switched supply rail, so it reads the cell on battery and ~4.4 V on USB; above 4.25 V is reported as USB power. On battery the caption shows a glyph + percentage from a LiPo OCV table; the menu shows voltage and source. Menu lists now wrap at both ends.
69 lines
2.6 KiB
C
69 lines
2.6 KiB
C
#include "battery.h"
|
|
#include "board.h"
|
|
#include "esp_adc/adc_oneshot.h"
|
|
#include "esp_adc/adc_cali.h"
|
|
#include "esp_adc/adc_cali_scheme.h"
|
|
#include "esp_log.h"
|
|
|
|
static const char *TAG = "battery";
|
|
static adc_oneshot_unit_handle_t unit;
|
|
static adc_cali_handle_t cali;
|
|
static adc_channel_t channel;
|
|
static bool ready;
|
|
|
|
void battery_init(void)
|
|
{
|
|
if (board->bat_adc < 0) return;
|
|
adc_unit_t unit_id;
|
|
if (adc_oneshot_io_to_channel(board->bat_adc, &unit_id, &channel) != ESP_OK || unit_id != ADC_UNIT_1) {
|
|
ESP_LOGW(TAG, "GPIO%d is not an ADC1 pin", board->bat_adc);
|
|
return;
|
|
}
|
|
adc_oneshot_unit_init_cfg_t ucfg = {.unit_id = ADC_UNIT_1};
|
|
ESP_ERROR_CHECK(adc_oneshot_new_unit(&ucfg, &unit));
|
|
adc_oneshot_chan_cfg_t ccfg = {.atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT};
|
|
ESP_ERROR_CHECK(adc_oneshot_config_channel(unit, channel, &ccfg));
|
|
adc_cali_curve_fitting_config_t cal = {.unit_id = ADC_UNIT_1, .chan = channel, .atten = ADC_ATTEN_DB_12,
|
|
.bitwidth = ADC_BITWIDTH_DEFAULT};
|
|
if (adc_cali_create_scheme_curve_fitting(&cal, &cali) != ESP_OK) cali = NULL;
|
|
ready = true;
|
|
int mv, pct;
|
|
bool usb;
|
|
if (battery_read(&mv, &pct, &usb)) ESP_LOGI(TAG, "%d mV: %s", mv, usb ? "USB powered" : "on battery");
|
|
}
|
|
|
|
// Open-circuit voltage -> charge, single LiPo cell.
|
|
static int percent_from_mv(int mv)
|
|
{
|
|
static const int table[][2] = {{4200, 100}, {4100, 90}, {4000, 78}, {3900, 62}, {3800, 45},
|
|
{3700, 25}, {3600, 10}, {3500, 3}, {3300, 0}};
|
|
const int n = sizeof table / sizeof table[0];
|
|
if (mv >= table[0][0]) return 100;
|
|
for (int i = 1; i < n; i++) {
|
|
if (mv >= table[i][0]) {
|
|
int v1 = table[i - 1][0], p1 = table[i - 1][1], v0 = table[i][0], p0 = table[i][1];
|
|
return p0 + (mv - v0) * (p1 - p0) / (v1 - v0);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#define USB_THRESHOLD_MV 4250
|
|
|
|
bool battery_read(int *millivolts, int *percent, bool *on_usb)
|
|
{
|
|
if (!ready) return false;
|
|
long sum = 0;
|
|
const int samples = 32;
|
|
for (int i = 0; i < samples; i++) {
|
|
int raw = 0, mv = 0;
|
|
if (adc_oneshot_read(unit, channel, &raw) != ESP_OK) return false;
|
|
if (cali && adc_cali_raw_to_voltage(cali, raw, &mv) == ESP_OK) sum += mv;
|
|
else sum += raw * 3100 / 4095; // rough fallback without calibration
|
|
}
|
|
*millivolts = (int)(sum / samples) * 2; // 100k/100k divider
|
|
*on_usb = *millivolts > USB_THRESHOLD_MV;
|
|
*percent = *on_usb ? 100 : percent_from_mv(*millivolts);
|
|
return true;
|
|
}
|