This commit is contained in:
2026-05-26 14:23:19 +02:00
parent 52bab32309
commit 87cba0b419
10 changed files with 421 additions and 15 deletions

View File

@@ -1,6 +1,6 @@
VERSION_MAJOR = 0 VERSION_MAJOR = 0
VERSION_MINOR = 0 VERSION_MINOR = 0
PATCHLEVEL = 13 PATCHLEVEL = 77
VERSION_TWEAK = 0 VERSION_TWEAK = 0
#if (IS_ENABLED(CONFIG_LOG)) #if (IS_ENABLED(CONFIG_LOG))
EXTRAVERSION = debug EXTRAVERSION = debug

View File

@@ -1,2 +1,22 @@
ERROR: Build directory /Users/edi/nrf_playground/buzzer_2/firmware/build targets board buzzy/nrf52840, but board {self.args.board} was specified. (Clean the directory, use --pristine, or use --build-dir to specify a different one.) -- west build: making build dir /Users/edi/nrf_playground/buzzer_2/firmware/build pristine
FATAL ERROR: refusing to proceed without --force due to above error WARNING: This looks like a fresh build and BOARD is unknown; so it probably won't work. To fix, use --board=<your-board>.
Note: to silence the above message, run 'west config build.board_warn false'
-- west build: generating a build system
Loading Zephyr module(s) (Zephyr base): sysbuild_default
-- Found Python3: /opt/nordic/ncs/toolchains/322ac893fe/opt/python@3.12/bin/python3.12 (found suitable version "3.12.4", minimum required is "3.10") found components: Interpreter
-- Cache files will be written to: /Users/edi/Library/Caches/zephyr
-- Found west (found suitable version "1.4.0", minimum required is "0.14.0")
CMake Error at /opt/nordic/ncs/v3.2.1/zephyr/cmake/modules/extensions.cmake:3518 (message):
BOARD is not being defined on the CMake command-line, in the environment or
by the app.
Call Stack (most recent call first):
/opt/nordic/ncs/v3.2.1/zephyr/cmake/modules/boards.cmake:61 (zephyr_check_cache)
cmake/modules/sysbuild_default.cmake:15 (include)
/opt/nordic/ncs/v3.2.1/zephyr/share/zephyr-package/cmake/ZephyrConfig.cmake:75 (include)
/opt/nordic/ncs/v3.2.1/zephyr/share/zephyr-package/cmake/ZephyrConfig.cmake:92 (include_boilerplate)
/opt/nordic/ncs/v3.2.1/zephyr/share/sysbuild-package/cmake/SysbuildConfig.cmake:8 (include)
template/CMakeLists.txt:10 (find_package)
-- Configuring incomplete, errors occurred!
FATAL ERROR: command exited with status 1: /opt/homebrew/bin/cmake -DWEST_PYTHON=/opt/nordic/ncs/toolchains/322ac893fe/opt/python@3.12/bin/python3.12 -B/Users/edi/nrf_playground/buzzer_2/firmware/build -GNinja -S/opt/nordic/ncs/v3.2.1/zephyr/share/sysbuild -DAPP_DIR:PATH=/Users/edi/nrf_playground/buzzer_2/firmware

View File

@@ -10,6 +10,7 @@ CONFIG_THREAD_STACK_INFO=y
### Lib logging levels ### Lib logging levels
CONFIG_BATT_MGMT_LOG_LEVEL_DBG=y CONFIG_BATT_MGMT_LOG_LEVEL_DBG=y
# CONFIG_BATT_MGMT_BLINK_INTERVAL_LOGGING=y
CONFIG_USB_MGMT_LOG_LEVEL_DBG=y CONFIG_USB_MGMT_LOG_LEVEL_DBG=y
### Bluetooth ### Bluetooth

View File

@@ -89,6 +89,15 @@ static void audio_set_state(enum audio_thread_state_t new_state, const char *rea
{ {
enum audio_thread_state_t old_state = atomic_get(&thread_state); enum audio_thread_state_t old_state = atomic_get(&thread_state);
if ((old_state == AUDIO_IDLE) && (new_state != AUDIO_IDLE))
{
event_mgmt_set_event(EVENT_MGMT_AUDIO_ACTIVE);
}
else if ((old_state != AUDIO_IDLE) && (new_state == AUDIO_IDLE))
{
event_mgmt_set_event(EVENT_MGMT_AUDIO_IDLE);
}
if (old_state != new_state) if (old_state != new_state)
{ {
LOG_INF("Audio state %s -> %s (%s)", LOG_INF("Audio state %s -> %s (%s)",

View File

@@ -2,12 +2,27 @@ menuconfig BATT_MGMT
bool "Battery Management" bool "Battery Management"
default y default y
select ADC select ADC
select EVENT_MGMT
select GPIO select GPIO
select USB_MGMT select USB_MGMT
select NRFX_SAADC
help help
Library for initializing and managing the battery subsystem. Library for initializing and managing the battery subsystem.
if BATT_MGMT 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 config BATT_MGMT_FULL_THRESHOLD
int "Battery Full Voltage (mV)" int "Battery Full Voltage (mV)"
default 3980 default 3980
@@ -39,6 +54,24 @@ if BATT_MGMT
Time window to detect charger pin blinking (state changes). If the pin state Time window to detect charger pin blinking (state changes). If the pin state
changes within this window, it's considered blinking (error 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 config BATT_MGMT_MONITOR_THREAD_STACK_SIZE
int "Battery monitor thread stack size" int "Battery monitor thread stack size"
default 1024 default 1024

View File

@@ -15,6 +15,13 @@ typedef enum {
BATT_STATE_UNKNOWN, BATT_STATE_UNKNOWN,
} batt_mgmt_state_t; } 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. * @brief Measure battery VDDH voltage.
* *
@@ -24,13 +31,53 @@ typedef enum {
*/ */
int batt_mgmt_measure_vddh_mv(uint8_t oversampling, int32_t *vddh_mv); 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). * @brief Get raw charger status (GPIO level).
* *
* Returns the current logic level of the charger status pin (typically from ETA6003). * 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. * 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); 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); 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 #endif

View File

@@ -4,6 +4,7 @@
#include <zephyr/init.h> #include <zephyr/init.h>
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
#include <nrfx_saadc.h>
#include <errno.h> #include <errno.h>
#include <stdbool.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 bool batt_mgmt_ready;
static int64_t batt_mgmt_last_chg_change_ms; 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 bool batt_mgmt_chg_interrupt_enabled;
static K_THREAD_STACK_DEFINE(batt_monitor_stack, CONFIG_BATT_MGMT_MONITOR_THREAD_STACK_SIZE); 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_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) 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; 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); rc = batt_mgmt_to_adc_oversampling(oversampling, &adc_oversampling);
if (rc < 0) 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; *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); LOG_DBG("Battery VDDH: %d mV (raw=%d, os=%ux)", *vddh_mv, raw_sample, oversampling);
return 0; 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, static void batt_mgmt_chg_interrupt_handler(const struct device *dev,
struct gpio_callback *cb, struct gpio_callback *cb,
uint32_t pins) uint32_t pins)
{ {
int64_t now_ms = k_uptime_get(); 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 #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); LOG_DBG("CHG interrupt: delta=%lld ms", delta_ms);
#endif #endif
@@ -227,6 +464,37 @@ batt_mgmt_state_t batt_mgmt_get_battery_state(void)
return BATT_STATE_DISCHARGING; 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) static void batt_mgmt_monitor_thread(void *unused1, void *unused2, void *unused3)
{ {
(void)unused1; (void)unused1;
@@ -288,8 +556,17 @@ static int batt_mgmt_init(void)
return rc; return rc;
} }
k_sem_init(&batt_mgmt_saadc_calib_sem, 0, 1);
batt_mgmt_last_offset_calib_ms = 0;
batt_mgmt_ready = true; 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); rc = batt_mgmt_measure_vddh_mv(BATT_MGMT_OVERSAMPLING_16X, &boot_vddh_mv);
if (rc < 0) if (rc < 0)
{ {
@@ -297,6 +574,7 @@ static int batt_mgmt_init(void)
return 0; return 0;
} }
batt_mgmt_last_vddh_mv = boot_vddh_mv;
LOG_DBG("Initial battery VDDH: %d mV", boot_vddh_mv); LOG_DBG("Initial battery VDDH: %d mV", boot_vddh_mv);
k_thread_create(&batt_monitor_thread_data, k_thread_create(&batt_monitor_thread_data,

View File

@@ -10,6 +10,7 @@
#include <zephyr/logging/log.h> #include <zephyr/logging/log.h>
#include "ble_mgmt.h" #include "ble_mgmt.h"
#include "event_mgmt.h"
LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL); LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL);
@@ -239,6 +240,8 @@ static void connected(struct bt_conn *conn, uint8_t err)
if (peer_count() < CONFIG_BT_MAX_CONN) { if (peer_count() < CONFIG_BT_MAX_CONN) {
(void)start_advertising(); (void)start_advertising();
} }
event_mgmt_set_event(EVENT_MGMT_BLE_CONNECTED);
} }
static void disconnected(struct bt_conn *conn, uint8_t reason) static void disconnected(struct bt_conn *conn, uint8_t reason)
@@ -252,6 +255,8 @@ static void disconnected(struct bt_conn *conn, uint8_t reason)
if (peer_count() < CONFIG_BT_MAX_CONN) { if (peer_count() < CONFIG_BT_MAX_CONN) {
(void)start_advertising(); (void)start_advertising();
} }
event_mgmt_set_event(EVENT_MGMT_BLE_DISCONNECTED);
} }
BT_CONN_CB_DEFINE(conn_callbacks) = { BT_CONN_CB_DEFINE(conn_callbacks) = {

View File

@@ -7,6 +7,8 @@
#define EVENT_MGMT_AUDIO_READY BIT(1) #define EVENT_MGMT_AUDIO_READY BIT(1)
#define EVENT_MGMT_BLE_CONNECTED BIT(2) #define EVENT_MGMT_BLE_CONNECTED BIT(2)
#define EVENT_MGMT_BLE_DISCONNECTED BIT(3) #define EVENT_MGMT_BLE_DISCONNECTED BIT(3)
#define EVENT_MGMT_AUDIO_ACTIVE BIT(4)
#define EVENT_MGMT_AUDIO_IDLE BIT(5)
extern struct k_event event_mgmt_events; extern struct k_event event_mgmt_events;

View File

@@ -78,21 +78,24 @@ int main(void)
LOG_WRN("BLE not enabled"); LOG_WRN("BLE not enabled");
#endif #endif
#if IS_ENABLED(CONFIG_LOG)
#if IS_ENABLED(CONFIG_BATT_MGMT) #if IS_ENABLED(CONFIG_BATT_MGMT)
k_sleep(K_MSEC(100)); k_sleep(K_SECONDS(1));
int32_t batt_mv; batt_mgmt_info_t batt_info;
int batt_rc = batt_mgmt_measure_vddh_mv(BATT_MGMT_OVERSAMPLING_16X, &batt_mv); int batt_rc = batt_mgmt_get_info(&batt_info);
if (batt_rc == 0) { if (batt_rc == 0) {
LOG_INF("Battery after boot: %d mV", batt_mv); LOG_INF("Battery after 1s: %d mV, %u%%, level=%u, state=%s (%d)",
batt_info.voltage_mv,
batt_info.percent,
batt_info.level,
battery_state_to_str(batt_info.state),
batt_info.state);
} else { } else {
LOG_WRN("Battery measurement after boot failed: %d", batt_rc); LOG_WRN("Battery info read failed: %d", batt_rc);
} }
#endif // CONFIG_BATT_MGMT
#endif // CONFIG_LOG
k_sleep(K_SECONDS(1));
batt_mgmt_state_t batt_state = batt_mgmt_get_battery_state();
LOG_INF("Battery state after 1s: %s (%d)", battery_state_to_str(batt_state), batt_state);
#endif
for (;;) { for (;;) {
int32_t rem_ms = k_sleep(K_FOREVER); int32_t rem_ms = k_sleep(K_FOREVER);
LOG_WRN("main woke unexpectedly (remaining=%d ms)", rem_ms); LOG_WRN("main woke unexpectedly (remaining=%d ms)", rem_ms);