From 87cba0b4195d18bd29c15ffff9b3908b12169826 Mon Sep 17 00:00:00 2001 From: Eduard Iten Date: Tue, 26 May 2026 14:23:19 +0200 Subject: [PATCH] Sync --- firmware/VERSION | 2 +- firmware/build_output.txt | 24 +- firmware/debug.conf | 1 + firmware/libs/audio/src/audio.c | 9 + firmware/libs/batt_mgmt/Kconfig | 33 +++ firmware/libs/batt_mgmt/include/batt_mgmt.h | 57 +++- firmware/libs/batt_mgmt/src/batt_mgmt.c | 280 +++++++++++++++++- firmware/libs/ble_mgmt/src/ble_mgmt.c | 5 + firmware/libs/event_mgmt/include/event_mgmt.h | 2 + firmware/src/main.c | 23 +- 10 files changed, 421 insertions(+), 15 deletions(-) diff --git a/firmware/VERSION b/firmware/VERSION index b0e4f44..3635a52 100644 --- a/firmware/VERSION +++ b/firmware/VERSION @@ -1,6 +1,6 @@ VERSION_MAJOR = 0 VERSION_MINOR = 0 -PATCHLEVEL = 13 +PATCHLEVEL = 77 VERSION_TWEAK = 0 #if (IS_ENABLED(CONFIG_LOG)) EXTRAVERSION = debug diff --git a/firmware/build_output.txt b/firmware/build_output.txt index b73456d..108203c 100644 --- a/firmware/build_output.txt +++ b/firmware/build_output.txt @@ -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.) -FATAL ERROR: refusing to proceed without --force due to above error +-- west build: making build dir /Users/edi/nrf_playground/buzzer_2/firmware/build pristine +WARNING: This looks like a fresh build and BOARD is unknown; so it probably won't work. To fix, use --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 diff --git a/firmware/debug.conf b/firmware/debug.conf index dcdae57..27b5647 100644 --- a/firmware/debug.conf +++ b/firmware/debug.conf @@ -10,6 +10,7 @@ CONFIG_THREAD_STACK_INFO=y ### Lib logging levels CONFIG_BATT_MGMT_LOG_LEVEL_DBG=y +# CONFIG_BATT_MGMT_BLINK_INTERVAL_LOGGING=y CONFIG_USB_MGMT_LOG_LEVEL_DBG=y ### Bluetooth diff --git a/firmware/libs/audio/src/audio.c b/firmware/libs/audio/src/audio.c index 10652da..4a42260 100644 --- a/firmware/libs/audio/src/audio.c +++ b/firmware/libs/audio/src/audio.c @@ -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); + 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) { LOG_INF("Audio state %s -> %s (%s)", diff --git a/firmware/libs/batt_mgmt/Kconfig b/firmware/libs/batt_mgmt/Kconfig index e607b30..2fa8de8 100644 --- a/firmware/libs/batt_mgmt/Kconfig +++ b/firmware/libs/batt_mgmt/Kconfig @@ -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 diff --git a/firmware/libs/batt_mgmt/include/batt_mgmt.h b/firmware/libs/batt_mgmt/include/batt_mgmt.h index 17e8081..6f70c1b 100644 --- a/firmware/libs/batt_mgmt/include/batt_mgmt.h +++ b/firmware/libs/batt_mgmt/include/batt_mgmt.h @@ -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 diff --git a/firmware/libs/batt_mgmt/src/batt_mgmt.c b/firmware/libs/batt_mgmt/src/batt_mgmt.c index 6166ffb..24c773d 100644 --- a/firmware/libs/batt_mgmt/src/batt_mgmt.c +++ b/firmware/libs/batt_mgmt/src/batt_mgmt.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -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, diff --git a/firmware/libs/ble_mgmt/src/ble_mgmt.c b/firmware/libs/ble_mgmt/src/ble_mgmt.c index 8c06d15..f882b4c 100644 --- a/firmware/libs/ble_mgmt/src/ble_mgmt.c +++ b/firmware/libs/ble_mgmt/src/ble_mgmt.c @@ -10,6 +10,7 @@ #include #include "ble_mgmt.h" +#include "event_mgmt.h" 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) { (void)start_advertising(); } + + event_mgmt_set_event(EVENT_MGMT_BLE_CONNECTED); } 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) { (void)start_advertising(); } + + event_mgmt_set_event(EVENT_MGMT_BLE_DISCONNECTED); } BT_CONN_CB_DEFINE(conn_callbacks) = { diff --git a/firmware/libs/event_mgmt/include/event_mgmt.h b/firmware/libs/event_mgmt/include/event_mgmt.h index bc18024..18c1e34 100644 --- a/firmware/libs/event_mgmt/include/event_mgmt.h +++ b/firmware/libs/event_mgmt/include/event_mgmt.h @@ -7,6 +7,8 @@ #define EVENT_MGMT_AUDIO_READY BIT(1) #define EVENT_MGMT_BLE_CONNECTED BIT(2) #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; diff --git a/firmware/src/main.c b/firmware/src/main.c index 8ce52f4..3453b30 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -78,21 +78,24 @@ int main(void) LOG_WRN("BLE not enabled"); #endif - +#if IS_ENABLED(CONFIG_LOG) #if IS_ENABLED(CONFIG_BATT_MGMT) - k_sleep(K_MSEC(100)); - int32_t batt_mv; - int batt_rc = batt_mgmt_measure_vddh_mv(BATT_MGMT_OVERSAMPLING_16X, &batt_mv); + k_sleep(K_SECONDS(1)); + batt_mgmt_info_t batt_info; + int batt_rc = batt_mgmt_get_info(&batt_info); 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 { - 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 (;;) { int32_t rem_ms = k_sleep(K_FOREVER); LOG_WRN("main woke unexpectedly (remaining=%d ms)", rem_ms);