Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions soc/nordic/nrf54l/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions soc/nordic/nrf54l/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions soc/nordic/nrf54l/Kconfig.defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
if SOC_SERIES_NRF54L

rsource "Kconfig.defconfig.nrf54l*"
rsource "health_monitoring_nrfx/Kconfig"

if ARM

Expand Down
18 changes: 18 additions & 0 deletions soc/nordic/nrf54l/health_monitoring_nrfx/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
87 changes: 87 additions & 0 deletions soc/nordic/nrf54l/health_monitoring_nrfx/Kconfig
Original file line number Diff line number Diff line change
@@ -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
176 changes: 176 additions & 0 deletions soc/nordic/nrf54l/health_monitoring_nrfx/battery_monitor.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/logging/log.h>
#include <nrfx_saadc.h>
#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");
}
16 changes: 16 additions & 0 deletions soc/nordic/nrf54l/health_monitoring_nrfx/battery_monitor.h
Original file line number Diff line number Diff line change
@@ -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 <zephyr/sys/atomic.h>

int battery_monitoring_setup(void);
atomic_t *get_battery_voltage_pointer(void);
void battery_below_threshold(void);

#endif /* _BATTERY_MONITOR_H_ */
45 changes: 45 additions & 0 deletions soc/nordic/nrf54l/health_monitoring_nrfx/health_monitoring.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include <stdlib.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/logging/log.h>
#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;
}
12 changes: 12 additions & 0 deletions soc/nordic/nrf54l/health_monitoring_nrfx/health_monitoring.h
Original file line number Diff line number Diff line change
@@ -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_ */
Loading