From f5c1c30c5ea9d626b1b80dead763172c56321bdb Mon Sep 17 00:00:00 2001 From: espressif2022 <111102666+espressif2022@users.noreply.github.com> Date: Thu, 29 May 2025 23:29:33 +0800 Subject: [PATCH] feat: add new board esp-hi (#666) * feat: add new board esp-hi * feat(esp-hi): update servo_dog_ctrl --------- Co-authored-by: Li Junru Co-authored-by: Xiaoxia --- main/CMakeLists.txt | 43 ++++ main/Kconfig.projbuild | 7 + main/application.cc | 14 +- main/application.h | 4 +- main/audio_codecs/adc_pdm_audio_codec.cc | 203 ++++++++++++++++++ main/audio_codecs/adc_pdm_audio_codec.h | 29 +++ main/audio_codecs/audio_codec.h | 6 +- main/audio_processing/wake_word_no_afe.cc | 67 ++++++ main/audio_processing/wake_word_no_afe.h | 42 ++++ main/boards/esp-hi/config.h | 44 ++++ main/boards/esp-hi/config.json | 37 ++++ main/boards/esp-hi/dog_action extra.cc | 64 ++++++ main/boards/esp-hi/dog_action_basic.cc | 75 +++++++ main/boards/esp-hi/dog_light.cc | 113 ++++++++++ main/boards/esp-hi/emoji_display.cc | 171 +++++++++++++++ main/boards/esp-hi/emoji_display.h | 54 +++++ main/boards/esp-hi/esp_hi.cc | 242 ++++++++++++++++++++++ main/idf_component.yml | 14 +- partitions_hi.csv | 8 + 19 files changed, 1225 insertions(+), 12 deletions(-) create mode 100644 main/audio_codecs/adc_pdm_audio_codec.cc create mode 100644 main/audio_codecs/adc_pdm_audio_codec.h create mode 100644 main/audio_processing/wake_word_no_afe.cc create mode 100644 main/audio_processing/wake_word_no_afe.h create mode 100644 main/boards/esp-hi/config.h create mode 100644 main/boards/esp-hi/config.json create mode 100644 main/boards/esp-hi/dog_action extra.cc create mode 100644 main/boards/esp-hi/dog_action_basic.cc create mode 100644 main/boards/esp-hi/dog_light.cc create mode 100644 main/boards/esp-hi/emoji_display.cc create mode 100644 main/boards/esp-hi/emoji_display.h create mode 100644 main/boards/esp-hi/esp_hi.cc create mode 100644 partitions_hi.csv diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 332d894e..02d5bde8 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES "audio_codecs/audio_codec.cc" "audio_codecs/no_audio_codec.cc" + "audio_codecs/adc_pdm_audio_codec.cc" "audio_codecs/box_audio_codec.cc" "audio_codecs/es8311_audio_codec.cc" "audio_codecs/es8374_audio_codec.cc" @@ -98,6 +99,8 @@ elseif(CONFIG_BOARD_TYPE_ESP_SPARKBOT) set(BOARD_TYPE "esp-sparkbot") elseif(CONFIG_BOARD_TYPE_ESP_SPOT_S3) set(BOARD_TYPE "esp-spot-s3") +elseif(CONFIG_BOARD_TYPE_ESP_HI) + set(BOARD_TYPE "esp-hi") elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8) set(BOARD_TYPE "esp32-s3-touch-amoled-1.8") elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75) @@ -197,6 +200,9 @@ endif() if(CONFIG_USE_WAKE_WORD_DETECT) list(APPEND SOURCES "audio_processing/wake_word_detect.cc") endif() +if(CONFIG_USE_WAKE_WORD_DETECT_NO_AFE) + list(APPEND SOURCES "audio_processing/wake_word_no_afe.cc") +endif() # 根据Kconfig选择语言目录 if(CONFIG_LANGUAGE_ZH_CN) @@ -254,3 +260,40 @@ add_custom_command( add_custom_target(lang_header ALL DEPENDS ${LANG_HEADER} ) + +if(CONFIG_BOARD_TYPE_ESP_HI) +set(URL "https://github.com/espressif2022/image_player/raw/main/test_apps/test_8bit") +set(SPIFFS_DIR "${CMAKE_BINARY_DIR}/emoji") +file(MAKE_DIRECTORY ${SPIFFS_DIR}) + +# List all files to download +set(FILES_TO_DOWNLOAD "") +list(APPEND FILES_TO_DOWNLOAD "Anger_enter.aaf" "Anger_loop.aaf" "Anger_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "happy_enter.aaf" "happy_loop.aaf" "happ_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "sad_enter.aaf" "sad_loop.aaf" "sad_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "scorn_enter.aaf" "scorn_loop.aaf" "scorn_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "left_enter.aaf" "left_loop.aaf" "left_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "right_enter.aaf" "right_loop.aaf" "right_return.aaf") +list(APPEND FILES_TO_DOWNLOAD "asking.aaf" "blink_once.aaf" "blink_quick.aaf") +list(APPEND FILES_TO_DOWNLOAD "connecting.aaf" "panic_enter.aaf" "panic_loop.aaf") +list(APPEND FILES_TO_DOWNLOAD "panic_return.aaf" "wake.aaf") + +foreach(FILENAME IN LISTS FILES_TO_DOWNLOAD) + set(REMOTE_FILE "${URL}/${FILENAME}") + set(LOCAL_FILE "${SPIFFS_DIR}/${FILENAME}") + message(STATUS "Downloading ${FILENAME}") + file(DOWNLOAD ${REMOTE_FILE} ${LOCAL_FILE} + STATUS DOWNLOAD_STATUS) + list(GET DOWNLOAD_STATUS 0 STATUS_CODE) + if(NOT STATUS_CODE EQUAL 0) + message(FATAL_ERROR "Failed to download ${FILENAME} from ${URL}") + endif() +endforeach() + +spiffs_create_partition_assets( + assets_A + ${SPIFFS_DIR} + FLASH_IN_PROJECT + MMAP_FILE_SUPPORT_FORMAT ".aaf" +) +endif() diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 9077432b..37137a96 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -94,6 +94,8 @@ choice BOARD_TYPE bool "ESP-SparkBot开发板" config BOARD_TYPE_ESP_SPOT_S3 bool "ESP-Spot-S3" + config BOARD_TYPE_ESP_HI + bool "ESP-HI" config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8 bool "Waveshare ESP32-S3-Touch-AMOLED-1.8" config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 @@ -268,6 +270,11 @@ config USE_WECHAT_MESSAGE_STYLE help 使用微信聊天界面风格 +config USE_WAKE_WORD_DETECT_NO_AFE + bool "Enable Wake Word Detection (without AFE)" + default y + depends on IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C5 + config USE_WAKE_WORD_DETECT bool "Enable Wake Word Detection" default y diff --git a/main/application.cc b/main/application.cc index 71753835..264185e3 100644 --- a/main/application.cc +++ b/main/application.cc @@ -129,7 +129,7 @@ void Application::CheckNewVersion() { auto& board = Board::GetInstance(); board.SetPowerSaveMode(false); -#if CONFIG_USE_WAKE_WORD_DETECT +#if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_WAKE_WORD_DETECT_NO_AFE wake_word_detect_.StopDetection(); #endif // 预先关闭音频输出,避免升级过程有音频操作 @@ -600,8 +600,9 @@ void Application::Start() { } }); -#if CONFIG_USE_WAKE_WORD_DETECT +#if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_WAKE_WORD_DETECT_NO_AFE wake_word_detect_.Initialize(codec); +#ifdef CONFIG_USE_WAKE_WORD_DETECT wake_word_detect_.OnWakeWordDetected([this](const std::string& wake_word) { Schedule([this, &wake_word]() { if (device_state_ == kDeviceStateIdle) { @@ -629,6 +630,7 @@ void Application::Start() { } }); }); +#endif wake_word_detect_.StartDetection(); #endif @@ -789,7 +791,7 @@ void Application::OnAudioOutput() { } void Application::OnAudioInput() { -#if CONFIG_USE_WAKE_WORD_DETECT +#if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_WAKE_WORD_DETECT_NO_AFE if (wake_word_detect_.IsDetectionRunning()) { std::vector data; int samples = wake_word_detect_.GetFeedSize(); @@ -883,7 +885,7 @@ void Application::SetDeviceState(DeviceState state) { display->SetEmotion("neutral"); audio_processor_->Stop(); -#if CONFIG_USE_WAKE_WORD_DETECT +#if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_WAKE_WORD_DETECT_NO_AFE wake_word_detect_.StartDetection(); #endif break; @@ -911,7 +913,7 @@ void Application::SetDeviceState(DeviceState state) { vTaskDelay(pdMS_TO_TICKS(120)); } opus_encoder_->ResetState(); -#if CONFIG_USE_WAKE_WORD_DETECT +#if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_WAKE_WORD_DETECT_NO_AFE wake_word_detect_.StopDetection(); #endif audio_processor_->Start(); @@ -922,7 +924,7 @@ void Application::SetDeviceState(DeviceState state) { if (listening_mode_ != kListeningModeRealtime) { audio_processor_->Stop(); -#if CONFIG_USE_WAKE_WORD_DETECT +#if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_WAKE_WORD_DETECT_NO_AFE wake_word_detect_.StartDetection(); #endif } diff --git a/main/application.h b/main/application.h index df182e31..0e327a08 100644 --- a/main/application.h +++ b/main/application.h @@ -24,6 +24,8 @@ #if CONFIG_USE_WAKE_WORD_DETECT #include "wake_word_detect.h" +#elif CONFIG_USE_WAKE_WORD_DETECT_NO_AFE +#include "wake_word_no_afe.h" #endif #define SCHEDULE_EVENT (1 << 0) @@ -86,7 +88,7 @@ private: Application(); ~Application(); -#if CONFIG_USE_WAKE_WORD_DETECT +#if CONFIG_USE_WAKE_WORD_DETECT || CONFIG_USE_WAKE_WORD_DETECT_NO_AFE WakeWordDetect wake_word_detect_; #endif std::unique_ptr audio_processor_; diff --git a/main/audio_codecs/adc_pdm_audio_codec.cc b/main/audio_codecs/adc_pdm_audio_codec.cc new file mode 100644 index 00000000..1344868e --- /dev/null +++ b/main/audio_codecs/adc_pdm_audio_codec.cc @@ -0,0 +1,203 @@ +#include "adc_pdm_audio_codec.h" + +#include +#include +#include +#include +#include "adc_mic.h" +#include "driver/i2s_pdm.h" +#include "soc/gpio_sig_map.h" +#include "soc/io_mux_reg.h" +#include "hal/rtc_io_hal.h" +#include "hal/gpio_ll.h" +#include "settings.h" + +static const char TAG[] = "AdcPdmAudioCodec"; + +#define BSP_I2S_GPIO_CFG(_dout) \ + { \ + .clk = GPIO_NUM_NC, \ + .dout = _dout, \ + .invert_flags = { \ + .clk_inv = false, \ + }, \ + } + +/** + * @brief Mono Duplex I2S configuration structure + * + * This configuration is used by default in bsp_audio_init() + */ +#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout) \ + { \ + .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \ + .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \ + .gpio_cfg = BSP_I2S_GPIO_CFG(_dout), \ + } + +AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate, + uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) { + + input_reference_ = false; + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + uint8_t adc_channel[1] = {0}; + adc_channel[0] = adc_mic_channel; + + audio_codec_adc_cfg_t cfg = { + .handle = NULL, + .max_store_buf_size = 1024 * 2, + .conv_frame_size = 1024, + .unit_id = ADC_UNIT_1, + .adc_channel_list = adc_channel, + .adc_channel_num = sizeof(adc_channel) / sizeof(adc_channel[0]), + .sample_rate_hz = (uint32_t)input_sample_rate, + }; + const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg); + + esp_codec_dev_cfg_t codec_dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_IN, + .data_if = adc_if, + }; + input_dev_ = esp_codec_dev_new(&codec_dev_cfg); + if (!input_dev_) { + ESP_LOGE(TAG, "Failed to create codec device"); + return; + } + + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL)); + + i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p); + pdm_cfg_default.clk_cfg.up_sample_fs = output_sample_rate / 100; + pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4; + pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4; + pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4; + pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4; + const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default; + + ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg)); + + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = NULL, + .tx_handle = tx_handle_, + }; + + const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg); + + codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT; + codec_dev_cfg.codec_if = NULL; + codec_dev_cfg.data_if = i2s_data_if; + output_dev_ = esp_codec_dev_new(&codec_dev_cfg); + + output_volume_ = 100; + if(pa_ctl != GPIO_NUM_NC) { + pa_ctrl_pin_ = pa_ctl; + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + } + gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0); + + if(pdm_speak_n != GPIO_NUM_NC){ + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pdm_speak_n], PIN_FUNC_GPIO); + gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT); + esp_rom_gpio_connect_out_signal(pdm_speak_n,I2SO_SD_OUT_IDX,1,0); //反转输出 SD OUT 信号 + gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0); + } + ESP_LOGI(TAG, "AdcPdmAudioCodec initialized"); +} + +AdcPdmAudioCodec::~AdcPdmAudioCodec() { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); +} + +void AdcPdmAudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void AdcPdmAudioCodec::EnableInput(bool enable) { + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), + .sample_rate = (uint32_t)input_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void AdcPdmAudioCodec::EnableOutput(bool enable) { + if (enable == output_enabled_) { + return; + } + if (enable) { + // Play 16bit 1 channel + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); + if(pa_ctrl_pin_ != GPIO_NUM_NC){ + gpio_set_level(pa_ctrl_pin_, 1); + } + + } else { + if(pa_ctrl_pin_ != GPIO_NUM_NC){ + gpio_set_level(pa_ctrl_pin_, 0); + } + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + } + AudioCodec::EnableOutput(enable); +} + +int AdcPdmAudioCodec::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} +int AdcPdmAudioCodec::Write(const int16_t* data, int samples) { + if (output_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); + } + return samples; +} + +void AdcPdmAudioCodec::Start() { + Settings settings("audio", false); + output_volume_ = settings.GetInt("output_volume", output_volume_); + if (output_volume_ <= 0) { + ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_); + output_volume_ = 10; + } + + ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_)); + + EnableInput(true); + EnableOutput(true); + ESP_LOGI(TAG, "Audio codec started"); +} diff --git a/main/audio_codecs/adc_pdm_audio_codec.h b/main/audio_codecs/adc_pdm_audio_codec.h new file mode 100644 index 00000000..cab1a2bf --- /dev/null +++ b/main/audio_codecs/adc_pdm_audio_codec.h @@ -0,0 +1,29 @@ +#ifndef _BOX_AUDIO_CODEC_H +#define _BOX_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include + +class AdcPdmAudioCodec : public AudioCodec { +private: + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + gpio_num_t pa_ctrl_pin_ = GPIO_NUM_NC; + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate, + uint32_t adc_mic_channel, gpio_num_t pdm_speak_p, gpio_num_t pdm_speak_n, gpio_num_t pa_ctl); + virtual ~AdcPdmAudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; + void Start(); +}; + +#endif // _BOX_AUDIO_CODEC_H diff --git a/main/audio_codecs/audio_codec.h b/main/audio_codecs/audio_codec.h index f2524756..2a0d376a 100644 --- a/main/audio_codecs/audio_codec.h +++ b/main/audio_codecs/audio_codec.h @@ -23,9 +23,9 @@ public: virtual void EnableInput(bool enable); virtual void EnableOutput(bool enable); - void Start(); - void OutputData(std::vector& data); - bool InputData(std::vector& data); + virtual void OutputData(std::vector& data); + virtual bool InputData(std::vector& data); + virtual void Start(); inline bool duplex() const { return duplex_; } inline bool input_reference() const { return input_reference_; } diff --git a/main/audio_processing/wake_word_no_afe.cc b/main/audio_processing/wake_word_no_afe.cc new file mode 100644 index 00000000..4bffb020 --- /dev/null +++ b/main/audio_processing/wake_word_no_afe.cc @@ -0,0 +1,67 @@ +#include "wake_word_no_afe.h" +#include "application.h" + +#include +#include +#include +#include + +#define DETECTION_RUNNING_EVENT 1 + +static const char* TAG = "WakeWordDetect"; + +WakeWordDetect::WakeWordDetect() { + event_group_ = xEventGroupCreate(); +} + +WakeWordDetect::~WakeWordDetect() { + if (wakenet_data_ != nullptr) { + wakenet_iface_->destroy(wakenet_data_); + esp_srmodel_deinit(wakenet_model_); + } + + vEventGroupDelete(event_group_); +} + +void WakeWordDetect::Initialize(AudioCodec* codec) { + codec_ = codec; + + wakenet_model_ = esp_srmodel_init("model"); + + if(wakenet_model_->num > 1) { + ESP_LOGW(TAG, "More than one model found, using the first one"); + } + char *model_name = wakenet_model_->model_name[0]; + wakenet_iface_ = (esp_wn_iface_t*)esp_wn_handle_from_name(model_name); + wakenet_data_ = wakenet_iface_->create(model_name, DET_MODE_95); + + int frequency = wakenet_iface_->get_samp_rate(wakenet_data_); + int audio_chunksize = wakenet_iface_->get_samp_chunksize(wakenet_data_); + ESP_LOGI(TAG, "Wake word(%s),freq: %d, chunksize: %d", model_name, frequency, audio_chunksize); +} + +void WakeWordDetect::StartDetection() { + xEventGroupSetBits(event_group_, DETECTION_RUNNING_EVENT); +} + +void WakeWordDetect::StopDetection() { + xEventGroupClearBits(event_group_, DETECTION_RUNNING_EVENT); +} + +bool WakeWordDetect::IsDetectionRunning() { + return xEventGroupGetBits(event_group_) & DETECTION_RUNNING_EVENT; +} + +void WakeWordDetect::Feed(const std::vector& data) { + int res = wakenet_iface_->detect(wakenet_data_, (int16_t *)data.data()); + if (res > 0) { + ESP_LOGI(TAG, "Wake word detected"); + auto& app = Application::GetInstance(); + app.ToggleChatState(); + } +} + +size_t WakeWordDetect::GetFeedSize() { + + return wakenet_iface_->get_samp_chunksize(wakenet_data_) * codec_->input_channels(); +} diff --git a/main/audio_processing/wake_word_no_afe.h b/main/audio_processing/wake_word_no_afe.h new file mode 100644 index 00000000..90e1110d --- /dev/null +++ b/main/audio_processing/wake_word_no_afe.h @@ -0,0 +1,42 @@ +#ifndef WAKE_WORD_DETECT_H +#define WAKE_WORD_DETECT_H + +#include +#include +#include + +#include "model_path.h" +#include "esp_wn_iface.h" +#include "esp_wn_models.h" + +#include +#include +#include +#include +#include +#include + +#include "audio_codec.h" +#include + +class WakeWordDetect { +public: + WakeWordDetect(); + ~WakeWordDetect(); + + void Initialize(AudioCodec* codec); + void Feed(const std::vector& data); + void StartDetection(); + void StopDetection(); + bool IsDetectionRunning(); + size_t GetFeedSize(); + +private: + esp_wn_iface_t *wakenet_iface_ = nullptr; + model_iface_data_t *wakenet_data_ = nullptr; + srmodel_list_t *wakenet_model_ = nullptr; + EventGroupHandle_t event_group_; + AudioCodec* codec_ = nullptr; +}; + +#endif diff --git a/main/boards/esp-hi/config.h b/main/boards/esp-hi/config.h new file mode 100644 index 00000000..9ea9ba74 --- /dev/null +++ b/main/boards/esp-hi/config.h @@ -0,0 +1,44 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_ADC_MIC_CHANNEL 2 +#define AUDIO_PDM_SPEAK_P_GPIO GPIO_NUM_6 +#define AUDIO_PDM_SPEAK_N_GPIO GPIO_NUM_7 +#define AUDIO_PA_CTL_GPIO GPIO_NUM_3 + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_9 +#define MOVE_WAKE_BUTTON_GPIO GPIO_NUM_0 +#define AUDIO_WAKE_BUTTON_GPIO GPIO_NUM_1 + +#define DISPLAY_MOSI_PIN GPIO_NUM_4 +#define DISPLAY_CLK_PIN GPIO_NUM_5 +#define DISPLAY_DC_PIN GPIO_NUM_10 +#define DISPLAY_RST_PIN GPIO_NUM_NC +#define DISPLAY_CS_PIN GPIO_NUM_NC + +#define FL_GPIO_NUM GPIO_NUM_21 +#define FR_GPIO_NUM GPIO_NUM_19 +#define BL_GPIO_NUM GPIO_NUM_20 +#define BR_GPIO_NUM GPIO_NUM_18 + +#define LCD_TYPE_ST7789_SERIAL +#define DISPLAY_WIDTH 160 +#define DISPLAY_HEIGHT 80 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY true + +#define DISPLAY_INVERT_COLOR true +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false +#define DISPLAY_SPI_MODE 0 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-hi/config.json b/main/boards/esp-hi/config.json new file mode 100644 index 00000000..bd31a9c3 --- /dev/null +++ b/main/boards/esp-hi/config.json @@ -0,0 +1,37 @@ +{ + "target": "esp32c3", + "builds": [ + { + "name": "esp-hi", + "sdkconfig_append": [ + "CONFIG_IDF_TARGET=\"esp32c3\"", + "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions_hi.csv\"", + "CONFIG_BOARD_TYPE_ESP_HI=y", + "CONFIG_SR_WN_WN9S_HILEXIN=y", + "CONFIG_FL_ANGLE_NEUTRAL=78", + "CONFIG_FR_ANGLE_NEUTRAL=108", + "CONFIG_BR_ANGLE_NEUTRAL=64", + "CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3", + "CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=4", + "CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n", + "CONFIG_ESP_WIFI_RX_BA_WIN=4", + "CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n", + "CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0", + "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n", + "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y", + "CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144", + "CONFIG_FREERTOS_HZ=1000", + "CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768", + "CONFIG_LWIP_MAX_SOCKETS=10", + "CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16", + "CONFIG_LWIP_IPV6=n", + "CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048", + "CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y", + "CONFIG_NEWLIB_NANO_FORMAT=y", + "CONFIG_MMAP_FILE_NAME_LENGTH=25", + "CONFIG_ESP_CONSOLE_NONE=y" + ] + } + ] +} diff --git a/main/boards/esp-hi/dog_action extra.cc b/main/boards/esp-hi/dog_action extra.cc new file mode 100644 index 00000000..59b3ab84 --- /dev/null +++ b/main/boards/esp-hi/dog_action extra.cc @@ -0,0 +1,64 @@ +#include "iot/thing.h" +#include "board.h" +#include "audio_codec.h" + +#include +#include +#include "servo_dog_ctrl.h" + +#define TAG "Message" + +namespace iot { + +class DogAction_extra : public Thing { +private: + bool is_moving_ = false; + + void InitializePlayer() + { + ESP_LOGI(TAG, "Dog action initialized"); + } + +public: + DogAction_extra() : Thing("DogAction_extra", "机器人扩展动作控制") + { + InitializePlayer(); + + // 定义设备的属性 + properties_.AddBooleanProperty("is_moving", "机器人是否正在移动", [this]() -> bool { + return is_moving_; + }); + + // 定义设备可以被远程执行的指令 + methods_.AddMethod("retract_legs", "机器人收回腿部", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + servo_dog_ctrl_send(DOG_STATE_RETRACT_LEGS, NULL); + }); + + methods_.AddMethod("stop", "立即停止机器人当前动作", ParameterList(), [this](const ParameterList & parameters) { + if (is_moving_) { + is_moving_ = false; + servo_dog_ctrl_send(DOG_STATE_IDLE, NULL); + } + }); + + methods_.AddMethod("shake_hand", "机器人做握手动作", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + servo_dog_ctrl_send(DOG_STATE_SHAKE_HAND, NULL); + }); + + methods_.AddMethod("shake_back_legs", "机器人伸懒腰", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + servo_dog_ctrl_send(DOG_STATE_SHAKE_BACK_LEGS, NULL); + }); + + methods_.AddMethod("jump_forward", "机器人向前跳跃", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + servo_dog_ctrl_send(DOG_STATE_JUMP_FORWARD, NULL); + }); + } +}; + +} // namespace iot + +DECLARE_THING(DogAction_extra); diff --git a/main/boards/esp-hi/dog_action_basic.cc b/main/boards/esp-hi/dog_action_basic.cc new file mode 100644 index 00000000..39765d9a --- /dev/null +++ b/main/boards/esp-hi/dog_action_basic.cc @@ -0,0 +1,75 @@ +#include "iot/thing.h" +#include "board.h" +#include "audio_codec.h" + +#include +#include +#include "servo_dog_ctrl.h" + +#define TAG "Message" + +namespace iot { + +class DogAction_basic : public Thing { +private: + bool is_moving_ = false; + + void InitializePlayer() + { + ESP_LOGI(TAG, "Dog action initialized"); + } + +public: + DogAction_basic() : Thing("DogAction_basic", "机器人基础动作控制") + { + InitializePlayer(); + + // 定义设备的属性 + properties_.AddBooleanProperty("is_moving", "机器人是否正在移动", [this]() -> bool { + return is_moving_; + }); + + // 定义设备可以被远程执行的指令 + methods_.AddMethod("forward", "机器人向前移动", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL); + }); + + methods_.AddMethod("backward", "机器人向后移动", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + servo_dog_ctrl_send(DOG_STATE_BACKWARD, NULL); + }); + + methods_.AddMethod("sway_back_forth", "机器人做前后摇摆动作", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + servo_dog_ctrl_send(DOG_STATE_SWAY_BACK_FORTH, NULL); + }); + + methods_.AddMethod("turn_left", "机器人向左转", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + servo_dog_ctrl_send(DOG_STATE_TURN_LEFT, NULL); + }); + + methods_.AddMethod("turn_right", "机器人向右转", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + servo_dog_ctrl_send(DOG_STATE_TURN_RIGHT, NULL); + }); + + methods_.AddMethod("lay_down", "机器人趴下", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + servo_dog_ctrl_send(DOG_STATE_LAY_DOWN, NULL); + }); + + methods_.AddMethod("sway", "机器人做左右摇摆动作", ParameterList(), [this](const ParameterList & parameters) { + is_moving_ = true; + dog_action_args_t args = { + .repeat_count = 4, + }; + servo_dog_ctrl_send(DOG_STATE_SWAY, &args); + }); + } +}; + +} // namespace iot + +DECLARE_THING(DogAction_basic); diff --git a/main/boards/esp-hi/dog_light.cc b/main/boards/esp-hi/dog_light.cc new file mode 100644 index 00000000..5d6d2fc5 --- /dev/null +++ b/main/boards/esp-hi/dog_light.cc @@ -0,0 +1,113 @@ +#include "iot/thing.h" +#include "board.h" +#include "audio_codec.h" + +#include +#include +#include "driver/rmt_tx.h" +#include "led_strip.h" + +#define TAG "Light" + +static led_strip_handle_t led_strip; + +static const led_strip_config_t bsp_strip_config = { + .strip_gpio_num = GPIO_NUM_8, + .max_leds = 4, + .led_model = LED_MODEL_WS2812, + .flags = { + .invert_out = false + } +}; + +static const led_strip_rmt_config_t bsp_rmt_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10 * 1000 * 1000, + .flags = { + .with_dma = false + } +}; + +esp_err_t bsp_led_init() +{ + ESP_LOGI(TAG, "BLINK_GPIO setting %d", bsp_strip_config.strip_gpio_num); + + ESP_ERROR_CHECK(led_strip_new_rmt_device(&bsp_strip_config, &bsp_rmt_config, &led_strip)); + led_strip_set_pixel(led_strip, 0, 0x00, 0x00, 0x00); + led_strip_set_pixel(led_strip, 1, 0x00, 0x00, 0x00); + led_strip_set_pixel(led_strip, 2, 0x00, 0x00, 0x00); + led_strip_set_pixel(led_strip, 3, 0x00, 0x00, 0x00); + led_strip_refresh(led_strip); + + return ESP_OK; +} + +esp_err_t bsp_led_rgb_set(uint8_t r, uint8_t g, uint8_t b) +{ + esp_err_t ret = ESP_OK; + + ret |= led_strip_set_pixel(led_strip, 0, r, g, b); + ret |= led_strip_set_pixel(led_strip, 1, r, g, b); + ret |= led_strip_set_pixel(led_strip, 2, r, g, b); + ret |= led_strip_set_pixel(led_strip, 3, r, g, b); + ret |= led_strip_refresh(led_strip); + return ret; +} + +namespace iot { +class DogLight : public Thing { +private: + bool power_ = false; + + void InitializeGpio() + { + bsp_led_init(); + bsp_led_rgb_set(0x00, 0x00, 0x00); + ESP_LOGI(TAG, "lamp InitializeGpio"); + } + +public: + DogLight() : Thing("DogLight", "机器人头灯"), power_(false) + { + InitializeGpio(); + + properties_.AddBooleanProperty("power", "灯是否打开", [this]() -> bool { + return power_; + }); + + methods_.AddMethod("TurnOn", "打开灯", ParameterList(), [this](const ParameterList & parameters) { + power_ = true; + bsp_led_rgb_set(0xFF, 0xFF, 0xFF); + ESP_LOGI(TAG, "lamp TurnOn"); + }); + + methods_.AddMethod("TurnOff", "关闭灯", ParameterList(), [this](const ParameterList & parameters) { + power_ = false; + bsp_led_rgb_set(0x00, 0x00, 0x00); + ESP_LOGI(TAG, "lamp TurnOff"); + }); + + methods_.AddMethod("SetRGB", "设置RGB颜色", + ParameterList({ + Parameter("r", "红色值(0-255)", kValueTypeNumber, true), + Parameter("g", "绿色值(0-255)", kValueTypeNumber, true), + Parameter("b", "蓝色值(0-255)", kValueTypeNumber, true) + }), [this](const ParameterList & parameters) { + int r = parameters["r"].number(); + int g = parameters["g"].number(); + int b = parameters["b"].number(); + + r = std::max(0, std::min(255, r)); + g = std::max(0, std::min(255, g)); + b = std::max(0, std::min(255, b)); + + power_ = true; + bsp_led_rgb_set(r, g, b); + ESP_LOGI(TAG, "lamp SetRGB: r=%d, g=%d, b=%d", r, g, b); + }); + } +}; + +} // namespace iot + +DECLARE_THING(DogLight); diff --git a/main/boards/esp-hi/emoji_display.cc b/main/boards/esp-hi/emoji_display.cc new file mode 100644 index 00000000..8d66c514 --- /dev/null +++ b/main/boards/esp-hi/emoji_display.cc @@ -0,0 +1,171 @@ +#include +#include "display/lcd_display.h" +#include +#include "mmap_generate_emoji.h" +#include "emoji_display.h" + +#include +#include +#include +#include +#include + +static const char *TAG = "emoji"; + +namespace anim { + +bool EmojiPlayer::OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) +{ + auto* disp_drv = static_cast(user_ctx); + anim_player_flush_ready(disp_drv); + return true; +} + +void EmojiPlayer::OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + auto* panel = static_cast(anim_player_get_user_data(handle)); + esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data); +} + +EmojiPlayer::EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) +{ + ESP_LOGI(TAG, "Create EmojiPlayer, panel: %p, panel_io: %p", panel, panel_io); + const mmap_assets_config_t assets_cfg = { + .partition_label = "assets_A", + .max_files = MMAP_EMOJI_FILES, + .checksum = MMAP_EMOJI_CHECKSUM, + .flags = {.mmap_enable = true, .full_check = true} + }; + + mmap_assets_new(&assets_cfg, &assets_handle_); + + anim_player_config_t player_cfg = { + .flush_cb = OnFlush, + .update_cb = NULL, + .user_data = panel, + .flags = {.swap = true}, + .task = ANIM_PLAYER_INIT_CONFIG() + }; + + player_handle_ = anim_player_init(&player_cfg); + + const esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = OnFlushIoReady, + }; + esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, player_handle_); + StartPlayer(MMAP_EMOJI_CONNECTING_AAF, true, 15); +} + +EmojiPlayer::~EmojiPlayer() +{ + if (player_handle_) { + anim_player_update(player_handle_, PLAYER_ACTION_STOP); + anim_player_deinit(player_handle_); + player_handle_ = nullptr; + } + + if (assets_handle_) { + mmap_assets_del(assets_handle_); + assets_handle_ = NULL; + } +} + +void EmojiPlayer::StartPlayer(int aaf, bool repeat, int fps) +{ + if (player_handle_) { + uint32_t start, end; + const void *src_data; + size_t src_len; + + src_data = mmap_assets_get_mem(assets_handle_, aaf); + src_len = mmap_assets_get_size(assets_handle_, aaf); + + anim_player_set_src_data(player_handle_, src_data, src_len); + anim_player_get_segment(player_handle_, &start, &end); + if(MMAP_EMOJI_WAKE_AAF == aaf){ + start = 7; + } + anim_player_set_segment(player_handle_, start, end, fps, true); + anim_player_update(player_handle_, PLAYER_ACTION_START); + } +} + +void EmojiPlayer::StopPlayer() +{ + if (player_handle_) { + anim_player_update(player_handle_, PLAYER_ACTION_STOP); + } +} + +EmojiWidget::EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) +{ + InitializePlayer(panel, panel_io); +} + +EmojiWidget::~EmojiWidget() +{ + +} + +void EmojiWidget::SetEmotion(const char* emotion) +{ + if (!player_) { + return; + } + + using Param = std::tuple; + static const std::unordered_map emotion_map = { + {"happy", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, + {"laughing", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, + {"funny", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, + {"loving", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, + {"embarrassed", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, + {"confident", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, + {"delicious", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, + {"sad", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}}, + {"crying", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}}, + {"sleepy", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}}, + {"silly", {MMAP_EMOJI_SAD_LOOP_AAF, true, 25}}, + {"angry", {MMAP_EMOJI_ANGER_LOOP_AAF, true, 25}}, + {"surprised", {MMAP_EMOJI_PANIC_LOOP_AAF, true, 25}}, + {"shocked", {MMAP_EMOJI_PANIC_LOOP_AAF, true, 25}}, + {"thinking", {MMAP_EMOJI_HAPPY_LOOP_AAF, true, 25}}, + {"winking", {MMAP_EMOJI_BLINK_QUICK_AAF, true, 5}}, + {"relaxed", {MMAP_EMOJI_SCORN_LOOP_AAF, true, 25}}, + {"confused", {MMAP_EMOJI_SCORN_LOOP_AAF, true, 25}}, + }; + + auto it = emotion_map.find(emotion); + if (it != emotion_map.end()) { + const auto& [aaf, repeat, fps] = it->second; + player_->StartPlayer(aaf, repeat, fps); + } else if (strcmp(emotion, "neutral") == 0) { + } +} + +void EmojiWidget::SetStatus(const char* status) +{ + if (player_) { + if (strcmp(status, "聆听中...") == 0) { + player_->StartPlayer(MMAP_EMOJI_ASKING_AAF, true, 15); + } else if (strcmp(status, "待命") == 0) { + player_->StartPlayer(MMAP_EMOJI_WAKE_AAF, true, 15); + } + } +} + +void EmojiWidget::InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io) +{ + player_ = std::make_unique(panel, panel_io); +} + +bool EmojiWidget::Lock(int timeout_ms) +{ + return true; +} + +void EmojiWidget::Unlock() +{ +} + +} // namespace anim diff --git a/main/boards/esp-hi/emoji_display.h b/main/boards/esp-hi/emoji_display.h new file mode 100644 index 00000000..2d29f62b --- /dev/null +++ b/main/boards/esp-hi/emoji_display.h @@ -0,0 +1,54 @@ +#pragma once + +#include "display/lcd_display.h" +#include +#include +#include +#include +#include "anim_player.h" +#include "mmap_generate_emoji.h" + +namespace anim { + +class EmojiPlayer; + +using FlushIoReadyCallback = std::function; +using FlushCallback = std::function; + +class EmojiPlayer { +public: + EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); + ~EmojiPlayer(); + + void StartPlayer(int aaf, bool repeat, int fps); + void StopPlayer(); + +private: + static bool OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); + static void OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data); + + anim_player_handle_t player_handle_; + mmap_assets_handle_t assets_handle_; +}; + +class EmojiWidget : public Display { +public: + EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); + virtual ~EmojiWidget(); + + virtual void SetEmotion(const char* emotion) override; + virtual void SetStatus(const char* status) override; + anim::EmojiPlayer* GetPlayer() + { + return player_.get(); + } + +private: + void InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io); + virtual bool Lock(int timeout_ms = 0) override; + virtual void Unlock() override; + + std::unique_ptr player_; +}; + +} // namespace anim diff --git a/main/boards/esp-hi/esp_hi.cc b/main/boards/esp-hi/esp_hi.cc new file mode 100644 index 00000000..7de5cb58 --- /dev/null +++ b/main/boards/esp-hi/esp_hi.cc @@ -0,0 +1,242 @@ +#include "wifi_board.h" +#include "audio_codecs/adc_pdm_audio_codec.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "iot/thing_manager.h" +#include +#include +#include +#include + +#include "display/lcd_display.h" +#include +#include +#include +#include "esp_lcd_ili9341.h" + +#include "assets/lang_config.h" +#include "anim_player.h" +#include "emoji_display.h" +#include "servo_dog_ctrl.h" + +#define TAG "ESP_HI" + +static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { + {0x11, NULL, 0, 120}, // Sleep out, Delay 120ms + {0xB1, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0}, + {0xB2, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0}, + {0xB3, (uint8_t []){0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A}, 6, 0}, + {0xB4, (uint8_t []){0x03}, 1, 0}, // Dot inversion + {0xC0, (uint8_t []){0x44, 0x04, 0x04}, 3, 0}, + {0xC1, (uint8_t []){0xC0}, 1, 0}, + {0xC2, (uint8_t []){0x0D, 0x00}, 2, 0}, + {0xC3, (uint8_t []){0x8D, 0x6A}, 2, 0}, + {0xC4, (uint8_t []){0x8D, 0xEE}, 2, 0}, + {0xC5, (uint8_t []){0x08}, 1, 0}, + {0xE0, (uint8_t []){0x0F, 0x10, 0x03, 0x03, 0x07, 0x02, 0x00, 0x02, 0x07, 0x0C, 0x13, 0x38, 0x0A, 0x0E, 0x03, 0x10}, 16, 0}, + {0xE1, (uint8_t []){0x10, 0x0B, 0x04, 0x04, 0x10, 0x03, 0x00, 0x03, 0x03, 0x09, 0x17, 0x33, 0x0B, 0x0C, 0x06, 0x10}, 16, 0}, + {0x35, (uint8_t []){0x00}, 1, 0}, + {0x3A, (uint8_t []){0x05}, 1, 0}, + {0x36, (uint8_t []){0xC8}, 1, 0}, + {0x29, NULL, 0, 0}, // Display on + {0x2C, NULL, 0, 0}, // Memory write +}; + +class EspHi : public WifiBoard { +private: + Button boot_button_; + Button audio_wake_button_; + Button move_wake_button_; + anim::EmojiWidget* display_ = nullptr; + + void HandleMoveWakePressDown(int64_t current_time, int64_t &last_trigger_time, int &gesture_state) + { + int64_t interval = last_trigger_time == 0 ? 0 : current_time - last_trigger_time; + last_trigger_time = current_time; + + if (interval > 1000) { + gesture_state = 0; + } else { + switch (gesture_state) { + case 0: + break; + case 1: + if (interval > 300) { + gesture_state = 2; + } + break; + case 2: + if (interval > 100) { + gesture_state = 0; + } + break; + } + } + } + + void HandleMoveWakePressUp(int64_t current_time, int64_t &last_trigger_time, int &gesture_state) + { + int64_t interval = current_time - last_trigger_time; + + if (interval > 1000) { + gesture_state = 0; + } else { + switch (gesture_state) { + case 0: + if (interval > 300) { + gesture_state = 1; + } + break; + case 1: + break; + case 2: + if (interval < 100) { + ESP_LOGI(TAG, "gesture detected"); + gesture_state = 0; + auto &app = Application::GetInstance(); + app.ToggleChatState(); + } + break; + } + } + } + + void InitializeButtons() + { + static int64_t last_trigger_time = 0; + static int gesture_state = 0; // 0: init, 1: wait second long interval, 2: wait oscillation + + boot_button_.OnClick([this]() { + auto &app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + audio_wake_button_.OnPressDown([this]() { + }); + + audio_wake_button_.OnPressUp([this]() { + }); + + move_wake_button_.OnPressDown([this]() { + int64_t current_time = esp_timer_get_time() / 1000; + HandleMoveWakePressDown(current_time, last_trigger_time, gesture_state); + }); + + move_wake_button_.OnPressUp([this]() { + int64_t current_time = esp_timer_get_time() / 1000; + HandleMoveWakePressUp(current_time, last_trigger_time, gesture_state); + }); + } + + void InitializeIot() + { + ESP_LOGI(TAG, "Initialize Iot"); + auto &thing_manager = iot::ThingManager::GetInstance(); + thing_manager.AddThing(iot::CreateThing("DogLight")); + thing_manager.AddThing(iot::CreateThing("DogAction_basic")); + thing_manager.AddThing(iot::CreateThing("DogAction_extra")); + } + + void InitializeSpi() + { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_CLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * 10 * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeLcdDisplay() + { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_CS_PIN; + io_config.dc_gpio_num = DISPLAY_DC_PIN; + io_config.spi_mode = DISPLAY_SPI_MODE; + io_config.pclk_hz = 40 * 1000 * 1000; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + const ili9341_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), + }; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = (void *) &vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_set_gap(panel, 0, 24); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + ESP_LOGI(TAG, "LCD panel create success, %p", panel); + + esp_lcd_panel_disp_on_off(panel, true); + + ESP_LOGI(TAG, "Create emoji widget, panel: %p, panel_io: %p", panel, panel_io); + display_ = new anim::EmojiWidget(panel, panel_io); + + servo_dog_ctrl_config_t config = { + .fl_gpio_num = FL_GPIO_NUM, + .fr_gpio_num = FR_GPIO_NUM, + .bl_gpio_num = BL_GPIO_NUM, + .br_gpio_num = BR_GPIO_NUM, + }; +#if CONFIG_ESP_CONSOLE_NONE + servo_dog_ctrl_init(&config); +#endif + } + +public: + EspHi() : boot_button_(BOOT_BUTTON_GPIO), + audio_wake_button_(AUDIO_WAKE_BUTTON_GPIO), + move_wake_button_(MOVE_WAKE_BUTTON_GPIO) + { + + InitializeButtons(); + InitializeIot(); + InitializeSpi(); + InitializeLcdDisplay(); + } + + virtual AudioCodec* GetAudioCodec() override + { + static AdcPdmAudioCodec audio_codec( + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_ADC_MIC_CHANNEL, + AUDIO_PDM_SPEAK_P_GPIO, + AUDIO_PDM_SPEAK_N_GPIO, + AUDIO_PA_CTL_GPIO); + return &audio_codec; + } + + virtual Display* GetDisplay() override + { + return display_; + } +}; + +DECLARE_BOARD(EspHi); diff --git a/main/idf_component.yml b/main/idf_component.yml index 212eacb1..5bedf574 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -4,7 +4,10 @@ dependencies: espressif/esp_lcd_ili9341: ==1.2.0 espressif/esp_lcd_gc9a01: ==2.0.1 espressif/esp_lcd_st77916: ^1.0.1 - espressif/esp_lcd_st7796: ==1.3.2 + espressif/esp_lcd_st7796: + version: '1.3.2' + rules: + - if: target not in [esp32c3] espressif/esp_lcd_spd2010: ==1.0.2 espressif/esp_io_expander_tca9554: ==2.0.0 espressif/esp_lcd_panel_io_additions: ^1.0.1 @@ -25,6 +28,10 @@ dependencies: lvgl/lvgl: ~9.2.2 esp_lvgl_port: ~2.6.0 espressif/esp_io_expander_tca95xx_16bit: ^2.0.0 + espressif2022/image_player: ^1.1.0 + espressif/adc_mic: ^0.1 + espressif/esp_mmap_assets: ">=1.2" + tny-robotics/sh1106-esp-idf: version: ^1.0.0 rules: @@ -46,8 +53,11 @@ dependencies: version: '*' rules: - if: target in [esp32p4] + lijunru-hub/servo_dog_ctrl: + version: '^0.1' + rules: + - if: target in [esp32c3] ## Required IDF version idf: version: '>=5.3' - diff --git a/partitions_hi.csv b/partitions_hi.csv new file mode 100644 index 00000000..90c9c43c --- /dev/null +++ b/partitions_hi.csv @@ -0,0 +1,8 @@ +# ESP-IDF Partition Table +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x4000, +otadata, data, ota, 0xd000, 0x2000, +phy_init, data, phy, 0xf000, 0x1000, +model, data, spiffs, 0x10000, 0xD0000, +factory, app, factory, 0xe0000, 2200K, +assets_A, data, spiffs, , 700K,