Skip to content

Commit

Permalink
implement max17261 driver (#220)
Browse files Browse the repository at this point in the history
* implement max17261 driver

* fix LSB, add comments

* add back uint8_t* cast

---------

Co-authored-by: Preston Dresser <pgdresse@uwaterloo.ca>
  • Loading branch information
03predo and pgdresse authored Nov 11, 2023
1 parent 9dcdcb2 commit f5195a3
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 0 deletions.
76 changes: 76 additions & 0 deletions libraries/ms-drivers/inc/max17261_fuel_gauge.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#pragma once
#include <stdint.h>

#include "i2c.h"
#include "max17261_fuel_gauge_defs.h"

/* Notes:
* 3 things are required for configuring the max17261:
* - Design Capacity (in milliamp hours)
* - Empty Voltage
* - Charge Termination Current
*
* Some of the units of the registers (like capacity and current) depend on RSense,
* it looks like max17261 automatically determines this value, it should be provided
* to the driver for conversions from register values to actual units
*
* See data sheet P.15 for more info on how registers are formatted
* Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max17261.pdf
*/

typedef struct {
I2CPort i2c_port;
I2CAddress i2c_address;

uint16_t design_capacity; // LSB = 5.0 (micro Volt Hours / R Sense)
uint16_t empty_voltage; // Only a 9-bit field, LSB = 78.125 (micro Volts)
uint16_t charge_term_current; // LSB = 1.5625 (micro Volts / R Sense)

uint16_t r_sense_ohms;
} Max17261Settings;

typedef struct {
Max17261Settings settings;
} Max17261Storage;

/* @brief Gets the current state of charge given by the max17261 in percentage
* @param storage - a pointer to an already initialized Max17261Storage struct
* @param soc_pct - state of charge in percentage will be returned in this var
* @return STATUS_CODE_OK on success
*/
StatusCode max17261_state_of_charge(Max17261Storage *storage, uint16_t *soc_pct);

/* @brief Gets the current remaining capactity in micro amp hours
* @param storage - a pointer to an already initialized Max17261Storage struct
* @param soc_pct - remaining capactity in micro amp hours returned in this var
* @return STATUS_CODE_OK on success
*/
StatusCode max17261_remaining_capacity(Max17261Storage *storage, uint32_t *rem_cap_uAhr);

/* @brief Gets the full charge capacity of the battery in micro amp hours
* @param storage - a pointer to an already initialized Max17261Storage struct
* @param soc_pct - full charge capacitry in micro amp hours returned in this var
* @return STATUS_CODE_OK on success
*/
StatusCode max17261_full_capacity(Max17261Storage *storage, uint16_t *full_cap_uAhr);

/* @brief Gets the time to empty in milliseconds
* @param storage - a pointer to an already initialized Max17261Storage struct
* @param soc_pct - time to empty in milliseconds returned in this var
* @return STATUS_CODE_OK on success
*/
StatusCode max17261_time_to_empty(Max17261Storage *storage, uint16_t *tte_ms);

/* @brief Gets the time to full in milliseconds
* @param storage - a pointer to an already initialized Max17261Storage struct
* @param soc_pct - time to full in milliseconds returned in this var
* @return STATUS_CODE_OK on success
*/
StatusCode max17261_time_to_full(Max17261Storage *storage, uint16_t *ttf_ms);

/* @brief Gets the time to full in milliseconds
* @param storage - a pointer to an uninitialized Max17261Storage struct
* @param settings - populated settings struct
* @return STATUS_CODE_OK on success
*/
StatusCode max17261_init(Max17261Storage *storage, Max17261Settings settings);
102 changes: 102 additions & 0 deletions libraries/ms-drivers/inc/max17261_fuel_gauge_defs.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#pragma once
// reg map is on P.27:
// https://www.analog.com/media/en/technical-documentation/user-guides/max1726x-modelgauge-m5-ez-user-guide.pdf
typedef enum {
MAX17261_STATUS = 0x00,
MAX17261_VOLT_ALRT_THRSH,
MAX17261_TEMP_ALRT_THRSH,
MAX17261_SOC_ALRT_THRSH,
MAX17261_AT_RATE,
MAX17261_CAP,
MAX17261_SOC,
MAX17261_AGE,
MAX17261_TEMP,
MAX17261_VCELL,
MAX17261_CURRENT,
MAX17261_AVG_CURRENT,
MAX17261_Q_RESIDUAL,
MAX17261_MIX_SOC,
MAX17261_AV_SOC,
MAX17261_MIX_CAP,

MAX17261_FULL_CAP_REP = 0x10,
MAX17261_TIME_TO_EMPTY,
MAX17261_QR_TABLE00,
MAX17261_FULL_SOC_THRSH,
MAX17261_R_CELL,
MAX17261_AVG_TA = 0x16,
MAX17261_CYCLES,
MAX17261_DESIGN_CAP,
MAX17261_AVG_V_CELL,
MAX17261_MAX_MIN_TEMP,
MAX17261_MAX_MIN_VOLT,
MAX17261_MAX_MIN_CURR,
MAX17261_CONFIG,
MAX17261_I_CHG_TERM,
MAX17261_AV_CAP,

MAX17261_TIME_TO_FULL = 0x20,
MAX17261_DEV_NAME,
MAX17261_QR_TABLE10,
MAX17261_FULL_CAP_NOM,
MAX17261_AIN = 0x27,
MAX17261_LEARN_CFG,
MAX17261_FILTER_CFG,
MAX17261_RELAX_CFG,
MAX17261_MISC_CFG,
MAX17261_T_GAIN,
MAX17261_T_OFF,
MAX17261_C_GAIN,
MAX17261_C_OFF,

MAX17261_QR_TABLE20 = 0x32,
MAX17261_DIE_TEMP = 0x34,
MAX17261_FULL_CAP,
MAX17261_R_COMP0 = 0x38,
MAX17261_TEMP_CO,
MAX17261_V_EMPTY,
MAX17261_FSTAT = 0x3D,
MAX17261_TIMER,
MAX17261_SHDN_TIMER,

MAX17261_QR_TABLE30 = 0x42,
MAX17261_R_GAIN,
MAX17261_DQ_ACC = 0x45,
MAX17261_DP_ACC,
MAX17261_CONVG_CFG = 0x49,
MAX17261_VF_REM_CAP,
MAX17261_QH = 0x4D,

MAX17261_STATUS2 = 0xB0,
MAX17261_POWER,
MAX17261_ID, // UserMem2
MAX17261_AVG_POWER,
MAX17261_I_ALRT_TH,
MAX17261_TTF_CFG,
MAX17261_CV_MIX_CAP,
MAX17261_CV_HALF_TIME,
MAX17261_CG_TEMP_CO,
MAX17261_CURVE,
MAX17261_HIB_CFG,
MAX17261_CONFIG2,
MAX17261_VRIPPLE,
MAX17261_RIPPLE_CFG,
MAX17261_TIMERH,

MAX17261_RSENSE = 0xD0, // UserMem3
MAX17261_SC_OCV_LIM,
MAX17261_VGAIN,
MAX17261_SOC_HOLD,
MAX17261_MAX_PEAK_POWER,
MAX17261_SUS_PEAK_POWER,
MAX17261_PACK_RESISTANCE,
MAX17261_SYS_RESISTANCE,
MAX17261_MIN_SYS_VOLTAGE,
MAX17261_MPP_CURRENT,
MAX17261_SPP_CURRENT,
MAX17261_MODEL_I_CFG,
MAX17261_AT_Q_RESIDUAL,
MAX17261_AT_TTE,
MAX17261_AT_AV_SOC,
MAX17261_AT_AV_CAP,
} Max17261Registers;
80 changes: 80 additions & 0 deletions libraries/ms-drivers/src/max17261_fuel_gauge.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#include "max17261_fuel_gauge.h"

#define PCT_LSB (1.0 / 256) // LSBit is 1/256%
#define CAP_LSB (5.0 / storage->settings.r_sense_ohms) // LSBit is 5 micro Volt hrs / Rsense
#define TIM_LSB (5625U) // LSBit is 5625ms

StatusCode max17261_get_reg(Max17261Storage *storage, Max17261Registers reg, uint16_t *value) {
// unsure of underlying type of enum, cast to uint8_t to be sure
uint8_t reg8 = reg;
status_ok_or_return(
i2c_write(storage->settings.i2c_port, storage->settings.i2c_address, &reg8, sizeof(uint8_t)));
// TODO: max17261 sends LSByte then MSByte, need to check if bytes are correctly written to
status_ok_or_return(i2c_read(storage->settings.i2c_port, storage->settings.i2c_address,
(uint8_t *)value, sizeof(uint16_t)));
return STATUS_CODE_OK;
}

StatusCode max17261_set_reg(Max17261Storage *storage, Max17261Registers reg, uint16_t value) {
uint8_t buf[3];
buf[0] = reg;
// send LSByte then MSByte as per datasheet
buf[1] = value & 0x00FF;
buf[2] = value >> 8;
status_ok_or_return(
i2c_write(storage->settings.i2c_port, storage->settings.i2c_address, buf, sizeof(buf)));
return STATUS_CODE_OK;
}

StatusCode max17261_state_of_charge(Max17261Storage *storage, uint16_t *soc_pct) {
uint16_t soc_reg_val = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_SOC, &soc_reg_val));
*soc_pct = soc_reg_val * PCT_LSB;
return STATUS_CODE_OK;
}

StatusCode max17261_remaining_capacity(Max17261Storage *storage, uint32_t *rem_cap_uAhr) {
uint16_t rem_cap_reg_val = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_CAP, &rem_cap_reg_val));
*rem_cap_uAhr = rem_cap_reg_val * CAP_LSB;
return STATUS_CODE_OK;
}

StatusCode max17261_full_capacity(Max17261Storage *storage, uint16_t *full_cap_uAhr) {
uint16_t full_cap_reg_val = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_FULL_CAP_REP, &full_cap_reg_val));
*full_cap_uAhr = full_cap_reg_val * CAP_LSB;
return STATUS_CODE_OK;
}

StatusCode max17261_time_to_empty(Max17261Storage *storage, uint16_t *tte_ms) {
uint16_t tte_reg_val = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_TIME_TO_EMPTY, &tte_reg_val));
*tte_ms = tte_reg_val * TIM_LSB;
return STATUS_CODE_OK;
}

StatusCode max17261_time_to_full(Max17261Storage *storage, uint16_t *ttf_ms) {
uint16_t ttf_reg_val = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_TIME_TO_FULL, &ttf_reg_val));
*ttf_ms = ttf_reg_val * TIM_LSB;
return STATUS_CODE_OK;
}

StatusCode max17261_init(Max17261Storage *storage, Max17261Settings settings) {
if (settings.i2c_port >= NUM_I2C_PORTS) {
return STATUS_CODE_INVALID_ARGS;
}

storage->settings = settings;

status_ok_or_return(max17261_set_reg(storage, MAX17261_DESIGN_CAP, settings.design_capacity));
status_ok_or_return(max17261_set_reg(storage, MAX17261_I_CHG_TERM, settings.charge_term_current));
uint16_t old_vempty = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_V_EMPTY, &old_vempty));
// the 7 LSBits of the vempty reg is the recovery voltage, the last 9 are the empty voltage
uint16_t new_vempty = (settings.empty_voltage << 7) + (old_vempty & 0x7F);
status_ok_or_return(max17261_set_reg(storage, MAX17261_V_EMPTY, new_vempty));

return STATUS_CODE_OK;
}

0 comments on commit f5195a3

Please sign in to comment.