From 9e1724e89249dd037f8d98d65f6f91009c47da0a Mon Sep 17 00:00:00 2001 From: tkpdx01 <99157325+tkpdx01@users.noreply.github.com> Date: Mon, 2 Feb 2026 10:01:36 +0800 Subject: [PATCH] feat: add M5Stack Cardputer Adv board support (#1718) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for M5Stack Cardputer Adv, a card-sized computer based on ESP32-S3FN8 (Stamp-S3A) with the following features: Hardware: - MCU: ESP32-S3FN8 @ 240MHz, 8MB Flash (no PSRAM) - Display: ST7789V2 1.14" 240x135 - Audio: ES8311 codec + NS4150B amplifier - Keyboard: 56-key via TCA8418 - IMU: BMI270 Key configurations: - SPI3_HOST with 3-wire SPI mode for display - 256Hz PWM frequency for backlight (matching M5GFX) - ES8311 with use_mclk=false (no MCLK pin) - Display offset X=40, Y=52 for correct alignment 新增 M5Stack Cardputer Adv 开发板支持 支持基于 ESP32-S3FN8 (Stamp-S3A) 的卡片式电脑 M5Stack Cardputer Adv: 硬件规格: - MCU: ESP32-S3FN8 @ 240MHz, 8MB Flash (无 PSRAM) - 显示屏: ST7789V2 1.14" 240x135 - 音频: ES8311 编解码器 + NS4150B 功放 - 键盘: 56键 (TCA8418) - IMU: BMI270 关键配置: - 显示使用 SPI3_HOST 和 3-wire SPI 模式 - 背光 PWM 频率 256Hz (与 M5GFX 一致) - ES8311 设置 use_mclk=false (无 MCLK 引脚) - 显示偏移 X=40, Y=52 以正确对齐 --- main/CMakeLists.txt | 5 + main/Kconfig.projbuild | 3 + main/boards/m5stack-cardputer-adv/README.md | 48 ++++++ main/boards/m5stack-cardputer-adv/config.h | 58 +++++++ main/boards/m5stack-cardputer-adv/config.json | 13 ++ .../m5stack_cardputer_adv.cc | 158 ++++++++++++++++++ 6 files changed, 285 insertions(+) create mode 100644 main/boards/m5stack-cardputer-adv/README.md create mode 100644 main/boards/m5stack-cardputer-adv/config.h create mode 100644 main/boards/m5stack-cardputer-adv/config.json create mode 100644 main/boards/m5stack-cardputer-adv/m5stack_cardputer_adv.cc diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 2092c2e..3132e59 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -201,6 +201,11 @@ elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_S3R_CAM_M12_ECHO_BASE) set(BOARD_TYPE "atoms3r-cam-m12-echo-base") elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_ECHOS3R) set(BOARD_TYPE "atom-echos3r") +elseif(CONFIG_BOARD_TYPE_M5STACK_CARDPUTER_ADV) + set(BOARD_TYPE "m5stack-cardputer-adv") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_32) elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_MATRIX_ECHO_BASE) set(BOARD_TYPE "atommatrix-echo-base") elseif(CONFIG_BOARD_TYPE_XMINI_C3_V3) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 97eac4c..47b2355 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -251,6 +251,9 @@ choice BOARD_TYPE config BOARD_TYPE_M5STACK_ATOM_ECHOS3R bool "M5Stack AtomEchoS3R" depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_M5STACK_CARDPUTER_ADV + bool "M5Stack Cardputer Adv" + depends on IDF_TARGET_ESP32S3 config BOARD_TYPE_M5STACK_ATOM_MATRIX_ECHO_BASE bool "M5Stack AtomMatrix + Echo Base" depends on IDF_TARGET_ESP32 diff --git a/main/boards/m5stack-cardputer-adv/README.md b/main/boards/m5stack-cardputer-adv/README.md new file mode 100644 index 0000000..89f70d1 --- /dev/null +++ b/main/boards/m5stack-cardputer-adv/README.md @@ -0,0 +1,48 @@ +# M5Stack Cardputer Adv + +M5Stack Cardputer Adv 是一款基于 ESP32-S3FN8 (Stamp-S3A) 的卡片式电脑。 + +## 硬件规格 + +| 组件 | 规格 | +|------|------| +| MCU | ESP32-S3FN8 @ 240MHz | +| Flash | 8MB | +| 显示屏 | ST7789V2 1.14" 240x135 | +| 音频编解码 | ES8311 | +| 功放 | NS4150B | +| 麦克风 | MEMS | +| 键盘 | 56键 (TCA8418) | +| IMU | BMI270 | +| 电池 | 1750mAh | + +## 引脚定义 + +### 显示屏 (ST7789V2) +| 功能 | GPIO | +|------|------| +| MOSI | GPIO35 | +| SCLK | GPIO36 | +| CS | GPIO37 | +| DC | GPIO34 | +| RST | GPIO33 | +| BL | GPIO38 | + +### 音频 (ES8311) +| 功能 | GPIO | +|------|------| +| I2C SDA | GPIO8 | +| I2C SCL | GPIO9 | +| I2S BCLK | GPIO41 | +| I2S LRCK | GPIO43 | +| I2S DOUT | GPIO46 | +| I2S DIN | GPIO42 | + +## 使用方法 + +1. 按下 BOOT 按钮进入配网模式 +2. 连接 WiFi 后即可使用语音助手功能 + +## 参考链接 + +- [M5Stack Cardputer Adv 官方文档](https://docs.m5stack.com/en/core/Cardputer-Adv) diff --git a/main/boards/m5stack-cardputer-adv/config.h b/main/boards/m5stack-cardputer-adv/config.h new file mode 100644 index 0000000..67f75bd --- /dev/null +++ b/main/boards/m5stack-cardputer-adv/config.h @@ -0,0 +1,58 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +// M5Stack Cardputer Adv Board configuration +// MCU: ESP32-S3FN8 (Stamp-S3A) +// Display: ST7789V2 1.14" 240x135 +// Audio: ES8311 + NS4150B + +#include + +// Audio settings +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +// I2S Audio pins (ES8311) +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41 // SCLK +#define AUDIO_I2S_GPIO_WS GPIO_NUM_43 // LRCK +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_42 // DSDIN (MCU -> ES8311) +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_46 // ASDOUT (ES8311 -> MCU) + +// I2C pins (shared for ES8311, TCA8418, BMI270) +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_9 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC // NS4150B is always on + +// Button +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +// Display ST7789V2 (SPI) +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 135 +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false +#define DISPLAY_SWAP_XY true + +#define DISPLAY_OFFSET_X 40 +#define DISPLAY_OFFSET_Y 52 + +#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_35 +#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_36 +#define DISPLAY_SPI_CS_PIN GPIO_NUM_37 +#define DISPLAY_DC_PIN GPIO_NUM_34 +#define DISPLAY_RST_PIN GPIO_NUM_33 +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_38 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +// Keyboard TCA8418 I2C address +#define KEYBOARD_TCA8418_ADDR 0x34 + +// IMU BMI270 I2C address +#define IMU_BMI270_ADDR 0x68 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/m5stack-cardputer-adv/config.json b/main/boards/m5stack-cardputer-adv/config.json new file mode 100644 index 0000000..0db2860 --- /dev/null +++ b/main/boards/m5stack-cardputer-adv/config.json @@ -0,0 +1,13 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "m5stack-cardputer-adv", + "sdkconfig_append": [ + "CONFIG_SPIRAM=n", + "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y", + "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"" + ] + } + ] +} diff --git a/main/boards/m5stack-cardputer-adv/m5stack_cardputer_adv.cc b/main/boards/m5stack-cardputer-adv/m5stack_cardputer_adv.cc new file mode 100644 index 0000000..5f9c266 --- /dev/null +++ b/main/boards/m5stack-cardputer-adv/m5stack_cardputer_adv.cc @@ -0,0 +1,158 @@ +#include "wifi_board.h" +#include "codecs/es8311_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "i2c_device.h" + +#include +#include +#include +#include +#include +#include + +#define TAG "CardputerAdv" + +class M5StackCardputerAdvBoard : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + LcdDisplay* display_; + Button boot_button_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializeI2c() { + ESP_LOGI(TAG, "Initialize I2C bus"); + 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, &i2c_bus_)); + } + + void I2cDetect() { + uint8_t address; + ESP_LOGI(TAG, "I2C device scan:"); + 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 InitializeSpi() { + ESP_LOGI(TAG, "Initialize SPI bus"); + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = DISPLAY_SPI_SCLK_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 InitializeSt7789Display() { + ESP_LOGI(TAG, "Initialize ST7789V2 display"); + + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_SPI_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; + io_config.flags.sio_mode = 1; // 3-wire SPI mode (M5GFX uses spi_3wire = true) + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install ST7789 panel driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_RST_PIN; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); + 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_, true)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(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); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; + } + app.ToggleChatState(); + }); + } + +public: + M5StackCardputerAdvBoard() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + I2cDetect(); + InitializeSpi(); + InitializeSt7789Display(); + InitializeButtons(); + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8311AudioCodec audio_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_ES8311_ADDR, + false); // use_mclk = false, Cardputer Adv has no MCLK pin + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + // M5GFX uses 256Hz PWM frequency for Cardputer backlight + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 256); + return &backlight; + } +}; + +DECLARE_BOARD(M5StackCardputerAdvBoard);