feat(vbat): Add support to use vbat as rtc battery

This commit is contained in:
C.S.M 2025-01-15 11:42:35 +08:00
parent 349d1dbd33
commit 59df7c1b8f
18 changed files with 395 additions and 10 deletions

View File

@ -151,6 +151,10 @@ if(NOT non_os_build)
list(APPEND srcs "power_supply/brownout.c")
endif()
if(CONFIG_ESP_VBAT_INIT_AUTO)
list(APPEND srcs "power_supply/vbat.c")
endif()
else()
if(ESP_TEE_BUILD)
list(APPEND srcs "esp_clk.c" "hw_random.c")

View File

@ -247,7 +247,7 @@ menu "Hardware Settings"
orsource "./port/$IDF_TARGET/Kconfig.ldo"
orsource "./power_supply/port/$IDF_TARGET/Kconfig.bod"
orsource "./power_supply/port/$IDF_TARGET/Kconfig.power"
# Invisible bringup bypass options for esp_hw_support component
config ESP_BRINGUP_BYPASS_CPU_CLK_SETTING

View File

@ -91,7 +91,8 @@ void esp_brownout_init(void)
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2
rtc_isr_register(rtc_brownout_isr_handler, NULL, RTC_CNTL_BROWN_OUT_INT_ENA_M, RTC_INTR_FLAG_IRAM);
#else
esp_intr_alloc_intrstatus(power_supply_periph_signal.irq, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED, (uint32_t)brownout_ll_intr_get_status_reg(), BROWNOUT_DETECTOR_LL_INTERRUPT_MASK, &rtc_brownout_isr_handler, NULL, NULL);
intr_handle_t bod_intr;
esp_intr_alloc_intrstatus(power_supply_periph_signal.irq, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED, (uint32_t)brownout_ll_intr_get_status_reg(), BROWNOUT_DETECTOR_LL_INTERRUPT_MASK, &rtc_brownout_isr_handler, NULL, &bod_intr);
#endif
brownout_ll_intr_enable(true);

View File

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Initialize the VBAT (RTC Backup Battery) management system.
*
* @return
* - ESP_OK: Initialization was successful.
*/
esp_err_t esp_vbat_init(void);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,92 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include "esp_attr.h"
#include "esp_intr_alloc.h"
#include "hal/vbat_ll.h"
#include "hal/brownout_ll.h"
#include "hal/vbat_hal.h"
#include "freertos/FreeRTOS.h"
#include "sdkconfig.h"
#include "esp_private/startup_internal.h"
#include "esp_check.h"
#include "soc/power_supply_periph.h"
#define VBAT_BROWNOUT_DET_LVL CONFIG_ESP_VBAT_BROWNOUT_DET_LVL
#if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY
#define VBAT_CHARGE_DET_LVL_LOW CONFIG_ESP_VBAT_DET_LVL_LOW
#define VBAT_CHARGE_DET_LVL_HIGH CONFIG_ESP_VBAT_DET_LVL_HIGH
#define VBAT_CHARGER_RESISTOR_VALUE CONFIG_ESP_VBAT_CHARGER_CIRCUIT_RESISTOR_VAL
#if (VBAT_CHARGER_RESISTOR_VALUE < 1000 || VBAT_CHARGER_RESISTOR_VALUE > 4500 || VBAT_CHARGER_RESISTOR_VALUE % 500 != 0)
#error "vbat charger resistor (ESP_VBAT_CHARGER_CIRCUIT_RESISTOR_VAL) must be between 1000 and 4500 ohms and must be a multiple of 500."
#endif
#if (VBAT_BROWNOUT_DET_LVL >= VBAT_CHARGE_DET_LVL_LOW)
#error "vbat charger low threshold is equal or lower than vbat brownout threshold, please put vbat brownout threshold lower than vbat charger low threshold"
#endif
#endif
static const char TAG[] = "VBAT";
IRAM_ATTR static void vbat_isr_handler(void *arg)
{
uint32_t int_status;
vbat_ll_get_interrupt_status(&int_status);
vbat_ll_clear_intr_mask(int_status);
if (int_status & VBAT_LL_CHARGER_UNDERVOLTAGE_INTR) {
ESP_DRAM_LOGW(TAG, "RTC battery voltage low, start charging...");
vbat_ll_start_battery_charge(true);
}
if (int_status & VBAT_LL_CHARGER_UPVOLTAGE_INTR) {
ESP_DRAM_LOGW(TAG, "RTC battery voltage reaches high limit , stop charging...");
vbat_ll_start_battery_charge(false);
}
if (int_status & VBAT_LL_BROWNOUT_INTR) {
// TODO: A callback may needed here to inform an under voltage event.
ESP_DRAM_LOGW(TAG, "RTC battery voltage low, please change battery...");
}
}
esp_err_t esp_vbat_init(void)
{
intr_handle_t vbat_intr;
#if CONFIG_ESP_VBAT_USE_RECHARGEABLE_BATTERY
vbat_hal_config_t vbat_cfg = {
.enable_vbat_charger = true,
.charger_resistor_value = VBAT_CHARGER_RESISTOR_VALUE,
.low_threshold = VBAT_CHARGE_DET_LVL_LOW,
.high_threshold = VBAT_CHARGE_DET_LVL_HIGH,
.brownout_threshold = VBAT_BROWNOUT_DET_LVL,
.undervoltage_filter_time = 20,
.upvoltage_filter_time = 10,
.interrupt_mask = (VBAT_LL_CHARGER_MASK | VBAT_LL_DETECT_MASK),
};
#else
vbat_hal_config_t vbat_cfg = {
.enable_vbat_charger = false,
.brownout_threshold = VBAT_BROWNOUT_DET_LVL,
.interrupt_mask = VBAT_LL_DETECT_MASK,
};
#endif
vbat_hal_config(&vbat_cfg);
ESP_RETURN_ON_ERROR(esp_intr_alloc_intrstatus(power_supply_periph_signal.irq, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_SHARED, (uint32_t)brownout_ll_intr_get_status_reg(), VBAT_LL_CHARGER_MASK | VBAT_LL_DETECT_MASK, &vbat_isr_handler, NULL, &vbat_intr), TAG, "Allocate vbat isr failed");
return ESP_OK;
}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -41,6 +41,7 @@
#include "esp_private/esp_clk.h"
#include "esp_private/spi_flash_os.h"
#include "esp_private/brownout.h"
#include "esp_private/vbat.h"
#include "esp_rom_caps.h"
#include "esp_rom_sys.h"
@ -78,6 +79,7 @@ ESP_SYSTEM_INIT_FN(init_brownout, CORE, BIT(0), 104)
{
// [refactor-todo] leads to call chain rtc_is_register (driver) -> esp_intr_alloc (esp32/esp32s2) ->
// malloc (newlib) -> heap_caps_malloc (heap), so heap must be at least initialized
esp_err_t ret = ESP_OK;
#if CONFIG_ESP_BROWNOUT_DET
esp_brownout_init();
#else
@ -85,7 +87,12 @@ ESP_SYSTEM_INIT_FN(init_brownout, CORE, BIT(0), 104)
brownout_ll_ana_reset_enable(false);
#endif // SOC_CAPS_NO_RESET_BY_ANA_BOD
#endif // CONFIG_ESP_BROWNOUT_DET
return ESP_OK;
#if CONFIG_ESP_VBAT_INIT_AUTO
ret = esp_vbat_init();
#endif
return ret;
}
#endif

View File

@ -217,6 +217,10 @@ if(NOT BOOTLOADER_BUILD AND NOT esp_tee_build)
list(APPEND srcs "brownout_hal.c")
endif()
if(CONFIG_SOC_VBAT_SUPPORTED)
list(APPEND srcs "vbat_hal.c")
endif()
if(CONFIG_SOC_JPEG_CODEC_SUPPORTED)
list(APPEND srcs "jpeg_hal.c")
endif()

View File

@ -121,7 +121,7 @@ static inline void brownout_ll_ana_reset_enable(bool enable)
__attribute__((always_inline))
static inline void brownout_ll_intr_clear(void)
{
LP_ANA_PERI.int_clr.bod_mode0_int_clr = 1;
LP_ANA_PERI.int_clr.val = BROWNOUT_DETECTOR_LL_INTERRUPT_MASK;
}
/**

View File

@ -121,7 +121,7 @@ static inline void brownout_ll_ana_reset_enable(bool enable)
__attribute__((always_inline))
static inline void brownout_ll_intr_clear(void)
{
LP_ANA_PERI.int_clr.bod_mode0 = 1;
LP_ANA_PERI.int_clr.val = BROWNOUT_DETECTOR_LL_INTERRUPT_MASK;
}
/**

View File

@ -121,7 +121,7 @@ static inline void brownout_ll_ana_reset_enable(bool enable)
__attribute__((always_inline))
static inline void brownout_ll_intr_clear(void)
{
LP_ANA.int_clr.bod_mode0_int_clr = 1;
LP_ANA.int_clr.val = BROWNOUT_DETECTOR_LL_INTERRUPT_MASK;
}
/**

View File

@ -122,7 +122,7 @@ static inline void brownout_ll_ana_reset_enable(bool enable)
__attribute__((always_inline))
static inline void brownout_ll_intr_clear(void)
{
LP_ANA_PERI.int_clr.bod_mode0_int_clr = 1;
LP_ANA_PERI.int_clr.val = BROWNOUT_DETECTOR_LL_INTERRUPT_MASK;
}
/**

View File

@ -102,6 +102,8 @@ static inline void brownout_ll_set_intr_wait_cycles(uint8_t cycle)
* @brief Enable brown out interrupt
*
* @param enable true: enable, false: disable
*
* @note Avoid concurrency risky with vbat_ll_enable_intr_mask
*/
static inline void brownout_ll_intr_enable(bool enable)
{
@ -127,7 +129,7 @@ static inline void brownout_ll_ana_reset_enable(bool enable)
__attribute__((always_inline))
static inline void brownout_ll_intr_clear(void)
{
LP_ANA_PERI.int_clr.bod_mode0_int_clr = 1;
LP_ANA_PERI.int_clr.val = BROWNOUT_DETECTOR_LL_INTERRUPT_MASK;
}
/**

View File

@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
* The ll is not public api, don't use in application code.
* See readme.md in hal/readme.md
******************************************************************************/
#pragma once
#include <stdbool.h>
#include "esp_bit_defs.h"
#include "soc/lp_analog_peri_struct.h"
#include "hal/regi2c_ctrl.h"
#include "soc/regi2c_brownout.h"
typedef enum {
VBAT_LL_CHARGER_UPVOLTAGE_INTR = BIT(27),
VBAT_LL_CHARGER_UNDERVOLTAGE_INTR = BIT(28),
VBAT_LL_BROWNOUT_INTR = BIT(30),
} vbat_ll_intr_t;
#define VBAT_LL_CHARGER_MASK (BIT(27)|BIT(28))
#define VBAT_LL_DETECT_MASK (BIT(30))
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Set vbat brownout threshold voltage
*
* @param threshold vbat brownout threshold
*/
static inline void vbat_ll_set_brownout_threshold(uint8_t threshold)
{
// Give same value
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_DREF_VBAT_L, threshold);
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_DREF_VBAT_H, threshold);
}
/**
* @brief Set vbat charge threshold voltage
*
* @param threshold vbat charge threshold
*/
static inline void vbat_ll_set_charger_threshold(uint8_t threshold_l, uint8_t threshold_h)
{
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_DREF_VBAT_CHARGER_L, threshold_l);
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_DREF_VBAT_CHARGER_H, threshold_h);
}
/**
* @brief Enable or disable the VBAT charger comparator
*
* @param enable Set to `true` to enable the comparator, or `false` to disable it.
*/
static inline void vbat_ll_enable_charger_comparator(bool enable)
{
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_FORCE_PU_VBAT_CHARGER, enable);
}
/**
* @brief Set the under voltage filter time for the charger detector
*
* @param time_tick The filter time in ticks (unit depends on the hardware implementation).
*/
static inline void vbat_ll_set_undervoltage_filter_time(uint32_t time_tick)
{
LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_undervoltage_target = time_tick;
}
/**
* @brief Set the upvoltage filter time for the charger detector
*
* @param time_tick The filter time in ticks (unit depends on the hardware implementation).
*/
static inline void vbat_ll_set_upvoltage_filter_time(uint32_t time_tick)
{
LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_upvoltage_target = time_tick;
}
/**
* @brief Set the charger resistor value for VBAT charging
*
* @param resistor Resistor value to be set (unit depends on the hardware implementation).
*/
static inline void vbat_ll_set_charger_resistor(uint32_t resistor)
{
REGI2C_WRITE_MASK(I2C_BOD, I2C_BIAS_OR_DRES_CHARGER, resistor);
}
/*
* @brief Start or stop the VBAT battery charging process
*
* @param start Set to true to start charging, or false to stop charging.
*/
static inline void vbat_ll_start_battery_charge(bool start)
{
LP_ANA_PERI.vddbat_bod_cntl.vddbat_charger = start;
}
/**
* @brief Enable the interrupt mask for vbat usage
*
* @param mask A bitmask representing the interrupts to enable.
* Each bit corresponds to a specific interrupt source.
* @param enable true for enabling the interrupt, otherwise false.
*
* @note Avoid concurrency risky with brownout_ll_intr_enable
*/
static inline void vbat_ll_enable_intr_mask(uint32_t mask, bool enable)
{
if (enable) {
LP_ANA_PERI.int_ena.val |= mask;
} else {
LP_ANA_PERI.int_ena.val &= ~mask;
}
}
/**
* @brief Clear the interrupt mask for vbat usage
*
* @param mask A bitmask representing the interrupts to clear.
* Each bit corresponds to a specific interrupt source.
*/
static inline void vbat_ll_clear_intr_mask(uint32_t mask)
{
LP_ANA_PERI.int_clr.val = mask;
}
/**
* @brief Get the current interrupt mask for vbat usage
*
* @param intr_status Pointer to a variable where the interrupt status mask will be stored.
* The function will write the current interrupt status to this variable.
*/
static inline void vbat_ll_get_interrupt_status(uint32_t *intr_status)
{
*intr_status = LP_ANA_PERI.int_st.val;
}
/**
* @brief Clear the VBAT count for charge detection
*
* This function clears the internal counter that tracks the number of charge events detected
* related to the VBAT power supply. It is typically used to reset the count for monitoring purposes.
*/
static inline void vbat_ll_clear_count(void)
{
LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_cnt_clr = 1;
LP_ANA_PERI.vddbat_charge_cntl.vddbat_charge_cnt_clr = 0;
}
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint32_t interrupt_mask; // interrupt mask.
uint16_t charger_resistor_value; // charger resistor value
bool enable_vbat_charger; // whether enable vbat charger comparator
uint8_t low_threshold; // low voltage threshold
uint8_t high_threshold; // high voltage threshold
uint8_t brownout_threshold; // brownout threshold
uint8_t undervoltage_filter_time; // under voltage filter time
uint8_t upvoltage_filter_time; // up voltage filter time
} vbat_hal_config_t;
/**
* @brief Config vbat hal.
*
* @param cfg Pointer of vbat configuration structure.
*/
void vbat_hal_config(const vbat_hal_config_t *cfg);
#ifdef __cplusplus
}
#endif

23
components/hal/vbat_hal.c Normal file
View File

@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "hal/vbat_ll.h"
#include "hal/vbat_hal.h"
void vbat_hal_config(const vbat_hal_config_t *cfg)
{
vbat_ll_clear_count();
vbat_ll_enable_charger_comparator(cfg->enable_vbat_charger);
if (cfg->enable_vbat_charger) {
uint8_t resistor_reg = (cfg->charger_resistor_value - 1000) / 500;
vbat_ll_set_charger_resistor(resistor_reg);
vbat_ll_set_charger_threshold(cfg->low_threshold, cfg->high_threshold);
vbat_ll_set_undervoltage_filter_time(cfg->undervoltage_filter_time);
vbat_ll_set_upvoltage_filter_time(cfg->upvoltage_filter_time);
}
vbat_ll_set_brownout_threshold(cfg->brownout_threshold);
vbat_ll_enable_intr_mask(cfg->interrupt_mask, true);
}

View File

@ -219,6 +219,10 @@ config SOC_BOD_SUPPORTED
bool
default y
config SOC_VBAT_SUPPORTED
bool
default y
config SOC_APM_SUPPORTED
bool
default y

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -23,3 +23,26 @@
#define I2C_BOD_THRESHOLD_H 0x9
#define I2C_BOD_THRESHOLD_H_MSB 7
#define I2C_BOD_THRESHOLD_H_LSB 5
#define I2C_BIAS_OR_DREF_VBAT_L 8
#define I2C_BIAS_OR_DREF_VBAT_L_MSB 4
#define I2C_BIAS_OR_DREF_VBAT_L_LSB 2
#define I2C_BIAS_OR_DREF_VBAT_H 8
#define I2C_BIAS_OR_DREF_VBAT_H_MSB 7
#define I2C_BIAS_OR_DREF_VBAT_H_LSB 5
#define I2C_BIAS_OR_DREF_VBAT_CHARGER_L 10
#define I2C_BIAS_OR_DREF_VBAT_CHARGER_L_MSB 4
#define I2C_BIAS_OR_DREF_VBAT_CHARGER_L_LSB 2
#define I2C_BIAS_OR_DREF_VBAT_CHARGER_H 10
#define I2C_BIAS_OR_DREF_VBAT_CHARGER_H_MSB 7
#define I2C_BIAS_OR_DREF_VBAT_CHARGER_H_LSB 5
#define I2C_BIAS_OR_FORCE_PU_VBAT_CHARGER 10
#define I2C_BIAS_OR_FORCE_PU_VBAT_CHARGER_MSB 0
#define I2C_BIAS_OR_FORCE_PU_VBAT_CHARGER_LSB 0
/** Change the charging current by adjusting the value of the series resistor. 1kΩ + N * 0.5kΩ */
#define I2C_BIAS_OR_DRES_CHARGER 3
#define I2C_BIAS_OR_DRES_CHARGER_MSB 7
#define I2C_BIAS_OR_DRES_CHARGER_LSB 4

View File

@ -71,6 +71,7 @@
#define SOC_FLASH_ENC_SUPPORTED 1
#define SOC_SECURE_BOOT_SUPPORTED 1
#define SOC_BOD_SUPPORTED 1
#define SOC_VBAT_SUPPORTED 1
#define SOC_APM_SUPPORTED 1
#define SOC_PMU_SUPPORTED 1
#define SOC_DCDC_SUPPORTED 1