Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

current sense SOC fixes #280

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 26 additions & 10 deletions libraries/ms-drivers/inc/max17261_fuel_gauge.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,33 @@ 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)
uint32_t pack_design_cap_mah;
uint16_t cell_empty_voltage_v;
uint16_t
charge_term_current_ma; // ref end-of-charge detection
// https://web.archive.org/web/20220121025712mp_/https://pdfserv.maximintegrated.com/en/an/user-guide-6597-max1726x-m5-ez-rev3-p4.pdf

uint16_t i_thresh_max;
int16_t i_thresh_min;
uint16_t temp_thresh_max;
uint16_t i_thresh_max_a;
int16_t i_thresh_min_a;
uint16_t temp_thresh_max_c;

float r_sense_mohms; // Rsense in micro ohms
float sense_resistor_mohms;
} Max17261Settings;

typedef struct {
Max17261Settings *settings;
} Max17261Storage;

// Storage for parameters learned by fuel guage
// Must be stored in flash to keep up to date after power cycle
typedef struct Max27261Params {
uint16_t rcomp0;
uint16_t tempco;
uint16_t fullcaprep;
uint16_t cycles;
uint16_t fullcapnom;
} Max27261Params;

/* @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
Expand All @@ -49,14 +61,14 @@ StatusCode max17261_state_of_charge(Max17261Storage *storage, uint16_t *soc_pct)
* @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);
StatusCode max17261_remaining_capacity(Max17261Storage *storage, uint32_t *rem_cap_mAh);

/* @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);
StatusCode max17261_full_capacity(Max17261Storage *storage, uint32_t *full_cap_mAh);

/* @brief Gets the time to empty in milliseconds
* @param storage - a pointer to an already initialized Max17261Storage struct
Expand Down Expand Up @@ -98,4 +110,8 @@ StatusCode max17261_temp(Max17261Storage *storage, uint16_t *temp_c);
* @param settings - populated settings struct
* @return STATUS_CODE_OK on success
*/
StatusCode max17261_init(Max17261Storage *storage, Max17261Settings *settings);
StatusCode max17261_init(Max17261Storage *storage, Max17261Settings *settings,
Max27261Params *params);

StatusCode max17261_set_learned_params(Max17261Storage *storage, Max27261Params *params);
StatusCode max17261_get_learned_params(Max17261Storage *storage, Max27261Params *params);
2 changes: 2 additions & 0 deletions libraries/ms-drivers/inc/max17261_fuel_gauge_defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ typedef enum {
MAX17261_VF_REM_CAP,
MAX17261_QH = 0x4D,

MAX17261_SOFT_WAKEUP = 0x60,

MAX17261_STATUS2 = 0xB0,
MAX17261_POWER,
MAX17261_ID, // UserMem2
Expand Down
182 changes: 141 additions & 41 deletions libraries/ms-drivers/src/max17261_fuel_gauge.c
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
#include "max17261_fuel_gauge.h"

#include <inttypes.h>

#include "delay.h"
#include "log.h"

// See Table 3 on pg.18 of the datasheet
#define PCT_LSB (1.0f / 256) // LSBit is 1/256%
#define CAP_LSB (5.0f / storage->settings->r_sense_mohms) // LSBit is 5 micro Volt hrs / Rsense
#define TIM_LSB (5625U) // LSBit is 5625ms
#define CUR_LSB (1.5625f / storage->settings->r_sense_mohms) // LSBit is 1.5625uA / Rsense
#define VOLT_LSB (1.25f / 16) // LSBit is 1.25mV / 16
#define TEMP_LSB (1.0f / 256) // LSBit is 1 / 256 C
#define PCT_LSB (1.0f / 256) // (%) LSBit is 1/256%
#define CAP_LSB \
(5.0f / storage->settings->sense_resistor_mohms) // (mAh) LSBit is 5 mili Volt hrs / Rsense (mAh)
#define TIM_LSB (5625U) // (ms) LSBit is 5625ms
#define CUR_LSB \
(1.5625f / storage->settings->sense_resistor_mohms) // (mA) LSBit is 1.5625uA / Rsense
#define VOLT_LSB (1.25f / 16) // (mV) LSBit is 1.25mV / 16
#define TEMP_LSB (1.0f / 256) // (C) LSBit is 1 / 256 C

static StatusCode max17261_get_reg(Max17261Storage *storage, Max17261Registers reg,
uint16_t *value) {
Expand All @@ -35,23 +40,34 @@ static StatusCode max17261_set_reg(Max17261Storage *storage, Max17261Registers r
}

StatusCode max17261_state_of_charge(Max17261Storage *storage, uint16_t *soc_pct) {
// clear lock bit if set - set on state of charge change
uint16_t reg = 0;
max17261_get_reg(storage, MAX17261_STATUS, &reg);
if (reg & (1 << 7)) {
max17261_set_reg(storage, MAX17261_STATUS, reg & (uint16_t) ~(1 << 7));
}

uint16_t status = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_STATUS, &status));

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) {
StatusCode max17261_remaining_capacity(Max17261Storage *storage, uint32_t *rem_cap_mAh) {
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;
*rem_cap_mAh = rem_cap_reg_val * CAP_LSB;
return STATUS_CODE_OK;
}

StatusCode max17261_full_capacity(Max17261Storage *storage, uint16_t *full_cap_uAhr) {
StatusCode max17261_full_capacity(Max17261Storage *storage, uint32_t *full_cap_mAh) {
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;
*full_cap_mAh = full_cap_reg_val * CAP_LSB;

return STATUS_CODE_OK;
}

Expand Down Expand Up @@ -79,7 +95,8 @@ StatusCode max17261_current(Max17261Storage *storage, int16_t *current_ua) {
StatusCode max17261_voltage(Max17261Storage *storage, uint16_t *vcell_mv) {
uint16_t vcell_reg_val = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_VCELL, &vcell_reg_val));
*vcell_mv = (uint16_t)((float)(vcell_reg_val)*VOLT_LSB / 2.5); // Reports 2.5x the Cell_X voltage
*vcell_mv = (uint16_t)((float)(vcell_reg_val)*VOLT_LSB);

return STATUS_CODE_OK;
}

Expand All @@ -90,44 +107,127 @@ StatusCode max17261_temp(Max17261Storage *storage, uint16_t *temp_c) {
return STATUS_CODE_OK;
}

StatusCode max17261_init(Max17261Storage *storage, Max17261Settings *settings) {
StatusCode max17261_get_learned_params(Max17261Storage *storage, Max27261Params *params) {
status_ok_or_return(max17261_get_reg(storage, MAX17261_R_COMP0, &params->rcomp0));
status_ok_or_return(max17261_get_reg(storage, MAX17261_TEMP_CO, &params->tempco));
status_ok_or_return(max17261_get_reg(storage, MAX17261_FULL_CAP_REP, &params->fullcaprep));
status_ok_or_return(max17261_get_reg(storage, MAX17261_CYCLES, &params->cycles));
status_ok_or_return(max17261_get_reg(storage, MAX17261_FULL_CAP_NOM, &params->fullcapnom));
return STATUS_CODE_OK;
}

StatusCode max17261_set_learned_params(Max17261Storage *storage, Max27261Params *params) {
status_ok_or_return(max17261_set_reg(storage, MAX17261_R_COMP0, params->rcomp0));
status_ok_or_return(max17261_set_reg(storage, MAX17261_TEMP_CO, params->tempco));
// somehow replaces fullcap with repcap, throws off all calculations. Battery degredation
// shouldn't be a huge factor for our time scale
// status_ok_or_return(max17261_set_reg(storage, MAX17261_FULL_CAP_REP, params->fullcaprep));
status_ok_or_return(max17261_set_reg(storage, MAX17261_CYCLES, params->cycles));
status_ok_or_return(max17261_set_reg(storage, MAX17261_FULL_CAP_NOM, params->fullcapnom));
return STATUS_CODE_OK;
}

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

storage->settings = settings;

// Configuration for alerts
// Enable current, voltage, and temperature alerts (pg.17 of datasheet, Config (1Dh) Format)
uint16_t config = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_CONFIG, &config));
config |= (1 << 2);
// Enable external temp readings
// config |= (1 << 15);
// config |= (1 << 8);
// config |= (1 << 4);
status_ok_or_return(max17261_set_reg(storage, MAX17261_CONFIG, config));
// Upper byte is IMAX and lower byte is IMIN
uint16_t current_th = (settings->i_thresh_max << 8) & (settings->i_thresh_min & 0x00FF);
status_ok_or_return(max17261_set_reg(storage, MAX17261_I_ALRT_TH, current_th));
// Upper byte is TMAX and lower byte is TMIN (leave TMIN as 0 C)
status_ok_or_return(
max17261_set_reg(storage, MAX17261_TEMP_ALRT_THRSH, (settings->temp_thresh_max << 8)));
// ** Based on
// https://web.archive.org/web/20240330212616/https://pdfserv.maximintegrated.com/en/an/MAX1726x-Software-Implementation-user-guide.pdf
// Step 0 - check if already configured
uint16_t status = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_STATUS, &status));
if ((status & 0x0002) != 0) {
// Step 1 -- delay until FSTAT.DNR bit == 0
uint16_t fstat = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_FSTAT, &fstat));
// Checks that initializaton has occurred, must happen before configuration
// Should not take longer than 250 ms
TickType_t start_time = xTaskGetTickCount();
while ((fstat & 1) != 0) {
LOG_DEBUG("data not ready, fstat: %d (%d)\n", fstat, fstat & 1);
status_ok_or_return(max17261_get_reg(storage, MAX17261_FSTAT, &fstat));
if (xTaskGetTickCount() >= start_time + pdMS_TO_TICKS(250)) {
LOG_DEBUG("fstat failed: %d (%d)\n", fstat, fstat & 1);
}
delay_ms(10);
}

// Step 2 -- disable hibernation
uint16_t hibcfg = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_HIB_CFG, &hibcfg));
status_ok_or_return(max17261_set_reg(storage, MAX17261_SOFT_WAKEUP, 0x90)); // wakup ic
status_ok_or_return(max17261_set_reg(storage, MAX17261_HIB_CFG, 0x0)); // disable hibernation
status_ok_or_return(
max17261_set_reg(storage, MAX17261_SOFT_WAKEUP, 0x0)); // clear wakeup command

delay_ms(
25); // unsure why this works, not mentioned in any documentation. Seems reliable enough.

// Step 2.1 -- configure
status_ok_or_return(
max17261_set_reg(storage, MAX17261_DESIGN_CAP, settings->pack_design_cap_mah / CAP_LSB));
status_ok_or_return(
max17261_set_reg(storage, MAX17261_I_CHG_TERM, settings->charge_term_current_ma / CUR_LSB));
status_ok_or_return(
max17261_set_reg(storage, MAX17261_V_EMPTY, settings->cell_empty_voltage_v / VOLT_LSB));

status_ok_or_return(
max17261_set_reg(storage, MAX17261_SOC_HOLD, 0x0)); // disable SOCHold, not relevant to us

uint16_t modelcfg = 0;
modelcfg |= (1 << 15); // refresh
modelcfg |= (0 << 13); // R100
modelcfg |= (0 << 10); // RChg
modelcfg |= (1 << 3); // mandatory 1
status_ok_or_return(max17261_set_reg(storage, MAX17261_MODEL_I_CFG, modelcfg));

// wait for modelcfg refresh to complete
// Should not take longer than 1sec
start_time = xTaskGetTickCount();
while (modelcfg & (1 << 15)) {
LOG_DEBUG("modelcfg refresh not cleared: %d\n", modelcfg);
status_ok_or_return(max17261_get_reg(storage, MAX17261_MODEL_I_CFG, &modelcfg));
if (xTaskGetTickCount() >= start_time + pdMS_TO_TICKS(1000)) {
LOG_DEBUG("modelcfg failed: %d (%d)\n", fstat, fstat & 1);
}
delay_ms(10);
}

// Configure alerts
uint16_t config = 0;
status_ok_or_return(max17261_get_reg(storage, MAX17261_CONFIG, &config));
config |= (1 << 2); // enable alerts
config |= (1 << 4); // thermal alerts
config |= (1 << 8); // external temp measurement
status_ok_or_return(max17261_set_reg(storage, MAX17261_CONFIG, config));

uint16_t current_th = (settings->i_thresh_max_a << 8) & (settings->i_thresh_min_a & 0x00FF);
status_ok_or_return(max17261_set_reg(storage, MAX17261_I_ALRT_TH, current_th));

status_ok_or_return(
max17261_set_reg(storage, MAX17261_TEMP_ALRT_THRSH, (settings->temp_thresh_max_c < 8)));

// Disable voltage alert (handled by AFE)
status_ok_or_return(max17261_set_reg(storage, MAX17261_VOLT_ALRT_THRSH, 0xFF00));
// Disable state of charge alerting (no need to fault on SOC)
status_ok_or_return(max17261_set_reg(storage, MAX17261_SOC_ALRT_THRSH, 0xFF00));

// enable hibernation
status_ok_or_return(max17261_set_reg(storage, MAX17261_HIB_CFG, hibcfg));
}

// Make sure voltage alerts are disabled (handled by AFEs) (see datasheet pg.25 for disabled
// VAlrtTh value)
status_ok_or_return(max17261_set_reg(storage, MAX17261_VOLT_ALRT_THRSH, (0xFF00)));
// Make sure SOC alerts are disabled (see datasheet pg.26 for disabled SAlrtTh value)
status_ok_or_return(max17261_set_reg(storage, MAX17261_SOC_ALRT_THRSH, (0xFF00)));
// Step 3
status_ok_or_return(max17261_get_reg(storage, MAX17261_STATUS, &status));
status_ok_or_return(max17261_set_reg(storage, MAX17261_STATUS,
status & (uint16_t) ~(1 << 1))); // clear status POR bit

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));
if (params) {
status_ok_or_return(max17261_set_learned_params(storage, params));
}

return STATUS_CODE_OK;
}
2 changes: 1 addition & 1 deletion libraries/ms-freertos/inc/FreeRTOSConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ extern uint32_t SystemCoreClock;
#define configUSE_TIMERS 1
#define configTIMER_TASK_PRIORITY 3
#define configTIMER_QUEUE_LENGTH 10
#define configTIMER_TASK_STACK_DEPTH configMINIMAL_STACK_SIZE
#define configTIMER_TASK_STACK_DEPTH ((uint16_t)256)

// Optional functions - most linkers will remove unused functions anyway
#define INCLUDE_vTaskPrioritySet 1
Expand Down
2 changes: 2 additions & 0 deletions projects/bms_carrier/inc/bms.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ typedef struct CurrentStorage {
uint16_t voltage;
uint16_t temperature;
uint32_t fuel_guage_cycle_ms; // Time in ms between conversions (soft timer kicks)
uint32_t remaining_capacity;
uint32_t full;
} CurrentStorage;

typedef struct BmsStorage {
Expand Down
32 changes: 11 additions & 21 deletions projects/bms_carrier/inc/current_sense.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
#pragma once

// Stores current readings from the ADS1259 in a ring buffer.
// Requires interrupts and soft timers to be initialized.

#include <stdbool.h>
#include <stdint.h>

Expand All @@ -17,35 +14,28 @@
#define MAX17261_I2C_PORT (I2C_PORT_2)
#define MAX17261_I2C_ADDR (0x36)

#define CURRENT_SENSE_R_SENSE_MOHMS (0.5)
#define PACK_CAPACITY_MAH 3000
#define SENSE_RESISTOR_MOHM (0.5)

// Capacity in units of 5.0uVh/Rsense
#define MAIN_PACK_DESIGN_CAPACITY (PACK_CAPACITY_MAH * CURRENT_SENSE_R_SENSE_MOHMS / 5)
#define PACK_CAPACITY_MAH (160000) // one module

// LSB = 78.125 (micro Volts)
#define MAIN_PACK_EMPTY_VOLTAGE_MV 2500
#define MAIN_PACK_EMPTY_VOLTAGE (MAIN_PACK_EMPTY_VOLTAGE_MV / 78.125)
#define CELL_EMPTY_VOLTAGE_MV 2500 // LG M50T datasheet

#define CHARGE_TERMINATION_CURRENT 0
#define CHARGE_TERMINATION_CURRENT_MA (400) // 50 mA * 8 (one module)

// // TODO (Adel C): Change these values to their actual values
// #define CURRENT_SENSE_R_SENSE_MILLI_OHMS (0.5)
// #define MAIN_PACK_DESIGN_CAPACITY
// (1.0f / CURRENT_SENSE_R_SENSE_MILLI_OHMS) // LSB = 5.0 (micro Volt Hours / R Sense)
// #define MAIN_PACK_EMPTY_VOLTAGE (1.0f / 78.125) // Only a 9-bit field, LSB = 78.125 (micro
// Volts) #define CHARGE_TERMINATION_CURRENT (1.0f / (1.5625f / CURRENT_SENSE_R_SENSE_MILLI_OHMS))
#define NUM_SERIES_CELLS 36 // Number of cells in series to multiply average cell voltage

// Thresholds for ALRT Pin
#define CURRENT_SENSE_MAX_CURRENT_A (58.2f) // 58.2 Amps
#define CURRENT_SENSE_MIN_CURRENT_A (27.0f) // Actually -27
#define CURRENT_SENSE_MAX_TEMP (60U)
#define CURRENT_SENSE_MAX_VOLTAGE (15000U) // uints of 10mv
#define CURRENT_SENSE_MAX_CURRENT_A (58.2f)
#define CURRENT_SENSE_MIN_CURRENT_A (-27.0f) // Actually -27
#define CURRENT_SENSE_MAX_TEMP_C (60U)
#define CURRENT_SENSE_MAX_VOLTAGE_V (15000)
#define ALRT_PIN_V_RES_MICRO_V (400)

#define CELL_X_R1_KOHMS 1780
#define CELL_X_R2_KOHMS 20

#define CURRENT_SENSE_STORE_FLASH NUM_FLASH_PAGES - 1

// Enum for GPIO IT alerts (just the one pin)
typedef enum { CURRENT_SENSE_RUN_CYCLE = 0, ALRT_GPIO_IT, KILLSWITCH_IT } CurrentSenseNotification;

Expand Down
Loading
Loading