ESP-IDF driver component for the MAX17048 battery fuel gauge IC with I2C interface and runtime configuration support.
- Accurate Battery Monitoring: Read State of Charge (SOC), voltage, and charge/discharge rate
- I2C Master API: Uses latest ESP-IDF I2C master driver (no deprecation warnings)
- Runtime Configuration: Flexible initialization without Kconfig dependencies
- Shared Bus Support: Works with multiple I2C devices on the same bus
- Low Power: Sleep mode support for power-conscious applications
- Easy Integration: Simple API for quick battery monitoring implementation
The MAX17048 communicates via I2C and requires minimal external components:
ESP32 MAX17048
===== ========
GPIO 0 → SCL
GPIO 1 → SDA
3.3V → VDD
GND → GND/VSS
Battery+ → CELL+ (via 1kΩ resistor)
Battery- → CELL-
Note: External pull-up resistors (10kΩ) recommended for SCL/SDA lines.
#include "max17048.h"
#include "driver/i2c_master.h"
void app_main(void)
{
// Setup I2C master bus
i2c_master_bus_handle_t i2c_bus;
i2c_master_bus_config_t i2c_config = {
.clk_source = I2C_CLK_SRC_DEFAULT,
.i2c_port = I2C_NUM_0,
.scl_io_num = GPIO_NUM_0,
.sda_io_num = GPIO_NUM_1,
.glitch_ignore_cnt = 7,
.flags.enable_internal_pullup = false, // Use external pull-ups
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_config, &i2c_bus));
// Configure MAX17048
max17048_config_t max_config;
max17048_get_default_config(&max_config);
max_config.i2c_bus_handle = i2c_bus;
max_config.device_address = 0x36; // MAX17048 I2C address
max_config.i2c_freq_hz = 100000; // 100kHz I2C frequency
// Initialize fuel gauge
ESP_ERROR_CHECK(max17048_init_on_bus_with_config(&max_config));
// Read battery data
float soc, voltage, charge_rate;
if (max17048_get_soc(&soc) == ESP_OK) {
printf("Battery SOC: %.2f%%\\n", soc);
}
if (max17048_get_voltage(&voltage) == ESP_OK) {
printf("Battery Voltage: %.3fV\\n", voltage);
}
if (max17048_get_crate(&charge_rate) == ESP_OK) {
printf("Charge Rate: %.2f%%/hr\\n", charge_rate);
}
}// Multiple devices on same I2C bus
i2c_master_bus_handle_t shared_i2c_bus;
// Initialize MAX17048
max17048_config_t max_config;
max17048_get_default_config(&max_config);
max_config.i2c_bus_handle = shared_i2c_bus;
max_config.device_address = 0x36;
ESP_ERROR_CHECK(max17048_init_on_bus_with_config(&max_config));
// Initialize other I2C device on same bus
// other_device_init(shared_i2c_bus, other_address);void battery_monitor_task(void *pvParameters)
{
while (1) {
float soc, voltage, charge_rate;
if (max17048_get_soc(&soc) == ESP_OK &&
max17048_get_voltage(&voltage) == ESP_OK &&
max17048_get_crate(&charge_rate) == ESP_OK) {
printf("Battery Status: SOC=%.1f%%, V=%.2fV, Rate=%.1f%%/hr\\n",
soc, voltage, charge_rate);
// Check for low battery
if (soc < 10.0f) {
printf("WARNING: Low battery!\\n");
}
// Check charging state
if (charge_rate > 0.0f) {
printf("Battery charging at %.1f%%/hr\\n", charge_rate);
} else if (charge_rate < 0.0f) {
printf("Battery discharging at %.1f%%/hr\\n", -charge_rate);
}
}
vTaskDelay(pdMS_TO_TICKS(5000)); // Update every 5 seconds
}
}max17048_get_default_config()- Get default configuration structuremax17048_init_on_bus_with_config()- Initialize with runtime configurationmax17048_deinit()- Deinitialize and cleanup resources
max17048_get_soc()- Read State of Charge (percentage)max17048_get_voltage()- Read battery voltage (volts)max17048_get_crate()- Read charge/discharge rate (%/hour)
max17048_get_version()- Read device versionmax17048_get_config_reg()- Read configuration register
max17048_sleep()- Enter sleep modemax17048_wake()- Wake from sleep modemax17048_quick_start()- Force quick-start for accurate SOC
max17048_init()- Legacy initialization (returns ESP_ERR_NOT_SUPPORTED)max17048_init_custom()- Legacy custom init (returns ESP_ERR_NOT_SUPPORTED)
For smartwatch applications, create a simple wrapper that handles initialization, monitoring, and 4.45V battery optimization:
#include "max17048.h"
static bool battery_initialized = false;
static TaskHandle_t battery_task_handle = NULL;
// Voltage-to-SOC mapping for 4.45V Li-ion battery
static const struct {
float voltage;
float soc;
} soc_map_4450mv[] = {
{4.45, 100.0}, {4.40, 95.0}, {4.35, 90.0}, {4.30, 85.0}, {4.25, 80.0},
{4.20, 75.0}, {4.15, 70.0}, {4.10, 65.0}, {4.05, 60.0}, {4.00, 55.0},
{3.95, 50.0}, {3.90, 45.0}, {3.85, 40.0}, {3.80, 35.0}, {3.75, 30.0},
{3.70, 25.0}, {3.65, 20.0}, {3.60, 15.0}, {3.55, 10.0}, {3.50, 5.0},
{3.40, 0.0}
};
// Convert voltage to SOC using 4.45V battery profile
static float battery_voltage_to_soc_4450mv(float voltage)
{
if (voltage >= 4.45) return 100.0;
if (voltage <= 3.40) return 0.0;
// Linear interpolation between points
for (size_t i = 0; i < 20; i++) {
if (voltage <= soc_map_4450mv[i].voltage &&
voltage >= soc_map_4450mv[i+1].voltage) {
float v_range = soc_map_4450mv[i].voltage - soc_map_4450mv[i+1].voltage;
float soc_range = soc_map_4450mv[i].soc - soc_map_4450mv[i+1].soc;
float v_offset = voltage - soc_map_4450mv[i+1].voltage;
return soc_map_4450mv[i+1].soc + (v_offset / v_range) * soc_range;
}
}
return 0.0f;
}
// Battery monitoring task
static void battery_task(void *pvParameters)
{
while (1) {
if (battery_initialized) {
float voltage, raw_soc, charge_rate;
if (max17048_get_voltage(&voltage) == ESP_OK &&
max17048_get_soc(&raw_soc) == ESP_OK &&
max17048_get_crate(&charge_rate) == ESP_OK) {
// Calculate corrected SOC for 4.45V battery
float corrected_soc = battery_voltage_to_soc_4450mv(voltage);
ESP_LOGI("BATTERY", "SOC=%.1f%% (Raw: %.1f%%), V=%.2fV, Rate=%.1f%%/hr %s",
corrected_soc, raw_soc, voltage, charge_rate,
charge_rate > 0 ? "(Charging)" :
charge_rate < 0 ? "(Discharging)" : "(Standby)");
// Low battery warnings
if (corrected_soc <= 5.0f) {
ESP_LOGW("BATTERY", "CRITICAL: Battery very low!");
} else if (corrected_soc <= 15.0f) {
ESP_LOGW("BATTERY", "WARNING: Low battery");
}
}
}
vTaskDelay(pdMS_TO_TICKS(5000)); // 5 second intervals
}
}
// Simple initialization
esp_err_t battery_init_complete(i2c_master_bus_handle_t i2c_bus)
{
if (battery_initialized) return ESP_OK;
// Initialize MAX17048 (uses menuconfig settings)
esp_err_t ret = max17048_init();
if (ret != ESP_OK) return ret;
// Verify communication
uint16_t version;
ret = max17048_get_version(&version);
if (ret != ESP_OK) return ret;
ESP_LOGI("BATTERY", "MAX17048 initialized - Version: 0x%04X", version);
// Create monitoring task
xTaskCreate(battery_task, "battery_task", 4096, NULL, 5, &battery_task_handle);
battery_initialized = true;
return ESP_OK;
}
// Simple status check for power management
bool battery_is_low(void)
{
if (!battery_initialized) return false;
float voltage;
if (max17048_get_voltage(&voltage) == ESP_OK) {
float soc = battery_voltage_to_soc_4450mv(voltage);
return soc <= 15.0f; // Low battery threshold
}
return false;
}
// Get current status (simple interface)
esp_err_t battery_get_status(float *soc, float *voltage, float *charge_rate)
{
if (!battery_initialized) return ESP_ERR_INVALID_STATE;
float raw_voltage;
esp_err_t ret = ESP_OK;
if (voltage && max17048_get_voltage(&raw_voltage) == ESP_OK) {
*voltage = raw_voltage;
if (soc) *soc = battery_voltage_to_soc_4450mv(raw_voltage);
} else {
ret = ESP_FAIL;
}
if (charge_rate && max17048_get_crate(charge_rate) != ESP_OK) {
ret = ESP_FAIL;
}
return ret;
}void app_main(void)
{
// Initialize I2C bus
i2c_master_bus_handle_t i2c_bus;
// ... I2C bus initialization ...
// Initialize battery monitoring with simple wrapper
ESP_ERROR_CHECK(battery_init_complete(i2c_bus));
// Main loop with power management
while (1) {
// Check battery for power management decisions
if (battery_is_low()) {
ESP_LOGW(TAG, "Low battery - entering power save mode");
// Implement power saving measures
}
// Get current battery status
float soc, voltage, charge_rate;
if (battery_get_status(&soc, &voltage, &charge_rate) == ESP_OK) {
// Use battery data for UI display or system decisions
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}- 4.45V Battery Optimization: Custom voltage-to-SOC mapping for high-voltage batteries
- Automatic Monitoring: Background task handles periodic battery updates
- Power Management Integration: Simple functions for low battery detection
- Reduced Code Complexity: Hide component configuration and error handling
- Consistent Logging: Unified battery status reporting format
- Easy Maintenance: Component library updates don't affect main application
typedef struct {
i2c_master_bus_handle_t i2c_bus_handle; // I2C master bus handle
uint8_t device_address; // I2C device address (0x36)
uint32_t i2c_freq_hz; // I2C frequency in Hz
} max17048_config_t;- Supply Voltage: 2.5V to 5.0V
- I2C Address: 0x36 (7-bit)
- I2C Speed: Up to 400kHz
- SOC Range: 0% to 100% (0.00390625% resolution)
- Voltage Range: 0V to 5.12V (78.125µV resolution)
- Rate Range: ±32%/hr (0.208%/hr resolution)
- Operating Temperature: -40°C to +85°C
ESP_OK- SuccessESP_ERR_INVALID_ARG- Invalid argumentESP_ERR_NOT_FOUND- Device not found on I2C busESP_FAIL- I2C communication errorESP_ERR_NOT_SUPPORTED- Legacy function not supported
driver- ESP-IDF driver frameworkesp_common- ESP common utilitiesfreertos- FreeRTOS kernellog- ESP logging framework
The component now uses the new I2C master API. Update your code:
// Old (deprecated)
max17048_init_custom(I2C_NUM_0, SDA_PIN, SCL_PIN);
// New (recommended)
max17048_config_t config;
max17048_get_default_config(&config);
config.i2c_bus_handle = your_i2c_bus_handle;
config.device_address = 0x36;
max17048_init_on_bus_with_config(&config);- Device not found: Check I2C wiring and pull-up resistors
- Inaccurate readings: Perform quick-start calibration
- Communication errors: Verify I2C frequency and bus configuration
- Power issues: Ensure proper supply voltage (2.5V-5.0V)
This component is provided under the MIT license.
For detailed specifications, refer to the MAX17048 Datasheet.