Files
t-embed-insecam-widget/main/battery.c
T
Juli f238cfc9bf Battery/USB readout and wrap-around menu scrolling
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.
2026-09-17 01:28:59 +02:00

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;
}