diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 5fdd5726b5..a832818fd7 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES "audio_codecs/audio_codec.cc" "audio_codecs/no_audio_codec.cc" "audio_codecs/box_audio_codec.cc" "audio_codecs/es8311_audio_codec.cc" + "audio_codecs/es8374_audio_codec.cc" "audio_codecs/es8388_audio_codec.cc" "led/single_led.cc" "led/circular_strip.cc" @@ -136,6 +137,8 @@ elseif(CONFIG_BOARD_TYPE_SENSECAP_WATCHER) set(BOARD_TYPE "sensecap-watcher") elseif(CONFIG_BOARD_TYPE_DOIT_S3_AIBOX) set(BOARD_TYPE "doit-s3-aibox") +elseif(CONFIG_BOARD_TYPE_MIXGO_NOVA) + set(BOARD_TYPE "mixgo-nova") elseif(CONFIG_BOARD_TYPE_ESP32_CGC) set(BOARD_TYPE "esp32-cgc") endif() diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 5fe196b31b..631cde6b80 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -160,6 +160,8 @@ choice BOARD_TYPE bool "SenseCAP Watcher" config BOARD_TYPE_DOIT_S3_AIBOX bool "四博智联AI陪伴盒子" + config BOARD_TYPE_MIXGO_NOVA + bool "元控·青春" endchoice choice DISPLAY_OLED_TYPE diff --git a/main/audio_codecs/es8374_audio_codec.cc b/main/audio_codecs/es8374_audio_codec.cc new file mode 100644 index 0000000000..54d13be0e7 --- /dev/null +++ b/main/audio_codecs/es8374_audio_codec.cc @@ -0,0 +1,194 @@ +#include "es8374_audio_codec.h" + +#include + +static const char TAG[] = "Es8374AudioCodec"; + +Es8374AudioCodec::Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8374_addr, bool use_mclk) { + duplex_ = true; // 是否双工 + input_reference_ = false; // 是否使用参考输入,实现回声消除 + input_channels_ = 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + pa_pin_ = pa_pin; + CreateDuplexChannels(mclk, bclk, ws, dout, din); + + // Do initialize of related interface: data_if, ctrl_if and gpio_if + audio_codec_i2s_cfg_t i2s_cfg = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + // Output + audio_codec_i2c_cfg_t i2c_cfg = { + .port = i2c_port, + .addr = es8374_addr, + .bus_handle = i2c_master_handle, + }; + ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(ctrl_if_ != NULL); + + gpio_if_ = audio_codec_new_gpio(); + assert(gpio_if_ != NULL); + + es8374_codec_cfg_t es8374_cfg = {}; + es8374_cfg.ctrl_if = ctrl_if_; + es8374_cfg.gpio_if = gpio_if_; + es8374_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH; + es8374_cfg.pa_pin = pa_pin; + codec_if_ = es8374_codec_new(&es8374_cfg); + assert(codec_if_ != NULL); + + esp_codec_dev_cfg_t dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_OUT, + .codec_if = codec_if_, + .data_if = data_if_, + }; + output_dev_ = esp_codec_dev_new(&dev_cfg); + assert(output_dev_ != NULL); + dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; + input_dev_ = esp_codec_dev_new(&dev_cfg); + assert(input_dev_ != NULL); + esp_codec_set_disable_when_closed(output_dev_, false); + esp_codec_set_disable_when_closed(input_dev_, false); + ESP_LOGI(TAG, "Es8374AudioCodec initialized"); +} + +Es8374AudioCodec::~Es8374AudioCodec() { + 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_); + + audio_codec_delete_codec_if(codec_if_); + audio_codec_delete_ctrl_if(ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void Es8374AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + assert(input_sample_rate_ == output_sample_rate_); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = 6, + .dma_frame_num = 240, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + #ifdef I2S_HW_VERSION_2 + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + #endif + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void Es8374AudioCodec::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void Es8374AudioCodec::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 = 0, + .sample_rate = (uint32_t)input_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 40.0)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void Es8374AudioCodec::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_pin_ != GPIO_NUM_NC) { + gpio_set_level(pa_pin_, 1); + } + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + if (pa_pin_ != GPIO_NUM_NC) { + gpio_set_level(pa_pin_, 0); + } + } + AudioCodec::EnableOutput(enable); +} + +int Es8374AudioCodec::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 Es8374AudioCodec::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; +} \ No newline at end of file diff --git a/main/audio_codecs/es8374_audio_codec.h b/main/audio_codecs/es8374_audio_codec.h new file mode 100644 index 0000000000..f10915d4f9 --- /dev/null +++ b/main/audio_codecs/es8374_audio_codec.h @@ -0,0 +1,38 @@ +#ifndef _ES8374_AUDIO_CODEC_H +#define _ES8374_AUDIO_CODEC_H + +#include "audio_codec.h" + +#include +#include +#include +#include + +class Es8374AudioCodec : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* ctrl_if_ = nullptr; + const audio_codec_if_t* codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + gpio_num_t pa_pin_ = GPIO_NUM_NC; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, + gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, + gpio_num_t pa_pin, uint8_t es8374_addr, bool use_mclk = true); + virtual ~Es8374AudioCodec(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _ES8374_AUDIO_CODEC_H \ No newline at end of file diff --git a/main/boards/mixgo-nova/README.md b/main/boards/mixgo-nova/README.md new file mode 100644 index 0000000000..5ed60049fa --- /dev/null +++ b/main/boards/mixgo-nova/README.md @@ -0,0 +1,72 @@ +# Mixgo_Nova(元控·青春) 开发板 + +Mixgo_Nova + +‌**[Mixgo_Nova](https://mixly.cn/fredqian/mixgo_nova)**‌ 是一款专为物联网、教育及创客项目设计的多功能开发板,集成丰富传感器与无线通信模块,支持图形化编程(Mixly)和离线语音交互,适合快速原型开发与教学。 + +--- + +## 🛠️ 编译配置命令 + +**ES8374 CODE MIC采集问题:** + +``` +managed_components\espressif__esp_codec_dev\device\es8374 + +static int es8374_config_adc_input(audio_codec_es8374_t *codec, es_adc_input_t input) +{ + int ret = 0; + int reg = 0; + ret |= es8374_read_reg(codec, 0x21, ®); + if (ret == 0) { + reg = (reg & 0xcf) | 0x24; + ret |= es8374_write_reg(codec, 0x21, reg); + } + return ret; +} + +PS: L386 reg = (reg & 0xcf) | 0x14; 改成 reg = (reg & 0xcf) | 0x24; +``` + +**配置编译目标为 ESP32S3:** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig:** + +```bash +idf.py menuconfig +``` + +**选择板子:** + +``` +Xiaozhi Assistant -> Board Type -> 元控·青春 +``` + +**修改 psram 配置:** + +``` +Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> QUAD Mode PSRAM +``` + +**修改 Flash 配置:** + +``` +Serial flasher config -> Flash size -> 8 MB +Partition Table -> Custom partition CSV file -> partitions_8M.csv +``` + +**编译:** + +```bash +idf.py build +``` + +**合并BIN:** + +```bash +idf.py merge-bin -o xiaozhi-nova.bin -f raw +``` \ No newline at end of file diff --git a/main/boards/mixgo-nova/config.h b/main/boards/mixgo-nova/config.h new file mode 100644 index 0000000000..060870b373 --- /dev/null +++ b/main/boards/mixgo-nova/config.h @@ -0,0 +1,42 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_35 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_34 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_33 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_37 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_36 +#define AUDIO_CODEC_ES8374_ADDR ES8374_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_38 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_MOSI_PIN GPIO_NUM_40 +#define DISPLAY_CLK_PIN GPIO_NUM_41 +#define DISPLAY_DC_PIN GPIO_NUM_18 +#define DISPLAY_CS_PIN GPIO_NUM_45 +#define DISPLAY_RST_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_14 +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 160 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_OFFSET_X 2 +#define DISPLAY_OFFSET_Y 1 +#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/mixgo-nova/config.json b/main/boards/mixgo-nova/config.json new file mode 100644 index 0000000000..c913e55cef --- /dev/null +++ b/main/boards/mixgo-nova/config.json @@ -0,0 +1,14 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "mixgo-nova", + "sdkconfig_append": [ + "CONFIG_SPIRAM_MODE_QUAD=y", + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions_8M.csv\"", + "CONFIG_LCD_ST7735_128X160=y" + ] + } + ] +} \ No newline at end of file diff --git a/main/boards/mixgo-nova/mixgo-nova.cc b/main/boards/mixgo-nova/mixgo-nova.cc new file mode 100644 index 0000000000..86c7f9db26 --- /dev/null +++ b/main/boards/mixgo-nova/mixgo-nova.cc @@ -0,0 +1,185 @@ +#include "wifi_board.h" +#include "audio_codecs/es8374_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" +#include "iot/thing_manager.h" +#include "led/circular_strip.h" +#include "assets/lang_config.h" + +#include +#include +#include +#include +#include + +#define TAG "MIXGO_NOVA" + +LV_FONT_DECLARE(font_puhui_16_4); +LV_FONT_DECLARE(font_awesome_16_4); + + +class MIXGO_NOVA : public WifiBoard { +private: + Button boot_button_; + Button volume_up_button_; + Button volume_down_button_; + LcdDisplay* display_; + i2c_master_bus_handle_t codec_i2c_bus_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + 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 * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + volume_up_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_up_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + volume_down_button_.OnClick([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() - 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + }); + + volume_down_button_.OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + } + + void InitializeSt7789Display() { + 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 = 0; + 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(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + 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; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(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_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY, + { + .text_font = &font_puhui_16_4, + .icon_font = &font_awesome_16_4, + .emoji_font = font_emoji_32_init(), + }); + } + + void InitializeIot() { + auto& thing_manager = iot::ThingManager::GetInstance(); + thing_manager.AddThing(iot::CreateThing("Speaker")); + thing_manager.AddThing(iot::CreateThing("Screen")); + } + +public: + MIXGO_NOVA() : + boot_button_(BOOT_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeSt7789Display(); + InitializeButtons(); + InitializeIot(); + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + GetBacklight()->RestoreBrightness(); + } + } + + virtual Led* GetLed() override { + static CircularStrip led(BUILTIN_LED_GPIO, 4); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8374AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8374_ADDR); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + return nullptr; + } + +}; + +DECLARE_BOARD(MIXGO_NOVA);