diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml index f859458c9..9a2391238 100644 --- a/.github/workflows/upload_component.yml +++ b/.github/workflows/upload_component.yml @@ -63,6 +63,7 @@ jobs: components/motor/esp_simplefoc; components/motor/servo; components/openai; + components/sensors/power_measure; components/sensors/gesture/apds9960; components/sensors/humiture/aht20; components/sensors/humiture/hdc2010; diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml index 06f26432f..dad110616 100644 --- a/.gitlab/ci/build.yml +++ b/.gitlab/ci/build.yml @@ -463,6 +463,14 @@ build_example_get_started_knob_power_save: variables: EXAMPLE_DIR: examples/get-started/knob_power_save +build_example_sensors_power_measure: + extends: + - .build_examples_template + - .rules:build:example_sensors_power_measure + - .build_idf_active_release_version + variables: + EXAMPLE_DIR: examples/sensors/power_measure + build_example_gprof_gprof_simple: extends: - .build_examples_template @@ -1221,6 +1229,17 @@ build_components_openai_test_apps: variables: EXAMPLE_DIR: components/openai/test_apps +build_components_sensors_power_measure_test_apps: + extends: + - .build_examples_template + - .rules:build:components_sensors_power_measure_test_apps + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.0 + - IMAGE: espressif/idf:release-v4.4 + variables: + EXAMPLE_DIR: components/sensors/power_measure/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 40e80feb7..57881dcb2 100644 --- a/.gitlab/ci/rules.yml +++ b/.gitlab/ci/rules.yml @@ -197,6 +197,10 @@ - "components/openai/**/*" - "tools/cmake_utilities/package_manager.cmake" +.patterns-components_sensors_power_measure: &patterns-components_sensors_power_measure + - "components/sensors/power_measure/**/*" + - "components/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" @@ -517,6 +521,9 @@ .patterns-example_sensors_ntc_temperature_sensor: &patterns-example_sensors_ntc_temperature_sensor - "examples/sensors/ntc_temperature_sensor/**/*" +.patterns-example_sensors_power_measure: &patterns-example_sensors_power_measure + - "examples/sensors/power_measure/**/*" + .patterns-example_usb_device_usb_uart_bridge: &patterns-example_usb_device_usb_uart_bridge - "examples/usb/device/usb_uart_bridge/**/*" @@ -957,6 +964,15 @@ - <<: *if-dev-push changes: *patterns-example_get_started_knob_power_save +.rules:build:example_sensors_power_measure: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-dev-push + changes: *patterns-components_sensors_power_measure + - <<: *if-dev-push + changes: *patterns-example_sensors_power_measure + .rules:build:example_gprof_gprof_simple: rules: - <<: *if-protected @@ -1542,6 +1558,17 @@ - <<: *if-dev-push changes: *patterns-components_button +.rules:build:components_sensors_power_measure_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_power_measure + .rules:build:components_display_digital_tube_ch450_test: rules: - <<: *if-protected diff --git a/.gitlab/ci/target_test.yml b/.gitlab/ci/target_test.yml index c2a740b8a..19daa877b 100644 --- a/.gitlab/ci/target_test.yml +++ b/.gitlab/ci/target_test.yml @@ -311,6 +311,29 @@ components_test_openai: TEST_TARGET: esp32s3 TEST_FOLDER: components/openai +components_test_power_measure: + extends: + - .pytest_template + - .rules:build:components_sensors_power_measure_test_apps + needs: + - job: "build_components_sensors_power_measure_test_apps" + artifacts: true + optional: false + parallel: + matrix: + - IDF_TARGET: esp32c3 + IDF_VERSION: "4.4" + - IDF_TARGET: esp32c3 + IDF_VERSION: "5.0" + tags: + - esp32c3 + - generic + image: $DOCKER_TARGET_TEST_v5_1_ENV_IMAGE + variables: + TEST_TARGET: esp32c3 + TEST_FOLDER: components/sensors/power_measure + TEST_ENV: generic + components_test_esp_lcd_axs15231b: extends: - .pytest_template diff --git a/README.md b/README.md index 940f0b83b..6207cdfce 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ The registered components in ESP-IoT-Solution are listed below: | [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) | | [openai](https://components.espressif.com/components/espressif/openai) | [![Component Registry](https://components.espressif.com/components/espressif/openai/badge.svg)](https://components.espressif.com/components/espressif/openai) | +| [power_measure](https://components.espressif.com/components/espressif/power_measure) | [![Component Registry](https://components.espressif.com/components/espressif/power_measure/badge.svg)](https://components.espressif.com/components/espressif/power_measure) | | [pwm_audio](https://components.espressif.com/components/espressif/pwm_audio) | [![Component Registry](https://components.espressif.com/components/espressif/pwm_audio/badge.svg)](https://components.espressif.com/components/espressif/pwm_audio) | | [servo](https://components.espressif.com/components/espressif/servo) | [![Component Registry](https://components.espressif.com/components/espressif/servo/badge.svg)](https://components.espressif.com/components/espressif/servo) | | [sht3x](https://components.espressif.com/components/espressif/sht3x) | [![Component Registry](https://components.espressif.com/components/espressif/sht3x/badge.svg)](https://components.espressif.com/components/espressif/sht3x) | diff --git a/README_CN.md b/README_CN.md index 792a75441..cf5e8f061 100644 --- a/README_CN.md +++ b/README_CN.md @@ -116,6 +116,7 @@ ESP-IoT-Solution 中注册的组件如下: | [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) | | [openai](https://components.espressif.com/components/espressif/openai) | [![Component Registry](https://components.espressif.com/components/espressif/openai/badge.svg)](https://components.espressif.com/components/espressif/openai) | +| [power_measure](https://components.espressif.com/components/espressif/power_measure) | [![Component Registry](https://components.espressif.com/components/espressif/power_measure/badge.svg)](https://components.espressif.com/components/espressif/power_measure) | | [pwm_audio](https://components.espressif.com/components/espressif/pwm_audio) | [![Component Registry](https://components.espressif.com/components/espressif/pwm_audio/badge.svg)](https://components.espressif.com/components/espressif/pwm_audio) | | [servo](https://components.espressif.com/components/espressif/servo) | [![Component Registry](https://components.espressif.com/components/espressif/servo/badge.svg)](https://components.espressif.com/components/espressif/servo) | | [sht3x](https://components.espressif.com/components/espressif/sht3x) | [![Component Registry](https://components.espressif.com/components/espressif/sht3x/badge.svg)](https://components.espressif.com/components/espressif/sht3x) | diff --git a/components/.build-rules.yml b/components/.build-rules.yml index 676ae1ff3..d3321a007 100644 --- a/components/.build-rules.yml +++ b/components/.build-rules.yml @@ -38,6 +38,10 @@ components/knob/test_apps: enable: - if: INCLUDE_DEFAULT == 1 +components/sensors/power_measure/test_apps: + enable: + - if: INCLUDE_DEFAULT == 1 + components/led/led_indicator/test_apps: enable: - if: INCLUDE_DEFAULT == 1 diff --git a/components/sensors/power_measure/CHANGELOG.md b/components/sensors/power_measure/CHANGELOG.md new file mode 100644 index 000000000..205200e65 --- /dev/null +++ b/components/sensors/power_measure/CHANGELOG.md @@ -0,0 +1,14 @@ +## v0.1.0 - 2024-11-5 + +### Enhancements: + +* Initial version +* Support BL0937 chip to measure voltage, current, energy + +## v0.1.1 - 2024-12-10 + +### Enhancements: + +* Modified the formatting of the document +* Added more detailed documentation +* Optimised some code diff --git a/components/sensors/power_measure/CMakeLists.txt b/components/sensors/power_measure/CMakeLists.txt new file mode 100644 index 000000000..96b2ccb82 --- /dev/null +++ b/components/sensors/power_measure/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register(SRC_DIRS "." + INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "priv_include" + REQUIRES esp_timer driver esp_event + ) + +include(package_manager) +cu_pkg_define_version(${CMAKE_CURRENT_LIST_DIR}) diff --git a/components/sensors/power_measure/Kconfig b/components/sensors/power_measure/Kconfig new file mode 100644 index 000000000..cc81bd877 --- /dev/null +++ b/components/sensors/power_measure/Kconfig @@ -0,0 +1,7 @@ +menu "power_measure" + config BL0937_IRAM_OPTIMIZED + bool "Enable IRAM optimization for BL0937" + default n + help + Enable IRAM optimization for BL0937 +endmenu diff --git a/components/sensors/power_measure/README.md b/components/sensors/power_measure/README.md new file mode 100644 index 000000000..07b1267a9 --- /dev/null +++ b/components/sensors/power_measure/README.md @@ -0,0 +1,34 @@ +# Component Power_Measure + +## Overview + +This example demonstrates how to use the **BL0937** power measurement chip to detect electrical parameters such as voltage, current, active power, and energy consumption. It is implemented for **ESP32** using FreeRTOS, and shows how to configure and interface with the BL0937 power measurement chip. The example initializes the power measurement system, fetches various parameters, and logs them at regular intervals. + +This example supports the **BL0937** power measurement chip, which is capable of measuring: + +1. **Voltage** +2. **Current** +3. **Active Power** +4. **Energy** + +The primary goal is to demonstrate how to configure the hardware pins, initialize the power measurement system, and retrieve the data from the chip. + +## Features + +* Measures **voltage** , **current** , **active power** , and **energy** . +* Configures **BL0937** power measurement chip. +* Supports overcurrent, overvoltage, and undervoltage protection. +* Energy detection is enabled for accurate readings. +* Regularly fetches power readings every second and logs them. + +## Hardware Requirements + +The example uses the **BL0937** power measurement chip. To connect it, the following pins must be configured on the ESP32: + +| Variable | GPIO Pin | Chip Pin | +| ------------------- | -------------- | -------- | +| `BL0937_CF_GPIO` | `GPIO_NUM_3` | CF Pin | +| `BL0937_SEL_GPIO` | `GPIO_NUM_4` | SEL Pin | +| `BL0937_CF1_GPIO` | `GPIO_NUM_7` | CF1 Pin | + +Make sure that these GPIO pins are correctly connected to the respective pins on the **BL0937** chip in your hardware setup. diff --git a/components/sensors/power_measure/esp_bl0937.c b/components/sensors/power_measure/esp_bl0937.c new file mode 100644 index 000000000..bf75d4bba --- /dev/null +++ b/components/sensors/power_measure/esp_bl0937.c @@ -0,0 +1,329 @@ +/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" + +#include "esp_system.h" +#include "esp_timer.h" +#include "esp_log.h" + +#include "driver/gpio.h" +#include "hal/gpio_ll.h" +#include "priv_include/esp_bl0937.h" + +#define TAG "BL0937" + +float sensor_current_resistor = R_CURRENT_BL0937; +float sensor_voltage_resistor = R_VOLTAGE_BL0937; +float vref = V_REF_BL0937; + +uint8_t current_mode; +volatile uint8_t mode; +static uint8_t setpin_io; + +volatile float sensor_current_multiplier; // Unit: us/A +volatile float sensor_voltage_multiplier; // Unit: us/V +volatile float sensor_power_multiplier; // Unit: us/W + +volatile uint64_t last_cf_interrupt; +volatile uint64_t last_cf1_interrupt; +volatile uint64_t first_cf1_interrupt; + +uint32_t pulse_timeout_us = PULSE_TIMEOUT_US; //Unit: us +volatile uint32_t sensor_voltage_pulse_width; //Unit: us +volatile uint32_t sensor_current_pulse_width; //Unit: us +volatile uint32_t sensor_power_pulse_width; //Unit: us +volatile uint32_t sensor_pulse_wave_counts; + +float sensor_current; +float sensor_voltage; +float sensor_power; + +#ifdef CONFIG_BL0937_IRAM_OPTIMIZED +#define IRAM_OPT IRAM_ATTR +#else +#define IRAM_OPT +#endif + +void IRAM_ATTR reset_energe(void) +{ + sensor_pulse_wave_counts = 0; +} + +static void IRAM_OPT bl0937_cf_isr_handler(void* arg) +{ + uint64_t now = esp_timer_get_time(); + sensor_power_pulse_width = now - last_cf_interrupt; + last_cf_interrupt = now; + sensor_pulse_wave_counts++; +} + +static void IRAM_OPT bl0937_cf1_isr_handler(void* arg) +{ + uint64_t now = esp_timer_get_time(); + + if ((now - first_cf1_interrupt) > pulse_timeout_us) { + uint32_t pulse_width; + + if (last_cf1_interrupt == first_cf1_interrupt) { + pulse_width = 0; + } else { + pulse_width = now - last_cf1_interrupt; + } + + if (mode == current_mode) { + sensor_current_pulse_width = pulse_width; + } else { + sensor_voltage_pulse_width = pulse_width; + } + + mode = 1 - mode; + gpio_ll_set_level(&GPIO, setpin_io, mode); + first_cf1_interrupt = now; + } + last_cf1_interrupt = now; +} + +/** + * @brief DefaultMultipliers: + * For power a frequency of 1Hz means around 12W + * For current a frequency of 1Hz means around 15mA + * For voltage a frequency of 1Hz means around 0.5V + */ +static void IRAM_OPT calculatedefaultmultipliers() +{ + sensor_power_multiplier = (50850000.0 * vref * vref * sensor_voltage_resistor / sensor_current_resistor / 48.0 / F_OSC_BL0937) / 1.1371681416f; //15102450 + sensor_voltage_multiplier = (221380000.0 * vref * sensor_voltage_resistor / 2.0 / F_OSC_BL0937) / 1.0474137931f; //221384120,171674 + sensor_current_multiplier = (531500000.0 * vref / sensor_current_resistor / 24.0 / F_OSC_BL0937) / 1.166666f; // +} + +esp_err_t IRAM_OPT bl0937_init(chip_config_t config) +{ + esp_err_t ret; + sensor_voltage_resistor = config.divider_resistor; + sensor_current_resistor = config.sampling_resistor; + current_mode = config.pin_mode; + vref = V_REF_BL0937; + setpin_io = config.sel_gpio; + + gpio_ll_intr_disable(&GPIO, config.cf_gpio); + gpio_ll_intr_disable(&GPIO, config.cf1_gpio); + + gpio_config_t io_conf; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << config.sel_gpio); + io_conf.pull_down_en = 0; + io_conf.pull_up_en = 0; + ret = gpio_config(&io_conf); + if (ret != ESP_OK) { + return ret; + } + + //interrupt of rising edge + io_conf.intr_type = GPIO_INTR_POSEDGE; + io_conf.pin_bit_mask = ((1ULL << config.cf1_gpio) | (1ULL << config.cf_gpio)); + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = 1; + ret = gpio_config(&io_conf); + if (ret != ESP_OK) { + return ret; + } + + gpio_install_isr_service(ESP_INTR_FLAG_IRAM); + gpio_isr_handler_add(config.cf_gpio, bl0937_cf_isr_handler, NULL); + gpio_isr_handler_add(config.cf1_gpio, bl0937_cf1_isr_handler, NULL); + + calculatedefaultmultipliers(); + mode = current_mode; + + gpio_ll_set_level(&GPIO, config.sel_gpio, mode); + + gpio_ll_intr_enable_on_core(&GPIO, 0, config.cf_gpio); + gpio_ll_intr_enable_on_core(&GPIO, 0, config.cf1_gpio); + + return ESP_OK; +} + +float IRAM_OPT bl0937_get_current_multiplier() +{ + return sensor_current_multiplier; +} + +float IRAM_OPT bl0937_get_voltage_multiplier() +{ + return sensor_voltage_multiplier; +} + +float IRAM_OPT bl0937_get_power_multiplier() +{ + return sensor_power_multiplier; +} + +void IRAM_OPT bl0937_set_current_multiplier(float current_multiplier) +{ + sensor_current_multiplier = current_multiplier; +} + +void IRAM_OPT bl0937_set_voltage_multiplier(float voltage_multiplier) +{ + sensor_voltage_multiplier = voltage_multiplier; +} + +void IRAM_OPT bl0937_set_power_multiplier(float power_multiplier) +{ + sensor_power_multiplier = power_multiplier; +} + +void IRAM_OPT bl0937_setmode(bl0937_mode_t mode) +{ + mode = (mode == MODE_CURRENT) ? current_mode : 1 - current_mode; + gpio_ll_set_level(&GPIO, setpin_io, mode); + + last_cf1_interrupt = first_cf1_interrupt = esp_timer_get_time(); +} + +bl0937_mode_t IRAM_OPT bl0937_getmode() +{ + return (mode == current_mode) ? MODE_CURRENT : MODE_VOLTAGE; +} + +bl0937_mode_t IRAM_OPT bl0937_togglemode() +{ + bl0937_mode_t new_mode = bl0937_getmode() == MODE_CURRENT ? MODE_VOLTAGE : MODE_CURRENT; + bl0937_setmode(new_mode); + + return new_mode; +} + +float IRAM_OPT bl0937_get_energy() +{ + return sensor_pulse_wave_counts * sensor_power_multiplier / 1000000.; +} + +void IRAM_OPT bl0937_checkcfsignal() +{ + if ((esp_timer_get_time() - last_cf_interrupt) > pulse_timeout_us) { + sensor_power_pulse_width = 0; + } +} + +void IRAM_OPT bl0937_checkcf1signal() +{ + if ((esp_timer_get_time() - last_cf1_interrupt) > pulse_timeout_us) { + if (mode == current_mode) { + sensor_current_pulse_width = 0; + } else { + sensor_voltage_pulse_width = 0; + } + bl0937_togglemode(); + } +} + +float IRAM_OPT bl0937_get_voltage() +{ + bl0937_checkcf1signal(); + + sensor_voltage = (sensor_voltage_pulse_width > 0) ? sensor_voltage_multiplier / sensor_voltage_pulse_width : 0; + + return sensor_voltage; +} + +void bl0937_multiplier_init() +{ + calculatedefaultmultipliers(); +} + +void IRAM_OPT bl0937_expected_voltage(float value) +{ + if (sensor_voltage == 0) { + bl0937_get_voltage(); + } + + if (sensor_voltage > 0) { + sensor_voltage_multiplier *= ((float) value / sensor_voltage); + } +} + +void IRAM_OPT bl0937_expected_current(float value) +{ + if (sensor_current == 0) { + bl0937_get_current(); + } + + if (sensor_current > 0) { + sensor_current_multiplier *= (value / sensor_current); + } +} + +void IRAM_OPT bl0937_expected_active_power(float value) +{ + if (sensor_power == 0) { + bl0937_get_active_power(); + } + + if (sensor_power > 0) { + sensor_power_multiplier *= ((float) value / sensor_power); + } +} + +float IRAM_OPT bl0937_get_active_power() +{ + bl0937_checkcfsignal(); + + sensor_power = (sensor_power_pulse_width > 0) ? sensor_power_multiplier / sensor_power_pulse_width : 0; + + return sensor_power; +} + +/* + Power measurements are more sensitive to switch offs, + so we first check if power is 0 to set _current to 0 too +*/ +float IRAM_OPT bl0937_get_current() +{ + bl0937_get_active_power(); + + if (sensor_power == 0) { + sensor_current_pulse_width = 0; + } else { + bl0937_checkcf1signal(); + } + sensor_current = (sensor_current_pulse_width > 0) ? sensor_current_multiplier / sensor_current_pulse_width : 0; + + return sensor_current; +} + +float IRAM_OPT bl0937_getapparentpower() +{ + float current = bl0937_get_current(); + float voltage = bl0937_get_voltage(); + + return voltage * current; +} + +float IRAM_OPT bl0937_get_power_factor() +{ + float active = bl0937_get_active_power(); + float apparent = bl0937_getapparentpower(); + + if (active > apparent) { + return 1; + } + + if (apparent == 0) { + return 0; + } + + return (float) active / apparent; +} diff --git a/components/sensors/power_measure/idf_component.yml b/components/sensors/power_measure/idf_component.yml new file mode 100644 index 000000000..5f051a5a9 --- /dev/null +++ b/components/sensors/power_measure/idf_component.yml @@ -0,0 +1,9 @@ +version: "0.1.1" +description: Support chips to measure voltage, current, energy +issues: https://github.com/espressif/esp-iot-solution/issues +repository: https://github.com/espressif/esp-iot-solution.git +dependencies: + idf: ">=4.4.1" + cmake_utilities: "0.*" +examples: + - path: ../../../examples/sensors/power_measure diff --git a/components/sensors/power_measure/include/power_measure.h b/components/sensors/power_measure/include/power_measure.h new file mode 100644 index 000000000..7eb538be8 --- /dev/null +++ b/components/sensors/power_measure/include/power_measure.h @@ -0,0 +1,283 @@ +/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __POWER_MEASURE__ +#define __POWER_MEASURE__ + +#include +#include +#include +#include +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief STORGE_FACTOR_KEY + */ +#define STORGE_FACTOR_KEY "factor" + +/** + * @brief POWER_MEASURE event base + */ +/** @cond **/ +ESP_EVENT_DECLARE_BASE(POWER_MEASURE_EVENT); +/** @endcond **/ + +/** + * @brief Enumeration of power measurement events. + */ +typedef enum { + POWER_MEASURE_INIT_DONE = 1, /*!< Initialisation Done */ + POWER_MEASURE_OVERCURRENT, /*!< Overcurrent detected */ + POWER_MEASURE_OVERVOLTAGE, /*!< Overvoltage detected */ + POWER_MEASURE_UNDERVOLTAGE, /*!< Undervoltage */ + POWER_MEASURE_ENERGY_REPORT, /*!< report energy */ + POWER_MEASURE_HOME_APPLIANCES_ONLINE, /*!< appliances online */ + POWER_MEASURE_HOME_APPLIANCES_OFFLINE, /*!< appliances offline */ +} power_measure_event_t; + +/** + * @brief Enumeration of rated voltage levels. + */ +typedef enum { + RATED_VOLTAGE_110V = 110, /*!< Rated voltage of 110 volts */ + RATED_VOLTAGE_120V = 120, /*!< Rated voltage of 120 volts */ + RATED_VOLTAGE_220V = 220, /*!< Rated voltage of 220 volts */ +} power_measure_rated_voltage_t; + +/** + * @brief Enumeration of supported chip types. + */ +typedef enum { + CHIP_BL0937 = 0, /*!< BL0937 chip type */ + CHIP_MAX, /*!< Maximum chip type */ +} power_measure_chip_t; + +/** + * @brief Data structure for overcurrent event data. + */ +typedef struct { + float current_value; /*!< Current value measured during the overcurrent event */ + uint16_t trigger_value; /*!< Trigger threshold for overcurrent detection */ +} overcurrent_event_data_t; + +/** + * @brief Data structure for overvoltage event data. + */ +typedef struct { + float voltage_value; /*!< Voltage value measured during the overvoltage event */ + uint16_t overvalue; /*!< Threshold value for overvoltage detection */ +} overvoltage_event_data_t; + +/** + * @brief Data structure for undervoltage event data. + */ +typedef struct { + float voltage_value; /*!< Voltage value measured during the undervoltage event */ + uint16_t undervalue; /*!< Threshold value for undervoltage detection */ +} undervoltage_event_data_t; + +/** + * @brief Structure for calibration parameters. + */ +typedef struct { + float standard_power; /*!< Standard power in watts */ + float standard_voltage; /*!< Standard voltage in volts */ + float standard_current; /*!< Standard current in amperes */ +} calibration_parameter_t; + +/** + * @brief Structure for calibration factors. + */ +typedef struct { + float ki; /*!< Calibration factor for current */ + float ku; /*!< Calibration factor for voltage */ + float kp; /*!< Calibration factor for power */ +} calibration_factor_t; + +/** + * @brief Configuration structure for the chip. + */ +typedef struct { + power_measure_chip_t type; /*!< Type of the chip used */ + calibration_factor_t factor; /*!< Calibration factors for the chip */ + gpio_num_t sel_gpio; /*!< GPIO number for selection */ + gpio_num_t cf1_gpio; /*!< GPIO number for CF1 */ + gpio_num_t cf_gpio; /*!< GPIO number for CF */ + uint8_t pin_mode; /*!< Pin mode configuration */ + float sampling_resistor; /*!< Value of the sampling resistor */ + float divider_resistor; /*!< Value of the divider resistor */ +} chip_config_t; + +/** + * @brief Initialization configuration structure for power measurement. + */ +typedef struct { + chip_config_t chip_config; /*!< Configuration for the chip */ + uint16_t overcurrent; /*!< Overcurrent threshold */ + uint16_t overvoltage; /*!< Overvoltage threshold */ + uint16_t undervoltage; /*!< Undervoltage threshold */ + bool enable_energy_detection; /*!< Flag to enable energy detection */ +} power_measure_init_config_t; + +/** + * @brief power measure component int + * + * @param config configuration for power measure hardware + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_NO_MEM + * - ESP_ERR_INVALID_ARG + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_init(power_measure_init_config_t* config); + +/** + * @brief power measure component deint + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t power_measure_deinit(); + +/** + * @brief get current vltage (V) + * + * @param voltage current voltage (V) + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_INVALID_ARG + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_get_voltage(float* voltage); + +/** + * @brief get current current (A) + * + * @param current current current (A) + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_INVALID_ARG + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_get_current(float* current); + +/** + * @brief get current active power (W) + * + * @param active_power current active power (W) + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_INVALID_ARG + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_get_active_power(float* active_power); + +/** + * @brief get current power factor + * + * @param power_factor power factor + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_INVALID_ARG + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_get_power_factor(float* power_factor); + +/** + * @brief get current energy (Kw/h) + * + * @param energy Power consumed by the load (Kw/h) + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_INVALID_ARG + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_get_energy(float* energy); + +/** + * @brief start the energy calculation + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_start_energy_calculation(); + +/** + * @brief stop the energy calculation + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_stop_energy_calculation(); + +/** + * @brief reset the energy accumulated value in flash + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_reset_energy_calculation(); + +/** + * @brief start calibration for factory test + * + * @param para calibration parameters + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_INVALID_ARG + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_start_calibration(calibration_parameter_t* para); + +/** + * @brief get calibration factor from flash + * + * @param factor calibration factor + * + * @return + * - ESP_OK + * - ESP_FAIL + * - ESP_ERR_INVALID_STATE + */ +esp_err_t power_measure_get_calibration_factor(calibration_factor_t* factor); + +/** + * @brief reset factor in flash + * + * @return + * - ESP_OK + * - ESP_FAIL + */ +esp_err_t power_measure_calibration_factor_reset(void); + +#ifdef __cplusplus +} +#endif + +#endif /**< __POWER_MEASURE__ */ diff --git a/components/sensors/power_measure/license.txt b/components/sensors/power_measure/license.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/components/sensors/power_measure/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/power_measure/power_measure.c b/components/sensors/power_measure/power_measure.c new file mode 100644 index 000000000..4fcdd51cb --- /dev/null +++ b/components/sensors/power_measure/power_measure.c @@ -0,0 +1,237 @@ +/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "math.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/timers.h" + +#include "esp_log.h" +#include "esp_timer.h" +#include "esp_check.h" + +#include "priv_include/esp_bl0937.h" +#include "power_measure.h" + +static const char* TAG = "power_measure"; + +#define TIMER_UPDATE_MS (1000 * 1000U) +#define CALIBRATION_TIMEOUT_S 5 +#define SAVE_INTERVAL 0.1 +#define TIME_TO_REPORT_S 28800 + +typedef struct { + float current; + float voltage; + float power; + float energy; + float power_factor; + float accumulated_energy; +} power_measure_live_value_group_t; + +static power_measure_live_value_group_t* g_value = NULL; +static power_measure_init_config_t* g_config = NULL; +static TaskHandle_t g_task_handle = NULL; +static SemaphoreHandle_t xSemaphore = NULL; +static float accumulated_value = 0; +static bool g_init_done = true; +static float start_value = 0; + +static void update_value(void) +{ + if (g_config->chip_config.type == CHIP_BL0937) { + if (g_config->enable_energy_detection == true) { + g_value->energy = bl0937_get_energy() / 3600000; // Unit: KW/h + accumulated_value = g_value->energy; + } + g_value->current = bl0937_get_current(); + g_value->voltage = bl0937_get_voltage(); + g_value->power = bl0937_get_active_power(); + g_value->power_factor = bl0937_get_power_factor(); + } +} + +static void clear_up(void) +{ + if (g_config) { + free(g_config); + g_config = NULL; + } + if (g_value) { + free(g_value); + g_value = NULL; + } + if (g_task_handle) { + vTaskDelete(g_task_handle); + g_task_handle = NULL; + } +} + +static void power_measure_task(void* arg) +{ + update_value(); + + float energyNow = 0, energyLast = start_value; + time_t tsNow = 0, tsLast = 0; + xSemaphore = xSemaphoreCreateMutex(); + while (1) { + vTaskDelay(1000 / portTICK_PERIOD_MS); + update_value(); + + energyNow = accumulated_value + start_value; + if (fabs(energyNow - energyLast) > SAVE_INTERVAL) { + xSemaphoreTake(xSemaphore, portMAX_DELAY); + energyLast = energyNow; + xSemaphoreGive(xSemaphore); + } + + time(&tsNow); + if (llabs(tsNow - tsLast) >= TIME_TO_REPORT_S) { + tsLast = tsNow; + /* reset pulse count and start value */ + power_measure_start_energy_calculation(); + } + } + vTaskDelete(NULL); +} + +esp_err_t power_measure_init(power_measure_init_config_t* config) +{ + ESP_LOGI(TAG, "Power_Measure Version: %d.%d.%d", POWER_MEASURE_VER_MAJOR, POWER_MEASURE_VER_MINOR, POWER_MEASURE_VER_PATCH); + esp_err_t err = ESP_FAIL; + esp_err_t ret = ESP_OK; + + ESP_RETURN_ON_ERROR(config == NULL, TAG, "Invalid argument"); + ESP_RETURN_ON_ERROR(g_config != NULL, TAG, "Has been initialized."); + + g_config = calloc(1, sizeof(power_measure_init_config_t)); + ESP_RETURN_ON_ERROR(g_config == NULL, TAG, "calloc fail"); + + g_value = calloc(1, sizeof(power_measure_live_value_group_t)); + ESP_RETURN_ON_ERROR(g_value == NULL, TAG, "calloc fail"); + + memcpy(&g_config->chip_config, &config->chip_config, sizeof(power_measure_init_config_t)); + /* driver init */ + ESP_GOTO_ON_ERROR(g_config->chip_config.type >= CHIP_MAX, EXIT, TAG, "Unsupported chip type"); + if (config->chip_config.type == CHIP_BL0937) { + err = bl0937_init(g_config->chip_config); + } + ESP_GOTO_ON_ERROR(err != ESP_OK, EXIT, TAG, "init fail"); + + BaseType_t ret_pd = xTaskCreate(power_measure_task, "power_measure_task", 4096, NULL, 5, &g_task_handle); + ESP_GOTO_ON_ERROR(ret_pd != pdTRUE, EXIT, TAG, "init fail"); + + g_init_done = true; + ESP_LOGI(TAG, "Power measure initialization done."); + return ret; + +EXIT: + clear_up(); + return ESP_FAIL; +} + +esp_err_t power_measure_deinit(void) +{ + ESP_LOGW(TAG, "Measure DeInit."); + clear_up(); + g_init_done = false; + return ESP_OK; +} + +esp_err_t power_measure_get_voltage(float* voltage) +{ + ESP_RETURN_ON_ERROR(!g_init_done, TAG, "power measure deinit"); + ESP_RETURN_ON_ERROR(g_config == NULL || voltage == NULL, TAG, "Invalid argument"); + *voltage = g_value->voltage; + return ESP_OK; +} + +esp_err_t power_measure_get_current(float* current) +{ + ESP_RETURN_ON_ERROR(!g_init_done, TAG, "power measure deinit"); + ESP_RETURN_ON_ERROR(g_config == NULL || current == NULL, TAG, "Invalid argument"); + *current = g_value->current; + return ESP_OK; +} + +esp_err_t power_measure_get_active_power(float* active_power) +{ + ESP_RETURN_ON_ERROR(!g_init_done, TAG, "power measure deinit"); + ESP_RETURN_ON_ERROR(g_config == NULL || active_power == NULL, TAG, "Invalid argument"); + *active_power = g_value->power; + return ESP_OK; +} + +esp_err_t power_measure_get_power_factor(float* power_factor) +{ + ESP_RETURN_ON_ERROR(!g_init_done, TAG, "power measure deinit"); + ESP_RETURN_ON_ERROR(g_config == NULL || power_factor == NULL, TAG, "Invalid argument"); + *power_factor = g_value->power_factor; + return ESP_OK; +} + +esp_err_t power_measure_get_energy(float* energy) +{ + ESP_RETURN_ON_ERROR(!g_init_done, TAG, "power measure deinit"); + ESP_RETURN_ON_ERROR(g_config == NULL || energy == NULL, TAG, "Invalid argument"); + ESP_RETURN_ON_ERROR(!g_config->enable_energy_detection, TAG, "Invalid state"); + *energy = accumulated_value + start_value; + return ESP_OK; +} + +esp_err_t power_measure_start_energy_calculation(void) +{ + ESP_RETURN_ON_ERROR(!g_init_done, TAG, "power measure deinit"); + esp_err_t ret = ESP_OK; + reset_energe(); + g_config->enable_energy_detection = true; + return ret; +} + +esp_err_t power_measure_stop_energy_calculation(void) +{ + ESP_RETURN_ON_ERROR(!g_init_done, TAG, "power measure deinit"); + g_config->enable_energy_detection = false; + return ESP_OK; +} + +esp_err_t power_measure_reset_energy_calculation(void) +{ + ESP_RETURN_ON_ERROR(!g_init_done, TAG, "power measure deinit"); + accumulated_value = start_value = 0; + reset_energe(); + return ESP_OK; +} + +esp_err_t power_measure_calibration_factor_reset(void) +{ + esp_err_t ret = ESP_OK; + bl0937_multiplier_init(); + return ret; +} + +esp_err_t power_measure_start_calibration(calibration_parameter_t* para) +{ + ESP_RETURN_ON_ERROR(!g_init_done, TAG, "power measure deinit"); + ESP_RETURN_ON_ERROR(para == NULL, TAG, "Invalid argument"); + bl0937_expected_voltage(para->standard_voltage); + bl0937_expected_current(para->standard_current); + bl0937_expected_active_power(para->standard_power); + return ESP_OK; +} + +esp_err_t power_measure_get_calibration_factor(calibration_factor_t* factor) +{ + ESP_RETURN_ON_ERROR(!g_init_done, TAG, "power measure deinit"); + ESP_RETURN_ON_ERROR(factor == NULL, TAG, "Invalid argument"); + factor->ki = bl0937_get_current_multiplier(); + factor->ku = bl0937_get_voltage_multiplier(); + factor->kp = bl0937_get_power_multiplier(); + return ESP_OK; +} diff --git a/components/sensors/power_measure/priv_include/esp_bl0937.h b/components/sensors/power_measure/priv_include/esp_bl0937.h new file mode 100644 index 000000000..85713b2f5 --- /dev/null +++ b/components/sensors/power_measure/priv_include/esp_bl0937.h @@ -0,0 +1,65 @@ +/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "power_measure.h" + +/** + * @brief Internal voltage reference value + */ +#define V_REF_BL0937 1.218 + +/** + * @brief The factor of a 1mOhm resistor that allows a ~30A max measurement + */ +#define R_CURRENT_BL0937 (0.002) + +/** + * @brief This is the factor of a voltage divider of 6x 470K upstream and 1k downstream + */ +#define R_VOLTAGE_BL0937 ((2000) + 1) + +/** + * @brief Frequency of the bl0937 internal clock + */ +#define F_OSC_BL0937 (2000000) + +/** + * @brief This value is purely experimental + * Higher values allow for a better precision but reduce sampling rate + * Lower values increase sampling rate but reduce precision + * Values below 0.5s are not recommended since current and voltage output will have no time to stabilise + */ +#define PULSE_TIMEOUT_US (1000000l) + +/** + * @brief CF1 mode + */ +typedef enum { + MODE_CURRENT = 0, /*!< CURRENT MODE */ + MODE_VOLTAGE /*!< VOLTAGE MODE */ +} bl0937_mode_t; + +esp_err_t bl0937_init(chip_config_t config); +float bl0937_get_energy(); +float bl0937_get_voltage(); +float bl0937_get_current(); +float bl0937_get_active_power(); +float bl0937_get_power_factor(); + +void bl0937_multiplier_init(); +void bl0937_expected_voltage(float value); +void bl0937_expected_current(float value); +void bl0937_expected_active_power(float value); + +float bl0937_get_current_multiplier(); +float bl0937_get_voltage_multiplier(); +float bl0937_get_power_multiplier(); + +void bl0937_set_current_multiplier(float current_multiplier); +void bl0937_set_voltage_multiplier(float voltage_multiplier); +void bl0937_set_power_multiplier(float power_multiplier); +void reset_energe(void); diff --git a/components/sensors/power_measure/test_apps/CMakeLists.txt b/components/sensors/power_measure/test_apps/CMakeLists.txt new file mode 100644 index 000000000..abd9c9d12 --- /dev/null +++ b/components/sensors/power_measure/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" + "../../power_measure") +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(test_power_measure) diff --git a/components/sensors/power_measure/test_apps/main/CMakeLists.txt b/components/sensors/power_measure/test_apps/main/CMakeLists.txt new file mode 100644 index 000000000..59b620c86 --- /dev/null +++ b/components/sensors/power_measure/test_apps/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "test_power_measure.c" "../../power_measure.c" "../../esp_bl0937.c" + INCLUDE_DIRS "." + INCLUDE_DIRS "../../include") diff --git a/components/sensors/power_measure/test_apps/main/test_power_measure.c b/components/sensors/power_measure/test_apps/main/test_power_measure.c new file mode 100644 index 000000000..b58b46d26 --- /dev/null +++ b/components/sensors/power_measure/test_apps/main/test_power_measure.c @@ -0,0 +1,185 @@ +/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "power_measure.h" +#include "nvs_flash.h" +#include "nvs.h" +#include "unity.h" + +#define TEST_MEMORY_LEAK_THRESHOLD (-400) +#define BL0937_CF_GPIO GPIO_NUM_3 // CF pin +#define BL0937_SEL_GPIO GPIO_NUM_4 // SEL pin +#define BL0937_CF1_GPIO GPIO_NUM_7 // CF1 pin + +static const char *TAG = "PowerMeasureTest"; + +static size_t before_free_8bit; +static size_t before_free_32bit; +// Default calibration factors +#define DEFAULT_KI 1.0f +#define DEFAULT_KU 1.0f +#define DEFAULT_KP 1.0f + +TEST_CASE("Power Measurement Initialization Test", "[power_measure]") +{ + power_measure_init_config_t config = { + .chip_config = { + .type = CHIP_BL0937, + .sel_gpio = BL0937_SEL_GPIO, + .cf1_gpio = BL0937_CF1_GPIO, + .cf_gpio = BL0937_CF_GPIO, + .sampling_resistor = 0.001f, + .divider_resistor = 1981.0f, + .factor = { .ki = DEFAULT_KI, .ku = DEFAULT_KU, .kp = DEFAULT_KP } + }, + .overcurrent = 15, + .overvoltage = 260, + .undervoltage = 180, + .enable_energy_detection = true + }; + + esp_err_t ret = power_measure_init(&config); + TEST_ASSERT_EQUAL(ESP_OK, ret); + TEST_ASSERT_EQUAL(ESP_OK, power_measure_deinit()); +} + +TEST_CASE("Power Measurement Get Voltage Test", "[power_measure]") +{ + power_measure_init_config_t config = { + .chip_config = { + .type = CHIP_BL0937, + .sel_gpio = BL0937_SEL_GPIO, + .cf1_gpio = BL0937_CF1_GPIO, + .cf_gpio = BL0937_CF_GPIO, + .sampling_resistor = 0.001f, + .divider_resistor = 1981.0f, + .factor = { .ki = DEFAULT_KI, .ku = DEFAULT_KU, .kp = DEFAULT_KP } + }, + .overcurrent = 15, + .overvoltage = 260, + .undervoltage = 180, + .enable_energy_detection = true + }; + + esp_err_t ret = power_measure_init(&config); + TEST_ASSERT_EQUAL(ESP_OK, ret); + float voltage = 0.0f; + esp_err_t ret_v = power_measure_get_voltage(&voltage); + TEST_ASSERT_EQUAL(ESP_OK, ret_v); + ESP_LOGI(TAG, "Voltage: %.2f V", voltage); + power_measure_deinit(); +} + +TEST_CASE("Power Measurement Get Current Test", "[power_measure]") +{ + power_measure_init_config_t config = { + .chip_config = { + .type = CHIP_BL0937, + .sel_gpio = BL0937_SEL_GPIO, + .cf1_gpio = BL0937_CF1_GPIO, + .cf_gpio = BL0937_CF_GPIO, + .sampling_resistor = 0.001f, + .divider_resistor = 1981.0f, + .factor = { .ki = DEFAULT_KI, .ku = DEFAULT_KU, .kp = DEFAULT_KP } + }, + .overcurrent = 15, + .overvoltage = 260, + .undervoltage = 180, + .enable_energy_detection = true + }; + + esp_err_t ret = power_measure_init(&config); + TEST_ASSERT_EQUAL(ESP_OK, ret); + float current = 0.0f; + esp_err_t ret_c = power_measure_get_current(¤t); + TEST_ASSERT_EQUAL(ESP_OK, ret_c); + ESP_LOGI(TAG, "Current: %.2f A", current); + power_measure_deinit(); +} + +TEST_CASE("Power Measurement Get Active Power Test", "[power_measure]") +{ + power_measure_init_config_t config = { + .chip_config = { + .type = CHIP_BL0937, + .sel_gpio = BL0937_SEL_GPIO, + .cf1_gpio = BL0937_CF1_GPIO, + .cf_gpio = BL0937_CF_GPIO, + .sampling_resistor = 0.001f, + .divider_resistor = 1981.0f, + .factor = { .ki = DEFAULT_KI, .ku = DEFAULT_KU, .kp = DEFAULT_KP } + }, + .overcurrent = 15, + .overvoltage = 260, + .undervoltage = 180, + .enable_energy_detection = true + }; + + esp_err_t ret = power_measure_init(&config); + TEST_ASSERT_EQUAL(ESP_OK, ret); + float power = 0.0f; + esp_err_t ret_p = power_measure_get_active_power(&power); + TEST_ASSERT_EQUAL(ESP_OK, ret_p); + ESP_LOGI(TAG, "Power: %.2f W", power); + power_measure_deinit(); +} + +TEST_CASE("Power Measurement Get Energy Test", "[power_measure]") +{ + power_measure_init_config_t config = { + .chip_config = { + .type = CHIP_BL0937, + .sel_gpio = BL0937_SEL_GPIO, + .cf1_gpio = BL0937_CF1_GPIO, + .cf_gpio = BL0937_CF_GPIO, + .sampling_resistor = 0.001f, + .divider_resistor = 1981.0f, + .factor = { .ki = DEFAULT_KI, .ku = DEFAULT_KU, .kp = DEFAULT_KP } + }, + .overcurrent = 15, + .overvoltage = 260, + .undervoltage = 180, + .enable_energy_detection = true + }; + + esp_err_t ret = power_measure_init(&config); + TEST_ASSERT_EQUAL(ESP_OK, ret); + float energy = 0.0f; + esp_err_t ret_e = power_measure_get_energy(&energy); + TEST_ASSERT_EQUAL(ESP_OK, ret_e); + ESP_LOGI(TAG, "Energy: %.2f Kw/h", energy); + power_measure_deinit(); +} + +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("POWER MEASURE TEST \n"); + unity_run_menu(); +} diff --git a/components/sensors/power_measure/test_apps/pytest_power_measure.py b/components/sensors/power_measure/test_apps/pytest_power_measure.py new file mode 100644 index 000000000..bc58b0287 --- /dev/null +++ b/components/sensors/power_measure/test_apps/pytest_power_measure.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +''' +Steps to run these cases: +- Build + - . ${IDF_PATH}/export.sh + - pip install idf_build_apps + - python tools/build_apps.py components/sensors/power_measure/test_apps -t esp32c3 +- Test + - pip install -r tools/requirements/requirement.pytest.txt + - pytest components/sensors/power_measure/test_apps --target esp32c3 +''' + +import pytest +from pytest_embedded import Dut + +@pytest.mark.target('esp32c3') +@pytest.mark.env('generic') +@pytest.mark.parametrize( + 'config', + [ + 'defaults', + ], +) +def test_power_measure(dut: Dut)-> None: + dut.expect_exact('Press ENTER to see the list of tests.') + dut.write('*') + dut.expect_unity_test_output(timeout = 300) diff --git a/components/sensors/power_measure/test_apps/sdkconfig.defaults b/components/sensors/power_measure/test_apps/sdkconfig.defaults new file mode 100644 index 000000000..fe5fab9b1 --- /dev/null +++ b/components/sensors/power_measure/test_apps/sdkconfig.defaults @@ -0,0 +1,13 @@ +# For IDF 5.0 +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 + +# For IDF4.4 +CONFIG_ESP32S2_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP_TASK_WDT=n + +CONFIG_FATFS_LONG_FILENAMES=y +CONFIG_FATFS_LFN_STACK=y diff --git a/docs/Doxyfile b/docs/Doxyfile index 29ba977ff..fb498fe9c 100644 --- a/docs/Doxyfile +++ b/docs/Doxyfile @@ -48,6 +48,7 @@ INPUT = \ $(PROJECT_PATH)/components/ir/ir_learn/include/ir_learn.h \ $(PROJECT_PATH)/components/keyboard_button/include/keyboard_button.h \ $(PROJECT_PATH)/components/knob/include/iot_knob.h \ + $(PROJECT_PATH)/components/sensors/power_measure/include/power_measure.h \ $(PROJECT_PATH)/components/led/led_indicator/include/led_indicator.h \ $(PROJECT_PATH)/components/led/lightbulb_driver/include/lightbulb.h \ $(PROJECT_PATH)/components/motor/esp_sensorless_bldc_control/control/include/bldc_control_param.h \ diff --git a/docs/en/sensors/index.rst b/docs/en/sensors/index.rst index fa908eca7..c3ab6c926 100644 --- a/docs/en/sensors/index.rst +++ b/docs/en/sensors/index.rst @@ -14,3 +14,4 @@ Sensors Gesture sensor NTC Sensor Power Monitor + Power Measure diff --git a/docs/en/sensors/power_measure.rst b/docs/en/sensors/power_measure.rst new file mode 100644 index 000000000..cba668c5e --- /dev/null +++ b/docs/en/sensors/power_measure.rst @@ -0,0 +1,23 @@ +**Power Measure** +================== + +:link_to_translation:`zh_CN:[中文]` + +This example demonstrates how to use the BL0937 power measurement chip to detect electrical parameters such as voltage, current, active power, and energy consumption. + +Adapted Products +----------------------- + ++----------+----------------------------------------------------------------------------------------------+------------+-------------------------------------------------------------------------------------------------------------------------+-----+ +| Name | Function | Vendor | Datasheet | HAL | ++==========+==============================================================================================+============+=========================================================================================================================+=====+ +| BL0937 | detect electrical parameters such as voltage, current, active power, and energy consumption | BELLING | `BL0937 Datasheet `_ | x | ++----------+----------------------------------------------------------------------------------------------+------------+-------------------------------------------------------------------------------------------------------------------------+-----+ + + +API Reference +-------------------- + +The following API implements hardware abstraction for power measure. Users can directly call this layer of code to write sensor applications. + +.. include-build-file:: inc/power_measure.inc \ No newline at end of file diff --git a/docs/zh_CN/sensors/index.rst b/docs/zh_CN/sensors/index.rst index 932c42a42..9784ce3fc 100644 --- a/docs/zh_CN/sensors/index.rst +++ b/docs/zh_CN/sensors/index.rst @@ -14,3 +14,4 @@ 手势传感器 热敏电阻传感器 功率监视器 + 功率测量 diff --git a/docs/zh_CN/sensors/power_measure.rst b/docs/zh_CN/sensors/power_measure.rst new file mode 100644 index 000000000..983434da8 --- /dev/null +++ b/docs/zh_CN/sensors/power_measure.rst @@ -0,0 +1,23 @@ +**功率测量** +================== + +:link_to_translation:`en:[English]` + +本示例演示了如何使用BL0937功率测量芯片来检测电压、电流、有功功率和能耗等电气参数。使用 FreeRTOS 在ESP32上实现,展示了如何配置BL0937功率测量芯片并与开发芯片连接。该示例初始化功率测量系统、获取各种参数并定期记录。 + +适配列表 +----------------------- + ++--------+--------------------------------------------------+---------+---------------------------------------------------------------------------------------------------------------------+------------+ +| 名称 | 功能 | 供应商 | 规格书 | 硬件抽象层 | ++========+==================================================+=========+=====================================================================================================================+============+ +| BL0937 | 检测电量参数,例如电压、电流以及功率和消耗的电量 | BELLING | `BL0937 Datasheet `_ | x | ++--------+--------------------------------------------------+---------+---------------------------------------------------------------------------------------------------------------------+------------+ + + +API 参考 +-------------------- + +The following API implements hardware abstraction for power measure. Users can directly call this layer of code to write sensor applications. + +.. include-build-file:: inc/power_measure.inc \ No newline at end of file diff --git a/examples/.build-rules.yml b/examples/.build-rules.yml index 9f1b54a47..62fdeb6d3 100644 --- a/examples/.build-rules.yml +++ b/examples/.build-rules.yml @@ -183,6 +183,10 @@ examples/get-started/knob_power_save: enable: - if: INCLUDE_DEFAULT == 1 +examples/sensors/power_measure: + enable: + - if: INCLUDE_DEFAULT == 1 + examples/hmi/perf_benchmark: enable: - if: IDF_TARGET in ["esp32","esp32s3","esp32c3"] diff --git a/examples/sensors/power_measure/CMakeLists.txt b/examples/sensors/power_measure/CMakeLists.txt new file mode 100644 index 000000000..48b48f633 --- /dev/null +++ b/examples/sensors/power_measure/CMakeLists.txt @@ -0,0 +1,8 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five 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) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(power_measure) diff --git a/examples/sensors/power_measure/README.md b/examples/sensors/power_measure/README.md new file mode 100644 index 000000000..3db025311 --- /dev/null +++ b/examples/sensors/power_measure/README.md @@ -0,0 +1,97 @@ +# Power Measurement Example + +## Overview + +This example demonstrates how to use the **BL0937** power measurement chip to detect electrical parameters such as voltage, current, active power, and energy consumption. It is implemented for **ESP32** using FreeRTOS, and shows how to configure and interface with the BL0937 power measurement chip. The example initializes the power measurement system, fetches various parameters, and logs them at regular intervals. + +This example supports the **BL0937** power measurement chip, which is capable of measuring: + +1. **Voltage** +2. **Current** +3. **Active Power** +4. **Energy** + +The primary goal is to demonstrate how to configure the hardware pins, initialize the power measurement system, and retrieve the data from the chip. + +## Features + +* Measures **voltage** , **current** , **active power** , and **energy** . +* Configures **BL0937** power measurement chip. +* Supports overcurrent, overvoltage, and undervoltage protection. +* Energy detection is enabled for accurate readings. +* Regularly fetches power readings every second and logs them. + +## Hardware Requirements + +The example uses the **BL0937** power measurement chip. To connect it, the following pins must be configured on the ESP32: + +| Variable | GPIO Pin | Chip Pin | +| ------------------- | -------------- | -------- | +| `BL0937_CF_GPIO` | `GPIO_NUM_3` | CF Pin | +| `BL0937_SEL_GPIO` | `GPIO_NUM_4` | SEL Pin | +| `BL0937_CF1_GPIO` | `GPIO_NUM_7` | CF1 Pin | + +Make sure that these GPIO pins are correctly connected to the respective pins on the **BL0937** chip in your hardware setup. + +## Software Requirements + +* ESP-IDF (Espressif IoT Development Framework) +* FreeRTOS for task management +* Power measurement driver (`power_measure.h`) + +## How It Works + +The `app_main()` function in the [power_measure_example.c](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) file is the entry point for the application. Here's how it works: + +1. **Initialization of Calibration Factors** : + +* The application retrieves the default calibration factors for the power measurement chip using the [power_measure_get_calibration_factor()](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) function. +* Default calibration factors (`DEFAULT_KI`, `DEFAULT_KU`, `DEFAULT_KP`) are used for the measurement. + +2. **Configuration of Power Measurement System** : + +* The [power_measure_init_config_t](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) structure is configured with the chip’s GPIO pins, resistor values, and calibration factors. +* Specific thresholds for overcurrent, overvoltage, and undervoltage are set, and energy detection is enabled. + +3. **Fetching Measurement Data** : + +* The application enters a loop where it repeatedly fetches and logs the following parameters every second: + * **Voltage** ([power_measure_get_voltage](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)) + * **Current** ([power_measure_get_current](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)) + * **Active Power** ([power_measure_get_active_power](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)) + * **Energy** ([power_measure_get_energy](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)) + +4. **Error Handling** : + +* If fetching any of the measurements fails, an error message is logged using `ESP_LOGE`. + +5. **De-initialization** : + +* After exiting the loop, the power measurement system is de-initialized using [power_measure_deinit()](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html). + +## Configuration + +You can modify the following parameters based on your specific setup: + +* **GPIO Pin Definitions** : If you're using different GPIO pins for **CF** , **SEL** , or **CF1** , modify the respective `#define` values. +* **Calibration Factors** : Update the default calibration factors to match the specifications of your connected hardware for more accurate measurements. +* **Thresholds** : Adjust the `overcurrent`, `overvoltage`, and `undervoltage` settings to suit your application's safety limits. + +## Logging + +The application uses `ESP_LOGI` for informational logging and `ESP_LOGE` for error logging. The log messages will show up in the serial monitor or logging console. + +Example log output: + +**I (12345) PowerMeasureExample: Voltage: 230.15 V** + +**I (12346) PowerMeasureExample: Current: 0.45 A** + +**I (12347) PowerMeasureExample: Power: 103.35 W** + +**I (12348) PowerMeasureExample: Energy: 0.12 Kw/h** + +## Troubleshooting + +1. **Failed Initialization** : If the initialization fails, ensure that all GPIO pins are correctly defined and connected to the **BL0937** chip. +2. **Measurement Failures** : If the measurements fail (e.g., voltage, current), verify that the **BL0937** chip is properly powered and communicating with the ESP32. diff --git a/examples/sensors/power_measure/README_CN.md b/examples/sensors/power_measure/README_CN.md new file mode 100644 index 000000000..65a15f4e0 --- /dev/null +++ b/examples/sensors/power_measure/README_CN.md @@ -0,0 +1,96 @@ +# 功率测量示例 + +## 概述 + +本示例演示了如何使用**BL0937**功率测量芯片来检测电压、电流、有功功率和能耗等电气参数。使用 FreeRTOS 在 **ESP32** 上实现,展示了如何配置**BL0937**功率测量芯片并与开发芯片连接。该示例初始化功率测量系统、获取各种参数并定期记录。 + +本示例目前支持**BL0937**功率测量芯片,它能够测量以下参数 + +1. **电压** +2. **电流** +3. **有功功率** +4. **能量** + +项目的主要目的是演示如何配置硬件引脚、初始化功率测量系统以及从芯片中获取数据。 + +## 功能 + +* 测量**电压**、**电流**、**有功功率**和**能量**。 +* 配置**BL0937**功率测量芯片。 +* 支持过流、过压和欠压保护。 +* 启用能量检测以获得准确读数。 +* 每秒定期获取功率读数并记录。 + +## 硬件要求 + +本示例使用**BL0937**功率测量芯片。要连接该芯片,须在 ESP32 上配置以下引脚: + +| 变量 | GPIO 引脚 | 芯片引脚 | +| ------------------- | -------------- | -------- | +| `BL0937_CF_GPIO` | `GPIO_NUM_3` | CF 引脚 | +| `BL0937_SEL_GPIO` | `GPIO_NUM_4` | SEL 引脚 | +| `BL0937_CF1_GPIO` | `GPIO_NUM_7` | CF1 引脚 | + +确保这些 GPIO 引脚正确连接到硬件设置中**BL0937**芯片上的相应引脚。 + +## 软件要求 + +* ESP-IDF +* 用于任务管理的FreeRTOS + +## 工作原理 + +power_measure_example.c文件中的 `app_main()` 函数是应用程序的入口。下面是它的工作原理: + +1. **校准因子初始化** : + +* 应用程序使用 [power_measure_get_calibration_factor()](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 函数获取功率测量芯片的默认校准因子。 +* 测量时使用默认校准因子 (`DEFAULT_KI`, `DEFAULT_KU`, `DEFAULT_KP`)。 + +2. **功率测量系统配置** : + +* power_measure_init_config_t 结构配置了芯片的 GPIO 引脚、电阻值和校准因子。 +* 设置过流、过压和欠压的特定阈值,并选择是否启用能量检测。 + +3. **获取测量数据** : + +* 应用程序进入一个循环,每秒重复获取并记录以下参数: +* **电压** ([power_measure_get_voltage](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)) +* **Current** ([power_measure_get_current](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)) +* **有功功率** ([power_measure_get_active_power](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)) +* **Energy** ([power_measure_get_energy](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html)) + +4. **错误处理** : + +* 如果获取任何测量值失败,将使用 `ESP_LOGE`记录错误信息。 + +5. **释放** : + +* 退出循环后,功率测量系统将使用 [power_measure_deinit()](vscode-file://vscode-app/snap/code/176/usr/share/code/resources/app/out/vs/code/electron-sandbox/workbench/workbench.html) 去初始化。 + +## 配置 + +您可以根据具体设置修改以下参数: + +* **GPIO引脚定义**:如果 **CF**、**SEL** 或 **CF1** 使用不同的 GPIO 引脚,请修改相应的 `#define`值。 +* **校准系数** :更新默认校准因子,使其与所连接硬件的规格相匹配,以实现更精确的测量。 +* **阈值** :调整 “过流”、“过压 ”和 “欠压 ”设置,以适应应用的安全限制。 + +## 日志 + +应用程序使用 `ESP_LOGI`记录信息日志,使用 `ESP_LOGE`记录错误日志。日志信息将显示在串行监视器或日志控制台中。 + +日志输出示例: + +**I (12345) PowerMeasureExample: 电压:230.15 V** + +**I (12346) PowerMeasureExample: 电流:0.45 A** + +**I (12347) PowerMeasureExample: 功率: 103.35 W** + +**I (12348) PowerMeasureExample: 能量: 0.12 Kw/h** + +## 故障排除 + +1. **初始化失败** : 如果初始化失败,请确保所有 GPIO 引脚都已正确定义并连接到 **BL0937**芯片。 +2. **测量失败** : 如果测量失败(如电压、电流),请检查 **BL0937** 芯片是否正确供电并与 ESP32 通信。 diff --git a/examples/sensors/power_measure/main/CMakeLists.txt b/examples/sensors/power_measure/main/CMakeLists.txt new file mode 100644 index 000000000..b48f80ae2 --- /dev/null +++ b/examples/sensors/power_measure/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "power_measure_example.c" + INCLUDE_DIRS ".") diff --git a/examples/sensors/power_measure/main/idf_component.yml b/examples/sensors/power_measure/main/idf_component.yml new file mode 100644 index 000000000..52bad0ada --- /dev/null +++ b/examples/sensors/power_measure/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + idf: ">=4.4" + power_measure: + override_path: "../../../../components/sensors/power_measure" diff --git a/examples/sensors/power_measure/main/power_measure_example.c b/examples/sensors/power_measure/main/power_measure_example.c new file mode 100644 index 000000000..4a58304bb --- /dev/null +++ b/examples/sensors/power_measure/main/power_measure_example.c @@ -0,0 +1,95 @@ +/* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "esp_log.h" + +#include "nvs_flash.h" +#include "nvs.h" +#include "driver/gpio.h" +#include "power_measure.h" + +#define BL0937_CF_GPIO GPIO_NUM_3 // CF pin +#define BL0937_SEL_GPIO GPIO_NUM_4 // SEL pin +#define BL0937_CF1_GPIO GPIO_NUM_7 // CF1 pin + +static const char *TAG = "PowerMeasureExample"; + +// Default calibration factors +#define DEFAULT_KI 1.0f +#define DEFAULT_KU 1.0f +#define DEFAULT_KP 1.0f + +void app_main(void) +{ + // Get calibration factors + calibration_factor_t factor; + power_measure_get_calibration_factor(&factor); + + // Initialize the power measure configuration + power_measure_init_config_t config = { + .chip_config = { + .type = CHIP_BL0937, // Set the chip type to BL0937 + .sel_gpio = BL0937_SEL_GPIO, // GPIO for SEL pin + .cf1_gpio = BL0937_CF1_GPIO, // GPIO for CF1 pin + .cf_gpio = BL0937_CF_GPIO, // GPIO for CF pin + .sampling_resistor = 0.001f, // Sampling resistor value + .divider_resistor = 1981.0f, // Divider resistor value + .factor = factor // Load calibration factor into chip_config + }, + .overcurrent = 15, // Set overcurrent threshold + .overvoltage = 260, // Set overvoltage threshold + .undervoltage = 180, // Set undervoltage threshold + .enable_energy_detection = true // Enable energy detection + }; + + // Initialize the power measure component + esp_err_t ret = power_measure_init(&config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize power measurement: %s", esp_err_to_name(ret)); + return; + } + + while (1) { + float voltage = 0.0f, current = 0.0f, power = 0.0f, energy = 0.0f; + + // Fetch the voltage measurement + ret = power_measure_get_voltage(&voltage); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Voltage: %.2f V", voltage); + } else { + ESP_LOGE(TAG, "Failed to get voltage: %s", esp_err_to_name(ret)); + } + + // Fetch the current measurement + ret = power_measure_get_current(¤t); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Current: %.2f A", current); + } else { + ESP_LOGE(TAG, "Failed to get current: %s", esp_err_to_name(ret)); + } + + // Fetch the active power measurement + ret = power_measure_get_active_power(&power); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Power: %.2f W", power); + } else { + ESP_LOGE(TAG, "Failed to get active power: %s", esp_err_to_name(ret)); + } + + // Fetch the energy measurement + ret = power_measure_get_energy(&energy); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Energy: %.2f Kw/h", energy); + } else { + ESP_LOGE(TAG, "Failed to get energy: %s", esp_err_to_name(ret)); + } + + vTaskDelay(pdMS_TO_TICKS(1000)); + } + power_measure_deinit(); +}