diff --git a/soc/nordic/nrf54l/CMakeLists.txt b/soc/nordic/nrf54l/CMakeLists.txt index b220b9d2a7a2..f2c09ba25eb2 100644 --- a/soc/nordic/nrf54l/CMakeLists.txt +++ b/soc/nordic/nrf54l/CMakeLists.txt @@ -7,6 +7,8 @@ zephyr_library_sources( ) zephyr_include_directories(.) +add_subdirectory_ifdef(CONFIG_SOC_HEALTH_MONITORING health_monitoring_nrfx) + dt_nodelabel(kmu_push_area_node NODELABEL nrf_kmu_reserved_push_area) # We need a buffer in memory in a static location which can be used by diff --git a/soc/nordic/nrf54l/Kconfig b/soc/nordic/nrf54l/Kconfig index 06955bb550f0..0ef50dc08bd3 100644 --- a/soc/nordic/nrf54l/Kconfig +++ b/soc/nordic/nrf54l/Kconfig @@ -128,4 +128,12 @@ config SOC_NRF54L05_DEVELOP_IN_NRF54L15 This symbol needs to be considered by certain system initialization functionality residing in system_nrf54l.c and SoC erratas. +config VBAT_MONITOR_ENABLE + bool "Enable VBAT monitoring" + select SOC_HEALTH_MONITORING + +config VINTERNAL_MONITOR_ENABLE + bool "Enable VInternal monitoring" + select SOC_HEALTH_MONITORING + endif # SOC_SERIES_NRF54L diff --git a/soc/nordic/nrf54l/Kconfig.defconfig b/soc/nordic/nrf54l/Kconfig.defconfig index bc7577b7c55e..3b79bd0ef477 100644 --- a/soc/nordic/nrf54l/Kconfig.defconfig +++ b/soc/nordic/nrf54l/Kconfig.defconfig @@ -6,6 +6,7 @@ if SOC_SERIES_NRF54L rsource "Kconfig.defconfig.nrf54l*" +rsource "health_monitoring_nrfx/Kconfig" if ARM diff --git a/soc/nordic/nrf54l/health_monitoring_nrfx/CMakeLists.txt b/soc/nordic/nrf54l/health_monitoring_nrfx/CMakeLists.txt new file mode 100644 index 000000000000..e585af8e237e --- /dev/null +++ b/soc/nordic/nrf54l/health_monitoring_nrfx/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (c) 2026 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +zephyr_library_sources(health_monitoring.c + battery_monitor.c + temperature_monitor.c) + +zephyr_include_directories(.) + +# TODO Currently this does not cause an error when enabled from Kconfig +if (CONFIG_ADC) + message(FATAL_ERROR + "Health Monitoring feautre can not be used in conjunction with other ADC measurements, + see sample XXXX to implement an alternative, disabling this feature may have + adverse effects on performance over temperature") +endif() diff --git a/soc/nordic/nrf54l/health_monitoring_nrfx/Kconfig b/soc/nordic/nrf54l/health_monitoring_nrfx/Kconfig new file mode 100644 index 000000000000..f1d32195a3e4 --- /dev/null +++ b/soc/nordic/nrf54l/health_monitoring_nrfx/Kconfig @@ -0,0 +1,87 @@ +# Nordic Semiconductor nRF71 MCU line + +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config VBAT_MONITOR_ENABLE + bool "Enable VBAT monitoring" + select SOC_HEALTH_MONITORING + select NRFX_SAADC + # select ADC # TODO: Only enable to verify error in CMakelists.txt + +config DIE_TEMP_MONITOR_ENABLE + bool "Enable die temperature monitoring" + select SOC_HEALTH_MONITORING + select SENSOR + select CONFIG_TEMP_NRF5 + +config VINTERNAL_MONITOR_ENABLE + bool "Enable VInternal monitoring" + select SOC_HEALTH_MONITORING + +config SOC_HEALTH_MONITORING + bool "Enable health monitoring system" + default n + select SOC_LATE_INIT_HOOK + # Debug + select SHELL + select KERNEL_SHELL + select LOG + help + Enable periodic VBAT voltage monitoring using SAADC. + When enabled, the application will periodically measure + the battery voltage and trigger a callback if it falls + below the configured threshold. + +if VBAT_MONITOR_ENABLE + +config VBAT_MONITOR_INTERVAL_MS + int "VBAT measurement interval (milliseconds)" + default 5000 + range 100 3600000 + help + Interval between VBAT measurements in milliseconds. + Minimum: 100 ms + Maximum: 3600000 ms (1 hour) + +config VBAT_MONITOR_LOWER_THRESHOLD_MV + int "VBAT low threshold (millivolts)" + default 50 + range 50 5000 + help + Battery voltage threshold in millivolts. When the measured + voltage falls below this value, the low battery callback + will be triggered. +# TODO: Set default and voltage range to expected + +endif # VBAT_MONITOR_ENABLE + +if DIE_TEMP_MONITOR_ENABLE + +config DIE_TEMP_MONITOR_INTERVAL_MS + int "Die temperature measurement interval (milliseconds)" + default 3000 + range 100 3600000 + help + Interval between die temperature measurements in milliseconds. + Minimum: 100 ms + Maximum: 3600000 ms (1 hour) + +endif # DIE_TEMP_MONITOR_ENABLE + +if SOC_HEALTH_MONITORING + +#TODO: Align this with style used elsewhere +config HEALTH_MONITOR_LOG_LEVEL + int "VBAT monitor log level" + default 3 + range 0 4 + help + Log level for VBAT monitor module: + 0 = OFF + 1 = ERROR + 2 = WARN + 3 = INFO + 4 = DEBUG + +endif # VBAT_MONITOR_ENABLE diff --git a/soc/nordic/nrf54l/health_monitoring_nrfx/battery_monitor.c b/soc/nordic/nrf54l/health_monitoring_nrfx/battery_monitor.c new file mode 100644 index 000000000000..4245772b5b43 --- /dev/null +++ b/soc/nordic/nrf54l/health_monitoring_nrfx/battery_monitor.c @@ -0,0 +1,176 @@ +#include +#include +#include +#include +#include "battery_monitor.h" + +LOG_MODULE_REGISTER(battery_monitoring, CONFIG_HEALTH_MONITOR_LOG_LEVEL); + +#define BATTERY_CHANNEL_ID 0 +#define VDD_VOLTAGE 900 +/* TOOD: Map Raw value from mv and gain / resolution */ +#define VBAT_MONITOR_LOWER_THRESHOLD_RAW CONFIG_VBAT_MONITOR_LOWER_THRESHOLD_MV + +/** @brief global variable to store battery voltage */ +static atomic_t battery_voltage_raw; + + +const nrf_saadc_channel_config_t channel_config = {.gain = NRF_SAADC_GAIN1_4, + .reference = NRF_SAADC_REFERENCE_INTERNAL, + .acq_time = NRFX_SAADC_DEFAULT_ACQTIME, + .mode = NRF_SAADC_MODE_SINGLE_ENDED, + .burst = NRF_SAADC_BURST_DISABLED}; + +/** @brief SAADC channel to measure battery level */ +const nrfx_saadc_channel_t m_multiple_channels[] = { + {/* TODO: If def around for measure battery and temp ADC + * Need to update to correct pins. + * Also need to set Channel to measure batt (instead of external pin), + * gain and divider must also be set + */ + .channel_index = BATTERY_CHANNEL_ID, + .pin_n = NRFX_ANALOG_INPUT_DISABLED, + .pin_p = NRFX_ANALOG_EXTERNAL_AIN4, + .channel_config = channel_config }, +}; +#define CHANNEL_COUNT NRFX_ARRAY_SIZE(m_multiple_channels) + +/** @brief Samples buffer defined with the size of @ref CHANNEL_COUNT + * symbol to store values from each channel ( @ref m_multiple_channels). + */ +static nrf_saadc_value_t m_samples_buffer[CHANNEL_COUNT]; + +static void battery_voltage_work_handler(struct k_work *work); + +K_WORK_DELAYABLE_DEFINE(get_battery_voltage, battery_voltage_work_handler); + +/** + * @brief Function for getting pointer to vbatt. + * + * @return Pointer to atomic variable holding battery voltage + */ +atomic_t *get_battery_voltage_pointer(void) +{ + return &battery_voltage_raw; +} + +/** + * @brief Function in the event battery voltage is below minimum threshold + * + */ +__weak void battery_below_threshold(void) +{ + LOG_WRN("IRQ LIMITL triggered on battery voltage"); + LOG_WRN("Placeholder - customer should implement their own requirements"); +} + +/** + * @brief Function for handling SAADC driver events. + * + * @param[in] p_event Pointer to an SAADC driver event. + */ +static void saadc_handler(nrfx_saadc_evt_t const *p_event) +{ +int status; + uint16_t samples_number; + /* Temp value allows observability of functionality for reading data */ + static int32_t voltage_temp; + + switch (p_event->type) { + case NRFX_SAADC_EVT_DONE: + LOG_INF("SAADC event: DONE"); + samples_number = p_event->data.done.size; + for (uint16_t i = 0; i < samples_number; i++) { + atomic_set(&battery_voltage_raw, + NRFX_SAADC_SAMPLE_GET(p_event->data.done.p_buffer, i)); + LOG_INF("[Raw %d] value == %ld", i, atomic_get(&battery_voltage_raw)); + /* TODO: Map values using resolution and gain not magic numbers */ + int battery_voltage_mv = ((VDD_VOLTAGE*4) * + atomic_get(&battery_voltage_raw)) / ((1<<12)); + + LOG_INF("[Voltage %d] value == %d mV", i, battery_voltage_mv); + } + atomic_set(&battery_voltage_raw, voltage_temp); + voltage_temp++; + k_work_schedule(&get_battery_voltage, + K_MSEC(CONFIG_VBAT_MONITOR_INTERVAL_MS)); + break; + + case NRFX_SAADC_EVT_CALIBRATEDONE: + LOG_INF("SAADC event: CALIBRATEDONE"); + status = nrfx_saadc_mode_trigger(); + NRFX_ASSERT(status == 0); + break; + + case NRFX_SAADC_EVT_LIMIT: + if (p_event->data.limit.limit_type == NRF_SAADC_LIMIT_LOW) { + if (p_event->data.limit.channel == BATTERY_CHANNEL_ID) { + battery_below_threshold(); + } + } else { + LOG_WRN("Undefined Threshold event triggered"); + } + break; + + default: + break; + } +} + +int battery_monitoring_setup(void) +{ + int err = 0; + + // TODO: This should cause error from CMakeLists.txt + IRQ_CONNECT(NRFX_IRQ_NUMBER_GET(NRF_SAADC), IRQ_PRIO_LOWEST, nrfx_saadc_irq_handler, 0, 0); + + err = nrfx_saadc_init(NRFX_SAADC_DEFAULT_CONFIG_IRQ_PRIORITY); + if (err != 0) { + LOG_ERR("SAADC init failed"); + return err; + } + + err = nrfx_saadc_channels_config(m_multiple_channels, CHANNEL_COUNT); + if (err != 0) { + LOG_ERR("SAADC channel config failed"); + return err; + } + + err = nrfx_saadc_simple_mode_set(nrfx_saadc_channels_configured_get(), + NRF_SAADC_RESOLUTION_12BIT, NRF_SAADC_OVERSAMPLE_DISABLED, + saadc_handler); + if (err != 0) { + LOG_ERR("SAADC mode setting failed"); + return err; + } + + err = nrfx_saadc_buffer_set(m_samples_buffer, CHANNEL_COUNT); + if (err != 0) { + LOG_ERR("Setting SAADC buffer failed"); + return err; + } + + err = nrfx_saadc_limits_set(BATTERY_CHANNEL_ID, VBAT_MONITOR_LOWER_THRESHOLD_RAW, + INT16_MAX); + if (err != 0) { + LOG_ERR("Setting voltage limits failed failed"); + return err; + } + + LOG_INF("SAADC Configured"); + k_work_schedule(&get_battery_voltage, K_MSEC(CONFIG_VBAT_MONITOR_INTERVAL_MS)); + return err; +} + +static void battery_voltage_work_handler(struct k_work *work) +{ + int err; + + (void)work; + + err = nrfx_saadc_offset_calibrate(saadc_handler); + if (err != 0) { + LOG_ERR("Offset calibration failed"); + } + LOG_INF("Calibration Requested"); +} diff --git a/soc/nordic/nrf54l/health_monitoring_nrfx/battery_monitor.h b/soc/nordic/nrf54l/health_monitoring_nrfx/battery_monitor.h new file mode 100644 index 000000000000..926ceeca54aa --- /dev/null +++ b/soc/nordic/nrf54l/health_monitoring_nrfx/battery_monitor.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _BATTERY_MONITOR_H_ +#define _BATTERY_MONITOR_H_ + +#include + +int battery_monitoring_setup(void); +atomic_t *get_battery_voltage_pointer(void); +void battery_below_threshold(void); + +#endif /* _BATTERY_MONITOR_H_ */ diff --git a/soc/nordic/nrf54l/health_monitoring_nrfx/health_monitoring.c b/soc/nordic/nrf54l/health_monitoring_nrfx/health_monitoring.c new file mode 100644 index 000000000000..afc5e165846b --- /dev/null +++ b/soc/nordic/nrf54l/health_monitoring_nrfx/health_monitoring.c @@ -0,0 +1,45 @@ +#include +#include +#include +#include +#include "battery_monitor.h" +#include "temperature_monitor.h" + +LOG_MODULE_REGISTER(health_monitoring, CONFIG_HEALTH_MONITOR_LOG_LEVEL); + +typedef struct { + atomic_t *g_vbatt_value; + atomic_t *g_die_temp_value; +} health_metrics_t; + +static health_metrics_t health_metrics = {}; + +int health_monitoring_setup(void) +{ + int err; + err = battery_monitoring_setup(); + + if (err != 0) { + LOG_ERR("Battery Monitoring Setup Failed"); + return err; + } + health_metrics.g_vbatt_value = get_battery_voltage_pointer(); + + err = die_temperature_monitoring_setup(); + if (err != 0) { + LOG_ERR("Die Temperature Monitoring Setup Failed"); + return err; + } + health_metrics.g_die_temp_value = get_die_temperature_pointer(); + +//Here for now to demonstrate functionality + for (;;) { + k_sleep(K_SECONDS(2)); + // uint32_t voltage = atomic_get(g_vbatt_value); + printk("Global ADC: %ld mV\n", *health_metrics.g_vbatt_value); + printk("Global die temp: %ld.%02u\n", *health_metrics.g_die_temp_value / 100, + abs(*health_metrics.g_die_temp_value) % 100); + } + + return 0; +} diff --git a/soc/nordic/nrf54l/health_monitoring_nrfx/health_monitoring.h b/soc/nordic/nrf54l/health_monitoring_nrfx/health_monitoring.h new file mode 100644 index 000000000000..ee91a2eada60 --- /dev/null +++ b/soc/nordic/nrf54l/health_monitoring_nrfx/health_monitoring.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _HEALTH_MONITORING_H_ +#define _HEALTH_MONITORING_H_ + +int health_monitoring_setup(void); + +#endif /* _HEALTH_MONITORING_H_ */ diff --git a/soc/nordic/nrf54l/health_monitoring_nrfx/temperature_monitor.c b/soc/nordic/nrf54l/health_monitoring_nrfx/temperature_monitor.c new file mode 100644 index 000000000000..32ba12a3d342 --- /dev/null +++ b/soc/nordic/nrf54l/health_monitoring_nrfx/temperature_monitor.c @@ -0,0 +1,70 @@ +// nrf/tests/benchmarks/peripheral_load/src/temp_thread.c +#include +#include +#include +#include +#include "temperature_monitor.h" + +LOG_MODULE_REGISTER(die_temp_monitoring, CONFIG_HEALTH_MONITOR_LOG_LEVEL); + +static const struct device *temp_dev = DEVICE_DT_GET(DT_NODELABEL(temp)); +static enum sensor_channel chan_to_use = SENSOR_CHAN_DIE_TEMP; + +/** @brief global variable to store battery voltage */ +static atomic_t die_temperature_raw; + +static void die_temperature_work_handler(struct k_work *work); + +K_WORK_DELAYABLE_DEFINE(get_die_temperature, die_temperature_work_handler); + +/** + * @brief Function for getting pointer to die temperature. + * + * @return Pointer to atomic variable holding die temperature + */ +atomic_t *get_die_temperature_pointer(void) +{ + return &die_temperature_raw; +} + +int die_temperature_monitoring_setup(void) +{ + int err = 0; + + if (!device_is_ready(temp_dev)) { + LOG_ERR("Device %s is not ready.", temp_dev->name); + return err = -ENODEV; + } + + k_work_schedule(&get_die_temperature, K_MSEC(CONFIG_DIE_TEMP_MONITOR_INTERVAL_MS)); + + return err; +} + +static void die_temperature_work_handler(struct k_work *work) +{ + int err; + struct sensor_value val; + int32_t temp_val; + + (void)work; + + err = sensor_sample_fetch(temp_dev); + if (err < 0) { + LOG_ERR("sensor_sample_fetch_chan() returned: %d", err); + return; + } + + err = sensor_channel_get(temp_dev, chan_to_use, &val); + if (err < 0) { + LOG_ERR("sensor_channel_get() returned: %d", err); + return; + } + + temp_val = (val.val1 * 100) + (val.val2 / 10000); + atomic_set(&die_temperature_raw,temp_val); + LOG_INF("DIE_TEMP is %d.%02u", temp_val / 100, abs(temp_val) % 100); + k_work_schedule(&get_die_temperature, K_MSEC(CONFIG_DIE_TEMP_MONITOR_INTERVAL_MS)); + + return; +} diff --git a/soc/nordic/nrf54l/health_monitoring_nrfx/temperature_monitor.h b/soc/nordic/nrf54l/health_monitoring_nrfx/temperature_monitor.h new file mode 100644 index 000000000000..ff2b6ff28890 --- /dev/null +++ b/soc/nordic/nrf54l/health_monitoring_nrfx/temperature_monitor.h @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _TEMPERATURE_MONITOR_H_ +#define _TEMPERATURE_MONITOR_H_ + +#include + +atomic_t* get_die_temperature_pointer(void); +int die_temperature_monitoring_setup(void); + +#endif //_TEMPERATURE_MONITOR_H_ diff --git a/soc/nordic/nrf54l/soc.c b/soc/nordic/nrf54l/soc.c index ec4e67443907..1ff2a282a233 100644 --- a/soc/nordic/nrf54l/soc.c +++ b/soc/nordic/nrf54l/soc.c @@ -35,6 +35,10 @@ LOG_MODULE_REGISTER(soc, CONFIG_SOC_LOG_LEVEL); #include #include +#ifdef CONFIG_SOC_HEALTH_MONITORING +#include "health_monitoring.h" +#endif + #define LFXO_NODE DT_NODELABEL(lfxo) #define HFXO_NODE DT_NODELABEL(hfxo) @@ -174,3 +178,9 @@ void arch_busy_wait(uint32_t time_us) /* Init must precede sys_clock_driver_init */ SYS_INIT(nordicsemi_nrf54l_init, EARLY, 0); + +#ifdef CONFIG_SOC_HEALTH_MONITORING +void soc_late_init_hook(){ + health_monitoring_setup(); +} +#endif