diff --git a/README.md b/README.md
index d27adb5b..15b423a2 100755
--- a/README.md
+++ b/README.md
@@ -63,3 +63,12 @@
详细的使用说明以及测试服的注意事项,请参考 [小智测试服的帮助说明](https://xiaozhi.me/help)。
+## Star History
+
+
+
+
+
+
+
+
diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt
old mode 100755
new mode 100644
index 2d93cc45..901cad05
--- 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/cores3_audio_codec.cc"
"display/display.cc"
"display/no_display.cc"
"display/st7789_display.cc"
@@ -54,6 +55,8 @@ elseif(CONFIG_BOARD_TYPE_TERRENCE_C3_DEV)
set(BOARD_TYPE "terrence-c3-dev")
elseif(CONFIG_BOARD_TYPE_MAGICLICK_2P4)
set(BOARD_TYPE "magiclick-2p4")
+elseif(CONFIG_BOARD_TYPE_M5STACK_CORE_S3)
+ set(BOARD_TYPE "m5stack-core-s3")
endif()
file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc)
list(APPEND SOURCES ${BOARD_SOURCES})
diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild
index a03751e4..a2706c99 100644
--- a/main/Kconfig.projbuild
+++ b/main/Kconfig.projbuild
@@ -52,6 +52,8 @@ choice BOARD_TYPE
bool "立创开发板"
config BOARD_TYPE_MAGICLICK_2P4
bool "神奇按钮Magiclick_2.4"
+ config BOARD_TYPE_M5STACK_CORE_S3
+ bool "M5Stack CoreS3"
endchoice
endmenu
diff --git a/main/audio_codecs/cores3_audio_codec.cc b/main/audio_codecs/cores3_audio_codec.cc
new file mode 100644
index 00000000..51c3bbc0
--- /dev/null
+++ b/main/audio_codecs/cores3_audio_codec.cc
@@ -0,0 +1,245 @@
+#include "cores3_audio_codec.h"
+
+#include
+#include
+#include
+#include
+
+
+static const char TAG[] = "CoreS3AudioCodec";
+
+CoreS3AudioCodec::CoreS3AudioCodec(void* i2c_master_handle, 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,
+ uint8_t aw88298_addr, uint8_t es7210_addr, bool input_reference) {
+ duplex_ = true; // 是否双工
+ input_reference_ = input_reference; // 是否使用参考输入,实现回声消除
+ input_channels_ = input_reference_ ? 2 : 1; // 输入通道数
+ input_sample_rate_ = input_sample_rate;
+ output_sample_rate_ = output_sample_rate;
+
+ 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);
+
+ // Audio Output(Speaker)
+ audio_codec_i2c_cfg_t i2c_cfg = {
+ .port = (i2c_port_t)1,
+ .addr = aw88298_addr,
+ .bus_handle = i2c_master_handle,
+ };
+ out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
+ assert(out_ctrl_if_ != NULL);
+
+ gpio_if_ = audio_codec_new_gpio();
+ assert(gpio_if_ != NULL);
+
+ aw88298_codec_cfg_t aw88298_cfg = {};
+ aw88298_cfg.ctrl_if = out_ctrl_if_;
+ aw88298_cfg.gpio_if = gpio_if_;
+ aw88298_cfg.reset_pin = GPIO_NUM_NC;
+ aw88298_cfg.hw_gain.pa_voltage = 5.0;
+ aw88298_cfg.hw_gain.codec_dac_voltage = 3.3;
+ aw88298_cfg.hw_gain.pa_gain = 1;
+ out_codec_if_ = aw88298_codec_new(&aw88298_cfg);
+ assert(out_codec_if_ != NULL);
+
+ esp_codec_dev_cfg_t dev_cfg = {
+ .dev_type = ESP_CODEC_DEV_TYPE_OUT,
+ .codec_if = out_codec_if_,
+ .data_if = data_if_,
+ };
+ output_dev_ = esp_codec_dev_new(&dev_cfg);
+ assert(output_dev_ != NULL);
+
+ // Audio Input(Microphone)
+ i2c_cfg.addr = es7210_addr;
+ in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
+ assert(in_ctrl_if_ != NULL);
+
+ es7210_codec_cfg_t es7210_cfg = {};
+ es7210_cfg.ctrl_if = in_ctrl_if_;
+ es7210_cfg.mic_selected = ES7120_SEL_MIC1 | ES7120_SEL_MIC2 | ES7120_SEL_MIC3;
+ in_codec_if_ = es7210_codec_new(&es7210_cfg);
+ assert(in_codec_if_ != NULL);
+
+ dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN;
+ dev_cfg.codec_if = in_codec_if_;
+ input_dev_ = esp_codec_dev_new(&dev_cfg);
+ assert(input_dev_ != NULL);
+
+ ESP_LOGI(TAG, "CoreS3AudioCodec initialized");
+}
+
+CoreS3AudioCodec::~CoreS3AudioCodec() {
+ 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(in_codec_if_);
+ audio_codec_delete_ctrl_if(in_ctrl_if_);
+ audio_codec_delete_codec_if(out_codec_if_);
+ audio_codec_delete_ctrl_if(out_ctrl_if_);
+ audio_codec_delete_gpio_if(gpio_if_);
+ audio_codec_delete_data_if(data_if_);
+}
+
+void CoreS3AudioCodec::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_);
+
+ ESP_LOGI(TAG, "Audio IOs: mclk: %d, bclk: %d, ws: %d, dout: %d, din: %d", mclk, bclk, ws, dout, din);
+
+ i2s_chan_config_t chan_cfg = {
+ .id = I2S_NUM_0,
+ .role = I2S_ROLE_MASTER,
+ .dma_desc_num = 2,
+ .dma_frame_num = 240 * 3,
+ .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,
+ .ext_clk_freq_hz = 0,
+ .mclk_multiple = I2S_MCLK_MULTIPLE_256
+ },
+ .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,
+ .left_align = true,
+ .big_endian = false,
+ .bit_order_lsb = false
+ },
+ .gpio_cfg = {
+ .mclk = mclk,
+ .bclk = bclk,
+ .ws = ws,
+ .dout = dout,
+ .din = I2S_GPIO_UNUSED,
+ .invert_flags = {
+ .mclk_inv = false,
+ .bclk_inv = false,
+ .ws_inv = false
+ }
+ }
+ };
+
+ i2s_tdm_config_t tdm_cfg = {
+ .clk_cfg = {
+ .sample_rate_hz = (uint32_t)input_sample_rate_,
+ .clk_src = I2S_CLK_SRC_DEFAULT,
+ .ext_clk_freq_hz = 0,
+ .mclk_multiple = I2S_MCLK_MULTIPLE_256,
+ .bclk_div = 8,
+ },
+ .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_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3),
+ .ws_width = I2S_TDM_AUTO_WS_WIDTH,
+ .ws_pol = false,
+ .bit_shift = true,
+ .left_align = false,
+ .big_endian = false,
+ .bit_order_lsb = false,
+ .skip_mask = false,
+ .total_slot = I2S_TDM_AUTO_SLOT_NUM
+ },
+ .gpio_cfg = {
+ .mclk = mclk,
+ .bclk = bclk,
+ .ws = ws,
+ .dout = I2S_GPIO_UNUSED,
+ .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_tdm_mode(rx_handle_, &tdm_cfg));
+ ESP_LOGI(TAG, "Duplex channels created");
+}
+
+void CoreS3AudioCodec::SetOutputVolume(int volume) {
+ ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void CoreS3AudioCodec::EnableInput(bool enable) {
+ if (enable == input_enabled_) {
+ return;
+ }
+ if (enable) {
+ esp_codec_dev_sample_info_t fs = {
+ .bits_per_sample = 16,
+ .channel = 2,
+ .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
+ .sample_rate = (uint32_t)output_sample_rate_,
+ .mclk_multiple = 0,
+ };
+ if (input_reference_) {
+ fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1);
+ }
+ ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
+ ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), 40.0));
+ } else {
+ ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
+ }
+ AudioCodec::EnableInput(enable);
+}
+
+void CoreS3AudioCodec::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_));
+ } else {
+ ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
+ }
+ AudioCodec::EnableOutput(enable);
+}
+
+int CoreS3AudioCodec::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 CoreS3AudioCodec::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;
+}
diff --git a/main/audio_codecs/cores3_audio_codec.h b/main/audio_codecs/cores3_audio_codec.h
new file mode 100644
index 00000000..4b034b48
--- /dev/null
+++ b/main/audio_codecs/cores3_audio_codec.h
@@ -0,0 +1,37 @@
+#ifndef _BOX_AUDIO_CODEC_H
+#define _BOX_AUDIO_CODEC_H
+
+#include "audio_codec.h"
+
+#include
+#include
+
+class CoreS3AudioCodec : public AudioCodec {
+private:
+ const audio_codec_data_if_t* data_if_ = nullptr;
+ const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr;
+ const audio_codec_if_t* out_codec_if_ = nullptr;
+ const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr;
+ const audio_codec_if_t* in_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;
+
+ 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:
+ CoreS3AudioCodec(void* i2c_master_handle, 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,
+ uint8_t aw88298_addr, uint8_t es7210_addr, bool input_reference);
+ virtual ~CoreS3AudioCodec();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+};
+
+#endif // _BOX_AUDIO_CODEC_H
diff --git a/main/boards/common/i2c_device.cc b/main/boards/common/i2c_device.cc
index 187efa5a..5a9667aa 100644
--- a/main/boards/common/i2c_device.cc
+++ b/main/boards/common/i2c_device.cc
@@ -29,3 +29,7 @@ uint8_t I2cDevice::ReadReg(uint8_t reg) {
ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, 1, 100));
return buffer[0];
}
+
+void I2cDevice::ReadRegs(uint8_t reg, uint8_t* buffer, size_t length) {
+ ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, length, 100));
+}
\ No newline at end of file
diff --git a/main/boards/common/i2c_device.h b/main/boards/common/i2c_device.h
index c095b027..7bc917bf 100644
--- a/main/boards/common/i2c_device.h
+++ b/main/boards/common/i2c_device.h
@@ -12,6 +12,7 @@ protected:
void WriteReg(uint8_t reg, uint8_t value);
uint8_t ReadReg(uint8_t reg);
+ void ReadRegs(uint8_t reg, uint8_t* buffer, size_t length);
};
#endif // I2C_DEVICE_H
diff --git a/main/boards/esp-box-3/config.h b/main/boards/esp-box-3/config.h
index a1255732..14bddbd3 100644
--- a/main/boards/esp-box-3/config.h
+++ b/main/boards/esp-box-3/config.h
@@ -26,12 +26,17 @@
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
-#define DISPLAY_SDA_PIN GPIO_NUM_NC
-#define DISPLAY_SCL_PIN GPIO_NUM_NC
-#define DISPLAY_WIDTH 128
-#define DISPLAY_HEIGHT 64
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_47
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_
diff --git a/main/boards/esp-box-3/esp_box3_board.cc b/main/boards/esp-box-3/esp_box3_board.cc
index c74106f0..7df36c19 100644
--- a/main/boards/esp-box-3/esp_box3_board.cc
+++ b/main/boards/esp-box-3/esp_box3_board.cc
@@ -1,6 +1,8 @@
#include "wifi_board.h"
#include "audio_codecs/box_audio_codec.h"
-#include "display/no_display.h"
+#include "display/st7789_display.h"
+#include "esp_lcd_ili9341.h"
+#include "font_awesome_symbols.h"
#include "application.h"
#include "button.h"
#include "led.h"
@@ -8,14 +10,119 @@
#include "iot/thing_manager.h"
#include
+#include
#include
+#include
#define TAG "EspBox3Board"
+// Can move to display/st7789_display.h
+LV_FONT_DECLARE(font_puhui_14_1);
+LV_FONT_DECLARE(font_awesome_30_1);
+LV_FONT_DECLARE(font_awesome_14_1);
+
+// Init ili9341 by custom cmd
+static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
+ {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0},
+ {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0},
+ {0xC5, (uint8_t []){0xD0}, 1, 0},
+ {0xC1, (uint8_t []){0x02}, 1, 0},
+ {0xB4, (uint8_t []){0x02}, 1, 0},
+ {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0},
+ {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0},
+
+ {0xB1, (uint8_t []){00, 0x1B}, 2, 0},
+ {0x36, (uint8_t []){0x08}, 1, 0},
+ {0x3A, (uint8_t []){0x55}, 1, 0},
+ {0xB7, (uint8_t []){0x06}, 1, 0},
+
+ {0x11, (uint8_t []){0}, 0x80, 0},
+ {0x29, (uint8_t []){0}, 0x80, 0},
+
+ {0, (uint8_t []){0}, 0xff, 0},
+};
+
+// Example Display and UI overwrite in different board
+class Ili9341Display : public St7789Display {
+private:
+ lv_obj_t *user_messge_label_ = nullptr;
+ lv_obj_t *ai_messge_label_ = nullptr;
+public:
+ Ili9341Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ gpio_num_t backlight_pin, bool backlight_output_invert,
+ int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy)
+ : St7789Display(panel_io, panel, backlight_pin, backlight_output_invert, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {}
+
+ void SetStatus(const std::string &status) override
+ {
+ if (status_label_ == nullptr) {
+ return;
+ }
+ if(status=="待命")
+ {
+ SetChatMessage(" "," ");
+ }
+ DisplayLockGuard lock(this);
+ lv_label_set_text(status_label_, status.c_str());
+ }
+
+ void SetChatMessage(const std::string &role, const std::string &content) override {
+ if (ai_messge_label_== nullptr || user_messge_label_== nullptr) {
+ return;
+ }
+ DisplayLockGuard lock(this);
+ ESP_LOGI(TAG,"role:%s",role.c_str());
+ if(role=="assistant")
+ {
+ std::string new_content = "AI:" + content;
+ lv_label_set_text(ai_messge_label_, new_content.c_str());
+ }
+ else if(role=="user")
+ {
+ std::string new_content = "User:" + content;
+ lv_label_set_text(user_messge_label_, new_content.c_str());
+ }
+ else{
+ std::string new_content = "AI:";
+ lv_label_set_text(ai_messge_label_, new_content.c_str());
+ new_content="User:";
+ lv_label_set_text(user_messge_label_, new_content.c_str());
+ }
+ }
+ void SetupUI() override {
+ DisplayLockGuard lock(this);
+
+ lv_obj_del(chat_message_label_);
+
+ lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布
+
+ lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0);
+ lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
+ lv_obj_align(emotion_label_,LV_ALIGN_TOP_MID, 0, -10); // 左侧居中,向右偏移10个单位
+
+ static lv_style_t style_msg;
+ lv_style_init(&style_msg);
+ lv_style_set_width(&style_msg, LV_HOR_RES-25);
+
+ user_messge_label_ = lv_label_create(content_);
+ lv_obj_set_style_text_font(user_messge_label_, &font_puhui_14_1, 0);
+ lv_label_set_text(user_messge_label_, "User:");
+ lv_obj_add_style(user_messge_label_, &style_msg, 0);
+ lv_obj_align(user_messge_label_,LV_ALIGN_TOP_LEFT, 2, 25);
+
+ ai_messge_label_ = lv_label_create(content_);
+ lv_obj_set_style_text_font(ai_messge_label_, &font_puhui_14_1, 0);
+ lv_label_set_text(ai_messge_label_, "AI:");
+ lv_obj_add_style(ai_messge_label_, &style_msg, 0);
+ lv_obj_align(ai_messge_label_,LV_ALIGN_TOP_LEFT, 2, 77);
+ }
+};
+
class EspBox3Board : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
+ Ili9341Display* display_;
void InitializeI2c() {
// Initialize I2C peripheral
@@ -34,12 +141,65 @@ private:
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
}
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_6;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_7;
+ 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]() {
Application::GetInstance().ToggleChatState();
});
}
+ void InitializeIli9341Display() {
+ 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 = GPIO_NUM_5;
+ io_config.dc_gpio_num = GPIO_NUM_4;
+ 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));
+
+ // 初始化液晶屏驱动芯片
+ 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 = GPIO_NUM_48;
+ panel_config.flags.reset_active_high = 1,
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ 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, true);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ display_ = new Ili9341Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ display_->SetupUI();
+ }
+
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
@@ -49,6 +209,8 @@ private:
public:
EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
+ InitializeSpi();
+ InitializeIli9341Display();
InitializeButtons();
InitializeIot();
}
@@ -70,9 +232,15 @@ public:
}
virtual Display* GetDisplay() override {
- static NoDisplay display;
- return &display;
+ return display_;
}
+
+ virtual bool GetBatteryLevel(int &level, bool& charging) override
+ {
+ charging = false;
+ level = 60;
+ return true;
+ };
};
DECLARE_BOARD(EspBox3Board);
diff --git a/main/boards/m5stack-core-s3/config.h b/main/boards/m5stack-core-s3/config.h
new file mode 100644
index 00000000..4929afa0
--- /dev/null
+++ b/main/boards/m5stack-core-s3/config.h
@@ -0,0 +1,44 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// M5Stack CoreS3 Board configuration
+
+#include
+
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_DEFAULT_OUTPUT_VOLUME 80
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_0
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_33
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_34
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_13
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_12
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_11
+#define AUDIO_CODEC_AW88298_ADDR AW88298_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#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_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/main/boards/m5stack-core-s3/m5stack_core_s3.cc b/main/boards/m5stack-core-s3/m5stack_core_s3.cc
new file mode 100644
index 00000000..fbcad424
--- /dev/null
+++ b/main/boards/m5stack-core-s3/m5stack_core_s3.cc
@@ -0,0 +1,284 @@
+#include "wifi_board.h"
+#include "audio_codecs/cores3_audio_codec.h"
+#include "display/st7789_display.h"
+#include "application.h"
+#include "button.h"
+#include "led.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "iot/thing_manager.h"
+
+#include
+#include
+#include
+#include
+#include "esp_lcd_ili9341.h"
+
+#define TAG "M5StackCoreS3Board"
+
+class Axp2101 : public I2cDevice {
+public:
+ // Power Init
+ Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t data = ReadReg(0x90);
+ data |= 0b10110100;
+ WriteReg(0x90, data);
+ WriteReg(0x99, (0b11110 - 5));
+ WriteReg(0x97, (0b11110 - 2));
+ WriteReg(0x69, 0b00110101);
+ WriteReg(0x30, 0b111111);
+ WriteReg(0x90, 0xBF);
+ WriteReg(0x94, 33 - 5);
+ WriteReg(0x95, 33 - 5);
+ }
+};
+
+class Aw9523 : public I2cDevice {
+public:
+ // Exanpd IO Init
+ Aw9523(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(0x02, 0b00000111); // P0
+ WriteReg(0x03, 0b10001111); // P1
+ WriteReg(0x04, 0b00011000); // CONFIG_P0
+ WriteReg(0x05, 0b00001100); // CONFIG_P1
+ WriteReg(0x11, 0b00010000); // GCR P0 port is Push-Pull mode.
+ WriteReg(0x12, 0b11111111); // LEDMODE_P0
+ WriteReg(0x13, 0b11111111); // LEDMODE_P1
+ }
+
+ void ResetAw88298() {
+ ESP_LOGI(TAG, "Reset AW88298");
+ WriteReg(0x02, 0b00000011);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ WriteReg(0x02, 0b00000111);
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+
+ void ResetIli9342() {
+ ESP_LOGI(TAG, "Reset IlI9342");
+ WriteReg(0x03, 0b10000001);
+ vTaskDelay(pdMS_TO_TICKS(20));
+ WriteReg(0x03, 0b10000011);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+};
+
+class Ft6336 : public I2cDevice {
+public:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+
+ Ft6336(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0xA3);
+ ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id);
+ read_buffer_ = new uint8_t[6];
+ }
+
+ ~Ft6336() {
+ delete[] read_buffer_;
+ }
+
+ void UpdateTouchPoint() {
+ ReadRegs(0x02, read_buffer_, 6);
+ tp_.num = read_buffer_[0] & 0x0F;
+ tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
+ tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
+ }
+
+ const TouchPoint_t& GetTouchPoint() {
+ return tp_;
+ }
+
+private:
+ uint8_t* read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+};
+
+class M5StackCoreS3Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Axp2101* axp2101_;
+ Aw9523* aw9523_;
+ Ft6336* ft6336_;
+ St7789Display* display_;
+ Button boot_button_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ axp2101_ = new Axp2101(i2c_bus_, 0x34);
+ }
+
+ void InitializeAw9523() {
+ ESP_LOGI(TAG, "Init AW9523");
+ aw9523_ = new Aw9523(i2c_bus_, 0x58);
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+
+ static void touchpad_daemon(void* param) {
+ vTaskDelay(pdMS_TO_TICKS(2000));
+ auto& board = (M5StackCoreS3Board&)Board::GetInstance();
+ auto touchpad = board.GetTouchpad();
+ bool was_touched = false;
+ while (1) {
+ touchpad->UpdateTouchPoint();
+ if (touchpad->GetTouchPoint().num > 0) {
+ // On press
+ if (!was_touched) {
+ was_touched = true;
+ Application::GetInstance().ToggleChatState();
+ }
+ }
+ // On release
+ else if (was_touched) {
+ was_touched = false;
+ }
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+ vTaskDelete(NULL);
+ }
+
+ void InitializeFt6336TouchPad() {
+ ESP_LOGI(TAG, "Init FT6336");
+ ft6336_ = new Ft6336(i2c_bus_, 0x38);
+ xTaskCreate(touchpad_daemon, "tp", 2048, NULL, 5, NULL);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_37;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_36;
+ 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 InitializeIli9342Display() {
+ ESP_LOGI(TAG, "Init IlI9342");
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_3;
+ io_config.dc_gpio_num = GPIO_NUM_35;
+ io_config.spi_mode = 2;
+ 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));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_NC;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ aw9523_->ResetIli9342();
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, true);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+
+ display_ = new St7789Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ Application::GetInstance().ToggleChatState();
+ });
+ }
+
+ // 物联网初始化,添加对 AI 可见设备
+ void InitializeIot() {
+ auto& thing_manager = iot::ThingManager::GetInstance();
+ thing_manager.AddThing(iot::CreateThing("Speaker"));
+ }
+
+public:
+ M5StackCoreS3Board() : boot_button_(GPIO_NUM_1) {
+ InitializeI2c();
+ InitializeAxp2101();
+ InitializeAw9523();
+ InitializeFt6336TouchPad();
+ I2cDetect();
+ InitializeSpi();
+ InitializeIli9342Display();
+ InitializeButtons();
+ InitializeIot();
+ }
+
+ virtual Led* GetBuiltinLed() override {
+ static Led led(GPIO_NUM_NC);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static CoreS3AudioCodec* audio_codec = nullptr;
+ if (audio_codec == nullptr) {
+ aw9523_->ResetAw88298();
+ audio_codec = new CoreS3AudioCodec(i2c_bus_, 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_AW88298_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
+ }
+ return audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ Ft6336* GetTouchpad() {
+ return ft6336_;
+ }
+};
+
+DECLARE_BOARD(M5StackCoreS3Board);
diff --git a/main/display/st7789_display.cc b/main/display/st7789_display.cc
index 37e0c652..1daa67f4 100644
--- a/main/display/st7789_display.cc
+++ b/main/display/st7789_display.cc
@@ -292,10 +292,19 @@ void St7789Display::SetupUI() {
lv_obj_set_width(content_, LV_HOR_RES);
lv_obj_set_flex_grow(content_, 1);
+ lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); // 垂直布局(从上到下)
+ lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布
+
emotion_label_ = lv_label_create(content_);
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
- lv_obj_center(emotion_label_);
+ // lv_obj_center(emotion_label_);
+
+ chat_message_label_ = lv_label_create(content_);
+ lv_label_set_text(chat_message_label_, "");
+ lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.8); // 限制宽度为屏幕宽度的 80%
+ lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式
+ lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐
/* Status bar */
lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
@@ -326,3 +335,10 @@ void St7789Display::SetupUI() {
lv_label_set_text(battery_label_, "");
lv_obj_set_style_text_font(battery_label_, &font_awesome_14_1, 0);
}
+
+void St7789Display::SetChatMessage(const std::string &role, const std::string &content) {
+ if (chat_message_label_ == nullptr) {
+ return;
+ }
+ lv_label_set_text(chat_message_label_, content.c_str());
+}
diff --git a/main/display/st7789_display.h b/main/display/st7789_display.h
index 604263d3..e053e344 100644
--- a/main/display/st7789_display.h
+++ b/main/display/st7789_display.h
@@ -11,7 +11,7 @@
#include
class St7789Display : public Display {
-private:
+protected:
esp_lcd_panel_io_handle_t panel_io_ = nullptr;
esp_lcd_panel_handle_t panel_ = nullptr;
gpio_num_t backlight_pin_ = GPIO_NUM_NC;
@@ -28,12 +28,13 @@ private:
lv_obj_t* content_ = nullptr;
lv_obj_t* container_ = nullptr;
lv_obj_t* side_bar_ = nullptr;
+ lv_obj_t* chat_message_label_ = nullptr;
void InitializeBacklight(gpio_num_t backlight_pin);
void SetBacklight(uint8_t brightness);
- void SetupUI();
void LvglTask();
+ virtual void SetupUI();
virtual bool Lock(int timeout_ms = 0) override;
virtual void Unlock() override;
@@ -42,6 +43,8 @@ public:
gpio_num_t backlight_pin, bool backlight_output_invert,
int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy);
~St7789Display();
+
+ void SetChatMessage(const std::string &role, const std::string &content) override;
};
#endif // ST7789_DISPLAY_H
diff --git a/main/idf_component.yml b/main/idf_component.yml
index 346960e1..45049fdd 100644
--- a/main/idf_component.yml
+++ b/main/idf_component.yml
@@ -1,5 +1,6 @@
## IDF Component Manager Manifest File
dependencies:
+ espressif/esp_lcd_ili9341: "==1.2.0"
78/esp-wifi-connect: "~1.4.1"
78/esp-opus-encoder: "~2.0.0"
78/esp-ml307: "~1.7.0"