Battery measurement, basic version

This commit is contained in:
2026-05-19 17:08:00 +02:00
parent dd51f45084
commit 52bab32309
19 changed files with 611 additions and 17 deletions

View File

@@ -4,4 +4,6 @@ add_subdirectory(fs_mgmt)
add_subdirectory(ble_mgmt)
add_subdirectory(buzz_proto)
add_subdirectory(audio)
add_subdirectory(event_mgmt)
add_subdirectory(event_mgmt)
add_subdirectory(batt_mgmt)
add_subdirectory(usb_mgmt)

View File

@@ -5,3 +5,5 @@ rsource "ble_mgmt/Kconfig"
rsource "buzz_proto/Kconfig"
rsource "audio/Kconfig"
rsource "event_mgmt/Kconfig"
rsource "batt_mgmt/Kconfig"
rsource "usb_mgmt/Kconfig"

View File

@@ -0,0 +1,5 @@
if(CONFIG_BATT_MGMT)
zephyr_library()
zephyr_library_sources(src/batt_mgmt.c)
zephyr_include_directories(include)
endif()

View File

@@ -0,0 +1,63 @@
menuconfig BATT_MGMT
bool "Battery Management"
default y
select ADC
select GPIO
select USB_MGMT
help
Library for initializing and managing the battery subsystem.
if BATT_MGMT
config BATT_MGMT_FULL_THRESHOLD
int "Battery Full Voltage (mV)"
default 3980
help
Set the voltage level (in millivolts) that represents a full battery. Default is 3980 mV.
config BATT_MGMT_80P_THRESHOLD
int "Battery 80% Voltage (mV)"
default 3820
help
Set the voltage level (in millivolts) that represents 80% battery. Default is 3820 mV.
config BATT_MGMT_50P_THRESHOLD
int "Battery 50% Voltage (mV)"
default 3720
help
Set the voltage level (in millivolts) that represents 50% battery. Default is 3720 mV.
config BATT_MGMT_20P_THRESHOLD
int "Battery 20% Voltage (mV)"
default 3620
help
Set the voltage level (in millivolts) that represents 20% battery. Default is 3620 mV.
config BATT_MGMT_CHG_BLINKING_WINDOW_MS
int "Charger blinking detection window (ms)"
default 700
help
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_MONITOR_THREAD_STACK_SIZE
int "Battery monitor thread stack size"
default 1024
help
Stack size for the USB-triggered charger status monitor thread.
config BATT_MGMT_MONITOR_THREAD_PRIORITY
int "Battery monitor thread priority"
default 7
help
Cooperative priority for the monitor thread (lower number = higher priority).
config BATT_MGMT_BLINK_INTERVAL_LOGGING
bool "Enable logging of charger pin blinking intervals"
default n
help
If enabled, logs the time intervals between charger pin state changes, which can help diagnose charger connection issues.
module = BATT_MGMT
module-str = batt_mgmt
source "subsys/logging/Kconfig.template.log_config"
endif # BATT_MGMT

View File

@@ -0,0 +1,52 @@
#ifndef BATT_MGMT_H
#define BATT_MGMT_H
#include <zephyr/types.h>
#include <stdbool.h>
#define BATT_MGMT_OVERSAMPLING_8X 8U
#define BATT_MGMT_OVERSAMPLING_16X 16U
typedef enum {
BATT_STATE_DISCHARGING = 0,
BATT_STATE_FULL,
BATT_STATE_CHARGING,
BATT_STATE_ERROR,
BATT_STATE_UNKNOWN,
} batt_mgmt_state_t;
/**
* @brief Measure battery VDDH voltage.
*
* @param oversampling Oversampling factor (BATT_MGMT_OVERSAMPLING_8X or BATT_MGMT_OVERSAMPLING_16X).
* @param vddh_mv Pointer to store the result in millivolts.
* @return 0 on success, negative errno on failure.
*/
int batt_mgmt_measure_vddh_mv(uint8_t oversampling, int32_t *vddh_mv);
/**
* @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.
*/
bool batt_mgmt_get_charger_status(void);
/**
* @brief Get processed battery state with blinking detection.
*
* Returns a derived state based on:
* - USB VBUS presence (from usb_mgmt)
* - Charger status GPIO level (from batt_mgmt_get_charger_status)
* - Blinking detection (state changes within CONFIG_BATT_MGMT_CHG_BLINKING_WINDOW_MS)
*
* Note: This differs from batt_mgmt_get_charger_status() which returns the **raw** GPIO level.
*
* @return Battery state enum (DISCHARGING, FULL, CHARGING, or ERROR if blinking detected).
*/
batt_mgmt_state_t batt_mgmt_get_battery_state(void);
#endif

View File

@@ -0,0 +1,313 @@
#include <zephyr/device.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/init.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <errno.h>
#include <stdbool.h>
#include "batt_mgmt.h"
#include "usb_mgmt.h"
LOG_MODULE_REGISTER(batt_mgmt, CONFIG_BATT_MGMT_LOG_LEVEL);
#define BATT_VDDH_DIVIDER_FACTOR 5
#define BATT_VDDH_ADC_NODE DT_PATH(zephyr_user)
static const struct adc_dt_spec batt_vddh_adc = ADC_DT_SPEC_GET_BY_NAME(BATT_VDDH_ADC_NODE, vddh);
#define BATT_CHG_STATUS_NODE DT_ALIAS(chg_status)
static const struct gpio_dt_spec batt_chg_status = GPIO_DT_SPEC_GET(BATT_CHG_STATUS_NODE, gpios);
static bool batt_mgmt_ready;
static int64_t batt_mgmt_last_chg_change_ms;
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 int batt_mgmt_to_adc_oversampling(uint8_t oversampling, uint8_t *adc_oversampling)
{
if (adc_oversampling == NULL)
{
return -EINVAL;
}
switch (oversampling)
{
case BATT_MGMT_OVERSAMPLING_8X:
*adc_oversampling = 3U;
return 0;
case BATT_MGMT_OVERSAMPLING_16X:
*adc_oversampling = 4U;
return 0;
default:
return -EINVAL;
}
}
int batt_mgmt_measure_vddh_mv(uint8_t oversampling, int32_t *vddh_mv)
{
int rc;
uint8_t adc_oversampling;
int16_t raw_sample;
int32_t input_mv;
struct adc_sequence sequence = {
.buffer = &raw_sample,
.buffer_size = sizeof(raw_sample),
};
if (vddh_mv == NULL)
{
return -EINVAL;
}
if (!batt_mgmt_ready)
{
return -EAGAIN;
}
rc = batt_mgmt_to_adc_oversampling(oversampling, &adc_oversampling);
if (rc < 0)
{
return rc;
}
rc = adc_sequence_init_dt(&batt_vddh_adc, &sequence);
if (rc < 0)
{
return rc;
}
sequence.oversampling = adc_oversampling;
rc = adc_read_dt(&batt_vddh_adc, &sequence);
if (rc < 0)
{
return rc;
}
input_mv = raw_sample;
rc = adc_raw_to_millivolts_dt(&batt_vddh_adc, &input_mv);
if (rc < 0)
{
return rc;
}
*vddh_mv = input_mv * BATT_VDDH_DIVIDER_FACTOR;
LOG_DBG("Battery VDDH: %d mV (raw=%d, os=%ux)", *vddh_mv, raw_sample, oversampling);
return 0;
}
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;
#ifdef CONFIG_BATT_MGMT_BLINK_INTERVAL_LOGGING
LOG_DBG("CHG interrupt: delta=%lld ms", delta_ms);
#endif
batt_mgmt_last_chg_change_ms = now_ms;
}
static int batt_mgmt_enable_chg_interrupt(void)
{
int rc;
static struct gpio_callback batt_chg_cb;
if (batt_mgmt_chg_interrupt_enabled)
{
return 0;
}
if (!gpio_is_ready_dt(&batt_chg_status))
{
LOG_ERR("CHG GPIO device not ready");
return -ENODEV;
}
rc = gpio_pin_configure_dt(&batt_chg_status, GPIO_INPUT);
if (rc < 0)
{
LOG_ERR("Failed to configure CHG GPIO: %d", rc);
return rc;
}
gpio_init_callback(&batt_chg_cb, batt_mgmt_chg_interrupt_handler, BIT(batt_chg_status.pin));
rc = gpio_add_callback(batt_chg_status.port, &batt_chg_cb);
if (rc < 0)
{
LOG_ERR("Failed to add CHG callback: %d", rc);
return rc;
}
rc = gpio_pin_interrupt_configure_dt(&batt_chg_status, GPIO_INT_EDGE_BOTH);
if (rc < 0)
{
LOG_ERR("Failed to configure CHG interrupt: %d", rc);
return rc;
}
batt_mgmt_chg_interrupt_enabled = true;
batt_mgmt_last_chg_change_ms = k_uptime_get();
LOG_INF("CHG interrupt enabled");
return 0;
}
static int batt_mgmt_disable_chg_interrupt(void)
{
int rc;
if (!batt_mgmt_chg_interrupt_enabled)
{
return 0;
}
rc = gpio_pin_interrupt_configure_dt(&batt_chg_status, GPIO_INT_DISABLE);
if (rc < 0)
{
LOG_WRN("Failed to disable CHG interrupt: %d", rc);
return rc;
}
rc = gpio_pin_configure(batt_chg_status.port, batt_chg_status.pin, GPIO_DISCONNECTED);
if (rc < 0)
{
LOG_WRN("Failed to disconnect CHG GPIO: %d", rc);
return rc;
}
batt_mgmt_chg_interrupt_enabled = false;
LOG_DBG("CHG interrupt disabled");
return 0;
}
bool batt_mgmt_get_charger_status(void)
{
if (!batt_mgmt_chg_interrupt_enabled)
{
return false;
}
return gpio_pin_get_dt(&batt_chg_status);
}
batt_mgmt_state_t batt_mgmt_get_battery_state(void)
{
int64_t now_ms = k_uptime_get();
int64_t delta_ms = now_ms - batt_mgmt_last_chg_change_ms;
bool chg_high = batt_mgmt_get_charger_status();
bool usb_present = usb_mgmt_is_vbus_present();
/* Blinking detection: state change within window */
if (delta_ms < CONFIG_BATT_MGMT_CHG_BLINKING_WINDOW_MS)
{
LOG_DBG("Blinking detected (delta=%lld ms)", delta_ms);
return BATT_STATE_ERROR;
}
/* Stable states */
if (chg_high)
{
return BATT_STATE_CHARGING;
}
if (usb_present)
{
return BATT_STATE_FULL;
}
return BATT_STATE_DISCHARGING;
}
static void batt_mgmt_monitor_thread(void *unused1, void *unused2, void *unused3)
{
(void)unused1;
(void)unused2;
(void)unused3;
if (!usb_mgmt_wait_ready(K_SECONDS(2)))
{
LOG_WRN("usb_mgmt not ready after timeout; using current VBUS state");
}
/* Sync initial state in case VBUS was already present before this thread started. */
bool vbus_present = usb_mgmt_is_vbus_present();
if (vbus_present)
{
(void)batt_mgmt_enable_chg_interrupt();
}
else
{
(void)batt_mgmt_disable_chg_interrupt();
}
while (1)
{
uint32_t events = k_event_wait(&usb_mgmt_events,
USB_MGMT_VBUS_CONNECTED | USB_MGMT_VBUS_DISCONNECTED,
false, K_FOREVER);
if (events & USB_MGMT_VBUS_CONNECTED)
{
(void)batt_mgmt_enable_chg_interrupt();
}
if (events & USB_MGMT_VBUS_DISCONNECTED)
{
(void)batt_mgmt_disable_chg_interrupt();
}
k_event_clear(&usb_mgmt_events, USB_MGMT_VBUS_CONNECTED | USB_MGMT_VBUS_DISCONNECTED);
}
}
static int batt_mgmt_init(void)
{
int32_t boot_vddh_mv;
int rc;
if (!adc_is_ready_dt(&batt_vddh_adc))
{
LOG_ERR("VDDH ADC device not ready");
return -ENODEV;
}
rc = adc_channel_setup_dt(&batt_vddh_adc);
if (rc < 0)
{
LOG_ERR("VDDH ADC channel setup failed: %d", rc);
return rc;
}
batt_mgmt_ready = true;
rc = batt_mgmt_measure_vddh_mv(BATT_MGMT_OVERSAMPLING_16X, &boot_vddh_mv);
if (rc < 0)
{
LOG_WRN("Initial battery measurement failed: %d", rc);
return 0;
}
LOG_DBG("Initial battery VDDH: %d mV", boot_vddh_mv);
k_thread_create(&batt_monitor_thread_data,
batt_monitor_stack,
K_THREAD_STACK_SIZEOF(batt_monitor_stack),
batt_mgmt_monitor_thread,
NULL, NULL, NULL,
K_PRIO_COOP(CONFIG_BATT_MGMT_MONITOR_THREAD_PRIORITY),
0, K_NO_WAIT);
return 0;
}
SYS_INIT(batt_mgmt_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);

View File

@@ -27,7 +27,11 @@ LOG_MODULE_REGISTER(settings_mgmt, LOG_LEVEL_INF);
/* The "Source of Truth" in RAM */
struct app_settings_t app_cfg = {
#ifdef CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME
.dev_name = CONFIG_BLE_MGMT_DEFAULT_DEVICE_NAME,
#else
.dev_name = CONFIG_BOARD, /* Default to board name if no explicit default set */
#endif
.vol = 100, /* 0-100 % */
.shuffle_mode = 0,
.ble_timeout = 0xFFFFFFFF,

View File

@@ -0,0 +1,5 @@
if(CONFIG_USB_MGMT)
zephyr_library()
zephyr_library_sources(src/usb_mgmt.c)
zephyr_include_directories(include)
endif()

View File

@@ -0,0 +1,13 @@
menuconfig USB_MGMT
bool "USB Management"
default y
select NRFX_POWER
help
Library for USB-related queries, e.g. VBUS presence detection
via the nRF52840 POWER peripheral.
if USB_MGMT
module = USB_MGMT
module-str = usb_mgmt
source "subsys/logging/Kconfig.template.log_config"
endif # USB_MGMT

View File

@@ -0,0 +1,30 @@
#ifndef USB_MGMT_H
#define USB_MGMT_H
#include <stdbool.h>
#include <zephyr/kernel.h>
#define USB_MGMT_VBUS_CONNECTED BIT(0)
#define USB_MGMT_VBUS_DISCONNECTED BIT(1)
extern struct k_event usb_mgmt_events;
/**
* @brief Wait until usb_mgmt has completed initialization.
*
* @param timeout Timeout for waiting.
*
* @retval true usb_mgmt is ready.
* @retval false timeout occurred.
*/
bool usb_mgmt_wait_ready(k_timeout_t timeout);
/**
* @brief Returns true when VBUS (USB 5 V) is present.
*
* Reads the USBREGSTATUS.VBUSDETECT bit in the nRF52840 POWER peripheral.
* No USB stack needs to be enabled.
*/
bool usb_mgmt_is_vbus_present(void);
#endif /* USB_MGMT_H */

View File

@@ -0,0 +1,65 @@
#include <hal/nrf_power.h>
#include <nrfx_power.h>
#include <errno.h>
#include <zephyr/logging/log.h>
#include "usb_mgmt.h"
LOG_MODULE_REGISTER(usb_mgmt, CONFIG_USB_MGMT_LOG_LEVEL);
K_EVENT_DEFINE(usb_mgmt_events);
K_SEM_DEFINE(usb_mgmt_ready_sem, 0, 1);
static bool usb_mgmt_last_vbus_state;
static bool usb_mgmt_ready;
static void usb_mgmt_power_handler(nrfx_power_usb_evt_t event)
{
bool is_present = (event == NRFX_POWER_USB_EVT_DETECTED);
LOG_INF("VBUS %s (event=%u)", is_present ? "connected" : "disconnected", event);
if (is_present != usb_mgmt_last_vbus_state) {
usb_mgmt_last_vbus_state = is_present;
k_event_post(&usb_mgmt_events,
is_present ? USB_MGMT_VBUS_CONNECTED : USB_MGMT_VBUS_DISCONNECTED);
}
}
static int usb_mgmt_init(void)
{
usb_mgmt_last_vbus_state = nrf_power_usbregstatus_vbusdet_get(NRF_POWER);
LOG_DBG("Boot VBUS state: %s", usb_mgmt_last_vbus_state ? "present" : "absent");
k_event_post(&usb_mgmt_events,
usb_mgmt_last_vbus_state ? USB_MGMT_VBUS_CONNECTED : USB_MGMT_VBUS_DISCONNECTED);
nrfx_power_config_t config = { 0 };
nrfx_power_usbevt_config_t usb_config = {
.handler = usb_mgmt_power_handler,
};
nrfx_power_init(&config);
nrfx_power_usbevt_init(&usb_config);
nrfx_power_usbevt_enable();
usb_mgmt_ready = true;
k_sem_give(&usb_mgmt_ready_sem);
return 0;
}
SYS_INIT(usb_mgmt_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
bool usb_mgmt_is_vbus_present(void)
{
return usb_mgmt_last_vbus_state;
}
bool usb_mgmt_wait_ready(k_timeout_t timeout)
{
if (usb_mgmt_ready) {
return true;
}
return k_sem_take(&usb_mgmt_ready_sem, timeout) == 0;
}