From fbad82ed7663d3824e8896ff8a7516bde86814bd Mon Sep 17 00:00:00 2001 From: yanke Date: Tue, 7 Jan 2025 15:02:24 +0800 Subject: [PATCH 1/2] feat(sensor): add max17048 battery fuel gauge Close https://github.com/espressif/esp-iot-solution/issues/8 --- .github/workflows/upload_component.yml | 1 + .gitlab/ci/build.yml | 8 + .gitlab/ci/rules.yml | 15 + README.md | 1 + README_CN.md | 1 + .../battery_fuel_gauge/max17048/CHANGELOG.md | 7 + .../max17048/CMakeLists.txt | 5 + .../battery_fuel_gauge/max17048/README.md | 31 + .../max17048/idf_component.yml | 10 + .../max17048/include/max17048.h | 396 +++++++++++++ .../battery_fuel_gauge/max17048/license.txt | 202 +++++++ .../battery_fuel_gauge/max17048/max17048.c | 541 ++++++++++++++++++ .../max17048/test_apps/CMakeLists.txt | 8 + .../max17048/test_apps/main/CMakeLists.txt | 2 + .../max17048/test_apps/main/idf_component.yml | 4 + .../max17048/test_apps/main/max17048_test.c | 196 +++++++ .../max17048/test_apps/sdkconfig.defaults | 9 + docs/en/sensors/battery_fuel_gauge.rst | 15 + docs/en/sensors/index.rst | 1 + docs/zh_CN/sensors/battery_fuel_gauge.rst | 15 + docs/zh_CN/sensors/index.rst | 1 + 21 files changed, 1469 insertions(+) create mode 100644 components/sensors/battery_fuel_gauge/max17048/CHANGELOG.md create mode 100644 components/sensors/battery_fuel_gauge/max17048/CMakeLists.txt create mode 100644 components/sensors/battery_fuel_gauge/max17048/README.md create mode 100644 components/sensors/battery_fuel_gauge/max17048/idf_component.yml create mode 100644 components/sensors/battery_fuel_gauge/max17048/include/max17048.h create mode 100644 components/sensors/battery_fuel_gauge/max17048/license.txt create mode 100644 components/sensors/battery_fuel_gauge/max17048/max17048.c create mode 100644 components/sensors/battery_fuel_gauge/max17048/test_apps/CMakeLists.txt create mode 100644 components/sensors/battery_fuel_gauge/max17048/test_apps/main/CMakeLists.txt create mode 100644 components/sensors/battery_fuel_gauge/max17048/test_apps/main/idf_component.yml create mode 100644 components/sensors/battery_fuel_gauge/max17048/test_apps/main/max17048_test.c create mode 100644 components/sensors/battery_fuel_gauge/max17048/test_apps/sdkconfig.defaults create mode 100644 docs/en/sensors/battery_fuel_gauge.rst create mode 100644 docs/zh_CN/sensors/battery_fuel_gauge.rst diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index ae8f38965..797441d87 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -64,6 +64,7 @@ jobs: components/motor/esp_simplefoc; components/motor/servo; components/openai; + components/sensors/battery_fuel_gauge/max17048; components/sensors/power_measure; components/sensors/gesture/apds9960; components/sensors/humiture/aht20; diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index 2c1d14dfa..176af50fc 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -1303,6 +1303,14 @@ build_components_sensors_power_measure_test_apps: variables: EXAMPLE_DIR: components/sensors/power_measure/test_apps +build_components_sensors_battery_fuel_gauge_max17048_test_apps: + extends: + - .build_examples_template + - .rules:build:components_sensors_battery_fuel_gauge_max17048_test_apps + - .build_idf_active_release_version + variables: + EXAMPLE_DIR: components/sensors/battery_fuel_gauge/max17048/test_apps + build_components_sensors_gesture_apds9960_test_apps: extends: - .build_examples_template diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml index 799b2ddc5..b9b55738a 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -202,6 +202,10 @@ - "components/sensors/power_measure/**/*" - "components/tools/cmake_utilities/package_manager.cmake" +.patterns-components_sensors_battery_fuel_gauge_max17048: &patterns-components_sensors_battery_fuel_gauge_max17048 + - "components/sensors/battery_fuel_gauge/max17048/**/*" + - "tools/cmake_utilities/package_manager.cmake" + .patterns-components_sensors_gesture_apds9960: &patterns-components_sensors_gesture_apds9960 - "components/sensors/gesture/apds9960/**/*" - "tools/cmake_utilities/package_manager.cmake" @@ -2016,6 +2020,17 @@ - <<: *if-dev-push changes: *patterns-components_openai +.rules:build:components_sensors_battery_fuel_gauge_max17048_test_apps: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-label-target_test + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-components_sensors_battery_fuel_gauge_max17048 + .rules:build:components_sensors_gesture_apds9960_test_apps: rules: - <<: *if-protected diff --git a/README.md b/README.md index 1937c176b..41b79a58f 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ The registered components in ESP-IoT-Solution are listed below: | [led_indicator](https://components.espressif.com/components/espressif/led_indicator) | [![Component Registry](https://components.espressif.com/components/espressif/led_indicator/badge.svg)](https://components.espressif.com/components/espressif/led_indicator) | | [lightbulb_driver](https://components.espressif.com/components/espressif/lightbulb_driver) | [![Component Registry](https://components.espressif.com/components/espressif/lightbulb_driver/badge.svg)](https://components.espressif.com/components/espressif/lightbulb_driver) | | [lis2dh12](https://components.espressif.com/components/espressif/lis2dh12) | [![Component Registry](https://components.espressif.com/components/espressif/lis2dh12/badge.svg)](https://components.espressif.com/components/espressif/lis2dh12) | +| [max17048](https://components.espressif.com/components/espressif/max17048) | [![Component Registry](https://components.espressif.com/components/espressif/max17048/badge.svg)](https://components.espressif.com/components/espressif/max17048) | | [mcp23017](https://components.espressif.com/components/espressif/mcp23017) | [![Component Registry](https://components.espressif.com/components/espressif/mcp23017/badge.svg)](https://components.espressif.com/components/espressif/mcp23017) | | [mvh3004d](https://components.espressif.com/components/espressif/mvh3004d) | [![Component Registry](https://components.espressif.com/components/espressif/mvh3004d/badge.svg)](https://components.espressif.com/components/espressif/mvh3004d) | | [ntc_driver](https://components.espressif.com/components/espressif/ntc_driver) | [![Component Registry](https://components.espressif.com/components/espressif/ntc_driver/badge.svg)](https://components.espressif.com/components/espressif/ntc_driver) | diff --git a/README_CN.md b/README_CN.md index 2551f2f6e..c5f4413e2 100644 --- a/README_CN.md +++ b/README_CN.md @@ -113,6 +113,7 @@ ESP-IoT-Solution 中注册的组件如下: | [led_indicator](https://components.espressif.com/components/espressif/led_indicator) | [![Component Registry](https://components.espressif.com/components/espressif/led_indicator/badge.svg)](https://components.espressif.com/components/espressif/led_indicator) | | [lightbulb_driver](https://components.espressif.com/components/espressif/lightbulb_driver) | [![Component Registry](https://components.espressif.com/components/espressif/lightbulb_driver/badge.svg)](https://components.espressif.com/components/espressif/lightbulb_driver) | | [lis2dh12](https://components.espressif.com/components/espressif/lis2dh12) | [![Component Registry](https://components.espressif.com/components/espressif/lis2dh12/badge.svg)](https://components.espressif.com/components/espressif/lis2dh12) | +| [max17048](https://components.espressif.com/components/espressif/max17048) | [![Component Registry](https://components.espressif.com/components/espressif/max17048/badge.svg)](https://components.espressif.com/components/espressif/max17048) | | [mcp23017](https://components.espressif.com/components/espressif/mcp23017) | [![Component Registry](https://components.espressif.com/components/espressif/mcp23017/badge.svg)](https://components.espressif.com/components/espressif/mcp23017) | | [mvh3004d](https://components.espressif.com/components/espressif/mvh3004d) | [![Component Registry](https://components.espressif.com/components/espressif/mvh3004d/badge.svg)](https://components.espressif.com/components/espressif/mvh3004d) | | [ntc_driver](https://components.espressif.com/components/espressif/ntc_driver) | [![Component Registry](https://components.espressif.com/components/espressif/ntc_driver/badge.svg)](https://components.espressif.com/components/espressif/ntc_driver) | diff --git a/components/sensors/battery_fuel_gauge/max17048/CHANGELOG.md b/components/sensors/battery_fuel_gauge/max17048/CHANGELOG.md new file mode 100644 index 000000000..631db1a28 --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/CHANGELOG.md @@ -0,0 +1,7 @@ +# ChangeLog + +## v0.1.0 - 2024-12-28 + +### Enhancements: + +* Initial version: Provide the basic functionality of the MAX17048/MAX17049 sensor. diff --git a/components/sensors/battery_fuel_gauge/max17048/CMakeLists.txt b/components/sensors/battery_fuel_gauge/max17048/CMakeLists.txt new file mode 100644 index 000000000..d053528b2 --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "max17048.c" + INCLUDE_DIRS include) + +include(package_manager) +cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR}) diff --git a/components/sensors/battery_fuel_gauge/max17048/README.md b/components/sensors/battery_fuel_gauge/max17048/README.md new file mode 100644 index 000000000..e28d9dafa --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/README.md @@ -0,0 +1,31 @@ +# MAX17048/MAX17049 + +The MAX17048/MAX17049 ICs are tiny, micropower current fuel gauges for lithium-ion (Li+) batteries in handheld and portable equipment. The MAX17048 operates with a single lithium cell and the MAX17049 with two lithium cells in series. The ICs use the sophisticated Li+ battery-modeling algorithm ModelGauge™ to track the battery relative state-of-charge (SOC) continuously over widely varying charge and discharge conditions. The ModelGauge algorithm eliminates current-sense resistor and battery-learn cycles required in traditional fuel gauges. Temperature compensation is implemented using the system microcontroller. + +More detailed data can be found in the [MAX17048/MAX17049 Datasheet](https://www.analog.com/media/en/technical-documentation/data-sheets/MAX17048-MAX17049.pdf). + +## Example of MAX17048/MAX17049 usage + +```c +i2c_bus_handle_t i2c_bus = NULL; +max17048_handle_t max17048 = NULL; + +//Step1: Init I2C bus +i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C_MASTER_SDA_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = I2C_MASTER_SCL_IO, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = 400 * 1000, +}; +i2c_bus = i2c_bus_create(I2C_NUM_0, &conf); + +//Step2: Init max17048 +max17048 = max17048_create(i2c_bus, MAX17048_I2CADDR_DEFAULT); + +// Step2: Get voltage and battery percentage +float voltage = 0, percent = 0; +max17048_get_cell_voltage(max17048, &voltage); +max17048_get_cell_percent(max17048, &percent); +``` diff --git a/components/sensors/battery_fuel_gauge/max17048/idf_component.yml b/components/sensors/battery_fuel_gauge/max17048/idf_component.yml new file mode 100644 index 000000000..39802cafe --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/idf_component.yml @@ -0,0 +1,10 @@ +version: "0.1.0" +description: I2C driver for MAX17048/MAX17049 micropower current fuel gauges +url: https://github.com/espressif/esp-iot-solution/tree/master/components/sensors/battery_fuel_gauge/max17048 +repository: https://github.com/espressif/esp-iot-solution.git +documentation: https://docs.espressif.com/projects/esp-iot-solution/en/latest/sensors/battery_fuel_gauge.html +issues: https://github.com/espressif/esp-iot-solution/issues +dependencies: + espressif/i2c_bus: "1.1.*" + idf: '>=4.4' + cmake_utilities: "0.*" diff --git a/components/sensors/battery_fuel_gauge/max17048/include/max17048.h b/components/sensors/battery_fuel_gauge/max17048/include/max17048.h new file mode 100644 index 000000000..cb6e51bcc --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/include/max17048.h @@ -0,0 +1,396 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "i2c_bus.h" + +#define MAX17048_I2C_ADDR_DEFAULT 0x36 + +#define MAX17048_VCELL_REG 0x02 /*!< ADC measurement of VCELL. */ +#define MAX17048_SOC_REG 0x04 /*!< Battery state of charge. */ +#define MAX17048_MODE_REG 0x06 /*!< Initiates quick-start, reports hibernate mode, and enables sleep mode. */ +#define MAX17048_VERSION_REG 0x08 /*!< IC production version. */ +#define MAX17048_HIBRT_REG 0x0A /*!< Controls thresholds for entering and exiting hibernate mode. */ +#define MAX17048_CONFIG_REG 0x0C /*!< Compensation to optimize performance, sleep mode, alert indicators, and configuration. */ +#define MAX17048_VALERT_REG 0x14 /*!< Configures the VCELL range outside of which alerts are generated. */ +#define MAX17048_CRATE_REG 0x16 /*!< Approximate charge or discharge rate of the battery. */ +#define MAX17048_VRESET_REG 0x18 /*!< Configures VCELL threshold below which the IC resets itself, ID is a one-time factory-programmable identifier. */ +#define MAX17048_CHIPID_REG 0x19 /*!< Register that holds semi-unique chip ID. */ +#define MAX17048_STATUS_REG 0x1A /*!< Indicates overvoltage, undervoltage, SOC change, SOC low, and reset alerts. */ +#define MAX17048_CMD_REG 0xFE /*!< Sends POR command. */ + +/** + * @brief Different alert acondition in the status register (0x1A) + * + */ +typedef enum { + MAX17048_ALERT_FLAG_RESET_INDICATOR = 0x01, /*!< RESET_INDICATOR is set when the device powers up. */ + MAX17048_ALERT_FLAG_VOLTAGE_HIGH = 0x02, /*!< VOLTAGE_HIGH is set when VCELL has been above ALRT.VALRTMAX. */ + MAX17048_ALERT_FLAG_VOLTAGE_LOW = 0x04, /*!< VOLTAGE_LOW is set when VCELL has been below ALRT.VALRTMIN. */ + MAX17048_ALERT_FLAG_VOLTAGE_RESET = 0x08, /*!< VOLTAGE_RESET is set after the device has been reset if EnVr is set. */ + MAX17048_ALERT_FLAG_SOC_LOW = 0x10, /*!< SOC_LOW is set when SOC crosses the value in CONFIG.ATHD. */ + MAX17048_ALERT_FLAG_SOC_CHARGE = 0x20, /*!< SOC_CHARGE is set when SOC changes by at least 1% if CONFIG.ALSC is set. */ +} max17048_alert_flag_t; + +typedef void *max17048_handle_t; + +/** + * @brief Create a MAX17048 device handle + * + * @param bus I2C bus handle used to communicate with the MAX17048 + * @param dev_addr I2C address of the MAX17048 device + * + * @return + * - A valid max17048_handle_t on success + * - NULL if initialization fails + */ +max17048_handle_t max17048_create(i2c_bus_handle_t bus, uint8_t dev_addr); + +/** + * @brief Delete and free resources associated with the MAX17048 handle + * + * @param sensor Pointer to the MAX17048 handle to be deleted + * + * @return + * - ESP_OK Success + * - ESP_FAIL Failure + */ +esp_err_t max17048_delete(max17048_handle_t *sensor); + +/** + * @brief Read the IC production version of the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param ic_version Pointer to a variable that will store the production version read from the sensor + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor or ic_version is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_get_ic_version(max17048_handle_t sensor, uint16_t *ic_version); + +/** + * @brief Read the one-time factory-programmable chip ID from the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param chip_id Pointer to a variable that will store the chip ID + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor or chip_id is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_get_chip_id(max17048_handle_t sensor, uint8_t *chip_id); + +/** + * @brief Perform a software reset of the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * + * @return + * - ESP_OK on success + * - ESP_FAIL command sent failed + */ +esp_err_t max17048_reset(max17048_handle_t sensor); + +/** + * @brief Read the battery cell voltage from the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param voltage Pointer to a float variable that will store the cell voltage (in volts) + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor or voltage is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_get_cell_voltage(max17048_handle_t sensor, float *voltage); + +/** + * @brief Read the battery cell percentage from the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param percent Pointer to a float variable that will store the cell percentage + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor or percent is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_get_cell_percent(max17048_handle_t sensor, float *percent); + +/** + * @brief Read the approximate battery charge or discharge rate from the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param rate Pointer to a float variable that will store the approximate charge or discharge rate + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor or rate is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_get_charge_rate(max17048_handle_t sensor, float *rate); + +/** + * @brief Read the minimum and maximum voltage alert thresholds from the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param minv Pointer to a float variable that will store the minimum voltage alert threshold (VALRT.MIN) + * @param maxv Pointer to a float variable that will store the maximum voltage alert threshold (VALRT.MAX) + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor, minv, or maxv is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_get_alert_voltage(max17048_handle_t sensor, float *minv, float *maxv); + +/** + * @brief Set the minimum and maximum voltage alert thresholds on the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param minv Minimum voltage alert threshold (in volts) + * @param maxv Maximum voltage alert threshold (in volts) + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_set_alert_volatge(max17048_handle_t sensor, float minv, float maxv); + +/** + * @brief Read the current alert flag from the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param flag Pointer to a variable where the alert flag will be stored + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor is NULL or flag is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_get_alert_flag(max17048_handle_t sensor, uint8_t *flag); + +/** + * @brief Clear the specified alert flag on the MAX17048 sensor, refer to the 0x1A register in the MAX17048 datasheet + * + * @param sensor Handle to the MAX17048 sensor + * @param flag The alert flag to clear, representing a specific condition + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor is NULL or if the provided flag is invalid + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_clear_alert_flag(max17048_handle_t sensor, max17048_alert_flag_t flag); + +/** + * @brief Clear alert status bit. The alert pin remains logic-low until the system software writes CONFIG.ALRT = 0 to clear the alert + * + * @param sensor Handle to the MAX17048 sensor + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_clear_alert_status_bit(max17048_handle_t sensor); + +/** + * @brief Read the hibernation threshold from the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param threshold Pointer to a float variable where the hibernation threshold (in %/hr) will be stored + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor is NULL or threshold pointer is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_get_hibernation_threshold(max17048_handle_t sensor, float *threshold); + +/** + * @brief Set the hibernation threshold (HibThr) on the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param threshold The desired hibernation threshold (in %/hr) + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if sensor is NULL + * - Other error codes based on I2C communication failure + */ +esp_err_t max17048_set_hibernation_threshold(max17048_handle_t sensor, float threshold); + +/** + * @brief Read the active threshold (ActThr) from the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param threshold Pointer to a float variable where the threshold value (in volts) will be stored + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle or threshold pointer is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_get_active_threshold(max17048_handle_t sensor, float *threshold); + +/** + * @brief Set the active threshold (ActThr) on the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param threshold The desired active threshold in volts + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_set_active_threshold(max17048_handle_t sensor, float threshold); + +/** + * @brief Put the MAX17048 device into hibernation mode + * + * @param sensor Handle to the initialized MAX17048 sensor + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_enter_hibernation_mode(max17048_handle_t sensor); + +/** + * @brief Exit hibernation mode on the MAX17048 device + * + * @param sensor Handle to the initialized MAX17048 sensor + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_exit_hibernation_mode(max17048_handle_t sensor); + +/** + * @brief Check whether the MAX17048 device is currently in hibernation mode + * + * @param sensor Handle to the initialized MAX17048 sensor + * @param is_hibernate Pointer to a boolean that will be set to true if the device is in hibernation mode, or false otherwise. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL or is_hibernate pointer is NULL. + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction. + */ +esp_err_t max17048_is_hibernate(max17048_handle_t sensor, bool *is_hibernate); + +/** + * @brief Read the voltage that the MAX17048 device considers 'resetting' + * + * @param sensor Handle to the initialized MAX17048 sensor + * @param voltage Pointer to a float variable where the voltage value (in volts) will be stored + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL or voltage pointer is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_get_reset_voltage(max17048_handle_t sensor, float *voltage); + +/** + * @brief Set the voltage that the MAX17048 device considers 'resetting' + * + * @param sensor Handle to the initialized MAX17048 sensor + * @param voltage Below the set voltage value will be considered as reset + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_set_reset_voltage(max17048_handle_t sensor, float voltage); + +/** + * @brief Enable or disable the voltage-reset alert in the MAX17048 sensor + * + * @param sensor Handle to the MAX17048 sensor + * @param enabled Boolean flag indicating whether the voltage-reset alert should be enabled (true) or disabled (false) + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_set_reset_voltage_alert_enabled(max17048_handle_t sensor, bool enabled); + +/** + * @brief Check if the voltage-reset alert in the MAX17048 sensor is enabled + * + * @param sensor Handle to the MAX17048 sensor + * @param enabled Pointer to a boolean that will store whether the voltage-reset alert is enabled (true) or disabled (false) + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_is_reset_voltage_alert_enabled(max17048_handle_t sensor, bool *enabled); + +/** + * @brief Configure temperature compensation on the MAX17048 device + * + * @param sensor Handle to the MAX17048 sensor + * @param temperature Temperature value (in degrees Celsius) for compensation + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_temperature_compensation(max17048_handle_t sensor, float temperature); + +/** + * @brief Enable or disable the MAX17048 sleep mode + * + * @param sensor Handle to the MAX17048 sensor + * @param enabled If true, sleep mode is enabled; otherwise, it is disabled + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_set_sleep_enabled(max17048_handle_t sensor, bool enabled); + +/** + * @brief Set the MAX17048 device sleep mode + * + * @param sensor Handle to the MAX17048 sensor + * @param sleep If true, the device enters sleep mode; otherwise, it exits sleep mode + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_set_sleep(max17048_handle_t sensor, bool sleep); + +/** + * @brief Check if the MAX17048 device is in sleep mode + * + * @param sensor Handle to the MAX17048 sensor + * @param is_sleeping Pointer to a bool that is set to true if the device is in sleep mode + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG The sensor handle is NULL or is_sleeping is NULL + * - Other esp_err_t codes Errors that may occur during I2C transactions or hardware interaction + */ +esp_err_t max17048_is_sleeping(max17048_handle_t sensor, bool *is_sleeping); diff --git a/components/sensors/battery_fuel_gauge/max17048/license.txt b/components/sensors/battery_fuel_gauge/max17048/license.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/license.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/sensors/battery_fuel_gauge/max17048/max17048.c b/components/sensors/battery_fuel_gauge/max17048/max17048.c new file mode 100644 index 000000000..258fac97c --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/max17048.c @@ -0,0 +1,541 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "esp_log.h" +#include "esp_system.h" +#include "max17048.h" + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +typedef struct { + i2c_bus_device_handle_t i2c_dev; + uint8_t dev_addr; +} max17048_sensor_t; + +max17048_handle_t max17048_create(i2c_bus_handle_t bus, uint8_t dev_addr) +{ + max17048_sensor_t *sens = (max17048_sensor_t *)calloc(1, sizeof(max17048_sensor_t)); + sens->i2c_dev = i2c_bus_device_create(bus, dev_addr, i2c_bus_get_current_clk_speed(bus)); + if (sens->i2c_dev == NULL) { + free(sens); + return NULL; + } + sens->dev_addr = dev_addr; + return (max17048_handle_t)(sens); +} + +esp_err_t max17048_delete(max17048_handle_t *sensor) +{ + if (*sensor == NULL) { + return ESP_OK; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)(*sensor); + i2c_bus_device_delete(&sens->i2c_dev); + free(sens); + *sensor = NULL; + return ESP_OK; +} + +esp_err_t max17048_get_ic_version(max17048_handle_t sensor, uint16_t *ic_version) +{ + if (sensor == NULL || ic_version == NULL) { + return ESP_ERR_INVALID_ARG; + } + + uint8_t buf[2] = {0x00}; + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_VERSION_REG, 2, buf); + if (ret != ESP_OK) { + return ret; + } + *ic_version = ((uint16_t)buf[0] << 8) | buf[1]; + return ret; +} + +esp_err_t max17048_get_chip_id(max17048_handle_t sensor, uint8_t *chip_id) +{ + if (sensor == NULL || chip_id == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + return i2c_bus_read_byte(sens->i2c_dev, MAX17048_CHIPID_REG, chip_id); +} + +static bool max17048_is_ready(max17048_handle_t sensor) +{ + uint16_t ic_version = 0; + esp_err_t ret = max17048_get_ic_version(sensor, &ic_version); + + if (ret != ESP_OK) { + return false; + } + return ((ic_version & 0xFFF0) == 0x0010); /*!< The value of IC production version is always 0x001_. */ +} + +esp_err_t max17048_reset(max17048_handle_t sensor) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t buf[2] = {0x54, 0x00}; + + esp_err_t ret = i2c_bus_write_bytes(sens->i2c_dev, MAX17048_CMD_REG, 2, buf); + if (ret == ESP_OK) { + return ESP_FAIL; /*!< When this command is sent, the MAX17048 will no longer respond to I2C. Therefore, if an I2C error occurs, it indicates that the reset has succeeded. */ + } + return ESP_OK; +} + +esp_err_t max17048_get_cell_voltage(max17048_handle_t sensor, float *voltage) +{ + if (sensor == NULL || voltage == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (!max17048_is_ready(sensor)) { + return ESP_FAIL; + } + + uint8_t buf[2] = {0x00}; + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_VCELL_REG, 2, buf); + if (ret != ESP_OK) { + return ESP_FAIL; + } + + uint16_t result = ((uint16_t)buf[0] << 8) | buf[1]; + *voltage = 1.0f * result * 78.125f / 1000000.0f; /*!< Unit is 78.125 μV/cell, needs to be converted to volts */ + return ret; +} + +esp_err_t max17048_get_cell_percent(max17048_handle_t sensor, float *percent) +{ + if (sensor == NULL || percent == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (!max17048_is_ready(sensor)) { + return ESP_FAIL; + } + + uint8_t buf[2] = {0x00}; + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_SOC_REG, 2, buf); + if (ret != ESP_OK) { + return ESP_FAIL; + } + + uint16_t result = ((uint16_t)buf[0] << 8) | buf[1]; + *percent = 1.0f * result / 256.0f; /*!< Unit is 1%/256 */ + return ret; +} + +esp_err_t max17048_get_charge_rate(max17048_handle_t sensor, float *percent) +{ + if (sensor == NULL || percent == NULL) { + return ESP_ERR_INVALID_ARG; + } + + if (!max17048_is_ready(sensor)) { + return ESP_FAIL; + } + + uint8_t buf[2] = {0x00}; + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_CRATE_REG, 2, buf); + if (ret != ESP_OK) { + return ESP_FAIL; + } + + int16_t result = (int16_t)((buf[0] << 8) | buf[1]); + *percent = (float)(result) * 0.208f; /*!< Unit is 0.208%/hr */ + return ret; +} + +esp_err_t max17048_get_alert_voltage(max17048_handle_t sensor, float *minv, float *maxv) +{ + if (sensor == NULL || minv == NULL || maxv == NULL) { + return ESP_ERR_INVALID_ARG; + } + + uint8_t result = 0; + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + esp_err_t ret = i2c_bus_read_byte(sens->i2c_dev, MAX17048_VALERT_REG, &result); + if (ret != ESP_OK) { + return ESP_FAIL; + } + *minv = (float)result * 0.02f; /*!< Unit is 20mV */ + + ret = i2c_bus_read_byte(sens->i2c_dev, MAX17048_VALERT_REG + 1, &result); + if (ret != ESP_OK) { + return ESP_FAIL; + } + *maxv = (float)result * 0.02f; /*!< Unit is 20mV */ + + return ESP_OK; +} + +esp_err_t max17048_set_alert_volatge(max17048_handle_t sensor, float minv, float maxv) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t minv_int = (uint8_t)min(255, max(0, (int)(minv / 0.02f))); + uint8_t maxv_int = (uint8_t)min(255, max(0, (int)(maxv / 0.02f))); + + esp_err_t ret = i2c_bus_write_byte(sens->i2c_dev, MAX17048_VALERT_REG, minv_int); + if (ret != ESP_OK) { + return ESP_FAIL; + } + + ret = i2c_bus_write_byte(sens->i2c_dev, MAX17048_VALERT_REG + 1, maxv_int); + if (ret != ESP_OK) { + return ESP_FAIL; + } + return ESP_OK; +} + +esp_err_t max17048_get_alert_flag(max17048_handle_t sensor, uint8_t *flag) +{ + if (sensor == NULL || flag == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + esp_err_t ret = i2c_bus_read_byte(sens->i2c_dev, MAX17048_STATUS_REG, flag); + if (ret != ESP_OK) { + return ESP_FAIL; + } + *flag = (*flag) & 0x7F; + return ESP_OK; +} + +esp_err_t max17048_clear_alert_flag(max17048_handle_t sensor, max17048_alert_flag_t flag) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + uint8_t status = 0; + esp_err_t ret = max17048_get_alert_flag(sensor, &status); + if (ret != ESP_OK) { + return ESP_FAIL; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + ret = i2c_bus_write_byte(sens->i2c_dev, MAX17048_STATUS_REG, (status & ~flag)); + return ret; +} + +esp_err_t max17048_clear_alert_status_bit(max17048_handle_t sensor) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t buf[2]; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_CONFIG_REG, 2, buf); + if (ret != ESP_OK) { + return ESP_FAIL; + } + + uint16_t result = ((uint16_t)buf[0] << 8) | buf[1]; + + result &= ~(1 << 5); /*!< Clear the alert status bit */ + buf[0] = (uint8_t)(result >> 8); + buf[1] = (uint8_t)(result & 0xFF); + return i2c_bus_write_bytes(sens->i2c_dev, MAX17048_CONFIG_REG, 2, buf); +} + +esp_err_t max17048_get_hibernation_threshold(max17048_handle_t sensor, float *threshold) +{ + if (sensor == NULL || threshold == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t result = 0; + esp_err_t ret = i2c_bus_read_byte(sens->i2c_dev, MAX17048_HIBRT_REG, &result); + if (ret != ESP_OK) { + return ret; + } + *threshold = (float)(result) * 0.208f; + return ESP_OK; +} + +esp_err_t max17048_set_hibernation_threshold(max17048_handle_t sensor, float threshold) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t threshold_int = (uint8_t)min(255, max(0, (int)(threshold / 0.208f))); + esp_err_t ret = i2c_bus_write_byte(sens->i2c_dev, MAX17048_HIBRT_REG, threshold_int); + if (ret != ESP_OK) { + return ESP_FAIL; + } + return ESP_OK; +} + +esp_err_t max17048_get_active_threshold(max17048_handle_t sensor, float *threshold) +{ + if (sensor == NULL || threshold == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t result = 0; + esp_err_t ret = i2c_bus_read_byte(sens->i2c_dev, MAX17048_HIBRT_REG + 1, &result); + if (ret != ESP_OK) { + return ESP_FAIL; + } + *threshold = (float)(result) * 0.00125f; + return ESP_OK; +} + +esp_err_t max17048_set_active_threshold(max17048_handle_t sensor, float threshold) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t threshold_int = (uint8_t)min(255, max(0, (int)(threshold / 0.00125f))); + esp_err_t ret = i2c_bus_write_byte(sens->i2c_dev, MAX17048_HIBRT_REG + 1, threshold_int); + if (ret != ESP_OK) { + return ESP_FAIL; + } + return ESP_OK; +} + +esp_err_t max17048_enter_hibernation_mode(max17048_handle_t sensor) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t buf[2] = {0xFF, 0xFF}; + return i2c_bus_write_bytes(sens->i2c_dev, MAX17048_HIBRT_REG, 2, buf); +} + +esp_err_t max17048_exit_hibernation_mode(max17048_handle_t sensor) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t buf[2] = {0x00, 0x00}; + return i2c_bus_write_bytes(sens->i2c_dev, MAX17048_HIBRT_REG, 2, buf); +} + +esp_err_t max17048_is_hibernate(max17048_handle_t sensor, bool *is_hibernate) +{ + if (sensor == NULL || is_hibernate == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t buf[2]; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_MODE_REG, 2, buf); + if (ret != ESP_OK) { + return ESP_FAIL; + } + + uint16_t result = ((uint16_t)buf[0] << 8) | buf[1]; + if (result & (1 << 12)) { + *is_hibernate = true; + } else { + *is_hibernate = false; + } + return ret; +} + +esp_err_t max17048_get_reset_voltage(max17048_handle_t sensor, float *voltage) +{ + if (sensor == NULL || voltage == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t result = 0; + esp_err_t ret = i2c_bus_read_byte(sens->i2c_dev, MAX17048_VRESET_REG, &result); + if (ret != ESP_OK) { + return ESP_FAIL; + } + + *voltage = result * 0.04f; + return ESP_OK; +} + +esp_err_t max17048_set_reset_voltage(max17048_handle_t sensor, float voltage) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t voltage_int = (uint8_t)max(min((int)(voltage / 0.04f), 127), 0); + esp_err_t ret = i2c_bus_write_byte(sens->i2c_dev, MAX17048_VRESET_REG, voltage_int); + return ret; +} + +esp_err_t max17048_set_reset_voltage_alert_enabled(max17048_handle_t sensor, bool enabled) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t buf[2] = {0x00}; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_STATUS_REG, 2, buf); + if (ret != ESP_OK) { + return ret; + } + uint16_t result = ((uint16_t)buf[0] << 8) | buf[1]; + + if (enabled) { + result |= (1 << 14); + } else { + result &= ~(1 << 14); + } + + buf[0] = (uint8_t)(result >> 8); + buf[1] = (uint8_t)(result & 0xFF); + return i2c_bus_write_bytes(sens->i2c_dev, MAX17048_STATUS_REG, 2, buf); +} + +esp_err_t max17048_is_reset_voltage_alert_enabled(max17048_handle_t sensor, bool *enabled) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t buf[2] = {0x00}; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_STATUS_REG, 2, buf); + if (ret != ESP_OK) { + return ret; + } + + uint16_t result = ((uint16_t)buf[0] << 8) | buf[1]; + if (result & (1 << 14)) { + *enabled = true; + } else { + *enabled = false; + } + return ESP_OK; +} + +esp_err_t max17048_temperature_compensation(max17048_handle_t sensor, float temperature) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + uint8_t temp_int = 0; + if (temperature > 20.0f) { + temp_int = 0x97 + (temperature - 20.0f) * -0.5f; + } else { + temp_int = 0x97 + (temperature - 20.0f) * -5.0f; + } + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + esp_err_t ret = i2c_bus_write_byte(sens->i2c_dev, MAX17048_CONFIG_REG, temp_int); + return ret; +} + +esp_err_t max17048_set_sleep_enabled(max17048_handle_t sensor, bool enabled) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t buf[2]; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_MODE_REG, 2, buf); + + if (ret != ESP_OK) { + return ret; + } + + uint16_t result = ((uint16_t)buf[0] << 8) | buf[1]; + if (enabled) { + result |= (1 << 13); + } else { + result &= ~(1 << 13); + } + + buf[0] = (uint8_t)(result >> 8); + buf[1] = (uint8_t)(result & 0xFF); + + return i2c_bus_write_bytes(sens->i2c_dev, MAX17048_CONFIG_REG, 2, buf); +} + +esp_err_t max17048_set_sleep(max17048_handle_t sensor, bool sleep) +{ + if (sensor == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + uint8_t buf[2]; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_CONFIG_REG, 2, buf); /*!< It is not possible to individually perform read/write operations on address 0x0D */ + + if (ret != ESP_OK) { + return ESP_FAIL; + } + + uint16_t result = ((uint16_t)buf[0] << 8) | buf[1]; + if (sleep) { + result |= (1 << 7); + } else { + result &= ~(1 << 7); + } + + buf[0] = (uint8_t)(result >> 8); + buf[1] = (uint8_t)(result & 0xFF); + + return i2c_bus_write_bytes(sens->i2c_dev, MAX17048_CONFIG_REG, 2, buf); +} + +esp_err_t max17048_is_sleeping(max17048_handle_t sensor, bool *is_sleeping) +{ + if (sensor == NULL || is_sleeping == NULL) { + return ESP_ERR_INVALID_ARG; + } + + max17048_sensor_t *sens = (max17048_sensor_t *)sensor; + + uint8_t buf[2]; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, MAX17048_CONFIG_REG, 2, buf); /*!< It is not possible to individually perform read/write operations on address 0x0D */ + if (ret != ESP_OK) { + return ret; + } + + uint16_t result = ((uint16_t)buf[0] << 8) | buf[1]; + if (result & (1 << 7)) { + *is_sleeping = true; + } else { + *is_sleeping = false; + } + return ESP_OK; +} diff --git a/components/sensors/battery_fuel_gauge/max17048/test_apps/CMakeLists.txt b/components/sensors/battery_fuel_gauge/max17048/test_apps/CMakeLists.txt new file mode 100644 index 000000000..482d15beb --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/test_apps/CMakeLists.txt @@ -0,0 +1,8 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/unit-test-app/components") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(max17048_test) diff --git a/components/sensors/battery_fuel_gauge/max17048/test_apps/main/CMakeLists.txt b/components/sensors/battery_fuel_gauge/max17048/test_apps/main/CMakeLists.txt new file mode 100644 index 000000000..ec40829d7 --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/test_apps/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRC_DIRS "." + PRIV_INCLUDE_DIRS ".") diff --git a/components/sensors/battery_fuel_gauge/max17048/test_apps/main/idf_component.yml b/components/sensors/battery_fuel_gauge/max17048/test_apps/main/idf_component.yml new file mode 100644 index 000000000..b36c35197 --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/test_apps/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + max17048: + version: "*" + override_path: "../../../max17048" diff --git a/components/sensors/battery_fuel_gauge/max17048/test_apps/main/max17048_test.c b/components/sensors/battery_fuel_gauge/max17048/test_apps/main/max17048_test.c new file mode 100644 index 000000000..a89ac9233 --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/test_apps/main/max17048_test.c @@ -0,0 +1,196 @@ +/* + * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "unity.h" +#include "string.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "max17048.h" + +#define I2C_MASTER_SCL_IO (gpio_num_t)4 /*!< gpio number for I2C master clock */ +#define I2C_MASTER_SDA_IO (gpio_num_t)5 /*!< gpio number for I2C master data */ +#define MAX17048_ALERT_IO (gpio_num_t)3 /*!< gpio number for MAX17048 alert pin */ + +#define TEST_MEMORY_LEAK_THRESHOLD (-400) + +static i2c_bus_handle_t i2c_bus = NULL; +static max17048_handle_t max17048 = NULL; +static const char* TAG = "max17048_test"; +SemaphoreHandle_t interrupt_semaphore; + +static void IRAM_ATTR gpio_isr_handler(void* arg) +{ + xSemaphoreGiveFromISR(interrupt_semaphore, NULL); +} + +static void max17048_init() +{ + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C_MASTER_SDA_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = I2C_MASTER_SCL_IO, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = 400 * 1000, + }; + i2c_bus = i2c_bus_create(I2C_NUM_0, &conf); + max17048 = max17048_create(i2c_bus, MAX17048_I2C_ADDR_DEFAULT); +} + +static void max17048_deinit() +{ + max17048_delete(&max17048); + i2c_bus_delete(&i2c_bus); +} + +TEST_CASE("max17048 basic information query test", "[voltage][percent][charge_rate]") +{ + max17048_init(); + float voltage = 0, percent = 0, charge_rate = 0; + TEST_ASSERT(max17048_get_cell_voltage(max17048, &voltage) == ESP_OK); + TEST_ASSERT(max17048_get_cell_percent(max17048, &percent) == ESP_OK); + TEST_ASSERT(max17048_get_charge_rate(max17048, &charge_rate) == ESP_OK); + ESP_LOGI(TAG, "Voltage:%.2fV, percent:%.2f%%, charge_rate:%.2f%%", voltage, percent, charge_rate); + max17048_deinit(); +} + +TEST_CASE("max17048 sotft reset test", "[reset]") +{ + max17048_init(); + TEST_ASSERT(max17048_reset(max17048) == ESP_OK); + max17048_deinit(); +} + +TEST_CASE("max17048 voltage alert threshold test", "[voltage][alert]") +{ + max17048_init(); + float voltage = 0, alert_min = 0, alert_max = 0; + TEST_ASSERT(max17048_get_cell_voltage(max17048, &voltage) == ESP_OK); + ESP_LOGI(TAG, "The set VLART.min and VLART.max are respectively: %.2fV and %.2fV", voltage - 0.2, voltage + 0.2); + TEST_ASSERT(max17048_set_alert_volatge(max17048, voltage - 0.2, voltage + 0.2) == ESP_OK); + TEST_ASSERT(max17048_get_alert_voltage(max17048, &alert_min, &alert_max) == ESP_OK); + ESP_LOGI(TAG, "The VLART.min and VLART.max reads are %.2fV and %.2fV respectively.", alert_min, alert_max); + max17048_deinit(); +} + +TEST_CASE("max17048 hibernation mode test", "[hibernation]") +{ + max17048_init(); + bool is_hibernation = false; + TEST_ASSERT(max17048_enter_hibernation_mode(max17048) == ESP_OK); + TEST_ASSERT(max17048_is_hibernate(max17048, &is_hibernation) == ESP_OK); + ESP_LOGI(TAG, "Hibernation_status:%d", is_hibernation); + max17048_deinit(); +} + +TEST_CASE("max17048 reset voltage test", "[reset_voltage]") +{ + max17048_init(); + float reset_voltage = 0; + TEST_ASSERT(max17048_set_reset_voltage(max17048, 2.5f) == ESP_OK); + TEST_ASSERT(max17048_get_reset_voltage(max17048, &reset_voltage) == ESP_OK); + ESP_LOGI(TAG, "Reset_voltage:%.2fV", reset_voltage); + + bool is_alert = false; + TEST_ASSERT(max17048_set_reset_voltage_alert_enabled(max17048, true) == ESP_OK); + TEST_ASSERT(max17048_is_reset_voltage_alert_enabled(max17048, &is_alert) == ESP_OK); + ESP_LOGI(TAG, "First reset_voltage_alert_status:%d", is_alert); + TEST_ASSERT(max17048_set_reset_voltage_alert_enabled(max17048, false) == ESP_OK); + TEST_ASSERT(max17048_is_reset_voltage_alert_enabled(max17048, &is_alert) == ESP_OK); + ESP_LOGI(TAG, "Second reset_voltage_alert_status:%d", is_alert); + max17048_deinit(); +} + +TEST_CASE("max17048 sleep mode test", "[sleep]") +{ + max17048_init(); + bool is_sleep = false; + TEST_ASSERT(max17048_set_sleep_enabled(max17048, true) == ESP_OK); + TEST_ASSERT(max17048_set_sleep(max17048, true) == ESP_OK); + TEST_ASSERT(max17048_is_sleeping(max17048, &is_sleep) == ESP_OK); + ESP_LOGI(TAG, "First sleep_status:%d", is_sleep); + TEST_ASSERT(max17048_set_sleep(max17048, false) == ESP_OK); + TEST_ASSERT(max17048_is_sleeping(max17048, &is_sleep) == ESP_OK); + ESP_LOGI(TAG, "Second sleep_status:%d", is_sleep); + max17048_deinit(); +} + +TEST_CASE("max17048 alert test", "[alert]") +{ + float voltage = 0; + interrupt_semaphore = xSemaphoreCreateBinary(); + TEST_ASSERT(interrupt_semaphore != NULL); + + gpio_config_t io_conf = { + .intr_type = GPIO_INTR_NEGEDGE, + .pull_up_en = GPIO_PULLUP_ENABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .mode = GPIO_MODE_INPUT, + .pin_bit_mask = (1ULL << MAX17048_ALERT_IO), + }; + gpio_config(&io_conf); + gpio_install_isr_service(0); + gpio_isr_handler_add(MAX17048_ALERT_IO, gpio_isr_handler, NULL); + + max17048_init(); + TEST_ASSERT(max17048_reset(max17048) == ESP_OK); + vTaskDelay(500 / portTICK_PERIOD_MS); + + // Set the alert voltage to trigger an alert + TEST_ASSERT(max17048_get_cell_voltage(max17048, &voltage) == ESP_OK); + TEST_ASSERT(max17048_set_alert_volatge(max17048, voltage - 0.5, voltage - 0.3) == ESP_OK); + ESP_LOGI(TAG, "Current voltage:%.2fV, alert_min:%.2fV, alert_max:%.2fV", voltage, voltage - 0.5, voltage - 0.3); + + if (xSemaphoreTake(interrupt_semaphore, pdMS_TO_TICKS(1000)) != pdTRUE) { + TEST_FAIL_MESSAGE("Interrupt was not triggered within the timeout period."); + } + + // Clear alert + TEST_ASSERT(max17048_set_alert_volatge(max17048, voltage - 0.3, voltage + 0.3) == ESP_OK); + max17048_clear_alert_status_bit(max17048); + vTaskDelay(50 / portTICK_PERIOD_MS); + TEST_ASSERT(gpio_get_level(MAX17048_ALERT_IO) == 1); + + gpio_isr_handler_remove(MAX17048_ALERT_IO); + gpio_uninstall_isr_service(); + if (interrupt_semaphore != NULL) { + vSemaphoreDelete(interrupt_semaphore); + } + max17048_deinit(); +} + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + printf("MAX17048/MAX17049 TEST \n"); + unity_run_menu(); +} diff --git a/components/sensors/battery_fuel_gauge/max17048/test_apps/sdkconfig.defaults b/components/sensors/battery_fuel_gauge/max17048/test_apps/sdkconfig.defaults new file mode 100644 index 000000000..3778471bc --- /dev/null +++ b/components/sensors/battery_fuel_gauge/max17048/test_apps/sdkconfig.defaults @@ -0,0 +1,9 @@ +# For IDF 5.0 +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_EN=n + +# For IDF4.4 +CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP_TASK_WDT=n diff --git a/docs/en/sensors/battery_fuel_gauge.rst b/docs/en/sensors/battery_fuel_gauge.rst new file mode 100644 index 000000000..fe57a1d55 --- /dev/null +++ b/docs/en/sensors/battery_fuel_gauge.rst @@ -0,0 +1,15 @@ +Battery Fuel Gauge +===================== + +:link_to_translation:`zh_CN:[中文]` + +A battery fuel gauge is a type of sensor used to monitor and measure battery charge. Its basic functions include monitoring voltage, charge/discharge current, and battery temperature, as well as estimating the battery’s SOC and full charge capacity (FCC). + +Adapted Products +--------------------- + ++-------------------+-------------------------------------------------------------------------------------------------------------------------+-----+----------------+-----------------------------------------------------------------------------------------------------------+-----+ +| Name | Function | Bus | Vendor | Datasheet | | ++===================+=========================================================================================================================+=====+================+===========================================================================================================+=====+ +| MAX17048/MAX17049 | Provide accurate battery voltage, charge status information, charge/discharge rates, and configurable alarm indicators. | I2C | Analog Devices | `Datesheet `__ | | ++-------------------+-------------------------------------------------------------------------------------------------------------------------+-----+----------------+-----------------------------------------------------------------------------------------------------------+-----+ diff --git a/docs/en/sensors/index.rst b/docs/en/sensors/index.rst index c3ab6c926..67b0c0e94 100644 --- a/docs/en/sensors/index.rst +++ b/docs/en/sensors/index.rst @@ -15,3 +15,4 @@ Sensors NTC Sensor Power Monitor Power Measure + Battery Fuel Gauge diff --git a/docs/zh_CN/sensors/battery_fuel_gauge.rst b/docs/zh_CN/sensors/battery_fuel_gauge.rst new file mode 100644 index 000000000..2ddb3863d --- /dev/null +++ b/docs/zh_CN/sensors/battery_fuel_gauge.rst @@ -0,0 +1,15 @@ +电池电量计 +=========== + +:link_to_translation:`en:[English]` + +电池电量计是一种用于监测电池、计量电量的传感器,其基本功能为监测电压、充电/放电电流与电池温度,并估计电池荷电状态 (SOC) 以及电池的完全充电容量 (FCC)。 + +已适配列表 +---------- + ++-------------------+----------------------------------------------------------------+------+----------------+--------------------------------------------------------------------------------------------------------+-----+ +| 名称 | 功能 | 总线 | 供应商 | 规格书 | | ++===================+================================================================+======+================+========================================================================================================+=====+ +| MAX17048/MAX17049 | 提供精确的电池电压、充电状态信息、充放电速率以及可配置报警指示 | I2C | Analog Devices | `规格书 `__ | | ++-------------------+----------------------------------------------------------------+------+----------------+--------------------------------------------------------------------------------------------------------+-----+ diff --git a/docs/zh_CN/sensors/index.rst b/docs/zh_CN/sensors/index.rst index 9784ce3fc..c931df543 100644 --- a/docs/zh_CN/sensors/index.rst +++ b/docs/zh_CN/sensors/index.rst @@ -15,3 +15,4 @@ 热敏电阻传感器 功率监视器 功率测量 + 电池电量计 From 4b47315b349a99fa13e4a3b74606416602b9c6c9 Mon Sep 17 00:00:00 2001 From: yanke Date: Tue, 7 Jan 2025 15:05:29 +0800 Subject: [PATCH 2/2] fix(esp_msc_ota): fix test_case of test_msc_ota_without_msc_host --- components/usb/esp_msc_ota/test_apps/main/test_msc_ota.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/usb/esp_msc_ota/test_apps/main/test_msc_ota.c b/components/usb/esp_msc_ota/test_apps/main/test_msc_ota.c index d092e962b..8c7a1f012 100644 --- a/components/usb/esp_msc_ota/test_apps/main/test_msc_ota.c +++ b/components/usb/esp_msc_ota/test_apps/main/test_msc_ota.c @@ -215,7 +215,7 @@ TEST_CASE("Test msc ota without msc host", "[MSC OTA][WITHOUT MSC HOST]") bsp_sdcard_mount(); char path[256]; - sprintf(path, "%s/ota_test.bin", CONFIG_BSP_uSD_MOUNT_POINT); + sprintf(path, "%s/ota_test.bin", CONFIG_BSP_SD_MOUNT_POINT); esp_msc_ota_handle_t msc_ota_handle = NULL; esp_msc_ota_config_t config = {