From fd54c74da387f085792da515e822baf9911a4148 Mon Sep 17 00:00:00 2001 From: shangjinlong Date: Tue, 27 Oct 2026 15:18:49 +0800 Subject: [PATCH 1/5] feat(platform): add three ESP32-S3 board profiles --- .github/workflows/ci.yml | 32 +- CHANGELOG.md | 13 +- README.md | 7 +- README.zh-CN.md | 7 +- VENDORED_SOURCES.md | 8 +- components/mybot_platform/boards/README.md | 5 +- components/mybot_platform/boards/boards.cmake | 9 + .../esp32-s3-touch-amoled-1.8-v2/README.md | 28 + .../amoled18_hardware.c | 179 +++++++ .../amoled18_hardware.h | 22 + .../esp32-s3-touch-amoled-1.8-v2/board.c | 156 ++++++ .../esp32-s3-touch-amoled-1.8-v2/board.cmake | 45 ++ .../board_config.h | 66 +++ .../sdkconfig.defaults | 14 + .../boards/esp32-s3-touch-lcd-1.85/README.md | 27 + .../boards/esp32-s3-touch-lcd-1.85/board.c | 148 ++++++ .../esp32-s3-touch-lcd-1.85/board.cmake | 40 ++ .../esp32-s3-touch-lcd-1.85/board_config.h | 66 +++ .../esp32-s3-touch-lcd-1.85/lcd185_hardware.c | 209 ++++++++ .../esp32-s3-touch-lcd-1.85/lcd185_hardware.h | 19 + .../sdkconfig.defaults | 11 + .../boards/genjutech-s3-1.54tft/README.md | 33 ++ .../boards/genjutech-s3-1.54tft/board.c | 134 +++++ .../boards/genjutech-s3-1.54tft/board.cmake | 42 ++ .../genjutech-s3-1.54tft/board_config.h | 54 ++ .../genjutech-s3-1.54tft/genjutech_hardware.c | 179 +++++++ .../genjutech-s3-1.54tft/genjutech_hardware.h | 22 + .../genjutech-s3-1.54tft/sdkconfig.defaults | 14 + .../src/drivers/audio/raw_i2s_audio.c | 65 ++- .../panels/co5300/co5300_amoled18_panel.c | 158 ++++++ .../panels/st77916/st77916_lcd185_panel.c | 345 +++++++++++++ .../src/drivers/input/cst816s_boot_input.c | 372 +++++++++++++ .../src/drivers/io_expander/tca9554.c | 85 +++ .../src/internal/io_expander/tca9554.h | 30 ++ docs/AUDIO_PLAYBACK.md | 11 +- docs/AUDIO_PLAYBACK.zh-CN.md | 9 +- docs/BOARD_PORTING.md | 18 + docs/BOARD_PORTING.zh-CN.md | 16 + docs/CORES3_UI.md | 2 +- docs/CORES3_UI.zh-CN.md | 2 +- docs/PLATFORM_UI.md | 8 +- docs/PLATFORM_UI.zh-CN.md | 8 +- docs/RELEASING.md | 14 +- docs/RELEASING.zh-CN.md | 12 +- docs/VALIDATION.md | 3 + tests/co5300_lifecycle/README.md | 6 +- tests/co5300_lifecycle/run.sh | 5 +- .../co5300_lifecycle/test_co5300_lifecycle.c | 41 +- tests/cst816s_input/mocks/button_gpio.h | 2 + tests/cst816s_input/mocks/driver/gpio.h | 2 + tests/cst816s_input/mocks/driver/i2c_master.h | 2 + tests/cst816s_input/mocks/driver/spi_common.h | 2 + tests/cst816s_input/mocks/esp_lcd_touch.h | 2 + .../mocks/esp_lcd_touch_cst816s.h | 2 + tests/cst816s_input/mocks/esp_log.h | 2 + tests/cst816s_input/mocks/freertos/FreeRTOS.h | 2 + tests/cst816s_input/mocks/freertos/semphr.h | 2 + tests/cst816s_input/mocks/freertos/task.h | 2 + tests/cst816s_input/mocks/iot_button.h | 2 + tests/cst816s_input/mocks/mock_platform.h | 166 ++++++ tests/cst816s_input/mocks/mybot/mybot.h | 2 + .../mocks/mybot/platform/mybot_key.h | 2 + .../mocks/platform/board_actions.h | 2 + tests/cst816s_input/run.sh | 21 + tests/cst816s_input/test_cst816s_input.c | 488 ++++++++++++++++++ tests/st7789_lifecycle/README.md | 4 +- tests/st7789_lifecycle/mocks/host_platform.h | 2 + tests/st7789_lifecycle/run.sh | 40 +- tests/tca9554/mocks/driver/i2c_master.h | 27 + tests/tca9554/mocks/esp_err.h | 2 + tests/tca9554/mocks/esp_log.h | 2 + tests/tca9554/run.sh | 21 + tests/tca9554/test_tca9554.c | 118 +++++ 73 files changed, 3643 insertions(+), 75 deletions(-) create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/amoled18_hardware.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/amoled18_hardware.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/lcd185_hardware.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/lcd185_hardware.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/genjutech-s3-1.54tft/README.md create mode 100644 components/mybot_platform/boards/genjutech-s3-1.54tft/board.c create mode 100644 components/mybot_platform/boards/genjutech-s3-1.54tft/board.cmake create mode 100644 components/mybot_platform/boards/genjutech-s3-1.54tft/board_config.h create mode 100644 components/mybot_platform/boards/genjutech-s3-1.54tft/genjutech_hardware.c create mode 100644 components/mybot_platform/boards/genjutech-s3-1.54tft/genjutech_hardware.h create mode 100644 components/mybot_platform/boards/genjutech-s3-1.54tft/sdkconfig.defaults create mode 100644 components/mybot_platform/src/drivers/display/panels/co5300/co5300_amoled18_panel.c create mode 100644 components/mybot_platform/src/drivers/display/panels/st77916/st77916_lcd185_panel.c create mode 100644 components/mybot_platform/src/drivers/input/cst816s_boot_input.c create mode 100644 components/mybot_platform/src/drivers/io_expander/tca9554.c create mode 100644 components/mybot_platform/src/internal/io_expander/tca9554.h create mode 100644 tests/cst816s_input/mocks/button_gpio.h create mode 100644 tests/cst816s_input/mocks/driver/gpio.h create mode 100644 tests/cst816s_input/mocks/driver/i2c_master.h create mode 100644 tests/cst816s_input/mocks/driver/spi_common.h create mode 100644 tests/cst816s_input/mocks/esp_lcd_touch.h create mode 100644 tests/cst816s_input/mocks/esp_lcd_touch_cst816s.h create mode 100644 tests/cst816s_input/mocks/esp_log.h create mode 100644 tests/cst816s_input/mocks/freertos/FreeRTOS.h create mode 100644 tests/cst816s_input/mocks/freertos/semphr.h create mode 100644 tests/cst816s_input/mocks/freertos/task.h create mode 100644 tests/cst816s_input/mocks/iot_button.h create mode 100644 tests/cst816s_input/mocks/mock_platform.h create mode 100644 tests/cst816s_input/mocks/mybot/mybot.h create mode 100644 tests/cst816s_input/mocks/mybot/platform/mybot_key.h create mode 100644 tests/cst816s_input/mocks/platform/board_actions.h create mode 100644 tests/cst816s_input/run.sh create mode 100644 tests/cst816s_input/test_cst816s_input.c create mode 100644 tests/tca9554/mocks/driver/i2c_master.h create mode 100644 tests/tca9554/mocks/esp_err.h create mode 100644 tests/tca9554/mocks/esp_log.h create mode 100644 tests/tca9554/run.sh create mode 100644 tests/tca9554/test_tca9554.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b17ce48..965c6d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: run: | find main components/mybot_platform tests/atom_echos3r \ tests/st7789_lifecycle tests/co5300_lifecycle tests/bread_compact_wifi_lcd \ - tests/cores3_pixel_ui -type f \ + tests/cst816s_input tests/tca9554 tests/cores3_pixel_ui -type f \ \( -name '*.c' -o -name '*.h' -o -name '*.cc' -o -name '*.cpp' \) \ -exec clang-format --dry-run --Werror {} + - name: Whitespace check @@ -40,7 +40,7 @@ jobs: run: | find main components/mybot_platform tests/atom_echos3r \ tests/st7789_lifecycle tests/co5300_lifecycle tests/bread_compact_wifi_lcd \ - tests/cores3_pixel_ui -type f \ + tests/cst816s_input tests/tca9554 tests/cores3_pixel_ui -type f \ \( -name '*.c' -o -name '*.h' -o -name '*.cc' -o -name '*.cpp' \) \ -print0 | while IFS= read -r -d '' file; do head -n 5 "$file" | grep -q 'SPDX-License-Identifier:' || { @@ -54,6 +54,10 @@ jobs: run: sh tests/st7789_lifecycle/run.sh - name: CO5300 host lifecycle tests run: sh tests/co5300_lifecycle/run.sh + - name: CST816S and Boot input host lifecycle tests + run: sh tests/cst816s_input/run.sh + - name: TCA9554 masked update host tests + run: sh tests/tca9554/run.sh - name: Bread Compact Wi-Fi LCD host lifecycle tests run: sh tests/bread_compact_wifi_lcd/run.sh - name: CoreS3 pixel UI host lifecycle tests @@ -153,6 +157,30 @@ jobs: language: en-US ptime: 60 defaults: ci/ptime60.defaults;ci/en-us.defaults + - board: esp32-s3-touch-amoled-1.8-v2 + language: zh-CN + ptime: 60 + defaults: ci/ptime60.defaults + - board: esp32-s3-touch-amoled-1.8-v2 + language: en-US + ptime: 60 + defaults: ci/ptime60.defaults;ci/en-us.defaults + - board: esp32-s3-touch-lcd-1.85 + language: zh-CN + ptime: 60 + defaults: ci/ptime60.defaults + - board: esp32-s3-touch-lcd-1.85 + language: en-US + ptime: 60 + defaults: ci/ptime60.defaults;ci/en-us.defaults + - board: genjutech-s3-1.54tft + language: zh-CN + ptime: 60 + defaults: ci/ptime60.defaults + - board: genjutech-s3-1.54tft + language: en-US + ptime: 60 + defaults: ci/ptime60.defaults;ci/en-us.defaults - board: m5stack-core-s3 language: zh-CN ptime: 60 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bcfa20..07ecd69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,12 @@ Versioning and Conventional Commits. ### Added -- Per-board hardware, input and operation documentation for all thirteen profiles, with shared +- Per-board hardware, input and operation documentation for all sixteen profiles, with shared build/flash instructions and one validation matrix. - CoreS3-only process-gray pixel-glasses UI, with 23 expressions, four 9 px terminal rows, a 10 px signature and fixed 32 px six-digit pairing codes on its 320x240 display. Automatic faces follow READY/conversation phases. Expressions affect only the LCD. - `MYBOT_BOARD` selects the view automatically; other twelve profiles retain their UI/hardware. + `MYBOT_BOARD` selects the view automatically; other fifteen profiles retain their UI/hardware. - Shared LVGL status UI for the other display-board profiles, with Chinese/English text, pairing codes, voiceprint status, and listening/thinking/speaking indicators. - LVGL light/dark themes, local state emoji, rounded status cards, persistent voiceprint @@ -42,6 +42,15 @@ Versioning and Conventional Commits. - Waveshare ESP32-S3 Touch AMOLED 2.16 Board profile with a 480x480 CO5300 panel, zero display gap, CST9217/Boot input, and shared AXP2101 and ES7210/ES8311 integration. Chinese and English variants passed ESP-IDF v5.5.2 reconfigure/build/size/merge-bin validation. +- Waveshare ESP32-S3 Touch AMOLED 1.8 V2 Board profile with a 368x448 CO5300 panel, TCA9554 + reset, AXP2101 power sequencing, CST816S/Boot input, and ES8311 audio. +- Waveshare ESP32-S3 Touch LCD 1.85 Board profile with a 360x360 ST77916 panel, panel-version + selection, dual I2C buses, TCA9554 reset, separated I2S, right-slot microphone capture, and + both-slot playback. +- GenjuTech S3 1.54 TFT Board profile with an inverting ST7789 display, ES8311 audio, Boot and + volume buttons, and board-specific PA/display power coupling. +- Chinese and English variants for all three new profiles passed ESP-IDF v5.5.2 + reconfigure/build/size/merge-bin validation. - Espressif ESP-VoCat Board profile with PCB V1.0/V1.2 runtime detection, ST77916 QSPI display, CST816S touch input, and ES7210/ES8311 full-duplex audio. - Pinned ST77916 2.0.2 and CST816S 1.1.1~1 production components. diff --git a/README.md b/README.md index 23ecc4d..10f88c6 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ESP32-S3 boards. The project owns board initialization, Wi-Fi provisioning, persistent storage, HTTPS transport, audio, input, display, and the firmware lifecycle around the SDK. -The development baseline is **ESP-IDF v5.5.2**. Thirteen compile-time board profiles are +The development baseline is **ESP-IDF v5.5.2**. Sixteen compile-time board profiles are supported; the board, Flash, PSRAM, partition table, and hardware drivers are selected with `MYBOT_BOARD`. See the [board documentation index](components/mybot_platform/boards/README.md) for hardware details, controls, build commands, and per-board limitations. @@ -39,8 +39,9 @@ Display profiles: `zhengchen-1.54tft-ml307`, `zhengchen-1.54tft-wifi`, `xingzhi-cube-1.54tft-wifi`, `bread-compact-wifi-lcd`, `esp-vocat`, `esp32-s3-touch-amoled-1.75`, `esp32-s3-touch-amoled-1.75c`, -`esp32-s3-touch-amoled-2.16`, `m5stack-core-s3`, `m5stack-stick-s3`, and -`sensecap-watcher`. +`esp32-s3-touch-amoled-2.16`, `esp32-s3-touch-amoled-1.8-v2`, +`esp32-s3-touch-lcd-1.85`, `genjutech-s3-1.54tft`, `m5stack-core-s3`, +`m5stack-stick-s3`, and `sensecap-watcher`. Headless profiles: diff --git a/README.zh-CN.md b/README.zh-CN.md index 72dae4d..bafcd10 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -9,7 +9,7 @@ 工程负责板级初始化、Wi-Fi 配网、持久化存储、HTTPS、音频、输入、显示,以及 SDK 外围的 固件生命周期。 -当前开发基线为 **ESP-IDF v5.5.2**,支持 13 个编译期 Board profile。通过 `MYBOT_BOARD` +当前开发基线为 **ESP-IDF v5.5.2**,支持 16 个编译期 Board profile。通过 `MYBOT_BOARD` 选择板型、Flash、PSRAM、分区表和硬件驱动。具体硬件配置、输入操作、构建命令和限制见 [板卡说明索引](components/mybot_platform/boards/README.md)。 @@ -35,8 +35,9 @@ `zhengchen-1.54tft-ml307`、`zhengchen-1.54tft-wifi`、 `xingzhi-cube-1.54tft-wifi`、`bread-compact-wifi-lcd`、`esp-vocat`、 `esp32-s3-touch-amoled-1.75`、`esp32-s3-touch-amoled-1.75c`、 -`esp32-s3-touch-amoled-2.16`、`m5stack-core-s3`、`m5stack-stick-s3` 和 -`sensecap-watcher`。 +`esp32-s3-touch-amoled-2.16`、`esp32-s3-touch-amoled-1.8-v2`、 +`esp32-s3-touch-lcd-1.85`、`genjutech-s3-1.54tft`、`m5stack-core-s3`、 +`m5stack-stick-s3` 和 `sensecap-watcher`。 无屏 profile: diff --git a/VENDORED_SOURCES.md b/VENDORED_SOURCES.md index 448d0fa..a44aaa8 100644 --- a/VENDORED_SOURCES.md +++ b/VENDORED_SOURCES.md @@ -59,7 +59,7 @@ are not vendored into this repository. Firmware integration differences are limited to the active ESP32-S3 build: - Only `m5stack-core-s3` selects the process-gray pixel-glasses view. CMake chooses it from - `MYBOT_BOARD`, with no manual renderer flag; the other twelve profiles retain their current + `MYBOT_BOARD`, with no manual renderer flag; the other fifteen profiles retain their current UI/hardware. The original Source Han Sans 20 px UI subset is reused, while the CoreS3 path uses fixed widgets/curves, five immutable A8 masks (13,631 alpha bytes), and the terminal subsets above instead of the shared view's four emoji. Audio, camera/video policy, input, @@ -82,6 +82,12 @@ Firmware integration differences are limited to the active ESP32-S3 build: with independent identity and 480x480 zero-gap geometry. Its conservative 16 MB Flash/Octal PSRAM defaults require physical capacity confirmation; no new dependency or upstream application service is added. +- The GenjuTech 1.54, AMOLED 1.8 V2 and LCD 1.85 profiles add independent Board identities, + TCA9554 state handling, CST816S/Boot input, CO5300 368x448 and ST77916 360x360 panel variants, + and no new package dependency. The LCD 1.85 raw-I2S path exposes 32-bit right-slot capture and + duplicates mono playback into both slots while the SDK boundary remains 16 kHz mono signed-16 + PCM. Build success and any recorded image do not establish panel, audio, touch, power or radio + behavior on hardware. - LVGL and its ESP-IDF port are local, pinned dependencies included automatically for display boards and omitted for headless boards. The build omits examples, tests, and registry download metadata. The UI uses a static bilingual font subset and existing public LCD state; no upstream application services, diff --git a/components/mybot_platform/boards/README.md b/components/mybot_platform/boards/README.md index 02e6239..504bd99 100644 --- a/components/mybot_platform/boards/README.md +++ b/components/mybot_platform/boards/README.md @@ -5,7 +5,7 @@ drivers and build constraints are defined by the checked-in `board.cmake`, `boar `sdkconfig.defaults` files. Current validation status is centralized in [`docs/VALIDATION.md`](../../../docs/VALIDATION.md). -以下说明对应本工程已实现的 13 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; +以下说明对应本工程已实现的 16 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; 验证状态统一见[验证矩阵](../../../docs/VALIDATION.md)。不同尺寸、芯片或硬件版本的板卡 不能仅凭外观共用固件。 @@ -17,6 +17,9 @@ drivers and build constraints are defined by the checked-in `board.cmake`, `boar | `esp32-s3-touch-amoled-1.75` | [README](esp32-s3-touch-amoled-1.75/README.md) | CO5300 AMOLED 触摸屏 | 无 | | `esp32-s3-touch-amoled-1.75c` | [README](esp32-s3-touch-amoled-1.75c/README.md) | CO5300 AMOLED 触摸屏 | 无 | | `esp32-s3-touch-amoled-2.16` | [README](esp32-s3-touch-amoled-2.16/README.md) | 480×480 CO5300 AMOLED 触摸屏 | 无 | +| `esp32-s3-touch-amoled-1.8-v2` | [README](esp32-s3-touch-amoled-1.8-v2/README.md) | 368×448 CO5300 AMOLED 触摸屏 | 无 | +| `esp32-s3-touch-lcd-1.85` | [README](esp32-s3-touch-lcd-1.85/README.md) | 360×360 ST77916 触摸屏 | 无 | +| `genjutech-s3-1.54tft` | [README](genjutech-s3-1.54tft/README.md) | 240×240 ST7789 | 无 | | `m5stack-core-s3` | [README](m5stack-core-s3/README.md) | ILI9342 触摸屏 | GC0308,上传 1 fps | | `m5stack-stick-s3` | [README](m5stack-stick-s3/README.md) | 135×240 ST7789P3 | 无 | | `respeaker-flex-xvf3800-circular4-xiao` | [README](respeaker-flex-xvf3800-circular4-xiao/README.md) | 无屏幕 | 无 | diff --git a/components/mybot_platform/boards/boards.cmake b/components/mybot_platform/boards/boards.cmake index 75d7b46..d372f39 100644 --- a/components/mybot_platform/boards/boards.cmake +++ b/components/mybot_platform/boards/boards.cmake @@ -5,6 +5,9 @@ set(MYBOT_SUPPORTED_BOARDS "esp32-s3-touch-amoled-1.75" "esp32-s3-touch-amoled-1.75c" "esp32-s3-touch-amoled-2.16" + "esp32-s3-touch-amoled-1.8-v2" + "esp32-s3-touch-lcd-1.85" + "genjutech-s3-1.54tft" "m5stack-core-s3" "m5stack-stick-s3" "respeaker-flex-xvf3800-circular4-xiao" @@ -28,6 +31,12 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-amoled-1.75c/board.cmake") elseif(board STREQUAL "esp32-s3-touch-amoled-2.16") set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-amoled-2.16/board.cmake") + elseif(board STREQUAL "esp32-s3-touch-amoled-1.8-v2") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-amoled-1.8-v2/board.cmake") + elseif(board STREQUAL "esp32-s3-touch-lcd-1.85") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-lcd-1.85/board.cmake") + elseif(board STREQUAL "genjutech-s3-1.54tft") + set(profile "${_MYBOT_BOARDS_ROOT}/genjutech-s3-1.54tft/board.cmake") elseif(board STREQUAL "m5stack-core-s3") set(profile "${_MYBOT_BOARDS_ROOT}/m5stack-core-s3/board.cmake") elseif(board STREQUAL "m5stack-stick-s3") diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/README.md b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/README.md new file mode 100644 index 0000000..33e0ca0 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/README.md @@ -0,0 +1,28 @@ +# ESP32-S3 Touch AMOLED 1.8 V2 + +板型 ID 为 `esp32-s3-touch-amoled-1.8-v2`。该 profile 使用独立的 368×448 CO5300 面板配置, +不能与 1.75、1.75C 或 2.16 AMOLED profile 交叉烧录。 + +| 项目 | 当前实现 | +| --- | --- | +| 存储 | 16 MB QIO Flash profile;80 MHz Octal PSRAM;实际容量待真机确认 | +| 分区 | [`partitions/v2/16m.csv`](../../../../partitions/v2/16m.csv),双 OTA | +| 电源 | TCA9554 执行面板复位;AXP2101 配置电源轨和充电参数 | +| 音频 | ES8311,16 kHz 单声道 PCM;GPIO46 扬声器功放 | +| 显示 | 368×448 CO5300 AMOLED,40 MHz QSPI,X 偏移 16,共享 LVGL | +| 输入 | CST816S 触摸与 GPIO0 Boot;短按切换对话,长按 3 秒配网 | +| 网络 / 视频 | Wi-Fi;未接入摄像头,不支持视频 | + +| 信号 | GPIO | +| --- | --- | +| I2S MCLK / BCLK / WS / DIN / DOUT | 16 / 9 / 45 / 10 / 8 | +| 公共 I2C SDA / SCL | 15 / 14 | +| QSPI CLK / CS / D0-D3 | 11 / 12 / 4、5、6、7 | +| CST816S RESET / INT | 39 / 13 | +| TCA9554 / AXP2101 地址 | `0x20` / `0x34` | + +逻辑界面保持 SDK 的 16 kHz 单声道 PCM 契约,I2S 与 ES8311 均运行在 16 kHz。 +CO5300 使用 RGB565 局部 DMA 刷新,传输区域保持偶数像素边界。 + +当前未实现 AXP2101 电源键快捷行为、电量/充电上报、休眠、关机、摄像头或低功耗行为。 +构建与真机状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/amoled18_hardware.c b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/amoled18_hardware.c new file mode 100644 index 0000000..99d0133 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/amoled18_hardware.c @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "amoled18_hardware.h" + +#include "board_config.h" +#include "io_expander/tca9554.h" + +#include "driver/gpio.h" +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define TAG "amoled18_hw" +#define I2C_TIMEOUT_MS 100 + +typedef struct { + i2c_master_bus_handle_t bus; + i2c_master_dev_handle_t axp2101; + mybot_tca9554_t expander; + bool pa_ready; + bool pa_enabled; + bool initialized; +} amoled18_hardware_t; + +static amoled18_hardware_t s_hardware; + +static esp_err_t write_axp_register(uint8_t reg, uint8_t value) { + const uint8_t command[] = {reg, value}; + return i2c_master_transmit(s_hardware.axp2101, command, sizeof(command), I2C_TIMEOUT_MS); +} + +static esp_err_t initialize_axp2101(void) { + static const uint8_t registers[][2] = { + {0x22, 0x06}, {0x27, 0x10}, {0x80, 0x01}, {0x90, 0x00}, {0x91, 0x00}, {0x82, 0x12}, + {0x92, 0x1c}, {0x90, 0x01}, {0x64, 0x02}, {0x61, 0x02}, {0x62, 0x08}, {0x63, 0x01}, + }; + for (size_t index = 0; index < sizeof(registers) / sizeof(registers[0]); ++index) { + esp_err_t result = write_axp_register(registers[index][0], registers[index][1]); + if (result != ESP_OK) { + return result; + } + } + return ESP_OK; +} + +static esp_err_t initialize_pa(void) { + const gpio_config_t config = { + .pin_bit_mask = 1ULL << MYBOT_AMOLED18_SPEAKER_PA, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + esp_err_t result = gpio_config(&config); + if (result == ESP_OK) { + result = gpio_set_level(MYBOT_AMOLED18_SPEAKER_PA, 0); + } + if (result == ESP_OK) { + s_hardware.pa_ready = true; + } + return result; +} + +static int release_hardware(void) { + int result = 0; + if (s_hardware.pa_ready && gpio_set_level(MYBOT_AMOLED18_SPEAKER_PA, 0) != ESP_OK) { + result = -1; + } + if (s_hardware.axp2101 && i2c_master_bus_rm_device(s_hardware.axp2101) != ESP_OK) { + result = -1; + } + if (mybot_tca9554_deinit(&s_hardware.expander) < 0) { + result = -1; + } + if (s_hardware.bus && i2c_del_master_bus(s_hardware.bus) != ESP_OK) { + result = -1; + } + if (result == 0) { + s_hardware = (amoled18_hardware_t){0}; + } else { + s_hardware.initialized = false; + s_hardware.pa_enabled = false; + } + return result; +} + +int mybot_amoled18_hardware_init(void) { + if (s_hardware.initialized) { + return 0; + } + if ((s_hardware.bus || s_hardware.axp2101 || s_hardware.expander.device || + s_hardware.pa_ready) && + release_hardware() < 0) { + return -1; + } + + esp_err_t result = initialize_pa(); + const i2c_master_bus_config_t bus_config = { + .i2c_port = MYBOT_AMOLED18_I2C_PORT, + .sda_io_num = MYBOT_AMOLED18_I2C_SDA, + .scl_io_num = MYBOT_AMOLED18_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + if (result == ESP_OK) { + result = i2c_new_master_bus(&bus_config, &s_hardware.bus); + } + if (result == ESP_OK) { + result = + mybot_tca9554_init(&s_hardware.expander, s_hardware.bus, MYBOT_AMOLED18_TCA9554_ADDRESS, + MYBOT_AMOLED18_I2C_SPEED_HZ) == 0 + ? ESP_OK + : ESP_FAIL; + } + if (result == ESP_OK) { + result = mybot_tca9554_configure_outputs(&s_hardware.expander, 0x07, 0x00) == 0 ? ESP_OK + : ESP_FAIL; + } + if (result == ESP_OK) { + result = + mybot_tca9554_set_levels(&s_hardware.expander, 0x07, 0x07) == 0 ? ESP_OK : ESP_FAIL; + vTaskDelay(pdMS_TO_TICKS(100)); + } + if (result == ESP_OK) { + result = + mybot_tca9554_set_levels(&s_hardware.expander, 0x07, 0x00) == 0 ? ESP_OK : ESP_FAIL; + vTaskDelay(pdMS_TO_TICKS(300)); + } + if (result == ESP_OK) { + result = + mybot_tca9554_set_levels(&s_hardware.expander, 0x07, 0x07) == 0 ? ESP_OK : ESP_FAIL; + } + + const i2c_device_config_t axp_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = MYBOT_AMOLED18_AXP2101_ADDRESS, + .scl_speed_hz = MYBOT_AMOLED18_I2C_SPEED_HZ, + }; + if (result == ESP_OK) { + result = i2c_master_bus_add_device(s_hardware.bus, &axp_config, &s_hardware.axp2101); + } + if (result == ESP_OK) { + result = initialize_axp2101(); + } + if (result != ESP_OK) { + ESP_LOGE(TAG, "event=hardware action=initialize result=error code=%s", + esp_err_to_name(result)); + (void)release_hardware(); + return -1; + } + + s_hardware.initialized = true; + ESP_LOGI(TAG, "event=hardware action=initialize result=ok expander=0x%02x axp2101=0x%02x", + MYBOT_AMOLED18_TCA9554_ADDRESS, MYBOT_AMOLED18_AXP2101_ADDRESS); + return 0; +} + +int mybot_amoled18_hardware_deinit(void) { + return release_hardware(); +} + +i2c_master_bus_handle_t mybot_amoled18_i2c_bus_handle(void) { + return s_hardware.initialized ? s_hardware.bus : NULL; +} + +int mybot_amoled18_set_speaker_power(bool enabled) { + if (!s_hardware.initialized || !s_hardware.pa_ready) { + return -1; + } + if (s_hardware.pa_enabled == enabled) { + return 0; + } + if (gpio_set_level(MYBOT_AMOLED18_SPEAKER_PA, enabled ? 1 : 0) != ESP_OK) { + return -1; + } + s_hardware.pa_enabled = enabled; + return 0; +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/amoled18_hardware.h b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/amoled18_hardware.h new file mode 100644 index 0000000..2284f0e --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/amoled18_hardware.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_AMOLED18_HARDWARE_H_ +#define MYBOT_AMOLED18_HARDWARE_H_ + +#include "driver/i2c_master.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int mybot_amoled18_hardware_init(void); +int mybot_amoled18_hardware_deinit(void); +i2c_master_bus_handle_t mybot_amoled18_i2c_bus_handle(void); +int mybot_amoled18_set_speaker_power(bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif /* MYBOT_AMOLED18_HARDWARE_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board.c b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board.c new file mode 100644 index 0000000..847c7bc --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board.c @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "mybot_platform/board.h" + +#include + +#include "amoled18_hardware.h" +#include "announcement/embedded_ogg_prompt.h" +#include "board_config.h" +#include "esp_log.h" +#include "network/wifi_control.h" +#include "platform/board_actions.h" + +#include + +#define TAG "amoled18_board" + +const mybot_audio_capture_ops_t *mybot_es8311_audio_capture_ops(void); +const mybot_audio_playback_ops_t *mybot_es8311_audio_playback_ops(void); +const mybot_audio_volume_ops_t *mybot_es8311_audio_volume_ops(void); +const mybot_announce_ops_t *mybot_esp32s3_announce_ops(void); +const mybot_https_ops_t *mybot_esp32s3_https_ops(void); +const mybot_key_ops_t *mybot_cst816s_input_ops(void); +const mybot_kv_store_ops_t *mybot_esp32s3_kv_store_ops(void); +const mybot_lcd_ops_t *mybot_shared_lvgl_ops(void); +const mybot_wifi_ops_t *mybot_esp32s3_wifi_ops(void); +int mybot_cst816s_input_start(void); +int mybot_cst816s_input_stop(void); + +static const mybot_lcd_ops_t *s_lcd_ops; +static void *s_lcd_context; +static bool s_automatic_provisioning_started; +static bool s_prepared; +static bool s_cleanup_pending; + +static int board_show_screen(mybot_lcd_screen_t screen) { + if (!s_lcd_ops || !s_lcd_context) { + return -1; + } + const mybot_lcd_content_t content = {.screen = screen}; + return s_lcd_ops->render(s_lcd_context, &content); +} + +static void board_on_provisioning(const char *trigger) { + (void)board_show_screen(MYBOT_LCD_SCREEN_WIFI_PROVISIONING); + (void)mybot_embedded_ogg_play_wifi_provisioning(mybot_es8311_audio_playback_ops(), + mybot_es8311_audio_volume_ops(), trigger); +} + +static void board_on_automatic_provisioning(void) { + s_automatic_provisioning_started = true; + board_on_provisioning("automatic"); +} + +static void board_on_input_provisioning(void) { + board_on_provisioning("input"); +} + +static int board_rollback_prepare(void) { + s_cleanup_pending = true; + if (mybot_cst816s_input_stop() < 0) { + return -1; + } + if (s_lcd_ops && s_lcd_context) { + s_lcd_ops->destroy(s_lcd_context); + s_lcd_context = NULL; + } + s_lcd_ops = NULL; + if (mybot_amoled18_hardware_deinit() < 0) { + return -1; + } + s_cleanup_pending = false; + return 0; +} + +static int board_prepare(void) { + if (s_prepared) { + return 0; + } + if (s_cleanup_pending && board_rollback_prepare() < 0) { + return -1; + } + if (mybot_amoled18_hardware_init() < 0) { + return -1; + } + s_lcd_ops = mybot_shared_lvgl_ops(); + if (!s_lcd_ops || s_lcd_ops->init(&s_lcd_context) < 0 || + board_show_screen(MYBOT_LCD_SCREEN_STARTING) < 0 || mybot_cst816s_input_start() < 0) { + (void)board_rollback_prepare(); + return -1; + } + s_prepared = true; + return 0; +} + +int mybot_board_handle_boot_long_press(void) { + mybot_board_request_wifi_provisioning(); + return 0; +} + +static int board_provision_wifi(void) { + return mybot_wifi_run_provisioning(board_on_input_provisioning); +} + +static int board_ensure_network(const char *device_id) { + s_automatic_provisioning_started = false; + int result = mybot_wifi_ensure_network(device_id, board_on_automatic_provisioning); + if (s_automatic_provisioning_started) { + (void)mybot_board_wait_wifi_provisioning_request(0); + } + return result; +} + +static void board_shutdown_network(void) { + mybot_wifi_shutdown_network(); +} + +static int board_register_platform(void) { + const mybot_platform_descriptor_t descriptor = { + .wifi = mybot_esp32s3_wifi_ops(), + .kv_store = mybot_esp32s3_kv_store_ops(), + .key = mybot_cst816s_input_ops(), + .audio_capture = mybot_es8311_audio_capture_ops(), + .audio_playback = mybot_es8311_audio_playback_ops(), + .audio_volume = mybot_es8311_audio_volume_ops(), + .announce = mybot_esp32s3_announce_ops(), + .https = mybot_esp32s3_https_ops(), + .lcd = mybot_shared_lvgl_ops(), + }; + return mybot_platform_register(&descriptor); +} + +void *mybot_es8311_board_i2c_bus_handle(void) { + return mybot_amoled18_i2c_bus_handle(); +} + +int mybot_es8311_board_set_speaker_power(bool enabled) { + return mybot_amoled18_set_speaker_power(enabled); +} + +i2c_master_bus_handle_t mybot_board_touch_i2c_bus_handle(void) { + return mybot_amoled18_i2c_bus_handle(); +} + +static const mybot_board_t s_board = { + .id = MYBOT_BOARD_NAME, + .hw_model = MYBOT_BOARD_NAME, + .prepare = board_prepare, + .register_platform = board_register_platform, + .ensure_network = board_ensure_network, + .provision_wifi = board_provision_wifi, + .shutdown_network = board_shutdown_network, +}; + +const mybot_board_t *mybot_board_get(void) { + return &s_board; +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board.cmake b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board.cmake new file mode 100644 index 0000000..5347444 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board.cmake @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_BOARD_TARGET "esp32s3") +set(MYBOT_BOARD_SDKCONFIG_DEFAULTS "${CMAKE_CURRENT_LIST_DIR}/sdkconfig.defaults") +set(MYBOT_BOARD_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}") +set(MYBOT_BOARD_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +set(MYBOT_BOARD_FORBIDDEN_CONFIGS + CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE +) +set(MYBOT_BOARD_PARTITION_TABLE "partitions/v2/16m.csv") + +get_filename_component(MYBOT_PLATFORM_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +include("${MYBOT_PLATFORM_ROOT}/src/drivers/display/display.cmake") +set(MYBOT_BOARD_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/board.c" + "${CMAKE_CURRENT_LIST_DIR}/amoled18_hardware.c" + "${MYBOT_PLATFORM_ROOT}/src/drivers/io_expander/tca9554.c" + "${MYBOT_PLATFORM_ROOT}/src/drivers/input/cst816s_boot_input.c" + "${MYBOT_PLATFORM_ROOT}/src/drivers/audio/es8311_codec_audio.c" +) +mybot_display_add_panel(MYBOT_BOARD_SOURCES "co5300/co5300_amoled18_panel.c") +mybot_display_add_lvgl_sources(MYBOT_BOARD_SOURCES "shared_lvgl_adapter.cc") +set(MYBOT_BOARD_REQUIRES + button + esp_codec_dev + esp_driver_gpio + esp_driver_i2c + esp_driver_i2s + esp_driver_spi + esp_lcd + esp_lcd_co5300 + esp_lcd_touch + esp_lcd_touch_cst816s + esp_lvgl_port + lvgl + nvs_flash +) diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board_config.h b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board_config.h new file mode 100644 index 0000000..82f18dc --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/board_config.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-touch-amoled-1.8-v2" +#define MYBOT_BOARD_HAS_TOUCH 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_45 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_8 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_10 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 + +#define MYBOT_AMOLED18_I2C_PORT I2C_NUM_0 +#define MYBOT_AMOLED18_I2C_SDA GPIO_NUM_15 +#define MYBOT_AMOLED18_I2C_SCL GPIO_NUM_14 +#define MYBOT_AMOLED18_I2C_SPEED_HZ 400000 +#define MYBOT_AMOLED18_TCA9554_ADDRESS 0x20 +#define MYBOT_AMOLED18_AXP2101_ADDRESS 0x34 +#define MYBOT_AMOLED18_SPEAKER_PA GPIO_NUM_46 + +#define MYBOT_ES8311_I2C_PORT MYBOT_AMOLED18_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_PCLK GPIO_NUM_11 +#define MYBOT_DISPLAY_DATA0 GPIO_NUM_4 +#define MYBOT_DISPLAY_DATA1 GPIO_NUM_5 +#define MYBOT_DISPLAY_DATA2 GPIO_NUM_6 +#define MYBOT_DISPLAY_DATA3 GPIO_NUM_7 +#define MYBOT_DISPLAY_CS GPIO_NUM_12 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_WIDTH 368 +#define MYBOT_DISPLAY_HEIGHT 448 +#define MYBOT_DISPLAY_OFFSET_X 16 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) + +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_39 +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_13 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/sdkconfig.defaults new file mode 100644 index 0000000..0be593f --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8-v2/sdkconfig.defaults @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" + +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/README.md b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/README.md new file mode 100644 index 0000000..f40cf5c --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/README.md @@ -0,0 +1,27 @@ +# ESP32-S3 Touch LCD 1.85 + +板型 ID 为 `esp32-s3-touch-lcd-1.85`。该 profile 使用 ST77916 QSPI 面板、双 I2C 总线、 +TCA9554 复位控制和独立麦克风/扬声器 I2S。 + +| 项目 | 当前实现 | +| --- | --- | +| 存储 | 16 MB QIO Flash profile;80 MHz Octal PSRAM;实际容量待真机确认 | +| 分区 | [`partitions/v2/16m.csv`](../../../../partitions/v2/16m.csv),双 OTA | +| 电源保持 | GPIO7 在进程运行期间保持高电平 | +| 音频 | 原始 I2S;16 kHz 单声道 PCM;麦克风读取右 slot,播放复制到两个 slot | +| 显示 | 360×360 ST77916,80 MHz QSPI,共享 LVGL | +| 输入 | CST816S 触摸与 GPIO0 Boot;短按切换对话,长按 3 秒配网 | +| 网络 / 视频 | Wi-Fi;未接入摄像头,不支持视频 | + +| 信号 | GPIO | +| --- | --- | +| 麦克风 I2S RX | WS 2、BCLK 15、DIN 39 | +| 扬声器 I2S TX | DOUT 47、BCLK 48、WS 38 | +| 控制 I2C SDA / SCL | 11 / 10,TCA9554 地址 `0x20` | +| 触摸 I2C SDA / SCL | 1 / 3,中断 GPIO4 | +| QSPI CLK / CS / D0-D3 | 40 / 21 / 46、45、42、41 | +| LCD 背光 | 5,LEDC 5 kHz、13-bit,默认 60% | + +TCA9554 的 bit0/bit1 用于释放 LCD 与触摸复位。当前 profile 不映射硬件参考中的 GPIO6 +电源按键,也未实现电量、充电上报、休眠、关机或低功耗行为。构建与真机状态见 +[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board.c b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board.c new file mode 100644 index 0000000..5e6872b --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board.c @@ -0,0 +1,148 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "mybot_platform/board.h" + +#include + +#include "announcement/embedded_ogg_prompt.h" +#include "board_config.h" +#include "esp_log.h" +#include "lcd185_hardware.h" +#include "network/wifi_control.h" +#include "platform/board_actions.h" + +#include + +#define TAG "lcd185_board" + +const mybot_audio_capture_ops_t *mybot_esp32s3_audio_capture_ops(void); +const mybot_audio_playback_ops_t *mybot_esp32s3_audio_playback_ops(void); +const mybot_audio_volume_ops_t *mybot_esp32s3_audio_volume_ops(void); +const mybot_announce_ops_t *mybot_esp32s3_announce_ops(void); +const mybot_https_ops_t *mybot_esp32s3_https_ops(void); +const mybot_key_ops_t *mybot_cst816s_input_ops(void); +const mybot_kv_store_ops_t *mybot_esp32s3_kv_store_ops(void); +const mybot_lcd_ops_t *mybot_shared_lvgl_ops(void); +const mybot_wifi_ops_t *mybot_esp32s3_wifi_ops(void); +int mybot_cst816s_input_start(void); +int mybot_cst816s_input_stop(void); + +static const mybot_lcd_ops_t *s_lcd_ops; +static void *s_lcd_context; +static bool s_automatic_provisioning_started; +static bool s_prepared; +static bool s_cleanup_pending; + +static int board_show_screen(mybot_lcd_screen_t screen) { + if (!s_lcd_ops || !s_lcd_context) { + return -1; + } + const mybot_lcd_content_t content = {.screen = screen}; + return s_lcd_ops->render(s_lcd_context, &content); +} + +static void board_on_provisioning(const char *trigger) { + (void)board_show_screen(MYBOT_LCD_SCREEN_WIFI_PROVISIONING); + (void)mybot_embedded_ogg_play_wifi_provisioning(mybot_esp32s3_audio_playback_ops(), + mybot_esp32s3_audio_volume_ops(), trigger); +} + +static void board_on_automatic_provisioning(void) { + s_automatic_provisioning_started = true; + board_on_provisioning("automatic"); +} + +static void board_on_input_provisioning(void) { + board_on_provisioning("input"); +} + +static int board_rollback_prepare(void) { + s_cleanup_pending = true; + if (mybot_cst816s_input_stop() < 0) { + return -1; + } + if (s_lcd_ops && s_lcd_context) { + s_lcd_ops->destroy(s_lcd_context); + s_lcd_context = NULL; + } + s_lcd_ops = NULL; + if (mybot_lcd185_hardware_deinit() < 0) { + return -1; + } + s_cleanup_pending = false; + return 0; +} + +static int board_prepare(void) { + if (s_prepared) { + return 0; + } + if (s_cleanup_pending && board_rollback_prepare() < 0) { + return -1; + } + if (mybot_lcd185_hardware_init() < 0) { + return -1; + } + s_lcd_ops = mybot_shared_lvgl_ops(); + if (!s_lcd_ops || s_lcd_ops->init(&s_lcd_context) < 0 || + board_show_screen(MYBOT_LCD_SCREEN_STARTING) < 0 || mybot_cst816s_input_start() < 0) { + (void)board_rollback_prepare(); + return -1; + } + s_prepared = true; + return 0; +} + +int mybot_board_handle_boot_long_press(void) { + mybot_board_request_wifi_provisioning(); + return 0; +} + +static int board_provision_wifi(void) { + return mybot_wifi_run_provisioning(board_on_input_provisioning); +} + +static int board_ensure_network(const char *device_id) { + s_automatic_provisioning_started = false; + int result = mybot_wifi_ensure_network(device_id, board_on_automatic_provisioning); + if (s_automatic_provisioning_started) { + (void)mybot_board_wait_wifi_provisioning_request(0); + } + return result; +} + +static void board_shutdown_network(void) { + mybot_wifi_shutdown_network(); +} + +static int board_register_platform(void) { + const mybot_platform_descriptor_t descriptor = { + .wifi = mybot_esp32s3_wifi_ops(), + .kv_store = mybot_esp32s3_kv_store_ops(), + .key = mybot_cst816s_input_ops(), + .audio_capture = mybot_esp32s3_audio_capture_ops(), + .audio_playback = mybot_esp32s3_audio_playback_ops(), + .audio_volume = mybot_esp32s3_audio_volume_ops(), + .announce = mybot_esp32s3_announce_ops(), + .https = mybot_esp32s3_https_ops(), + .lcd = mybot_shared_lvgl_ops(), + }; + return mybot_platform_register(&descriptor); +} + +i2c_master_bus_handle_t mybot_board_touch_i2c_bus_handle(void) { + return mybot_lcd185_touch_i2c_bus_handle(); +} + +static const mybot_board_t s_board = { + .id = MYBOT_BOARD_NAME, + .hw_model = MYBOT_BOARD_NAME, + .prepare = board_prepare, + .register_platform = board_register_platform, + .ensure_network = board_ensure_network, + .provision_wifi = board_provision_wifi, + .shutdown_network = board_shutdown_network, +}; + +const mybot_board_t *mybot_board_get(void) { + return &s_board; +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board.cmake b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board.cmake new file mode 100644 index 0000000..49202db --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board.cmake @@ -0,0 +1,40 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_BOARD_TARGET "esp32s3") +set(MYBOT_BOARD_SDKCONFIG_DEFAULTS "${CMAKE_CURRENT_LIST_DIR}/sdkconfig.defaults") +set(MYBOT_BOARD_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}") +set(MYBOT_BOARD_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M +) +set(MYBOT_BOARD_PARTITION_TABLE "partitions/v2/16m.csv") + +get_filename_component(MYBOT_PLATFORM_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +include("${MYBOT_PLATFORM_ROOT}/src/drivers/display/display.cmake") +set(MYBOT_BOARD_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/board.c" + "${CMAKE_CURRENT_LIST_DIR}/lcd185_hardware.c" + "${MYBOT_PLATFORM_ROOT}/src/drivers/io_expander/tca9554.c" + "${MYBOT_PLATFORM_ROOT}/src/drivers/input/cst816s_boot_input.c" + "${MYBOT_PLATFORM_ROOT}/src/drivers/audio/raw_i2s_audio.c" +) +mybot_display_add_panel(MYBOT_BOARD_SOURCES "st77916/st77916_lcd185_panel.c") +mybot_display_add_lvgl_sources(MYBOT_BOARD_SOURCES "shared_lvgl_adapter.cc") +set(MYBOT_BOARD_REQUIRES + button + esp_driver_gpio + esp_driver_i2c + esp_driver_i2s + esp_driver_ledc + esp_driver_spi + esp_lcd + esp_lcd_st77916 + esp_lcd_touch + esp_lcd_touch_cst816s + esp_lvgl_port + lvgl +) diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board_config.h b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board_config.h new file mode 100644 index 0000000..d6a2734 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/board_config.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-touch-lcd-1.85" +#define MYBOT_BOARD_HAS_TOUCH 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_I2S_MIC_WS GPIO_NUM_2 +#define MYBOT_AUDIO_I2S_MIC_BCLK GPIO_NUM_15 +#define MYBOT_AUDIO_I2S_MIC_DIN GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_CAPTURE_RIGHT_SLOT 1 +#define MYBOT_AUDIO_I2S_SPK_DOUT GPIO_NUM_47 +#define MYBOT_AUDIO_I2S_SPK_BCLK GPIO_NUM_48 +#define MYBOT_AUDIO_I2S_SPK_WS GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS 1 + +#define MYBOT_LCD185_I2C_PORT I2C_NUM_0 +#define MYBOT_LCD185_I2C_SDA GPIO_NUM_11 +#define MYBOT_LCD185_I2C_SCL GPIO_NUM_10 +#define MYBOT_LCD185_TCA9554_ADDRESS 0x20 +#define MYBOT_LCD185_POWER_CONTROL GPIO_NUM_7 + +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_PCLK GPIO_NUM_40 +#define MYBOT_DISPLAY_DATA0 GPIO_NUM_46 +#define MYBOT_DISPLAY_DATA1 GPIO_NUM_45 +#define MYBOT_DISPLAY_DATA2 GPIO_NUM_42 +#define MYBOT_DISPLAY_DATA3 GPIO_NUM_41 +#define MYBOT_DISPLAY_CS GPIO_NUM_21 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT GPIO_NUM_5 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 360 +#define MYBOT_DISPLAY_HEIGHT 360 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (80 * 1000 * 1000) + +#define MYBOT_TOUCH_I2C_PORT I2C_NUM_1 +#define MYBOT_TOUCH_I2C_SDA GPIO_NUM_1 +#define MYBOT_TOUCH_I2C_SCL GPIO_NUM_3 +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_NC +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_4 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/lcd185_hardware.c b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/lcd185_hardware.c new file mode 100644 index 0000000..7057e2c --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/lcd185_hardware.c @@ -0,0 +1,209 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "lcd185_hardware.h" + +#include "board_config.h" +#include "io_expander/tca9554.h" + +#include "driver/gpio.h" +#include "driver/ledc.h" +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define TAG "lcd185_hw" +#define BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE +#define BACKLIGHT_LEDC_TIMER LEDC_TIMER_0 +#define BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0 +#define BACKLIGHT_LEDC_RESOLUTION LEDC_TIMER_13_BIT +#define BACKLIGHT_LEDC_FREQUENCY_HZ 5000 +#define BACKLIGHT_MAX_DUTY ((1U << 13) - 1U) + +typedef struct { + i2c_master_bus_handle_t control_bus; + i2c_master_bus_handle_t touch_bus; + mybot_tca9554_t expander; + bool power_hold_ready; + bool backlight_ready; + bool initialized; +} lcd185_hardware_t; + +static lcd185_hardware_t s_hardware; + +static esp_err_t initialize_power_hold(void) { + const gpio_config_t config = { + .pin_bit_mask = 1ULL << MYBOT_LCD185_POWER_CONTROL, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + esp_err_t result = gpio_config(&config); + if (result == ESP_OK) { + result = gpio_set_level(MYBOT_LCD185_POWER_CONTROL, 1); + } + if (result == ESP_OK) { + s_hardware.power_hold_ready = true; + } + return result; +} + +static esp_err_t initialize_backlight(void) { + const ledc_timer_config_t timer_config = { + .speed_mode = BACKLIGHT_LEDC_MODE, + .duty_resolution = BACKLIGHT_LEDC_RESOLUTION, + .timer_num = BACKLIGHT_LEDC_TIMER, + .freq_hz = BACKLIGHT_LEDC_FREQUENCY_HZ, + .clk_cfg = LEDC_AUTO_CLK, + }; + esp_err_t result = ledc_timer_config(&timer_config); + if (result != ESP_OK) { + return result; + } + const ledc_channel_config_t channel_config = { + .gpio_num = MYBOT_DISPLAY_BACKLIGHT, + .speed_mode = BACKLIGHT_LEDC_MODE, + .channel = BACKLIGHT_LEDC_CHANNEL, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = BACKLIGHT_LEDC_TIMER, + .duty = 0, + .hpoint = 0, + .sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD, + }; + result = ledc_channel_config(&channel_config); + if (result == ESP_OK) { + s_hardware.backlight_ready = true; + } + return result; +} + +static esp_err_t initialize_i2c(void) { + const i2c_master_bus_config_t control_config = { + .i2c_port = MYBOT_LCD185_I2C_PORT, + .sda_io_num = MYBOT_LCD185_I2C_SDA, + .scl_io_num = MYBOT_LCD185_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + esp_err_t result = i2c_new_master_bus(&control_config, &s_hardware.control_bus); + const i2c_master_bus_config_t touch_config = { + .i2c_port = MYBOT_TOUCH_I2C_PORT, + .sda_io_num = MYBOT_TOUCH_I2C_SDA, + .scl_io_num = MYBOT_TOUCH_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + if (result == ESP_OK) { + result = i2c_new_master_bus(&touch_config, &s_hardware.touch_bus); + } + return result; +} + +static esp_err_t pulse_panel_resets(void) { + esp_err_t result = + mybot_tca9554_configure_outputs(&s_hardware.expander, 0x03, 0x00) == 0 ? ESP_OK : ESP_FAIL; + if (result == ESP_OK) { + result = + mybot_tca9554_set_levels(&s_hardware.expander, 0x03, 0x03) == 0 ? ESP_OK : ESP_FAIL; + vTaskDelay(pdMS_TO_TICKS(300)); + } + if (result == ESP_OK) { + result = + mybot_tca9554_set_levels(&s_hardware.expander, 0x03, 0x00) == 0 ? ESP_OK : ESP_FAIL; + vTaskDelay(pdMS_TO_TICKS(300)); + } + if (result == ESP_OK) { + result = + mybot_tca9554_set_levels(&s_hardware.expander, 0x03, 0x03) == 0 ? ESP_OK : ESP_FAIL; + } + return result; +} + +static int release_hardware(void) { + int result = 0; + if (s_hardware.backlight_ready && + ledc_stop(BACKLIGHT_LEDC_MODE, BACKLIGHT_LEDC_CHANNEL, 0) != ESP_OK) { + result = -1; + } + if (s_hardware.backlight_ready && gpio_reset_pin(MYBOT_DISPLAY_BACKLIGHT) != ESP_OK) { + result = -1; + } + if (s_hardware.power_hold_ready && gpio_set_level(MYBOT_LCD185_POWER_CONTROL, 0) != ESP_OK) { + result = -1; + } + if (mybot_tca9554_deinit(&s_hardware.expander) < 0) { + result = -1; + } + if (s_hardware.touch_bus && i2c_del_master_bus(s_hardware.touch_bus) != ESP_OK) { + result = -1; + } + if (s_hardware.control_bus && i2c_del_master_bus(s_hardware.control_bus) != ESP_OK) { + result = -1; + } + if (result == 0) { + s_hardware = (lcd185_hardware_t){0}; + } else { + s_hardware.initialized = false; + } + return result; +} + +int mybot_lcd185_hardware_init(void) { + if (s_hardware.initialized) { + return 0; + } + if ((s_hardware.control_bus || s_hardware.touch_bus || s_hardware.expander.device || + s_hardware.power_hold_ready || s_hardware.backlight_ready) && + release_hardware() < 0) { + return -1; + } + + esp_err_t result = initialize_power_hold(); + if (result == ESP_OK) { + result = initialize_backlight(); + } + if (result == ESP_OK) { + result = initialize_i2c(); + } + if (result == ESP_OK) { + result = mybot_tca9554_init(&s_hardware.expander, s_hardware.control_bus, + MYBOT_LCD185_TCA9554_ADDRESS, 400000) == 0 + ? ESP_OK + : ESP_FAIL; + } + if (result == ESP_OK) { + result = pulse_panel_resets(); + } + if (result != ESP_OK) { + ESP_LOGE(TAG, "event=hardware action=initialize result=error code=%s", + esp_err_to_name(result)); + (void)release_hardware(); + return -1; + } + + s_hardware.initialized = true; + ESP_LOGI(TAG, "event=hardware action=initialize result=ok"); + return 0; +} + +int mybot_lcd185_hardware_deinit(void) { + return release_hardware(); +} + +i2c_master_bus_handle_t mybot_lcd185_touch_i2c_bus_handle(void) { + return s_hardware.initialized ? s_hardware.touch_bus : NULL; +} + +int mybot_board_set_display_backlight(unsigned int percent) { + if (!s_hardware.initialized || !s_hardware.backlight_ready || percent > 100) { + return -1; + } + uint32_t duty = (BACKLIGHT_MAX_DUTY * percent + 50U) / 100U; + esp_err_t result = ledc_set_duty(BACKLIGHT_LEDC_MODE, BACKLIGHT_LEDC_CHANNEL, duty); + if (result == ESP_OK) { + result = ledc_update_duty(BACKLIGHT_LEDC_MODE, BACKLIGHT_LEDC_CHANNEL); + } + return result == ESP_OK ? 0 : -1; +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/lcd185_hardware.h b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/lcd185_hardware.h new file mode 100644 index 0000000..db2f4ca --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/lcd185_hardware.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_LCD185_HARDWARE_H_ +#define MYBOT_LCD185_HARDWARE_H_ + +#include "driver/i2c_master.h" + +#ifdef __cplusplus +extern "C" { +#endif + +int mybot_lcd185_hardware_init(void); +int mybot_lcd185_hardware_deinit(void); +i2c_master_bus_handle_t mybot_lcd185_touch_i2c_bus_handle(void); + +#ifdef __cplusplus +} +#endif + +#endif /* MYBOT_LCD185_HARDWARE_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/sdkconfig.defaults new file mode 100644 index 0000000..dda7423 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" + +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/components/mybot_platform/boards/genjutech-s3-1.54tft/README.md b/components/mybot_platform/boards/genjutech-s3-1.54tft/README.md new file mode 100644 index 0000000..b873184 --- /dev/null +++ b/components/mybot_platform/boards/genjutech-s3-1.54tft/README.md @@ -0,0 +1,33 @@ +# GenJuTech S3 1.54 TFT + +板型 ID 为 `genjutech-s3-1.54tft`。硬件参数、引脚和能力由本目录的 +[`board_config.h`](board_config.h)、[`board.cmake`](board.cmake)、[`board.c`](board.c) +与 [`sdkconfig.defaults`](sdkconfig.defaults) 定义。 + +| 项目 | 当前实现 | +| --- | --- | +| 存储 | 16 MB QIO Flash profile;80 MHz Octal PSRAM;实际容量待真机确认 | +| 分区 | [`partitions/v2/16m.csv`](../../../../partitions/v2/16m.csv),双 OTA | +| 音频 | ES8311,16 kHz 单声道 PCM;GPIO21 扬声器功放 | +| 显示 | 240×240 ST7789,SPI3 mode 3,80 MHz,颜色反转,共享 LVGL | +| 输入 | GPIO0 Boot、GPIO42 音量加、GPIO41 音量减,均低电平有效 | +| 网络 / 视频 | Wi-Fi;未接入摄像头,不支持视频 | + +| 信号 | GPIO | +| --- | --- | +| I2S MCLK / BCLK / WS / DIN / DOUT | 14 / 13 / 11 / 12 / 10 | +| ES8311 I2C SDA / SCL | 9 / 8 | +| LCD MOSI / SCLK / DC / CS / RESET | 3 / 4 / 5 / 6 / 7 | +| LCD 背光 | 2,LEDC 5 kHz、13-bit,默认 60% | + +屏幕、音频和输入在板级初始化后由进程持有。该板的扬声器使能与显示 I/O 存在板级耦合, +因此首次播放开启 GPIO21 后保持有效,直到板级硬件拆除;停止播放仅让 I2S 流空闲。 + +## 操作 + +- 短按 Boot:开始或结束对话。 +- 短按音量加/减:调节并持久化软件音量。 +- 长按 Boot 3 秒:停止 MyBot 后进入 Wi-Fi 配网。 + +当前未实现电量/充电、LED、触摸、摄像头、休眠、关机或低功耗行为。构建与真机状态见 +[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/genjutech-s3-1.54tft/board.c b/components/mybot_platform/boards/genjutech-s3-1.54tft/board.c new file mode 100644 index 0000000..2797728 --- /dev/null +++ b/components/mybot_platform/boards/genjutech-s3-1.54tft/board.c @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "mybot_platform/board.h" + +#include + +#include "announcement/embedded_ogg_prompt.h" +#include "board_config.h" +#include "esp_log.h" +#include "genjutech_hardware.h" +#include "network/wifi_control.h" +#include "platform/board_actions.h" + +#include + +#define TAG "genjutech_board" + +const mybot_audio_capture_ops_t *mybot_es8311_audio_capture_ops(void); +const mybot_audio_playback_ops_t *mybot_es8311_audio_playback_ops(void); +const mybot_audio_volume_ops_t *mybot_es8311_audio_volume_ops(void); +const mybot_announce_ops_t *mybot_esp32s3_announce_ops(void); +const mybot_https_ops_t *mybot_esp32s3_https_ops(void); +const mybot_key_ops_t *mybot_esp32s3_button_ops(void); +const mybot_kv_store_ops_t *mybot_esp32s3_kv_store_ops(void); +const mybot_lcd_ops_t *mybot_shared_lvgl_ops(void); +const mybot_wifi_ops_t *mybot_esp32s3_wifi_ops(void); +int mybot_esp32s3_buttons_start(void); + +static const mybot_lcd_ops_t *s_lcd_ops; +static void *s_lcd_context; +static bool s_automatic_provisioning_started; +static bool s_prepared; + +static int board_show_screen(mybot_lcd_screen_t screen) { + if (!s_lcd_ops || !s_lcd_context) { + return -1; + } + const mybot_lcd_content_t content = {.screen = screen}; + return s_lcd_ops->render(s_lcd_context, &content); +} + +static void board_on_provisioning(const char *trigger) { + (void)board_show_screen(MYBOT_LCD_SCREEN_WIFI_PROVISIONING); + (void)mybot_embedded_ogg_play_wifi_provisioning(mybot_es8311_audio_playback_ops(), + mybot_es8311_audio_volume_ops(), trigger); +} + +static void board_on_automatic_provisioning(void) { + s_automatic_provisioning_started = true; + board_on_provisioning("automatic"); +} + +static void board_on_button_provisioning(void) { + board_on_provisioning("button"); +} + +static int board_prepare(void) { + if (s_prepared) { + return 0; + } + if (mybot_genjutech_hardware_init() < 0) { + return -1; + } + s_lcd_ops = mybot_shared_lvgl_ops(); + if (!s_lcd_ops || s_lcd_ops->init(&s_lcd_context) < 0 || + board_show_screen(MYBOT_LCD_SCREEN_STARTING) < 0 || mybot_esp32s3_buttons_start() < 0) { + if (s_lcd_context) { + s_lcd_ops->destroy(s_lcd_context); + s_lcd_context = NULL; + } + (void)mybot_genjutech_hardware_deinit(); + return -1; + } + s_prepared = true; + return 0; +} + +int mybot_board_handle_boot_long_press(void) { + mybot_board_request_wifi_provisioning(); + return 0; +} + +static int board_provision_wifi(void) { + return mybot_wifi_run_provisioning(board_on_button_provisioning); +} + +static int board_ensure_network(const char *device_id) { + s_automatic_provisioning_started = false; + int result = mybot_wifi_ensure_network(device_id, board_on_automatic_provisioning); + if (s_automatic_provisioning_started) { + (void)mybot_board_wait_wifi_provisioning_request(0); + } + return result; +} + +static void board_shutdown_network(void) { + mybot_wifi_shutdown_network(); +} + +static int board_register_platform(void) { + const mybot_platform_descriptor_t descriptor = { + .wifi = mybot_esp32s3_wifi_ops(), + .kv_store = mybot_esp32s3_kv_store_ops(), + .key = mybot_esp32s3_button_ops(), + .audio_capture = mybot_es8311_audio_capture_ops(), + .audio_playback = mybot_es8311_audio_playback_ops(), + .audio_volume = mybot_es8311_audio_volume_ops(), + .announce = mybot_esp32s3_announce_ops(), + .https = mybot_esp32s3_https_ops(), + .lcd = mybot_shared_lvgl_ops(), + }; + return mybot_platform_register(&descriptor); +} + +void *mybot_es8311_board_i2c_bus_handle(void) { + return mybot_genjutech_i2c_bus_handle(); +} + +int mybot_es8311_board_set_speaker_power(bool enabled) { + return mybot_genjutech_set_speaker_power(enabled); +} + +static const mybot_board_t s_board = { + .id = MYBOT_BOARD_NAME, + .hw_model = MYBOT_BOARD_NAME, + .prepare = board_prepare, + .register_platform = board_register_platform, + .ensure_network = board_ensure_network, + .provision_wifi = board_provision_wifi, + .shutdown_network = board_shutdown_network, +}; + +const mybot_board_t *mybot_board_get(void) { + return &s_board; +} diff --git a/components/mybot_platform/boards/genjutech-s3-1.54tft/board.cmake b/components/mybot_platform/boards/genjutech-s3-1.54tft/board.cmake new file mode 100644 index 0000000..c38c96d --- /dev/null +++ b/components/mybot_platform/boards/genjutech-s3-1.54tft/board.cmake @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_BOARD_TARGET "esp32s3") +set(MYBOT_BOARD_SDKCONFIG_DEFAULTS "${CMAKE_CURRENT_LIST_DIR}/sdkconfig.defaults") +set(MYBOT_BOARD_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}") +set(MYBOT_BOARD_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +set(MYBOT_BOARD_FORBIDDEN_CONFIGS + CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE +) +set(MYBOT_BOARD_PARTITION_TABLE "partitions/v2/16m.csv") + +get_filename_component(MYBOT_PLATFORM_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +include("${MYBOT_PLATFORM_ROOT}/src/drivers/display/display.cmake") +set(MYBOT_BOARD_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/board.c" + "${CMAKE_CURRENT_LIST_DIR}/genjutech_hardware.c" + "${MYBOT_PLATFORM_ROOT}/src/drivers/audio/es8311_codec_audio.c" + "${MYBOT_PLATFORM_ROOT}/src/drivers/input/gpio_buttons.c" +) +mybot_display_add_panel(MYBOT_BOARD_SOURCES "st7789/st7789_panel.c") +mybot_display_add_lvgl_sources(MYBOT_BOARD_SOURCES "shared_lvgl_adapter.cc") +set(MYBOT_BOARD_REQUIRES + button + esp_codec_dev + esp_driver_gpio + esp_driver_i2c + esp_driver_i2s + esp_driver_ledc + esp_driver_spi + esp_lcd + esp_lvgl_port + lvgl + nvs_flash +) diff --git a/components/mybot_platform/boards/genjutech-s3-1.54tft/board_config.h b/components/mybot_platform/boards/genjutech-s3-1.54tft/board_config.h new file mode 100644 index 0000000..37310fe --- /dev/null +++ b/components/mybot_platform/boards/genjutech-s3-1.54tft/board_config.h @@ -0,0 +1,54 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "genjutech-s3-1.54tft" + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_14 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_13 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_11 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_12 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_10 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 + +#define MYBOT_GENJUTECH_I2C_PORT I2C_NUM_0 +#define MYBOT_GENJUTECH_I2C_SDA GPIO_NUM_9 +#define MYBOT_GENJUTECH_I2C_SCL GPIO_NUM_8 +#define MYBOT_GENJUTECH_I2C_SPEED_HZ 400000 +#define MYBOT_GENJUTECH_SPEAKER_PA GPIO_NUM_21 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENJUTECH_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_42 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_41 + +#define MYBOT_DISPLAY_MOSI GPIO_NUM_3 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_4 +#define MYBOT_DISPLAY_DC GPIO_NUM_5 +#define MYBOT_DISPLAY_CS GPIO_NUM_6 +#define MYBOT_DISPLAY_RESET GPIO_NUM_7 +#define MYBOT_DISPLAY_BACKLIGHT GPIO_NUM_2 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_SPI_MODE 3 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (80 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/genjutech-s3-1.54tft/genjutech_hardware.c b/components/mybot_platform/boards/genjutech-s3-1.54tft/genjutech_hardware.c new file mode 100644 index 0000000..1b3e2ac --- /dev/null +++ b/components/mybot_platform/boards/genjutech-s3-1.54tft/genjutech_hardware.c @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "genjutech_hardware.h" + +#include "board_config.h" + +#include "driver/gpio.h" +#include "driver/ledc.h" +#include "esp_err.h" +#include "esp_log.h" + +#define TAG "genjutech_hw" +#define BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE +#define BACKLIGHT_LEDC_TIMER LEDC_TIMER_0 +#define BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0 +#define BACKLIGHT_LEDC_RESOLUTION LEDC_TIMER_13_BIT +#define BACKLIGHT_LEDC_FREQUENCY_HZ 5000 +#define BACKLIGHT_MAX_DUTY ((1U << 13) - 1U) + +typedef struct { + i2c_master_bus_handle_t bus; + bool pa_ready; + bool pa_enabled; + bool backlight_ready; + bool initialized; +} genjutech_hardware_t; + +static genjutech_hardware_t s_hardware; + +static esp_err_t initialize_pa(void) { + const gpio_config_t config = { + .pin_bit_mask = 1ULL << MYBOT_GENJUTECH_SPEAKER_PA, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_ENABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + esp_err_t result = gpio_config(&config); + if (result == ESP_OK) { + result = gpio_set_level(MYBOT_GENJUTECH_SPEAKER_PA, 0); + } + if (result == ESP_OK) { + s_hardware.pa_ready = true; + s_hardware.pa_enabled = false; + } + return result; +} + +static esp_err_t initialize_backlight(void) { + const ledc_timer_config_t timer_config = { + .speed_mode = BACKLIGHT_LEDC_MODE, + .duty_resolution = BACKLIGHT_LEDC_RESOLUTION, + .timer_num = BACKLIGHT_LEDC_TIMER, + .freq_hz = BACKLIGHT_LEDC_FREQUENCY_HZ, + .clk_cfg = LEDC_AUTO_CLK, + }; + esp_err_t result = ledc_timer_config(&timer_config); + if (result != ESP_OK) { + return result; + } + const ledc_channel_config_t channel_config = { + .gpio_num = MYBOT_DISPLAY_BACKLIGHT, + .speed_mode = BACKLIGHT_LEDC_MODE, + .channel = BACKLIGHT_LEDC_CHANNEL, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = BACKLIGHT_LEDC_TIMER, + .duty = 0, + .hpoint = 0, + .sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD, + }; + result = ledc_channel_config(&channel_config); + if (result == ESP_OK) { + s_hardware.backlight_ready = true; + } + return result; +} + +static int release_hardware(void) { + int result = 0; + if (s_hardware.backlight_ready) { + if (ledc_stop(BACKLIGHT_LEDC_MODE, BACKLIGHT_LEDC_CHANNEL, 0) == ESP_OK) { + esp_err_t reset_result = gpio_reset_pin(MYBOT_DISPLAY_BACKLIGHT); + if (reset_result != ESP_OK) { + result = -1; + } + s_hardware.backlight_ready = false; + } else { + result = -1; + } + } + if (s_hardware.pa_ready && gpio_set_level(MYBOT_GENJUTECH_SPEAKER_PA, 0) != ESP_OK) { + result = -1; + } + if (s_hardware.bus) { + if (i2c_del_master_bus(s_hardware.bus) == ESP_OK) { + s_hardware.bus = NULL; + } else { + result = -1; + } + } + s_hardware.initialized = false; + s_hardware.pa_enabled = false; + return result; +} + +int mybot_genjutech_hardware_init(void) { + if (s_hardware.initialized) { + return 0; + } + if ((s_hardware.bus || s_hardware.pa_ready || s_hardware.backlight_ready) && + release_hardware() < 0) { + return -1; + } + + esp_err_t result = initialize_pa(); + if (result == ESP_OK) { + result = initialize_backlight(); + } + const i2c_master_bus_config_t bus_config = { + .i2c_port = MYBOT_GENJUTECH_I2C_PORT, + .sda_io_num = MYBOT_GENJUTECH_I2C_SDA, + .scl_io_num = MYBOT_GENJUTECH_I2C_SCL, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + if (result == ESP_OK) { + result = i2c_new_master_bus(&bus_config, &s_hardware.bus); + } + if (result != ESP_OK) { + ESP_LOGE(TAG, "event=hardware action=initialize result=error code=%s", + esp_err_to_name(result)); + (void)release_hardware(); + return -1; + } + + s_hardware.initialized = true; + ESP_LOGI(TAG, "event=hardware action=initialize result=ok speaker=off backlight=off"); + return 0; +} + +int mybot_genjutech_hardware_deinit(void) { + return release_hardware(); +} + +i2c_master_bus_handle_t mybot_genjutech_i2c_bus_handle(void) { + return s_hardware.initialized ? s_hardware.bus : NULL; +} + +int mybot_genjutech_set_speaker_power(bool enabled) { + if (!s_hardware.initialized || !s_hardware.pa_ready) { + return -1; + } + if (s_hardware.pa_enabled == enabled) { + return 0; + } + if (!enabled) { + /* This board couples PA shutdown to display I/O. Keep the amplifier + * enabled until hardware teardown and let the I2S stream go idle. */ + return 0; + } + esp_err_t result = gpio_set_level(MYBOT_GENJUTECH_SPEAKER_PA, enabled ? 1 : 0); + if (result != ESP_OK) { + return -1; + } + s_hardware.pa_enabled = enabled; + return 0; +} + +int mybot_board_set_display_backlight(unsigned int percent) { + if (!s_hardware.initialized || !s_hardware.backlight_ready || percent > 100) { + return -1; + } + uint32_t duty = (BACKLIGHT_MAX_DUTY * percent + 50U) / 100U; + esp_err_t result = ledc_set_duty(BACKLIGHT_LEDC_MODE, BACKLIGHT_LEDC_CHANNEL, duty); + if (result == ESP_OK) { + result = ledc_update_duty(BACKLIGHT_LEDC_MODE, BACKLIGHT_LEDC_CHANNEL); + } + return result == ESP_OK ? 0 : -1; +} diff --git a/components/mybot_platform/boards/genjutech-s3-1.54tft/genjutech_hardware.h b/components/mybot_platform/boards/genjutech-s3-1.54tft/genjutech_hardware.h new file mode 100644 index 0000000..e629808 --- /dev/null +++ b/components/mybot_platform/boards/genjutech-s3-1.54tft/genjutech_hardware.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_GENJUTECH_HARDWARE_H_ +#define MYBOT_GENJUTECH_HARDWARE_H_ + +#include "driver/i2c_master.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int mybot_genjutech_hardware_init(void); +int mybot_genjutech_hardware_deinit(void); +i2c_master_bus_handle_t mybot_genjutech_i2c_bus_handle(void); +int mybot_genjutech_set_speaker_power(bool enabled); + +#ifdef __cplusplus +} +#endif + +#endif /* MYBOT_GENJUTECH_HARDWARE_H_ */ diff --git a/components/mybot_platform/boards/genjutech-s3-1.54tft/sdkconfig.defaults b/components/mybot_platform/boards/genjutech-s3-1.54tft/sdkconfig.defaults new file mode 100644 index 0000000..0be593f --- /dev/null +++ b/components/mybot_platform/boards/genjutech-s3-1.54tft/sdkconfig.defaults @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" + +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/src/drivers/audio/raw_i2s_audio.c b/components/mybot_platform/src/drivers/audio/raw_i2s_audio.c index ad1f162..4d403ac 100644 --- a/components/mybot_platform/src/drivers/audio/raw_i2s_audio.c +++ b/components/mybot_platform/src/drivers/audio/raw_i2s_audio.c @@ -26,6 +26,20 @@ #define AUDIO_VOLUME_NVS_NAMESPACE "audio" #define TAG "mybot_audio" +#ifndef MYBOT_AUDIO_I2S_CAPTURE_RIGHT_SLOT +#define MYBOT_AUDIO_I2S_CAPTURE_RIGHT_SLOT 0 +#endif + +#ifndef MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS +#define MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS 0 +#endif + +#if MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS +#define AUDIO_SCRATCH_FRAMES (AUDIO_MAX_FRAMES * 2) +#else +#define AUDIO_SCRATCH_FRAMES AUDIO_MAX_FRAMES +#endif + typedef struct { i2s_chan_handle_t channel; aosl_atomic_t started; @@ -35,7 +49,7 @@ typedef struct { bool playback; bool enabled; bool stop_pending; - int32_t scratch[AUDIO_MAX_FRAMES]; + int32_t scratch[AUDIO_SCRATCH_FRAMES]; } audio_context_t; typedef struct { @@ -206,10 +220,11 @@ static int validate_format(int rate, int channels, int bits) { } static i2s_std_config_t make_i2s_config(int rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, - gpio_num_t din) { + gpio_num_t din, bool both_slots, bool right_slot) { i2s_std_config_t config = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(rate), - .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_32BIT, I2S_SLOT_MODE_MONO), + .slot_cfg = I2S_STD_MSB_SLOT_DEFAULT_CONFIG( + I2S_DATA_BIT_WIDTH_32BIT, both_slots ? I2S_SLOT_MODE_STEREO : I2S_SLOT_MODE_MONO), .gpio_cfg = { .mclk = I2S_GPIO_UNUSED, @@ -225,7 +240,8 @@ static i2s_std_config_t make_i2s_config(int rate, gpio_num_t bclk, gpio_num_t ws }, }, }; - config.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT; + config.slot_cfg.slot_mask = + both_slots ? I2S_STD_SLOT_BOTH : (right_slot ? I2S_STD_SLOT_RIGHT : I2S_STD_SLOT_LEFT); config.slot_cfg.ws_width = I2S_DATA_BIT_WIDTH_32BIT; config.slot_cfg.bit_shift = true; config.slot_cfg.left_align = true; @@ -262,7 +278,7 @@ static int capture_init(void **out_ctx, int rate, int channels, int bits) { i2s_std_config_t std_config = make_i2s_config(rate, MYBOT_AUDIO_I2S_MIC_BCLK, MYBOT_AUDIO_I2S_MIC_WS, I2S_GPIO_UNUSED, - MYBOT_AUDIO_I2S_MIC_DIN); + MYBOT_AUDIO_I2S_MIC_DIN, false, MYBOT_AUDIO_I2S_CAPTURE_RIGHT_SLOT != 0); if (i2s_channel_init_std_mode(ctx->channel, &std_config) != ESP_OK) { i2s_del_channel(ctx->channel); free(ctx); @@ -323,6 +339,8 @@ static int capture_read(void *opaque, void *buffer, int frames) { } static int clear_playback_dma_locked(audio_context_t *ctx) { + const size_t bytes_per_frame = + sizeof(ctx->scratch[0]) * (MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS ? 2 : 1); memset(ctx->scratch, 0, sizeof(ctx->scratch)); while (ctx->dma_clear_frames < AUDIO_DMA_FRAMES) { size_t frames = AUDIO_DMA_FRAMES - ctx->dma_clear_frames; @@ -330,13 +348,13 @@ static int clear_playback_dma_locked(audio_context_t *ctx) { frames = AUDIO_MAX_FRAMES; } size_t loaded = 0; - esp_err_t err = i2s_channel_preload_data(ctx->channel, ctx->scratch, - frames * sizeof(ctx->scratch[0]), &loaded); - if (loaded > frames * sizeof(ctx->scratch[0]) || loaded % sizeof(ctx->scratch[0])) { + esp_err_t err = + i2s_channel_preload_data(ctx->channel, ctx->scratch, frames * bytes_per_frame, &loaded); + if (loaded > frames * bytes_per_frame || loaded % bytes_per_frame) { return -1; } - ctx->dma_clear_frames += loaded / sizeof(ctx->scratch[0]); - if (err != ESP_OK || loaded != frames * sizeof(ctx->scratch[0])) { + ctx->dma_clear_frames += loaded / bytes_per_frame; + if (err != ESP_OK || loaded != frames * bytes_per_frame) { ESP_LOGE(TAG, "event=audio_device direction=playback action=clear_dma result=error " "error=%s loaded=%u", @@ -419,16 +437,25 @@ static esp_err_t playback_sink_write(void *opaque, const int16_t *pcm, size_t fr } int32_t gain_q16 = (int32_t)aosl_atomic_read(&s_output_gain_q16); for (size_t i = 0; i < frames; ++i) { - ctx->scratch[i] = (int32_t)((int64_t)pcm[i] * gain_q16); + const int32_t sample = (int32_t)((int64_t)pcm[i] * gain_q16); + if (MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS) { + ctx->scratch[i * 2] = sample; + ctx->scratch[i * 2 + 1] = sample; + } else { + ctx->scratch[i] = sample; + } } + const size_t sample_count = frames * (MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS ? 2U : 1U); + const size_t bytes_per_frame = + sizeof(ctx->scratch[0]) * (MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS ? 2U : 1U); size_t bytes_written = 0; - esp_err_t err = i2s_channel_write(ctx->channel, ctx->scratch, frames * sizeof(ctx->scratch[0]), - &bytes_written, timeout_ms); - if (bytes_written > frames * sizeof(ctx->scratch[0]) || - bytes_written % sizeof(ctx->scratch[0])) { + esp_err_t err = + i2s_channel_write(ctx->channel, ctx->scratch, sample_count * sizeof(ctx->scratch[0]), + &bytes_written, timeout_ms); + if (bytes_written > sample_count * sizeof(ctx->scratch[0]) || bytes_written % bytes_per_frame) { return ESP_FAIL; } - *written_frames = bytes_written / sizeof(ctx->scratch[0]); + *written_frames = bytes_written / bytes_per_frame; return err; } @@ -454,9 +481,9 @@ static int playback_init(void **out_ctx, int rate, int channels, int bits) { return -1; } - i2s_std_config_t std_config = - make_i2s_config(rate, MYBOT_AUDIO_I2S_SPK_BCLK, MYBOT_AUDIO_I2S_SPK_WS, - MYBOT_AUDIO_I2S_SPK_DOUT, I2S_GPIO_UNUSED); + i2s_std_config_t std_config = make_i2s_config( + rate, MYBOT_AUDIO_I2S_SPK_BCLK, MYBOT_AUDIO_I2S_SPK_WS, MYBOT_AUDIO_I2S_SPK_DOUT, + I2S_GPIO_UNUSED, MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS != 0, false); if (i2s_channel_init_std_mode(ctx->channel, &std_config) != ESP_OK) { i2s_del_channel(ctx->channel); free(ctx); diff --git a/components/mybot_platform/src/drivers/display/panels/co5300/co5300_amoled18_panel.c b/components/mybot_platform/src/drivers/display/panels/co5300/co5300_amoled18_panel.c new file mode 100644 index 0000000..a880d82 --- /dev/null +++ b/components/mybot_platform/src/drivers/display/panels/co5300/co5300_amoled18_panel.c @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "board_config.h" +#include "display/display_panel.h" + +#include "driver/spi_master.h" +#include "esp_err.h" +#include "esp_lcd_co5300.h" +#include "esp_lcd_io_spi.h" +#include "esp_log.h" + +#include +#include + +#define TAG "co5300_amoled18" +#define BRIGHTNESS_PERCENT 60 + +_Static_assert((MYBOT_DISPLAY_WIDTH % 2) == 0, "CO5300 transfers require an even width"); +_Static_assert((MYBOT_DISPLAY_HEIGHT % 2) == 0, "CO5300 transfers require an even height"); +_Static_assert((MYBOT_DISPLAY_TRANSFER_ROWS % 2) == 0, "CO5300 transfer rows must be even"); + +static const co5300_lcd_init_cmd_t s_vendor_init[] = { + {0x11, NULL, 0, 600}, + {0xFE, (uint8_t[]){0x20}, 1, 0}, + {0x19, (uint8_t[]){0x10}, 1, 0}, + {0x1C, (uint8_t[]){0xA0}, 1, 0}, + {0xFE, (uint8_t[]){0x00}, 1, 0}, + {0xC4, (uint8_t[]){0x80}, 1, 0}, + {0x3A, (uint8_t[]){0x55}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x53, (uint8_t[]){0x20}, 1, 0}, + {0x51, (uint8_t[]){0xFF}, 1, 0}, + {0x63, (uint8_t[]){0xFF}, 1, 0}, + {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0xDF}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xDF}, 4, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x29, NULL, 0, 600}, +}; + +int mybot_display_panel_close(mybot_display_panel_t *panel) { + if (!panel) { + return -1; + } + if (panel->panel && esp_lcd_panel_co5300_set_brightness(panel->panel, 0) != ESP_OK) { + ESP_LOGW(TAG, "event=panel action=brightness_off result=error"); + } + if (panel->panel) { + if (panel->ready && esp_lcd_panel_disp_on_off(panel->panel, false) != ESP_OK) { + ESP_LOGW(TAG, "event=panel action=display_off result=error"); + } + if (esp_lcd_panel_del(panel->panel) != ESP_OK) { + return -1; + } + panel->panel = NULL; + } + if (panel->io) { + if (esp_lcd_panel_io_del(panel->io) != ESP_OK) { + return -1; + } + panel->io = NULL; + } + if (panel->spi_ready) { + if (spi_bus_free(MYBOT_DISPLAY_SPI_HOST) != ESP_OK) { + return -1; + } + panel->spi_ready = false; + } + panel->ready = false; + panel->x_alignment = 0; + panel->y_alignment = 0; + return 0; +} + +int mybot_display_panel_open(mybot_display_panel_t *panel) { + if (!panel || panel->ready || mybot_display_panel_close(panel) < 0) { + return -1; + } + + const spi_bus_config_t bus_config = { + .sclk_io_num = MYBOT_DISPLAY_PCLK, + .data0_io_num = MYBOT_DISPLAY_DATA0, + .data1_io_num = MYBOT_DISPLAY_DATA1, + .data2_io_num = MYBOT_DISPLAY_DATA2, + .data3_io_num = MYBOT_DISPLAY_DATA3, + .max_transfer_sz = MYBOT_DISPLAY_WIDTH * MYBOT_DISPLAY_TRANSFER_ROWS * sizeof(uint16_t), + .flags = SPICOMMON_BUSFLAG_QUAD, + }; + esp_err_t result = spi_bus_initialize(MYBOT_DISPLAY_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO); + if (result != ESP_OK) { + goto fail; + } + panel->spi_ready = true; + + const esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = MYBOT_DISPLAY_CS, + .dc_gpio_num = GPIO_NUM_NC, + .spi_mode = 0, + .pclk_hz = MYBOT_DISPLAY_PIXEL_CLOCK_HZ, + .trans_queue_depth = 2, + .lcd_cmd_bits = 32, + .lcd_param_bits = 8, + .flags.quad_mode = true, + }; + result = esp_lcd_new_panel_io_spi(MYBOT_DISPLAY_SPI_HOST, &io_config, &panel->io); + if (result != ESP_OK) { + goto fail; + } + + const co5300_vendor_config_t vendor_config = { + .init_cmds = s_vendor_init, + .init_cmds_size = sizeof(s_vendor_init) / sizeof(s_vendor_init[0]), + .flags.use_qspi_interface = true, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = MYBOT_DISPLAY_RESET, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + .vendor_config = (void *)&vendor_config, + }; + result = esp_lcd_new_panel_co5300(panel->io, &panel_config, &panel->panel); + if (result == ESP_OK) { + result = + esp_lcd_panel_set_gap(panel->panel, MYBOT_DISPLAY_OFFSET_X, MYBOT_DISPLAY_OFFSET_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_reset(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_init(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_invert_color(panel->panel, MYBOT_DISPLAY_INVERT_COLOR); + } + if (result == ESP_OK) { + result = esp_lcd_panel_swap_xy(panel->panel, MYBOT_DISPLAY_SWAP_XY); + } + if (result == ESP_OK) { + result = esp_lcd_panel_mirror(panel->panel, MYBOT_DISPLAY_MIRROR_X, MYBOT_DISPLAY_MIRROR_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_disp_on_off(panel->panel, true); + } + if (result == ESP_OK) { + result = esp_lcd_panel_co5300_set_brightness(panel->panel, BRIGHTNESS_PERCENT); + } + if (result != ESP_OK) { + goto fail; + } + + panel->ready = true; + panel->x_alignment = 2; + panel->y_alignment = 2; + return 0; + +fail: + ESP_LOGE(TAG, "event=panel action=initialize result=error code=%s", esp_err_to_name(result)); + (void)mybot_display_panel_close(panel); + return -1; +} diff --git a/components/mybot_platform/src/drivers/display/panels/st77916/st77916_lcd185_panel.c b/components/mybot_platform/src/drivers/display/panels/st77916/st77916_lcd185_panel.c new file mode 100644 index 0000000..bdfb7de --- /dev/null +++ b/components/mybot_platform/src/drivers/display/panels/st77916/st77916_lcd185_panel.c @@ -0,0 +1,345 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "board_config.h" +#include "display/board_backlight.h" +#include "display/display_panel.h" + +#include "driver/spi_master.h" +#include "esp_err.h" +#include "esp_lcd_io_spi.h" +#include "esp_lcd_st77916.h" +#include "esp_log.h" + +#include +#include + +#define TAG "st77916_lcd185" +#define LCD_OPCODE_READ_CMD 0x0B + +static const st77916_lcd_init_cmd_t s_vendor_init[] = { + {0xF0, (uint8_t[]){0x28}, 1, 0}, + {0xF2, (uint8_t[]){0x28}, 1, 0}, + {0x73, (uint8_t[]){0xF0}, 1, 0}, + {0x7C, (uint8_t[]){0xD1}, 1, 0}, + {0x83, (uint8_t[]){0xE0}, 1, 0}, + {0x84, (uint8_t[]){0x61}, 1, 0}, + {0xF2, (uint8_t[]){0x82}, 1, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0xF0, (uint8_t[]){0x01}, 1, 0}, + {0xF1, (uint8_t[]){0x01}, 1, 0}, + {0xB0, (uint8_t[]){0x56}, 1, 0}, + {0xB1, (uint8_t[]){0x4D}, 1, 0}, + {0xB2, (uint8_t[]){0x24}, 1, 0}, + {0xB4, (uint8_t[]){0x87}, 1, 0}, + {0xB5, (uint8_t[]){0x44}, 1, 0}, + {0xB6, (uint8_t[]){0x8B}, 1, 0}, + {0xB7, (uint8_t[]){0x40}, 1, 0}, + {0xB8, (uint8_t[]){0x86}, 1, 0}, + {0xBA, (uint8_t[]){0x00}, 1, 0}, + {0xBB, (uint8_t[]){0x08}, 1, 0}, + {0xBC, (uint8_t[]){0x08}, 1, 0}, + {0xBD, (uint8_t[]){0x00}, 1, 0}, + {0xC0, (uint8_t[]){0x80}, 1, 0}, + {0xC1, (uint8_t[]){0x10}, 1, 0}, + {0xC2, (uint8_t[]){0x37}, 1, 0}, + {0xC3, (uint8_t[]){0x80}, 1, 0}, + {0xC4, (uint8_t[]){0x10}, 1, 0}, + {0xC5, (uint8_t[]){0x37}, 1, 0}, + {0xC6, (uint8_t[]){0xA9}, 1, 0}, + {0xC7, (uint8_t[]){0x41}, 1, 0}, + {0xC8, (uint8_t[]){0x01}, 1, 0}, + {0xC9, (uint8_t[]){0xA9}, 1, 0}, + {0xCA, (uint8_t[]){0x41}, 1, 0}, + {0xCB, (uint8_t[]){0x01}, 1, 0}, + {0xD0, (uint8_t[]){0x91}, 1, 0}, + {0xD1, (uint8_t[]){0x68}, 1, 0}, + {0xD2, (uint8_t[]){0x68}, 1, 0}, + {0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0}, + {0xDD, (uint8_t[]){0x4F}, 1, 0}, + {0xDE, (uint8_t[]){0x4F}, 1, 0}, + {0xF1, (uint8_t[]){0x10}, 1, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0xF0, (uint8_t[]){0x02}, 1, 0}, + {0xE0, + (uint8_t[]){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, + 0x34}, + 14, 0}, + {0xE1, + (uint8_t[]){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, + 0x33}, + 14, 0}, + {0xF0, (uint8_t[]){0x10}, 1, 0}, + {0xF3, (uint8_t[]){0x10}, 1, 0}, + {0xE0, (uint8_t[]){0x07}, 1, 0}, + {0xE1, (uint8_t[]){0x00}, 1, 0}, + {0xE2, (uint8_t[]){0x00}, 1, 0}, + {0xE3, (uint8_t[]){0x00}, 1, 0}, + {0xE4, (uint8_t[]){0xE0}, 1, 0}, + {0xE5, (uint8_t[]){0x06}, 1, 0}, + {0xE6, (uint8_t[]){0x21}, 1, 0}, + {0xE7, (uint8_t[]){0x01}, 1, 0}, + {0xE8, (uint8_t[]){0x05}, 1, 0}, + {0xE9, (uint8_t[]){0x02}, 1, 0}, + {0xEA, (uint8_t[]){0xDA}, 1, 0}, + {0xEB, (uint8_t[]){0x00}, 1, 0}, + {0xEC, (uint8_t[]){0x00}, 1, 0}, + {0xED, (uint8_t[]){0x0F}, 1, 0}, + {0xEE, (uint8_t[]){0x00}, 1, 0}, + {0xEF, (uint8_t[]){0x00}, 1, 0}, + {0xF8, (uint8_t[]){0x00}, 1, 0}, + {0xF9, (uint8_t[]){0x00}, 1, 0}, + {0xFA, (uint8_t[]){0x00}, 1, 0}, + {0xFB, (uint8_t[]){0x00}, 1, 0}, + {0xFC, (uint8_t[]){0x00}, 1, 0}, + {0xFD, (uint8_t[]){0x00}, 1, 0}, + {0xFE, (uint8_t[]){0x00}, 1, 0}, + {0xFF, (uint8_t[]){0x00}, 1, 0}, + {0x60, (uint8_t[]){0x40}, 1, 0}, + {0x61, (uint8_t[]){0x04}, 1, 0}, + {0x62, (uint8_t[]){0x00}, 1, 0}, + {0x63, (uint8_t[]){0x42}, 1, 0}, + {0x64, (uint8_t[]){0xD9}, 1, 0}, + {0x65, (uint8_t[]){0x00}, 1, 0}, + {0x66, (uint8_t[]){0x00}, 1, 0}, + {0x67, (uint8_t[]){0x00}, 1, 0}, + {0x68, (uint8_t[]){0x00}, 1, 0}, + {0x69, (uint8_t[]){0x00}, 1, 0}, + {0x6A, (uint8_t[]){0x00}, 1, 0}, + {0x6B, (uint8_t[]){0x00}, 1, 0}, + {0x70, (uint8_t[]){0x40}, 1, 0}, + {0x71, (uint8_t[]){0x03}, 1, 0}, + {0x72, (uint8_t[]){0x00}, 1, 0}, + {0x73, (uint8_t[]){0x42}, 1, 0}, + {0x74, (uint8_t[]){0xD8}, 1, 0}, + {0x75, (uint8_t[]){0x00}, 1, 0}, + {0x76, (uint8_t[]){0x00}, 1, 0}, + {0x77, (uint8_t[]){0x00}, 1, 0}, + {0x78, (uint8_t[]){0x00}, 1, 0}, + {0x79, (uint8_t[]){0x00}, 1, 0}, + {0x7A, (uint8_t[]){0x00}, 1, 0}, + {0x7B, (uint8_t[]){0x00}, 1, 0}, + {0x80, (uint8_t[]){0x48}, 1, 0}, + {0x81, (uint8_t[]){0x00}, 1, 0}, + {0x82, (uint8_t[]){0x06}, 1, 0}, + {0x83, (uint8_t[]){0x02}, 1, 0}, + {0x84, (uint8_t[]){0xD6}, 1, 0}, + {0x85, (uint8_t[]){0x04}, 1, 0}, + {0x86, (uint8_t[]){0x00}, 1, 0}, + {0x87, (uint8_t[]){0x00}, 1, 0}, + {0x88, (uint8_t[]){0x48}, 1, 0}, + {0x89, (uint8_t[]){0x00}, 1, 0}, + {0x8A, (uint8_t[]){0x08}, 1, 0}, + {0x8B, (uint8_t[]){0x02}, 1, 0}, + {0x8C, (uint8_t[]){0xD8}, 1, 0}, + {0x8D, (uint8_t[]){0x04}, 1, 0}, + {0x8E, (uint8_t[]){0x00}, 1, 0}, + {0x8F, (uint8_t[]){0x00}, 1, 0}, + {0x90, (uint8_t[]){0x48}, 1, 0}, + {0x91, (uint8_t[]){0x00}, 1, 0}, + {0x92, (uint8_t[]){0x0A}, 1, 0}, + {0x93, (uint8_t[]){0x02}, 1, 0}, + {0x94, (uint8_t[]){0xDA}, 1, 0}, + {0x95, (uint8_t[]){0x04}, 1, 0}, + {0x96, (uint8_t[]){0x00}, 1, 0}, + {0x97, (uint8_t[]){0x00}, 1, 0}, + {0x98, (uint8_t[]){0x48}, 1, 0}, + {0x99, (uint8_t[]){0x00}, 1, 0}, + {0x9A, (uint8_t[]){0x0C}, 1, 0}, + {0x9B, (uint8_t[]){0x02}, 1, 0}, + {0x9C, (uint8_t[]){0xDC}, 1, 0}, + {0x9D, (uint8_t[]){0x04}, 1, 0}, + {0x9E, (uint8_t[]){0x00}, 1, 0}, + {0x9F, (uint8_t[]){0x00}, 1, 0}, + {0xA0, (uint8_t[]){0x48}, 1, 0}, + {0xA1, (uint8_t[]){0x00}, 1, 0}, + {0xA2, (uint8_t[]){0x05}, 1, 0}, + {0xA3, (uint8_t[]){0x02}, 1, 0}, + {0xA4, (uint8_t[]){0xD5}, 1, 0}, + {0xA5, (uint8_t[]){0x04}, 1, 0}, + {0xA6, (uint8_t[]){0x00}, 1, 0}, + {0xA7, (uint8_t[]){0x00}, 1, 0}, + {0xA8, (uint8_t[]){0x48}, 1, 0}, + {0xA9, (uint8_t[]){0x00}, 1, 0}, + {0xAA, (uint8_t[]){0x07}, 1, 0}, + {0xAB, (uint8_t[]){0x02}, 1, 0}, + {0xAC, (uint8_t[]){0xD7}, 1, 0}, + {0xAD, (uint8_t[]){0x04}, 1, 0}, + {0xAE, (uint8_t[]){0x00}, 1, 0}, + {0xAF, (uint8_t[]){0x00}, 1, 0}, + {0xB0, (uint8_t[]){0x48}, 1, 0}, + {0xB1, (uint8_t[]){0x00}, 1, 0}, + {0xB2, (uint8_t[]){0x09}, 1, 0}, + {0xB3, (uint8_t[]){0x02}, 1, 0}, + {0xB4, (uint8_t[]){0xD9}, 1, 0}, + {0xB5, (uint8_t[]){0x04}, 1, 0}, + {0xB6, (uint8_t[]){0x00}, 1, 0}, + {0xB7, (uint8_t[]){0x00}, 1, 0}, + {0xB8, (uint8_t[]){0x48}, 1, 0}, + {0xB9, (uint8_t[]){0x00}, 1, 0}, + {0xBA, (uint8_t[]){0x0B}, 1, 0}, + {0xBB, (uint8_t[]){0x02}, 1, 0}, + {0xBC, (uint8_t[]){0xDB}, 1, 0}, + {0xBD, (uint8_t[]){0x04}, 1, 0}, + {0xBE, (uint8_t[]){0x00}, 1, 0}, + {0xBF, (uint8_t[]){0x00}, 1, 0}, + {0xC0, (uint8_t[]){0x10}, 1, 0}, + {0xC1, (uint8_t[]){0x47}, 1, 0}, + {0xC2, (uint8_t[]){0x56}, 1, 0}, + {0xC3, (uint8_t[]){0x65}, 1, 0}, + {0xC4, (uint8_t[]){0x74}, 1, 0}, + {0xC5, (uint8_t[]){0x88}, 1, 0}, + {0xC6, (uint8_t[]){0x99}, 1, 0}, + {0xC7, (uint8_t[]){0x01}, 1, 0}, + {0xC8, (uint8_t[]){0xBB}, 1, 0}, + {0xC9, (uint8_t[]){0xAA}, 1, 0}, + {0xD0, (uint8_t[]){0x10}, 1, 0}, + {0xD1, (uint8_t[]){0x47}, 1, 0}, + {0xD2, (uint8_t[]){0x56}, 1, 0}, + {0xD3, (uint8_t[]){0x65}, 1, 0}, + {0xD4, (uint8_t[]){0x74}, 1, 0}, + {0xD5, (uint8_t[]){0x88}, 1, 0}, + {0xD6, (uint8_t[]){0x99}, 1, 0}, + {0xD7, (uint8_t[]){0x01}, 1, 0}, + {0xD8, (uint8_t[]){0xBB}, 1, 0}, + {0xD9, (uint8_t[]){0xAA}, 1, 0}, + {0xF3, (uint8_t[]){0x01}, 1, 0}, + {0xF0, (uint8_t[]){0x00}, 1, 0}, + {0x21, NULL, 0, 0}, + {0x11, NULL, 0, 120}, + {0x29, NULL, 0, 0}, +}; + +static esp_err_t create_panel_io(uint32_t clock_hz, esp_lcd_panel_io_handle_t *io) { + const esp_lcd_panel_io_spi_config_t config = { + .cs_gpio_num = MYBOT_DISPLAY_CS, + .dc_gpio_num = GPIO_NUM_NC, + .spi_mode = 0, + .pclk_hz = clock_hz, + .trans_queue_depth = 2, + .lcd_cmd_bits = 32, + .lcd_param_bits = 8, + .flags.quad_mode = true, + }; + return esp_lcd_new_panel_io_spi(MYBOT_DISPLAY_SPI_HOST, &config, io); +} + +int mybot_display_panel_close(mybot_display_panel_t *panel) { + if (!panel) { + return -1; + } + if (panel->backlight_ready) { + if (mybot_board_set_display_backlight(0) < 0) { + return -1; + } + panel->backlight_ready = false; + } + if (panel->panel) { + if (panel->ready && esp_lcd_panel_disp_on_off(panel->panel, false) != ESP_OK) { + ESP_LOGW(TAG, "event=panel action=display_off result=error"); + } + if (esp_lcd_panel_del(panel->panel) != ESP_OK) { + return -1; + } + panel->panel = NULL; + } + if (panel->io) { + if (esp_lcd_panel_io_del(panel->io) != ESP_OK) { + return -1; + } + panel->io = NULL; + } + if (panel->spi_ready) { + if (spi_bus_free(MYBOT_DISPLAY_SPI_HOST) != ESP_OK) { + return -1; + } + panel->spi_ready = false; + } + panel->ready = false; + return 0; +} + +int mybot_display_panel_open(mybot_display_panel_t *panel) { + if (!panel || panel->ready || mybot_display_panel_close(panel) < 0 || + mybot_board_set_display_backlight(0) < 0) { + return -1; + } + panel->backlight_ready = true; + + const spi_bus_config_t bus_config = { + .sclk_io_num = MYBOT_DISPLAY_PCLK, + .data0_io_num = MYBOT_DISPLAY_DATA0, + .data1_io_num = MYBOT_DISPLAY_DATA1, + .data2_io_num = MYBOT_DISPLAY_DATA2, + .data3_io_num = MYBOT_DISPLAY_DATA3, + .max_transfer_sz = MYBOT_DISPLAY_WIDTH * MYBOT_DISPLAY_TRANSFER_ROWS * sizeof(uint16_t), + .flags = SPICOMMON_BUSFLAG_QUAD, + }; + esp_err_t result = spi_bus_initialize(MYBOT_DISPLAY_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO); + if (result != ESP_OK) { + goto fail; + } + panel->spi_ready = true; + + esp_lcd_panel_io_handle_t probe_io = NULL; + uint8_t panel_version[4] = {0}; + result = create_panel_io(3 * 1000 * 1000, &probe_io); + if (result == ESP_OK) { + uint32_t command = ((uint32_t)LCD_OPCODE_READ_CMD << 24) | 0x04; + result = esp_lcd_panel_io_rx_param(probe_io, command, panel_version, sizeof(panel_version)); + (void)esp_lcd_panel_io_del(probe_io); + } + if (result != ESP_OK) { + ESP_LOGW(TAG, "event=panel action=read_version result=error code=%s", + esp_err_to_name(result)); + result = ESP_OK; + } + + result = create_panel_io(MYBOT_DISPLAY_PIXEL_CLOCK_HZ, &panel->io); + if (result != ESP_OK) { + goto fail; + } + st77916_vendor_config_t vendor_config = { + .flags.use_qspi_interface = true, + }; + if (panel_version[0] == 0x00 && panel_version[1] == 0x02 && panel_version[2] == 0x7F && + panel_version[3] == 0x7F) { + vendor_config.init_cmds = s_vendor_init; + vendor_config.init_cmds_size = sizeof(s_vendor_init) / sizeof(s_vendor_init[0]); + } + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = MYBOT_DISPLAY_RESET, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + .vendor_config = &vendor_config, + }; + result = esp_lcd_new_panel_st77916(panel->io, &panel_config, &panel->panel); + if (result == ESP_OK) { + result = esp_lcd_panel_reset(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_init(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_swap_xy(panel->panel, MYBOT_DISPLAY_SWAP_XY); + } + if (result == ESP_OK) { + result = esp_lcd_panel_mirror(panel->panel, MYBOT_DISPLAY_MIRROR_X, MYBOT_DISPLAY_MIRROR_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_disp_on_off(panel->panel, true); + } + if (result == ESP_OK) { + result = mybot_board_set_display_backlight(MYBOT_DISPLAY_BRIGHTNESS_PERCENT) == 0 + ? ESP_OK + : ESP_FAIL; + } + if (result != ESP_OK) { + goto fail; + } + panel->ready = true; + return 0; + +fail: + ESP_LOGE(TAG, "event=panel action=initialize result=error code=%s", esp_err_to_name(result)); + (void)mybot_display_panel_close(panel); + return -1; +} diff --git a/components/mybot_platform/src/drivers/input/cst816s_boot_input.c b/components/mybot_platform/src/drivers/input/cst816s_boot_input.c new file mode 100644 index 0000000..203718f --- /dev/null +++ b/components/mybot_platform/src/drivers/input/cst816s_boot_input.c @@ -0,0 +1,372 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "board_config.h" + +#include +#include + +#include "button_gpio.h" +#include "esp_lcd_touch.h" +#include "esp_lcd_touch_cst816s.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "iot_button.h" +#include "platform/board_actions.h" + +#include + +#define TAG "cst816s_input" + +typedef struct { + portMUX_TYPE lock; + bool active; + bool started; + bool task_stop_requested; + unsigned callbacks_in_flight; + mybot_key_event_handler_t emit; + void *user_data; + button_handle_t boot; + esp_lcd_touch_handle_t touch; + esp_lcd_panel_io_handle_t touch_io; + TaskHandle_t touch_task; +} input_context_t; + +static input_context_t s_context = { + .lock = portMUX_INITIALIZER_UNLOCKED, +}; +static StaticSemaphore_t s_task_stopped_storage; +static SemaphoreHandle_t s_task_stopped; + +i2c_master_bus_handle_t mybot_board_touch_i2c_bus_handle(void); + +static bool adapter_is_active(void) { + portENTER_CRITICAL(&s_context.lock); + const bool active = s_context.active; + portEXIT_CRITICAL(&s_context.lock); + return active; +} + +static bool task_should_stop(void) { + portENTER_CRITICAL(&s_context.lock); + const bool stop = s_context.task_stop_requested; + portEXIT_CRITICAL(&s_context.lock); + return stop; +} + +static void emit_event(mybot_key_event_t event) { + mybot_key_event_handler_t emit = NULL; + void *user_data = NULL; + + portENTER_CRITICAL(&s_context.lock); + if (s_context.active && s_context.emit) { + ++s_context.callbacks_in_flight; + emit = s_context.emit; + user_data = s_context.user_data; + } + portEXIT_CRITICAL(&s_context.lock); + + if (!emit) { + return; + } + emit(event, user_data); + + portENTER_CRITICAL(&s_context.lock); + --s_context.callbacks_in_flight; + portEXIT_CRITICAL(&s_context.lock); +} + +static void handle_short_press(void) { + if (!adapter_is_active()) { + return; + } + const mybot_state_t state = mybot_get_state(); + if (state == MYBOT_STATE_READY) { + emit_event(MYBOT_KEY_EVENT_CONVERSATION_START); + } else if (state == MYBOT_STATE_IN_CONVERSATION) { + emit_event(MYBOT_KEY_EVENT_CONVERSATION_STOP); + } +} + +static void handle_long_press(const char *source) { + (void)source; + ESP_LOGI(TAG, "event=input source=%s action=wifi_provisioning_request", source); + if (mybot_board_handle_boot_long_press() < 0) { + ESP_LOGE(TAG, "event=provision_request source=%s result=error", source); + } +} + +static void boot_click(void *button, void *user_data) { + (void)button; + (void)user_data; + handle_short_press(); +} + +static void boot_long_press(void *button, void *user_data) { + (void)button; + (void)user_data; + handle_long_press("boot_button"); +} + +static void touch_task(void *argument) { + (void)argument; + bool pressed = false; + bool long_fired = false; + TickType_t pressed_at = 0; + + while (!task_should_stop()) { + esp_lcd_touch_point_data_t point = {0}; + uint8_t count = 0; + const bool valid = esp_lcd_touch_read_data(s_context.touch) == ESP_OK && + esp_lcd_touch_get_data(s_context.touch, &point, &count, 1) == ESP_OK; + const bool down = + valid && count > 0 && point.x <= MYBOT_DISPLAY_WIDTH && point.y <= MYBOT_DISPLAY_HEIGHT; + const TickType_t now = xTaskGetTickCount(); + + if (down && !pressed) { + pressed = true; + long_fired = false; + pressed_at = now; + } else if (down && pressed && !long_fired && + now - pressed_at >= pdMS_TO_TICKS(MYBOT_TOUCH_LONG_PRESS_MS)) { + long_fired = true; + handle_long_press("touch"); + } else if (!down && pressed) { + pressed = false; + const TickType_t duration = now - pressed_at; + if (!long_fired && duration >= pdMS_TO_TICKS(MYBOT_TOUCH_SHORT_PRESS_MIN_MS)) { + handle_short_press(); + } + } + vTaskDelay(pdMS_TO_TICKS(MYBOT_TOUCH_POLL_INTERVAL_MS)); + } + + TaskHandle_t current = xTaskGetCurrentTaskHandle(); + portENTER_CRITICAL(&s_context.lock); + if (s_context.touch_task == current) { + s_context.touch_task = NULL; + } + portEXIT_CRITICAL(&s_context.lock); + xSemaphoreGive(s_task_stopped); + vTaskDelete(NULL); +} + +static int create_touch(void) { + i2c_master_bus_handle_t bus = mybot_board_touch_i2c_bus_handle(); + if (!bus) { + return -1; + } + + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = ESP_LCD_TOUCH_IO_I2C_CST816S_ADDRESS, + .scl_speed_hz = MYBOT_TOUCH_I2C_SPEED_HZ, + .control_phase_bytes = 1, + .dc_bit_offset = 0, + .lcd_cmd_bits = 8, + .lcd_param_bits = 0, + .flags.disable_control_phase = 1, + }; + esp_err_t result = esp_lcd_new_panel_io_i2c(bus, &io_config, &s_context.touch_io); + if (result != ESP_OK) { + return -1; + } + + const esp_lcd_touch_config_t touch_config = { + .x_max = MYBOT_DISPLAY_WIDTH - 1, + .y_max = MYBOT_DISPLAY_HEIGHT - 1, + .rst_gpio_num = MYBOT_TOUCH_RESET, + .int_gpio_num = MYBOT_TOUCH_INTERRUPT, + .levels = + { + .reset = MYBOT_TOUCH_RESET_LEVEL, + .interrupt = MYBOT_TOUCH_INTERRUPT_LEVEL, + }, + .flags = + { + .swap_xy = MYBOT_TOUCH_SWAP_XY, + .mirror_x = MYBOT_TOUCH_MIRROR_X, + .mirror_y = MYBOT_TOUCH_MIRROR_Y, + }, + }; + result = esp_lcd_touch_new_i2c_cst816s(s_context.touch_io, &touch_config, &s_context.touch); + if (result != ESP_OK) { + (void)esp_lcd_panel_io_del(s_context.touch_io); + s_context.touch_io = NULL; + return -1; + } + return 0; +} + +static int release_input_devices(void) { + int result = 0; + if (s_context.boot && iot_button_delete(s_context.boot) == ESP_OK) { + s_context.boot = NULL; + } else if (s_context.boot) { + result = -1; + } + if (s_context.touch && esp_lcd_touch_del(s_context.touch) == ESP_OK) { + s_context.touch = NULL; + } else if (s_context.touch) { + result = -1; + } + if (!s_context.touch && s_context.touch_io && + esp_lcd_panel_io_del(s_context.touch_io) == ESP_OK) { + s_context.touch_io = NULL; + } else if (!s_context.touch && s_context.touch_io) { + result = -1; + } + return result; +} + +static int release_input(void) { + portENTER_CRITICAL(&s_context.lock); + s_context.active = false; + s_context.task_stop_requested = true; + TaskHandle_t task = s_context.touch_task; + portEXIT_CRITICAL(&s_context.lock); + + if (task && (!s_task_stopped || xSemaphoreTake(s_task_stopped, portMAX_DELAY) != pdTRUE)) { + ESP_LOGE(TAG, "event=input action=deinitialize result=error reason=task_join"); + return -1; + } + + unsigned in_flight = 0; + do { + portENTER_CRITICAL(&s_context.lock); + in_flight = s_context.callbacks_in_flight; + portEXIT_CRITICAL(&s_context.lock); + if (in_flight) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + } while (in_flight); + + const int result = release_input_devices(); + portENTER_CRITICAL(&s_context.lock); + s_context.emit = NULL; + s_context.user_data = NULL; + s_context.started = + s_context.boot || s_context.touch || s_context.touch_io || s_context.touch_task; + if (!s_context.started) { + s_context.task_stop_requested = false; + } + portEXIT_CRITICAL(&s_context.lock); + return result; +} + +int mybot_cst816s_input_start(void) { + portENTER_CRITICAL(&s_context.lock); + const bool already_started = s_context.started || s_context.boot || s_context.touch || + s_context.touch_io || s_context.touch_task; + portEXIT_CRITICAL(&s_context.lock); + if (already_started) { + return -1; + } + + if (!s_task_stopped) { + s_task_stopped = xSemaphoreCreateBinaryStatic(&s_task_stopped_storage); + } + if (!s_task_stopped) { + return -1; + } + (void)xSemaphoreTake(s_task_stopped, 0); + + const button_config_t button_config = { + .long_press_time = MYBOT_BOOT_BUTTON_LONG_PRESS_MS, + .short_press_time = 50, + }; + const button_gpio_config_t gpio_config = { + .gpio_num = MYBOT_BOOT_BUTTON_GPIO, + .active_level = 0, + .enable_power_save = false, + .disable_pull = false, + }; + esp_err_t result = iot_button_new_gpio_device(&button_config, &gpio_config, &s_context.boot); + if (result == ESP_OK) { + result = + iot_button_register_cb(s_context.boot, BUTTON_SINGLE_CLICK, NULL, boot_click, NULL); + } + if (result == ESP_OK) { + result = iot_button_register_cb(s_context.boot, BUTTON_LONG_PRESS_START, NULL, + boot_long_press, NULL); + } + if (result == ESP_OK && create_touch() < 0) { + result = ESP_FAIL; + } + if (result == ESP_OK) { + portENTER_CRITICAL(&s_context.lock); + s_context.task_stop_requested = false; + portEXIT_CRITICAL(&s_context.lock); + if (xTaskCreate(touch_task, "mybot_touch", 4096, NULL, 5, &s_context.touch_task) != + pdPASS) { + result = ESP_ERR_NO_MEM; + } + } + if (result != ESP_OK) { + portENTER_CRITICAL(&s_context.lock); + s_context.task_stop_requested = true; + portEXIT_CRITICAL(&s_context.lock); + if (release_input() < 0) { + ESP_LOGE(TAG, "event=input action=initialize cleanup=error"); + } + return -1; + } + + portENTER_CRITICAL(&s_context.lock); + s_context.started = true; + portEXIT_CRITICAL(&s_context.lock); + return 0; +} + +int mybot_cst816s_input_stop(void) { + return release_input(); +} + +static int input_init(void **out_context, mybot_key_event_handler_t emit, void *user_data) { + if (!out_context || !emit) { + return -1; + } + *out_context = NULL; + + portENTER_CRITICAL(&s_context.lock); + if (s_context.active || !s_context.started) { + portEXIT_CRITICAL(&s_context.lock); + return -1; + } + s_context.emit = emit; + s_context.user_data = user_data; + s_context.active = true; + portEXIT_CRITICAL(&s_context.lock); + *out_context = &s_context; + return 0; +} + +static void input_destroy(void *opaque) { + if (opaque != &s_context) { + return; + } + + portENTER_CRITICAL(&s_context.lock); + s_context.active = false; + unsigned in_flight = s_context.callbacks_in_flight; + portEXIT_CRITICAL(&s_context.lock); + while (in_flight) { + vTaskDelay(pdMS_TO_TICKS(1)); + portENTER_CRITICAL(&s_context.lock); + in_flight = s_context.callbacks_in_flight; + portEXIT_CRITICAL(&s_context.lock); + } + + portENTER_CRITICAL(&s_context.lock); + s_context.emit = NULL; + s_context.user_data = NULL; + portEXIT_CRITICAL(&s_context.lock); +} + +static const mybot_key_ops_t s_ops = { + .init = input_init, + .destroy = input_destroy, +}; + +const mybot_key_ops_t *mybot_cst816s_input_ops(void) { + return &s_ops; +} diff --git a/components/mybot_platform/src/drivers/io_expander/tca9554.c b/components/mybot_platform/src/drivers/io_expander/tca9554.c new file mode 100644 index 0000000..4593666 --- /dev/null +++ b/components/mybot_platform/src/drivers/io_expander/tca9554.c @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "io_expander/tca9554.h" + +#include "esp_err.h" +#include "esp_log.h" + +#define TAG "tca9554" +#define TCA9554_I2C_TIMEOUT_MS 100 +#define TCA9554_REG_OUTPUT 0x01 +#define TCA9554_REG_CONFIG 0x03 + +static int write_register(mybot_tca9554_t *expander, uint8_t reg, uint8_t value) { + const uint8_t command[] = {reg, value}; + esp_err_t result = + i2c_master_transmit(expander->device, command, sizeof(command), TCA9554_I2C_TIMEOUT_MS); + if (result != ESP_OK) { + ESP_LOGE(TAG, "event=io_expander action=write register=0x%02x result=error code=%s", reg, + esp_err_to_name(result)); + return -1; + } + return 0; +} + +int mybot_tca9554_init(mybot_tca9554_t *expander, i2c_master_bus_handle_t bus, uint8_t address, + uint32_t speed_hz) { + if (!expander || !bus || expander->device) { + return -1; + } + *expander = (mybot_tca9554_t){.bus = bus}; + const i2c_device_config_t config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = address, + .scl_speed_hz = speed_hz, + }; + esp_err_t result = i2c_master_bus_add_device(bus, &config, &expander->device); + if (result != ESP_OK) { + expander->device = NULL; + ESP_LOGE(TAG, "event=io_expander action=initialize address=0x%02x result=error code=%s", + address, esp_err_to_name(result)); + return -1; + } + return 0; +} + +int mybot_tca9554_deinit(mybot_tca9554_t *expander) { + if (!expander) { + return -1; + } + int result = 0; + if (expander->device && i2c_master_bus_rm_device(expander->device) != ESP_OK) { + result = -1; + } + if (result == 0) { + expander->device = NULL; + expander->bus = NULL; + expander->output_state = 0; + } + return result; +} + +int mybot_tca9554_configure_outputs(mybot_tca9554_t *expander, uint8_t output_mask, + uint8_t high_mask) { + if (!expander || !expander->device) { + return -1; + } + const uint8_t output_state = high_mask & output_mask; + if (write_register(expander, TCA9554_REG_OUTPUT, output_state) < 0 || + write_register(expander, TCA9554_REG_CONFIG, (uint8_t)~output_mask) < 0) { + return -1; + } + expander->output_state = output_state; + return 0; +} + +int mybot_tca9554_set_levels(mybot_tca9554_t *expander, uint8_t mask, uint8_t high_mask) { + if (!expander || !expander->device) { + return -1; + } + const uint8_t output_state = (uint8_t)((expander->output_state & ~mask) | (high_mask & mask)); + if (write_register(expander, TCA9554_REG_OUTPUT, output_state) < 0) { + return -1; + } + expander->output_state = output_state; + return 0; +} diff --git a/components/mybot_platform/src/internal/io_expander/tca9554.h b/components/mybot_platform/src/internal/io_expander/tca9554.h new file mode 100644 index 0000000..c487437 --- /dev/null +++ b/components/mybot_platform/src/internal/io_expander/tca9554.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_TCA9554_H_ +#define MYBOT_TCA9554_H_ + +#include "driver/i2c_master.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + i2c_master_bus_handle_t bus; + i2c_master_dev_handle_t device; + uint8_t output_state; +} mybot_tca9554_t; + +int mybot_tca9554_init(mybot_tca9554_t *expander, i2c_master_bus_handle_t bus, uint8_t address, + uint32_t speed_hz); +int mybot_tca9554_deinit(mybot_tca9554_t *expander); +int mybot_tca9554_configure_outputs(mybot_tca9554_t *expander, uint8_t output_mask, + uint8_t high_mask); +int mybot_tca9554_set_levels(mybot_tca9554_t *expander, uint8_t mask, uint8_t high_mask); + +#ifdef __cplusplus +} +#endif + +#endif /* MYBOT_TCA9554_H_ */ diff --git a/docs/AUDIO_PLAYBACK.md b/docs/AUDIO_PLAYBACK.md index 28dac40..7d74b3c 100644 --- a/docs/AUDIO_PLAYBACK.md +++ b/docs/AUDIO_PLAYBACK.md @@ -2,7 +2,7 @@ [English](AUDIO_PLAYBACK.md) | [简体中文](AUDIO_PLAYBACK.zh-CN.md) -All thirteen board profiles use one PCM playback buffer through seven board audio drivers. This +All sixteen board profiles use one PCM playback buffer through seven board audio drivers. This keeps the SDK boundary at 16 kHz, mono signed-16 PCM and moves continuous I2S feeding into an independent task. The shared implementation is [`pcm_playback_buffer.c`](../components/mybot_platform/src/services/audio/pcm_playback_buffer.c); @@ -50,6 +50,9 @@ the number or width of I2S slots. Native conversion occurs only in the worker's | `esp32-s3-touch-amoled-1.75` | `amoled175_codec_audio.c` | 16-bit stereo; duplicate mono into both slots | | `esp32-s3-touch-amoled-1.75c` | `amoled175_codec_audio.c` | 16-bit stereo; duplicate mono into both slots | | `esp32-s3-touch-amoled-2.16` | `amoled175_codec_audio.c` | 16-bit stereo; duplicate mono into both slots | +| `esp32-s3-touch-amoled-1.8-v2` | `es8311_codec_audio.c` | 16-bit stereo; duplicate mono into both slots | +| `esp32-s3-touch-lcd-1.85` | `raw_i2s_audio.c` | 32-bit right-slot capture; duplicate mono into both playback slots | +| `genjutech-s3-1.54tft` | `es8311_codec_audio.c` | 16-bit stereo; duplicate mono into both slots | | `respeaker-flex-xvf3800-circular4-xiao` | `xvf3800_audio.c` | 32-bit stereo; existing volume scaling and slot alignment | Writes retry unwritten frames after a short write or timeout, preserving order. Invalid byte @@ -64,9 +67,9 @@ If that wait fails, resources remain allocated for cleanup retry. Restart resets prebuffer state, so audio from a previous conversation cannot remain in the software queue. Codec-based boards mute or close the output before allowing old DMA data to clear. TX clocks -remain active when capture still needs them. Zhengchen, Xingzhi Cube, Bread, and XVF3800 disable the -playback channel and preload silence into DMA before reusing it. The board-specific power, clock, and volume -behavior remains in each driver. +remain active when capture still needs them. Raw-I2S boards (Zhengchen, Xingzhi Cube, Bread, and +LCD 1.85) and XVF3800 disable the playback channel and preload silence into DMA before reusing it. +The board-specific power, clock, and volume behavior remains in each driver. Wi-Fi provisioning prompts and the offline test use a separate drain operation before stopping, allowing the FIFO and hardware tail to finish. Provisioning prompts feed this same FIFO in diff --git a/docs/AUDIO_PLAYBACK.zh-CN.md b/docs/AUDIO_PLAYBACK.zh-CN.md index d9ab567..35eab19 100644 --- a/docs/AUDIO_PLAYBACK.zh-CN.md +++ b/docs/AUDIO_PLAYBACK.zh-CN.md @@ -2,7 +2,7 @@ [English](AUDIO_PLAYBACK.md) | [简体中文](AUDIO_PLAYBACK.zh-CN.md) -当前 13 个 Board profile 通过 7 个板级音频驱动共用同一套 PCM 播放缓冲。SDK 边界保持 +当前 16 个 Board profile 通过 7 个板级音频驱动共用同一套 PCM 播放缓冲。SDK 边界保持 16 kHz、单声道 signed-16 PCM,独立任务持续向 I2S 供数。公共实现位于 [`pcm_playback_buffer.c`](../components/mybot_platform/src/services/audio/pcm_playback_buffer.c), 各板级驱动提供原生 I2S sink。 @@ -46,6 +46,9 @@ SDK 和缓冲接口中的帧数始终表示**单声道 PCM 帧数**,不随 I2S | `esp32-s3-touch-amoled-1.75` | `amoled175_codec_audio.c` | 16 位双声道,将单声道复制到两个槽 | | `esp32-s3-touch-amoled-1.75c` | `amoled175_codec_audio.c` | 16 位双声道,将单声道复制到两个槽 | | `esp32-s3-touch-amoled-2.16` | `amoled175_codec_audio.c` | 16 位双声道,将单声道复制到两个槽 | +| `esp32-s3-touch-amoled-1.8-v2` | `es8311_codec_audio.c` | 16 位双声道,将单声道复制到两个槽 | +| `esp32-s3-touch-lcd-1.85` | `raw_i2s_audio.c` | 32 位右 slot 采集;播放将单声道复制到两个 slot | +| `genjutech-s3-1.54tft` | `es8311_codec_audio.c` | 16 位双声道,将单声道复制到两个槽 | | `respeaker-flex-xvf3800-circular4-xiao` | `xvf3800_audio.c` | 32 位双声道,保留音量缩放及槽内对齐 | 短写或超时后继续重试剩余帧,保持 PCM 顺序。返回非法字节数、非超时驱动错误,或连续 @@ -59,8 +62,8 @@ SDK 和缓冲接口中的帧数始终表示**单声道 PCM 帧数**,不随 I2S 对话音频残留在软件队列中。 使用 codec 的板型先静音或关闭输出,再等待旧 DMA 数据清除;如果采集仍需要 TX 时钟, -则保留该时钟。征辰、行者 Cube、Bread 和 XVF3800 停用播放通道并向 DMA 预装静音后再复用。电源、时钟、 -音量行为仍由板级驱动维护。 +则保留该时钟。原始 I2S 板型(征辰、行者 Cube、Bread 与 LCD 1.85)和 XVF3800 停用播放 +通道并向 DMA 预装静音后再复用。电源、时钟、音量行为仍由板级驱动维护。 配网提示音及离线测试停止前使用单独的排空操作,等待 FIFO 和硬件尾音完成。 配网提示音以 320 帧为一块写入同一 FIFO,不经过 SDK 的 960 帧供数节奏。最后一次 diff --git a/docs/BOARD_PORTING.md b/docs/BOARD_PORTING.md index 2c600e4..699d13a 100644 --- a/docs/BOARD_PORTING.md +++ b/docs/BOARD_PORTING.md @@ -75,6 +75,8 @@ and Octal PSRAM. its physical Flash/PSRAM capacities require confirmation on the release device. `esp32-s3-touch-amoled-2.16` also uses a conservative 16 MB QIO Flash profile and 80 MHz Octal PSRAM; confirm its physical capacities before release. +The GenjuTech 1.54, AMOLED 1.8 V2 and LCD 1.85 profiles use the same 16 MB QIO Flash and 80 MHz +Octal PSRAM profile; confirm each device before release. `bread-compact-wifi-lcd` is a fixed ESP32-S3 N16R8 profile with 16 MB QIO Flash and 8 MB Octal PSRAM at 80 MHz; validate the module and detected capacities on the wired device. @@ -251,6 +253,22 @@ I2S link on real hardware. Keep any required resampling in the platform driver a Camera, RTC, IMU, TF card, battery reporting, shutdown, and low-power behavior remain unsupported. Build and device status for 2.16 is tracked in the [validation matrix](VALIDATION.md). +The `genjutech-s3-1.54tft` profile reuses the ES8311, raw button, ST7789, and shared LVGL paths. +Its PA pin is coupled to display I/O by the board design, so the platform keeps the PA enabled +after its first playback start and only disables it during board teardown. Touch, LED, battery, +charging, sleep, shutdown, and video are not implemented. + +The `esp32-s3-touch-amoled-1.8-v2` profile owns a 368 x 448 CO5300 variant with a 16-pixel X gap. +TCA9554 resets the panel before AXP2101 rail configuration; CST816S and Boot share the process-lifetime +input path. The SDK and ES8311 I2S path remain at 16 kHz mono signed-16 PCM. AXP2101 power-key +actions, battery reporting, sleep, shutdown, and video are not implemented. + +The `esp32-s3-touch-lcd-1.85` profile uses separate control and touch I2C buses plus separate +microphone and speaker I2S peripherals. The microphone is read from the right slot and mono playback +is copied into both TX slots while the SDK boundary remains mono 16 kHz signed-16 PCM. TCA9554 +releases LCD/touch reset, GPIO7 holds power, and GPIO5 controls backlight. The GPIO6 power button, +battery reporting, sleep, shutdown, and video are not implemented. + The ReSpeaker Flex profile is an example of a hardware audio front end rather than a raw microphone codec. Its XVF3800 must run separately flashed Circular-4 16 kHz, two-channel I2S firmware and owns AEC, beamforming, AGC, and noise suppression. The ESP32-S3 uses I2S slave mode and converts the diff --git a/docs/BOARD_PORTING.zh-CN.md b/docs/BOARD_PORTING.zh-CN.md index 228d21b..11f3ab4 100644 --- a/docs/BOARD_PORTING.zh-CN.md +++ b/docs/BOARD_PORTING.zh-CN.md @@ -74,6 +74,8 @@ Octal PSRAM。 实际 Flash/PSRAM 容量必须在发布设备上核对。 `esp32-s3-touch-amoled-2.16` 同样采用保守的 16 MB QIO Flash profile 与 80 MHz Octal PSRAM; 发布前确认实际容量。 +GenjuTech 1.54、AMOLED 1.8 V2 与 LCD 1.85 profile 同样使用 16 MB QIO Flash 与 80 MHz +Octal PSRAM;发布前应逐台确认实际容量。 `bread-compact-wifi-lcd` 固定为 ESP32-S3 N16R8 profile,16 MB QIO Flash、80 MHz 8 MB Octal PSRAM;需在实际接线设备验证模组和检测到的容量。 @@ -223,6 +225,20 @@ mybot 停止期间任一输入仍能请求配网。真机状态见[验证矩阵] 应留在平台驱动内,并保持 Cloud AEC。摄像头、RTC、IMU、TF 卡、电量、关机与低功耗尚未支持。 2.16 的构建与真机状态见[验证矩阵](VALIDATION.md)。 +`genjutech-s3-1.54tft` 复用 ES8311、raw 按键、ST7789 与共享 LVGL 路径。该板功放引脚与显示 +I/O 存在设计耦合,因此首次播放开启 PA 后保持有效,仅在板级拆除时关闭;停止播放只让 I2S +流空闲。触摸、LED、电量/充电、休眠、关机与视频尚未实现。 + +`esp32-s3-touch-amoled-1.8-v2` 使用独立的 368 x 448 CO5300 变体和 16 像素 X 偏移。 +TCA9554 先复位面板,AXP2101 再配置电源轨;CST816S 与 Boot 共用进程级输入路径。SDK 与 +ES8311 I2S 均保持 16 kHz 单声道 signed-16 PCM。AXP2101 电源键行为、电量、休眠、关机与 +视频尚未实现。 + +`esp32-s3-touch-lcd-1.85` 使用独立的控制 I2C、触摸 I2C、麦克风 I2S 与扬声器 I2S。麦克风读取 +右 slot,播放将单声道复制到两个 TX slot,SDK 边界仍为单声道 16 kHz signed-16 PCM。 +TCA9554 释放 LCD/触摸复位,GPIO7 保持电源,GPIO5 控制背光。GPIO6 电源键、电量、休眠、 +关机与视频尚未实现。 + ReSpeaker Flex profile 是硬件音频前端而非原始麦克风 codec 的示例。XVF3800 必须预先单独 烧录 Circular-4 16 kHz 双通道 I2S 固件,并负责 AEC、波束成形、AGC 与降噪。ESP32-S3 工作在 I2S 从机模式,将选定的 32-bit 采集 slot 转换到 SDK 的单声道 signed-16 边界。该 diff --git a/docs/CORES3_UI.md b/docs/CORES3_UI.md index 8bafe01..e211f6f 100644 --- a/docs/CORES3_UI.md +++ b/docs/CORES3_UI.md @@ -4,7 +4,7 @@ `MYBOT_BOARD=m5stack-core-s3` automatically selects the process-gray pixel-glasses view on the 320 × 240 ILI9342 display. It uses the existing CoreS3 LVGL adapter and common LCD-view API. -There is no manual renderer switch or `CONFIG_MYBOT_LVGL_UI` override. The other twelve board +There is no manual renderer switch or `CONFIG_MYBOT_LVGL_UI` override. The other fifteen board profiles retain their existing UI and hardware behavior; see [Platform UI](PLATFORM_UI.md). This replaces CoreS3's earlier status-card view. Audio, camera/video policy, FT6336 touch gestures, diff --git a/docs/CORES3_UI.zh-CN.md b/docs/CORES3_UI.zh-CN.md index 4849da6..4d640d9 100644 --- a/docs/CORES3_UI.zh-CN.md +++ b/docs/CORES3_UI.zh-CN.md @@ -4,7 +4,7 @@ `MYBOT_BOARD=m5stack-core-s3` 自动为320 × 240 ILI9342屏选择工艺灰像素眼镜界面,复用原 CoreS3 LVGL适配器及公共LCD视图接口。不增加手工渲染器开关,无需覆盖隐藏的 -`CONFIG_MYBOT_LVGL_UI`。其他十二个Board profile保留原UI与硬件行为,见 +`CONFIG_MYBOT_LVGL_UI`。其他十五个Board profile保留原UI与硬件行为,见 [平台UI](PLATFORM_UI.zh-CN.md)。 本视图替换CoreS3旧状态卡片。音频、摄像头/视频策略、FT6336触摸手势、电源、引脚、PSRAM diff --git a/docs/PLATFORM_UI.md b/docs/PLATFORM_UI.md index 30f5cef..45c36b5 100644 --- a/docs/PLATFORM_UI.md +++ b/docs/PLATFORM_UI.md @@ -22,6 +22,9 @@ retain their existing board behavior. | `esp32-s3-touch-amoled-1.75` | CO5300, 466 × 466 | shared panel adapter | existing touch/key path | | `esp32-s3-touch-amoled-1.75c` | CO5300, 466 × 466 | shared panel adapter | existing touch/key path | | `esp32-s3-touch-amoled-2.16` | CO5300 480 variant, 480 × 480 | shared panel adapter | CST9217 and Boot | +| `esp32-s3-touch-amoled-1.8-v2` | CO5300 368×448 variant, 368 × 448 | shared panel adapter | CST816S and Boot | +| `esp32-s3-touch-lcd-1.85` | ST77916, 360 × 360 | shared panel adapter | CST816S and Boot | +| `genjutech-s3-1.54tft` | ST7789, 240 × 240 | shared ST7789 adapter | Boot and volume buttons | | `sensecap-watcher` | SPD2010, 412 × 412 | shared panel adapter | existing encoder path | The AtomEchoS3R and ReSpeaker Flex profiles have no LCD and do not include LVGL. The ML307 and @@ -33,6 +36,9 @@ and GPIO42 LEDC backlight at 5 kHz, 13-bit resolution, and 60% duty. It has one uses the shared view at its portrait geometry. AMOLED 2.16 uses a zero panel gap and 40 MHz QSPI. Its CO5300 transfer regions retain even pixel boundaries on both axes. +AMOLED 1.8 V2 uses a 16-pixel X gap and 40 MHz QSPI with the same even-pixel CO5300 transfer +constraint. LCD 1.85 uses GPIO5 LEDC backlight at 60% and a 360 × 360 ST77916 panel. GenjuTech +1.54 uses ST7789 SPI3 mode 3 at 80 MHz, color inversion, and GPIO2 LEDC backlight at 60%. ## CoreS3 pixel-glasses view @@ -46,7 +52,7 @@ LCD-only changes. See [CoreS3 UI](CORES3_UI.md) for timing, resources and valida CoreS3's legacy light-theme setting is retained but does not change the pixel skin. Its animation option controls expression motion and transitions; disabling it retains the opener hold and -necessary guidance scrolling. The other twelve profiles retain their existing UI/hardware; +necessary guidance scrolling. The other fifteen profiles retain their existing UI/hardware; SDK, AOSL, RTSA, camera/video policy, input, power and memory configuration are unchanged. ## Shared workflow view on other display boards diff --git a/docs/PLATFORM_UI.zh-CN.md b/docs/PLATFORM_UI.zh-CN.md index 425b8c2..abe9c2b 100644 --- a/docs/PLATFORM_UI.zh-CN.md +++ b/docs/PLATFORM_UI.zh-CN.md @@ -20,6 +20,9 @@ | `esp32-s3-touch-amoled-1.75` | CO5300,466 × 466 | 通用面板适配器 | 原有触摸/按键路径 | | `esp32-s3-touch-amoled-1.75c` | CO5300,466 × 466 | 通用面板适配器 | 原有触摸/按键路径 | | `esp32-s3-touch-amoled-2.16` | CO5300 480 变体,480 × 480 | 通用面板适配器 | CST9217 与 Boot | +| `esp32-s3-touch-amoled-1.8-v2` | CO5300 368×448 变体,368 × 448 | 通用面板适配器 | CST816S 与 Boot | +| `esp32-s3-touch-lcd-1.85` | ST77916,360 × 360 | 通用面板适配器 | CST816S 与 Boot | +| `genjutech-s3-1.54tft` | ST7789,240 × 240 | 通用 ST7789 | Boot 与音量按键 | | `sensecap-watcher` | SPD2010,412 × 412 | 通用面板适配器 | 原有旋钮路径 | AtomEchoS3R 与 ReSpeaker Flex 没有 LCD,不编入 LVGL。ML307、Wi-Fi 及音视频协议与显示后端 @@ -31,6 +34,9 @@ GPIO42 LEDC 背光(5 kHz、13-bit、60% 占空比)。仅有一个 Boot 输 布局。 AMOLED 2.16 使用零 panel gap 与 40 MHz QSPI;CO5300 传输区域两个方向均保持偶数像素 边界。 +AMOLED 1.8 V2 使用 16 像素 X 偏移与 40 MHz QSPI,并保持相同的 CO5300 偶数像素约束。 +LCD 1.85 使用 GPIO5 LEDC 背光(60%)与 360 × 360 ST77916 面板。GenjuTech 1.54 使用 +ST7789 SPI3 mode 3 / 80 MHz、颜色反转与 GPIO2 LEDC 背光(60%)。 ## CoreS3像素眼镜视图 @@ -42,7 +48,7 @@ THINKING显示对应表情,连续思考三秒后转PROCESSING,SPEAKING/无 [CoreS3 UI](CORES3_UI.zh-CN.md)。 CoreS3保留浅色主题字段兼容但不改变像素皮肤;动画开关控制表情运动/切换,关闭后仍推进 -开场保持并保留必要的引导文字滚动。其他十二个profile保留原UI/硬件;摄像头/视频策略、 +开场保持并保留必要的引导文字滚动。其他十五个profile保留原UI/硬件;摄像头/视频策略、 输入、电源及内存配置不变。 ## 其他带屏板型的共享流程视图 diff --git a/docs/RELEASING.md b/docs/RELEASING.md index de321f5..c4b7501 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -85,9 +85,19 @@ git diff --check 480x480 CO5300 output with zero gap, even pixel boundaries, panel colors/orientation/brightness, CST9217 press/release/hold, 16 kHz primary-mic and full-duplex audio against the reference 24 kHz setting, PA noise, volume persistence, and repeated provisioning/start/stop. +- [ ] For Waveshare AMOLED 1.8 V2, verify physical Flash/PSRAM, TCA9554/AXP2101 initialization, + the 368x448 CO5300 gap/colors/orientation/brightness, CST816S press/release/hold, ES8311 + bi-directional audio, PA noise, volume persistence, and provisioning while mybot is stopped. +- [ ] For Waveshare LCD 1.85, verify physical Flash/PSRAM, GPIO7 power hold, TCA9554 LCD/touch reset, + panel-version selection, 360x360 colors/orientation, GPIO5 backlight, CST816S, right-slot + microphone capture, both-slot playback, full-duplex audio, volume persistence, and repeated + provisioning/start/stop. +- [ ] For GenjuTech 1.54, verify physical Flash/PSRAM, ST7789 colors/orientation/inversion and GPIO2 + backlight, ES8311 audio and GPIO21 PA/display coupling, Boot/volume buttons, volume + persistence, prompt drain, and repeated provisioning/start/stop. - [ ] Verify an existing build directory rejects a change of `MYBOT_BOARD`, and keep separate - artifacts for all three Waveshare AMOLED profiles. A clean build accepts any profile; it - cannot detect the connected board's revision. Match the physical revision before flashing. + artifacts for every Waveshare touch profile. A clean build accepts any profile; it cannot + detect the connected board's revision. Match the physical device before flashing. - [ ] For SenseCAP Watcher, back up and checksum the 200 KiB `nvsfactory` region before first flash; verify normal flashing leaves it unchanged and never publish an `erase-flash` procedure. - [ ] For M5Stack StickS3, test USB and battery boot, M5PM1 G2/G3 sequencing, speaker pop/noise, diff --git a/docs/RELEASING.zh-CN.md b/docs/RELEASING.zh-CN.md index e5196f8..35a1a35 100644 --- a/docs/RELEASING.zh-CN.md +++ b/docs/RELEASING.zh-CN.md @@ -74,9 +74,17 @@ git diff --check USB/电池上电时序、可选 TCA9554 搭载情况、MCLK42/LCD-reset39/touch-reset40、480x480 CO5300 零偏移和偶数像素边界、颜色/方向/亮度、CST9217 按下/释放/长按。结合参考 24 kHz 配置验证 16 kHz 主麦和全双工音频、PA 噪声、音量持久化及反复配网/启停。 -- [ ] 确认已有构建目录拒绝切换 `MYBOT_BOARD`,并为三个 Waveshare AMOLED profile 保留独立 +- [ ] 对 Waveshare AMOLED 1.8 V2,验证实际 Flash/PSRAM、TCA9554/AXP2101 初始化、 + 368x448 CO5300 偏移/颜色/方向/亮度、CST816S 按下/释放/长按、ES8311 双向音频、PA + 噪声、音量持久化,以及 mybot 停止期间配网。 +- [ ] 对 Waveshare LCD 1.85,验证实际 Flash/PSRAM、GPIO7 电源保持、TCA9554 LCD/触摸复位、 + 面板版本选择、360x360 颜色/方向、GPIO5 背光、CST816S、右 slot 麦克风采集、双 slot + 播放、全双工音频、音量持久化及反复配网/启停。 +- [ ] 对 GenjuTech 1.54,验证实际 Flash/PSRAM、ST7789 颜色/方向/反转与 GPIO2 背光、 + ES8311 音频与 GPIO21 PA/显示耦合、Boot/音量按键、音量持久化、提示音排空及反复配网/启停。 +- [ ] 确认已有构建目录拒绝切换 `MYBOT_BOARD`,并为每个 Waveshare 触屏 profile 保留独立 制品。全新构建目录可以使用任一 profile,无法识别连接设备的硬件版本;烧录前须核对 - 实际硬件版本与固件是否匹配。 + 实际硬件与固件是否匹配。 - [ ] 对 SenseCAP Watcher,首次烧录前备份并校验 200 KiB `nvsfactory` 区域;确认正常烧录不 改变该区域,且发布流程不得包含 `erase-flash`。 - [ ] 对 M5Stack StickS3,验证 USB 与电池启动、M5PM1 G2/G3 时序、扬声器爆音/噪声、 diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index 0e3ab87..e6025ad 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -27,6 +27,9 @@ unsupported areas. Board READMEs describe hardware and operation only; the | `esp32-s3-touch-amoled-1.75` | CI: zh-CN, en-US | Not completed / 未完成 | Power, audio slots, display and touch; no video, playback reference, local AEC, RTC, IMU, TF card, battery reporting, sleep or shutdown. | | `esp32-s3-touch-amoled-1.75c` | CI: zh-CN, en-US | Not completed / 未完成 | Same as 1.75, with no TCA9554 support; verify detected Flash capacity before expanding the partition layout. | | `esp32-s3-touch-amoled-2.16` | CI: zh-CN, en-US; recorded merged build / 已记录合并构建 | Not completed / 未完成 | Physical Flash/PSRAM and TCA9554 presence, power, 480x480 display, touch and audio; no video, playback reference, local AEC, RTC, IMU, TF card, battery reporting, sleep or shutdown. | +| `esp32-s3-touch-amoled-1.8-v2` | CI: zh-CN, en-US; recorded merged build / 已记录合并构建 | Not completed / 未完成 | Physical Flash/PSRAM, AXP2101/TCA9554 sequencing, 368x448 QSPI display, CST816S and 16 kHz ES8311 audio require device validation; no AXP2101 power-key action, battery reporting, sleep, shutdown or video. | +| `esp32-s3-touch-lcd-1.85` | CI: zh-CN, en-US; recorded merged build / 已记录合并构建 | Not completed / 未完成 | Physical Flash/PSRAM, GPIO7 power hold, panel-version selection, backlight, CST816S and right-slot microphone/both-slot playback require device validation; GPIO6 power button, battery reporting, sleep, shutdown and video are not implemented. | +| `genjutech-s3-1.54tft` | CI: zh-CN, en-US; recorded merged build / 已记录合并构建 | Not completed / 未完成 | Physical Flash/PSRAM, ST7789, ES8311, PA/display coupling, Boot and volume inputs require device validation; no touch, LED, battery/charging, sleep, shutdown or video. | | `m5stack-core-s3` | CI: zh-CN, en-US, video, light-theme and static-animation variants; recorded video build / 已记录视频构建 | Partial / 部分通过: earlier provisioning, bidirectional audio, camera uplink and status-card UI; current pixel UI and shared UI changes pending / 旧通路通过,当前像素 UI 与共享 UI 待回归 | Current pixel UI, capture scheduling and DMA regression; no video downlink, camera preview, battery reporting or automatic sleep. Video is opt-in for direct builds. | | `m5stack-stick-s3` | CI: zh-CN, en-US | Not completed / 未完成 | Power, audio, input and panel; no GPIO12 support, IMU, infrared, battery reporting, shutdown or low power. | | `respeaker-flex-xvf3800-circular4-xiao` | CI: zh-CN, en-US | Not completed / 未完成 | Requires separately flashed XVF3800 Circular-4 16 kHz two-channel I2S firmware; XVF version, clock direction, routing and audio pending. No Linear-4, XVF firmware update, LED ring, LCD or video. | diff --git a/tests/co5300_lifecycle/README.md b/tests/co5300_lifecycle/README.md index c77a718..4fd8a68 100644 --- a/tests/co5300_lifecycle/README.md +++ b/tests/co5300_lifecycle/README.md @@ -5,10 +5,10 @@ sh tests/co5300_lifecycle/run.sh ``` This compiles the original production panel against the AMOLED 1.75 and 1.75C -profiles and the separate 480-pixel panel against the 2.16 profile. Constructor -mocks inspect the initialization commands, +profiles, the separate 480-pixel panel against the 2.16 profile, and the +368x448 panel against the 1.8 V2 profile. Constructor mocks inspect the initialization commands, address windows, QSPI configuration, panel dimensions, reset pin, and brightness. -The original 1.75/1.75C command bytes and delays are checked for compatibility. +Each panel variant's command bytes and delays are checked for compatibility. Fault injection covers initialization errors, partially returned handles, cleanup timeouts, retries, and repeated open/close. The existing diff --git a/tests/co5300_lifecycle/run.sh b/tests/co5300_lifecycle/run.sh index 3000013..ca919c4 100644 --- a/tests/co5300_lifecycle/run.sh +++ b/tests/co5300_lifecycle/run.sh @@ -8,12 +8,15 @@ test_build_dir=$(mktemp -d) trap 'rm -r -- "$test_build_dir"' EXIT HUP INT TERM if [ "$#" -eq 0 ]; then - set -- esp32-s3-touch-amoled-1.75 esp32-s3-touch-amoled-1.75c esp32-s3-touch-amoled-2.16 + set -- esp32-s3-touch-amoled-1.75 esp32-s3-touch-amoled-1.75c esp32-s3-touch-amoled-2.16 \ + esp32-s3-touch-amoled-1.8-v2 fi for board_id do panel_source="$repo_root/components/mybot_platform/src/drivers/display/panels/co5300/co5300_panel.c" if [ "$board_id" = esp32-s3-touch-amoled-2.16 ]; then panel_source="$repo_root/components/mybot_platform/src/drivers/display/panels/co5300/co5300_480_panel.c" + elif [ "$board_id" = esp32-s3-touch-amoled-1.8-v2 ]; then + panel_source="$repo_root/components/mybot_platform/src/drivers/display/panels/co5300/co5300_amoled18_panel.c" fi "${CC:-cc}" -std=c11 -Wall -Wextra -Werror \ -I"$test_dir/mocks" -I"$repo_root/tests/st7789_lifecycle/mocks" \ diff --git a/tests/co5300_lifecycle/test_co5300_lifecycle.c b/tests/co5300_lifecycle/test_co5300_lifecycle.c index 8f54a20..c6796bc 100644 --- a/tests/co5300_lifecycle/test_co5300_lifecycle.c +++ b/tests/co5300_lifecycle/test_co5300_lifecycle.c @@ -9,6 +9,14 @@ #include #include +#ifdef MYBOT_DISPLAY_SPI_HOST +#define TEST_SPI_HOST MYBOT_DISPLAY_SPI_HOST +#define TEST_PIXEL_CLOCK_HZ MYBOT_DISPLAY_PIXEL_CLOCK_HZ +#else +#define TEST_SPI_HOST MYBOT_AMOLED175_LCD_SPI_HOST +#define TEST_PIXEL_CLOCK_HZ MYBOT_AMOLED175_LCD_PIXEL_CLOCK_HZ +#endif + #define CHECK(condition) \ do { \ if (!(condition)) { \ @@ -116,6 +124,24 @@ static const co5300_lcd_init_cmd_t s_216_commands[] = { {0x29, NULL, 0, 600}, }; +static const co5300_lcd_init_cmd_t s_18_v2_commands[] = { + {0x11, NULL, 0, 600}, + {0xfe, (uint8_t[]){0x20}, 1, 0}, + {0x19, (uint8_t[]){0x10}, 1, 0}, + {0x1c, (uint8_t[]){0xa0}, 1, 0}, + {0xfe, (uint8_t[]){0x00}, 1, 0}, + {0xc4, (uint8_t[]){0x80}, 1, 0}, + {0x3a, (uint8_t[]){0x55}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x53, (uint8_t[]){0x20}, 1, 0}, + {0x51, (uint8_t[]){0xff}, 1, 0}, + {0x63, (uint8_t[]){0xff}, 1, 0}, + {0x2a, (uint8_t[]){0x00, 0x00, 0x01, 0xdf}, 4, 0}, + {0x2b, (uint8_t[]){0x00, 0x00, 0x01, 0xdf}, 4, 0}, + {0x36, (uint8_t[]){0x00}, 1, 0}, + {0x29, NULL, 0, 600}, +}; + static void check_command(const co5300_lcd_init_cmd_t *actual, const co5300_lcd_init_cmd_t *expected) { CHECK(actual->cmd == expected->cmd && actual->data_bytes == expected->data_bytes); @@ -136,6 +162,13 @@ static void check_vendor_commands(const co5300_vendor_config_t *vendor) { for (size_t i = 0; i < vendor->init_cmds_size; ++i) { check_command(&vendor->init_cmds[i], &s_216_commands[i]); } + } else if (!strcmp(MYBOT_BOARD_NAME, "esp32-s3-touch-amoled-1.8-v2")) { + CHECK(MYBOT_DISPLAY_WIDTH == 368 && MYBOT_DISPLAY_HEIGHT == 448); + CHECK(MYBOT_DISPLAY_OFFSET_X == 16 && MYBOT_DISPLAY_OFFSET_Y == 0); + CHECK(vendor->init_cmds_size == sizeof(s_18_v2_commands) / sizeof(s_18_v2_commands[0])); + for (size_t i = 0; i < vendor->init_cmds_size; ++i) { + check_command(&vendor->init_cmds[i], &s_18_v2_commands[i]); + } } else { CHECK(!strcmp(MYBOT_BOARD_NAME, "esp32-s3-touch-amoled-1.75") || !strcmp(MYBOT_BOARD_NAME, "esp32-s3-touch-amoled-1.75c")); @@ -154,7 +187,7 @@ const char *esp_err_to_name(esp_err_t error) { } esp_err_t spi_bus_initialize(int host, const spi_bus_config_t *config, int dma) { - CHECK(host == MYBOT_AMOLED175_LCD_SPI_HOST && dma == SPI_DMA_CH_AUTO && !s_spi_open); + CHECK(host == TEST_SPI_HOST && dma == SPI_DMA_CH_AUTO && !s_spi_open); CHECK(config->sclk_io_num == MYBOT_DISPLAY_PCLK); CHECK(config->data0_io_num == MYBOT_DISPLAY_DATA0 && config->data1_io_num == MYBOT_DISPLAY_DATA1); @@ -176,7 +209,7 @@ esp_err_t spi_bus_initialize(int host, const spi_bus_config_t *config, int dma) } esp_err_t spi_bus_free(int host) { - CHECK(host == MYBOT_AMOLED175_LCD_SPI_HOST && s_spi_open && !s_io.open && !s_panel.open); + CHECK(host == TEST_SPI_HOST && s_spi_open && !s_io.open && !s_panel.open); const esp_err_t result = step("spi_free", EVENT_SPI_FREE); if (result == ESP_OK) { s_spi_open = false; @@ -187,9 +220,9 @@ esp_err_t spi_bus_free(int host) { esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t host, const esp_lcd_panel_io_spi_config_t *config, esp_lcd_panel_io_handle_t *out) { - CHECK(host == MYBOT_AMOLED175_LCD_SPI_HOST && s_spi_open && !s_io.open); + CHECK(host == TEST_SPI_HOST && s_spi_open && !s_io.open); CHECK(config->cs_gpio_num == MYBOT_DISPLAY_CS && config->dc_gpio_num == GPIO_NUM_NC); - CHECK(config->spi_mode == 0 && config->pclk_hz == MYBOT_AMOLED175_LCD_PIXEL_CLOCK_HZ); + CHECK(config->spi_mode == 0 && config->pclk_hz == TEST_PIXEL_CLOCK_HZ); CHECK(config->lcd_cmd_bits == 32 && config->lcd_param_bits == 8 && config->flags.quad_mode); CHECK(config->trans_queue_depth == 2); const esp_err_t result = step("io_new", EVENT_IO_NEW); diff --git a/tests/cst816s_input/mocks/button_gpio.h b/tests/cst816s_input/mocks/button_gpio.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/button_gpio.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/driver/gpio.h b/tests/cst816s_input/mocks/driver/gpio.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/driver/gpio.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/driver/i2c_master.h b/tests/cst816s_input/mocks/driver/i2c_master.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/driver/i2c_master.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/driver/spi_common.h b/tests/cst816s_input/mocks/driver/spi_common.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/driver/spi_common.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/esp_lcd_touch.h b/tests/cst816s_input/mocks/esp_lcd_touch.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/esp_lcd_touch.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/esp_lcd_touch_cst816s.h b/tests/cst816s_input/mocks/esp_lcd_touch_cst816s.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/esp_lcd_touch_cst816s.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/esp_log.h b/tests/cst816s_input/mocks/esp_log.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/esp_log.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/freertos/FreeRTOS.h b/tests/cst816s_input/mocks/freertos/FreeRTOS.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/freertos/FreeRTOS.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/freertos/semphr.h b/tests/cst816s_input/mocks/freertos/semphr.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/freertos/semphr.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/freertos/task.h b/tests/cst816s_input/mocks/freertos/task.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/freertos/task.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/iot_button.h b/tests/cst816s_input/mocks/iot_button.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/iot_button.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/mock_platform.h b/tests/cst816s_input/mocks/mock_platform.h new file mode 100644 index 0000000..2847300 --- /dev/null +++ b/tests/cst816s_input/mocks/mock_platform.h @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef CST816S_INPUT_MOCK_PLATFORM_H_ +#define CST816S_INPUT_MOCK_PLATFORM_H_ + +#include +#include +#include + +typedef int esp_err_t; +#define ESP_OK 0 +#define ESP_FAIL -1 +#define ESP_ERR_NO_MEM 0x101 +const char *esp_err_to_name(esp_err_t error); + +typedef int gpio_num_t; +#define GPIO_NUM_NC -1 +#define GPIO_NUM_0 0 +#define GPIO_NUM_1 1 +#define GPIO_NUM_2 2 +#define GPIO_NUM_3 3 +#define GPIO_NUM_4 4 +#define GPIO_NUM_5 5 +#define GPIO_NUM_7 7 +#define GPIO_NUM_10 10 +#define GPIO_NUM_11 11 +#define GPIO_NUM_15 15 +#define GPIO_NUM_21 21 +#define GPIO_NUM_38 38 +#define GPIO_NUM_39 39 +#define GPIO_NUM_40 40 +#define GPIO_NUM_41 41 +#define GPIO_NUM_42 42 +#define GPIO_NUM_45 45 +#define GPIO_NUM_46 46 +#define GPIO_NUM_47 47 +#define GPIO_NUM_48 48 + +#define I2C_NUM_0 0 +#define I2C_NUM_1 1 +#define SPI2_HOST 2 + +#define ESP_LOGI(...) ((void)0) +#define ESP_LOGW(...) ((void)0) +#define ESP_LOGE(...) ((void)0) + +typedef pthread_mutex_t portMUX_TYPE; +#define portMUX_INITIALIZER_UNLOCKED PTHREAD_MUTEX_INITIALIZER +#define portENTER_CRITICAL(lock) ((void)pthread_mutex_lock(lock)) +#define portEXIT_CRITICAL(lock) ((void)pthread_mutex_unlock(lock)) + +#define pdMS_TO_TICKS(ms) (ms) +#define pdPASS 1 +#define pdTRUE 1 +#define portMAX_DELAY UINT32_MAX +typedef uint32_t TickType_t; + +typedef struct mock_task *TaskHandle_t; +typedef void (*task_function_t)(void *); +int xTaskCreate(task_function_t task, const char *name, uint32_t stack_size, void *argument, + unsigned int priority, TaskHandle_t *out_task); +TaskHandle_t xTaskGetCurrentTaskHandle(void); +void vTaskDelete(TaskHandle_t task); +void vTaskDelay(TickType_t ticks); +TickType_t xTaskGetTickCount(void); + +typedef struct mock_semaphore { + pthread_mutex_t lock; + pthread_cond_t condition; + bool given; +} StaticSemaphore_t; +typedef StaticSemaphore_t *SemaphoreHandle_t; +SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *storage); +int xSemaphoreTake(SemaphoreHandle_t semaphore, TickType_t ticks); +int xSemaphoreGive(SemaphoreHandle_t semaphore); + +typedef struct mock_i2c_bus *i2c_master_bus_handle_t; +typedef struct mock_panel_io *esp_lcd_panel_io_handle_t; +typedef struct mock_touch *esp_lcd_touch_handle_t; + +typedef struct { + uint16_t dev_addr; + uint32_t scl_speed_hz; + unsigned int control_phase_bytes; + unsigned int dc_bit_offset; + unsigned int lcd_cmd_bits; + unsigned int lcd_param_bits; + struct { + unsigned int disable_control_phase : 1; + } flags; +} esp_lcd_panel_io_i2c_config_t; +esp_err_t esp_lcd_new_panel_io_i2c(i2c_master_bus_handle_t bus, + const esp_lcd_panel_io_i2c_config_t *config, + esp_lcd_panel_io_handle_t *out_io); +esp_err_t esp_lcd_panel_io_del(esp_lcd_panel_io_handle_t io); + +typedef struct { + uint16_t x; + uint16_t y; +} esp_lcd_touch_point_data_t; +typedef struct { + uint16_t x_max; + uint16_t y_max; + gpio_num_t rst_gpio_num; + gpio_num_t int_gpio_num; + struct { + uint8_t reset; + uint8_t interrupt; + } levels; + struct { + uint8_t swap_xy; + uint8_t mirror_x; + uint8_t mirror_y; + } flags; +} esp_lcd_touch_config_t; +esp_err_t esp_lcd_touch_new_i2c_cst816s(esp_lcd_panel_io_handle_t io, + const esp_lcd_touch_config_t *config, + esp_lcd_touch_handle_t *out_touch); +esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t touch); +esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t touch); +esp_err_t esp_lcd_touch_get_data(esp_lcd_touch_handle_t touch, esp_lcd_touch_point_data_t *data, + uint8_t *count, uint8_t max_count); +#define ESP_LCD_TOUCH_IO_I2C_CST816S_ADDRESS 0x15 + +typedef struct mock_button *button_handle_t; +typedef struct { + uint32_t long_press_time; + uint32_t short_press_time; +} button_config_t; +typedef struct { + gpio_num_t gpio_num; + int active_level; + bool enable_power_save; + bool disable_pull; +} button_gpio_config_t; +typedef enum { + BUTTON_SINGLE_CLICK = 1, + BUTTON_LONG_PRESS_START = 2, +} button_event_t; +typedef void (*button_callback_t)(void *button, void *user_data); +esp_err_t iot_button_new_gpio_device(const button_config_t *config, + const button_gpio_config_t *gpio_config, + button_handle_t *out_button); +esp_err_t iot_button_register_cb(button_handle_t button, button_event_t event, void *event_data, + button_callback_t callback, void *user_data); +esp_err_t iot_button_delete(button_handle_t button); + +typedef enum { + MYBOT_STATE_STARTING, + MYBOT_STATE_READY, + MYBOT_STATE_IN_CONVERSATION, +} mybot_state_t; +mybot_state_t mybot_get_state(void); + +typedef enum { + MYBOT_KEY_EVENT_CONVERSATION_START, + MYBOT_KEY_EVENT_CONVERSATION_STOP, +} mybot_key_event_t; +typedef void (*mybot_key_event_handler_t)(mybot_key_event_t event, void *user_data); +typedef struct { + int (*init)(void **out_context, mybot_key_event_handler_t emit, void *user_data); + void (*destroy)(void *context); +} mybot_key_ops_t; + +int mybot_board_handle_boot_long_press(void); + +#endif /* CST816S_INPUT_MOCK_PLATFORM_H_ */ diff --git a/tests/cst816s_input/mocks/mybot/mybot.h b/tests/cst816s_input/mocks/mybot/mybot.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/mybot/mybot.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/mybot/platform/mybot_key.h b/tests/cst816s_input/mocks/mybot/platform/mybot_key.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/mybot/platform/mybot_key.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/mocks/platform/board_actions.h b/tests/cst816s_input/mocks/platform/board_actions.h new file mode 100644 index 0000000..b65df2b --- /dev/null +++ b/tests/cst816s_input/mocks/platform/board_actions.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" diff --git a/tests/cst816s_input/run.sh b/tests/cst816s_input/run.sh new file mode 100644 index 0000000..11e0396 --- /dev/null +++ b/tests/cst816s_input/run.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# SPDX-License-Identifier: MIT +set -eu + +repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) +test_dir="$repo_root/tests/cst816s_input" +test_build_dir=$(mktemp -d) +trap 'rm -r -- "$test_build_dir"' EXIT HUP INT TERM + +"${CC:-cc}" -std=c11 -D_POSIX_C_SOURCE=200809L -Wall -Wextra -Werror -pthread \ + -I"$test_dir/mocks" \ + -I"$repo_root/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85" \ + "$test_dir/test_cst816s_input.c" \ + "$repo_root/components/mybot_platform/src/drivers/input/cst816s_boot_input.c" \ + -o "$test_build_dir/test_cst816s_input" + +if command -v timeout >/dev/null 2>&1; then + timeout 20 "$test_build_dir/test_cst816s_input" +else + "$test_build_dir/test_cst816s_input" +fi diff --git a/tests/cst816s_input/test_cst816s_input.c b/tests/cst816s_input/test_cst816s_input.c new file mode 100644 index 0000000..f84a54d --- /dev/null +++ b/tests/cst816s_input/test_cst816s_input.c @@ -0,0 +1,488 @@ +/* SPDX-License-Identifier: MIT */ +#include "mock_platform.h" + +#include "board_config.h" + +#include +#include +#include +#include +#include + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \ + exit(1); \ + } \ + } while (0) + +struct mock_i2c_bus { + int unused; +}; +struct mock_panel_io { + bool open; +}; +struct mock_touch { + bool open; +}; +struct mock_button { + bool open; + button_callback_t click; + button_callback_t long_press; + void *click_user_data; + void *long_press_user_data; +}; +struct mock_task { + pthread_t thread; + task_function_t function; + void *argument; +}; + +static struct mock_i2c_bus g_bus; +static struct mock_panel_io g_io; +static struct mock_touch g_touch; +static struct mock_button g_button; +static bool g_fail_button_new; +static button_event_t g_fail_register_event; +static bool g_fail_button_delete; +static bool g_fail_io_new; +static bool g_fail_io_delete; +static bool g_fail_touch_new; +static bool g_fail_touch_delete; +static bool g_fail_task_create; +static unsigned int g_button_delete_calls; +static unsigned int g_provision_calls; +static unsigned int g_state_calls; +static mybot_state_t g_state = MYBOT_STATE_STARTING; +static _Thread_local struct mock_task *g_current_task; + +const char *esp_err_to_name(esp_err_t error) { + return error == ESP_OK ? "ESP_OK" : "ESP_FAIL"; +} + +i2c_master_bus_handle_t mybot_board_touch_i2c_bus_handle(void) { + return &g_bus; +} + +esp_err_t esp_lcd_new_panel_io_i2c(i2c_master_bus_handle_t bus, + const esp_lcd_panel_io_i2c_config_t *config, + esp_lcd_panel_io_handle_t *out_io) { + CHECK(bus == &g_bus && !g_io.open); + CHECK(config->dev_addr == ESP_LCD_TOUCH_IO_I2C_CST816S_ADDRESS); + CHECK(config->scl_speed_hz == MYBOT_TOUCH_I2C_SPEED_HZ); + CHECK(config->control_phase_bytes == 1 && config->dc_bit_offset == 0); + CHECK(config->lcd_cmd_bits == 8 && config->lcd_param_bits == 0); + CHECK(config->flags.disable_control_phase == 1); + if (g_fail_io_new) { + g_fail_io_new = false; + return ESP_FAIL; + } + g_io.open = true; + *out_io = &g_io; + return ESP_OK; +} + +esp_err_t esp_lcd_panel_io_del(esp_lcd_panel_io_handle_t io) { + CHECK(io == &g_io && g_io.open); + if (g_fail_io_delete) { + g_fail_io_delete = false; + return ESP_FAIL; + } + g_io.open = false; + return ESP_OK; +} + +esp_err_t esp_lcd_touch_new_i2c_cst816s(esp_lcd_panel_io_handle_t io, + const esp_lcd_touch_config_t *config, + esp_lcd_touch_handle_t *out_touch) { + CHECK(io == &g_io && g_io.open && !g_touch.open); + CHECK(config->x_max == MYBOT_DISPLAY_WIDTH - 1); + CHECK(config->y_max == MYBOT_DISPLAY_HEIGHT - 1); + CHECK(config->rst_gpio_num == MYBOT_TOUCH_RESET); + CHECK(config->int_gpio_num == MYBOT_TOUCH_INTERRUPT); + CHECK(config->levels.reset == MYBOT_TOUCH_RESET_LEVEL); + CHECK(config->levels.interrupt == MYBOT_TOUCH_INTERRUPT_LEVEL); + CHECK(config->flags.swap_xy == MYBOT_TOUCH_SWAP_XY); + CHECK(config->flags.mirror_x == MYBOT_TOUCH_MIRROR_X); + CHECK(config->flags.mirror_y == MYBOT_TOUCH_MIRROR_Y); + if (g_fail_touch_new) { + g_fail_touch_new = false; + return ESP_FAIL; + } + g_touch.open = true; + *out_touch = &g_touch; + return ESP_OK; +} + +esp_err_t esp_lcd_touch_del(esp_lcd_touch_handle_t touch) { + CHECK(touch == &g_touch && g_touch.open); + if (g_fail_touch_delete) { + g_fail_touch_delete = false; + return ESP_FAIL; + } + g_touch.open = false; + return ESP_OK; +} + +esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t touch) { + CHECK(touch == &g_touch && g_touch.open); + return ESP_FAIL; +} + +esp_err_t esp_lcd_touch_get_data(esp_lcd_touch_handle_t touch, esp_lcd_touch_point_data_t *data, + uint8_t *count, uint8_t max_count) { + (void)touch; + (void)data; + (void)max_count; + *count = 0; + return ESP_FAIL; +} + +esp_err_t iot_button_new_gpio_device(const button_config_t *config, + const button_gpio_config_t *gpio_config, + button_handle_t *out_button) { + CHECK(config->long_press_time == MYBOT_BOOT_BUTTON_LONG_PRESS_MS); + CHECK(config->short_press_time == 50); + CHECK(gpio_config->gpio_num == MYBOT_BOOT_BUTTON_GPIO); + CHECK(gpio_config->active_level == 0); + CHECK(!g_button.open); + if (g_fail_button_new) { + g_fail_button_new = false; + return ESP_FAIL; + } + g_button.open = true; + *out_button = &g_button; + return ESP_OK; +} + +esp_err_t iot_button_register_cb(button_handle_t button, button_event_t event, void *event_data, + button_callback_t callback, void *user_data) { + CHECK(button == &g_button && button->open); + CHECK(event_data == NULL && callback != NULL); + if (g_fail_register_event == event) { + g_fail_register_event = 0; + return ESP_FAIL; + } + if (event == BUTTON_SINGLE_CLICK) { + button->click = callback; + button->click_user_data = user_data; + } else { + CHECK(event == BUTTON_LONG_PRESS_START); + button->long_press = callback; + button->long_press_user_data = user_data; + } + return ESP_OK; +} + +esp_err_t iot_button_delete(button_handle_t button) { + CHECK(button == &g_button && button->open); + ++g_button_delete_calls; + if (g_fail_button_delete) { + g_fail_button_delete = false; + return ESP_FAIL; + } + button->open = false; + button->click = NULL; + button->long_press = NULL; + return ESP_OK; +} + +mybot_state_t mybot_get_state(void) { + ++g_state_calls; + return g_state; +} + +int mybot_board_handle_boot_long_press(void) { + ++g_provision_calls; + return 0; +} + +static void *task_trampoline(void *argument) { + struct mock_task *task = argument; + g_current_task = task; + task->function(task->argument); + return NULL; +} + +int xTaskCreate(task_function_t task, const char *name, uint32_t stack_size, void *argument, + unsigned int priority, TaskHandle_t *out_task) { + (void)name; + CHECK(stack_size == 4096 && priority == 5); + if (g_fail_task_create) { + g_fail_task_create = false; + *out_task = NULL; + return 0; + } + struct mock_task *created = calloc(1, sizeof(*created)); + CHECK(created); + created->function = task; + created->argument = argument; + CHECK(pthread_create(&created->thread, NULL, task_trampoline, created) == 0); + CHECK(pthread_detach(created->thread) == 0); + *out_task = created; + return pdPASS; +} + +TaskHandle_t xTaskGetCurrentTaskHandle(void) { + return g_current_task; +} + +void vTaskDelete(TaskHandle_t task) { + CHECK(task == NULL || task == g_current_task); + if (g_current_task) { + free(g_current_task); + g_current_task = NULL; + } + pthread_exit(NULL); +} + +void vTaskDelay(TickType_t ticks) { + struct timespec delay = { + .tv_sec = ticks / 1000, + .tv_nsec = (long)(ticks % 1000) * 1000000L, + }; + nanosleep(&delay, NULL); +} + +TickType_t xTaskGetTickCount(void) { + struct timespec now; + CHECK(clock_gettime(CLOCK_MONOTONIC, &now) == 0); + return (TickType_t)((uint64_t)now.tv_sec * 1000U + (uint64_t)now.tv_nsec / 1000000U); +} + +SemaphoreHandle_t xSemaphoreCreateBinaryStatic(StaticSemaphore_t *storage) { + CHECK(pthread_mutex_init(&storage->lock, NULL) == 0); + CHECK(pthread_cond_init(&storage->condition, NULL) == 0); + storage->given = false; + return storage; +} + +int xSemaphoreTake(SemaphoreHandle_t semaphore, TickType_t ticks) { + CHECK(semaphore); + pthread_mutex_lock(&semaphore->lock); + if (!ticks) { + const bool taken = semaphore->given; + semaphore->given = false; + pthread_mutex_unlock(&semaphore->lock); + return taken ? pdTRUE : 0; + } + CHECK(ticks == portMAX_DELAY); + while (!semaphore->given) { + CHECK(pthread_cond_wait(&semaphore->condition, &semaphore->lock) == 0); + } + semaphore->given = false; + pthread_mutex_unlock(&semaphore->lock); + return pdTRUE; +} + +int xSemaphoreGive(SemaphoreHandle_t semaphore) { + CHECK(semaphore); + pthread_mutex_lock(&semaphore->lock); + semaphore->given = true; + CHECK(pthread_cond_signal(&semaphore->condition) == 0); + pthread_mutex_unlock(&semaphore->lock); + return pdTRUE; +} + +int mybot_cst816s_input_start(void); +int mybot_cst816s_input_stop(void); +const mybot_key_ops_t *mybot_cst816s_input_ops(void); + +static void fire_button(button_event_t event) { + CHECK(g_button.open); + button_callback_t callback = + event == BUTTON_SINGLE_CLICK ? g_button.click : g_button.long_press; + void *user_data = + event == BUTTON_SINGLE_CLICK ? g_button.click_user_data : g_button.long_press_user_data; + CHECK(callback); + callback(&g_button, user_data); +} + +static void assert_released(void) { + CHECK(!g_button.open && !g_touch.open && !g_io.open); +} + +static unsigned int g_event_count; +static mybot_key_event_t g_last_event; + +static void record_event(mybot_key_event_t event, void *user_data) { + CHECK(user_data == &g_event_count); + ++g_event_count; + g_last_event = event; +} + +static void test_start_failures_and_retry(void) { + const mybot_key_ops_t *ops = mybot_cst816s_input_ops(); + void *context = NULL; + CHECK(ops && ops->init && ops->destroy); + CHECK(ops->init(&context, record_event, &g_event_count) < 0 && !context); + CHECK(mybot_cst816s_input_stop() == 0); + + g_fail_button_new = true; + CHECK(mybot_cst816s_input_start() < 0); + assert_released(); + g_fail_register_event = BUTTON_LONG_PRESS_START; + CHECK(mybot_cst816s_input_start() < 0); + assert_released(); + g_fail_io_new = true; + CHECK(mybot_cst816s_input_start() < 0); + assert_released(); + g_fail_touch_new = true; + CHECK(mybot_cst816s_input_start() < 0); + assert_released(); + g_fail_task_create = true; + CHECK(mybot_cst816s_input_start() < 0); + assert_released(); + + CHECK(mybot_cst816s_input_start() == 0); + CHECK(g_button.open && g_touch.open && g_io.open); + CHECK(mybot_cst816s_input_start() < 0); + CHECK(mybot_cst816s_input_stop() == 0); + CHECK(mybot_cst816s_input_stop() == 0); + assert_released(); + CHECK(mybot_cst816s_input_start() == 0); + CHECK(mybot_cst816s_input_stop() == 0); + assert_released(); +} + +static void test_cleanup_retry(void) { + CHECK(mybot_cst816s_input_start() == 0); + g_fail_touch_delete = true; + CHECK(mybot_cst816s_input_stop() < 0); + CHECK(!g_button.open && g_touch.open && g_io.open); + CHECK(mybot_cst816s_input_stop() == 0); + assert_released(); + + CHECK(mybot_cst816s_input_start() == 0); + g_fail_io_delete = true; + CHECK(mybot_cst816s_input_stop() < 0); + CHECK(!g_button.open && !g_touch.open && g_io.open); + CHECK(mybot_cst816s_input_stop() == 0); + assert_released(); + + CHECK(mybot_cst816s_input_start() == 0); + g_fail_button_delete = true; + CHECK(mybot_cst816s_input_stop() < 0); + CHECK(g_button.open && !g_touch.open && !g_io.open); + CHECK(mybot_cst816s_input_stop() == 0); + assert_released(); +} + +static void test_callback_lifecycle(void) { + const mybot_key_ops_t *ops = mybot_cst816s_input_ops(); + CHECK(mybot_cst816s_input_start() == 0); + for (unsigned int cycle = 0; cycle < 3; ++cycle) { + void *context = NULL; + CHECK(ops->init(&context, record_event, &g_event_count) == 0 && context); + void *duplicate = NULL; + CHECK(ops->init(&duplicate, record_event, &g_event_count) < 0 && !duplicate); + + g_state = MYBOT_STATE_READY; + fire_button(BUTTON_SINGLE_CLICK); + CHECK(g_event_count == cycle * 2 + 1); + CHECK(g_last_event == MYBOT_KEY_EVENT_CONVERSATION_START); + g_state = MYBOT_STATE_IN_CONVERSATION; + fire_button(BUTTON_SINGLE_CLICK); + CHECK(g_event_count == cycle * 2 + 2); + CHECK(g_last_event == MYBOT_KEY_EVENT_CONVERSATION_STOP); + g_state = MYBOT_STATE_STARTING; + fire_button(BUTTON_SINGLE_CLICK); + CHECK(g_event_count == cycle * 2 + 2); + + unsigned int provisions = g_provision_calls; + fire_button(BUTTON_LONG_PRESS_START); + CHECK(g_provision_calls == provisions + 1); + ops->destroy(context); + + const unsigned int states = g_state_calls; + g_state = MYBOT_STATE_READY; + fire_button(BUTTON_SINGLE_CLICK); + CHECK(g_state_calls == states && g_event_count == cycle * 2 + 2); + fire_button(BUTTON_LONG_PRESS_START); + CHECK(g_provision_calls == provisions + 2); + } + CHECK(mybot_cst816s_input_stop() == 0); +} + +static pthread_mutex_t g_callback_lock = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t g_callback_condition = PTHREAD_COND_INITIALIZER; +static bool g_callback_entered; +static bool g_callback_release; +static atomic_bool g_destroy_done; + +static void blocking_event(mybot_key_event_t event, void *user_data) { + CHECK(event == MYBOT_KEY_EVENT_CONVERSATION_START); + CHECK(user_data == &g_callback_entered); + pthread_mutex_lock(&g_callback_lock); + g_callback_entered = true; + pthread_cond_broadcast(&g_callback_condition); + while (!g_callback_release) { + CHECK(pthread_cond_wait(&g_callback_condition, &g_callback_lock) == 0); + } + pthread_mutex_unlock(&g_callback_lock); +} + +static void wait_for_callback(void) { + struct timespec deadline; + CHECK(clock_gettime(CLOCK_REALTIME, &deadline) == 0); + deadline.tv_sec += 2; + pthread_mutex_lock(&g_callback_lock); + while (!g_callback_entered) { + const int result = + pthread_cond_timedwait(&g_callback_condition, &g_callback_lock, &deadline); + CHECK(result != ETIMEDOUT && result == 0); + } + pthread_mutex_unlock(&g_callback_lock); +} + +static void *click_thread(void *argument) { + (void)argument; + fire_button(BUTTON_SINGLE_CLICK); + return NULL; +} + +static void *destroy_thread(void *argument) { + mybot_cst816s_input_ops()->destroy(argument); + atomic_store(&g_destroy_done, true); + return NULL; +} + +static void test_destroy_waits_for_callback(void) { + const mybot_key_ops_t *ops = mybot_cst816s_input_ops(); + CHECK(mybot_cst816s_input_start() == 0); + void *context = NULL; + g_state = MYBOT_STATE_READY; + CHECK(ops->init(&context, blocking_event, &g_callback_entered) == 0); + + pthread_t click; + CHECK(pthread_create(&click, NULL, click_thread, NULL) == 0); + wait_for_callback(); + atomic_store(&g_destroy_done, false); + pthread_t destroy; + CHECK(pthread_create(&destroy, NULL, destroy_thread, context) == 0); + + struct timespec pause = {.tv_nsec = 50000000L}; + nanosleep(&pause, NULL); + CHECK(!atomic_load(&g_destroy_done)); + + pthread_mutex_lock(&g_callback_lock); + g_callback_release = true; + pthread_cond_broadcast(&g_callback_condition); + pthread_mutex_unlock(&g_callback_lock); + CHECK(pthread_join(click, NULL) == 0); + CHECK(pthread_join(destroy, NULL) == 0); + CHECK(atomic_load(&g_destroy_done)); + CHECK(mybot_cst816s_input_stop() == 0); + assert_released(); +} + +int main(void) { + test_start_failures_and_retry(); + test_cleanup_retry(); + test_callback_lifecycle(); + test_destroy_waits_for_callback(); + printf("CST816S/Boot input host fault-injection tests passed (%u button deletes)\n", + g_button_delete_calls); + return 0; +} diff --git a/tests/st7789_lifecycle/README.md b/tests/st7789_lifecycle/README.md index 6a8938c..c51f5db 100644 --- a/tests/st7789_lifecycle/README.md +++ b/tests/st7789_lifecycle/README.md @@ -7,8 +7,8 @@ sh tests/st7789_lifecycle/run.sh ``` The harness compiles the production ST7789 panel and shared LVGL adapter with -host implementations of the ESP-IDF and LVGL APIs. It uses the -`xingzhi-cube-1.54tft-wifi` board configuration. +host implementations of the ESP-IDF and LVGL APIs. It runs against the +`xingzhi-cube-1.54tft-wifi` and `genjutech-s3-1.54tft` board configurations. Coverage includes initialization failures, partially returned constructor handles, retained resources after cleanup failures, repeated attach/detach and diff --git a/tests/st7789_lifecycle/mocks/host_platform.h b/tests/st7789_lifecycle/mocks/host_platform.h index 0be1bcb..27f990e 100644 --- a/tests/st7789_lifecycle/mocks/host_platform.h +++ b/tests/st7789_lifecycle/mocks/host_platform.h @@ -37,6 +37,7 @@ esp_err_t rtc_gpio_deinit(gpio_num_t gpio); #define GPIO_NUM_0 0 #define GPIO_NUM_1 1 #define GPIO_NUM_2 2 +#define GPIO_NUM_3 3 #define GPIO_NUM_4 4 #define GPIO_NUM_5 5 #define GPIO_NUM_6 6 @@ -44,6 +45,7 @@ esp_err_t rtc_gpio_deinit(gpio_num_t gpio); #define GPIO_NUM_8 8 #define GPIO_NUM_9 9 #define GPIO_NUM_10 10 +#define GPIO_NUM_11 11 #define GPIO_NUM_12 12 #define GPIO_NUM_13 13 #define GPIO_NUM_14 14 diff --git a/tests/st7789_lifecycle/run.sh b/tests/st7789_lifecycle/run.sh index 27ab9ea..d4041f9 100644 --- a/tests/st7789_lifecycle/run.sh +++ b/tests/st7789_lifecycle/run.sh @@ -4,23 +4,31 @@ set -eu repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) test_dir="$repo_root/tests/st7789_lifecycle" -board_dir="$repo_root/components/mybot_platform/boards/xingzhi-cube-1.54tft-wifi" test_build_dir=$(mktemp -d) trap 'rm -r -- "$test_build_dir"' EXIT HUP INT TERM -"${CC:-cc}" -std=c11 -Wall -Wextra -Werror \ - -I"$test_dir/mocks" -I"$board_dir" \ - -I"$repo_root/components/mybot_platform/src/internal" \ - -c "$repo_root/components/mybot_platform/src/drivers/display/panels/st7789/st7789_panel.c" \ - -o "$test_build_dir/panel.o" -"${CXX:-c++}" -std=c++17 -Wall -Wextra -Werror -pthread \ - -I"$test_dir/mocks" -I"$board_dir" \ - -I"$repo_root/components/mybot_platform/src/internal" \ - -I"$repo_root/components/mybot_stack/mybot_sdk/mybot/include" \ - "$test_dir/test_st7789_lifecycle.cc" \ - "$repo_root/components/mybot_platform/src/drivers/display/adapters/lvgl/shared_lvgl_adapter.cc" \ - "$test_build_dir/panel.o" -o "$test_build_dir/test_st7789_lifecycle" +for board_id in xingzhi-cube-1.54tft-wifi genjutech-s3-1.54tft; do + board_dir="$repo_root/components/mybot_platform/boards/$board_id" + "${CC:-cc}" -std=c11 -Wall -Wextra -Werror \ + -I"$test_dir/mocks" -I"$board_dir" \ + -I"$repo_root/components/mybot_platform/src/internal" \ + -c "$repo_root/components/mybot_platform/src/drivers/display/panels/st7789/st7789_panel.c" \ + -o "$test_build_dir/panel-$board_id.o" + "${CXX:-c++}" -std=c++17 -Wall -Wextra -Werror -pthread \ + -I"$test_dir/mocks" -I"$board_dir" \ + -I"$repo_root/components/mybot_platform/src/internal" \ + -I"$repo_root/components/mybot_stack/mybot_sdk/mybot/include" \ + "$test_dir/test_st7789_lifecycle.cc" \ + "$repo_root/components/mybot_platform/src/drivers/display/adapters/lvgl/shared_lvgl_adapter.cc" \ + "$test_build_dir/panel-$board_id.o" -o "$test_build_dir/test_st7789_lifecycle-$board_id" + if command -v timeout >/dev/null 2>&1; then + timeout 20 "$test_build_dir/test_st7789_lifecycle-$board_id" + else + "$test_build_dir/test_st7789_lifecycle-$board_id" + fi +done +board_dir="$repo_root/components/mybot_platform/boards/xingzhi-cube-1.54tft-wifi" "${CC:-cc}" -std=c11 -Wall -Wextra -Werror \ -I"$test_dir/mocks" -I"$board_dir" \ -I"$repo_root/components/mybot_platform/include" \ @@ -35,12 +43,6 @@ trap 'rm -r -- "$test_build_dir"' EXIT HUP INT TERM "$test_dir/test_xingzhi_board.cc" "$test_build_dir/board.o" \ -o "$test_build_dir/test_xingzhi_board" -if command -v timeout >/dev/null 2>&1; then - timeout 20 "$test_build_dir/test_st7789_lifecycle" -else - "$test_build_dir/test_st7789_lifecycle" -fi - for scenario in rtc_hold_dis rtc_deinit gpio_config gpio_high ledc_timer ledc_channel \ ledc_duty ledc_update lcd_init lcd_render buttons_start ok; do "$test_build_dir/test_xingzhi_board" "$scenario" diff --git a/tests/tca9554/mocks/driver/i2c_master.h b/tests/tca9554/mocks/driver/i2c_master.h new file mode 100644 index 0000000..1b6e7c8 --- /dev/null +++ b/tests/tca9554/mocks/driver/i2c_master.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef TCA9554_MOCK_I2C_H_ +#define TCA9554_MOCK_I2C_H_ + +#include +#include + +typedef int esp_err_t; +#define ESP_OK 0 +#define ESP_FAIL -1 +#define I2C_ADDR_BIT_LEN_7 0 + +typedef struct mock_i2c_bus *i2c_master_bus_handle_t; +typedef struct mock_i2c_device *i2c_master_dev_handle_t; +typedef struct { + int dev_addr_length; + uint16_t device_address; + uint32_t scl_speed_hz; +} i2c_device_config_t; + +esp_err_t i2c_master_bus_add_device(i2c_master_bus_handle_t bus, const i2c_device_config_t *config, + i2c_master_dev_handle_t *out_device); +esp_err_t i2c_master_bus_rm_device(i2c_master_dev_handle_t device); +esp_err_t i2c_master_transmit(i2c_master_dev_handle_t device, const uint8_t *data, size_t size, + int timeout_ms); + +#endif /* TCA9554_MOCK_I2C_H_ */ diff --git a/tests/tca9554/mocks/esp_err.h b/tests/tca9554/mocks/esp_err.h new file mode 100644 index 0000000..bc3b792 --- /dev/null +++ b/tests/tca9554/mocks/esp_err.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "driver/i2c_master.h" diff --git a/tests/tca9554/mocks/esp_log.h b/tests/tca9554/mocks/esp_log.h new file mode 100644 index 0000000..e8a566b --- /dev/null +++ b/tests/tca9554/mocks/esp_log.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#define ESP_LOGE(...) ((void)0) diff --git a/tests/tca9554/run.sh b/tests/tca9554/run.sh new file mode 100644 index 0000000..a4a63bc --- /dev/null +++ b/tests/tca9554/run.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# SPDX-License-Identifier: MIT +set -eu + +repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) +test_dir="$repo_root/tests/tca9554" +test_build_dir=$(mktemp -d) +trap 'rm -r -- "$test_build_dir"' EXIT HUP INT TERM + +"${CC:-cc}" -std=c11 -Wall -Wextra -Werror \ + -I"$test_dir/mocks" \ + -I"$repo_root/components/mybot_platform/src/internal" \ + "$test_dir/test_tca9554.c" \ + "$repo_root/components/mybot_platform/src/drivers/io_expander/tca9554.c" \ + -o "$test_build_dir/test_tca9554" + +if command -v timeout >/dev/null 2>&1; then + timeout 20 "$test_build_dir/test_tca9554" +else + "$test_build_dir/test_tca9554" +fi diff --git a/tests/tca9554/test_tca9554.c b/tests/tca9554/test_tca9554.c new file mode 100644 index 0000000..52c8d9c --- /dev/null +++ b/tests/tca9554/test_tca9554.c @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: MIT */ +#include "io_expander/tca9554.h" + +#include +#include +#include + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \ + exit(1); \ + } \ + } while (0) + +struct mock_i2c_bus { + int unused; +}; +struct mock_i2c_device { + i2c_master_bus_handle_t bus; + uint16_t address; + uint32_t speed_hz; + bool open; +}; + +static struct mock_i2c_bus g_bus; +static struct mock_i2c_device g_device; +static uint8_t g_register; +static uint8_t g_value; +static bool g_fail_add; +static bool g_fail_remove; +static bool g_fail_write; +static unsigned int g_writes; + +const char *esp_err_to_name(esp_err_t error) { + return error == ESP_OK ? "ESP_OK" : "ESP_FAIL"; +} + +esp_err_t i2c_master_bus_add_device(i2c_master_bus_handle_t bus, const i2c_device_config_t *config, + i2c_master_dev_handle_t *out_device) { + CHECK(bus == &g_bus && !g_device.open); + CHECK(config->dev_addr_length == I2C_ADDR_BIT_LEN_7); + CHECK(config->device_address == 0x20 && config->scl_speed_hz == 400000); + if (g_fail_add) { + g_fail_add = false; + return ESP_FAIL; + } + g_device = (struct mock_i2c_device){ + .bus = bus, + .address = config->device_address, + .speed_hz = config->scl_speed_hz, + .open = true, + }; + *out_device = &g_device; + return ESP_OK; +} + +esp_err_t i2c_master_bus_rm_device(i2c_master_dev_handle_t device) { + CHECK(device == &g_device && g_device.open); + if (g_fail_remove) { + g_fail_remove = false; + return ESP_FAIL; + } + g_device.open = false; + return ESP_OK; +} + +esp_err_t i2c_master_transmit(i2c_master_dev_handle_t device, const uint8_t *data, size_t size, + int timeout_ms) { + CHECK(device == &g_device && device->open); + CHECK(data && size == 2 && timeout_ms == 100); + if (g_fail_write) { + g_fail_write = false; + return ESP_FAIL; + } + g_register = data[0]; + g_value = data[1]; + ++g_writes; + return ESP_OK; +} + +int main(void) { + mybot_tca9554_t expander = {0}; + CHECK(mybot_tca9554_init(NULL, &g_bus, 0x20, 400000) < 0); + CHECK(mybot_tca9554_init(&expander, NULL, 0x20, 400000) < 0); + g_fail_add = true; + CHECK(mybot_tca9554_init(&expander, &g_bus, 0x20, 400000) < 0); + CHECK(!expander.device); + + CHECK(mybot_tca9554_init(&expander, &g_bus, 0x20, 400000) == 0); + CHECK(expander.device == &g_device && expander.output_state == 0); + CHECK(mybot_tca9554_init(&expander, &g_bus, 0x20, 400000) < 0); + + CHECK(mybot_tca9554_configure_outputs(&expander, 0x07, 0x02) == 0); + CHECK(g_register == 0x03 && g_value == 0xf8); + CHECK(g_writes == 2 && expander.output_state == 0x02); + CHECK(mybot_tca9554_set_levels(&expander, 0x01, 0x01) == 0); + CHECK(g_register == 0x01 && g_value == 0x03); + CHECK(mybot_tca9554_set_levels(&expander, 0x02, 0x02) == 0); + CHECK(g_value == 0x03); + CHECK(mybot_tca9554_set_levels(&expander, 0x03, 0x00) == 0); + CHECK(g_value == 0x00 && expander.output_state == 0); + + g_fail_write = true; + CHECK(mybot_tca9554_set_levels(&expander, 0x01, 0x01) < 0); + CHECK(expander.output_state == 0); + CHECK(mybot_tca9554_set_levels(&expander, 0x01, 0x01) == 0); + CHECK(expander.output_state == 0x01); + + g_fail_remove = true; + CHECK(mybot_tca9554_deinit(&expander) < 0); + CHECK(expander.device == &g_device); + CHECK(mybot_tca9554_deinit(&expander) == 0); + CHECK(!expander.device && !expander.bus && expander.output_state == 0); + CHECK(mybot_tca9554_deinit(&expander) == 0); + puts("TCA9554 masked update host tests passed"); + return 0; +} From 3e7bb2d399784eb136f6a5c3713b8a3c1ed43655 Mon Sep 17 00:00:00 2001 From: shangjinlong Date: Tue, 27 Oct 2026 16:59:40 +0800 Subject: [PATCH 2/5] feat(platform): add twenty-two ESP32-S3 board profiles --- .github/workflows/ci.yml | 65 + CHANGELOG.md | 15 +- README.md | 16 +- README.zh-CN.md | 16 +- THIRD_PARTY_NOTICES.md | 7 + VENDORED_SOURCES.md | 9 +- components/esp_lcd_gc9a01/CMakeLists.txt | 12 + components/esp_lcd_gc9a01/LICENSE | 202 + components/esp_lcd_gc9a01/esp_lcd_gc9a01.c | 393 ++ .../esp_lcd_gc9a01/include/esp_lcd_gc9a01.h | 107 + components/esp_lcd_nv3023/CMakeLists.txt | 12 + components/esp_lcd_nv3023/LICENSE | 202 + components/esp_lcd_nv3023/esp_lcd_nv3023.c | 399 ++ .../esp_lcd_nv3023/include/esp_lcd_nv3023.h | 97 + components/esp_lcd_sh8601/CMakeLists.txt | 12 + components/esp_lcd_sh8601/LICENSE | 202 + components/esp_lcd_sh8601/esp_lcd_sh8601.c | 347 ++ .../esp_lcd_sh8601/include/esp_lcd_sh8601.h | 115 + .../esp_lcd_touch_ft5x06/CMakeLists.txt | 6 + components/esp_lcd_touch_ft5x06/LICENSE | 202 + .../esp_lcd_touch_ft5x06.c | 357 ++ .../include/esp_lcd_touch_ft5x06.h | 61 + components/esp_lcd_touch_gt911/CMakeLists.txt | 6 + components/esp_lcd_touch_gt911/LICENSE | 202 + .../esp_lcd_touch_gt911/esp_lcd_touch_gt911.c | 447 ++ .../include/esp_lcd_touch_gt911.h | 72 + components/m5ioe1/CMakeLists.txt | 6 + components/m5ioe1/LICENSE | 21 + components/m5ioe1/src/M5IOE1.cpp | 5285 +++++++++++++++++ components/m5ioe1/src/M5IOE1.h | 1913 ++++++ components/m5ioe1/src/M5IOE1_i2c_compat.h | 788 +++ components/mybot_platform/boards/README.md | 36 +- .../boards/atoms3r-echo-base/README.md | 14 + .../boards/atoms3r-echo-base/board.c | 6 + .../boards/atoms3r-echo-base/board.cmake | 17 + .../boards/atoms3r-echo-base/board_config.h | 61 + .../atoms3r-echo-base/sdkconfig.defaults | 10 + .../boards/atoms3r-echo-pyramid/README.md | 14 + .../boards/atoms3r-echo-pyramid/board.c | 6 + .../boards/atoms3r-echo-pyramid/board.cmake | 18 + .../atoms3r-echo-pyramid/board_config.h | 60 + .../atoms3r-echo-pyramid/sdkconfig.defaults | 11 + components/mybot_platform/boards/boards.cmake | 66 + .../boards/common/generic_board.c | 200 + .../boards/common/generic_board.h | 17 + .../boards/common/generic_hardware.c | 454 ++ .../boards/common/generic_hardware.h | 24 + .../boards/common/generic_profile.cmake | 90 + .../mybot_platform/boards/df-k10/README.md | 14 + .../mybot_platform/boards/df-k10/board.c | 6 + .../mybot_platform/boards/df-k10/board.cmake | 18 + .../boards/df-k10/board_config.h | 61 + .../boards/df-k10/sdkconfig.defaults | 11 + .../mybot_platform/boards/esp-box-3/README.md | 14 + .../mybot_platform/boards/esp-box-3/board.c | 6 + .../boards/esp-box-3/board.cmake | 18 + .../boards/esp-box-3/board_config.h | 62 + .../boards/esp-box-3/sdkconfig.defaults | 11 + .../boards/esp-box-lite/README.md | 15 + .../boards/esp-box-lite/board.c | 6 + .../boards/esp-box-lite/board.cmake | 18 + .../boards/esp-box-lite/board_config.h | 61 + .../boards/esp-box-lite/sdkconfig.defaults | 11 + .../mybot_platform/boards/esp-box/README.md | 14 + .../mybot_platform/boards/esp-box/board.c | 6 + .../mybot_platform/boards/esp-box/board.cmake | 18 + .../boards/esp-box/board_config.h | 61 + .../boards/esp-box/sdkconfig.defaults | 11 + .../esp32-s3-touch-amoled-1.32/README.md | 14 + .../boards/esp32-s3-touch-amoled-1.32/board.c | 6 + .../esp32-s3-touch-amoled-1.32/board.cmake | 18 + .../esp32-s3-touch-amoled-1.32/board_config.h | 60 + .../sdkconfig.defaults | 10 + .../esp32-s3-touch-amoled-1.43c/README.md | 14 + .../esp32-s3-touch-amoled-1.43c/board.c | 6 + .../esp32-s3-touch-amoled-1.43c/board.cmake | 19 + .../board_config.h | 57 + .../sdkconfig.defaults | 11 + .../esp32-s3-touch-amoled-1.8/README.md | 15 + .../boards/esp32-s3-touch-amoled-1.8/board.c | 6 + .../esp32-s3-touch-amoled-1.8/board.cmake | 20 + .../esp32-s3-touch-amoled-1.8/board_config.h | 75 + .../sdkconfig.defaults | 10 + .../esp32-s3-touch-amoled-2.06/README.md | 15 + .../boards/esp32-s3-touch-amoled-2.06/board.c | 6 + .../esp32-s3-touch-amoled-2.06/board.cmake | 20 + .../esp32-s3-touch-amoled-2.06/board_config.h | 71 + .../sdkconfig.defaults | 11 + .../boards/esp32s3-korvo2-v3/README.md | 15 + .../boards/esp32s3-korvo2-v3/board.c | 6 + .../boards/esp32s3-korvo2-v3/board.cmake | 19 + .../boards/esp32s3-korvo2-v3/board_config.h | 65 + .../esp32s3-korvo2-v3/sdkconfig.defaults | 11 + .../boards/lichuang-dev/README.md | 15 + .../boards/lichuang-dev/board.c | 6 + .../boards/lichuang-dev/board.cmake | 19 + .../boards/lichuang-dev/board_config.h | 79 + .../boards/lichuang-dev/sdkconfig.defaults | 11 + .../boards/m5stack-stopwatch/README.md | 16 + .../boards/m5stack-stopwatch/board.c | 6 + .../boards/m5stack-stopwatch/board.cmake | 21 + .../boards/m5stack-stopwatch/board_config.h | 72 + .../m5stack_stopwatch_hardware.cc | 127 + .../m5stack_stopwatch_hardware.h | 21 + .../m5stack-stopwatch/sdkconfig.defaults | 11 + .../boards/mixgo-nova/README.md | 14 + .../mybot_platform/boards/mixgo-nova/board.c | 6 + .../boards/mixgo-nova/board.cmake | 15 + .../boards/mixgo-nova/board_config.h | 60 + .../boards/mixgo-nova/sdkconfig.defaults | 8 + .../boards/quandong-s3-dev/README.md | 14 + .../boards/quandong-s3-dev/board.c | 6 + .../boards/quandong-s3-dev/board.cmake | 18 + .../boards/quandong-s3-dev/board_config.h | 67 + .../boards/quandong-s3-dev/sdkconfig.defaults | 10 + .../boards/rymcu-bigsmart/README.md | 15 + .../boards/rymcu-bigsmart/board.c | 6 + .../boards/rymcu-bigsmart/board.cmake | 19 + .../boards/rymcu-bigsmart/board_config.h | 79 + .../boards/rymcu-bigsmart/sdkconfig.defaults | 11 + .../boards/sp-esp32-s3-1.28-box/README.md | 15 + .../boards/sp-esp32-s3-1.28-box/board.c | 6 + .../boards/sp-esp32-s3-1.28-box/board.cmake | 19 + .../sp-esp32-s3-1.28-box/board_config.h | 78 + .../sp-esp32-s3-1.28-box/sdkconfig.defaults | 11 + .../boards/taiji-pi-s3/README.md | 15 + .../mybot_platform/boards/taiji-pi-s3/board.c | 6 + .../boards/taiji-pi-s3/board.cmake | 18 + .../boards/taiji-pi-s3/board_config.h | 77 + .../boards/taiji-pi-s3/sdkconfig.defaults | 9 + .../boards/xingzhi-abs-2.0/README.md | 15 + .../boards/xingzhi-abs-2.0/board.c | 6 + .../boards/xingzhi-abs-2.0/board.cmake | 17 + .../boards/xingzhi-abs-2.0/board_config.h | 63 + .../boards/xingzhi-abs-2.0/sdkconfig.defaults | 10 + .../xingzhi-cube-0.85tft-wifi/README.md | 17 + .../boards/xingzhi-cube-0.85tft-wifi/board.c | 6 + .../xingzhi-cube-0.85tft-wifi/board.cmake | 17 + .../xingzhi-cube-0.85tft-wifi/board_config.h | 56 + .../sdkconfig.defaults | 8 + .../xingzhi-cube-0.96oled-wifi/README.md | 15 + .../boards/xingzhi-cube-0.96oled-wifi/board.c | 6 + .../xingzhi-cube-0.96oled-wifi/board.cmake | 15 + .../xingzhi-cube-0.96oled-wifi/board_config.h | 33 + .../sdkconfig.defaults | 8 + .../boards/xingzhi-metal-1.54-wifi/README.md | 15 + .../boards/xingzhi-metal-1.54-wifi/board.c | 6 + .../xingzhi-metal-1.54-wifi/board.cmake | 17 + .../xingzhi-metal-1.54-wifi/board_config.h | 76 + .../sdkconfig.defaults | 10 + .../src/drivers/audio/es8311_codec_audio.c | 21 +- .../src/drivers/audio/es8374_codec.c | 742 +++ .../src/drivers/audio/raw_i2s_audio.c | 68 +- .../display/panels/co5300/co5300_466_panel.c | 157 + .../panels/generic/qspi_st77916_panel.c | 144 + .../display/panels/generic/rgb565_spi_panel.c | 225 + .../display/panels/sh8601/sh8601_panel.c | 210 + .../display/renderers/lvgl/lvgl_view.cc | 81 +- .../src/drivers/input/cst816s_boot_input.c | 44 +- .../src/drivers/input/gpio_buttons.c | 16 +- .../src/drivers/io_expander/pca9557.c | 82 + .../src/internal/audio/es8374_codec.h | 45 + .../src/internal/audio/es_common.h | 183 + .../src/internal/io_expander/pca9557.h | 30 + docs/AUDIO_PLAYBACK.md | 2 +- docs/AUDIO_PLAYBACK.zh-CN.md | 2 +- docs/BOARD_PORTING.md | 23 +- docs/BOARD_PORTING.zh-CN.md | 18 +- docs/CORES3_UI.md | 2 +- docs/CORES3_UI.zh-CN.md | 2 +- docs/PLATFORM_UI.md | 21 +- docs/PLATFORM_UI.zh-CN.md | 20 +- docs/RELEASING.md | 4 + docs/RELEASING.zh-CN.md | 3 + docs/VALIDATION.md | 22 + scripts/build_all.py | 5 +- 176 files changed, 17256 insertions(+), 58 deletions(-) create mode 100644 components/esp_lcd_gc9a01/CMakeLists.txt create mode 100644 components/esp_lcd_gc9a01/LICENSE create mode 100644 components/esp_lcd_gc9a01/esp_lcd_gc9a01.c create mode 100644 components/esp_lcd_gc9a01/include/esp_lcd_gc9a01.h create mode 100644 components/esp_lcd_nv3023/CMakeLists.txt create mode 100644 components/esp_lcd_nv3023/LICENSE create mode 100644 components/esp_lcd_nv3023/esp_lcd_nv3023.c create mode 100644 components/esp_lcd_nv3023/include/esp_lcd_nv3023.h create mode 100644 components/esp_lcd_sh8601/CMakeLists.txt create mode 100644 components/esp_lcd_sh8601/LICENSE create mode 100644 components/esp_lcd_sh8601/esp_lcd_sh8601.c create mode 100644 components/esp_lcd_sh8601/include/esp_lcd_sh8601.h create mode 100644 components/esp_lcd_touch_ft5x06/CMakeLists.txt create mode 100644 components/esp_lcd_touch_ft5x06/LICENSE create mode 100644 components/esp_lcd_touch_ft5x06/esp_lcd_touch_ft5x06.c create mode 100644 components/esp_lcd_touch_ft5x06/include/esp_lcd_touch_ft5x06.h create mode 100644 components/esp_lcd_touch_gt911/CMakeLists.txt create mode 100644 components/esp_lcd_touch_gt911/LICENSE create mode 100644 components/esp_lcd_touch_gt911/esp_lcd_touch_gt911.c create mode 100644 components/esp_lcd_touch_gt911/include/esp_lcd_touch_gt911.h create mode 100644 components/m5ioe1/CMakeLists.txt create mode 100644 components/m5ioe1/LICENSE create mode 100644 components/m5ioe1/src/M5IOE1.cpp create mode 100644 components/m5ioe1/src/M5IOE1.h create mode 100644 components/m5ioe1/src/M5IOE1_i2c_compat.h create mode 100644 components/mybot_platform/boards/atoms3r-echo-base/README.md create mode 100644 components/mybot_platform/boards/atoms3r-echo-base/board.c create mode 100644 components/mybot_platform/boards/atoms3r-echo-base/board.cmake create mode 100644 components/mybot_platform/boards/atoms3r-echo-base/board_config.h create mode 100644 components/mybot_platform/boards/atoms3r-echo-base/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/atoms3r-echo-pyramid/README.md create mode 100644 components/mybot_platform/boards/atoms3r-echo-pyramid/board.c create mode 100644 components/mybot_platform/boards/atoms3r-echo-pyramid/board.cmake create mode 100644 components/mybot_platform/boards/atoms3r-echo-pyramid/board_config.h create mode 100644 components/mybot_platform/boards/atoms3r-echo-pyramid/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/common/generic_board.c create mode 100644 components/mybot_platform/boards/common/generic_board.h create mode 100644 components/mybot_platform/boards/common/generic_hardware.c create mode 100644 components/mybot_platform/boards/common/generic_hardware.h create mode 100644 components/mybot_platform/boards/common/generic_profile.cmake create mode 100644 components/mybot_platform/boards/df-k10/README.md create mode 100644 components/mybot_platform/boards/df-k10/board.c create mode 100644 components/mybot_platform/boards/df-k10/board.cmake create mode 100644 components/mybot_platform/boards/df-k10/board_config.h create mode 100644 components/mybot_platform/boards/df-k10/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp-box-3/README.md create mode 100644 components/mybot_platform/boards/esp-box-3/board.c create mode 100644 components/mybot_platform/boards/esp-box-3/board.cmake create mode 100644 components/mybot_platform/boards/esp-box-3/board_config.h create mode 100644 components/mybot_platform/boards/esp-box-3/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp-box-lite/README.md create mode 100644 components/mybot_platform/boards/esp-box-lite/board.c create mode 100644 components/mybot_platform/boards/esp-box-lite/board.cmake create mode 100644 components/mybot_platform/boards/esp-box-lite/board_config.h create mode 100644 components/mybot_platform/boards/esp-box-lite/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp-box/README.md create mode 100644 components/mybot_platform/boards/esp-box/board.c create mode 100644 components/mybot_platform/boards/esp-box/board.cmake create mode 100644 components/mybot_platform/boards/esp-box/board_config.h create mode 100644 components/mybot_platform/boards/esp-box/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32s3-korvo2-v3/README.md create mode 100644 components/mybot_platform/boards/esp32s3-korvo2-v3/board.c create mode 100644 components/mybot_platform/boards/esp32s3-korvo2-v3/board.cmake create mode 100644 components/mybot_platform/boards/esp32s3-korvo2-v3/board_config.h create mode 100644 components/mybot_platform/boards/esp32s3-korvo2-v3/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/lichuang-dev/README.md create mode 100644 components/mybot_platform/boards/lichuang-dev/board.c create mode 100644 components/mybot_platform/boards/lichuang-dev/board.cmake create mode 100644 components/mybot_platform/boards/lichuang-dev/board_config.h create mode 100644 components/mybot_platform/boards/lichuang-dev/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/m5stack-stopwatch/README.md create mode 100644 components/mybot_platform/boards/m5stack-stopwatch/board.c create mode 100644 components/mybot_platform/boards/m5stack-stopwatch/board.cmake create mode 100644 components/mybot_platform/boards/m5stack-stopwatch/board_config.h create mode 100644 components/mybot_platform/boards/m5stack-stopwatch/m5stack_stopwatch_hardware.cc create mode 100644 components/mybot_platform/boards/m5stack-stopwatch/m5stack_stopwatch_hardware.h create mode 100644 components/mybot_platform/boards/m5stack-stopwatch/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/mixgo-nova/README.md create mode 100644 components/mybot_platform/boards/mixgo-nova/board.c create mode 100644 components/mybot_platform/boards/mixgo-nova/board.cmake create mode 100644 components/mybot_platform/boards/mixgo-nova/board_config.h create mode 100644 components/mybot_platform/boards/mixgo-nova/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/quandong-s3-dev/README.md create mode 100644 components/mybot_platform/boards/quandong-s3-dev/board.c create mode 100644 components/mybot_platform/boards/quandong-s3-dev/board.cmake create mode 100644 components/mybot_platform/boards/quandong-s3-dev/board_config.h create mode 100644 components/mybot_platform/boards/quandong-s3-dev/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/rymcu-bigsmart/README.md create mode 100644 components/mybot_platform/boards/rymcu-bigsmart/board.c create mode 100644 components/mybot_platform/boards/rymcu-bigsmart/board.cmake create mode 100644 components/mybot_platform/boards/rymcu-bigsmart/board_config.h create mode 100644 components/mybot_platform/boards/rymcu-bigsmart/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/sp-esp32-s3-1.28-box/README.md create mode 100644 components/mybot_platform/boards/sp-esp32-s3-1.28-box/board.c create mode 100644 components/mybot_platform/boards/sp-esp32-s3-1.28-box/board.cmake create mode 100644 components/mybot_platform/boards/sp-esp32-s3-1.28-box/board_config.h create mode 100644 components/mybot_platform/boards/sp-esp32-s3-1.28-box/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/taiji-pi-s3/README.md create mode 100644 components/mybot_platform/boards/taiji-pi-s3/board.c create mode 100644 components/mybot_platform/boards/taiji-pi-s3/board.cmake create mode 100644 components/mybot_platform/boards/taiji-pi-s3/board_config.h create mode 100644 components/mybot_platform/boards/taiji-pi-s3/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/xingzhi-abs-2.0/README.md create mode 100644 components/mybot_platform/boards/xingzhi-abs-2.0/board.c create mode 100644 components/mybot_platform/boards/xingzhi-abs-2.0/board.cmake create mode 100644 components/mybot_platform/boards/xingzhi-abs-2.0/board_config.h create mode 100644 components/mybot_platform/boards/xingzhi-abs-2.0/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/README.md create mode 100644 components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board.c create mode 100644 components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board.cmake create mode 100644 components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board_config.h create mode 100644 components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/README.md create mode 100644 components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board.c create mode 100644 components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board.cmake create mode 100644 components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board_config.h create mode 100644 components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/xingzhi-metal-1.54-wifi/README.md create mode 100644 components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board.c create mode 100644 components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board.cmake create mode 100644 components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board_config.h create mode 100644 components/mybot_platform/boards/xingzhi-metal-1.54-wifi/sdkconfig.defaults create mode 100644 components/mybot_platform/src/drivers/audio/es8374_codec.c create mode 100644 components/mybot_platform/src/drivers/display/panels/co5300/co5300_466_panel.c create mode 100644 components/mybot_platform/src/drivers/display/panels/generic/qspi_st77916_panel.c create mode 100644 components/mybot_platform/src/drivers/display/panels/generic/rgb565_spi_panel.c create mode 100644 components/mybot_platform/src/drivers/display/panels/sh8601/sh8601_panel.c create mode 100644 components/mybot_platform/src/drivers/io_expander/pca9557.c create mode 100644 components/mybot_platform/src/internal/audio/es8374_codec.h create mode 100644 components/mybot_platform/src/internal/audio/es_common.h create mode 100644 components/mybot_platform/src/internal/io_expander/pca9557.h diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 965c6d9..1175a6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -269,3 +269,68 @@ jobs: build-${{ matrix.board }}-ptime-${{ matrix.ptime }}-ui-${{ matrix.ui || 'default' }}-lang-${{ matrix.language }}-video-${{ matrix.video || 'off' }}/bootloader/bootloader.bin build-${{ matrix.board }}-ptime-${{ matrix.ptime }}-ui-${{ matrix.ui || 'default' }}-lang-${{ matrix.language }}-video-${{ matrix.video || 'off' }}/partition_table/partition-table.bin build-${{ matrix.board }}-ptime-${{ matrix.ptime }}-ui-${{ matrix.ui || 'default' }}-lang-${{ matrix.language }}-video-${{ matrix.video || 'off' }}/ota_data_initial.bin + + extended-idf-build: + name: extended-${{ matrix.board }}-${{ matrix.language }} + runs-on: ubuntu-24.04 + container: + image: espressif/idf:v5.5.2 + defaults: + run: + shell: bash + env: + BUILD_DIR: build-extended-${{ matrix.board }}-${{ matrix.language }} + strategy: + fail-fast: false + matrix: + board: + - xingzhi-metal-1.54-wifi + - xingzhi-cube-0.85tft-wifi + - xingzhi-cube-0.96oled-wifi + - xingzhi-abs-2.0 + - m5stack-stopwatch + - esp32-s3-touch-amoled-1.8 + - esp32-s3-touch-amoled-1.32 + - esp32-s3-touch-amoled-1.43c + - esp32-s3-touch-amoled-2.06 + - atoms3r-echo-base + - atoms3r-echo-pyramid + - sp-esp32-s3-1.28-box + - esp-box + - esp-box-3 + - esp-box-lite + - esp32s3-korvo2-v3 + - lichuang-dev + - quandong-s3-dev + - rymcu-bigsmart + - df-k10 + - mixgo-nova + - taiji-pi-s3 + language: [zh-CN, en-US] + steps: + - uses: actions/checkout@v4 + - name: Build ESP32-S3 firmware + run: | + source /opt/esp/idf/export.sh >/dev/null + defaults="ci/ptime60.defaults" + if [ "${{ matrix.language }}" = "en-US" ]; then + defaults="$defaults;ci/en-us.defaults" + fi + idf.py -B "$BUILD_DIR" \ + -DMYBOT_BOARD=${{ matrix.board }} \ + -DSDKCONFIG="$BUILD_DIR/sdkconfig" \ + -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;$defaults" \ + build + - name: Report size + run: | + source /opt/esp/idf/export.sh >/dev/null + idf.py -B "$BUILD_DIR" size + - name: Upload firmware + uses: actions/upload-artifact@v4 + with: + name: mybot-esp32-${{ matrix.board }}-${{ matrix.language }}-${{ github.sha }} + path: | + ${{ env.BUILD_DIR }}/mybot_esp32.bin + ${{ env.BUILD_DIR }}/bootloader/bootloader.bin + ${{ env.BUILD_DIR }}/partition_table/partition-table.bin + ${{ env.BUILD_DIR }}/ota_data_initial.bin diff --git a/CHANGELOG.md b/CHANGELOG.md index 07ecd69..f1ea6e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,12 +7,20 @@ Versioning and Conventional Commits. ### Added -- Per-board hardware, input and operation documentation for all sixteen profiles, with shared +- Twenty-two additional ESP32-S3 board profiles covering NV3023, GC9A01, ILI9341, SH8601 and + ST77916 panels; PCA9557/TCA9554, M5PM1/M5IOE1 and AXP2101 reset/power paths; FT5x06, GT911, + CST816S-compatible touch and GPIO input; ES8311, ES7210, ES8374 and raw/PDM audio paths. + `xingzhi-cube-0.96oled-wifi` is intentionally headless. All new profiles passed local + ESP-IDF v5.5.2 Chinese and English reconfigure/build/size/merge-bin validation; real-device + validation remains pending. Each new profile has its own concise board README linked from the + board index. +- Shared compile-time board/hardware skeleton for board families whose lifecycle contracts match. +- Per-board hardware, input and operation documentation for the original profiles, with shared build/flash instructions and one validation matrix. - CoreS3-only process-gray pixel-glasses UI, with 23 expressions, four 9 px terminal rows, a 10 px signature and fixed 32 px six-digit pairing codes on its 320x240 display. Automatic faces follow READY/conversation phases. Expressions affect only the LCD. - `MYBOT_BOARD` selects the view automatically; other fifteen profiles retain their UI/hardware. + `MYBOT_BOARD` selects the view automatically; other profiles retain their UI/hardware. - Shared LVGL status UI for the other display-board profiles, with Chinese/English text, pairing codes, voiceprint status, and listening/thinking/speaking indicators. - LVGL light/dark themes, local state emoji, rounded status cards, persistent voiceprint @@ -83,6 +91,9 @@ Versioning and Conventional Commits. ### Changed +- Centralize generic hardware sequencing, round-screen safe-area layout, dual-I2C selection, + speaker PA control and display/audio capability derivation so additional profiles do not copy + board lifecycle code. - Centralize build and flash commands in the board index and current validation status in `docs/VALIDATION.md`; remove repeated status paragraphs, volatile build timings and per-board command copies from board and topic documentation. diff --git a/README.md b/README.md index 10f88c6..8ca8529 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ESP32-S3 boards. The project owns board initialization, Wi-Fi provisioning, persistent storage, HTTPS transport, audio, input, display, and the firmware lifecycle around the SDK. -The development baseline is **ESP-IDF v5.5.2**. Sixteen compile-time board profiles are +The development baseline is **ESP-IDF v5.5.2**. Thirty-eight compile-time board profiles are supported; the board, Flash, PSRAM, partition table, and hardware drivers are selected with `MYBOT_BOARD`. See the [board documentation index](components/mybot_platform/boards/README.md) for hardware details, controls, build commands, and per-board limitations. @@ -40,12 +40,20 @@ Display profiles: `xingzhi-cube-1.54tft-wifi`, `bread-compact-wifi-lcd`, `esp-vocat`, `esp32-s3-touch-amoled-1.75`, `esp32-s3-touch-amoled-1.75c`, `esp32-s3-touch-amoled-2.16`, `esp32-s3-touch-amoled-1.8-v2`, -`esp32-s3-touch-lcd-1.85`, `genjutech-s3-1.54tft`, `m5stack-core-s3`, -`m5stack-stick-s3`, and `sensecap-watcher`. +`esp32-s3-touch-lcd-1.85`, `esp32-s3-touch-amoled-1.8`, +`esp32-s3-touch-amoled-1.32`, `esp32-s3-touch-amoled-1.43c`, +`esp32-s3-touch-amoled-2.06`, `atoms3r-echo-base`, `atoms3r-echo-pyramid`, +`sp-esp32-s3-1.28-box`, `esp-box`, `esp-box-3`, `esp-box-lite`, +`esp32s3-korvo2-v3`, `lichuang-dev`, `quandong-s3-dev`, `rymcu-bigsmart`, +`df-k10`, `mixgo-nova`, `taiji-pi-s3`, `m5stack-stopwatch`, +`xingzhi-metal-1.54-wifi`, `xingzhi-cube-0.85tft-wifi`, `xingzhi-abs-2.0`, +`genjutech-s3-1.54tft`, `m5stack-core-s3`, `m5stack-stick-s3`, and +`sensecap-watcher`. Headless profiles: -`atom-echos3r` and `respeaker-flex-xvf3800-circular4-xiao`. +`atom-echos3r`, `xingzhi-cube-0.96oled-wifi`, and +`respeaker-flex-xvf3800-circular4-xiao`. All profiles currently use Wi-Fi. The ML307 profile reserves modem UART pins but does not implement modem or 4G networking. Only `m5stack-core-s3` currently exposes camera video. diff --git a/README.zh-CN.md b/README.zh-CN.md index bafcd10..c232a16 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -9,7 +9,7 @@ 工程负责板级初始化、Wi-Fi 配网、持久化存储、HTTPS、音频、输入、显示,以及 SDK 外围的 固件生命周期。 -当前开发基线为 **ESP-IDF v5.5.2**,支持 16 个编译期 Board profile。通过 `MYBOT_BOARD` +当前开发基线为 **ESP-IDF v5.5.2**,支持 38 个编译期 Board profile。通过 `MYBOT_BOARD` 选择板型、Flash、PSRAM、分区表和硬件驱动。具体硬件配置、输入操作、构建命令和限制见 [板卡说明索引](components/mybot_platform/boards/README.md)。 @@ -36,12 +36,20 @@ `xingzhi-cube-1.54tft-wifi`、`bread-compact-wifi-lcd`、`esp-vocat`、 `esp32-s3-touch-amoled-1.75`、`esp32-s3-touch-amoled-1.75c`、 `esp32-s3-touch-amoled-2.16`、`esp32-s3-touch-amoled-1.8-v2`、 -`esp32-s3-touch-lcd-1.85`、`genjutech-s3-1.54tft`、`m5stack-core-s3`、 -`m5stack-stick-s3` 和 `sensecap-watcher`。 +`esp32-s3-touch-lcd-1.85`、`esp32-s3-touch-amoled-1.8`、 +`esp32-s3-touch-amoled-1.32`、`esp32-s3-touch-amoled-1.43c`、 +`esp32-s3-touch-amoled-2.06`、`atoms3r-echo-base`、`atoms3r-echo-pyramid`、 +`sp-esp32-s3-1.28-box`、`esp-box`、`esp-box-3`、`esp-box-lite`、 +`esp32s3-korvo2-v3`、`lichuang-dev`、`quandong-s3-dev`、`rymcu-bigsmart`、 +`df-k10`、`mixgo-nova`、`taiji-pi-s3`、`m5stack-stopwatch`、 +`xingzhi-metal-1.54-wifi`、`xingzhi-cube-0.85tft-wifi`、`xingzhi-abs-2.0`、 +`genjutech-s3-1.54tft`、`m5stack-core-s3`、`m5stack-stick-s3` 和 +`sensecap-watcher`。 无屏 profile: -`atom-echos3r` 和 `respeaker-flex-xvf3800-circular4-xiao`。 +`atom-echos3r`、`xingzhi-cube-0.96oled-wifi` 和 +`respeaker-flex-xvf3800-circular4-xiao`。 所有 profile 当前均使用 Wi-Fi。ML307 profile 仅预留 Modem UART 引脚,未实现 Modem 或 4G 联网。目前仅 `m5stack-core-s3` 提供摄像头视频能力。 diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 942e0f4..2a1bee4 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -16,6 +16,9 @@ It does not replace third-party terms. This file is informational and is not leg | Espressif SCCB 0.0.8 (I2C subset) | Apache-2.0 | `components/esp_sccb_intf` | | Espressif JPEG 0.6.1 (ESP32-S3 binary) | Espressif MIT, use on Espressif products | `components/esp_new_jpeg` | | Espressif ILI9341 LCD driver 2.0.2 | Apache-2.0 | `components/esp_lcd_ili9341` | +| Espressif GC9A01 LCD driver 2.0.4 | Apache-2.0 | `components/esp_lcd_gc9a01` | +| Espressif NV3023 LCD driver 1.0.1 | Apache-2.0 | `components/esp_lcd_nv3023` | +| Espressif SH8601 LCD driver 1.0.2 | Apache-2.0 | `components/esp_lcd_sh8601` | | LVGL 9.5.0 | MIT plus retained font/library-specific terms | `components/lvgl` | | Espressif LVGL port 2.8.0~1 | Apache-2.0 | `components/esp_lvgl_port` | | Espressif SPD2010 LCD driver 2.0.0~1 | Apache-2.0 | `components/esp_lcd_spd2010` | @@ -24,12 +27,16 @@ It does not replace third-party terms. This file is informational and is not leg | Espressif LCD touch 1.2.1 | Apache-2.0 | `components/esp_lcd_touch` | | Espressif CST816S touch driver 1.1.1~1 | Apache-2.0 | `components/esp_lcd_touch_cst816s` | | Waveshare CST9217 touch driver 1.0.4 | Apache-2.0 | `components/esp_lcd_touch_cst9217` | +| Espressif FT5x06 touch driver 1.1.0~1 | Apache-2.0 | `components/esp_lcd_touch_ft5x06` | +| Espressif GT911 touch driver 1.2.0~2 | Apache-2.0 | `components/esp_lcd_touch_gt911` | | Espressif IO expander 1.2.1 | Apache-2.0 | `components/esp_io_expander` | | Espressif TCA95xx 16-bit IO expander 2.0.2 | Apache-2.0 | `components/esp_io_expander_tca95xx_16bit` | | Espressif button 4.2.0 | Apache-2.0 | `components/button` | | Espressif knob 1.1.0 | Apache-2.0 | `components/knob` | | Espressif CMake utilities 0.5.0 | Apache-2.0 | `components/cmake_utilities` | | M5Stack M5PM1 1.0.7 | MIT | `components/m5pm1` | +| M5Stack M5IOE1 1.0.9 | MIT | `components/m5ioe1` | +| Espressif ES8374 codec source | Apache-2.0 | `components/mybot_platform/src/drivers/audio/es8374_codec.c` and its private headers | | Pairing prompt and digit audio (`7854c1d` SDK reference) | Apache-2.0 | `prompt.ogg` and `0.ogg` through `9.ogg` in `components/mybot_platform/assets/locales/{en-US,zh-CN}` | | Source Han Sans UI glyph subset | SIL Open Font License 1.1 | `components/mybot_platform/src/drivers/display/renderers/lvgl/lvgl_fonts.c`, `components/mybot_platform/src/drivers/display/assets/licenses/LVGL_VIEW_FONT_LICENSE.txt` | | Noto Color Emoji rasterized glyphs | SIL Open Font License 1.1 | `components/mybot_platform/assets/ui/noto_emoji`, `components/mybot_platform/src/drivers/display/renderers/lvgl/lvgl_assets.c` | diff --git a/VENDORED_SOURCES.md b/VENDORED_SOURCES.md index a44aaa8..2986770 100644 --- a/VENDORED_SOURCES.md +++ b/VENDORED_SOURCES.md @@ -16,6 +16,9 @@ redistribution terms are documented separately in [THIRD_PARTY_NOTICES.md](THIRD | `components/esp_sccb_intf` | `github.com/espressif/esp-video-components`, `esp_sccb_intf` | 0.0.8, commit `3384d3510c5880edeb0f4c14d9e0196760280637` | | `components/esp_new_jpeg` | `github.com/espressif/esp-adf-libs`, `esp_new_jpeg` | 0.6.1, commit `35c37e1656db36dbf13b9aee8064d1b59f482f6c`; ESP32-S3 library SHA-256 `4205b1258ce0ef9fd9946abfc7cd5f105b08316ea567e1136cec60b3e6330896` | | `components/esp_lcd_ili9341` | `github.com/espressif/esp-bsp` | 2.0.2, commit `fc8bd325efcdef6d5802554659debba303058af6` | +| `components/esp_lcd_gc9a01` | ESP LCD GC9A01 production driver | 2.0.4; production source only, local CMake metadata | +| `components/esp_lcd_nv3023` | ESP LCD NV3023 production driver | 1.0.1; production source only, local CMake metadata | +| `components/esp_lcd_sh8601` | ESP LCD SH8601 production driver | 1.0.2; production source only, local CMake metadata | | `components/lvgl` | `github.com/lvgl/lvgl` | 9.5.0, commit `85aa60d18b3d5e5588d7b247abf90198f07c8a63` | | `components/esp_lvgl_port` | `github.com/espressif/esp-bsp`, `components/esp_lvgl_port` | 2.8.0~1, commit `d14ff131266bf1392efff88db72cb4638897507c` | | `components/esp_lcd_spd2010` | ESP Component Registry `espressif/esp_lcd_spd2010` | 2.0.0~1, commit `12f6ca1182ec48889b17ec570fadaaf267cb336e` | @@ -24,12 +27,16 @@ redistribution terms are documented separately in [THIRD_PARTY_NOTICES.md](THIRD | `components/esp_lcd_touch` | ESP Component Registry `espressif/esp_lcd_touch` | 1.2.1, commit `c927778a85eed239dd403c1719d4f543ad56e693`, content hash `3f85a7d95af876f1a6ecca8eb90a81614890d0f03a038390804e5a77e2caf862` | | `components/esp_lcd_touch_cst816s` | ESP Component Registry `espressif/esp_lcd_touch_cst816s` | 1.1.1~1, commit `ab5696c61e761b272a163740f94b0e5720c3dde4` | | `components/esp_lcd_touch_cst9217` | ESP Component Registry `waveshare/esp_lcd_touch_cst9217` | 1.0.4, commit `43e6e696bbf0cf8671a97fcfe02c4c720e4e4d27`, content hash `27a00845832b7987cacf4c4125cbed5415d940987583065b01870ac080930856` | +| `components/esp_lcd_touch_ft5x06` | ESP Component Registry `espressif/esp_lcd_touch_ft5x06` | 1.1.0~1; production source and Apache-2.0 license retained | +| `components/esp_lcd_touch_gt911` | ESP Component Registry `espressif/esp_lcd_touch_gt911` | 1.2.0~2; production source and Apache-2.0 license retained | | `components/esp_io_expander` | ESP Component Registry `espressif/esp_io_expander` | 1.2.1, commit `eb76dc6ecf21ccc4ee7ee58bfea3d3d31fa090cf` | | `components/esp_io_expander_tca95xx_16bit` | ESP Component Registry `espressif/esp_io_expander_tca95xx_16bit` | 2.0.2, commit `53f6127ba3a1dd80fbdf9a76b759ccd1a8dc0101` | | `components/button` | ESP Component Registry `espressif/button` | 4.2.0, commit `5f9cb98ae4d0e8153c4b4d1accf471214e5b6fe8` | | `components/knob` | ESP Component Registry `espressif/knob` | 1.1.0, commit `5f9cb98ae4d0e8153c4b4d1accf471214e5b6fe8` | | `components/cmake_utilities` | ESP Component Registry `espressif/cmake_utilities` | 0.5.0 | | `components/m5pm1` | ESP Component Registry `m5stack/m5pm1` | 1.0.7, content hash `731f79d0629e245787440f5419aac5d7a82befeb25f97689d6a2d0331a24a72d` | +| `components/m5ioe1` | ESP Component Registry `m5stack/m5ioe1` | 1.0.9; upstream MIT license retained | +| `components/mybot_platform/src/drivers/audio/es8374_codec.c`, `components/mybot_platform/src/internal/audio/es8374_codec.h`, `components/mybot_platform/src/internal/audio/es_common.h` | Espressif `esp_codec_dev` ES8374 device source | Same 1.5.11 source baseline as `components/esp_codec_dev`; vendored inside the platform component because the local codec-dev source set omits ES8374 | | Pairing `prompt.ogg` and `0.ogg` through `9.ogg` under `components/mybot_platform/assets/locales/{en-US,zh-CN}` | `github.com/junlon2006/mybot`, `assets/locales` | commit `7854c1d5798004485aa1947ff55116bdb7ab0237`; 16 kHz mono s16 PCM converted to 20 ms Opus/Ogg; source/output SHA-256 and encoding command in `components/mybot_platform/assets/locales/SOURCES.json` | | `components/mybot_platform/src/drivers/display/renderers/lvgl/lvgl_fonts.c`, `components/mybot_platform/src/drivers/display/assets/licenses/LVGL_VIEW_FONT_LICENSE.txt` | `github.com/lvgl/lvgl`, `scripts/built_in_font/SourceHanSansSC-Normal.otf` | LVGL 9.5.0 source font SHA-256 `1ee89e1669362dee13851129c0a8a791a87521eb4148e5efbf5d26596738e25b`; generated 20 px, 4 bpp static UI subset | | `components/mybot_platform/assets/ui/noto_emoji`, `components/mybot_platform/src/drivers/display/renderers/lvgl/lvgl_assets.c` | Noto Color Emoji font glyphs rasterized by `github.com/78/noto-fonts`, `png/noto-color-emoji_64` | 2.0.0, commit `d45dbc64052d57048f20ab1770074172ce9eb53b`; per-image SHA-256 values and glyph provenance in `assets/ui/noto_emoji/SOURCES.json` | @@ -59,7 +66,7 @@ are not vendored into this repository. Firmware integration differences are limited to the active ESP32-S3 build: - Only `m5stack-core-s3` selects the process-gray pixel-glasses view. CMake chooses it from - `MYBOT_BOARD`, with no manual renderer flag; the other fifteen profiles retain their current + `MYBOT_BOARD`, with no manual renderer flag; the other profiles retain their current UI/hardware. The original Source Han Sans 20 px UI subset is reused, while the CoreS3 path uses fixed widgets/curves, five immutable A8 masks (13,631 alpha bytes), and the terminal subsets above instead of the shared view's four emoji. Audio, camera/video policy, input, diff --git a/components/esp_lcd_gc9a01/CMakeLists.txt b/components/esp_lcd_gc9a01/CMakeLists.txt new file mode 100644 index 0000000..a7a6d7a --- /dev/null +++ b/components/esp_lcd_gc9a01/CMakeLists.txt @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 +idf_component_register( + SRCS "esp_lcd_gc9a01.c" + INCLUDE_DIRS "include" + REQUIRES esp_lcd + PRIV_REQUIRES driver +) +target_compile_definitions(${COMPONENT_LIB} PRIVATE + ESP_LCD_GC9A01_VER_MAJOR=2 + ESP_LCD_GC9A01_VER_MINOR=0 + ESP_LCD_GC9A01_VER_PATCH=4 +) diff --git a/components/esp_lcd_gc9a01/LICENSE b/components/esp_lcd_gc9a01/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/components/esp_lcd_gc9a01/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/esp_lcd_gc9a01/esp_lcd_gc9a01.c b/components/esp_lcd_gc9a01/esp_lcd_gc9a01.c new file mode 100644 index 0000000..34c4211 --- /dev/null +++ b/components/esp_lcd_gc9a01/esp_lcd_gc9a01.c @@ -0,0 +1,393 @@ +/* + * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_check.h" + +#include "esp_lcd_gc9a01.h" + +static const char *TAG = "gc9a01"; + +static esp_err_t panel_gc9a01_del(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9a01_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9a01_init(esp_lcd_panel_t *panel); +static esp_err_t panel_gc9a01_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t panel_gc9a01_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_gc9a01_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_gc9a01_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_gc9a01_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_gc9a01_disp_on_off(esp_lcd_panel_t *panel, bool off); + +typedef struct { + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + bool reset_level; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register + const gc9a01_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; +} gc9a01_panel_t; + +esp_err_t esp_lcd_new_panel_gc9a01(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) +{ + esp_err_t ret = ESP_OK; + gc9a01_panel_t *gc9a01 = NULL; + gpio_config_t io_conf = { 0 }; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + gc9a01 = (gc9a01_panel_t *)calloc(1, sizeof(gc9a01_panel_t)); + ESP_GOTO_ON_FALSE(gc9a01, ESP_ERR_NO_MEM, err, TAG, "no mem for gc9a01 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) { + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + switch (panel_dev_config->color_space) { + case ESP_LCD_COLOR_SPACE_RGB: + gc9a01->madctl_val = 0; + break; + case ESP_LCD_COLOR_SPACE_BGR: + gc9a01->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); + break; + } +#elif ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0) + switch (panel_dev_config->rgb_endian) { + case LCD_RGB_ENDIAN_RGB: + gc9a01->madctl_val = 0; + break; + case LCD_RGB_ENDIAN_BGR: + gc9a01->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); + break; + } +#else + switch (panel_dev_config->rgb_ele_order) { + case LCD_RGB_ELEMENT_ORDER_RGB: + gc9a01->madctl_val = 0; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + gc9a01->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb element order"); + break; + } +#endif + + switch (panel_dev_config->bits_per_pixel) { + case 16: // RGB565 + gc9a01->colmod_val = 0x55; + gc9a01->fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + gc9a01->colmod_val = 0x66; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + gc9a01->fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + gc9a01->io = io; + gc9a01->reset_gpio_num = panel_dev_config->reset_gpio_num; + gc9a01->reset_level = panel_dev_config->flags.reset_active_high; + if (panel_dev_config->vendor_config) { + gc9a01->init_cmds = ((gc9a01_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; + gc9a01->init_cmds_size = ((gc9a01_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; + } + gc9a01->base.del = panel_gc9a01_del; + gc9a01->base.reset = panel_gc9a01_reset; + gc9a01->base.init = panel_gc9a01_init; + gc9a01->base.draw_bitmap = panel_gc9a01_draw_bitmap; + gc9a01->base.invert_color = panel_gc9a01_invert_color; + gc9a01->base.set_gap = panel_gc9a01_set_gap; + gc9a01->base.mirror = panel_gc9a01_mirror; + gc9a01->base.swap_xy = panel_gc9a01_swap_xy; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + gc9a01->base.disp_off = panel_gc9a01_disp_on_off; +#else + gc9a01->base.disp_on_off = panel_gc9a01_disp_on_off; +#endif + *ret_panel = &(gc9a01->base); + ESP_LOGD(TAG, "new gc9a01 panel @%p", gc9a01); + + ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9A01_VER_MAJOR, ESP_LCD_GC9A01_VER_MINOR, + ESP_LCD_GC9A01_VER_PATCH); + + return ESP_OK; + +err: + if (gc9a01) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(gc9a01); + } + return ret; +} + +static esp_err_t panel_gc9a01_del(esp_lcd_panel_t *panel) +{ + gc9a01_panel_t *gc9a01 = __containerof(panel, gc9a01_panel_t, base); + + if (gc9a01->reset_gpio_num >= 0) { + gpio_reset_pin(gc9a01->reset_gpio_num); + } + ESP_LOGD(TAG, "del gc9a01 panel @%p", gc9a01); + free(gc9a01); + return ESP_OK; +} + +static esp_err_t panel_gc9a01_reset(esp_lcd_panel_t *panel) +{ + gc9a01_panel_t *gc9a01 = __containerof(panel, gc9a01_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9a01->io; + + // perform hardware reset + if (gc9a01->reset_gpio_num >= 0) { + gpio_set_level(gc9a01->reset_gpio_num, gc9a01->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(gc9a01->reset_gpio_num, !gc9a01->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + } else { // perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command + } + + return ESP_OK; +} + +static const gc9a01_lcd_init_cmd_t vendor_specific_init_default[] = { +// {cmd, { data }, data_size, delay_ms} + // Enable Inter Register + {0xfe, (uint8_t []){0x00}, 0, 0}, + {0xef, (uint8_t []){0x00}, 0, 0}, + {0xeb, (uint8_t []){0x14}, 1, 0}, + {0x84, (uint8_t []){0x60}, 1, 0}, + {0x85, (uint8_t []){0xff}, 1, 0}, + {0x86, (uint8_t []){0xff}, 1, 0}, + {0x87, (uint8_t []){0xff}, 1, 0}, + {0x8e, (uint8_t []){0xff}, 1, 0}, + {0x8f, (uint8_t []){0xff}, 1, 0}, + {0x88, (uint8_t []){0x0a}, 1, 0}, + {0x89, (uint8_t []){0x23}, 1, 0}, + {0x8a, (uint8_t []){0x00}, 1, 0}, + {0x8b, (uint8_t []){0x80}, 1, 0}, + {0x8c, (uint8_t []){0x01}, 1, 0}, + {0x8d, (uint8_t []){0x03}, 1, 0}, + {0x90, (uint8_t []){0x08, 0x08, 0x08, 0x08}, 4, 0}, + {0xff, (uint8_t []){0x60, 0x01, 0x04}, 3, 0}, + {0xC3, (uint8_t []){0x13}, 1, 0}, + {0xC4, (uint8_t []){0x13}, 1, 0}, + {0xC9, (uint8_t []){0x30}, 1, 0}, + {0xbe, (uint8_t []){0x11}, 1, 0}, + {0xe1, (uint8_t []){0x10, 0x0e}, 2, 0}, + {0xdf, (uint8_t []){0x21, 0x0c, 0x02}, 3, 0}, + // Set gamma + {0xF0, (uint8_t []){0x45, 0x09, 0x08, 0x08, 0x26, 0x2a}, 6, 0}, + {0xF1, (uint8_t []){0x43, 0x70, 0x72, 0x36, 0x37, 0x6f}, 6, 0}, + {0xF2, (uint8_t []){0x45, 0x09, 0x08, 0x08, 0x26, 0x2a}, 6, 0}, + {0xF3, (uint8_t []){0x43, 0x70, 0x72, 0x36, 0x37, 0x6f}, 6, 0}, + {0xed, (uint8_t []){0x1b, 0x0b}, 2, 0}, + {0xae, (uint8_t []){0x77}, 1, 0}, + {0xcd, (uint8_t []){0x63}, 1, 0}, + {0x70, (uint8_t []){0x07, 0x07, 0x04, 0x0e, 0x0f, 0x09, 0x07, 0x08, 0x03}, 9, 0}, + {0xE8, (uint8_t []){0x34}, 1, 0}, // 4 dot inversion + {0x60, (uint8_t []){0x38, 0x0b, 0x6D, 0x6D, 0x39, 0xf0, 0x6D, 0x6D}, 8, 0}, + {0x61, (uint8_t []){0x38, 0xf4, 0x6D, 0x6D, 0x38, 0xf7, 0x6D, 0x6D}, 8, 0}, + {0x62, (uint8_t []){0x38, 0x0D, 0x71, 0xED, 0x70, 0x70, 0x38, 0x0F, 0x71, 0xEF, 0x70, 0x70}, 12, 0}, + {0x63, (uint8_t []){0x38, 0x11, 0x71, 0xF1, 0x70, 0x70, 0x38, 0x13, 0x71, 0xF3, 0x70, 0x70}, 12, 0}, + {0x64, (uint8_t []){0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07}, 7, 0}, + {0x66, (uint8_t []){0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00}, 10, 0}, + {0x67, (uint8_t []){0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98}, 10, 0}, + {0x74, (uint8_t []){0x10, 0x45, 0x80, 0x00, 0x00, 0x4E, 0x00}, 7, 0}, + {0x98, (uint8_t []){0x3e, 0x07}, 2, 0}, + {0x99, (uint8_t []){0x3e, 0x07}, 2, 0}, +}; + +static esp_err_t panel_gc9a01_init(esp_lcd_panel_t *panel) +{ + gc9a01_panel_t *gc9a01 = __containerof(panel, gc9a01_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9a01->io; + + // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + gc9a01->madctl_val, + }, 1), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) { + gc9a01->colmod_val, + }, 1), TAG, "send command failed"); + + const gc9a01_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (gc9a01->init_cmds) { + init_cmds = gc9a01->init_cmds; + init_cmds_size = gc9a01->init_cmds_size; + } else { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9a01_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++) { + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd) { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + gc9a01->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + gc9a01->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) { + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_gc9a01_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + gc9a01_panel_t *gc9a01 = __containerof(panel, gc9a01_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = gc9a01->io; + + x_start += gc9a01->x_gap; + x_end += gc9a01->x_gap; + y_start += gc9a01->y_gap; + y_end += gc9a01->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) { + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) { + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * gc9a01->fb_bits_per_pixel / 8; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "send color failed"); + + return ESP_OK; +} + +static esp_err_t panel_gc9a01_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + gc9a01_panel_t *gc9a01 = __containerof(panel, gc9a01_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9a01->io; + int command = 0; + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9a01_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + gc9a01_panel_t *gc9a01 = __containerof(panel, gc9a01_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9a01->io; + if (mirror_x) { + gc9a01->madctl_val |= LCD_CMD_MX_BIT; + } else { + gc9a01->madctl_val &= ~LCD_CMD_MX_BIT; + } + if (mirror_y) { + gc9a01->madctl_val |= LCD_CMD_MY_BIT; + } else { + gc9a01->madctl_val &= ~LCD_CMD_MY_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + gc9a01->madctl_val + }, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9a01_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) +{ + gc9a01_panel_t *gc9a01 = __containerof(panel, gc9a01_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9a01->io; + if (swap_axes) { + gc9a01->madctl_val |= LCD_CMD_MV_BIT; + } else { + gc9a01->madctl_val &= ~LCD_CMD_MV_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + gc9a01->madctl_val + }, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_gc9a01_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) +{ + gc9a01_panel_t *gc9a01 = __containerof(panel, gc9a01_panel_t, base); + gc9a01->x_gap = x_gap; + gc9a01->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_gc9a01_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + gc9a01_panel_t *gc9a01 = __containerof(panel, gc9a01_panel_t, base); + esp_lcd_panel_io_handle_t io = gc9a01->io; + int command = 0; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + on_off = !on_off; +#endif + + if (on_off) { + command = LCD_CMD_DISPON; + } else { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/components/esp_lcd_gc9a01/include/esp_lcd_gc9a01.h b/components/esp_lcd_gc9a01/include/esp_lcd_gc9a01.h new file mode 100644 index 0000000..a2c3531 --- /dev/null +++ b/components/esp_lcd_gc9a01/include/esp_lcd_gc9a01.h @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +/** + * @file + * @brief ESP LCD: GC9A01 + */ + +#pragma once + +#include "esp_lcd_panel_vendor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* +#include +#include "esp_idf_version.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "esp_check.h" + +#include "esp_lcd_nv3023.h" + +static const char *TAG = "lcd_panel.nv3023"; + +static esp_err_t panel_nv3023_del(esp_lcd_panel_t *panel); +static esp_err_t panel_nv3023_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_nv3023_init(esp_lcd_panel_t *panel); +static esp_err_t panel_nv3023_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t panel_nv3023_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_nv3023_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_nv3023_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_nv3023_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_nv3023_disp_on_off(esp_lcd_panel_t *panel, bool off); + +typedef struct { + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + gpio_num_t reset_gpio_num; + bool reset_level; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register + const nv3023_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; +} nv3023_panel_t; + +esp_err_t esp_lcd_new_panel_nv3023(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) +{ + esp_err_t ret = ESP_OK; + nv3023_panel_t *nv3023 = NULL; + gpio_config_t io_conf = { 0 }; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + nv3023 = (nv3023_panel_t *)calloc(1, sizeof(nv3023_panel_t)); + ESP_GOTO_ON_FALSE(nv3023, ESP_ERR_NO_MEM, err, TAG, "no mem for nv3023 panel"); + + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + if (panel_dev_config->reset_gpio_num != GPIO_NUM_NC) { +#else + if (panel_dev_config->reset_gpio_num >= 0) { +#endif + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + switch (panel_dev_config->color_space) { + case ESP_LCD_COLOR_SPACE_RGB: + nv3023->madctl_val = 0; + break; + case ESP_LCD_COLOR_SPACE_BGR: + nv3023->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); + break; + } +#elif ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(6, 0, 0) + switch (panel_dev_config->rgb_endian) { + case LCD_RGB_ENDIAN_RGB: + nv3023->madctl_val = 0; + break; + case LCD_RGB_ENDIAN_BGR: + nv3023->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); + break; + } +#else + switch (panel_dev_config->rgb_ele_order) { + case LCD_RGB_ELEMENT_ORDER_RGB: + nv3023->madctl_val = 0; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + nv3023->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb element order"); + break; + } +#endif + + switch (panel_dev_config->bits_per_pixel) { + case 12: // RGB444 + nv3023->colmod_val = 0x33; + nv3023->fb_bits_per_pixel = 16; + break; + case 16: // RGB565 + nv3023->colmod_val = 0x55; + nv3023->fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + nv3023->colmod_val = 0x66; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + nv3023->fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + nv3023->io = io; + nv3023->reset_gpio_num = panel_dev_config->reset_gpio_num; + nv3023->reset_level = panel_dev_config->flags.reset_active_high; + if (panel_dev_config->vendor_config) { + nv3023->init_cmds = ((nv3023_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; + nv3023->init_cmds_size = ((nv3023_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; + } + nv3023->base.del = panel_nv3023_del; + nv3023->base.reset = panel_nv3023_reset; + nv3023->base.init = panel_nv3023_init; + nv3023->base.draw_bitmap = panel_nv3023_draw_bitmap; + nv3023->base.invert_color = panel_nv3023_invert_color; + nv3023->base.set_gap = panel_nv3023_set_gap; + nv3023->base.mirror = panel_nv3023_mirror; + nv3023->base.swap_xy = panel_nv3023_swap_xy; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + nv3023->base.disp_off = panel_nv3023_disp_on_off; +#else + nv3023->base.disp_on_off = panel_nv3023_disp_on_off; +#endif + *ret_panel = &(nv3023->base); + ESP_LOGD(TAG, "new nv3023 panel @%p", nv3023); + + ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_NV3023_VER_MAJOR, ESP_LCD_NV3023_VER_MINOR, + ESP_LCD_NV3023_VER_PATCH); + + return ESP_OK; + +err: + if (nv3023) { +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + if (panel_dev_config->reset_gpio_num != GPIO_NUM_NC) { +#else + if (panel_dev_config->reset_gpio_num >= 0) { +#endif + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(nv3023); + } + return ret; +} + +static esp_err_t panel_nv3023_del(esp_lcd_panel_t *panel) +{ + nv3023_panel_t *nv3023 = __containerof(panel, nv3023_panel_t, base); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + if (nv3023->reset_gpio_num != GPIO_NUM_NC) { +#else + if (nv3023->reset_gpio_num >= 0) { +#endif + gpio_reset_pin(nv3023->reset_gpio_num); + } + ESP_LOGD(TAG, "del nv3023 panel @%p", nv3023); + free(nv3023); + return ESP_OK; +} + +static esp_err_t panel_nv3023_reset(esp_lcd_panel_t *panel) +{ + nv3023_panel_t *nv3023 = __containerof(panel, nv3023_panel_t, base); + esp_lcd_panel_io_handle_t io = nv3023->io; + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + if (nv3023->reset_gpio_num != GPIO_NUM_NC) { +#else + if (nv3023->reset_gpio_num >= 0) { +#endif + gpio_set_level(nv3023->reset_gpio_num, nv3023->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(nv3023->reset_gpio_num, !nv3023->reset_level); + vTaskDelay(pdMS_TO_TICKS(120)); + } else { + esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0); + vTaskDelay(pdMS_TO_TICKS(120)); + } + + return ESP_OK; +} + +// Modified by MakerM0 +// Driver: nv3023, 0.85'TFT +static const nv3023_lcd_init_cmd_t vendor_specific_init_default[] = { +// {cmd, { data }, data_size, delay_ms} + {0xff, (uint8_t []){0xa5}, 1, 0}, + {0x3e, (uint8_t []){0x09}, 1, 0}, + {0x3a, (uint8_t []){0x05}, 1, 0}, + {0x82, (uint8_t []){0x00}, 1, 0}, + {0x98, (uint8_t []){0x00}, 1, 0}, + {0x63, (uint8_t []){0x0f}, 1, 0}, + {0x64, (uint8_t []){0x0f}, 1, 0}, + {0xb4, (uint8_t []){0x34}, 1, 0}, + {0xb5, (uint8_t []){0x30}, 1, 0}, + {0x83, (uint8_t []){0x03}, 1, 0}, + {0x86, (uint8_t []){0x04}, 1, 0}, + {0x87, (uint8_t []){0x16}, 1, 0}, + {0x88, (uint8_t []){0x0a}, 1, 0}, + {0x89, (uint8_t []){0x27}, 1, 0}, + {0x93, (uint8_t []){0x63}, 1, 0}, + {0x96, (uint8_t []){0x81}, 1, 0}, + {0xc3, (uint8_t []){0x10}, 1, 0}, + {0xe6, (uint8_t []){0x00}, 1, 0}, + {0x99, (uint8_t []){0x01}, 1, 0}, + {0x70, (uint8_t []){0x09, 0x1d, 0x14, 0x0a, 0x11, 0x16, 0x38, 0x0b, 0x08, 0x3e, 0x07, 0x0d, 0x16, 0x0F,0x14,0x05}, 16, 0}, + {0xa0, (uint8_t []){0x04, 0x28, 0x0c, 0x11, 0x0b, 0x23, 0x45, 0x07, 0x0a, 0x3b, 0x0d, 0x18, 0x14, 0x0F,0x19,0x08}, 16, 0}, + {0xff, (uint8_t []){0x00}, 1, 0}, + {0x11, (uint8_t []){0x00}, 0, 200}, + // {0x36, (uint8_t []){0x88}, 1, 0}, + // {0x2a, (uint8_t []){0x00, 0x00, 0x00, 0xef}, 4, 0}, + // {0x2b, (uint8_t []){0x00, 0x00, 0x01, 0x1b}, 4, 0}, + {0x29, (uint8_t []){0}, 0, 10}, + {0x2c, (uint8_t []){0}, 0, 0}, +}; + +static esp_err_t panel_nv3023_init(esp_lcd_panel_t *panel) +{ + nv3023_panel_t *nv3023 = __containerof(panel, nv3023_panel_t, base); + esp_lcd_panel_io_handle_t io = nv3023->io; + + // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + nv3023->madctl_val, + }, 1), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) { + nv3023->colmod_val, + }, 1), TAG, "send command failed"); + + const nv3023_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (nv3023->init_cmds) { + init_cmds = nv3023->init_cmds; + init_cmds_size = nv3023->init_cmds_size; + } else { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(nv3023_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++) { + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd) { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + nv3023->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + nv3023->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) { + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_nv3023_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + nv3023_panel_t *nv3023 = __containerof(panel, nv3023_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = nv3023->io; + + x_start += nv3023->x_gap; + x_end += nv3023->x_gap; + y_start += nv3023->y_gap; + y_end += nv3023->y_gap; + + // define an area of frame memory where MCU can access + esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) { + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, 4); + esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) { + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, 4); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * nv3023->fb_bits_per_pixel / 8; + esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len); + + return ESP_OK; +} + +static esp_err_t panel_nv3023_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + nv3023_panel_t *nv3023 = __containerof(panel, nv3023_panel_t, base); + esp_lcd_panel_io_handle_t io = nv3023->io; + int command = 0; + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_nv3023_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + nv3023_panel_t *nv3023 = __containerof(panel, nv3023_panel_t, base); + esp_lcd_panel_io_handle_t io = nv3023->io; + if (mirror_x) { + nv3023->madctl_val |= LCD_CMD_MX_BIT; + } else { + nv3023->madctl_val &= ~LCD_CMD_MX_BIT; + } + if (mirror_y) { + nv3023->madctl_val |= LCD_CMD_MY_BIT; + } else { + nv3023->madctl_val &= ~LCD_CMD_MY_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + nv3023->madctl_val + }, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_nv3023_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) +{ + nv3023_panel_t *nv3023 = __containerof(panel, nv3023_panel_t, base); + esp_lcd_panel_io_handle_t io = nv3023->io; + if (swap_axes) { + nv3023->madctl_val |= LCD_CMD_MV_BIT; + } else { + nv3023->madctl_val &= ~LCD_CMD_MV_BIT; + } + esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + nv3023->madctl_val + }, 1); + return ESP_OK; +} + +static esp_err_t panel_nv3023_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) +{ + nv3023_panel_t *nv3023 = __containerof(panel, nv3023_panel_t, base); + nv3023->x_gap = x_gap; + nv3023->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_nv3023_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + nv3023_panel_t *nv3023 = __containerof(panel, nv3023_panel_t, base); + esp_lcd_panel_io_handle_t io = nv3023->io; + int command = 0; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + on_off = !on_off; +#endif + + if (on_off) { + command = LCD_CMD_DISPON; + } else { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/components/esp_lcd_nv3023/include/esp_lcd_nv3023.h b/components/esp_lcd_nv3023/include/esp_lcd_nv3023.h new file mode 100644 index 0000000..cc1acac --- /dev/null +++ b/components/esp_lcd_nv3023/include/esp_lcd_nv3023.h @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "esp_lcd_panel_vendor.h" +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" +#include "esp_check.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_commands.h" +#include "esp_log.h" + +#include "esp_lcd_sh8601.h" + +#define LCD_OPCODE_WRITE_CMD (0x02ULL) +#define LCD_OPCODE_READ_CMD (0x03ULL) +#define LCD_OPCODE_WRITE_COLOR (0x32ULL) + +static const char *TAG = "sh8601"; + +static esp_err_t panel_sh8601_del(esp_lcd_panel_t *panel); +static esp_err_t panel_sh8601_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_sh8601_init(esp_lcd_panel_t *panel); +static esp_err_t panel_sh8601_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t panel_sh8601_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_sh8601_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_sh8601_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_sh8601_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_sh8601_disp_on_off(esp_lcd_panel_t *panel, bool off); + +typedef struct { + esp_lcd_panel_t base; + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + int x_gap; + int y_gap; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save surrent value of LCD_CMD_COLMOD register + const sh8601_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; + struct { + unsigned int use_qspi_interface: 1; + unsigned int reset_level: 1; + } flags; +} sh8601_panel_t; + +esp_err_t esp_lcd_new_panel_sh8601(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel) +{ + ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + esp_err_t ret = ESP_OK; + sh8601_panel_t *sh8601 = NULL; + sh8601 = calloc(1, sizeof(sh8601_panel_t)); + ESP_GOTO_ON_FALSE(sh8601, ESP_ERR_NO_MEM, err, TAG, "no mem for sh8601 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_config_t io_conf = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); + } + + switch (panel_dev_config->rgb_ele_order) { + case LCD_RGB_ELEMENT_ORDER_RGB: + sh8601->madctl_val = 0; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + sh8601->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order"); + break; + } + + uint8_t fb_bits_per_pixel = 0; + switch (panel_dev_config->bits_per_pixel) { + case 16: // RGB565 + sh8601->colmod_val = 0x55; + fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + sh8601->colmod_val = 0x66; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + fb_bits_per_pixel = 24; + break; + case 24: // RGB888 + sh8601->colmod_val = 0x77; + fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + sh8601->io = io; + sh8601->reset_gpio_num = panel_dev_config->reset_gpio_num; + sh8601->fb_bits_per_pixel = fb_bits_per_pixel; + sh8601_vendor_config_t *vendor_config = (sh8601_vendor_config_t *)panel_dev_config->vendor_config; + if (vendor_config) { + sh8601->init_cmds = vendor_config->init_cmds; + sh8601->init_cmds_size = vendor_config->init_cmds_size; + sh8601->flags.use_qspi_interface = vendor_config->flags.use_qspi_interface; + } + sh8601->flags.reset_level = panel_dev_config->flags.reset_active_high; + sh8601->base.del = panel_sh8601_del; + sh8601->base.reset = panel_sh8601_reset; + sh8601->base.init = panel_sh8601_init; + sh8601->base.draw_bitmap = panel_sh8601_draw_bitmap; + sh8601->base.invert_color = panel_sh8601_invert_color; + sh8601->base.set_gap = panel_sh8601_set_gap; + sh8601->base.mirror = panel_sh8601_mirror; + sh8601->base.swap_xy = panel_sh8601_swap_xy; + sh8601->base.disp_on_off = panel_sh8601_disp_on_off; + *ret_panel = &(sh8601->base); + ESP_LOGD(TAG, "new sh8601 panel @%p", sh8601); + + ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_SH8601_VER_MAJOR, ESP_LCD_SH8601_VER_MINOR, + ESP_LCD_SH8601_VER_PATCH); + + return ESP_OK; + +err: + if (sh8601) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(sh8601); + } + return ret; +} + +static esp_err_t tx_param(sh8601_panel_t *sh8601, esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size) +{ + if (sh8601->flags.use_qspi_interface) { + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24; + } + return esp_lcd_panel_io_tx_param(io, lcd_cmd, param, param_size); +} + +static esp_err_t tx_color(sh8601_panel_t *sh8601, esp_lcd_panel_io_handle_t io, int lcd_cmd, const void *param, size_t param_size) +{ + if (sh8601->flags.use_qspi_interface) { + lcd_cmd &= 0xff; + lcd_cmd <<= 8; + lcd_cmd |= LCD_OPCODE_WRITE_COLOR << 24; + } + return esp_lcd_panel_io_tx_color(io, lcd_cmd, param, param_size); +} + +static esp_err_t panel_sh8601_del(esp_lcd_panel_t *panel) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + + if (sh8601->reset_gpio_num >= 0) { + gpio_reset_pin(sh8601->reset_gpio_num); + } + ESP_LOGD(TAG, "del sh8601 panel @%p", sh8601); + free(sh8601); + return ESP_OK; +} + +static esp_err_t panel_sh8601_reset(esp_lcd_panel_t *panel) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + esp_lcd_panel_io_handle_t io = sh8601->io; + + // Perform hardware reset + if (sh8601->reset_gpio_num >= 0) { + gpio_set_level(sh8601->reset_gpio_num, sh8601->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(sh8601->reset_gpio_num, !sh8601->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(150)); + } else { // Perform software reset + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(80)); + } + + return ESP_OK; +} + +static const sh8601_lcd_init_cmd_t vendor_specific_init_default[] = { +// {cmd, { data }, data_size, delay_ms} + {0x44, (uint8_t []){0x01, 0xD1}, 2, 0}, + {0x35, (uint8_t []){0x00}, 0, 0}, + {0x53, (uint8_t []){0x20}, 1, 25}, +}; + +static esp_err_t panel_sh8601_init(esp_lcd_panel_t *panel) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + esp_lcd_panel_io_handle_t io = sh8601->io; + const sh8601_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + bool is_cmd_overwritten = false; + + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_MADCTL, (uint8_t[]) { + sh8601->madctl_val, + }, 1), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_COLMOD, (uint8_t[]) { + sh8601->colmod_val, + }, 1), TAG, "send command failed"); + + // vendor specific initialization, it can be different between manufacturers + // should consult the LCD supplier for initialization sequence code + if (sh8601->init_cmds) { + init_cmds = sh8601->init_cmds; + init_cmds_size = sh8601->init_cmds_size; + } else { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(sh8601_lcd_init_cmd_t); + } + + for (int i = 0; i < init_cmds_size; i++) { + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd) { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + sh8601->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + sh8601->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) { + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, + "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_sh8601_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position"); + esp_lcd_panel_io_handle_t io = sh8601->io; + + x_start += sh8601->x_gap; + x_end += sh8601->x_gap; + y_start += sh8601->y_gap; + y_end += sh8601->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_CASET, (uint8_t[]) { + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_RASET, (uint8_t[]) { + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * sh8601->fb_bits_per_pixel / 8; + tx_color(sh8601, io, LCD_CMD_RAMWR, color_data, len); + + return ESP_OK; +} + +static esp_err_t panel_sh8601_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + esp_lcd_panel_io_handle_t io = sh8601->io; + int command = 0; + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_sh8601_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + esp_lcd_panel_io_handle_t io = sh8601->io; + esp_err_t ret = ESP_OK; + + if (mirror_x) { + sh8601->madctl_val |= BIT(6); + } else { + sh8601->madctl_val &= ~BIT(6); + } + if (mirror_y) { + ESP_LOGE(TAG, "mirror_y is not supported by this panel"); + ret = ESP_ERR_NOT_SUPPORTED; + } + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, LCD_CMD_MADCTL, (uint8_t[]) { + sh8601->madctl_val + }, 1), TAG, "send command failed"); + return ret; +} + +static esp_err_t panel_sh8601_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) +{ + ESP_LOGE(TAG, "swap_xy is not supported by this panel"); + return ESP_ERR_NOT_SUPPORTED; +} + +static esp_err_t panel_sh8601_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + sh8601->x_gap = x_gap; + sh8601->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_sh8601_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + sh8601_panel_t *sh8601 = __containerof(panel, sh8601_panel_t, base); + esp_lcd_panel_io_handle_t io = sh8601->io; + int command = 0; + + if (on_off) { + command = LCD_CMD_DISPON; + } else { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(tx_param(sh8601, io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/components/esp_lcd_sh8601/include/esp_lcd_sh8601.h b/components/esp_lcd_sh8601/include/esp_lcd_sh8601.h new file mode 100644 index 0000000..4073ccd --- /dev/null +++ b/components/esp_lcd_sh8601/include/esp_lcd_sh8601.h @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include + +#include "esp_lcd_panel_vendor.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /* +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_touch.h" + +static const char *TAG = "FT5x06"; + +/* Registers */ +#define FT5x06_DEVICE_MODE (0x00) +#define FT5x06_GESTURE_ID (0x01) +#define FT5x06_TOUCH_POINTS (0x02) + +#define FT5x06_TOUCH1_EV_FLAG (0x03) +#define FT5x06_TOUCH1_XH (0x03) +#define FT5x06_TOUCH1_XL (0x04) +#define FT5x06_TOUCH1_YH (0x05) +#define FT5x06_TOUCH1_YL (0x06) + +#define FT5x06_TOUCH2_EV_FLAG (0x09) +#define FT5x06_TOUCH2_XH (0x09) +#define FT5x06_TOUCH2_XL (0x0A) +#define FT5x06_TOUCH2_YH (0x0B) +#define FT5x06_TOUCH2_YL (0x0C) + +#define FT5x06_TOUCH3_EV_FLAG (0x0F) +#define FT5x06_TOUCH3_XH (0x0F) +#define FT5x06_TOUCH3_XL (0x10) +#define FT5x06_TOUCH3_YH (0x11) +#define FT5x06_TOUCH3_YL (0x12) + +#define FT5x06_TOUCH4_EV_FLAG (0x15) +#define FT5x06_TOUCH4_XH (0x15) +#define FT5x06_TOUCH4_XL (0x16) +#define FT5x06_TOUCH4_YH (0x17) +#define FT5x06_TOUCH4_YL (0x18) + +#define FT5x06_TOUCH5_EV_FLAG (0x1B) +#define FT5x06_TOUCH5_XH (0x1B) +#define FT5x06_TOUCH5_XL (0x1C) +#define FT5x06_TOUCH5_YH (0x1D) +#define FT5x06_TOUCH5_YL (0x1E) + +#define FT5x06_ID_G_THGROUP (0x80) +#define FT5x06_ID_G_THPEAK (0x81) +#define FT5x06_ID_G_THCAL (0x82) +#define FT5x06_ID_G_THWATER (0x83) +#define FT5x06_ID_G_THTEMP (0x84) +#define FT5x06_ID_G_THDIFF (0x85) +#define FT5x06_ID_G_CTRL (0x86) +#define FT5x06_ID_G_TIME_ENTER_MONITOR (0x87) +#define FT5x06_ID_G_PERIODACTIVE (0x88) +#define FT5x06_ID_G_PERIODMONITOR (0x89) +#define FT5x06_ID_G_AUTO_CLB_MODE (0xA0) +#define FT5x06_ID_G_LIB_VERSION_H (0xA1) +#define FT5x06_ID_G_LIB_VERSION_L (0xA2) +#define FT5x06_ID_G_CIPHER (0xA3) +#define FT5x06_ID_G_MODE (0xA4) +#define FT5x06_ID_G_PMODE (0xA5) +#define FT5x06_ID_G_FIRMID (0xA6) +#define FT5x06_ID_G_STATE (0xA7) +#define FT5x06_ID_G_FT5201ID (0xA8) +#define FT5x06_ID_G_ERR (0xA9) + +/******************************************************************************* +* Function definitions +*******************************************************************************/ +static esp_err_t esp_lcd_touch_ft5x06_read_data(esp_lcd_touch_handle_t tp); +static bool esp_lcd_touch_ft5x06_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, + uint8_t *point_num, uint8_t max_point_num); +static esp_err_t esp_lcd_touch_ft5x06_get_track_id(esp_lcd_touch_handle_t tp, uint8_t *track_id, uint8_t max_point_num); +static esp_err_t esp_lcd_touch_ft5x06_del(esp_lcd_touch_handle_t tp); + +/* I2C read */ +static esp_err_t touch_ft5x06_i2c_write(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t data); +static esp_err_t touch_ft5x06_i2c_read(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t *data, uint8_t len); + +/* FT5x06 init */ +static esp_err_t touch_ft5x06_init(esp_lcd_touch_handle_t tp); +/* FT5x06 reset */ +static esp_err_t touch_ft5x06_reset(esp_lcd_touch_handle_t tp); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +esp_err_t esp_lcd_touch_new_i2c_ft5x06(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, + esp_lcd_touch_handle_t *out_touch) +{ + esp_err_t ret = ESP_OK; + + ESP_RETURN_ON_FALSE(io != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller io handle can't be NULL"); + ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, + "Pointer to the touch controller configuration can't be NULL"); + ESP_RETURN_ON_FALSE(out_touch != NULL, ESP_ERR_INVALID_ARG, TAG, + "Pointer to the touch controller handle can't be NULL"); + + /* Prepare main structure */ + esp_lcd_touch_handle_t esp_lcd_touch_ft5x06 = heap_caps_calloc(1, sizeof(esp_lcd_touch_t), MALLOC_CAP_DEFAULT); + ESP_GOTO_ON_FALSE(esp_lcd_touch_ft5x06, ESP_ERR_NO_MEM, err, TAG, "no mem for FT5x06 controller"); + + /* Communication interface */ + esp_lcd_touch_ft5x06->io = io; + + /* Only supported callbacks are set */ + esp_lcd_touch_ft5x06->read_data = esp_lcd_touch_ft5x06_read_data; + esp_lcd_touch_ft5x06->get_xy = esp_lcd_touch_ft5x06_get_xy; + esp_lcd_touch_ft5x06->get_track_id = esp_lcd_touch_ft5x06_get_track_id; + esp_lcd_touch_ft5x06->del = esp_lcd_touch_ft5x06_del; + + /* Mutex */ + esp_lcd_touch_ft5x06->data.lock.owner = portMUX_FREE_VAL; + + /* Save config */ + memcpy(&esp_lcd_touch_ft5x06->config, config, sizeof(esp_lcd_touch_config_t)); + + /* Prepare pin for touch interrupt */ + if (esp_lcd_touch_ft5x06->config.int_gpio_num != GPIO_NUM_NC) { + const gpio_config_t int_gpio_config = { + .mode = GPIO_MODE_INPUT, + .intr_type = (esp_lcd_touch_ft5x06->config.levels.interrupt ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE), + .pin_bit_mask = BIT64(esp_lcd_touch_ft5x06->config.int_gpio_num) + }; + ret = gpio_config(&int_gpio_config); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed"); + + /* Register interrupt callback */ + if (esp_lcd_touch_ft5x06->config.interrupt_callback) { + esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_ft5x06, esp_lcd_touch_ft5x06->config.interrupt_callback); + } + } + + /* Prepare pin for touch controller reset */ + if (esp_lcd_touch_ft5x06->config.rst_gpio_num != GPIO_NUM_NC) { + const gpio_config_t rst_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = BIT64(esp_lcd_touch_ft5x06->config.rst_gpio_num) + }; + ret = gpio_config(&rst_gpio_config); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed"); + } + + /* Reset controller */ + ret = touch_ft5x06_reset(esp_lcd_touch_ft5x06); + ESP_GOTO_ON_ERROR(ret, err, TAG, "FT5x06 reset failed"); + + /* Init controller */ + ret = touch_ft5x06_init(esp_lcd_touch_ft5x06); + ESP_GOTO_ON_ERROR(ret, err, TAG, "FT5x06 init failed"); + + *out_touch = esp_lcd_touch_ft5x06; + +err: + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (0x%x)! Touch controller FT5x06 initialization failed!", ret); + if (esp_lcd_touch_ft5x06) { + esp_lcd_touch_ft5x06_del(esp_lcd_touch_ft5x06); + } + } + + return ret; +} + +static esp_err_t esp_lcd_touch_ft5x06_read_data(esp_lcd_touch_handle_t tp) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + esp_err_t err; + uint8_t data[30]; + uint8_t points; + size_t i = 0; + + err = touch_ft5x06_i2c_read(tp, FT5x06_TOUCH_POINTS, &points, 1); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + if (points > 5 || points == 0) { + return ESP_OK; + } + + /* Number of touched points */ + points = (points > CONFIG_ESP_LCD_TOUCH_MAX_POINTS ? CONFIG_ESP_LCD_TOUCH_MAX_POINTS : points); + + err = touch_ft5x06_i2c_read(tp, FT5x06_TOUCH1_XH, data, 6 * points); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + portENTER_CRITICAL(&tp->data.lock); + + /* Number of touched points */ + tp->data.points = points; + + /* Fill all coordinates */ + for (i = 0; i < points; i++) { + tp->data.coords[i].track_id = ((data[(i * 6) + 2] & 0xf0) >> 4); + tp->data.coords[i].x = (((uint16_t)data[(i * 6) + 0] & 0x0f) << 8) + data[(i * 6) + 1]; + tp->data.coords[i].y = (((uint16_t)data[(i * 6) + 2] & 0x0f) << 8) + data[(i * 6) + 3]; + } + + portEXIT_CRITICAL(&tp->data.lock); + + return ESP_OK; +} + +static bool esp_lcd_touch_ft5x06_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, + uint8_t *point_num, uint8_t max_point_num) +{ + ESP_RETURN_ON_FALSE(tp != NULL, false, TAG, "Touch controller handle can't be NULL"); + ESP_RETURN_ON_FALSE(x != NULL, false, TAG, "Pointer to the x coordinates array can't be NULL"); + ESP_RETURN_ON_FALSE(y != NULL, false, TAG, "Pointer to the y coordinates array can't be NULL"); + ESP_RETURN_ON_FALSE(point_num != NULL, false, TAG, "Pointer to number of touch points can't be NULL"); + ESP_RETURN_ON_FALSE(max_point_num > 0, false, TAG, "Array size must be equal or larger than 1"); + + portENTER_CRITICAL(&tp->data.lock); + + /* Count of points */ + *point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points); + + for (size_t i = 0; i < *point_num; i++) { + x[i] = tp->data.coords[i].x; + y[i] = tp->data.coords[i].y; + + if (strength) { + strength[i] = tp->data.coords[i].strength; + } + } + + /* Invalidate */ + tp->data.points = 0; + + portEXIT_CRITICAL(&tp->data.lock); + + return (*point_num > 0); +} + +static esp_err_t esp_lcd_touch_ft5x06_get_track_id(esp_lcd_touch_handle_t tp, uint8_t *track_id, uint8_t max_point_num) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + ESP_RETURN_ON_FALSE(track_id != NULL, ESP_ERR_INVALID_ARG, TAG, "Pointer to the track ID array can't be NULL"); + ESP_RETURN_ON_FALSE(max_point_num > 0, ESP_ERR_INVALID_ARG, TAG, "Array size must be equal or larger than 1"); + + portENTER_CRITICAL(&tp->data.lock); + + for (int i = 0; i < max_point_num; i++) { + track_id[i] = tp->data.coords[i].track_id; + } + + portEXIT_CRITICAL(&tp->data.lock); + + return ESP_OK; +} + +static esp_err_t esp_lcd_touch_ft5x06_del(esp_lcd_touch_handle_t tp) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + /* Reset GPIO pin settings */ + if (tp->config.int_gpio_num != GPIO_NUM_NC) { + gpio_reset_pin(tp->config.int_gpio_num); + if (tp->config.interrupt_callback) { + gpio_isr_handler_remove(tp->config.int_gpio_num); + } + } + + /* Reset GPIO pin settings */ + if (tp->config.rst_gpio_num != GPIO_NUM_NC) { + gpio_reset_pin(tp->config.rst_gpio_num); + } + + free(tp); + + return ESP_OK; +} + +/******************************************************************************* +* Private API function +*******************************************************************************/ + +static esp_err_t touch_ft5x06_init(esp_lcd_touch_handle_t tp) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + esp_err_t ret = ESP_OK; + + // Valid touching detect threshold + ret |= touch_ft5x06_i2c_write(tp, FT5x06_ID_G_THGROUP, 70); + + // valid touching peak detect threshold + ret |= touch_ft5x06_i2c_write(tp, FT5x06_ID_G_THPEAK, 60); + + // Touch focus threshold + ret |= touch_ft5x06_i2c_write(tp, FT5x06_ID_G_THCAL, 16); + + // threshold when there is surface water + ret |= touch_ft5x06_i2c_write(tp, FT5x06_ID_G_THWATER, 60); + + // threshold of temperature compensation + ret |= touch_ft5x06_i2c_write(tp, FT5x06_ID_G_THTEMP, 10); + + // Touch difference threshold + ret |= touch_ft5x06_i2c_write(tp, FT5x06_ID_G_THDIFF, 20); + + // Delay to enter 'Monitor' status (s) + ret |= touch_ft5x06_i2c_write(tp, FT5x06_ID_G_TIME_ENTER_MONITOR, 2); + + // Period of 'Active' status (ms) + ret |= touch_ft5x06_i2c_write(tp, FT5x06_ID_G_PERIODACTIVE, 12); + + // Timer to enter 'idle' when in 'Monitor' (ms) + ret |= touch_ft5x06_i2c_write(tp, FT5x06_ID_G_PERIODMONITOR, 40); + + return ret; +} + +/* Reset controller */ +static esp_err_t touch_ft5x06_reset(esp_lcd_touch_handle_t tp) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + if (tp->config.rst_gpio_num != GPIO_NUM_NC) { + ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + return ESP_OK; +} + +static esp_err_t touch_ft5x06_i2c_write(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t data) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + // *INDENT-OFF* + /* Write data */ + return esp_lcd_panel_io_tx_param(tp->io, reg, (uint8_t[]){data}, 1); + // *INDENT-ON* +} + +static esp_err_t touch_ft5x06_i2c_read(esp_lcd_touch_handle_t tp, uint8_t reg, uint8_t *data, uint8_t len) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + ESP_RETURN_ON_FALSE(data != NULL, ESP_ERR_INVALID_ARG, TAG, "Pointer to the data array can't be NULL"); + + /* Read data */ + return esp_lcd_panel_io_rx_param(tp->io, reg, data, len); +} diff --git a/components/esp_lcd_touch_ft5x06/include/esp_lcd_touch_ft5x06.h b/components/esp_lcd_touch_ft5x06/include/esp_lcd_touch_ft5x06.h new file mode 100644 index 0000000..75488fc --- /dev/null +++ b/components/esp_lcd_touch_ft5x06/include/esp_lcd_touch_ft5x06.h @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LCD touch: FT5x06 + */ + +#pragma once + +#include "esp_lcd_touch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create a new FT5x06 touch driver + * + * @note The I2C communication should be initialized before use this function. + * + * @param io LCD/Touch panel IO handle + * @param config: Touch configuration + * @param out_touch: Touch instance handle + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if there is no memory for allocating main structure + */ +esp_err_t esp_lcd_touch_new_i2c_ft5x06(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, + esp_lcd_touch_handle_t *out_touch); + +/** + * @brief I2C address of the FT5x06 controller + * + */ +#define ESP_LCD_TOUCH_IO_I2C_FT5x06_ADDRESS (0x38) + +/** + * @brief Touch IO configuration structure + * + */ +#define ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG() \ + { \ + .scl_speed_hz = 100000, \ + .dev_addr = ESP_LCD_TOUCH_IO_I2C_FT5x06_ADDRESS, \ + .control_phase_bytes = 1, \ + .dc_bit_offset = 0, \ + .lcd_cmd_bits = 8, \ + .flags = \ + { \ + .disable_control_phase = 1, \ + } \ + } + + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lcd_touch_gt911/CMakeLists.txt b/components/esp_lcd_touch_gt911/CMakeLists.txt new file mode 100644 index 0000000..7cf0dee --- /dev/null +++ b/components/esp_lcd_touch_gt911/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: Apache-2.0 +idf_component_register( + SRCS "esp_lcd_touch_gt911.c" + INCLUDE_DIRS "include" + REQUIRES esp_lcd esp_lcd_touch +) diff --git a/components/esp_lcd_touch_gt911/LICENSE b/components/esp_lcd_touch_gt911/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/components/esp_lcd_touch_gt911/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/components/esp_lcd_touch_gt911/esp_lcd_touch_gt911.c b/components/esp_lcd_touch_gt911/esp_lcd_touch_gt911.c new file mode 100644 index 0000000..28faa3f --- /dev/null +++ b/components/esp_lcd_touch_gt911/esp_lcd_touch_gt911.c @@ -0,0 +1,447 @@ +/* + * SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_system.h" +#include "esp_err.h" +#include "esp_log.h" +#include "esp_check.h" +#include "driver/gpio.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_touch.h" +#include "esp_lcd_touch_gt911.h" + +static const char *TAG = "GT911"; + +/* GT911 registers */ +#define ESP_LCD_TOUCH_GT911_READ_KEY_REG (0x8093) +#define ESP_LCD_TOUCH_GT911_READ_XY_REG (0x814E) +#define ESP_LCD_TOUCH_GT911_CONFIG_REG (0x8047) +#define ESP_LCD_TOUCH_GT911_PRODUCT_ID_REG (0x8140) +#define ESP_LCD_TOUCH_GT911_ENTER_SLEEP (0x8040) + +/* GT911 support key num */ +#define ESP_GT911_TOUCH_MAX_BUTTONS (4) + +/******************************************************************************* +* Function definitions +*******************************************************************************/ +static esp_err_t esp_lcd_touch_gt911_read_data(esp_lcd_touch_handle_t tp); +static bool esp_lcd_touch_gt911_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, + uint8_t *point_num, uint8_t max_point_num); +static esp_err_t esp_lcd_touch_gt911_get_track_id(esp_lcd_touch_handle_t tp, uint8_t *track_id, uint8_t point_num); +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) +static esp_err_t esp_lcd_touch_gt911_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state); +#endif +static esp_err_t esp_lcd_touch_gt911_del(esp_lcd_touch_handle_t tp); + +/* I2C read/write */ +static esp_err_t touch_gt911_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len); +static esp_err_t touch_gt911_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data); + +/* GT911 reset */ +static esp_err_t touch_gt911_reset(esp_lcd_touch_handle_t tp); +/* Read status and config register */ +static esp_err_t touch_gt911_read_cfg(esp_lcd_touch_handle_t tp); + +/* GT911 enter/exit sleep mode */ +static esp_err_t esp_lcd_touch_gt911_enter_sleep(esp_lcd_touch_handle_t tp); +static esp_err_t esp_lcd_touch_gt911_exit_sleep(esp_lcd_touch_handle_t tp); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +esp_err_t esp_lcd_touch_new_i2c_gt911(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, + esp_lcd_touch_handle_t *out_touch) +{ + esp_err_t ret = ESP_OK; + + ESP_RETURN_ON_FALSE(io != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller io handle can't be NULL"); + ESP_RETURN_ON_FALSE(config != NULL, ESP_ERR_INVALID_ARG, TAG, + "Pointer to the touch controller configuration can't be NULL"); + ESP_RETURN_ON_FALSE(out_touch != NULL, ESP_ERR_INVALID_ARG, TAG, + "Pointer to the touch controller handle can't be NULL"); + + /* Prepare main structure */ + esp_lcd_touch_handle_t esp_lcd_touch_gt911 = heap_caps_calloc(1, sizeof(esp_lcd_touch_t), MALLOC_CAP_DEFAULT); + ESP_GOTO_ON_FALSE(esp_lcd_touch_gt911, ESP_ERR_NO_MEM, err, TAG, "no mem for GT911 controller"); + + /* Communication interface */ + esp_lcd_touch_gt911->io = io; + + /* Only supported callbacks are set */ + esp_lcd_touch_gt911->read_data = esp_lcd_touch_gt911_read_data; + esp_lcd_touch_gt911->get_xy = esp_lcd_touch_gt911_get_xy; + esp_lcd_touch_gt911->get_track_id = esp_lcd_touch_gt911_get_track_id; +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) + esp_lcd_touch_gt911->get_button_state = esp_lcd_touch_gt911_get_button_state; +#endif + esp_lcd_touch_gt911->del = esp_lcd_touch_gt911_del; + esp_lcd_touch_gt911->enter_sleep = esp_lcd_touch_gt911_enter_sleep; + esp_lcd_touch_gt911->exit_sleep = esp_lcd_touch_gt911_exit_sleep; + + /* Mutex */ + esp_lcd_touch_gt911->data.lock.owner = portMUX_FREE_VAL; + + /* Save config */ + memcpy(&esp_lcd_touch_gt911->config, config, sizeof(esp_lcd_touch_config_t)); + esp_lcd_touch_io_gt911_config_t *gt911_config = (esp_lcd_touch_io_gt911_config_t *) + esp_lcd_touch_gt911->config.driver_data; + + /* Prepare pin for touch controller reset */ + if (esp_lcd_touch_gt911->config.rst_gpio_num != GPIO_NUM_NC) { + const gpio_config_t rst_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.rst_gpio_num) + }; + ret = gpio_config(&rst_gpio_config); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed"); + } + + if (gt911_config && esp_lcd_touch_gt911->config.rst_gpio_num != GPIO_NUM_NC + && esp_lcd_touch_gt911->config.int_gpio_num != GPIO_NUM_NC) { + /* Prepare pin for touch controller int */ + const gpio_config_t int_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .intr_type = GPIO_INTR_DISABLE, + .pull_down_en = 0, + .pull_up_en = 1, + .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num), + }; + ret = gpio_config(&int_gpio_config); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed"); + + ESP_RETURN_ON_ERROR(gpio_set_level(esp_lcd_touch_gt911->config.rst_gpio_num, esp_lcd_touch_gt911->config.levels.reset), + TAG, "GPIO set level error!"); + ESP_RETURN_ON_ERROR(gpio_set_level(esp_lcd_touch_gt911->config.int_gpio_num, 0), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(10)); + + /* Select I2C addr, set output high or low */ + uint32_t gpio_level; + if (ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP == gt911_config->dev_addr) { + gpio_level = 1; + } else if (ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS == gt911_config->dev_addr) { + gpio_level = 0; + } else { + gpio_level = 0; + ESP_LOGE(TAG, "Addr (0x%X) is invalid", gt911_config->dev_addr); + } + ESP_RETURN_ON_ERROR(gpio_set_level(esp_lcd_touch_gt911->config.int_gpio_num, gpio_level), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(1)); + + ESP_RETURN_ON_ERROR(gpio_set_level(esp_lcd_touch_gt911->config.rst_gpio_num, !esp_lcd_touch_gt911->config.levels.reset), + TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(10)); + + vTaskDelay(pdMS_TO_TICKS(50)); + } else { + ESP_LOGI(TAG, "I2C address initialization procedure skipped - using default GT9xx setup"); + /* Reset controller */ + ret = touch_gt911_reset(esp_lcd_touch_gt911); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 reset failed"); + } + + /* Prepare pin for touch interrupt */ + if (esp_lcd_touch_gt911->config.int_gpio_num != GPIO_NUM_NC) { + const gpio_config_t int_gpio_config = { + .mode = GPIO_MODE_INPUT, + .intr_type = (esp_lcd_touch_gt911->config.levels.interrupt ? GPIO_INTR_POSEDGE : GPIO_INTR_NEGEDGE), + .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num) + }; + ret = gpio_config(&int_gpio_config); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GPIO config failed"); + + /* Register interrupt callback */ + if (esp_lcd_touch_gt911->config.interrupt_callback) { + esp_lcd_touch_register_interrupt_callback(esp_lcd_touch_gt911, esp_lcd_touch_gt911->config.interrupt_callback); + } + } + + /* Read status and config info */ + ret = touch_gt911_read_cfg(esp_lcd_touch_gt911); + ESP_GOTO_ON_ERROR(ret, err, TAG, "GT911 init failed"); + + *out_touch = esp_lcd_touch_gt911; + +err: + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Error (0x%x)! Touch controller GT911 initialization failed!", ret); + if (esp_lcd_touch_gt911) { + esp_lcd_touch_gt911_del(esp_lcd_touch_gt911); + } + } + + return ret; +} + +static esp_err_t esp_lcd_touch_gt911_enter_sleep(esp_lcd_touch_handle_t tp) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + esp_err_t err = touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_ENTER_SLEEP, 0x05); + ESP_RETURN_ON_ERROR(err, TAG, "Enter Sleep failed!"); + + return ESP_OK; +} + +static esp_err_t esp_lcd_touch_gt911_exit_sleep(esp_lcd_touch_handle_t tp) +{ + esp_err_t ret; + esp_lcd_touch_handle_t esp_lcd_touch_gt911 = tp; + + if (esp_lcd_touch_gt911->config.int_gpio_num != GPIO_NUM_NC) { + const gpio_config_t int_gpio_config_high = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num) + }; + ret = gpio_config(&int_gpio_config_high); + ESP_RETURN_ON_ERROR(ret, TAG, "High GPIO config failed"); + gpio_set_level(esp_lcd_touch_gt911->config.int_gpio_num, 1); + + vTaskDelay(pdMS_TO_TICKS(5)); + + const gpio_config_t int_gpio_config_float = { + .mode = GPIO_MODE_OUTPUT_OD, + .pin_bit_mask = BIT64(esp_lcd_touch_gt911->config.int_gpio_num) + }; + ret = gpio_config(&int_gpio_config_float); + ESP_RETURN_ON_ERROR(ret, TAG, "Float GPIO config failed"); + } + + return ESP_OK; +} + +static esp_err_t esp_lcd_touch_gt911_read_data(esp_lcd_touch_handle_t tp) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + esp_err_t err; + uint8_t buf[41]; + uint8_t touch_cnt = 0; + uint8_t clear = 0; + size_t i = 0; + + err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, buf, 1); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + /* Any touch data? */ + if ((buf[0] & 0x80) == 0x00) { + touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear); +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) + } else if ((buf[0] & 0x10) == 0x10) { + /* Read all keys */ + uint8_t key_max = ((ESP_GT911_TOUCH_MAX_BUTTONS < CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS) ? \ + (ESP_GT911_TOUCH_MAX_BUTTONS) : (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS)); + err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_KEY_REG, &buf[0], key_max); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + /* Clear all */ + touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear); + ESP_RETURN_ON_ERROR(err, TAG, "I2C write error!"); + + portENTER_CRITICAL(&tp->data.lock); + + /* Buttons count */ + tp->data.buttons = key_max; + for (i = 0; i < key_max; i++) { + tp->data.button[i].status = buf[i] ? 1 : 0; + } + + portEXIT_CRITICAL(&tp->data.lock); +#endif + } else if ((buf[0] & 0x80) == 0x80) { + portENTER_CRITICAL(&tp->data.lock); + /* Invalidate */ + tp->data.points = 0; +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) + for (i = 0; i < CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS; i++) { + tp->data.button[i].status = 0; + } +#endif + portEXIT_CRITICAL(&tp->data.lock); + /* Count of touched points */ + touch_cnt = buf[0] & 0x0f; + if (touch_cnt > 5 || touch_cnt == 0) { + touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear); + return ESP_OK; + } + + /* Read all points */ + err = touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG + 1, &buf[1], touch_cnt * 8); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + /* Clear all */ + err = touch_gt911_i2c_write(tp, ESP_LCD_TOUCH_GT911_READ_XY_REG, clear); + ESP_RETURN_ON_ERROR(err, TAG, "I2C read error!"); + + portENTER_CRITICAL(&tp->data.lock); + + /* Number of touched points */ + touch_cnt = (touch_cnt > CONFIG_ESP_LCD_TOUCH_MAX_POINTS ? CONFIG_ESP_LCD_TOUCH_MAX_POINTS : touch_cnt); + tp->data.points = touch_cnt; + + /* Fill all coordinates */ + for (i = 0; i < touch_cnt; i++) { + tp->data.coords[i].track_id = buf[(i * 8) + 1]; + tp->data.coords[i].x = ((uint16_t)buf[(i * 8) + 3] << 8) + buf[(i * 8) + 2]; + tp->data.coords[i].y = (((uint16_t)buf[(i * 8) + 5] << 8) + buf[(i * 8) + 4]); + tp->data.coords[i].strength = (((uint16_t)buf[(i * 8) + 7] << 8) + buf[(i * 8) + 6]); + } + + portEXIT_CRITICAL(&tp->data.lock); + } + + return ESP_OK; +} + +static bool esp_lcd_touch_gt911_get_xy(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, + uint8_t *point_num, uint8_t max_point_num) +{ + ESP_RETURN_ON_FALSE(tp != NULL, false, TAG, "Touch controller handle can't be NULL"); + ESP_RETURN_ON_FALSE(x != NULL, false, TAG, "Pointer to the x coordinates array can't be NULL"); + ESP_RETURN_ON_FALSE(y != NULL, false, TAG, "Pointer to the y coordinates array can't be NULL"); + ESP_RETURN_ON_FALSE(point_num != NULL, false, TAG, "Pointer to number of touch points can't be NULL"); + ESP_RETURN_ON_FALSE(max_point_num > 0, false, TAG, "Array size must be equal or larger than 1"); + + portENTER_CRITICAL(&tp->data.lock); + + /* Count of points */ + *point_num = (tp->data.points > max_point_num ? max_point_num : tp->data.points); + + for (size_t i = 0; i < *point_num; i++) { + x[i] = tp->data.coords[i].x; + y[i] = tp->data.coords[i].y; + + if (strength) { + strength[i] = tp->data.coords[i].strength; + } + } + + portEXIT_CRITICAL(&tp->data.lock); + + return (*point_num > 0); +} + +static esp_err_t esp_lcd_touch_gt911_get_track_id(esp_lcd_touch_handle_t tp, uint8_t *track_id, uint8_t max_point_num) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + ESP_RETURN_ON_FALSE(track_id != NULL, ESP_ERR_INVALID_ARG, TAG, "Pointer to the track ID array can't be NULL"); + ESP_RETURN_ON_FALSE(max_point_num > 0, ESP_ERR_INVALID_ARG, TAG, "Array size must be equal or larger than 1"); + + portENTER_CRITICAL(&tp->data.lock); + + for (int i = 0; i < max_point_num; i++) { + track_id[i] = tp->data.coords[i].track_id; + } + + portEXIT_CRITICAL(&tp->data.lock); + + return ESP_OK; +} + +#if (CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS > 0) +static esp_err_t esp_lcd_touch_gt911_get_button_state(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state) +{ + esp_err_t err = ESP_OK; + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + ESP_RETURN_ON_FALSE(state != NULL, ESP_ERR_INVALID_ARG, TAG, "Pointer to the state array can't be NULL"); + + *state = 0; + + portENTER_CRITICAL(&tp->data.lock); + + if (n > tp->data.buttons) { + err = ESP_ERR_INVALID_ARG; + } else { + *state = tp->data.button[n].status; + } + + portEXIT_CRITICAL(&tp->data.lock); + + return err; +} +#endif + +static esp_err_t esp_lcd_touch_gt911_del(esp_lcd_touch_handle_t tp) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + /* Reset GPIO pin settings */ + if (tp->config.int_gpio_num != GPIO_NUM_NC) { + gpio_reset_pin(tp->config.int_gpio_num); + if (tp->config.interrupt_callback) { + gpio_isr_handler_remove(tp->config.int_gpio_num); + } + } + + /* Reset GPIO pin settings */ + if (tp->config.rst_gpio_num != GPIO_NUM_NC) { + gpio_reset_pin(tp->config.rst_gpio_num); + } + + free(tp); + + return ESP_OK; +} + +/******************************************************************************* +* Private API function +*******************************************************************************/ + +/* Reset controller */ +static esp_err_t touch_gt911_reset(esp_lcd_touch_handle_t tp) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + if (tp->config.rst_gpio_num != GPIO_NUM_NC) { + ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, tp->config.levels.reset), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(10)); + ESP_RETURN_ON_ERROR(gpio_set_level(tp->config.rst_gpio_num, !tp->config.levels.reset), TAG, "GPIO set level error!"); + vTaskDelay(pdMS_TO_TICKS(10)); + } + + return ESP_OK; +} + +static esp_err_t touch_gt911_read_cfg(esp_lcd_touch_handle_t tp) +{ + uint8_t buf[4]; + + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_PRODUCT_ID_REG, (uint8_t *)&buf[0], 3), TAG, + "GT911 read error!"); + ESP_RETURN_ON_ERROR(touch_gt911_i2c_read(tp, ESP_LCD_TOUCH_GT911_CONFIG_REG, (uint8_t *)&buf[3], 1), TAG, + "GT911 read error!"); + + ESP_LOGI(TAG, "TouchPad_ID:0x%02x,0x%02x,0x%02x", buf[0], buf[1], buf[2]); + ESP_LOGI(TAG, "TouchPad_Config_Version:%d", buf[3]); + + return ESP_OK; +} + +static esp_err_t touch_gt911_i2c_read(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t *data, uint8_t len) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + ESP_RETURN_ON_FALSE(data != NULL, ESP_ERR_INVALID_ARG, TAG, "Pointer to the data array can't be NULL"); + + /* Read data */ + return esp_lcd_panel_io_rx_param(tp->io, reg, data, len); +} + +static esp_err_t touch_gt911_i2c_write(esp_lcd_touch_handle_t tp, uint16_t reg, uint8_t data) +{ + ESP_RETURN_ON_FALSE(tp != NULL, ESP_ERR_INVALID_ARG, TAG, "Touch controller handle can't be NULL"); + + // *INDENT-OFF* + /* Write data */ + return esp_lcd_panel_io_tx_param(tp->io, reg, (uint8_t[]){data}, 1); + // *INDENT-ON* +} diff --git a/components/esp_lcd_touch_gt911/include/esp_lcd_touch_gt911.h b/components/esp_lcd_touch_gt911/include/esp_lcd_touch_gt911.h new file mode 100644 index 0000000..468eda3 --- /dev/null +++ b/components/esp_lcd_touch_gt911/include/esp_lcd_touch_gt911.h @@ -0,0 +1,72 @@ +/* + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LCD touch: GT911 + */ + +#pragma once + +#include "esp_lcd_touch.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Create a new GT911 touch driver + * + * @note The I2C communication should be initialized before use this function. + * + * @param io LCD/Touch panel IO handle + * @param config: Touch configuration + * @param out_touch: Touch instance handle + * @return + * - ESP_OK on success + * - ESP_ERR_NO_MEM if there is no memory for allocating main structure + */ +esp_err_t esp_lcd_touch_new_i2c_gt911(const esp_lcd_panel_io_handle_t io, const esp_lcd_touch_config_t *config, + esp_lcd_touch_handle_t *out_touch); + +/** + * @brief I2C address of the GT911 controller + * + * @note When power-on detects low level of the interrupt gpio, address is 0x5D. + * @note Interrupt gpio is high level, address is 0x14. + * + */ +#define ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS (0x5D) +#define ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP (0x14) + +/** + * @brief GT911 Configuration Type + * + */ +typedef struct { + uint8_t dev_addr; /*!< I2C device address */ +} esp_lcd_touch_io_gt911_config_t; + +/** + * @brief Touch IO configuration structure + * + */ +#define ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG() \ + { \ + .scl_speed_hz = 100000, \ + .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS, \ + .control_phase_bytes = 1, \ + .dc_bit_offset = 0, \ + .lcd_cmd_bits = 16, \ + .flags = \ + { \ + .disable_control_phase = 1, \ + } \ + } + +#ifdef __cplusplus +} +#endif diff --git a/components/m5ioe1/CMakeLists.txt b/components/m5ioe1/CMakeLists.txt new file mode 100644 index 0000000..727dec2 --- /dev/null +++ b/components/m5ioe1/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: MIT +idf_component_register( + SRCS "src/M5IOE1.cpp" + INCLUDE_DIRS "src" + REQUIRES driver esp_driver_gpio esp_driver_i2c esp_rom +) diff --git a/components/m5ioe1/LICENSE b/components/m5ioe1/LICENSE new file mode 100644 index 0000000..98d36b6 --- /dev/null +++ b/components/m5ioe1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 M5Stack Technology CO LTD + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/components/m5ioe1/src/M5IOE1.cpp b/components/m5ioe1/src/M5IOE1.cpp new file mode 100644 index 0000000..af98ac0 --- /dev/null +++ b/components/m5ioe1/src/M5IOE1.cpp @@ -0,0 +1,5285 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#include "M5IOE1.h" +#include +#include + +static const char* TAG = "M5IOE1"; +static const char* TAG_I2C = "M5IOE1_I2C"; +static const char* TAG_GPIO = "M5IOE1_GPIO"; +static const char* TAG_ADC = "M5IOE1_ADC"; +static const char* TAG_PWM = "M5IOE1_PWM"; +static const char* TAG_LED = "M5IOE1_LED"; +static const char* TAG_AMP = "M5IOE1_AMP"; +static const char* TAG_IRQ = "M5IOE1_IRQ"; +static const char* TAG_SYS = "M5IOE1_SYS"; + +#ifdef ARDUINO +#include +#define M5IOE1_DELAY_MS(ms) delay(ms) +#define M5IOE1_GET_TIME_MS() millis() + +// Arduino 日志级别控制 +// Arduino log level control +static m5ioe1_log_level_t _m5ioe1_log_level = M5IOE1_LOG_LEVEL_INFO; + +#define M5IOE1_LOG_I(tag, fmt, ...) \ + do { \ + if (_m5ioe1_log_level >= M5IOE1_LOG_LEVEL_INFO) { \ + log_printf("[I][%s] " fmt "\r\n", tag, ##__VA_ARGS__); \ + } \ + } while (0) + +#define M5IOE1_LOG_W(tag, fmt, ...) \ + do { \ + if (_m5ioe1_log_level >= M5IOE1_LOG_LEVEL_WARN) { \ + log_printf("[W][%s] " fmt "\r\n", tag, ##__VA_ARGS__); \ + } \ + } while (0) + +#define M5IOE1_LOG_E(tag, fmt, ...) \ + do { \ + if (_m5ioe1_log_level >= M5IOE1_LOG_LEVEL_ERROR) { \ + log_printf("[E][%s] " fmt "\r\n", tag, ##__VA_ARGS__); \ + } \ + } while (0) + +#define M5IOE1_LOG_D(tag, fmt, ...) \ + do { \ + if (_m5ioe1_log_level >= M5IOE1_LOG_LEVEL_DEBUG) { \ + log_printf("[D][%s] " fmt "\r\n", tag, ##__VA_ARGS__); \ + } \ + } while (0) + +#define M5IOE1_LOG_V(tag, fmt, ...) \ + do { \ + if (_m5ioe1_log_level >= M5IOE1_LOG_LEVEL_VERBOSE) { \ + log_printf("[V][%s] " fmt "\r\n", tag, ##__VA_ARGS__); \ + } \ + } while (0) + +// (Arduino 轮询/中断任务句柄已移至实例成员 _pollTask,支持多实例) +// (Arduino polling/interrupt task handles moved to instance member _pollTask for multi-instance support) +#else +// 强制本组件编译所有级别的日志,实际输出由运行时的 esp_log_level_set 控制 +// Force compile all log levels for this component; actual output controlled by esp_log_level_set at runtime +#ifndef LOG_LOCAL_LEVEL +#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE +#endif +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "driver/gpio.h" +#define M5IOE1_DELAY_MS(ms) vTaskDelay(pdMS_TO_TICKS(ms)) +#define M5IOE1_GET_TIME_MS() (xTaskGetTickCount() * portTICK_PERIOD_MS) +#define M5IOE1_LOG_I(tag, fmt, ...) ESP_LOGI(tag, fmt, ##__VA_ARGS__) +#define M5IOE1_LOG_W(tag, fmt, ...) ESP_LOGW(tag, fmt, ##__VA_ARGS__) +#define M5IOE1_LOG_E(tag, fmt, ...) ESP_LOGE(tag, fmt, ##__VA_ARGS__) +#define M5IOE1_LOG_D(tag, fmt, ...) ESP_LOGD(tag, fmt, ##__VA_ARGS__) +#define M5IOE1_LOG_V(tag, fmt, ...) ESP_LOGV(tag, fmt, ##__VA_ARGS__) + +// ESP-IDF 平台日志级别控制 +// ESP-IDF platform log level control +static m5ioe1_log_level_t _m5ioe1_current_log_level = M5IOE1_LOG_LEVEL_INFO; +#endif + +// ============================ +// 全局日志级别控制 +// Global Log Level Control +// ============================ + +void M5IOE1::setLogLevel(m5ioe1_log_level_t level) +{ +#ifdef ARDUINO + _m5ioe1_log_level = level; +#else + _m5ioe1_current_log_level = level; + + // 将 M5IOE1 日志级别映射到 ESP-IDF 日志级别 + // Map M5IOE1 log level to ESP-IDF log level + esp_log_level_t esp_level; + switch (level) { + case M5IOE1_LOG_LEVEL_NONE: + esp_level = ESP_LOG_NONE; + break; + case M5IOE1_LOG_LEVEL_ERROR: + esp_level = ESP_LOG_ERROR; + break; + case M5IOE1_LOG_LEVEL_WARN: + esp_level = ESP_LOG_WARN; + break; + case M5IOE1_LOG_LEVEL_INFO: + esp_level = ESP_LOG_INFO; + break; + case M5IOE1_LOG_LEVEL_DEBUG: + esp_level = ESP_LOG_DEBUG; + break; + case M5IOE1_LOG_LEVEL_VERBOSE: + esp_level = ESP_LOG_VERBOSE; + break; + default: + esp_level = ESP_LOG_INFO; + break; + } + + esp_log_level_set(TAG, esp_level); +#endif +} + +m5ioe1_log_level_t M5IOE1::getLogLevel() +{ +#ifdef ARDUINO + return _m5ioe1_log_level; +#else + return _m5ioe1_current_log_level; +#endif +} + +void M5IOE1::enableDefaultInterruptLog(bool enable) +{ + _enableDefaultIsrLog = enable; +} + +// ============================ +// 构造函数 +// 析构函数 +// ============================ + +M5IOE1::M5IOE1() +{ + _addr = M5IOE1_DEFAULT_ADDR; + _initialized = false; + _autoSnapshot = true; + _enableDefaultIsrLog = false; + _requestedSpeed = M5IOE1_I2C_FREQ_DEFAULT; + _autoWakeEnabled = false; + _lastCommTime = 0; + _intMode = M5IOE1_INT_MODE_DISABLED; + _intPin = -1; + _pollingInterval = 5000; + _pinStatesValid = false; + _pwmStatesValid = false; + _pwmFrequency = 0; + _adcStateValid = false; + _i2cConfigValid = false; + +#ifdef ARDUINO + _wire = nullptr; + _sda = 0; + _scl = 0; +#if M5IOE1_HAS_M5UNIFIED_I2C + _m5_i2c = nullptr; + _commFreq = 0; +#endif +#else + _i2cDriverType = M5IOE1_I2C_DRIVER_NONE; +#if M5IOE1_HAS_I2C_MASTER + _i2c_master_bus = nullptr; + _i2c_master_dev = nullptr; +#endif // M5IOE1_HAS_I2C_MASTER +#if M5IOE1_HAS_I2C_BUS + _i2c_bus = nullptr; + _i2c_device = nullptr; +#endif + _busExternal = false; + _sda = -1; + _scl = -1; + _port = I2C_NUM_0; + _intrQueue = nullptr; +#if M5IOE1_HAS_M5UNIFIED_I2C + _m5_i2c = nullptr; + _commFreq = M5IOE1_I2C_FREQ_100K; +#endif +#endif + + _pollTask = nullptr; + + memset(_callbacks, 0, sizeof(_callbacks)); + _clearPinStates(); + _clearPwmStates(); + _clearAdcState(); + _clearI2cConfig(); + _clearAw8737a(); +} + +M5IOE1::~M5IOE1() +{ +#ifdef ARDUINO + _cleanupPollingArduino(); + _cleanupHardwareInterruptArduino(); +#else + _cleanupPolling(); + _cleanupHardwareInterrupt(); + + // 根据驱动类型进行清理 + // Cleanup based on driver type + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + // 自创建:先删除设备,再删除总线 + // Self-created: delete device first, then bus + if (_i2c_master_dev) { + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + } + if (_i2c_master_bus) { + i2c_del_master_bus(_i2c_master_bus); + _i2c_master_bus = nullptr; + } + break; + + case M5IOE1_I2C_DRIVER_MASTER: + // 外部 i2c_master:总是删除我们创建的设备句柄,但不删除总线 + // External i2c_master: always delete the device handle we created, but not the bus + if (_i2c_master_dev) { + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + } + break; +#endif // M5IOE1_HAS_I2C_MASTER + +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + // 外部 i2c_bus:总是删除我们创建的设备句柄,但不删除总线 + // External i2c_bus: always delete the device handle we created, but not the bus + if (_i2c_device) { + i2c_bus_device_delete(&_i2c_device); + _i2c_device = nullptr; + } + break; +#endif // M5IOE1_HAS_I2C_BUS + +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: + // Legacy I2C:如果是自创建则卸载驱动 + // Legacy I2C: uninstall driver if self-created + if (!_busExternal) { + i2c_driver_delete(_port); + } + break; +#endif // !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + + default: + break; + } +#endif +} + +// ============================ +// 初始化函数 +// Initialization Functions +// ============================ + +#ifdef ARDUINO + +m5ioe1_err_t M5IOE1::begin(TwoWire* wire, uint8_t addr, uint8_t sda, uint8_t scl, uint32_t speed, int8_t intPin, + m5ioe1_int_mode_t intMode) +{ + _wire = wire; + _addr = addr; + _sda = sda; // 保存 SDA 引脚用于 I2C 重新初始化 + _scl = scl; // 保存 SCL 引脚用于 I2C 重新初始化 + _intPin = intPin; + + if (intMode == M5IOE1_INT_MODE_HARDWARE && _intPin < 0) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + + // 验证 I2C 频率 - M5IOE1 仅支持 100KHz 或 400KHz + // Validate I2C frequency - M5IOE1 only supports 100KHz or 400KHz + if (!_isValidI2cFrequency(speed)) { + M5IOE1_LOG_W(TAG_I2C, + "Invalid I2C frequency: %" PRIu32 + " Hz. M5IOE1 only supports 100KHz or 400KHz. Falling back to 100KHz.", + speed); + _requestedSpeed = M5IOE1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + + // 始终以 100KHz 开始 - M5IOE1 在上电/复位后默认为 100KHz + // Always start with 100KHz - M5IOE1 defaults to 100KHz after power-on/reset + M5IOE1_LOG_I(TAG_I2C, "Initializing M5IOE1 with 100KHz (device default)"); + + // 在开始新的 I2C 会话之前结束之前的会话(修复 ESP_ERR_INVALID_STATE) + // End any previous I2C session before starting new one (fixes ESP_ERR_INVALID_STATE) + // 注意:ESP32 Arduino Wire 库需要足够的延时 + // Note: ESP32 Arduino Wire library needs sufficient delay + _wire->end(); + M5IOE1_DELAY_MS(50); + + // 初始化 I2C 总线并检查返回值 + // Initialize I2C bus and check return value + if (!_wire->begin(sda, scl, M5IOE1_I2C_FREQ_100K)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to initialize I2C bus (SDA=%d, SCL=%d)", sda, scl); + return M5IOE1_ERR_I2C_CONFIG; + } + + // 给 I2C 总线时间在初始化后稳定 + // Give the I2C bus time to stabilize after initialization + M5IOE1_DELAY_MS(100); + + // 使用地址解析流程:探测候选地址、REV 校验、完整初始化 + // Use address resolution: probe candidates, REV check, full init + return _resolveAndInit(addr, intMode); +} + +#if M5IOE1_HAS_M5UNIFIED_I2C +// ===================================================== +// Type A (Arduino): M5Unified I2C_Class, no hardware interrupt +// ===================================================== +m5ioe1_err_t M5IOE1::begin(m5::I2C_Class* i2c, uint8_t addr, uint32_t speed, m5ioe1_int_mode_t intMode) +{ + if (!i2c || !i2c->isEnabled()) { + M5IOE1_LOG_E(TAG_I2C, "M5Unified I2C_Class not initialized"); + return M5IOE1_ERR_INVALID_ARG; + } + + _wire = nullptr; + _m5_i2c = i2c; + _addr = addr; + _sda = 0; + _scl = 0; + _intPin = -1; + + if (intMode == M5IOE1_INT_MODE_HARDWARE) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + + // 验证 I2C 频率 + // Validate I2C frequency + if (!_isValidI2cFrequency(speed)) { + M5IOE1_LOG_W(TAG_I2C, "Invalid I2C frequency: %" PRIu32 " Hz. Falling back to 100KHz.", speed); + _requestedSpeed = M5IOE1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + + M5IOE1_LOG_I(TAG_I2C, "Initializing M5IOE1 with 100KHz (device default)"); + + // 步骤 1: 尝试唤醒设备(初始 100K) + // Step 1: Wake device at initial 100K + _commFreq = M5IOE1_I2C_FREQ_100K; + + // 使用地址解析流程:探测候选地址、REV 校验、完整初始化 + // Use address resolution: probe candidates, REV check, full init + return _resolveAndInit(addr, intMode); +} + +// ===================================================== +// Type B (Arduino): M5Unified I2C_Class, with hardware interrupt +// ===================================================== +m5ioe1_err_t M5IOE1::begin(m5::I2C_Class* i2c, uint8_t addr, uint32_t speed, int intPin, m5ioe1_int_mode_t intMode) +{ + m5ioe1_err_t err = begin(i2c, addr, speed, M5IOE1_INT_MODE_DISABLED); + if (err != M5IOE1_OK) { + return err; + } + + _intPin = intPin; + if (intMode == M5IOE1_INT_MODE_HARDWARE && _intPin < 0) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + if (intMode != M5IOE1_INT_MODE_DISABLED) { + err = setInterruptMode(intMode); + if (err != M5IOE1_OK) { + _initialized = false; + return err; + } + } + return M5IOE1_OK; +} +#endif // M5IOE1_HAS_M5UNIFIED_I2C + +#else // ESP-IDF + +// ===================================================== +// Type 1A: Self-created I2C bus, no hardware interrupt +// ===================================================== +m5ioe1_err_t M5IOE1::begin(i2c_port_t port, uint8_t addr, int sda, int scl, uint32_t speed, m5ioe1_int_mode_t intMode) +{ + _addr = addr; + _busExternal = false; +#if M5IOE1_HAS_I2C_MASTER + _i2cDriverType = M5IOE1_I2C_DRIVER_SELF_CREATED; +#else + _i2cDriverType = M5IOE1_I2C_DRIVER_LEGACY; +#endif + _intPin = -1; + _port = port; + _sda = sda; + _scl = scl; + + if (intMode == M5IOE1_INT_MODE_HARDWARE) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + + // 验证 I2C 频率 - M5IOE1 仅支持 100KHz 或 400KHz + // Validate I2C frequency - M5IOE1 only supports 100KHz or 400KHz + if (!_isValidI2cFrequency(speed)) { + M5IOE1_LOG_W(TAG_I2C, + "Invalid I2C frequency: %" PRIu32 + " Hz. M5IOE1 only supports 100KHz or 400KHz. Falling back to 100KHz.", + speed); + _requestedSpeed = M5IOE1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + +#if M5IOE1_HAS_I2C_MASTER + // 始终以 100KHz 开始 - M5IOE1 在上电/复位后默认为 100KHz + // Always start with 100KHz - M5IOE1 defaults to 100KHz after power-on/reset + M5IOE1_LOG_I(TAG_I2C, "Initializing M5IOE1 with 100KHz (device default)"); + + // 使用 ESP-IDF 原生驱动创建 I2C 主总线 + // Create I2C master bus using ESP-IDF native driver + i2c_master_bus_config_t bus_config = { + .i2c_port = port, + .sda_io_num = (gpio_num_t)sda, + .scl_io_num = (gpio_num_t)scl, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = + { + .enable_internal_pullup = true, +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 4, 0) + .allow_pd = false, +#endif + }, + }; + + esp_err_t ret = i2c_new_master_bus(&bus_config, &_i2c_master_bus); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "Failed to create I2C master bus: %s", esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + + // 使用地址解析流程:探测候选地址、REV 校验、完整初始化 + // Use address resolution: probe candidates, REV check, full init + m5ioe1_err_t res = _resolveAndInit(addr, intMode); + if (res != M5IOE1_OK) { + // 清理自创建的总线 + // Cleanup self-created bus on failure + if (_i2c_master_dev) { + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + } + i2c_del_master_bus(_i2c_master_bus); + _i2c_master_bus = nullptr; + } + return res; + +#else // !M5IOE1_HAS_I2C_MASTER + // Legacy API (IDF < 5.3.0) + M5IOE1_LOG_I(TAG_I2C, "Initializing M5IOE1 with 100KHz (Legacy API, IDF < 5.3.0)"); + + i2c_config_t i2c_conf = {}; + i2c_conf.mode = I2C_MODE_MASTER; + i2c_conf.sda_io_num = sda; + i2c_conf.scl_io_num = scl; + i2c_conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + i2c_conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + i2c_conf.master.clk_speed = M5IOE1_I2C_FREQ_100K; + + esp_err_t ret = i2c_param_config(port, &i2c_conf); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "i2c_param_config failed: %s", esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + + ret = i2c_driver_install(port, I2C_MODE_MASTER, 0, 0, 0); + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + M5IOE1_LOG_E(TAG_I2C, "i2c_driver_install failed: %s", esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + + // 使用地址解析流程 + // Use address resolution flow + m5ioe1_err_t res = _resolveAndInit(addr, intMode); + if (res != M5IOE1_OK && !_busExternal) { + i2c_driver_delete(port); + } + return res; + +#endif // M5IOE1_HAS_I2C_MASTER +} + +// ===================================================== +// Type 1B: Self-created I2C bus, with hardware interrupt +// ===================================================== +m5ioe1_err_t M5IOE1::begin(i2c_port_t port, uint8_t addr, int sda, int scl, uint32_t speed, int intPin, + m5ioe1_int_mode_t intMode) +{ + m5ioe1_err_t err = begin(port, addr, sda, scl, speed, M5IOE1_INT_MODE_DISABLED); + if (err != M5IOE1_OK) { + return err; + } + + _intPin = intPin; + if (intMode == M5IOE1_INT_MODE_HARDWARE && _intPin < 0) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + if (intMode != M5IOE1_INT_MODE_DISABLED) { + err = setInterruptMode(intMode); + if (err != M5IOE1_OK) { + _initialized = false; + return err; + } + } + return M5IOE1_OK; +} + +#if M5IOE1_HAS_I2C_MASTER +// ===================================================== +// Type 2A: Existing i2c_master_bus_handle_t, no hardware interrupt +// ===================================================== +m5ioe1_err_t M5IOE1::begin(i2c_master_bus_handle_t bus, uint8_t addr, uint32_t speed, m5ioe1_int_mode_t intMode) +{ + _addr = addr; + _busExternal = true; + _i2cDriverType = M5IOE1_I2C_DRIVER_MASTER; + _intPin = -1; + _i2c_master_bus = bus; + _sda = -1; // 外部总线未知 + // Unknown for external bus + _scl = -1; + + if (intMode == M5IOE1_INT_MODE_HARDWARE) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + + // 验证 I2C 频率 + // Validate I2C frequency + if (!_isValidI2cFrequency(speed)) { + M5IOE1_LOG_W(TAG_I2C, "Invalid I2C frequency: %" PRIu32 " Hz. Falling back to 100KHz.", speed); + _requestedSpeed = M5IOE1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + + M5IOE1_LOG_I(TAG_I2C, "Initializing M5IOE1 with 100KHz (device default)"); + + // 使用地址解析流程 + // Use address resolution flow + m5ioe1_err_t res = _resolveAndInit(addr, intMode); + if (res != M5IOE1_OK && _i2c_master_dev) { + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + } + return res; +} + +// ===================================================== +// Type 2B: Existing i2c_master_bus_handle_t, with hardware interrupt +// ===================================================== +m5ioe1_err_t M5IOE1::begin(i2c_master_bus_handle_t bus, uint8_t addr, uint32_t speed, int intPin, + m5ioe1_int_mode_t intMode) +{ + m5ioe1_err_t err = begin(bus, addr, speed, M5IOE1_INT_MODE_DISABLED); + if (err != M5IOE1_OK) { + return err; + } + + _intPin = intPin; + if (intMode == M5IOE1_INT_MODE_HARDWARE && _intPin < 0) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + if (intMode != M5IOE1_INT_MODE_DISABLED) { + err = setInterruptMode(intMode); + if (err != M5IOE1_OK) { + _initialized = false; + return err; + } + } + return M5IOE1_OK; +} +#endif // M5IOE1_HAS_I2C_MASTER + +// ===================================================== +// Type 3A: Existing i2c_bus_handle_t, no hardware interrupt +// ===================================================== +#if M5IOE1_HAS_I2C_BUS +m5ioe1_err_t M5IOE1::begin(i2c_bus_handle_t bus, uint8_t addr, uint32_t speed, m5ioe1_int_mode_t intMode) +{ + _addr = addr; + _busExternal = true; + _i2cDriverType = M5IOE1_I2C_DRIVER_BUS; + _intPin = -1; + _i2c_bus = bus; + _sda = -1; // 外部总线未知 + // Unknown for external bus + _scl = -1; + + if (intMode == M5IOE1_INT_MODE_HARDWARE) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + + // 验证 I2C 频率 + // Validate I2C frequency + if (!_isValidI2cFrequency(speed)) { + M5IOE1_LOG_W(TAG_I2C, "Invalid I2C frequency: %" PRIu32 " Hz. Falling back to 100KHz.", speed); + _requestedSpeed = M5IOE1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + + M5IOE1_LOG_I(TAG_I2C, "Initializing M5IOE1 with 100KHz (device default)"); + + // 使用地址解析流程 + // Use address resolution flow + m5ioe1_err_t res = _resolveAndInit(addr, intMode); + if (res != M5IOE1_OK && _i2c_device) { + i2c_bus_device_delete(&_i2c_device); + _i2c_device = nullptr; + } + return res; +} + +// ===================================================== +// Type 3B: Existing i2c_bus_handle_t, with hardware interrupt +// ===================================================== +m5ioe1_err_t M5IOE1::begin(i2c_bus_handle_t bus, uint8_t addr, uint32_t speed, int intPin, m5ioe1_int_mode_t intMode) +{ + m5ioe1_err_t err = begin(bus, addr, speed, M5IOE1_INT_MODE_DISABLED); + if (err != M5IOE1_OK) { + return err; + } + + _intPin = intPin; + if (intMode == M5IOE1_INT_MODE_HARDWARE && _intPin < 0) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + if (intMode != M5IOE1_INT_MODE_DISABLED) { + err = setInterruptMode(intMode); + if (err != M5IOE1_OK) { + _initialized = false; + return err; + } + } + return M5IOE1_OK; +} + +#endif // M5IOE1_HAS_I2C_BUS + +#if M5IOE1_HAS_M5UNIFIED_I2C +// ===================================================== +// Type 4A: M5Unified I2C_Class, no hardware interrupt +// ===================================================== +m5ioe1_err_t M5IOE1::begin(m5::I2C_Class* i2c, uint8_t addr, uint32_t speed, m5ioe1_int_mode_t intMode) +{ + if (!i2c || !i2c->isEnabled()) { + M5IOE1_LOG_E(TAG_I2C, "M5Unified I2C_Class not initialized"); + return M5IOE1_ERR_INVALID_ARG; + } + + _addr = addr; + _busExternal = true; + _i2cDriverType = M5IOE1_I2C_DRIVER_M5UNIFIED; + _intPin = -1; + _m5_i2c = i2c; + _sda = -1; + _scl = -1; + + if (intMode == M5IOE1_INT_MODE_HARDWARE) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + + // 验证 I2C 频率 + // Validate I2C frequency + if (!_isValidI2cFrequency(speed)) { + M5IOE1_LOG_W(TAG_I2C, "Invalid I2C frequency: %" PRIu32 " Hz. Falling back to 100KHz.", speed); + _requestedSpeed = M5IOE1_I2C_FREQ_100K; + } else { + _requestedSpeed = speed; + } + + M5IOE1_LOG_I(TAG_I2C, "Initializing M5IOE1 with 100KHz (device default)"); + + // 步骤 1: 设置初始通信频率 + // Step 1: Set initial communication frequency + _commFreq = M5IOE1_I2C_FREQ_100K; + + // 使用地址解析流程 + // Use address resolution flow + return _resolveAndInit(addr, intMode); +} + +// ===================================================== +// Type 4B: M5Unified I2C_Class, with hardware interrupt +// ===================================================== +m5ioe1_err_t M5IOE1::begin(m5::I2C_Class* i2c, uint8_t addr, uint32_t speed, int intPin, m5ioe1_int_mode_t intMode) +{ + m5ioe1_err_t err = begin(i2c, addr, speed, M5IOE1_INT_MODE_DISABLED); + if (err != M5IOE1_OK) { + return err; + } + + _intPin = intPin; + if (intMode == M5IOE1_INT_MODE_HARDWARE && _intPin < 0) { + M5IOE1_LOG_E(TAG_I2C, "Hardware interrupt mode requires interrupt pin"); + return M5IOE1_ERR_INVALID_ARG; + } + if (intMode != M5IOE1_INT_MODE_DISABLED) { + err = setInterruptMode(intMode); + if (err != M5IOE1_OK) { + _initialized = false; + return err; + } + } + return M5IOE1_OK; +} +#endif // M5IOE1_HAS_M5UNIFIED_I2C + +#endif // !ARDUINO + +m5ioe1_err_t M5IOE1::setInterruptMode(m5ioe1_int_mode_t intMode, uint32_t pollingIntervalMs) +{ + const char* modeName = "UNKNOWN"; + if (intMode == M5IOE1_INT_MODE_DISABLED) { + modeName = "DISABLED"; + } else if (intMode == M5IOE1_INT_MODE_POLLING) { + modeName = "POLLING"; + } else if (intMode == M5IOE1_INT_MODE_HARDWARE) { + modeName = "HARDWARE"; + } + + if (intMode == M5IOE1_INT_MODE_POLLING) { + M5IOE1_LOG_I(TAG_IRQ, "Interrupt mode -> %s (pause if I2C sleep on; resume when off)", modeName); + } else { + M5IOE1_LOG_I(TAG_IRQ, "Interrupt mode -> %s", modeName); + } + + _intMode = intMode; + _pollingInterval = pollingIntervalMs; + +#ifdef ARDUINO + _cleanupPollingArduino(); + _cleanupHardwareInterruptArduino(); + + switch (intMode) { + case M5IOE1_INT_MODE_POLLING: + if (_i2cConfigValid && _i2cConfig.sleepTime > 0) { + return M5IOE1_OK; + } + return _setupPollingArduino() ? M5IOE1_OK : M5IOE1_ERR_INTERNAL; + case M5IOE1_INT_MODE_HARDWARE: + return _setupHardwareInterruptArduino() ? M5IOE1_OK : M5IOE1_ERR_INTERNAL; + default: + break; + } +#else + _cleanupPolling(); + _cleanupHardwareInterrupt(); + + switch (intMode) { + case M5IOE1_INT_MODE_POLLING: + if (_i2cConfigValid && _i2cConfig.sleepTime > 0) { + return M5IOE1_OK; + } + return _setupPolling() ? M5IOE1_OK : M5IOE1_ERR_INTERNAL; + case M5IOE1_INT_MODE_HARDWARE: + return _setupHardwareInterrupt() ? M5IOE1_OK : M5IOE1_ERR_INTERNAL; + default: + break; + } +#endif + + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setPollingInterval(float seconds) +{ + if (seconds < 0.001f || seconds > 3600.0f) { + M5IOE1_LOG_E(TAG_IRQ, "Invalid polling interval: %.3f seconds (valid range: 0.001-3600)", seconds); + return M5IOE1_ERR_INVALID_ARG; + } + + uint32_t intervalMs = (uint32_t)(seconds * 1000.0f); + _pollingInterval = intervalMs; + + // 如果当前处于轮询模式,使用新间隔重新启动 + // If currently in polling mode, restart with new interval + if (_intMode == M5IOE1_INT_MODE_POLLING) { +#ifdef ARDUINO + _cleanupPollingArduino(); + _cleanupHardwareInterruptArduino(); + if (_i2cConfigValid && _i2cConfig.sleepTime > 0) { + return M5IOE1_OK; + } + return _setupPollingArduino() ? M5IOE1_OK : M5IOE1_ERR_INTERNAL; +#else + _cleanupPolling(); + _cleanupHardwareInterrupt(); + if (_i2cConfigValid && _i2cConfig.sleepTime > 0) { + return M5IOE1_OK; + } + return _setupPolling() ? M5IOE1_OK : M5IOE1_ERR_INTERNAL; +#endif + } + + M5IOE1_LOG_I(TAG_IRQ, "Polling interval set to %.3f seconds (%u ms)", seconds, static_cast(intervalMs)); + return M5IOE1_OK; +} + +// ============================ +// 设备信息 +// Device Information +// ============================ + +m5ioe1_err_t M5IOE1::getUID(uint16_t* uid) +{ + if (uid == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + return _readReg16(M5IOE1_REG_UID_L, uid) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +m5ioe1_err_t M5IOE1::getVersion(uint8_t* version) +{ + if (version == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + return _readReg(M5IOE1_REG_REV, version) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +m5ioe1_err_t M5IOE1::getRefVoltage(uint16_t* voltage_mv) +{ + if (voltage_mv == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + return _readReg16(M5IOE1_REG_REF_VOLTAGE_L, voltage_mv) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +// ============================ +// GPIO 功能 +// GPIO Functions +// ============================ + +m5ioe1_err_t M5IOE1::_pinModeWithErr(uint8_t pin, uint8_t mode) +{ + if (!_isValidPin(pin)) { + M5IOE1_LOG_E(TAG_GPIO, "Invalid pin"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_GPIO, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint16_t modeReg = 0, puReg = 0, pdReg = 0, drvReg = 0; + + M5IOE1_LOG_D(TAG_GPIO, "_pinModeWithErr: pin=%d mode=0x%02X", pin, mode); + + if (!_readReg16(M5IOE1_REG_GPIO_MODE_L, &modeReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read GPIO_MODE register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_readReg16(M5IOE1_REG_GPIO_PU_L, &puReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read GPIO_PU register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_readReg16(M5IOE1_REG_GPIO_PD_L, &pdReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read GPIO_PD register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_readReg16(M5IOE1_REG_GPIO_DRV_L, &drvReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read GPIO_DRV register"); + return M5IOE1_ERR_I2C_COMM; + } + + switch (mode) { + case INPUT: // 0x01 + modeReg &= ~(1 << pin); + puReg &= ~(1 << pin); + pdReg &= ~(1 << pin); + _pinStates[pin].isOutput = false; + _pinStates[pin].pull = 0; + break; + case PULLUP: // 0x04 - 仅上拉 + case INPUT_PULLUP: // 0x05 + modeReg &= ~(1 << pin); + puReg |= (1 << pin); + pdReg &= ~(1 << pin); + _pinStates[pin].isOutput = false; + _pinStates[pin].pull = 1; + break; + case PULLDOWN: // 0x08 - 仅下拉 + case INPUT_PULLDOWN: // 0x09 + modeReg &= ~(1 << pin); + puReg &= ~(1 << pin); + pdReg |= (1 << pin); + _pinStates[pin].isOutput = false; + _pinStates[pin].pull = 2; + break; + case OUTPUT: // 0x03 + // 如果此引脚上启用了 PWM 则禁用 + // Disable PWM if enabled on this pin + if (_isPwmPin(pin)) { + uint8_t ch = _getPwmChannel(pin); + uint8_t regL = M5IOE1_REG_PWM1_DUTY_L + ch * 2; + uint16_t pwmData = 0; + if (_readReg16(regL, &pwmData)) { + if (pwmData & ((uint16_t)M5IOE1_PWM_ENABLE << 8)) { + pwmData &= ~((uint16_t)M5IOE1_PWM_ENABLE << 8); + _writeReg16(regL, pwmData); + } + } + } + modeReg |= (1 << pin); + puReg &= ~(1 << pin); + pdReg &= ~(1 << pin); + drvReg &= ~(1 << pin); // 推挽 + // Push-pull + _pinStates[pin].isOutput = true; + _pinStates[pin].drive = 0; + break; + case OPEN_DRAIN: // 0x10 + case OUTPUT_OPEN_DRAIN: // 0x13 + // 如果此引脚上启用了 PWM 则禁用 + // Disable PWM if enabled on this pin + if (_isPwmPin(pin)) { + uint8_t ch = _getPwmChannel(pin); + uint8_t regL = M5IOE1_REG_PWM1_DUTY_L + ch * 2; + uint16_t pwmData = 0; + if (_readReg16(regL, &pwmData)) { + if (pwmData & ((uint16_t)M5IOE1_PWM_ENABLE << 8)) { + pwmData &= ~((uint16_t)M5IOE1_PWM_ENABLE << 8); + _writeReg16(regL, pwmData); + } + } + } + modeReg |= (1 << pin); + puReg &= ~(1 << pin); + pdReg &= ~(1 << pin); + drvReg |= (1 << pin); // 开漏 + // Open-drain + _pinStates[pin].isOutput = true; + _pinStates[pin].drive = 1; + break; + case ANALOG: // 0xC0 + // 模拟模式 - 设置为输入,无上拉下拉 + // Analog mode - set as input, no pull-up/down + modeReg &= ~(1 << pin); + puReg &= ~(1 << pin); + pdReg &= ~(1 << pin); + _pinStates[pin].isOutput = false; + _pinStates[pin].pull = 0; + break; + default: + M5IOE1_LOG_E(TAG_GPIO, "Invalid mode: %d", mode); + return M5IOE1_ERR_INVALID_ARG; + } + + // 步骤 1: 写入寄存器 + // Step 1: Write registers + if (!_writeReg16(M5IOE1_REG_GPIO_PU_L, puReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to write GPIO_PU register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_writeReg16(M5IOE1_REG_GPIO_PD_L, pdReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to write GPIO_PD register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_writeReg16(M5IOE1_REG_GPIO_DRV_L, drvReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to write GPIO_DRV register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_writeReg16(M5IOE1_REG_GPIO_MODE_L, modeReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to write GPIO_MODE register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint16_t actualPu = 0, actualPd = 0, actualDrv = 0, actualMode = 0; + if (!_readReg16(M5IOE1_REG_GPIO_PU_L, &actualPu)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read back GPIO_PU register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_readReg16(M5IOE1_REG_GPIO_PD_L, &actualPd)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read back GPIO_PD register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_readReg16(M5IOE1_REG_GPIO_DRV_L, &actualDrv)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read back GPIO_DRV register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_readReg16(M5IOE1_REG_GPIO_MODE_L, &actualMode)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read back GPIO_MODE register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + bool puMatch = ((actualPu & (1 << pin)) == (puReg & (1 << pin))); + bool pdMatch = ((actualPd & (1 << pin)) == (pdReg & (1 << pin))); + bool drvMatch = ((actualDrv & (1 << pin)) == (drvReg & (1 << pin))); + bool modeMatch = ((actualMode & (1 << pin)) == (modeReg & (1 << pin))); + + if (!puMatch || !pdMatch || !drvMatch || !modeMatch) { + M5IOE1_LOG_E(TAG_GPIO, "Pin %d mode verification failed: PU=%c, PD=%c, DRV=%c, MODE=%c", pin, + puMatch ? 'Y' : 'N', pdMatch ? 'Y' : 'N', drvMatch ? 'Y' : 'N', modeMatch ? 'Y' : 'N'); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _autoSnapshotUpdate(M5IOE1_SNAPSHOT_DOMAIN_GPIO); + + const char* modeName = (mode == INPUT) ? "INPUT" + : (mode == OUTPUT) ? "OUTPUT" + : (mode == INPUT_PULLUP) ? "INPUT_PULLUP" + : (mode == INPUT_PULLDOWN) ? "INPUT_PULLDOWN" + : (mode == OPEN_DRAIN) ? "OPEN_DRAIN" + : (mode == OUTPUT_OPEN_DRAIN) ? "OUTPUT_OPEN_DRAIN" + : "UNKNOWN"; + M5IOE1_LOG_I(TAG_GPIO, "Pin %d mode set and verified: %s (0x%02X)", pin, modeName, mode); + return M5IOE1_OK; +} + +void M5IOE1::pinMode(uint8_t pin, uint8_t mode) +{ + (void)_pinModeWithErr(pin, mode); +} + +void M5IOE1::pinModeWithRes(uint8_t pin, uint8_t mode, m5ioe1_err_t* err) +{ + if (err == nullptr) { + M5IOE1_LOG_E(TAG_GPIO, "pinModeWithRes err is null"); + return; + } + *err = _pinModeWithErr(pin, mode); +} + +m5ioe1_err_t M5IOE1::_digitalWriteWithErr(uint8_t pin, uint8_t value) +{ + if (!_isValidPin(pin)) { + M5IOE1_LOG_E(TAG_GPIO, "Invalid pin"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_GPIO, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint16_t outReg = 0; + M5IOE1_LOG_D(TAG_GPIO, "_digitalWriteWithErr: pin=%d val=%d", pin, value); + if (!_readReg16(M5IOE1_REG_GPIO_OUT_L, &outReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read GPIO_OUT register"); + return M5IOE1_ERR_I2C_COMM; + } + + if (value) { + outReg |= (1 << pin); + } else { + outReg &= ~(1 << pin); + } + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg16(M5IOE1_REG_GPIO_OUT_L, outReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to write GPIO_OUT register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint16_t actualOut = 0; + if (!_readReg16(M5IOE1_REG_GPIO_OUT_L, &actualOut)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read back GPIO_OUT register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + bool outMatch = ((actualOut & (1 << pin)) == (outReg & (1 << pin))); + + if (!outMatch) { + M5IOE1_LOG_E(TAG_GPIO, "Pin %d write verification failed: expected %d, actual %d", pin, value ? HIGH : LOW, + (actualOut & (1 << pin)) ? HIGH : LOW); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _pinStates[pin].outputLevel = value ? 1 : 0; + + // 步骤 5: 打印日志 + // Step 5: Log + M5IOE1_LOG_I(TAG_GPIO, "Pin %d digital write and verified: %d", pin, value ? HIGH : LOW); + + return M5IOE1_OK; +} + +void M5IOE1::digitalWrite(uint8_t pin, uint8_t value) +{ + (void)_digitalWriteWithErr(pin, value); +} + +void M5IOE1::digitalWriteWithRes(uint8_t pin, uint8_t value, m5ioe1_err_t* err) +{ + if (err == nullptr) { + M5IOE1_LOG_E(TAG_GPIO, "digitalWriteWithRes err is null"); + return; + } + *err = _digitalWriteWithErr(pin, value); +} + +m5ioe1_err_t M5IOE1::_digitalReadWithErr(uint8_t pin, int* value) +{ + if (value == nullptr) { + return M5IOE1_ERR_INVALID_ARG; + } + if (!_isValidPin(pin)) { + M5IOE1_LOG_E(TAG_GPIO, "Invalid pin"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_GPIO, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint16_t inReg = 0; + if (!_readReg16(M5IOE1_REG_GPIO_IN_L, &inReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read GPIO_IN register"); + return M5IOE1_ERR_I2C_COMM; + } + + *value = (inReg & (1 << pin)) ? HIGH : LOW; + M5IOE1_LOG_D(TAG_GPIO, "_digitalReadWithErr: pin=%d -> %d", pin, *value); + return M5IOE1_OK; +} + +int M5IOE1::digitalRead(uint8_t pin) +{ + int value = -1; + if (_digitalReadWithErr(pin, &value) != M5IOE1_OK) { + return -1; + } + return value; +} + +int M5IOE1::digitalReadWithRes(uint8_t pin, m5ioe1_err_t* err) +{ + if (err == nullptr) { + M5IOE1_LOG_E(TAG_GPIO, "digitalReadWithRes err is null"); + return -1; + } + + int value = -1; + *err = _digitalReadWithErr(pin, &value); + return value; +} + +// ============================ +// 高级 GPIO 功能 +// Advanced GPIO Functions +// ============================ + +m5ioe1_err_t M5IOE1::setPullMode(uint8_t pin, uint8_t pullMode) +{ + if (!_isValidPin(pin)) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_FAIL; + + M5IOE1_LOG_D(TAG_GPIO, "setPullMode: pin=%d pull=%d", pin, pullMode); + + uint16_t puReg = 0, pdReg = 0; + if (!_readReg16(M5IOE1_REG_GPIO_PU_L, &puReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read GPIO_PU register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_readReg16(M5IOE1_REG_GPIO_PD_L, &pdReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read GPIO_PD register"); + return M5IOE1_ERR_I2C_COMM; + } + + puReg &= ~(1 << pin); + pdReg &= ~(1 << pin); + + if (pullMode == M5IOE1_PULL_UP) { + puReg |= (1 << pin); + _pinStates[pin].pull = 1; + } else if (pullMode == M5IOE1_PULL_DOWN) { + pdReg |= (1 << pin); + _pinStates[pin].pull = 2; + } else { + _pinStates[pin].pull = 0; + } + + // 步骤 1: 写入寄存器 + // Step 1: Write registers + if (!_writeReg16(M5IOE1_REG_GPIO_PU_L, puReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to write GPIO_PU register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_writeReg16(M5IOE1_REG_GPIO_PD_L, pdReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to write GPIO_PD register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint16_t actualPu = 0, actualPd = 0; + if (!_readReg16(M5IOE1_REG_GPIO_PU_L, &actualPu)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read back GPIO_PU register"); + return M5IOE1_ERR_I2C_COMM; + } + if (!_readReg16(M5IOE1_REG_GPIO_PD_L, &actualPd)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read back GPIO_PD register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + bool puMatch = ((actualPu & (1 << pin)) == (puReg & (1 << pin))); + bool pdMatch = ((actualPd & (1 << pin)) == (pdReg & (1 << pin))); + + if (!puMatch || !pdMatch) { + M5IOE1_LOG_E(TAG_GPIO, "Pin %d pull mode verification failed: PU=%c, PD=%c", pin, puMatch ? 'Y' : 'N', + pdMatch ? 'Y' : 'N'); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _autoSnapshotUpdate(M5IOE1_SNAPSHOT_DOMAIN_GPIO); + + const char* pullName = (pullMode == M5IOE1_PULL_NONE) ? "NONE" + : (pullMode == M5IOE1_PULL_UP) ? "PULL_UP" + : (pullMode == M5IOE1_PULL_DOWN) ? "PULL_DOWN" + : "UNKNOWN"; + M5IOE1_LOG_I(TAG_GPIO, "Pin %d pull mode set and verified: %s", pin, pullName); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setDriveMode(uint8_t pin, uint8_t driveMode) +{ + if (!_isValidPin(pin)) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_FAIL; + + M5IOE1_LOG_D(TAG_GPIO, "setDriveMode: pin=%d drive=%d", pin, driveMode); + + uint16_t drvReg = 0; + if (!_readReg16(M5IOE1_REG_GPIO_DRV_L, &drvReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read GPIO_DRV register"); + return M5IOE1_ERR_I2C_COMM; + } + + if (driveMode == M5IOE1_DRIVE_OPENDRAIN) { + drvReg |= (1 << pin); + _pinStates[pin].drive = 1; + } else { + drvReg &= ~(1 << pin); + _pinStates[pin].drive = 0; + } + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg16(M5IOE1_REG_GPIO_DRV_L, drvReg)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to write GPIO_DRV register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint16_t actualDrv = 0; + if (!_readReg16(M5IOE1_REG_GPIO_DRV_L, &actualDrv)) { + M5IOE1_LOG_E(TAG_GPIO, "Failed to read back GPIO_DRV register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + bool drvMatch = ((actualDrv & (1 << pin)) == (drvReg & (1 << pin))); + + if (!drvMatch) { + M5IOE1_LOG_E(TAG_GPIO, "Pin %d drive mode verification failed: DRV=%c", pin, drvMatch ? 'Y' : 'N'); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _autoSnapshotUpdate(M5IOE1_SNAPSHOT_DOMAIN_GPIO); + + M5IOE1_LOG_I(TAG_GPIO, "Pin %d drive mode set and verified: %s", pin, + (driveMode == M5IOE1_DRIVE_OPENDRAIN) ? "OpenDrain" : "PushPull"); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::getInputState(uint8_t pin, uint8_t* state) +{ + if (!_isValidPin(pin)) return M5IOE1_ERR_INVALID_ARG; + if (state == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_FAIL; + + int val = digitalRead(pin); + if (val < 0) return M5IOE1_ERR_I2C_COMM; + + *state = (uint8_t)val; + return M5IOE1_OK; +} + +// ============================ +// 中断功能 +// Interrupt Functions +// ============================ + +void M5IOE1::attachInterrupt(uint8_t pin, m5ioe1_callback_t callback, uint8_t mode) +{ + if (!_isValidPin(pin) || callback == nullptr || !_initialized) return; + + // 检查冲突的中断 + // Check for conflicting interrupts + if (_hasConflictingInterrupt(pin)) { + M5IOE1_LOG_E(TAG_IRQ, "Interrupt conflict on pin %d", pin); + return; + } + + _callbacks[pin].callback = callback; + _callbacks[pin].callbackArg = nullptr; + _callbacks[pin].arg = nullptr; + _callbacks[pin].enabled = true; + _callbacks[pin].rising = (mode == RISING); + + // 将引脚配置为输入 + // Configure pin as input + uint16_t modeReg = 0; + _readReg16(M5IOE1_REG_GPIO_MODE_L, &modeReg); + modeReg &= ~(1 << pin); + + // 步骤 1: 写入 GPIO_MODE 寄存器 + // Step 1: Write GPIO_MODE register + if (!_writeReg16(M5IOE1_REG_GPIO_MODE_L, modeReg)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to write GPIO_MODE register for interrupt"); + return; + } + + // 配置中断 + // Configure interrupt + uint16_t ieReg = 0, itReg = 0; + _readReg16(M5IOE1_REG_GPIO_IE_L, &ieReg); + _readReg16(M5IOE1_REG_GPIO_IP_L, &itReg); + + ieReg |= (1 << pin); + if (mode == RISING) { + itReg |= (1 << pin); + } else { + itReg &= ~(1 << pin); + } + + // 步骤 2: 写入中断寄存器 + // Step 2: Write interrupt registers + if (!_writeReg16(M5IOE1_REG_GPIO_IE_L, ieReg)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to write GPIO_IE register"); + return; + } + if (!_writeReg16(M5IOE1_REG_GPIO_IP_L, itReg)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to write GPIO_IP register"); + return; + } + + // 步骤 3: 回读验证 + // Step 3: Read-back verification + uint16_t actualMode = 0, actualIe = 0, actualIt = 0; + if (!_readReg16(M5IOE1_REG_GPIO_MODE_L, &actualMode)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to read back GPIO_MODE register"); + return; + } + if (!_readReg16(M5IOE1_REG_GPIO_IE_L, &actualIe)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to read back GPIO_IE register"); + return; + } + if (!_readReg16(M5IOE1_REG_GPIO_IP_L, &actualIt)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to read back GPIO_IP register"); + return; + } + + // 步骤 4: 验证关键位是否匹配 + // Step 4: Verify critical bits match + bool modeMatch = ((actualMode & (1 << pin)) == 0); // 输入模式 + bool ieMatch = ((actualIe & (1 << pin)) != 0); // 中断使能 + bool itMatch = ((actualIt & (1 << pin)) == (itReg & (1 << pin))); // 触发方式 + + if (!modeMatch || !ieMatch || !itMatch) { + M5IOE1_LOG_E(TAG_IRQ, "Pin %d interrupt verification failed: MODE=%c, IE=%c, IT=%c", pin, modeMatch ? 'Y' : 'N', + ieMatch ? 'Y' : 'N', itMatch ? 'Y' : 'N'); + return; + } + + // 步骤 5: 验证成功,更新缓存 + // Step 5: Verification passed, update cache + _pinStates[pin].intrEnabled = true; + _pinStates[pin].intrRising = (mode == RISING); + + M5IOE1_LOG_I(TAG_IRQ, "Pin %d interrupt attached and verified: mode=%s", pin, + mode == RISING ? "RISING" : "FALLING"); +} + +void M5IOE1::attachInterruptArg(uint8_t pin, m5ioe1_callback_arg_t callback, void* arg, uint8_t mode) +{ + if (!_isValidPin(pin) || callback == nullptr || !_initialized) return; + + if (_hasConflictingInterrupt(pin)) { + M5IOE1_LOG_E(TAG_IRQ, "Interrupt conflict on pin %d", pin); + return; + } + + _callbacks[pin].callback = nullptr; + _callbacks[pin].callbackArg = callback; + _callbacks[pin].arg = arg; + _callbacks[pin].enabled = true; + _callbacks[pin].rising = (mode == RISING); + + // 将引脚配置为输入 + // Configure pin as input + uint16_t modeReg = 0; + _readReg16(M5IOE1_REG_GPIO_MODE_L, &modeReg); + modeReg &= ~(1 << pin); + + // 配置中断 + // Configure interrupt + uint16_t ieReg = 0, itReg = 0; + _readReg16(M5IOE1_REG_GPIO_IE_L, &ieReg); + _readReg16(M5IOE1_REG_GPIO_IP_L, &itReg); + + ieReg |= (1 << pin); + if (mode == RISING) { + itReg |= (1 << pin); + } else { + itReg &= ~(1 << pin); + } + + // 步骤 1: 写入寄存器 + // Step 1: Write registers + if (!_writeReg16(M5IOE1_REG_GPIO_MODE_L, modeReg)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to write GPIO_MODE register"); + return; + } + if (!_writeReg16(M5IOE1_REG_GPIO_IE_L, ieReg)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to write GPIO_IE register"); + return; + } + if (!_writeReg16(M5IOE1_REG_GPIO_IP_L, itReg)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to write GPIO_IP register"); + return; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint16_t actualMode = 0, actualIe = 0, actualIt = 0; + if (!_readReg16(M5IOE1_REG_GPIO_MODE_L, &actualMode)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to read back GPIO_MODE register"); + return; + } + if (!_readReg16(M5IOE1_REG_GPIO_IE_L, &actualIe)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to read back GPIO_IE register"); + return; + } + if (!_readReg16(M5IOE1_REG_GPIO_IP_L, &actualIt)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to read back GPIO_IP register"); + return; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + bool modeMatch = ((actualMode & (1 << pin)) == 0); // 输入模式 + bool ieMatch = ((actualIe & (1 << pin)) != 0); // 中断使能 + bool itMatch = ((actualIt & (1 << pin)) == (itReg & (1 << pin))); // 触发方式 + + if (!modeMatch || !ieMatch || !itMatch) { + M5IOE1_LOG_E(TAG_IRQ, "Pin %d interrupt verification failed: MODE=%c, IE=%c, IT=%c", pin, modeMatch ? 'Y' : 'N', + ieMatch ? 'Y' : 'N', itMatch ? 'Y' : 'N'); + return; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _pinStates[pin].intrEnabled = true; + _pinStates[pin].intrRising = (mode == RISING); + + M5IOE1_LOG_I(TAG_IRQ, "Pin %d interrupt attached and verified: mode=%s", pin, + mode == RISING ? "RISING" : "FALLING"); +} + +void M5IOE1::detachInterrupt(uint8_t pin) +{ + if (!_isValidPin(pin) || !_initialized) return; + + uint16_t ieReg = 0; + _readReg16(M5IOE1_REG_GPIO_IE_L, &ieReg); + ieReg &= ~(1 << pin); + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg16(M5IOE1_REG_GPIO_IE_L, ieReg)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to write GPIO_IE register for detach"); + return; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint16_t actualIe = 0; + if (!_readReg16(M5IOE1_REG_GPIO_IE_L, &actualIe)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to read back GPIO_IE register for detach"); + return; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + bool ieMatch = ((actualIe & (1 << pin)) == 0); // 中断已禁用 + + if (!ieMatch) { + M5IOE1_LOG_E(TAG_IRQ, "Pin %d interrupt detach verification failed: IE=%c", pin, ieMatch ? 'Y' : 'N'); + return; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _callbacks[pin].callback = nullptr; + _callbacks[pin].callbackArg = nullptr; + _callbacks[pin].arg = nullptr; + _callbacks[pin].enabled = false; + _pinStates[pin].intrEnabled = false; + + M5IOE1_LOG_I(TAG_IRQ, "Pin %d interrupt detached and verified", pin); +} + +void M5IOE1::enableInterrupt(uint8_t pin) +{ + if (!_isValidPin(pin)) return; + _callbacks[pin].enabled = true; +} + +void M5IOE1::disableInterrupt(uint8_t pin) +{ + if (!_isValidPin(pin)) return; + _callbacks[pin].enabled = false; +} + +m5ioe1_err_t M5IOE1::getInterruptStatus(uint16_t* status) +{ + if (status == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_FAIL; + + if (!_readReg16(M5IOE1_REG_GPIO_IS_L, status)) { + return M5IOE1_ERR_I2C_COMM; + } + M5IOE1_LOG_D(TAG_IRQ, "IRQ status: 0x%04X", *status); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::clearInterrupt() +{ + if (!_initialized) return M5IOE1_FAIL; + + // GPIO_IS 寄存器是"写0清除"语义,必须写入0来清除所有中断 + // GPIO_IS register uses "write 0 to clear" semantics, must write 0 to clear all interrupts + if (!_writeReg16(M5IOE1_REG_GPIO_IS_L, 0)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to write GPIO_IS register for clear"); + return M5IOE1_ERR_I2C_COMM; + } + + M5IOE1_LOG_I(TAG_IRQ, "Interrupt cleared (all pins)"); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::clearInterrupt(uint8_t pin) +{ + if (!_initialized) return M5IOE1_FAIL; + if (pin >= M5IOE1_MAX_GPIO_PINS) { + M5IOE1_LOG_E(TAG_IRQ, "Invalid pin: %d", pin); + return M5IOE1_ERR_INVALID_ARG; + } + + // GPIO_IS 寄存器为"写 0 清除"语义:写 0 清除对应位,写 1 不影响 + // GPIO_IS register uses "write 0 to clear" semantics: write 0 clears the bit, write 1 has no effect + uint16_t mask = ~(1U << pin) & 0xFFFF; + if (!_writeReg16(M5IOE1_REG_GPIO_IS_L, mask)) { + M5IOE1_LOG_E(TAG_IRQ, "Failed to write GPIO_IS register for pin %d", pin); + return M5IOE1_ERR_I2C_COMM; + } + + M5IOE1_LOG_I(TAG_IRQ, "Interrupt cleared (pin %d)", pin); + return M5IOE1_OK; +} + +// ============================ +// ADC 功能 +// ADC Functions +// ============================ + +m5ioe1_err_t M5IOE1::analogRead(uint8_t channel, uint16_t* result) +{ + if (result == nullptr) { + M5IOE1_LOG_E(TAG_ADC, "analogRead result is null"); + return M5IOE1_ERR_INVALID_ARG; + } + if (channel < 1 || channel > 4) { + M5IOE1_LOG_E(TAG_ADC, "Invalid ADC channel: %d", channel); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_ADC, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + // 开始转换 + // Start conversion + uint8_t ctrl = (channel & M5IOE1_ADC_CH_MASK) | M5IOE1_ADC_START; + if (!_writeReg(M5IOE1_REG_ADC_CTRL, ctrl)) { + M5IOE1_LOG_E(TAG_ADC, "Failed to write ADC_CTRL register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 等待完成 + // Wait for completion + uint8_t reg = 0; + int tries = 0; + do { + M5IOE1_DELAY_MS(1); + if (!_readReg(M5IOE1_REG_ADC_CTRL, ®)) { + M5IOE1_LOG_E(TAG_ADC, "Failed to read ADC_CTRL register"); + return M5IOE1_ERR_I2C_COMM; + } + M5IOE1_LOG_V(TAG_ADC, "ADC ch%d waiting... tries=%d status=0x%02X", channel, tries, reg); + tries++; + } while ((reg & M5IOE1_ADC_BUSY) && tries < 20); + + if (reg & M5IOE1_ADC_BUSY) { + M5IOE1_LOG_E(TAG_ADC, "ADC conversion timeout on channel %d", channel); + return M5IOE1_ERR_TIMEOUT; + } + + if (!_readReg16(M5IOE1_REG_ADC_DATA_L, result)) { + M5IOE1_LOG_E(TAG_ADC, "Failed to read ADC_DATA register"); + return M5IOE1_ERR_I2C_COMM; + } + M5IOE1_LOG_D(TAG_ADC, "ADC ch%d: raw=%u (tries=%d)", channel, *result, tries); + _autoSnapshotUpdate(M5IOE1_SNAPSHOT_DOMAIN_ADC); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::isAdcBusy(bool* busy) +{ + if (busy == nullptr) { + M5IOE1_LOG_E(TAG_ADC, "isAdcBusy busy is null"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_ADC, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t ctrl = 0; + if (!_readReg(M5IOE1_REG_ADC_CTRL, &ctrl)) { + M5IOE1_LOG_E(TAG_ADC, "Failed to read ADC_CTRL register"); + return M5IOE1_ERR_I2C_COMM; + } + + *busy = (ctrl & M5IOE1_ADC_BUSY) != 0; + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::disableAdc() +{ + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg(M5IOE1_REG_ADC_CTRL, 0)) { + M5IOE1_LOG_E(TAG_ADC, "Failed to write ADC_CTRL register for disable"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint8_t actualCtrl = 0; + if (!_readReg(M5IOE1_REG_ADC_CTRL, &actualCtrl)) { + M5IOE1_LOG_E(TAG_ADC, "Failed to read back ADC_CTRL register for disable"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + // START/BUSY 位应该清零,通道位也应该为 0 + // START/BUSY bits should be cleared, channel bits should also be 0 + if (actualCtrl != 0) { + M5IOE1_LOG_E(TAG_ADC, "ADC disable verification failed: expected=0, actual=0x%02X", actualCtrl); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _autoSnapshotUpdate(M5IOE1_SNAPSHOT_DOMAIN_ADC); + + M5IOE1_LOG_I(TAG_ADC, "ADC disabled and verified"); + return M5IOE1_OK; +} + +// ============================ +// 温度传感器 +// Temperature Sensor +// ============================ + +m5ioe1_err_t M5IOE1::readTemperature(uint16_t* temperature) +{ + if (temperature == nullptr) { + M5IOE1_LOG_E(TAG_ADC, "readTemperature temperature is null"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_ADC, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + if (!_writeReg(M5IOE1_REG_TEMP_CTRL, M5IOE1_TEMP_START)) { + M5IOE1_LOG_E(TAG_ADC, "Failed to write TEMP_CTRL register"); + return M5IOE1_ERR_I2C_COMM; + } + + uint8_t ctrl = 0; + int tries = 0; + do { + M5IOE1_DELAY_MS(1); + if (!_readReg(M5IOE1_REG_TEMP_CTRL, &ctrl)) { + M5IOE1_LOG_E(TAG_ADC, "Failed to read TEMP_CTRL register"); + return M5IOE1_ERR_I2C_COMM; + } + tries++; + } while ((ctrl & M5IOE1_TEMP_BUSY) && tries < 20); + + if (ctrl & M5IOE1_TEMP_BUSY) { + M5IOE1_LOG_E(TAG_ADC, "Temperature conversion timeout"); + return M5IOE1_ERR_TIMEOUT; + } + + if (!_readReg16(M5IOE1_REG_TEMP_DATA_L, temperature)) { + M5IOE1_LOG_E(TAG_ADC, "Failed to read TEMP_DATA register"); + return M5IOE1_ERR_I2C_COMM; + } + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::isTemperatureBusy(bool* busy) +{ + if (busy == nullptr) { + M5IOE1_LOG_E(TAG_ADC, "isTemperatureBusy busy is null"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_ADC, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t ctrl = 0; + if (!_readReg(M5IOE1_REG_TEMP_CTRL, &ctrl)) { + M5IOE1_LOG_E(TAG_ADC, "Failed to read TEMP_CTRL register"); + return M5IOE1_ERR_I2C_COMM; + } + + *busy = (ctrl & M5IOE1_TEMP_BUSY) != 0; + return M5IOE1_OK; +} + +// ============================ +// PWM 功能 +// PWM Functions +// ============================ + +m5ioe1_err_t M5IOE1::setPwmFrequency(uint16_t frequency) +{ + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + M5IOE1_LOG_D(TAG_PWM, "setPwmFrequency: %d Hz", frequency); + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg16(M5IOE1_REG_PWM_FREQ_L, frequency)) { + M5IOE1_LOG_E(TAG_PWM, "Failed to write PWM_FREQ register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint16_t actualFreq = 0; + if (!_readReg16(M5IOE1_REG_PWM_FREQ_L, &actualFreq)) { + M5IOE1_LOG_E(TAG_PWM, "Failed to read back PWM_FREQ register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + if (actualFreq != frequency) { + M5IOE1_LOG_E(TAG_PWM, "PWM frequency verification failed: expected=%d, actual=%d", frequency, actualFreq); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _pwmFrequency = frequency; + _autoSnapshotUpdate(M5IOE1_SNAPSHOT_DOMAIN_PWM); + + M5IOE1_LOG_I(TAG_PWM, "PWM frequency set and verified: %d Hz", frequency); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::getPwmFrequency(uint16_t* frequency) +{ + if (frequency == nullptr) { + M5IOE1_LOG_E(TAG_PWM, "getPwmFrequency frequency is null"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_PWM, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + if (!_readReg16(M5IOE1_REG_PWM_FREQ_L, frequency)) { + M5IOE1_LOG_E(TAG_PWM, "Failed to read PWM_FREQ register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 更新缓存 + // Update cache + _pwmFrequency = *frequency; + + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setPwmDuty(uint8_t channel, uint8_t duty, bool polarity, bool enable) +{ + if (channel > 3 || duty > 100) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + // 将百分比转换为 12 位 (0-4095) + // Convert percentage to 12-bit (0-4095) + uint16_t duty12 = (uint16_t)((duty * 0x0FFF) / 100); + return setPwmDuty12bit(channel, duty12, polarity, enable); +} + +m5ioe1_err_t M5IOE1::getPwmDuty(uint8_t channel, uint8_t* duty, bool* polarity, bool* enable) +{ + if (channel > 3) { + M5IOE1_LOG_E(TAG_PWM, "Invalid PWM channel: %d", channel); + return M5IOE1_ERR_INVALID_ARG; + } + if (duty == nullptr || polarity == nullptr || enable == nullptr) { + M5IOE1_LOG_E(TAG_PWM, "getPwmDuty output pointer is null"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_PWM, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t regL = M5IOE1_REG_PWM1_DUTY_L + (channel * 2); + uint16_t data = 0; + if (!_readReg16(regL, &data)) { + M5IOE1_LOG_E(TAG_PWM, "Failed to read PWM channel %d duty register", channel); + return M5IOE1_ERR_I2C_COMM; + } + + uint16_t duty12 = data & 0x0FFF; + *duty = (uint8_t)((duty12 * 100) / 0x0FFF); + *polarity = (data & ((uint16_t)M5IOE1_PWM_POLARITY << 8)) != 0; + *enable = (data & ((uint16_t)M5IOE1_PWM_ENABLE << 8)) != 0; + + // 更新缓存 + // Update cache + _pwmStates[channel].duty12 = duty12; + _pwmStates[channel].polarity = *polarity; + _pwmStates[channel].enabled = *enable; + + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setPwmDuty12bit(uint8_t channel, uint16_t duty12, bool polarity, bool enable) +{ + if (channel > 3 || duty12 > 0x0FFF) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + M5IOE1_LOG_D(TAG_PWM, "setPwmDuty12bit: ch=%d duty=%d pol=%s en=%s", channel, duty12, polarity ? "Inv" : "Norm", + enable ? "On" : "Off"); + + // 获取对应的引脚 + // Get corresponding pin + uint8_t pin = (channel == 0) ? 8 : (channel == 1) ? 7 : (channel == 2) ? 10 : 9; + + uint8_t regL = M5IOE1_REG_PWM1_DUTY_L + (channel * 2); + + uint8_t dataL = (uint8_t)(duty12 & 0xFF); + uint8_t dataH = (uint8_t)((duty12 >> 8) & 0x0F); + if (polarity) dataH |= M5IOE1_PWM_POLARITY; + if (enable) dataH |= M5IOE1_PWM_ENABLE; + + uint8_t buf[2] = {dataL, dataH}; + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeBytes(regL, buf, 2)) { + M5IOE1_LOG_E(TAG_PWM, "Failed to write PWM channel %d duty register", channel); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint16_t actualData = 0; + if (!_readReg16(regL, &actualData)) { + M5IOE1_LOG_E(TAG_PWM, "Failed to read back PWM channel %d duty register", channel); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + uint16_t actualDuty12 = actualData & 0x0FFF; + bool actualPolarity = (actualData & ((uint16_t)M5IOE1_PWM_POLARITY << 8)) != 0; + bool actualEnable = (actualData & ((uint16_t)M5IOE1_PWM_ENABLE << 8)) != 0; + + if (actualDuty12 != duty12 || actualPolarity != polarity || actualEnable != enable) { + M5IOE1_LOG_E(TAG_PWM, "PWM channel %d verification failed: duty=%d/%d, pol=%d/%d, en=%d/%d", channel, duty12, + actualDuty12, polarity, actualPolarity, enable, actualEnable); + return M5IOE1_FAIL; + } + + // 步骤 4: 如果启用,将引脚设置为输出模式 + // Step 4: If enabled, set pin to output mode + if (enable) { + uint16_t modeReg = 0; + if (_readReg16(M5IOE1_REG_GPIO_MODE_L, &modeReg)) { + modeReg |= (1 << pin); + if (!_writeReg16(M5IOE1_REG_GPIO_MODE_L, modeReg)) { + M5IOE1_LOG_W(TAG_PWM, "Failed to set pin %d to output mode for PWM", pin); + } + } + } + + // 步骤 5: 验证成功,更新缓存 + // Step 5: Verification passed, update cache + _pwmStates[channel].duty12 = duty12; + _pwmStates[channel].polarity = polarity; + _pwmStates[channel].enabled = enable; + + _autoSnapshotUpdate(M5IOE1_SNAPSHOT_DOMAIN_PWM | M5IOE1_SNAPSHOT_DOMAIN_GPIO); + + M5IOE1_LOG_I(TAG_PWM, "PWM ch%d verified: duty=%d pol=%s en=%s", channel, duty12, polarity ? "Inv" : "Norm", + enable ? "On" : "Off"); + + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setPwmConfig(uint8_t channel, bool enable, bool polarity, uint16_t frequency, uint16_t duty12) +{ + if (channel > 3 || duty12 > 0x0FFF) { + M5IOE1_LOG_E(TAG_PWM, "Invalid PWM config: ch=%d duty12=%d", channel, duty12); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_PWM, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + static const uint8_t kPwmPinMap[4] = {8, 7, 10, 9}; + uint8_t pin = kPwmPinMap[channel]; + + m5ioe1_validation_t validation = validateConfig(pin, M5IOE1_CONFIG_PWM, enable); + if (!validation.valid) { + M5IOE1_LOG_W(TAG_PWM, "PWM config warning on IO%d: %s", pin + 1, validation.error_msg); + } + + if (_pinStatesValid && _pinStates[pin].intrEnabled) { + M5IOE1_LOG_W(TAG_PWM, "PWM pin IO%d has interrupt enabled", pin + 1); + } + + if (_pwmStatesValid && _pwmFrequency != frequency) { + for (uint8_t other = 0; other < M5IOE1_MAX_PWM_CHANNELS; other++) { + if (other == channel) continue; + if (_pwmStates[other].enabled) { + M5IOE1_LOG_W(TAG_PWM, "PWM frequency change affects other channels: %d -> %d", _pwmFrequency, + frequency); + break; + } + } + } + + m5ioe1_err_t err = setPwmDuty12bit(channel, duty12, polarity, enable); + if (err != M5IOE1_OK) return err; + return setPwmFrequency(frequency); +} + +m5ioe1_err_t M5IOE1::getPwmDuty12bit(uint8_t channel, uint16_t* duty12, bool* polarity, bool* enable) +{ + if (channel > 3 || duty12 == nullptr || polarity == nullptr || enable == nullptr) { + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + uint8_t regL = M5IOE1_REG_PWM1_DUTY_L + (channel * 2); + uint16_t data = 0; + if (!_readReg16(regL, &data)) { + return M5IOE1_ERR_I2C_COMM; + } + + *duty12 = data & 0x0FFF; + *polarity = (data & ((uint16_t)M5IOE1_PWM_POLARITY << 8)) != 0; + *enable = (data & ((uint16_t)M5IOE1_PWM_ENABLE << 8)) != 0; + + // 更新缓存 + // Update cache + _pwmStates[channel].duty12 = *duty12; + _pwmStates[channel].polarity = *polarity; + _pwmStates[channel].enabled = *enable; + + return M5IOE1_OK; +} + +// ============================ +// Arduino 兼容 analogWrite +// Arduino-compatible analogWrite +// ============================ + +m5ioe1_err_t M5IOE1::analogWrite(uint8_t channel, uint8_t value) +{ + if (channel > 3) { + M5IOE1_LOG_E(TAG_PWM, "Invalid channel: ch=%d", channel); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_PWM, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + // 值为 0 时关闭 PWM 输出 + // Turn off PWM when value is 0 + if (value == 0) { + return setPwmDuty12bit(channel, 0, false, false); + } + + // 将 8-bit 值 (0-255) 缩放到 12-bit (0-4095) + // Scale 8-bit value (0-255) to 12-bit (0-4095) + // 公式:duty12 = (value * 4095) / 255 = value * 16 + value / 16 + // Formula: duty12 = (value * 4095) / 255 = value * 16 + value / 16 + uint16_t duty12 = (uint16_t)value * 16 + (uint16_t)value / 16; + + // 设置 PWM,默认启用输出,正常极性 + // Set PWM with output enabled, normal polarity + return setPwmDuty12bit(channel, duty12, false, true); +} + +// ============================ +// NeoPixel LED 功能 +// NeoPixel LED Functions +// ============================ + +m5ioe1_err_t M5IOE1::setLedCount(uint8_t count) +{ + if (count > M5IOE1_MAX_LED_COUNT) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + uint8_t cfg = count & M5IOE1_LED_NUM_MASK; + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg(M5IOE1_REG_LED_CFG, cfg)) { + M5IOE1_LOG_E(TAG_LED, "Failed to write LED_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint8_t actualCfg = 0; + if (!_readReg(M5IOE1_REG_LED_CFG, &actualCfg)) { + M5IOE1_LOG_E(TAG_LED, "Failed to read back LED_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + // 只比对 LED 数量位 [5:0],忽略 REFRESH 位 [6] + // Only compare LED count bits [5:0], ignore REFRESH bit [6] + bool countMatch = ((actualCfg & M5IOE1_LED_NUM_MASK) == (cfg & M5IOE1_LED_NUM_MASK)); + + if (!countMatch) { + M5IOE1_LOG_E(TAG_LED, "LED count verification failed: expected=%d, actual=%d", count, + actualCfg & M5IOE1_LED_NUM_MASK); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + + M5IOE1_LOG_I(TAG_LED, "LED count set and verified: %d", count); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setLedColor(uint8_t index, uint8_t r, uint8_t g, uint8_t b) +{ + if (index >= M5IOE1_MAX_LED_COUNT) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + M5IOE1_LOG_D(TAG_LED, "setLedColor: idx=%d RGB(%d,%d,%d)", index, r, g, b); + + // 转换为 RGB565 + // Convert to RGB565 + uint16_t r5 = (r >> 3) & 0x1F; + uint16_t g6 = (g >> 2) & 0x3F; + uint16_t b5 = (b >> 3) & 0x1F; + uint16_t rgb565 = (r5 << 11) | (g6 << 5) | b5; + + uint8_t regAddr = M5IOE1_REG_LED_RAM_START + (index * 2); + uint8_t data[2] = {(uint8_t)(rgb565 & 0xFF), (uint8_t)((rgb565 >> 8) & 0xFF)}; + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeBytes(regAddr, data, 2)) { + M5IOE1_LOG_E(TAG_LED, "Failed to write LED_RAM register for index %d", index); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint16_t actualRgb565 = 0; + uint8_t actualData[2]; + if (!_readBytes(regAddr, actualData, 2)) { + M5IOE1_LOG_E(TAG_LED, "Failed to read back LED_RAM register for index %d", index); + return M5IOE1_ERR_I2C_COMM; + } + actualRgb565 = ((uint16_t)actualData[1] << 8) | actualData[0]; + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + if (actualRgb565 != rgb565) { + M5IOE1_LOG_E(TAG_LED, "LED color verification failed for index %d: expected=0x%04X, actual=0x%04X", index, + rgb565, actualRgb565); + return M5IOE1_FAIL; + } + + M5IOE1_LOG_I(TAG_LED, "LED color set and verified for index %d: RGB(%d,%d,%d)", index, r, g, b); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setLedColor(uint8_t index, m5ioe1_rgb_t color) +{ + return setLedColor(index, color.r, color.g, color.b); +} + +m5ioe1_err_t M5IOE1::refreshLeds() +{ + if (!_initialized) { + M5IOE1_LOG_E(TAG_LED, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5IOE1_REG_LED_CFG, &cfg)) { + M5IOE1_LOG_E(TAG_LED, "Failed to read LED_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + cfg |= M5IOE1_LED_REFRESH; + if (!_writeReg(M5IOE1_REG_LED_CFG, cfg)) { + M5IOE1_LOG_E(TAG_LED, "Failed to write LED_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::disableLeds() +{ + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg(M5IOE1_REG_LED_CFG, 0)) { + M5IOE1_LOG_E(TAG_LED, "Failed to write LED_CFG register for disable"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint8_t actualCfg = 0; + if (!_readReg(M5IOE1_REG_LED_CFG, &actualCfg)) { + M5IOE1_LOG_E(TAG_LED, "Failed to read back LED_CFG register for disable"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + // LED 数量为 0 且 REFRESH 位为 0 + // LED count is 0 and REFRESH bit is 0 + if (actualCfg != 0) { + M5IOE1_LOG_E(TAG_LED, "LED disable verification failed: expected=0, actual=0x%02X", actualCfg); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + + M5IOE1_LOG_I(TAG_LED, "LEDs disabled and verified"); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::clearLedRam() +{ + M5IOE1_LOG_D(TAG_LED, "clearLedRam"); + if (!_initialized) { + M5IOE1_LOG_E(TAG_LED, "Device not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + // 清除所有 LED RAM (0x30 - 0x6F, 共 64 字节,支持 32 个 LED) + // Clear all LED RAM (0x30 - 0x6F, 64 bytes total, supports 32 LEDs) + uint8_t zeroData[64] = {0}; + + M5IOE1_LOG_I(TAG_LED, "Clearing LED RAM..."); + + // 分块写入,每次写入 8 字节,避免 I2C 缓冲区溢出 + // Write in chunks of 8 bytes to avoid I2C buffer overflow + for (uint8_t offset = 0; offset < 64; offset += 8) { + uint8_t regAddr = M5IOE1_REG_LED_RAM_START + offset; + if (!_writeBytes(regAddr, zeroData, 8)) { + M5IOE1_LOG_E(TAG_LED, "Failed to clear LED RAM at offset %d", offset); + return M5IOE1_ERR_I2C_COMM; + } + // 每次写入后增加延迟,确保设备有时间处理 + // Add delay after each write to ensure device has time to process + M5IOE1_DELAY_MS(2); + } + + M5IOE1_LOG_I(TAG_LED, "LED RAM cleared successfully"); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setLeds(const m5ioe1_rgb_t* colors, uint8_t count, uint8_t arraySize, bool autoRefresh) +{ + // 步骤 1: 参数验证 + // Step 1: Parameter validation + if (!_initialized) { + M5IOE1_LOG_E(TAG_LED, "Device not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + if (colors == nullptr) { + M5IOE1_LOG_E(TAG_LED, "Colors array is null"); + return M5IOE1_ERR_INVALID_ARG; + } + + if (count == 0) { + M5IOE1_LOG_E(TAG_LED, "LED count cannot be 0"); + return M5IOE1_ERR_INVALID_ARG; + } + + if (count > M5IOE1_MAX_LED_COUNT) { + M5IOE1_LOG_E(TAG_LED, "LED count %d exceeds maximum %d", count, M5IOE1_MAX_LED_COUNT); + return M5IOE1_ERR_INVALID_ARG; + } + + // 边界检查:确保不会越界访问数组 + // Bounds check: ensure no out-of-bounds array access + if (count > arraySize) { + M5IOE1_LOG_E(TAG_LED, "LED count %d exceeds array size %d (would cause out-of-bounds access)", count, + arraySize); + return M5IOE1_ERR_INVALID_ARG; + } + + // 步骤 2: 设置 LED 数量 + // Step 2: Set LED count + uint8_t cfg = count & M5IOE1_LED_NUM_MASK; + if (!_writeReg(M5IOE1_REG_LED_CFG, cfg)) { + M5IOE1_LOG_E(TAG_LED, "Failed to write LED_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 验证 LED 数量设置 + // Verify LED count setting + uint8_t actualCfg = 0; + if (!_readReg(M5IOE1_REG_LED_CFG, &actualCfg)) { + M5IOE1_LOG_E(TAG_LED, "Failed to read back LED_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + if ((actualCfg & M5IOE1_LED_NUM_MASK) != cfg) { + M5IOE1_LOG_E(TAG_LED, "LED count verification failed: expected=%d, actual=%d", cfg, + actualCfg & M5IOE1_LED_NUM_MASK); + return M5IOE1_FAIL; + } + + // 步骤 3: 写入所有 LED 颜色数据并回读验证 + // Step 3: Write all LED color data and read-back verification + for (uint8_t i = 0; i < count; i++) { + // 转换为 RGB565 + // Convert to RGB565 + uint16_t r5 = (colors[i].r >> 3) & 0x1F; + uint16_t g6 = (colors[i].g >> 2) & 0x3F; + uint16_t b5 = (colors[i].b >> 3) & 0x1F; + uint16_t rgb565 = (r5 << 11) | (g6 << 5) | b5; + + uint8_t regAddr = M5IOE1_REG_LED_RAM_START + (i * 2); + uint8_t data[2] = {(uint8_t)(rgb565 & 0xFF), (uint8_t)((rgb565 >> 8) & 0xFF)}; + + if (!_writeBytes(regAddr, data, 2)) { + M5IOE1_LOG_E(TAG_LED, "Failed to write LED_RAM for index %d", i); + return M5IOE1_ERR_I2C_COMM; + } + + // 回读验证颜色数据 + // Read-back verification for color data + uint8_t actualData[2]; + if (!_readBytes(regAddr, actualData, 2)) { + M5IOE1_LOG_E(TAG_LED, "Failed to read back LED_RAM for index %d", i); + return M5IOE1_ERR_I2C_COMM; + } + uint16_t actualRgb565 = ((uint16_t)actualData[1] << 8) | actualData[0]; + if (actualRgb565 != rgb565) { + M5IOE1_LOG_E(TAG_LED, "LED color verification failed for index %d: expected=0x%04X, actual=0x%04X", i, + rgb565, actualRgb565); + return M5IOE1_FAIL; + } + } + + // 步骤 4: 更新缓存 + // Step 4: Update cache + + // 步骤 5: 可选刷新 + // Step 5: Optional refresh + if (autoRefresh) { + if (!_readReg(M5IOE1_REG_LED_CFG, &actualCfg)) { + M5IOE1_LOG_E(TAG_LED, "Failed to read LED_CFG for refresh"); + return M5IOE1_ERR_I2C_COMM; + } + actualCfg |= M5IOE1_LED_REFRESH; + if (!_writeReg(M5IOE1_REG_LED_CFG, actualCfg)) { + M5IOE1_LOG_E(TAG_LED, "Failed to trigger LED refresh"); + return M5IOE1_ERR_I2C_COMM; + } + } + + M5IOE1_LOG_I(TAG_LED, "Set %d LEDs successfully%s", count, autoRefresh ? " (refreshed)" : ""); + return M5IOE1_OK; +} + +// ============================ +// AW8737A 脉冲功能 +// AW8737A Pulse Functions +// ============================ + +m5ioe1_err_t M5IOE1::setAw8737aPulse(uint8_t pin, m5ioe1_aw8737a_pulse_t pulseNum, m5ioe1_aw8737a_refresh_t refresh) +{ + if (!_initialized) { + M5IOE1_LOG_E(TAG_AMP, "Device not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + // 验证引脚范围 (0-13) + // Validate pin range (0-13) + if (pin >= M5IOE1_MAX_GPIO_PINS) { + M5IOE1_LOG_E(TAG_AMP, "Invalid pin number: %d (valid range: 0-%d)", pin, M5IOE1_MAX_GPIO_PINS - 1); + return M5IOE1_ERR_INVALID_ARG; + } + + // 验证脉冲编号 (0-3) + // Validate pulse number (0-3) + if (pulseNum > M5IOE1_AW8737A_PULSE_3) { + M5IOE1_LOG_E(TAG_AMP, "Invalid pulse number: %d (valid range: 0-3)", pulseNum); + return M5IOE1_ERR_INVALID_ARG; + } + + // 检查引脚是否为输出模式,如果不是则自动配置为推挽输出 + // Check if pin is output mode, if not auto-configure as push-pull output + if (!_pinStates[pin].isOutput) { + M5IOE1_LOG_I(TAG_AMP, "AW8737A: Pin %d not output mode, auto-configuring as push-pull output", pin); + pinMode(pin, OUTPUT); + } + + // 保存配置到成员变量 + // Save configuration to member variables + _aw8737aConfigured = true; + _aw8737aPin = pin; + _aw8737aPulseNum = pulseNum; + + // 构建寄存器值(不含 REFRESH 位) + // Build register value (without REFRESH bit) + // [7] REFRESH | [6:5] NUM[1:0] | [4:0] GPIO[4:0] + uint8_t regValue = 0; + regValue |= (pin & M5IOE1_AW8737A_GPIO_MASK); // 位[4:0]: GPIO 选择 + // Bits[4:0]: GPIO selection + regValue |= ((pulseNum & M5IOE1_AW8737A_NUM_MASK) << M5IOE1_AW8737A_NUM_SHIFT); // 位[6:5]: 脉冲编号 + // Bits[6:5]: Pulse number + + // 写入寄存器(不含 REFRESH 位) + // Write register (without REFRESH bit) + if (!_writeReg(M5IOE1_REG_AW8737A_PULSE, regValue)) { + M5IOE1_LOG_E(TAG_AMP, "Failed to set AW8737A pulse config"); + return M5IOE1_ERR_I2C_COMM; + } + + // 更新缓存 + // Update cache + _aw8737aRegValue = regValue; + _aw8737aStateValid = true; + + // 回读验证 + // Read-back verification + uint8_t actualReg = 0; + if (!_readReg(M5IOE1_REG_AW8737A_PULSE, &actualReg)) { + M5IOE1_LOG_E(TAG_AMP, "Failed to read back AW8737A pulse register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 验证关键位是否匹配 + // Verify critical bits match + uint8_t actualValue = actualReg & 0x7F; + if (actualValue != regValue) { + M5IOE1_LOG_E(TAG_AMP, "AW8737A pulse verification failed: expected=0x%02X, actual=0x%02X", regValue, + actualValue); + return M5IOE1_FAIL; + } + + M5IOE1_LOG_I(TAG_AMP, "AW8737A pulse configured: pin=%d, num=%d (reg=0x%02X)", pin, pulseNum, regValue); + + // 如果需要立即刷新,调用 refreshAw8737aPulse + // If immediate refresh needed, call refreshAw8737aPulse + if (refresh == M5IOE1_AW8737A_REFRESH_NOW) { + return refreshAw8737aPulse(); + } + + _autoSnapshotUpdate(M5IOE1_SNAPSHOT_DOMAIN_AW8737A); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::refreshAw8737aPulse() +{ + if (!_initialized) { + M5IOE1_LOG_E(TAG_AMP, "Device not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + // 检查是否已配置 + // Check if configured + if (!_aw8737aConfigured) { + M5IOE1_LOG_W(TAG_AMP, "AW8737A pulse not configured, executing anyway"); + } + + // 检查引脚是否为输出模式,如果不是则自动配置为推挽输出 + // Check if pin is output mode, if not auto-configure as push-pull output + if (!_pinStates[_aw8737aPin].isOutput) { + M5IOE1_LOG_I(TAG_AMP, "AW8737A: Pin %d not output mode, auto-configuring as push-pull output", _aw8737aPin); + pinMode(_aw8737aPin, OUTPUT); + } + + // 读取当前寄存器值 + // Read current register value + uint8_t regValue = 0; + if (!_readReg(M5IOE1_REG_AW8737A_PULSE, ®Value)) { + M5IOE1_LOG_E(TAG_AMP, "Failed to read AW8737A pulse register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 设置 REFRESH 位 + // Set REFRESH bit + regValue |= M5IOE1_AW8737A_REFRESH; + + if (!_writeReg(M5IOE1_REG_AW8737A_PULSE, regValue)) { + M5IOE1_LOG_E(TAG_AMP, "Failed to refresh AW8737A pulse"); + return M5IOE1_ERR_I2C_COMM; + } + + // 输出详细日志 + // Output detailed log + const char* driveStr = (_pinStates[_aw8737aPin].drive == M5IOE1_DRIVE_PUSHPULL) ? "PUSH-PULL" : "OPEN-DRAIN"; + M5IOE1_LOG_I(TAG_AMP, "AW8737A pulse refresh: pin=%d, pulseNum=%d, mode=OUTPUT, drive=%s", _aw8737aPin, + _aw8737aPulseNum, driveStr); + + // 写入位 7 后等待 20ms,因为它会影响 I2C 通信 + // Wait 20ms after writing bit 7, as it affects I2C communication + M5IOE1_DELAY_MS(20); + + _autoSnapshotUpdate(M5IOE1_SNAPSHOT_DOMAIN_AW8737A); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setAw8737aMode(uint8_t pin, m5ioe1_aw8737a_mode_t mode, m5ioe1_aw8737a_refresh_t refresh) +{ + // Mode 直接映射到 Pulse (MODE_1=0pulse, MODE_2=1pulse, MODE_3=2pulses, MODE_4=3pulses) + // Mode directly maps to Pulse + if (mode > M5IOE1_AW8737A_MODE_4) { + M5IOE1_LOG_E(TAG_AMP, "Invalid AW8737A mode: %d (valid range: 0-3)", mode); + return M5IOE1_ERR_INVALID_ARG; + } + + return setAw8737aPulse(pin, (m5ioe1_aw8737a_pulse_t)mode, refresh); +} + +m5ioe1_err_t M5IOE1::refreshAw8737aMode() +{ + return refreshAw8737aPulse(); +} + +// ============================ +// RTC RAM 功能 +// RTC RAM Functions +// ============================ + +m5ioe1_err_t M5IOE1::writeRtcRAM(uint8_t offset, const uint8_t* data, uint8_t length) +{ + if (data == nullptr || offset >= M5IOE1_RTC_RAM_SIZE || length == 0 || (offset + length) > M5IOE1_RTC_RAM_SIZE) { + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t regAddr = M5IOE1_REG_RTC_RAM_START + offset; + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeBytes(regAddr, data, length)) { + M5IOE1_LOG_E(TAG_SYS, "Failed to write RTC_RAM register at offset %d", offset); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint8_t actualData[M5IOE1_RTC_RAM_SIZE]; + if (!_readBytes(regAddr, actualData, length)) { + M5IOE1_LOG_E(TAG_SYS, "Failed to read back RTC_RAM register at offset %d", offset); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + // 逐字节比较写入的数据 + // Compare written data byte by byte + bool allMatch = true; + for (uint8_t i = 0; i < length; i++) { + if (actualData[i] != data[i]) { + allMatch = false; + M5IOE1_LOG_E(TAG_SYS, "RTC_RAM verification failed at offset %d: expected=0x%02X, actual=0x%02X", + offset + i, data[i], actualData[i]); + break; + } + } + + if (!allMatch) { + return M5IOE1_FAIL; + } + + M5IOE1_LOG_I(TAG_SYS, "RTC_RAM write and verified: offset=%d, length=%d", offset, length); + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::readRtcRAM(uint8_t offset, uint8_t* data, uint8_t length) +{ + if (data == nullptr || offset >= M5IOE1_RTC_RAM_SIZE || length == 0 || (offset + length) > M5IOE1_RTC_RAM_SIZE) { + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t regAddr = M5IOE1_REG_RTC_RAM_START + offset; + return _readBytes(regAddr, data, length) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +// ============================ +// 系统配置 +// System Configuration +// ============================ + +m5ioe1_err_t M5IOE1::setI2cConfig(uint8_t sleepTime, m5ioe1_i2c_speed_t speed, m5ioe1_wake_edge_t wakeEdge, + m5ioe1_pull_config_t pullConfig) +{ + if (sleepTime > 15) { + M5IOE1_LOG_E(TAG_I2C, "Invalid I2C sleep time: %d", sleepTime); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_I2C, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint32_t targetFreq = (speed == M5IOE1_I2C_SPEED_400K) ? M5IOE1_I2C_FREQ_400K : M5IOE1_I2C_FREQ_100K; + bool speedChanged = (targetFreq != _requestedSpeed); + + uint8_t cfg = (sleepTime & M5IOE1_I2C_SLEEP_MASK); + if (speed == M5IOE1_I2C_SPEED_400K) cfg |= M5IOE1_I2C_SPEED_400K_BIT; + if (wakeEdge == M5IOE1_WAKE_EDGE_RISING) cfg |= M5IOE1_I2C_WAKE_RISING; + if (pullConfig == M5IOE1_PULL_DISABLED) cfg |= M5IOE1_I2C_PULL_OFF; + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg(M5IOE1_REG_I2C_CFG, cfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to write I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint8_t actualCfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &actualCfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read back I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证关键位是否匹配 + // Step 3: Verify critical bits match + uint8_t expectedSleep = sleepTime & M5IOE1_I2C_SLEEP_MASK; + uint8_t actualSleep = actualCfg & M5IOE1_I2C_SLEEP_MASK; + bool actualSpeed400k = (actualCfg & M5IOE1_I2C_SPEED_400K_BIT) != 0; + bool actualWakeRising = (actualCfg & M5IOE1_I2C_WAKE_RISING) != 0; + bool actualPullOff = (actualCfg & M5IOE1_I2C_PULL_OFF) != 0; + + if (actualSleep != expectedSleep || actualSpeed400k != (speed == M5IOE1_I2C_SPEED_400K) || + actualWakeRising != (wakeEdge == M5IOE1_WAKE_EDGE_RISING) || + actualPullOff != (pullConfig == M5IOE1_PULL_DISABLED)) { + M5IOE1_LOG_E(TAG_I2C, "I2C_CFG verification failed: expected=0x%02X, actual=0x%02X", cfg, actualCfg); + return M5IOE1_FAIL; + } + + // 步骤 4: 如果速度改变,切换主机 I2C 总线 + // Step 4: If speed changed, switch host I2C bus + if (speedChanged) { + M5IOE1_LOG_D(TAG_I2C, "Switching host I2C bus from %" PRIu32 " Hz to %" PRIu32 " Hz", _requestedSpeed, + targetFreq); + +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + _commFreq = targetFreq; + M5IOE1_LOG_D(TAG_I2C, "M5Unified I2C frequency updated to %" PRIu32 " Hz", targetFreq); + } else +#endif + if (_wire != nullptr) { + _wire->end(); + M5IOE1_DELAY_MS(10); + if (!_wire->begin(_sda, _scl, targetFreq)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to reinitialize I2C bus at %" PRIu32 " Hz", targetFreq); + // 回滚设备配置 + // Rollback device config + uint8_t rollbackCfg = actualCfg; + if (speed == M5IOE1_I2C_SPEED_400K) { + rollbackCfg &= ~M5IOE1_I2C_SPEED_400K_BIT; + } else { + rollbackCfg |= M5IOE1_I2C_SPEED_400K_BIT; + } + _writeReg(M5IOE1_REG_I2C_CFG, rollbackCfg); + return M5IOE1_ERR_I2C_CONFIG; + } + M5IOE1_DELAY_MS(10); + } +#else + esp_err_t ret; + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + if (_i2c_master_dev != nullptr) { + ret = i2c_master_bus_rm_device(_i2c_master_dev); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "Failed to remove I2C device: %s", esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + _i2c_master_dev = nullptr; + + i2c_device_config_t dev_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = _addr, + .scl_speed_hz = targetFreq, + .scl_wait_us = 0, + .flags = {.disable_ack_check = false}, + }; + + ret = i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "Failed to add I2C device at %" PRIu32 " Hz: %s", targetFreq, + esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + } + break; +#endif // M5IOE1_HAS_I2C_MASTER + +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + if (_i2c_device != nullptr) { + ret = i2c_bus_device_delete(&_i2c_device); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "Failed to delete I2C device: %s", esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + + _i2c_device = i2c_bus_device_create(_i2c_bus, _addr, targetFreq); + if (_i2c_device == nullptr) { + M5IOE1_LOG_E(TAG_I2C, "Failed to create I2C device at %" PRIu32 " Hz", targetFreq); + return M5IOE1_ERR_I2C_CONFIG; + } + } + break; +#endif // M5IOE1_HAS_I2C_BUS + +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: { + i2c_config_t i2c_conf = {}; + i2c_conf.mode = I2C_MODE_MASTER; + i2c_conf.sda_io_num = _sda; + i2c_conf.scl_io_num = _scl; + i2c_conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + i2c_conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + i2c_conf.master.clk_speed = targetFreq; + ret = i2c_param_config(_port, &i2c_conf); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "i2c_param_config failed: %s", esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + break; + } +#endif // !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + _commFreq = targetFreq; + M5IOE1_LOG_D(TAG_I2C, "M5Unified I2C frequency updated to %" PRIu32 " Hz", targetFreq); + break; +#endif + default: + break; + } +#endif + + // 步骤 5: 验证通信仍然有效 + // Step 5: Verify communication still works + uint16_t uid = 0; + if (!_readReg16(M5IOE1_REG_UID_L, &uid)) { + M5IOE1_LOG_E(TAG_I2C, "Communication failed after switching to %" PRIu32 " Hz", targetFreq); + // 回滚 + // Rollback + uint32_t originalFreq = _requestedSpeed; +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + _commFreq = originalFreq; + } else +#endif + if (_wire != nullptr) { + _wire->end(); + M5IOE1_DELAY_MS(10); + _wire->begin(_sda, _scl, originalFreq); + M5IOE1_DELAY_MS(10); + } +#else + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + if (_i2c_master_dev != nullptr) { + i2c_master_bus_rm_device(_i2c_master_dev); + i2c_device_config_t dev_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = _addr, + .scl_speed_hz = originalFreq, + .scl_wait_us = 0, + .flags = {.disable_ack_check = false}, + }; + i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + } + break; +#endif // M5IOE1_HAS_I2C_MASTER +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + if (_i2c_device != nullptr) { + i2c_bus_device_delete(&_i2c_device); + _i2c_device = i2c_bus_device_create(_i2c_bus, _addr, originalFreq); + } + break; +#endif // M5IOE1_HAS_I2C_BUS +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: { + i2c_config_t i2c_conf = {}; + i2c_conf.mode = I2C_MODE_MASTER; + i2c_conf.sda_io_num = _sda; + i2c_conf.scl_io_num = _scl; + i2c_conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + i2c_conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + i2c_conf.master.clk_speed = originalFreq; + i2c_param_config(_port, &i2c_conf); // best effort rollback + break; + } +#endif // !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + _commFreq = originalFreq; + break; +#endif + default: + break; + } +#endif + // 回滚设备配置 + // Rollback device config + uint8_t rollbackCfg = actualCfg; + if (speed == M5IOE1_I2C_SPEED_400K) { + rollbackCfg &= ~M5IOE1_I2C_SPEED_400K_BIT; + } else { + rollbackCfg |= M5IOE1_I2C_SPEED_400K_BIT; + } + _writeReg(M5IOE1_REG_I2C_CFG, rollbackCfg); + return M5IOE1_ERR_I2C_COMM; + } + + // 更新请求的速度 + // Update requested speed + _requestedSpeed = targetFreq; + M5IOE1_LOG_D(TAG_I2C, "Host I2C bus switched to %" PRIu32 " Hz", targetFreq); + } + + // 步骤 6: 验证成功,更新缓存 + // Step 6: Verification passed, update cache + _i2cConfig.sleepTime = sleepTime; + _i2cConfig.speed400k = (speed == M5IOE1_I2C_SPEED_400K); + _i2cConfig.wakeRising = (wakeEdge == M5IOE1_WAKE_EDGE_RISING); + _i2cConfig.pullOff = (pullConfig == M5IOE1_PULL_DISABLED); + _i2cConfigValid = true; + + // 若配置了休眠时间,自动启用 autoWake 防止数据丢失 + // If sleep time is configured, auto-enable autoWake to prevent data loss + if (sleepTime > 0 && !_autoWakeEnabled) { + setAutoWakeEnable(true); + M5IOE1_LOG_W(TAG_I2C, "I2C sleep enabled (sleepTime=%d), auto-wake automatically enabled", sleepTime); + } + + _updatePollingForI2cSleep(sleepTime); + + M5IOE1_LOG_I(TAG_I2C, "I2C config set and verified: sleep=%d, speed=%s, wake=%s, pull=%s", sleepTime, + speed == M5IOE1_I2C_SPEED_400K ? "400K" : "100K", + wakeEdge == M5IOE1_WAKE_EDGE_RISING ? "rising" : "falling", + pullConfig == M5IOE1_PULL_DISABLED ? "off" : "on"); + + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setI2cSleepTime(uint8_t sleepTime) +{ + if (sleepTime > 15) { + M5IOE1_LOG_E(TAG_I2C, "Invalid I2C sleep time: %d", sleepTime); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_I2C, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &cfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 清除 SLEEP 位 [3:0],设置新值 + // Clear SLEEP bits [3:0], set new value + cfg = (cfg & ~M5IOE1_I2C_SLEEP_MASK) | (sleepTime & M5IOE1_I2C_SLEEP_MASK); + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg(M5IOE1_REG_I2C_CFG, cfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to write I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint8_t actualCfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &actualCfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read back I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证 SLEEP 位是否匹配 + // Step 3: Verify SLEEP bits match + uint8_t actualSleep = actualCfg & M5IOE1_I2C_SLEEP_MASK; + if (actualSleep != sleepTime) { + M5IOE1_LOG_E(TAG_I2C, "I2C_CFG sleep time verification failed: expected=%d, actual=%d", sleepTime, actualSleep); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _i2cConfig.sleepTime = sleepTime; + _i2cConfigValid = true; + + M5IOE1_LOG_I(TAG_I2C, "I2C sleep time set and verified to %d", sleepTime); + + // 若配置了休眠时间,自动启用 autoWake 防止数据丢失 + // If sleep time is configured, auto-enable autoWake to prevent data loss + if (sleepTime > 0 && !_autoWakeEnabled) { + setAutoWakeEnable(true); + M5IOE1_LOG_W(TAG_I2C, "I2C sleep enabled, auto-wake automatically enabled"); + } + + _updatePollingForI2cSleep(sleepTime); + + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::getI2cSleepTime(uint8_t* sleepTime) +{ + if (sleepTime == nullptr) { + M5IOE1_LOG_E(TAG_I2C, "getI2cSleepTime sleepTime is null"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_I2C, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &cfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 更新缓存 + // Update cache + _i2cConfig.sleepTime = cfg & M5IOE1_I2C_SLEEP_MASK; + _i2cConfigValid = true; + + *sleepTime = _i2cConfig.sleepTime; + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setI2cWakeEdge(m5ioe1_wake_edge_t edge) +{ + if (!_initialized) { + M5IOE1_LOG_E(TAG_I2C, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &cfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 设置或清除 WAKE 位 [5] + // Set or clear WAKE bit [5] + if (edge == M5IOE1_WAKE_EDGE_RISING) { + cfg |= M5IOE1_I2C_WAKE_RISING; + } else { + cfg &= ~M5IOE1_I2C_WAKE_RISING; + } + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg(M5IOE1_REG_I2C_CFG, cfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to write I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint8_t actualCfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &actualCfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read back I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证 WAKE 位是否匹配 + // Step 3: Verify WAKE bit matches + bool actualWakeRising = (actualCfg & M5IOE1_I2C_WAKE_RISING) != 0; + bool expectedWakeRising = (edge == M5IOE1_WAKE_EDGE_RISING); + if (actualWakeRising != expectedWakeRising) { + M5IOE1_LOG_E(TAG_I2C, "I2C_CFG wake edge verification failed: expected=%s, actual=%s", + expectedWakeRising ? "rising" : "falling", actualWakeRising ? "rising" : "falling"); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _i2cConfig.wakeRising = expectedWakeRising; + _i2cConfigValid = true; + + M5IOE1_LOG_I(TAG_I2C, "I2C wake edge set and verified to %s", expectedWakeRising ? "rising" : "falling"); + + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::getI2cWakeEdge(m5ioe1_wake_edge_t* edge) +{ + if (edge == nullptr) { + M5IOE1_LOG_E(TAG_I2C, "getI2cWakeEdge edge is null"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_I2C, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &cfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 更新缓存 + // Update cache + _i2cConfig.wakeRising = (cfg & M5IOE1_I2C_WAKE_RISING) != 0; + _i2cConfigValid = true; + + *edge = _i2cConfig.wakeRising ? M5IOE1_WAKE_EDGE_RISING : M5IOE1_WAKE_EDGE_FALLING; + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::setI2cPullConfig(m5ioe1_pull_config_t config) +{ + if (!_initialized) { + M5IOE1_LOG_E(TAG_I2C, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &cfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 设置或清除 INT_PU/PD 位 [6] + // Set or clear INT_PU/PD bit [6] + if (config == M5IOE1_PULL_DISABLED) { + cfg |= M5IOE1_I2C_PULL_OFF; + } else { + cfg &= ~M5IOE1_I2C_PULL_OFF; + } + + // 步骤 1: 写入寄存器 + // Step 1: Write register + if (!_writeReg(M5IOE1_REG_I2C_CFG, cfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to write I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 回读验证 + // Step 2: Read-back verification + uint8_t actualCfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &actualCfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read back I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 3: 验证 INT_PU/PD 位是否匹配 + // Step 3: Verify INT_PU/PD bit matches + bool actualPullOff = (actualCfg & M5IOE1_I2C_PULL_OFF) != 0; + bool expectedPullOff = (config == M5IOE1_PULL_DISABLED); + if (actualPullOff != expectedPullOff) { + M5IOE1_LOG_E(TAG_I2C, "I2C_CFG pull config verification failed: expected=%s, actual=%s", + expectedPullOff ? "disabled" : "enabled", actualPullOff ? "disabled" : "enabled"); + return M5IOE1_FAIL; + } + + // 步骤 4: 验证成功,更新缓存 + // Step 4: Verification passed, update cache + _i2cConfig.pullOff = expectedPullOff; + _i2cConfigValid = true; + + M5IOE1_LOG_I(TAG_I2C, "I2C internal pull-up set and verified to %s", expectedPullOff ? "disabled" : "enabled"); + + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::getI2cPullConfig(m5ioe1_pull_config_t* config) +{ + if (config == nullptr) { + M5IOE1_LOG_E(TAG_I2C, "getI2cPullConfig config is null"); + return M5IOE1_ERR_INVALID_ARG; + } + if (!_initialized) { + M5IOE1_LOG_E(TAG_I2C, "Not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint8_t cfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &cfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read I2C_CFG register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 更新缓存 + // Update cache + _i2cConfig.pullOff = (cfg & M5IOE1_I2C_PULL_OFF) != 0; + _i2cConfigValid = true; + + *config = _i2cConfig.pullOff ? M5IOE1_PULL_DISABLED : M5IOE1_PULL_ENABLED; + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::factoryReset() +{ + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + if (!_writeReg(M5IOE1_REG_FACTORY_RESET, M5IOE1_FACTORY_RESET_KEY)) { + return M5IOE1_ERR_I2C_COMM; + } + + M5IOE1_DELAY_MS(100); + _initialized = false; + _pinStatesValid = false; + _pwmStatesValid = false; + _adcStateValid = false; + _clearPinStates(); + _clearPwmStates(); + _clearAdcState(); + M5IOE1_LOG_W(TAG_SYS, "Factory reset complete. Call begin() to reinitialize."); + + return M5IOE1_OK; +} + +// ============================ +// 自动唤醒功能 +// Auto Wake Feature +// ============================ + +void M5IOE1::setAutoWakeEnable(bool enable) +{ + _autoWakeEnabled = enable; + if (enable) { + _lastCommTime = M5IOE1_GET_TIME_MS(); + } + M5IOE1_LOG_I(TAG_I2C, "Auto wake %s", enable ? "enabled" : "disabled"); +} + +bool M5IOE1::isAutoWakeEnabled() const +{ + return _autoWakeEnabled; +} + +m5ioe1_err_t M5IOE1::sendWakeSignal() +{ +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + M5IOE1_M5UNIFIED_SEND_WAKE(_m5_i2c, _addr, _commFreq); + return M5IOE1_OK; + } +#endif + M5IOE1_I2C_ARDUINO_SEND_WAKE(_wire, _addr); + return M5IOE1_OK; +#else + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + return M5IOE1_I2C_MASTER_SEND_WAKE(_i2c_master_bus, _addr) == ESP_OK ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +#endif +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + return M5IOE1_I2C_BUS_SEND_WAKE(_i2c_device, M5IOE1_REG_REV) == ESP_OK ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +#endif +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: + return M5IOE1_I2C_LEGACY_SEND_WAKE(_port, _addr) == ESP_OK ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +#endif +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + return M5IOE1_M5UNIFIED_SEND_WAKE(_m5_i2c, _addr, _commFreq) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +#endif + default: + return M5IOE1_ERR_INTERNAL; + } +#endif +} + +void M5IOE1::_checkAutoWake() +{ + if (!_autoWakeEnabled || !_i2cConfigValid || _i2cConfig.sleepTime == 0) return; + + uint32_t now = M5IOE1_GET_TIME_MS(); + uint32_t elapsed = now - _lastCommTime; + + // 如果距离上次通信超过1秒,发送唤醒信号 + // If more than 1 second since last communication, send wake signal + if (elapsed >= 1000) { + sendWakeSignal(); + M5IOE1_DELAY_MS(10); + } + _lastCommTime = now; +} + +void M5IOE1::_updatePollingForI2cSleep(uint8_t sleepTime) +{ + if (_intMode != M5IOE1_INT_MODE_POLLING) return; + +#ifdef ARDUINO + if (sleepTime > 0) { + if (_pollTask != nullptr) { + _cleanupPollingArduino(); + } + } else { + if (_pollTask == nullptr) { + _setupPollingArduino(); + } + } +#else + if (sleepTime > 0) { + if (_pollTask != nullptr) { + _cleanupPolling(); + } + } else { + if (_pollTask == nullptr) { + _setupPolling(); + } + } +#endif +} + +// ============================ +// 状态快照功能 +// State Snapshot Functions +// ============================ + +void M5IOE1::setAutoSnapshot(bool enable) +{ + _autoSnapshot = enable; +} + +bool M5IOE1::isAutoSnapshotEnabled() const +{ + return _autoSnapshot; +} + +m5ioe1_err_t M5IOE1::updateSnapshot() +{ + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + bool gpio = _snapshotPinStates(); + bool pwm = _snapshotPwmStates(); + bool adc = _snapshotAdcState(); + bool aw8737a = _snapshotAw8737a(); + + return (gpio && pwm && adc && aw8737a) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +// ============================ +// 调试功能 +// Debug Functions +// ============================ + +m5ioe1_err_t M5IOE1::getModeReg(uint16_t* reg) +{ + if (reg == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + return _readReg16(M5IOE1_REG_GPIO_MODE_L, reg) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +m5ioe1_err_t M5IOE1::getOutputReg(uint16_t* reg) +{ + if (reg == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + return _readReg16(M5IOE1_REG_GPIO_OUT_L, reg) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +m5ioe1_err_t M5IOE1::getInputReg(uint16_t* reg) +{ + if (reg == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + return _readReg16(M5IOE1_REG_GPIO_IN_L, reg) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +m5ioe1_err_t M5IOE1::getPullUpReg(uint16_t* reg) +{ + if (reg == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + return _readReg16(M5IOE1_REG_GPIO_PU_L, reg) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +m5ioe1_err_t M5IOE1::getPullDownReg(uint16_t* reg) +{ + if (reg == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + return _readReg16(M5IOE1_REG_GPIO_PD_L, reg) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +m5ioe1_err_t M5IOE1::getDriveReg(uint16_t* reg) +{ + if (reg == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + return _readReg16(M5IOE1_REG_GPIO_DRV_L, reg) ? M5IOE1_OK : M5IOE1_ERR_I2C_COMM; +} + +// ============================ +// 配置验证 +// Configuration Validation +// ============================ + +m5ioe1_validation_t M5IOE1::validateConfig(uint8_t pin, m5ioe1_config_type_t configType, bool enable) +{ + m5ioe1_validation_t result = {false, {0}, 0xFF}; + + // 基本验证 + // Basic validation + if (!_isValidPin(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), "Invalid pin %d", pin); + return result; + } + + if (!_initialized) { + snprintf(result.error_msg, sizeof(result.error_msg), "Not initialized"); + return result; + } + + // 如果禁用,无需冲突检查 + // If disabling, no conflict check needed + if (!enable) { + result.valid = true; + return result; + } + + uint8_t mutexPin = 0xFF; + + switch (configType) { + case M5IOE1_CONFIG_GPIO_INPUT: + case M5IOE1_CONFIG_GPIO_OUTPUT: + // 检查引脚是否用于特殊功能 + // Check if pin is being used for special functions + if (_hasActivePwm(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for PWM. Disable first (e.g. setPwmDuty)", pin); + return result; + } + if (_hasActiveAdc(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d is used for ADC. Disable first (disableAdc())", pin); + return result; + } + if (_isNeopixelPin(pin) && _isLedEnabled()) { + snprintf(result.error_msg, sizeof(result.error_msg), + "Pin %d (IO14) used for NeoPixel; call disableLeds()", pin); + return result; + } + break; + + case M5IOE1_CONFIG_GPIO_INTERRUPT: + // 检查中断互斥约束 + // Check interrupt mutex constraint + if (_getInterruptMutexPin(pin, &mutexPin)) { + if (_hasActiveInterrupt(mutexPin)) { + snprintf(result.error_msg, sizeof(result.error_msg), "Interrupt conflict: IO%d and IO%d are mutex", + pin + 1, mutexPin + 1); + result.conflicting_pin = mutexPin; + return result; + } + } + // 检查 IO5 的 I2C 睡眠模式冲突 + // Check I2C sleep mode conflict for IO5 + if (pin == 4 && _hasI2cSleepEnabled()) { // IO5 is pin index 4 + snprintf(result.error_msg, sizeof(result.error_msg), "IO5 interrupt disabled when I2C sleep enabled"); + return result; + } + break; + + case M5IOE1_CONFIG_ADC: + // 检查引脚是否支持 ADC + // Check if pin supports ADC + if (!_isAdcPin(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), "Pin %d does not support ADC", pin); + return result; + } + // 检查引脚是否配置为输出 + // Check if pin is configured as output + if (_pinStatesValid && _pinStates[pin].isOutput) { + snprintf(result.error_msg, sizeof(result.error_msg), "Pin %d is configured as output", pin); + return result; + } + break; + + case M5IOE1_CONFIG_PWM: + // 检查引脚是否支持 PWM + // Check if pin supports PWM + if (!_isPwmPin(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), "Pin %d does not support PWM", pin); + return result; + } + break; + + case M5IOE1_CONFIG_NEOPIXEL: + // NeoPixel 仅在 IO14 上工作(引脚索引 13) + // NeoPixel only works on IO14 (pin index 13) + if (!_isNeopixelPin(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), "NeoPixel only supported on IO14 (pin 13)"); + return result; + } + // 检查引脚是否有活动中断 + // Check if pin has active interrupt + if (_hasActiveInterrupt(pin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "IO14 has active interrupt, conflicts with NeoPixel"); + return result; + } + // 检查中断互斥(IO10 和 IO14 是互斥的) + // Check interrupt mutex (IO10 and IO14 are mutex) + if (_getInterruptMutexPin(pin, &mutexPin)) { + if (_hasActiveInterrupt(mutexPin)) { + snprintf(result.error_msg, sizeof(result.error_msg), + "IO10 has interrupt enabled, mutex with IO14 NeoPixel"); + result.conflicting_pin = mutexPin; + return result; + } + } + break; + + case M5IOE1_CONFIG_I2C_SLEEP: + // I2C 睡眠模式禁用 IO5 中断 + // I2C sleep mode disables IO5 interrupt + if (_hasActiveInterrupt(4)) { // IO5 is pin index 4 + snprintf(result.error_msg, sizeof(result.error_msg), "I2C sleep mode will disable IO5 interrupt"); + result.conflicting_pin = 4; + return result; + } + break; + + default: + snprintf(result.error_msg, sizeof(result.error_msg), "Unknown config type"); + return result; + } + + result.valid = true; + return result; +} + +// ============================ +// 内部辅助函数 +// Internal Helper Functions +// ============================ + +bool M5IOE1::_writeReg(uint8_t reg, uint8_t value) +{ + _checkAutoWake(); + for (int attempt = 0; attempt < M5IOE1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + if (M5IOE1_M5UNIFIED_WRITE_BYTE(_m5_i2c, _addr, reg, value, _commFreq)) { + M5IOE1_LOG_D(TAG_I2C, "Write Reg[0x%02X] <- 0x%02X", reg, value); + return true; + } + } else +#endif + if (M5IOE1_I2C_ARDUINO_WRITE_BYTE(_wire, _addr, reg, value)) { + M5IOE1_LOG_D(TAG_I2C, "Write Reg[0x%02X] <- 0x%02X", reg, value); + return true; + } +#else + bool ok = false; + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + ok = (M5IOE1_I2C_MASTER_WRITE_BYTE(_i2c_master_dev, reg, value) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + ok = (M5IOE1_I2C_BUS_WRITE_BYTE(_i2c_device, reg, value) == ESP_OK); + break; +#endif +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: + ok = (M5IOE1_I2C_LEGACY_WRITE_BYTE(_port, _addr, reg, value) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + ok = M5IOE1_M5UNIFIED_WRITE_BYTE(_m5_i2c, _addr, reg, value, _commFreq); + break; +#endif + default: + ok = false; + break; + } + if (ok) { + M5IOE1_LOG_D(TAG_I2C, "Write Reg[0x%02X] <- 0x%02X", reg, value); + return true; + } +#endif + if (attempt + 1 < M5IOE1_I2C_RETRY_COUNT) { + M5IOE1_DELAY_MS(M5IOE1_I2C_RETRY_DELAY_MS); + } + } + M5IOE1_LOG_E(TAG_I2C, "Write Reg[0x%02X] failed", reg); + return false; +} + +bool M5IOE1::_writeReg16(uint8_t reg, uint16_t value) +{ + _checkAutoWake(); + for (int attempt = 0; attempt < M5IOE1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + if (M5IOE1_M5UNIFIED_WRITE_REG16(_m5_i2c, _addr, reg, value, _commFreq)) { + M5IOE1_LOG_D(TAG_I2C, "Write16 Reg[0x%02X] <- 0x%04X", reg, value); + return true; + } + } else +#endif + if (M5IOE1_I2C_ARDUINO_WRITE_REG16(_wire, _addr, reg, value)) { + M5IOE1_LOG_D(TAG_I2C, "Write16 Reg[0x%02X] <- 0x%04X", reg, value); + return true; + } +#else + bool ok = false; + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + ok = (M5IOE1_I2C_MASTER_WRITE_REG16(_i2c_master_dev, reg, value) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + ok = (M5IOE1_I2C_BUS_WRITE_REG16(_i2c_device, reg, value) == ESP_OK); + break; +#endif +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: + ok = (M5IOE1_I2C_LEGACY_WRITE_REG16(_port, _addr, reg, value) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + ok = M5IOE1_M5UNIFIED_WRITE_REG16(_m5_i2c, _addr, reg, value, _commFreq); + break; +#endif + default: + ok = false; + break; + } + if (ok) { + M5IOE1_LOG_D(TAG_I2C, "Write16 Reg[0x%02X] <- 0x%04X", reg, value); + return true; + } +#endif + if (attempt + 1 < M5IOE1_I2C_RETRY_COUNT) { + M5IOE1_DELAY_MS(M5IOE1_I2C_RETRY_DELAY_MS); + } + } + M5IOE1_LOG_E(TAG_I2C, "Write16 Reg[0x%02X] failed", reg); + return false; +} + +bool M5IOE1::_readReg(uint8_t reg, uint8_t* value) +{ + if (value == nullptr) { + M5IOE1_LOG_E(TAG_I2C, "Read Reg[0x%02X] failed: null value", reg); + return false; + } + + _checkAutoWake(); + for (int attempt = 0; attempt < M5IOE1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + if (M5IOE1_M5UNIFIED_READ_BYTE(_m5_i2c, _addr, reg, value, _commFreq)) { + M5IOE1_LOG_D(TAG_I2C, "Read Reg[0x%02X] -> 0x%02X", reg, *value); + return true; + } + } else +#endif + if (M5IOE1_I2C_ARDUINO_READ_BYTE(_wire, _addr, reg, value)) { + M5IOE1_LOG_D(TAG_I2C, "Read Reg[0x%02X] -> 0x%02X", reg, *value); + return true; + } +#else + bool ok = false; + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + ok = (M5IOE1_I2C_MASTER_READ_BYTE(_i2c_master_dev, reg, value) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + ok = (M5IOE1_I2C_BUS_READ_BYTE(_i2c_device, reg, value) == ESP_OK); + break; +#endif +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: + ok = (M5IOE1_I2C_LEGACY_READ_BYTE(_port, _addr, reg, value) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + ok = M5IOE1_M5UNIFIED_READ_BYTE(_m5_i2c, _addr, reg, value, _commFreq); + break; +#endif + default: + ok = false; + break; + } + if (ok) { + M5IOE1_LOG_D(TAG_I2C, "Read Reg[0x%02X] -> 0x%02X", reg, *value); + return true; + } +#endif + if (attempt + 1 < M5IOE1_I2C_RETRY_COUNT) { + M5IOE1_DELAY_MS(M5IOE1_I2C_RETRY_DELAY_MS); + } + } + M5IOE1_LOG_E(TAG_I2C, "Read Reg[0x%02X] failed", reg); + return false; +} + +bool M5IOE1::_readReg16(uint8_t reg, uint16_t* value) +{ + if (value == nullptr) { + M5IOE1_LOG_E(TAG_I2C, "Read16 Reg[0x%02X] failed: null value", reg); + return false; + } + + _checkAutoWake(); + for (int attempt = 0; attempt < M5IOE1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + if (M5IOE1_M5UNIFIED_READ_REG16(_m5_i2c, _addr, reg, value, _commFreq)) { + M5IOE1_LOG_D(TAG_I2C, "Read16 Reg[0x%02X] -> 0x%04X", reg, *value); + return true; + } + } else +#endif + if (M5IOE1_I2C_ARDUINO_READ_REG16(_wire, _addr, reg, value)) { + M5IOE1_LOG_D(TAG_I2C, "Read16 Reg[0x%02X] -> 0x%04X", reg, *value); + return true; + } +#else + bool ok = false; + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + ok = (M5IOE1_I2C_MASTER_READ_REG16(_i2c_master_dev, reg, value) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + ok = (M5IOE1_I2C_BUS_READ_REG16(_i2c_device, reg, value) == ESP_OK); + break; +#endif +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: + ok = (M5IOE1_I2C_LEGACY_READ_REG16(_port, _addr, reg, value) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + ok = M5IOE1_M5UNIFIED_READ_REG16(_m5_i2c, _addr, reg, value, _commFreq); + break; +#endif + default: + ok = false; + break; + } + if (ok) { + M5IOE1_LOG_D(TAG_I2C, "Read16 Reg[0x%02X] -> 0x%04X", reg, *value); + return true; + } +#endif + if (attempt + 1 < M5IOE1_I2C_RETRY_COUNT) { + M5IOE1_DELAY_MS(M5IOE1_I2C_RETRY_DELAY_MS); + } + } + M5IOE1_LOG_E(TAG_I2C, "Read16 Reg[0x%02X] failed", reg); + return false; +} + +bool M5IOE1::_writeBytes(uint8_t reg, const uint8_t* data, uint8_t len) +{ + if (data == nullptr && len > 0) { + M5IOE1_LOG_E(TAG_I2C, "WriteBytes Reg[0x%02X] len=%d failed: null data", reg, len); + return false; + } + + _checkAutoWake(); + for (int attempt = 0; attempt < M5IOE1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + if (M5IOE1_M5UNIFIED_WRITE_BYTES(_m5_i2c, _addr, reg, len, data, _commFreq)) { + M5IOE1_LOG_V(TAG_I2C, "WriteBytes Reg[0x%02X] len=%d: %02X %02X %02X...", reg, len, + len > 0 ? data[0] : 0, len > 1 ? data[1] : 0, len > 2 ? data[2] : 0); + return true; + } + } else +#endif + if (M5IOE1_I2C_ARDUINO_WRITE_BYTES(_wire, _addr, reg, len, data)) { + M5IOE1_LOG_V(TAG_I2C, "WriteBytes Reg[0x%02X] len=%d: %02X %02X %02X...", reg, len, len > 0 ? data[0] : 0, + len > 1 ? data[1] : 0, len > 2 ? data[2] : 0); + return true; + } +#else + bool ok = false; + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + ok = (M5IOE1_I2C_MASTER_WRITE_BYTES(_i2c_master_dev, reg, len, data) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + ok = (M5IOE1_I2C_BUS_WRITE_BYTES(_i2c_device, reg, len, data) == ESP_OK); + break; +#endif +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: + ok = (M5IOE1_I2C_LEGACY_WRITE_BYTES(_port, _addr, reg, len, data) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + ok = M5IOE1_M5UNIFIED_WRITE_BYTES(_m5_i2c, _addr, reg, len, data, _commFreq); + break; +#endif + default: + ok = false; + break; + } + if (ok) { + M5IOE1_LOG_V(TAG_I2C, "WriteBytes Reg[0x%02X] len=%d: %02X %02X %02X...", reg, len, len > 0 ? data[0] : 0, + len > 1 ? data[1] : 0, len > 2 ? data[2] : 0); + return true; + } +#endif + if (attempt + 1 < M5IOE1_I2C_RETRY_COUNT) { + M5IOE1_DELAY_MS(M5IOE1_I2C_RETRY_DELAY_MS); + } + } + M5IOE1_LOG_E(TAG_I2C, "WriteBytes Reg[0x%02X] len=%d failed", reg, len); + return false; +} + +bool M5IOE1::_readBytes(uint8_t reg, uint8_t* data, uint8_t len) +{ + if (data == nullptr && len > 0) { + M5IOE1_LOG_E(TAG_I2C, "ReadBytes Reg[0x%02X] len=%d failed: null data", reg, len); + return false; + } + + _checkAutoWake(); + for (int attempt = 0; attempt < M5IOE1_I2C_RETRY_COUNT; ++attempt) { +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + if (M5IOE1_M5UNIFIED_READ_BYTES(_m5_i2c, _addr, reg, len, data, _commFreq)) { + M5IOE1_LOG_V(TAG_I2C, "ReadBytes Reg[0x%02X] len=%d: %02X %02X %02X...", reg, len, + len > 0 ? data[0] : 0, len > 1 ? data[1] : 0, len > 2 ? data[2] : 0); + return true; + } + } else +#endif + if (M5IOE1_I2C_ARDUINO_READ_BYTES(_wire, _addr, reg, len, data)) { + M5IOE1_LOG_V(TAG_I2C, "ReadBytes Reg[0x%02X] len=%d: %02X %02X %02X...", reg, len, len > 0 ? data[0] : 0, + len > 1 ? data[1] : 0, len > 2 ? data[2] : 0); + return true; + } +#else + bool ok = false; + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + ok = (M5IOE1_I2C_MASTER_READ_BYTES(_i2c_master_dev, reg, len, data) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + ok = (M5IOE1_I2C_BUS_READ_BYTES(_i2c_device, reg, len, data) == ESP_OK); + break; +#endif +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: + ok = (M5IOE1_I2C_LEGACY_READ_BYTES(_port, _addr, reg, len, data) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + ok = M5IOE1_M5UNIFIED_READ_BYTES(_m5_i2c, _addr, reg, len, data, _commFreq); + break; +#endif + default: + ok = false; + break; + } + if (ok) { + M5IOE1_LOG_V(TAG_I2C, "ReadBytes Reg[0x%02X] len=%d: %02X %02X %02X...", reg, len, len > 0 ? data[0] : 0, + len > 1 ? data[1] : 0, len > 2 ? data[2] : 0); + return true; + } +#endif + if (attempt + 1 < M5IOE1_I2C_RETRY_COUNT) { + M5IOE1_DELAY_MS(M5IOE1_I2C_RETRY_DELAY_MS); + } + } + M5IOE1_LOG_E(TAG_I2C, "ReadBytes Reg[0x%02X] len=%d failed", reg, len); + return false; +} + +bool M5IOE1::_isValidPin(uint8_t pin) +{ + return pin < M5IOE1_MAX_GPIO_PINS; +} + +bool M5IOE1::_isAdcPin(uint8_t pin) +{ + // ADC 引脚:IO2(1), IO4(3), IO5(4), IO7(6) + // ADC pins: IO2(1), IO4(3), IO5(4), IO7(6) + return (pin == 1 || pin == 3 || pin == 4 || pin == 6); +} + +bool M5IOE1::_isPwmPin(uint8_t pin) +{ + // PWM 引脚:IO8(7), IO9(8), IO10(9), IO11(10) + // PWM pins: IO8(7), IO9(8), IO10(9), IO11(10) + return (pin == 7 || pin == 8 || pin == 9 || pin == 10); +} + +uint8_t M5IOE1::_getAdcChannel(uint8_t pin) +{ + switch (pin) { + case 1: + return 1; // IO2 + case 3: + return 2; // IO4 + case 4: + return 3; // IO5 + case 6: + return 4; // IO7 + default: + return 0; + } +} + +uint8_t M5IOE1::_getPwmChannel(uint8_t pin) +{ + switch (pin) { + case 8: + return 0; // IO9 -> PWM1 + case 7: + return 1; // IO8 -> PWM2 + case 10: + return 2; // IO11 -> PWM3 + case 9: + return 3; // IO10 -> PWM4 + default: + return 0; + } +} + +void M5IOE1::_clearPinStates() +{ + memset(_pinStates, 0, sizeof(_pinStates)); + _pinStatesValid = false; +} + +void M5IOE1::_clearPwmStates() +{ + memset(_pwmStates, 0, sizeof(_pwmStates)); + _pwmFrequency = 0; + _pwmStatesValid = false; +} + +void M5IOE1::_clearAdcState() +{ + memset(&_adcState, 0, sizeof(_adcState)); + _adcStateValid = false; +} + +bool M5IOE1::_snapshotPinStates() +{ + if (!_initialized) return false; + + uint16_t modeReg = 0, outReg = 0, inReg = 0, puReg = 0, pdReg = 0, drvReg = 0, ieReg = 0, itReg = 0; + + if (!_readReg16(M5IOE1_REG_GPIO_MODE_L, &modeReg)) return false; + if (!_readReg16(M5IOE1_REG_GPIO_OUT_L, &outReg)) return false; + if (!_readReg16(M5IOE1_REG_GPIO_IN_L, &inReg)) return false; + if (!_readReg16(M5IOE1_REG_GPIO_PU_L, &puReg)) return false; + if (!_readReg16(M5IOE1_REG_GPIO_PD_L, &pdReg)) return false; + if (!_readReg16(M5IOE1_REG_GPIO_DRV_L, &drvReg)) return false; + if (!_readReg16(M5IOE1_REG_GPIO_IE_L, &ieReg)) return false; + if (!_readReg16(M5IOE1_REG_GPIO_IP_L, &itReg)) return false; + + for (uint8_t pin = 0; pin < M5IOE1_MAX_GPIO_PINS; pin++) { + _pinStates[pin].isOutput = (modeReg & (1 << pin)) != 0; + _pinStates[pin].outputLevel = (outReg & (1 << pin)) ? 1 : 0; + _pinStates[pin].inputLevel = (inReg & (1 << pin)) ? 1 : 0; + _pinStates[pin].pull = (puReg & (1 << pin)) ? 1 : ((pdReg & (1 << pin)) ? 2 : 0); + _pinStates[pin].drive = (drvReg & (1 << pin)) ? 1 : 0; + _pinStates[pin].intrEnabled = (ieReg & (1 << pin)) != 0; + _pinStates[pin].intrRising = (itReg & (1 << pin)) != 0; + } + + _pinStatesValid = true; + return true; +} + +bool M5IOE1::_snapshotPwmStates() +{ + if (!_initialized) return false; + + if (!_readReg16(M5IOE1_REG_PWM_FREQ_L, &_pwmFrequency)) return false; + + for (uint8_t ch = 0; ch < M5IOE1_MAX_PWM_CHANNELS; ch++) { + uint8_t regL = M5IOE1_REG_PWM1_DUTY_L + (ch * 2); + uint16_t data = 0; + if (!_readReg16(regL, &data)) return false; + + _pwmStates[ch].duty12 = data & 0x0FFF; + _pwmStates[ch].enabled = (data & ((uint16_t)M5IOE1_PWM_ENABLE << 8)) != 0; + _pwmStates[ch].polarity = (data & ((uint16_t)M5IOE1_PWM_POLARITY << 8)) != 0; + } + + _pwmStatesValid = true; + return true; +} + +bool M5IOE1::_snapshotAdcState() +{ + if (!_initialized) return false; + + uint8_t ctrl = 0; + if (!_readReg(M5IOE1_REG_ADC_CTRL, &ctrl)) return false; + + _adcState.activeChannel = ctrl & M5IOE1_ADC_CH_MASK; + _adcState.busy = (ctrl & M5IOE1_ADC_BUSY) != 0; + + if (!_adcState.busy) { + if (!_readReg16(M5IOE1_REG_ADC_DATA_L, &_adcState.lastValue)) { + _adcState.lastValue = 0; + } + } + + _adcStateValid = true; + return true; +} + +void M5IOE1::_autoSnapshotUpdate(uint8_t domains) +{ + if (!_autoSnapshot || !_initialized) return; + + if (domains & M5IOE1_SNAPSHOT_DOMAIN_GPIO) { + _snapshotPinStates(); + } + if (domains & M5IOE1_SNAPSHOT_DOMAIN_PWM) { + _snapshotPwmStates(); + } + if (domains & M5IOE1_SNAPSHOT_DOMAIN_ADC) { + _snapshotAdcState(); + } + if (domains & M5IOE1_SNAPSHOT_DOMAIN_AW8737A) { + _snapshotAw8737a(); + } +} + +bool M5IOE1::_isValidI2cFrequency(uint32_t speed) +{ + return (speed == M5IOE1_I2C_FREQ_100K || speed == M5IOE1_I2C_FREQ_400K); +} + +m5ioe1_err_t M5IOE1::getI2cSpeed(m5ioe1_i2c_speed_t* speed) +{ + if (speed == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_initialized) return M5IOE1_ERR_NOT_INIT; + + uint8_t cfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &cfg)) return M5IOE1_ERR_I2C_COMM; + + // 更新缓存 + // Update cache + _i2cConfig.speed400k = (cfg & M5IOE1_I2C_SPEED_400K_BIT) != 0; + _i2cConfigValid = true; + + *speed = _i2cConfig.speed400k ? M5IOE1_I2C_SPEED_400K : M5IOE1_I2C_SPEED_100K; + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::switchI2cSpeed(m5ioe1_i2c_speed_t speed) +{ + if (!_initialized) { + M5IOE1_LOG_E(TAG_I2C, "Cannot switch I2C speed: device not initialized"); + return M5IOE1_ERR_NOT_INIT; + } + + uint32_t targetFreq = (speed == M5IOE1_I2C_SPEED_400K) ? M5IOE1_I2C_FREQ_400K : M5IOE1_I2C_FREQ_100K; + + // 如果目标速度与当前速度相同,直接返回成功 + // If target speed is same as current, return success directly + if (targetFreq == _requestedSpeed) { + M5IOE1_LOG_I(TAG_I2C, "I2C speed already at %" PRIu32 " Hz, no change needed", targetFreq); + return M5IOE1_OK; + } + + // 步骤 1: 读取当前 I2C 配置 + // Step 1: Read current I2C config + uint8_t i2cCfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &i2cCfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to read I2C config register"); + return M5IOE1_ERR_I2C_COMM; + } + + // 步骤 2: 根据目标速度设置或清除 400KHz 模式位 + // Step 2: Set or clear 400KHz mode bit based on target speed + if (speed == M5IOE1_I2C_SPEED_400K) { + i2cCfg |= M5IOE1_I2C_SPEED_400K_BIT; + } else { + i2cCfg &= ~M5IOE1_I2C_SPEED_400K_BIT; + } + + if (!_writeReg(M5IOE1_REG_I2C_CFG, i2cCfg)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to write I2C config register"); + return M5IOE1_ERR_I2C_COMM; + } + + M5IOE1_LOG_D(TAG_I2C, "M5IOE1 I2C config set to %" PRIu32 " Hz mode", targetFreq); + + // 步骤 3: 短暂延迟以允许设备处理配置更改 + // Step 3: Small delay to allow device to process the configuration change + M5IOE1_DELAY_MS(5); + + // 步骤 4: 将主机 I2C 总线切换到目标速度 + // Step 4: Switch host I2C bus to target speed +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + _commFreq = targetFreq; + M5IOE1_LOG_D(TAG_I2C, "M5Unified I2C frequency updated to %" PRIu32 " Hz", targetFreq); + } else +#endif + if (_wire != nullptr) { + // 必须使用 Wire.end() + Wire.begin() 在 ESP32 上正确切换 I2C 频率 + // Must use Wire.end() + Wire.begin() to properly switch I2C frequency on ESP32 + _wire->end(); + M5IOE1_DELAY_MS(10); + if (!_wire->begin(_sda, _scl, targetFreq)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to re-initialize I2C bus at %" PRIu32 " Hz", targetFreq); + // 尝试恢复到原来的速度 + // Try to recover with original speed + uint32_t originalFreq = (speed == M5IOE1_I2C_SPEED_400K) ? M5IOE1_I2C_FREQ_100K : M5IOE1_I2C_FREQ_400K; + _wire->begin(_sda, _scl, originalFreq); + // 恢复设备配置 + // Revert device config + if (speed == M5IOE1_I2C_SPEED_400K) { + i2cCfg &= ~M5IOE1_I2C_SPEED_400K_BIT; + } else { + i2cCfg |= M5IOE1_I2C_SPEED_400K_BIT; + } + _writeReg(M5IOE1_REG_I2C_CFG, i2cCfg); + return M5IOE1_ERR_I2C_CONFIG; + } + M5IOE1_DELAY_MS(10); + M5IOE1_LOG_D(TAG_I2C, "Host I2C bus switched to %" PRIu32 " Hz", targetFreq); + } +#else + // ESP-IDF:处理不同的驱动类型 + // ESP-IDF: Handle different driver types + esp_err_t ret; + + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + // 对于 i2c_master 驱动:删除设备并以新速度重新添加 + // For i2c_master driver: remove device and add with new speed + if (_i2c_master_dev != nullptr) { + ret = i2c_master_bus_rm_device(_i2c_master_dev); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "Failed to remove I2C device: %s", esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + _i2c_master_dev = nullptr; + + // 以目标速度重新创建设备句柄 + // Recreate device handle with target speed + i2c_device_config_t dev_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = _addr, + .scl_speed_hz = targetFreq, + .scl_wait_us = 0, + .flags = + { + .disable_ack_check = false, + }, + }; + + ret = i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "Failed to add I2C device at %" PRIu32 " Hz: %s", targetFreq, + esp_err_to_name(ret)); + // 尝试恢复到原来的速度 + // Try to recover with original speed + uint32_t originalFreq = + (speed == M5IOE1_I2C_SPEED_400K) ? M5IOE1_I2C_FREQ_100K : M5IOE1_I2C_FREQ_400K; + dev_config.scl_speed_hz = originalFreq; + i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + return M5IOE1_ERR_I2C_CONFIG; + } + M5IOE1_LOG_D(TAG_I2C, "I2C master device recreated at %" PRIu32 " Hz", targetFreq); + } + break; +#endif // M5IOE1_HAS_I2C_MASTER + +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + // 对于 i2c_bus 驱动:删除设备并以新速度创建 + // For i2c_bus driver: delete device and create with new speed + if (_i2c_device != nullptr) { + ret = i2c_bus_device_delete(&_i2c_device); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "Failed to delete I2C device: %s", esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + + // 以目标速度重新创建设备句柄 + // Recreate device handle with target speed + _i2c_device = i2c_bus_device_create(_i2c_bus, _addr, targetFreq); + if (_i2c_device == nullptr) { + M5IOE1_LOG_E(TAG_I2C, "Failed to create I2C device at %" PRIu32 " Hz", targetFreq); + // 尝试恢复到原来的速度 + // Try to recover with original speed + uint32_t originalFreq = + (speed == M5IOE1_I2C_SPEED_400K) ? M5IOE1_I2C_FREQ_100K : M5IOE1_I2C_FREQ_400K; + _i2c_device = i2c_bus_device_create(_i2c_bus, _addr, originalFreq); + return M5IOE1_ERR_I2C_CONFIG; + } + M5IOE1_LOG_D(TAG_I2C, "I2C bus device recreated at %" PRIu32 " Hz", targetFreq); + } + break; +#endif // M5IOE1_HAS_I2C_BUS + +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: { + i2c_config_t i2c_conf = {}; + i2c_conf.mode = I2C_MODE_MASTER; + i2c_conf.sda_io_num = _sda; + i2c_conf.scl_io_num = _scl; + i2c_conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + i2c_conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + i2c_conf.master.clk_speed = targetFreq; + ret = i2c_param_config(_port, &i2c_conf); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "i2c_param_config failed: %s", esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + M5IOE1_LOG_D(TAG_I2C, "Legacy I2C reconfigured to %" PRIu32 " Hz", targetFreq); + break; + } +#endif // !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + _commFreq = targetFreq; + M5IOE1_LOG_D(TAG_I2C, "M5Unified I2C frequency updated to %" PRIu32 " Hz", targetFreq); + break; +#endif + default: + M5IOE1_LOG_E(TAG_I2C, "Unknown I2C driver type"); + return M5IOE1_ERR_INTERNAL; + } +#endif + + // 步骤 5: 验证通信仍然有效 + // Step 5: Verify communication still works + uint16_t uid = 0; + if (!_readReg16(M5IOE1_REG_UID_L, &uid)) { + M5IOE1_LOG_E(TAG_I2C, "Communication failed after switching to %" PRIu32 " Hz, reverting", targetFreq); + + // 恢复设备配置 + // Revert device config + if (speed == M5IOE1_I2C_SPEED_400K) { + i2cCfg &= ~M5IOE1_I2C_SPEED_400K_BIT; + } else { + i2cCfg |= M5IOE1_I2C_SPEED_400K_BIT; + } + uint32_t originalFreq = (speed == M5IOE1_I2C_SPEED_400K) ? M5IOE1_I2C_FREQ_100K : M5IOE1_I2C_FREQ_400K; + +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + _commFreq = originalFreq; + } else +#endif + if (_wire != nullptr) { + _wire->end(); + M5IOE1_DELAY_MS(10); + _wire->begin(_sda, _scl, originalFreq); + M5IOE1_DELAY_MS(10); + } +#else + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + if (_i2c_master_dev != nullptr) { + i2c_master_bus_rm_device(_i2c_master_dev); + i2c_device_config_t dev_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = _addr, + .scl_speed_hz = originalFreq, + .scl_wait_us = 0, + .flags = {.disable_ack_check = false}, + }; + i2c_master_bus_add_device(_i2c_master_bus, &dev_config, &_i2c_master_dev); + } + break; +#endif // M5IOE1_HAS_I2C_MASTER +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + if (_i2c_device != nullptr) { + i2c_bus_device_delete(&_i2c_device); + _i2c_device = i2c_bus_device_create(_i2c_bus, _addr, originalFreq); + } + break; +#endif // M5IOE1_HAS_I2C_BUS +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: { + i2c_config_t i2c_conf = {}; + i2c_conf.mode = I2C_MODE_MASTER; + i2c_conf.sda_io_num = _sda; + i2c_conf.scl_io_num = _scl; + i2c_conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + i2c_conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + i2c_conf.master.clk_speed = originalFreq; + i2c_param_config(_port, &i2c_conf); // best effort rollback + break; + } +#endif // !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + _commFreq = originalFreq; + break; +#endif + default: + break; + } +#endif + _writeReg(M5IOE1_REG_I2C_CFG, i2cCfg); + return M5IOE1_ERR_I2C_COMM; + } + + // 更新 I2C 配置缓存和请求速度 + // Update I2C config cache and requested speed + _i2cConfig.speed400k = (speed == M5IOE1_I2C_SPEED_400K); + _requestedSpeed = targetFreq; + + M5IOE1_LOG_I(TAG_I2C, "Successfully switched to %" PRIu32 " Hz I2C mode", targetFreq); + return M5IOE1_OK; +} + +// ============================ +// 地址候选与 REV 规则辅助实现 +// Address candidate & REV rule helpers implementation +// ============================ + +// 地址段 <-> 允许 REV 字符列表 +// Address range -> allowed REV characters (each char = one allowed revision) +const M5IOE1::m5ioe1_rev_rule_t M5IOE1::kRevRules[] = { + {0x6F, 0x76, "A"}, + {0x4F, 0x50, "W"}, +}; + +const M5IOE1::m5ioe1_rev_rule_t* M5IOE1::_findRevRule(uint8_t addr) const +{ + for (size_t i = 0; i < sizeof(kRevRules) / sizeof(kRevRules[0]); ++i) { + if (addr >= kRevRules[i].start && addr <= kRevRules[i].end) { + return &kRevRules[i]; + } + } + return nullptr; +} + +bool M5IOE1::_isRevisionAllowed(uint8_t addr, uint8_t rev) const +{ + const m5ioe1_rev_rule_t* rule = _findRevRule(addr); + if (!rule) return true; // 无规则则放行 + const char* p = rule->valid_revs; + while (*p) { + if ((uint8_t)(*p) == rev) return true; + ++p; + } + return false; +} + +// 对当前已应用的 _addr 探测 REV 寄存器,不打 ERROR 日志(直接调底层读取一次) +// Probe REV register for currently applied _addr, no ERROR log (single low-level read) +M5IOE1::RevCheckResult M5IOE1::_checkAddressRevision(uint8_t addr) const +{ + // 读取一次,不走 retry + uint8_t rev = 0; + bool ok = false; + +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + ok = M5IOE1_M5UNIFIED_READ_BYTE(_m5_i2c, addr, M5IOE1_REG_REV, &rev, _commFreq); + } else +#endif + { + ok = M5IOE1_I2C_ARDUINO_READ_BYTE(_wire, addr, M5IOE1_REG_REV, &rev); + } +#else + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: + ok = (M5IOE1_I2C_MASTER_READ_BYTE(_i2c_master_dev, M5IOE1_REG_REV, &rev) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: + ok = (M5IOE1_I2C_BUS_READ_BYTE(_i2c_device, M5IOE1_REG_REV, &rev) == ESP_OK); + break; +#endif +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: + ok = (M5IOE1_I2C_LEGACY_READ_BYTE(_port, addr, M5IOE1_REG_REV, &rev) == ESP_OK); + break; +#endif +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + ok = M5IOE1_M5UNIFIED_READ_BYTE(_m5_i2c, addr, M5IOE1_REG_REV, &rev, _commFreq); + break; +#endif + default: + ok = false; + break; + } +#endif + + if (!ok) { + return RevCheckResult::NO_RESPONSE; + } + + const m5ioe1_rev_rule_t* rule = _findRevRule(addr); + if (!rule) { + M5IOE1_LOG_D(TAG_I2C, "Addr 0x%02X REV=0x%02X(%c), no rule -> allow", addr, rev, rev); + return RevCheckResult::NO_RULE; + } + if (_isRevisionAllowed(addr, rev)) { + M5IOE1_LOG_D(TAG_I2C, "Addr 0x%02X REV=0x%02X(%c) matched", addr, rev, rev); + return RevCheckResult::MATCHED; + } + M5IOE1_LOG_W(TAG_I2C, "Addr 0x%02X REV=0x%02X(%c) not in allowed list '%s', abort", addr, rev, rev, + rule->valid_revs); + return RevCheckResult::MISMATCH; +} + +// 构建候选地址列表 +// Build candidate address list +// 规则: +// 0xFF (AUTODETECT): 按固定序列 0x4F, 0x6F, 0x70..0x76, 0x50 +// 0x6F: 如失败,兜底尝试 0x4F +// 0x4F: 如失败,兜底尝试 0x6F +// 其他: 如失败,兜底尝试 0x4F, 0x6F +uint8_t M5IOE1::_buildAddressCandidates(uint8_t requested, uint8_t* candidates) +{ + uint8_t n = 0; + if (requested == M5IOE1_AUTODETECT_ADDR) { + // 自动探测序列:0x4F, 0x6F, 0x70..0x76, 0x50 + // Auto-detect sequence: 0x4F, 0x6F, 0x70..0x76, 0x50 + candidates[n++] = 0x4F; + candidates[n++] = 0x6F; + for (uint8_t a = 0x70; a <= 0x76; ++a) candidates[n++] = a; + candidates[n++] = 0x50; + } else { + // 显式地址:先放用户地址,再放兜底地址 + // Explicit address: user address first, then fallback addresses + candidates[n++] = requested; + if (requested == 0x6F) { + candidates[n++] = 0x4F; + } else if (requested == 0x4F) { + candidates[n++] = 0x6F; + } else { + candidates[n++] = 0x4F; + candidates[n++] = 0x6F; + } + } + return n; +} + +// 将候选地址(及速度)应用到当前驱动上下文(重建 device handle 如有必要) +// Apply candidate address and speed to current driver context (rebuild device handle if needed) +m5ioe1_err_t M5IOE1::_applyAddressToDriver(uint8_t candidate, uint32_t freqHz) +{ + _addr = candidate; +#ifdef ARDUINO +#if M5IOE1_HAS_M5UNIFIED_I2C + if (_m5_i2c) { + _commFreq = freqHz; + return M5IOE1_OK; + } +#endif + // TwoWire: 需要重新 begin 以切换频率 + // TwoWire: need to re-begin to switch frequency + if (_wire != nullptr) { + _wire->end(); + M5IOE1_DELAY_MS(50); + if (!_wire->begin(_sda, _scl, freqHz)) { + M5IOE1_LOG_E(TAG_I2C, "Failed to re-init I2C @%" PRIu32 "Hz for addr 0x%02X", freqHz, candidate); + return M5IOE1_ERR_I2C_CONFIG; + } + M5IOE1_DELAY_MS(50); + } + return M5IOE1_OK; +#else + switch (_i2cDriverType) { +#if M5IOE1_HAS_I2C_MASTER + case M5IOE1_I2C_DRIVER_SELF_CREATED: + case M5IOE1_I2C_DRIVER_MASTER: { + // 删除旧 device handle,以新地址和速度重新创建 + // Delete old device handle and recreate with new address and speed + if (_i2c_master_dev) { + i2c_master_bus_rm_device(_i2c_master_dev); + _i2c_master_dev = nullptr; + } + i2c_device_config_t dev_cfg = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = candidate, + .scl_speed_hz = freqHz, + .scl_wait_us = 0, + .flags = {.disable_ack_check = false}, + }; + esp_err_t ret = i2c_master_bus_add_device(_i2c_master_bus, &dev_cfg, &_i2c_master_dev); + if (ret != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "Failed to add device 0x%02X @%" PRIu32 "Hz: %s", candidate, freqHz, + esp_err_to_name(ret)); + return M5IOE1_ERR_I2C_CONFIG; + } + return M5IOE1_OK; + } +#endif +#if M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_BUS: { + if (_i2c_device) { + i2c_bus_device_delete(&_i2c_device); + _i2c_device = nullptr; + } + _i2c_device = i2c_bus_device_create(_i2c_bus, candidate, freqHz); + if (!_i2c_device) { + M5IOE1_LOG_E(TAG_I2C, "Failed to create bus device 0x%02X @%" PRIu32 "Hz", candidate, freqHz); + return M5IOE1_ERR_I2C_CONFIG; + } + return M5IOE1_OK; + } +#endif +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + case M5IOE1_I2C_DRIVER_LEGACY: { + // Legacy: 重新配置 I2C 参数以更新速度 + // Legacy: reconfigure I2C params to update speed + i2c_config_t cfg = {}; + cfg.mode = I2C_MODE_MASTER; + cfg.sda_io_num = _sda; + cfg.scl_io_num = _scl; + cfg.sda_pullup_en = GPIO_PULLUP_ENABLE; + cfg.scl_pullup_en = GPIO_PULLUP_ENABLE; + cfg.master.clk_speed = freqHz; + esp_err_t r = i2c_param_config(_port, &cfg); + if (r != ESP_OK) { + M5IOE1_LOG_E(TAG_I2C, "Legacy i2c_param_config failed @%" PRIu32 "Hz: %s", freqHz, esp_err_to_name(r)); + return M5IOE1_ERR_I2C_CONFIG; + } + return M5IOE1_OK; + } +#endif +#if M5IOE1_HAS_M5UNIFIED_I2C + case M5IOE1_I2C_DRIVER_M5UNIFIED: + _commFreq = freqHz; + return M5IOE1_OK; +#endif + default: + return M5IOE1_ERR_I2C_CONFIG; + } +#endif +} + +// 对单个候选地址执行完整初始化尝试(100K → 800ms → 100K → 400K) +// Full init attempt for one candidate (100K→800ms→100K→400K) +m5ioe1_err_t M5IOE1::_tryInitAtAddress(uint8_t candidate) +{ + // 以 100KHz 切换候选地址 + m5ioe1_err_t err = _applyAddressToDriver(candidate, M5IOE1_I2C_FREQ_100K); + if (err != M5IOE1_OK) return err; + + // 发送唤醒 + sendWakeSignal(); + M5IOE1_DELAY_MS(10); + + // 尝试1:100K + if (_initDevice()) return M5IOE1_OK; + + // 尝试2:等待 800ms 后再尝试 100K + M5IOE1_LOG_W(TAG_I2C, "Addr 0x%02X: 100K failed, waiting 800ms and retrying...", candidate); + M5IOE1_DELAY_MS(800); + sendWakeSignal(); + M5IOE1_DELAY_MS(10); + if (_initDevice()) return M5IOE1_OK; + + // 尝试3:切换到 400K + M5IOE1_LOG_W(TAG_I2C, "Addr 0x%02X: 100K retry failed, trying 400K...", candidate); + err = _applyAddressToDriver(candidate, M5IOE1_I2C_FREQ_400K); + if (err != M5IOE1_OK) return err; + + sendWakeSignal(); + M5IOE1_DELAY_MS(10); + if (_initDevice()) return M5IOE1_OK; + + M5IOE1_LOG_E(TAG_I2C, "Addr 0x%02X: all attempts failed", candidate); + return M5IOE1_ERR_I2C_COMM; +} + +// 初始化成功后的收尾流程 +// Post-init finalization +m5ioe1_err_t M5IOE1::_finishInit(m5ioe1_int_mode_t intMode) +{ + _initialized = true; + + // 强制配置 I2C(用户请求的频率 + 关闭休眠) + // Force configure I2C (user requested speed + disable sleep) + m5ioe1_i2c_speed_t targetSpeed = + (_requestedSpeed == M5IOE1_I2C_FREQ_400K) ? M5IOE1_I2C_SPEED_400K : M5IOE1_I2C_SPEED_100K; + + m5ioe1_i2c_speed_t currentSpeed; + if (getI2cSpeed(¤tSpeed) == M5IOE1_OK) { + M5IOE1_LOG_I(TAG_I2C, "Current I2C speed: %s, Requested: %s", + (currentSpeed == M5IOE1_I2C_SPEED_400K) ? "400K" : "100K", + (targetSpeed == M5IOE1_I2C_SPEED_400K) ? "400K" : "100K"); + } + + if (setI2cConfig(0, targetSpeed, M5IOE1_WAKE_EDGE_FALLING, M5IOE1_PULL_ENABLED) != M5IOE1_OK) { + M5IOE1_LOG_W(TAG_I2C, "Failed to set I2C config"); + } + +#if defined(M5IOE1_HAS_M5UNIFIED_I2C) && M5IOE1_HAS_M5UNIFIED_I2C +#ifdef ARDUINO + if (_m5_i2c) _commFreq = _requestedSpeed; +#else + if (_i2cDriverType == M5IOE1_I2C_DRIVER_M5UNIFIED) _commFreq = _requestedSpeed; +#endif +#endif + + _snapshotPinStates(); + _snapshotPwmStates(); + _snapshotAdcState(); + _snapshotAw8737a(); + _snapshotI2cConfig(); + M5IOE1_LOG_D(TAG_I2C, "Snapshot completed (pins/pwm/adc/aw8737a/i2c)"); + + M5IOE1_LOG_I(TAG_I2C, "M5IOE1 initialized at address 0x%02X (I2C: %" PRIu32 " Hz)", _addr, _requestedSpeed); + + if (intMode != M5IOE1_INT_MODE_DISABLED) { + m5ioe1_err_t err = setInterruptMode(intMode); + if (err != M5IOE1_OK) { + _initialized = false; + return err; + } + } + return M5IOE1_OK; +} + +// ============================ +// 地址解析与初始化核心流程 +// Address resolution and init core flow +// ============================ +// 调用此函数前,调用方必须已完成 bus 初始化和 _requestedSpeed 设置。 +// Caller must have completed bus init and set _requestedSpeed before calling this. +// 此函数负责地址探测/兜底、REV 校验、完整通信初始化、以及调用 _finishInit。 +// This function handles address probe/fallback, REV validation, full comms init, and calls _finishInit. +m5ioe1_err_t M5IOE1::_resolveAndInit(uint8_t requestedAddr, m5ioe1_int_mode_t intMode) +{ + uint8_t candidates[kMaxCandidates]; + uint8_t nCandidates = _buildAddressCandidates(requestedAddr, candidates); + + bool firstCandidate = true; + + for (uint8_t i = 0; i < nCandidates; ++i) { + uint8_t candidate = candidates[i]; + + // 兜底候选(非首个候选)以及自动探测的所有候选都需要 REV 校验 + // Fallback candidates (non-first) and all auto-detect candidates need REV check + bool needRevCheck = (!firstCandidate) || (requestedAddr == M5IOE1_AUTODETECT_ADDR); + firstCandidate = false; + + if (needRevCheck) { + // 先以 100K 读取 REV。若无响应,再仅切到 400K 读取 REV。 + // Read REV first at 100K. If there is no response, try REV-only probe at 400K. + if (_applyAddressToDriver(candidate, M5IOE1_I2C_FREQ_100K) != M5IOE1_OK) { + continue; + } + + RevCheckResult revResult = _checkAddressRevision(candidate); + if (revResult == RevCheckResult::NO_RESPONSE) { + if (_applyAddressToDriver(candidate, M5IOE1_I2C_FREQ_400K) != M5IOE1_OK) { + continue; + } + revResult = _checkAddressRevision(candidate); + } + + if (revResult == RevCheckResult::MISMATCH) { + // REV 不匹配:立即中止,不继续探测后续地址 + // REV mismatch: abort immediately, do not try further addresses + M5IOE1_LOG_E(TAG_I2C, "Addr 0x%02X: REV mismatch, aborting address resolution", candidate); + return M5IOE1_ERR_I2C_COMM; + } + if (revResult == RevCheckResult::NO_RESPONSE) { + M5IOE1_LOG_D(TAG_I2C, "Addr 0x%02X: no response during REV probe, trying next", candidate); + continue; + } + // MATCHED 或 NO_RULE:继续完整初始化 + M5IOE1_LOG_D(TAG_I2C, "Addr 0x%02X: REV probe passed (%s), proceeding with full init", candidate, + (revResult == RevCheckResult::MATCHED) ? "MATCHED" : "NO_RULE"); + } else { + M5IOE1_LOG_D(TAG_I2C, "Addr 0x%02X: primary address, skipping REV pre-check", candidate); + } + + // 执行完整初始化尝试(内部会以 100K 开始) + // Perform full init attempt (starts at 100K internally) + m5ioe1_err_t err = _tryInitAtAddress(candidate); + if (err == M5IOE1_OK) { + M5IOE1_LOG_I(TAG_I2C, "Device found at address 0x%02X", candidate); + + // 兜底成功:用户显式指定了地址,但通过备用地址才完成初始化 + // 明确告知用户硬件实际地址与传入参数不一致,建议修正调用方代码以避免后续维护混淆 + // Fallback success: user explicitly specified an address but init succeeded via a fallback + // candidate. Notify the user that the hardware's actual address differs from the argument + // and recommend updating the caller to prevent future maintenance confusion. + if (requestedAddr != M5IOE1_AUTODETECT_ADDR && candidate != requestedAddr) { + M5IOE1_LOG_W(TAG_I2C, + "[Address Fallback Notice] M5IOE1 initialization completed using a fallback address. " + "Requested address: 0x%02X, actual device address: 0x%02X. " + "Please update your initialization parameters to 0x%02X to ensure deterministic behavior " + "and avoid relying on the fallback mechanism.", + requestedAddr, candidate, candidate); + } + + return _finishInit(intMode); + } + + M5IOE1_LOG_W(TAG_I2C, "Addr 0x%02X: full init failed, trying next candidate", candidate); + } + + M5IOE1_LOG_E(TAG_I2C, "No M5IOE1 device found (requested=0x%02X)", requestedAddr); + return M5IOE1_ERR_I2C_COMM; +} + +bool M5IOE1::_initDevice() +{ + // 读取设备信息以验证通信 + // Read device info to verify communication + uint16_t uid = 0; + uint8_t version = 0; + + if (!_readReg16(M5IOE1_REG_UID_L, &uid)) { + M5IOE1_LOG_E(TAG_SYS, "Failed to read device UID"); + return false; + } + + if (!_readReg(M5IOE1_REG_REV, &version)) { + M5IOE1_LOG_E(TAG_SYS, "Failed to read device version"); + return false; + } + + M5IOE1_LOG_I(TAG_SYS, "Device UID: 0x%04X, FW Version: %02X(%c)", uid, version, version); + return true; +} + +void M5IOE1::_handleInterrupt() +{ + // 步骤 1: 读取一次中断状态 + // Step 1: Read interrupt status once + uint16_t status = 0; + if (getInterruptStatus(&status) != M5IOE1_OK) return; + + if (status == 0) return; + + // 步骤 2: 立即写入0清除所有中断(避免在处理回调期间丢失新中断) + // Step 2: Immediately write 0 to clear all interrupts (avoid losing new interrupts during callback processing) + _writeReg16(M5IOE1_REG_GPIO_IS_L, 0); + + // 步骤 3: 处理所有触发的引脚回调 + // Step 3: Process all triggered pin callbacks + for (uint8_t pin = 0; pin < M5IOE1_MAX_GPIO_PINS; pin++) { + if ((status & (1 << pin)) && _callbacks[pin].enabled) { + if (_enableDefaultIsrLog) { + M5IOE1_LOG_I(TAG_IRQ, "Pin %d triggered by %s edge", pin, + _callbacks[pin].rising ? "RISING" : "FALLING"); + } + + if (_callbacks[pin].callbackArg != nullptr) { + _callbacks[pin].callbackArg(_callbacks[pin].arg); + } else if (_callbacks[pin].callback != nullptr) { + _callbacks[pin].callback(); + } + } + } +} + +bool M5IOE1::_pinsConflict(uint8_t a, uint8_t b) +{ + // 中断冲突对(1-based IO 编号): + // Interrupt conflict pairs (1-based IO numbers): + // (1,6), (2,3), (7,12), (8,9), (10,14), (11,13) + uint8_t A = a + 1, B = b + 1; + + auto eq = [](uint8_t x, uint8_t y, uint8_t m, uint8_t n) { return (x == m && y == n) || (x == n && y == m); }; + + return eq(A, B, 1, 6) || eq(A, B, 2, 3) || eq(A, B, 7, 12) || eq(A, B, 8, 9) || eq(A, B, 10, 14) || + eq(A, B, 11, 13); +} + +bool M5IOE1::_hasConflictingInterrupt(uint8_t pin) +{ + for (uint8_t i = 0; i < M5IOE1_MAX_GPIO_PINS; i++) { + if (i != pin && _pinStates[i].intrEnabled && _pinsConflict(pin, i)) { + return true; + } + } + return false; +} + +// ============================ +// 配置验证辅助函数 +// Configuration Validation Helpers +// ============================ + +bool M5IOE1::_getInterruptMutexPin(uint8_t pin, uint8_t* mutexPin) +{ + if (mutexPin == nullptr) return false; + + // 中断互斥对(0-based 引脚索引): + // Interrupt mutex pairs (0-based pin indices): + // IO1(0) <-> IO6(5) + // IO2(1) <-> IO3(2) + // IO7(6) <-> IO12(11) + // IO8(7) <-> IO9(8) + // IO10(9) <-> IO14(13) + // IO11(10) <-> IO13(12) + + static const uint8_t mutexPairs[][2] = { + {0, 5}, // IO1 <-> IO6 + {1, 2}, // IO2 <-> IO3 + {6, 11}, // IO7 <-> IO12 + {7, 8}, // IO8 <-> IO9 + {9, 13}, // IO10 <-> IO14 + {10, 12} // IO11 <-> IO13 + }; + + for (size_t i = 0; i < sizeof(mutexPairs) / sizeof(mutexPairs[0]); i++) { + if (pin == mutexPairs[i][0]) { + *mutexPin = mutexPairs[i][1]; + return true; + } + if (pin == mutexPairs[i][1]) { + *mutexPin = mutexPairs[i][0]; + return true; + } + } + + return false; +} + +bool M5IOE1::_isNeopixelPin(uint8_t pin) +{ + // NeoPixel LED 功能仅在 IO14 上可用(引脚索引 13) + // NeoPixel LED function only available on IO14 (pin index 13) + return (pin == 13); +} + +bool M5IOE1::_hasActiveInterrupt(uint8_t pin) +{ + if (!_isValidPin(pin)) return false; + if (!_pinStatesValid) return false; + return _pinStates[pin].intrEnabled; +} + +bool M5IOE1::_hasActiveAdc(uint8_t pin) +{ + if (!_isAdcPin(pin)) return false; + if (!_adcStateValid) return false; + + // 检查 ADC 当前是否正在使用此引脚的通道 + // Check if the ADC is currently using this pin's channel + uint8_t channel = _getAdcChannel(pin); + return (_adcState.activeChannel == channel); +} + +bool M5IOE1::_hasActivePwm(uint8_t pin) +{ + if (!_isPwmPin(pin)) return false; + if (!_pwmStatesValid) return false; + + uint8_t channel = _getPwmChannel(pin); + return _pwmStates[channel].enabled; +} + +bool M5IOE1::_hasI2cSleepEnabled() +{ + if (!_i2cConfigValid) return false; + return (_i2cConfig.sleepTime > 0); +} + +bool M5IOE1::_isLedEnabled() +{ + // 直接读取 LED_CFG 寄存器 + // Read LED_CFG register directly + uint8_t ledCfg = 0; + if (!_readReg(M5IOE1_REG_LED_CFG, &ledCfg)) { + return false; + } + uint8_t ledCount = ledCfg & M5IOE1_LED_NUM_MASK; + return (ledCount > 0); +} + +// ============================ +// I2C 配置快照 +// I2C Config Snapshot +// ============================ + +void M5IOE1::_clearI2cConfig() +{ + memset(&_i2cConfig, 0, sizeof(_i2cConfig)); + _i2cConfigValid = false; +} + +bool M5IOE1::_snapshotI2cConfig() +{ + if (!_initialized) return false; + + uint8_t cfg = 0; + if (!_readReg(M5IOE1_REG_I2C_CFG, &cfg)) return false; + + _i2cConfig.sleepTime = cfg & M5IOE1_I2C_SLEEP_MASK; + _i2cConfig.speed400k = (cfg & M5IOE1_I2C_SPEED_400K_BIT) != 0; + _i2cConfig.wakeRising = (cfg & M5IOE1_I2C_WAKE_RISING) != 0; + _i2cConfig.pullOff = (cfg & M5IOE1_I2C_PULL_OFF) != 0; + _i2cConfigValid = true; + + return true; +} + +bool M5IOE1::_snapshotAw8737a() +{ + if (!_initialized) return false; + + uint8_t regValue = 0; + if (!_readReg(M5IOE1_REG_AW8737A_PULSE, ®Value)) { + M5IOE1_LOG_E(TAG_AMP, "Failed to read AW8737A pulse register"); + _aw8737aStateValid = false; + return false; + } + + // 解析寄存器值(不含 REFRESH 位) + // Parse register value (without REFRESH bit) + _aw8737aRegValue = regValue & 0x7F; + _aw8737aPin = regValue & M5IOE1_AW8737A_GPIO_MASK; + _aw8737aPulseNum = (m5ioe1_aw8737a_pulse_t)((regValue >> M5IOE1_AW8737A_NUM_SHIFT) & M5IOE1_AW8737A_NUM_MASK); + _aw8737aStateValid = true; + + return true; +} + +void M5IOE1::_clearAw8737a() +{ + _aw8737aConfigured = false; + _aw8737aPin = 0; + _aw8737aPulseNum = M5IOE1_AW8737A_PULSE_0; + _aw8737aRegValue = 0; + _aw8737aStateValid = false; +} + +// ============================ +// 快照验证 +// Snapshot Verification +// ============================ + +m5ioe1_snapshot_verify_t M5IOE1::verifySnapshot() +{ + m5ioe1_snapshot_verify_t result = {}; + result.consistent = true; + + if (!_initialized) { + result.consistent = false; + return result; + } + + // 验证 GPIO 寄存器 + // Verify GPIO registers + if (_pinStatesValid) { + uint16_t actualMode = 0, actualOutput = 0; + + if (_readReg16(M5IOE1_REG_GPIO_MODE_L, &actualMode) && _readReg16(M5IOE1_REG_GPIO_OUT_L, &actualOutput)) { + // 从缓存构建期望值 + // Build expected values from cache + uint16_t expectedMode = 0, expectedOutput = 0; + for (uint8_t i = 0; i < M5IOE1_MAX_GPIO_PINS; i++) { + if (_pinStates[i].isOutput) { + expectedMode |= (1 << i); + } + if (_pinStates[i].outputLevel) { + expectedOutput |= (1 << i); + } + } + + result.expected_mode = expectedMode; + result.actual_mode = actualMode; + result.expected_output = expectedOutput; + result.actual_output = actualOutput; + + if (expectedMode != actualMode || expectedOutput != actualOutput) { + result.gpio_mismatch = true; + result.consistent = false; + } + } else { + result.consistent = false; + } + } + + // 验证 PWM 寄存器 + // Verify PWM registers + if (_pwmStatesValid) { + uint16_t actualFreq = 0; + if (_readReg16(M5IOE1_REG_PWM_FREQ_L, &actualFreq)) { + if (actualFreq != _pwmFrequency) { + result.pwm_mismatch = true; + result.consistent = false; + } + } + + for (uint8_t ch = 0; ch < M5IOE1_MAX_PWM_CHANNELS; ch++) { + uint8_t regL = M5IOE1_REG_PWM1_DUTY_L + (ch * 2); + uint16_t data = 0; + if (_readReg16(regL, &data)) { + uint16_t actualDuty = data & 0x0FFF; + bool actualEnabled = (data & ((uint16_t)M5IOE1_PWM_ENABLE << 8)) != 0; + bool actualPolarity = (data & ((uint16_t)M5IOE1_PWM_POLARITY << 8)) != 0; + + if (actualDuty != _pwmStates[ch].duty12 || actualEnabled != _pwmStates[ch].enabled || + actualPolarity != _pwmStates[ch].polarity) { + result.pwm_mismatch = true; + result.consistent = false; + } + } + } + } + + // 验证 ADC 寄存器 + // Verify ADC registers + if (_adcStateValid) { + uint8_t ctrl = 0; + if (_readReg(M5IOE1_REG_ADC_CTRL, &ctrl)) { + uint8_t actualChannel = ctrl & M5IOE1_ADC_CH_MASK; + if (actualChannel != _adcState.activeChannel) { + result.adc_mismatch = true; + result.consistent = false; + } + } + } + + // 验证 AW8737A 寄存器 + // Verify AW8737A registers + if (_aw8737aStateValid) { + uint8_t actualReg = 0; + if (_readReg(M5IOE1_REG_AW8737A_PULSE, &actualReg)) { + result.expected_aw8737a = _aw8737aRegValue; + result.actual_aw8737a = actualReg & 0x7F; + + if (result.expected_aw8737a != result.actual_aw8737a) { + result.aw8737a_mismatch = true; + result.consistent = false; + } + } else { + result.consistent = false; + } + } + + return result; +} + +// ============================ +// 缓存状态查询函数 +// Cached State Query Functions +// ============================ + +m5ioe1_err_t M5IOE1::getCachedPwmFrequency(uint16_t* frequency) +{ + if (frequency == nullptr) return M5IOE1_ERR_INVALID_ARG; + if (!_pwmStatesValid) return M5IOE1_FAIL; + *frequency = _pwmFrequency; + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::getCachedPwmState(uint8_t channel, uint16_t* duty12, bool* polarity, bool* enabled) +{ + if (channel > 3 || duty12 == nullptr || polarity == nullptr || enabled == nullptr) { + return M5IOE1_ERR_INVALID_ARG; + } + if (!_pwmStatesValid) return M5IOE1_FAIL; + + *duty12 = _pwmStates[channel].duty12; + *polarity = _pwmStates[channel].polarity; + *enabled = _pwmStates[channel].enabled; + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::getCachedAdcState(uint8_t* activeChannel, bool* busy, uint16_t* lastValue) +{ + if (activeChannel == nullptr || busy == nullptr || lastValue == nullptr) { + return M5IOE1_ERR_INVALID_ARG; + } + if (!_adcStateValid) return M5IOE1_FAIL; + + *activeChannel = _adcState.activeChannel; + *busy = _adcState.busy; + *lastValue = _adcState.lastValue; + return M5IOE1_OK; +} + +m5ioe1_err_t M5IOE1::getCachedPinState(uint8_t pin, bool* isOutput, uint8_t* level, uint8_t* pull) +{ + if (!_isValidPin(pin) || isOutput == nullptr || level == nullptr || pull == nullptr) { + return M5IOE1_ERR_INVALID_ARG; + } + if (!_pinStatesValid) return M5IOE1_FAIL; + + *isOutput = _pinStates[pin].isOutput; + *level = _pinStates[pin].isOutput ? _pinStates[pin].outputLevel : _pinStates[pin].inputLevel; + *pull = _pinStates[pin].pull; + return M5IOE1_OK; +} + +// ============================ +// 平台特定函数 +// Platform-Specific Functions +// ============================ + +#ifdef ARDUINO + +void IRAM_ATTR M5IOE1::_arduinoIsrHandler(void* arg) +{ + M5IOE1* self = static_cast(arg); + if (self == nullptr || self->_pollTask == nullptr) { + return; + } + BaseType_t hpw = pdFALSE; + vTaskNotifyGiveFromISR(self->_pollTask, &hpw); + if (hpw == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + +void M5IOE1::_pollTaskArduino(void* arg) +{ + M5IOE1* self = static_cast(arg); + + while (true) { + uint16_t status = 0; + if (self->getInterruptStatus(&status) == M5IOE1_OK && status != 0) { + self->_handleInterrupt(); + } + vTaskDelay(pdMS_TO_TICKS(self->_pollingInterval)); + } +} + +bool M5IOE1::_setupPollingArduino() +{ + BaseType_t ok = xTaskCreatePinnedToCore(_pollTaskArduino, "m5ioe1_poll", 4096, this, 5, &_pollTask, tskNO_AFFINITY); + return ok == pdPASS; +} + +void M5IOE1::_cleanupPollingArduino() +{ + if (_pollTask) { + vTaskDelete(_pollTask); + _pollTask = nullptr; + } +} + +void M5IOE1::_intrTaskArduino(void* arg) +{ + M5IOE1* self = static_cast(arg); + while (true) { + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + if (self) { + self->_handleInterrupt(); + } + } +} + +bool M5IOE1::_setupHardwareInterruptArduino() +{ + if (_intPin < 0) { + M5IOE1_LOG_E(TAG_IRQ, "Hardware interrupt mode requires interrupt pin"); + return false; + } + + pinMode(_intPin, INPUT_PULLUP); + + BaseType_t ok = xTaskCreatePinnedToCore(_intrTaskArduino, "m5ioe1_intr", 4096, this, 5, &_pollTask, tskNO_AFFINITY); + if (ok != pdPASS) { + _pollTask = nullptr; + return false; + } + + attachInterruptArg(digitalPinToInterrupt(_intPin), _arduinoIsrHandler, this, FALLING); + return true; +} + +void M5IOE1::_cleanupHardwareInterruptArduino() +{ + if (_intPin >= 0) { + detachInterrupt(_intPin); + } + if (_pollTask) { + vTaskDelete(_pollTask); + _pollTask = nullptr; + } +} + +#else // ESP-IDF + +void M5IOE1::_pollTaskFunc(void* arg) +{ + M5IOE1* self = static_cast(arg); + + while (true) { + uint16_t status = 0; + if (self->getInterruptStatus(&status) == M5IOE1_OK && status != 0) { + self->_handleInterrupt(); + } + vTaskDelay(pdMS_TO_TICKS(self->_pollingInterval)); + } +} + +void IRAM_ATTR M5IOE1::_isrHandler(void* arg) +{ + M5IOE1* self = static_cast(arg); + if (self && self->_intrQueue) { + // 立即禁用中断,防止电平触发造成 ISR 风暴 + // Disable interrupt immediately to prevent level-triggered ISR storm + gpio_intr_disable((gpio_num_t)self->_intPin); + uint32_t evt = 1; + BaseType_t hpw = pdFALSE; + xQueueSendFromISR(self->_intrQueue, &evt, &hpw); + if (hpw == pdTRUE) portYIELD_FROM_ISR(); + } +} + +bool M5IOE1::_setupHardwareInterrupt() +{ + if (_intPin < 0) { + M5IOE1_LOG_E(TAG_IRQ, "Hardware interrupt mode requires interrupt pin"); + return false; + } + + _intrQueue = xQueueCreate(8, sizeof(uint32_t)); + if (_intrQueue == nullptr) return false; + + gpio_config_t io_conf = {}; + io_conf.pin_bit_mask = (1ULL << _intPin); + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pull_up_en = GPIO_PULLUP_ENABLE; + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.intr_type = GPIO_INTR_LOW_LEVEL; + + if (gpio_config(&io_conf) != ESP_OK) { + vQueueDelete(_intrQueue); + _intrQueue = nullptr; + return false; + } + + esp_err_t isr_err = gpio_install_isr_service(ESP_INTR_FLAG_IRAM); + if (isr_err != ESP_OK && isr_err != ESP_ERR_INVALID_STATE) { + vQueueDelete(_intrQueue); + _intrQueue = nullptr; + return false; + } + + if (gpio_isr_handler_add((gpio_num_t)_intPin, _isrHandler, this) != ESP_OK) { + vQueueDelete(_intrQueue); + _intrQueue = nullptr; + return false; + } + + // 创建任务以处理中断 + // Create task to handle interrupts + BaseType_t ok = xTaskCreatePinnedToCore( + [](void* arg) { + M5IOE1* self = static_cast(arg); + uint32_t evt; + while (true) { + if (xQueueReceive(self->_intrQueue, &evt, portMAX_DELAY) == pdTRUE) { + gpio_intr_disable((gpio_num_t)self->_intPin); + self->_handleInterrupt(); + gpio_intr_enable((gpio_num_t)self->_intPin); + } + } + }, + "m5ioe1_intr", 4096, this, configMAX_PRIORITIES - 2, &_pollTask, tskNO_AFFINITY); + + if (ok != pdPASS) { + gpio_isr_handler_remove((gpio_num_t)_intPin); + vQueueDelete(_intrQueue); + _intrQueue = nullptr; + return false; + } + + return true; +} + +bool M5IOE1::_setupPolling() +{ + BaseType_t ok = xTaskCreatePinnedToCore(_pollTaskFunc, "m5ioe1_poll", 4096, this, 5, &_pollTask, tskNO_AFFINITY); + return ok == pdPASS; +} + +void M5IOE1::_cleanupPolling() +{ + if (_pollTask) { + vTaskDelete(_pollTask); + _pollTask = nullptr; + } +} + +void M5IOE1::_cleanupHardwareInterrupt() +{ + if (_intrQueue) { + vQueueDelete(_intrQueue); + _intrQueue = nullptr; + } + if (_intPin >= 0) { + // 移除 GPIO ISR handler + // Remove GPIO ISR handler + // 如果返回错误(如"GPIO isr service is not installed")可忽略 + // The returned error (e.g., "GPIO isr service is not installed") can be ignored + // This error indicates that the ISR service is not currently installed and does not need to be removed, which + // is expected behavior + esp_err_t err = gpio_isr_handler_remove((gpio_num_t)_intPin); + if (err != ESP_OK) { + M5IOE1_LOG_W(TAG_IRQ, + "gpio_isr_handler_remove failed (can be ignored if ISR service was not installed): %s", + esp_err_to_name(err)); + } + } +} + +#endif diff --git a/components/m5ioe1/src/M5IOE1.h b/components/m5ioe1/src/M5IOE1.h new file mode 100644 index 0000000..bed8056 --- /dev/null +++ b/components/m5ioe1/src/M5IOE1.h @@ -0,0 +1,1913 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#ifndef _M5IOE1_H_ +#define _M5IOE1_H_ + +#include "M5IOE1_i2c_compat.h" +#include +#include + +#ifdef ARDUINO +// Arduino:FreeRTOS 头文件通过 Arduino 框架包含 +// Arduino: FreeRTOS headers included via Arduino framework +#else +// ESP-IDF 专用包含文件 +// ESP-IDF specific includes +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_attr.h" +#endif + +// ============================ +// IO 引脚定义 +// IO Pin Definitions +// ============================ +typedef enum { + M5IOE1_PIN_NC = -1, + M5IOE1_PIN_1 = 0, + M5IOE1_PIN_2 = 1, + M5IOE1_PIN_3 = 2, + M5IOE1_PIN_4 = 3, + M5IOE1_PIN_5 = 4, + M5IOE1_PIN_6 = 5, + M5IOE1_PIN_7 = 6, + M5IOE1_PIN_8 = 7, + M5IOE1_PIN_9 = 8, + M5IOE1_PIN_10 = 9, + M5IOE1_PIN_11 = 10, + M5IOE1_PIN_12 = 11, + M5IOE1_PIN_13 = 12, + M5IOE1_PIN_14 = 13 +} m5ioe1_pin_t; + +// ============================ +// 错误码 +// Error Codes +// ============================ +typedef enum { + M5IOE1_OK = 0, // 成功 + // Success + M5IOE1_FAIL = -1, // 一般失败 + // General failure + M5IOE1_ERR_I2C_CONFIG = -2, // I2C 配置错误 (如频率不支持) + // I2C configuration error + M5IOE1_ERR_RULE_VIOLATION = -3, // 条件规则错误 (如引脚冲突,互斥功能) + // Condition rule violation + M5IOE1_ERR_INVALID_ARG = -4, // 无效参数 + // Invalid argument + M5IOE1_ERR_TIMEOUT = -5, // 超时 + // Timeout + M5IOE1_ERR_NOT_SUPPORTED = -6, // 不支持的功能 + // Function not supported + M5IOE1_ERR_I2C_COMM = -7, // I2C 通信错误 + // I2C communication error + M5IOE1_ERR_NOT_INIT = -8, // 设备未初始化 + // Device not initialized + M5IOE1_ERR_INTERNAL = -9, // 内部错误 + // Internal error +} m5ioe1_err_t; + +// ============================ +// 设备常量 +// Device Constants +// ============================ +#define M5IOE1_DEFAULT_ADDR 0x6F // 默认 I2C 地址 / Default I2C address +#define M5IOE1_DEFAULT_ADDR_2 0x4F // 第二地址(部分作为从机的产品) / Second address (for some slave products) +#define M5IOE1_AUTODETECT_ADDR 0xFF // 自动检测地址(0x6F~0x76:A 0x4F~0x50:W) / Auto-detect address +#define M5IOE1_MAX_GPIO_PINS 14 +#define M5IOE1_MAX_ADC_CHANNELS 4 +#define M5IOE1_MAX_PWM_CHANNELS 4 +#define M5IOE1_MAX_LED_COUNT 32 +#define M5IOE1_RTC_RAM_SIZE 32 + +// I2C retry settings +#define M5IOE1_I2C_RETRY_COUNT 2 +#define M5IOE1_I2C_RETRY_DELAY_MS 50 + +// ============================ +// I2C 频率常量 +// I2C Frequency Constants +// ============================ +#define M5IOE1_I2C_FREQ_100K 100000 +#define M5IOE1_I2C_FREQ_400K 400000 +#define M5IOE1_I2C_FREQ_DEFAULT M5IOE1_I2C_FREQ_100K + +// ============================ +// 寄存器地址 +// Register Addresses +// ============================ +// System +#define M5IOE1_REG_UID_L 0x00 // R [7:0] UID Low Byte +#define M5IOE1_REG_UID_H 0x01 // R [15:8] UID High Byte +#define M5IOE1_REG_REV 0x02 // R [7:0] SW7-SW0 Version +// GPIO +#define M5IOE1_REG_GPIO_MODE_L 0x03 // R/W [7:0] Mode P8-P1 +#define M5IOE1_REG_GPIO_MODE_H 0x04 // R/W [7:6] Res | [5:0] Mode P14-P9 +#define M5IOE1_REG_GPIO_OUT_L 0x05 // R/W [7:0] Out P8-P1 +#define M5IOE1_REG_GPIO_OUT_H 0x06 // R/W [7:6] Res | [5:0] Out P14-P9 +#define M5IOE1_REG_GPIO_IN_L 0x07 // R [7:0] In P8-P1 +#define M5IOE1_REG_GPIO_IN_H 0x08 // R [7:6] Res | [5:0] In P14-P9 +#define M5IOE1_REG_GPIO_PU_L 0x09 // R/W [7:0] PU P8-P1 +#define M5IOE1_REG_GPIO_PU_H 0x0A // R/W [7:6] Res | [5:0] PU P14-P9 +#define M5IOE1_REG_GPIO_PD_L 0x0B // R/W [7:0] PD P8-P1 +#define M5IOE1_REG_GPIO_PD_H 0x0C // R/W [7:6] Res | [5:0] PD P14-P9 +#define M5IOE1_REG_GPIO_IE_L 0x0D // R/W [7:0] IE P8-P1 +#define M5IOE1_REG_GPIO_IE_H 0x0E // R/W [7:6] Res | [5:0] IE P14-P9 +#define M5IOE1_REG_GPIO_IP_L 0x0F // R/W [7:0] IP P8-P1 +#define M5IOE1_REG_GPIO_IP_H 0x10 // R/W [7:6] Res | [5:0] IP P14-P9 +#define M5IOE1_REG_GPIO_IS_L 0x11 // R [7:0] IS P8-P1 +#define M5IOE1_REG_GPIO_IS_H 0x12 // R [7:6] Res | [5:0] IS P14-P9 +#define M5IOE1_REG_GPIO_DRV_L 0x13 // R/W [7:0] Drive P8-P1 +#define M5IOE1_REG_GPIO_DRV_H 0x14 // R/W [7:6] Res | [5:0] Drive P14-P9 +// ADC +#define M5IOE1_REG_ADC_CTRL 0x15 // R/W [7] BUSY | [6] START | [2:0] Channel +#define M5IOE1_REG_ADC_DATA_L 0x16 // R [7:0] ADC Data Low +#define M5IOE1_REG_ADC_DATA_H 0x17 // R [3:0] ADC Data High +// Temperature +#define M5IOE1_REG_TEMP_CTRL 0x18 // R/W [7] TBUSY | [6] TSTART +#define M5IOE1_REG_TEMP_DATA_L 0x19 // R [7:0] Temp Data Low +#define M5IOE1_REG_TEMP_DATA_H 0x1A // R [3:0] Temp Data High +// PWM +#define M5IOE1_REG_PWM1_DUTY_L 0x1B // R/W [7:0] PWM1 Duty Low +#define M5IOE1_REG_PWM1_DUTY_H 0x1C // R/W [7] EN | [6] POL | [3:0] PWM1 Duty High +#define M5IOE1_REG_PWM2_DUTY_L 0x1D // R/W [7:0] PWM2 Duty Low +#define M5IOE1_REG_PWM2_DUTY_H 0x1E // R/W [7] EN | [6] POL | [3:0] PWM2 Duty High +#define M5IOE1_REG_PWM3_DUTY_L 0x1F // R/W [7:0] PWM3 Duty Low +#define M5IOE1_REG_PWM3_DUTY_H 0x20 // R/W [7] EN | [6] POL | [3:0] PWM3 Duty High +#define M5IOE1_REG_PWM4_DUTY_L 0x21 // R/W [7:0] PWM4 Duty Low +#define M5IOE1_REG_PWM4_DUTY_H 0x22 // R/W [7] EN | [6] POL | [3:0] PWM4 Duty High +// System Config +#define M5IOE1_REG_I2C_CFG 0x23 // R/W [6] INT_PU/PD | [5] WAKE | [4] SPD | [3:0] SLEEP +#define M5IOE1_REG_LED_CFG 0x24 // R/W [6] REFRESH | [5:0] LED Num +#define M5IOE1_REG_PWM_FREQ_L 0x25 // R/W [7:0] PWM Freq Low +#define M5IOE1_REG_PWM_FREQ_H 0x26 // R/W [7:0] PWM Freq High +#define M5IOE1_REG_REF_VOLTAGE_L 0x27 // R [7:0] Ref Voltage Low +#define M5IOE1_REG_REF_VOLTAGE_H 0x28 // R [15:8] Ref Voltage High +#define M5IOE1_REG_FACTORY_RESET 0x29 // W [7:0] Reset Key +// Data areas +#define M5IOE1_REG_LED_RAM_START 0x30 // R/W NeoPixel RGB565 Data (32 LEDs x 2B) +#define M5IOE1_REG_LED_RAM_END 0x6F +#define M5IOE1_REG_RTC_RAM_START 0x70 // R/W RTC Retention RAM (32B) +#define M5IOE1_REG_RTC_RAM_END 0x8F +// Extended +#define M5IOE1_REG_AW8737A_PULSE 0x90 // R/W [7] REFRESH | [6:5] NUM | [4:0] GPIO + +// ============================ +// 位定义 +// Bit Definitions +// ============================ +// ADC Control +#define M5IOE1_ADC_CH_MASK 0x07 +#define M5IOE1_ADC_START (1 << 6) +#define M5IOE1_ADC_BUSY (1 << 7) +// Temperature Control +#define M5IOE1_TEMP_START (1 << 6) +#define M5IOE1_TEMP_BUSY (1 << 7) +// PWM Control +#define M5IOE1_PWM_POLARITY (1 << 6) +#define M5IOE1_PWM_ENABLE (1 << 7) +// I2C Config +#define M5IOE1_I2C_SLEEP_MASK 0x0F +#define M5IOE1_I2C_SPEED_400K_BIT (1 << 4) +#define M5IOE1_I2C_WAKE_RISING (1 << 5) +#define M5IOE1_I2C_PULL_OFF (1 << 6) +// LED Config +#define M5IOE1_LED_NUM_MASK 0x3F +#define M5IOE1_LED_REFRESH (1 << 6) +// Factory Reset +#define M5IOE1_FACTORY_RESET_KEY 0x3A +// AW8737A Pulse +#define M5IOE1_AW8737A_GPIO_MASK 0x1F +#define M5IOE1_AW8737A_NUM_SHIFT 5 +#define M5IOE1_AW8737A_NUM_MASK 0x03 +#define M5IOE1_AW8737A_REFRESH (1 << 7) + +// ============================ +// ADC 通道定义(支持 ADC 的 IO 引脚) +// ADC Channel Definitions (IO pins that support ADC) +// ============================ +#define M5IOE1_ADC_CH1 1 // IO2 (pin index 1) +#define M5IOE1_ADC_CH2 2 // IO4 (pin index 3) +#define M5IOE1_ADC_CH3 3 // IO5 (pin index 4) +#define M5IOE1_ADC_CH4 4 // IO7 (pin index 6) + +// ============================ +// PWM 通道定义(支持 PWM 的 IO 引脚) +// PWM Channel Definitions (IO pins that support PWM) +// ============================ +#define M5IOE1_PWM_CH1 0 // IO9 (pin index 8) +#define M5IOE1_PWM_CH2 1 // IO8 (pin index 7) +#define M5IOE1_PWM_CH3 2 // IO11 (pin index 10) +#define M5IOE1_PWM_CH4 3 // IO10 (pin index 9) + +// ============================ +// GPIO 电平定义 +// GPIO Level Definitions +// ============================ +#ifndef LOW +#define LOW 0x0 +#endif +#ifndef HIGH +#define HIGH 0x1 +#endif + +// ============================ +// GPIO 模式定义(Arduino 兼容) +// GPIO Mode Definitions (Arduino-compatible) +// ============================ +#ifndef INPUT +#define INPUT 0x01 +#endif +#ifndef OUTPUT +#define OUTPUT 0x03 +#endif +#ifndef PULLUP +#define PULLUP 0x04 +#endif +#ifndef INPUT_PULLUP +#define INPUT_PULLUP 0x05 +#endif +#ifndef PULLDOWN +#define PULLDOWN 0x08 +#endif +#ifndef INPUT_PULLDOWN +#define INPUT_PULLDOWN 0x09 +#endif +#ifndef OPEN_DRAIN +#define OPEN_DRAIN 0x10 +#endif +#ifndef OUTPUT_OPEN_DRAIN +#define OUTPUT_OPEN_DRAIN 0x13 +#endif +#ifndef ANALOG +#define ANALOG 0xC0 +#endif + +// ============================ +// 中断模式定义 +// Interrupt Mode Definitions +// ============================ +#ifndef DISABLED +#define DISABLED 0x00 +#endif +#ifndef RISING +#define RISING 0x01 +#endif +#ifndef FALLING +#define FALLING 0x02 +#endif +#ifndef CHANGE +#define CHANGE 0x03 +#endif +#ifndef ONLOW +#define ONLOW 0x04 +#endif +#ifndef ONHIGH +#define ONHIGH 0x05 +#endif +#ifndef ONLOW_WE +#define ONLOW_WE 0x0C +#endif +#ifndef ONHIGH_WE +#define ONHIGH_WE 0x0D +#endif + +// ============================ +// 上拉/下拉模式定义 +// Pull Mode Definitions +// ============================ +#define M5IOE1_PULL_NONE 0x00 +#define M5IOE1_PULL_UP 0x01 +#define M5IOE1_PULL_DOWN 0x02 + +// ============================ +// 驱动模式定义 +// Drive Mode Definitions +// ============================ +#define M5IOE1_DRIVE_PUSHPULL 0x00 +#define M5IOE1_DRIVE_OPENDRAIN 0x01 + +// ============================ +// AW8737A 脉冲刷新类型 +// AW8737A PULSE Refresh Types +// ============================ +typedef enum { + M5IOE1_AW8737A_REFRESH_WAIT = 0, // 不刷新,等待下一次触发 + // No refresh, wait for next trigger + M5IOE1_AW8737A_REFRESH_NOW = 1 // 刷新并立即执行 + // Refresh and execute immediately +} m5ioe1_aw8737a_refresh_t; + +// ============================ +// AW8737A 脉冲数量类型 +// AW8737A PULSE NUM Types +// ============================ +typedef enum { + M5IOE1_AW8737A_PULSE_0 = 0, // 0 个脉冲 + // 0 pulse + M5IOE1_AW8737A_PULSE_1 = 1, // 1 个脉冲 + // 1 pulse + M5IOE1_AW8737A_PULSE_2 = 2, // 2 个脉冲 + // 2 pulses + M5IOE1_AW8737A_PULSE_3 = 3 // 3 个脉冲 + // 3 pulses +} m5ioe1_aw8737a_pulse_t; + +// ============================ +// AW8737A 模式类型 +// AW8737A Mode Types +// ============================ +typedef enum { + M5IOE1_AW8737A_MODE_1 = 0, // 模式1: 0脉冲 (关闭/静音) + // Mode 1: 0 pulse (off/mute) + M5IOE1_AW8737A_MODE_2 = 1, // 模式2: 1脉冲 (低增益) + // Mode 2: 1 pulse (low gain) + M5IOE1_AW8737A_MODE_3 = 2, // 模式3: 2脉冲 (中增益) + // Mode 3: 2 pulses (medium gain) + M5IOE1_AW8737A_MODE_4 = 3 // 模式4: 3脉冲 (高增益) + // Mode 4: 3 pulses (high gain) +} m5ioe1_aw8737a_mode_t; + +// ============================ +// 中断处理模式 +// Interrupt Handling Mode +// ============================ +typedef enum { + M5IOE1_INT_MODE_DISABLED = 0, // 中断处理已禁用 + // Interrupt handling disabled + M5IOE1_INT_MODE_POLLING, // 轮询模式(默认) + // Polling mode (default) + M5IOE1_INT_MODE_HARDWARE // 硬件中断模式 + // Hardware interrupt mode +} m5ioe1_int_mode_t; + +// ============================ +// I2C 速度定义 +// I2C Speed Definitions +// ============================ +typedef enum { + M5IOE1_I2C_SPEED_100K = 0, // 100KHz 标准模式 + // 100KHz standard mode + M5IOE1_I2C_SPEED_400K = 1 // 400KHz 快速模式 + // 400KHz fast mode +} m5ioe1_i2c_speed_t; + +// ============================ +// I2C 唤醒边沿定义 +// I2C Wake Edge Definitions +// ============================ +typedef enum { + M5IOE1_WAKE_EDGE_FALLING = 0, // 下降沿唤醒(默认) + // Falling edge wake (default) + M5IOE1_WAKE_EDGE_RISING = 1 // 上升沿唤醒 + // Rising edge wake +} m5ioe1_wake_edge_t; + +// ============================ +// I2C 内部上拉定义 +// I2C Internal Pull-up Definitions +// ============================ +typedef enum { + M5IOE1_PULL_ENABLED = 0, // 内部上拉启用(默认) + // Internal pull-up enabled (default) + M5IOE1_PULL_DISABLED = 1 // 内部上拉禁用 + // Internal pull-up disabled +} m5ioe1_pull_config_t; + +// ============================ +// 快照域定义(位掩码) +// Snapshot Domain Definitions (bitmask) +// ============================ +typedef enum { + M5IOE1_SNAPSHOT_DOMAIN_GPIO = 1 << 0, // GPIO 引脚状态 + // GPIO pin states + M5IOE1_SNAPSHOT_DOMAIN_PWM = 1 << 1, // PWM 配置 + // PWM configuration + M5IOE1_SNAPSHOT_DOMAIN_ADC = 1 << 2, // ADC 状态 + // ADC state + M5IOE1_SNAPSHOT_DOMAIN_AW8737A = 1 << 3, // AW8737A 音频放大器 + // AW8737A audio amplifier + M5IOE1_SNAPSHOT_DOMAIN_ALL = 0x0F // 所有域 + // All domains +} m5ioe1_snapshot_domain_t; + +// ============================ +// 日志级别定义 +// Log Level Definitions +// ============================ +typedef enum { + M5IOE1_LOG_LEVEL_NONE = 0, // 无日志输出 + // No log output + M5IOE1_LOG_LEVEL_ERROR, // 仅错误消息 + // Error messages only + M5IOE1_LOG_LEVEL_WARN, // 警告和错误消息 + // Warning and error messages + M5IOE1_LOG_LEVEL_INFO, // 信息、警告和错误消息(默认) + // Info, warning and error messages (default) + M5IOE1_LOG_LEVEL_DEBUG, // 调试、信息、警告和错误消息 + // Debug, info, warning and error messages + M5IOE1_LOG_LEVEL_VERBOSE // 所有消息包括详细输出 + // All messages including verbose +} m5ioe1_log_level_t; + +// ============================ +// 用于验证的配置类型 +// Configuration Type for Validation +// ============================ +typedef enum { + M5IOE1_CONFIG_GPIO_INPUT = 0, // GPIO 输入模式 + // GPIO input mode + M5IOE1_CONFIG_GPIO_OUTPUT, // GPIO 输出模式 + // GPIO output mode + M5IOE1_CONFIG_GPIO_INTERRUPT, // GPIO 中断模式 + // GPIO interrupt mode + M5IOE1_CONFIG_ADC, // ADC 功能 + // ADC function + M5IOE1_CONFIG_PWM, // PWM 功能 + // PWM function + M5IOE1_CONFIG_NEOPIXEL, // NeoPixel LED 功能(仅 IO14) + // NeoPixel LED function (IO14 only) + M5IOE1_CONFIG_I2C_SLEEP // I2C 睡眠模式配置 + // I2C sleep mode configuration +} m5ioe1_config_type_t; + +// ============================ +// RGB 颜色结构 +// RGB Color Structure +// ============================ +typedef struct { + uint8_t r; + uint8_t g; + uint8_t b; +} m5ioe1_rgb_t; + +// ============================ +// 配置验证结果 +// Configuration Validation Result +// ============================ +typedef struct { + bool valid; + char error_msg[64]; + uint8_t conflicting_pin; +} m5ioe1_validation_t; + +// ============================ +// 快照验证结果 +// Snapshot Verification Result +// ============================ +typedef struct { + bool consistent; // 如果所有缓存值与硬件寄存器匹配则为 true + // true if all cached values match hardware registers + bool gpio_mismatch; // 如果 GPIO 寄存器与缓存不匹配则为 true + // true if GPIO registers don't match cache + bool pwm_mismatch; // 如果 PWM 寄存器与缓存不匹配则为 true + // true if PWM registers don't match cache + bool adc_mismatch; // 如果 ADC 寄存器与缓存不匹配则为 true + // true if ADC registers don't match cache + bool aw8737a_mismatch; // 如果 AW8737A 寄存器与缓存不匹配则为 true + // true if AW8737A registers don't match cache + uint16_t expected_mode; // 缓存的 GPIO 模式寄存器值 + // cached GPIO mode register value + uint16_t actual_mode; // 实际的 GPIO 模式寄存器值 + // actual GPIO mode register value + uint16_t expected_output; // 缓存的 GPIO 输出寄存器值 + // cached GPIO output register value + uint16_t actual_output; // 实际的 GPIO 输出寄存器值 + // actual GPIO output register value + uint8_t expected_aw8737a; // 缓存的 AW8737A 寄存器值 + // cached AW8737A register value + uint8_t actual_aw8737a; // 实际的 AW8737A 寄存器值 + // actual AW8737A register value +} m5ioe1_snapshot_verify_t; + +// ============================ +// 回调类型 +// Callback Types +// ============================ +typedef void (*m5ioe1_callback_t)(void); +typedef void (*m5ioe1_callback_arg_t)(void*); + +// ============================ +// M5IOE1 类 +// M5IOE1 Class +// ============================ +class M5IOE1 { +public: + /** + * @brief 构造 M5IOE1 对象 + * Construct M5IOE1 object + */ + M5IOE1(); + /** + * @brief 析构 M5IOE1 对象并释放资源 + * Destroy M5IOE1 object and release resources + */ + ~M5IOE1(); + + // ======================== + // 初始化 + // Initialization + // ======================== +#ifdef ARDUINO + /** + * @brief Initialize the M5IOE1 device (Arduino) + * @note Without intPin (or intPin=-1): Only POLLING and DISABLED modes are supported + * @note With intPin >= 0: Supports HARDWARE, POLLING, and DISABLED modes + * @note HARDWARE mode: Configures intPin as input with internal pull-up. Uses FALLING edge, then reads GPIO_IS + * registers (0x11-0x12) + * @note POLLING mode: Periodically reads GPIO_IS registers (0x11-0x12). Non-zero indicates one or more pins + * triggered + * @note DISABLED mode: No interrupt handling + * @param wire Pointer to TwoWire instance + * @param addr I2C address (default 0x6F) + * @param sda SDA pin (default -1, uses default I2C pins) + * @param scl SCL pin (default -1, uses default I2C pins) + * @param speed I2C speed in Hz (default 100000) + * @param intPin Hardware interrupt pin (-1 = no hardware interrupt, default -1) + * @param intMode Interrupt mode (default M5IOE1_INT_MODE_POLLING) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(TwoWire* wire, uint8_t addr = M5IOE1_DEFAULT_ADDR, uint8_t sda = -1, uint8_t scl = -1, + uint32_t speed = 100000, int8_t intPin = -1, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_POLLING); +#if M5IOE1_HAS_M5UNIFIED_I2C + /** + * @brief Initialize with M5Unified I2C_Class instance, no hardware interrupt (Arduino) + * @note The I2C bus must already be initialized (i2c->begin() called). + * M5IOE1 borrows the I2C_Class; caller retains ownership and lifecycle. + * 不负责驱动安装或释放,I2C 由调用方全程管理。 + * @param i2c 已 begin() 的 m5::I2C_Class 指针 / Already begin()-ed m5::I2C_Class + * @param addr I2C 地址 / I2C address (default 0x6F) + * @param speed I2C 速率(Hz)/ I2C speed in Hz (default 100000) + * @param intMode 中断模式 / Interrupt mode (default POLLING) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(m5::I2C_Class* i2c, uint8_t addr = M5IOE1_DEFAULT_ADDR, uint32_t speed = M5IOE1_I2C_FREQ_100K, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_POLLING); + + /** + * @brief Initialize with M5Unified I2C_Class instance, with hardware interrupt (Arduino) + * @param i2c 已 begin() 的 m5::I2C_Class 指针 / Already begin()-ed m5::I2C_Class + * @param addr I2C 地址 / I2C address + * @param speed I2C 速率(Hz)/ I2C speed in Hz + * @param intPin 硬件中断引脚 / Hardware interrupt pin + * @param intMode 中断模式 / Interrupt mode (default HARDWARE) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(m5::I2C_Class* i2c, uint8_t addr, uint32_t speed, int intPin, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_HARDWARE); +#endif // M5IOE1_HAS_M5UNIFIED_I2C +#else // ESP-IDF + // ===================================================== + // Type 1A: Self-created I2C bus, no hardware interrupt + // ===================================================== + /** + * @brief Initialize with self-created I2C bus, no hardware interrupt (ESP-IDF) + * @param port I2C port number (I2C_NUM_0 or I2C_NUM_1) + * @param addr I2C address (default 0x6F) + * @param sda SDA pin (default 21) + * @param scl SCL pin (default 22) + * @param speed I2C speed in Hz (only 100000 or 400000 supported) + * @param intMode Interrupt mode: M5IOE1_INT_MODE_POLLING or M5IOE1_INT_MODE_DISABLED + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(i2c_port_t port = I2C_NUM_0, uint8_t addr = M5IOE1_DEFAULT_ADDR, int sda = 21, int scl = 22, + uint32_t speed = M5IOE1_I2C_FREQ_100K, m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_POLLING); + + // ===================================================== + // Type 1B: Self-created I2C bus, with hardware interrupt + // ===================================================== + /** + * @brief Initialize with self-created I2C bus, with hardware interrupt (ESP-IDF) + * @param port I2C port number + * @param addr I2C address + * @param sda SDA pin + * @param scl SCL pin + * @param speed I2C speed in Hz + * @param intPin Hardware interrupt GPIO pin + * @param intMode Interrupt mode: M5IOE1_INT_MODE_HARDWARE, POLLING, or DISABLED + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(i2c_port_t port, uint8_t addr, int sda, int scl, uint32_t speed, int intPin, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_HARDWARE); + + // ===================================================== + // Type 2A/2B: Existing i2c_master_bus_handle_t (ESP-IDF >= 5.3.0 only) + // ===================================================== +#if M5IOE1_HAS_I2C_MASTER + /** + * @brief Initialize with existing i2c_master_bus handle (ESP-IDF native driver, IDF >= 5.3.0) + * @param bus Existing i2c_master_bus_handle_t + * @param addr I2C address (default 0x6F) + * @param speed I2C speed in Hz (for device handle creation) + * @param intMode Interrupt mode: M5IOE1_INT_MODE_POLLING or M5IOE1_INT_MODE_DISABLED + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(i2c_master_bus_handle_t bus, uint8_t addr = M5IOE1_DEFAULT_ADDR, + uint32_t speed = M5IOE1_I2C_FREQ_100K, m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_POLLING); + + /** + * @brief Initialize with existing i2c_master_bus handle, with hardware interrupt (IDF >= 5.3.0) + * @param bus Existing i2c_master_bus_handle_t + * @param addr I2C address + * @param speed I2C speed in Hz + * @param intPin Hardware interrupt GPIO pin + * @param intMode Interrupt mode + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(i2c_master_bus_handle_t bus, uint8_t addr, uint32_t speed, int intPin, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_HARDWARE); +#endif // M5IOE1_HAS_I2C_MASTER + + // ===================================================== + // Type 3A: Existing i2c_bus_handle_t, no hardware interrupt + // ===================================================== +#if M5IOE1_HAS_I2C_BUS + /** + * @brief Initialize with existing i2c_bus handle (esp-idf-lib component) + * @param bus Existing i2c_bus_handle_t + * @param addr I2C address (default 0x6F) + * @param speed I2C speed in Hz (for device handle creation) + * @param intMode Interrupt mode: M5IOE1_INT_MODE_POLLING or M5IOE1_INT_MODE_DISABLED + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(i2c_bus_handle_t bus, uint8_t addr = M5IOE1_DEFAULT_ADDR, uint32_t speed = M5IOE1_I2C_FREQ_100K, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_POLLING); + + // ===================================================== + // Type 3B: Existing i2c_bus_handle_t, with hardware interrupt + // ===================================================== + /** + * @brief Initialize with existing i2c_bus handle, with hardware interrupt + * @param bus Existing i2c_bus_handle_t + * @param addr I2C address + * @param speed I2C speed in Hz + * @param intPin Hardware interrupt GPIO pin + * @param intMode Interrupt mode + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(i2c_bus_handle_t bus, uint8_t addr, uint32_t speed, int intPin, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_HARDWARE); +#else + /** + * @brief i2c_bus overload is intentionally kept for diagnostics when unavailable + * @note This overload exists only to provide a clear compile-time message when called. + */ + inline m5ioe1_err_t begin(i2c_bus_handle_t bus, uint8_t addr = M5IOE1_DEFAULT_ADDR, + uint32_t speed = M5IOE1_I2C_FREQ_100K, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_POLLING) + { + (void)bus; + (void)addr; + (void)speed; + (void)intMode; + return M5IOE1_ERR_NOT_SUPPORTED; + } + inline m5ioe1_err_t begin(i2c_bus_handle_t bus, uint8_t addr, uint32_t speed, int intPin, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_HARDWARE) + { + (void)bus; + (void)addr; + (void)speed; + (void)intPin; + (void)intMode; + return M5IOE1_ERR_NOT_SUPPORTED; + } +#endif // M5IOE1_HAS_I2C_BUS + +#if M5IOE1_HAS_M5UNIFIED_I2C + // ===================================================== + // Type 4A: M5Unified I2C_Class, no hardware interrupt + // ===================================================== + /** + * @brief Initialize with M5Unified I2C_Class instance, no hardware interrupt (ESP-IDF) + * @note The I2C bus must already be initialized (i2c->begin() called). + * M5IOE1 borrows the I2C_Class; caller retains ownership and lifecycle. + * 不负责驱动安装或释放,I2C 由调用方全程管理。 + * @param i2c 已 begin() 的 m5::I2C_Class 指针 / Already begin()-ed m5::I2C_Class + * @param addr I2C 地址 / I2C address (default 0x6F) + * @param speed I2C 速率(Hz)/ I2C speed in Hz (default 100000) + * @param intMode 中断模式 / Interrupt mode (POLLING or DISABLED) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(m5::I2C_Class* i2c, uint8_t addr = M5IOE1_DEFAULT_ADDR, uint32_t speed = M5IOE1_I2C_FREQ_100K, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_POLLING); + + // ===================================================== + // Type 4B: M5Unified I2C_Class, with hardware interrupt + // ===================================================== + /** + * @brief Initialize with M5Unified I2C_Class instance, with hardware interrupt (ESP-IDF) + * @param i2c 已 begin() 的 m5::I2C_Class 指针 / Already begin()-ed m5::I2C_Class + * @param addr I2C 地址 / I2C address + * @param speed I2C 速率(Hz)/ I2C speed in Hz + * @param intPin 硬件中断 GPIO引脚 / Hardware interrupt GPIO pin + * @param intMode 中断模式 / Interrupt mode + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t begin(m5::I2C_Class* i2c, uint8_t addr, uint32_t speed, int intPin, + m5ioe1_int_mode_t intMode = M5IOE1_INT_MODE_HARDWARE); +#endif // M5IOE1_HAS_M5UNIFIED_I2C + +#endif // !ARDUINO + + /** + * @brief Set interrupt handling mode + * @param intMode Interrupt mode (DISABLED, POLLING, HARDWARE) + * @param pollingIntervalMs Polling interval in ms (for polling mode, default 5000ms) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setInterruptMode(m5ioe1_int_mode_t intMode, uint32_t pollingIntervalMs = 5000); + + /** + * @brief Set polling interval for POLLING mode + * @param seconds Polling interval in seconds (range: 0.001 to 3600, supports float) + * @return M5IOE1_OK if successful, M5IOE1_ERR_INVALID_ARG if out of range + * @note If already in POLLING mode, the task will be restarted with new interval + */ + m5ioe1_err_t setPollingInterval(float seconds); + + /** + * @brief Set global log level for M5IOE1 library + * @param level Log level (ERROR, WARN, INFO, DEBUG, VERBOSE) + * @note For ESP-IDF: Uses esp_log_level_set() to control ESP_LOGx macros + * @note For Arduino: Controls Serial.printf output filtering + * @note Default level is M5IOE1_LOG_LEVEL_INFO + */ + static void setLogLevel(m5ioe1_log_level_t level); + + /** + * @brief Get current global log level + * @return Current log level + */ + static m5ioe1_log_level_t getLogLevel(); + + // ======================== + // 设备信息 + // Device Information + // ======================== + /** + * @brief 读取设备 UID + * Read device UID + * @param uid 输出:UID(16-bit) + * Output: 16-bit UID + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getUID(uint16_t* uid); + /** + * @brief 读取设备版本号 + * Read device version + * @param version 输出:版本号 + * Output: version value + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getVersion(uint8_t* version); + /** + * @brief 读取参考电压(mV) + * Read reference voltage (mV) + * @param voltage_mv 输出:参考电压(mV) + * Output: reference voltage (mV) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getRefVoltage(uint16_t* voltage_mv); + + // ======================== + // GPIO 功能(Arduino 风格) + // GPIO Functions (Arduino-style) + // ======================== + /** + * @brief 设置 GPIO 模式(无返回值) + * Set GPIO mode (no return value) + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param mode 模式:INPUT/OUTPUT/INPUT_PULLUP/INPUT_PULLDOWN/OUTPUT_OPEN_DRAIN 等 + * Mode: INPUT/OUTPUT/INPUT_PULLUP/INPUT_PULLDOWN/OUTPUT_OPEN_DRAIN, etc. + * @note 如需错误码请使用 pinModeWithRes + * Use pinModeWithRes for error codes + */ + void pinMode(uint8_t pin, uint8_t mode); + /** + * @brief 写入数字电平(无返回值) + * Write digital level (no return value) + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param value 电平值:LOW 或 HIGH + * Level: LOW or HIGH + * @note 如需错误码请使用 digitalWriteWithRes + * Use digitalWriteWithRes for error codes + */ + void digitalWrite(uint8_t pin, uint8_t value); + /** + * @brief 读取数字电平(错误时返回 -1) + * Read digital level (returns -1 on error) + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @return 成功返回 0/1,失败返回 -1 + * Returns 0/1 on success, -1 on failure + */ + int digitalRead(uint8_t pin); + + // ======================== + // GPIO 功能(Arduino 风格 - 带错误码) + // GPIO Functions (Arduino-style - with error code) + // ======================== + /** + * @brief 设置 GPIO 模式并返回错误码 + * Set GPIO mode and return error code + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param mode 模式:INPUT/OUTPUT/INPUT_PULLUP/INPUT_PULLDOWN/OUTPUT_OPEN_DRAIN 等 + * Mode: INPUT/OUTPUT/INPUT_PULLUP/INPUT_PULLDOWN/OUTPUT_OPEN_DRAIN, etc. + * @param err 输出:错误码指针(不能为空) + * Output: error code pointer (must not be NULL) + * @note err 为空时仅记录日志并返回 + * If err is NULL, the function only logs and returns + */ + void pinModeWithRes(uint8_t pin, uint8_t mode, m5ioe1_err_t* err); + /** + * @brief 写入数字电平并返回错误码 + * Write digital level and return error code + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param value 电平值:LOW 或 HIGH + * Level: LOW or HIGH + * @param err 输出:错误码指针(不能为空) + * Output: error code pointer (must not be NULL) + * @note err 为空时仅记录日志并返回 + * If err is NULL, the function only logs and returns + */ + void digitalWriteWithRes(uint8_t pin, uint8_t value, m5ioe1_err_t* err); + /** + * @brief 读取数字电平并返回错误码 + * Read digital level and return error code + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param err 输出:错误码指针(不能为空) + * Output: error code pointer (must not be NULL) + * @return 成功返回 0/1,失败返回 -1 + * Returns 0/1 on success, -1 on failure + * @note err 为空时仅记录日志并返回 -1 + * If err is NULL, the function only logs and returns -1 + */ + int digitalReadWithRes(uint8_t pin, m5ioe1_err_t* err); + + // ======================== + // 中断功能 + // Interrupt Functions + // ======================== + /** + * @brief 绑定 GPIO 中断回调(无参数) + * Attach GPIO interrupt callback (no argument) + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param callback 回调函数 + * Callback function + * @param mode 中断模式:RISING 或 FALLING(非 RISING 值均视为 FALLING) + * Interrupt mode: RISING or FALLING (non-RISING values treated as FALLING) + * @note 需要先调用 begin 完成初始化 + * begin must be called before use + */ + void attachInterrupt(uint8_t pin, m5ioe1_callback_t callback, uint8_t mode); + /** + * @brief 绑定 GPIO 中断回调(带参数) + * Attach GPIO interrupt callback (with argument) + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param callback 回调函数(带参数) + * Callback function with argument + * @param arg 回调参数 + * Callback argument + * @param mode 中断模式:RISING 或 FALLING(非 RISING 值均视为 FALLING) + * Interrupt mode: RISING or FALLING (non-RISING values treated as FALLING) + * @note 需要先调用 begin 完成初始化 + * begin must be called before use + */ + void attachInterruptArg(uint8_t pin, m5ioe1_callback_arg_t callback, void* arg, uint8_t mode); + /** + * @brief 解绑并禁用 GPIO 中断 + * Detach and disable GPIO interrupt + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + */ + void detachInterrupt(uint8_t pin); + /** + * @brief 启用回调触发(软件开关) + * Enable callback trigger (software switch) + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @note 不修改硬件中断寄存器 + * Does not change hardware interrupt registers + */ + void enableInterrupt(uint8_t pin); + /** + * @brief 禁用回调触发(软件开关) + * Disable callback trigger (software switch) + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @note 不修改硬件中断寄存器 + * Does not change hardware interrupt registers + */ + void disableInterrupt(uint8_t pin); + /** + * @brief 获取中断状态位(GPIO_IS) + * Get interrupt status bits (GPIO_IS) + * @param status 输出:中断状态位掩码 + * Output: interrupt status bitmask + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getInterruptStatus(uint16_t* status); + /** + * @brief 清除所有中断状态 + * Clear all interrupt status + * @return M5IOE1_OK if successful, error code otherwise + * @note GPIO_IS 寄存器为"写 0 清除"语义,会清除所有引脚的中断状态 + * GPIO_IS register uses "write 0 to clear" semantics, clears all pins' interrupt status + */ + m5ioe1_err_t clearInterrupt(); + + /** + * @brief 清除指定引脚的中断状态 + * Clear interrupt status for a specific pin + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @return M5IOE1_OK if successful, error code otherwise + * @note GPIO_IS 寄存器为"写 0 清除"语义,仅清除指定引脚的中断位 + * GPIO_IS register uses "write 0 to clear" semantics, clears only the specified pin's interrupt bit + */ + m5ioe1_err_t clearInterrupt(uint8_t pin); + + // ======================== + // 高级 GPIO 功能 + // Advanced GPIO Functions + // ======================== + /** + * @brief 设置上拉/下拉模式 + * Set pull-up/pull-down mode + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param pullMode 上拉/下拉模式:M5IOE1_PULL_NONE/UP/DOWN + * Pull mode: M5IOE1_PULL_NONE/UP/DOWN + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setPullMode(uint8_t pin, uint8_t pullMode); + /** + * @brief 设置输出驱动模式 + * Set output drive mode + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param driveMode 驱动模式:M5IOE1_DRIVE_PUSHPULL 或 M5IOE1_DRIVE_OPENDRAIN + * Drive mode: M5IOE1_DRIVE_PUSHPULL or M5IOE1_DRIVE_OPENDRAIN + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setDriveMode(uint8_t pin, uint8_t driveMode); + /** + * @brief 读取 GPIO 输入电平 + * Read GPIO input level + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param state 输出:电平状态(0/1) + * Output: level state (0/1) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getInputState(uint8_t pin, uint8_t* state); + + // ======================== + // ADC 功能 + // ADC Functions + // ======================== + /** + * @brief Read ADC value + * @param channel ADC channel (1-4) + * @param result Pointer to store 12-bit result + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t analogRead(uint8_t channel, uint16_t* result); + /** + * @brief 查询 ADC 是否忙 + * Check if ADC is busy + * @param busy 输出:busy 状态 + * Output: busy status + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t isAdcBusy(bool* busy); + /** + * @brief 禁用 ADC 并清零控制寄存器 + * Disable ADC and clear control register + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t disableAdc(); + + // ======================== + // 温度传感器 + // Temperature Sensor + // ======================== + /** + * @brief 读取温度传感器原始值 + * Read temperature sensor raw value + * @param temperature 输出:温度原始值(12-bit) + * Output: raw temperature value (12-bit) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t readTemperature(uint16_t* temperature); + /** + * @brief 查询温度转换是否忙 + * Check if temperature conversion is busy + * @param busy 输出:busy 状态 + * Output: busy status + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t isTemperatureBusy(bool* busy); + + // ======================== + // PWM 功能 + // PWM Functions + // ======================== + /** + * @brief Set PWM frequency (shared by all channels) + * @param frequency Frequency in Hz + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setPwmFrequency(uint16_t frequency); + /** + * @brief 读取 PWM 频率 + * Read PWM frequency + * @param frequency 输出:频率(Hz) + * Output: frequency in Hz + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getPwmFrequency(uint16_t* frequency); + + /** + * @brief Set PWM duty cycle (percentage) + * @param channel PWM channel (0-3) + * @param duty Duty cycle percentage (0-100) + * @param polarity PWM polarity (false=normal, true=inverted) + * @param enable Enable PWM output + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setPwmDuty(uint8_t channel, uint8_t duty, bool polarity = false, bool enable = true); + /** + * @brief 读取 PWM 占空比(百分比) + * Read PWM duty cycle (percentage) + * @param channel PWM 通道(0-3) + * PWM channel (0-3) + * @param duty 输出:占空比百分比(0-100) + * Output: duty percentage (0-100) + * @param polarity 输出:极性状态 + * Output: polarity state + * @param enable 输出:使能状态 + * Output: enable state + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getPwmDuty(uint8_t channel, uint8_t* duty, bool* polarity, bool* enable); + + /** + * @brief Set PWM duty cycle (12-bit value) + * @param channel PWM channel (0-3) + * @param duty12 12-bit duty value (0-4095) + * @param polarity PWM polarity + * @param enable Enable PWM output + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setPwmDuty12bit(uint8_t channel, uint16_t duty12, bool polarity = false, bool enable = true); + /** + * @brief 读取 PWM 占空比(12-bit) + * Read PWM duty cycle (12-bit) + * @param channel PWM 通道(0-3) + * PWM channel (0-3) + * @param duty12 输出:12-bit 占空比(0-4095) + * Output: 12-bit duty (0-4095) + * @param polarity 输出:极性状态 + * Output: polarity state + * @param enable 输出:使能状态 + * Output: enable state + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getPwmDuty12bit(uint8_t channel, uint16_t* duty12, bool* polarity, bool* enable); + + /** + * @brief 一次性配置 PWM 参数 + * Configure PWM in one call + * @param channel PWM 通道(0-3) + * PWM channel (0-3) + * @param enable 输出使能(true=启用,false=禁用) + * Enable output (true=enable, false=disable) + * @param polarity 极性(false=正常,true=反相) + * Polarity (false=normal, true=inverted) + * @param frequency PWM 频率(0-65535,全通道共享) + * PWM frequency in Hz (0-65535, shared by all channels) + * @param duty12 12 位占空比(0-4095) + * 12-bit duty (0-4095) + * @note 此 API 对冲突仅告警,仍会继续配置 + * This API warns on conflicts but still applies settings + * @note 变更频率会影响所有通道 + * Changing frequency affects all channels + */ + m5ioe1_err_t setPwmConfig(uint8_t channel, bool enable, bool polarity, uint16_t frequency, uint16_t duty12); + + /** + * @brief Arduino 兼容的 analogWrite 函数(PWM 输出) + * Arduino-compatible analogWrite function (PWM output) + * @param channel PWM 通道(0-3):M5IOE1_PWM_CH1/CH2/CH3/CH4 + * PWM channel (0-3): M5IOE1_PWM_CH1/CH2/CH3/CH4 + * CH1=IO9, CH2=IO8, CH3=IO11, CH4=IO10 + * @param value PWM 占空比(0-255,8-bit Arduino 标准) + * PWM duty cycle (0-255, 8-bit Arduino standard) + * 0 = 0% duty, 127 = 50% duty, 255 = 100% duty + * @return M5IOE1_OK if successful, error code otherwise + * @note 此函数内部将 8-bit 值缩放到 12-bit + * This function scales 8-bit value to 12-bit internally + * @note 值为 0 时关闭 PWM 输出 + * Value 0 turns off PWM output + */ + m5ioe1_err_t analogWrite(uint8_t channel, uint8_t value); + + // ======================== + // NeoPixel LED 功能 + // NeoPixel LED Functions + // ======================== + /** + * @brief 一次性配置并设置所有 NeoPixel LED / Configure and set all NeoPixel LEDs at once + * @param colors RGB 颜色数组 / Array of RGB colors + * @param arraySize 颜色数组的大小(用于边界检查) / Size of the colors array (for bounds checking) + * @param count 要设置的 LED 数量(1-32) / Number of LEDs to set (1-32) + * @param autoRefresh 如果为 true,设置后自动刷新 LED(默认:true) + * If true, automatically refresh LEDs after setting (default: true) + * @return M5IOE1_OK if successful, error code otherwise + * @note 此函数将: + * 1. 验证参数(count <= arraySize,count <= 32) + * 2. 设置 LED 数量寄存器 + * 3. 将所有 RGB 数据写入 LED RAM + * 4. 可选触发刷新 + * @note This function will: + * 1. Validate parameters (count <= arraySize, count <= 32) + * 2. Set LED count register + * 3. Write all RGB data to LED RAM + * 4. Optionally trigger refresh + * @example + * m5ioe1_rgb_t leds[8] = { + * {255, 0, 0}, // Red + * {0, 255, 0}, // Green + * {0, 0, 255}, // Blue + * {255, 255, 0}, // Yellow + * {255, 0, 255}, // Magenta + * {0, 255, 255}, // Cyan + * {255, 255, 255},// White + * {128, 128, 128} // Gray + * }; + * ioe1.setLeds(leds, sizeof(leds)/sizeof(leds[0]), 8, true); + */ + m5ioe1_err_t setLeds(const m5ioe1_rgb_t* colors, uint8_t arraySize, uint8_t count, bool autoRefresh = true); + /** + * @brief 设置 NeoPixel LED 数量 + * Set NeoPixel LED count + * @param count LED 数量(0-32) + * LED count (0-32) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setLedCount(uint8_t count); + /** + * @brief 设置单个 LED 颜色(RGB888) + * Set a single LED color (RGB888) + * @param index LED 索引(0-31) + * LED index (0-31) + * @param r 红色分量(0-255) + * Red component (0-255) + * @param g 绿色分量(0-255) + * Green component (0-255) + * @param b 蓝色分量(0-255) + * Blue component (0-255) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setLedColor(uint8_t index, uint8_t r, uint8_t g, uint8_t b); + /** + * @brief 设置单个 LED 颜色(结构体) + * Set a single LED color (struct) + * @param index LED 索引(0-31) + * LED index (0-31) + * @param color RGB 颜色结构体 + * RGB color struct + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setLedColor(uint8_t index, m5ioe1_rgb_t color); + /** + * @brief 刷新 LED 显示 + * Refresh LED display + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t refreshLeds(); + /** + * @brief 禁用所有 LED 并清零配置 + * Disable all LEDs and clear configuration + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t disableLeds(); + + /** + * @brief 清除 LED RAM 寄存器(将所有 LED 数据设置为 0) + * Clear LED RAM registers (set all LED data to 0) + * @return M5IOE1_OK if successful, error code otherwise + * @note 此函数会清除所有 32 个 LED 的颜色数据,建议在 begin() 后调用 + * This function clears color data for all 32 LEDs, recommended to call after begin() + */ + m5ioe1_err_t clearLedRam(); + + // ======================== + // AW8737A 脉冲功能 + // AW8737A Pulse Functions + // ======================== + /** + * @brief 设置 AW8737A 脉冲输出配置 + * Set AW8737A pulse output configuration + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param pulseNum 脉冲数量(0-3) + * Number of pulses (0-3) + * @param refresh 刷新控制:WAIT 或 NOW + * Refresh control: WAIT or NOW + * @return M5IOE1_OK if successful, error code otherwise + * @note 如果引脚不是输出模式,会自动配置为推挽输出;如果已是输出模式则保持原有配置 + * If pin is not output mode, it will be auto-configured as push-pull output; if already output, keeps current + * config + * @note 如果使用开漏输出,需要外部上拉电阻 + * If using open-drain output, external pull-up is required + * @note 当 refresh=NOW 时,执行后会有 20ms 延迟 + * When refresh=NOW, there will be a 20ms delay after execution + */ + m5ioe1_err_t setAw8737aPulse(uint8_t pin, m5ioe1_aw8737a_pulse_t pulseNum, + m5ioe1_aw8737a_refresh_t refresh = M5IOE1_AW8737A_REFRESH_NOW); + + /** + * @brief 触发 AW8737A 脉冲刷新 + * Trigger AW8737A pulse refresh + * @return M5IOE1_OK if successful, error code otherwise + * @note 需要先调用 setAw8737aPulse 并设置 refresh=WAIT,然后调用此函数触发 + * Call setAw8737aPulse with refresh=WAIT first, then call this to trigger + * @note 如果引脚不是输出模式,会自动配置为推挽输出;如果已是输出模式则保持原有配置 + * If pin is not output mode, it will be auto-configured as push-pull output; if already output, keeps current + * config + * @note 如果使用开漏输出,需要外部上拉电阻 + * If using open-drain output, external pull-up is required + * @note 执行后会有 20ms 延迟 + * There will be a 20ms delay after execution + */ + m5ioe1_err_t refreshAw8737aPulse(); + + /** + * @brief 设置 AW8737A 增益模式并可选刷新 + * Set AW8737A gain mode with optional refresh + * @param pin GPIO 引脚号(0-13) + * GPIO pin number (0-13) + * @param mode 增益模式 (MODE_1 到 MODE_4) + * Gain mode (MODE_1 to MODE_4) + * @param refresh 刷新控制:WAIT 或 NOW + * Refresh control: WAIT or NOW + * @return M5IOE1_OK if successful, error code otherwise + * @note 如果引脚不是输出模式,会自动配置为推挽输出;如果已是输出模式则保持原有配置 + * If pin is not output mode, it will be auto-configured as push-pull output; if already output, keeps current + * config + * @note 如果使用开漏输出,需要外部上拉电阻 + * If using open-drain output, external pull-up is required + * @note 当 refresh=NOW 时,执行后会有 20ms 延迟 + * When refresh=NOW, there will be a 20ms delay after execution + */ + m5ioe1_err_t setAw8737aMode(uint8_t pin, m5ioe1_aw8737a_mode_t mode, + m5ioe1_aw8737a_refresh_t refresh = M5IOE1_AW8737A_REFRESH_NOW); + + /** + * @brief 刷新 AW8737A 模式配置 + * Refresh AW8737A mode configuration + * @return M5IOE1_OK if successful, error code otherwise + * @note 需要先调用 setAw8737aMode 并设置 refresh=WAIT,然后调用此函数触发 + * Call setAw8737aMode with refresh=WAIT first, then call this to trigger + * @note 如果引脚不是输出模式,会自动配置为推挽输出;如果已是输出模式则保持原有配置 + * If pin is not output mode, it will be auto-configured as push-pull output; if already output, keeps current + * config + * @note 如果使用开漏输出,需要外部上拉电阻 + * If using open-drain output, external pull-up is required + * @note 执行后会有 20ms 延迟 + * There will be a 20ms delay after execution + */ + m5ioe1_err_t refreshAw8737aMode(); + + // ======================== + // RTC RAM 功能 + // RTC RAM Functions + // ======================== + /** + * @brief 写入 RTC RAM 并回读验证 + * Write RTC RAM with read-back verification + * @param offset 起始偏移(0-31) + * Start offset (0-31) + * @param data 输入数据指针 + * Input data pointer + * @param length 数据长度(1-32,且 offset+length <= 32) + * Data length (1-32, offset+length <= 32) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t writeRtcRAM(uint8_t offset, const uint8_t* data, uint8_t length); + /** + * @brief 读取 RTC RAM + * Read RTC RAM + * @param offset 起始偏移(0-31) + * Start offset (0-31) + * @param data 输出数据缓冲区 + * Output data buffer + * @param length 数据长度(1-32,且 offset+length <= 32) + * Data length (1-32, offset+length <= 32) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t readRtcRAM(uint8_t offset, uint8_t* data, uint8_t length); + + // ======================== + // 系统配置 + // System Configuration + // ======================== + + /** + * @brief 一次性设置所有 I2C 配置 / Set all I2C configuration at once + * @param sleepTime 休眠超时(0=禁用,1-15=超时值) + * @param sleepTime Sleep timeout (0=disabled, 1-15=timeout value) + * @param speed I2C 速度(M5IOE1_I2C_SPEED_100K 或 M5IOE1_I2C_SPEED_400K) + * @param speed I2C speed (M5IOE1_I2C_SPEED_100K or M5IOE1_I2C_SPEED_400K) + * @param wakeEdge 唤醒边沿(M5IOE1_WAKE_EDGE_FALLING 或 M5IOE1_WAKE_EDGE_RISING) + * @param wakeEdge Wake edge (M5IOE1_WAKE_EDGE_FALLING or M5IOE1_WAKE_EDGE_RISING) + * @param pullConfig 上拉配置(M5IOE1_PULL_ENABLED 或 M5IOE1_PULL_DISABLED) + * @param pullConfig Pull-up config (M5IOE1_PULL_ENABLED or M5IOE1_PULL_DISABLED) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setI2cConfig(uint8_t sleepTime, m5ioe1_i2c_speed_t speed = M5IOE1_I2C_SPEED_100K, + m5ioe1_wake_edge_t wakeEdge = M5IOE1_WAKE_EDGE_FALLING, + m5ioe1_pull_config_t pullConfig = M5IOE1_PULL_ENABLED); + + /** + * @brief 切换 I2C 通讯速度 / Switch I2C communication speed + * @param speed 目标速度 (M5IOE1_I2C_SPEED_100K 或 M5IOE1_I2C_SPEED_400K) + * @param speed Target speed (M5IOE1_I2C_SPEED_100K or M5IOE1_I2C_SPEED_400K) + * @return M5IOE1_OK if successful, error code otherwise + * @note 此函数会同时配置设备和主机 I2C 总线 + * @note This function will configure both the device and host I2C bus + */ + m5ioe1_err_t switchI2cSpeed(m5ioe1_i2c_speed_t speed); + + /** + * @brief 获取当前 I2C 速度设置 / Get current I2C speed setting + * @param speed 存储速度值的指针 / Pointer to store the speed value + * @return M5IOE1_OK if successful, error code otherwise + * @note 从设备寄存器读取并更新内部缓存 + * @note Reads from device register and updates internal cache + */ + m5ioe1_err_t getI2cSpeed(m5ioe1_i2c_speed_t* speed); + + /** + * @brief 设置 I2C 休眠超时 / Set I2C sleep timeout + * @param sleepTime 休眠超时值(0=禁用,1-15=超时) + * @param sleepTime Sleep timeout value (0=disabled, 1-15=timeout) + * @return M5IOE1_OK if successful, error code otherwise + * @note 休眠时间公式:T = sleepTime * 基础时间 + * @note Sleep time formula: T = sleepTime * base_time + */ + m5ioe1_err_t setI2cSleepTime(uint8_t sleepTime); + + /** + * @brief 获取当前 I2C 休眠超时 / Get current I2C sleep timeout + * @param sleepTime 存储休眠超时值的指针 / Pointer to store the sleep timeout value + * @return M5IOE1_OK if successful, error code otherwise + * @note 从设备寄存器读取并更新内部缓存 + * @note Reads from device register and updates internal cache + */ + m5ioe1_err_t getI2cSleepTime(uint8_t* sleepTime); + + /** + * @brief 设置 I2C 唤醒边沿 / Set I2C wake edge + * @param edge 唤醒边沿(M5IOE1_WAKE_EDGE_FALLING 或 M5IOE1_WAKE_EDGE_RISING) + * @param edge Wake edge (M5IOE1_WAKE_EDGE_FALLING or M5IOE1_WAKE_EDGE_RISING) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setI2cWakeEdge(m5ioe1_wake_edge_t edge); + + /** + * @brief 获取当前 I2C 唤醒边沿 / Get current I2C wake edge + * @param edge 存储唤醒边沿设置的指针 / Pointer to store the wake edge setting + * @return M5IOE1_OK if successful, error code otherwise + * @note 从设备寄存器读取并更新内部缓存 + * @note Reads from device register and updates internal cache + */ + m5ioe1_err_t getI2cWakeEdge(m5ioe1_wake_edge_t* edge); + + /** + * @brief 设置 I2C 内部上拉配置 / Set I2C internal pull-up configuration + * @param config 上拉配置(M5IOE1_PULL_ENABLED 或 M5IOE1_PULL_DISABLED) + * @param config Pull-up config (M5IOE1_PULL_ENABLED or M5IOE1_PULL_DISABLED) + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t setI2cPullConfig(m5ioe1_pull_config_t config); + + /** + * @brief 获取当前 I2C 内部上拉配置 / Get current I2C internal pull-up configuration + * @param config 存储上拉配置的指针 / Pointer to store the pull-up configuration + * @return M5IOE1_OK if successful, error code otherwise + * @note 从设备寄存器读取并更新内部缓存 + * @note Reads from device register and updates internal cache + */ + m5ioe1_err_t getI2cPullConfig(m5ioe1_pull_config_t* config); + + /** + * @brief 恢复出厂设置 / Factory reset the device + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t factoryReset(); + + // ======================== + // 自动唤醒功能 + // Auto Wake Feature + // ======================== + /** + * @brief 启用/禁用 I2C 操作前的自动唤醒信号 + * Enable/disable automatic wake signal before I2C operations + * @param enable true to enable auto-wake, false to disable + * @note 当 IOE1 进入睡眠模式(I2C 睡眠超时)后,需要在 SDA 上发送 + * START 信号来唤醒。此功能会在需要时自动发送唤醒信号。 + * When IOE1 enters sleep mode (I2C sleep timeout), it needs a + * START signal on SDA to wake up. This feature automatically + * sends the wake signal when needed. + * @note 即使不启用此选项,通讯在大多数情况下也能成功,因为第一次 + * I2C 传输本身就能唤醒设备。启用此选项可确保可靠性。 + * Even without enabling this option, communication will likely + * succeed in most cases, as the first I2C transaction itself + * can wake the device. Enable this for guaranteed reliability. + */ + void setAutoWakeEnable(bool enable); + + /** + * @brief 检查自动唤醒是否启用 / Check if auto wake is enabled + * @return true if enabled + */ + bool isAutoWakeEnabled() const; + + /** + * @brief 手动发送唤醒信号到 IOE1 / Manually send wake signal to IOE1 + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t sendWakeSignal(); + + // ======================== + // 状态快照功能 + // State Snapshot Functions + // ======================== + /** + * @brief 启用/禁用自动快照更新 + * Enable/disable automatic snapshot updates + * @param enable true 启用,false 禁用 + * true to enable, false to disable + */ + void setAutoSnapshot(bool enable); + /** + * @brief 查询自动快照是否启用 + * Check if auto snapshot is enabled + * @return true if enabled + */ + bool isAutoSnapshotEnabled() const; + /** + * @brief 立即更新快照缓存 + * Update snapshot cache immediately + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t updateSnapshot(); + + // ======================== + // 调试功能 + // Debug Functions + // ======================== + /** + * @brief 读取 GPIO 模式寄存器 + * Read GPIO mode register + * @param reg 输出:寄存器值 + * Output: register value + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getModeReg(uint16_t* reg); + /** + * @brief 读取 GPIO 输出寄存器 + * Read GPIO output register + * @param reg 输出:寄存器值 + * Output: register value + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getOutputReg(uint16_t* reg); + /** + * @brief 读取 GPIO 输入寄存器 + * Read GPIO input register + * @param reg 输出:寄存器值 + * Output: register value + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getInputReg(uint16_t* reg); + /** + * @brief 读取 GPIO 上拉寄存器 + * Read GPIO pull-up register + * @param reg 输出:寄存器值 + * Output: register value + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getPullUpReg(uint16_t* reg); + /** + * @brief 读取 GPIO 下拉寄存器 + * Read GPIO pull-down register + * @param reg 输出:寄存器值 + * Output: register value + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getPullDownReg(uint16_t* reg); + /** + * @brief 读取 GPIO 驱动寄存器 + * Read GPIO drive register + * @param reg 输出:寄存器值 + * Output: register value + * @return M5IOE1_OK if successful, error code otherwise + */ + m5ioe1_err_t getDriveReg(uint16_t* reg); + + // ======================== + // 配置验证 + // Configuration Validation + // ======================== + /** + * @brief Validate pin configuration before applying + * @param pin Pin number (0-13) + * @param configType Configuration type (m5ioe1_config_type_t) + * @param enable true to enable config, false to disable + * @return Validation result with error details if invalid + */ + m5ioe1_validation_t validateConfig(uint8_t pin, m5ioe1_config_type_t configType, bool enable = true); + + // ======================== + // 快照验证 + // Snapshot Verification + // ======================== + /** + * @brief Verify that cached state matches actual hardware registers + * @return Verification result with mismatch details + */ + m5ioe1_snapshot_verify_t verifySnapshot(); + + // ======================== + // 缓存状态查询函数 + // Cached State Query Functions + // ======================== + /** + * @brief Get cached PWM frequency + * @param frequency Output: PWM frequency in Hz + * @return M5IOE1_OK if cache is valid, M5IOE1_FAIL if cache invalid + */ + m5ioe1_err_t getCachedPwmFrequency(uint16_t* frequency); + + /** + * @brief Get cached PWM channel state + * @param channel PWM channel (0-3) + * @param duty12 Output: 12-bit duty value + * @param polarity Output: polarity setting + * @param enabled Output: enable state + * @return M5IOE1_OK if cache is valid, M5IOE1_FAIL if cache invalid + */ + m5ioe1_err_t getCachedPwmState(uint8_t channel, uint16_t* duty12, bool* polarity, bool* enabled); + + /** + * @brief Get cached ADC state + * @param activeChannel Output: current ADC channel (0=disabled, 1-4=channel) + * @param busy Output: conversion status + * @param lastValue Output: last conversion result + * @return M5IOE1_OK if cache is valid, M5IOE1_FAIL if cache invalid + */ + m5ioe1_err_t getCachedAdcState(uint8_t* activeChannel, bool* busy, uint16_t* lastValue); + + /** + * @brief Get cached GPIO pin state + * @param pin Pin number (0-13) + * @param isOutput Output: true if pin is output + * @param level Output: current level (output or input) + * @param pull Output: pull mode (0=none, 1=up, 2=down) + * @return M5IOE1_OK if cache is valid, M5IOE1_FAIL if cache invalid + */ + m5ioe1_err_t getCachedPinState(uint8_t pin, bool* isOutput, uint8_t* level, uint8_t* pull); + + /** + * @brief Enable or disable default interrupt logging when no callback is registered + * @param enable true to enable logging, false to disable (default) + */ + void enableDefaultInterruptLog(bool enable); + +private: + // 设备状态 + // Device state + uint8_t _addr; + bool _initialized; + bool _autoSnapshot; + bool _enableDefaultIsrLog; + uint32_t _requestedSpeed; // 用户请求的 I2C 速度(用于 400K 切换) + // User requested I2C speed (for 400K switch) + + // 自动唤醒状态 + // Auto wake state + bool _autoWakeEnabled; // 自动唤醒是否启用 + // Whether auto wake is enabled + uint32_t _lastCommTime; // 上次通信时间(毫秒) + // Last communication time (milliseconds) + + // 中断模式 + // Interrupt mode + m5ioe1_int_mode_t _intMode; + int8_t _intPin; + uint32_t _pollingInterval; + +#ifdef ARDUINO + TwoWire* _wire; + uint8_t _sda; // SDA 引脚编号,用于 I2C 重新初始化 + // SDA pin number for I2C re-initialization + uint8_t _scl; // SCL 引脚编号,用于 I2C 重新初始化 + // SCL pin number for I2C re-initialization +#if M5IOE1_HAS_M5UNIFIED_I2C + m5::I2C_Class* _m5_i2c; + uint32_t _commFreq; // M5UNIFIED 路径专用,init 时从 100K 起,完成后切换至 _requestedSpeed + // M5UNIFIED path: starts at 100K during init, switches to _requestedSpeed after +#endif +#else + // I2C 驱动类型选择 + // I2C driver type selection + m5ioe1_i2c_driver_t _i2cDriverType; + + // I2C 句柄(根据驱动类型仅使用一对) + // I2C handles (only one pair is used based on driver type) + // M5IOE1_I2C_DRIVER_SELF_CREATED (IDF >= 5.3.0): 使用 _i2c_master_bus + _i2c_master_dev + // uses _i2c_master_bus + _i2c_master_dev + // M5IOE1_I2C_DRIVER_MASTER: 使用 _i2c_master_bus + _i2c_master_dev + // uses _i2c_master_bus + _i2c_master_dev + // M5IOE1_I2C_DRIVER_BUS: 使用 _i2c_bus + _i2c_device + // uses _i2c_bus + _i2c_device + // M5IOE1_I2C_DRIVER_LEGACY (IDF < 5.3.0): 使用 _port + _addr,无额外句柄 + // uses _port + _addr, no additional handle +#if M5IOE1_HAS_I2C_MASTER + i2c_master_bus_handle_t _i2c_master_bus; // ESP-IDF 原生驱动/自创建 (IDF >= 5.3.0) + // ESP-IDF native driver / self-created (IDF >= 5.3.0) + i2c_master_dev_handle_t _i2c_master_dev; // ESP-IDF 原生驱动/自创建 (IDF >= 5.3.0) + // ESP-IDF native driver / self-created (IDF >= 5.3.0) +#endif // M5IOE1_HAS_I2C_MASTER +#if M5IOE1_HAS_I2C_BUS + i2c_bus_handle_t _i2c_bus; // esp-idf-lib 组件 + // esp-idf-lib component + i2c_bus_device_handle_t _i2c_device; // esp-idf-lib 组件 + // esp-idf-lib component +#endif + + // I2C 管理标志 + // I2C management flags + bool _busExternal; // 如果总线句柄由外部提供则为 true + // true if bus handle is provided externally + + // 自创建总线的 I2C 引脚(用于频率切换) + // I2C pins for self-created bus (for frequency switching) + int _sda; + int _scl; + i2c_port_t _port; + + // 中断处理 + // Interrupt handling + QueueHandle_t _intrQueue; + + // M5Unified I2C_Class 借用句柄及当前通信频率 + // Borrowed M5Unified I2C_Class handle and current communication frequency +#if M5IOE1_HAS_M5UNIFIED_I2C + m5::I2C_Class* _m5_i2c; + uint32_t _commFreq; // M5UNIFIED 路径专用,init 时从 100K 起,完成后切换至 _requestedSpeed + // M5UNIFIED path: starts at 100K during init, switches to _requestedSpeed after +#endif +#endif + + // 中断轮询任务句柄(Arduino / ESP-IDF 共用) + // Interrupt poll task handle (shared by Arduino and ESP-IDF) + TaskHandle_t _pollTask; + + // 中断回调 + // Interrupt callbacks + struct { + m5ioe1_callback_t callback; + m5ioe1_callback_arg_t callbackArg; + void* arg; + bool enabled; + bool rising; + } _callbacks[M5IOE1_MAX_GPIO_PINS]; + + // 缓存的引脚状态 + // Cached pin states + struct { + bool isOutput; + uint8_t outputLevel; + uint8_t inputLevel; + uint8_t pull; // 0:无, 1:上拉, 2:下拉 + // 0:none, 1:up, 2:down + uint8_t drive; // 0:推挽, 1:开漏 + // 0:push-pull, 1:open-drain + bool intrEnabled; + bool intrRising; + } _pinStates[M5IOE1_MAX_GPIO_PINS]; + bool _pinStatesValid; + + // 缓存的 PWM 状态 + // Cached PWM states + struct { + uint16_t duty12; + bool enabled; + bool polarity; + } _pwmStates[M5IOE1_MAX_PWM_CHANNELS]; + uint16_t _pwmFrequency; + bool _pwmStatesValid; + + // 缓存的 ADC 状态 + // Cached ADC state + struct { + uint8_t activeChannel; + bool busy; + uint16_t lastValue; + } _adcState; + bool _adcStateValid; + + // 缓存的 I2C 配置状态(用于睡眠模式检测) + // Cached I2C config state (for sleep mode detection) + struct { + uint8_t sleepTime; // 0=禁用, 1-15=睡眠时间 + // 0=disabled, 1-15=sleep time + bool speed400k; // I2C 速度模式 + // I2C speed mode + bool wakeRising; // 唤醒边沿模式 + // Wake edge mode + bool pullOff; // 内部上拉关闭 + // Internal pull-up off + } _i2cConfig; + bool _i2cConfigValid; + + // NeoPixel 状态 + // NeoPixel state + + // AW8737A 配置状态缓存 + // AW8737A configuration state cache + bool _aw8737aConfigured; // 是否已调用 setAw8737aPulse 配置 + // Whether setAw8737aPulse has been called + uint8_t _aw8737aPin; // 配置的引脚号 + // Configured pin number + m5ioe1_aw8737a_pulse_t _aw8737aPulseNum; // 配置的脉冲数 + // Configured pulse count + uint8_t _aw8737aRegValue; // 缓存的寄存器值(不含 REFRESH 位) + // Cached register value (without REFRESH bit) + bool _aw8737aStateValid; // 缓存有效性标志 + // Cache validity flag + + // ======================== + // 内部辅助函数 + // Internal Helper Functions + // ======================== + m5ioe1_err_t _pinModeWithErr(uint8_t pin, uint8_t mode); + m5ioe1_err_t _digitalWriteWithErr(uint8_t pin, uint8_t value); + m5ioe1_err_t _digitalReadWithErr(uint8_t pin, int* value); + + bool _writeReg(uint8_t reg, uint8_t value); + bool _writeReg16(uint8_t reg, uint16_t value); + bool _readReg(uint8_t reg, uint8_t* value); + bool _readReg16(uint8_t reg, uint16_t* value); + bool _writeBytes(uint8_t reg, const uint8_t* data, uint8_t len); + bool _readBytes(uint8_t reg, uint8_t* data, uint8_t len); + + bool _isValidPin(uint8_t pin); + bool _isAdcPin(uint8_t pin); + bool _isPwmPin(uint8_t pin); + uint8_t _getAdcChannel(uint8_t pin); + uint8_t _getPwmChannel(uint8_t pin); + + void _clearPinStates(); + void _clearPwmStates(); + void _clearAdcState(); + bool _snapshotPinStates(); + bool _snapshotPwmStates(); + bool _snapshotAdcState(); + bool _snapshotAw8737a(); + void _clearAw8737a(); + void _autoSnapshotUpdate(uint8_t domains = M5IOE1_SNAPSHOT_DOMAIN_ALL); + + // ======================== + // 地址候选与 REV 规则辅助 + // Address candidate & REV rule helpers + // ======================== + + // 地址段 <-> 允许 REV 字符列表 + // Address range to allowed REV character list + struct m5ioe1_rev_rule_t { + uint8_t start; + uint8_t end; + const char* valid_revs; // 允许的 REV 字符(字符串,每个字符为一个允许值) + // Allowed REV characters (string, each char is one allowed value) + }; + static const m5ioe1_rev_rule_t kRevRules[]; + + // REV 校验三态结果 + // REV check tri-state result + enum class RevCheckResult { + NO_RESPONSE, // 地址无响应 + // No response at address + MATCHED, // REV 在允许列表中 + // REV is in allowed list + MISMATCH, // 地址有响应但 REV 不在允许列表中 + // Address responded but REV not in allowed list + NO_RULE, // 该地址段无规则(允许通过) + // No rule for this address range (allow through) + }; + + const m5ioe1_rev_rule_t* _findRevRule(uint8_t addr) const; + bool _isRevisionAllowed(uint8_t addr, uint8_t rev) const; + + // 低噪声探测 REV 寄存器(不打 error 日志) + // Low-noise probe for REV register (no error log) + RevCheckResult _checkAddressRevision(uint8_t addr) const; + + // 构建候选地址列表,返回实际候选数 + // Build candidate address list, returns actual candidate count + static constexpr uint8_t kMaxCandidates = 12; + static uint8_t _buildAddressCandidates(uint8_t requested, uint8_t* candidates); + + // 针对当前驱动上下文,将候选地址应用到 I2C 句柄(必要时重建 device handle) + // Apply candidate address to I2C handle for current driver context (rebuild device handle if needed) + m5ioe1_err_t _applyAddressToDriver(uint8_t candidate, uint32_t freqHz); + + // 对单个候选地址执行完整初始化尝试(100K → 800ms → 100K → 400K),不做 REV 检查 + // Perform full init attempt for one candidate (100K→800ms→100K→400K), no REV check + m5ioe1_err_t _tryInitAtAddress(uint8_t candidate); + + // 初始化成功后的收尾流程(快照、setI2cConfig、中断模式) + // Post-init finalization (snapshot, setI2cConfig, interrupt mode) + m5ioe1_err_t _finishInit(m5ioe1_int_mode_t intMode); + + // 地址解析与初始化核心流程(必须在 bus 初始化完成后调用) + // Address resolution and init core flow (must be called after bus init is complete) + m5ioe1_err_t _resolveAndInit(uint8_t requestedAddr, m5ioe1_int_mode_t intMode); + + bool _initDevice(); + void _handleInterrupt(); + + // 自动唤醒检查 + // Auto wake check + void _checkAutoWake(); + + // I2C 休眠与轮询任务联动 + // I2C sleep and polling task linkage + void _updatePollingForI2cSleep(uint8_t sleepTime); + + // I2C 频率验证 + // I2C frequency validation + bool _isValidI2cFrequency(uint32_t speed); + + // 中断互斥对检查 + // Interrupt mutex pairs check + static bool _pinsConflict(uint8_t a, uint8_t b); + bool _hasConflictingInterrupt(uint8_t pin); + + // 配置验证辅助函数 + // Configuration validation helpers + bool _getInterruptMutexPin(uint8_t pin, uint8_t* mutexPin); + bool _isNeopixelPin(uint8_t pin); + bool _hasActiveInterrupt(uint8_t pin); + bool _hasActiveAdc(uint8_t pin); + bool _hasActivePwm(uint8_t pin); + bool _hasI2cSleepEnabled(); + bool _isLedEnabled(); + + // I2C 配置快照 + // I2C config snapshot + void _clearI2cConfig(); + bool _snapshotI2cConfig(); + +#ifdef ARDUINO + // Arduino 专用 + // Arduino specific + bool _setupPollingArduino(); + void _cleanupPollingArduino(); + static void _pollTaskArduino(void* arg); + bool _setupHardwareInterruptArduino(); + void _cleanupHardwareInterruptArduino(); + static void _intrTaskArduino(void* arg); + static void IRAM_ATTR _arduinoIsrHandler(void* arg); +#else + // ESP-IDF 专用 + // ESP-IDF specific + static void _pollTaskFunc(void* arg); + static void IRAM_ATTR _isrHandler(void* arg); + bool _setupHardwareInterrupt(); + bool _setupPolling(); + void _cleanupPolling(); + void _cleanupHardwareInterrupt(); +#endif +}; + +#endif // _M5IOE1_H_ diff --git a/components/m5ioe1/src/M5IOE1_i2c_compat.h b/components/m5ioe1/src/M5IOE1_i2c_compat.h new file mode 100644 index 0000000..420d262 --- /dev/null +++ b/components/m5ioe1/src/M5IOE1_i2c_compat.h @@ -0,0 +1,788 @@ +/* + * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __M5IOE1_I2C_COMPAT_H__ +#define __M5IOE1_I2C_COMPAT_H__ + +#include +#include +#include + +// ============================ +// I2C 延迟配置 (可调整以解决总线冲突) +// I2C Delay Configuration (adjustable to resolve bus conflicts) +// 适用于所有平台和驱动 (Arduino Wire / M5Unified / i2c_master / i2c_bus / Legacy) +// Applies to all platforms and drivers (Arduino Wire / M5Unified / i2c_master / i2c_bus / Legacy) +// ============================ +#ifndef M5IOE1_I2C_WRITE_DELAY_US +#define M5IOE1_I2C_WRITE_DELAY_US 500 // 写操作后延迟(微秒) / Delay after write (microseconds) +#endif + +#ifndef M5IOE1_I2C_READ_DELAY_US +#define M5IOE1_I2C_READ_DELAY_US 500 // 读操作前/后延迟(微秒) / Delay around read (microseconds) +#endif + +// 微秒级延迟宏 (平台无关) / Microsecond delay macro (platform-agnostic) +#ifdef ARDUINO +#define M5IOE1_I2C_DELAY_US(us) delayMicroseconds(us) +#else +#define M5IOE1_I2C_DELAY_US(us) esp_rom_delay_us(us) +#endif + +#ifdef ARDUINO + +#include "Wire.h" + +// ============================ +// Arduino I2C 功能 +// Arduino I2C Functions +// ============================ + +#ifndef M5IOE1_I2C_ARDUINO_READ_BYTE +static inline bool M5IOE1_I2C_ARDUINO_READ_BYTE(TwoWire *wire, uint8_t addr, uint8_t reg, uint8_t *data) +{ + wire->beginTransmission(addr); + wire->write(reg); + if (wire->endTransmission(false) != 0) { + return false; + } + delayMicroseconds(M5IOE1_I2C_READ_DELAY_US); // 增加延迟避免 I2C 总线冲突 / extra delay to avoid bus conflicts + if (wire->requestFrom(addr, (uint8_t)1) != 1) { + return false; + } + *data = wire->read(); + return true; +} +#endif + +#ifndef M5IOE1_I2C_ARDUINO_READ_BYTES +static inline bool M5IOE1_I2C_ARDUINO_READ_BYTES(TwoWire *wire, uint8_t addr, uint8_t start_reg, size_t len, + uint8_t *data) +{ + wire->beginTransmission(addr); + wire->write(start_reg); + if (wire->endTransmission(false) != 0) { + return false; + } + delayMicroseconds(M5IOE1_I2C_READ_DELAY_US); // 增加延迟避免 I2C 总线冲突 + if (wire->requestFrom(addr, (uint8_t)len) != len) { + return false; + } + for (size_t i = 0; i < len; i++) { + data[i] = wire->read(); + } + return true; +} +#endif + +#ifndef M5IOE1_I2C_ARDUINO_READ_REG16 +static inline bool M5IOE1_I2C_ARDUINO_READ_REG16(TwoWire *wire, uint8_t addr, uint8_t reg, uint16_t *data) +{ + uint8_t buf[2]; + if (!M5IOE1_I2C_ARDUINO_READ_BYTES(wire, addr, reg, 2, buf)) { + return false; + } + // 小端模式:低字节在前 + // Little-endian: low byte first + *data = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8); + return true; +} +#endif + +#ifndef M5IOE1_I2C_ARDUINO_WRITE_BYTE +static inline bool M5IOE1_I2C_ARDUINO_WRITE_BYTE(TwoWire *wire, uint8_t addr, uint8_t reg, uint8_t data) +{ + wire->beginTransmission(addr); + wire->write(reg); + wire->write(data); + if (wire->endTransmission() != 0) { + return false; + } + delayMicroseconds(M5IOE1_I2C_WRITE_DELAY_US); // 增加延迟给设备处理时间 + return true; +} +#endif + +#ifndef M5IOE1_I2C_ARDUINO_WRITE_BYTES +static inline bool M5IOE1_I2C_ARDUINO_WRITE_BYTES(TwoWire *wire, uint8_t addr, uint8_t start_reg, size_t len, + const uint8_t *data) +{ + wire->beginTransmission(addr); + wire->write(start_reg); + for (size_t i = 0; i < len; i++) { + wire->write(data[i]); + } + if (wire->endTransmission() != 0) { + return false; + } + delayMicroseconds(M5IOE1_I2C_WRITE_DELAY_US); // 增加延迟给设备处理时间 + return true; +} +#endif + +#ifndef M5IOE1_I2C_ARDUINO_WRITE_REG16 +static inline bool M5IOE1_I2C_ARDUINO_WRITE_REG16(TwoWire *wire, uint8_t addr, uint8_t reg, uint16_t data) +{ + uint8_t buf[2]; + // 小端模式:低字节在前 + // Little-endian: low byte first + buf[0] = (uint8_t)(data & 0xFF); + buf[1] = (uint8_t)((data >> 8) & 0xFF); + return M5IOE1_I2C_ARDUINO_WRITE_BYTES(wire, addr, reg, 2, buf); +} +#endif + +// 唤醒信号发送 / Wake signal send +// IOE1 使用 SDA 引脚下降沿触发唤醒中断 +// IOE1 uses SDA pin falling edge to trigger wake interrupt +#ifndef M5IOE1_I2C_ARDUINO_SEND_WAKE +static inline void M5IOE1_I2C_ARDUINO_SEND_WAKE(TwoWire *wire, uint8_t addr) +{ + // Send START then STOP to avoid leaving the bus in a busy state + wire->beginTransmission(addr); + wire->endTransmission(); // Send START with STOP +} +#endif + +// ============================ +// M5Unified I2C_Class 检测 (Arduino) +// M5Unified I2C_Class Detection (Arduino) +// ============================ +#if defined(__cplusplus) && __has_include() +#define M5IOE1_HAS_M5UNIFIED_I2C 1 +#include +#else +#define M5IOE1_HAS_M5UNIFIED_I2C 0 +#endif + +// ============================ +// M5Unified I2C_Class 通信封装 (Arduino) +// M5Unified I2C_Class Communication Wrappers (Arduino) +// (C++ only — m5::I2C_Class is a C++ class) +// ============================ +#if M5IOE1_HAS_M5UNIFIED_I2C + +#ifndef M5IOE1_M5UNIFIED_READ_BYTE +static inline bool M5IOE1_M5UNIFIED_READ_BYTE(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint8_t *data, + uint32_t freq) +{ + bool ok = i2c->readRegister(addr, reg, data, 1, freq); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + return ok; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_READ_BYTES +static inline bool M5IOE1_M5UNIFIED_READ_BYTES(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, size_t len, uint8_t *data, + uint32_t freq) +{ + bool ok = i2c->readRegister(addr, reg, data, len, freq); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + return ok; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_READ_REG16 +static inline bool M5IOE1_M5UNIFIED_READ_REG16(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint16_t *data, + uint32_t freq) +{ + uint8_t buf[2]; + if (!i2c->readRegister(addr, reg, buf, 2, freq)) return false; + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + // 小端模式:低字节在前 / Little-endian: low byte first + *data = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8); + return true; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_WRITE_BYTE +static inline bool M5IOE1_M5UNIFIED_WRITE_BYTE(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint8_t data, + uint32_t freq) +{ + bool ok = i2c->writeRegister8(addr, reg, data, freq); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ok; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_WRITE_BYTES +static inline bool M5IOE1_M5UNIFIED_WRITE_BYTES(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, size_t len, + const uint8_t *data, uint32_t freq) +{ + bool ok = i2c->writeRegister(addr, reg, data, len, freq); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ok; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_WRITE_REG16 +static inline bool M5IOE1_M5UNIFIED_WRITE_REG16(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint16_t data, + uint32_t freq) +{ + uint8_t buf[2]; + // 小端模式:低字节在前 / Little-endian: low byte first + buf[0] = (uint8_t)(data & 0xFF); + buf[1] = (uint8_t)((data >> 8) & 0xFF); + bool ok = i2c->writeRegister(addr, reg, buf, 2, freq); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ok; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_SEND_WAKE +// 产生 I2C START 信号以唤醒处于睡眠的 IOE1 +// Generate I2C START signal to wake IOE1 from sleep +static inline bool M5IOE1_M5UNIFIED_SEND_WAKE(m5::I2C_Class *i2c, uint8_t addr, uint32_t freq) +{ + i2c->start(addr, false, freq); + i2c->stop(); + return true; +} +#endif + +#endif // M5IOE1_HAS_M5UNIFIED_I2C + +#else // ESP-IDF + +#include +#include +#include // esp_rom_delay_us + +// ============================ +// I2C 驱动检测 +// I2C Driver Detection +// ============================ + +// 检测 i2c_bus 是否可用 +// Detect if i2c_bus is available +// +// ESP-IDF < 5.3.0 +// Without BACKWARD_CONFIG: i2c_bus not supported; use legacy driver/i2c.h API. +// With BACKWARD_CONFIG: i2c_bus.h falls back to driver/i2c.h internally, safe to use. +// +// ESP-IDF >= 5.3.0 +// [Highest priority] M5Unified is present: +// -> i2c_bus mode is not safe because M5Unified owns its I2C_Class bus. +// Fix: use ioe1.begin(&M5.In_I2C, addr, freq) instead. +// M5GFX alone: +// -> header presence is not enough to prove an I2C runtime conflict. Projects that use +// M5GFX only for display/SPI can still use espressif/i2c_bus for external devices. +// Do not call lgfx::i2c::init() on the same port while i2c_bus owns it. +// With BACKWARD_CONFIG (no M5GFX/M5Unified): i2c_bus.h uses driver/i2c.h internally, safe. +// Without BACKWARD_CONFIG (no M5GFX/M5Unified): +// _DRIVER_I2C_H_ defined: i2c_config_t typedef conflict risk, disabled. +// _DRIVER_I2C_H_ not defined: no conflict risk, enable i2c_bus. +#if __has_include() +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) +#if defined(CONFIG_I2C_BUS_BACKWARD_CONFIG) +#define M5IOE1_HAS_I2C_BUS 1 // IDF < 5.3.0 + BACKWARD_CONFIG:可用 / available +#else +#define M5IOE1_HAS_I2C_BUS 0 // IDF < 5.3.0:默认 Legacy API / legacy API by default +#endif +#else +// IDF >= 5.3.0 +// +// Runtime conflict (highest priority): +// M5Unified owns an I2C_Class instance. espressif__i2c_bus conflicts with it on both code paths: +// BACKWARD_CONFIG=y -> i2c_bus.c calls i2c_driver_install() (legacy) -> runtime abort() +// BACKWARD_CONFIG=n -> i2c_bus_v2.c calls i2c_new_master_bus() on the same port -> undefined behaviour +// Therefore, i2c_bus mode MUST be disabled whenever M5Unified is in the project. +// +// M5GFX alone is intentionally allowed here. The presence of M5GFX.h does not mean lgfx::i2c::init() +// is used, and SPI/display-only M5GFX projects can share the project with espressif__i2c_bus safely. +#if __has_include() +#if defined(_I2C_BUS_H_) +#error \ + "[M5IOE1] i2c_bus cannot be used together with M5Unified. " \ + "M5Unified owns its I2C_Class bus. " \ + "BACKWARD_CONFIG=y: i2c_bus.c calls i2c_driver_install() (legacy driver) -> runtime abort() in check_i2c_driver_conflict(). " \ + "BACKWARD_CONFIG=n: i2c_bus_v2.c calls i2c_new_master_bus() on the same port -> undefined behaviour. " \ + "Fix: use ioe1.begin(&M5.In_I2C, addr, freq) and set I2C_USE_MODE=0." +#endif +#define M5IOE1_HAS_I2C_BUS 0 +#elif defined(CONFIG_I2C_BUS_BACKWARD_CONFIG) +#define M5IOE1_HAS_I2C_BUS 1 // BACKWARD_CONFIG, no M5Unified: compatible +#elif defined(_DRIVER_I2C_H_) +#define M5IOE1_HAS_I2C_BUS 0 // driver/i2c.h already included -> i2c_config_t typedef conflict risk, disabled +#else +#define M5IOE1_HAS_I2C_BUS 1 // no conflict risk +#endif +#endif +#else +#define M5IOE1_HAS_I2C_BUS 0 +#endif + +// 选择 I2C 驱动头文件 +// Select I2C driver header +// +// ESP-IDF < 5.3.0:使用传统 Legacy I2C API(driver/i2c.h),与 i2c_bus 无关 +// ESP-IDF < 5.3.0: use legacy I2C API (driver/i2c.h), independent of i2c_bus. +// +// ESP-IDF >= 5.3.0:优先使用新版 i2c_master 驱动 +// ESP-IDF >= 5.3.0: prefer the new i2c_master driver. +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 3, 0) +#include +#elif __has_include() +#include +#else +#include +#endif + +// i2c_master 原生驱动可用性检测 +// i2c_master native driver availability detection +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && __has_include() +#define M5IOE1_HAS_I2C_MASTER 1 +#else +#define M5IOE1_HAS_I2C_MASTER 0 +#endif + +// ============================ +// M5Unified I2C_Class 检测 +// M5Unified I2C_Class Detection +// ============================ +// +#if M5IOE1_HAS_I2C_BUS +#include +#else +// i2c_bus 桩类型 +// i2c_bus stub types +typedef void *i2c_bus_handle_t; +typedef void *i2c_bus_device_handle_t; +#endif + +// 仅 C++ 环境下检测 M5Unified 的 I2C_Class 是否可用。 +// Detects whether M5Unified's I2C_Class is available (C++ only). +// 注意:i2c_bus.h 必须先于 I2C_Class.hpp 包含,避免 i2c_config_t typedef 冲突。 +// Note: i2c_bus.h must be included before I2C_Class.hpp to avoid i2c_config_t typedef conflict. +#if defined(__cplusplus) && __has_include() +#define M5IOE1_HAS_M5UNIFIED_I2C 1 +#include +#else +#define M5IOE1_HAS_M5UNIFIED_I2C 0 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// ============================ +// I2C 驱动类型选择 +// I2C Driver Type Selection +// ============================ +typedef enum { + M5IOE1_I2C_DRIVER_NONE = 0, // 未初始化 + // Not initialized + M5IOE1_I2C_DRIVER_SELF_CREATED, // 使用 i2c_port_t 自创建(IDF >= 5.3.0 用 i2c_master,否则用 Legacy) + // Self-created using i2c_port_t (i2c_master on IDF >= 5.3.0, Legacy otherwise) + M5IOE1_I2C_DRIVER_MASTER, // ESP-IDF 原生 i2c_master 驱动(仅 IDF >= 5.3.0) + // ESP-IDF native i2c_master driver (IDF >= 5.3.0 only) +#if M5IOE1_HAS_I2C_BUS + M5IOE1_I2C_DRIVER_BUS, // esp-idf-lib i2c_bus 组件 + // esp-idf-lib i2c_bus component +#endif +#if !M5IOE1_HAS_I2C_MASTER && !M5IOE1_HAS_I2C_BUS + M5IOE1_I2C_DRIVER_LEGACY, // 传统 driver/i2c.h API(IDF < 5.3.0 且无 i2c_bus) + // Legacy driver/i2c.h API (IDF < 5.3.0 without i2c_bus) +#endif +#if M5IOE1_HAS_M5UNIFIED_I2C + M5IOE1_I2C_DRIVER_M5UNIFIED, // 借用 M5Unified I2C_Class 通信(不负责驱动安装/释放) + // Borrow M5Unified I2C_Class for communication (no driver lifecycle) +#endif +} m5ioe1_i2c_driver_t; + +// ============================ +// ESP-IDF I2C 函数 (i2c_bus) +// ESP-IDF I2C Functions (i2c_bus) +// ============================ + +#if M5IOE1_HAS_I2C_BUS + +#ifndef M5IOE1_I2C_BUS_READ_BYTE +static inline esp_err_t M5IOE1_I2C_BUS_READ_BYTE(i2c_bus_device_handle_t dev, uint8_t reg, uint8_t *data) +{ + esp_err_t ret = i2c_bus_read_byte(dev, reg, data); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_BUS_READ_BYTES +static inline esp_err_t M5IOE1_I2C_BUS_READ_BYTES(i2c_bus_device_handle_t dev, uint8_t start_reg, size_t len, + uint8_t *data) +{ + esp_err_t ret = i2c_bus_read_bytes(dev, start_reg, len, data); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_BUS_READ_REG16 +static inline esp_err_t M5IOE1_I2C_BUS_READ_REG16(i2c_bus_device_handle_t dev, uint8_t reg, uint16_t *data) +{ + uint8_t buf[2]; + esp_err_t ret = i2c_bus_read_bytes(dev, reg, 2, buf); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + if (ret == ESP_OK) { + // 小端模式:低字节在前 + // Little-endian: low byte first + *data = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8); + } + return ret; +} +#endif + +#ifndef M5IOE1_I2C_BUS_WRITE_BYTE +static inline esp_err_t M5IOE1_I2C_BUS_WRITE_BYTE(i2c_bus_device_handle_t dev, uint8_t reg, uint8_t data) +{ + esp_err_t ret = i2c_bus_write_byte(dev, reg, data); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_BUS_WRITE_BYTES +static inline esp_err_t M5IOE1_I2C_BUS_WRITE_BYTES(i2c_bus_device_handle_t dev, uint8_t start_reg, size_t len, + const uint8_t *data) +{ + esp_err_t ret = i2c_bus_write_bytes(dev, start_reg, len, (uint8_t *)data); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_BUS_WRITE_REG16 +static inline esp_err_t M5IOE1_I2C_BUS_WRITE_REG16(i2c_bus_device_handle_t dev, uint8_t reg, uint16_t data) +{ + uint8_t buf[2]; + // 小端模式:低字节在前 + // Little-endian: low byte first + buf[0] = (uint8_t)(data & 0xFF); + buf[1] = (uint8_t)((data >> 8) & 0xFF); + esp_err_t ret = i2c_bus_write_bytes(dev, reg, 2, buf); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ret; +} +#endif + +// 唤醒信号发送 (i2c_bus) / Wake signal send (i2c_bus) +#ifndef M5IOE1_I2C_BUS_SEND_WAKE +static inline esp_err_t M5IOE1_I2C_BUS_SEND_WAKE(i2c_bus_device_handle_t dev, uint8_t reg) +{ + // Read any register to generate I2C start signal for wake + uint8_t dummy; + return i2c_bus_read_byte(dev, reg, &dummy); +} +#endif + +#else // !M5IOE1_HAS_I2C_BUS + +// ============================ +// ESP-IDF I2C 函数 (Legacy - driver/i2c.h,仅 IDF < 5.3.0 且无 i2c_bus) +// ESP-IDF I2C Functions (Legacy - driver/i2c.h, IDF < 5.3.0 without i2c_bus only) +// 使用 IDF 4.3+ 引入的简单 API:i2c_master_write_to_device / i2c_master_write_read_device +// Uses simple API introduced in IDF 4.3+: i2c_master_write_to_device / i2c_master_write_read_device +// ============================ + +#if !M5IOE1_HAS_I2C_MASTER + +#include +#include + +// 传统 I2C 超时,单位毫秒 / Legacy I2C timeout in milliseconds +#ifndef M5IOE1_I2C_LEGACY_TIMEOUT_MS +#define M5IOE1_I2C_LEGACY_TIMEOUT_MS 100 +#endif + +#ifndef M5IOE1_I2C_LEGACY_READ_BYTE +static inline esp_err_t M5IOE1_I2C_LEGACY_READ_BYTE(i2c_port_t port, uint8_t addr, uint8_t reg, uint8_t *data) +{ + esp_err_t ret = + i2c_master_write_read_device(port, addr, ®, 1, data, 1, pdMS_TO_TICKS(M5IOE1_I2C_LEGACY_TIMEOUT_MS)); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_LEGACY_READ_BYTES +static inline esp_err_t M5IOE1_I2C_LEGACY_READ_BYTES(i2c_port_t port, uint8_t addr, uint8_t start_reg, size_t len, + uint8_t *data) +{ + esp_err_t ret = + i2c_master_write_read_device(port, addr, &start_reg, 1, data, len, pdMS_TO_TICKS(M5IOE1_I2C_LEGACY_TIMEOUT_MS)); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_LEGACY_READ_REG16 +static inline esp_err_t M5IOE1_I2C_LEGACY_READ_REG16(i2c_port_t port, uint8_t addr, uint8_t reg, uint16_t *data) +{ + uint8_t buf[2]; + esp_err_t ret = + i2c_master_write_read_device(port, addr, ®, 1, buf, 2, pdMS_TO_TICKS(M5IOE1_I2C_LEGACY_TIMEOUT_MS)); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + if (ret == ESP_OK) { + // 小端模式:低字节在前 + // Little-endian: low byte first + *data = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8); + } + return ret; +} +#endif + +#ifndef M5IOE1_I2C_LEGACY_WRITE_BYTE +static inline esp_err_t M5IOE1_I2C_LEGACY_WRITE_BYTE(i2c_port_t port, uint8_t addr, uint8_t reg, uint8_t data) +{ + uint8_t buf[2] = {reg, data}; + esp_err_t ret = i2c_master_write_to_device(port, addr, buf, 2, pdMS_TO_TICKS(M5IOE1_I2C_LEGACY_TIMEOUT_MS)); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_LEGACY_WRITE_BYTES +static inline esp_err_t M5IOE1_I2C_LEGACY_WRITE_BYTES(i2c_port_t port, uint8_t addr, uint8_t start_reg, size_t len, + const uint8_t *data) +{ + // 需要在数据前添加寄存器地址 + // Need to prepend register address before data + uint8_t *buf = (uint8_t *)malloc(len + 1); + if (buf == NULL) return ESP_ERR_NO_MEM; + buf[0] = start_reg; + memcpy(buf + 1, data, len); + esp_err_t ret = i2c_master_write_to_device(port, addr, buf, len + 1, pdMS_TO_TICKS(M5IOE1_I2C_LEGACY_TIMEOUT_MS)); + free(buf); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_LEGACY_WRITE_REG16 +static inline esp_err_t M5IOE1_I2C_LEGACY_WRITE_REG16(i2c_port_t port, uint8_t addr, uint8_t reg, uint16_t data) +{ + uint8_t buf[3]; + buf[0] = reg; + // 小端模式:低字节在前 + // Little-endian: low byte first + buf[1] = (uint8_t)(data & 0xFF); + buf[2] = (uint8_t)((data >> 8) & 0xFF); + esp_err_t ret = i2c_master_write_to_device(port, addr, buf, 3, pdMS_TO_TICKS(M5IOE1_I2C_LEGACY_TIMEOUT_MS)); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ret; +} +#endif + +// 唤醒信号发送 (Legacy) / Wake signal send (Legacy) +// 通过读取一个字节来产生 I2C START 信号 +// Generates I2C START signal by reading one byte +#ifndef M5IOE1_I2C_LEGACY_SEND_WAKE +static inline esp_err_t M5IOE1_I2C_LEGACY_SEND_WAKE(i2c_port_t port, uint8_t addr) +{ + uint8_t dummy = 0; + uint8_t reg = 0x00; + // 忽略错误:唤醒时设备可能处于睡眠状态,ACK 可能超时 + // Ignore error: device may be asleep during wake, ACK may timeout + i2c_master_write_read_device(port, addr, ®, 1, &dummy, 1, pdMS_TO_TICKS(10)); + return ESP_OK; +} +#endif + +#endif // !M5IOE1_HAS_I2C_MASTER + +#endif // M5IOE1_HAS_I2C_BUS + +// ============================ +// ESP-IDF I2C 函数 (i2c_master - 原生驱动) +// ESP-IDF I2C Functions (i2c_master - native driver) +// 仅 ESP-IDF >= 5.3.0 支持(driver/i2c_master.h 引入于 5.1,API 稳定于 5.3) +// Available on ESP-IDF >= 5.3.0 only (driver/i2c_master.h introduced in 5.1, stable in 5.3) +// ============================ + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) + +#ifndef M5IOE1_I2C_MASTER_READ_BYTE +static inline esp_err_t M5IOE1_I2C_MASTER_READ_BYTE(i2c_master_dev_handle_t dev, uint8_t reg, uint8_t *data) +{ + esp_err_t ret = i2c_master_transmit_receive(dev, ®, 1, data, 1, -1); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_MASTER_READ_BYTES +static inline esp_err_t M5IOE1_I2C_MASTER_READ_BYTES(i2c_master_dev_handle_t dev, uint8_t start_reg, size_t len, + uint8_t *data) +{ + esp_err_t ret = i2c_master_transmit_receive(dev, &start_reg, 1, data, len, -1); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_MASTER_READ_REG16 +static inline esp_err_t M5IOE1_I2C_MASTER_READ_REG16(i2c_master_dev_handle_t dev, uint8_t reg, uint16_t *data) +{ + uint8_t buf[2]; + esp_err_t ret = i2c_master_transmit_receive(dev, ®, 1, buf, 2, -1); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + if (ret == ESP_OK) { + // 小端模式:低字节在前 + // Little-endian: low byte first + *data = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8); + } + return ret; +} +#endif + +#ifndef M5IOE1_I2C_MASTER_WRITE_BYTE +static inline esp_err_t M5IOE1_I2C_MASTER_WRITE_BYTE(i2c_master_dev_handle_t dev, uint8_t reg, uint8_t data) +{ + uint8_t buf[2] = {reg, data}; + esp_err_t ret = i2c_master_transmit(dev, buf, 2, -1); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_MASTER_WRITE_BYTES +static inline esp_err_t M5IOE1_I2C_MASTER_WRITE_BYTES(i2c_master_dev_handle_t dev, uint8_t start_reg, size_t len, + const uint8_t *data) +{ + // 需要在数据前添加寄存器地址 + // Need to prepend register address + uint8_t *buf = (uint8_t *)malloc(len + 1); + if (buf == NULL) return ESP_ERR_NO_MEM; + buf[0] = start_reg; + memcpy(buf + 1, data, len); + esp_err_t ret = i2c_master_transmit(dev, buf, len + 1, -1); + free(buf); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ret; +} +#endif + +#ifndef M5IOE1_I2C_MASTER_WRITE_REG16 +static inline esp_err_t M5IOE1_I2C_MASTER_WRITE_REG16(i2c_master_dev_handle_t dev, uint8_t reg, uint16_t data) +{ + uint8_t buf[3]; + buf[0] = reg; + // 小端模式:低字节在前 + // Little-endian: low byte first + buf[1] = (uint8_t)(data & 0xFF); + buf[2] = (uint8_t)((data >> 8) & 0xFF); + esp_err_t ret = i2c_master_transmit(dev, buf, 3, -1); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ret; +} +#endif + +// 唤醒信号发送 (i2c_master) / Wake signal send (i2c_master) +#ifndef M5IOE1_I2C_MASTER_SEND_WAKE +static inline esp_err_t M5IOE1_I2C_MASTER_SEND_WAKE(i2c_master_bus_handle_t bus, uint8_t addr) +{ + // Use i2c_master_probe to generate START signal for wake + return i2c_master_probe(bus, addr, 10); +} +#endif + +#endif // ESP_IDF_VERSION >= 5.3.0 + +#ifdef __cplusplus +} +#endif + +// ============================ +// M5Unified I2C_Class 通信封装 +// M5Unified I2C_Class Communication Wrappers +// (C++ only — m5::I2C_Class is a C++ class) +// ============================ +#if M5IOE1_HAS_M5UNIFIED_I2C +#ifdef __cplusplus + +#ifndef M5IOE1_M5UNIFIED_READ_BYTE +static inline bool M5IOE1_M5UNIFIED_READ_BYTE(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint8_t *data, + uint32_t freq) +{ + bool ok = i2c->readRegister(addr, reg, data, 1, freq); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + return ok; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_READ_BYTES +static inline bool M5IOE1_M5UNIFIED_READ_BYTES(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, size_t len, uint8_t *data, + uint32_t freq) +{ + bool ok = i2c->readRegister(addr, reg, data, len, freq); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + return ok; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_READ_REG16 +static inline bool M5IOE1_M5UNIFIED_READ_REG16(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint16_t *data, + uint32_t freq) +{ + uint8_t buf[2]; + if (!i2c->readRegister(addr, reg, buf, 2, freq)) return false; + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_READ_DELAY_US); + // 小端模式:低字节在前 / Little-endian: low byte first + *data = (uint16_t)buf[0] | ((uint16_t)buf[1] << 8); + return true; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_WRITE_BYTE +static inline bool M5IOE1_M5UNIFIED_WRITE_BYTE(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint8_t data, + uint32_t freq) +{ + bool ok = i2c->writeRegister8(addr, reg, data, freq); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ok; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_WRITE_BYTES +static inline bool M5IOE1_M5UNIFIED_WRITE_BYTES(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, size_t len, + const uint8_t *data, uint32_t freq) +{ + bool ok = i2c->writeRegister(addr, reg, data, len, freq); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ok; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_WRITE_REG16 +static inline bool M5IOE1_M5UNIFIED_WRITE_REG16(m5::I2C_Class *i2c, uint8_t addr, uint8_t reg, uint16_t data, + uint32_t freq) +{ + uint8_t buf[2]; + // 小端模式:低字节在前 / Little-endian: low byte first + buf[0] = (uint8_t)(data & 0xFF); + buf[1] = (uint8_t)((data >> 8) & 0xFF); + bool ok = i2c->writeRegister(addr, reg, buf, 2, freq); + M5IOE1_I2C_DELAY_US(M5IOE1_I2C_WRITE_DELAY_US); + return ok; +} +#endif + +#ifndef M5IOE1_M5UNIFIED_SEND_WAKE +// 产生 I2C START 信号以唤醒处于睡眠的 IOE1 +// Generate I2C START signal to wake IOE1 from sleep +static inline bool M5IOE1_M5UNIFIED_SEND_WAKE(m5::I2C_Class *i2c, uint8_t addr, uint32_t freq) +{ + i2c->start(addr, false, freq); + i2c->stop(); + return true; +} +#endif + +#endif // __cplusplus +#endif // M5IOE1_HAS_M5UNIFIED_I2C + +#endif // ARDUINO + +#endif // __M5IOE1_I2C_COMPAT_H__ diff --git a/components/mybot_platform/boards/README.md b/components/mybot_platform/boards/README.md index 504bd99..a0e1d54 100644 --- a/components/mybot_platform/boards/README.md +++ b/components/mybot_platform/boards/README.md @@ -5,7 +5,7 @@ drivers and build constraints are defined by the checked-in `board.cmake`, `boar `sdkconfig.defaults` files. Current validation status is centralized in [`docs/VALIDATION.md`](../../../docs/VALIDATION.md). -以下说明对应本工程已实现的 16 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; +以下说明对应本工程已实现的 38 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; 验证状态统一见[验证矩阵](../../../docs/VALIDATION.md)。不同尺寸、芯片或硬件版本的板卡 不能仅凭外观共用固件。 @@ -28,6 +28,40 @@ drivers and build constraints are defined by the checked-in `board.cmake`, `boar | `zhengchen-1.54tft-ml307` | [README](zhengchen-1.54tft-ml307/README.md) | ST7789 | 无 | | `zhengchen-1.54tft-wifi` | [README](zhengchen-1.54tft-wifi/README.md) | ST7789 | 无 | +### Additional ESP32-S3 profiles / 新增板型 + +These profiles reuse the shared board lifecycle in `boards/common/`. Their `board_config.h` and +`board.cmake` files are the authoritative pin and capability records. None has completed +real-device validation. + +这些板型复用 `boards/common/` 的共享生命周期;引脚与能力以各自的 `board_config.h` 和 +`board.cmake` 为准。以下板型均未完成真机验证。 + +| Board profile | Display / 显示 | Audio / 音频 | Input / 输入 | Reduced scope / 当前子集 | +| --- | --- | --- | --- | --- | +| [xingzhi-metal-1.54-wifi](xingzhi-metal-1.54-wifi/README.md) | 240×240 ST7789 | ES8311 | PCA9557 touch + Boot | Touch, PCA9557 power/reset and PA require device validation. | +| [xingzhi-cube-0.85tft-wifi](xingzhi-cube-0.85tft-wifi/README.md) | 128×128 NV3023 | Raw I2S | Boot | No touch or codec control. | +| [xingzhi-cube-0.96oled-wifi](xingzhi-cube-0.96oled-wifi/README.md) | Headless | Raw I2S | Boot + two volume keys | SSD1306 monochrome UI is not implemented. | +| [xingzhi-abs-2.0](xingzhi-abs-2.0/README.md) | 240×240 ST7789 | ES8311 | Boot + two volume keys | Wi-Fi only; SD, vibration and cellular are not implemented. | +| [m5stack-stopwatch](m5stack-stopwatch/README.md) | 466×466 round CO5300 | ES8311 | CST820B-compatible CST816S path + two buttons | M5PM1/M5IOE1 power sequencing and round safe area are implemented; battery reporting, motor, sleep and shutdown are not. | +| [esp32-s3-touch-amoled-1.8](esp32-s3-touch-amoled-1.8/README.md) | 368×448 SH8601 QSPI | ES8311 | FT5x06 + Boot | Device timing and touch mapping require validation. | +| [esp32-s3-touch-amoled-1.32](esp32-s3-touch-amoled-1.32/README.md) | 466×466 SH8601 QSPI | ES8311 | Boot | Touchless profile. | +| [esp32-s3-touch-amoled-1.43c](esp32-s3-touch-amoled-1.43c/README.md) | 466×466 SH8601 QSPI | ES8311 + ES7210 | Boot | Touchless profile. | +| [esp32-s3-touch-amoled-2.06](esp32-s3-touch-amoled-2.06/README.md) | 410×502 SH8601 QSPI | ES8311 + ES7210 | FT5x06 + Boot | 22-pixel X gap; device timing requires validation. | +| [atoms3r-echo-base](atoms3r-echo-base/README.md) | 128×128 GC9A01-class SPI panel | ES8311 | Boot | Echo-base IO/power and LP5562 backlight integration are not implemented. | +| [atoms3r-echo-pyramid](atoms3r-echo-pyramid/README.md) | 128×128 GC9A01-class SPI panel | ES8311 + ES7210 | Boot | Echo-pyramid IO/power and LP5562 backlight integration are not implemented. | +| [sp-esp32-s3-1.28-box](sp-esp32-s3-1.28-box/README.md) | 240×240 round GC9A01 | ES8311 | CST816S-compatible touch + Boot | Battery/charging reporting and panel vendor tuning require validation. | +| [esp-box](esp-box/README.md) | 320×240 ILI9341 | ES8311 + ES7210 | Boot | Touch, board PMIC options and power sequencing require validation. | +| [esp-box-3](esp-box-3/README.md) | 320×240 ILI9341 | ES8311 + ES7210 | Boot | Touch, board PMIC options and power sequencing require validation. | +| [esp-box-lite](esp-box-lite/README.md) | 320×240 ILI9341 | ES8311 + ES7210 profile | Boot | ADC buttons, codec assignment and power sequencing require validation. | +| [esp32s3-korvo2-v3](esp32s3-korvo2-v3/README.md) | 320×240 ILI9341 | ES8311 + ES7210 | Boot | TCA9554 reset/CS is implemented; ADC keys, camera, battery and sleep are not. | +| [lichuang-dev](lichuang-dev/README.md) | 320×240 ST7789 | ES8311 + ES7210 | FT5x06 + Boot | PCA9557 display/PA control is implemented; camera and battery are not. | +| [quandong-s3-dev](quandong-s3-dev/README.md) | 320×240 ILI9341 | ES8311 | Boot | PCA9557 setup and active-low PA are implemented; hardware mapping requires validation. | +| [rymcu-bigsmart](rymcu-bigsmart/README.md) | 320×240 ST7789 | ES8311 + ES7210 | GT911 + Boot | PCA9557 display/PA control is implemented; camera and battery are not. | +| [df-k10](df-k10/README.md) | 240×320 ILI9341 | ES8311 + ES7210 | Boot | TCA9555 keys, camera, LED ring and power paths are not implemented. | +| [mixgo-nova](mixgo-nova/README.md) | 128×160 ST7789 | ES8374 | Boot | RGB LED and hardware validation are pending. | +| [taiji-pi-s3](taiji-pi-s3/README.md) | 360×360 round ST77916 QSPI | ES8311 playback + PDM capture | CST816S + Boot | PDM capture and mute/PA lifecycle are implemented; board power sequencing requires validation. | + ## Build, flash and validation / 构建、烧录与验证 Activate ESP-IDF v5.5.2 from the repository root: diff --git a/components/mybot_platform/boards/atoms3r-echo-base/README.md b/components/mybot_platform/boards/atoms3r-echo-base/README.md new file mode 100644 index 0000000..7bb9460 --- /dev/null +++ b/components/mybot_platform/boards/atoms3r-echo-base/README.md @@ -0,0 +1,14 @@ +# AtomS3R Echo Base + +板型 ID 为 `atoms3r-echo-base`。 + +- 显示:128×128 GC9A01 类 SPI 面板,Y 偏移 32,共享 LVGL 紧凑布局。 +- 音频:ES8311,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO41 Boot;无触摸和音量键。 +- 存储:8 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +当前只接入显示和 ES8311 主路径。Echo Base 的外围 IO/电源管理、LP5562 背光和底座连接检测 +未实现;显示、音频、按键极性和电源仍需真机验证。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/atoms3r-echo-base/board.c b/components/mybot_platform/boards/atoms3r-echo-base/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/atoms3r-echo-base/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/atoms3r-echo-base/board.cmake b/components/mybot_platform/boards/atoms3r-echo-base/board.cmake new file mode 100644 index 0000000..70d4a4d --- /dev/null +++ b/components/mybot_platform/boards/atoms3r-echo-base/board.cmake @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/8m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_8MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_gc9a01) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/atoms3r-echo-base/board_config.h b/components/mybot_platform/boards/atoms3r-echo-base/board_config.h new file mode 100644 index 0000000..1bbbfe1 --- /dev/null +++ b/components/mybot_platform/boards/atoms3r-echo-base/board_config.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "atoms3r-echo-base" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_NC +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_8 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_7 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_5 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_38 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_39 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_41 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 1 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_21 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_15 +#define MYBOT_DISPLAY_DC GPIO_NUM_42 +#define MYBOT_DISPLAY_CS GPIO_NUM_14 +#define MYBOT_DISPLAY_RESET GPIO_NUM_48 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 1 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 128 +#define MYBOT_DISPLAY_HEIGHT 128 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 32 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/atoms3r-echo-base/sdkconfig.defaults b/components/mybot_platform/boards/atoms3r-echo-base/sdkconfig.defaults new file mode 100644 index 0000000..63ebd39 --- /dev/null +++ b/components/mybot_platform/boards/atoms3r-echo-base/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/8m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/atoms3r-echo-pyramid/README.md b/components/mybot_platform/boards/atoms3r-echo-pyramid/README.md new file mode 100644 index 0000000..68ef69d --- /dev/null +++ b/components/mybot_platform/boards/atoms3r-echo-pyramid/README.md @@ -0,0 +1,14 @@ +# AtomS3R Echo Pyramid + +板型 ID 为 `atoms3r-echo-pyramid`。 + +- 显示:128×128 GC9A01 类 SPI 面板,共享 LVGL 紧凑布局。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO41 Boot;无触摸和音量键。 +- 存储:8 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +当前只接入显示和双 codec 主路径。Echo Pyramid 的外围 IO/电源管理、LP5562 背光和底座连接 +检测未实现;显示、双 codec 路由、按键极性和电源仍需真机验证。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/atoms3r-echo-pyramid/board.c b/components/mybot_platform/boards/atoms3r-echo-pyramid/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/atoms3r-echo-pyramid/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/atoms3r-echo-pyramid/board.cmake b/components/mybot_platform/boards/atoms3r-echo-pyramid/board.cmake new file mode 100644 index 0000000..5a5af0c --- /dev/null +++ b/components/mybot_platform/boards/atoms3r-echo-pyramid/board.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/8m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_8MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_gc9a01) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/atoms3r-echo-pyramid/board_config.h b/components/mybot_platform/boards/atoms3r-echo-pyramid/board_config.h new file mode 100644 index 0000000..d110d0d --- /dev/null +++ b/components/mybot_platform/boards/atoms3r-echo-pyramid/board_config.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "atoms3r-echo-pyramid" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_NC +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_8 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_7 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_38 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_39 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_41 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 1 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_21 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_15 +#define MYBOT_DISPLAY_DC GPIO_NUM_42 +#define MYBOT_DISPLAY_CS GPIO_NUM_14 +#define MYBOT_DISPLAY_RESET GPIO_NUM_48 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 1 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 128 +#define MYBOT_DISPLAY_HEIGHT 128 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/atoms3r-echo-pyramid/sdkconfig.defaults b/components/mybot_platform/boards/atoms3r-echo-pyramid/sdkconfig.defaults new file mode 100644 index 0000000..ece839c --- /dev/null +++ b/components/mybot_platform/boards/atoms3r-echo-pyramid/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/8m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/boards.cmake b/components/mybot_platform/boards/boards.cmake index d372f39..33be7dc 100644 --- a/components/mybot_platform/boards/boards.cmake +++ b/components/mybot_platform/boards/boards.cmake @@ -6,13 +6,35 @@ set(MYBOT_SUPPORTED_BOARDS "esp32-s3-touch-amoled-1.75c" "esp32-s3-touch-amoled-2.16" "esp32-s3-touch-amoled-1.8-v2" + "esp32-s3-touch-amoled-1.8" + "esp32-s3-touch-amoled-1.32" + "esp32-s3-touch-amoled-1.43c" + "esp32-s3-touch-amoled-2.06" "esp32-s3-touch-lcd-1.85" + "esp-box" + "esp-box-3" + "esp-box-lite" + "esp32s3-korvo2-v3" "genjutech-s3-1.54tft" + "atoms3r-echo-base" + "atoms3r-echo-pyramid" + "df-k10" + "lichuang-dev" "m5stack-core-s3" "m5stack-stick-s3" + "m5stack-stopwatch" + "mixgo-nova" + "quandong-s3-dev" "respeaker-flex-xvf3800-circular4-xiao" + "rymcu-bigsmart" "sensecap-watcher" + "sp-esp32-s3-1.28-box" + "taiji-pi-s3" + "xingzhi-abs-2.0" + "xingzhi-cube-0.85tft-wifi" + "xingzhi-cube-0.96oled-wifi" "xingzhi-cube-1.54tft-wifi" + "xingzhi-metal-1.54-wifi" "zhengchen-1.54tft-ml307" "zhengchen-1.54tft-wifi" ) @@ -33,21 +55,65 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-amoled-2.16/board.cmake") elseif(board STREQUAL "esp32-s3-touch-amoled-1.8-v2") set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-amoled-1.8-v2/board.cmake") + elseif(board STREQUAL "esp32-s3-touch-amoled-1.8") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-amoled-1.8/board.cmake") + elseif(board STREQUAL "esp32-s3-touch-amoled-1.32") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-amoled-1.32/board.cmake") + elseif(board STREQUAL "esp32-s3-touch-amoled-1.43c") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-amoled-1.43c/board.cmake") + elseif(board STREQUAL "esp32-s3-touch-amoled-2.06") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-amoled-2.06/board.cmake") elseif(board STREQUAL "esp32-s3-touch-lcd-1.85") set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-lcd-1.85/board.cmake") + elseif(board STREQUAL "esp-box") + set(profile "${_MYBOT_BOARDS_ROOT}/esp-box/board.cmake") + elseif(board STREQUAL "esp-box-3") + set(profile "${_MYBOT_BOARDS_ROOT}/esp-box-3/board.cmake") + elseif(board STREQUAL "esp-box-lite") + set(profile "${_MYBOT_BOARDS_ROOT}/esp-box-lite/board.cmake") + elseif(board STREQUAL "esp32s3-korvo2-v3") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32s3-korvo2-v3/board.cmake") elseif(board STREQUAL "genjutech-s3-1.54tft") set(profile "${_MYBOT_BOARDS_ROOT}/genjutech-s3-1.54tft/board.cmake") + elseif(board STREQUAL "atoms3r-echo-base") + set(profile "${_MYBOT_BOARDS_ROOT}/atoms3r-echo-base/board.cmake") + elseif(board STREQUAL "atoms3r-echo-pyramid") + set(profile "${_MYBOT_BOARDS_ROOT}/atoms3r-echo-pyramid/board.cmake") + elseif(board STREQUAL "df-k10") + set(profile "${_MYBOT_BOARDS_ROOT}/df-k10/board.cmake") + elseif(board STREQUAL "lichuang-dev") + set(profile "${_MYBOT_BOARDS_ROOT}/lichuang-dev/board.cmake") elseif(board STREQUAL "m5stack-core-s3") set(profile "${_MYBOT_BOARDS_ROOT}/m5stack-core-s3/board.cmake") elseif(board STREQUAL "m5stack-stick-s3") set(profile "${_MYBOT_BOARDS_ROOT}/m5stack-stick-s3/board.cmake") + elseif(board STREQUAL "m5stack-stopwatch") + set(profile "${_MYBOT_BOARDS_ROOT}/m5stack-stopwatch/board.cmake") + elseif(board STREQUAL "mixgo-nova") + set(profile "${_MYBOT_BOARDS_ROOT}/mixgo-nova/board.cmake") + elseif(board STREQUAL "quandong-s3-dev") + set(profile "${_MYBOT_BOARDS_ROOT}/quandong-s3-dev/board.cmake") elseif(board STREQUAL "respeaker-flex-xvf3800-circular4-xiao") set(profile "${_MYBOT_BOARDS_ROOT}/respeaker-flex-xvf3800-circular4-xiao/board.cmake") + elseif(board STREQUAL "rymcu-bigsmart") + set(profile "${_MYBOT_BOARDS_ROOT}/rymcu-bigsmart/board.cmake") elseif(board STREQUAL "sensecap-watcher") set(profile "${_MYBOT_BOARDS_ROOT}/sensecap-watcher/board.cmake") + elseif(board STREQUAL "sp-esp32-s3-1.28-box") + set(profile "${_MYBOT_BOARDS_ROOT}/sp-esp32-s3-1.28-box/board.cmake") + elseif(board STREQUAL "taiji-pi-s3") + set(profile "${_MYBOT_BOARDS_ROOT}/taiji-pi-s3/board.cmake") + elseif(board STREQUAL "xingzhi-abs-2.0") + set(profile "${_MYBOT_BOARDS_ROOT}/xingzhi-abs-2.0/board.cmake") + elseif(board STREQUAL "xingzhi-cube-0.85tft-wifi") + set(profile "${_MYBOT_BOARDS_ROOT}/xingzhi-cube-0.85tft-wifi/board.cmake") + elseif(board STREQUAL "xingzhi-cube-0.96oled-wifi") + set(profile "${_MYBOT_BOARDS_ROOT}/xingzhi-cube-0.96oled-wifi/board.cmake") elseif(board STREQUAL "xingzhi-cube-1.54tft-wifi") set(profile "${_MYBOT_BOARDS_ROOT}/xingzhi-cube-1.54tft-wifi/board.cmake") + elseif(board STREQUAL "xingzhi-metal-1.54-wifi") + set(profile "${_MYBOT_BOARDS_ROOT}/xingzhi-metal-1.54-wifi/board.cmake") elseif(board STREQUAL "zhengchen-1.54tft-ml307") set(profile "${_MYBOT_BOARDS_ROOT}/zhengchen-1.54tft-ml307/board.cmake") elseif(board STREQUAL "zhengchen-1.54tft-wifi") diff --git a/components/mybot_platform/boards/common/generic_board.c b/components/mybot_platform/boards/common/generic_board.c new file mode 100644 index 0000000..e742463 --- /dev/null +++ b/components/mybot_platform/boards/common/generic_board.c @@ -0,0 +1,200 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" +#include "generic_hardware.h" + +#include + +#include "announcement/embedded_ogg_prompt.h" +#include "board_config.h" +#include "esp_log.h" +#include "network/wifi_control.h" +#include "platform/board_actions.h" + +#include + +#define TAG "generic_board" + +#if MYBOT_GENERIC_AUDIO_KIND == 1 || MYBOT_GENERIC_AUDIO_KIND == 4 +const mybot_audio_capture_ops_t *mybot_es8311_audio_capture_ops(void); +const mybot_audio_playback_ops_t *mybot_es8311_audio_playback_ops(void); +const mybot_audio_volume_ops_t *mybot_es8311_audio_volume_ops(void); +#define CAPTURE_OPS mybot_es8311_audio_capture_ops +#define PLAYBACK_OPS mybot_es8311_audio_playback_ops +#define VOLUME_OPS mybot_es8311_audio_volume_ops +#elif MYBOT_GENERIC_AUDIO_KIND == 2 +const mybot_audio_capture_ops_t *mybot_esp32s3_audio_capture_ops(void); +const mybot_audio_playback_ops_t *mybot_esp32s3_audio_playback_ops(void); +const mybot_audio_volume_ops_t *mybot_esp32s3_audio_volume_ops(void); +#define CAPTURE_OPS mybot_esp32s3_audio_capture_ops +#define PLAYBACK_OPS mybot_esp32s3_audio_playback_ops +#define VOLUME_OPS mybot_esp32s3_audio_volume_ops +#elif MYBOT_GENERIC_AUDIO_KIND == 3 +const mybot_audio_capture_ops_t *mybot_amoled175_audio_capture_ops(void); +const mybot_audio_playback_ops_t *mybot_amoled175_audio_playback_ops(void); +const mybot_audio_volume_ops_t *mybot_amoled175_audio_volume_ops(void); +#define CAPTURE_OPS mybot_amoled175_audio_capture_ops +#define PLAYBACK_OPS mybot_amoled175_audio_playback_ops +#define VOLUME_OPS mybot_amoled175_audio_volume_ops +#else +#error "MYBOT_GENERIC_AUDIO_KIND must select an audio path" +#endif + +const mybot_announce_ops_t *mybot_esp32s3_announce_ops(void); +const mybot_https_ops_t *mybot_esp32s3_https_ops(void); +const mybot_kv_store_ops_t *mybot_esp32s3_kv_store_ops(void); +const mybot_wifi_ops_t *mybot_esp32s3_wifi_ops(void); + +#if MYBOT_GENERIC_TOUCH_KIND +const mybot_key_ops_t *mybot_cst816s_input_ops(void); +int mybot_cst816s_input_start(void); +int mybot_cst816s_input_stop(void); +#define KEY_OPS mybot_cst816s_input_ops +#else +const mybot_key_ops_t *mybot_esp32s3_button_ops(void); +int mybot_esp32s3_buttons_start(void); +#define KEY_OPS mybot_esp32s3_button_ops +#endif + +#if MYBOT_BOARD_HAS_DISPLAY +const mybot_lcd_ops_t *mybot_shared_lvgl_ops(void); +static const mybot_lcd_ops_t *s_lcd_ops; +static void *s_lcd_context; +#endif +static bool s_automatic_provisioning_started; +static bool s_prepared; +static bool s_cleanup_pending; + +static int board_show_screen(mybot_lcd_screen_t screen) { +#if MYBOT_BOARD_HAS_DISPLAY + if (s_lcd_ops && s_lcd_context) { + const mybot_lcd_content_t content = {.screen = screen}; + return s_lcd_ops->render(s_lcd_context, &content); + } +#else + (void)screen; +#endif + return 0; +} + +static void board_on_provisioning(const char *trigger) { + (void)board_show_screen(MYBOT_LCD_SCREEN_WIFI_PROVISIONING); + (void)mybot_embedded_ogg_play_wifi_provisioning(PLAYBACK_OPS(), VOLUME_OPS(), trigger); +} + +static void board_on_automatic_provisioning(void) { + s_automatic_provisioning_started = true; + board_on_provisioning("automatic"); +} + +static void board_on_input_provisioning(void) { + board_on_provisioning("input"); +} + +static int board_rollback_prepare(void) { + s_cleanup_pending = true; +#if MYBOT_GENERIC_TOUCH_KIND + if (mybot_cst816s_input_stop() < 0) { + return -1; + } +#endif +#if MYBOT_BOARD_HAS_DISPLAY + if (s_lcd_ops && s_lcd_context) { + s_lcd_ops->destroy(s_lcd_context); + s_lcd_context = NULL; + } + s_lcd_ops = NULL; +#endif + if (mybot_generic_hardware_deinit() < 0) { + return -1; + } + s_cleanup_pending = false; + return 0; +} + +static int board_prepare(void) { + if (s_prepared) { + return 0; + } + if (s_cleanup_pending && board_rollback_prepare() < 0) { + return -1; + } + if (mybot_generic_hardware_init() < 0) { + return -1; + } +#if MYBOT_BOARD_HAS_DISPLAY + s_lcd_ops = mybot_shared_lvgl_ops(); + if (!s_lcd_ops || s_lcd_ops->init(&s_lcd_context) < 0 || + board_show_screen(MYBOT_LCD_SCREEN_STARTING) < 0) { + (void)board_rollback_prepare(); + return -1; + } +#endif +#if MYBOT_GENERIC_TOUCH_KIND + if (mybot_cst816s_input_start() < 0) { + (void)board_rollback_prepare(); + return -1; + } +#else + if (mybot_esp32s3_buttons_start() < 0) { + (void)board_rollback_prepare(); + return -1; + } +#endif + s_prepared = true; + return 0; +} + +int mybot_board_handle_boot_long_press(void) { + mybot_board_request_wifi_provisioning(); + return 0; +} + +static int board_provision_wifi(void) { + return mybot_wifi_run_provisioning(board_on_input_provisioning); +} + +static int board_ensure_network(const char *device_id) { + s_automatic_provisioning_started = false; + const int result = mybot_wifi_ensure_network(device_id, board_on_automatic_provisioning); + if (s_automatic_provisioning_started) { + (void)mybot_board_wait_wifi_provisioning_request(0); + } + return result; +} + +static void board_shutdown_network(void) { + mybot_wifi_shutdown_network(); +} + +static int board_register_platform(void) { + const mybot_platform_descriptor_t descriptor = { + .wifi = mybot_esp32s3_wifi_ops(), + .kv_store = mybot_esp32s3_kv_store_ops(), + .key = KEY_OPS(), + .audio_capture = CAPTURE_OPS(), + .audio_playback = PLAYBACK_OPS(), + .audio_volume = VOLUME_OPS(), + .announce = mybot_esp32s3_announce_ops(), + .https = mybot_esp32s3_https_ops(), +#if MYBOT_BOARD_HAS_DISPLAY + .lcd = mybot_shared_lvgl_ops(), +#else + .lcd = NULL, +#endif + }; + return mybot_platform_register(&descriptor); +} + +static const mybot_board_t s_board = { + .id = MYBOT_BOARD_NAME, + .hw_model = MYBOT_BOARD_NAME, + .prepare = board_prepare, + .register_platform = board_register_platform, + .ensure_network = board_ensure_network, + .provision_wifi = board_provision_wifi, + .shutdown_network = board_shutdown_network, +}; + +const mybot_board_t *mybot_generic_board_get(void) { + return &s_board; +} diff --git a/components/mybot_platform/boards/common/generic_board.h b/components/mybot_platform/boards/common/generic_board.h new file mode 100644 index 0000000..8266de0 --- /dev/null +++ b/components/mybot_platform/boards/common/generic_board.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_GENERIC_BOARD_H_ +#define MYBOT_GENERIC_BOARD_H_ + +#include "mybot_platform/board.h" + +#ifdef __cplusplus +extern "C" { +#endif + +const mybot_board_t *mybot_generic_board_get(void); + +#ifdef __cplusplus +} +#endif + +#endif /* MYBOT_GENERIC_BOARD_H_ */ diff --git a/components/mybot_platform/boards/common/generic_hardware.c b/components/mybot_platform/boards/common/generic_hardware.c new file mode 100644 index 0000000..fafae3c --- /dev/null +++ b/components/mybot_platform/boards/common/generic_hardware.c @@ -0,0 +1,454 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_hardware.h" + +#include "board_config.h" +#include "io_expander/pca9557.h" +#include "io_expander/tca9554.h" + +#include "driver/gpio.h" +#include "driver/ledc.h" +#include "esp_err.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define TAG "generic_board" +#define BACKLIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE +#define BACKLIGHT_LEDC_TIMER LEDC_TIMER_0 +#define BACKLIGHT_LEDC_CHANNEL LEDC_CHANNEL_0 +#define BACKLIGHT_LEDC_RESOLUTION LEDC_TIMER_13_BIT +#define BACKLIGHT_LEDC_FREQUENCY_HZ 5000 +#define BACKLIGHT_MAX_DUTY ((1U << 13) - 1U) + +#if MYBOT_GENERIC_M5STACK_STOPWATCH +#include "m5stack_stopwatch_hardware.h" +#endif + +#ifndef MYBOT_GENERIC_AUDIO_KIND +#define MYBOT_GENERIC_AUDIO_KIND 0 +#endif + +#ifndef MYBOT_GENERIC_TOUCH_KIND +#define MYBOT_GENERIC_TOUCH_KIND 0 +#endif + +#ifndef MYBOT_GENERIC_PA_ACTIVE_LEVEL +#define MYBOT_GENERIC_PA_ACTIVE_LEVEL 1 +#endif + +#ifndef MYBOT_GENERIC_PA_GPIO +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC +#endif + +#ifndef MYBOT_GENERIC_POWER_ACTIVE_LEVEL +#define MYBOT_GENERIC_POWER_ACTIVE_LEVEL 1 +#endif + +typedef struct { + i2c_master_bus_handle_t audio_bus; + i2c_master_bus_handle_t touch_bus; + mybot_pca9557_t expander; + mybot_tca9554_t tca9554; + i2c_master_dev_handle_t axp2101; + bool expander_ready; + bool pa_ready; + bool pa_enabled; + bool power_ready; + bool backlight_ready; + bool stopwatch_hardware_ready; + bool initialized; +} hardware_state_t; + +static hardware_state_t s_hardware; + +#ifdef MYBOT_GENERIC_AXP2101_ADDRESS +static esp_err_t initialize_axp2101(void) { + static const uint8_t registers[][2] = { + {0x22, 0x06}, {0x27, 0x10}, {0x80, 0x01}, {0x90, 0x00}, {0x91, 0x00}, + {0x82, 0x12}, {0x92, 0x1c}, {0x93, 0x1c}, {0x90, 0x03}, {0x64, 0x02}, + {0x61, 0x02}, {0x62, 0x0a}, {0x63, 0x01}, + }; + for (size_t i = 0; i < sizeof(registers) / sizeof(registers[0]); ++i) { + const uint8_t command[] = {registers[i][0], registers[i][1]}; + const esp_err_t result = + i2c_master_transmit(s_hardware.axp2101, command, sizeof(command), 100); + if (result != ESP_OK) { + return result; + } + } + return ESP_OK; +} +#endif + +static bool codec_needs_i2c(void) { + return MYBOT_GENERIC_AUDIO_KIND == 1 || MYBOT_GENERIC_AUDIO_KIND == 3 || + MYBOT_GENERIC_AUDIO_KIND == 4; +} + +static esp_err_t create_i2c_bus(int port, gpio_num_t sda, gpio_num_t scl, + i2c_master_bus_handle_t *out_bus) { + const i2c_master_bus_config_t config = { + .i2c_port = port, + .sda_io_num = sda, + .scl_io_num = scl, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .flags.enable_internal_pullup = true, + }; + return i2c_new_master_bus(&config, out_bus); +} + +static esp_err_t configure_output_gpio(gpio_num_t gpio, int initial_level) { + if (gpio == GPIO_NUM_NC) { + return ESP_OK; + } + const gpio_config_t config = { + .pin_bit_mask = 1ULL << gpio, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + const esp_err_t result = gpio_config(&config); + return result == ESP_OK ? gpio_set_level(gpio, initial_level) : result; +} + +static esp_err_t initialize_backlight(void) { +#ifdef MYBOT_DISPLAY_BACKLIGHT_GPIO + if (MYBOT_DISPLAY_BACKLIGHT_GPIO == GPIO_NUM_NC) { + return ESP_OK; + } + const ledc_timer_config_t timer_config = { + .speed_mode = BACKLIGHT_LEDC_MODE, + .duty_resolution = BACKLIGHT_LEDC_RESOLUTION, + .timer_num = BACKLIGHT_LEDC_TIMER, + .freq_hz = BACKLIGHT_LEDC_FREQUENCY_HZ, + .clk_cfg = LEDC_AUTO_CLK, + }; + esp_err_t result = ledc_timer_config(&timer_config); + if (result != ESP_OK) { + return result; + } + const ledc_channel_config_t channel_config = { + .gpio_num = MYBOT_DISPLAY_BACKLIGHT_GPIO, + .speed_mode = BACKLIGHT_LEDC_MODE, + .channel = BACKLIGHT_LEDC_CHANNEL, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = BACKLIGHT_LEDC_TIMER, + .duty = MYBOT_DISPLAY_BACKLIGHT_INVERT ? BACKLIGHT_MAX_DUTY : 0, + .hpoint = 0, + .sleep_mode = LEDC_SLEEP_MODE_NO_ALIVE_NO_PD, + }; + result = ledc_channel_config(&channel_config); + if (result == ESP_OK) { + s_hardware.backlight_ready = true; + } + return result; +#else + return ESP_OK; +#endif +} + +static int release_hardware(void) { + int result = 0; + if (s_hardware.backlight_ready && + ledc_stop(BACKLIGHT_LEDC_MODE, BACKLIGHT_LEDC_CHANNEL, 0) != ESP_OK) { + result = -1; + } + s_hardware.backlight_ready = false; + if (s_hardware.pa_ready) { + (void)mybot_generic_set_speaker_power(false); + } +#ifdef MYBOT_GENERIC_POWER_GPIO + if (s_hardware.power_ready && + gpio_set_level(MYBOT_GENERIC_POWER_GPIO, !MYBOT_GENERIC_POWER_ACTIVE_LEVEL) != ESP_OK) { + result = -1; + } +#endif + s_hardware.power_ready = false; +#if MYBOT_GENERIC_M5STACK_STOPWATCH + if (s_hardware.stopwatch_hardware_ready && mybot_m5stack_stopwatch_hardware_deinit() < 0) { + result = -1; + } + s_hardware.stopwatch_hardware_ready = false; +#endif +#ifdef MYBOT_GENERIC_AXP2101_ADDRESS + if (s_hardware.axp2101) { + if (i2c_master_bus_rm_device(s_hardware.axp2101) != ESP_OK) { + result = -1; + } else { + s_hardware.axp2101 = NULL; + } + } +#endif +#ifdef MYBOT_GENERIC_TCA9554_ADDRESS + if (s_hardware.tca9554.device && mybot_tca9554_deinit(&s_hardware.tca9554) < 0) { + result = -1; + } +#endif +#ifdef MYBOT_GENERIC_PCA9557_ADDRESS + if (s_hardware.expander_ready && mybot_pca9557_deinit(&s_hardware.expander) < 0) { + result = -1; + } +#endif + s_hardware.expander_ready = false; + if (s_hardware.touch_bus && i2c_del_master_bus(s_hardware.touch_bus) != ESP_OK) { + result = -1; + } else { + s_hardware.touch_bus = NULL; + } + if (s_hardware.audio_bus && i2c_del_master_bus(s_hardware.audio_bus) != ESP_OK) { + result = -1; + } else { + s_hardware.audio_bus = NULL; + } + s_hardware.initialized = false; + s_hardware.pa_ready = false; + s_hardware.pa_enabled = false; + return result; +} + +int mybot_generic_hardware_init(void) { + if (s_hardware.initialized) { + return 0; + } + if ((s_hardware.audio_bus || s_hardware.touch_bus || s_hardware.expander_ready || + s_hardware.tca9554.device || s_hardware.axp2101 || s_hardware.pa_ready || + s_hardware.power_ready || s_hardware.backlight_ready) && + release_hardware() < 0) { + return -1; + } + +#ifdef MYBOT_GENERIC_POWER_GPIO + esp_err_t result = + configure_output_gpio(MYBOT_GENERIC_POWER_GPIO, MYBOT_GENERIC_POWER_ACTIVE_LEVEL); + if (result != ESP_OK) { + return -1; + } + s_hardware.power_ready = MYBOT_GENERIC_POWER_GPIO != GPIO_NUM_NC; +#else + esp_err_t result = ESP_OK; +#endif + + const bool touch_separate_bus = +#ifdef MYBOT_GENERIC_TOUCH_I2C_PORT + MYBOT_GENERIC_TOUCH_I2C_PORT != MYBOT_GENERIC_I2C_PORT; +#else + false; +#endif + if ((codec_needs_i2c() || (MYBOT_GENERIC_TOUCH_KIND != 0 && !touch_separate_bus)) && + !s_hardware.audio_bus) { + result = create_i2c_bus(MYBOT_GENERIC_I2C_PORT, MYBOT_GENERIC_I2C_SDA, + MYBOT_GENERIC_I2C_SCL, &s_hardware.audio_bus); + } +#if MYBOT_GENERIC_M5STACK_STOPWATCH + if (result == ESP_OK && s_hardware.audio_bus && + mybot_m5stack_stopwatch_hardware_init(s_hardware.audio_bus) == 0) { + s_hardware.stopwatch_hardware_ready = true; + } else if (result == ESP_OK) { + result = ESP_FAIL; + } +#endif +#ifdef MYBOT_GENERIC_TOUCH_I2C_PORT + if (MYBOT_GENERIC_TOUCH_KIND != 0 && touch_separate_bus && result == ESP_OK) { + result = create_i2c_bus(MYBOT_GENERIC_TOUCH_I2C_PORT, MYBOT_GENERIC_TOUCH_I2C_SDA, + MYBOT_GENERIC_TOUCH_I2C_SCL, &s_hardware.touch_bus); + } +#endif + +#ifdef MYBOT_GENERIC_PCA9557_ADDRESS + if (result == ESP_OK && s_hardware.audio_bus) { + result = mybot_pca9557_init(&s_hardware.expander, s_hardware.audio_bus, + MYBOT_GENERIC_PCA9557_ADDRESS, MYBOT_GENERIC_I2C_SPEED_HZ) == 0 + ? ESP_OK + : ESP_FAIL; + s_hardware.expander_ready = result == ESP_OK; + } + if (result == ESP_OK && s_hardware.expander_ready) { + result = + mybot_pca9557_configure_outputs(&s_hardware.expander, MYBOT_GENERIC_PCA9557_OUTPUT_MASK, + MYBOT_GENERIC_PCA9557_HIGH_MASK) == 0 + ? ESP_OK + : ESP_FAIL; + } +#ifdef MYBOT_GENERIC_PCA9557_LOW_MASK + if (result == ESP_OK && s_hardware.expander_ready) { + result = + mybot_pca9557_set_levels(&s_hardware.expander, MYBOT_GENERIC_PCA9557_LOW_MASK, 0) == 0 + ? ESP_OK + : ESP_FAIL; + } +#endif +#endif + +#ifdef MYBOT_GENERIC_TCA9554_ADDRESS + if (result == ESP_OK && s_hardware.audio_bus) { + result = mybot_tca9554_init(&s_hardware.tca9554, s_hardware.audio_bus, + MYBOT_GENERIC_TCA9554_ADDRESS, MYBOT_GENERIC_I2C_SPEED_HZ) == 0 + ? ESP_OK + : ESP_FAIL; + } + if (result == ESP_OK) { + result = mybot_tca9554_configure_outputs(&s_hardware.tca9554, + MYBOT_GENERIC_TCA9554_OUTPUT_MASK, 0) == 0 + ? ESP_OK + : ESP_FAIL; + } + if (result == ESP_OK) { + result = mybot_tca9554_set_levels(&s_hardware.tca9554, MYBOT_GENERIC_TCA9554_RESET_MASK, + MYBOT_GENERIC_TCA9554_RESET_MASK) == 0 + ? ESP_OK + : ESP_FAIL; + vTaskDelay(pdMS_TO_TICKS(120)); + } + if (result == ESP_OK) { + result = + mybot_tca9554_set_levels(&s_hardware.tca9554, MYBOT_GENERIC_TCA9554_RESET_MASK, 0) == 0 + ? ESP_OK + : ESP_FAIL; + vTaskDelay(pdMS_TO_TICKS(300)); + } + if (result == ESP_OK) { + result = mybot_tca9554_set_levels(&s_hardware.tca9554, MYBOT_GENERIC_TCA9554_RESET_MASK, + MYBOT_GENERIC_TCA9554_RESET_MASK) == 0 + ? ESP_OK + : ESP_FAIL; + } +#ifdef MYBOT_GENERIC_TCA9554_LOW_MASK + if (result == ESP_OK) { + result = + mybot_tca9554_set_levels(&s_hardware.tca9554, MYBOT_GENERIC_TCA9554_LOW_MASK, 0) == 0 + ? ESP_OK + : ESP_FAIL; + } +#endif +#endif + +#ifdef MYBOT_GENERIC_AXP2101_ADDRESS + if (result == ESP_OK && s_hardware.audio_bus) { + const i2c_device_config_t axp_config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = MYBOT_GENERIC_AXP2101_ADDRESS, + .scl_speed_hz = MYBOT_GENERIC_I2C_SPEED_HZ, + }; + result = i2c_master_bus_add_device(s_hardware.audio_bus, &axp_config, &s_hardware.axp2101); + } + if (result == ESP_OK) { + result = initialize_axp2101(); + } +#endif + + if (result == ESP_OK) { + result = configure_output_gpio(MYBOT_GENERIC_PA_GPIO, !MYBOT_GENERIC_PA_ACTIVE_LEVEL); + s_hardware.pa_ready = MYBOT_GENERIC_PA_GPIO != GPIO_NUM_NC; + } + if (result == ESP_OK) { + result = initialize_backlight(); + } + if (result != ESP_OK) { + ESP_LOGE(TAG, "event=hardware action=initialize result=error code=%s", + esp_err_to_name(result)); + (void)release_hardware(); + return -1; + } + + s_hardware.initialized = true; + ESP_LOGI(TAG, "event=hardware action=initialize board=%s result=ok", MYBOT_BOARD_NAME); + return 0; +} + +int mybot_generic_hardware_deinit(void) { + return release_hardware(); +} + +i2c_master_bus_handle_t mybot_generic_audio_i2c_bus(void) { + return s_hardware.initialized ? s_hardware.audio_bus : NULL; +} + +i2c_master_bus_handle_t mybot_generic_touch_i2c_bus(void) { + if (!s_hardware.initialized) { + return NULL; + } + return s_hardware.touch_bus ? s_hardware.touch_bus : s_hardware.audio_bus; +} + +int mybot_generic_set_speaker_power(bool enabled) { + if (!s_hardware.initialized) { + return -1; + } + if (s_hardware.pa_enabled == enabled) { + return 0; + } +#ifdef MYBOT_GENERIC_PCA9557_PA_MASK + if (s_hardware.expander_ready) { + const uint8_t mask = MYBOT_GENERIC_PCA9557_PA_MASK; + if (mybot_pca9557_set_levels(&s_hardware.expander, mask, enabled ? mask : 0) < 0) { + return -1; + } + s_hardware.pa_enabled = enabled; + return 0; + } +#endif +#if MYBOT_GENERIC_M5STACK_STOPWATCH + if (s_hardware.stopwatch_hardware_ready) { + if (mybot_m5stack_stopwatch_set_speaker_pa(enabled) < 0) { + return -1; + } + s_hardware.pa_enabled = enabled; + return 0; + } +#endif + if (!s_hardware.pa_ready) { + s_hardware.pa_enabled = enabled; + return 0; + } + const int level = enabled ? MYBOT_GENERIC_PA_ACTIVE_LEVEL : !MYBOT_GENERIC_PA_ACTIVE_LEVEL; + if (gpio_set_level(MYBOT_GENERIC_PA_GPIO, level) != ESP_OK) { + return -1; + } + s_hardware.pa_enabled = enabled; + return 0; +} + +int mybot_board_set_display_backlight(unsigned int percent) { +#ifdef MYBOT_DISPLAY_BACKLIGHT_GPIO + if (MYBOT_DISPLAY_BACKLIGHT_GPIO == GPIO_NUM_NC) { + return 0; + } + if (!s_hardware.initialized || !s_hardware.backlight_ready || percent > 100) { + return -1; + } + uint32_t duty = (BACKLIGHT_MAX_DUTY * percent + 50U) / 100U; + if (MYBOT_DISPLAY_BACKLIGHT_INVERT) { + duty = BACKLIGHT_MAX_DUTY - duty; + } + if (ledc_set_duty(BACKLIGHT_LEDC_MODE, BACKLIGHT_LEDC_CHANNEL, duty) != ESP_OK || + ledc_update_duty(BACKLIGHT_LEDC_MODE, BACKLIGHT_LEDC_CHANNEL) != ESP_OK) { + return -1; + } + return 0; +#else + (void)percent; + return 0; +#endif +} + +i2c_master_bus_handle_t mybot_es8311_board_i2c_bus_handle(void) { + return mybot_generic_audio_i2c_bus(); +} + +int mybot_es8311_board_set_speaker_power(bool enabled) { + return mybot_generic_set_speaker_power(enabled); +} + +i2c_master_bus_handle_t mybot_amoled175_i2c_bus_handle(void) { + return mybot_generic_audio_i2c_bus(); +} + +int mybot_amoled175_set_speaker_pa(bool enabled) { + return mybot_generic_set_speaker_power(enabled); +} + +i2c_master_bus_handle_t mybot_board_touch_i2c_bus_handle(void) { + return mybot_generic_touch_i2c_bus(); +} diff --git a/components/mybot_platform/boards/common/generic_hardware.h b/components/mybot_platform/boards/common/generic_hardware.h new file mode 100644 index 0000000..3f0d206 --- /dev/null +++ b/components/mybot_platform/boards/common/generic_hardware.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_GENERIC_HARDWARE_H_ +#define MYBOT_GENERIC_HARDWARE_H_ + +#include "driver/i2c_master.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int mybot_generic_hardware_init(void); +int mybot_generic_hardware_deinit(void); +i2c_master_bus_handle_t mybot_generic_audio_i2c_bus(void); +i2c_master_bus_handle_t mybot_generic_touch_i2c_bus(void); +int mybot_generic_set_speaker_power(bool enabled); +int mybot_board_set_display_backlight(unsigned int percent); + +#ifdef __cplusplus +} +#endif + +#endif /* MYBOT_GENERIC_HARDWARE_H_ */ diff --git a/components/mybot_platform/boards/common/generic_profile.cmake b/components/mybot_platform/boards/common/generic_profile.cmake new file mode 100644 index 0000000..47072dc --- /dev/null +++ b/components/mybot_platform/boards/common/generic_profile.cmake @@ -0,0 +1,90 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_BOARD_TARGET "esp32s3") +get_filename_component(MYBOT_GENERIC_BOARD_DIR "${MYBOT_BOARD_PROFILE}" DIRECTORY) +get_filename_component(MYBOT_GENERIC_COMMON_DIR + "${CMAKE_CURRENT_LIST_DIR}" ABSOLUTE) +set(MYBOT_BOARD_SDKCONFIG_DEFAULTS "${MYBOT_GENERIC_BOARD_DIR}/sdkconfig.defaults") +if(NOT MYBOT_GENERIC_DISPLAY) + set(MYBOT_BOARD_HEADLESS TRUE) +endif() +set(MYBOT_BOARD_INCLUDE_DIR + "${MYBOT_GENERIC_BOARD_DIR}" + "${MYBOT_GENERIC_COMMON_DIR}" + "${MYBOT_GENERIC_COMMON_DIR}/../esp32-s3-touch-amoled-1.75-common" +) +set(MYBOT_BOARD_PARTITION_TABLE "${MYBOT_GENERIC_PARTITION_TABLE}") +set(MYBOT_BOARD_REQUIRED_CONFIGS ${MYBOT_GENERIC_REQUIRED_CONFIGS}) +set(MYBOT_BOARD_FORBIDDEN_CONFIGS ${MYBOT_GENERIC_FORBIDDEN_CONFIGS}) + +get_filename_component(MYBOT_PLATFORM_ROOT "${CMAKE_CURRENT_LIST_DIR}/../.." ABSOLUTE) +set(MYBOT_BOARD_SOURCES + "${MYBOT_GENERIC_BOARD_DIR}/board.c" + "${MYBOT_GENERIC_COMMON_DIR}/generic_board.c" + "${MYBOT_GENERIC_COMMON_DIR}/generic_hardware.c" + ${MYBOT_GENERIC_EXTRA_SOURCES} +) + +if(MYBOT_GENERIC_DISPLAY) + include("${MYBOT_PLATFORM_ROOT}/src/drivers/display/display.cmake") + mybot_display_add_panel(MYBOT_BOARD_SOURCES "${MYBOT_GENERIC_DISPLAY}") + mybot_display_add_lvgl_sources(MYBOT_BOARD_SOURCES "shared_lvgl_adapter.cc") +endif() + +if(MYBOT_GENERIC_AUDIO STREQUAL "es8311") + list(APPEND MYBOT_BOARD_SOURCES "${MYBOT_PLATFORM_ROOT}/src/drivers/audio/es8311_codec_audio.c") +elseif(MYBOT_GENERIC_AUDIO STREQUAL "raw") + list(APPEND MYBOT_BOARD_SOURCES "${MYBOT_PLATFORM_ROOT}/src/drivers/audio/raw_i2s_audio.c") +elseif(MYBOT_GENERIC_AUDIO STREQUAL "dual") + list(APPEND MYBOT_BOARD_SOURCES + "${MYBOT_PLATFORM_ROOT}/src/drivers/audio/amoled175_codec_audio.c") +elseif(MYBOT_GENERIC_AUDIO STREQUAL "es8374") + list(APPEND MYBOT_BOARD_SOURCES + "${MYBOT_PLATFORM_ROOT}/src/drivers/audio/es8311_codec_audio.c" + "${MYBOT_PLATFORM_ROOT}/src/drivers/audio/es8374_codec.c") +endif() + +if(MYBOT_GENERIC_TOUCH) + list(APPEND MYBOT_BOARD_SOURCES + "${MYBOT_PLATFORM_ROOT}/src/drivers/input/cst816s_boot_input.c") + if(NOT MYBOT_GENERIC_TOUCH_DRIVER OR MYBOT_GENERIC_TOUCH_DRIVER STREQUAL "cst816s") + list(APPEND MYBOT_GENERIC_EXTRA_REQUIRES + esp_lcd_touch + esp_lcd_touch_cst816s) + elseif(MYBOT_GENERIC_TOUCH_DRIVER STREQUAL "ft5x06") + list(APPEND MYBOT_GENERIC_EXTRA_REQUIRES + esp_lcd_touch + esp_lcd_touch_ft5x06) + elseif(MYBOT_GENERIC_TOUCH_DRIVER STREQUAL "gt911") + list(APPEND MYBOT_GENERIC_EXTRA_REQUIRES + esp_lcd_touch + esp_lcd_touch_gt911) + else() + message(FATAL_ERROR "Unknown MYBOT_GENERIC_TOUCH_DRIVER") + endif() +else() + list(APPEND MYBOT_BOARD_SOURCES + "${MYBOT_PLATFORM_ROOT}/src/drivers/input/gpio_buttons.c") +endif() + +if(MYBOT_GENERIC_PCA9557) + list(APPEND MYBOT_BOARD_SOURCES + "${MYBOT_PLATFORM_ROOT}/src/drivers/io_expander/pca9557.c") +endif() + +if(MYBOT_GENERIC_TCA9554) + list(APPEND MYBOT_BOARD_SOURCES + "${MYBOT_PLATFORM_ROOT}/src/drivers/io_expander/tca9554.c") +endif() + +set(MYBOT_BOARD_REQUIRES + button + esp_codec_dev + esp_driver_gpio + esp_driver_i2c + esp_driver_i2s + esp_driver_ledc + esp_driver_spi + esp_lcd + ${MYBOT_GENERIC_EXTRA_REQUIRES} +) diff --git a/components/mybot_platform/boards/df-k10/README.md b/components/mybot_platform/boards/df-k10/README.md new file mode 100644 index 0000000..a43574a --- /dev/null +++ b/components/mybot_platform/boards/df-k10/README.md @@ -0,0 +1,14 @@ +# DFRobot K10 + +板型 ID 为 `df-k10`。 + +- 显示:240×320 ILI9341,SPI3,共享 LVGL 紧凑布局。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;TCA9555 扩展按键尚未实现。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +扩展按键、摄像头、LED 环和板级电源路径未实现。ILI9341 初始化、双 codec 路由和音频仍需 +真机验证。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/df-k10/board.c b/components/mybot_platform/boards/df-k10/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/df-k10/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/df-k10/board.cmake b/components/mybot_platform/boards/df-k10/board.cmake new file mode 100644 index 0000000..f3cb647 --- /dev/null +++ b/components/mybot_platform/boards/df-k10/board.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_ili9341) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/df-k10/board_config.h b/components/mybot_platform/boards/df-k10/board_config.h new file mode 100644 index 0000000..6dc01d6 --- /dev/null +++ b/components/mybot_platform/boards/df-k10/board_config.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "df-k10" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_3 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_0 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_45 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_47 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_48 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x23 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 3 +#define MYBOT_ILI9341_PROFILE 0 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_21 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_12 +#define MYBOT_DISPLAY_DC GPIO_NUM_13 +#define MYBOT_DISPLAY_CS GPIO_NUM_14 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 320 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/df-k10/sdkconfig.defaults b/components/mybot_platform/boards/df-k10/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/df-k10/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/esp-box-3/README.md b/components/mybot_platform/boards/esp-box-3/README.md new file mode 100644 index 0000000..ed60ecb --- /dev/null +++ b/components/mybot_platform/boards/esp-box-3/README.md @@ -0,0 +1,14 @@ +# ESP-Box 3 + +板型 ID 为 `esp-box-3`。 + +- 显示:320×240 ILI9341,SPI3,共享 LVGL UI。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;当前未实现触摸。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 +- 背光:GPIO47 LEDC;LCD reset 为高有效。 + +ILI9341 厂商初始化、双 codec 路由、触摸、板级 PMIC/电源时序和音频仍需真机验证。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp-box-3/board.c b/components/mybot_platform/boards/esp-box-3/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp-box-3/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp-box-3/board.cmake b/components/mybot_platform/boards/esp-box-3/board.cmake new file mode 100644 index 0000000..f3cb647 --- /dev/null +++ b/components/mybot_platform/boards/esp-box-3/board.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_ili9341) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp-box-3/board_config.h b/components/mybot_platform/boards/esp-box-3/board_config.h new file mode 100644 index 0000000..4ba6e67 --- /dev/null +++ b/components/mybot_platform/boards/esp-box-3/board_config.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp-box-3" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_2 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_17 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_45 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_15 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_8 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_18 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_46 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 3 +#define MYBOT_ILI9341_PROFILE 1 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_6 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_7 +#define MYBOT_DISPLAY_DC GPIO_NUM_4 +#define MYBOT_DISPLAY_CS GPIO_NUM_5 +#define MYBOT_DISPLAY_RESET GPIO_NUM_48 +#define MYBOT_DISPLAY_RESET_ACTIVE_HIGH 1 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_47 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 320 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp-box-3/sdkconfig.defaults b/components/mybot_platform/boards/esp-box-3/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/esp-box-3/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/esp-box-lite/README.md b/components/mybot_platform/boards/esp-box-lite/README.md new file mode 100644 index 0000000..2aac23c --- /dev/null +++ b/components/mybot_platform/boards/esp-box-lite/README.md @@ -0,0 +1,15 @@ +# ESP-Box Lite + +板型 ID 为 `esp-box-lite`。 + +- 显示:320×240 ILI9341,SPI3,共享 LVGL UI。 +- 音频:当前按 ES8311 输出与 ES7210 采集 profile 接入,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;ADC 按键尚未实现。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 +- 背光:GPIO45 LEDC;LCD reset 为低有效。 + +实际 codec 型号、ADC 按键、ILI9341 初始化、电源时序和音频仍需真机核对。若实物 codec 与 +当前 profile 不同,应新增独立驱动,不得沿用错误配置。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp-box-lite/board.c b/components/mybot_platform/boards/esp-box-lite/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp-box-lite/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp-box-lite/board.cmake b/components/mybot_platform/boards/esp-box-lite/board.cmake new file mode 100644 index 0000000..f3cb647 --- /dev/null +++ b/components/mybot_platform/boards/esp-box-lite/board.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_ili9341) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp-box-lite/board_config.h b/components/mybot_platform/boards/esp-box-lite/board_config.h new file mode 100644 index 0000000..c4326df --- /dev/null +++ b/components/mybot_platform/boards/esp-box-lite/board_config.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp-box-lite" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_2 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_17 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_47 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_15 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_8 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_18 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_46 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 3 +#define MYBOT_ILI9341_PROFILE 1 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_6 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_7 +#define MYBOT_DISPLAY_DC GPIO_NUM_4 +#define MYBOT_DISPLAY_CS GPIO_NUM_5 +#define MYBOT_DISPLAY_RESET GPIO_NUM_48 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_45 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 1 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 320 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp-box-lite/sdkconfig.defaults b/components/mybot_platform/boards/esp-box-lite/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/esp-box-lite/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/esp-box/README.md b/components/mybot_platform/boards/esp-box/README.md new file mode 100644 index 0000000..d88ac7c --- /dev/null +++ b/components/mybot_platform/boards/esp-box/README.md @@ -0,0 +1,14 @@ +# ESP-Box + +板型 ID 为 `esp-box`。 + +- 显示:320×240 ILI9341,SPI3,共享 LVGL UI。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;当前未实现触摸。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 +- 背光:GPIO45 LEDC,默认 60%。 + +ILI9341 厂商初始化、双 codec 路由、触摸、板级 PMIC/电源时序和音频仍需真机验证。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp-box/board.c b/components/mybot_platform/boards/esp-box/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp-box/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp-box/board.cmake b/components/mybot_platform/boards/esp-box/board.cmake new file mode 100644 index 0000000..f3cb647 --- /dev/null +++ b/components/mybot_platform/boards/esp-box/board.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_ili9341) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp-box/board_config.h b/components/mybot_platform/boards/esp-box/board_config.h new file mode 100644 index 0000000..7389872 --- /dev/null +++ b/components/mybot_platform/boards/esp-box/board_config.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp-box" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_2 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_17 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_47 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_15 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_8 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_18 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_46 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 3 +#define MYBOT_ILI9341_PROFILE 1 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_6 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_7 +#define MYBOT_DISPLAY_DC GPIO_NUM_4 +#define MYBOT_DISPLAY_CS GPIO_NUM_5 +#define MYBOT_DISPLAY_RESET GPIO_NUM_48 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_45 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 320 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp-box/sdkconfig.defaults b/components/mybot_platform/boards/esp-box/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/esp-box/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/README.md b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/README.md new file mode 100644 index 0000000..729515a --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/README.md @@ -0,0 +1,14 @@ +# ESP32-S3 Touch AMOLED 1.32 + +板型 ID 为 `esp32-s3-touch-amoled-1.32`。 + +- 显示:466×466 SH8601 QSPI,共享 LVGL UI。 +- 音频:ES8311,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;当前未实现触摸。 +- 电源:GPIO18 电源使能。 +- 存储:8 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +面板偏移、颜色、方向、电源轨、音频和按键仍需真机验证;睡眠、关机、电池上报和视频未实现。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board.c b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board.cmake b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board.cmake new file mode 100644 index 0000000..56dda31 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/8m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "sh8601/sh8601_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_PCA9557 OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_8MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_sh8601) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board_config.h b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board_config.h new file mode 100644 index 0000000..cd9c85e --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/board_config.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-touch-amoled-1.32" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_41 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_40 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_42 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_47 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_48 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_46 +#define MYBOT_GENERIC_POWER_GPIO GPIO_NUM_18 +#define MYBOT_GENERIC_POWER_ACTIVE_LEVEL 1 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define MYBOT_SH8601_PROFILE 2 +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_PCLK GPIO_NUM_11 +#define MYBOT_DISPLAY_DATA0 GPIO_NUM_12 +#define MYBOT_DISPLAY_DATA1 GPIO_NUM_13 +#define MYBOT_DISPLAY_DATA2 GPIO_NUM_14 +#define MYBOT_DISPLAY_DATA3 GPIO_NUM_15 +#define MYBOT_DISPLAY_CS GPIO_NUM_10 +#define MYBOT_DISPLAY_RESET GPIO_NUM_8 +#define MYBOT_DISPLAY_RESET_ACTIVE_HIGH 0 +#define MYBOT_DISPLAY_WIDTH 466 +#define MYBOT_DISPLAY_HEIGHT 466 +#define MYBOT_DISPLAY_OFFSET_X 6 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/sdkconfig.defaults new file mode 100644 index 0000000..63ebd39 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.32/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/8m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/README.md b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/README.md new file mode 100644 index 0000000..013bc99 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/README.md @@ -0,0 +1,14 @@ +# ESP32-S3 Touch AMOLED 1.43C + +板型 ID 为 `esp32-s3-touch-amoled-1.43c`。 + +- 显示:466×466 SH8601 QSPI,共享 LVGL UI。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;当前未实现触摸。 +- 存储:8 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +SH8601 时序、双 codec 路由、电源轨、音频和按键仍需真机验证;睡眠、关机、电池上报和视频 +未实现。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board.c b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board.cmake b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board.cmake new file mode 100644 index 0000000..3d32c4c --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board.cmake @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/8m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "sh8601/sh8601_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_PCA9557 OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_8MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_sh8601) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board_config.h b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board_config.h new file mode 100644 index 0000000..be6c2c8 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/board_config.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-touch-amoled-1.43c" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_40 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_42 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_41 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_47 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_48 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_46 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define MYBOT_SH8601_PROFILE 3 +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_PCLK GPIO_NUM_14 +#define MYBOT_DISPLAY_DATA0 GPIO_NUM_9 +#define MYBOT_DISPLAY_DATA1 GPIO_NUM_10 +#define MYBOT_DISPLAY_DATA2 GPIO_NUM_11 +#define MYBOT_DISPLAY_DATA3 GPIO_NUM_12 +#define MYBOT_DISPLAY_CS GPIO_NUM_15 +#define MYBOT_DISPLAY_RESET GPIO_NUM_13 +#define MYBOT_DISPLAY_RESET_ACTIVE_HIGH 0 +#define MYBOT_DISPLAY_WIDTH 466 +#define MYBOT_DISPLAY_HEIGHT 466 +#define MYBOT_DISPLAY_OFFSET_X 8 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/sdkconfig.defaults new file mode 100644 index 0000000..ece839c --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.43c/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/8m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/README.md b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/README.md new file mode 100644 index 0000000..fb339a5 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/README.md @@ -0,0 +1,15 @@ +# ESP32-S3 Touch AMOLED 1.8 + +板型 ID 为 `esp32-s3-touch-amoled-1.8`。 + +- 显示:368×448 SH8601 QSPI,共享 LVGL UI。 +- 音频:ES8311,16 kHz 单声道 signed-16 PCM。 +- 输入:FT5x06 触摸和 GPIO0 Boot。 +- 电源/扩展:AXP2101 `0x34`、TCA9554 `0x20`,用于电源轨和 LCD/触摸复位。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +AXP2101/TCA9554 上电顺序、SH8601 时序、触摸映射、音频和 PA 仍需真机验证。睡眠、关机、 +电池上报和视频未实现。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board.c b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board.cmake b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board.cmake new file mode 100644 index 0000000..27f5b1a --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board.cmake @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "sh8601/sh8601_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "ft5x06") +set(MYBOT_GENERIC_TCA9554 ON) +set(MYBOT_GENERIC_PCA9557 OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_sh8601) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board_config.h b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board_config.h new file mode 100644 index 0000000..ac945bc --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/board_config.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-touch-amoled-1.8" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 2 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_45 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_10 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_8 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_15 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_14 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_46 +#define MYBOT_GENERIC_TCA9554_ADDRESS 0x20 +#define MYBOT_GENERIC_TCA9554_OUTPUT_MASK 0x07 +#define MYBOT_GENERIC_TCA9554_RESET_MASK 0x07 +#define MYBOT_GENERIC_AXP2101_ADDRESS 0x34 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define MYBOT_TOUCH_DRIVER_KIND 2 +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_39 +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_13 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_SH8601_PROFILE 1 +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_PCLK GPIO_NUM_11 +#define MYBOT_DISPLAY_DATA0 GPIO_NUM_4 +#define MYBOT_DISPLAY_DATA1 GPIO_NUM_5 +#define MYBOT_DISPLAY_DATA2 GPIO_NUM_6 +#define MYBOT_DISPLAY_DATA3 GPIO_NUM_7 +#define MYBOT_DISPLAY_CS GPIO_NUM_12 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_RESET_ACTIVE_HIGH 1 +#define MYBOT_DISPLAY_WIDTH 368 +#define MYBOT_DISPLAY_HEIGHT 448 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-1.8/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/README.md b/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/README.md new file mode 100644 index 0000000..9cdcf07 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/README.md @@ -0,0 +1,15 @@ +# ESP32-S3 Touch AMOLED 2.06 + +板型 ID 为 `esp32-s3-touch-amoled-2.06`。 + +- 显示:410×502 SH8601 QSPI,X 方向 panel gap 为 22 像素。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 输入:FT5x06 触摸和 GPIO0 Boot。 +- 电源:AXP2101 `0x34`。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +面板时序、偏移、颜色、触摸映射、AXP2101 电源和双 codec 音频仍需真机验证。睡眠、关机、 +电池上报和视频未实现。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board.c b/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board.cmake b/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board.cmake new file mode 100644 index 0000000..0fd3c46 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board.cmake @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "sh8601/sh8601_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "ft5x06") +set(MYBOT_GENERIC_PCA9557 OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_sh8601) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board_config.h b/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board_config.h new file mode 100644 index 0000000..f99c001 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/board_config.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-touch-amoled-2.06" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 2 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_41 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_45 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_42 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_40 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_15 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_14 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_46 +#define MYBOT_GENERIC_AXP2101_ADDRESS 0x34 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define MYBOT_TOUCH_DRIVER_KIND 2 +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_9 +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_38 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_SH8601_PROFILE 4 +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_PCLK GPIO_NUM_11 +#define MYBOT_DISPLAY_DATA0 GPIO_NUM_4 +#define MYBOT_DISPLAY_DATA1 GPIO_NUM_5 +#define MYBOT_DISPLAY_DATA2 GPIO_NUM_6 +#define MYBOT_DISPLAY_DATA3 GPIO_NUM_7 +#define MYBOT_DISPLAY_CS GPIO_NUM_12 +#define MYBOT_DISPLAY_RESET GPIO_NUM_8 +#define MYBOT_DISPLAY_RESET_ACTIVE_HIGH 0 +#define MYBOT_DISPLAY_WIDTH 410 +#define MYBOT_DISPLAY_HEIGHT 502 +#define MYBOT_DISPLAY_OFFSET_X 22 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-amoled-2.06/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/esp32s3-korvo2-v3/README.md b/components/mybot_platform/boards/esp32s3-korvo2-v3/README.md new file mode 100644 index 0000000..49753fa --- /dev/null +++ b/components/mybot_platform/boards/esp32s3-korvo2-v3/README.md @@ -0,0 +1,15 @@ +# ESP32-S3 Korvo2 V3 + +板型 ID 为 `esp32s3-korvo2-v3`。 + +- 显示:320×240 ILI9341,SPI3,TCA9554 控制 LCD CS 与复位。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO5 Boot;ADC 按键尚未实现。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 +- 扩展:TCA9554 `0x20`;GPIO48 扬声器 PA。 + +ADC 按键、摄像头、电池上报、休眠和板级电源管理未实现。TCA9554 复位/CS 时序、ILI9341、 +双 codec 路由和音频仍需真机验证。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32s3-korvo2-v3/board.c b/components/mybot_platform/boards/esp32s3-korvo2-v3/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp32s3-korvo2-v3/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp32s3-korvo2-v3/board.cmake b/components/mybot_platform/boards/esp32s3-korvo2-v3/board.cmake new file mode 100644 index 0000000..41392ad --- /dev/null +++ b/components/mybot_platform/boards/esp32s3-korvo2-v3/board.cmake @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_TCA9554 ON) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_ili9341) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp32s3-korvo2-v3/board_config.h b/components/mybot_platform/boards/esp32s3-korvo2-v3/board_config.h new file mode 100644 index 0000000..bb054cf --- /dev/null +++ b/components/mybot_platform/boards/esp32s3-korvo2-v3/board_config.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32s3-korvo2-v3" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_45 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_10 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_8 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_17 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_18 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_48 +#define MYBOT_GENERIC_TCA9554_ADDRESS 0x20 +#define MYBOT_GENERIC_TCA9554_OUTPUT_MASK 0x0F +#define MYBOT_GENERIC_TCA9554_RESET_MASK 0x07 +#define MYBOT_GENERIC_TCA9554_LOW_MASK 0x08 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_5 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 3 +#define MYBOT_ILI9341_PROFILE 0 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_0 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_1 +#define MYBOT_DISPLAY_DC GPIO_NUM_2 +#define MYBOT_DISPLAY_CS GPIO_NUM_NC +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 320 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32s3-korvo2-v3/sdkconfig.defaults b/components/mybot_platform/boards/esp32s3-korvo2-v3/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/esp32s3-korvo2-v3/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/lichuang-dev/README.md b/components/mybot_platform/boards/lichuang-dev/README.md new file mode 100644 index 0000000..924b09c --- /dev/null +++ b/components/mybot_platform/boards/lichuang-dev/README.md @@ -0,0 +1,15 @@ +# Lichuang Dev + +板型 ID 为 `lichuang-dev`。 + +- 显示:320×240 ST7789,SPI3 mode 2,共享 LVGL UI。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 输入:FT5x06 触摸和 GPIO0 Boot。 +- 扩展:PCA9557 `0x19`,控制显示复位和 speaker PA。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +PCA9557 输出映射、FT5x06 坐标、ST7789 初始化、双 codec 路由、音频和背光仍需真机验证。 +摄像头和电池上报未实现。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/lichuang-dev/board.c b/components/mybot_platform/boards/lichuang-dev/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/lichuang-dev/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/lichuang-dev/board.cmake b/components/mybot_platform/boards/lichuang-dev/board.cmake new file mode 100644 index 0000000..1811316 --- /dev/null +++ b/components/mybot_platform/boards/lichuang-dev/board.cmake @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "ft5x06") +set(MYBOT_GENERIC_PCA9557 ON) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/lichuang-dev/board_config.h b/components/mybot_platform/boards/lichuang-dev/board_config.h new file mode 100644 index 0000000..5456073 --- /dev/null +++ b/components/mybot_platform/boards/lichuang-dev/board_config.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "lichuang-dev" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 2 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_14 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_13 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_12 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_45 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_1 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_2 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC +#define MYBOT_GENERIC_PCA9557_ADDRESS 0x19 +#define MYBOT_GENERIC_PCA9557_OUTPUT_MASK 0x03 +#define MYBOT_GENERIC_PCA9557_HIGH_MASK 0x00 +#define MYBOT_GENERIC_PCA9557_LOW_MASK 0x01 +#define MYBOT_GENERIC_PCA9557_PA_MASK 0x02 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_TOUCH_DRIVER_KIND 2 +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_NC +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_NC +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 1 +#define MYBOT_TOUCH_MIRROR_X 1 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_X_MAX 240 +#define MYBOT_TOUCH_Y_MAX 320 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_40 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_41 +#define MYBOT_DISPLAY_DC GPIO_NUM_39 +#define MYBOT_DISPLAY_CS GPIO_NUM_NC +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_42 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 1 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 320 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY true +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 2 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (80 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/lichuang-dev/sdkconfig.defaults b/components/mybot_platform/boards/lichuang-dev/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/lichuang-dev/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/m5stack-stopwatch/README.md b/components/mybot_platform/boards/m5stack-stopwatch/README.md new file mode 100644 index 0000000..c3f9976 --- /dev/null +++ b/components/mybot_platform/boards/m5stack-stopwatch/README.md @@ -0,0 +1,16 @@ +# M5Stack StopWatch + +板型 ID 为 `m5stack-stopwatch`。这是 466×466 圆屏 profile,使用共享 LVGL 中央安全区布局。 + +- 显示:CO5300 QSPI,466×466,内部亮度控制。 +- 音频:ES8311 `0x18`,16 kHz 单声道 signed-16 PCM。 +- 输入:CST820B 兼容触摸路径、GPIO1 主按键、GPIO2 音量按键。 +- 电源/扩展:M5PM1 `0x6E`、M5IOE1 `0x4F`,负责充电、LCD/codec 电源、复位、 + speaker PA 和电机默认关闭。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +M5PM1/M5IOE1 时序、触摸协议、圆屏裁切、音频和按键行为尚未真机验证。电池上报、振动输出、 +休眠和关机尚未实现。 + +详细引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/m5stack-stopwatch/board.c b/components/mybot_platform/boards/m5stack-stopwatch/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/m5stack-stopwatch/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/m5stack-stopwatch/board.cmake b/components/mybot_platform/boards/m5stack-stopwatch/board.cmake new file mode 100644 index 0000000..380f6fa --- /dev/null +++ b/components/mybot_platform/boards/m5stack-stopwatch/board.cmake @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "co5300/co5300_466_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "cst816s") +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID +) +set(MYBOT_GENERIC_EXTRA_SOURCES + "${CMAKE_CURRENT_LIST_DIR}/m5stack_stopwatch_hardware.cc") +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_co5300 m5ioe1 m5pm1) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/m5stack-stopwatch/board_config.h b/components/mybot_platform/boards/m5stack-stopwatch/board_config.h new file mode 100644 index 0000000..df116c3 --- /dev/null +++ b/components/mybot_platform/boards/m5stack-stopwatch/board_config.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "m5stack-stopwatch" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_DISPLAY_ROUND 1 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 1 +#define MYBOT_GENERIC_M5STACK_STOPWATCH 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_18 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_17 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_15 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_21 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_16 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_47 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_48 +#define MYBOT_GENERIC_I2C_SPEED_HZ 100000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x18 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_1 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_2 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_TOUCH_DRIVER_KIND 1 +#define MYBOT_TOUCH_I2C_SPEED_HZ 100000 +#define MYBOT_TOUCH_RESET GPIO_NUM_NC +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_13 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_PCLK GPIO_NUM_40 +#define MYBOT_DISPLAY_DATA0 GPIO_NUM_41 +#define MYBOT_DISPLAY_DATA1 GPIO_NUM_42 +#define MYBOT_DISPLAY_DATA2 GPIO_NUM_46 +#define MYBOT_DISPLAY_DATA3 GPIO_NUM_45 +#define MYBOT_DISPLAY_CS GPIO_NUM_39 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_WIDTH 466 +#define MYBOT_DISPLAY_HEIGHT 466 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/m5stack-stopwatch/m5stack_stopwatch_hardware.cc b/components/mybot_platform/boards/m5stack-stopwatch/m5stack_stopwatch_hardware.cc new file mode 100644 index 0000000..f8193a5 --- /dev/null +++ b/components/mybot_platform/boards/m5stack-stopwatch/m5stack_stopwatch_hardware.cc @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "m5stack_stopwatch_hardware.h" + +#include "M5IOE1.h" +#include "M5PM1.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include + +#define TAG "m5stack_stopwatch_hw" + +namespace { +constexpr uint8_t kIoExpanderAddress = 0x4F; +constexpr uint8_t kPowerManagerAddress = 0x6E; +constexpr uint8_t kChargeStatePin = 2; +constexpr uint8_t kChargeCurrentPin = 3; +constexpr uint8_t kLcdPowerPin = 7; +constexpr uint8_t kLcdResetPin = 4; +constexpr uint8_t kTouchResetPin = 3; +constexpr uint8_t kCodecPowerPin = 2; +constexpr uint8_t kSpeakerPaPin = 9; +constexpr uint8_t kMotorPin = 8; + +M5IOE1 *s_io_expander; +M5PM1 *s_power_manager; +i2c_master_bus_handle_t s_bus; +bool s_initialized; + +int set_ioe_output(M5IOE1 &ioe, uint8_t pin, uint8_t level) { + ioe.pinMode(pin, OUTPUT); + if (ioe.setDriveMode(pin, M5IOE1_DRIVE_PUSHPULL) != M5IOE1_OK) { + return -1; + } + m5ioe1_err_t result = M5IOE1_FAIL; + ioe.digitalWriteWithRes(pin, level, &result); + return result == M5IOE1_OK ? 0 : -1; +} + +int initialize_expander() { + s_io_expander = new M5IOE1(); + if (!s_io_expander || s_io_expander->begin(s_bus, kIoExpanderAddress, M5IOE1_I2C_FREQ_100K, + M5IOE1_INT_MODE_POLLING) != M5IOE1_OK) { + return -1; + } + if (set_ioe_output(*s_io_expander, kLcdPowerPin, HIGH) < 0 || + set_ioe_output(*s_io_expander, kCodecPowerPin, HIGH) < 0 || + set_ioe_output(*s_io_expander, kSpeakerPaPin, LOW) < 0 || + set_ioe_output(*s_io_expander, kMotorPin, LOW) < 0 || + set_ioe_output(*s_io_expander, kTouchResetPin, HIGH) < 0 || + set_ioe_output(*s_io_expander, kLcdResetPin, HIGH) < 0) { + return -1; + } + vTaskDelay(pdMS_TO_TICKS(20)); + if (set_ioe_output(*s_io_expander, kLcdResetPin, LOW) < 0) { + return -1; + } + vTaskDelay(pdMS_TO_TICKS(10)); + if (set_ioe_output(*s_io_expander, kLcdResetPin, HIGH) < 0) { + return -1; + } + vTaskDelay(pdMS_TO_TICKS(120)); + return 0; +} + +int initialize_power_manager() { + s_power_manager = new M5PM1(); + if (!s_power_manager || + s_power_manager->begin(s_bus, kPowerManagerAddress, M5PM1_I2C_FREQ_100K) != M5PM1_OK) { + return -1; + } + if (s_power_manager->setChargeEnable(true) != M5PM1_OK || + s_power_manager->setBoostEnable(true) != M5PM1_OK) { + return -1; + } + s_power_manager->pinMode(kChargeStatePin, INPUT); + s_power_manager->pinMode(kChargeCurrentPin, OUTPUT); + s_power_manager->digitalWrite(kChargeCurrentPin, LOW); + return 0; +} +} // namespace + +int mybot_m5stack_stopwatch_hardware_init(i2c_master_bus_handle_t bus) { + if (s_initialized || !bus) { + return -1; + } + s_bus = bus; + if (initialize_expander() < 0 || initialize_power_manager() < 0) { + ESP_LOGE(TAG, "event=hardware action=initialize result=error"); + (void)mybot_m5stack_stopwatch_hardware_deinit(); + return -1; + } + s_initialized = true; + return 0; +} + +int mybot_m5stack_stopwatch_set_speaker_pa(bool enabled) { + if (!s_initialized || !s_io_expander) { + return -1; + } + m5ioe1_err_t result = M5IOE1_FAIL; + s_io_expander->digitalWriteWithRes(kSpeakerPaPin, enabled ? HIGH : LOW, &result); + return result == M5IOE1_OK ? 0 : -1; +} + +int mybot_m5stack_stopwatch_hardware_deinit(void) { + int result = 0; + if (s_io_expander) { + m5ioe1_err_t io_result = M5IOE1_FAIL; + s_io_expander->digitalWriteWithRes(kSpeakerPaPin, LOW, &io_result); + if (io_result != M5IOE1_OK) { + result = -1; + } + s_io_expander->digitalWriteWithRes(kMotorPin, LOW, &io_result); + if (io_result != M5IOE1_OK) { + result = -1; + } + } + delete s_io_expander; + s_io_expander = nullptr; + delete s_power_manager; + s_power_manager = nullptr; + s_bus = nullptr; + s_initialized = false; + return result; +} diff --git a/components/mybot_platform/boards/m5stack-stopwatch/m5stack_stopwatch_hardware.h b/components/mybot_platform/boards/m5stack-stopwatch/m5stack_stopwatch_hardware.h new file mode 100644 index 0000000..463152a --- /dev/null +++ b/components/mybot_platform/boards/m5stack-stopwatch/m5stack_stopwatch_hardware.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_M5STACK_STOPWATCH_HARDWARE_H_ +#define MYBOT_M5STACK_STOPWATCH_HARDWARE_H_ + +#include "driver/i2c_master.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int mybot_m5stack_stopwatch_hardware_init(i2c_master_bus_handle_t bus); +int mybot_m5stack_stopwatch_set_speaker_pa(bool enabled); +int mybot_m5stack_stopwatch_hardware_deinit(void); + +#ifdef __cplusplus +} +#endif + +#endif /* MYBOT_M5STACK_STOPWATCH_HARDWARE_H_ */ diff --git a/components/mybot_platform/boards/m5stack-stopwatch/sdkconfig.defaults b/components/mybot_platform/boards/m5stack-stopwatch/sdkconfig.defaults new file mode 100644 index 0000000..68a9fd4 --- /dev/null +++ b/components/mybot_platform/boards/m5stack-stopwatch/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n +CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID=y diff --git a/components/mybot_platform/boards/mixgo-nova/README.md b/components/mybot_platform/boards/mixgo-nova/README.md new file mode 100644 index 0000000..9e3494b --- /dev/null +++ b/components/mybot_platform/boards/mixgo-nova/README.md @@ -0,0 +1,14 @@ +# MIXGO Nova + +板型 ID 为 `mixgo-nova`。 + +- 显示:128×160 ST7789,SPI3,共享 LVGL 紧凑布局。 +- 音频:ES8374 `0x20`,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;无触摸和音量键。 +- 存储:8 MB QIO Flash profile、Quad PSRAM、双 OTA 分区。 +- 背光:GPIO14 LEDC,默认 60%。 + +ES8374、ST7789 偏移/方向、背光、音频和电源仍需真机验证;RGB LED 未实现。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/mixgo-nova/board.c b/components/mybot_platform/boards/mixgo-nova/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/mixgo-nova/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/mixgo-nova/board.cmake b/components/mybot_platform/boards/mixgo-nova/board.cmake new file mode 100644 index 0000000..933e8cd --- /dev/null +++ b/components/mybot_platform/boards/mixgo-nova/board.cmake @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/8m.csv") +set(MYBOT_GENERIC_AUDIO "es8374") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_8MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_QUAD + CONFIG_SPIRAM_SPEED_80M +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/mixgo-nova/board_config.h b/components/mybot_platform/boards/mixgo-nova/board_config.h new file mode 100644 index 0000000..fcd45e6 --- /dev/null +++ b/components/mybot_platform/boards/mixgo-nova/board_config.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "mixgo-nova" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 4 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_35 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_34 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_47 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_33 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_48 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_37 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_36 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x20 +#define MYBOT_ES8311_INPUT_GAIN_DB 18.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_40 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_41 +#define MYBOT_DISPLAY_DC GPIO_NUM_18 +#define MYBOT_DISPLAY_CS GPIO_NUM_45 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_14 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 128 +#define MYBOT_DISPLAY_HEIGHT 160 +#define MYBOT_DISPLAY_OFFSET_X 2 +#define MYBOT_DISPLAY_OFFSET_Y 1 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/mixgo-nova/sdkconfig.defaults b/components/mybot_platform/boards/mixgo-nova/sdkconfig.defaults new file mode 100644 index 0000000..b2445ea --- /dev/null +++ b/components/mybot_platform/boards/mixgo-nova/sdkconfig.defaults @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/8m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_QUAD=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/components/mybot_platform/boards/quandong-s3-dev/README.md b/components/mybot_platform/boards/quandong-s3-dev/README.md new file mode 100644 index 0000000..254ece8 --- /dev/null +++ b/components/mybot_platform/boards/quandong-s3-dev/README.md @@ -0,0 +1,14 @@ +# Quandong S3 Dev + +板型 ID 为 `quandong-s3-dev`。 + +- 显示:320×240 ILI9341,SPI2,共享 LVGL UI。 +- 音频:ES8311,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;无触摸和音量键。 +- 扩展:PCA9557 `0x19`;GPIO1 为低有效 PA 使能。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +PCA9557 输出映射、ILI9341 厂商初始化、PA 极性和时序、音频及背光仍需真机验证。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/quandong-s3-dev/board.c b/components/mybot_platform/boards/quandong-s3-dev/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/quandong-s3-dev/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/quandong-s3-dev/board.cmake b/components/mybot_platform/boards/quandong-s3-dev/board.cmake new file mode 100644 index 0000000..538257c --- /dev/null +++ b/components/mybot_platform/boards/quandong-s3-dev/board.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_PCA9557 ON) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_ili9341) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/quandong-s3-dev/board_config.h b/components/mybot_platform/boards/quandong-s3-dev/board_config.h new file mode 100644 index 0000000..8855c4e --- /dev/null +++ b/components/mybot_platform/boards/quandong-s3-dev/board_config.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "quandong-s3-dev" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_4 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_7 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_8 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_16 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_15 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_1 +#define MYBOT_GENERIC_PA_ACTIVE_LEVEL 0 +#define MYBOT_GENERIC_PCA9557_ADDRESS 0x19 +#define MYBOT_GENERIC_PCA9557_OUTPUT_MASK 0xF8 +#define MYBOT_GENERIC_PCA9557_HIGH_MASK 0x00 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 3 +#define MYBOT_ILI9341_PROFILE 2 +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_11 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_12 +#define MYBOT_DISPLAY_DC GPIO_NUM_46 +#define MYBOT_DISPLAY_CS GPIO_NUM_10 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_RESET_ACTIVE_HIGH 1 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_45 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 320 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY true +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/quandong-s3-dev/sdkconfig.defaults b/components/mybot_platform/boards/quandong-s3-dev/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/quandong-s3-dev/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/rymcu-bigsmart/README.md b/components/mybot_platform/boards/rymcu-bigsmart/README.md new file mode 100644 index 0000000..e48fbec --- /dev/null +++ b/components/mybot_platform/boards/rymcu-bigsmart/README.md @@ -0,0 +1,15 @@ +# RYMCU BigSmart + +板型 ID 为 `rymcu-bigsmart`。 + +- 显示:320×240 ST7789,SPI3 mode 2,共享 LVGL UI。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 输入:GT911 触摸和 GPIO0 Boot。 +- 扩展:PCA9557 `0x19`,控制显示复位和 speaker PA。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +PCA9557 输出映射、GT911 坐标、ST7789 初始化、双 codec 路由、音频和电源仍需真机验证。 +摄像头和电池上报未实现。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/rymcu-bigsmart/board.c b/components/mybot_platform/boards/rymcu-bigsmart/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/rymcu-bigsmart/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/rymcu-bigsmart/board.cmake b/components/mybot_platform/boards/rymcu-bigsmart/board.cmake new file mode 100644 index 0000000..73f50ff --- /dev/null +++ b/components/mybot_platform/boards/rymcu-bigsmart/board.cmake @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "gt911") +set(MYBOT_GENERIC_PCA9557 ON) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/rymcu-bigsmart/board_config.h b/components/mybot_platform/boards/rymcu-bigsmart/board_config.h new file mode 100644 index 0000000..2df20ea --- /dev/null +++ b/components/mybot_platform/boards/rymcu-bigsmart/board_config.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "rymcu-bigsmart" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 3 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_14 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_13 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_12 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_45 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_1 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_2 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC +#define MYBOT_GENERIC_PCA9557_ADDRESS 0x19 +#define MYBOT_GENERIC_PCA9557_OUTPUT_MASK 0x03 +#define MYBOT_GENERIC_PCA9557_HIGH_MASK 0x00 +#define MYBOT_GENERIC_PCA9557_LOW_MASK 0x01 +#define MYBOT_GENERIC_PCA9557_PA_MASK 0x02 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_TOUCH_DRIVER_KIND 3 +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_NC +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_NC +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 1 +#define MYBOT_TOUCH_MIRROR_X 1 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_X_MAX 240 +#define MYBOT_TOUCH_Y_MAX 320 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_40 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_41 +#define MYBOT_DISPLAY_DC GPIO_NUM_39 +#define MYBOT_DISPLAY_CS GPIO_NUM_NC +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_42 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 1 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 320 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY true +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 2 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (80 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/rymcu-bigsmart/sdkconfig.defaults b/components/mybot_platform/boards/rymcu-bigsmart/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/rymcu-bigsmart/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/sp-esp32-s3-1.28-box/README.md b/components/mybot_platform/boards/sp-esp32-s3-1.28-box/README.md new file mode 100644 index 0000000..a91d746 --- /dev/null +++ b/components/mybot_platform/boards/sp-esp32-s3-1.28-box/README.md @@ -0,0 +1,15 @@ +# SP ESP32-S3 1.28 Box + +板型 ID 为 `sp-esp32-s3-1.28-box`。 + +- 显示:240×240 圆屏 GC9A01,SPI3,共享 LVGL 圆屏安全区布局。 +- 音频:ES8311,16 kHz 单声道 signed-16 PCM。 +- 输入:CST816S 兼容触摸、GPIO0 Boot;触摸使用独立 I2C1 GPIO11/7。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 +- 背光:GPIO42 LEDC,默认 60%。 + +触摸复位/中断、面板厂商初始化、颜色、方向、背光、音频和电源仍需真机验证。电池/充电上报 +未实现。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/sp-esp32-s3-1.28-box/board.c b/components/mybot_platform/boards/sp-esp32-s3-1.28-box/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/sp-esp32-s3-1.28-box/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/sp-esp32-s3-1.28-box/board.cmake b/components/mybot_platform/boards/sp-esp32-s3-1.28-box/board.cmake new file mode 100644 index 0000000..3987162 --- /dev/null +++ b/components/mybot_platform/boards/sp-esp32-s3-1.28-box/board.cmake @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "cst816s") +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_gc9a01) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/sp-esp32-s3-1.28-box/board_config.h b/components/mybot_platform/boards/sp-esp32-s3-1.28-box/board_config.h new file mode 100644 index 0000000..5768f36 --- /dev/null +++ b/components/mybot_platform/boards/sp-esp32-s3-1.28-box/board_config.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "sp-esp32-s3-1.28-box" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_DISPLAY_ROUND 1 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_45 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_10 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_8 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_15 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_14 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_46 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_GENERIC_TOUCH_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_TOUCH_I2C_SDA GPIO_NUM_11 +#define MYBOT_GENERIC_TOUCH_I2C_SCL GPIO_NUM_7 +#define MYBOT_TOUCH_DRIVER_KIND 1 +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_6 +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_12 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_PANEL_CONTROLLER 1 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_2 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_4 +#define MYBOT_DISPLAY_DC GPIO_NUM_47 +#define MYBOT_DISPLAY_CS GPIO_NUM_5 +#define MYBOT_DISPLAY_RESET GPIO_NUM_38 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_42 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 1 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/sp-esp32-s3-1.28-box/sdkconfig.defaults b/components/mybot_platform/boards/sp-esp32-s3-1.28-box/sdkconfig.defaults new file mode 100644 index 0000000..68a9fd4 --- /dev/null +++ b/components/mybot_platform/boards/sp-esp32-s3-1.28-box/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n +CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID=y diff --git a/components/mybot_platform/boards/taiji-pi-s3/README.md b/components/mybot_platform/boards/taiji-pi-s3/README.md new file mode 100644 index 0000000..cb913bf --- /dev/null +++ b/components/mybot_platform/boards/taiji-pi-s3/README.md @@ -0,0 +1,15 @@ +# Taiji Pi S3 + +板型 ID 为 `taiji-pi-s3`。 + +- 显示:360×360 圆屏 ST77916 QSPI,共享 LVGL 圆屏安全区布局。 +- 音频:I2S 播放和 PDM 采集;PDM 解调后直接提供 16 kHz 单声道 signed-16 PCM。 +- 输入:CST816S 触摸和 GPIO0 Boot;触摸使用独立 I2C1 GPIO7/8。 +- 静音/PA:GPIO48 低有效,随播放启停。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +ST77916 默认初始化、PDM 时钟/增益、I2S 播放、mute/PA、触摸、背光和板级电源仍需真机验证。 +完整板级电源时序尚未实现。 + +引脚见 [board_config.h](board_config.h);公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/taiji-pi-s3/board.c b/components/mybot_platform/boards/taiji-pi-s3/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/taiji-pi-s3/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/taiji-pi-s3/board.cmake b/components/mybot_platform/boards/taiji-pi-s3/board.cmake new file mode 100644 index 0000000..db9a1b1 --- /dev/null +++ b/components/mybot_platform/boards/taiji-pi-s3/board.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "raw") +set(MYBOT_GENERIC_DISPLAY "generic/qspi_st77916_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "cst816s") +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_st77916) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/taiji-pi-s3/board_config.h b/components/mybot_platform/boards/taiji-pi-s3/board_config.h new file mode 100644 index 0000000..41b2d00 --- /dev/null +++ b/components/mybot_platform/boards/taiji-pi-s3/board_config.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "taiji-pi-s3" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_DISPLAY_ROUND 1 +#define MYBOT_GENERIC_AUDIO_KIND 2 +#define MYBOT_GENERIC_TOUCH_KIND 1 +#define MYBOT_AUDIO_I2S_CAPTURE_PDM 1 +#define MYBOT_AUDIO_I2S_CONTROL_SPEAKER_POWER 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 80 +#define MYBOT_AUDIO_I2S_MIC_BCLK GPIO_NUM_42 +#define MYBOT_AUDIO_I2S_MIC_WS GPIO_NUM_45 +#define MYBOT_AUDIO_I2S_MIC_DIN GPIO_NUM_46 +#define MYBOT_AUDIO_I2S_SPK_BCLK GPIO_NUM_18 +#define MYBOT_AUDIO_I2S_SPK_WS GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_SPK_DOUT GPIO_NUM_17 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_48 +#define MYBOT_GENERIC_PA_ACTIVE_LEVEL 0 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_GENERIC_TOUCH_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_TOUCH_I2C_SDA GPIO_NUM_7 +#define MYBOT_GENERIC_TOUCH_I2C_SCL GPIO_NUM_8 +#define MYBOT_TOUCH_DRIVER_KIND 1 +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_40 +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_41 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_PCLK GPIO_NUM_9 +#define MYBOT_DISPLAY_DATA0 GPIO_NUM_11 +#define MYBOT_DISPLAY_DATA1 GPIO_NUM_12 +#define MYBOT_DISPLAY_DATA2 GPIO_NUM_13 +#define MYBOT_DISPLAY_DATA3 GPIO_NUM_14 +#define MYBOT_DISPLAY_CS GPIO_NUM_10 +#define MYBOT_DISPLAY_RESET GPIO_NUM_47 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_15 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 360 +#define MYBOT_DISPLAY_HEIGHT 360 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/taiji-pi-s3/sdkconfig.defaults b/components/mybot_platform/boards/taiji-pi-s3/sdkconfig.defaults new file mode 100644 index 0000000..24f50e9 --- /dev/null +++ b/components/mybot_platform/boards/taiji-pi-s3/sdkconfig.defaults @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID=y diff --git a/components/mybot_platform/boards/xingzhi-abs-2.0/README.md b/components/mybot_platform/boards/xingzhi-abs-2.0/README.md new file mode 100644 index 0000000..5c0e4ab --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-abs-2.0/README.md @@ -0,0 +1,15 @@ +# 行者 ABS 2.0 + +板型 ID 为 `xingzhi-abs-2.0`。当前只实现 Wi-Fi 子集,不包含蜂窝网络。 + +- 显示:240×240 ST7789,SPI3 mode 3,共享 LVGL UI。 +- 音频:ES8311,16 kHz 单声道 signed-16 PCM;I2C0 GPIO41/42。 +- 输入:GPIO0 Boot、GPIO45 音量加、GPIO2 音量减。 +- 扩展:PCA9557 `0x19`;GPIO47 进程级电源保持。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +SD 卡、振动、蜂窝、电池/充电、扩展电源路径、休眠和关机未实现。PCA9557、PA、面板、触摸 +能力与上电顺序仍需真机验证。 + +引脚以 [board_config.h](board_config.h) 为准;公共构建见[板卡索引](../README.md), +验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/xingzhi-abs-2.0/board.c b/components/mybot_platform/boards/xingzhi-abs-2.0/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-abs-2.0/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/xingzhi-abs-2.0/board.cmake b/components/mybot_platform/boards/xingzhi-abs-2.0/board.cmake new file mode 100644 index 0000000..e4214da --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-abs-2.0/board.cmake @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_PCA9557 ON) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/xingzhi-abs-2.0/board_config.h b/components/mybot_platform/boards/xingzhi-abs-2.0/board_config.h new file mode 100644 index 0000000..98ad26b --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-abs-2.0/board_config.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "xingzhi-abs-2.0" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_4 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_17 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_15 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_41 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_42 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_21 +#define MYBOT_GENERIC_POWER_GPIO GPIO_NUM_47 +#define MYBOT_GENERIC_PCA9557_ADDRESS 0x19 +#define MYBOT_GENERIC_PCA9557_OUTPUT_MASK 0x00 +#define MYBOT_GENERIC_PCA9557_HIGH_MASK 0x00 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_45 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_2 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_10 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_9 +#define MYBOT_DISPLAY_DC GPIO_NUM_8 +#define MYBOT_DISPLAY_CS GPIO_NUM_14 +#define MYBOT_DISPLAY_RESET GPIO_NUM_18 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_13 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 3 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (80 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/xingzhi-abs-2.0/sdkconfig.defaults b/components/mybot_platform/boards/xingzhi-abs-2.0/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-abs-2.0/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/README.md b/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/README.md new file mode 100644 index 0000000..1ce9258 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/README.md @@ -0,0 +1,17 @@ +# 行者 Cube 0.85 TFT Wi-Fi + +板型 ID 为 `xingzhi-cube-0.85tft-wifi`。硬件参数定义在本目录的 +[board_config.h](board_config.h)、[board.cmake](board.cmake) 和 +[sdkconfig.defaults](sdkconfig.defaults)。 + +- 显示:128×128 NV3023,SPI3,共享 LVGL 紧凑布局。 +- 音频:独立麦克风/扬声器 raw I2S,平台转换为 16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;无触摸和音量键。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 +- 背光:GPIO13 LEDC,默认 60%。 + +当前没有 codec 控制、触摸、电池、休眠或关机实现。面板初始化、方向、颜色、背光、音频和 +电源仍需真机验证。 + +公共构建和烧录步骤见[板卡索引](../README.md),验证状态见 +[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board.c b/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board.cmake b/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board.cmake new file mode 100644 index 0000000..da6e5ec --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board.cmake @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "raw") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_PCA9557 OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_nv3023) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board_config.h b/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board_config.h new file mode 100644 index 0000000..858c661 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/board_config.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "xingzhi-cube-0.85tft-wifi" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 2 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MIC_WS GPIO_NUM_4 +#define MYBOT_AUDIO_I2S_MIC_BCLK GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_MIC_DIN GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_SPK_DOUT GPIO_NUM_7 +#define MYBOT_AUDIO_I2S_SPK_BCLK GPIO_NUM_15 +#define MYBOT_AUDIO_I2S_SPK_WS GPIO_NUM_16 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define MYBOT_PANEL_CONTROLLER 2 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_10 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_9 +#define MYBOT_DISPLAY_DC GPIO_NUM_8 +#define MYBOT_DISPLAY_CS GPIO_NUM_14 +#define MYBOT_DISPLAY_RESET GPIO_NUM_18 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_13 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 128 +#define MYBOT_DISPLAY_HEIGHT 128 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/sdkconfig.defaults b/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/sdkconfig.defaults new file mode 100644 index 0000000..214c8f7 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-cube-0.85tft-wifi/sdkconfig.defaults @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/README.md b/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/README.md new file mode 100644 index 0000000..88362e9 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/README.md @@ -0,0 +1,15 @@ +# 行者 Cube 0.96 OLED Wi-Fi + +板型 ID 为 `xingzhi-cube-0.96oled-wifi`。该 profile 当前是 headless 子集,不编入 LVGL。 + +- 显示:未启用;SSD1306 单色 UI 尚未实现。 +- 音频:独立麦克风/扬声器 raw I2S,SDK 边界为 16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot、GPIO40 音量加、GPIO39 音量减。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 + +初始化、按键极性和音频仍需真机验证。引脚与构建能力以 +[board_config.h](board_config.h)、[board.cmake](board.cmake) 和 +[sdkconfig.defaults](sdkconfig.defaults) 为准。 + +公共构建和烧录步骤见[板卡索引](../README.md),验证状态见 +[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board.c b/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board.cmake b/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board.cmake new file mode 100644 index 0000000..541e956 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board.cmake @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "raw") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_PCA9557 OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board_config.h b/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board_config.h new file mode 100644 index 0000000..648e1a8 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/board_config.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" + +#define MYBOT_BOARD_NAME "xingzhi-cube-0.96oled-wifi" +#define MYBOT_BOARD_HAS_DISPLAY 0 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 2 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MIC_WS GPIO_NUM_4 +#define MYBOT_AUDIO_I2S_MIC_BCLK GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_MIC_DIN GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_SPK_DOUT GPIO_NUM_7 +#define MYBOT_AUDIO_I2S_SPK_BCLK GPIO_NUM_15 +#define MYBOT_AUDIO_I2S_SPK_WS GPIO_NUM_16 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/sdkconfig.defaults b/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/sdkconfig.defaults new file mode 100644 index 0000000..214c8f7 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-cube-0.96oled-wifi/sdkconfig.defaults @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/README.md b/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/README.md new file mode 100644 index 0000000..0e8a4a1 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/README.md @@ -0,0 +1,15 @@ +# 行者 Metal 1.54 Wi-Fi + +板型 ID 为 `xingzhi-metal-1.54-wifi`。引脚和能力以 [board_config.h](board_config.h)、 +[board.cmake](board.cmake) 与 [sdkconfig.defaults](sdkconfig.defaults) 为准。 + +- 显示:240×240 ST7789,SPI3 mode 3,共享 LVGL UI。 +- 音频:ES8311,16 kHz 单声道 signed-16 PCM;I2C0 GPIO41/42。 +- 输入:PCA9557 `0x19`、CST816S 触摸和 GPIO0 Boot。 +- 存储:16 MB QIO Flash profile、80 MHz Octal PSRAM、双 OTA 分区。 +- 电源:GPIO48 进程级电源保持;实际极性和上电时序待真机确认。 + +当前未实现电池/充电上报、休眠和关机;PCA9557 电源/复位、触摸、PA 和面板仍需真机验证。 + +公共构建、烧录和验证入口见[板卡索引](../README.md)与 +[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board.c b/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board.cmake b/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board.cmake new file mode 100644 index 0000000..f46d7ec --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board.cmake @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_PCA9557 ON) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board_config.h b/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board_config.h new file mode 100644 index 0000000..fd4af66 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/board_config.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "xingzhi-metal-1.54-wifi" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_4 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_17 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_15 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_41 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_42 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_21 +#define MYBOT_GENERIC_POWER_GPIO GPIO_NUM_48 +#define MYBOT_GENERIC_PCA9557_ADDRESS 0x19 +#define MYBOT_GENERIC_PCA9557_OUTPUT_MASK 0x00 +#define MYBOT_GENERIC_PCA9557_HIGH_MASK 0x00 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_NC +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_NC +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_10 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_9 +#define MYBOT_DISPLAY_DC GPIO_NUM_8 +#define MYBOT_DISPLAY_CS GPIO_NUM_14 +#define MYBOT_DISPLAY_RESET GPIO_NUM_18 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_13 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 3 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (80 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/sdkconfig.defaults b/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/xingzhi-metal-1.54-wifi/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/src/drivers/audio/es8311_codec_audio.c b/components/mybot_platform/src/drivers/audio/es8311_codec_audio.c index ff9ee40..6b0b352 100644 --- a/components/mybot_platform/src/drivers/audio/es8311_codec_audio.c +++ b/components/mybot_platform/src/drivers/audio/es8311_codec_audio.c @@ -8,7 +8,11 @@ #include #include "driver/i2s_std.h" +#if MYBOT_GENERIC_AUDIO_KIND == 4 +#include "audio/es8374_codec.h" +#else #include "es8311_codec.h" +#endif #include "esp_codec_dev.h" #include "esp_codec_dev_defaults.h" #include "esp_err.h" @@ -33,7 +37,7 @@ #define TAG "es8311_audio" #if MYBOT_AUDIO_SAMPLE_RATE != 16000 -#error "ES8311 audio must expose 16 kHz PCM to mybot" +#error "Onboard codec audio must expose 16 kHz PCM to mybot" #endif #if MYBOT_AUDIO_DEFAULT_VOLUME < MYBOT_AUDIO_VOLUME_MIN || \ @@ -372,6 +376,16 @@ static int initialize_codec_locked(void) { goto codec_error; } +#if MYBOT_GENERIC_AUDIO_KIND == 4 + es8374_codec_cfg_t codec_config = { + .ctrl_if = s_audio.ctrl_if, + .gpio_if = s_audio.gpio_if, + .codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH, + .master_mode = false, + .pa_pin = GPIO_NUM_NC, + .pa_reverted = false, + }; +#else es8311_codec_cfg_t codec_config = { .ctrl_if = s_audio.ctrl_if, .gpio_if = s_audio.gpio_if, @@ -388,7 +402,12 @@ static int initialize_codec_locked(void) { .no_dac_ref = true, .mclk_div = 256, }; +#endif +#if MYBOT_GENERIC_AUDIO_KIND == 4 + s_audio.codec_if = es8374_codec_new(&codec_config); +#else s_audio.codec_if = es8311_codec_new(&codec_config); +#endif if (!s_audio.codec_if) { goto codec_error; } diff --git a/components/mybot_platform/src/drivers/audio/es8374_codec.c b/components/mybot_platform/src/drivers/audio/es8374_codec.c new file mode 100644 index 0000000..3cc12c7 --- /dev/null +++ b/components/mybot_platform/src/drivers/audio/es8374_codec.c @@ -0,0 +1,742 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "audio/es8374_codec.h" +#include "esp_log.h" +#include "esp_codec_dev_vol.h" +#include "audio/es_common.h" + +#define TAG "ES8374" + +typedef struct { + audio_codec_if_t base; + es8374_codec_cfg_t cfg; + bool is_open; + bool enabled; +} audio_codec_es8374_t; + +static const esp_codec_dev_vol_range_t vol_range = { + .min_vol = + { + .vol = 0xC0, + .db_value = -96.0, + }, + .max_vol = + { + .vol = 0x0, + .db_value = 0.0, + }, +}; + +static int es8374_write_reg(audio_codec_es8374_t *codec, int reg, int value) { + return codec->cfg.ctrl_if->write_reg(codec->cfg.ctrl_if, reg, 1, &value, 1); +} + +static int es8374_read_reg(audio_codec_es8374_t *codec, int reg, int *value) { + *value = 0; + return codec->cfg.ctrl_if->read_reg(codec->cfg.ctrl_if, reg, 1, value, 1); +} + +static int es8374_set_voice_mute(audio_codec_es8374_t *codec, bool enable) { + int ret = 0; + int reg = 0; + ret |= es8374_read_reg(codec, 0x36, ®); + if (ret == 0) { + reg = reg & 0xdf; + ret |= es8374_write_reg(codec, 0x36, reg | (((int)enable) << 5)); + } + return ret; +} + +static es_bits_length_t get_bits(uint8_t bits) { + switch (bits) { + default: + case 16: + return BIT_LENGTH_16BITS; + case 18: + return BIT_LENGTH_18BITS; + case 20: + return BIT_LENGTH_20BITS; + case 24: + return BIT_LENGTH_24BITS; + case 32: + return BIT_LENGTH_32BITS; + } +} + +static int es8374_set_bits_per_sample(audio_codec_es8374_t *codec, uint8_t bits) { + int ret = ESP_CODEC_DEV_OK; + int reg = 0; + es_bits_length_t bit_per_sample = get_bits(bits); + bits = (int)bit_per_sample & 0x0f; + + if (codec->cfg.codec_mode & ESP_CODEC_DEV_WORK_MODE_ADC) { + ret |= es8374_read_reg(codec, 0x10, ®); + if (ret == 0) { + reg = reg & 0xe3; + ret |= es8374_write_reg(codec, 0x10, reg | (bits << 2)); + } + } + if (codec->cfg.codec_mode & ESP_CODEC_DEV_WORK_MODE_DAC) { + ret |= es8374_read_reg(codec, 0x11, ®); + if (ret == 0) { + reg = reg & 0xe3; + ret |= es8374_write_reg(codec, 0x11, reg | (bits << 2)); + } + } + return ret; +} + +static int _set_mic_gain(audio_codec_es8374_t *codec, float gain) { + int ret = 0; + if (gain >= 0 && gain < 24) { + int gain_n = 0; + gain_n = (int)gain / 3; + ret = es8374_write_reg(codec, 0x22, gain_n | (gain_n << 4)); // MIC PGA + } else { + ret = -1; + } + return ret; +} + +static int es8374_i2s_config_clock(audio_codec_es8374_t *codec, es_i2s_clock_t cfg) { + int ret = 0; + int reg = 0; + ret |= es8374_read_reg(codec, 0x0f, ®); // power up adc and input + reg &= 0xe0; + int divratio = 0; + switch (cfg.sclk_div) { + case MCLK_DIV_1: + divratio = 1; + break; + case MCLK_DIV_2: // = 2, + divratio = 2; + break; + case MCLK_DIV_3: // = 3, + divratio = 3; + break; + case MCLK_DIV_4: // = 4, + divratio = 4; + break; + case MCLK_DIV_5: // = 20, + divratio = 5; + break; + case MCLK_DIV_6: // = 5, + divratio = 6; + break; + case MCLK_DIV_7: // = 29, + divratio = 7; + break; + case MCLK_DIV_8: // = 6, + divratio = 8; + break; + case MCLK_DIV_9: // = 7, + divratio = 9; + break; + case MCLK_DIV_10: // = 21, + divratio = 10; + break; + case MCLK_DIV_11: // = 8, + divratio = 11; + break; + case MCLK_DIV_12: // = 9, + divratio = 12; + break; + case MCLK_DIV_13: // = 30, + divratio = 13; + break; + case MCLK_DIV_14: // = 31 + divratio = 14; + break; + case MCLK_DIV_15: // = 22, + divratio = 15; + break; + case MCLK_DIV_16: // = 10, + divratio = 16; + break; + case MCLK_DIV_17: // = 23, + divratio = 17; + break; + case MCLK_DIV_18: // = 11, + divratio = 18; + break; + case MCLK_DIV_20: // = 24, + divratio = 19; + break; + case MCLK_DIV_22: // = 12, + divratio = 20; + break; + case MCLK_DIV_24: // = 13, + divratio = 21; + break; + case MCLK_DIV_25: // = 25, + divratio = 22; + break; + case MCLK_DIV_30: // = 26, + divratio = 23; + break; + case MCLK_DIV_32: // = 27, + divratio = 24; + break; + case MCLK_DIV_33: // = 14, + divratio = 25; + break; + case MCLK_DIV_34: // = 28, + divratio = 26; + break; + case MCLK_DIV_36: // = 15, + divratio = 27; + break; + case MCLK_DIV_44: // = 16, + divratio = 28; + break; + case MCLK_DIV_48: // = 17, + divratio = 29; + break; + case MCLK_DIV_66: // = 18, + divratio = 30; + break; + case MCLK_DIV_72: // = 19, + divratio = 31; + break; + default: + break; + } + reg |= divratio; + ret |= es8374_write_reg(codec, 0x0f, reg); + + int dacratio_l = 0; + int dacratio_h = 0; + + switch (cfg.lclk_div) { + case LCLK_DIV_128: + dacratio_l = 128 % 256; + dacratio_h = 128 / 256; + break; + case LCLK_DIV_192: + dacratio_l = 192 % 256; + dacratio_h = 192 / 256; + break; + case LCLK_DIV_256: + dacratio_l = 256 % 256; + dacratio_h = 256 / 256; + break; + case LCLK_DIV_384: + dacratio_l = 384 % 256; + dacratio_h = 384 / 256; + break; + case LCLK_DIV_512: + dacratio_l = 512 % 256; + dacratio_h = 512 / 256; + break; + case LCLK_DIV_576: + dacratio_l = 576 % 256; + dacratio_h = 576 / 256; + break; + case LCLK_DIV_768: + dacratio_l = 768 % 256; + dacratio_h = 768 / 256; + break; + case LCLK_DIV_1024: + dacratio_l = 1024 % 256; + dacratio_h = 1024 / 256; + break; + case LCLK_DIV_1152: + dacratio_l = 1152 % 256; + dacratio_h = 1152 / 256; + break; + case LCLK_DIV_1408: + dacratio_l = 1408 % 256; + dacratio_h = 1408 / 256; + break; + case LCLK_DIV_1536: + dacratio_l = 1536 % 256; + dacratio_h = 1536 / 256; + break; + case LCLK_DIV_2112: + dacratio_l = 2112 % 256; + dacratio_h = 2112 / 256; + break; + case LCLK_DIV_2304: + dacratio_l = 2304 % 256; + dacratio_h = 2304 / 256; + break; + case LCLK_DIV_125: + dacratio_l = 125 % 256; + dacratio_h = 125 / 256; + break; + case LCLK_DIV_136: + dacratio_l = 136 % 256; + dacratio_h = 136 / 256; + break; + case LCLK_DIV_250: + dacratio_l = 250 % 256; + dacratio_h = 250 / 256; + break; + case LCLK_DIV_272: + dacratio_l = 272 % 256; + dacratio_h = 272 / 256; + break; + case LCLK_DIV_375: + dacratio_l = 375 % 256; + dacratio_h = 375 / 256; + break; + case LCLK_DIV_500: + dacratio_l = 500 % 256; + dacratio_h = 500 / 256; + break; + case LCLK_DIV_544: + dacratio_l = 544 % 256; + dacratio_h = 544 / 256; + break; + case LCLK_DIV_750: + dacratio_l = 750 % 256; + dacratio_h = 750 / 256; + break; + case LCLK_DIV_1000: + dacratio_l = 1000 % 256; + dacratio_h = 1000 / 256; + break; + case LCLK_DIV_1088: + dacratio_l = 1088 % 256; + dacratio_h = 1088 / 256; + break; + case LCLK_DIV_1496: + dacratio_l = 1496 % 256; + dacratio_h = 1496 / 256; + break; + case LCLK_DIV_1500: + dacratio_l = 1500 % 256; + dacratio_h = 1500 / 256; + break; + default: + break; + } + ret |= es8374_write_reg(codec, 0x06, dacratio_h); // ADCFsMode, single SPEED, RATIO=256 + ret |= es8374_write_reg(codec, 0x07, dacratio_l); // ADCFsMode, single SPEED, RATIO=256 + + return ret; +} + +static int es8374_set_d2se_pga(audio_codec_es8374_t *codec, es_d2se_pga_t gain) { + int ret = 0; + int reg = 0; + if (gain > D2SE_PGA_GAIN_MIN && gain < D2SE_PGA_GAIN_MAX) { + ret = es8374_read_reg(codec, 0x21, ®); + reg &= 0xfb; + reg |= gain << 2; + ret = es8374_write_reg(codec, 0x21, reg); // MIC PGA + } + return ret; +} + +static int es8374_config_fmt(audio_codec_es8374_t *codec, es_i2s_fmt_t fmt) { + int ret = 0; + int reg = 0; + int fmt_i2s = fmt & 0x0f; + if (codec->cfg.codec_mode & ESP_CODEC_DEV_WORK_MODE_ADC) { + ret |= es8374_read_reg(codec, 0x10, ®); + if (ret == 0) { + reg = reg & 0xfc; + ret |= es8374_write_reg(codec, 0x10, reg | fmt_i2s); + } + } + if (codec->cfg.codec_mode & ESP_CODEC_DEV_WORK_MODE_DAC) { + ret |= es8374_read_reg(codec, 0x11, ®); + if (ret == 0) { + reg = reg & 0xfc; + ret |= es8374_write_reg(codec, 0x11, reg | (fmt_i2s)); + } + } + return ret; +} + +static int es8374_config_dac_output(audio_codec_es8374_t *codec, es_dac_output_t output) { + int ret = 0; + int reg = 0; + ret = es8374_write_reg(codec, 0x1d, 0x02); + ret |= es8374_read_reg(codec, 0x1c, ®); // set spk mixer + reg |= 0x80; + ret |= es8374_write_reg(codec, 0x1c, reg); + ret |= es8374_write_reg(codec, 0x1D, 0x02); // spk set + ret |= es8374_write_reg(codec, 0x1F, 0x00); // spk set + ret |= es8374_write_reg(codec, 0x1E, 0xA0); // spk on + return ret; +} + +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) | 0x14; + ret |= es8374_write_reg(codec, 0x21, reg); + } + return ret; +} + +static int es8374_set_adc_dac_volume(audio_codec_es8374_t *codec, esp_codec_dec_work_mode_t mode, + float db_value) { + int reg = esp_codec_dev_vol_calc_reg(&vol_range, db_value); + int ret = ESP_CODEC_DEV_OK; + if (mode & ESP_CODEC_DEV_WORK_MODE_ADC) { + ret = es8374_write_reg(codec, 0x25, (uint8_t)reg); + } + if (mode & ESP_CODEC_DEV_WORK_MODE_DAC) { + ret = es8374_write_reg(codec, 0x38, (uint8_t)reg); + } + return ret; +} + +static int es8374_init_reg(audio_codec_es8374_t *codec, es_i2s_fmt_t fmt, es_i2s_clock_t cfg, + es_dac_output_t out_channel, es_adc_input_t in_channel) { + int ret = 0; + int reg = 0; + + ret |= es8374_write_reg(codec, 0x00, 0x3F); // IC Rst start + ret |= es8374_write_reg(codec, 0x00, 0x03); // IC Rst stop + ret |= es8374_write_reg(codec, 0x01, 0x7F); // IC clk on + + ret |= es8374_read_reg(codec, 0x0F, ®); + reg &= 0x7f; + + reg |= (codec->cfg.master_mode << 7); + ret |= es8374_write_reg(codec, 0x0f, reg); // CODEC IN I2S SLAVE MODE + + ret |= es8374_write_reg(codec, 0x6F, 0xA0); // pll set:mode enable + ret |= es8374_write_reg(codec, 0x72, 0x41); // pll set:mode set + ret |= es8374_write_reg(codec, 0x09, 0x01); // pll set:reset on ,set start + ret |= es8374_write_reg(codec, 0x0C, 0x22); // pll set:k + ret |= es8374_write_reg(codec, 0x0D, 0x2E); // pll set:k + ret |= es8374_write_reg(codec, 0x0E, 0xC6); // pll set:k + ret |= es8374_write_reg(codec, 0x0A, 0x3A); // pll set: + ret |= es8374_write_reg(codec, 0x0B, 0x07); // pll set:n + ret |= es8374_write_reg(codec, 0x09, 0x41); // pll set:reset off ,set stop + + ret |= es8374_i2s_config_clock(codec, cfg); + + ret |= es8374_write_reg(codec, 0x24, 0x08); // adc set + ret |= es8374_write_reg(codec, 0x36, 0x00); // dac set + ret |= es8374_write_reg(codec, 0x12, 0x30); // timing set + ret |= es8374_write_reg(codec, 0x13, 0x20); // timing set + + ret |= es8374_config_fmt(codec, fmt); + + ret |= es8374_write_reg(codec, 0x21, 0x50); // adc set: SEL LIN1 CH+PGAGAIN=0DB + ret |= es8374_write_reg(codec, 0x22, 0xFF); // adc set: PGA GAIN=0DB + ret |= es8374_write_reg(codec, 0x21, 0x14); // adc set: SEL LIN1 CH+PGAGAIN=18DB + ret |= es8374_write_reg(codec, 0x22, 0x55); // pga = +15db + ret |= es8374_write_reg( + codec, 0x08, + 0x21); // set class d divider = 33, to avoid the high frequency tone on laudspeaker + ret |= es8374_write_reg(codec, 0x00, 0x80); // IC START + + ret |= es8374_set_adc_dac_volume(codec, ESP_CODEC_DEV_WORK_MODE_ADC, 0.0); // 0db + + ret |= es8374_write_reg(codec, 0x14, 0x8A); // IC START + ret |= es8374_write_reg(codec, 0x15, 0x40); // IC START + ret |= es8374_write_reg(codec, 0x1A, 0xA0); // monoout set + ret |= es8374_write_reg(codec, 0x1B, 0x19); // monoout set + ret |= es8374_write_reg(codec, 0x1C, 0x90); // spk set + ret |= es8374_write_reg(codec, 0x1D, 0x01); // spk set + ret |= es8374_write_reg(codec, 0x1F, 0x00); // spk set + ret |= es8374_write_reg(codec, 0x1E, 0x20); // spk on + ret |= es8374_write_reg(codec, 0x28, 0x00); // alc set + ret |= es8374_write_reg(codec, 0x25, 0x00); // ADCVOLUME on + ret |= es8374_write_reg(codec, 0x38, 0x00); // DACVOLUME on + ret |= es8374_write_reg(codec, 0x37, 0x30); // dac set + ret |= es8374_write_reg(codec, 0x6D, 0x60); // SEL:GPIO1=DMIC CLK OUT+SEL:GPIO2=PLL CLK OUT + ret |= es8374_write_reg(codec, 0x71, 0x05); // for automute setting + ret |= es8374_write_reg(codec, 0x73, 0x70); + + ret |= es8374_config_dac_output(codec, out_channel); // 0x3c Enable DAC and Enable Lout/Rout/1/2 + ret |= es8374_config_adc_input(codec, in_channel); // 0x00 LINSEL & RINSEL + ret |= es8374_set_adc_dac_volume(codec, ESP_CODEC_DEV_WORK_MODE_DAC, -96.0); + ret |= es8374_write_reg(codec, 0x37, 0x00); // dac set + return ret; +} + +static int es8374_stop(audio_codec_es8374_t *codec) { + int ret = 0; + int reg = 0; + if (codec->cfg.codec_mode == ESP_CODEC_DEV_WORK_MODE_LINE) { + ret |= es8374_read_reg(codec, 0x1a, ®); // disable lout + reg |= 0x08; + ret |= es8374_write_reg(codec, 0x1a, reg); + reg &= 0x9f; + ret |= es8374_write_reg(codec, 0x1a, reg); + ret |= es8374_write_reg(codec, 0x1D, 0x12); // mute speaker + ret |= es8374_write_reg(codec, 0x1E, 0x20); // disable class d + ret |= es8374_read_reg(codec, 0x1c, ®); // disable spkmixer + reg &= 0xbf; + ret |= es8374_write_reg(codec, 0x1c, reg); + ret |= es8374_write_reg(codec, 0x1F, 0x00); // spk set + } + if (codec->cfg.codec_mode & ESP_CODEC_DEV_WORK_MODE_DAC) { + ret |= es8374_set_voice_mute(codec, true); + + ret |= es8374_read_reg(codec, 0x1a, ®); // disable lout + reg |= 0x08; + ret |= es8374_write_reg(codec, 0x1a, reg); + reg &= 0xdf; + ret |= es8374_write_reg(codec, 0x1a, reg); + ret |= es8374_write_reg(codec, 0x1D, 0x12); // mute speaker + ret |= es8374_write_reg(codec, 0x1E, 0x20); // disable class d + ret |= es8374_read_reg(codec, 0x15, ®); // power up dac + reg |= 0x20; + ret |= es8374_write_reg(codec, 0x15, reg); + } + if (codec->cfg.codec_mode & ESP_CODEC_DEV_WORK_MODE_ADC) { + ret |= es8374_read_reg(codec, 0x10, ®); // power up adc and input + reg |= 0xc0; + ret |= es8374_write_reg(codec, 0x10, reg); + ret |= es8374_read_reg(codec, 0x21, ®); // power up adc and input + reg |= 0xc0; + ret |= es8374_write_reg(codec, 0x21, reg); + } + return ret; +} + +static int es8374_start(audio_codec_es8374_t *codec) { + int ret = ESP_CODEC_DEV_OK; + int reg = 0; + bool mode_line = (codec->cfg.codec_mode == ESP_CODEC_DEV_WORK_MODE_LINE); + if (mode_line) { + ret |= es8374_read_reg(codec, 0x1a, ®); // set monomixer + reg |= 0x60; + reg |= 0x20; + reg &= 0xf7; + ret |= es8374_write_reg(codec, 0x1a, reg); + ret |= es8374_read_reg(codec, 0x1c, ®); // set spk mixer + reg |= 0x40; + ret |= es8374_write_reg(codec, 0x1c, reg); + ret |= es8374_write_reg(codec, 0x1D, 0x02); // spk set + ret |= es8374_write_reg(codec, 0x1F, 0x00); // spk set + ret |= es8374_write_reg(codec, 0x1E, 0xA0); // spk on + } + if (mode_line || (codec->cfg.codec_mode & ESP_CODEC_DEV_WORK_MODE_ADC)) { + ret |= es8374_read_reg(codec, 0x21, ®); // power up adc and input + reg &= 0x3f; + ret |= es8374_write_reg(codec, 0x21, reg); + ret |= es8374_read_reg(codec, 0x10, ®); // power up adc and input + reg &= 0x3f; + ret |= es8374_write_reg(codec, 0x10, reg); + } + + if (mode_line || (codec->cfg.codec_mode & ESP_CODEC_DEV_WORK_MODE_DAC)) { + ret |= es8374_read_reg(codec, 0x1a, ®); // disable lout + reg |= 0x08; + ret |= es8374_write_reg(codec, 0x1a, reg); + reg &= 0xdf; + ret |= es8374_write_reg(codec, 0x1a, reg); + ret |= es8374_write_reg(codec, 0x1D, 0x12); // mute speaker + ret |= es8374_write_reg(codec, 0x1E, 0x20); // disable class d + ret |= es8374_read_reg(codec, 0x15, ®); // power up dac + reg &= 0xdf; + ret |= es8374_write_reg(codec, 0x15, reg); + ret |= es8374_read_reg(codec, 0x1a, ®); // disable lout + reg |= 0x20; + ret |= es8374_write_reg(codec, 0x1a, reg); + reg &= 0xf7; + ret |= es8374_write_reg(codec, 0x1a, reg); + ret |= es8374_write_reg(codec, 0x1D, 0x02); // mute speaker + ret |= es8374_write_reg(codec, 0x1E, 0xa0); // disable class d + ret |= es8374_set_voice_mute(codec, false); + } + return ret; +} + +static int es8374_set_mute(const audio_codec_if_t *h, bool mute) { + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)h; + if (codec == NULL || codec->is_open == false) { + return ESP_CODEC_DEV_INVALID_ARG; + } + return es8374_set_voice_mute(codec, mute); +} + +static int es8374_set_vol(const audio_codec_if_t *h, float db_value) { + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)h; + if (codec == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open == false) { + return ESP_CODEC_DEV_WRONG_STATE; + } + return es8374_set_adc_dac_volume(codec, ESP_CODEC_DEV_WORK_MODE_DAC, db_value); +} + +static int es8374_set_mic_gain(const audio_codec_if_t *h, float db) { + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)h; + if (codec == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open == false) { + return ESP_CODEC_DEV_WRONG_STATE; + } + int ret = _set_mic_gain(codec, db); + return ret == 0 ? ESP_CODEC_DEV_OK : ESP_CODEC_DEV_WRITE_FAIL; +} + +static void es8374_pa_power(audio_codec_es8374_t *codec, bool enable) { + int16_t pa_pin = codec->cfg.pa_pin; + if (pa_pin == -1 || codec->cfg.gpio_if == NULL) { + return; + } + codec->cfg.gpio_if->setup(pa_pin, AUDIO_GPIO_DIR_OUT, AUDIO_GPIO_MODE_FLOAT); + codec->cfg.gpio_if->set(pa_pin, codec->cfg.pa_reverted ? !enable : enable); +} + +static int es8374_open(const audio_codec_if_t *h, void *cfg, int cfg_size) { + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)h; + es8374_codec_cfg_t *codec_cfg = (es8374_codec_cfg_t *)cfg; + if (codec == NULL || codec_cfg == NULL || codec_cfg->ctrl_if == NULL || + cfg_size != sizeof(es8374_codec_cfg_t)) { + return ESP_CODEC_DEV_INVALID_ARG; + } + int ret = ESP_CODEC_DEV_OK; + memcpy(&codec->cfg, codec_cfg, sizeof(es8374_codec_cfg_t)); + es8374_pa_power(codec, false); + es_i2s_clock_t clkdiv; + clkdiv.lclk_div = LCLK_DIV_256; + clkdiv.sclk_div = MCLK_DIV_4; + ret |= es8374_stop(codec); + ret |= es8374_init_reg(codec, (BIT_LENGTH_16BITS << 4) | ES_I2S_NORMAL, clkdiv, DAC_OUTPUT_ALL, + ADC_INPUT_LINPUT1_RINPUT1); + ret |= _set_mic_gain(codec, 15); + ret |= es8374_set_d2se_pga(codec, D2SE_PGA_GAIN_EN); + ret |= es8374_config_fmt(codec, ES_I2S_NORMAL); + if (ret != 0) { + return ESP_CODEC_DEV_WRITE_FAIL; + } + codec->is_open = true; + return ESP_CODEC_DEV_OK; +} + +static int es8374_close(const audio_codec_if_t *h) { + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)h; + if (codec == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open) { + es8374_pa_power(codec, false); + es8374_stop(codec); + es8374_write_reg(codec, 0x00, 0x7F); // IC Reset and STOP + codec->is_open = false; + } + return ESP_CODEC_DEV_OK; +} + +static int es8374_set_fs(const audio_codec_if_t *h, esp_codec_dev_sample_info_t *fs) { + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)h; + if (codec == NULL || codec->is_open == false) { + return ESP_CODEC_DEV_INVALID_ARG; + } + int ret = 0; + ret |= es8374_config_fmt(codec, ES_I2S_NORMAL); + ret |= es8374_set_bits_per_sample(codec, fs->bits_per_sample); + return ESP_CODEC_DEV_OK; +} + +static int es8374_enable(const audio_codec_if_t *h, bool enable) { + int ret = ESP_CODEC_DEV_OK; + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)h; + if (codec == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open == false) { + return ESP_CODEC_DEV_WRONG_STATE; + } + if (codec->enabled == enable) { + return ESP_CODEC_DEV_OK; + } + if (enable) { + ret = es8374_start(codec); + es8374_pa_power(codec, true); + } else { + es8374_pa_power(codec, false); + ret = es8374_stop(codec); + es8374_write_reg(codec, 0x00, 0x7F); // IC Reset and STOP + } + if (ret == ESP_CODEC_DEV_OK) { + codec->enabled = enable; + ESP_LOGD(TAG, "Codec is %s", enable ? "enable" : "disable"); + } + return ret; +} + +static int es8374_set_reg(const audio_codec_if_t *h, int reg, int value) { + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)h; + if (codec == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open == false) { + return ESP_CODEC_DEV_WRONG_STATE; + } + return es8374_write_reg(codec, reg, value); +} + +static int es8374_get_reg(const audio_codec_if_t *h, int reg, int *value) { + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)h; + if (codec == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open == false) { + return ESP_CODEC_DEV_WRONG_STATE; + } + return es8374_read_reg(codec, reg, value); +} + +static void es8374_dump(const audio_codec_if_t *h) { + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)h; + if (codec == NULL || codec->is_open == false) { + return; + } + for (int i = 0; i < 50; i++) { + int value = 0; + int ret = es8374_read_reg(codec, i, &value); + if (ret != ESP_CODEC_DEV_OK) { + break; + } + ESP_LOGI(TAG, "%02x: %02x", i, value); + } +} + +const audio_codec_if_t *es8374_codec_new(es8374_codec_cfg_t *codec_cfg) { + if (codec_cfg == NULL || codec_cfg->ctrl_if == NULL) { + ESP_LOGE(TAG, "Wrong codec config"); + return NULL; + } + if (codec_cfg->ctrl_if->is_open(codec_cfg->ctrl_if) == false) { + ESP_LOGE(TAG, "Control interface not open yet"); + return NULL; + } + audio_codec_es8374_t *codec = (audio_codec_es8374_t *)calloc(1, sizeof(audio_codec_es8374_t)); + if (codec == NULL) { + CODEC_MEM_CHECK(codec); + return NULL; + } + codec->base.open = es8374_open; + codec->base.enable = es8374_enable; + codec->base.set_fs = es8374_set_fs; + codec->base.mute = es8374_set_mute; + codec->base.set_vol = es8374_set_vol; + codec->base.set_mic_gain = es8374_set_mic_gain; + codec->base.set_reg = es8374_set_reg; + codec->base.get_reg = es8374_get_reg; + codec->base.dump_reg = es8374_dump, codec->base.close = es8374_close; + do { + int ret = codec->base.open(&codec->base, codec_cfg, sizeof(es8374_codec_cfg_t)); + if (ret != 0) { + ESP_LOGE(TAG, "Open fail"); + break; + } + return &codec->base; + } while (0); + if (codec) { + free(codec); + } + return NULL; +} diff --git a/components/mybot_platform/src/drivers/audio/raw_i2s_audio.c b/components/mybot_platform/src/drivers/audio/raw_i2s_audio.c index 4d403ac..47157e3 100644 --- a/components/mybot_platform/src/drivers/audio/raw_i2s_audio.c +++ b/components/mybot_platform/src/drivers/audio/raw_i2s_audio.c @@ -5,6 +5,7 @@ #include #include +#include "driver/i2s_pdm.h" #include "driver/i2s_std.h" #include "esp_err.h" #include "esp_log.h" @@ -34,6 +35,18 @@ #define MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS 0 #endif +#ifndef MYBOT_AUDIO_I2S_CAPTURE_PDM +#define MYBOT_AUDIO_I2S_CAPTURE_PDM 0 +#endif + +#ifndef MYBOT_AUDIO_I2S_CONTROL_SPEAKER_POWER +#define MYBOT_AUDIO_I2S_CONTROL_SPEAKER_POWER 0 +#endif + +#if MYBOT_AUDIO_I2S_CONTROL_SPEAKER_POWER +int mybot_generic_set_speaker_power(bool enabled); +#endif + #if MYBOT_AUDIO_I2S_PLAYBACK_BOTH_SLOTS #define AUDIO_SCRATCH_FRAMES (AUDIO_MAX_FRAMES * 2) #else @@ -268,7 +281,8 @@ static int capture_init(void **out_ctx, int rate, int channels, int bits) { return -1; } - i2s_chan_config_t channel_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER); + i2s_chan_config_t channel_config = I2S_CHANNEL_DEFAULT_CONFIG( + MYBOT_AUDIO_I2S_CAPTURE_PDM ? I2S_NUM_0 : I2S_NUM_1, I2S_ROLE_MASTER); channel_config.dma_desc_num = 6; channel_config.dma_frame_num = 240; if (i2s_new_channel(&channel_config, NULL, &ctx->channel) != ESP_OK) { @@ -276,10 +290,33 @@ static int capture_init(void **out_ctx, int rate, int channels, int bits) { return -1; } - i2s_std_config_t std_config = +#if MYBOT_AUDIO_I2S_CAPTURE_PDM + if (!SOC_I2S_SUPPORTS_PDM_RX) { + i2s_del_channel(ctx->channel); + free(ctx); + return -1; + } + const i2s_pdm_rx_config_t pdm_config = { + .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)rate), + .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .gpio_cfg = + { + .clk = MYBOT_AUDIO_I2S_MIC_BCLK, + .din = MYBOT_AUDIO_I2S_MIC_DIN, + .invert_flags = + { + .clk_inv = false, + }, + }, + }; + const esp_err_t result = i2s_channel_init_pdm_rx_mode(ctx->channel, &pdm_config); +#else + const i2s_std_config_t std_config = make_i2s_config(rate, MYBOT_AUDIO_I2S_MIC_BCLK, MYBOT_AUDIO_I2S_MIC_WS, I2S_GPIO_UNUSED, MYBOT_AUDIO_I2S_MIC_DIN, false, MYBOT_AUDIO_I2S_CAPTURE_RIGHT_SLOT != 0); - if (i2s_channel_init_std_mode(ctx->channel, &std_config) != ESP_OK) { + const esp_err_t result = i2s_channel_init_std_mode(ctx->channel, &std_config); +#endif + if (result != ESP_OK) { i2s_del_channel(ctx->channel); free(ctx); return -1; @@ -314,6 +351,14 @@ static int capture_read(void *opaque, void *buffer, int frames) { } size_t bytes_read = 0; +#if MYBOT_AUDIO_I2S_CAPTURE_PDM + esp_err_t err = i2s_channel_read(ctx->channel, buffer, (size_t)frames * sizeof(int16_t), + &bytes_read, AUDIO_IO_TIMEOUT_MS); + if (err == ESP_ERR_TIMEOUT) { + return 0; + } + return err == ESP_OK ? (int)(bytes_read / sizeof(int16_t)) : -1; +#else esp_err_t err = i2s_channel_read(ctx->channel, ctx->scratch, (size_t)frames * sizeof(ctx->scratch[0]), &bytes_read, AUDIO_IO_TIMEOUT_MS); @@ -336,6 +381,7 @@ static int capture_read(void *opaque, void *buffer, int frames) { output[i] = (int16_t)value; } return samples; +#endif } static int clear_playback_dma_locked(audio_context_t *ctx) { @@ -382,6 +428,11 @@ static int audio_stop_locked(audio_context_t *ctx) { ctx->enabled = false; ctx->dma_clear_frames = 0; } +#if MYBOT_AUDIO_I2S_CONTROL_SPEAKER_POWER + if (ctx->playback && mybot_generic_set_speaker_power(false) < 0) { + return -1; + } +#endif if (ctx->playback && clear_playback_dma_locked(ctx) < 0) { return -1; } @@ -472,7 +523,8 @@ static int playback_init(void **out_ctx, int rate, int channels, int bits) { } ctx->playback = true; - i2s_chan_config_t channel_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + i2s_chan_config_t channel_config = I2S_CHANNEL_DEFAULT_CONFIG( + MYBOT_AUDIO_I2S_CAPTURE_PDM ? I2S_NUM_1 : I2S_NUM_0, I2S_ROLE_MASTER); channel_config.dma_desc_num = 6; channel_config.dma_frame_num = 240; channel_config.auto_clear_after_cb = true; @@ -512,6 +564,14 @@ static int playback_start(void *opaque) { return -1; } ctx->enabled = true; +#if MYBOT_AUDIO_I2S_CONTROL_SPEAKER_POWER + if (mybot_generic_set_speaker_power(true) < 0) { + (void)i2s_channel_disable(ctx->channel); + ctx->enabled = false; + audio_unlock(); + return -1; + } +#endif if (mybot_pcm_playback_buffer_start(ctx->playback_buffer) < 0) { ctx->stop_pending = true; (void)audio_stop_locked(ctx); diff --git a/components/mybot_platform/src/drivers/display/panels/co5300/co5300_466_panel.c b/components/mybot_platform/src/drivers/display/panels/co5300/co5300_466_panel.c new file mode 100644 index 0000000..881b0a1 --- /dev/null +++ b/components/mybot_platform/src/drivers/display/panels/co5300/co5300_466_panel.c @@ -0,0 +1,157 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "board_config.h" +#include "display/display_panel.h" + +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "esp_err.h" +#include "esp_lcd_co5300.h" +#include "esp_lcd_io_spi.h" +#include "esp_log.h" + +#include +#include + +#define TAG "co5300_466_panel" +#define BRIGHTNESS_PERCENT 60 + +_Static_assert(MYBOT_DISPLAY_WIDTH == 466 && MYBOT_DISPLAY_HEIGHT == 466, + "This CO5300 variant requires a 466x466 panel"); +_Static_assert(MYBOT_DISPLAY_OFFSET_X == 0 && MYBOT_DISPLAY_OFFSET_Y == 0, + "This CO5300 variant requires zero panel gaps"); +_Static_assert((MYBOT_DISPLAY_TRANSFER_ROWS % 2) == 0, "CO5300 transfer rows must be even"); + +static const co5300_lcd_init_cmd_t s_vendor_init[] = { + {0xFE, (uint8_t[]){0x00}, 1, 0}, + {0xC4, (uint8_t[]){0x80}, 1, 0}, + {0x3A, (uint8_t[]){0x55}, 1, 10}, + {0x35, (uint8_t[]){0x00}, 1, 10}, + {0x53, (uint8_t[]){0x20}, 1, 10}, + {0x51, (uint8_t[]){0xFF}, 1, 10}, + {0x63, (uint8_t[]){0xFF}, 1, 10}, + {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0xD1}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xD1}, 4, 0}, + {0x11, NULL, 0, 120}, + {0x29, NULL, 0, 20}, +}; + +int mybot_display_panel_close(mybot_display_panel_t *panel) { + if (!panel) { + return -1; + } + if (panel->panel && esp_lcd_panel_co5300_set_brightness(panel->panel, 0) != ESP_OK) { + ESP_LOGW(TAG, "event=panel action=brightness_off result=error"); + } + if (panel->panel) { + if (panel->ready && esp_lcd_panel_disp_on_off(panel->panel, false) != ESP_OK) { + ESP_LOGW(TAG, "event=panel action=display_off result=error"); + } + if (esp_lcd_panel_del(panel->panel) != ESP_OK) { + return -1; + } + panel->panel = NULL; + } + if (panel->io) { + if (esp_lcd_panel_io_del(panel->io) != ESP_OK) { + return -1; + } + panel->io = NULL; + } + if (panel->spi_ready) { + if (spi_bus_free(MYBOT_DISPLAY_SPI_HOST) != ESP_OK) { + return -1; + } + panel->spi_ready = false; + } + panel->ready = false; + panel->x_alignment = 0; + panel->y_alignment = 0; + return 0; +} + +int mybot_display_panel_open(mybot_display_panel_t *panel) { + if (!panel || panel->ready || mybot_display_panel_close(panel) < 0) { + return -1; + } + + const spi_bus_config_t bus_config = { + .sclk_io_num = MYBOT_DISPLAY_PCLK, + .data0_io_num = MYBOT_DISPLAY_DATA0, + .data1_io_num = MYBOT_DISPLAY_DATA1, + .data2_io_num = MYBOT_DISPLAY_DATA2, + .data3_io_num = MYBOT_DISPLAY_DATA3, + .max_transfer_sz = MYBOT_DISPLAY_WIDTH * MYBOT_DISPLAY_TRANSFER_ROWS * sizeof(uint16_t), + .flags = SPICOMMON_BUSFLAG_QUAD, + }; + esp_err_t result = spi_bus_initialize(MYBOT_DISPLAY_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO); + if (result != ESP_OK) { + goto fail; + } + panel->spi_ready = true; + + const esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = MYBOT_DISPLAY_CS, + .dc_gpio_num = GPIO_NUM_NC, + .spi_mode = 0, + .pclk_hz = MYBOT_DISPLAY_PIXEL_CLOCK_HZ, + .trans_queue_depth = 2, + .lcd_cmd_bits = 32, + .lcd_param_bits = 8, + .flags.quad_mode = true, + }; + result = esp_lcd_new_panel_io_spi(MYBOT_DISPLAY_SPI_HOST, &io_config, &panel->io); + if (result != ESP_OK) { + goto fail; + } + + const co5300_vendor_config_t vendor_config = { + .init_cmds = s_vendor_init, + .init_cmds_size = sizeof(s_vendor_init) / sizeof(s_vendor_init[0]), + .flags.use_qspi_interface = true, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = MYBOT_DISPLAY_RESET, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + .vendor_config = (void *)&vendor_config, + }; + result = esp_lcd_new_panel_co5300(panel->io, &panel_config, &panel->panel); + if (result == ESP_OK) { + result = + esp_lcd_panel_set_gap(panel->panel, MYBOT_DISPLAY_OFFSET_X, MYBOT_DISPLAY_OFFSET_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_reset(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_init(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_invert_color(panel->panel, MYBOT_DISPLAY_INVERT_COLOR); + } + if (result == ESP_OK) { + result = esp_lcd_panel_swap_xy(panel->panel, MYBOT_DISPLAY_SWAP_XY); + } + if (result == ESP_OK) { + result = esp_lcd_panel_mirror(panel->panel, MYBOT_DISPLAY_MIRROR_X, MYBOT_DISPLAY_MIRROR_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_disp_on_off(panel->panel, true); + } + if (result == ESP_OK) { + result = esp_lcd_panel_co5300_set_brightness(panel->panel, BRIGHTNESS_PERCENT); + } + if (result != ESP_OK) { + goto fail; + } + + panel->ready = true; + panel->x_alignment = 2; + panel->y_alignment = 2; + return 0; + +fail: + ESP_LOGE(TAG, "event=panel action=initialize result=error code=%s", esp_err_to_name(result)); + (void)mybot_display_panel_close(panel); + return -1; +} diff --git a/components/mybot_platform/src/drivers/display/panels/generic/qspi_st77916_panel.c b/components/mybot_platform/src/drivers/display/panels/generic/qspi_st77916_panel.c new file mode 100644 index 0000000..cbaec00 --- /dev/null +++ b/components/mybot_platform/src/drivers/display/panels/generic/qspi_st77916_panel.c @@ -0,0 +1,144 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "board_config.h" +#include "display/board_backlight.h" +#include "display/display_panel.h" + +#include "driver/gpio.h" +#include "driver/spi_master.h" +#include "esp_err.h" +#include "esp_lcd_io_spi.h" +#include "esp_lcd_st77916.h" +#include "esp_log.h" + +#include +#include + +#define TAG "st77916_qspi_panel" + +#ifndef MYBOT_DISPLAY_RESET_ACTIVE_HIGH +#define MYBOT_DISPLAY_RESET_ACTIVE_HIGH 0 +#endif + +int mybot_display_panel_close(mybot_display_panel_t *panel) { + if (!panel) { + return -1; + } + if (panel->backlight_ready && mybot_board_set_display_backlight(0) < 0) { + return -1; + } + panel->backlight_ready = false; + if (panel->panel) { + if (panel->ready && esp_lcd_panel_disp_on_off(panel->panel, false) != ESP_OK) { + ESP_LOGW(TAG, "event=panel action=display_off result=error"); + } + if (esp_lcd_panel_del(panel->panel) != ESP_OK) { + return -1; + } + panel->panel = NULL; + } + if (panel->io) { + if (esp_lcd_panel_io_del(panel->io) != ESP_OK) { + return -1; + } + panel->io = NULL; + } + if (panel->spi_ready) { + if (spi_bus_free(MYBOT_DISPLAY_SPI_HOST) != ESP_OK) { + return -1; + } + panel->spi_ready = false; + } + panel->ready = false; + panel->x_alignment = 0; + panel->y_alignment = 0; + return 0; +} + +int mybot_display_panel_open(mybot_display_panel_t *panel) { + if (!panel || panel->ready || mybot_display_panel_close(panel) < 0 || + mybot_board_set_display_backlight(0) < 0) { + return -1; + } + panel->backlight_ready = true; + + const spi_bus_config_t bus_config = { + .sclk_io_num = MYBOT_DISPLAY_PCLK, + .data0_io_num = MYBOT_DISPLAY_DATA0, + .data1_io_num = MYBOT_DISPLAY_DATA1, + .data2_io_num = MYBOT_DISPLAY_DATA2, + .data3_io_num = MYBOT_DISPLAY_DATA3, + .max_transfer_sz = MYBOT_DISPLAY_WIDTH * MYBOT_DISPLAY_TRANSFER_ROWS * sizeof(uint16_t), + .flags = SPICOMMON_BUSFLAG_QUAD, + }; + esp_err_t result = spi_bus_initialize(MYBOT_DISPLAY_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO); + if (result != ESP_OK) { + goto fail; + } + panel->spi_ready = true; + + const esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = MYBOT_DISPLAY_CS, + .dc_gpio_num = GPIO_NUM_NC, + .spi_mode = 0, + .pclk_hz = MYBOT_DISPLAY_PIXEL_CLOCK_HZ, + .trans_queue_depth = 2, + .lcd_cmd_bits = 32, + .lcd_param_bits = 8, + .flags.quad_mode = true, + }; + result = esp_lcd_new_panel_io_spi(MYBOT_DISPLAY_SPI_HOST, &io_config, &panel->io); + if (result != ESP_OK) { + goto fail; + } + + const st77916_vendor_config_t vendor_config = { + .flags.use_qspi_interface = true, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = MYBOT_DISPLAY_RESET, + .rgb_ele_order = MYBOT_DISPLAY_RGB_ORDER, + .bits_per_pixel = 16, + .flags.reset_active_high = MYBOT_DISPLAY_RESET_ACTIVE_HIGH, + .vendor_config = (void *)&vendor_config, + }; + result = esp_lcd_new_panel_st77916(panel->io, &panel_config, &panel->panel); + if (result == ESP_OK) { + result = + esp_lcd_panel_set_gap(panel->panel, MYBOT_DISPLAY_OFFSET_X, MYBOT_DISPLAY_OFFSET_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_reset(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_init(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_invert_color(panel->panel, MYBOT_DISPLAY_INVERT_COLOR); + } + if (result == ESP_OK) { + result = esp_lcd_panel_swap_xy(panel->panel, MYBOT_DISPLAY_SWAP_XY); + } + if (result == ESP_OK) { + result = esp_lcd_panel_mirror(panel->panel, MYBOT_DISPLAY_MIRROR_X, MYBOT_DISPLAY_MIRROR_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_disp_on_off(panel->panel, true); + } + if (result == ESP_OK) { + result = mybot_board_set_display_backlight(MYBOT_DISPLAY_BRIGHTNESS_PERCENT) == 0 + ? ESP_OK + : ESP_FAIL; + } + if (result != ESP_OK) { + goto fail; + } + panel->ready = true; + panel->x_alignment = 2; + panel->y_alignment = 2; + return 0; + +fail: + ESP_LOGE(TAG, "event=panel action=initialize result=error code=%s", esp_err_to_name(result)); + (void)mybot_display_panel_close(panel); + return -1; +} diff --git a/components/mybot_platform/src/drivers/display/panels/generic/rgb565_spi_panel.c b/components/mybot_platform/src/drivers/display/panels/generic/rgb565_spi_panel.c new file mode 100644 index 0000000..0910b32 --- /dev/null +++ b/components/mybot_platform/src/drivers/display/panels/generic/rgb565_spi_panel.c @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "board_config.h" +#include "display/board_backlight.h" +#include "display/display_panel.h" + +#include "driver/spi_master.h" +#include "esp_err.h" +#include "esp_lcd_io_spi.h" +#include "esp_log.h" + +#if MYBOT_PANEL_CONTROLLER == 1 +#include "esp_lcd_gc9a01.h" +#elif MYBOT_PANEL_CONTROLLER == 2 +#include "esp_lcd_nv3023.h" +#elif MYBOT_PANEL_CONTROLLER == 3 +#include "esp_lcd_ili9341.h" +#else +#error "Unsupported MYBOT_PANEL_CONTROLLER" +#endif + +#include +#include + +#define TAG "generic_spi_panel" + +#ifndef MYBOT_DISPLAY_RESET_ACTIVE_HIGH +#define MYBOT_DISPLAY_RESET_ACTIVE_HIGH 0 +#endif + +#if MYBOT_PANEL_CONTROLLER == 3 +#ifndef MYBOT_ILI9341_PROFILE +#define MYBOT_ILI9341_PROFILE 0 +#endif + +#if MYBOT_ILI9341_PROFILE == 1 +static const ili9341_lcd_init_cmd_t s_ili9341_profile_1[] = { + {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[]){0x00, 0x1B}, 2, 0}, + {0x36, (uint8_t[]){0x08}, 1, 0}, + {0x3A, (uint8_t[]){0x55}, 1, 0}, + {0xB7, (uint8_t[]){0x06}, 1, 0}, + {0x11, NULL, 0, 128}, + {0x29, NULL, 0, 128}, +}; +#elif MYBOT_ILI9341_PROFILE == 2 +static const ili9341_lcd_init_cmd_t s_ili9341_profile_2[] = { + {0xCF, (uint8_t[]){0x00, 0xC1, 0x30}, 3, 0}, + {0xED, (uint8_t[]){0x64, 0x03, 0x12, 0x81}, 4, 0}, + {0xE8, (uint8_t[]){0x85, 0x00, 0x78}, 3, 0}, + {0xCB, (uint8_t[]){0x39, 0x2C, 0x00, 0x34, 0x02}, 5, 0}, + {0xF7, (uint8_t[]){0x20}, 1, 0}, + {0xEA, (uint8_t[]){0x00, 0x00}, 2, 0}, + {0xC0, (uint8_t[]){0x13}, 1, 0}, + {0xC1, (uint8_t[]){0x13}, 1, 0}, + {0xC5, (uint8_t[]){0x22, 0x35}, 2, 0}, + {0xC7, (uint8_t[]){0xBD}, 1, 0}, + {0x36, (uint8_t[]){0x08}, 1, 0}, + {0xB6, (uint8_t[]){0x0A, 0xA2}, 2, 0}, + {0x3A, (uint8_t[]){0x55}, 1, 0}, + {0xF6, (uint8_t[]){0x01, 0x30}, 2, 0}, + {0xB1, (uint8_t[]){0x00, 0x1B}, 2, 0}, + {0xF2, (uint8_t[]){0x00}, 1, 0}, + {0x26, (uint8_t[]){0x01}, 1, 0}, + {0xE0, + (uint8_t[]){0x0F, 0x35, 0x31, 0x0B, 0x0E, 0x06, 0x49, 0xA7, 0x33, 0x07, 0x0F, 0x03, 0x0C, 0x0A, + 0x00}, + 15, 0}, + {0xE1, + (uint8_t[]){0x00, 0x0A, 0x0F, 0x04, 0x11, 0x08, 0x36, 0x58, 0x4D, 0x07, 0x10, 0x0C, 0x32, 0x34, + 0x0F}, + 15, 0}, + {0x11, NULL, 0, 0}, + {0x29, NULL, 0, 0}, +}; +#endif +#endif + +int mybot_display_panel_close(mybot_display_panel_t *panel) { + if (!panel) { + return -1; + } + if (panel->backlight_ready && mybot_board_set_display_backlight(0) < 0) { + return -1; + } + panel->backlight_ready = false; + if (panel->panel) { + if (panel->ready && esp_lcd_panel_disp_on_off(panel->panel, false) != ESP_OK) { + ESP_LOGW(TAG, "event=panel action=display_off result=error"); + } + if (esp_lcd_panel_del(panel->panel) != ESP_OK) { + return -1; + } + panel->panel = NULL; + } + if (panel->io) { + if (esp_lcd_panel_io_del(panel->io) != ESP_OK) { + return -1; + } + panel->io = NULL; + } + if (panel->spi_ready) { + if (spi_bus_free(MYBOT_DISPLAY_SPI_HOST) != ESP_OK) { + return -1; + } + panel->spi_ready = false; + } + panel->ready = false; + panel->x_alignment = 0; + panel->y_alignment = 0; + return 0; +} + +int mybot_display_panel_open(mybot_display_panel_t *panel) { + if (!panel || panel->ready || mybot_display_panel_close(panel) < 0 || + mybot_board_set_display_backlight(0) < 0) { + return -1; + } + panel->backlight_ready = true; + + const spi_bus_config_t bus_config = { + .mosi_io_num = MYBOT_DISPLAY_MOSI, + .miso_io_num = GPIO_NUM_NC, + .sclk_io_num = MYBOT_DISPLAY_SCLK, + .quadwp_io_num = GPIO_NUM_NC, + .quadhd_io_num = GPIO_NUM_NC, + .max_transfer_sz = MYBOT_DISPLAY_WIDTH * MYBOT_DISPLAY_TRANSFER_ROWS * sizeof(uint16_t), + }; + esp_err_t result = spi_bus_initialize(MYBOT_DISPLAY_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO); + if (result != ESP_OK) { + goto fail; + } + panel->spi_ready = true; + + const esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = MYBOT_DISPLAY_CS, + .dc_gpio_num = MYBOT_DISPLAY_DC, + .spi_mode = MYBOT_DISPLAY_SPI_MODE, + .pclk_hz = MYBOT_DISPLAY_PIXEL_CLOCK_HZ, + .trans_queue_depth = 2, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + }; + result = esp_lcd_new_panel_io_spi(MYBOT_DISPLAY_SPI_HOST, &io_config, &panel->io); + if (result != ESP_OK) { + goto fail; + } + + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = MYBOT_DISPLAY_RESET, + .rgb_ele_order = MYBOT_DISPLAY_RGB_ORDER, + .bits_per_pixel = 16, + .flags.reset_active_high = MYBOT_DISPLAY_RESET_ACTIVE_HIGH, + }; +#if MYBOT_PANEL_CONTROLLER == 1 + result = esp_lcd_new_panel_gc9a01(panel->io, &panel_config, &panel->panel); +#elif MYBOT_PANEL_CONTROLLER == 2 + result = esp_lcd_new_panel_nv3023(panel->io, &panel_config, &panel->panel); +#else +#if MYBOT_ILI9341_PROFILE == 1 + const ili9341_vendor_config_t vendor_config = { + .init_cmds = s_ili9341_profile_1, + .init_cmds_size = sizeof(s_ili9341_profile_1) / sizeof(s_ili9341_profile_1[0]), + }; + panel_config.vendor_config = (void *)&vendor_config; +#elif MYBOT_ILI9341_PROFILE == 2 + const ili9341_vendor_config_t vendor_config = { + .init_cmds = s_ili9341_profile_2, + .init_cmds_size = sizeof(s_ili9341_profile_2) / sizeof(s_ili9341_profile_2[0]), + }; + panel_config.vendor_config = (void *)&vendor_config; +#endif + result = esp_lcd_new_panel_ili9341(panel->io, &panel_config, &panel->panel); +#endif + if (result == ESP_OK) { + result = esp_lcd_panel_reset(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_init(panel->panel); + } + if (result == ESP_OK) { + result = + esp_lcd_panel_set_gap(panel->panel, MYBOT_DISPLAY_OFFSET_X, MYBOT_DISPLAY_OFFSET_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_invert_color(panel->panel, MYBOT_DISPLAY_INVERT_COLOR); + } + if (result == ESP_OK) { + result = esp_lcd_panel_swap_xy(panel->panel, MYBOT_DISPLAY_SWAP_XY); + } + if (result == ESP_OK) { + result = esp_lcd_panel_mirror(panel->panel, MYBOT_DISPLAY_MIRROR_X, MYBOT_DISPLAY_MIRROR_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_disp_on_off(panel->panel, true); + } + if (result == ESP_OK) { + result = mybot_board_set_display_backlight(MYBOT_DISPLAY_BRIGHTNESS_PERCENT) == 0 + ? ESP_OK + : ESP_FAIL; + } + if (result != ESP_OK) { + goto fail; + } + panel->ready = true; + panel->x_alignment = 1; + panel->y_alignment = 1; + return 0; + +fail: + ESP_LOGE(TAG, "event=panel action=initialize result=error code=%s", esp_err_to_name(result)); + (void)mybot_display_panel_close(panel); + return -1; +} diff --git a/components/mybot_platform/src/drivers/display/panels/sh8601/sh8601_panel.c b/components/mybot_platform/src/drivers/display/panels/sh8601/sh8601_panel.c new file mode 100644 index 0000000..f36babd --- /dev/null +++ b/components/mybot_platform/src/drivers/display/panels/sh8601/sh8601_panel.c @@ -0,0 +1,210 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "board_config.h" +#include "display/display_panel.h" + +#include "driver/spi_master.h" +#include "esp_err.h" +#include "esp_lcd_io_spi.h" +#include "esp_lcd_sh8601.h" +#include "esp_log.h" + +#include +#include + +#define TAG "sh8601_panel" + +#if MYBOT_SH8601_PROFILE == 1 +static const sh8601_lcd_init_cmd_t s_profile_18[] = { + {0x11, (uint8_t[]){0x00}, 0, 120}, + {0x44, (uint8_t[]){0x01, 0xD1}, 2, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x53, (uint8_t[]){0x20}, 1, 10}, + {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x6F}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xBF}, 4, 0}, + {0x51, (uint8_t[]){0x00}, 1, 10}, + {0x29, (uint8_t[]){0x00}, 0, 10}, +}; +#endif + +#if MYBOT_SH8601_PROFILE == 2 +static const sh8601_lcd_init_cmd_t s_profile_132[] = { + {0xFE, (uint8_t[]){0x00}, 1, 0}, + {0xC4, (uint8_t[]){0x80}, 1, 0}, + {0x3A, (uint8_t[]){0x55}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x53, (uint8_t[]){0x20}, 1, 0}, + {0x51, (uint8_t[]){0xFF}, 1, 0}, + {0x63, (uint8_t[]){0xFF}, 1, 0}, + {0x2A, (uint8_t[]){0x00, 0x06, 0x01, 0xD7}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xD1}, 4, 0}, + {0x11, NULL, 0, 100}, + {0x29, NULL, 0, 0}, +}; +#endif + +#if MYBOT_SH8601_PROFILE == 3 +static const sh8601_lcd_init_cmd_t s_profile_143c[] = { + {0xFE, (uint8_t[]){0x00}, 1, 0}, + {0xC4, (uint8_t[]){0x80}, 1, 0}, + {0x3A, (uint8_t[]){0x55}, 1, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x53, (uint8_t[]){0x20}, 1, 0}, + {0x51, (uint8_t[]){0xFF}, 1, 0}, + {0x36, (uint8_t[]){0xC0}, 1, 0}, + {0x63, (uint8_t[]){0xFF}, 1, 0}, + {0x2A, (uint8_t[]){0x00, 0x06, 0x01, 0xD7}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xD1}, 4, 0}, + {0x11, NULL, 0, 100}, + {0x29, NULL, 0, 0}, +}; +#endif + +#if MYBOT_SH8601_PROFILE == 4 +static const sh8601_lcd_init_cmd_t s_profile_206[] = { + {0x11, (uint8_t[]){0x00}, 0, 120}, + {0xC4, (uint8_t[]){0x80}, 1, 0}, + {0x44, (uint8_t[]){0x01, 0xD1}, 2, 0}, + {0x35, (uint8_t[]){0x00}, 1, 0}, + {0x53, (uint8_t[]){0x20}, 1, 10}, + {0x63, (uint8_t[]){0xFF}, 1, 10}, + {0x51, (uint8_t[]){0x00}, 1, 10}, + {0x2A, (uint8_t[]){0x00, 0x16, 0x01, 0xAF}, 4, 0}, + {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xF5}, 4, 0}, + {0x29, (uint8_t[]){0x00}, 0, 10}, + {0x51, (uint8_t[]){0xFF}, 1, 0}, +}; +#endif + +static const sh8601_lcd_init_cmd_t *selected_init(size_t *size) { +#if MYBOT_SH8601_PROFILE == 1 + *size = sizeof(s_profile_18) / sizeof(s_profile_18[0]); + return s_profile_18; +#elif MYBOT_SH8601_PROFILE == 2 + *size = sizeof(s_profile_132) / sizeof(s_profile_132[0]); + return s_profile_132; +#elif MYBOT_SH8601_PROFILE == 3 + *size = sizeof(s_profile_143c) / sizeof(s_profile_143c[0]); + return s_profile_143c; +#elif MYBOT_SH8601_PROFILE == 4 + *size = sizeof(s_profile_206) / sizeof(s_profile_206[0]); + return s_profile_206; +#else +#error "Unsupported MYBOT_SH8601_PROFILE" +#endif +} + +int mybot_display_panel_close(mybot_display_panel_t *panel) { + if (!panel) { + return -1; + } + if (panel->panel) { + if (panel->ready && esp_lcd_panel_disp_on_off(panel->panel, false) != ESP_OK) { + ESP_LOGW(TAG, "event=panel action=display_off result=error"); + } + if (esp_lcd_panel_del(panel->panel) != ESP_OK) { + return -1; + } + panel->panel = NULL; + } + if (panel->io) { + if (esp_lcd_panel_io_del(panel->io) != ESP_OK) { + return -1; + } + panel->io = NULL; + } + if (panel->spi_ready) { + if (spi_bus_free(MYBOT_DISPLAY_SPI_HOST) != ESP_OK) { + return -1; + } + panel->spi_ready = false; + } + panel->ready = false; + panel->x_alignment = 0; + panel->y_alignment = 0; + return 0; +} + +int mybot_display_panel_open(mybot_display_panel_t *panel) { + if (!panel || panel->ready || mybot_display_panel_close(panel) < 0) { + return -1; + } + const spi_bus_config_t bus_config = { + .sclk_io_num = MYBOT_DISPLAY_PCLK, + .data0_io_num = MYBOT_DISPLAY_DATA0, + .data1_io_num = MYBOT_DISPLAY_DATA1, + .data2_io_num = MYBOT_DISPLAY_DATA2, + .data3_io_num = MYBOT_DISPLAY_DATA3, + .max_transfer_sz = MYBOT_DISPLAY_WIDTH * MYBOT_DISPLAY_TRANSFER_ROWS * sizeof(uint16_t), + .flags = SPICOMMON_BUSFLAG_QUAD, + }; + esp_err_t result = spi_bus_initialize(MYBOT_DISPLAY_SPI_HOST, &bus_config, SPI_DMA_CH_AUTO); + if (result != ESP_OK) { + goto fail; + } + panel->spi_ready = true; + + const esp_lcd_panel_io_spi_config_t io_config = { + .cs_gpio_num = MYBOT_DISPLAY_CS, + .dc_gpio_num = GPIO_NUM_NC, + .spi_mode = 0, + .pclk_hz = MYBOT_DISPLAY_PIXEL_CLOCK_HZ, + .trans_queue_depth = 2, + .lcd_cmd_bits = 32, + .lcd_param_bits = 8, + .flags.quad_mode = true, + }; + result = esp_lcd_new_panel_io_spi(MYBOT_DISPLAY_SPI_HOST, &io_config, &panel->io); + if (result != ESP_OK) { + goto fail; + } + + size_t command_count = 0; + const sh8601_lcd_init_cmd_t *commands = selected_init(&command_count); + const sh8601_vendor_config_t vendor_config = { + .init_cmds = commands, + .init_cmds_size = command_count, + .flags.use_qspi_interface = true, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = MYBOT_DISPLAY_RESET, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = 16, + .flags.reset_active_high = MYBOT_DISPLAY_RESET_ACTIVE_HIGH, + .vendor_config = (void *)&vendor_config, + }; + result = esp_lcd_new_panel_sh8601(panel->io, &panel_config, &panel->panel); + if (result == ESP_OK) { + result = + esp_lcd_panel_set_gap(panel->panel, MYBOT_DISPLAY_OFFSET_X, MYBOT_DISPLAY_OFFSET_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_reset(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_init(panel->panel); + } + if (result == ESP_OK) { + result = esp_lcd_panel_invert_color(panel->panel, MYBOT_DISPLAY_INVERT_COLOR); + } + if (result == ESP_OK) { + result = esp_lcd_panel_swap_xy(panel->panel, MYBOT_DISPLAY_SWAP_XY); + } + if (result == ESP_OK) { + result = esp_lcd_panel_mirror(panel->panel, MYBOT_DISPLAY_MIRROR_X, MYBOT_DISPLAY_MIRROR_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_disp_on_off(panel->panel, true); + } + if (result != ESP_OK) { + goto fail; + } + panel->ready = true; + panel->x_alignment = 2; + panel->y_alignment = 2; + return 0; + +fail: + ESP_LOGE(TAG, "event=panel action=initialize result=error code=%s", esp_err_to_name(result)); + (void)mybot_display_panel_close(panel); + return -1; +} diff --git a/components/mybot_platform/src/drivers/display/renderers/lvgl/lvgl_view.cc b/components/mybot_platform/src/drivers/display/renderers/lvgl/lvgl_view.cc index 852057f..14ad920 100644 --- a/components/mybot_platform/src/drivers/display/renderers/lvgl/lvgl_view.cc +++ b/components/mybot_platform/src/drivers/display/renderers/lvgl/lvgl_view.cc @@ -335,6 +335,13 @@ void update_notifications(const mybot_lcd_content_t &content) { int view_width() { return s_view.width; } + +int title_y(bool pairing_code) { + if (s_view.height < 200) { + return pairing_code ? 8 : 36; + } + return pairing_code ? 20 : 81; +} } // namespace int mybot_lvgl_view_create_sized(lv_display_t *display, int width, int height) { @@ -345,20 +352,41 @@ int mybot_lvgl_view_create_sized(lv_display_t *display, int width, int height) { } s_view.width = width; s_view.height = height; + const bool compact = height < 200; +#ifndef MYBOT_DISPLAY_ROUND +#define MYBOT_DISPLAY_ROUND 0 +#endif +#if MYBOT_DISPLAY_ROUND + const int margin = width / 5; + const int header_x = margin; + const int header_y = height >= 400 ? 44 : 36; + const int header_width = width - margin * 2; + const int card_y = height >= 400 ? 92 : 82; + const int card_height = height >= 400 ? 220 : height - 150; + const int footer_y = height - 66; +#else const int margin = width >= 240 ? 12 : 4; + const int header_x = 0; + const int header_y = 0; + const int header_width = width; + const int card_y = compact ? 22 : 46; + const int card_height = compact ? height - 54 : (height >= 220 ? 138 : (height - 102)); + const int footer_y = compact ? height - 22 : height - 44; +#endif const int card_width = width - margin * 2; const int card_x = margin; - const int card_y = 46; - const int card_height = height >= 220 ? 138 : (height - 102); - const int center_x = (card_width - 64) / 2; - const int footer_y = height - 44; + const int header_height = compact ? 20 : 38; + const int badge_size = compact ? 32 : 64; + const int center_x = (card_width - badge_size) / 2; s_view.root = make_panel(lv_display_get_screen_active(display), 0, 0, width, height, kTheme.background); if (!s_view.root) { return -1; } - lv_obj_t *header = make_panel(s_view.root, 0, 0, width, 38, kTheme.background); - lv_obj_t *footer = make_panel(s_view.root, margin, footer_y, card_width, 34, kTheme.surface); + lv_obj_t *header = + make_panel(s_view.root, header_x, header_y, header_width, header_height, kTheme.background); + lv_obj_t *footer = + make_panel(s_view.root, margin, footer_y, card_width, compact ? 18 : 34, kTheme.surface); s_view.card = make_panel(s_view.root, card_x, card_y, card_width, card_height, kTheme.surface); if (!header || !footer || !s_view.card) { mybot_lvgl_view_destroy(); @@ -368,8 +396,11 @@ int mybot_lvgl_view_create_sized(lv_display_t *display, int width, int height) { lv_obj_set_style_border_color(s_view.card, lv_color_hex(kTheme.border), 0); lv_obj_set_style_border_width(s_view.card, 1, 0); lv_obj_set_style_radius(footer, 12, 0); - s_view.badge = make_panel(s_view.card, center_x, 8, 64, 64, kTheme.surface); - s_view.activity = make_panel(s_view.card, center_x, card_height - 26, 64, 16, kTheme.surface); + s_view.badge = + make_panel(s_view.card, center_x, compact ? 2 : 8, badge_size, badge_size, kTheme.surface); + s_view.activity = + make_panel(s_view.card, center_x, compact ? card_height - 16 : card_height - 26, + compact ? 40 : 64, compact ? 10 : 16, kTheme.surface); if (!s_view.badge || !s_view.activity) { mybot_lvgl_view_destroy(); return -1; @@ -378,25 +409,30 @@ int mybot_lvgl_view_create_sized(lv_display_t *display, int width, int height) { lv_obj_set_style_border_width(s_view.badge, 2, 0); lv_obj_set_style_bg_opa(s_view.activity, LV_OPA_TRANSP, 0); - const int brand_width = width < 240 ? width / 2 : width / 3; - s_view.brand = make_label(header, 8, 4, brand_width, 30); - s_view.status = make_label(header, brand_width, 4, width - brand_width - 8, 30); - s_view.notification = make_label(header, 4, 4, width - 8, 30); - s_view.icon = make_label(s_view.badge, 0, 0, 60, 40); + const int brand_width = header_width < 240 ? header_width / 2 : header_width / 3; + const int header_label_height = compact ? 18 : 30; + s_view.brand = + make_label(header, compact ? 2 : 8, compact ? 1 : 4, brand_width, header_label_height); + s_view.status = make_label(header, brand_width, compact ? 1 : 4, header_width - brand_width - 8, + header_label_height); + s_view.notification = + make_label(header, compact ? 2 : 4, compact ? 1 : 4, header_width - 4, header_label_height); + s_view.icon = make_label(s_view.badge, 0, 0, badge_size - 4, compact ? 22 : 40); s_view.emoji = lv_image_create(s_view.card); - s_view.title = make_label(s_view.card, 4, 81, card_width - 8, 30); - s_view.code = make_label(s_view.card, 2, 63, card_width - 4, 45); - s_view.notice = make_label(footer, 2, 3, card_width - 4, 28); + s_view.title = make_label(s_view.card, 4, title_y(false), card_width - 8, compact ? 22 : 30); + s_view.code = make_label(s_view.card, 2, compact ? 28 : 63, card_width - 4, compact ? 32 : 45); + s_view.notice = make_label(footer, 2, compact ? 0 : 3, card_width - 4, compact ? 18 : 28); if (!s_view.brand || !s_view.status || !s_view.notification || !s_view.icon || !s_view.emoji || !s_view.title || !s_view.code || !s_view.notice) { mybot_lvgl_view_destroy(); return -1; } - lv_obj_set_pos(s_view.emoji, center_x, 8); - lv_obj_set_size(s_view.emoji, 64, 64); + lv_obj_set_pos(s_view.emoji, center_x, compact ? 2 : 8); + lv_obj_set_size(s_view.emoji, badge_size, badge_size); lv_obj_set_style_text_align(s_view.brand, LV_TEXT_ALIGN_LEFT, 0); if (width < 240) { - lv_obj_set_style_text_font(s_view.brand, &lv_font_montserrat_14, 0); + lv_obj_set_style_text_font(s_view.brand, + compact ? &lv_font_montserrat_10 : &lv_font_montserrat_14, 0); #if CONFIG_MYBOT_LANGUAGE_ZH_CN lv_obj_set_style_text_font(s_view.status, &mybot_lvgl_font_20, 0); lv_obj_set_style_text_font(s_view.notification, &mybot_lvgl_font_20, 0); @@ -418,8 +454,9 @@ int mybot_lvgl_view_create_sized(lv_display_t *display, int width, int height) { lv_obj_center(s_view.icon); lv_obj_set_style_text_letter_space(s_view.code, 0, 0); for (unsigned i = 0; i < 5; ++i) { - s_view.bars[i] = - make_panel(s_view.activity, 7 + static_cast(i) * 11, 4, 6, 8, kTheme.blue); + s_view.bars[i] = make_panel( + s_view.activity, compact ? 2 + static_cast(i) * 8 : 7 + static_cast(i) * 11, + compact ? 2 : 4, compact ? 5 : 6, compact ? 6 : 8, kTheme.blue); if (!s_view.bars[i]) { mybot_lvgl_view_destroy(); return -1; @@ -554,7 +591,7 @@ void mybot_lvgl_view_update(const mybot_lcd_content_t *content) { } if (!s_view.has_content || pairing_code != (s_view.previous.screen == MYBOT_LCD_SCREEN_PAIR_CODE)) { - lv_obj_set_y(s_view.title, pairing_code ? 20 : 81); + lv_obj_set_y(s_view.title, title_y(pairing_code)); } if (pairing_code && (!s_view.has_content || normalized.screen != s_view.previous.screen || std::strcmp(normalized.pair_code, s_view.previous.pair_code) != 0)) { diff --git a/components/mybot_platform/src/drivers/input/cst816s_boot_input.c b/components/mybot_platform/src/drivers/input/cst816s_boot_input.c index 203718f..81e6ad9 100644 --- a/components/mybot_platform/src/drivers/input/cst816s_boot_input.c +++ b/components/mybot_platform/src/drivers/input/cst816s_boot_input.c @@ -6,7 +6,21 @@ #include "button_gpio.h" #include "esp_lcd_touch.h" + +#ifndef MYBOT_TOUCH_DRIVER_KIND +#define MYBOT_TOUCH_DRIVER_KIND 1 +#endif + +#if MYBOT_TOUCH_DRIVER_KIND == 1 #include "esp_lcd_touch_cst816s.h" +#elif MYBOT_TOUCH_DRIVER_KIND == 2 +#include "esp_lcd_touch_ft5x06.h" +#elif MYBOT_TOUCH_DRIVER_KIND == 3 +#include "esp_lcd_touch_gt911.h" +#else +#error "Unsupported MYBOT_TOUCH_DRIVER_KIND" +#endif + #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -18,6 +32,18 @@ #define TAG "cst816s_input" +#ifndef MYBOT_BOOT_BUTTON_LONG_PRESS_MS +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 +#endif + +#ifndef MYBOT_TOUCH_X_MAX +#define MYBOT_TOUCH_X_MAX (MYBOT_DISPLAY_WIDTH - 1) +#endif + +#ifndef MYBOT_TOUCH_Y_MAX +#define MYBOT_TOUCH_Y_MAX (MYBOT_DISPLAY_HEIGHT - 1) +#endif + typedef struct { portMUX_TYPE lock; bool active; @@ -157,6 +183,7 @@ static int create_touch(void) { return -1; } +#if MYBOT_TOUCH_DRIVER_KIND == 1 esp_lcd_panel_io_i2c_config_t io_config = { .dev_addr = ESP_LCD_TOUCH_IO_I2C_CST816S_ADDRESS, .scl_speed_hz = MYBOT_TOUCH_I2C_SPEED_HZ, @@ -166,14 +193,21 @@ static int create_touch(void) { .lcd_param_bits = 0, .flags.disable_control_phase = 1, }; +#elif MYBOT_TOUCH_DRIVER_KIND == 2 + esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG(); + io_config.scl_speed_hz = MYBOT_TOUCH_I2C_SPEED_HZ; +#elif MYBOT_TOUCH_DRIVER_KIND == 3 + esp_lcd_panel_io_i2c_config_t io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); + io_config.scl_speed_hz = MYBOT_TOUCH_I2C_SPEED_HZ; +#endif esp_err_t result = esp_lcd_new_panel_io_i2c(bus, &io_config, &s_context.touch_io); if (result != ESP_OK) { return -1; } const esp_lcd_touch_config_t touch_config = { - .x_max = MYBOT_DISPLAY_WIDTH - 1, - .y_max = MYBOT_DISPLAY_HEIGHT - 1, + .x_max = MYBOT_TOUCH_X_MAX, + .y_max = MYBOT_TOUCH_Y_MAX, .rst_gpio_num = MYBOT_TOUCH_RESET, .int_gpio_num = MYBOT_TOUCH_INTERRUPT, .levels = @@ -188,7 +222,13 @@ static int create_touch(void) { .mirror_y = MYBOT_TOUCH_MIRROR_Y, }, }; +#if MYBOT_TOUCH_DRIVER_KIND == 1 result = esp_lcd_touch_new_i2c_cst816s(s_context.touch_io, &touch_config, &s_context.touch); +#elif MYBOT_TOUCH_DRIVER_KIND == 2 + result = esp_lcd_touch_new_i2c_ft5x06(s_context.touch_io, &touch_config, &s_context.touch); +#elif MYBOT_TOUCH_DRIVER_KIND == 3 + result = esp_lcd_touch_new_i2c_gt911(s_context.touch_io, &touch_config, &s_context.touch); +#endif if (result != ESP_OK) { (void)esp_lcd_panel_io_del(s_context.touch_io); s_context.touch_io = NULL; diff --git a/components/mybot_platform/src/drivers/input/gpio_buttons.c b/components/mybot_platform/src/drivers/input/gpio_buttons.c index 850bbd0..49daca6 100644 --- a/components/mybot_platform/src/drivers/input/gpio_buttons.c +++ b/components/mybot_platform/src/drivers/input/gpio_buttons.c @@ -117,16 +117,18 @@ int mybot_esp32s3_buttons_start(void) { button_handle_t boot = NULL; button_handle_t volume_up = NULL; button_handle_t volume_down = NULL; + const bool has_volume_up = MYBOT_VOLUME_UP_BUTTON_GPIO != GPIO_NUM_NC; + const bool has_volume_down = MYBOT_VOLUME_DOWN_BUTTON_GPIO != GPIO_NUM_NC; if (create_button(MYBOT_BOOT_BUTTON_GPIO, &boot) < 0 || - create_button(MYBOT_VOLUME_UP_BUTTON_GPIO, &volume_up) < 0 || - create_button(MYBOT_VOLUME_DOWN_BUTTON_GPIO, &volume_down) < 0 || + (has_volume_up && create_button(MYBOT_VOLUME_UP_BUTTON_GPIO, &volume_up) < 0) || + (has_volume_down && create_button(MYBOT_VOLUME_DOWN_BUTTON_GPIO, &volume_down) < 0) || iot_button_register_cb(boot, BUTTON_SINGLE_CLICK, NULL, boot_click, NULL) != ESP_OK || iot_button_register_cb(boot, BUTTON_LONG_PRESS_START, NULL, boot_long_press, NULL) != ESP_OK || - iot_button_register_cb(volume_up, BUTTON_SINGLE_CLICK, NULL, volume_up_click, NULL) != - ESP_OK || - iot_button_register_cb(volume_down, BUTTON_SINGLE_CLICK, NULL, volume_down_click, NULL) != - ESP_OK) { + (volume_up && iot_button_register_cb(volume_up, BUTTON_SINGLE_CLICK, NULL, volume_up_click, + NULL) != ESP_OK) || + (volume_down && iot_button_register_cb(volume_down, BUTTON_SINGLE_CLICK, NULL, + volume_down_click, NULL) != ESP_OK)) { if (volume_down) { (void)iot_button_delete(volume_down); } @@ -155,7 +157,7 @@ static int buttons_init(void **out_ctx, mybot_key_event_handler_t emit, void *us *out_ctx = NULL; portENTER_CRITICAL(&s_context.lock); - if (s_context.active || !s_context.boot || !s_context.volume_up || !s_context.volume_down) { + if (s_context.active || !s_context.boot) { portEXIT_CRITICAL(&s_context.lock); return -1; } diff --git a/components/mybot_platform/src/drivers/io_expander/pca9557.c b/components/mybot_platform/src/drivers/io_expander/pca9557.c new file mode 100644 index 0000000..88cc06b --- /dev/null +++ b/components/mybot_platform/src/drivers/io_expander/pca9557.c @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "io_expander/pca9557.h" + +#include "esp_err.h" +#include "esp_log.h" + +#define TAG "pca9557" +#define PCA9557_I2C_TIMEOUT_MS 100 +#define PCA9557_REG_OUTPUT 0x01 +#define PCA9557_REG_CONFIG 0x03 + +static int write_register(mybot_pca9557_t *expander, uint8_t reg, uint8_t value) { + const uint8_t command[] = {reg, value}; + const esp_err_t result = + i2c_master_transmit(expander->device, command, sizeof(command), PCA9557_I2C_TIMEOUT_MS); + if (result != ESP_OK) { + ESP_LOGE(TAG, "event=io_expander action=write register=0x%02x result=error code=%s", reg, + esp_err_to_name(result)); + return -1; + } + return 0; +} + +int mybot_pca9557_init(mybot_pca9557_t *expander, i2c_master_bus_handle_t bus, uint8_t address, + uint32_t speed_hz) { + if (!expander || !bus || expander->device) { + return -1; + } + *expander = (mybot_pca9557_t){.bus = bus}; + const i2c_device_config_t config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = address, + .scl_speed_hz = speed_hz, + }; + const esp_err_t result = i2c_master_bus_add_device(bus, &config, &expander->device); + if (result != ESP_OK) { + expander->device = NULL; + ESP_LOGE(TAG, "event=io_expander action=initialize address=0x%02x result=error code=%s", + address, esp_err_to_name(result)); + return -1; + } + return 0; +} + +int mybot_pca9557_deinit(mybot_pca9557_t *expander) { + if (!expander) { + return -1; + } + if (expander->device && i2c_master_bus_rm_device(expander->device) != ESP_OK) { + return -1; + } + expander->device = NULL; + expander->bus = NULL; + expander->output_state = 0; + return 0; +} + +int mybot_pca9557_configure_outputs(mybot_pca9557_t *expander, uint8_t output_mask, + uint8_t high_mask) { + if (!expander || !expander->device) { + return -1; + } + const uint8_t state = high_mask & output_mask; + if (write_register(expander, PCA9557_REG_OUTPUT, state) < 0 || + write_register(expander, PCA9557_REG_CONFIG, (uint8_t)~output_mask) < 0) { + return -1; + } + expander->output_state = state; + return 0; +} + +int mybot_pca9557_set_levels(mybot_pca9557_t *expander, uint8_t mask, uint8_t high_mask) { + if (!expander || !expander->device) { + return -1; + } + const uint8_t state = (uint8_t)((expander->output_state & ~mask) | (high_mask & mask)); + if (write_register(expander, PCA9557_REG_OUTPUT, state) < 0) { + return -1; + } + expander->output_state = state; + return 0; +} diff --git a/components/mybot_platform/src/internal/audio/es8374_codec.h b/components/mybot_platform/src/internal/audio/es8374_codec.h new file mode 100644 index 0000000..8bcb46e --- /dev/null +++ b/components/mybot_platform/src/internal/audio/es8374_codec.h @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _ES8374_CODEC_H_ +#define _ES8374_CODEC_H_ + +#include "audio_codec_if.h" +#include "audio_codec_ctrl_if.h" +#include "audio_codec_gpio_if.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ES8374_CODEC_DEFAULT_ADDR (0x20) +#define ES8374_CODEC_DEFAULT_ADDR_1 (0x21) + +/** + * @brief ES8374 codec configuration + */ +typedef struct { + const audio_codec_ctrl_if_t *ctrl_if; /*!< Codec Control interface */ + const audio_codec_gpio_if_t *gpio_if; /*!< Codec GPIO interface */ + esp_codec_dec_work_mode_t codec_mode; /*!< Codec work mode: ADC or DAC */ + bool master_mode; /*!< Whether codec works as I2S master or not */ + int16_t pa_pin; /*!< PA chip power pin */ + bool pa_reverted; /*!< false: enable PA when pin set to 1, true: enable PA when pin set to 0 */ +} es8374_codec_cfg_t; + +/** + * @brief New ES8374 codec interface + * @param codec_cfg: ES8374 codec configuration + * @return NULL: Fail to new ES8374 codec interface + * -Others: ES8374 codec interface + */ +const audio_codec_if_t *es8374_codec_new(es8374_codec_cfg_t *codec_cfg); + +#ifdef __cplusplus +} +#endif + +#endif //__ES8374_H__ diff --git a/components/mybot_platform/src/internal/audio/es_common.h b/components/mybot_platform/src/internal/audio/es_common.h new file mode 100644 index 0000000..232ddad --- /dev/null +++ b/components/mybot_platform/src/internal/audio/es_common.h @@ -0,0 +1,183 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _ES_COMMON_H_ +#define _ES_COMMON_H_ + +#include "esp_log.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CODEC_MEM_CHECK(ptr) \ + if (ptr == NULL) { \ + ESP_LOGE(TAG, "Fail to alloc memory at %s:%d", __FUNCTION__, __LINE__); \ + } + +#define BITS(n) (1 << n) + +#define MCLK_DEFAULT_DIV (256) + +typedef enum { + BIT_LENGTH_MIN = -1, + BIT_LENGTH_16BITS = 0x03, + BIT_LENGTH_18BITS = 0x02, + BIT_LENGTH_20BITS = 0x01, + BIT_LENGTH_24BITS = 0x00, + BIT_LENGTH_32BITS = 0x04, + BIT_LENGTH_MAX, +} es_bits_length_t; + +typedef enum { + MCLK_DIV_MIN = -1, + MCLK_DIV_1 = 1, + MCLK_DIV_2 = 2, + MCLK_DIV_3 = 3, + MCLK_DIV_4 = 4, + MCLK_DIV_6 = 5, + MCLK_DIV_8 = 6, + MCLK_DIV_9 = 7, + MCLK_DIV_11 = 8, + MCLK_DIV_12 = 9, + MCLK_DIV_16 = 10, + MCLK_DIV_18 = 11, + MCLK_DIV_22 = 12, + MCLK_DIV_24 = 13, + MCLK_DIV_33 = 14, + MCLK_DIV_36 = 15, + MCLK_DIV_44 = 16, + MCLK_DIV_48 = 17, + MCLK_DIV_66 = 18, + MCLK_DIV_72 = 19, + MCLK_DIV_5 = 20, + MCLK_DIV_10 = 21, + MCLK_DIV_15 = 22, + MCLK_DIV_17 = 23, + MCLK_DIV_20 = 24, + MCLK_DIV_25 = 25, + MCLK_DIV_30 = 26, + MCLK_DIV_32 = 27, + MCLK_DIV_34 = 28, + MCLK_DIV_7 = 29, + MCLK_DIV_13 = 30, + MCLK_DIV_14 = 31, + MCLK_DIV_MAX, +} es_sclk_div_t; + +typedef enum { + LCLK_DIV_MIN = -1, + LCLK_DIV_128 = 0, + LCLK_DIV_192 = 1, + LCLK_DIV_256 = 2, + LCLK_DIV_384 = 3, + LCLK_DIV_512 = 4, + LCLK_DIV_576 = 5, + LCLK_DIV_768 = 6, + LCLK_DIV_1024 = 7, + LCLK_DIV_1152 = 8, + LCLK_DIV_1408 = 9, + LCLK_DIV_1536 = 10, + LCLK_DIV_2112 = 11, + LCLK_DIV_2304 = 12, + + LCLK_DIV_125 = 16, + LCLK_DIV_136 = 17, + LCLK_DIV_250 = 18, + LCLK_DIV_272 = 19, + LCLK_DIV_375 = 20, + LCLK_DIV_500 = 21, + LCLK_DIV_544 = 22, + LCLK_DIV_750 = 23, + LCLK_DIV_1000 = 24, + LCLK_DIV_1088 = 25, + LCLK_DIV_1496 = 26, + LCLK_DIV_1500 = 27, + LCLK_DIV_MAX, +} es_lclk_div_t; + +typedef enum { + D2SE_PGA_GAIN_MIN = -1, + D2SE_PGA_GAIN_DIS = 0, + D2SE_PGA_GAIN_EN = 1, + D2SE_PGA_GAIN_MAX = 2, +} es_d2se_pga_t; + +typedef enum { + ADC_INPUT_MIN = -1, + ADC_INPUT_LINPUT1_RINPUT1 = 0x00, + ADC_INPUT_MIC1 = 0x05, + ADC_INPUT_MIC2 = 0x06, + ADC_INPUT_LINPUT2_RINPUT2 = 0x50, + ADC_INPUT_DIFFERENCE = 0xf0, + ADC_INPUT_MAX, +} es_adc_input_t; + +typedef enum { + DAC_OUTPUT_MIN = -1, + DAC_OUTPUT_LOUT1 = 0x04, + DAC_OUTPUT_LOUT2 = 0x08, + DAC_OUTPUT_SPK = 0x09, + DAC_OUTPUT_ROUT1 = 0x10, + DAC_OUTPUT_ROUT2 = 0x20, + DAC_OUTPUT_ALL = 0x3c, + DAC_OUTPUT_MAX, +} es_dac_output_t; + +typedef enum { + MIC_GAIN_MIN = -1, + MIC_GAIN_0DB = 0, + MIC_GAIN_3DB = 3, + MIC_GAIN_6DB = 6, + MIC_GAIN_9DB = 9, + MIC_GAIN_12DB = 12, + MIC_GAIN_15DB = 15, + MIC_GAIN_18DB = 18, + MIC_GAIN_21DB = 21, + MIC_GAIN_24DB = 24, + MIC_GAIN_MAX, +} es_mic_gain_t; + +typedef enum { + ES_MODULE_MIN = -1, + ES_MODULE_ADC = 0x01, + ES_MODULE_DAC = 0x02, + ES_MODULE_ADC_DAC = 0x03, + ES_MODULE_LINE = 0x04, + ES_MODULE_MAX +} es_module_t; + +typedef enum { + ES_MODE_MIN = -1, + ES_MODE_SLAVE = 0x00, + ES_MODE_MASTER = 0x01, + ES_MODE_MAX, +} es_mode_t; + +typedef enum { + ES_I2S_MIN = -1, + ES_I2S_NORMAL = 0, + ES_I2S_LEFT = 1, + ES_I2S_RIGHT = 2, + ES_I2S_DSP = 3, + ES_I2S_MAX +} es_i2s_fmt_t; + +typedef struct { + es_sclk_div_t sclk_div; /*!< bits clock divide */ + es_lclk_div_t lclk_div; /*!< WS clock divide */ +} es_i2s_clock_t; + +typedef enum { + ES_PA_SETUP = 1, + ES_PA_ENABLE = (1 << 1), + ES_PA_DISABLE = (1 << 2), +} es_pa_setting_t; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/mybot_platform/src/internal/io_expander/pca9557.h b/components/mybot_platform/src/internal/io_expander/pca9557.h new file mode 100644 index 0000000..5cbe2e5 --- /dev/null +++ b/components/mybot_platform/src/internal/io_expander/pca9557.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_PCA9557_H_ +#define MYBOT_PCA9557_H_ + +#include "driver/i2c_master.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + i2c_master_bus_handle_t bus; + i2c_master_dev_handle_t device; + uint8_t output_state; +} mybot_pca9557_t; + +int mybot_pca9557_init(mybot_pca9557_t *expander, i2c_master_bus_handle_t bus, uint8_t address, + uint32_t speed_hz); +int mybot_pca9557_deinit(mybot_pca9557_t *expander); +int mybot_pca9557_configure_outputs(mybot_pca9557_t *expander, uint8_t output_mask, + uint8_t high_mask); +int mybot_pca9557_set_levels(mybot_pca9557_t *expander, uint8_t mask, uint8_t high_mask); + +#ifdef __cplusplus +} +#endif + +#endif /* MYBOT_PCA9557_H_ */ diff --git a/docs/AUDIO_PLAYBACK.md b/docs/AUDIO_PLAYBACK.md index 7d74b3c..1253994 100644 --- a/docs/AUDIO_PLAYBACK.md +++ b/docs/AUDIO_PLAYBACK.md @@ -2,7 +2,7 @@ [English](AUDIO_PLAYBACK.md) | [简体中文](AUDIO_PLAYBACK.zh-CN.md) -All sixteen board profiles use one PCM playback buffer through seven board audio drivers. This +All supported board profiles use one PCM playback buffer through the board audio adapters. This keeps the SDK boundary at 16 kHz, mono signed-16 PCM and moves continuous I2S feeding into an independent task. The shared implementation is [`pcm_playback_buffer.c`](../components/mybot_platform/src/services/audio/pcm_playback_buffer.c); diff --git a/docs/AUDIO_PLAYBACK.zh-CN.md b/docs/AUDIO_PLAYBACK.zh-CN.md index 35eab19..44cec98 100644 --- a/docs/AUDIO_PLAYBACK.zh-CN.md +++ b/docs/AUDIO_PLAYBACK.zh-CN.md @@ -2,7 +2,7 @@ [English](AUDIO_PLAYBACK.md) | [简体中文](AUDIO_PLAYBACK.zh-CN.md) -当前 16 个 Board profile 通过 7 个板级音频驱动共用同一套 PCM 播放缓冲。SDK 边界保持 +当前全部 Board profile 通过板级音频适配器共用同一套 PCM 播放缓冲。SDK 边界保持 16 kHz、单声道 signed-16 PCM,独立任务持续向 I2S 供数。公共实现位于 [`pcm_playback_buffer.c`](../components/mybot_platform/src/services/audio/pcm_playback_buffer.c), 各板级驱动提供原生 I2S sink。 diff --git a/docs/BOARD_PORTING.md b/docs/BOARD_PORTING.md index 699d13a..ed44767 100644 --- a/docs/BOARD_PORTING.md +++ b/docs/BOARD_PORTING.md @@ -107,8 +107,9 @@ The bundled RTSA package supports only 60 ms audio frames. Although menuconfig l 6. Preserve the SDK audio boundary: 16 kHz, mono, signed 16-bit PCM, with frame counts rather than byte counts. 7. Select the appropriate display capability in `components/mybot_platform/CMakeLists.txt`: - `Kconfig.display` for LVGL or `Kconfig.headless` for a board without a panel. The current headless - selection covers AtomEchoS3R and ReSpeaker Flex; update it when adding another headless board. + `Kconfig.display` for LVGL or `Kconfig.headless` for a board without a panel. The current + headless selection covers AtomEchoS3R, Xingzhi Cube 0.96 OLED and ReSpeaker Flex; update it + when adding another headless board. 8. Add isolated CI builds for both languages and size reports, then record real-device provisioning, HTTPS, RTC, bidirectional audio, input, display, hangup, and repeated start/stop validation. @@ -182,6 +183,24 @@ after hardware tests establish it is required. Other panel sizes and shared-cloc require separate profiles rather than runtime selection. Chinese and English build support for this fixed N16R8 profile is tracked in the [validation matrix](VALIDATION.md). +## Shared profile skeleton / 共享板型骨架 + +`boards/common/generic_profile.cmake` composes the shared board lifecycle, audio adapter, panel, +touch/key input, optional PCA9557/TCA9554/AXP2101 setup, display backlight and SDK registration. +Each board still owns its `board.cmake`, `board_config.h`, `sdkconfig.defaults` and `board.c`. +The generic path supports ES8311, ES8311+ES7210, ES8374 and raw I2S; raw capture can use standard +I2S or PDM. Keep board-specific power or expander code outside the common path when it changes +the lifecycle order. + +`M5Stack StopWatch` uses M5PM1 and M5IOE1 before display and codec startup. The bridge raises LCD +and codec power, resets LCD/touch, controls the speaker PA and leaves the motor off. The shared +round layout narrows widgets to a center safe rectangle; this is not a substitute for panel and +touch validation. + +`Taiji Pi S3` uses PDM RX on I2S0 and standard-I2S playback on I2S1. PDM samples enter the +platform already as signed 16-bit mono values, so the SDK receives 16 kHz mono signed-16 without +another slot conversion. The active-low GPIO48 mute path follows playback start/stop. + The `esp-vocat` profile supports PCB V1.0 and V1.2 under one compile-time Board because they share the same target, storage, partition, and component configuration. This is runtime hardware-revision detection, not runtime Board selection. Board preparation creates native I2C0 on GPIO2/GPIO1, drives diff --git a/docs/BOARD_PORTING.zh-CN.md b/docs/BOARD_PORTING.zh-CN.md index 11f3ab4..0bfe0cc 100644 --- a/docs/BOARD_PORTING.zh-CN.md +++ b/docs/BOARD_PORTING.zh-CN.md @@ -106,13 +106,29 @@ RTSA 包前,SDK 构建封装会拒绝这些配置。当前构建与真机状 6. 保持 SDK 音频边界:16 kHz、单声道、signed 16-bit PCM,接口传帧数而不是字节数。 7. 在 `components/mybot_platform/CMakeLists.txt` 选择显示能力:带屏板型使用 `Kconfig.display` 启用 LVGL,无屏板型使用 `Kconfig.headless`。当前无屏选择覆盖 - AtomEchoS3R 与 ReSpeaker Flex,新增无屏板型时需同步此选择。 + AtomEchoS3R、行者 Cube 0.96 OLED 与 ReSpeaker Flex,新增无屏板型时需同步此选择。 8. 增加两种语言的隔离 CI 构建与尺寸报告,并记录真实设备的配网、HTTPS、RTC、双向音频、 输入、显示、挂断与重复启停验证。 所有带屏板型使用共享 LVGL 界面,不保留旧渲染器或后端选择开关。控制器初始化和销毁放在 `src/drivers/display/panels/`,通过 `display.cmake` 和可复用的 LVGL 适配器接入。 `CONFIG_MYBOT_LVGL_UI` 是根据板型确定的隐藏配置,无屏板型不启用;主题与动画仍可配置。 + +## 共享板型骨架 + +`boards/common/generic_profile.cmake` 组合共享板级生命周期、音频适配器、面板、触摸/按键、 +可选的 PCA9557/TCA9554/AXP2101 初始化、显示背光和 SDK 注册。每个板型仍独立维护 +`board.cmake`、`board_config.h`、`sdkconfig.defaults` 与 `board.c`。通用路径支持 ES8311、 +ES8311+ES7210、ES8374 和原始 I2S;原始采集可使用标准 I2S 或 PDM。会改变启动顺序的板级 +电源或扩展器逻辑不应塞入公共路径。 + +`M5Stack StopWatch` 在显示和 codec 启动前初始化 M5PM1 与 M5IOE1:打开 LCD/codec 电源, +复位 LCD/触摸,控制扬声器 PA,并默认关闭振动电机。共享圆屏布局把控件收窄到中央安全 +矩形;这不替代面板和触摸真机验证。 + +`Taiji Pi S3` 在 I2S0 上使用 PDM 采集、I2S1 上使用标准 I2S 播放。PDM 解调后直接提供 +16 kHz 单声道 signed-16 数据,SDK 边界不再做 slot 转换。低有效 GPIO48 静音脚随播放 +启停。 共享面板契约位于 `src/internal/display/display_panel.h`,内部头文件不属于 SDK API; 板级电源和背光控制仍归 Board 层所有。 diff --git a/docs/CORES3_UI.md b/docs/CORES3_UI.md index e211f6f..6a7ac83 100644 --- a/docs/CORES3_UI.md +++ b/docs/CORES3_UI.md @@ -4,7 +4,7 @@ `MYBOT_BOARD=m5stack-core-s3` automatically selects the process-gray pixel-glasses view on the 320 × 240 ILI9342 display. It uses the existing CoreS3 LVGL adapter and common LCD-view API. -There is no manual renderer switch or `CONFIG_MYBOT_LVGL_UI` override. The other fifteen board +There is no manual renderer switch or `CONFIG_MYBOT_LVGL_UI` override. The other board profiles retain their existing UI and hardware behavior; see [Platform UI](PLATFORM_UI.md). This replaces CoreS3's earlier status-card view. Audio, camera/video policy, FT6336 touch gestures, diff --git a/docs/CORES3_UI.zh-CN.md b/docs/CORES3_UI.zh-CN.md index 4d640d9..5ac5388 100644 --- a/docs/CORES3_UI.zh-CN.md +++ b/docs/CORES3_UI.zh-CN.md @@ -4,7 +4,7 @@ `MYBOT_BOARD=m5stack-core-s3` 自动为320 × 240 ILI9342屏选择工艺灰像素眼镜界面,复用原 CoreS3 LVGL适配器及公共LCD视图接口。不增加手工渲染器开关,无需覆盖隐藏的 -`CONFIG_MYBOT_LVGL_UI`。其他十五个Board profile保留原UI与硬件行为,见 +`CONFIG_MYBOT_LVGL_UI`。其他Board profile保留原UI与硬件行为,见 [平台UI](PLATFORM_UI.zh-CN.md)。 本视图替换CoreS3旧状态卡片。音频、摄像头/视频策略、FT6336触摸手势、电源、引脚、PSRAM diff --git a/docs/PLATFORM_UI.md b/docs/PLATFORM_UI.md index 45c36b5..2a92002 100644 --- a/docs/PLATFORM_UI.md +++ b/docs/PLATFORM_UI.md @@ -26,6 +26,20 @@ retain their existing board behavior. | `esp32-s3-touch-lcd-1.85` | ST77916, 360 × 360 | shared panel adapter | CST816S and Boot | | `genjutech-s3-1.54tft` | ST7789, 240 × 240 | shared ST7789 adapter | Boot and volume buttons | | `sensecap-watcher` | SPD2010, 412 × 412 | shared panel adapter | existing encoder path | +| `xingzhi-metal-1.54-wifi`, `xingzhi-abs-2.0` | ST7789, 240 × 240 | shared panel adapter | Boot/touch or Boot/volume | +| `xingzhi-cube-0.85tft-wifi` | NV3023, 128 × 128 | shared panel adapter | Boot | +| `xingzhi-cube-0.96oled-wifi` | Headless | none | Boot and volume buttons | +| `m5stack-stopwatch` | CO5300, 466 × 466 round | shared panel adapter | CST820B-compatible touch and buttons | +| `esp32-s3-touch-amoled-1.8` | SH8601, 368 × 448 | shared panel adapter | FT5x06 and Boot | +| AMOLED 1.32/1.43c/2.06 | SH8601, 466 × 466 or 410 × 502 | shared panel adapter | Boot; FT5x06 on 2.06 | +| `atoms3r-echo-base`, `atoms3r-echo-pyramid` | GC9A01-class, 128 × 128 | shared panel adapter | Boot | +| `sp-esp32-s3-1.28-box` | GC9A01, 240 × 240 round | shared panel adapter | CST816S-compatible touch and Boot | +| `esp-box`, `esp-box-3`, `esp-box-lite` | ILI9341, 320 × 240 | shared panel adapter | Boot; touch not implemented | +| `esp32s3-korvo2-v3`, `df-k10` | ILI9341, 320 × 240 | shared panel adapter | Boot | +| `lichuang-dev`, `rymcu-bigsmart` | ST7789, 320 × 240 | shared panel adapter | FT5x06 or GT911 plus Boot | +| `quandong-s3-dev` | ILI9341, 320 × 240 | shared panel adapter | Boot | +| `mixgo-nova` | ST7789, 128 × 160 | shared panel adapter | Boot | +| `taiji-pi-s3` | ST77916, 360 × 360 round | shared panel adapter | CST816S and Boot | The AtomEchoS3R and ReSpeaker Flex profiles have no LCD and do not include LVGL. The ML307 and Wi-Fi variants, along with the audio/video protocols, are independent of the display backend. @@ -52,7 +66,7 @@ LCD-only changes. See [CoreS3 UI](CORES3_UI.md) for timing, resources and valida CoreS3's legacy light-theme setting is retained but does not change the pixel skin. Its animation option controls expression motion and transitions; disabling it retains the opener hold and -necessary guidance scrolling. The other fifteen profiles retain their existing UI/hardware; +necessary guidance scrolling. The other profiles retain their existing UI/hardware; SDK, AOSL, RTSA, camera/video policy, input, power and memory configuration are unchanged. ## Shared workflow view on other display boards @@ -84,6 +98,11 @@ CoreS3's stripe is 10,240 bytes, StickS3's is 4,320 bytes, and AMOLED 2.16's is separate from task stacks and driver allocations. The hardware integration owns reset, offsets, color order, backlight, and teardown. +Round profiles set `MYBOT_DISPLAY_ROUND=1`. The shared view then narrows the header, card and +footer to a central safe rectangle instead of placing widgets in the panel corners. This layout +logic is build-verified only; color, orientation, clipping and touch alignment still require +real-device checks for each panel. + ## Build examples Use a separate build directory for every board and language. No extra UI defaults are required: diff --git a/docs/PLATFORM_UI.zh-CN.md b/docs/PLATFORM_UI.zh-CN.md index abe9c2b..e583ae3 100644 --- a/docs/PLATFORM_UI.zh-CN.md +++ b/docs/PLATFORM_UI.zh-CN.md @@ -24,6 +24,20 @@ | `esp32-s3-touch-lcd-1.85` | ST77916,360 × 360 | 通用面板适配器 | CST816S 与 Boot | | `genjutech-s3-1.54tft` | ST7789,240 × 240 | 通用 ST7789 | Boot 与音量按键 | | `sensecap-watcher` | SPD2010,412 × 412 | 通用面板适配器 | 原有旋钮路径 | +| `xingzhi-metal-1.54-wifi`、`xingzhi-abs-2.0` | ST7789,240 × 240 | 通用面板适配器 | Boot/触摸或 Boot/音量键 | +| `xingzhi-cube-0.85tft-wifi` | NV3023,128 × 128 | 通用面板适配器 | Boot | +| `xingzhi-cube-0.96oled-wifi` | 无屏 | 无 | Boot 与音量键 | +| `m5stack-stopwatch` | CO5300,466 × 466 圆屏 | 通用面板适配器 | CST820B 兼容触摸与按键 | +| `esp32-s3-touch-amoled-1.8` | SH8601,368 × 448 | 通用面板适配器 | FT5x06 与 Boot | +| AMOLED 1.32/1.43c/2.06 | SH8601,466 × 466 或 410 × 502 | 通用面板适配器 | Boot;2.06 使用 FT5x06 | +| `atoms3r-echo-base`、`atoms3r-echo-pyramid` | GC9A01 类,128 × 128 | 通用面板适配器 | Boot | +| `sp-esp32-s3-1.28-box` | GC9A01,240 × 240 圆屏 | 通用面板适配器 | CST816S 兼容触摸与 Boot | +| `esp-box`、`esp-box-3`、`esp-box-lite` | ILI9341,320 × 240 | 通用面板适配器 | Boot;未实现触摸 | +| `esp32s3-korvo2-v3`、`df-k10` | ILI9341,320 × 240 | 通用面板适配器 | Boot | +| `lichuang-dev`、`rymcu-bigsmart` | ST7789,320 × 240 | 通用面板适配器 | FT5x06 或 GT911,以及 Boot | +| `quandong-s3-dev` | ILI9341,320 × 240 | 通用面板适配器 | Boot | +| `mixgo-nova` | ST7789,128 × 160 | 通用面板适配器 | Boot | +| `taiji-pi-s3` | ST77916,360 × 360 圆屏 | 通用面板适配器 | CST816S 与 Boot | AtomEchoS3R 与 ReSpeaker Flex 没有 LCD,不编入 LVGL。ML307、Wi-Fi 及音视频协议与显示后端 相互独立。 @@ -48,7 +62,7 @@ THINKING显示对应表情,连续思考三秒后转PROCESSING,SPEAKING/无 [CoreS3 UI](CORES3_UI.zh-CN.md)。 CoreS3保留浅色主题字段兼容但不改变像素皮肤;动画开关控制表情运动/切换,关闭后仍推进 -开场保持并保留必要的引导文字滚动。其他十五个profile保留原UI/硬件;摄像头/视频策略、 +开场保持并保留必要的引导文字滚动。其他profile保留原UI/硬件;摄像头/视频策略、 输入、电源及内存配置不变。 ## 其他带屏板型的共享流程视图 @@ -76,6 +90,10 @@ Flash,不加入运行时 PNG/GIF 解码器或全屏缓存。 硬件集成负责复位、偏移、颜色顺序、 背光和销毁。 +圆屏 profile 设置 `MYBOT_DISPLAY_ROUND=1`,共享视图会把标题栏、状态卡和页脚收窄到中央 +安全矩形,避免控件落入圆角区域。当前只完成构建验证;颜色、方向、裁切和触摸坐标仍需逐板 +真机确认。 + ## 编译示例 不同板型和语言请使用独立构建目录,无需额外启用 UI: diff --git a/docs/RELEASING.md b/docs/RELEASING.md index c4b7501..0ac0ed0 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -107,6 +107,10 @@ git diff --check polarity and short/long presses, first-boot Wi-Fi AP and localized prompt, 16 kHz capture and playback pitch/noise and full-duplex quality, volume persistence, and repeated provisioning after mybot stop/start. Confirm the headless build omits LVGL. +- [ ] For every additional profile marked "Not completed" in the validation matrix, verify the + applicable panel/controller, touch or keys, codec/I2S path, reset and power ordering, round or + narrow UI clipping, provisioning, volume persistence, and repeated mybot start/stop. Do not + promote a profile to hardware-validated from build or host-test evidence alone. ## Publish diff --git a/docs/RELEASING.zh-CN.md b/docs/RELEASING.zh-CN.md index 35a1a35..56697bd 100644 --- a/docs/RELEASING.zh-CN.md +++ b/docs/RELEASING.zh-CN.md @@ -93,6 +93,9 @@ git diff --check - [ ] 对 M5Stack AtomEchoS3R,确认检测到 8 MB Flash 与 8 MB Octal PSRAM,验证 GPIO41 按键极性和短/长按、首次配网热点及对应语言提示音、16 kHz 采集/播放的音调与噪声和全双工 质量、音量持久化及 mybot 停止/重启后的重复配网。确认无屏固件不编入 LVGL。 +- [ ] 对验证矩阵中所有标记“未完成”的新增板型,按实际硬件验证面板/控制器、触摸或按键、 + codec/I2S 通路、复位与上电顺序、圆屏或窄屏裁切、配网、音量持久化及反复启停。不得仅凭 + 构建或宿主测试将板型升级为真机验证通过。 ## 发布 diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index e6025ad..2a178b2 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -37,6 +37,28 @@ unsupported areas. Board READMEs describe hardware and operation only; the | `xingzhi-cube-1.54tft-wifi` | CI: zh-CN, en-US; recorded merged build / 已记录合并构建 | Not completed / 未完成 | Physical Flash/PSRAM, GPIO21 power hold, backlight, display, buttons and 16 kHz audio; no touch, video, battery/charging, shutdown, sleep or low power. | | `zhengchen-1.54tft-ml307` | CI: zh-CN, en-US | Not completed / 未完成 | ML307/4G is not implemented; Wi-Fi remains the network path. Audio, panel and buttons still need device validation; no battery status, temperature monitoring, sleep or low power. | | `zhengchen-1.54tft-wifi` | CI: zh-CN, en-US | Not completed / 未完成 | Physical PSRAM capacity, audio, panel and buttons still need device validation; no modem/4G, battery/charging, temperature monitoring, sleep or low power. | +| `xingzhi-metal-1.54-wifi` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | PCA9557, ES8311, ST7789, touch and PA mapping require device validation; no battery, sleep or shutdown. | +| `xingzhi-cube-0.85tft-wifi` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | NV3023 panel, backlight, raw I2S and power require device validation; no touch or codec control. | +| `xingzhi-cube-0.96oled-wifi` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Headless subset; SSD1306 monochrome UI is not implemented. Raw I2S and volume keys require device validation. | +| `xingzhi-abs-2.0` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Wi-Fi subset only; SD, vibration, cellular, battery, PCA9557 power paths, sleep and shutdown are not implemented. | +| `m5stack-stopwatch` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | M5PM1/M5IOE1 sequencing, CO5300 466×466 panel, CST820B touch, buttons, audio and round safe area require device validation; battery reporting and motor are not implemented. | +| `esp32-s3-touch-amoled-1.8` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | TCA9554/AXP2101 sequencing, SH8601 368×448 timing and FT5x06 mapping require device validation. | +| `esp32-s3-touch-amoled-1.32` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | SH8601 466×466 timing, power rail, audio and panel offset require device validation; touch is not implemented. | +| `esp32-s3-touch-amoled-1.43c` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | SH8601 466×466 timing, ES8311/ES7210 routing and power require device validation; touch is not implemented. | +| `esp32-s3-touch-amoled-2.06` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | SH8601 410×502 timing, 22-pixel X gap, AXP2101, FT5x06 and dual-codec routing require device validation. | +| `atoms3r-echo-base` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Display/audio mapping requires validation; companion IO/power and LP5562 backlight integration are not implemented. | +| `atoms3r-echo-pyramid` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Display/dual-codec mapping requires validation; companion IO/power and LP5562 backlight integration are not implemented. | +| `sp-esp32-s3-1.28-box` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | GC9A01 color/orientation, touch, backlight and power require device validation; battery/charging reporting is not implemented. | +| `esp-box` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ILI9341 initialization, audio routing, touch and board PMIC/power sequencing require device validation. | +| `esp-box-3` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ILI9341 initialization, audio routing, touch and board PMIC/power sequencing require device validation. | +| `esp-box-lite` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Codec assignment, ILI9341, power sequencing and ADC buttons require validation; ADC-button support is not implemented. | +| `esp32s3-korvo2-v3` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | TCA9554 reset/CS, ILI9341, dual-codec routing and board power require device validation; ADC keys, camera and battery are not implemented. | +| `lichuang-dev` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | PCA9557 display/PA control, FT5x06, ST7789 and dual-codec routing require device validation; camera and battery are not implemented. | +| `quandong-s3-dev` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | PCA9557 output mapping, active-low PA, ILI9341 and audio routing require device validation. | +| `rymcu-bigsmart` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | PCA9557 display/PA control, GT911, ST7789 and dual-codec routing require device validation; camera and battery are not implemented. | +| `df-k10` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ILI9341 and dual-codec mapping require validation; TCA9555 keys, camera, LED ring and board power paths are not implemented. | +| `mixgo-nova` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ES8374/ST7789 mapping, power, audio and 128×160 UI require device validation; RGB LED is not implemented. | +| `taiji-pi-s3` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST77916, CST816S, PDM capture, I2S playback, mute/PA and round UI require device validation; board power sequencing is not complete. | ## Global boundaries / 全局边界 diff --git a/scripts/build_all.py b/scripts/build_all.py index f9c2dcc..d7cd2ce 100644 --- a/scripts/build_all.py +++ b/scripts/build_all.py @@ -57,7 +57,10 @@ def supported_boards() -> list[BoardProfile]: profiles.append( BoardProfile( name=name, - has_display="mybot_display_add_" in profile, + has_display=( + "mybot_display_add_" in profile + or "set(MYBOT_GENERIC_DISPLAY " in profile + ), video_capable="cores3_camera_video.c" in profile, ) ) From 420815318a891f93ef2063aa951f84f1618da536 Mon Sep 17 00:00:00 2001 From: shangjinlong Date: Tue, 27 Oct 2026 17:44:06 +0800 Subject: [PATCH 3/5] feat(platform): add ten more ESP32-S3 board profiles --- .github/workflows/ci.yml | 10 +++ CHANGELOG.md | 7 ++ README.md | 7 +- README.zh-CN.md | 7 +- components/mybot_platform/boards/README.md | 12 ++- .../mybot_platform/boards/aipi-lite/README.md | 14 ++++ .../mybot_platform/boards/aipi-lite/board.c | 6 ++ .../boards/aipi-lite/board.cmake | 16 ++++ .../boards/aipi-lite/board_config.h | 61 +++++++++++++++ .../boards/aipi-lite/sdkconfig.defaults | 10 +++ .../boards/atk-dnesp32s3-box0/README.md | 13 ++++ .../boards/atk-dnesp32s3-box0/board.c | 6 ++ .../boards/atk-dnesp32s3-box0/board.cmake | 16 ++++ .../boards/atk-dnesp32s3-box0/board_config.h | 62 +++++++++++++++ .../atk-dnesp32s3-box0/sdkconfig.defaults | 10 +++ components/mybot_platform/boards/boards.cmake | 30 ++++++++ .../boards/common/generic_hardware.c | 23 +++++- .../boards/esp32-s3-lcd-0.85/README.md | 13 ++++ .../boards/esp32-s3-lcd-0.85/board.c | 6 ++ .../boards/esp32-s3-lcd-0.85/board.cmake | 17 +++++ .../boards/esp32-s3-lcd-0.85/board_config.h | 60 +++++++++++++++ .../esp32-s3-lcd-0.85/sdkconfig.defaults | 11 +++ .../boards/esp32-s3-touch-lcd-1.54/README.md | 13 ++++ .../boards/esp32-s3-touch-lcd-1.54/board.c | 6 ++ .../esp32-s3-touch-lcd-1.54/board.cmake | 19 +++++ .../esp32-s3-touch-lcd-1.54/board_config.h | 73 ++++++++++++++++++ .../sdkconfig.defaults | 12 +++ .../boards/esp32-s3-touch-lcd-1.83/README.md | 13 ++++ .../boards/esp32-s3-touch-lcd-1.83/board.c | 6 ++ .../esp32-s3-touch-lcd-1.83/board.cmake | 19 +++++ .../esp32-s3-touch-lcd-1.83/board_config.h | 73 ++++++++++++++++++ .../sdkconfig.defaults | 12 +++ .../boards/esp32-s3-touch-lcd-1.85b/README.md | 14 ++++ .../boards/esp32-s3-touch-lcd-1.85b/board.c | 6 ++ .../esp32-s3-touch-lcd-1.85b/board.cmake | 20 +++++ .../esp32-s3-touch-lcd-1.85b/board_config.h | 73 ++++++++++++++++++ .../sdkconfig.defaults | 12 +++ .../boards/esp32-s3-touch-lcd-1.85c/README.md | 14 ++++ .../boards/esp32-s3-touch-lcd-1.85c/board.c | 6 ++ .../esp32-s3-touch-lcd-1.85c/board.cmake | 21 +++++ .../esp32-s3-touch-lcd-1.85c/board_config.h | 76 +++++++++++++++++++ .../sdkconfig.defaults | 12 +++ .../README.md | 13 ++++ .../freenove-esp32s3-display-2.8-lcd/board.c | 6 ++ .../board.cmake | 17 +++++ .../board_config.h | 62 +++++++++++++++ .../sdkconfig.defaults | 10 +++ .../boards/nulllab-ai-vox-v3/README.md | 13 ++++ .../boards/nulllab-ai-vox-v3/board.c | 6 ++ .../boards/nulllab-ai-vox-v3/board.cmake | 16 ++++ .../boards/nulllab-ai-vox-v3/board_config.h | 60 +++++++++++++++ .../nulllab-ai-vox-v3/sdkconfig.defaults | 10 +++ .../boards/sp-esp32-s3-1.54-muma/README.md | 13 ++++ .../boards/sp-esp32-s3-1.54-muma/board.c | 6 ++ .../boards/sp-esp32-s3-1.54-muma/board.cmake | 18 +++++ .../sp-esp32-s3-1.54-muma/board_config.h | 76 +++++++++++++++++++ .../sp-esp32-s3-1.54-muma/sdkconfig.defaults | 11 +++ docs/BOARD_PORTING.md | 4 + docs/BOARD_PORTING.zh-CN.md | 4 + docs/PLATFORM_UI.md | 6 ++ docs/PLATFORM_UI.zh-CN.md | 6 ++ docs/VALIDATION.md | 10 +++ 62 files changed, 1280 insertions(+), 4 deletions(-) create mode 100644 components/mybot_platform/boards/aipi-lite/README.md create mode 100644 components/mybot_platform/boards/aipi-lite/board.c create mode 100644 components/mybot_platform/boards/aipi-lite/board.cmake create mode 100644 components/mybot_platform/boards/aipi-lite/board_config.h create mode 100644 components/mybot_platform/boards/aipi-lite/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/atk-dnesp32s3-box0/README.md create mode 100644 components/mybot_platform/boards/atk-dnesp32s3-box0/board.c create mode 100644 components/mybot_platform/boards/atk-dnesp32s3-box0/board.cmake create mode 100644 components/mybot_platform/boards/atk-dnesp32s3-box0/board_config.h create mode 100644 components/mybot_platform/boards/atk-dnesp32s3-box0/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32-s3-lcd-0.85/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-lcd-0.85/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-lcd-0.85/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-lcd-0.85/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-lcd-0.85/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/README.md create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board.c create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board.cmake create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board_config.h create mode 100644 components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/README.md create mode 100644 components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board.c create mode 100644 components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board.cmake create mode 100644 components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board_config.h create mode 100644 components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/nulllab-ai-vox-v3/README.md create mode 100644 components/mybot_platform/boards/nulllab-ai-vox-v3/board.c create mode 100644 components/mybot_platform/boards/nulllab-ai-vox-v3/board.cmake create mode 100644 components/mybot_platform/boards/nulllab-ai-vox-v3/board_config.h create mode 100644 components/mybot_platform/boards/nulllab-ai-vox-v3/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/sp-esp32-s3-1.54-muma/README.md create mode 100644 components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board.c create mode 100644 components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board.cmake create mode 100644 components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board_config.h create mode 100644 components/mybot_platform/boards/sp-esp32-s3-1.54-muma/sdkconfig.defaults diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1175a6f..c474cbb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -306,6 +306,16 @@ jobs: - df-k10 - mixgo-nova - taiji-pi-s3 + - freenove-esp32s3-display-2.8-lcd + - esp32-s3-touch-lcd-1.54 + - esp32-s3-touch-lcd-1.83 + - esp32-s3-lcd-0.85 + - esp32-s3-touch-lcd-1.85b + - esp32-s3-touch-lcd-1.85c + - sp-esp32-s3-1.54-muma + - atk-dnesp32s3-box0 + - nulllab-ai-vox-v3 + - aipi-lite language: [zh-CN, en-US] steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index f1ea6e9..4c13507 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ Versioning and Conventional Commits. ### Added +- Ten additional ESP32-S3 profiles: eight ST7789/ILI9341 boards using ES8311 or + ES8311+ES7210 audio, followed by ST77916 LCD 1.85B/1.85C and SP 1.54 Muma. The batch adds + compact 128×128 layout coverage, AXP2101/TCA9554 reset paths, a secondary board power rail, + CST816S touch, and board-specific READMEs. Chinese and English local merged builds passed; + real-device validation remains pending. - Twenty-two additional ESP32-S3 board profiles covering NV3023, GC9A01, ILI9341, SH8601 and ST77916 panels; PCA9557/TCA9554, M5PM1/M5IOE1 and AXP2101 reset/power paths; FT5x06, GT911, CST816S-compatible touch and GPIO input; ES8311, ES7210, ES8374 and raw/PDM audio paths. @@ -91,6 +96,8 @@ Versioning and Conventional Commits. ### Changed +- Extend the shared board skeleton with an optional secondary power rail for profiles that keep + codec and system power on separate GPIOs. - Centralize generic hardware sequencing, round-screen safe-area layout, dual-I2C selection, speaker PA control and display/audio capability derivation so additional profiles do not copy board lifecycle code. diff --git a/README.md b/README.md index 8ca8529..421bf8e 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ESP32-S3 boards. The project owns board initialization, Wi-Fi provisioning, persistent storage, HTTPS transport, audio, input, display, and the firmware lifecycle around the SDK. -The development baseline is **ESP-IDF v5.5.2**. Thirty-eight compile-time board profiles are +The development baseline is **ESP-IDF v5.5.2**. Forty-eight compile-time board profiles are supported; the board, Flash, PSRAM, partition table, and hardware drivers are selected with `MYBOT_BOARD`. See the [board documentation index](components/mybot_platform/boards/README.md) for hardware details, controls, build commands, and per-board limitations. @@ -47,6 +47,11 @@ Display profiles: `esp32s3-korvo2-v3`, `lichuang-dev`, `quandong-s3-dev`, `rymcu-bigsmart`, `df-k10`, `mixgo-nova`, `taiji-pi-s3`, `m5stack-stopwatch`, `xingzhi-metal-1.54-wifi`, `xingzhi-cube-0.85tft-wifi`, `xingzhi-abs-2.0`, +`freenove-esp32s3-display-2.8-lcd`, `esp32-s3-touch-lcd-1.54`, +`esp32-s3-touch-lcd-1.83`, `esp32-s3-lcd-0.85`, +`esp32-s3-touch-lcd-1.85b`, `esp32-s3-touch-lcd-1.85c`, +`sp-esp32-s3-1.54-muma`, `atk-dnesp32s3-box0`, `nulllab-ai-vox-v3`, +`aipi-lite`, `genjutech-s3-1.54tft`, `m5stack-core-s3`, `m5stack-stick-s3`, and `sensecap-watcher`. diff --git a/README.zh-CN.md b/README.zh-CN.md index c232a16..4a29bd8 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -9,7 +9,7 @@ 工程负责板级初始化、Wi-Fi 配网、持久化存储、HTTPS、音频、输入、显示,以及 SDK 外围的 固件生命周期。 -当前开发基线为 **ESP-IDF v5.5.2**,支持 38 个编译期 Board profile。通过 `MYBOT_BOARD` +当前开发基线为 **ESP-IDF v5.5.2**,支持 48 个编译期 Board profile。通过 `MYBOT_BOARD` 选择板型、Flash、PSRAM、分区表和硬件驱动。具体硬件配置、输入操作、构建命令和限制见 [板卡说明索引](components/mybot_platform/boards/README.md)。 @@ -43,6 +43,11 @@ `esp32s3-korvo2-v3`、`lichuang-dev`、`quandong-s3-dev`、`rymcu-bigsmart`、 `df-k10`、`mixgo-nova`、`taiji-pi-s3`、`m5stack-stopwatch`、 `xingzhi-metal-1.54-wifi`、`xingzhi-cube-0.85tft-wifi`、`xingzhi-abs-2.0`、 +`freenove-esp32s3-display-2.8-lcd`、`esp32-s3-touch-lcd-1.54`、 +`esp32-s3-touch-lcd-1.83`、`esp32-s3-lcd-0.85`、 +`esp32-s3-touch-lcd-1.85b`、`esp32-s3-touch-lcd-1.85c`、 +`sp-esp32-s3-1.54-muma`、`atk-dnesp32s3-box0`、`nulllab-ai-vox-v3`、 +`aipi-lite`、 `genjutech-s3-1.54tft`、`m5stack-core-s3`、`m5stack-stick-s3` 和 `sensecap-watcher`。 diff --git a/components/mybot_platform/boards/README.md b/components/mybot_platform/boards/README.md index a0e1d54..c35949d 100644 --- a/components/mybot_platform/boards/README.md +++ b/components/mybot_platform/boards/README.md @@ -5,7 +5,7 @@ drivers and build constraints are defined by the checked-in `board.cmake`, `boar `sdkconfig.defaults` files. Current validation status is centralized in [`docs/VALIDATION.md`](../../../docs/VALIDATION.md). -以下说明对应本工程已实现的 38 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; +以下说明对应本工程已实现的 48 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; 验证状态统一见[验证矩阵](../../../docs/VALIDATION.md)。不同尺寸、芯片或硬件版本的板卡 不能仅凭外观共用固件。 @@ -61,6 +61,16 @@ real-device validation. | [df-k10](df-k10/README.md) | 240×320 ILI9341 | ES8311 + ES7210 | Boot | TCA9555 keys, camera, LED ring and power paths are not implemented. | | [mixgo-nova](mixgo-nova/README.md) | 128×160 ST7789 | ES8374 | Boot | RGB LED and hardware validation are pending. | | [taiji-pi-s3](taiji-pi-s3/README.md) | 360×360 round ST77916 QSPI | ES8311 playback + PDM capture | CST816S + Boot | PDM capture and mute/PA lifecycle are implemented; board power sequencing requires validation. | +| [freenove-esp32s3-display-2.8-lcd](freenove-esp32s3-display-2.8-lcd/README.md) | 320×240 ILI9341 | ES8311 | Boot | Camera, LED and battery paths are not implemented. | +| [esp32-s3-touch-lcd-1.54](esp32-s3-touch-lcd-1.54/README.md) | 240×240 ST7789 | ES8311 + ES7210 | CST816S + three buttons | Battery management, sleep and shutdown are not implemented. | +| [esp32-s3-touch-lcd-1.83](esp32-s3-touch-lcd-1.83/README.md) | 240×284 ST7789 | ES8311 + ES7210 | CST816S + Boot | AXP2101, PWR41, battery reporting and sleep require device validation. | +| [esp32-s3-lcd-0.85](esp32-s3-lcd-0.85/README.md) | 128×128 ST7789 | ES8311 + ES7210 | three buttons | Touchless profile; battery timing and 8 MB layout require validation. | +| [esp32-s3-touch-lcd-1.85b](esp32-s3-touch-lcd-1.85b/README.md) | 360×360 ST77916 QSPI | ES8311 + ES7210 | CST816S-compatible touch + Boot | Vendor panel revisions and touch mapping require validation. | +| [esp32-s3-touch-lcd-1.85c](esp32-s3-touch-lcd-1.85c/README.md) | 360×360 ST77916 QSPI | ES8311 + ES7210 V2 path | CST816S-compatible touch + Boot; TCA9554 reset | V1 raw-I2S path and touch mapping require validation. | +| [sp-esp32-s3-1.54-muma](sp-esp32-s3-1.54-muma/README.md) | 240×240 ST7789 | ES8311 | CST816S + Boot | Vendor panel tuning, battery and power hold require validation. | +| [atk-dnesp32s3-box0](atk-dnesp32s3-box0/README.md) | 240×240 ST7789 | ES8311 | three buttons | Sleep, shutdown, charging and battery reporting are not implemented. | +| [nulllab-ai-vox-v3](nulllab-ai-vox-v3/README.md) | 240×240 ST7789 | ES8311 | Boot + two volume keys | Wi-Fi subset only; ML307 networking is not implemented. | +| [aipi-lite](aipi-lite/README.md) | 128×128 ST7789 | ES8311 | Boot | Power key, sleep and battery reporting are not implemented. | ## Build, flash and validation / 构建、烧录与验证 diff --git a/components/mybot_platform/boards/aipi-lite/README.md b/components/mybot_platform/boards/aipi-lite/README.md new file mode 100644 index 0000000..abee946 --- /dev/null +++ b/components/mybot_platform/boards/aipi-lite/README.md @@ -0,0 +1,14 @@ +# AIPI Lite + +板型 ID 为 `aipi-lite`。 + +- 显示:128×128 ST7789,SPI3 mode 0,40 MHz,共享 LVGL 紧凑布局。 +- 音频:ES8311,I2C1 GPIO5/4,GPIO9 speaker PA。 +- 输入:GPIO42 Boot;GPIO1 电源键未接入。 +- 电源:GPIO10 电源保持。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO3 LEDC,默认 60%。 + +电源键动作、电池检测、休眠、关机、面板和音频仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/aipi-lite/board.c b/components/mybot_platform/boards/aipi-lite/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/aipi-lite/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/aipi-lite/board.cmake b/components/mybot_platform/boards/aipi-lite/board.cmake new file mode 100644 index 0000000..6f155e4 --- /dev/null +++ b/components/mybot_platform/boards/aipi-lite/board.cmake @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/aipi-lite/board_config.h b/components/mybot_platform/boards/aipi-lite/board_config.h new file mode 100644 index 0000000..c18e8f9 --- /dev/null +++ b/components/mybot_platform/boards/aipi-lite/board_config.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "aipi-lite" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_14 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_12 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_13 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_11 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_5 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_4 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_POWER_GPIO GPIO_NUM_10 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_9 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_42 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_17 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_16 +#define MYBOT_DISPLAY_DC GPIO_NUM_7 +#define MYBOT_DISPLAY_CS GPIO_NUM_15 +#define MYBOT_DISPLAY_RESET GPIO_NUM_18 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_3 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 128 +#define MYBOT_DISPLAY_HEIGHT 128 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY true +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/aipi-lite/sdkconfig.defaults b/components/mybot_platform/boards/aipi-lite/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/aipi-lite/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/atk-dnesp32s3-box0/README.md b/components/mybot_platform/boards/atk-dnesp32s3-box0/README.md new file mode 100644 index 0000000..8e355af --- /dev/null +++ b/components/mybot_platform/boards/atk-dnesp32s3-box0/README.md @@ -0,0 +1,13 @@ +# ATK DNESP32S3 Box0 + +板型 ID 为 `atk-dnesp32s3-box0`。 + +- 显示:240×240 ST7789,SPI2,80 MHz,共享 LVGL UI。 +- 音频:ES8311,I2C0 GPIO11/12,GPIO21 speaker PA。 +- 输入:GPIO4 主按键、GPIO0 音量加、GPIO3 音量减。 +- 电源:GPIO2 系统保持、GPIO14 codec 电源、GPIO42 背光。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 + +电源时序、面板、音频、按键和电池检测仍需真机验证;休眠、深度睡眠和关机未实现。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/atk-dnesp32s3-box0/board.c b/components/mybot_platform/boards/atk-dnesp32s3-box0/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/atk-dnesp32s3-box0/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/atk-dnesp32s3-box0/board.cmake b/components/mybot_platform/boards/atk-dnesp32s3-box0/board.cmake new file mode 100644 index 0000000..6f155e4 --- /dev/null +++ b/components/mybot_platform/boards/atk-dnesp32s3-box0/board.cmake @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/atk-dnesp32s3-box0/board_config.h b/components/mybot_platform/boards/atk-dnesp32s3-box0/board_config.h new file mode 100644 index 0000000..2e3e5ad --- /dev/null +++ b/components/mybot_platform/boards/atk-dnesp32s3-box0/board_config.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "atk-dnesp32s3-box0" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_13 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_10 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_9 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_11 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_12 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_POWER_GPIO GPIO_NUM_2 +#define MYBOT_GENERIC_AUX_POWER_GPIO GPIO_NUM_14 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_21 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_4 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_3 +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_40 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_39 +#define MYBOT_DISPLAY_DC GPIO_NUM_38 +#define MYBOT_DISPLAY_CS GPIO_NUM_41 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_42 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (80 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/atk-dnesp32s3-box0/sdkconfig.defaults b/components/mybot_platform/boards/atk-dnesp32s3-box0/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/atk-dnesp32s3-box0/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/boards.cmake b/components/mybot_platform/boards/boards.cmake index 33be7dc..9d0a829 100644 --- a/components/mybot_platform/boards/boards.cmake +++ b/components/mybot_platform/boards/boards.cmake @@ -11,6 +11,11 @@ set(MYBOT_SUPPORTED_BOARDS "esp32-s3-touch-amoled-1.43c" "esp32-s3-touch-amoled-2.06" "esp32-s3-touch-lcd-1.85" + "esp32-s3-touch-lcd-1.85b" + "esp32-s3-touch-lcd-1.85c" + "esp32-s3-touch-lcd-1.54" + "esp32-s3-touch-lcd-1.83" + "esp32-s3-lcd-0.85" "esp-box" "esp-box-3" "esp-box-lite" @@ -18,17 +23,22 @@ set(MYBOT_SUPPORTED_BOARDS "genjutech-s3-1.54tft" "atoms3r-echo-base" "atoms3r-echo-pyramid" + "aipi-lite" + "atk-dnesp32s3-box0" "df-k10" + "freenove-esp32s3-display-2.8-lcd" "lichuang-dev" "m5stack-core-s3" "m5stack-stick-s3" "m5stack-stopwatch" "mixgo-nova" + "nulllab-ai-vox-v3" "quandong-s3-dev" "respeaker-flex-xvf3800-circular4-xiao" "rymcu-bigsmart" "sensecap-watcher" "sp-esp32-s3-1.28-box" + "sp-esp32-s3-1.54-muma" "taiji-pi-s3" "xingzhi-abs-2.0" "xingzhi-cube-0.85tft-wifi" @@ -65,6 +75,16 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-amoled-2.06/board.cmake") elseif(board STREQUAL "esp32-s3-touch-lcd-1.85") set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-lcd-1.85/board.cmake") + elseif(board STREQUAL "esp32-s3-touch-lcd-1.85b") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-lcd-1.85b/board.cmake") + elseif(board STREQUAL "esp32-s3-touch-lcd-1.85c") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-lcd-1.85c/board.cmake") + elseif(board STREQUAL "esp32-s3-touch-lcd-1.54") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-lcd-1.54/board.cmake") + elseif(board STREQUAL "esp32-s3-touch-lcd-1.83") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-touch-lcd-1.83/board.cmake") + elseif(board STREQUAL "esp32-s3-lcd-0.85") + set(profile "${_MYBOT_BOARDS_ROOT}/esp32-s3-lcd-0.85/board.cmake") elseif(board STREQUAL "esp-box") set(profile "${_MYBOT_BOARDS_ROOT}/esp-box/board.cmake") elseif(board STREQUAL "esp-box-3") @@ -79,6 +99,10 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/atoms3r-echo-base/board.cmake") elseif(board STREQUAL "atoms3r-echo-pyramid") set(profile "${_MYBOT_BOARDS_ROOT}/atoms3r-echo-pyramid/board.cmake") + elseif(board STREQUAL "aipi-lite") + set(profile "${_MYBOT_BOARDS_ROOT}/aipi-lite/board.cmake") + elseif(board STREQUAL "atk-dnesp32s3-box0") + set(profile "${_MYBOT_BOARDS_ROOT}/atk-dnesp32s3-box0/board.cmake") elseif(board STREQUAL "df-k10") set(profile "${_MYBOT_BOARDS_ROOT}/df-k10/board.cmake") elseif(board STREQUAL "lichuang-dev") @@ -91,6 +115,8 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/m5stack-stopwatch/board.cmake") elseif(board STREQUAL "mixgo-nova") set(profile "${_MYBOT_BOARDS_ROOT}/mixgo-nova/board.cmake") + elseif(board STREQUAL "nulllab-ai-vox-v3") + set(profile "${_MYBOT_BOARDS_ROOT}/nulllab-ai-vox-v3/board.cmake") elseif(board STREQUAL "quandong-s3-dev") set(profile "${_MYBOT_BOARDS_ROOT}/quandong-s3-dev/board.cmake") elseif(board STREQUAL "respeaker-flex-xvf3800-circular4-xiao") @@ -102,6 +128,8 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/sensecap-watcher/board.cmake") elseif(board STREQUAL "sp-esp32-s3-1.28-box") set(profile "${_MYBOT_BOARDS_ROOT}/sp-esp32-s3-1.28-box/board.cmake") + elseif(board STREQUAL "sp-esp32-s3-1.54-muma") + set(profile "${_MYBOT_BOARDS_ROOT}/sp-esp32-s3-1.54-muma/board.cmake") elseif(board STREQUAL "taiji-pi-s3") set(profile "${_MYBOT_BOARDS_ROOT}/taiji-pi-s3/board.cmake") elseif(board STREQUAL "xingzhi-abs-2.0") @@ -118,6 +146,8 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/zhengchen-1.54tft-ml307/board.cmake") elseif(board STREQUAL "zhengchen-1.54tft-wifi") set(profile "${_MYBOT_BOARDS_ROOT}/zhengchen-1.54tft-wifi/board.cmake") + elseif(board STREQUAL "freenove-esp32s3-display-2.8-lcd") + set(profile "${_MYBOT_BOARDS_ROOT}/freenove-esp32s3-display-2.8-lcd/board.cmake") else() message(FATAL_ERROR "Unsupported MYBOT_BOARD: ${board}") endif() diff --git a/components/mybot_platform/boards/common/generic_hardware.c b/components/mybot_platform/boards/common/generic_hardware.c index fafae3c..614cb3c 100644 --- a/components/mybot_platform/boards/common/generic_hardware.c +++ b/components/mybot_platform/boards/common/generic_hardware.c @@ -44,6 +44,14 @@ #define MYBOT_GENERIC_POWER_ACTIVE_LEVEL 1 #endif +#ifndef MYBOT_GENERIC_AUX_POWER_GPIO +#define MYBOT_GENERIC_AUX_POWER_GPIO GPIO_NUM_NC +#endif + +#ifndef MYBOT_GENERIC_AUX_POWER_ACTIVE_LEVEL +#define MYBOT_GENERIC_AUX_POWER_ACTIVE_LEVEL 1 +#endif + typedef struct { i2c_master_bus_handle_t audio_bus; i2c_master_bus_handle_t touch_bus; @@ -54,6 +62,7 @@ typedef struct { bool pa_ready; bool pa_enabled; bool power_ready; + bool aux_power_ready; bool backlight_ready; bool stopwatch_hardware_ready; bool initialized; @@ -166,6 +175,12 @@ static int release_hardware(void) { } #endif s_hardware.power_ready = false; + if (s_hardware.aux_power_ready && + gpio_set_level(MYBOT_GENERIC_AUX_POWER_GPIO, !MYBOT_GENERIC_AUX_POWER_ACTIVE_LEVEL) != + ESP_OK) { + result = -1; + } + s_hardware.aux_power_ready = false; #if MYBOT_GENERIC_M5STACK_STOPWATCH if (s_hardware.stopwatch_hardware_ready && mybot_m5stack_stopwatch_hardware_deinit() < 0) { result = -1; @@ -214,7 +229,7 @@ int mybot_generic_hardware_init(void) { } if ((s_hardware.audio_bus || s_hardware.touch_bus || s_hardware.expander_ready || s_hardware.tca9554.device || s_hardware.axp2101 || s_hardware.pa_ready || - s_hardware.power_ready || s_hardware.backlight_ready) && + s_hardware.power_ready || s_hardware.aux_power_ready || s_hardware.backlight_ready) && release_hardware() < 0) { return -1; } @@ -229,6 +244,12 @@ int mybot_generic_hardware_init(void) { #else esp_err_t result = ESP_OK; #endif + result = + configure_output_gpio(MYBOT_GENERIC_AUX_POWER_GPIO, MYBOT_GENERIC_AUX_POWER_ACTIVE_LEVEL); + if (result != ESP_OK) { + return -1; + } + s_hardware.aux_power_ready = MYBOT_GENERIC_AUX_POWER_GPIO != GPIO_NUM_NC; const bool touch_separate_bus = #ifdef MYBOT_GENERIC_TOUCH_I2C_PORT diff --git a/components/mybot_platform/boards/esp32-s3-lcd-0.85/README.md b/components/mybot_platform/boards/esp32-s3-lcd-0.85/README.md new file mode 100644 index 0000000..89fc668 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-lcd-0.85/README.md @@ -0,0 +1,13 @@ +# ESP32-S3 LCD 0.85 + +板型 ID 为 `esp32-s3-lcd-0.85`。 + +- 显示:128×128 ST7789,SPI3 mode 3,40 MHz,偏移 `(2,1)`。 +- 音频:ES8311 与 ES7210,16 kHz mono signed-16 PCM。 +- 输入:GPIO5 主按键、GPIO4 音量加、GPIO0 音量减。 +- 电源:GPIO2 电池使能;GPIO46 背光;GPIO7 speaker PA。 +- 存储:8 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 + +当前无触摸。小屏 UI、面板方向、音频、电池和按键仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-lcd-0.85/board.c b/components/mybot_platform/boards/esp32-s3-lcd-0.85/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-lcd-0.85/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp32-s3-lcd-0.85/board.cmake b/components/mybot_platform/boards/esp32-s3-lcd-0.85/board.cmake new file mode 100644 index 0000000..3ffeed0 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-lcd-0.85/board.cmake @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/8m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_8MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp32-s3-lcd-0.85/board_config.h b/components/mybot_platform/boards/esp32-s3-lcd-0.85/board_config.h new file mode 100644 index 0000000..1a91517 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-lcd-0.85/board_config.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-lcd-0.85" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_8 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_10 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_11 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_12 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_42 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_41 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_7 +#define MYBOT_GENERIC_AUX_POWER_GPIO GPIO_NUM_2 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_5 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_4 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_39 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_38 +#define MYBOT_DISPLAY_DC GPIO_NUM_45 +#define MYBOT_DISPLAY_CS GPIO_NUM_21 +#define MYBOT_DISPLAY_RESET GPIO_NUM_40 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_46 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 128 +#define MYBOT_DISPLAY_HEIGHT 128 +#define MYBOT_DISPLAY_OFFSET_X 2 +#define MYBOT_DISPLAY_OFFSET_Y 1 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 3 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-lcd-0.85/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-lcd-0.85/sdkconfig.defaults new file mode 100644 index 0000000..ece839c --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-lcd-0.85/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/8m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/README.md b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/README.md new file mode 100644 index 0000000..3fea534 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/README.md @@ -0,0 +1,13 @@ +# ESP32-S3 Touch LCD 1.54 + +板型 ID 为 `esp32-s3-touch-lcd-1.54`。 + +- 显示:240×240 ST7789,SPI3 mode 3,40 MHz,共享 LVGL UI。 +- 音频:ES8311 与 ES7210,16 kHz mono signed-16 PCM。 +- 输入:CST816S 触摸、GPIO5 主按键、GPIO4 音量加、GPIO0 音量减。 +- 电源:GPIO2 电池/外设使能。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 + +AXP/电池管理未完整接入;触摸、音频、面板、电源和按键仍需真机验证。睡眠与关机未实现。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board.c b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board.cmake b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board.cmake new file mode 100644 index 0000000..978c07e --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board.cmake @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "cst816s") +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT + CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board_config.h b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board_config.h new file mode 100644 index 0000000..07cabe4 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/board_config.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-touch-lcd-1.54" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_8 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_10 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_11 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_12 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_42 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_41 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_7 +#define MYBOT_GENERIC_AUX_POWER_GPIO GPIO_NUM_2 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_5 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_4 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_TOUCH_DRIVER_KIND 1 +#define MYBOT_TOUCH_I2C_SPEED_HZ 100000 +#define MYBOT_TOUCH_RESET GPIO_NUM_47 +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_48 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_39 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_38 +#define MYBOT_DISPLAY_DC GPIO_NUM_45 +#define MYBOT_DISPLAY_CS GPIO_NUM_21 +#define MYBOT_DISPLAY_RESET GPIO_NUM_40 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_46 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 3 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/sdkconfig.defaults new file mode 100644 index 0000000..58f71c7 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.54/sdkconfig.defaults @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n +CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID=y diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/README.md b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/README.md new file mode 100644 index 0000000..7819bdf --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/README.md @@ -0,0 +1,13 @@ +# ESP32-S3 Touch LCD 1.83 + +板型 ID 为 `esp32-s3-touch-lcd-1.83`。 + +- 显示:240×284 ST7789,SPI3 mode 0,24 MHz,共享 LVGL UI。 +- 音频:ES8311 与 ES7210,16 kHz mono signed-16 PCM。 +- 输入:CST816S 触摸,GPIO0 Boot。 +- 电源:AXP2101 `0x34`;GPIO46 speaker PA。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 + +AXP2101 电源、触摸、面板和双 codec 音频仍需真机验证。PWR41、电池上报、睡眠和关机未接入。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board.c b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board.cmake b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board.cmake new file mode 100644 index 0000000..978c07e --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board.cmake @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "cst816s") +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT + CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board_config.h b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board_config.h new file mode 100644 index 0000000..8a8eadf --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/board_config.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-touch-lcd-1.83" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_45 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_10 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_8 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_15 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_14 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_46 +#define MYBOT_GENERIC_AXP2101_ADDRESS 0x34 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_TOUCH_DRIVER_KIND 1 +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_39 +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_13 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_7 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_6 +#define MYBOT_DISPLAY_DC GPIO_NUM_4 +#define MYBOT_DISPLAY_CS GPIO_NUM_5 +#define MYBOT_DISPLAY_RESET GPIO_NUM_38 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_40 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 284 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (24 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/sdkconfig.defaults new file mode 100644 index 0000000..58f71c7 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.83/sdkconfig.defaults @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n +CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID=y diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/README.md b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/README.md new file mode 100644 index 0000000..dbe4ad5 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/README.md @@ -0,0 +1,14 @@ +# ESP32-S3 Touch LCD 1.85B + +板型 ID 为 `esp32-s3-touch-lcd-1.85b`。 + +- 显示:360×360 ST77916 QSPI,SPI2,共享 LVGL UI。 +- 音频:ES8311 与 ES7210,16 kHz mono signed-16 PCM。 +- 输入:CST816S 兼容触摸和 GPIO0 Boot。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 面板复位:GPIO3;背光:GPIO5。 + +该板存在面板版本差异,当前使用 ST77916 默认初始化,厂商版本探测和专用初始化尚未接入。 +触摸型号/坐标、面板时序/颜色、音频和电源仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board.c b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board.cmake b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board.cmake new file mode 100644 index 0000000..5b264b6 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board.cmake @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "generic/qspi_st77916_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "cst816s") +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT + CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_st77916) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board_config.h b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board_config.h new file mode 100644 index 0000000..a75f1ae --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/board_config.h @@ -0,0 +1,73 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-touch-lcd-1.85b" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_2 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_48 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_47 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_11 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_10 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_9 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_TOUCH_DRIVER_KIND 1 +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_1 +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_4 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_PCLK GPIO_NUM_40 +#define MYBOT_DISPLAY_DATA0 GPIO_NUM_46 +#define MYBOT_DISPLAY_DATA1 GPIO_NUM_45 +#define MYBOT_DISPLAY_DATA2 GPIO_NUM_42 +#define MYBOT_DISPLAY_DATA3 GPIO_NUM_41 +#define MYBOT_DISPLAY_CS GPIO_NUM_21 +#define MYBOT_DISPLAY_RESET GPIO_NUM_3 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_5 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 360 +#define MYBOT_DISPLAY_HEIGHT 360 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/sdkconfig.defaults new file mode 100644 index 0000000..58f71c7 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85b/sdkconfig.defaults @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n +CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID=y diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/README.md b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/README.md new file mode 100644 index 0000000..5e4f1e5 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/README.md @@ -0,0 +1,14 @@ +# ESP32-S3 Touch LCD 1.85C + +板型 ID 为 `esp32-s3-touch-lcd-1.85c`。 + +- 显示:360×360 ST77916 QSPI,SPI2,共享 LVGL UI。 +- 音频:当前实现 V2 的 ES8311 + ES7210 路径。 +- 输入:CST816S 兼容触摸和 GPIO0 Boot。 +- 扩展:TCA9554 `0x20`,bit0/bit1 用于 LCD/触摸复位。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO5;speaker PA:GPIO15。 + +V1 的 raw I2S 路径、触摸型号/坐标、面板版本初始化、电源和双 codec 音频仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board.c b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board.cmake b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board.cmake new file mode 100644 index 0000000..8bc01f9 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board.cmake @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "dual") +set(MYBOT_GENERIC_DISPLAY "generic/qspi_st77916_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "cst816s") +set(MYBOT_GENERIC_TCA9554 ON) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_CODEC_ES7210_SUPPORT + CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_st77916) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board_config.h b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board_config.h new file mode 100644 index 0000000..0fa35be --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/board_config.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "esp32-s3-touch-lcd-1.85c" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_GENERIC_AUDIO_KIND 3 +#define MYBOT_GENERIC_TOUCH_KIND 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_2 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_48 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_47 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_11 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_10 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_15 +#define MYBOT_GENERIC_TCA9554_ADDRESS 0x20 +#define MYBOT_GENERIC_TCA9554_OUTPUT_MASK 0x03 +#define MYBOT_GENERIC_TCA9554_RESET_MASK 0x03 + +#define MYBOT_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_TOUCH_DRIVER_KIND 1 +#define MYBOT_TOUCH_I2C_SPEED_HZ 400000 +#define MYBOT_TOUCH_RESET GPIO_NUM_NC +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_4 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_DISPLAY_SPI_HOST SPI2_HOST +#define MYBOT_DISPLAY_PCLK GPIO_NUM_40 +#define MYBOT_DISPLAY_DATA0 GPIO_NUM_46 +#define MYBOT_DISPLAY_DATA1 GPIO_NUM_45 +#define MYBOT_DISPLAY_DATA2 GPIO_NUM_42 +#define MYBOT_DISPLAY_DATA3 GPIO_NUM_41 +#define MYBOT_DISPLAY_CS GPIO_NUM_21 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_5 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 360 +#define MYBOT_DISPLAY_HEIGHT 360 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/sdkconfig.defaults b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/sdkconfig.defaults new file mode 100644 index 0000000..58f71c7 --- /dev/null +++ b/components/mybot_platform/boards/esp32-s3-touch-lcd-1.85c/sdkconfig.defaults @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_ES7210_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n +CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID=y diff --git a/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/README.md b/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/README.md new file mode 100644 index 0000000..197a18d --- /dev/null +++ b/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/README.md @@ -0,0 +1,13 @@ +# Freenove ESP32-S3 Display 2.8 LCD + +板型 ID 为 `freenove-esp32s3-display-2.8-lcd`。 + +- 显示:320×240 ILI9341,SPI3,20 MHz,共享 LVGL UI。 +- 音频:ES8311,I2C0 GPIO16/15,GPIO1 speaker PA。 +- 输入:GPIO0 Boot;无触摸和音量键。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO45 LEDC,默认 60%。 + +面板、音频、PA 和按键仍需真机验证。摄像头、LED、电池、休眠和关机未实现。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board.c b/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board.cmake b/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board.cmake new file mode 100644 index 0000000..64b1c97 --- /dev/null +++ b/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board.cmake @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_ili9341) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board_config.h b/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board_config.h new file mode 100644 index 0000000..ebd2dec --- /dev/null +++ b/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/board_config.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "freenove-esp32s3-display-2.8-lcd" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_4 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_7 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_8 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_16 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_15 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_1 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 3 +#define MYBOT_ILI9341_PROFILE 0 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_11 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_12 +#define MYBOT_DISPLAY_DC GPIO_NUM_46 +#define MYBOT_DISPLAY_CS GPIO_NUM_10 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_45 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 320 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY true +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (20 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/sdkconfig.defaults b/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/freenove-esp32s3-display-2.8-lcd/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/nulllab-ai-vox-v3/README.md b/components/mybot_platform/boards/nulllab-ai-vox-v3/README.md new file mode 100644 index 0000000..8c2345e --- /dev/null +++ b/components/mybot_platform/boards/nulllab-ai-vox-v3/README.md @@ -0,0 +1,13 @@ +# NullLab AI Vox V3 + +板型 ID 为 `nulllab-ai-vox-v3`。 + +- 显示:240×240 ST7789,SPI3 mode 0,40 MHz,共享 LVGL UI。 +- 音频:ES8311,MCLK GPIO11、BCLK GPIO10、WS GPIO8、DIN GPIO9、DOUT GPIO7。 +- 输入:GPIO0 Boot、GPIO46 音量加、GPIO45 音量减。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO16 LEDC,默认 60%。 + +当前只实现 Wi-Fi 子集;ML307 蜂窝网络未接入。面板、音频、PA、电池和按键仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/nulllab-ai-vox-v3/board.c b/components/mybot_platform/boards/nulllab-ai-vox-v3/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/nulllab-ai-vox-v3/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/nulllab-ai-vox-v3/board.cmake b/components/mybot_platform/boards/nulllab-ai-vox-v3/board.cmake new file mode 100644 index 0000000..6f155e4 --- /dev/null +++ b/components/mybot_platform/boards/nulllab-ai-vox-v3/board.cmake @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/nulllab-ai-vox-v3/board_config.h b/components/mybot_platform/boards/nulllab-ai-vox-v3/board_config.h new file mode 100644 index 0000000..425cd41 --- /dev/null +++ b/components/mybot_platform/boards/nulllab-ai-vox-v3/board_config.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "nulllab-ai-vox-v3" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_11 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_10 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_8 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_7 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_13 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_12 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_46 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_45 +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_21 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_17 +#define MYBOT_DISPLAY_DC GPIO_NUM_14 +#define MYBOT_DISPLAY_CS GPIO_NUM_15 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_16 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/nulllab-ai-vox-v3/sdkconfig.defaults b/components/mybot_platform/boards/nulllab-ai-vox-v3/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/nulllab-ai-vox-v3/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/README.md b/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/README.md new file mode 100644 index 0000000..b83dc28 --- /dev/null +++ b/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/README.md @@ -0,0 +1,13 @@ +# SP ESP32-S3 1.54 Muma + +板型 ID 为 `sp-esp32-s3-1.54-muma`。 + +- 显示:240×240 ST7789,SPI3 mode 0,40 MHz,共享 LVGL UI。 +- 音频:ES8311,I2C0 GPIO15/14,GPIO46 speaker PA。 +- 输入:CST816S 兼容触摸和 GPIO0 Boot;触摸使用独立 I2C1 GPIO11/7。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO42,低有效 PWM。 + +面板 vendor 调优、触摸、音频、电池检测、电源保持和按键仍需真机验证;休眠和关机未实现。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board.c b/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board.cmake b/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board.cmake new file mode 100644 index 0000000..e47b122 --- /dev/null +++ b/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board.cmake @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH ON) +set(MYBOT_GENERIC_TOUCH_DRIVER "cst816s") +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT + CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board_config.h b/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board_config.h new file mode 100644 index 0000000..eae40eb --- /dev/null +++ b/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/board_config.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "sp-esp32-s3-1.54-muma" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 1 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_45 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_10 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_8 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_15 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_14 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_46 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_GENERIC_TOUCH_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_TOUCH_I2C_SDA GPIO_NUM_11 +#define MYBOT_GENERIC_TOUCH_I2C_SCL GPIO_NUM_7 +#define MYBOT_TOUCH_DRIVER_KIND 1 +#define MYBOT_TOUCH_I2C_SPEED_HZ 100000 +#define MYBOT_TOUCH_RESET GPIO_NUM_6 +#define MYBOT_TOUCH_INTERRUPT GPIO_NUM_12 +#define MYBOT_TOUCH_RESET_LEVEL 0 +#define MYBOT_TOUCH_INTERRUPT_LEVEL 0 +#define MYBOT_TOUCH_SWAP_XY 0 +#define MYBOT_TOUCH_MIRROR_X 0 +#define MYBOT_TOUCH_MIRROR_Y 0 +#define MYBOT_TOUCH_LONG_PRESS_MS 3000 +#define MYBOT_TOUCH_SHORT_PRESS_MIN_MS 50 +#define MYBOT_TOUCH_POLL_INTERVAL_MS 20 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_2 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_4 +#define MYBOT_DISPLAY_DC GPIO_NUM_47 +#define MYBOT_DISPLAY_CS GPIO_NUM_5 +#define MYBOT_DISPLAY_RESET GPIO_NUM_38 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_42 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 1 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/sdkconfig.defaults b/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/sdkconfig.defaults new file mode 100644 index 0000000..68a9fd4 --- /dev/null +++ b/components/mybot_platform/boards/sp-esp32-s3-1.54-muma/sdkconfig.defaults @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n +CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID=y diff --git a/docs/BOARD_PORTING.md b/docs/BOARD_PORTING.md index ed44767..9d5a5d5 100644 --- a/docs/BOARD_PORTING.md +++ b/docs/BOARD_PORTING.md @@ -197,6 +197,10 @@ and codec power, resets LCD/touch, controls the speaker PA and leaves the motor round layout narrows widgets to a center safe rectangle; this is not a substitute for panel and touch validation. +Profiles with separate system and codec rails may set `MYBOT_GENERIC_AUX_POWER_GPIO` in addition +to `MYBOT_GENERIC_POWER_GPIO`. Both rails are asserted before I2C, display and codec clients are +created and are released during board teardown. + `Taiji Pi S3` uses PDM RX on I2S0 and standard-I2S playback on I2S1. PDM samples enter the platform already as signed 16-bit mono values, so the SDK receives 16 kHz mono signed-16 without another slot conversion. The active-low GPIO48 mute path follows playback start/stop. diff --git a/docs/BOARD_PORTING.zh-CN.md b/docs/BOARD_PORTING.zh-CN.md index 0bfe0cc..074321e 100644 --- a/docs/BOARD_PORTING.zh-CN.md +++ b/docs/BOARD_PORTING.zh-CN.md @@ -126,6 +126,10 @@ ES8311+ES7210、ES8374 和原始 I2S;原始采集可使用标准 I2S 或 PDM 复位 LCD/触摸,控制扬声器 PA,并默认关闭振动电机。共享圆屏布局把控件收窄到中央安全 矩形;这不替代面板和触摸真机验证。 +系统电源与 codec 电源分开的板型可在 `MYBOT_GENERIC_POWER_GPIO` 之外设置 +`MYBOT_GENERIC_AUX_POWER_GPIO`。两路电源都会在创建 I2C、显示和 codec 客户端之前打开, +并在板级销毁时释放。 + `Taiji Pi S3` 在 I2S0 上使用 PDM 采集、I2S1 上使用标准 I2S 播放。PDM 解调后直接提供 16 kHz 单声道 signed-16 数据,SDK 边界不再做 slot 转换。低有效 GPIO48 静音脚随播放 启停。 diff --git a/docs/PLATFORM_UI.md b/docs/PLATFORM_UI.md index 2a92002..f033dcb 100644 --- a/docs/PLATFORM_UI.md +++ b/docs/PLATFORM_UI.md @@ -40,6 +40,12 @@ retain their existing board behavior. | `quandong-s3-dev` | ILI9341, 320 × 240 | shared panel adapter | Boot | | `mixgo-nova` | ST7789, 128 × 160 | shared panel adapter | Boot | | `taiji-pi-s3` | ST77916, 360 × 360 round | shared panel adapter | CST816S and Boot | +| `freenove-esp32s3-display-2.8-lcd` | ILI9341, 320 × 240 | shared panel adapter | Boot | +| `esp32-s3-touch-lcd-1.54`, `esp32-s3-touch-lcd-1.83` | ST7789, 240 × 240 or 240 × 284 | shared ST7789 adapter | CST816S and buttons | +| `esp32-s3-lcd-0.85`, `aipi-lite` | ST7789, 128 × 128 | shared ST7789 compact layout | buttons | +| `esp32-s3-touch-lcd-1.85b`, `esp32-s3-touch-lcd-1.85c` | ST77916, 360 × 360 | shared panel adapter | CST816S-compatible touch and Boot | +| `sp-esp32-s3-1.54-muma` | ST7789, 240 × 240 | shared ST7789 adapter | CST816S and Boot | +| `atk-dnesp32s3-box0`, `nulllab-ai-vox-v3` | ST7789, 240 × 240 | shared ST7789 adapter | buttons | The AtomEchoS3R and ReSpeaker Flex profiles have no LCD and do not include LVGL. The ML307 and Wi-Fi variants, along with the audio/video protocols, are independent of the display backend. diff --git a/docs/PLATFORM_UI.zh-CN.md b/docs/PLATFORM_UI.zh-CN.md index e583ae3..8bf8126 100644 --- a/docs/PLATFORM_UI.zh-CN.md +++ b/docs/PLATFORM_UI.zh-CN.md @@ -38,6 +38,12 @@ | `quandong-s3-dev` | ILI9341,320 × 240 | 通用面板适配器 | Boot | | `mixgo-nova` | ST7789,128 × 160 | 通用面板适配器 | Boot | | `taiji-pi-s3` | ST77916,360 × 360 圆屏 | 通用面板适配器 | CST816S 与 Boot | +| `freenove-esp32s3-display-2.8-lcd` | ILI9341,320 × 240 | 通用面板适配器 | Boot | +| `esp32-s3-touch-lcd-1.54`、`esp32-s3-touch-lcd-1.83` | ST7789,240 × 240 或 240 × 284 | 通用 ST7789 | CST816S 与按键 | +| `esp32-s3-lcd-0.85`、`aipi-lite` | ST7789,128 × 128 | 通用 ST7789 紧凑布局 | 按键 | +| `esp32-s3-touch-lcd-1.85b`、`esp32-s3-touch-lcd-1.85c` | ST77916,360 × 360 | 通用面板适配器 | CST816S 兼容触摸与 Boot | +| `sp-esp32-s3-1.54-muma` | ST7789,240 × 240 | 通用 ST7789 | CST816S 与 Boot | +| `atk-dnesp32s3-box0`、`nulllab-ai-vox-v3` | ST7789,240 × 240 | 通用 ST7789 | 按键 | AtomEchoS3R 与 ReSpeaker Flex 没有 LCD,不编入 LVGL。ML307、Wi-Fi 及音视频协议与显示后端 相互独立。 diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index 2a178b2..0437e1f 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -59,6 +59,16 @@ unsupported areas. Board READMEs describe hardware and operation only; the | `df-k10` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ILI9341 and dual-codec mapping require validation; TCA9555 keys, camera, LED ring and board power paths are not implemented. | | `mixgo-nova` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ES8374/ST7789 mapping, power, audio and 128×160 UI require device validation; RGB LED is not implemented. | | `taiji-pi-s3` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST77916, CST816S, PDM capture, I2S playback, mute/PA and round UI require device validation; board power sequencing is not complete. | +| `freenove-esp32s3-display-2.8-lcd` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ILI9341, ES8311, PA and backlight require device validation; camera, LED and battery paths are not implemented. | +| `esp32-s3-touch-lcd-1.54` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST7789, CST816S, dual codec, auxiliary power and three keys require device validation; battery management, sleep and shutdown are not implemented. | +| `esp32-s3-touch-lcd-1.83` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST7789, CST816S, AXP2101 and dual codec require device validation; PWR41, battery reporting, sleep and shutdown are not implemented. | +| `esp32-s3-lcd-0.85` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST7789 128×128 layout, dual codec, battery enable and keys require device validation; touch is not implemented. | +| `esp32-s3-touch-lcd-1.85b` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST77916 panel revisions, CST816S-compatible touch, dual codec, reset/backlight and power require device validation. | +| `esp32-s3-touch-lcd-1.85c` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST77916, TCA9554 reset, CST816S-compatible touch, dual codec V2 path and power require device validation; V1 raw-I2S is not implemented. | +| `sp-esp32-s3-1.54-muma` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST7789 vendor tuning, CST816S, ES8311, battery and power hold require device validation; sleep and shutdown are not implemented. | +| `atk-dnesp32s3-box0` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST7789, ES8311, GPIO2/GPIO14 power sequencing, three keys and battery require device validation; sleep, shutdown and charging are not implemented. | +| `nulllab-ai-vox-v3` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Wi-Fi subset; ST7789, ES8311, battery and keys require device validation. ML307 networking is not implemented. | +| `aipi-lite` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST7789 128×128, ES8311, power hold and battery require device validation; power key, sleep and shutdown are not implemented. | ## Global boundaries / 全局边界 From a3b8eea44c4f49d6f7d6321dbb9f75b4bfc8c7bb Mon Sep 17 00:00:00 2001 From: shangjinlong Date: Tue, 27 Oct 2026 17:56:03 +0800 Subject: [PATCH 4/5] feat(platform): add six more ESP32-S3 board profiles --- .github/workflows/ci.yml | 6 ++ CHANGELOG.md | 3 + README.md | 8 ++- README.zh-CN.md | 8 ++- components/mybot_platform/boards/README.md | 8 ++- components/mybot_platform/boards/boards.cmake | 18 ++++++ .../boards/bread-compact-wifi/README.md | 12 ++++ .../boards/bread-compact-wifi/board.c | 6 ++ .../boards/bread-compact-wifi/board.cmake | 14 +++++ .../boards/bread-compact-wifi/board_config.h | 34 +++++++++++ .../bread-compact-wifi/sdkconfig.defaults | 8 +++ .../boards/doit-s3-aibox/README.md | 12 ++++ .../boards/doit-s3-aibox/board.c | 6 ++ .../boards/doit-s3-aibox/board.cmake | 14 +++++ .../boards/doit-s3-aibox/board_config.h | 36 +++++++++++ .../boards/doit-s3-aibox/sdkconfig.defaults | 8 +++ .../mybot_platform/boards/hu-087/README.md | 13 ++++ .../mybot_platform/boards/hu-087/board.c | 6 ++ .../mybot_platform/boards/hu-087/board.cmake | 14 +++++ .../boards/hu-087/board_config.h | 35 +++++++++++ .../boards/hu-087/sdkconfig.defaults | 8 +++ .../boards/magiclick-2p4/README.md | 13 ++++ .../boards/magiclick-2p4/board.c | 6 ++ .../boards/magiclick-2p4/board.cmake | 17 ++++++ .../boards/magiclick-2p4/board_config.h | 61 +++++++++++++++++++ .../boards/magiclick-2p4/sdkconfig.defaults | 10 +++ .../boards/movecall-cuican-esp32s3/README.md | 13 ++++ .../boards/movecall-cuican-esp32s3/board.c | 6 ++ .../movecall-cuican-esp32s3/board.cmake | 17 ++++++ .../movecall-cuican-esp32s3/board_config.h | 61 +++++++++++++++++++ .../sdkconfig.defaults | 10 +++ .../boards/movecall-moji-esp32s3/README.md | 13 ++++ .../boards/movecall-moji-esp32s3/board.c | 6 ++ .../boards/movecall-moji-esp32s3/board.cmake | 17 ++++++ .../movecall-moji-esp32s3/board_config.h | 61 +++++++++++++++++++ .../movecall-moji-esp32s3/sdkconfig.defaults | 10 +++ docs/PLATFORM_UI.md | 5 +- docs/PLATFORM_UI.zh-CN.md | 5 +- docs/VALIDATION.md | 6 ++ 39 files changed, 605 insertions(+), 9 deletions(-) create mode 100644 components/mybot_platform/boards/bread-compact-wifi/README.md create mode 100644 components/mybot_platform/boards/bread-compact-wifi/board.c create mode 100644 components/mybot_platform/boards/bread-compact-wifi/board.cmake create mode 100644 components/mybot_platform/boards/bread-compact-wifi/board_config.h create mode 100644 components/mybot_platform/boards/bread-compact-wifi/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/doit-s3-aibox/README.md create mode 100644 components/mybot_platform/boards/doit-s3-aibox/board.c create mode 100644 components/mybot_platform/boards/doit-s3-aibox/board.cmake create mode 100644 components/mybot_platform/boards/doit-s3-aibox/board_config.h create mode 100644 components/mybot_platform/boards/doit-s3-aibox/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/hu-087/README.md create mode 100644 components/mybot_platform/boards/hu-087/board.c create mode 100644 components/mybot_platform/boards/hu-087/board.cmake create mode 100644 components/mybot_platform/boards/hu-087/board_config.h create mode 100644 components/mybot_platform/boards/hu-087/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/magiclick-2p4/README.md create mode 100644 components/mybot_platform/boards/magiclick-2p4/board.c create mode 100644 components/mybot_platform/boards/magiclick-2p4/board.cmake create mode 100644 components/mybot_platform/boards/magiclick-2p4/board_config.h create mode 100644 components/mybot_platform/boards/magiclick-2p4/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/movecall-cuican-esp32s3/README.md create mode 100644 components/mybot_platform/boards/movecall-cuican-esp32s3/board.c create mode 100644 components/mybot_platform/boards/movecall-cuican-esp32s3/board.cmake create mode 100644 components/mybot_platform/boards/movecall-cuican-esp32s3/board_config.h create mode 100644 components/mybot_platform/boards/movecall-cuican-esp32s3/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/movecall-moji-esp32s3/README.md create mode 100644 components/mybot_platform/boards/movecall-moji-esp32s3/board.c create mode 100644 components/mybot_platform/boards/movecall-moji-esp32s3/board.cmake create mode 100644 components/mybot_platform/boards/movecall-moji-esp32s3/board_config.h create mode 100644 components/mybot_platform/boards/movecall-moji-esp32s3/sdkconfig.defaults diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c474cbb..aa7e377 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -316,6 +316,12 @@ jobs: - atk-dnesp32s3-box0 - nulllab-ai-vox-v3 - aipi-lite + - doit-s3-aibox + - magiclick-2p4 + - movecall-cuican-esp32s3 + - movecall-moji-esp32s3 + - bread-compact-wifi + - hu-087 language: [zh-CN, en-US] steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c13507..3bf6391 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ Versioning and Conventional Commits. ### Added +- Six additional ESP32-S3 profiles: DOIT S3 AI Box with PDM capture, Magiclick 2.4 with + NV3023, Movecall CuiCan/Moji with GC9A01, and Bread Compact Wi-Fi/HU-087 as headless raw-I2S + subsets. Chinese and English local merged builds passed; real-device validation remains pending. - Ten additional ESP32-S3 profiles: eight ST7789/ILI9341 boards using ES8311 or ES8311+ES7210 audio, followed by ST77916 LCD 1.85B/1.85C and SP 1.54 Muma. The batch adds compact 128×128 layout coverage, AXP2101/TCA9554 reset paths, a secondary board power rail, diff --git a/README.md b/README.md index 421bf8e..b52d890 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ESP32-S3 boards. The project owns board initialization, Wi-Fi provisioning, persistent storage, HTTPS transport, audio, input, display, and the firmware lifecycle around the SDK. -The development baseline is **ESP-IDF v5.5.2**. Forty-eight compile-time board profiles are +The development baseline is **ESP-IDF v5.5.2**. Fifty-four compile-time board profiles are supported; the board, Flash, PSRAM, partition table, and hardware drivers are selected with `MYBOT_BOARD`. See the [board documentation index](components/mybot_platform/boards/README.md) for hardware details, controls, build commands, and per-board limitations. @@ -51,13 +51,15 @@ Display profiles: `esp32-s3-touch-lcd-1.83`, `esp32-s3-lcd-0.85`, `esp32-s3-touch-lcd-1.85b`, `esp32-s3-touch-lcd-1.85c`, `sp-esp32-s3-1.54-muma`, `atk-dnesp32s3-box0`, `nulllab-ai-vox-v3`, -`aipi-lite`, +`aipi-lite`, `magiclick-2p4`, `movecall-cuican-esp32s3`, +`movecall-moji-esp32s3`, `genjutech-s3-1.54tft`, `m5stack-core-s3`, `m5stack-stick-s3`, and `sensecap-watcher`. Headless profiles: -`atom-echos3r`, `xingzhi-cube-0.96oled-wifi`, and +`atom-echos3r`, `xingzhi-cube-0.96oled-wifi`, `doit-s3-aibox`, +`bread-compact-wifi`, `hu-087`, and `respeaker-flex-xvf3800-circular4-xiao`. All profiles currently use Wi-Fi. The ML307 profile reserves modem UART pins but does not diff --git a/README.zh-CN.md b/README.zh-CN.md index 4a29bd8..591193e 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -9,7 +9,7 @@ 工程负责板级初始化、Wi-Fi 配网、持久化存储、HTTPS、音频、输入、显示,以及 SDK 外围的 固件生命周期。 -当前开发基线为 **ESP-IDF v5.5.2**,支持 48 个编译期 Board profile。通过 `MYBOT_BOARD` +当前开发基线为 **ESP-IDF v5.5.2**,支持 54 个编译期 Board profile。通过 `MYBOT_BOARD` 选择板型、Flash、PSRAM、分区表和硬件驱动。具体硬件配置、输入操作、构建命令和限制见 [板卡说明索引](components/mybot_platform/boards/README.md)。 @@ -47,13 +47,15 @@ `esp32-s3-touch-lcd-1.83`、`esp32-s3-lcd-0.85`、 `esp32-s3-touch-lcd-1.85b`、`esp32-s3-touch-lcd-1.85c`、 `sp-esp32-s3-1.54-muma`、`atk-dnesp32s3-box0`、`nulllab-ai-vox-v3`、 -`aipi-lite`、 +`aipi-lite`、`magiclick-2p4`、`movecall-cuican-esp32s3`、 +`movecall-moji-esp32s3`、 `genjutech-s3-1.54tft`、`m5stack-core-s3`、`m5stack-stick-s3` 和 `sensecap-watcher`。 无屏 profile: -`atom-echos3r`、`xingzhi-cube-0.96oled-wifi` 和 +`atom-echos3r`、`xingzhi-cube-0.96oled-wifi`、`doit-s3-aibox`、 +`bread-compact-wifi`、`hu-087` 和 `respeaker-flex-xvf3800-circular4-xiao`。 所有 profile 当前均使用 Wi-Fi。ML307 profile 仅预留 Modem UART 引脚,未实现 Modem 或 diff --git a/components/mybot_platform/boards/README.md b/components/mybot_platform/boards/README.md index c35949d..7131bcf 100644 --- a/components/mybot_platform/boards/README.md +++ b/components/mybot_platform/boards/README.md @@ -5,7 +5,7 @@ drivers and build constraints are defined by the checked-in `board.cmake`, `boar `sdkconfig.defaults` files. Current validation status is centralized in [`docs/VALIDATION.md`](../../../docs/VALIDATION.md). -以下说明对应本工程已实现的 48 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; +以下说明对应本工程已实现的 54 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; 验证状态统一见[验证矩阵](../../../docs/VALIDATION.md)。不同尺寸、芯片或硬件版本的板卡 不能仅凭外观共用固件。 @@ -71,6 +71,12 @@ real-device validation. | [atk-dnesp32s3-box0](atk-dnesp32s3-box0/README.md) | 240×240 ST7789 | ES8311 | three buttons | Sleep, shutdown, charging and battery reporting are not implemented. | | [nulllab-ai-vox-v3](nulllab-ai-vox-v3/README.md) | 240×240 ST7789 | ES8311 | Boot + two volume keys | Wi-Fi subset only; ML307 networking is not implemented. | | [aipi-lite](aipi-lite/README.md) | 128×128 ST7789 | ES8311 | Boot | Power key, sleep and battery reporting are not implemented. | +| [magiclick-2p4](magiclick-2p4/README.md) | 128×128 NV3023 | ES8311 | three buttons | Battery, LED, sleep and shutdown are not implemented. | +| [movecall-cuican-esp32s3](movecall-cuican-esp32s3/README.md) | 240×240 GC9A01 | ES8311 | Boot | 8 MB layout requires validation; LED, battery, sleep and shutdown are not implemented. | +| [movecall-moji-esp32s3](movecall-moji-esp32s3/README.md) | 240×240 GC9A01 | ES8311 | Boot | LED, battery, sleep and shutdown are not implemented. | +| [doit-s3-aibox](doit-s3-aibox/README.md) | Headless | PDM capture + I2S playback | three buttons | PDM timing, LED, sleep and shutdown require validation. | +| [bread-compact-wifi](bread-compact-wifi/README.md) | Headless | separate raw I2S | Boot + two volume keys | OLED UI, GPIO47 key, lamp and LED are not implemented. | +| [hu-087](hu-087/README.md) | Headless | separate raw I2S | one button | OLED UI is not implemented; long press is provision instead of volume. | ## Build, flash and validation / 构建、烧录与验证 diff --git a/components/mybot_platform/boards/boards.cmake b/components/mybot_platform/boards/boards.cmake index 9d0a829..e9c73cd 100644 --- a/components/mybot_platform/boards/boards.cmake +++ b/components/mybot_platform/boards/boards.cmake @@ -1,6 +1,7 @@ set(MYBOT_SUPPORTED_BOARDS "atom-echos3r" "bread-compact-wifi-lcd" + "bread-compact-wifi" "esp-vocat" "esp32-s3-touch-amoled-1.75" "esp32-s3-touch-amoled-1.75c" @@ -26,12 +27,17 @@ set(MYBOT_SUPPORTED_BOARDS "aipi-lite" "atk-dnesp32s3-box0" "df-k10" + "doit-s3-aibox" "freenove-esp32s3-display-2.8-lcd" + "hu-087" "lichuang-dev" + "magiclick-2p4" "m5stack-core-s3" "m5stack-stick-s3" "m5stack-stopwatch" "mixgo-nova" + "movecall-cuican-esp32s3" + "movecall-moji-esp32s3" "nulllab-ai-vox-v3" "quandong-s3-dev" "respeaker-flex-xvf3800-circular4-xiao" @@ -55,6 +61,8 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/atom-echos3r/board.cmake") elseif(board STREQUAL "bread-compact-wifi-lcd") set(profile "${_MYBOT_BOARDS_ROOT}/bread-compact-wifi-lcd/board.cmake") + elseif(board STREQUAL "bread-compact-wifi") + set(profile "${_MYBOT_BOARDS_ROOT}/bread-compact-wifi/board.cmake") elseif(board STREQUAL "esp-vocat") set(profile "${_MYBOT_BOARDS_ROOT}/esp-vocat/board.cmake") elseif(board STREQUAL "esp32-s3-touch-amoled-1.75") @@ -105,8 +113,14 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/atk-dnesp32s3-box0/board.cmake") elseif(board STREQUAL "df-k10") set(profile "${_MYBOT_BOARDS_ROOT}/df-k10/board.cmake") + elseif(board STREQUAL "doit-s3-aibox") + set(profile "${_MYBOT_BOARDS_ROOT}/doit-s3-aibox/board.cmake") + elseif(board STREQUAL "hu-087") + set(profile "${_MYBOT_BOARDS_ROOT}/hu-087/board.cmake") elseif(board STREQUAL "lichuang-dev") set(profile "${_MYBOT_BOARDS_ROOT}/lichuang-dev/board.cmake") + elseif(board STREQUAL "magiclick-2p4") + set(profile "${_MYBOT_BOARDS_ROOT}/magiclick-2p4/board.cmake") elseif(board STREQUAL "m5stack-core-s3") set(profile "${_MYBOT_BOARDS_ROOT}/m5stack-core-s3/board.cmake") elseif(board STREQUAL "m5stack-stick-s3") @@ -115,6 +129,10 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/m5stack-stopwatch/board.cmake") elseif(board STREQUAL "mixgo-nova") set(profile "${_MYBOT_BOARDS_ROOT}/mixgo-nova/board.cmake") + elseif(board STREQUAL "movecall-cuican-esp32s3") + set(profile "${_MYBOT_BOARDS_ROOT}/movecall-cuican-esp32s3/board.cmake") + elseif(board STREQUAL "movecall-moji-esp32s3") + set(profile "${_MYBOT_BOARDS_ROOT}/movecall-moji-esp32s3/board.cmake") elseif(board STREQUAL "nulllab-ai-vox-v3") set(profile "${_MYBOT_BOARDS_ROOT}/nulllab-ai-vox-v3/board.cmake") elseif(board STREQUAL "quandong-s3-dev") diff --git a/components/mybot_platform/boards/bread-compact-wifi/README.md b/components/mybot_platform/boards/bread-compact-wifi/README.md new file mode 100644 index 0000000..7fce42b --- /dev/null +++ b/components/mybot_platform/boards/bread-compact-wifi/README.md @@ -0,0 +1,12 @@ +# Bread Compact Wi-Fi + +板型 ID 为 `bread-compact-wifi`。当前为 headless profile。 + +- 音频:独立麦克风/扬声器 I2S,16 kHz mono signed-16 PCM。 +- 输入:GPIO0 Boot、GPIO40 音量加、GPIO39 音量减。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 + +SSD1306/SH1106 单色屏 UI、GPIO47 触摸按键、GPIO18 灯和 GPIO48 LED 未接入。音频、 +按键和实际 Flash/PSRAM 仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/bread-compact-wifi/board.c b/components/mybot_platform/boards/bread-compact-wifi/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/bread-compact-wifi/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/bread-compact-wifi/board.cmake b/components/mybot_platform/boards/bread-compact-wifi/board.cmake new file mode 100644 index 0000000..14b6647 --- /dev/null +++ b/components/mybot_platform/boards/bread-compact-wifi/board.cmake @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "raw") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/bread-compact-wifi/board_config.h b/components/mybot_platform/boards/bread-compact-wifi/board_config.h new file mode 100644 index 0000000..c95ff45 --- /dev/null +++ b/components/mybot_platform/boards/bread-compact-wifi/board_config.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" + +#define MYBOT_BOARD_NAME "bread-compact-wifi" +#define MYBOT_BOARD_HAS_DISPLAY 0 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 2 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MIC_BCLK GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_MIC_WS GPIO_NUM_4 +#define MYBOT_AUDIO_I2S_MIC_DIN GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_SPK_BCLK GPIO_NUM_15 +#define MYBOT_AUDIO_I2S_SPK_WS GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_SPK_DOUT GPIO_NUM_7 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/bread-compact-wifi/sdkconfig.defaults b/components/mybot_platform/boards/bread-compact-wifi/sdkconfig.defaults new file mode 100644 index 0000000..214c8f7 --- /dev/null +++ b/components/mybot_platform/boards/bread-compact-wifi/sdkconfig.defaults @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/components/mybot_platform/boards/doit-s3-aibox/README.md b/components/mybot_platform/boards/doit-s3-aibox/README.md new file mode 100644 index 0000000..23c9c63 --- /dev/null +++ b/components/mybot_platform/boards/doit-s3-aibox/README.md @@ -0,0 +1,12 @@ +# DOIT S3 AI Box + +板型 ID 为 `doit-s3-aibox`。当前为 headless profile,不编入 LVGL。 + +- 音频:PDM 采集 GPIO40/42,标准 I2S 播放 GPIO17/16/18。 +- 输入:GPIO10 主按键、GPIO15 音量加、GPIO9 音量减。 +- 电源:GPIO48 进程级保持。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 + +PDM 时钟、录音增益、播放、按键极性和电源仍需真机验证;LED、休眠和关机未实现。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/doit-s3-aibox/board.c b/components/mybot_platform/boards/doit-s3-aibox/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/doit-s3-aibox/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/doit-s3-aibox/board.cmake b/components/mybot_platform/boards/doit-s3-aibox/board.cmake new file mode 100644 index 0000000..14b6647 --- /dev/null +++ b/components/mybot_platform/boards/doit-s3-aibox/board.cmake @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "raw") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/doit-s3-aibox/board_config.h b/components/mybot_platform/boards/doit-s3-aibox/board_config.h new file mode 100644 index 0000000..46a9bf0 --- /dev/null +++ b/components/mybot_platform/boards/doit-s3-aibox/board_config.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" + +#define MYBOT_BOARD_NAME "doit-s3-aibox" +#define MYBOT_BOARD_HAS_DISPLAY 0 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 2 +#define MYBOT_GENERIC_TOUCH_KIND 0 +#define MYBOT_AUDIO_I2S_CAPTURE_PDM 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MIC_BCLK GPIO_NUM_40 +#define MYBOT_AUDIO_I2S_MIC_WS GPIO_NUM_41 +#define MYBOT_AUDIO_I2S_MIC_DIN GPIO_NUM_42 +#define MYBOT_AUDIO_I2S_SPK_BCLK GPIO_NUM_17 +#define MYBOT_AUDIO_I2S_SPK_WS GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_SPK_DOUT GPIO_NUM_18 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_POWER_GPIO GPIO_NUM_48 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_10 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_15 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_9 +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/doit-s3-aibox/sdkconfig.defaults b/components/mybot_platform/boards/doit-s3-aibox/sdkconfig.defaults new file mode 100644 index 0000000..214c8f7 --- /dev/null +++ b/components/mybot_platform/boards/doit-s3-aibox/sdkconfig.defaults @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/components/mybot_platform/boards/hu-087/README.md b/components/mybot_platform/boards/hu-087/README.md new file mode 100644 index 0000000..3920e52 --- /dev/null +++ b/components/mybot_platform/boards/hu-087/README.md @@ -0,0 +1,13 @@ +# HU-087 + +板型 ID 为 `hu-087`。当前为 headless profile。 + +- 音频:独立麦克风/扬声器 I2S,16 kHz mono signed-16 PCM。 +- 功放控制:GPIO17,随播放启停。 +- 输入:GPIO18 触摸/按键。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 + +SSD1306 单色屏 UI 未接入;长按被映射为配网请求,原固件的长按音量行为不再保留。 +音频、功放、输入和实际 Flash/PSRAM 仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/hu-087/board.c b/components/mybot_platform/boards/hu-087/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/hu-087/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/hu-087/board.cmake b/components/mybot_platform/boards/hu-087/board.cmake new file mode 100644 index 0000000..14b6647 --- /dev/null +++ b/components/mybot_platform/boards/hu-087/board.cmake @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "raw") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/hu-087/board_config.h b/components/mybot_platform/boards/hu-087/board_config.h new file mode 100644 index 0000000..b91b672 --- /dev/null +++ b/components/mybot_platform/boards/hu-087/board_config.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" + +#define MYBOT_BOARD_NAME "hu-087" +#define MYBOT_BOARD_HAS_DISPLAY 0 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 2 +#define MYBOT_GENERIC_TOUCH_KIND 0 +#define MYBOT_AUDIO_I2S_CONTROL_SPEAKER_POWER 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MIC_BCLK GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_MIC_WS GPIO_NUM_4 +#define MYBOT_AUDIO_I2S_MIC_DIN GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_SPK_BCLK GPIO_NUM_15 +#define MYBOT_AUDIO_I2S_SPK_WS GPIO_NUM_16 +#define MYBOT_AUDIO_I2S_SPK_DOUT GPIO_NUM_7 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_17 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_18 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/hu-087/sdkconfig.defaults b/components/mybot_platform/boards/hu-087/sdkconfig.defaults new file mode 100644 index 0000000..214c8f7 --- /dev/null +++ b/components/mybot_platform/boards/hu-087/sdkconfig.defaults @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/components/mybot_platform/boards/magiclick-2p4/README.md b/components/mybot_platform/boards/magiclick-2p4/README.md new file mode 100644 index 0000000..b52e1d9 --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p4/README.md @@ -0,0 +1,13 @@ +# Magiclick 2.4 + +板型 ID 为 `magiclick-2p4`。 + +- 显示:128×128 NV3023,SPI3 mode 0,40 MHz,共享紧凑 LVGL 布局。 +- 音频:ES8311,I2C0 GPIO5/6,GPIO4 speaker PA。 +- 输入:GPIO21 主按键、GPIO47 音量加、GPIO0 音量减。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO13 LEDC,默认 60%。 + +电池管理、LED、休眠和关机未实现;面板、PA、音频和按键仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/magiclick-2p4/board.c b/components/mybot_platform/boards/magiclick-2p4/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p4/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/magiclick-2p4/board.cmake b/components/mybot_platform/boards/magiclick-2p4/board.cmake new file mode 100644 index 0000000..9919751 --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p4/board.cmake @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_nv3023) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/magiclick-2p4/board_config.h b/components/mybot_platform/boards/magiclick-2p4/board_config.h new file mode 100644 index 0000000..804ab98 --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p4/board_config.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "magiclick-2p4" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_8 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_11 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_10 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_12 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_5 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_6 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_4 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_21 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_47 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 2 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_15 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_16 +#define MYBOT_DISPLAY_DC GPIO_NUM_18 +#define MYBOT_DISPLAY_CS GPIO_NUM_17 +#define MYBOT_DISPLAY_RESET GPIO_NUM_14 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_13 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 128 +#define MYBOT_DISPLAY_HEIGHT 128 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/magiclick-2p4/sdkconfig.defaults b/components/mybot_platform/boards/magiclick-2p4/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p4/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/movecall-cuican-esp32s3/README.md b/components/mybot_platform/boards/movecall-cuican-esp32s3/README.md new file mode 100644 index 0000000..5a36101 --- /dev/null +++ b/components/mybot_platform/boards/movecall-cuican-esp32s3/README.md @@ -0,0 +1,13 @@ +# Movecall CuiCan ESP32-S3 + +板型 ID 为 `movecall-cuican-esp32s3`。 + +- 显示:240×240 GC9A01,SPI3,40 MHz,共享 LVGL UI。 +- 音频:ES8311,I2C0 GPIO6/7,GPIO17 speaker PA。 +- 输入:GPIO0 Boot;无触摸和音量键。 +- 存储:8 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO16 LEDC,默认 60%。 + +面板、音频、PA、按键和 8 MB 配置仍需真机验证;LED、电池、休眠和关机未实现。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/movecall-cuican-esp32s3/board.c b/components/mybot_platform/boards/movecall-cuican-esp32s3/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/movecall-cuican-esp32s3/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/movecall-cuican-esp32s3/board.cmake b/components/mybot_platform/boards/movecall-cuican-esp32s3/board.cmake new file mode 100644 index 0000000..70d4a4d --- /dev/null +++ b/components/mybot_platform/boards/movecall-cuican-esp32s3/board.cmake @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/8m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_8MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_gc9a01) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/movecall-cuican-esp32s3/board_config.h b/components/mybot_platform/boards/movecall-cuican-esp32s3/board_config.h new file mode 100644 index 0000000..bccc3f7 --- /dev/null +++ b/components/mybot_platform/boards/movecall-cuican-esp32s3/board_config.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "movecall-cuican-esp32s3" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_45 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_41 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_40 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_42 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_6 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_7 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_17 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 1 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_10 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_12 +#define MYBOT_DISPLAY_DC GPIO_NUM_14 +#define MYBOT_DISPLAY_CS GPIO_NUM_13 +#define MYBOT_DISPLAY_RESET GPIO_NUM_11 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_16 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/movecall-cuican-esp32s3/sdkconfig.defaults b/components/mybot_platform/boards/movecall-cuican-esp32s3/sdkconfig.defaults new file mode 100644 index 0000000..63ebd39 --- /dev/null +++ b/components/mybot_platform/boards/movecall-cuican-esp32s3/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/8m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/movecall-moji-esp32s3/README.md b/components/mybot_platform/boards/movecall-moji-esp32s3/README.md new file mode 100644 index 0000000..e7d5ed5 --- /dev/null +++ b/components/mybot_platform/boards/movecall-moji-esp32s3/README.md @@ -0,0 +1,13 @@ +# Movecall Moji ESP32-S3 + +板型 ID 为 `movecall-moji-esp32s3`。 + +- 显示:240×240 GC9A01,SPI3,40 MHz,共享 LVGL UI。 +- 音频:ES8311,I2C0 GPIO5/4,GPIO9 speaker PA。 +- 输入:GPIO0 Boot;无触摸和音量键。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO3 LEDC,默认 60%。 + +面板、音频、PA、按键和电源仍需真机验证;LED、电池、休眠和关机未实现。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/movecall-moji-esp32s3/board.c b/components/mybot_platform/boards/movecall-moji-esp32s3/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/movecall-moji-esp32s3/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/movecall-moji-esp32s3/board.cmake b/components/mybot_platform/boards/movecall-moji-esp32s3/board.cmake new file mode 100644 index 0000000..87663cf --- /dev/null +++ b/components/mybot_platform/boards/movecall-moji-esp32s3/board.cmake @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "generic/rgb565_spi_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +set(MYBOT_GENERIC_EXTRA_REQUIRES esp_lcd_gc9a01) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/movecall-moji-esp32s3/board_config.h b/components/mybot_platform/boards/movecall-moji-esp32s3/board_config.h new file mode 100644 index 0000000..8063f3d --- /dev/null +++ b/components/mybot_platform/boards/movecall-moji-esp32s3/board_config.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "movecall-moji-esp32s3" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_14 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_12 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_13 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_11 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_5 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_4 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_9 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_PANEL_CONTROLLER 1 +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_17 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_16 +#define MYBOT_DISPLAY_DC GPIO_NUM_7 +#define MYBOT_DISPLAY_CS GPIO_NUM_15 +#define MYBOT_DISPLAY_RESET GPIO_NUM_18 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_3 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 240 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 0 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/movecall-moji-esp32s3/sdkconfig.defaults b/components/mybot_platform/boards/movecall-moji-esp32s3/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/movecall-moji-esp32s3/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/docs/PLATFORM_UI.md b/docs/PLATFORM_UI.md index f033dcb..cf31e29 100644 --- a/docs/PLATFORM_UI.md +++ b/docs/PLATFORM_UI.md @@ -46,8 +46,11 @@ retain their existing board behavior. | `esp32-s3-touch-lcd-1.85b`, `esp32-s3-touch-lcd-1.85c` | ST77916, 360 × 360 | shared panel adapter | CST816S-compatible touch and Boot | | `sp-esp32-s3-1.54-muma` | ST7789, 240 × 240 | shared ST7789 adapter | CST816S and Boot | | `atk-dnesp32s3-box0`, `nulllab-ai-vox-v3` | ST7789, 240 × 240 | shared ST7789 adapter | buttons | +| `magiclick-2p4` | NV3023, 128 × 128 | shared panel adapter | three buttons | +| `movecall-cuican-esp32s3`, `movecall-moji-esp32s3` | GC9A01, 240 × 240 | shared panel adapter | Boot | -The AtomEchoS3R and ReSpeaker Flex profiles have no LCD and do not include LVGL. The ML307 and +The AtomEchoS3R, DOIT S3 AI Box, Bread Compact Wi-Fi, HU-087 and ReSpeaker Flex profiles have no +LCD and do not include LVGL. The ML307 and Wi-Fi variants, along with the audio/video protocols, are independent of the display backend. The Xingzhi Cube uses SPI3 mode 3 at 40 MHz, zero offsets, and GPIO13 LEDC backlight with 5 kHz, 13-bit PWM at 60% duty. diff --git a/docs/PLATFORM_UI.zh-CN.md b/docs/PLATFORM_UI.zh-CN.md index 8bf8126..c6648b1 100644 --- a/docs/PLATFORM_UI.zh-CN.md +++ b/docs/PLATFORM_UI.zh-CN.md @@ -44,8 +44,11 @@ | `esp32-s3-touch-lcd-1.85b`、`esp32-s3-touch-lcd-1.85c` | ST77916,360 × 360 | 通用面板适配器 | CST816S 兼容触摸与 Boot | | `sp-esp32-s3-1.54-muma` | ST7789,240 × 240 | 通用 ST7789 | CST816S 与 Boot | | `atk-dnesp32s3-box0`、`nulllab-ai-vox-v3` | ST7789,240 × 240 | 通用 ST7789 | 按键 | +| `magiclick-2p4` | NV3023,128 × 128 | 通用面板适配器 | 三个按键 | +| `movecall-cuican-esp32s3`、`movecall-moji-esp32s3` | GC9A01,240 × 240 | 通用面板适配器 | Boot | -AtomEchoS3R 与 ReSpeaker Flex 没有 LCD,不编入 LVGL。ML307、Wi-Fi 及音视频协议与显示后端 +AtomEchoS3R、DOIT S3 AI Box、Bread Compact Wi-Fi、HU-087 与 ReSpeaker Flex 没有 LCD, +不编入 LVGL。ML307、Wi-Fi 及音视频协议与显示后端 相互独立。 行者 Cube 使用 SPI3 mode 3 / 40 MHz、零偏移与 GPIO13 LEDC 背光,PWM 为 5 kHz、13-bit、 60% 占空比。 diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index 0437e1f..36cecfd 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -69,6 +69,12 @@ unsupported areas. Board READMEs describe hardware and operation only; the | `atk-dnesp32s3-box0` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST7789, ES8311, GPIO2/GPIO14 power sequencing, three keys and battery require device validation; sleep, shutdown and charging are not implemented. | | `nulllab-ai-vox-v3` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Wi-Fi subset; ST7789, ES8311, battery and keys require device validation. ML307 networking is not implemented. | | `aipi-lite` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | ST7789 128×128, ES8311, power hold and battery require device validation; power key, sleep and shutdown are not implemented. | +| `magiclick-2p4` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | NV3023, ES8311, PA, keys and power require device validation; battery, LED, sleep and shutdown are not implemented. | +| `movecall-cuican-esp32s3` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | GC9A01, ES8311, PA and 8 MB layout require device validation; LED, battery, sleep and shutdown are not implemented. | +| `movecall-moji-esp32s3` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | GC9A01, ES8311, PA, keys and power require device validation; LED, battery, sleep and shutdown are not implemented. | +| `doit-s3-aibox` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Headless PDM capture, I2S playback, GPIO48 power and keys require device validation; LED, sleep and shutdown are not implemented. | +| `bread-compact-wifi` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Headless raw I2S, Boot/volume keys and storage require validation; OLED UI, GPIO47 key, lamp and LED are not implemented. | +| `hu-087` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Headless raw I2S, GPIO17 amplifier enable and GPIO18 input require validation; SSD1306 UI is not implemented and long press is used for provisioning. | ## Global boundaries / 全局边界 From a954d31236362c2efab006799b48e1e1352ed977 Mon Sep 17 00:00:00 2001 From: shangjinlong Date: Tue, 27 Oct 2026 19:02:46 +0800 Subject: [PATCH 5/5] feat(platform): add KEVIN SP V4 and DuChat X profiles --- .github/workflows/ci.yml | 2 + CHANGELOG.md | 4 ++ README.md | 3 +- README.zh-CN.md | 3 +- components/mybot_platform/boards/README.md | 4 +- components/mybot_platform/boards/boards.cmake | 6 ++ .../mybot_platform/boards/du-chatx/README.md | 15 +++++ .../mybot_platform/boards/du-chatx/board.c | 6 ++ .../boards/du-chatx/board.cmake | 15 +++++ .../boards/du-chatx/board_config.h | 59 ++++++++++++++++++ .../boards/du-chatx/sdkconfig.defaults | 8 +++ .../boards/kevin-sp-v4-dev/README.md | 14 +++++ .../boards/kevin-sp-v4-dev/board.c | 6 ++ .../boards/kevin-sp-v4-dev/board.cmake | 16 +++++ .../boards/kevin-sp-v4-dev/board_config.h | 60 +++++++++++++++++++ .../boards/kevin-sp-v4-dev/sdkconfig.defaults | 10 ++++ docs/PLATFORM_UI.md | 9 ++- docs/PLATFORM_UI.zh-CN.md | 7 ++- docs/VALIDATION.md | 2 + 19 files changed, 243 insertions(+), 6 deletions(-) create mode 100644 components/mybot_platform/boards/du-chatx/README.md create mode 100644 components/mybot_platform/boards/du-chatx/board.c create mode 100644 components/mybot_platform/boards/du-chatx/board.cmake create mode 100644 components/mybot_platform/boards/du-chatx/board_config.h create mode 100644 components/mybot_platform/boards/du-chatx/sdkconfig.defaults create mode 100644 components/mybot_platform/boards/kevin-sp-v4-dev/README.md create mode 100644 components/mybot_platform/boards/kevin-sp-v4-dev/board.c create mode 100644 components/mybot_platform/boards/kevin-sp-v4-dev/board.cmake create mode 100644 components/mybot_platform/boards/kevin-sp-v4-dev/board_config.h create mode 100644 components/mybot_platform/boards/kevin-sp-v4-dev/sdkconfig.defaults diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aa7e377..37810ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -322,6 +322,8 @@ jobs: - movecall-moji-esp32s3 - bread-compact-wifi - hu-087 + - kevin-sp-v4-dev + - du-chatx language: [zh-CN, en-US] steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bf6391..930f2fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ Versioning and Conventional Commits. ### Added +- KEVIN SP V4 Dev with a 240×280 ST7789 panel and ES8311 audio, plus DuChat X with a 128×160 + ST7789 panel, separate raw I2S audio and GPIO1 power hold. Both passed local ESP-IDF v5.5.2 + Chinese and English reconfigure/build/size/merge-bin validation; real-device validation + remains pending. - Six additional ESP32-S3 profiles: DOIT S3 AI Box with PDM capture, Magiclick 2.4 with NV3023, Movecall CuiCan/Moji with GC9A01, and Bread Compact Wi-Fi/HU-087 as headless raw-I2S subsets. Chinese and English local merged builds passed; real-device validation remains pending. diff --git a/README.md b/README.md index b52d890..ea49cc8 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ ESP32-S3 boards. The project owns board initialization, Wi-Fi provisioning, persistent storage, HTTPS transport, audio, input, display, and the firmware lifecycle around the SDK. -The development baseline is **ESP-IDF v5.5.2**. Fifty-four compile-time board profiles are +The development baseline is **ESP-IDF v5.5.2**. Fifty-six compile-time board profiles are supported; the board, Flash, PSRAM, partition table, and hardware drivers are selected with `MYBOT_BOARD`. See the [board documentation index](components/mybot_platform/boards/README.md) for hardware details, controls, build commands, and per-board limitations. @@ -53,6 +53,7 @@ Display profiles: `sp-esp32-s3-1.54-muma`, `atk-dnesp32s3-box0`, `nulllab-ai-vox-v3`, `aipi-lite`, `magiclick-2p4`, `movecall-cuican-esp32s3`, `movecall-moji-esp32s3`, +`kevin-sp-v4-dev`, `du-chatx`, `genjutech-s3-1.54tft`, `m5stack-core-s3`, `m5stack-stick-s3`, and `sensecap-watcher`. diff --git a/README.zh-CN.md b/README.zh-CN.md index 591193e..6d97cf5 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -9,7 +9,7 @@ 工程负责板级初始化、Wi-Fi 配网、持久化存储、HTTPS、音频、输入、显示,以及 SDK 外围的 固件生命周期。 -当前开发基线为 **ESP-IDF v5.5.2**,支持 54 个编译期 Board profile。通过 `MYBOT_BOARD` +当前开发基线为 **ESP-IDF v5.5.2**,支持 56 个编译期 Board profile。通过 `MYBOT_BOARD` 选择板型、Flash、PSRAM、分区表和硬件驱动。具体硬件配置、输入操作、构建命令和限制见 [板卡说明索引](components/mybot_platform/boards/README.md)。 @@ -49,6 +49,7 @@ `sp-esp32-s3-1.54-muma`、`atk-dnesp32s3-box0`、`nulllab-ai-vox-v3`、 `aipi-lite`、`magiclick-2p4`、`movecall-cuican-esp32s3`、 `movecall-moji-esp32s3`、 +`kevin-sp-v4-dev`、`du-chatx`、 `genjutech-s3-1.54tft`、`m5stack-core-s3`、`m5stack-stick-s3` 和 `sensecap-watcher`。 diff --git a/components/mybot_platform/boards/README.md b/components/mybot_platform/boards/README.md index 7131bcf..04bf045 100644 --- a/components/mybot_platform/boards/README.md +++ b/components/mybot_platform/boards/README.md @@ -5,7 +5,7 @@ drivers and build constraints are defined by the checked-in `board.cmake`, `boar `sdkconfig.defaults` files. Current validation status is centralized in [`docs/VALIDATION.md`](../../../docs/VALIDATION.md). -以下说明对应本工程已实现的 54 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; +以下说明对应本工程已实现的 56 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; 验证状态统一见[验证矩阵](../../../docs/VALIDATION.md)。不同尺寸、芯片或硬件版本的板卡 不能仅凭外观共用固件。 @@ -77,6 +77,8 @@ real-device validation. | [doit-s3-aibox](doit-s3-aibox/README.md) | Headless | PDM capture + I2S playback | three buttons | PDM timing, LED, sleep and shutdown require validation. | | [bread-compact-wifi](bread-compact-wifi/README.md) | Headless | separate raw I2S | Boot + two volume keys | OLED UI, GPIO47 key, lamp and LED are not implemented. | | [hu-087](hu-087/README.md) | Headless | separate raw I2S | one button | OLED UI is not implemented; long press is provision instead of volume. | +| [kevin-sp-v4-dev](kevin-sp-v4-dev/README.md) | 240×280 ST7789 | ES8311 | Boot | Camera, LED, battery, sleep and shutdown are not implemented. | +| [du-chatx](du-chatx/README.md) | 128×160 ST7789 | separate raw I2S | Boot | GPIO1 power hold is implemented; battery, charging, LED, sleep and shutdown are not. | ## Build, flash and validation / 构建、烧录与验证 diff --git a/components/mybot_platform/boards/boards.cmake b/components/mybot_platform/boards/boards.cmake index e9c73cd..9c84764 100644 --- a/components/mybot_platform/boards/boards.cmake +++ b/components/mybot_platform/boards/boards.cmake @@ -53,6 +53,8 @@ set(MYBOT_SUPPORTED_BOARDS "xingzhi-metal-1.54-wifi" "zhengchen-1.54tft-ml307" "zhengchen-1.54tft-wifi" + "kevin-sp-v4-dev" + "du-chatx" ) set(_MYBOT_BOARDS_ROOT "${CMAKE_CURRENT_LIST_DIR}") @@ -166,6 +168,10 @@ function(mybot_resolve_board board) set(profile "${_MYBOT_BOARDS_ROOT}/zhengchen-1.54tft-wifi/board.cmake") elseif(board STREQUAL "freenove-esp32s3-display-2.8-lcd") set(profile "${_MYBOT_BOARDS_ROOT}/freenove-esp32s3-display-2.8-lcd/board.cmake") + elseif(board STREQUAL "kevin-sp-v4-dev") + set(profile "${_MYBOT_BOARDS_ROOT}/kevin-sp-v4-dev/board.cmake") + elseif(board STREQUAL "du-chatx") + set(profile "${_MYBOT_BOARDS_ROOT}/du-chatx/board.cmake") else() message(FATAL_ERROR "Unsupported MYBOT_BOARD: ${board}") endif() diff --git a/components/mybot_platform/boards/du-chatx/README.md b/components/mybot_platform/boards/du-chatx/README.md new file mode 100644 index 0000000..446e9b4 --- /dev/null +++ b/components/mybot_platform/boards/du-chatx/README.md @@ -0,0 +1,15 @@ +# DuChat X + +板型 ID 为 `du-chatx`。 + +- 显示:128×160 ST7789,SPI3 mode 0,40 MHz,X/Y 偏移 2/1,共享紧凑 LVGL UI。 +- 音频:独立麦克风与扬声器 raw I2S,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;无触摸和音量键。 +- 电源:GPIO1 高电平锁存,初始化时保持供电。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO9 LEDC,默认 60%。 + +电池、充电状态、LED、休眠和关机未实现。物理 Flash/PSRAM 容量、GPIO1 上电时序、面板方向、 +电源状态读取和音频仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/du-chatx/board.c b/components/mybot_platform/boards/du-chatx/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/du-chatx/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/du-chatx/board.cmake b/components/mybot_platform/boards/du-chatx/board.cmake new file mode 100644 index 0000000..ee77d8a --- /dev/null +++ b/components/mybot_platform/boards/du-chatx/board.cmake @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "raw") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/du-chatx/board_config.h b/components/mybot_platform/boards/du-chatx/board_config.h new file mode 100644 index 0000000..772361f --- /dev/null +++ b/components/mybot_platform/boards/du-chatx/board_config.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "du-chatx" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 2 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MIC_BCLK GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_MIC_WS GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_MIC_DIN GPIO_NUM_7 +#define MYBOT_AUDIO_I2S_SPK_BCLK GPIO_NUM_42 +#define MYBOT_AUDIO_I2S_SPK_WS GPIO_NUM_2 +#define MYBOT_AUDIO_I2S_SPK_DOUT GPIO_NUM_40 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_NC +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_GENERIC_POWER_GPIO GPIO_NUM_1 +#define MYBOT_GENERIC_POWER_ACTIVE_LEVEL 1 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_18 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_17 +#define MYBOT_DISPLAY_DC GPIO_NUM_8 +#define MYBOT_DISPLAY_CS GPIO_NUM_16 +#define MYBOT_DISPLAY_RESET GPIO_NUM_20 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_9 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 128 +#define MYBOT_DISPLAY_HEIGHT 160 +#define MYBOT_DISPLAY_OFFSET_X 2 +#define MYBOT_DISPLAY_OFFSET_Y 1 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X true +#define MYBOT_DISPLAY_MIRROR_Y true +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (40 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/du-chatx/sdkconfig.defaults b/components/mybot_platform/boards/du-chatx/sdkconfig.defaults new file mode 100644 index 0000000..214c8f7 --- /dev/null +++ b/components/mybot_platform/boards/du-chatx/sdkconfig.defaults @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/components/mybot_platform/boards/kevin-sp-v4-dev/README.md b/components/mybot_platform/boards/kevin-sp-v4-dev/README.md new file mode 100644 index 0000000..bd5f438 --- /dev/null +++ b/components/mybot_platform/boards/kevin-sp-v4-dev/README.md @@ -0,0 +1,14 @@ +# KEVIN SP V4 Dev + +板型 ID 为 `kevin-sp-v4-dev`。 + +- 显示:240×280 ST7789,SPI3 mode 3,80 MHz,Y 偏移 20,颜色反转,共享 LVGL UI。 +- 音频:ES8311,I2C1 GPIO4/5,GPIO41 speaker PA,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;无触摸和音量键。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO48 LEDC,默认 60%。 + +摄像头、LED、电池、休眠和关机未实现。物理 Flash/PSRAM 容量、I2C1 上的 ES8311、面板方向、 +音频和按键仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/kevin-sp-v4-dev/board.c b/components/mybot_platform/boards/kevin-sp-v4-dev/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/kevin-sp-v4-dev/board.c @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "generic_board.h" + +const mybot_board_t *mybot_board_get(void) { + return mybot_generic_board_get(); +} diff --git a/components/mybot_platform/boards/kevin-sp-v4-dev/board.cmake b/components/mybot_platform/boards/kevin-sp-v4-dev/board.cmake new file mode 100644 index 0000000..6f155e4 --- /dev/null +++ b/components/mybot_platform/boards/kevin-sp-v4-dev/board.cmake @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: Apache-2.0 + +set(MYBOT_GENERIC_PARTITION_TABLE "partitions/v2/16m.csv") +set(MYBOT_GENERIC_AUDIO "es8311") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_OCT + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8311_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/kevin-sp-v4-dev/board_config.h b/components/mybot_platform/boards/kevin-sp-v4-dev/board_config.h new file mode 100644 index 0000000..197893a --- /dev/null +++ b/components/mybot_platform/boards/kevin-sp-v4-dev/board_config.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_BOARD_CONFIG_H_ +#define MYBOT_BOARD_CONFIG_H_ + +#include "driver/gpio.h" +#include "driver/i2c_master.h" +#include "driver/spi_common.h" + +#define MYBOT_BOARD_NAME "kevin-sp-v4-dev" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 1 +#define MYBOT_GENERIC_TOUCH_KIND 0 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_42 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_46 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_1 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_2 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_3 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_4 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_5 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_41 + +#define MYBOT_ES8311_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_ES8311_I2C_ADDRESS 0x30 +#define MYBOT_ES8311_INPUT_GAIN_DB 30.0f +#define MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT 60 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#define MYBOT_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_47 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_21 +#define MYBOT_DISPLAY_DC GPIO_NUM_45 +#define MYBOT_DISPLAY_CS GPIO_NUM_14 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_48 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 240 +#define MYBOT_DISPLAY_HEIGHT 280 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 20 +#define MYBOT_DISPLAY_SWAP_XY false +#define MYBOT_DISPLAY_MIRROR_X false +#define MYBOT_DISPLAY_MIRROR_Y false +#define MYBOT_DISPLAY_INVERT_COLOR true +#define MYBOT_DISPLAY_SPI_MODE 3 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (80 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/kevin-sp-v4-dev/sdkconfig.defaults b/components/mybot_platform/boards/kevin-sp-v4-dev/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/kevin-sp-v4-dev/sdkconfig.defaults @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: Apache-2.0 +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv" +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_ESPTOOLPY_FLASHMODE_QIO=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8311_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/docs/PLATFORM_UI.md b/docs/PLATFORM_UI.md index cf31e29..0b97ddf 100644 --- a/docs/PLATFORM_UI.md +++ b/docs/PLATFORM_UI.md @@ -48,6 +48,8 @@ retain their existing board behavior. | `atk-dnesp32s3-box0`, `nulllab-ai-vox-v3` | ST7789, 240 × 240 | shared ST7789 adapter | buttons | | `magiclick-2p4` | NV3023, 128 × 128 | shared panel adapter | three buttons | | `movecall-cuican-esp32s3`, `movecall-moji-esp32s3` | GC9A01, 240 × 240 | shared panel adapter | Boot | +| `kevin-sp-v4-dev` | ST7789, 240 × 280 | shared ST7789 adapter | Boot | +| `du-chatx` | ST7789, 128 × 160 | shared ST7789 compact layout | Boot | The AtomEchoS3R, DOIT S3 AI Box, Bread Compact Wi-Fi, HU-087 and ReSpeaker Flex profiles have no LCD and do not include LVGL. The ML307 and @@ -62,6 +64,9 @@ boundaries on both axes. AMOLED 1.8 V2 uses a 16-pixel X gap and 40 MHz QSPI with the same even-pixel CO5300 transfer constraint. LCD 1.85 uses GPIO5 LEDC backlight at 60% and a 360 × 360 ST77916 panel. GenjuTech 1.54 uses ST7789 SPI3 mode 3 at 80 MHz, color inversion, and GPIO2 LEDC backlight at 60%. +KEVIN SP V4 Dev uses ST7789 SPI3 mode 3 at 80 MHz, a 20-pixel Y offset, color inversion, and a +GPIO48 LEDC backlight. DuChat X uses ST7789 SPI3 mode 0 at 40 MHz, a 2×1-pixel offset, mirrored +axes, and a GPIO9 LEDC backlight. ## CoreS3 pixel-glasses view @@ -93,8 +98,8 @@ available when `CONFIG_MYBOT_LVGL_UI_ANIMATIONS` disables state activity animati The pairing-code screen directs users to enter the code in the web console. Its instruction scrolls only when wider than the footer label and stops scrolling on exit. -On screens narrower than 240 pixels (currently StickS3), Chinese labels retain the 20 px bilingual -font; English status, notification, and ordinary labels use 10 px text. The provisioning SSID +On screens narrower than 240 pixels, Chinese labels retain the 20 px bilingual font; English +status, notification, and ordinary labels use 10 px text. The provisioning SSID uses 20 px in both languages and scrolls if needed. Pairing codes select a 32, 20, or 10 px font to fit, with tighter spacing for long codes on narrow screens. The built-in Chinese subset covers the UI labels, not arbitrary conversation text. diff --git a/docs/PLATFORM_UI.zh-CN.md b/docs/PLATFORM_UI.zh-CN.md index c6648b1..fbc7499 100644 --- a/docs/PLATFORM_UI.zh-CN.md +++ b/docs/PLATFORM_UI.zh-CN.md @@ -46,6 +46,8 @@ | `atk-dnesp32s3-box0`、`nulllab-ai-vox-v3` | ST7789,240 × 240 | 通用 ST7789 | 按键 | | `magiclick-2p4` | NV3023,128 × 128 | 通用面板适配器 | 三个按键 | | `movecall-cuican-esp32s3`、`movecall-moji-esp32s3` | GC9A01,240 × 240 | 通用面板适配器 | Boot | +| `kevin-sp-v4-dev` | ST7789,240 × 280 | 通用 ST7789 | Boot | +| `du-chatx` | ST7789,128 × 160 | 通用 ST7789 紧凑布局 | Boot | AtomEchoS3R、DOIT S3 AI Box、Bread Compact Wi-Fi、HU-087 与 ReSpeaker Flex 没有 LCD, 不编入 LVGL。ML307、Wi-Fi 及音视频协议与显示后端 @@ -60,6 +62,9 @@ AMOLED 2.16 使用零 panel gap 与 40 MHz QSPI;CO5300 传输区域两个方 AMOLED 1.8 V2 使用 16 像素 X 偏移与 40 MHz QSPI,并保持相同的 CO5300 偶数像素约束。 LCD 1.85 使用 GPIO5 LEDC 背光(60%)与 360 × 360 ST77916 面板。GenjuTech 1.54 使用 ST7789 SPI3 mode 3 / 80 MHz、颜色反转与 GPIO2 LEDC 背光(60%)。 +KEVIN SP V4 Dev 使用 ST7789 SPI3 mode 3 / 80 MHz、Y 偏移 20、颜色反转与 GPIO48 LEDC +背光。DuChat X 使用 ST7789 SPI3 mode 0 / 40 MHz、2×1 像素偏移、X/Y 镜像与 GPIO9 LEDC +背光。 ## CoreS3像素眼镜视图 @@ -87,7 +92,7 @@ Flash,不加入运行时 PNG/GIF 解码器或全屏缓存。 配对码页面提示用户在网页控制台输入配对码;提示超出底部标签宽度时才循环滚动,离开 配对码页面后停止滚动。 -宽度小于 240 像素的屏幕(当前为 StickS3)保留 20 px 中文字库;英文状态、通知及普通 +宽度小于 240 像素的屏幕保留 20 px 中文字库;英文状态、通知及普通 标签使用 10 px 字体。配网 SSID 在两种语言下均使用 20 px,超宽时滚动。配对码根据宽度 选择 32、20 或 10 px 字体,窄屏长配对码同时收紧字距。内置中文字库覆盖 UI 文案, 不覆盖任意中文聊天正文。 diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index 36cecfd..cf79d15 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -75,6 +75,8 @@ unsupported areas. Board READMEs describe hardware and operation only; the | `doit-s3-aibox` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Headless PDM capture, I2S playback, GPIO48 power and keys require device validation; LED, sleep and shutdown are not implemented. | | `bread-compact-wifi` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Headless raw I2S, Boot/volume keys and storage require validation; OLED UI, GPIO47 key, lamp and LED are not implemented. | | `hu-087` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Headless raw I2S, GPIO17 amplifier enable and GPIO18 input require validation; SSD1306 UI is not implemented and long press is used for provisioning. | +| `kevin-sp-v4-dev` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Physical Flash/PSRAM, ST7789 mode 3 timing, ES8311 on I2C1, PA and Boot input require device validation; camera, LED, battery, sleep and shutdown are not implemented. | +| `du-chatx` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Physical Flash/PSRAM, GPIO1 power hold, ST7789 orientation, raw audio slots and Boot input require device validation; battery, charging, LED, sleep and shutdown are not implemented. | ## Global boundaries / 全局边界