From 3f4a275aec231a83345cd3414cd4c2b9218addfd Mon Sep 17 00:00:00 2001 From: Create123 Date: Tue, 16 Jun 2026 00:57:53 +0800 Subject: [PATCH] Add M5Stack AtomS3/AtomS3R st7735 display support. (#2055) Co-authored-by: luoweiyuan --- main/boards/atoms3-echo-base/README.md | 83 ++++++-- .../atoms3-echo-base/atoms3_echo_base.cc | 180 ++++++++++++++++-- main/boards/atoms3-echo-base/config.h | 5 + main/boards/atoms3r-echo-base/README.md | 129 ++++++++----- .../atoms3r-echo-base/atoms3r_echo_base.cc | 178 +++++++++++++++-- main/boards/atoms3r-echo-base/config.h | 5 + main/boards/atoms3r-echo-pyramid/README.md | 70 +++++-- .../atoms3r_echo_pyramid.cc | 177 +++++++++++++++-- main/boards/atoms3r-echo-pyramid/config.h | 23 ++- 9 files changed, 707 insertions(+), 143 deletions(-) diff --git a/main/boards/atoms3-echo-base/README.md b/main/boards/atoms3-echo-base/README.md index ef2ad28b..482c0932 100644 --- a/main/boards/atoms3-echo-base/README.md +++ b/main/boards/atoms3-echo-base/README.md @@ -1,49 +1,102 @@ -# 编译配置命令 +# M5Stack AtomS3 + Echo Base -**配置编译目标为 ESP32S3:** +## 快速构建 + +推荐使用 release 脚本生成完整固件包: + +```bash +python scripts/release.py atoms3-echo-base --name atoms3-echo-base +``` + +生成的固件压缩包位于: + +```text +releases/v2.2.6_atoms3-echo-base.zip +``` + +AtomS3 不带 PSRAM,`config.json` 已通过 `CONFIG_SPIRAM=n` 关闭片外 PSRAM,并使用 8 MB Flash 分区配置。由于没有 PSRAM,AtomS3 + Echo Base 不支持语音唤醒功能,手动配置时需要关闭语音唤醒与音频处理。 + +## 手动配置 + +配置编译目标: ```bash idf.py set-target esp32s3 ``` -**打开 menuconfig:** +打开配置菜单: ```bash idf.py menuconfig ``` -**选择板子:** +选择板卡: -``` +```text Xiaozhi Assistant -> Board Type -> AtomS3 + Echo Base ``` -**关闭语音唤醒:** +关闭语音唤醒与音频处理: -``` +```text Xiaozhi Assistant -> [ ] 启用语音唤醒与音频处理 -> Unselect ``` -**修改 flash 大小:** +配置 Flash 大小: -``` +```text Serial flasher config -> Flash size -> 8 MB ``` -**修改分区表:** +配置分区表: -``` +```text Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv ``` -**关闭片外 PSRAM:** +关闭片外 PSRAM: -``` +```text Component config -> ESP PSRAM -> [ ] Support for external, SPI-connected RAM -> Unselect ``` -**编译:** +对应 `sdkconfig` 设置为: + +```text +CONFIG_SPIRAM=n +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/8m.csv" +``` + +编译: ```bash idf.py build -``` \ No newline at end of file +``` + +## 合并固件 + +手动构建后,可使用以下命令合并烧录固件: + +```bash +esptool.py --chip esp32s3 merge_bin \ + --flash_mode dio \ + --flash_freq 80m \ + --flash_size 8MB \ + 0x0 build/bootloader/bootloader.bin \ + 0x8000 build/partition_table/partition-table.bin \ + 0xd000 build/ota_data_initial.bin \ + 0x20000 build/xiaozhi.bin \ + 0x600000 build/generated_assets.bin \ + -o AtomS3-EchoBase-XiaoZhi-v2.2.6_0x00.bin +``` + +烧录合并后的固件: + +```bash +esptool.py -b 1500000 write_flash -z 0 AtomS3-EchoBase-XiaoZhi-v2.2.6_0x00.bin +``` + +## 使用说明 + +Echo Base 正常运行时请从 Echo Base 底座的 USB-C 口供电;AtomS3 的 USB-C 口主要用于烧录。 diff --git a/main/boards/atoms3-echo-base/atoms3_echo_base.cc b/main/boards/atoms3-echo-base/atoms3_echo_base.cc index bbff9ed9..f7f01b3f 100644 --- a/main/boards/atoms3-echo-base/atoms3_echo_base.cc +++ b/main/boards/atoms3-echo-base/atoms3_echo_base.cc @@ -7,14 +7,19 @@ #include "i2c_device.h" #include "assets/lang_config.h" +#include #include #include +#include +#include #include #include #include +#include #define TAG "AtomS3+EchoBase" + static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { // {cmd, { data }, data_size, delay_ms} {0xfe, (uint8_t[]){0x00}, 0, 0}, @@ -46,6 +51,22 @@ static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { class AtomS3EchoBaseBoard : public WifiBoard { private: + enum class LcdPanelType { + kGc9107, + kSt7735, + }; + + struct LcdPanelConfig { + LcdPanelType type; + const char* name; + int gap_x; + int gap_y; + bool mirror_x; + bool mirror_y; + bool swap_xy; + bool invert_color; + }; + i2c_master_bus_handle_t i2c_bus_; Display* display_; Button boot_button_; @@ -103,7 +124,7 @@ private: // Pop error page InitializeSpi(); - InitializeGc9107Display(); + InitializeLcdDisplay(DetectLcdPanel()); InitializeButtons(); GetBacklight()->SetBrightness(100); @@ -135,23 +156,131 @@ private: void InitializeSpi() { ESP_LOGI(TAG, "Initialize SPI bus"); spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_21; + buscfg.mosi_io_num = LCD_MOSI_GPIO; buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_17; + buscfg.sclk_io_num = LCD_SCLK_GPIO; 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 InitializeGc9107Display() { - ESP_LOGI(TAG, "Init GC9107 display"); + void ResetLcdPanel() { + gpio_config_t reset_config = {}; + reset_config.pin_bit_mask = BIT64(LCD_RST_GPIO); + reset_config.mode = GPIO_MODE_OUTPUT; + gpio_config(&reset_config); + + gpio_set_level(LCD_RST_GPIO, 0); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(LCD_RST_GPIO, 1); + vTaskDelay(pdMS_TO_TICKS(120)); + } + + uint32_t ReadLcdPanelId() { + ResetLcdPanel(); + + gpio_config_t cs_config = {}; + cs_config.pin_bit_mask = BIT64(LCD_CS_GPIO); + cs_config.mode = GPIO_MODE_OUTPUT; + gpio_config(&cs_config); + gpio_set_level(LCD_CS_GPIO, 1); + + spi_device_interface_config_t devcfg = {}; + devcfg.clock_speed_hz = 16 * 1000 * 1000; + devcfg.mode = 0; + devcfg.spics_io_num = GPIO_NUM_NC; + devcfg.queue_size = 1; + devcfg.command_bits = 8; + devcfg.dummy_bits = 1; + devcfg.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE; + + spi_device_handle_t spi = nullptr; + esp_err_t ret = spi_bus_add_device(SPI3_HOST, &devcfg, &spi); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to add SPI device for panel ID read: %s", esp_err_to_name(ret)); + return 0; + } + + spi_transaction_t wake = {}; + wake.length = 8; + wake.flags = SPI_TRANS_USE_TXDATA; + wake.tx_data[0] = 0x00; + ret = spi_device_polling_transmit(spi, &wake); + + spi_transaction_t trans = {}; + trans.cmd = 0x04; // RDDID + trans.rxlength = 32; + trans.flags = SPI_TRANS_USE_RXDATA; + gpio_set_level(LCD_CS_GPIO, 0); + if (ret == ESP_OK) { + ret = spi_device_polling_transmit(spi, &trans); + } + gpio_set_level(LCD_CS_GPIO, 1); + spi_bus_remove_device(spi); + + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to read LCD panel ID: %s", esp_err_to_name(ret)); + return 0; + } + + uint32_t id = static_cast(trans.rx_data[0]) | + (static_cast(trans.rx_data[1]) << 8) | + (static_cast(trans.rx_data[2]) << 16) | + (static_cast(trans.rx_data[3]) << 24); + ESP_LOGI(TAG, "LCD panel ID: 0x%08" PRIx32, id); + return id; + } + + LcdPanelConfig GetLcdPanelConfig(LcdPanelType type) { + if (type == LcdPanelType::kSt7735) { + return { + .type = type, + .name = "ST7735", + .gap_x = 2, + .gap_y = 3, + .mirror_x = true, + .mirror_y = true, + .swap_xy = false, + .invert_color = true, + }; + } + + return { + .type = LcdPanelType::kGc9107, + .name = "GC9107", + .gap_x = DISPLAY_OFFSET_X, + .gap_y = DISPLAY_OFFSET_Y, + .mirror_x = DISPLAY_MIRROR_X, + .mirror_y = DISPLAY_MIRROR_Y, + .swap_xy = DISPLAY_SWAP_XY, + .invert_color = false, + }; + } + + LcdPanelConfig DetectLcdPanel() { + uint32_t id = ReadLcdPanelId(); + if ((id & 0xFFFF) == 0x7683 || (id & 0xFFFF) == 0x897C) { + ESP_LOGI(TAG, "Detected ST7735 LCD panel"); + return GetLcdPanelConfig(LcdPanelType::kSt7735); + } + if ((id & 0xFFFFFF) == 0x079100) { + ESP_LOGI(TAG, "Detected GC9107 LCD panel"); + return GetLcdPanelConfig(LcdPanelType::kGc9107); + } + + ESP_LOGW(TAG, "Unknown LCD panel ID 0x%08" PRIx32 ", fallback to GC9107", id); + return GetLcdPanelConfig(LcdPanelType::kGc9107); + } + + void InitializeLcdDisplay(const LcdPanelConfig& config) { + ESP_LOGI(TAG, "Init %s display", config.name); ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_handle_t io_handle = nullptr; esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_15; - io_config.dc_gpio_num = GPIO_NUM_33; + io_config.cs_gpio_num = LCD_CS_GPIO; + io_config.dc_gpio_num = LCD_DC_GPIO; io_config.spi_mode = 0; io_config.pclk_hz = 40 * 1000 * 1000; io_config.trans_queue_depth = 10; @@ -159,25 +288,34 @@ private: io_config.lcd_param_bits = 8; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); - ESP_LOGI(TAG, "Install GC9A01 panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; + ESP_LOGI(TAG, "Install %s panel driver", config.name); + esp_lcd_panel_handle_t panel_handle = nullptr; esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_34; // Set to -1 if not use + panel_config.reset_gpio_num = LCD_RST_GPIO; // Set to -1 if not use panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) - panel_config.vendor_config = &gc9107_vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + if (config.type == LcdPanelType::kGc9107) { + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; + panel_config.vendor_config = &gc9107_vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + } else { + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); + } + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, config.invert_color)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, config.swap_xy)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, config.mirror_x, config.mirror_y)); + ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, config.gap_x, config.gap_y)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); display_ = new SpiLcdDisplay(io_handle, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + DISPLAY_WIDTH, DISPLAY_HEIGHT, 0, 0, config.mirror_x, config.mirror_y, config.swap_xy); } void InitializeButtons() { @@ -197,7 +335,7 @@ public: I2cDetect(); CheckEchoBaseConnection(); InitializeSpi(); - InitializeGc9107Display(); + InitializeLcdDisplay(DetectLcdPanel()); InitializeButtons(); GetBacklight()->RestoreBrightness(); } @@ -229,4 +367,4 @@ public: } }; -DECLARE_BOARD(AtomS3EchoBaseBoard); \ No newline at end of file +DECLARE_BOARD(AtomS3EchoBaseBoard); diff --git a/main/boards/atoms3-echo-base/config.h b/main/boards/atoms3-echo-base/config.h index 6b2fdada..098cce20 100644 --- a/main/boards/atoms3-echo-base/config.h +++ b/main/boards/atoms3-echo-base/config.h @@ -27,6 +27,11 @@ #define DISPLAY_SDA_PIN GPIO_NUM_NC #define DISPLAY_SCL_PIN GPIO_NUM_NC +#define LCD_CS_GPIO GPIO_NUM_15 +#define LCD_DC_GPIO GPIO_NUM_33 +#define LCD_RST_GPIO GPIO_NUM_34 +#define LCD_MOSI_GPIO GPIO_NUM_21 +#define LCD_SCLK_GPIO GPIO_NUM_17 #define DISPLAY_WIDTH 128 #define DISPLAY_HEIGHT 128 #define DISPLAY_MIRROR_X false diff --git a/main/boards/atoms3r-echo-base/README.md b/main/boards/atoms3r-echo-base/README.md index 13e8a36f..1d9b513d 100644 --- a/main/boards/atoms3r-echo-base/README.md +++ b/main/boards/atoms3r-echo-base/README.md @@ -1,43 +1,86 @@ -# 编译配置命令 - -**配置编译目标为 ESP32S3:** - -```bash -idf.py set-target esp32s3 -``` - -**打开 menuconfig:** - -```bash -idf.py menuconfig -``` - -**选择板子:** - -``` -Xiaozhi Assistant -> Board Type -> AtomS3R + Echo Base -``` - -**修改 flash 大小:** - -``` -Serial flasher config -> Flash size -> 8 MB -``` - -**修改分区表:** - -``` -Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv -``` - -**修改 psram 配置:** - -``` -Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM -``` - -**编译:** - -```bash -idf.py build -``` \ No newline at end of file +# M5Stack AtomS3R + Echo Base + +## 快速构建 + +推荐使用 release 脚本生成完整固件包: + +```bash +python scripts/release.py atoms3r-echo-base --name atoms3r-echo-base +``` + +生成的固件压缩包位于: + +```text +releases/v2.2.6_atoms3r-echo-base.zip +``` + +## 手动配置 + +配置编译目标: + +```bash +idf.py set-target esp32s3 +``` + +打开配置菜单: + +```bash +idf.py menuconfig +``` + +选择板卡: + +```text +Xiaozhi Assistant -> Board Type -> AtomS3R + Echo Base +``` + +配置 Flash 大小: + +```text +Serial flasher config -> Flash size -> 8 MB +``` + +配置分区表: + +```text +Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv +``` + +配置 PSRAM: + +```text +Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM +``` + +编译: + +```bash +idf.py build +``` + +## 合并固件 + +手动构建后,可使用以下命令合并烧录固件: + +```bash +esptool.py --chip esp32s3 merge_bin \ + --flash_mode dio \ + --flash_freq 80m \ + --flash_size 8MB \ + 0x0 build/bootloader/bootloader.bin \ + 0x8000 build/partition_table/partition-table.bin \ + 0xd000 build/ota_data_initial.bin \ + 0x20000 build/xiaozhi.bin \ + 0x600000 build/generated_assets.bin \ + -o AtomS3R-EchoBase-XiaoZhi-v2.2.6_0x00.bin +``` + +烧录合并后的固件: + +```bash +esptool.py -b 1500000 write_flash -z 0 AtomS3R-EchoBase-XiaoZhi-v2.2.6_0x00.bin +``` + +## 使用说明 + +Echo Base 正常运行时请从 Echo Base 底座的 USB-C 口供电;AtomS3R 的 USB-C 口主要用于烧录。 diff --git a/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc b/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc index fed0a24d..5ed99aa9 100644 --- a/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc +++ b/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc @@ -7,14 +7,19 @@ #include "i2c_device.h" #include "assets/lang_config.h" +#include #include #include +#include +#include #include #include #include +#include #define TAG "AtomS3R+EchoBase" + #define PI4IOE_ADDR 0x43 #define PI4IOE_REG_CTRL 0x00 #define PI4IOE_REG_IO_PP 0x07 @@ -103,6 +108,22 @@ static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { class AtomS3rEchoBaseBoard : public WifiBoard { private: + enum class LcdPanelType { + kGc9107, + kSt7735, + }; + + struct LcdPanelConfig { + LcdPanelType type; + const char* name; + int gap_x; + int gap_y; + bool mirror_x; + bool mirror_y; + bool swap_xy; + bool invert_color; + }; + i2c_master_bus_handle_t i2c_bus_; i2c_master_bus_handle_t i2c_bus_internal_; Pi4ioe* pi4ioe_ = nullptr; @@ -170,7 +191,7 @@ private: // Pop error page InitializeLp5562(); InitializeSpi(); - InitializeGc9107Display(); + InitializeLcdDisplay(DetectLcdPanel()); InitializeButtons(); GetBacklight()->SetBrightness(100); @@ -213,23 +234,131 @@ private: void InitializeSpi() { ESP_LOGI(TAG, "Initialize SPI bus"); spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_21; + buscfg.mosi_io_num = LCD_MOSI_GPIO; buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_15; + buscfg.sclk_io_num = LCD_SCLK_GPIO; 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 InitializeGc9107Display() { - ESP_LOGI(TAG, "Init GC9107 display"); + void ResetLcdPanel() { + gpio_config_t reset_config = {}; + reset_config.pin_bit_mask = BIT64(LCD_RST_GPIO); + reset_config.mode = GPIO_MODE_OUTPUT; + gpio_config(&reset_config); + + gpio_set_level(LCD_RST_GPIO, 0); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(LCD_RST_GPIO, 1); + vTaskDelay(pdMS_TO_TICKS(120)); + } + + uint32_t ReadLcdPanelId() { + ResetLcdPanel(); + + gpio_config_t cs_config = {}; + cs_config.pin_bit_mask = BIT64(LCD_CS_GPIO); + cs_config.mode = GPIO_MODE_OUTPUT; + gpio_config(&cs_config); + gpio_set_level(LCD_CS_GPIO, 1); + + spi_device_interface_config_t devcfg = {}; + devcfg.clock_speed_hz = 16 * 1000 * 1000; + devcfg.mode = 0; + devcfg.spics_io_num = GPIO_NUM_NC; + devcfg.queue_size = 1; + devcfg.command_bits = 8; + devcfg.dummy_bits = 1; + devcfg.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE; + + spi_device_handle_t spi = nullptr; + esp_err_t ret = spi_bus_add_device(SPI3_HOST, &devcfg, &spi); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to add SPI device for panel ID read: %s", esp_err_to_name(ret)); + return 0; + } + + spi_transaction_t wake = {}; + wake.length = 8; + wake.flags = SPI_TRANS_USE_TXDATA; + wake.tx_data[0] = 0x00; + ret = spi_device_polling_transmit(spi, &wake); + + spi_transaction_t trans = {}; + trans.cmd = 0x04; // RDDID + trans.rxlength = 32; + trans.flags = SPI_TRANS_USE_RXDATA; + gpio_set_level(LCD_CS_GPIO, 0); + if (ret == ESP_OK) { + ret = spi_device_polling_transmit(spi, &trans); + } + gpio_set_level(LCD_CS_GPIO, 1); + spi_bus_remove_device(spi); + + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to read LCD panel ID: %s", esp_err_to_name(ret)); + return 0; + } + + uint32_t id = static_cast(trans.rx_data[0]) | + (static_cast(trans.rx_data[1]) << 8) | + (static_cast(trans.rx_data[2]) << 16) | + (static_cast(trans.rx_data[3]) << 24); + ESP_LOGI(TAG, "LCD panel ID: 0x%08" PRIx32, id); + return id; + } + + LcdPanelConfig GetLcdPanelConfig(LcdPanelType type) { + if (type == LcdPanelType::kSt7735) { + return { + .type = type, + .name = "ST7735", + .gap_x = 2, + .gap_y = 3, + .mirror_x = true, + .mirror_y = true, + .swap_xy = false, + .invert_color = true, + }; + } + + return { + .type = LcdPanelType::kGc9107, + .name = "GC9107", + .gap_x = DISPLAY_OFFSET_X, + .gap_y = DISPLAY_OFFSET_Y, + .mirror_x = DISPLAY_MIRROR_X, + .mirror_y = DISPLAY_MIRROR_Y, + .swap_xy = DISPLAY_SWAP_XY, + .invert_color = false, + }; + } + + LcdPanelConfig DetectLcdPanel() { + uint32_t id = ReadLcdPanelId(); + if ((id & 0xFFFF) == 0x7683 || (id & 0xFFFF) == 0x897C) { + ESP_LOGI(TAG, "Detected ST7735 LCD panel"); + return GetLcdPanelConfig(LcdPanelType::kSt7735); + } + if ((id & 0xFFFFFF) == 0x079100) { + ESP_LOGI(TAG, "Detected GC9107 LCD panel"); + return GetLcdPanelConfig(LcdPanelType::kGc9107); + } + + ESP_LOGW(TAG, "Unknown LCD panel ID 0x%08" PRIx32 ", fallback to GC9107", id); + return GetLcdPanelConfig(LcdPanelType::kGc9107); + } + + void InitializeLcdDisplay(const LcdPanelConfig& config) { + ESP_LOGI(TAG, "Init %s display", config.name); ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_handle_t io_handle = nullptr; esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_14; - io_config.dc_gpio_num = GPIO_NUM_42; + io_config.cs_gpio_num = LCD_CS_GPIO; + io_config.dc_gpio_num = LCD_DC_GPIO; io_config.spi_mode = 0; io_config.pclk_hz = 40 * 1000 * 1000; io_config.trans_queue_depth = 10; @@ -237,25 +366,34 @@ private: io_config.lcd_param_bits = 8; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); - ESP_LOGI(TAG, "Install GC9A01 panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; + ESP_LOGI(TAG, "Install %s panel driver", config.name); + esp_lcd_panel_handle_t panel_handle = nullptr; esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_48; // Set to -1 if not use + panel_config.reset_gpio_num = LCD_RST_GPIO; // Set to -1 if not use panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18) - panel_config.vendor_config = &gc9107_vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + if (config.type == LcdPanelType::kGc9107) { + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; + panel_config.vendor_config = &gc9107_vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + } else { + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); + } + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, config.invert_color)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, config.swap_xy)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, config.mirror_x, config.mirror_y)); + ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, config.gap_x, config.gap_y)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); display_ = new SpiLcdDisplay(io_handle, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + DISPLAY_WIDTH, DISPLAY_HEIGHT, 0, 0, config.mirror_x, config.mirror_y, config.swap_xy); } void InitializeButtons() { @@ -277,7 +415,7 @@ public: InitializePi4ioe(); InitializeLp5562(); InitializeSpi(); - InitializeGc9107Display(); + InitializeLcdDisplay(DetectLcdPanel()); InitializeButtons(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/atoms3r-echo-base/config.h b/main/boards/atoms3r-echo-base/config.h index d519c2ee..14d386d9 100644 --- a/main/boards/atoms3r-echo-base/config.h +++ b/main/boards/atoms3r-echo-base/config.h @@ -27,6 +27,11 @@ #define DISPLAY_SDA_PIN GPIO_NUM_NC #define DISPLAY_SCL_PIN GPIO_NUM_NC +#define LCD_CS_GPIO GPIO_NUM_14 +#define LCD_DC_GPIO GPIO_NUM_42 +#define LCD_RST_GPIO GPIO_NUM_48 +#define LCD_MOSI_GPIO GPIO_NUM_21 +#define LCD_SCLK_GPIO GPIO_NUM_15 #define DISPLAY_WIDTH 128 #define DISPLAY_HEIGHT 128 #define DISPLAY_MIRROR_X false diff --git a/main/boards/atoms3r-echo-pyramid/README.md b/main/boards/atoms3r-echo-pyramid/README.md index 38fbc899..b95dfe3a 100644 --- a/main/boards/atoms3r-echo-pyramid/README.md +++ b/main/boards/atoms3r-echo-pyramid/README.md @@ -1,51 +1,89 @@ -# 编译配置命令 +# M5Stack AtomS3R + Echo Pyramid -**配置编译目标为 ESP32S3:** +> [!NOTE] +> Echo Pyramid 正常运行时请从 Pyramid 底座的 USB-C 口供电;AtomS3R 的 USB-C 口主要用于烧录。 + +## 快速构建 + +推荐使用 release 脚本生成完整固件包: + +```bash +python scripts/release.py atoms3r-echo-pyramid --name atoms3r-echo-pyramid +``` + +生成的固件压缩包位于: + +```text +releases/v2.2.6_atoms3r-echo-pyramid.zip +``` + +## 手动配置 + +配置编译目标: ```bash idf.py set-target esp32s3 ``` -**打开 menuconfig:** +打开配置菜单: ```bash idf.py menuconfig ``` -**选择板子:** +选择板卡: -``` +```text Xiaozhi Assistant -> Board Type -> M5Stack AtomS3R + Echo Pyramid ``` -**修改 flash 大小:** +配置 Flash 大小: -``` +```text Serial flasher config -> Flash size -> 8 MB ``` -**修改分区表:** +配置分区表: -``` +```text Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv ``` -**修改 psram 配置:** +配置 PSRAM: -``` +```text Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM ``` -**编译:** +编译: ```bash idf.py build ``` -## 使用说明 +## 合并固件 -Echo Pyramid 正常运行时请从 Pyramid 底座的 USB-C 口供电;AtomS3R 的 USB-C 口主要用于烧录。 +手动构建后,可使用以下命令合并烧录固件: -# 参考资料 +```bash +esptool.py --chip esp32s3 merge_bin \ + --flash_mode dio \ + --flash_freq 80m \ + --flash_size 8MB \ + 0x0 build/bootloader/bootloader.bin \ + 0x8000 build/partition_table/partition-table.bin \ + 0xd000 build/ota_data_initial.bin \ + 0x20000 build/xiaozhi.bin \ + 0x600000 build/generated_assets.bin \ + -o AtomS3R-EchoPyramid-XiaoZhi-v2.2.6_0x00.bin +``` -https://github.com/m5stack/M5Echo-Pyramid \ No newline at end of file +烧录合并后的固件: + +```bash +esptool.py -b 1500000 write_flash -z 0 AtomS3R-EchoPyramid-XiaoZhi-v2.2.6_0x00.bin +``` + +## 参考资料 + +- https://github.com/m5stack/M5Echo-Pyramid diff --git a/main/boards/atoms3r-echo-pyramid/atoms3r_echo_pyramid.cc b/main/boards/atoms3r-echo-pyramid/atoms3r_echo_pyramid.cc index 5fe3fdff..86ace342 100644 --- a/main/boards/atoms3r-echo-pyramid/atoms3r_echo_pyramid.cc +++ b/main/boards/atoms3r-echo-pyramid/atoms3r_echo_pyramid.cc @@ -8,6 +8,10 @@ #include "led/led.h" #include "assets/lang_config.h" +#include +#include +#include +#include #include #include #include @@ -21,6 +25,7 @@ #define TAG "AtomS3R+EchoPyramid" + #define PYRAMID_SI5351_ADDR 0x60 #define PYRAMID_STM32_ADDR 0x1A #define PYRAMID_AW87559_ADDR 0x5B @@ -519,6 +524,22 @@ static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = { class AtomS3rEchoPyramidBoard : public WifiBoard { private: + enum class LcdPanelType { + kGc9107, + kSt7735, + }; + + struct LcdPanelConfig { + LcdPanelType type; + const char* name; + int gap_x; + int gap_y; + bool mirror_x; + bool mirror_y; + bool swap_xy; + bool invert_color; + }; + i2c_master_bus_handle_t i2c_bus_; i2c_master_bus_handle_t i2c_bus_internal_; Si5351* si5351_ = nullptr; @@ -615,7 +636,7 @@ private: InitializeLp5562(); InitializeSpi(); - InitializeGc9107Display(); + InitializeLcdDisplay(DetectLcdPanel()); InitializeButtons(); GetBacklight()->SetBrightness(100); @@ -659,23 +680,128 @@ private: void InitializeSpi() { ESP_LOGI(TAG, "Initialize SPI bus"); spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = GPIO_NUM_21; + buscfg.mosi_io_num = LCD_MOSI_GPIO; buscfg.miso_io_num = GPIO_NUM_NC; - buscfg.sclk_io_num = GPIO_NUM_15; + buscfg.sclk_io_num = LCD_SCLK_GPIO; 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 InitializeGc9107Display() { - ESP_LOGI(TAG, "Init GC9107 display"); + void ResetLcdPanel() { + gpio_config_t reset_config = {}; + reset_config.pin_bit_mask = BIT64(LCD_RST_GPIO); + reset_config.mode = GPIO_MODE_OUTPUT; + gpio_config(&reset_config); + + gpio_set_level(LCD_RST_GPIO, 0); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(LCD_RST_GPIO, 1); + vTaskDelay(pdMS_TO_TICKS(120)); + } + + uint32_t ReadLcdPanelId() { + ResetLcdPanel(); + + gpio_config_t cs_config = {}; + cs_config.pin_bit_mask = BIT64(LCD_CS_GPIO); + cs_config.mode = GPIO_MODE_OUTPUT; + gpio_config(&cs_config); + gpio_set_level(LCD_CS_GPIO, 1); + + spi_device_interface_config_t devcfg = {}; + devcfg.clock_speed_hz = 16 * 1000 * 1000; + devcfg.mode = 0; + devcfg.spics_io_num = GPIO_NUM_NC; + devcfg.queue_size = 1; + devcfg.command_bits = 8; + devcfg.dummy_bits = 1; + devcfg.flags = SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_3WIRE; + + spi_device_handle_t spi = nullptr; + esp_err_t ret = spi_bus_add_device(SPI3_HOST, &devcfg, &spi); + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to add SPI device for panel ID read: %s", esp_err_to_name(ret)); + return 0; + } + + spi_transaction_t wake = {}; + wake.length = 8; + wake.flags = SPI_TRANS_USE_TXDATA; + wake.tx_data[0] = 0x00; + ret = spi_device_polling_transmit(spi, &wake); + + spi_transaction_t trans = {}; + trans.cmd = 0x04; // RDDID + trans.rxlength = 32; + trans.flags = SPI_TRANS_USE_RXDATA; + gpio_set_level(LCD_CS_GPIO, 0); + if (ret == ESP_OK) { + ret = spi_device_polling_transmit(spi, &trans); + } + gpio_set_level(LCD_CS_GPIO, 1); + spi_bus_remove_device(spi); + + if (ret != ESP_OK) { + ESP_LOGW(TAG, "Failed to read LCD panel ID: %s", esp_err_to_name(ret)); + return 0; + } + + uint32_t id = static_cast(trans.rx_data[0]) | + (static_cast(trans.rx_data[1]) << 8) | + (static_cast(trans.rx_data[2]) << 16) | + (static_cast(trans.rx_data[3]) << 24); + ESP_LOGI(TAG, "LCD panel ID: 0x%08" PRIx32, id); + return id; + } + + const LcdPanelConfig& GetLcdPanelConfig(LcdPanelType type) { + static const LcdPanelConfig gc9107 = { + .type = LcdPanelType::kGc9107, + .name = "GC9107", + .gap_x = DISPLAY_OFFSET_X, + .gap_y = DISPLAY_OFFSET_Y, + .mirror_x = DISPLAY_MIRROR_X, + .mirror_y = DISPLAY_MIRROR_Y, + .swap_xy = DISPLAY_SWAP_XY, + .invert_color = false, + }; + static const LcdPanelConfig st7735 = { + .type = LcdPanelType::kSt7735, + .name = "ST7735", + .gap_x = 2, + .gap_y = 3, + .mirror_x = false, + .mirror_y = false, + .swap_xy = false, + .invert_color = true, + }; + return type == LcdPanelType::kSt7735 ? st7735 : gc9107; + } + + LcdPanelConfig DetectLcdPanel() { + uint32_t id = ReadLcdPanelId(); + if ((id & 0xFFFF) == 0x7683 || (id & 0xFFFF) == 0x897C) { + ESP_LOGI(TAG, "Detected ST7735 LCD panel"); + return GetLcdPanelConfig(LcdPanelType::kSt7735); + } + if ((id & 0xFFFFFF) == 0x079100) { + ESP_LOGI(TAG, "Detected GC9107 LCD panel"); + return GetLcdPanelConfig(LcdPanelType::kGc9107); + } + ESP_LOGW(TAG, "Unknown LCD panel ID 0x%08" PRIx32 ", fallback to GC9107", id); + return GetLcdPanelConfig(LcdPanelType::kGc9107); + } + + void InitializeLcdDisplay(const LcdPanelConfig& config) { + ESP_LOGI(TAG, "Init %s display", config.name); ESP_LOGI(TAG, "Install panel IO"); - esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_handle_t io_handle = nullptr; esp_lcd_panel_io_spi_config_t io_config = {}; - io_config.cs_gpio_num = GPIO_NUM_14; - io_config.dc_gpio_num = GPIO_NUM_42; + io_config.cs_gpio_num = LCD_CS_GPIO; + io_config.dc_gpio_num = LCD_DC_GPIO; io_config.spi_mode = 0; io_config.pclk_hz = 40 * 1000 * 1000; io_config.trans_queue_depth = 10; @@ -683,25 +809,38 @@ private: io_config.lcd_param_bits = 8; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); - ESP_LOGI(TAG, "Install GC9A01 panel driver"); - esp_lcd_panel_handle_t panel_handle = NULL; - gc9a01_vendor_config_t gc9107_vendor_config = { - .init_cmds = gc9107_lcd_init_cmds, - .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), - }; + ESP_LOGI(TAG, "Install %s panel driver", config.name); + esp_lcd_panel_handle_t panel_handle = nullptr; esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = GPIO_NUM_48; + panel_config.reset_gpio_num = LCD_RST_GPIO; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR; +#else panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; +#endif panel_config.bits_per_pixel = 16; - panel_config.vendor_config = &gc9107_vendor_config; - ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + if (config.type == LcdPanelType::kGc9107) { + gc9a01_vendor_config_t gc9107_vendor_config = { + .init_cmds = gc9107_lcd_init_cmds, + .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t), + }; + panel_config.vendor_config = &gc9107_vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); + } else { + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle)); + } + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, config.invert_color)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_handle, config.swap_xy)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, config.mirror_x, config.mirror_y)); + ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel_handle, config.gap_x, config.gap_y)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); display_ = new SpiLcdDisplay(io_handle, panel_handle, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + DISPLAY_WIDTH, DISPLAY_HEIGHT, 0, 0, config.mirror_x, config.mirror_y, config.swap_xy); } void InitializeButtons() { @@ -723,7 +862,7 @@ public: InitializePyramidDevices(); InitializeLp5562(); InitializeSpi(); - InitializeGc9107Display(); + InitializeLcdDisplay(DetectLcdPanel()); InitializeButtons(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/atoms3r-echo-pyramid/config.h b/main/boards/atoms3r-echo-pyramid/config.h index 62959a50..0523eebf 100644 --- a/main/boards/atoms3r-echo-pyramid/config.h +++ b/main/boards/atoms3r-echo-pyramid/config.h @@ -26,16 +26,21 @@ #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 128 -#define DISPLAY_MIRROR_X false -#define DISPLAY_MIRROR_Y false -#define DISPLAY_SWAP_XY false +#define DISPLAY_SDA_PIN GPIO_NUM_NC +#define DISPLAY_SCL_PIN GPIO_NUM_NC +#define LCD_CS_GPIO GPIO_NUM_14 +#define LCD_DC_GPIO GPIO_NUM_42 +#define LCD_RST_GPIO GPIO_NUM_48 +#define LCD_MOSI_GPIO GPIO_NUM_21 +#define LCD_SCLK_GPIO GPIO_NUM_15 +#define DISPLAY_WIDTH 128 +#define DISPLAY_HEIGHT 128 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false -#define DISPLAY_OFFSET_X 0 -#define DISPLAY_OFFSET_Y 32 +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 #define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC #define DISPLAY_BACKLIGHT_OUTPUT_INVERT true