SoC values after battery profiling

Hello

Awhile back I profiled my battery using power up app @ 4C 25C and 45C for a battery between 2.75V and 4.2V battery model is attached in zip.

The profiles themselves seem okay, so i think the measurement went well (see graphs below), however in practice i have a couple of problems:

1) Batteries never charge to 100%. I can confirm this by using the PPK2 to power my system instead of the battery. if i set the voltage to 4.2V source mode the SoC measurement comes back as 98% even though my profiles are all set up for VTerm = 4.2V so surely this should give me 100%?

My firmware is set up to listen to events from the NPM1300 to control the LED status and i never seem to get the battery charged event.

2) battery voltages sometimes seem to shift quite a bit post charge. Like they'll get to 98% and quite quickly drop to something like 94%. I've checked the power draw of my PCBs and they are all good, drawing the expected 15uA so I don't think this drop is real.

How does the SoC measurement work, i.e. is there smoothing or do the values update quite rapidly? I'm wondering if it's susceptible to spikes on the VBat supply.

Related to that, i was reviewing my implementation of the NPM1300 versus the EK and I can see I missed putting a capacitor on the battery directly so I wonder if this might make it susceptible to spikes that would cause the battery voltage to dip when a measurement is taken?

I've also included my npm1300 code.

battery_model.zip

/** ============================================================================
 *  @file       power.c
 *
 *  @brief      Power management
 *
 *  ============================================================================
 */

/*******************************************************************************
 * INCLUDES
 *******************************************************************************/
#include <stdlib.h>

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/regulator.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/sensor/npm1300_charger.h>
#include <zephyr/dt-bindings/regulator/npm1300.h>
#include <zephyr/drivers/mfd/npm1300.h>
#include "fuel_gauge.h"
#include <zephyr/sys/reboot.h>

/* Project Specific files */
#include "power.h"
#include "tWatchdog.h"
#include "tApplication.h"
#include "tAuditLog.h"

#include "extperiph/sensori2c.h" //PMIC_I2C_instance

/*******************************************************************************
 * CONSTANTS AND MACROS
 *******************************************************************************/
#define SENSOR_SELECT()     SensorI2C_Select(SENSOR_I2C_1, 0x68)
#define SENSOR_DESELECT()   SensorI2C_Deselect()

LOG_MODULE_REGISTER(nept_Power);

K_MUTEX_DEFINE(power_ready_mutex);    
K_CONDVAR_DEFINE(power_ready_condvar); 

#define FUEL_GAUGE_PERIOD_VBUS_NOT_PRESENT_MS 60000
#define FUEL_GAUGE_PERIOD_VBUS_PRESENT_MS 5000

#define REG_MAIN_BASEADDR 0x0
#define REG_TASKSWRESET 0x1
#define REG_EVENTSADCSET 0x2
#define REG_EVENTSADCCLR 0x3
#define REG_INTENEVENTSADCSET 0x4
#define REG_INTENEVENTSADCCLR 0x5
#define REG_EVENTSBCHARGER0SET 0x6
#define REG_EVENTSBCHARGER0CLR 0x7
#define REG_INTENEVENTSBCHARGER0SET 0x8
#define REG_INTENEVENTSBCHARGER0CLR 0x9
#define REG_EVENTSBCHARGER1SET 0xa
#define REG_EVENTSBCHARGER1CLR 0xb
#define REG_INTENEVENTSBCHARGER1SET 0xc
#define REG_INTENEVENTSBCHARGER1CLR 0xd
#define REG_EVENTSBCHARGER2SET 0xe
#define REG_EVENTSBCHARGER2CLR 0xf
#define REG_INTENEVENTSBCHARGER2SET 0x10
#define REG_INTENEVENTSBCHARGER2CLR 0x11
#define REG_EVENTSSHPHLDSET 0x12
#define REG_EVENTSSHPHLDCLR 0x13
#define REG_INTENEVENTSSHPHLDSET 0x14
#define REG_INTENEVENTSSHPHLDCLR 0x15
#define REG_EVENTSVBUSIN0SET 0x16
#define REG_EVENTSVBUSIN0CLR 0x17
#define REG_INTENEVENTSVBUSIN0SET 0x18
#define REG_INTENEVENTSVBUSIN0CLR 0x19
#define REG_EVENTSVBUSIN1SET 0x1a
#define REG_EVENTSVBUSIN1CLR 0x1b
#define REG_INTENEVENTSVBUSIN1SET 0x1c
#define REG_INTENEVENTSVBUSIN1CLR 0x1d
#define REG_EVENTSGPIOSET 0x22
#define REG_EVENTSGPIOCLR 0x23
#define REG_INTENEVENTSGPIOSET 0x24
#define REG_INTENEVENTSGPIOCLR 0x25

#define REG_BCHARGER_BASEADDR 0x3
#define REG_BCHARGER_TASKRELEASEERR 0x0
#define REG_BCHARGER_TASKCLEARCHGERR 0x1
#define REG_BCHARGER_TASKCLEARSAFETYTIMER 0x2
#define REG_BCHARGER_BCHGENABLESET 0x4
#define REG_BCHARGER_BCHGENABLECLR 0x5
#define REG_BCHARGER_BCHGDISABLESET 0x6
#define REG_BCHARGER_BCHGDISABLECLR 0x7
#define REG_BCHARGER_BCHGISETMSB 0x8
#define REG_BCHARGER_BCHGISETLSB 0x9
#define REG_BCHARGER_BCHGISETDISCHARGEMSB 0xa
#define REG_BCHARGER_BCHGISETDISCHARGELSB 0xb
#define REG_BCHARGER_BCHGVTERM 0xc
#define REG_BCHARGER_BCHGVTERMR 0xd
#define REG_BCHARGER_BCHGVTRICKLESEL 0xe
#define REG_BCHARGER_BCHGITERMSEL 0xf
#define REG_BCHARGER_NTCCOLD 0x10
#define REG_BCHARGER_NTCCOLDLSB 0x11
#define REG_BCHARGER_NTCCOOL 0x12
#define REG_BCHARGER_NTCCOOLLSB 0x13
#define REG_BCHARGER_NTCWARM 0x14
#define REG_BCHARGER_NTCWARMLSB 0x15
#define REG_BCHARGER_NTCHOT 0x16
#define REG_BCHARGER_NTCHOTLSB 0x17
#define REG_BCHARGER_DIETEMPSTOP 0x18
#define REG_BCHARGER_DIETEMPSTOPLSB 0x19
#define REG_BCHARGER_DIETEMPRESUME 0x1a
#define REG_BCHARGER_DIETEMPRESUMELSB 0x1b
#define REG_BCHARGER_BCHGILIMSTATUS 0x2d
#define REG_BCHARGER_NTCSTATUS 0x32
#define REG_BCHARGER_DIETEMPSTATUS 0x33
#define REG_BCHARGER_BCHGCHARGESTATUS 0x34
#define REG_BCHARGER_BCHGERRREASON 0x36
#define REG_BCHARGER_BCHGERRSENSOR 0x37
#define REG_BCHARGER_BCHGCONFIG 0x3c


#define REG_BUCK_BASEADDR 0x4
#define REG_BUCK_BUCK1ENASET 0x0
#define REG_BUCK_BUCK1ENACLR 0x1
#define REG_BUCK_BUCK2ENASET 0x2
#define REG_BUCK_BUCK2ENACLR 0x3
#define REG_BUCK_BUCK1PWMSET 0x4
#define REG_BUCK_BUCK1PWMCLR 0x5
#define REG_BUCK_BUCK2PWMSET 0x6
#define REG_BUCK_BUCK2PWMCLR 0x7
#define REG_BUCK_BUCK1NORMVOUT 0x8
#define REG_BUCK_BUCK1RETVOUT 0x9
#define REG_BUCK_BUCK2NORMVOUT 0xA
#define REG_BUCK_BUCK2RETVOUT 0xB
#define REG_BUCK_BUCKENCTRL 0xC
#define REG_BUCK_BUCKVRETCTRL 0xD
#define REG_BUCK_BUCKPWMCTRL 0xE
#define REG_BUCK_BUCKSWCTRLSEL 0xF
#define REG_BUCK_BUCK1VOUTSTATUS 0x10
#define REG_BUCK_BUCK2VOUTSTATUS 0x11
#define REG_BUCK_BUCKCTRL0 0x15
#define REG_BUCK_BUCKSTATUS 0x34

#define BUCK1_3V0_VAL 20

#define REG_ADC_BASEADDR 0x5
#define REG_TASK_VBATMEASURE 0x0
#define REG_TASK_NTCMEASURE 0x1
#define REG_TASK_TEMPMEASURE 0x2
#define REG_TASK_SYSMEASURE 0x3
#define REG_TASK_BUS7MEASURE 0x7
#define REG_TASK_DELAYEDVBATMEASURE 0x8
#define REG_ADCCONFIG 0x9
#define REG_ADCNTCRSEL 0xA
#define REG_ADCAUTOTIMCONF 0xB
#define REG_TASK_AUTOTIMUPDATE 0xC
#define REG_ADCDELTIMCONF 0xD
#define REG_VBATRESULTMSB 0x11
#define REG_ADCNTCRESULTMSB 0x12
#define REG_ADCTEMPRESULTMSB 0x13
#define REG_ADCVSYSRESULTMSB 0x14
#define REG_ADCGP0RESULTLSBS 0x15
#define REG_ADCVBAT0RESULTMSB 0x16
#define REG_ADCVBAT1RESULTMSB 0x17
#define REG_ADCVBAT2RESULTMSB 0x18
#define REG_ADCVBAT3RESULTMSB 0x19
#define REG_ADCGP1RESULTLSBS 0x1A


#define REG_GPIO_BASEADDR 0x6
#define REG_GPIO_MODE_0 0x0
#define REG_GPIO_MODE_1 0x1
#define REG_GPIO_MODE_2 0x2
#define REG_GPIO_MODE_3 0x3
#define REG_GPIO_MODE_4 0x4
#define REG_GPIO_DRIVE_0 0x5
#define REG_GPIO_DRIVE_1 0x6
#define REG_GPIO_DRIVE_2 0x7
#define REG_GPIO_DRIVE_30 0x8
#define REG_GPIO_DRIVE_4 0x9
#define REG_GPIO_PUEN_0 0xA
#define REG_GPIO_PUEN_1 0xB
#define REG_GPIO_PUEN_2 0xC
#define REG_GPIO_PUEN_3 0xD
#define REG_GPIO_PUEN_4 0xE
#define REG_GPIO_PDEN_0 0xF
#define REG_GPIO_PDEN_1 0x10
#define REG_GPIO_PDEN_2 0x11
#define REG_GPIO_PDEN_3 0x12
#define REG_GPIO_PDEN_4 0x13
#define REG_GPIO_OD_0 0x14
#define REG_GPIO_OD_1 0x15
#define REG_GPIO_OD_2 0x16
#define REG_GPIO_OD_3 0x17
#define REG_GPIO_OD_4 0x18
#define REG_GPIO_BOUNCE_0 0x19
#define REG_GPIO_BOUNCE_1 0x1A
#define REG_GPIO_BOUNCE_2 0x1B
#define REG_GPIO_BOUNCE_3 0x1C
#define REG_GPIO_BOUNCE_4 0x1D
#define REG_GPIO_STATUS 0x1E

#define GPIO_MODE_IRQ 5

#define REG_LDSW_BASEADDR 0x8
#define REG_LDSW_TASKLDSW1SET 0x0
#define REG_LDSW_TASKLDSW1CLR 0x1
#define REG_LDSW_TASKLDSW2SET 0x2
#define REG_LDSW_TASKLDSW2CLR 0x3
#define REG_LDSW_LDSWSTATUS 0x4
#define REG_LDSW_LDSW1GPISEL 0x5
#define REG_LDSW_LDSW2GPISEL 0x6
#define REG_LDSW_LDSWCONFIG 0x7

#define REG_ERRLOG_BASEADDR 0xe
#define REG_TASKCLRERRLOG 0x0
#define REG_SCRATCH0 0x1
#define REG_SCRATCH1 0x2
#define REG_RSTCAUSE 0x3
#define REG_CHARGERERRREASON 0x4
#define REG_CHARGERERRSENSOR 0x5

#define ADC_PARAM_MAX_ENUMERABLE 7

#define REG_TIMER_BASEADDR 0x7
#define REG_TIMER_TIMERSET 0x0
#define REG_TIMER_TIMERCLR 0x1
#define REG_TIMER_TIMERTARGETSTROBE 0x3
#define REG_TIMER_WATCHDOGKICK 0x4
#define REG_TIMER_TIMERCONFIG 0x5
#define REG_TIMER_TIMERSTATUS 0x6
#define REG_TIMER_TIMERHIBYTE 0x8
#define REG_TIMER_TIMERMIDBYTE 0x9
#define REG_TIMER_TIMERLOBYTE 0xa

#define TIMER_MAX_VAL 167777215 /* 2^24 from 3 byte registers for lo/mid/hi*/
#define TIMER_MODE_WAKEUP 4

#define REG_SHIP_BASEADDR 0xb
#define REG_SHIP_TASKENTERHIBERNATE 0x0
#define REG_SHIP_TASKSHPHLDCFGSTROBE 0x1
#define REG_SHIP_TASKENTERSHIPMODE 0x2
#define REG_SHIP_TASKRESETCFG 0x3
#define REG_SHIP_SHPHLDCONFIG 0x4
#define REG_SHIP_SHPHLDSTATUS 0x5
#define REG_SHIP_LPRESETCONFIG 0x6

#define REG_ERRLOG_BASEADDR 0xe
#define REG_ERRLOG_TASKCLEARERRLOG 0x0
#define REG_ERRLOG_SCRATCH0 0x1
#define REG_ERRLOG_SCRATCH1 0x2
#define REG_ERRLOG_RSTCAUSE 0x3
#define REG_ERRLOG_CHARGERERREASON 0x4
#define REG_ERRLOG_CHARGERERSENSOR 0x5

/*******************************************************************************
 * TYPES
 *******************************************************************************/
struct power_supply {
    uint8_t ref_count;
    bool is_on;
};

/*******************************************************************************
 * PRIVATE FUNCTION PROTOTYPES
 *******************************************************************************/
static void fuel_gauge_cb(struct k_work *work);
static void shphld_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins);
static void chg_completed_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins);
static void chg_error_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins);
static void vbus_detected_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins);
static void vbus_removed_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins);
static void initEvents(void);

/*******************************************************************************
 * PRIVATE GLOBAL VARIABLES
 *******************************************************************************/
static volatile bool bPowerManagerReady = false;
static const struct device *pmic = DEVICE_DT_GET(DT_NODELABEL(npm1300_ek_pmic));
static const struct device *charger = DEVICE_DT_GET(DT_NODELABEL(npm1300_ek_charger));

#ifdef CONFIG_NRF_FUEL_GAUGE
static K_WORK_DELAYABLE_DEFINE(fuel_gauge_work, fuel_gauge_cb);
#endif

static bool bFuelGaugeEnabled = false;
static bool bVBusPresent = false;
static uint8_t u8BatteryStateOfCharge = 0u;
static struct power_supply ps_3v0_b = {0, false};
static struct power_supply ps_3v0_c = {0, false};

K_MUTEX_DEFINE(ps_mutex);

static const tAuditLog_ModuleCodes_t ModuleId = MODULE_POWER;

/*******************************************************************************
 * PRIVATE FUNCTIONS
 *******************************************************************************/
/*******************************************************************************
 * @fn          bottle_inserted_debounce_cb
 *
 * @brief       After the alarm has triggered for the bottle inserted timer, this
 *              is called
 *
 * @return      None
 ******************************************************************************/
#ifdef CONFIG_NRF_FUEL_GAUGE
static void fuel_gauge_cb(struct k_work *work)
{
    ARG_UNUSED(work);
    uint32_t u32FuelGaugePeriod = 0;
    uint8_t u8Soc = 0u;
    static bool bFirstRead = true;
    static bool bStartedLowBattery = false;

    int32_t err = SENSOR_SELECT();
    if (err != 0)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Error selecting PMIC on I2C Bus");
        SENSOR_DESELECT();
        return;
    }
    err = fuel_gauge_update(charger, &u8Soc); //PMIC_I2C_instance
    SENSOR_DESELECT();
    if(err)
    {
        LOG_ERR("Failed to update fuel gauge algorithm");
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
    }
    else
    {
        /* We only want to reset if we go from a good battery state to a bad one. otherwise the application disabled state will handle it */
        if(bFirstRead && (u8Soc < CONFIG_MIN_BATTERY_PERCENT_FOR_OPERATION))
        {
            bStartedLowBattery = true;
        }

        if(!bStartedLowBattery && (u8Soc < CONFIG_MIN_BATTERY_PERCENT_FOR_OPERATION))
        {
            /* Wait for all tasks to finish any work before resetting so a dispense or audit log write isn't interrupted */
#ifndef CONFIG_APP_TEST_SHELL_MODE
            while(!Watchdog_IsAllTaskSleeping()){};
            sys_reboot(SYS_REBOOT_WARM);
#endif
        }
        u8BatteryStateOfCharge = u8Soc;
    }

    bFirstRead = false;

    if(!bFuelGaugeEnabled)
    {
        return;
    }

    if(bVBusPresent)
    {
        u32FuelGaugePeriod = FUEL_GAUGE_PERIOD_VBUS_PRESENT_MS;
    }
    else
    {
        u32FuelGaugePeriod = FUEL_GAUGE_PERIOD_VBUS_NOT_PRESENT_MS;
    }

    err = k_work_reschedule(&fuel_gauge_work, K_MSEC(u32FuelGaugePeriod));
    if(err < 0)
    {
        LOG_ERR("Failed to reschedule work (err=%d)",err);
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
    }
}
#endif /* CONFIG_NRF_FUEL_GAUGE */

static void shphld_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	k_event_post(&ApplicationEvents, APPLICATION_EVT_USERBTN_PRESSED);
}

static void chg_completed_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	k_event_post(&ApplicationEvents, APPLICATION_EVT_BATTERY_CHARGED);
}

static void chg_error_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
	k_event_post(&ApplicationEvents, APPLICATION_EVT_BATTERY_CHARGING_ERROR); 
}

static void vbus_detected_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
    bVBusPresent = true;
#ifdef CONFIG_NRF_FUEL_GAUGE
    int32_t err = k_work_reschedule(&fuel_gauge_work, K_MSEC(FUEL_GAUGE_PERIOD_VBUS_PRESENT_MS));
    if(err < 0)
    {
        LOG_ERR("Failed to reschedule work (err=%d)",err);
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
    }
#endif
	k_event_post(&ApplicationEvents, APPLICATION_EVT_BATTERY_CHARGING);
}

static void vbus_removed_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
    bVBusPresent = false;
#ifdef CONFIG_NRF_FUEL_GAUGE
    int32_t err = k_work_reschedule(&fuel_gauge_work,K_MSEC(FUEL_GAUGE_PERIOD_VBUS_NOT_PRESENT_MS));
    if(err < 0)
    {
        LOG_ERR("Failed to reschedule work (err=%d)",err);
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
    }
#endif
	k_event_post(&ApplicationEvents, APPLICATION_EVT_BATTERY_NOT_CHARGING);
}

/*******************************************************************************
 * @fn          initEvents 
 *
 * @brief       Set up the Events to feed ISRs into the task
 *
 * @return      None
 ******************************************************************************/
static void initEvents(void)
{
	if (!device_is_ready(pmic)) {
        tAuditLog_SendErr(ModuleId, __LINE__, 0, 0);
		LOG_ERR("Pmic device not ready.\n");
		return;
	}

    static struct gpio_callback shphld_cb;
    static struct gpio_callback chg_completed_cb;
    static struct gpio_callback chg_error_cb;
    static struct gpio_callback vbus_detected_cb;
    static struct gpio_callback vbus_removed_cb;

    gpio_init_callback(&shphld_cb, shphld_callback, BIT(NPM1300_EVENT_SHIPHOLD_PRESS));
    gpio_init_callback(&chg_completed_cb, chg_completed_callback, BIT(NPM1300_EVENT_CHG_COMPLETED));
    gpio_init_callback(&chg_error_cb, chg_error_callback, BIT(NPM1300_EVENT_CHG_ERROR));
    gpio_init_callback(&vbus_detected_cb, vbus_detected_callback, BIT(NPM1300_EVENT_VBUS_DETECTED));
    gpio_init_callback(&vbus_removed_cb, vbus_removed_callback, BIT(NPM1300_EVENT_VBUS_REMOVED));

	int32_t err = mfd_npm1300_add_callback(pmic, &shphld_cb);
    if(err)
    {
        LOG_ERR("Failed to add shphld cb");
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
    }

    err = mfd_npm1300_add_callback(pmic, &chg_completed_cb);
    if(err)
    {
        LOG_ERR("Failed to add chg complete cb");
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
    }

    err = mfd_npm1300_add_callback(pmic, &chg_error_cb);
        if(err)
    {
        LOG_ERR("Failed to add chg error cb");
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
    }

    err = mfd_npm1300_add_callback(pmic, &vbus_detected_cb);
    if(err)
    {
        LOG_ERR("Failed to add vbus detected cb");
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
    }

    err = mfd_npm1300_add_callback(pmic, &vbus_removed_cb);
    if(err)
    {
        LOG_ERR("Failed to add vbus removed cb");
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
    }
}

/*******************************************************************************
 * PUBLIC FUNCTIONS
 *******************************************************************************/

int32_t Power_Init(uint8_t * pu8BatSoc)
{
    uint8_t u8Reg = 0;
    int32_t err =0;

    k_mutex_lock(&power_ready_mutex, K_FOREVER);

    bool b3v0b_enabled_on_startup = false;

    initEvents();

    /* Get the battery voltage first, so we can disable the system if the voltage
    is too low. */
    #ifdef CONFIG_NRF_FUEL_GAUGE
    if (!device_is_ready(charger)) {
        tAuditLog_SendErr(ModuleId, __LINE__, 0, 0);
        LOG_ERR("Charger device not ready.\n");
        return -EBUSY;
	}
  
    err = fuel_gauge_init(charger);
	if (err) {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
		LOG_ERR("Could not initialise fuel gauge.\n");
		return err;
	}

    /* Start monitoring battery state-of-charge */
    bFuelGaugeEnabled = true;
    fuel_gauge_cb(NULL);

    if(pu8BatSoc != NULL)
    {
        *pu8BatSoc = u8BatteryStateOfCharge;

        /* if we're below the threshold then go into the disabled state*/
        if(*pu8BatSoc < CONFIG_MIN_BATTERY_PERCENT_FOR_OPERATION)
        {
            return 0;
        }
    }
#endif

    err = Power_SupplyGetStatus(POWER_SUPPLY_3V0_B);

    if(err < 0)
    {
        LOG_ERR("Failed to get power supply status");
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
    }
    else
    {
        b3v0b_enabled_on_startup = (bool)err;
    }

    /* Disable reset on long hold (>10s) of shphld btn */
    err = SENSOR_SELECT();
    if (err != 0)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Error selecting PMIC on I2C Bus");
        SENSOR_DESELECT();
        return err;
    }
    err = mfd_npm1300_reg_write(pmic, REG_SHIP_BASEADDR, REG_SHIP_LPRESETCONFIG, 1); //PMIC_I2C_instance
    SENSOR_DESELECT();
    if(err)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Failed to disabled the long hold reset");
        return err;
    }

    /* Enable sw control of buck 1 voltage */
    err = SENSOR_SELECT();
    if (err != 0)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Error selecting PMIC on I2C Bus");
        SENSOR_DESELECT();
        return err;
    }
    err = mfd_npm1300_reg_write(pmic, REG_BUCK_BASEADDR, REG_BUCK_BUCKSWCTRLSEL, 1u); //PMIC_I2C_instance
    SENSOR_DESELECT();
    if(err)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Failed to enable vbat interrupt");
        return err;
    }

    /* Set output buck 1 output voltage to 3v0*/
    err = SENSOR_SELECT();
    if (err != 0)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Error selecting PMIC on I2C Bus");
        SENSOR_DESELECT();
        return err;
    }
    err = mfd_npm1300_reg_write(pmic, REG_BUCK_BASEADDR, REG_BUCK_BUCK1NORMVOUT, BUCK1_3V0_VAL); //PMIC_I2C_instance
    SENSOR_DESELECT();
    if(err)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Failed to enable vbat interrupt");
        return err;
    }

    /* Enable the output cap dishcharge on Buck 1 turn off*/
    err = SENSOR_SELECT();
    if (err != 0)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Error selecting PMIC on I2C Bus");
        SENSOR_DESELECT();
        return err;
    }
    err = mfd_npm1300_reg_write(pmic, REG_BUCK_BASEADDR, REG_BUCK_BUCKCTRL0, 0b100); //PMIC_I2C_instance
    SENSOR_DESELECT();
    if(err)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Failed to enable vbat interrupt");
        return err;
    }

    /* Zephyr cannot do a deferred initialisation of a device.
    the flash chip is powered by 3V0_B so init will fail unless we
    enable the buck then perform a system reset so the kernel can init
    the qspi once. 3v0_b can then be disabled as usual. */
    if(!b3v0b_enabled_on_startup)
    {
        LOG_INF("Enabling 3V0_B to allow QSPI init");
        Power_SupplyEnable(POWER_SUPPLY_3V0_B);
        k_sleep(K_MSEC(100));
        sys_reboot(SYS_REBOOT_WARM);
    }

    err = Power_SupplyDisable(POWER_SUPPLY_3V0_B);
    if(err)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Failed to disable buck 1");
        return err;
    }

    /* Enable the output cap dishcharge on LS1 turn off*/
    err = SENSOR_SELECT();
    if (err != 0)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Error selecting PMIC on I2C Bus");
        SENSOR_DESELECT();
        return err;
    }
    err = mfd_npm1300_reg_write(pmic, REG_LDSW_BASEADDR, REG_LDSW_LDSWCONFIG, 0b01000000); //PMIC_I2C_instance
    SENSOR_DESELECT();
    if(err)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Failed to enable vbat interrupt");
        return err;
    }

    err = SENSOR_SELECT();
    if (err != 0)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Error selecting PMIC on I2C Bus");
        SENSOR_DESELECT();
        return err;
    }
    err = mfd_npm1300_reg_read(pmic, 0x02, 0x07, &u8Reg); //PMIC_I2C_instance
    SENSOR_DESELECT();
    if(err)
    {
        tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        LOG_ERR("Failed to read VBUS status from NPM1300 err=%d",err);
        return err;
    }
    else
    {
        bVBusPresent = u8Reg & 1u;
    }

    if(bVBusPresent)
    {
        vbus_detected_callback(NULL,NULL,0); 
    }

    bPowerManagerReady = true;

    Power_NotifySysOkay();

    return err;
}

void Power_NotifySysOkay(void)
{
    /* Wake up all waiting threads */
    k_condvar_broadcast(&power_ready_condvar); 
    k_mutex_unlock(&power_ready_mutex); 
}

void Power_AwaitReady(void)
{
    k_mutex_lock(&power_ready_mutex, K_FOREVER);

    while (!bPowerManagerReady)
    {
        /* Wait for init to complete */
        k_condvar_wait(&power_ready_condvar, &power_ready_mutex, K_FOREVER);
    }

    k_mutex_unlock(&power_ready_mutex);
}

int32_t Power_SupplyEnable(Power_Supply_t powerSupply)
{
    int32_t err = 0;
    switch(powerSupply)
    {
        case POWER_SUPPLY_3V0_B:
            k_mutex_lock(&ps_mutex, K_FOREVER);
            if(ps_3v0_b.ref_count == 0)
            {
                err = SENSOR_SELECT();
                if (err != 0)
                {
                    tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                    LOG_ERR("Error selecting PMIC on I2C Bus");
                    SENSOR_DESELECT();
                    return err;
                }
                err = mfd_npm1300_reg_write(pmic, REG_BUCK_BASEADDR, REG_BUCK_BUCK1ENASET, 1); //PMIC_I2C_instance
                SENSOR_DESELECT();
                if(err)
                {
                    tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                    LOG_ERR("Failed to enable Power Supply B");
                }
                k_sleep(K_MSEC(10)); /* Start up time for peripherals */
                ps_3v0_b.is_on = true;
            }
            ps_3v0_b.ref_count++;
            k_mutex_unlock(&ps_mutex);
        break;
        case POWER_SUPPLY_3V0_C:
            k_mutex_lock(&ps_mutex, K_FOREVER);
            if(ps_3v0_c.ref_count == 0)
            {
                err = SENSOR_SELECT();
                if (err != 0)
                {
                    tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                    LOG_ERR("Error selecting PMIC on I2C Bus");
                    SENSOR_DESELECT();
                    return err;
                }
                err = mfd_npm1300_reg_write(pmic, REG_LDSW_BASEADDR, REG_LDSW_TASKLDSW1SET, 1); //PMIC_I2C_instance
                SENSOR_DESELECT();
                if(err)
                {
                    tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                    LOG_ERR("Failed to enable Power Supply C");
                }
                k_sleep(K_MSEC(10)); /* Start up time for peripherals */
                ps_3v0_c.is_on = true;
            }
            ps_3v0_c.ref_count++;
            k_mutex_unlock(&ps_mutex);
        break;
        default:
            err = -EINVAL;
            LOG_ERR("Invalid power supply selected");
            tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        break;
    }
    return err;
}

int32_t Power_SupplyDisable(Power_Supply_t powerSupply)
{
    int32_t err = 0;
    switch(powerSupply)
    {
        case POWER_SUPPLY_3V0_B:
            k_mutex_lock(&ps_mutex, K_FOREVER);
            ps_3v0_b.ref_count = (ps_3v0_b.ref_count > 0) ? (ps_3v0_b.ref_count - 1) : 0;
            if(ps_3v0_b.ref_count == 0)
            {
                err = SENSOR_SELECT();
                if (err != 0)
                {
                    tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                    LOG_ERR("Error selecting PMIC on I2C Bus");
                    SENSOR_DESELECT();
                    return err;
                }
                err = mfd_npm1300_reg_write(pmic, REG_BUCK_BASEADDR, REG_BUCK_BUCK1ENACLR, 1); //PMIC_I2C_instance
                SENSOR_DESELECT();
                if(err)
                {
                    tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                    LOG_ERR("Failed to disable power supply B");
                }
                ps_3v0_b.is_on = false;
            }
            k_mutex_unlock(&ps_mutex);
        break;
        case POWER_SUPPLY_3V0_C:
            k_mutex_lock(&ps_mutex, K_FOREVER);
            ps_3v0_c.ref_count = (ps_3v0_c.ref_count > 0) ? (ps_3v0_c.ref_count - 1) : 0;
            if(ps_3v0_c.ref_count == 0)
            {
                err = SENSOR_SELECT();
                if (err != 0)
                {
                    tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                    LOG_ERR("Error selecting PMIC on I2C Bus");
                    SENSOR_DESELECT();
                    return err;
                }
                err = mfd_npm1300_reg_write(pmic, REG_LDSW_BASEADDR, REG_LDSW_TASKLDSW1CLR, 1); //PMIC_I2C_instance
                SENSOR_DESELECT();
                if(err)
                {
                    tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                    LOG_ERR("Failed to disable power supply C");
                }
                ps_3v0_c.is_on = false;
            }
            k_mutex_unlock(&ps_mutex);
        break;
        default:
            err = -EINVAL;
            LOG_ERR("Invalid power supply selected");
            tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
        break;
    }
    return err;
}

int32_t Power_SupplyGetStatus(Power_Supply_t powerSupply)
{
    uint8_t u8result = 0;
    int32_t err = 0;

    switch(powerSupply)
    {
        case POWER_SUPPLY_3V0_B:
            err = SENSOR_SELECT();
            if (err != 0)
            {
                tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                LOG_ERR("Error selecting PMIC on I2C Bus");
                SENSOR_DESELECT();
                return err;
            }
            err = mfd_npm1300_reg_read(pmic, REG_BUCK_BASEADDR, REG_BUCK_BUCKSTATUS, &u8result); //PMIC_I2C_instance
            SENSOR_DESELECT();
            if(err)
            {
                tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                LOG_ERR("Failed to read 3V0_B status from NPM1300 err=%d",err);
            }
            else
            {
                return u8result & 0b100;
            }
        break;
        case POWER_SUPPLY_3V0_C:
            err = SENSOR_SELECT();
            if (err != 0)
            {
                tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                LOG_ERR("Error selecting PMIC on I2C Bus");
                SENSOR_DESELECT();
                return err;
            }
            err = mfd_npm1300_reg_read(pmic, REG_LDSW_BASEADDR, REG_LDSW_LDSWSTATUS, &u8result); //PMIC_I2C_instance
            SENSOR_DESELECT();
            if(err)
            {
                tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
                LOG_ERR("Failed to read 3V0_C status from NPM1300 err=%d",err);
            }
            else
            {
                return u8result & 0b10000;
            }
        break;
        default:
            err = -EINVAL;
            tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
            LOG_ERR("Invalid power supply selected");
            return err;
        break;
    }

    return err;
}

uint8_t Power_GetBatterySoc(void)
{
    return u8BatteryStateOfCharge;
}

/*
 * Copyright (c) 2023 Nordic Semiconductor ASA
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */
#ifdef CONFIG_NRF_FUEL_GAUGE
#include <stdlib.h>

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/sensor/npm1300_charger.h>

#include "nrf_fuel_gauge.h"
#include "tAuditLog.h"

/* nPM1300 CHARGER.BCHGCHARGESTATUS.CONSTANTCURRENT register bitmask */
#define NPM1300_CHG_STATUS_CC_MASK BIT_MASK(3)

static float max_charge_current;
static float term_charge_current;
static int64_t ref_time;

LOG_MODULE_REGISTER(nept_fuel_gauge);
static const tAuditLog_ModuleCodes_t ModuleId = MODULE_FUEL_GAUGE;


static const struct battery_model battery_model = {
#include "battery_model/EEMBLP902030_25C_4C_45C.inc"
};

static int read_sensors(const struct device *charger, float *voltage, float *current, float *temp, int32_t *chg_status)
{
	struct sensor_value value;
	int32_t err;

	err = sensor_sample_fetch(charger);
	 if (err < 0) {
		LOG_ERR("Failed to fetch sensor sample");
		tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
		return err;
	}

	err = sensor_channel_get(charger, SENSOR_CHAN_GAUGE_VOLTAGE, &value);
	if(err)
	{
		LOG_ERR("Failed to fetch voltage channel");
		tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
		return err;
	}
	*voltage = (float)value.val1 + ((float)value.val2 / 1000000);

	err = sensor_channel_get(charger, SENSOR_CHAN_GAUGE_TEMP, &value);
	if(err)
	{
		LOG_ERR("Failed to fetch temp channel");
		tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
		return err;
	}
	*temp = (float)value.val1 + ((float)value.val2 / 1000000);

	err = sensor_channel_get(charger, SENSOR_CHAN_GAUGE_AVG_CURRENT, &value);
	if(err)
	{
		LOG_ERR("Failed to fetch avg. current channel");
		tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
		return err;
	}
	*current = (float)value.val1 + ((float)value.val2 / 1000000);

	err = sensor_channel_get(charger, SENSOR_CHAN_NPM1300_CHARGER_STATUS, &value);
	if(err)
	{
		LOG_ERR("Failed to fetch charge status");
		tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
		return err;
	}
	*chg_status = value.val1;

	return 0;
}

int fuel_gauge_init(const struct device *charger)
{
	struct sensor_value value;
	struct nrf_fuel_gauge_init_parameters parameters = { .model = &battery_model };
	int32_t err;
	int32_t chg_status;

	err = read_sensors(charger, &parameters.v0, &parameters.i0, &parameters.t0, &chg_status);
	if (err) {
		LOG_ERR("Failed to fetch sensor data");
		tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
		return err;
	}

	/* Store charge nominal and termination current, needed for ttf calculation */
	err = sensor_channel_get(charger, SENSOR_CHAN_GAUGE_DESIRED_CHARGING_CURRENT, &value);
	if(err)
	{
		LOG_ERR("Failed to fetch desired charging current");
		tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
		return err;
	}
	max_charge_current = (float)value.val1 + ((float)value.val2 / 1000000);
	term_charge_current = max_charge_current / 10.f;

	err = nrf_fuel_gauge_init(&parameters, NULL);
	if(err)
	{
		LOG_ERR("Failed to init fuel gauge algorithm");
		tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
		return err;
	}

	ref_time = k_uptime_get();

	return 0;
}

int fuel_gauge_update(const struct device *charger, uint8_t *pu8Soc)
{
	float voltage;
	float current;
	float temp;
	float soc;
	float tte;
	float ttf;
	float delta;
	int32_t chg_status;
	bool cc_charging;
	int32_t err;

	err = read_sensors(charger, &voltage, &current, &temp, &chg_status);
	if (err < 0) {
		LOG_ERR("Failed to fetch sensor data");
		tAuditLog_SendErr(ModuleId, __LINE__, err, 0);
		return err;
	}

	cc_charging = (chg_status & NPM1300_CHG_STATUS_CC_MASK) != 0;

	delta = (float) k_uptime_delta(&ref_time) / 1000.f;

	soc = nrf_fuel_gauge_process(voltage, current, temp, delta, NULL);
	tte = nrf_fuel_gauge_tte_get();
	ttf = nrf_fuel_gauge_ttf_get(cc_charging, -term_charge_current);

	LOG_INF("V: %.3f, I: %.3f, T: %.2f, ", voltage, current, temp);
	LOG_INF("SoC: %.2f, TTE: %.0f, TTF: %.0f", soc, tte, ttf);

	*pu8Soc = (uint8_t)soc;

	return 0;
}

#endif /* CONFIG_NRF_FUEL_GAUGE */

Related