Merge branch 'feat/lp_i2s_vad' into 'master'

lp_i2s: vad lp driver and lp/hp core wakeup feature

Closes IDF-10211 and IDF-6517

See merge request espressif/esp-idf!33804
This commit is contained in:
Armando (Dou Yiwen)
2024-10-17 15:01:08 +08:00
40 changed files with 1142 additions and 10 deletions

View File

@@ -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

View File

@@ -0,0 +1,153 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#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

View File

@@ -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

View File

@@ -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;
}

View File

@@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#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 */

View File

@@ -1 +0,0 @@
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y

View File

@@ -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)

View File

@@ -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;

View File

@@ -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

View File

@@ -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(&param_default, &power_default, sleep_flags, adjustment, slowclk_period, fastclk_period);

View File

@@ -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]);

View File

@@ -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"]

View File

@@ -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)

View File

@@ -0,0 +1,3 @@
| Supported Targets | ESP32-P4 |
| ----------------- | -------- |

View File

@@ -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")

View File

@@ -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();
}

View File

@@ -0,0 +1,162 @@
/*
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#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);

View File

@@ -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()

View File

@@ -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

View File

@@ -14,6 +14,7 @@ extern "C" {
#include <stdbool.h>
#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

View File

@@ -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 */

View File

@@ -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

View File

@@ -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

View File

@@ -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 */

View File

@@ -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}

View File

@@ -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})

View File

@@ -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

View File

@@ -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 */

View File

@@ -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()) {

View File

@@ -0,0 +1,121 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#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

View File

@@ -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 */

View File

@@ -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()

View File

@@ -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

View File

@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
volatile bool vad_wakup;
int main(void)
{
vad_wakup = true;
return 0;
}

View File

@@ -0,0 +1,154 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <string.h>
#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

View File

@@ -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

View File

@@ -0,0 +1 @@
CONFIG_TEST_LP_CORE_VAD_ENABLE=y