diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index c9da16ec3d..b98b73e27a 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -526,6 +526,11 @@ elseif(CONFIG_BOARD_TYPE_WIRELESS_TAG_WTP4C5MP07S) set(BUILTIN_TEXT_FONT font_puhui_basic_30_4) set(BUILTIN_ICON_FONT font_awesome_30_4) set(DEFAULT_EMOJI_COLLECTION twemoji_64) +elseif(CONFIG_BOARD_TYPE_SEEEDSTUDIO_S3_WIFI) + set(BOARD_TYPE "seeedstudio-s3-wifi") + set(BUILTIN_TEXT_FONT font_puhui_basic_14_1) + set(BUILTIN_ICON_FONT font_awesome_14_1) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) endif() file(GLOB BOARD_SOURCES diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 9664ebdf09..6c1c1b77e8 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -424,6 +424,9 @@ choice BOARD_TYPE config BOARD_TYPE_WIRELESS_TAG_WTP4C5MP07S bool "Wireless-Tag WTP4C5MP07S" depends on IDF_TARGET_ESP32P4 + config BOARD_TYPE_SEEEDSTUDIO_S3_WIFI + bool "Seeed Studio XIAO ESP32-S3" + depends on IDF_TARGET_ESP32S3 endchoice choice @@ -457,7 +460,7 @@ choice ESP_S3_LCD_EV_Board_Version_TYPE endchoice choice DISPLAY_OLED_TYPE - depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_ESP32 + depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_ESP32 || BOARD_TYPE_SEEEDSTUDIO_S3_WIFI prompt "OLED Type" default OLED_SSD1306_128X32 help diff --git a/main/boards/seeedstudio-s3-wifi/README.md b/main/boards/seeedstudio-s3-wifi/README.md new file mode 100644 index 0000000000..03ae80ae7e --- /dev/null +++ b/main/boards/seeedstudio-s3-wifi/README.md @@ -0,0 +1,26 @@ +# Seeed Studio XIAO ESP32-S3 +参照Bread-compact-wifi改造的适配XIAO ESP32-S3开发板的面包板版本。 + +## 🛠️ 编译指南 +**开发环境**:ESP-IDF v5.4.1 + +### 编译步骤: +默认Windows已安装ESP-IDF环境 +1. 双击打开ESP-IDF 5.5CMD桌面图标,并 cd 进入项目文件夹 +2. 输入 idf.py fullclean, 回车 +3. 输入 idf.py set-target esp32s3,回车 +4. 输入 idf.py menuconfig +5. 进入 Serial flasher config -> Flash size -> 8MB +6. 进入 Partition Table -> Custom partition CSV file -> 复制粘贴此路径:partitions/v2/8m.csv +7. 进入 Xiaozhi Assistant -> Board Type -> Seeed Studio XIAO ESP32-S3 +8. 进入 OLED Type -> SSD1306 128*32 +8. 按S保存,Q退出。 +9. 输入 idf.py build +10. 等待编译结束后开始烧录 + +## 🔌 烧录步骤 +1. 连接开发板到电脑 +2. Windows: 此电脑右键找到管理,进入设备管理器查看端口 +3. 输入 idf.py -p COM(你的端口号) flash +4. 烧录完成后可以输入 idf.py -p COM(你的端口号) monitor 打开串口监视器查看开发板状态 +5. 祝你玩儿的开心~~~ \ No newline at end of file diff --git a/main/boards/seeedstudio-s3-wifi/board_config.h b/main/boards/seeedstudio-s3-wifi/board_config.h new file mode 100644 index 0000000000..2eb71eecdf --- /dev/null +++ b/main/boards/seeedstudio-s3-wifi/board_config.h @@ -0,0 +1,56 @@ +#ifndef BOARD_CONFIG_H_ +#define BOARD_CONFIG_H_ + +#include + +// 音频速率 +#define AUDIO_INPUT_REFERENCE true +#define AUDIO_INPUT_SAMPLE_RATE 16000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +// 开 Simplex(两套总线) +#define AUDIO_I2S_METHOD_SIMPLEX + +// 扬声器(TX, MASTER)—— 总线#0 +#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_7 +#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_4 +#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_2 + +// 麦克风(RX, MASTER)—— 总线#1 +#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_44 +#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_9 +#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_1 + +// OLED I2C (SDA/SCL) +// D4 -> GPIO5 : SDA +// D5 -> GPIO6 : SCL +#define DISPLAY_SDA_PIN GPIO_NUM_5 +#define DISPLAY_SCL_PIN GPIO_NUM_6 +#define DISPLAY_WIDTH 128 + +#if CONFIG_OLED_SSD1306_128X32 +#define DISPLAY_HEIGHT 32 +#elif CONFIG_OLED_SSD1306_128X64 +#define DISPLAY_HEIGHT 64 +#elif CONFIG_OLED_SH1106_128X64 +#define DISPLAY_HEIGHT 64 +#define SH1106 +#else +#error "未选择 OLED 屏幕类型" +#endif + +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true + +// 板载LED +#define BUILTIN_LED_GPIO GPIO_NUM_21 + +// 按钮 +#define BOOT_BUTTON_GPIO GPIO_NUM_0 // 板载 Boot +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_3 // D2 +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_8 // D9 + +// 触摸键暂时不用,但为了兼容代码构造函数我们给个占位 +#define TOUCH_BUTTON_GPIO GPIO_NUM_3 + +#endif // BOARD_CONFIG_H_ diff --git a/main/boards/seeedstudio-s3-wifi/config.json b/main/boards/seeedstudio-s3-wifi/config.json new file mode 100644 index 0000000000..bae4ac3614 --- /dev/null +++ b/main/boards/seeedstudio-s3-wifi/config.json @@ -0,0 +1,23 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "seeedstudio-s3-wifi", + "sdkconfig_append": [ + "CONFIG_BOARD_SEEEDSTUDIO_S3_WIFI=y", + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB_y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"", + "CONFIG_OLED_SSD1306_128X64=y" + ] + }, + { + "name": "seeedstudio-s3-wifi", + "sdkconfig_append": [ + "CONFIG_BOARD_SEEEDSTUDIO_S3_WIFI=y", + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB_y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"", + "CONFIG_OLED_SSD1306_128X32=y" + ] + } + ] +} diff --git a/main/boards/seeedstudio-s3-wifi/seeedstudio_s3_wifi_board.cc b/main/boards/seeedstudio-s3-wifi/seeedstudio_s3_wifi_board.cc new file mode 100644 index 0000000000..82ca74753e --- /dev/null +++ b/main/boards/seeedstudio-s3-wifi/seeedstudio_s3_wifi_board.cc @@ -0,0 +1,190 @@ +#include "wifi_board.h" +#include "codecs/no_audio_codec.h" +#include "display/oled_display.h" +#include "system_reset.h" +#include "application.h" +#include "button.h" +#include "assets/lang_config.h" +#include "led/single_led.h" + +#include "board_config.h" // <- 很重要,来自当前目录 +#include + +#include +#include +#include +#include +#ifdef SH1106 +#include +#endif + +#define TAG "SeeedStudioS3WifiBoard" + +// 这个类专门代表 XIAO ESP32-S3 这块板 +class SeeedStudioS3WifiBoard : public WifiBoard { +private: + i2c_master_bus_handle_t display_i2c_bus_ = nullptr; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + + Button boot_button_; + Button touch_button_; + Button volume_up_button_; + Button volume_down_button_; + + void InitDisplayI2C() { + i2c_master_bus_config_t bus_config = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = DISPLAY_SDA_PIN, + .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_)); + } + + void InitializeSsd1306Display() { + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { .dc_low_on_data = 0, .disable_control_phase = 0 }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + +#ifdef SH1106 + ESP_ERROR_CHECK(esp_lcd_new_panel_sh1106(panel_io_, &panel_config, &panel_)); +#else + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); +#endif + + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay( + panel_io_, + panel_, + DISPLAY_WIDTH, + DISPLAY_HEIGHT, + DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y + ); + } + + void InitButtons() { + // Boot 按钮:切换聊天 / 复位 WiFi + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && + !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + }); + + // touch_button_:兼容语音按住说话逻辑(目前硬件上可以不接) + touch_button_.OnPressDown([this]() { + Application::GetInstance().StartListening(); + }); + touch_button_.OnPressUp([this]() { + Application::GetInstance().StopListening(); + }); + + // 音量+ + 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); + }); + } + +public: + SeeedStudioS3WifiBoard() : + boot_button_(BOOT_BUTTON_GPIO), + touch_button_(TOUCH_BUTTON_GPIO), + volume_up_button_(VOLUME_UP_BUTTON_GPIO), + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + + ESP_LOGI(TAG, "Init SeeedStudioS3WifiBoard"); + InitDisplayI2C(); + InitializeSsd1306Display(); + InitButtons(); + } + + virtual Led* GetLed() override { + static SingleLed led(BUILTIN_LED_GPIO); + return &led; + } + + virtual AudioCodec* GetAudioCodec() override { +#ifndef AUDIO_I2S_METHOD_SIMPLEX + static NoAudioCodecDuplex audio_codec( + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN + ); +#else + static NoAudioCodecSimplex audio_codec( + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, + AUDIO_I2S_SPK_GPIO_LRCK, + AUDIO_I2S_SPK_GPIO_DOUT, + AUDIO_I2S_MIC_GPIO_SCK, + AUDIO_I2S_MIC_GPIO_WS, + AUDIO_I2S_MIC_GPIO_DIN + ); +#endif + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } +}; + +// 把这个板注册出去,供系统使用 +DECLARE_BOARD(SeeedStudioS3WifiBoard);