Sync
This commit is contained in:
@@ -2,12 +2,27 @@ menuconfig BATT_MGMT
|
||||
bool "Battery Management"
|
||||
default y
|
||||
select ADC
|
||||
select EVENT_MGMT
|
||||
select GPIO
|
||||
select USB_MGMT
|
||||
select NRFX_SAADC
|
||||
help
|
||||
Library for initializing and managing the battery subsystem.
|
||||
|
||||
if BATT_MGMT
|
||||
config BATT_MGMT_EMPTY_THRESHOLD
|
||||
int "Battery Empty Voltage (mV)"
|
||||
default 3400
|
||||
help
|
||||
Voltage threshold treated as empty battery level (0/5).
|
||||
|
||||
config BATT_MGMT_SHUTDOWN_THRESHOLD
|
||||
int "Critical shutdown voltage (mV)"
|
||||
default 3300
|
||||
help
|
||||
If measured battery voltage is less than or equal to this threshold,
|
||||
the device will power off to protect the battery.
|
||||
|
||||
config BATT_MGMT_FULL_THRESHOLD
|
||||
int "Battery Full Voltage (mV)"
|
||||
default 3980
|
||||
@@ -39,6 +54,24 @@ if BATT_MGMT
|
||||
Time window to detect charger pin blinking (state changes). If the pin state
|
||||
changes within this window, it's considered blinking (error state).
|
||||
|
||||
config BATT_MGMT_STANDBY_MEASURE_INTERVAL_MIN
|
||||
int "Standby measurement interval (minutes)"
|
||||
default 360
|
||||
help
|
||||
Measurement interval while idle in standby (no charging, no BLE, no audio).
|
||||
|
||||
config BATT_MGMT_ACTIVE_MEASURE_INTERVAL_MIN
|
||||
int "Active measurement interval (minutes)"
|
||||
default 1
|
||||
help
|
||||
Measurement interval while charging or BLE connected.
|
||||
|
||||
config BATT_MGMT_AUDIO_COOLDOWN_SEC
|
||||
int "Audio cooldown before next measure (seconds)"
|
||||
default 60
|
||||
help
|
||||
Delay before restarting periodic battery measurement after audio playback ends.
|
||||
|
||||
config BATT_MGMT_MONITOR_THREAD_STACK_SIZE
|
||||
int "Battery monitor thread stack size"
|
||||
default 1024
|
||||
|
||||
@@ -15,6 +15,13 @@ typedef enum {
|
||||
BATT_STATE_UNKNOWN,
|
||||
} batt_mgmt_state_t;
|
||||
|
||||
typedef struct {
|
||||
batt_mgmt_state_t state;
|
||||
uint8_t level;
|
||||
uint8_t percent;
|
||||
int32_t voltage_mv;
|
||||
} batt_mgmt_info_t;
|
||||
|
||||
/**
|
||||
* @brief Measure battery VDDH voltage.
|
||||
*
|
||||
@@ -24,13 +31,53 @@ typedef enum {
|
||||
*/
|
||||
int batt_mgmt_measure_vddh_mv(uint8_t oversampling, int32_t *vddh_mv);
|
||||
|
||||
/**
|
||||
* @brief Get display voltage in millivolts.
|
||||
*
|
||||
* Returns the voltage value intended for UI/protocol display. Currently this is
|
||||
* the latest measured VDDH value cached by batt_mgmt.
|
||||
*
|
||||
* @param vddh_mv Pointer receiving display voltage in mV.
|
||||
* @return 0 on success, negative errno on failure.
|
||||
*/
|
||||
int batt_mgmt_get_display_voltage_mv(int32_t *vddh_mv);
|
||||
|
||||
/**
|
||||
* @brief Get battery level bucket (0..4) from configured thresholds.
|
||||
*
|
||||
* Level mapping:
|
||||
* - 0: below 20% threshold
|
||||
* - 1: 20%..49%
|
||||
* - 2: 50%..79%
|
||||
* - 3: 80%..99%
|
||||
* - 4: full and above
|
||||
*
|
||||
* @param level Pointer receiving level in range 0..4.
|
||||
* @return 0 on success, negative errno on failure.
|
||||
*/
|
||||
int batt_mgmt_get_battery_level(uint8_t *level);
|
||||
|
||||
/**
|
||||
* @brief Get battery percentage (0-100) from configured voltage thresholds.
|
||||
*
|
||||
* Uses piecewise linear interpolation over:
|
||||
* - EMPTY..20%
|
||||
* - 20%..50%
|
||||
* - 50%..80%
|
||||
* - 80%..FULL
|
||||
*
|
||||
* @param percent Pointer receiving percentage in range 0..100.
|
||||
* @return 0 on success, negative errno on failure.
|
||||
*/
|
||||
int batt_mgmt_get_battery_percent(uint8_t *percent);
|
||||
|
||||
/**
|
||||
* @brief Get raw charger status (GPIO level).
|
||||
*
|
||||
* Returns the current logic level of the charger status pin (typically from ETA6003).
|
||||
* This is the **raw GPIO status**, not a processed state machine result.
|
||||
*
|
||||
* @return true if charger pin is high (e.g., battery is "full"), false if low.
|
||||
* @return true if charger pin is high (e.g., battery is "charging"), false if low.
|
||||
*/
|
||||
bool batt_mgmt_get_charger_status(void);
|
||||
|
||||
@@ -48,5 +95,13 @@ bool batt_mgmt_get_charger_status(void);
|
||||
*/
|
||||
batt_mgmt_state_t batt_mgmt_get_battery_state(void);
|
||||
|
||||
/**
|
||||
* @brief Get all battery information in one call.
|
||||
*
|
||||
* @param info Pointer receiving state, level (0..4), percent (0..100), and voltage in mV.
|
||||
* @return 0 on success, negative errno on failure.
|
||||
*/
|
||||
int batt_mgmt_get_info(batt_mgmt_info_t *info);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <zephyr/init.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <nrfx_saadc.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
@@ -23,9 +24,64 @@ static const struct gpio_dt_spec batt_chg_status = GPIO_DT_SPEC_GET(BATT_CHG_STA
|
||||
|
||||
static bool batt_mgmt_ready;
|
||||
static int64_t batt_mgmt_last_chg_change_ms;
|
||||
static int64_t batt_mgmt_last_offset_calib_ms;
|
||||
static int32_t batt_mgmt_last_vddh_mv;
|
||||
static bool batt_mgmt_chg_interrupt_enabled;
|
||||
static K_THREAD_STACK_DEFINE(batt_monitor_stack, CONFIG_BATT_MGMT_MONITOR_THREAD_STACK_SIZE);
|
||||
static struct k_thread batt_monitor_thread_data;
|
||||
static struct k_sem batt_mgmt_saadc_calib_sem;
|
||||
static int batt_mgmt_saadc_calib_rc;
|
||||
|
||||
static void batt_mgmt_saadc_calib_handler(nrfx_saadc_evt_t const *p_event)
|
||||
{
|
||||
if ((p_event == NULL) || (p_event->type != NRFX_SAADC_EVT_CALIBRATEDONE))
|
||||
{
|
||||
batt_mgmt_saadc_calib_rc = -EIO;
|
||||
}
|
||||
else
|
||||
{
|
||||
batt_mgmt_saadc_calib_rc = 0;
|
||||
}
|
||||
|
||||
k_sem_give(&batt_mgmt_saadc_calib_sem);
|
||||
}
|
||||
|
||||
static int batt_mgmt_calibrate_offset_if_needed(bool force)
|
||||
{
|
||||
int rc;
|
||||
int64_t now_ms = k_uptime_get();
|
||||
int64_t calib_interval_ms = (int64_t)CONFIG_BATT_MGMT_STANDBY_MEASURE_INTERVAL_MIN * 60 * 1000;
|
||||
|
||||
if (!force && (now_ms - batt_mgmt_last_offset_calib_ms < calib_interval_ms))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (k_sem_take(&batt_mgmt_saadc_calib_sem, K_NO_WAIT) == 0)
|
||||
{
|
||||
}
|
||||
|
||||
batt_mgmt_saadc_calib_rc = -EIO;
|
||||
rc = nrfx_saadc_offset_calibrate(batt_mgmt_saadc_calib_handler);
|
||||
if (rc < 0)
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = k_sem_take(&batt_mgmt_saadc_calib_sem, K_MSEC(100));
|
||||
if (rc < 0)
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (batt_mgmt_saadc_calib_rc < 0)
|
||||
{
|
||||
return batt_mgmt_saadc_calib_rc;
|
||||
}
|
||||
|
||||
batt_mgmt_last_offset_calib_ms = k_uptime_get();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int batt_mgmt_to_adc_oversampling(uint8_t oversampling, uint8_t *adc_oversampling)
|
||||
{
|
||||
@@ -68,6 +124,12 @@ int batt_mgmt_measure_vddh_mv(uint8_t oversampling, int32_t *vddh_mv)
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
rc = batt_mgmt_calibrate_offset_if_needed(false);
|
||||
if (rc < 0)
|
||||
{
|
||||
LOG_WRN("ADC offset calibration failed before measurement: %d", rc);
|
||||
}
|
||||
|
||||
rc = batt_mgmt_to_adc_oversampling(oversampling, &adc_oversampling);
|
||||
if (rc < 0)
|
||||
{
|
||||
@@ -96,19 +158,194 @@ int batt_mgmt_measure_vddh_mv(uint8_t oversampling, int32_t *vddh_mv)
|
||||
}
|
||||
|
||||
*vddh_mv = input_mv * BATT_VDDH_DIVIDER_FACTOR;
|
||||
batt_mgmt_last_vddh_mv = *vddh_mv;
|
||||
|
||||
LOG_DBG("Battery VDDH: %d mV (raw=%d, os=%ux)", *vddh_mv, raw_sample, oversampling);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int batt_mgmt_linear_percent(int32_t mv, int32_t lo_mv, int32_t hi_mv,
|
||||
uint8_t lo_pct, uint8_t hi_pct, uint8_t *out_pct)
|
||||
{
|
||||
int32_t num;
|
||||
int32_t den;
|
||||
int32_t pct;
|
||||
|
||||
if (out_pct == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (hi_mv <= lo_mv)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (mv <= lo_mv)
|
||||
{
|
||||
*out_pct = lo_pct;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mv >= hi_mv)
|
||||
{
|
||||
*out_pct = hi_pct;
|
||||
return 0;
|
||||
}
|
||||
|
||||
num = (mv - lo_mv) * (int32_t)(hi_pct - lo_pct);
|
||||
den = hi_mv - lo_mv;
|
||||
pct = (int32_t)lo_pct + (num / den);
|
||||
|
||||
if (pct < 0)
|
||||
{
|
||||
pct = 0;
|
||||
}
|
||||
else if (pct > 100)
|
||||
{
|
||||
pct = 100;
|
||||
}
|
||||
|
||||
*out_pct = (uint8_t)pct;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int batt_mgmt_get_display_voltage_mv(int32_t *vddh_mv)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (vddh_mv == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!batt_mgmt_ready)
|
||||
{
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
if (batt_mgmt_last_vddh_mv <= 0)
|
||||
{
|
||||
rc = batt_mgmt_measure_vddh_mv(BATT_MGMT_OVERSAMPLING_16X, &batt_mgmt_last_vddh_mv);
|
||||
if (rc < 0)
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
*vddh_mv = batt_mgmt_last_vddh_mv;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int batt_mgmt_get_battery_level(uint8_t *level)
|
||||
{
|
||||
int32_t mv;
|
||||
int rc;
|
||||
|
||||
if (level == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = batt_mgmt_get_display_voltage_mv(&mv);
|
||||
if (rc < 0)
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (mv >= CONFIG_BATT_MGMT_FULL_THRESHOLD)
|
||||
{
|
||||
*level = 4U;
|
||||
}
|
||||
else if (mv >= CONFIG_BATT_MGMT_80P_THRESHOLD)
|
||||
{
|
||||
*level = 3U;
|
||||
}
|
||||
else if (mv >= CONFIG_BATT_MGMT_50P_THRESHOLD)
|
||||
{
|
||||
*level = 2U;
|
||||
}
|
||||
else if (mv >= CONFIG_BATT_MGMT_20P_THRESHOLD)
|
||||
{
|
||||
*level = 1U;
|
||||
}
|
||||
else
|
||||
{
|
||||
*level = 0U;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int batt_mgmt_get_battery_percent(uint8_t *percent)
|
||||
{
|
||||
int32_t mv;
|
||||
int rc;
|
||||
|
||||
if (percent == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = batt_mgmt_get_display_voltage_mv(&mv);
|
||||
if (rc < 0)
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (mv <= CONFIG_BATT_MGMT_EMPTY_THRESHOLD)
|
||||
{
|
||||
*percent = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mv >= CONFIG_BATT_MGMT_FULL_THRESHOLD)
|
||||
{
|
||||
*percent = 100;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mv < CONFIG_BATT_MGMT_20P_THRESHOLD)
|
||||
{
|
||||
return batt_mgmt_linear_percent(mv,
|
||||
CONFIG_BATT_MGMT_EMPTY_THRESHOLD,
|
||||
CONFIG_BATT_MGMT_20P_THRESHOLD,
|
||||
0, 20, percent);
|
||||
}
|
||||
|
||||
if (mv < CONFIG_BATT_MGMT_50P_THRESHOLD)
|
||||
{
|
||||
return batt_mgmt_linear_percent(mv,
|
||||
CONFIG_BATT_MGMT_20P_THRESHOLD,
|
||||
CONFIG_BATT_MGMT_50P_THRESHOLD,
|
||||
20, 50, percent);
|
||||
}
|
||||
|
||||
if (mv < CONFIG_BATT_MGMT_80P_THRESHOLD)
|
||||
{
|
||||
return batt_mgmt_linear_percent(mv,
|
||||
CONFIG_BATT_MGMT_50P_THRESHOLD,
|
||||
CONFIG_BATT_MGMT_80P_THRESHOLD,
|
||||
50, 80, percent);
|
||||
}
|
||||
|
||||
return batt_mgmt_linear_percent(mv,
|
||||
CONFIG_BATT_MGMT_80P_THRESHOLD,
|
||||
CONFIG_BATT_MGMT_FULL_THRESHOLD,
|
||||
80, 100, percent);
|
||||
}
|
||||
|
||||
static void batt_mgmt_chg_interrupt_handler(const struct device *dev,
|
||||
struct gpio_callback *cb,
|
||||
uint32_t pins)
|
||||
{
|
||||
int64_t now_ms = k_uptime_get();
|
||||
int64_t delta_ms = now_ms - batt_mgmt_last_chg_change_ms;
|
||||
ARG_UNUSED(dev);
|
||||
ARG_UNUSED(cb);
|
||||
ARG_UNUSED(pins);
|
||||
|
||||
#ifdef CONFIG_BATT_MGMT_BLINK_INTERVAL_LOGGING
|
||||
int64_t delta_ms = now_ms - batt_mgmt_last_chg_change_ms;
|
||||
LOG_DBG("CHG interrupt: delta=%lld ms", delta_ms);
|
||||
#endif
|
||||
|
||||
@@ -227,6 +464,37 @@ batt_mgmt_state_t batt_mgmt_get_battery_state(void)
|
||||
return BATT_STATE_DISCHARGING;
|
||||
}
|
||||
|
||||
int batt_mgmt_get_info(batt_mgmt_info_t *info)
|
||||
{
|
||||
int rc;
|
||||
|
||||
if (info == NULL)
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = batt_mgmt_get_display_voltage_mv(&info->voltage_mv);
|
||||
if (rc < 0)
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = batt_mgmt_get_battery_level(&info->level);
|
||||
if (rc < 0)
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = batt_mgmt_get_battery_percent(&info->percent);
|
||||
if (rc < 0)
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
info->state = batt_mgmt_get_battery_state();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void batt_mgmt_monitor_thread(void *unused1, void *unused2, void *unused3)
|
||||
{
|
||||
(void)unused1;
|
||||
@@ -288,8 +556,17 @@ static int batt_mgmt_init(void)
|
||||
return rc;
|
||||
}
|
||||
|
||||
k_sem_init(&batt_mgmt_saadc_calib_sem, 0, 1);
|
||||
batt_mgmt_last_offset_calib_ms = 0;
|
||||
|
||||
batt_mgmt_ready = true;
|
||||
|
||||
rc = batt_mgmt_calibrate_offset_if_needed(true);
|
||||
if (rc < 0)
|
||||
{
|
||||
LOG_WRN("Initial ADC offset calibration failed: %d", rc);
|
||||
}
|
||||
|
||||
rc = batt_mgmt_measure_vddh_mv(BATT_MGMT_OVERSAMPLING_16X, &boot_vddh_mv);
|
||||
if (rc < 0)
|
||||
{
|
||||
@@ -297,6 +574,7 @@ static int batt_mgmt_init(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
batt_mgmt_last_vddh_mv = boot_vddh_mv;
|
||||
LOG_DBG("Initial battery VDDH: %d mV", boot_vddh_mv);
|
||||
|
||||
k_thread_create(&batt_monitor_thread_data,
|
||||
|
||||
Reference in New Issue
Block a user