205 lines
6.0 KiB
C
205 lines
6.0 KiB
C
#include "battery.h"
|
|
#include "system.h"
|
|
#include "gpio.h"
|
|
#include "esp_log.h"
|
|
#include "esp_adc/adc_oneshot.h"
|
|
#include "esp_adc/adc_cali.h"
|
|
#include "esp_adc/adc_cali_scheme.h"
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
|
|
static const char *TAG = "battery";
|
|
|
|
static adc_oneshot_unit_handle_t adc1_handle = NULL;
|
|
static adc_cali_handle_t adc1_cali_handle = NULL;
|
|
static bool adc_calibration_enabled = false;
|
|
|
|
// ADC Calibration initialization
|
|
static bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
|
|
{
|
|
adc_cali_handle_t handle = NULL;
|
|
esp_err_t ret = ESP_FAIL;
|
|
bool calibrated = false;
|
|
|
|
#if ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
|
|
if (!calibrated) {
|
|
ESP_LOGI(TAG, "Calibration scheme: Curve Fitting");
|
|
adc_cali_curve_fitting_config_t cali_config = {
|
|
.unit_id = unit,
|
|
.chan = channel,
|
|
.atten = atten,
|
|
.bitwidth = BATTERY_ADC_WIDTH,
|
|
};
|
|
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
|
|
if (ret == ESP_OK) {
|
|
calibrated = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if ADC_CALI_SCHEME_LINE_FITTING_SUPPORTED
|
|
if (!calibrated) {
|
|
ESP_LOGI(TAG, "Calibration scheme: Line Fitting");
|
|
adc_cali_line_fitting_config_t cali_config = {
|
|
.unit_id = unit,
|
|
.atten = atten,
|
|
.bitwidth = BATTERY_ADC_WIDTH,
|
|
};
|
|
ret = adc_cali_create_scheme_line_fitting(&cali_config, &handle);
|
|
if (ret == ESP_OK) {
|
|
calibrated = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
*out_handle = handle;
|
|
if (ret == ESP_OK) {
|
|
ESP_LOGI(TAG, "ADC calibration successful");
|
|
} else {
|
|
ESP_LOGW(TAG, "ADC calibration failed: %s", esp_err_to_name(ret));
|
|
}
|
|
return calibrated;
|
|
}
|
|
|
|
esp_err_t battery_init(void)
|
|
{
|
|
esp_err_t ret;
|
|
|
|
// Configure ADC1 oneshot mode
|
|
adc_oneshot_unit_init_cfg_t init_config = {
|
|
.unit_id = ADC_UNIT_1,
|
|
};
|
|
ret = adc_oneshot_new_unit(&init_config, &adc1_handle);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to initialize ADC unit: %s", esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
|
|
// Configure ADC channel
|
|
adc_oneshot_chan_cfg_t config = {
|
|
.bitwidth = BATTERY_ADC_WIDTH,
|
|
.atten = BATTERY_ADC_ATTEN,
|
|
};
|
|
ret = adc_oneshot_config_channel(adc1_handle, BATTERY_ADC_CHANNEL, &config);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to configure ADC channel: %s", esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
|
|
// Initialize calibration
|
|
adc_calibration_enabled = adc_calibration_init(ADC_UNIT_1, BATTERY_ADC_CHANNEL,
|
|
BATTERY_ADC_ATTEN, &adc1_cali_handle);
|
|
|
|
ESP_LOGI(TAG, "Battery monitoring initialized on GPIO34 (ADC1_CH6)");
|
|
return ESP_OK;
|
|
}
|
|
|
|
int battery_read_raw(void)
|
|
{
|
|
int adc_raw = 0;
|
|
esp_err_t ret = adc_oneshot_read(adc1_handle, BATTERY_ADC_CHANNEL, &adc_raw);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "Failed to read ADC: %s", esp_err_to_name(ret));
|
|
return -1;
|
|
}
|
|
return adc_raw;
|
|
}
|
|
|
|
int battery_read_voltage_mv(void)
|
|
{
|
|
int voltage_mv = 0;
|
|
int adc_sum = 0;
|
|
int valid_samples = 0;
|
|
|
|
// Take multiple samples and average
|
|
for (int i = 0; i < BATTERY_SAMPLES; i++) {
|
|
int adc_raw = battery_read_raw();
|
|
if (adc_raw >= 0) {
|
|
adc_sum += adc_raw;
|
|
valid_samples++;
|
|
}
|
|
vTaskDelay(pdMS_TO_TICKS(1)); // Small delay between samples
|
|
}
|
|
|
|
if (valid_samples == 0) {
|
|
ESP_LOGE(TAG, "No valid ADC samples");
|
|
return -1;
|
|
}
|
|
|
|
int adc_avg = adc_sum / valid_samples;
|
|
|
|
// Convert to voltage using calibration if available
|
|
if (adc_calibration_enabled) {
|
|
esp_err_t ret = adc_cali_raw_to_voltage(adc1_cali_handle, adc_avg, &voltage_mv);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGW(TAG, "Calibration conversion failed, using raw calculation");
|
|
adc_calibration_enabled = false; // Disable for future reads
|
|
}
|
|
}
|
|
|
|
// Fallback to manual calculation if calibration not available
|
|
if (!adc_calibration_enabled) {
|
|
// Simple linear conversion for 12-bit ADC with 12dB attenuation
|
|
// Approximate range: 0-3300mV for 0-4095 raw values
|
|
voltage_mv = (adc_avg * 3300) / 4095;
|
|
}
|
|
|
|
// Apply voltage divider ratio to get actual battery voltage
|
|
voltage_mv = (int)(voltage_mv * BATTERY_VOLTAGE_DIVIDER_RATIO);
|
|
|
|
return voltage_mv;
|
|
}
|
|
|
|
int battery_get_percentage(void)
|
|
{
|
|
int voltage_mv = battery_read_voltage_mv();
|
|
|
|
if (voltage_mv < 0) {
|
|
return -1;
|
|
}
|
|
|
|
// Clamp to min/max range
|
|
if (voltage_mv >= BATTERY_VOLTAGE_MAX) {
|
|
return 100;
|
|
}
|
|
if (voltage_mv <= BATTERY_VOLTAGE_MIN) {
|
|
return 0;
|
|
}
|
|
|
|
// Linear interpolation between min and max
|
|
int percentage = ((voltage_mv - BATTERY_VOLTAGE_MIN) * 100) /
|
|
(BATTERY_VOLTAGE_MAX - BATTERY_VOLTAGE_MIN);
|
|
|
|
return percentage;
|
|
}
|
|
|
|
// Battery monitoring task
|
|
static void battery_monitoring_task(void *pvParameters)
|
|
{
|
|
ESP_LOGI(TAG, "Battery monitoring task started");
|
|
|
|
while (1) {
|
|
int voltage_mv = battery_read_voltage_mv();
|
|
int percentage = battery_get_percentage();
|
|
|
|
if (voltage_mv >= 0 && percentage >= 0) {
|
|
// Update system state with battery info
|
|
system_setBatteryVoltage(voltage_mv);
|
|
system_setBatteryPercentage(percentage);
|
|
|
|
ESP_LOGI(TAG, "Battery: %d mV (%d%%)", voltage_mv, percentage);
|
|
} else {
|
|
ESP_LOGW(TAG, "Failed to read battery voltage");
|
|
}
|
|
|
|
// Read battery every 30 seconds
|
|
vTaskDelay(pdMS_TO_TICKS(30000));
|
|
}
|
|
}
|
|
|
|
void battery_start_monitoring_task(void)
|
|
{
|
|
xTaskCreate(battery_monitoring_task, "battery_task", 3072, NULL, 5, NULL);
|
|
ESP_LOGI(TAG, "Battery monitoring task created");
|
|
}
|