TKK_E32221292/Firmware ESP32/main/battery_monitor.c

140 lines
4.4 KiB
C

#include "battery_monitor.h"
#include <stdbool.h>
#include <stdint.h>
#include "esp_adc/adc_oneshot.h"
#include "esp_err.h"
#include "esp_log.h"
static const char *TAG = "BATTERY";
// Battery monitor wiring:
// Battery + -> 360k -> GPIO35 ADC point -> 47k -> GND
// Divider factor = (360k + 47k) / 47k = 8.659574
static const adc_unit_t BATTERY_ADC_UNIT = ADC_UNIT_1;
static const adc_channel_t BATTERY_ADC_CHANNEL = ADC_CHANNEL_7; // GPIO35 on ESP32
static const adc_atten_t BATTERY_ADC_ATTEN = ADC_ATTEN_DB_12;
static const adc_bitwidth_t BATTERY_ADC_WIDTH = ADC_BITWIDTH_12;
static const float BATTERY_R_TOP_OHM = 360000.0f;
static const float BATTERY_R_BOTTOM_OHM = 47000.0f;
static const float BATTERY_DIVIDER_FACTOR = (BATTERY_R_TOP_OHM + BATTERY_R_BOTTOM_OHM) / BATTERY_R_BOTTOM_OHM;
// Calibration based on real measurement:
// Pack measured by multimeter = 21.43 V
// App/firmware before calibration = 20.20 V
// Calibration multiplier = 21.43 / 20.20 = 1.0619
static const float BATTERY_CALIBRATION_MULTIPLIER = 1.062f;
static const float ADC_REFERENCE_VOLTAGE = 3.30f;
static const float ADC_MAX_RAW = 4095.0f;
// Project decision for battery percentage display:
// 6S pack full = 25.2 V (4.2 V/cell).
// Display 0% = 20.4 V (3.4 V/cell), so 21.6 V is still shown
// as a low but usable battery level instead of immediately becoming 0%.
// Low-battery auto-stop/lockout is intentionally NOT enabled in this patch.
static const float BATTERY_EMPTY_VOLTAGE = 20.40f;
static const float BATTERY_FULL_VOLTAGE = 25.20f;
static const int BATTERY_SAMPLE_COUNT = 16;
static adc_oneshot_unit_handle_t s_adc_handle = NULL;
static bool s_initialized = false;
static float s_voltage = 0.0f;
static int s_percent = 0;
static bool s_percent_initialized = false;
static int clamp_percent(float value)
{
if (value <= 0.0f) {
return 0;
}
if (value >= 100.0f) {
return 100;
}
return (int)(value + 0.5f);
}
esp_err_t battery_monitor_init(void)
{
adc_oneshot_unit_init_cfg_t unit_cfg = {
.unit_id = BATTERY_ADC_UNIT,
};
esp_err_t err = adc_oneshot_new_unit(&unit_cfg, &s_adc_handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "adc_oneshot_new_unit failed: %s", esp_err_to_name(err));
return err;
}
adc_oneshot_chan_cfg_t channel_cfg = {
.atten = BATTERY_ADC_ATTEN,
.bitwidth = BATTERY_ADC_WIDTH,
};
err = adc_oneshot_config_channel(s_adc_handle, BATTERY_ADC_CHANNEL, &channel_cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "adc_oneshot_config_channel failed: %s", esp_err_to_name(err));
return err;
}
s_initialized = true;
battery_monitor_update();
ESP_LOGI(TAG, "Battery monitor ready on GPIO35 with divider 360k/47k, calibration %.3f", BATTERY_CALIBRATION_MULTIPLIER);
return ESP_OK;
}
void battery_monitor_update(void)
{
if (!s_initialized || s_adc_handle == NULL) {
return;
}
int raw_total = 0;
int valid_samples = 0;
for (int i = 0; i < BATTERY_SAMPLE_COUNT; i++) {
int raw = 0;
esp_err_t err = adc_oneshot_read(s_adc_handle, BATTERY_ADC_CHANNEL, &raw);
if (err == ESP_OK) {
raw_total += raw;
valid_samples++;
}
}
if (valid_samples <= 0) {
return;
}
const float raw_avg = (float)raw_total / (float)valid_samples;
const float adc_voltage = (raw_avg / ADC_MAX_RAW) * ADC_REFERENCE_VOLTAGE;
const float battery_voltage = adc_voltage * BATTERY_DIVIDER_FACTOR * BATTERY_CALIBRATION_MULTIPLIER;
// Light smoothing to avoid flickering on the UI.
if (s_voltage <= 0.01f) {
s_voltage = battery_voltage;
} else {
s_voltage = (s_voltage * 0.80f) + (battery_voltage * 0.20f);
}
const float percent = ((s_voltage - BATTERY_EMPTY_VOLTAGE) /
(BATTERY_FULL_VOLTAGE - BATTERY_EMPTY_VOLTAGE)) * 100.0f;
const int computed_percent = clamp_percent(percent);
// During normal discharge, the displayed percentage should not jump upward
// due to ADC noise or voltage recovery after motor load is removed.
// Charging detection will be handled separately in the later low-battery patch.
if (!s_percent_initialized) {
s_percent = computed_percent;
s_percent_initialized = true;
} else if (computed_percent < s_percent) {
s_percent = computed_percent;
}
}
float battery_monitor_get_voltage(void)
{
return s_voltage;
}
int battery_monitor_get_percent(void)
{
return s_percent;
}