diff --git a/components/esp_driver_i2s/CMakeLists.txt b/components/esp_driver_i2s/CMakeLists.txt index cc39943ae6..2498fa6126 100644 --- a/components/esp_driver_i2s/CMakeLists.txt +++ b/components/esp_driver_i2s/CMakeLists.txt @@ -30,6 +30,10 @@ if(CONFIG_SOC_LP_I2S_SUPPORTED) list(APPEND srcs "lp_i2s.c" "lp_i2s_std.c" "lp_i2s_pdm.c") endif() +if(CONFIG_SOC_LP_I2S_SUPPORT_VAD) + list(APPEND srcs "lp_i2s_vad.c") +endif() + idf_component_register(SRCS ${srcs} INCLUDE_DIRS ${include} PRIV_REQUIRES esp_driver_gpio esp_pm esp_mm diff --git a/components/esp_driver_i2s/include/driver/lp_i2s_vad.h b/components/esp_driver_i2s/include/driver/lp_i2s_vad.h new file mode 100644 index 0000000000..ce52370cde --- /dev/null +++ b/components/esp_driver_i2s/include/driver/lp_i2s_vad.h @@ -0,0 +1,153 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "soc/soc_caps.h" +#include "esp_err.h" +#include "driver/i2s_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief State Machine + ┌──────────────────────────────────┐ + │ │ + ┌─────────────┤ speak-activity-listening-state │ ◄───────────────┐ + │ │ │ │ + │ └──────────────────────────────────┘ │ + │ ▲ │ + │ │ │ + │ │ │ + │ │ │ + │ │ │ +detected speak activity │ │ detected speak activity │ detected speak activity + >= │ │ >= │ >= +'speak_activity_thresh' │ │ 'min_speak_activity_thresh' │ 'max_speak_activity_thresh' + │ │ │ + │ │ && │ + │ │ │ + │ │ detected non-speak activity │ + │ │ < │ + │ │ 'non_speak_activity_thresh' │ + │ │ │ + │ │ │ + │ │ │ + │ │ │ + │ │ │ + │ ┌───────────┴─────────────────────┐ │ + │ │ │ │ + └───────────► │ speak-activity-detected-state ├─────────────────┘ + │ │ + └─┬───────────────────────────────┘ + │ + │ ▲ + │ │ + │ │ + │ │ detected speak activity + │ │ >= + │ │ 'min_speak_activity_thresh' + │ │ + │ │ && + │ │ + │ │ detected non-speak activity + │ │ < + └─────────────────────┘ 'non_speak_activity_thresh' +*/ + +/** + * @brief LP VAD peripheral + */ +typedef uint32_t lp_vad_t; + +/** + * @brief Type of VAD unit handle + */ +typedef struct vad_unit_ctx_t *vad_unit_handle_t; + +/** + * @brief LP VAD configurations + */ +typedef struct { + int init_frame_num; /**< Number of init frames that are used for VAD to denoise, this helps the VAD to decrease the accidental trigger ratio. + Note too big values may lead to voice activity miss */ + int min_energy_thresh; ///< Min energy threshold. + bool skip_band_energy_thresh; ///< Skip band energy threshold or not + + int speak_activity_thresh; /**< When in speak-activity-listening-state, if number of the detected speak activity is higher than this value, VAD runs into speak-activity-detected-state */ + + int non_speak_activity_thresh; /**< When in speak-activity-detected-state, if the number of the detected speak activity is higher than this value, but lower than `max_speak_activity_thresh`: + - if the number of the detected non-speak activity is higher than this value, VAD runs into speak-activity-listening-state + - if the number of the detected non-speak activity is lower than this value, VAD keeps in speak-activity-detected-state */ + + int min_speak_activity_thresh; /**< When in speak-activity-detected-state, if the number of the detected speak activity is higher than this value, but lower than `max_speak_activity_thresh`, + then the VAD state machine will depends on the value of `non_speak_activity_thresh` */ + + int max_speak_activity_thresh; /**< When in speak-activity-detected-state, if the number of the detected speak activity is higher than this value, VAD runs into speak-activity-listening-state */ +} lp_vad_config_t; + +typedef struct { + lp_i2s_chan_handle_t lp_i2s_chan; ///< LP I2S channel handle + lp_vad_config_t vad_config; ///< LP VAD config +} lp_vad_init_config_t; + +/** + * @brief New LP VAD unit + * @param[in] vad_id VAD id + * @param[in] init_config Initial configurations + * @param[out] ret_unit Unit handle + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_i2s_vad_new_unit(lp_vad_t vad_id, const lp_vad_init_config_t *init_config, vad_unit_handle_t *ret_unit); + +/** + * @brief Enable LP VAD + * + * @param[in] unit VAD handle + * @param[in] init_config Initial configurations + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_i2s_vad_enable(vad_unit_handle_t unit); + +/** + * @brief Disable LP VAD + * + * @param[in] unit VAD handle + * @param[in] init_config Initial configurations + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_i2s_vad_disable(vad_unit_handle_t unit); + +/** + * @brief Delete LP VAD unit + * @param[in] unit VAD handle + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_i2s_vad_del_unit(vad_unit_handle_t unit); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_i2s/include/esp_private/lp_i2s_private.h b/components/esp_driver_i2s/include/esp_private/lp_i2s_private.h new file mode 100644 index 0000000000..2785ca0263 --- /dev/null +++ b/components/esp_driver_i2s/include/esp_private/lp_i2s_private.h @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "hal/lp_i2s_hal.h" +#include "driver/i2s_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Get LP I2S soc handle + * + * @param[in] chan LP I2S channel handle + * + * @return LP I2S soc handle + */ +lp_i2s_soc_handle_t lp_i2s_get_soc_handle(lp_i2s_chan_handle_t chan); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_i2s/lp_i2s.c b/components/esp_driver_i2s/lp_i2s.c index 7084fc8947..0d5e43dd8d 100644 --- a/components/esp_driver_i2s/lp_i2s.c +++ b/components/esp_driver_i2s/lp_i2s.c @@ -25,6 +25,7 @@ #include "driver/lp_i2s.h" #include "esp_private/periph_ctrl.h" #include "esp_private/i2s_platform.h" +#include "esp_private/lp_i2s_private.h" #include "i2s_private.h" #include "soc/i2s_periph.h" @@ -329,3 +330,15 @@ static void IRAM_ATTR s_i2s_default_isr(void *arg) portYIELD_FROM_ISR(); } } + +/*--------------------------------------------------------------- + HELPERS +---------------------------------------------------------------*/ +lp_i2s_soc_handle_t lp_i2s_get_soc_handle(lp_i2s_chan_handle_t chan) +{ + if (!chan) { + return NULL; + } + + return chan->ctlr->hal.dev; +} diff --git a/components/esp_driver_i2s/lp_i2s_vad.c b/components/esp_driver_i2s/lp_i2s_vad.c new file mode 100644 index 0000000000..f1cc63559e --- /dev/null +++ b/components/esp_driver_i2s/lp_i2s_vad.c @@ -0,0 +1,112 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "soc/soc_caps.h" +#include "stdatomic.h" +#if SOC_LP_VAD_SUPPORTED +#include "esp_check.h" +#include "esp_err.h" +#include "driver/lp_i2s_vad.h" +#include "esp_heap_caps.h" +#include "hal/lp_i2s_ll.h" +#include "hal/lp_i2s_hal.h" +#include "esp_private/lp_i2s_private.h" + +#define LP_VAD_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) + +static const char *LP_VAD_TAG = "LP_VAD"; + +typedef enum { + VAD_FSM_INIT, + VAD_FSM_ENABLE, +} vad_fsm_t; + +typedef struct vad_unit_ctx_t { + lp_i2s_soc_handle_t hw; + lp_vad_t vad_id; + vad_fsm_t fsm; +} vad_unit_ctx_t; + +static atomic_bool s_vad_id_claimed[SOC_ADC_PERIPH_NUM] = {ATOMIC_VAR_INIT(false)}; + +static bool s_vad_claim(lp_vad_t vad_id) +{ + bool false_var = false; + return atomic_compare_exchange_strong(&s_vad_id_claimed[vad_id], &false_var, true); +} + +static bool s_vad_free(lp_vad_t vad_id) +{ + bool true_var = true; + return atomic_compare_exchange_strong(&s_vad_id_claimed[vad_id], &true_var, false); +} + +esp_err_t lp_i2s_vad_new_unit(lp_vad_t vad_id, const lp_vad_init_config_t *init_config, vad_unit_handle_t *ret_unit) +{ + esp_err_t ret = ESP_OK; + ESP_RETURN_ON_FALSE(init_config, ESP_ERR_INVALID_ARG, LP_VAD_TAG, "invalid arg"); + ESP_RETURN_ON_FALSE(init_config->lp_i2s_chan, ESP_ERR_INVALID_ARG, LP_VAD_TAG, "LP I2S not initialised"); + ESP_RETURN_ON_FALSE(init_config->vad_config.init_frame_num >= LP_VAD_LL_INIT_FRAME_MIN && init_config->vad_config.init_frame_num <= LP_VAD_LL_INIT_FRAME_MAX, ESP_ERR_INVALID_ARG, LP_VAD_TAG, "invalid init frame num"); + + bool success_claim = s_vad_claim(vad_id); + ESP_RETURN_ON_FALSE(success_claim, ESP_ERR_NOT_FOUND, LP_VAD_TAG, "vad%"PRId32" is already in use", vad_id); + + vad_unit_ctx_t *unit = heap_caps_calloc(1, sizeof(vad_unit_ctx_t), LP_VAD_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(unit, ESP_ERR_NO_MEM, err, LP_VAD_TAG, "no mem for unit"); + + unit->hw = lp_i2s_get_soc_handle(init_config->lp_i2s_chan); + ESP_LOGD(LP_VAD_TAG, "unit->hw: %p", unit->hw); + lp_vad_ll_set_init_frame_num(unit->hw, init_config->vad_config.init_frame_num); + lp_vad_ll_set_init_min_energy(unit->hw, init_config->vad_config.min_energy_thresh); + lp_vad_ll_set_speak_activity_thresh(unit->hw, init_config->vad_config.speak_activity_thresh); + lp_vad_ll_set_non_speak_activity_thresh(unit->hw, init_config->vad_config.non_speak_activity_thresh); + lp_vad_ll_set_min_speak_activity_thresh(unit->hw, init_config->vad_config.min_speak_activity_thresh); + lp_vad_ll_set_max_speak_activity_thresh(unit->hw, init_config->vad_config.max_speak_activity_thresh); + lp_vad_ll_skip_band_energy(unit->hw, init_config->vad_config.skip_band_energy_thresh); + unit->fsm = VAD_FSM_INIT; + *ret_unit = unit; + + return ESP_OK; +err: + bool success_free = s_vad_free(vad_id); + assert(success_free); + + return ret; +} + +esp_err_t lp_i2s_vad_enable(vad_unit_handle_t unit) +{ + ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, LP_VAD_TAG, "invalid arg"); + ESP_RETURN_ON_FALSE(unit->fsm == VAD_FSM_INIT, ESP_ERR_INVALID_STATE, LP_VAD_TAG, "The driver is enabled already"); + + lp_vad_ll_enable(unit->hw, true); + unit->fsm = VAD_FSM_ENABLE; + return ESP_OK; +} + +esp_err_t lp_i2s_vad_disable(vad_unit_handle_t unit) +{ + ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, LP_VAD_TAG, "invalid arg"); + ESP_RETURN_ON_FALSE(unit->fsm == VAD_FSM_ENABLE, ESP_ERR_INVALID_STATE, LP_VAD_TAG, "The driver is not enabled yet"); + + lp_vad_ll_enable(unit->hw, false); + unit->fsm = VAD_FSM_INIT; + return ESP_OK; +} + +esp_err_t lp_i2s_vad_del_unit(vad_unit_handle_t unit) +{ + ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, LP_VAD_TAG, "invalid arg"); + ESP_RETURN_ON_FALSE(unit->fsm == VAD_FSM_INIT, ESP_ERR_INVALID_STATE, LP_VAD_TAG, "The driver is still in enabled state"); + + bool success_free = s_vad_free(unit->vad_id); + ESP_RETURN_ON_FALSE(success_free, ESP_ERR_NOT_FOUND, LP_VAD_TAG, "vad%"PRId32" isn't in use", unit->vad_id); + + free(unit); + return ESP_OK; +} +#endif /* SOC_LP_VAD_SUPPORTED */ diff --git a/components/esp_driver_i2s/test_apps/lp_i2s/sdkconfig.ci.defaults b/components/esp_driver_i2s/test_apps/lp_i2s/sdkconfig.ci.defaults index 5e9f8e25bd..e69de29bb2 100644 --- a/components/esp_driver_i2s/test_apps/lp_i2s/sdkconfig.ci.defaults +++ b/components/esp_driver_i2s/test_apps/lp_i2s/sdkconfig.ci.defaults @@ -1 +0,0 @@ -CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y diff --git a/components/esp_hw_support/include/esp_private/esp_pmu.h b/components/esp_hw_support/include/esp_private/esp_pmu.h index 5b48194607..6d347788a2 100644 --- a/components/esp_hw_support/include/esp_private/esp_pmu.h +++ b/components/esp_hw_support/include/esp_private/esp_pmu.h @@ -50,6 +50,7 @@ typedef enum { #define RTC_SLEEP_USE_ADC_TESEN_MONITOR BIT(17) #define RTC_SLEEP_NO_ULTRA_LOW BIT(18) //!< Avoid using ultra low power in deep sleep, in which RTCIO cannot be used as input, and RTCMEM can't work under high temperature #define RTC_SLEEP_XTAL_AS_RTC_FAST BIT(19) +#define RTC_SLEEP_LP_PERIPH_USE_XTAL BIT(20) #if SOC_PM_SUPPORT_EXT0_WAKEUP #define RTC_EXT0_TRIG_EN PMU_EXT0_WAKEUP_EN //!< EXT0 wakeup @@ -109,6 +110,12 @@ typedef enum { #define RTC_LP_CORE_TRIG_EN 0 #endif //SOC_LP_CORE_SUPPORTED +#if SOC_LP_VAD_SUPPORTED +#define RTC_LP_VAD_TRIG_EN PMU_LP_I2S_WAKEUP_EN //!< LP VAD wakeup +#else +#define RTC_LP_VAD_TRIG_EN 0 +#endif //SOC_LP_VAD_SUPPORTED + #define RTC_XTAL32K_DEAD_TRIG_EN 0 // TODO #define RTC_BROWNOUT_DET_TRIG_EN 0 // TODO @@ -127,6 +134,7 @@ typedef enum { RTC_TOUCH_TRIG_EN | \ RTC_XTAL32K_DEAD_TRIG_EN | \ RTC_USB_TRIG_EN | \ + RTC_LP_VAD_TRIG_EN | \ RTC_BROWNOUT_DET_TRIG_EN) diff --git a/components/esp_hw_support/include/esp_private/esp_sleep_internal.h b/components/esp_hw_support/include/esp_private/esp_sleep_internal.h index df9c2a79e2..7805b71495 100644 --- a/components/esp_hw_support/include/esp_private/esp_sleep_internal.h +++ b/components/esp_hw_support/include/esp_private/esp_sleep_internal.h @@ -38,6 +38,7 @@ typedef enum { ESP_SLEEP_ULTRA_LOW_MODE, //!< In ultra low mode, 2uA is saved, but RTC memory can't use at high temperature, and RTCIO can't be used as INPUT. ESP_SLEEP_RTC_FAST_USE_XTAL_MODE, //!< The mode in which the crystal is used as the RTC_FAST clock source, need keep XTAL on in HP_SLEEP mode when ULP is working. ESP_SLEEP_DIG_USE_XTAL_MODE, //!< The mode requested by digital peripherals to keep XTAL clock on during sleep (both HP_SLEEP and LP_SLEEP mode). (!!! Only valid for lightsleep, will override the XTAL domain config by esp_sleep_pd_config) + ESP_SLEEP_LP_USE_XTAL_MODE, //!< The mode requested by lp peripherals to keep XTAL clock on during sleep. Only valid for lightsleep. ESP_SLEEP_MODE_MAX, } esp_sleep_sub_mode_t; diff --git a/components/esp_hw_support/include/esp_sleep.h b/components/esp_hw_support/include/esp_sleep.h index de25aeb268..76233f1d62 100644 --- a/components/esp_hw_support/include/esp_sleep.h +++ b/components/esp_hw_support/include/esp_sleep.h @@ -118,6 +118,7 @@ typedef enum { ESP_SLEEP_WAKEUP_COCPU, //!< Wakeup caused by COCPU int ESP_SLEEP_WAKEUP_COCPU_TRAP_TRIG, //!< Wakeup caused by COCPU crash ESP_SLEEP_WAKEUP_BT, //!< Wakeup caused by BT (light sleep only) + ESP_SLEEP_WAKEUP_VAD, //!< Wakeup caused by VAD } esp_sleep_source_t; /** @@ -179,6 +180,16 @@ esp_err_t esp_sleep_enable_ulp_wakeup(void); */ esp_err_t esp_sleep_enable_timer_wakeup(uint64_t time_in_us); +#if SOC_LP_VAD_SUPPORTED +/** + * @brief Enable wakeup by VAD + * + * @return + * - ESP_OK on success + */ +esp_err_t esp_sleep_enable_vad_wakeup(void); +#endif + #if SOC_TOUCH_SENSOR_SUPPORTED /** * @brief Enable wakeup by touch sensor diff --git a/components/esp_hw_support/port/esp32p4/pmu_sleep.c b/components/esp_hw_support/port/esp32p4/pmu_sleep.c index 97c112aa6b..b6be295d10 100644 --- a/components/esp_hw_support/port/esp32p4/pmu_sleep.c +++ b/components/esp_hw_support/port/esp32p4/pmu_sleep.c @@ -29,6 +29,7 @@ #include "hal/pmu_hal.h" #include "hal/psram_ctrlr_ll.h" #include "hal/lp_sys_ll.h" +#include "hal/clk_gate_ll.h" #include "esp_private/esp_pmu.h" #include "pmu_param.h" #include "esp_rom_sys.h" @@ -202,6 +203,10 @@ const pmu_sleep_config_t* pmu_sleep_config_default( config->analog.hp_sys.analog.dbias = HP_CALI_ACTIVE_DBIAS_DEFAULT; } + if (sleep_flags & RTC_SLEEP_LP_PERIPH_USE_XTAL) { + _clk_gate_ll_xtal_to_lp_periph_en(true); + } + config->power = power_default; pmu_sleep_param_config_t param_default = PMU_SLEEP_PARAM_CONFIG_DEFAULT(sleep_flags); config->param = *pmu_sleep_param_config_default(¶m_default, &power_default, sleep_flags, adjustment, slowclk_period, fastclk_period); diff --git a/components/esp_hw_support/sleep_modes.c b/components/esp_hw_support/sleep_modes.c index 0f36b970c4..22201a21ce 100644 --- a/components/esp_hw_support/sleep_modes.c +++ b/components/esp_hw_support/sleep_modes.c @@ -223,7 +223,7 @@ typedef struct { } domain[ESP_PD_DOMAIN_MAX]; portMUX_TYPE lock; uint64_t sleep_duration; - uint32_t wakeup_triggers : 15; + uint32_t wakeup_triggers : 20; #if SOC_PM_SUPPORT_EXT1_WAKEUP uint32_t ext1_trigger_mode : 22; // 22 is the maximum RTCIO number in all chips uint32_t ext1_rtc_gpio_mask : 22; @@ -924,6 +924,12 @@ static esp_err_t IRAM_ATTR esp_sleep_start(uint32_t pd_flags, esp_sleep_mode_t m sleep_flags |= RTC_SLEEP_XTAL_AS_RTC_FAST; } +#if SOC_LP_VAD_SUPPORTED + if (s_sleep_sub_mode_ref_cnt[ESP_SLEEP_LP_USE_XTAL_MODE] && !deep_sleep) { + sleep_flags |= RTC_SLEEP_LP_PERIPH_USE_XTAL; + } +#endif + #if CONFIG_ESP_SLEEP_DEBUG if (s_sleep_ctx != NULL) { s_sleep_ctx->sleep_flags = sleep_flags; @@ -1650,6 +1656,14 @@ esp_err_t esp_sleep_enable_timer_wakeup(uint64_t time_in_us) return ESP_OK; } +#if SOC_LP_VAD_SUPPORTED +esp_err_t esp_sleep_enable_vad_wakeup(void) +{ + s_config.wakeup_triggers |= RTC_LP_VAD_TRIG_EN; + return esp_sleep_sub_mode_config(ESP_SLEEP_LP_USE_XTAL_MODE, true); +} +#endif + static esp_err_t timer_wakeup_prepare(int64_t sleep_duration) { if (sleep_duration < 0) { @@ -2170,6 +2184,10 @@ esp_sleep_wakeup_cause_t esp_sleep_get_wakeup_cause(void) #if SOC_LP_CORE_SUPPORTED } else if (wakeup_cause & RTC_LP_CORE_TRIG_EN) { return ESP_SLEEP_WAKEUP_ULP; +#endif +#if SOC_LP_VAD_SUPPORTED + } else if (wakeup_cause & RTC_LP_VAD_TRIG_EN) { + return ESP_SLEEP_WAKEUP_VAD; #endif } else { return ESP_SLEEP_WAKEUP_UNDEFINED; @@ -2234,6 +2252,7 @@ int32_t* esp_sleep_sub_mode_dump_config(FILE *stream) { [ESP_SLEEP_ULTRA_LOW_MODE] = "ESP_SLEEP_ULTRA_LOW_MODE", [ESP_SLEEP_RTC_FAST_USE_XTAL_MODE] = "ESP_SLEEP_RTC_FAST_USE_XTAL_MODE", [ESP_SLEEP_DIG_USE_XTAL_MODE] = "ESP_SLEEP_DIG_USE_XTAL_MODE", + [ESP_SLEEP_LP_USE_XTAL_MODE] = "ESP_SLEEP_LP_USE_XTAL_MODE", }[mode], s_sleep_sub_mode_ref_cnt[mode] ? "ENABLED" : "DISABLED", s_sleep_sub_mode_ref_cnt[mode]); diff --git a/components/esp_hw_support/test_apps/.build-test-rules.yml b/components/esp_hw_support/test_apps/.build-test-rules.yml index b61d8878c6..a13010cf3a 100644 --- a/components/esp_hw_support/test_apps/.build-test-rules.yml +++ b/components/esp_hw_support/test_apps/.build-test-rules.yml @@ -42,6 +42,10 @@ components/esp_hw_support/test_apps/rtc_power_modes: temporary: true reason: the other targets are not tested yet +components/esp_hw_support/test_apps/vad_wakeup: + disable: + - if: SOC_LP_VAD_SUPPORTED != 1 + components/esp_hw_support/test_apps/wakeup_tests: disable: - if: IDF_TARGET in ["esp32c5", "esp32p4", "linux", "esp32c61"] diff --git a/components/esp_hw_support/test_apps/vad_wakeup/CMakeLists.txt b/components/esp_hw_support/test_apps/vad_wakeup/CMakeLists.txt new file mode 100644 index 0000000000..5036560011 --- /dev/null +++ b/components/esp_hw_support/test_apps/vad_wakeup/CMakeLists.txt @@ -0,0 +1,8 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(vad_wakeup) diff --git a/components/esp_hw_support/test_apps/vad_wakeup/README.md b/components/esp_hw_support/test_apps/vad_wakeup/README.md new file mode 100644 index 0000000000..f8ea707124 --- /dev/null +++ b/components/esp_hw_support/test_apps/vad_wakeup/README.md @@ -0,0 +1,3 @@ +| Supported Targets | ESP32-P4 | +| ----------------- | -------- | + diff --git a/components/esp_hw_support/test_apps/vad_wakeup/main/CMakeLists.txt b/components/esp_hw_support/test_apps/vad_wakeup/main/CMakeLists.txt new file mode 100644 index 0000000000..3a92a5d2bd --- /dev/null +++ b/components/esp_hw_support/test_apps/vad_wakeup/main/CMakeLists.txt @@ -0,0 +1,7 @@ +set(srcs "test_app_main.c" + "test_vad_wakeup.c") + +idf_component_register(SRCS ${srcs} + REQUIRES unity esp_driver_i2s esp_driver_uart ulp esp_timer + WHOLE_ARCHIVE + EMBED_FILES "test_vad_8k.pcm") diff --git a/components/esp_hw_support/test_apps/vad_wakeup/main/test_app_main.c b/components/esp_hw_support/test_apps/vad_wakeup/main/test_app_main.c new file mode 100644 index 0000000000..1d34b63e44 --- /dev/null +++ b/components/esp_hw_support/test_apps/vad_wakeup/main/test_app_main.c @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils.h" + +#define LEAKS (400) + + +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + unity_utils_evaluate_leaks_direct(LEAKS); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/esp_hw_support/test_apps/vad_wakeup/main/test_vad_8k.pcm b/components/esp_hw_support/test_apps/vad_wakeup/main/test_vad_8k.pcm new file mode 100644 index 0000000000..5b6c32a037 Binary files /dev/null and b/components/esp_hw_support/test_apps/vad_wakeup/main/test_vad_8k.pcm differ diff --git a/components/esp_hw_support/test_apps/vad_wakeup/main/test_vad_wakeup.c b/components/esp_hw_support/test_apps/vad_wakeup/main/test_vad_wakeup.c new file mode 100644 index 0000000000..2a245bfc6f --- /dev/null +++ b/components/esp_hw_support/test_apps/vad_wakeup/main/test_vad_wakeup.c @@ -0,0 +1,162 @@ +/* + * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_sleep.h" +#include "esp_log.h" +#include "driver/uart.h" +#include "driver/lp_i2s.h" +#include "driver/lp_i2s_std.h" +#include "driver/i2s_std.h" +#include "ulp_lp_core_lp_vad_shared.h" +#include "unity.h" +#include "esp_timer.h" + +#define TEST_I2S_FRAME_SIZE (128) // Frame numbers in every writing / reading +#define TEST_I2S_TRANS_SIZE (4096) // Trans size +#define TEST_LP_I2S_PIN_BCK 4 +#define TEST_LP_I2S_PIN_WS 5 +#define TEST_LP_I2S_PIN_DIN 6 + + +extern const uint8_t test_vad_pcm_start[] asm("_binary_test_vad_8k_pcm_start"); +extern const uint8_t test_vad_pcm_end[] asm("_binary_test_vad_8k_pcm_end"); +static const char *TAG = "TEST_VAD"; + +static void s_hp_i2s_config(void) +{ + esp_err_t ret = ESP_FAIL; + int pcm_size = test_vad_pcm_end - test_vad_pcm_start; + printf("pcm_size: %d\n", pcm_size); + + i2s_chan_handle_t tx_handle = NULL; + i2s_chan_config_t i2s_channel_config = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = 16, + .dma_frame_num = TEST_I2S_FRAME_SIZE, + .auto_clear = false, + }; + TEST_ESP_OK(i2s_new_channel(&i2s_channel_config, &tx_handle, NULL)); + + + i2s_std_config_t i2s_std_config = { + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = GPIO_NUM_7, + .ws = GPIO_NUM_8, + .dout = GPIO_NUM_21, + .din = -1, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + i2s_std_config.clk_cfg = (i2s_std_clk_config_t)I2S_STD_CLK_DEFAULT_CONFIG(16000); + i2s_std_config.slot_cfg = (i2s_std_slot_config_t)I2S_STD_PCM_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO); + TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &i2s_std_config)); + + + uint8_t *txbuf = (uint8_t *)heap_caps_calloc(1, TEST_I2S_TRANS_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT(txbuf); + + uint8_t *prebuf = (uint8_t *)heap_caps_calloc(1, TEST_I2S_TRANS_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT(prebuf); + + memcpy(prebuf, test_vad_pcm_start, TEST_I2S_TRANS_SIZE); + memcpy(txbuf, test_vad_pcm_start, TEST_I2S_TRANS_SIZE); + + for (int i = 0; i < TEST_I2S_TRANS_SIZE; i++) { + ESP_LOGD(TAG, "prebuf[%d]: %d", i, prebuf[i]); + ESP_LOGD(TAG, "txbuf[%d]: %d", i, txbuf[i]); + } + + size_t bytes_written = 0; + TEST_ESP_OK(i2s_channel_preload_data(tx_handle, prebuf, TEST_I2S_TRANS_SIZE, &bytes_written)); + + TEST_ESP_OK(i2s_channel_enable(tx_handle)); + + while (1) { + ret = i2s_channel_write(tx_handle, txbuf, TEST_I2S_TRANS_SIZE, &bytes_written, 0); + if (ret != ESP_OK && ret != ESP_ERR_TIMEOUT) { + TEST_ESP_OK(ret); + } + ESP_LOGD(TAG, "bytes_written: %d", bytes_written); + vTaskDelay(1); + } +} + +static void s_lp_vad_config(void) +{ + ESP_ERROR_CHECK(esp_sleep_enable_vad_wakeup()); + ESP_ERROR_CHECK(esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON)); + ESP_ERROR_CHECK(esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_ON)); + + lp_i2s_chan_handle_t rx_handle = NULL; + lp_i2s_chan_config_t config = { + .id = 0, + .role = I2S_ROLE_SLAVE, + .threshold = 512, + }; + TEST_ESP_OK(lp_i2s_new_channel(&config, NULL, &rx_handle)); + + lp_i2s_std_config_t lp_std_cfg = { + .pin_cfg = { + .bck = TEST_LP_I2S_PIN_BCK, + .ws = TEST_LP_I2S_PIN_WS, + .din = TEST_LP_I2S_PIN_DIN, + }, + }; + lp_std_cfg.slot_cfg = (lp_i2s_std_slot_config_t)LP_I2S_STD_PCM_SHORT_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO); + TEST_ESP_OK(lp_i2s_channel_init_std_mode(rx_handle, &lp_std_cfg)); + + // LP VAD Init + lp_vad_init_config_t init_config = { + .lp_i2s_chan = rx_handle, + .vad_config = { + .init_frame_num = 100, + .min_energy_thresh = 100, + .speak_activity_thresh = 10, + .non_speak_activity_thresh = 30, + .min_speak_activity_thresh = 3, + .max_speak_activity_thresh = 100, + }, + }; + TEST_ESP_OK(lp_core_lp_vad_init(0, &init_config)); + TEST_ESP_OK(lp_i2s_channel_enable(rx_handle)); + TEST_ESP_OK(lp_core_lp_vad_enable(0)); + + printf("Entering light sleep\n"); + /* To make sure the complete line is printed before entering sleep mode, + * need to wait until UART TX FIFO is empty: + */ + uart_wait_tx_idle_polling(CONFIG_ESP_CONSOLE_UART_NUM); + + /* Enter sleep mode */ + esp_light_sleep_start(); + + /* Determine wake up reason */ + const char* wakeup_reason; + switch (esp_sleep_get_wakeup_cause()) { + case ESP_SLEEP_WAKEUP_VAD: + wakeup_reason = "vad"; + break; + default: + wakeup_reason = "other"; + TEST_ASSERT(false); + break; + } + + ESP_LOGI(TAG, "wakeup, reason: %s", wakeup_reason); +} + +TEST_CASE_MULTIPLE_DEVICES("test LP VAD wakeup", "[vad][ignore][manual]", s_hp_i2s_config, s_lp_vad_config); diff --git a/components/esp_hw_support/test_apps/vad_wakeup/pytest_wakeup_vad.py b/components/esp_hw_support/test_apps/vad_wakeup/pytest_wakeup_vad.py new file mode 100644 index 0000000000..b4d9f85aff --- /dev/null +++ b/components/esp_hw_support/test_apps/vad_wakeup/pytest_wakeup_vad.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Unlicense OR CC0-1.0 +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32p4 +@pytest.mark.temp_skip_ci(targets=['esp32p4'], reason='lack of runners for now') +@pytest.mark.lp_i2s +def test_efuse(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/esp_hw_support/test_apps/vad_wakeup/sdkconfig.defaults b/components/esp_hw_support/test_apps/vad_wakeup/sdkconfig.defaults new file mode 100644 index 0000000000..cac3f9a01f --- /dev/null +++ b/components/esp_hw_support/test_apps/vad_wakeup/sdkconfig.defaults @@ -0,0 +1,8 @@ +CONFIG_ESP_TASK_WDT_INIT=n + +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_LP_CORE=y +CONFIG_ULP_COPROC_RESERVE_MEM=12000 +CONFIG_ULP_PANIC_OUTPUT_ENABLE=y + +CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y diff --git a/components/hal/esp32p4/include/hal/clk_gate_ll.h b/components/hal/esp32p4/include/hal/clk_gate_ll.h index 8c5fc05cfb..9a49fd41e0 100644 --- a/components/hal/esp32p4/include/hal/clk_gate_ll.h +++ b/components/hal/esp32p4/include/hal/clk_gate_ll.h @@ -14,6 +14,7 @@ extern "C" { #include #include "esp_attr.h" #include "soc/hp_sys_clkrst_struct.h" +#include "soc/lp_clkrst_struct.h" /** * Enable or disable the clock gate for ref_20m. @@ -75,6 +76,18 @@ FORCE_INLINE_ATTR void _clk_gate_ll_ref_240m_clk_en(bool enable) /// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance #define clk_gate_ll_ref_240m_clk_en(...) (void)__DECLARE_RCC_ATOMIC_ENV; _clk_gate_ll_ref_240m_clk_en(__VA_ARGS__) +/** + * Enable or disable the clock gate for xtal to lp periph + * @param enable Enable / disable + */ +FORCE_INLINE_ATTR void _clk_gate_ll_xtal_to_lp_periph_en(bool enable) +{ + LP_AON_CLKRST.lp_clk_en.xtal_clk_force_on = enable; +} +/// use a macro to wrap the function, force the caller to use it in a critical section +/// the critical section needs to declare the __DECLARE_RCC_ATOMIC_ENV variable in advance +#define clk_gate_ll_xtal_to_lp_periph_en(...) (void)__DECLARE_RCC_ATOMIC_ENV; _clk_gate_ll_xtal_to_lp_periph_en(__VA_ARGS__) + #ifdef __cplusplus } #endif diff --git a/components/hal/esp32p4/include/hal/lp_core_ll.h b/components/hal/esp32p4/include/hal/lp_core_ll.h index 27a9a0997f..eb2c57be31 100644 --- a/components/hal/esp32p4/include/hal/lp_core_ll.h +++ b/components/hal/esp32p4/include/hal/lp_core_ll.h @@ -28,7 +28,7 @@ extern "C" { #define LP_CORE_LL_WAKEUP_SOURCE_LP_BOD BIT(14) #define LP_CORE_LL_WAKEUP_SOURCE_ETM BIT(17) #define LP_CORE_LL_WAKEUP_SOURCE_LP_TIMER_1 BIT(18) -#define LP_CORE_LL_WAKEUP_SOURCE_LP_I2S BIT(19) +#define LP_CORE_LL_WAKEUP_SOURCE_LP_VAD BIT(19) #define LP_CORE_LL_WAKEUP_SOURCE_HP_CPU BIT(22) /* Use lp timer 1 as the normal wakeup timer, timer 0 is used by deep sleep */ diff --git a/components/hal/esp32p4/include/hal/lp_i2s_ll.h b/components/hal/esp32p4/include/hal/lp_i2s_ll.h index c9f0d5611b..23e81cd2a5 100644 --- a/components/hal/esp32p4/include/hal/lp_i2s_ll.h +++ b/components/hal/esp32p4/include/hal/lp_i2s_ll.h @@ -32,6 +32,8 @@ extern "C" { #define LP_I2S_LL_EVENT_RX_DONE_INT (1<<0) #define LP_I2S_LL_EVENT_RX_HUNG_INT_INT (1<<1) #define LP_I2S_LL_EVENT_RX_FIFOMEM_UDF_INT (1<<2) +#define LP_I2S_LL_EVENT_VAD_DONE_INT (1<<3) +#define LP_I2S_LL_EVENT_VAD_RESET_DONE_INT (1<<4) #define LP_I2S_LL_EVENT_RX_MEM_THRESHOLD_INT (1<<5) #define LP_I2S_LL_TDM_CH_MASK (0x03UL) @@ -709,9 +711,9 @@ static inline uint32_t lp_i2s_ll_get_intr_status_reg_addr(lp_i2s_dev_t *hw) /** * @brief Enable LP I2S RX channel interrupt * - * @param hw LP I2S hardware instance - * @param mask mask - * @param enable enable or disable + * @param[in] hw LP I2S hardware instance + * @param[in] mask mask + * @param[in] enable enable or disable */ static inline void lp_i2s_ll_rx_enable_interrupt(lp_i2s_dev_t *hw, uint32_t mask, bool enable) { @@ -727,8 +729,8 @@ static inline void lp_i2s_ll_rx_enable_interrupt(lp_i2s_dev_t *hw, uint32_t mask /** * @brief Clear LP I2S RX channel interrupt * - * @param hw LP I2S hardware instance - * @param mask mask + * @param[in] hw LP I2S hardware instance + * @param[in] mask mask */ __attribute__((always_inline)) static inline void lp_i2s_ll_rx_clear_interrupt_status(lp_i2s_dev_t *hw, uint32_t mask) @@ -736,6 +738,100 @@ static inline void lp_i2s_ll_rx_clear_interrupt_status(lp_i2s_dev_t *hw, uint32_ hw->int_clr.val = mask; } +/*--------------------------------------------------------------- + VAD +---------------------------------------------------------------*/ +#define LP_VAD_LL_INIT_FRAME_MIN 100 +#define LP_VAD_LL_INIT_FRAME_MAX 200 + +/** + * @brief Set VAD init frame number + * + * @param[in] hw LP I2S hardware instance + * @param[in] frame_num Frame number + */ +static inline void lp_vad_ll_set_init_frame_num(lp_i2s_dev_t *hw, int frame_num) +{ + hw->vad_param0.param_init_frame_num = frame_num; +} + +/** + * @brief Set VAD min energy + * + * @param[in] hw LP I2S hardware instance + * @param[in] min_energy Min energy + */ +static inline void lp_vad_ll_set_init_min_energy(lp_i2s_dev_t *hw, int min_energy) +{ + HAL_FORCE_MODIFY_U32_REG_FIELD(hw->vad_param0, param_min_energy, min_energy); +} + +/** + * @brief Set VAD speak activity thresh + * + * @param[in] hw LP I2S hardware instance + * @param[in] thresh Threshold + */ +static inline void lp_vad_ll_set_speak_activity_thresh(lp_i2s_dev_t *hw, int thresh) +{ + hw->vad_param1.param_hangover_speech = thresh; +} + +/** + * @brief Set VAD non speak activity thresh + * + * @param[in] hw LP I2S hardware instance + * @param[in] thresh Threshold + */ +static inline void lp_vad_ll_set_non_speak_activity_thresh(lp_i2s_dev_t *hw, int thresh) +{ + hw->vad_param1.param_hangover_silent = thresh; +} + +/** + * @brief Set VAD min speak activity thresh + * + * @param[in] hw LP I2S hardware instance + * @param[in] thresh Threshold + */ +static inline void lp_vad_ll_set_min_speak_activity_thresh(lp_i2s_dev_t *hw, int thresh) +{ + hw->vad_param1.param_min_speech_count = thresh; +} + +/** + * @brief Set VAD max speak activity thresh + * + * @param[in] hw LP I2S hardware instance + * @param[in] thresh Threshold + */ +static inline void lp_vad_ll_set_max_speak_activity_thresh(lp_i2s_dev_t *hw, int thresh) +{ + hw->vad_param1.param_max_speech_count = thresh; +} + +/** + * @brief Skip band energy check + * + * @param[in] hw LP I2S hardware instance + * @param[in] skip 1: skip; 0: not skip + */ +static inline void lp_vad_ll_skip_band_energy(lp_i2s_dev_t *hw, bool skip) +{ + hw->vad_param1.param_skip_band_energy = skip; +} + +/** + * @brief Enable LP I2S 24 fill + * + * @param[in] hw LP I2S hardware instance + * @param[in] en enable or disable + */ +static inline void lp_vad_ll_enable(lp_i2s_dev_t *hw, bool en) +{ + hw->vad_conf.vad_en = en; +} + #ifdef __cplusplus } #endif diff --git a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in index fcc964add8..402a686f3e 100644 --- a/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32p4/include/soc/Kconfig.soc_caps.in @@ -263,6 +263,10 @@ config SOC_LP_ADC_SUPPORTED bool default y +config SOC_LP_VAD_SUPPORTED + bool + default y + config SOC_SPIRAM_SUPPORTED bool default y @@ -1763,6 +1767,10 @@ config SOC_UART_SUPPORT_FSM_TX_WAIT_SEND bool default y +config SOC_LP_I2S_SUPPORT_VAD + bool + default y + config SOC_COEX_HW_PTI bool default y @@ -1851,6 +1859,14 @@ config SOC_PM_PAU_REGDMA_UPDATE_CACHE_BEFORE_WAIT_COMPARE bool default y +config SOC_SLEEP_SYSTIMER_STALL_WORKAROUND + bool + default y + +config SOC_SLEEP_TGWDT_STOP_WORKAROUND + bool + default y + config SOC_PSRAM_VDD_POWER_MPLL bool default y @@ -1966,3 +1982,7 @@ config SOC_LP_CORE_SUPPORT_ETM config SOC_LP_CORE_SUPPORT_LP_ADC bool default y + +config SOC_LP_CORE_SUPPORT_LP_VAD + bool + default y diff --git a/components/soc/esp32p4/include/soc/soc_caps.h b/components/soc/esp32p4/include/soc/soc_caps.h index 70755ca405..725d954dd4 100644 --- a/components/soc/esp32p4/include/soc/soc_caps.h +++ b/components/soc/esp32p4/include/soc/soc_caps.h @@ -83,6 +83,7 @@ #define SOC_LP_I2S_SUPPORTED 1 #define SOC_LP_SPI_SUPPORTED 1 #define SOC_LP_ADC_SUPPORTED 1 +#define SOC_LP_VAD_SUPPORTED 1 #define SOC_SPIRAM_SUPPORTED 1 #define SOC_PSRAM_DMA_CAPABLE 1 #define SOC_SDMMC_HOST_SUPPORTED 1 @@ -662,6 +663,9 @@ // UART has an extra TX_WAIT_SEND state when the FIFO is not empty and XOFF is enabled #define SOC_UART_SUPPORT_FSM_TX_WAIT_SEND (1) +/*-------------------------- LP_VAD CAPS -------------------------------------*/ +#define SOC_LP_I2S_SUPPORT_VAD (1) + // TODO: IDF-5679 (Copy from esp32c3, need check) /*-------------------------- COEXISTENCE HARDWARE PTI CAPS -------------------------------*/ #define SOC_COEX_HW_PTI (1) @@ -698,6 +702,8 @@ #define SOC_CPU_IN_TOP_DOMAIN (1) #define SOC_PM_PAU_REGDMA_UPDATE_CACHE_BEFORE_WAIT_COMPARE (1) +#define SOC_SLEEP_SYSTIMER_STALL_WORKAROUND 1 //TODO IDF-11381: replace with all xtal field clk gate control +#define SOC_SLEEP_TGWDT_STOP_WORKAROUND 1 //TODO IDF-11381: replace with all xtal field clk gate control /*-------------------------- PSRAM CAPS ----------------------------*/ #define SOC_PSRAM_VDD_POWER_MPLL (1) @@ -747,3 +753,4 @@ /*------------------------------------- ULP CAPS -------------------------------------*/ #define SOC_LP_CORE_SUPPORT_ETM (1) /*!< LP Core supports ETM */ #define SOC_LP_CORE_SUPPORT_LP_ADC (1) /*!< LP ADC can be accessed from the LP-Core */ +#define SOC_LP_CORE_SUPPORT_LP_VAD (1) /*!< LP VAD can be accessed from the LP-Core */ diff --git a/components/ulp/CMakeLists.txt b/components/ulp/CMakeLists.txt index 058d05922b..dbe990e652 100644 --- a/components/ulp/CMakeLists.txt +++ b/components/ulp/CMakeLists.txt @@ -78,6 +78,10 @@ if(CONFIG_ULP_COPROC_TYPE_LP_CORE) if(CONFIG_SOC_LP_ADC_SUPPORTED) list(APPEND srcs "lp_core/shared/ulp_lp_core_lp_adc_shared.c") endif() + + if(CONFIG_SOC_LP_VAD_SUPPORTED) + list(APPEND srcs "lp_core/shared/ulp_lp_core_lp_vad_shared.c") + endif() endif() idf_component_register(SRCS ${srcs} diff --git a/components/ulp/cmake/IDFULPProject.cmake b/components/ulp/cmake/IDFULPProject.cmake index 04da71b00c..fd90cfd87f 100644 --- a/components/ulp/cmake/IDFULPProject.cmake +++ b/components/ulp/cmake/IDFULPProject.cmake @@ -126,6 +126,7 @@ function(ulp_apply_default_sources ulp_app_name) "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_spi.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_ubsan.c" "${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_adc_shared.c" + "${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_vad_shared.c" "${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_critical_section_shared.c") set(target_folder ${IDF_TARGET}) diff --git a/components/ulp/lp_core/include/ulp_lp_core.h b/components/ulp/lp_core/include/ulp_lp_core.h index d737d638ac..6a0373aefa 100644 --- a/components/ulp/lp_core/include/ulp_lp_core.h +++ b/components/ulp/lp_core/include/ulp_lp_core.h @@ -22,6 +22,7 @@ extern "C" { #define ULP_LP_CORE_WAKEUP_SOURCE_LP_IO BIT(2) // Enable wake-up by LP IO interrupt #define ULP_LP_CORE_WAKEUP_SOURCE_ETM BIT(3) // Enable wake-up by ETM event #define ULP_LP_CORE_WAKEUP_SOURCE_LP_TIMER BIT(4) // Enable wake-up by LP timer +#define ULP_LP_CORE_WAKEUP_SOURCE_LP_VAD BIT(5) // Enable wake-up by LP VAD /** * @brief ULP LP core init parameters diff --git a/components/ulp/lp_core/lp_core.c b/components/ulp/lp_core/lp_core.c index aedcf0e78e..cfc8fc81cc 100644 --- a/components/ulp/lp_core/lp_core.c +++ b/components/ulp/lp_core/lp_core.c @@ -35,7 +35,7 @@ extern uint32_t _rtc_ulp_memory_start; const static char* TAG = "ulp-lp-core"; -#define WAKEUP_SOURCE_MAX_NUMBER 5 +#define WAKEUP_SOURCE_MAX_NUMBER 6 #define RESET_HANDLER_ADDR (intptr_t)(&_rtc_ulp_memory_start + 0x80 / 4) // Placed after the 0x80 byte long vector table @@ -46,6 +46,9 @@ static uint32_t wakeup_src_sw_to_hw_flag_lookup[WAKEUP_SOURCE_MAX_NUMBER] = { LP_CORE_LL_WAKEUP_SOURCE_LP_IO, LP_CORE_LL_WAKEUP_SOURCE_ETM, LP_CORE_LL_WAKEUP_SOURCE_LP_TIMER, +#if SOC_LP_VAD_SUPPORTED + LP_CORE_LL_WAKEUP_SOURCE_LP_VAD, +#endif }; /* Convert the wake-up sources defined in ulp_lp_core.h to the actual HW wake-up source values */ diff --git a/components/ulp/lp_core/lp_core/lp_core_utils.c b/components/ulp/lp_core/lp_core/lp_core_utils.c index 8b358741fb..d3e8664907 100644 --- a/components/ulp/lp_core/lp_core/lp_core_utils.c +++ b/components/ulp/lp_core/lp_core/lp_core_utils.c @@ -14,6 +14,10 @@ #include "hal/pmu_ll.h" #include "hal/uart_ll.h" #include "hal/rtc_io_ll.h" +#if SOC_LP_I2S_SUPPORT_VAD +//For VAD +#include "hal/lp_i2s_ll.h" +#endif #if SOC_LP_TIMER_SUPPORTED #include "hal/lp_timer_ll.h" @@ -56,6 +60,13 @@ void ulp_lp_core_update_wakeup_cause(void) rtcio_ll_clear_interrupt_status(); } +#if SOC_LP_VAD_SUPPORTED + if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_VAD)) { + lp_wakeup_cause |= LP_CORE_LL_WAKEUP_SOURCE_LP_VAD; + lp_i2s_ll_rx_clear_interrupt_status(&LP_I2S, LP_I2S_LL_EVENT_VAD_DONE_INT); + } +#endif + #if SOC_ETM_SUPPORTED if ((lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_ETM) \ && lp_core_ll_get_etm_wakeup_flag()) { diff --git a/components/ulp/lp_core/shared/include/ulp_lp_core_lp_vad_shared.h b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_vad_shared.h new file mode 100644 index 0000000000..8a37dee54d --- /dev/null +++ b/components/ulp/lp_core/shared/include/ulp_lp_core_lp_vad_shared.h @@ -0,0 +1,121 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "driver/lp_i2s_vad.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LP VAD configurations + */ +typedef lp_vad_init_config_t lp_core_lp_vad_cfg_t; + +/** + * @brief State Machine + ┌──────────────────────────────────┐ + │ │ + ┌─────────────┤ speak-activity-listening-state │ ◄───────────────┐ + │ │ │ │ + │ └──────────────────────────────────┘ │ + │ ▲ │ + │ │ │ + │ │ │ + │ │ │ + │ │ │ +detected speak activity │ │ detected speak activity │ detected speak activity + >= │ │ >= │ >= +'speak_activity_thresh' │ │ 'min_speak_activity_thresh' │ 'max_speak_activity_thresh' + │ │ │ + │ │ && │ + │ │ │ + │ │ detected non-speak activity │ + │ │ < │ + │ │ 'non_speak_activity_thresh' │ + │ │ │ + │ │ │ + │ │ │ + │ │ │ + │ │ │ + │ ┌───────────┴─────────────────────┐ │ + │ │ │ │ + └───────────► │ speak-activity-detected-state ├─────────────────┘ + │ │ + └─┬───────────────────────────────┘ + │ + │ ▲ + │ │ + │ │ + │ │ detected speak activity + │ │ >= + │ │ 'min_speak_activity_thresh' + │ │ + │ │ && + │ │ + │ │ detected non-speak activity + │ │ < + └─────────────────────┘ 'non_speak_activity_thresh' +*/ + +/** + * @brief LP VAD init + * + * @param[in] vad_id VAD ID + * @param[in] init_config Initial configurations + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_core_lp_vad_init(lp_vad_t vad_id, const lp_core_lp_vad_cfg_t *init_config); + +/** + * @brief Enable LP VAD + * + * @param[in] vad_id VAD ID + * @param[in] init_config Initial configurations + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_core_lp_vad_enable(lp_vad_t vad_id); + +/** + * @brief Disable LP VAD + * + * @param[in] vad_id VAD ID + * @param[in] init_config Initial configurations + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_core_lp_vad_disable(lp_vad_t vad_id); + +/** + * @brief Deinit LP VAD + * + * @param[in] vad_id VAD ID + * + * @return + * - ESP_OK: On success + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: Driver state is invalid, you shouldn't call this API at this moment + */ +esp_err_t lp_core_lp_vad_deinit(lp_vad_t vad_id); + +#ifdef __cplusplus +} +#endif diff --git a/components/ulp/lp_core/shared/ulp_lp_core_lp_vad_shared.c b/components/ulp/lp_core/shared/ulp_lp_core_lp_vad_shared.c new file mode 100644 index 0000000000..db78e74633 --- /dev/null +++ b/components/ulp/lp_core/shared/ulp_lp_core_lp_vad_shared.c @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" +#if SOC_LP_VAD_SUPPORTED +#include "esp_check.h" +#include "esp_err.h" +#include "ulp_lp_core_lp_vad_shared.h" +#if SOC_LP_I2S_SUPPORT_VAD +//For VAD +#include "hal/lp_i2s_ll.h" +#include "hal/lp_i2s_hal.h" +#include "esp_private/lp_i2s_private.h" +#endif //SOC_LP_I2S_SUPPORT_VAD + +//make this available for multi vad id in future +vad_unit_handle_t s_vad_handle; + +esp_err_t lp_core_lp_vad_init(lp_vad_t vad_id, const lp_core_lp_vad_cfg_t *init_config) +{ +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + esp_err_t ret = lp_i2s_vad_new_unit(vad_id, init_config, &s_vad_handle); + return ret; +#endif +} + +esp_err_t lp_core_lp_vad_enable(lp_vad_t vad_id) +{ +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + esp_err_t ret = lp_i2s_vad_enable(s_vad_handle); + return ret; +#endif +} + +esp_err_t lp_core_lp_vad_disable(lp_vad_t vad_id) +{ +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + esp_err_t ret = lp_i2s_vad_disable(s_vad_handle); + return ret; +#endif +} + +esp_err_t lp_core_lp_vad_deinit(lp_vad_t vad_id) +{ +#if IS_ULP_COCPU + // Not supported + return ESP_ERR_NOT_SUPPORTED; +#else + esp_err_t ret = lp_i2s_vad_del_unit(s_vad_handle); + return ret; +#endif +} +#endif /* SOC_LP_VAD_SUPPORTED */ diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt index 63d8f905d7..239b78cf33 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/CMakeLists.txt @@ -20,6 +20,10 @@ if(CONFIG_SOC_LP_ADC_SUPPORTED) list(APPEND app_sources "test_lp_core_adc.c") endif() +if(CONFIG_SOC_LP_VAD_SUPPORTED) + list(APPEND app_sources "test_lp_core_vad.c") +endif() + set(lp_core_sources "lp_core/test_main.c") set(lp_core_sources_counter "lp_core/test_main_counter.c") @@ -46,10 +50,15 @@ if(CONFIG_SOC_LP_ADC_SUPPORTED) set(lp_core_sources_adc "lp_core/test_main_adc.c") endif() +if(CONFIG_SOC_LP_VAD_SUPPORTED) + set(lp_core_sources_vad "lp_core/test_main_vad.c") +endif() + idf_component_register(SRCS ${app_sources} INCLUDE_DIRS "lp_core" REQUIRES ulp unity esp_timer test_utils - WHOLE_ARCHIVE) + WHOLE_ARCHIVE + EMBED_FILES "test_vad_8k.pcm") set(lp_core_exp_dep_srcs ${app_sources}) @@ -79,3 +88,7 @@ endif() if(CONFIG_SOC_LP_ADC_SUPPORTED) ulp_embed_binary(lp_core_test_app_adc "${lp_core_sources_adc}" "${lp_core_exp_dep_srcs}") endif() + +if(CONFIG_SOC_LP_VAD_SUPPORTED) + ulp_embed_binary(lp_core_test_app_vad "${lp_core_sources_vad}" "${lp_core_exp_dep_srcs}") +endif() diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/Kconfig.projbuild b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/Kconfig.projbuild new file mode 100644 index 0000000000..5783fc5daf --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/Kconfig.projbuild @@ -0,0 +1,9 @@ +menu "Test Configurations" + + config TEST_LP_CORE_VAD_ENABLE + bool "Enable LP VAD test" + default n + help + Enable this to trigger LP VAD test + +endmenu diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_vad.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_vad.c new file mode 100644 index 0000000000..5f6198fe37 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/lp_core/test_main_vad.c @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +volatile bool vad_wakup; + +int main(void) +{ + vad_wakup = true; + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_vad.c b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_vad.c new file mode 100644 index 0000000000..16a3c059dd --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_lp_core_vad.c @@ -0,0 +1,154 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "unity.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/lp_i2s.h" +#include "driver/lp_i2s_std.h" +#include "driver/i2s_std.h" +#include "ulp_lp_core.h" +#include "ulp_lp_core_lp_vad_shared.h" +#include "lp_core_test_app_vad.h" + +#if CONFIG_TEST_LP_CORE_VAD_ENABLE + +#define TEST_I2S_FRAME_SIZE (128) // Frame numbers in every writing / reading +#define TEST_I2S_TRANS_SIZE (4096) // Trans size + +extern const uint8_t test_vad_pcm_start[] asm("_binary_test_vad_8k_pcm_start"); +extern const uint8_t test_vad_pcm_end[] asm("_binary_test_vad_8k_pcm_end"); +extern const uint8_t lp_core_main_vad_bin_start[] asm("_binary_lp_core_test_app_vad_bin_start"); +extern const uint8_t lp_core_main_vad_bin_end[] asm("_binary_lp_core_test_app_vad_bin_end"); +static const char *TAG = "TEST_VAD"; + +static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) +{ + TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, + (firmware_end - firmware_start)) == ESP_OK); + + TEST_ASSERT(ulp_lp_core_run(cfg) == ESP_OK); +} + +void test_lp_vad(lp_vad_t vad_id) +{ + esp_err_t ret = ESP_FAIL; + int pcm_size = test_vad_pcm_end - test_vad_pcm_start; + printf("pcm_size: %d\n", pcm_size); + + lp_i2s_chan_handle_t rx_handle = NULL; + lp_i2s_chan_config_t config = { + .id = 0, + .role = I2S_ROLE_SLAVE, + .threshold = 512, + }; + TEST_ESP_OK(lp_i2s_new_channel(&config, NULL, &rx_handle)); + + i2s_chan_handle_t tx_handle = NULL; + i2s_chan_config_t i2s_channel_config = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = 4, + .dma_frame_num = TEST_I2S_FRAME_SIZE, + .auto_clear = false, + }; + TEST_ESP_OK(i2s_new_channel(&i2s_channel_config, &tx_handle, NULL)); + + lp_i2s_std_config_t lp_std_cfg = { + .pin_cfg = { + .bck = 4, + .ws = 5, + .din = 6, + }, + }; + lp_std_cfg.slot_cfg = (lp_i2s_std_slot_config_t)LP_I2S_STD_PCM_SHORT_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO); + TEST_ESP_OK(lp_i2s_channel_init_std_mode(rx_handle, &lp_std_cfg)); + + i2s_std_config_t i2s_std_config = { + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = GPIO_NUM_7, + .ws = GPIO_NUM_8, + .dout = GPIO_NUM_21, + .din = -1, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false, + }, + }, + }; + i2s_std_config.clk_cfg = (i2s_std_clk_config_t)I2S_STD_CLK_DEFAULT_CONFIG(16000); + i2s_std_config.slot_cfg = (i2s_std_slot_config_t)I2S_STD_PCM_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO); + TEST_ESP_OK(i2s_channel_init_std_mode(tx_handle, &i2s_std_config)); + + // LP VAD Init + lp_vad_init_config_t init_config = { + .lp_i2s_chan = rx_handle, + .vad_config = { + .init_frame_num = 100, + .min_energy_thresh = 100, + .speak_activity_thresh = 10, + .non_speak_activity_thresh = 30, + .min_speak_activity_thresh = 3, + .max_speak_activity_thresh = 100, + }, + }; + TEST_ESP_OK(lp_core_lp_vad_init(0, &init_config)); + + /* Load ULP firmware and start the coprocessor */ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_VAD, + }; + load_and_start_lp_core_firmware(&cfg, lp_core_main_vad_bin_start, lp_core_main_vad_bin_end); + + uint8_t *txbuf = (uint8_t *)heap_caps_calloc(1, TEST_I2S_TRANS_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT(txbuf); + + uint8_t *prebuf = (uint8_t *)heap_caps_calloc(1, TEST_I2S_TRANS_SIZE, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL); + TEST_ASSERT(prebuf); + + memcpy(prebuf, test_vad_pcm_start, TEST_I2S_TRANS_SIZE); + memcpy(txbuf, test_vad_pcm_start, TEST_I2S_TRANS_SIZE); + + for (int i = 0; i < TEST_I2S_TRANS_SIZE; i++) { + ESP_LOGD(TAG, "prebuf[%d]: %d", i, prebuf[i]); + ESP_LOGD(TAG, "txbuf[%d]: %d", i, txbuf[i]); + } + + size_t bytes_written = 0; + TEST_ESP_OK(i2s_channel_preload_data(tx_handle, prebuf, TEST_I2S_TRANS_SIZE, &bytes_written)); + TEST_ESP_OK(lp_i2s_channel_enable(rx_handle)); + TEST_ESP_OK(lp_core_lp_vad_enable(0)); + TEST_ESP_OK(i2s_channel_enable(tx_handle)); + + ret = i2s_channel_write(tx_handle, txbuf, TEST_I2S_TRANS_SIZE, &bytes_written, 0); + if (ret != ESP_OK && ret != ESP_ERR_TIMEOUT) { + TEST_ESP_OK(ret); + } + ESP_LOGD(TAG, "bytes_written: %d", bytes_written); + + while (!ulp_vad_wakup) { + ; + } + + ESP_LOGI(TAG, "wakeup"); + + TEST_ESP_OK(lp_i2s_channel_disable(rx_handle)); + TEST_ESP_OK(lp_i2s_del_channel(rx_handle)); + TEST_ESP_OK(i2s_channel_disable(tx_handle)); + TEST_ESP_OK(i2s_del_channel(tx_handle)); + free(txbuf); + free(prebuf); +} + +TEST_CASE("LP VAD wakeup test", "[lp_core][lp_vad]") +{ + test_lp_vad(0); +} +#endif //CONFIG_TEST_LP_CORE_VAD_ENABLE diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_vad_8k.pcm b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_vad_8k.pcm new file mode 100644 index 0000000000..5b6c32a037 Binary files /dev/null and b/components/ulp/test_apps/lp_core/lp_core_basic_tests/main/test_vad_8k.pcm differ diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py b/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py index 53178dca4e..33e7b3c950 100644 --- a/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/pytest_lp_core_basic.py @@ -33,6 +33,19 @@ def test_lp_core_xtal(dut: Dut) -> None: dut.run_all_single_board_cases() +@pytest.mark.esp32p4 +@pytest.mark.lp_i2s +@pytest.mark.parametrize( + 'config', + [ + 'lp_vad', + ], + indirect=True, +) +def test_lp_vad(dut: Dut) -> None: + dut.run_all_single_board_cases(group='lp_vad') + + @pytest.mark.esp32c6 # TODO: Enable LP I2C test for esp32p4 (IDF-9407) @pytest.mark.generic_multi_device diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.default b/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.default deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.lp_vad b/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.lp_vad new file mode 100644 index 0000000000..766d6c83cd --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_basic_tests/sdkconfig.ci.lp_vad @@ -0,0 +1 @@ +CONFIG_TEST_LP_CORE_VAD_ENABLE=y