diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37810ee..0b87fe1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,8 @@ jobs: run: | find main components/mybot_platform tests/atom_echos3r \ tests/st7789_lifecycle tests/co5300_lifecycle tests/bread_compact_wifi_lcd \ - tests/cst816s_input tests/tca9554 tests/cores3_pixel_ui -type f \ + tests/cst816s_input tests/tca9554 tests/aw9523b tests/compact_panel \ + tests/cores3_pixel_ui tests/labplus_stm8_reset -type f \ \( -name '*.c' -o -name '*.h' -o -name '*.cc' -o -name '*.cpp' \) \ -exec clang-format --dry-run --Werror {} + - name: Whitespace check @@ -40,7 +41,8 @@ jobs: run: | find main components/mybot_platform tests/atom_echos3r \ tests/st7789_lifecycle tests/co5300_lifecycle tests/bread_compact_wifi_lcd \ - tests/cst816s_input tests/tca9554 tests/cores3_pixel_ui -type f \ + tests/cst816s_input tests/tca9554 tests/aw9523b tests/compact_panel \ + tests/cores3_pixel_ui tests/labplus_stm8_reset -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:' || { @@ -58,6 +60,12 @@ jobs: run: sh tests/cst816s_input/run.sh - name: TCA9554 masked update host tests run: sh tests/tca9554/run.sh + - name: AW9523B masked update host tests + run: sh tests/aw9523b/run.sh + - name: LabPlus STM8 display reset host tests + run: sh tests/labplus_stm8_reset/run.sh + - name: Compact GC9107/ST7735 panel lifecycle tests + run: sh tests/compact_panel/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 @@ -324,6 +332,13 @@ jobs: - hu-087 - kevin-sp-v4-dev - du-chatx + - minsi-k08-dual + - kevin-box-2 + - magiclick-2p5 + - atk-dnesp32s3-box3 + - zhengchen-cam + - labplus-ledong-v2 + - labplus-mpython-v3 language: [zh-CN, en-US] steps: - uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index ff5fc69..7a6b437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ Versioning and Conventional Commits. ### Added +- Three additional ESP32-S3 profiles: Zhengchen Cam with ST7789, ES8311/ES7210 and PCA9557 PA + control; LabPlus Ledong V2 with a 320×172 ST7789-compatible panel, ES8388 and STM8 display + reset; and LabPlus Mpython V3 with the same display/audio family and separate CS/backlight + wiring. Added ES8388 device support and a host test for the bounded STM8 reset sequence. All + three passed local ESP-IDF v5.5.2 Chinese and English + reconfigure/build/size/merge-bin validation; real-device validation remains pending. +- Four additional ESP32-S3 profiles: Minsi K08 Dual with ST7789 and raw I2S, KEVIN Box 2 as a + headless ES8311/ES7210 subset, Magiclick 2.5 with runtime GC9107/ST7735 panel selection, and + ATK DNESP32S3 Box 3 with ST7789, dual codecs and AW9523B power/backlight/PA control. Added + shared compact-panel and AW9523B drivers with host lifecycle tests. All four passed local + ESP-IDF v5.5.2 Chinese and English reconfigure/build/size/merge-bin validation; real-device + validation remains pending. The upstream AtomS3 Echo Base profile was not added because + stock AtomS3 lacks the PSRAM required by the current MyBot/AOSL memory backend. - 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 diff --git a/README.md b/README.md index d676deb..3c2e170 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ voice-chat runtime on 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-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. +The development baseline is **ESP-IDF v5.5.2**. Sixty-three compile-time board profiles are +supported (56 display and 7 headless); 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. > Project-maintained code is Apache-2.0 unless a file says otherwise. Bundled dependencies and > media assets have separate terms. Read [License and dependencies](#license-and-dependencies) @@ -56,13 +57,14 @@ Display profiles: `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`. +`minsi-k08-dual`, `magiclick-2p5`, `atk-dnesp32s3-box3`, +`genjutech-s3-1.54tft`, `zhengchen-cam`, `labplus-ledong-v2`, +`labplus-mpython-v3`, `m5stack-core-s3`, `m5stack-stick-s3`, and `sensecap-watcher`. Headless profiles: `atom-echos3r`, `xingzhi-cube-0.96oled-wifi`, `doit-s3-aibox`, -`bread-compact-wifi`, `hu-087`, and +`bread-compact-wifi`, `hu-087`, `kevin-box-2`, 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 c8c8feb..6acd9c0 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -11,9 +11,9 @@ 工程负责板级初始化、Wi-Fi 配网、持久化存储、HTTPS、音频、输入、显示,以及 SDK 外围的 固件生命周期。 -当前开发基线为 **ESP-IDF v5.5.2**,支持 56 个编译期 Board profile。通过 `MYBOT_BOARD` -选择板型、Flash、PSRAM、分区表和硬件驱动。具体硬件配置、输入操作、构建命令和限制见 -[板卡说明索引](components/mybot_platform/boards/README.md)。 +当前开发基线为 **ESP-IDF v5.5.2**,支持 63 个编译期 Board profile(56 个带屏、7 个无屏)。 +通过 `MYBOT_BOARD` 选择板型、Flash、PSRAM、分区表和硬件驱动。具体硬件配置、输入操作、 +构建命令和限制见[板卡说明索引](components/mybot_platform/boards/README.md)。 > 除非文件另有声明,本工程自维护代码使用 Apache-2.0。仓库内依赖和媒体资源有各自的 > 许可条款;重新分发源码或固件前请阅读[许可证与依赖](#许可证与依赖)。 @@ -52,13 +52,14 @@ `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`。 +`minsi-k08-dual`、`magiclick-2p5`、`atk-dnesp32s3-box3`、 +`genjutech-s3-1.54tft`、`zhengchen-cam`、`labplus-ledong-v2`、 +`labplus-mpython-v3`、`m5stack-core-s3`、`m5stack-stick-s3` 和 `sensecap-watcher`。 无屏 profile: `atom-echos3r`、`xingzhi-cube-0.96oled-wifi`、`doit-s3-aibox`、 -`bread-compact-wifi`、`hu-087` 和 +`bread-compact-wifi`、`hu-087`、`kevin-box-2` 和 `respeaker-flex-xvf3800-circular4-xiao`。 所有 profile 当前均使用 Wi-Fi。ML307 profile 仅预留 Modem UART 引脚,未实现 Modem 或 diff --git a/VENDORED_SOURCES.md b/VENDORED_SOURCES.md index f07e8ed..f2cf918 100644 --- a/VENDORED_SOURCES.md +++ b/VENDORED_SOURCES.md @@ -152,8 +152,9 @@ Firmware integration differences are limited to the active ESP32-S3 build: DSCP/TOS support required by the bundled RTSA package. - Component Registry manifests and download-cache checksum metadata are omitted because dependencies are local or supplied by ESP-IDF v5.5.2. -- The codec-device source set contains only the common adapters and ES8311, ES7243E, ES7210, and - AW88298 devices used by supported boards; codec-setting errors are propagated to platform callers. +- The codec-device source set contains only the common adapters and ES8311, ES8388, ES7243E, + ES7210, and AW88298 devices used by supported boards; codec-setting errors are propagated to + platform callers. - The TCA95xx constructor removes its I2C device if register reset fails. - The SenseCAP profile uses explicit IO-expander calls and disables the optional global GPIO API wrapper. diff --git a/components/esp_codec_dev/CMakeLists.txt b/components/esp_codec_dev/CMakeLists.txt index acb9761..dae39eb 100644 --- a/components/esp_codec_dev/CMakeLists.txt +++ b/components/esp_codec_dev/CMakeLists.txt @@ -13,6 +13,9 @@ set(component_srcs if(CONFIG_CODEC_ES8311_SUPPORT) list(APPEND component_srcs "device/es8311/es8311.c") endif() +if(CONFIG_CODEC_ES8388_SUPPORT) + list(APPEND component_srcs "device/es8388/es8388.c") +endif() if(CONFIG_CODEC_ES7243E_SUPPORT) list(APPEND component_srcs "device/es7243e/es7243e.c") endif() diff --git a/components/esp_codec_dev/Kconfig b/components/esp_codec_dev/Kconfig index 5e9c13c..1b291cc 100644 --- a/components/esp_codec_dev/Kconfig +++ b/components/esp_codec_dev/Kconfig @@ -11,6 +11,10 @@ menu "Audio Codec Device Configuration" bool "Support ES8311 codec" default n + config CODEC_ES8388_SUPPORT + bool "Support ES8388 codec" + default n + config CODEC_ES7243E_SUPPORT bool "Support ES7243E codec" default n diff --git a/components/esp_codec_dev/device/es8388/es8388.c b/components/esp_codec_dev/device/es8388/es8388.c new file mode 100644 index 0000000..15deb47 --- /dev/null +++ b/components/esp_codec_dev/device/es8388/es8388.c @@ -0,0 +1,432 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "esp_log.h" +#include "es8388_reg.h" +#include "es8388_codec.h" +#include "es_common.h" +#include "esp_codec_dev_vol.h" + +#define TAG "ES8388" + +typedef struct { + audio_codec_if_t base; + const audio_codec_ctrl_if_t *ctrl_if; + const audio_codec_gpio_if_t *gpio_if; + bool is_open; + bool enabled; + esp_codec_dec_work_mode_t codec_mode; + int16_t pa_pin; + bool pa_reverted; + float hw_gain; +} audio_codec_es8388_t; + +static const esp_codec_dev_vol_range_t vol_range = { + .min_vol = + { + .vol = 0xC0, + .db_value = -96, + }, + .max_vol = + { + .vol = 0, + .db_value = 0.0, + }, +}; + +static int es8388_write_reg(audio_codec_es8388_t *codec, int reg, int value) +{ + return codec->ctrl_if->write_reg(codec->ctrl_if, reg, 1, &value, 1); +} + +static int es8388_read_reg(audio_codec_es8388_t *codec, int reg, int *value) +{ + *value = 0; + return codec->ctrl_if->read_reg(codec->ctrl_if, reg, 1, value, 1); +} + +static int es8388_set_adc_dac_volume(audio_codec_es8388_t *codec, esp_codec_dec_work_mode_t mode, int volume, int dot) +{ + int res = 0; + if (volume < -96 || volume > 0) { + if (volume < -96) + volume = -96; + else + volume = 0; + } + dot = (dot >= 5 ? 1 : 0); + volume = (-volume << 1) + dot; + if (mode & ESP_CODEC_DEV_WORK_MODE_ADC) { + res |= es8388_write_reg(codec, ES8388_ADCCONTROL8, volume); + res |= es8388_write_reg(codec, ES8388_ADCCONTROL9, volume); // ADC Right Volume=0db + } + if (mode & ESP_CODEC_DEV_WORK_MODE_DAC) { + res |= es8388_write_reg(codec, ES8388_DACCONTROL5, volume); + res |= es8388_write_reg(codec, ES8388_DACCONTROL4, volume); + } + return res; +} + +static int es8388_set_voice_mute(audio_codec_es8388_t *codec, bool enable) +{ + int res = 0; + int reg = 0; + res = es8388_read_reg(codec, ES8388_DACCONTROL3, ®); + reg = reg & 0xFB; + res |= es8388_write_reg(codec, ES8388_DACCONTROL3, reg | (((int) enable) << 2)); + return res; +} + +static int es8388_start(audio_codec_es8388_t *codec, esp_codec_dec_work_mode_t mode) +{ + int res = 0; + int prev_data = 0, data = 0; + es8388_read_reg(codec, ES8388_DACCONTROL21, &prev_data); + if (mode == ESP_CODEC_DEV_WORK_MODE_LINE) { + res |= es8388_write_reg(codec, ES8388_DACCONTROL16, + 0x09); // 0x00 audio on LIN1&RIN1, 0x09 LIN2&RIN2 by pass enable + res |= es8388_write_reg( + codec, ES8388_DACCONTROL17, + 0x50); // left DAC to left mixer enable and LIN signal to left mixer enable 0db : bupass enable + res |= es8388_write_reg( + codec, ES8388_DACCONTROL20, + 0x50); // right DAC to right mixer enable and LIN signal to right mixer enable 0db : bupass enable + res |= es8388_write_reg(codec, ES8388_DACCONTROL21, 0xC0); // enable adc + } else { + res |= es8388_write_reg(codec, ES8388_DACCONTROL21, 0x80); // enable dac + } + es8388_read_reg(codec, ES8388_DACCONTROL21, &data); + if (prev_data != data) { + res |= es8388_write_reg(codec, ES8388_CHIPPOWER, 0xF0); // start state machine + res |= es8388_write_reg(codec, ES8388_CHIPPOWER, 0x00); // start state machine + } + if ((mode & ESP_CODEC_DEV_WORK_MODE_ADC) || mode == ESP_CODEC_DEV_WORK_MODE_LINE) { + res |= es8388_write_reg(codec, ES8388_ADCPOWER, 0x00); // power up adc and line in + } + if ((mode & ESP_CODEC_DEV_WORK_MODE_DAC) || mode == ESP_CODEC_DEV_WORK_MODE_LINE) { + res |= es8388_write_reg(codec, ES8388_DACPOWER, 0x3c); // power up dac and line out + res |= es8388_set_voice_mute(codec, false); + ESP_LOGI(TAG, "Start on mode:%d", mode); + } + return res; +} + +static int es8388_stop(audio_codec_es8388_t *codec, esp_codec_dec_work_mode_t mode) +{ + int res = 0; + if (mode == ESP_CODEC_DEV_WORK_MODE_LINE) { + res |= es8388_write_reg(codec, ES8388_DACCONTROL21, 0x80); // enable dac + res |= es8388_write_reg(codec, ES8388_DACCONTROL16, 0x00); // 0x00 audio on LIN1&RIN1, 0x09 LIN2&RIN2 + res |= es8388_write_reg(codec, ES8388_DACCONTROL17, 0x90); // only left DAC to left mixer enable 0db + res |= es8388_write_reg(codec, ES8388_DACCONTROL20, 0x90); // only right DAC to right mixer enable 0db + return res; + } + if (mode & ESP_CODEC_DEV_WORK_MODE_DAC) { + res |= es8388_write_reg(codec, ES8388_DACPOWER, 0x00); + res |= es8388_set_voice_mute(codec, true); + } + if (mode & ESP_CODEC_DEV_WORK_MODE_ADC) { + res |= es8388_write_reg(codec, ES8388_ADCPOWER, 0xFF); // power down adc and line in + } + if (mode == ESP_CODEC_DEV_WORK_MODE_BOTH) { + res |= es8388_write_reg(codec, ES8388_DACCONTROL21, 0x9C); // disable mclk + } + return res; +} + +static int es8388_config_fmt(audio_codec_es8388_t *codec, esp_codec_dec_work_mode_t mode, es_i2s_fmt_t fmt) +{ + int res = 0; + int reg = 0; + if (mode & ESP_CODEC_DEV_WORK_MODE_ADC) { + res = es8388_read_reg(codec, ES8388_ADCCONTROL4, ®); + reg = reg & 0xfc; + res |= es8388_write_reg(codec, ES8388_ADCCONTROL4, reg | fmt); + } + if (mode & ESP_CODEC_DEV_WORK_MODE_DAC) { + res = es8388_read_reg(codec, ES8388_DACCONTROL1, ®); + reg = reg & 0xf9; + res |= es8388_write_reg(codec, ES8388_DACCONTROL1, reg | (fmt << 1)); + } + return res; +} + +static int es8388_set_mic_gain(audio_codec_es8388_t *codec, float db) +{ + int gain = db > 0 ? (int) (db / 3) : 0; + gain = (gain << 4) + gain; + return es8388_write_reg(codec, ES8388_ADCCONTROL1, gain); // MIC PGA +} + +static es_bits_length_t get_bits_enum(uint8_t bits) +{ + switch (bits) { + case 16: + default: + 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 es8388_set_bits_per_sample(audio_codec_es8388_t *codec, esp_codec_dec_work_mode_t mode, uint8_t bits_length) +{ + int res = 0; + int reg = 0; + int bits = (int) get_bits_enum(bits_length); + + if (mode & ESP_CODEC_DEV_WORK_MODE_ADC) { + res = es8388_read_reg(codec, ES8388_ADCCONTROL4, ®); + reg = reg & 0xe3; + res |= es8388_write_reg(codec, ES8388_ADCCONTROL4, reg | (bits << 2)); + } + if (mode & ESP_CODEC_DEV_WORK_MODE_DAC) { + res = es8388_read_reg(codec, ES8388_DACCONTROL1, ®); + reg = reg & 0xc7; + res |= es8388_write_reg(codec, ES8388_DACCONTROL1, reg | (bits << 3)); + } + return res; +} + +static void es8388_pa_power(audio_codec_es8388_t *codec, bool enable) +{ + int16_t pa_pin = codec->pa_pin; + if (pa_pin == -1 || codec->gpio_if == NULL) { + return; + } + codec->gpio_if->setup(pa_pin, AUDIO_GPIO_DIR_OUT, AUDIO_GPIO_MODE_FLOAT); + codec->gpio_if->set(pa_pin, codec->pa_reverted ? !enable : enable); +} + +static int es8388_open(const audio_codec_if_t *h, void *cfg, int cfg_size) +{ + audio_codec_es8388_t *codec = (audio_codec_es8388_t *) h; + es8388_codec_cfg_t *codec_cfg = (es8388_codec_cfg_t *) cfg; + if (codec == NULL || codec_cfg->ctrl_if == NULL || cfg_size != sizeof(es8388_codec_cfg_t)) { + return ESP_CODEC_DEV_INVALID_ARG; + } + int res = ESP_CODEC_DEV_OK; + codec->ctrl_if = codec_cfg->ctrl_if; + codec->gpio_if = codec_cfg->gpio_if; + codec->pa_pin = codec_cfg->pa_pin; + codec->pa_reverted = codec_cfg->pa_reverted; + codec->codec_mode = codec_cfg->codec_mode; + + es8388_pa_power(codec, false); + + // 0x04 mute/0x00 unmute&ramp; + res |= es8388_write_reg(codec, ES8388_DACCONTROL3, 0x04); + /* Chip Control and Power Management */ + res |= es8388_write_reg(codec, ES8388_CONTROL2, 0x50); + res |= es8388_write_reg(codec, ES8388_CHIPPOWER, 0x00); // normal all and power up all + + // Disable the internal DLL to improve 8K sample rate + res |= es8388_write_reg(codec, 0x35, 0xA0); + res |= es8388_write_reg(codec, 0x37, 0xD0); + res |= es8388_write_reg(codec, 0x39, 0xD0); + + res |= es8388_write_reg(codec, ES8388_MASTERMODE, codec_cfg->master_mode); // CODEC IN I2S SLAVE MODE + + /* dac */ + res |= es8388_write_reg(codec, ES8388_DACPOWER, 0xC0); // disable DAC and disable Lout/Rout/1/2 + res |= es8388_write_reg(codec, ES8388_CONTROL1, 0x12); // Enfr=0,Play&Record Mode,(0x17-both of mic&play) + // res |= es8388_write_reg(codec, ES8388_CONTROL2, 0); //LPVrefBuf=0,Pdn_ana=0 + res |= es8388_write_reg(codec, ES8388_DACCONTROL1, 0x18); // 1a 0x18:16bit iis , 0x00:24 + res |= es8388_write_reg(codec, ES8388_DACCONTROL2, 0x02); // DACFsMode,SINGLE SPEED; DACFsRatio,256 + res |= es8388_write_reg(codec, ES8388_DACCONTROL16, 0x00); // 0x00 audio on LIN1&RIN1, 0x09 LIN2&RIN2 + res |= es8388_write_reg(codec, ES8388_DACCONTROL17, 0x90); // only left DAC to left mixer enable 0db + res |= es8388_write_reg(codec, ES8388_DACCONTROL20, 0x90); // only right DAC to right mixer enable 0db + // set internal ADC and DAC use the same LRCK clock, ADC LRCK as internal LRCK + res |= es8388_write_reg(codec, ES8388_DACCONTROL21, 0x80); + res |= es8388_write_reg(codec, ES8388_DACCONTROL23, 0x00); // vroi=0 + res |= es8388_set_adc_dac_volume(codec, ES_MODULE_DAC, 0, 0); // 0db + + res |= es8388_write_reg(codec, ES8388_DACCONTROL24, 0x1E); // Set L1 R1 L2 R2 volume. 0x00: -30dB, 0x1E: 0dB, 0x21: 3dB + res |= es8388_write_reg(codec, ES8388_DACCONTROL25, 0x1E); + res |= es8388_write_reg(codec, ES8388_DACCONTROL26, 0); + res |= es8388_write_reg(codec, ES8388_DACCONTROL27, 0); + + // TODO default use DAC_ALL + int tmp = DAC_OUTPUT_LOUT1 | DAC_OUTPUT_LOUT2 | DAC_OUTPUT_ROUT1 | DAC_OUTPUT_ROUT2; + res |= es8388_write_reg(codec, ES8388_DACPOWER, tmp); // 0x3c Enable DAC and Enable Lout/Rout/1/2 + /* adc */ + res |= es8388_write_reg(codec, ES8388_ADCPOWER, 0xFF); + res |= es8388_write_reg(codec, ES8388_ADCCONTROL1, 0xbb); // MIC Left and Right channel PGA gain + tmp = 0; + // TODO default use ADC LINE1 + // 0x00 LINSEL & RINSEL, LIN1/RIN1 as ADC Input; DSSEL,use one DS Reg11; DSR, LINPUT1-RINPUT1 + res |= es8388_write_reg(codec, ES8388_ADCCONTROL2, ADC_INPUT_LINPUT1_RINPUT1); + res |= es8388_write_reg(codec, ES8388_ADCCONTROL3, 0x02); + res |= es8388_write_reg(codec, ES8388_ADCCONTROL4, 0x0c); // 16 Bits length and I2S serial audio data format + res |= es8388_write_reg(codec, ES8388_ADCCONTROL5, 0x02); // ADCFsMode, single SPEED, RATIO=256 + // ALC for Microphone + res |= es8388_set_adc_dac_volume(codec, ESP_CODEC_DEV_WORK_MODE_ADC, 0, 0); // 0db + res |= es8388_write_reg(codec, ES8388_ADCPOWER, 0x09); // Power on ADC + if (res != 0) { + ESP_LOGI(TAG, "Fail to write register"); + return ESP_CODEC_DEV_WRITE_FAIL; + } + codec->is_open = true; + return ESP_CODEC_DEV_OK; +} + +static int es8388_enable(const audio_codec_if_t *h, bool enable) +{ + audio_codec_es8388_t *codec = (audio_codec_es8388_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; + } + int res; + if (enable == false) { + es8388_pa_power(codec, false); + res = es8388_stop(codec, codec->codec_mode); + } else { + res = es8388_start(codec, codec->codec_mode); + es8388_pa_power(codec, true); + } + if (res == ESP_CODEC_DEV_OK) { + codec->enabled = enable; + ESP_LOGD(TAG, "Codec is %s", enable ? "enabled" : "disabled"); + } + return res; +} + +static int es8388_mute(const audio_codec_if_t *h, bool mute) +{ + audio_codec_es8388_t *codec = (audio_codec_es8388_t *) h; + if (codec == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open == false) { + return ESP_CODEC_DEV_WRONG_STATE; + } + return es8388_set_voice_mute(codec, mute); +} + +static int es8388_set_vol(const audio_codec_if_t *h, float db_value) +{ + audio_codec_es8388_t *codec = (audio_codec_es8388_t *) h; + if (codec == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open == false) { + return ESP_CODEC_DEV_WRONG_STATE; + } + db_value -= codec->hw_gain; + int volume = esp_codec_dev_vol_calc_reg(&vol_range, db_value); + int res = es8388_write_reg(codec, ES8388_DACCONTROL5, volume); + res |= es8388_write_reg(codec, ES8388_DACCONTROL4, volume); + ESP_LOGD(TAG, "Set volume reg:%x db:%f", volume, db_value); + return res ? ESP_CODEC_DEV_WRITE_FAIL : ESP_CODEC_DEV_OK; +} + +static int es8388_set_fs(const audio_codec_if_t *h, esp_codec_dev_sample_info_t *fs) +{ + audio_codec_es8388_t *codec = (audio_codec_es8388_t *) h; + if (codec == NULL || fs == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open == false) { + return ESP_CODEC_DEV_WRONG_STATE; + } + int res = 0; + res |= es8388_config_fmt(codec, ESP_CODEC_DEV_WORK_MODE_BOTH, ES_I2S_NORMAL); + res |= es8388_set_bits_per_sample(codec, ESP_CODEC_DEV_WORK_MODE_BOTH, fs->bits_per_sample); + return res; +} + +static int es8388_set_gain(const audio_codec_if_t *h, float db) +{ + audio_codec_es8388_t *codec = (audio_codec_es8388_t *) h; + if (codec == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open == false) { + return ESP_CODEC_DEV_WRONG_STATE; + } + return es8388_set_mic_gain(codec, db); +} + +static int es8388_close(const audio_codec_if_t *h) +{ + audio_codec_es8388_t *codec = (audio_codec_es8388_t *) h; + if (codec == NULL) { + return ESP_CODEC_DEV_INVALID_ARG; + } + if (codec->is_open) { + es8388_pa_power(codec, false); + codec->is_open = false; + } + return ESP_CODEC_DEV_OK; +} + +static void es8388_dump(const audio_codec_if_t *h) +{ + audio_codec_es8388_t *codec = (audio_codec_es8388_t *) h; + if (codec == NULL || codec->is_open == false) { + return; + } + for (int i = 0; i <= ES8388_DACCONTROL30; i++) { + int value = 0; + int ret = es8388_read_reg(codec, i, &value); + if (ret != ESP_CODEC_DEV_OK) { + break; + } + ESP_LOGI(TAG, "%02x: %02x", i, value); + } +} + +const audio_codec_if_t *es8388_codec_new(es8388_codec_cfg_t *codec_cfg) +{ + // verify param + 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_es8388_t *codec = (audio_codec_es8388_t *) calloc(1, sizeof(audio_codec_es8388_t)); + if (codec == NULL) { + CODEC_MEM_CHECK(codec); + return NULL; + } + codec->ctrl_if = codec_cfg->ctrl_if; + codec->base.open = es8388_open; + codec->base.enable = es8388_enable; + codec->base.set_fs = es8388_set_fs; + codec->base.set_vol = es8388_set_vol; + codec->base.mute = es8388_mute; + codec->base.set_mic_gain = es8388_set_gain; + codec->base.dump_reg = es8388_dump; + codec->base.close = es8388_close; + codec->hw_gain = esp_codec_dev_col_calc_hw_gain(&codec_cfg->hw_gain); + do { + int ret = codec->base.open(&codec->base, codec_cfg, sizeof(es8388_codec_cfg_t)); + if (ret != 0) { + ESP_LOGE(TAG, "Fail to open"); + break; + } + return &codec->base; + } while (0); + if (codec) { + free(codec); + } + return NULL; +} diff --git a/components/esp_codec_dev/device/es8388/es8388_reg.h b/components/esp_codec_dev/device/es8388/es8388_reg.h new file mode 100644 index 0000000..85131d4 --- /dev/null +++ b/components/esp_codec_dev/device/es8388/es8388_reg.h @@ -0,0 +1,79 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _ES8388_REG_ +#define _ES8388_REG_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* ES8388 register */ +#define ES8388_CONTROL1 0x00 +#define ES8388_CONTROL2 0x01 + +#define ES8388_CHIPPOWER 0x02 + +#define ES8388_ADCPOWER 0x03 +#define ES8388_DACPOWER 0x04 + +#define ES8388_CHIPLOPOW1 0x05 +#define ES8388_CHIPLOPOW2 0x06 + +#define ES8388_ANAVOLMANAG 0x07 + +#define ES8388_MASTERMODE 0x08 +/* ADC */ +#define ES8388_ADCCONTROL1 0x09 +#define ES8388_ADCCONTROL2 0x0a +#define ES8388_ADCCONTROL3 0x0b +#define ES8388_ADCCONTROL4 0x0c +#define ES8388_ADCCONTROL5 0x0d +#define ES8388_ADCCONTROL6 0x0e +#define ES8388_ADCCONTROL7 0x0f +#define ES8388_ADCCONTROL8 0x10 +#define ES8388_ADCCONTROL9 0x11 +#define ES8388_ADCCONTROL10 0x12 +#define ES8388_ADCCONTROL11 0x13 +#define ES8388_ADCCONTROL12 0x14 +#define ES8388_ADCCONTROL13 0x15 +#define ES8388_ADCCONTROL14 0x16 +/* DAC */ +#define ES8388_DACCONTROL1 0x17 +#define ES8388_DACCONTROL2 0x18 +#define ES8388_DACCONTROL3 0x19 +#define ES8388_DACCONTROL4 0x1a +#define ES8388_DACCONTROL5 0x1b +#define ES8388_DACCONTROL6 0x1c +#define ES8388_DACCONTROL7 0x1d +#define ES8388_DACCONTROL8 0x1e +#define ES8388_DACCONTROL9 0x1f +#define ES8388_DACCONTROL10 0x20 +#define ES8388_DACCONTROL11 0x21 +#define ES8388_DACCONTROL12 0x22 +#define ES8388_DACCONTROL13 0x23 +#define ES8388_DACCONTROL14 0x24 +#define ES8388_DACCONTROL15 0x25 +#define ES8388_DACCONTROL16 0x26 +#define ES8388_DACCONTROL17 0x27 +#define ES8388_DACCONTROL18 0x28 +#define ES8388_DACCONTROL19 0x29 +#define ES8388_DACCONTROL20 0x2a +#define ES8388_DACCONTROL21 0x2b +#define ES8388_DACCONTROL22 0x2c +#define ES8388_DACCONTROL23 0x2d +#define ES8388_DACCONTROL24 0x2e +#define ES8388_DACCONTROL25 0x2f +#define ES8388_DACCONTROL26 0x30 +#define ES8388_DACCONTROL27 0x31 +#define ES8388_DACCONTROL28 0x32 +#define ES8388_DACCONTROL29 0x33 +#define ES8388_DACCONTROL30 0x34 + +#ifdef __cplusplus +} +#endif + +#endif //__ES8388_H__ diff --git a/components/esp_codec_dev/device/include/es8388_codec.h b/components/esp_codec_dev/device/include/es8388_codec.h new file mode 100644 index 0000000..865ec34 --- /dev/null +++ b/components/esp_codec_dev/device/include/es8388_codec.h @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef _ES8388_CODEC_H_ +#define _ES8388_CODEC_H_ + +#include "audio_codec_if.h" +#include "audio_codec_ctrl_if.h" +#include "audio_codec_gpio_if.h" +#include "esp_codec_dev_vol.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief ES8388 default I2C address + */ +#define ES8388_CODEC_DEFAULT_ADDR (0x20) +#define ES8388_CODEC_DEFAULT_ADDR_1 (0x22) +/** + * @brief ES8388 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 on 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 */ + esp_codec_dev_hw_gain_t hw_gain; /*!< Hardware gain */ +} es8388_codec_cfg_t; + +/** + * @brief New ES8388 codec interface + * @param codec_cfg: ES8388 codec configuration + * @return NULL: Fail to new ES8388 codec interface + * -Others: ES8388 codec interface + */ +const audio_codec_if_t *es8388_codec_new(es8388_codec_cfg_t *codec_cfg); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/components/mybot_platform/boards/README.md b/components/mybot_platform/boards/README.md index d728221..7430ded 100644 --- a/components/mybot_platform/boards/README.md +++ b/components/mybot_platform/boards/README.md @@ -4,9 +4,10 @@ These documents describe the board profiles implemented by `agora-esp32-devkit`. drivers and build constraints are defined by the checked-in `board.cmake`, `board_config.h` and `sdkconfig.defaults` files. Use ESP-IDF v5.5.2 and select a board with `MYBOT_BOARD`. -以下说明对应本工程已实现的 56 个板型。硬件配置、输入行为和构建约束以当前板级代码为准; -验证状态统一见[验证矩阵](../../../docs/VALIDATION.md)。不同尺寸、芯片或硬件版本的板卡 -不能仅凭外观共用固件。 +以下说明对应本工程已实现的 63 个板型,其中 56 个带屏、7 个无屏。硬件配置、输入行为和 +构建约束以当前板级代码为准;验证状态统一见 +[验证矩阵](../../../docs/VALIDATION.md)。不同尺寸、芯片或硬件版本的板卡不能仅凭外观 +共用固件。 | Board profile | 板卡说明 | 显示 | 视频 | | --- | --- | --- | --- | @@ -78,6 +79,13 @@ real-device validation. | [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. | +| [minsi-k08-dual](minsi-k08-dual/README.md) | 240×240 ST7789 | separate raw I2S | Boot + two volume keys | Wi-Fi only; lamp, LED, battery, sleep and shutdown are not implemented. | +| [kevin-box-2](kevin-box-2/README.md) | Headless | ES8311 + ES7210 | Boot + two volume keys | SSD1306 UI, ML307, LED, battery, sleep and shutdown are not implemented. | +| [magiclick-2p5](magiclick-2p5/README.md) | 128×128 GC9107 or ST7735 | ES8311 | three buttons | Wi-Fi only; battery, LED, sleep and shutdown are not implemented. | +| [atk-dnesp32s3-box3](atk-dnesp32s3-box3/README.md) | 320×240 ST7789 | ES8311 + ES7210 | Boot | AW9523B power/PA/backlight implemented; K1/K2, camera, battery, sleep and shutdown are not. | +| [zhengchen-cam](zhengchen-cam/README.md) | 320×240 ST7789 | ES8311 output + ES7210 capture | Boot + two volume buttons | Camera, portrait mode, battery, temperature, sleep and shutdown are not implemented; PCA9557 PA and audio require validation. | +| [labplus-ledong-v2](labplus-ledong-v2/README.md) | 320×172 ST7789-compatible | ES8388 | Boot | STM8 display reset is implemented; touch, sensors, buzzer, RGB LED, motor, camera, sleep and shutdown are not. | +| [labplus-mpython-v3](labplus-mpython-v3/README.md) | 320×172 ST7789-compatible | ES8388 | Boot | Touch, sensors, buzzer, RGB LED, motor, camera, sleep and shutdown are not implemented. | ## Build, flash and validation / 构建、烧录与验证 diff --git a/components/mybot_platform/boards/atk-dnesp32s3-box3/README.md b/components/mybot_platform/boards/atk-dnesp32s3-box3/README.md new file mode 100644 index 0000000..d6a26fb --- /dev/null +++ b/components/mybot_platform/boards/atk-dnesp32s3-box3/README.md @@ -0,0 +1,15 @@ +# ATK DNESP32S3 Box 3 + +板型 ID 为 `atk-dnesp32s3-box3`。 + +- 显示:320×240 ST7789,SPI2 mode 0,60 MHz,共享 LVGL UI。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 扩展器:AW9523B `0x59` 负责音频/屏幕电源、PA 和背光。 +- 输入:GPIO0 Boot;AW9523B K1/K2 未实现。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:AW9523B bit 8,固定开启;PA 为 bit 5。 + +摄像头、K1/K2、电池、充电、LED、休眠和关机未实现。AW9523B 电源时序、显示数据端序、 +双 codec 路由、PA 和背光仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/atk-dnesp32s3-box3/board.c b/components/mybot_platform/boards/atk-dnesp32s3-box3/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/atk-dnesp32s3-box3/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-box3/board.cmake b/components/mybot_platform/boards/atk-dnesp32s3-box3/board.cmake new file mode 100644 index 0000000..48ba3e6 --- /dev/null +++ b/components/mybot_platform/boards/atk-dnesp32s3-box3/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 "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_AW9523B 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/atk-dnesp32s3-box3/board_config.h b/components/mybot_platform/boards/atk-dnesp32s3-box3/board_config.h new file mode 100644 index 0000000..59a9547 --- /dev/null +++ b/components/mybot_platform/boards/atk-dnesp32s3-box3/board_config.h @@ -0,0 +1,70 @@ +/* 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" + +#include + +#define MYBOT_BOARD_NAME "atk-dnesp32s3-box3" +#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_21 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_41 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_40 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_3 +#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_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_GENERIC_AW9523B_ADDRESS 0x59 +#define MYBOT_GENERIC_AW9523B_OUTPUT_MASK 0xfffcU +#define MYBOT_GENERIC_AW9523B_HIGH_MASK \ + ((uint16_t)((1U << 4) | (1U << 5) | (1U << 11) | (1U << 12) | (1U << 13) | (1U << 14))) +#define MYBOT_GENERIC_AW9523B_PA_MASK ((uint16_t)(1U << 5)) +#define MYBOT_GENERIC_AW9523B_BACKLIGHT_MASK ((uint16_t)(1U << 8)) +#define MYBOT_GENERIC_AW9523B_SETTLE_MS 50 + +#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 SPI2_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_16 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_15 +#define MYBOT_DISPLAY_DC GPIO_NUM_48 +#define MYBOT_DISPLAY_CS GPIO_NUM_47 +#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 100 +#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 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (60 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define MYBOT_DISPLAY_DATA_ENDIAN LCD_RGB_DATA_ENDIAN_BIG + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/atk-dnesp32s3-box3/sdkconfig.defaults b/components/mybot_platform/boards/atk-dnesp32s3-box3/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/atk-dnesp32s3-box3/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/boards.cmake b/components/mybot_platform/boards/boards.cmake index 9c84764..2cb426f 100644 --- a/components/mybot_platform/boards/boards.cmake +++ b/components/mybot_platform/boards/boards.cmake @@ -55,6 +55,13 @@ set(MYBOT_SUPPORTED_BOARDS "zhengchen-1.54tft-wifi" "kevin-sp-v4-dev" "du-chatx" + "minsi-k08-dual" + "kevin-box-2" + "magiclick-2p5" + "atk-dnesp32s3-box3" + "zhengchen-cam" + "labplus-ledong-v2" + "labplus-mpython-v3" ) set(_MYBOT_BOARDS_ROOT "${CMAKE_CURRENT_LIST_DIR}") @@ -172,6 +179,20 @@ function(mybot_resolve_board board) 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") + elseif(board STREQUAL "minsi-k08-dual") + set(profile "${_MYBOT_BOARDS_ROOT}/minsi-k08-dual/board.cmake") + elseif(board STREQUAL "kevin-box-2") + set(profile "${_MYBOT_BOARDS_ROOT}/kevin-box-2/board.cmake") + elseif(board STREQUAL "magiclick-2p5") + set(profile "${_MYBOT_BOARDS_ROOT}/magiclick-2p5/board.cmake") + elseif(board STREQUAL "atk-dnesp32s3-box3") + set(profile "${_MYBOT_BOARDS_ROOT}/atk-dnesp32s3-box3/board.cmake") + elseif(board STREQUAL "zhengchen-cam") + set(profile "${_MYBOT_BOARDS_ROOT}/zhengchen-cam/board.cmake") + elseif(board STREQUAL "labplus-ledong-v2") + set(profile "${_MYBOT_BOARDS_ROOT}/labplus-ledong-v2/board.cmake") + elseif(board STREQUAL "labplus-mpython-v3") + set(profile "${_MYBOT_BOARDS_ROOT}/labplus-mpython-v3/board.cmake") else() message(FATAL_ERROR "Unsupported MYBOT_BOARD: ${board}") endif() diff --git a/components/mybot_platform/boards/common/generic_board.c b/components/mybot_platform/boards/common/generic_board.c index e742463..80ff500 100644 --- a/components/mybot_platform/boards/common/generic_board.c +++ b/components/mybot_platform/boards/common/generic_board.c @@ -14,7 +14,7 @@ #define TAG "generic_board" -#if MYBOT_GENERIC_AUDIO_KIND == 1 || MYBOT_GENERIC_AUDIO_KIND == 4 +#if MYBOT_GENERIC_AUDIO_KIND == 1 || MYBOT_GENERIC_AUDIO_KIND == 4 || MYBOT_GENERIC_AUDIO_KIND == 5 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); diff --git a/components/mybot_platform/boards/common/generic_hardware.c b/components/mybot_platform/boards/common/generic_hardware.c index 614cb3c..c7de59a 100644 --- a/components/mybot_platform/boards/common/generic_hardware.c +++ b/components/mybot_platform/boards/common/generic_hardware.c @@ -2,6 +2,7 @@ #include "generic_hardware.h" #include "board_config.h" +#include "io_expander/aw9523b.h" #include "io_expander/pca9557.h" #include "io_expander/tca9554.h" @@ -32,6 +33,10 @@ #define MYBOT_GENERIC_TOUCH_KIND 0 #endif +#ifndef MYBOT_GENERIC_EXTRA_INIT +#define MYBOT_GENERIC_EXTRA_INIT 0 +#endif + #ifndef MYBOT_GENERIC_PA_ACTIVE_LEVEL #define MYBOT_GENERIC_PA_ACTIVE_LEVEL 1 #endif @@ -57,8 +62,10 @@ typedef struct { i2c_master_bus_handle_t touch_bus; mybot_pca9557_t expander; mybot_tca9554_t tca9554; + mybot_aw9523b_t aw9523b; i2c_master_dev_handle_t axp2101; bool expander_ready; + bool aw9523b_ready; bool pa_ready; bool pa_enabled; bool power_ready; @@ -70,6 +77,10 @@ typedef struct { static hardware_state_t s_hardware; +#if MYBOT_GENERIC_EXTRA_INIT +int mybot_generic_board_extra_init(i2c_master_bus_handle_t bus); +#endif + #ifdef MYBOT_GENERIC_AXP2101_ADDRESS static esp_err_t initialize_axp2101(void) { static const uint8_t registers[][2] = { @@ -91,7 +102,7 @@ static esp_err_t initialize_axp2101(void) { static bool codec_needs_i2c(void) { return MYBOT_GENERIC_AUDIO_KIND == 1 || MYBOT_GENERIC_AUDIO_KIND == 3 || - MYBOT_GENERIC_AUDIO_KIND == 4; + MYBOT_GENERIC_AUDIO_KIND == 4 || MYBOT_GENERIC_AUDIO_KIND == 5; } static esp_err_t create_i2c_bus(int port, gpio_num_t sda, gpio_num_t scl, @@ -123,7 +134,15 @@ static esp_err_t configure_output_gpio(gpio_num_t gpio, int initial_level) { } static esp_err_t initialize_backlight(void) { -#ifdef MYBOT_DISPLAY_BACKLIGHT_GPIO +#ifdef MYBOT_GENERIC_AW9523B_BACKLIGHT_MASK + if (!s_hardware.aw9523b_ready || + mybot_aw9523b_set_levels(&s_hardware.aw9523b, MYBOT_GENERIC_AW9523B_BACKLIGHT_MASK, 0) < + 0) { + return ESP_FAIL; + } + s_hardware.backlight_ready = true; + return ESP_OK; +#elif defined(MYBOT_DISPLAY_BACKLIGHT_GPIO) if (MYBOT_DISPLAY_BACKLIGHT_GPIO == GPIO_NUM_NC) { return ESP_OK; } @@ -201,12 +220,18 @@ static int release_hardware(void) { result = -1; } #endif +#ifdef MYBOT_GENERIC_AW9523B_ADDRESS + if (s_hardware.aw9523b.device && mybot_aw9523b_deinit(&s_hardware.aw9523b) < 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; + s_hardware.aw9523b_ready = false; if (s_hardware.touch_bus && i2c_del_master_bus(s_hardware.touch_bus) != ESP_OK) { result = -1; } else { @@ -228,8 +253,9 @@ int mybot_generic_hardware_init(void) { 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.aux_power_ready || s_hardware.backlight_ready) && + s_hardware.tca9554.device || s_hardware.aw9523b.device || s_hardware.axp2101 || + s_hardware.pa_ready || s_hardware.power_ready || s_hardware.aux_power_ready || + s_hardware.backlight_ready) && release_hardware() < 0) { return -1; } @@ -276,6 +302,11 @@ int mybot_generic_hardware_init(void) { MYBOT_GENERIC_TOUCH_I2C_SCL, &s_hardware.touch_bus); } #endif +#if MYBOT_GENERIC_EXTRA_INIT + if (result == ESP_OK && s_hardware.audio_bus) { + result = mybot_generic_board_extra_init(s_hardware.audio_bus) == 0 ? ESP_OK : ESP_FAIL; + } +#endif #ifdef MYBOT_GENERIC_PCA9557_ADDRESS if (result == ESP_OK && s_hardware.audio_bus) { @@ -345,6 +376,26 @@ int mybot_generic_hardware_init(void) { #endif #endif +#ifdef MYBOT_GENERIC_AW9523B_ADDRESS + if (result == ESP_OK && s_hardware.audio_bus) { + result = mybot_aw9523b_init(&s_hardware.aw9523b, s_hardware.audio_bus, + MYBOT_GENERIC_AW9523B_ADDRESS, MYBOT_GENERIC_I2C_SPEED_HZ) == 0 + ? ESP_OK + : ESP_FAIL; + s_hardware.aw9523b_ready = result == ESP_OK; + } + if (result == ESP_OK && s_hardware.aw9523b_ready) { + result = + mybot_aw9523b_configure_outputs(&s_hardware.aw9523b, MYBOT_GENERIC_AW9523B_OUTPUT_MASK, + MYBOT_GENERIC_AW9523B_HIGH_MASK) == 0 + ? ESP_OK + : ESP_FAIL; + } + if (result == ESP_OK) { + vTaskDelay(pdMS_TO_TICKS(MYBOT_GENERIC_AW9523B_SETTLE_MS)); + } +#endif + #ifdef MYBOT_GENERIC_AXP2101_ADDRESS if (result == ESP_OK && s_hardware.audio_bus) { const i2c_device_config_t axp_config = { @@ -410,6 +461,16 @@ int mybot_generic_set_speaker_power(bool enabled) { return 0; } #endif +#ifdef MYBOT_GENERIC_AW9523B_PA_MASK + if (s_hardware.aw9523b_ready) { + const uint16_t mask = MYBOT_GENERIC_AW9523B_PA_MASK; + if (mybot_aw9523b_set_levels(&s_hardware.aw9523b, 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) { @@ -432,7 +493,13 @@ int mybot_generic_set_speaker_power(bool enabled) { } int mybot_board_set_display_backlight(unsigned int percent) { -#ifdef MYBOT_DISPLAY_BACKLIGHT_GPIO +#ifdef MYBOT_GENERIC_AW9523B_BACKLIGHT_MASK + if (!s_hardware.initialized || !s_hardware.backlight_ready || percent > 100) { + return -1; + } + const uint16_t mask = MYBOT_GENERIC_AW9523B_BACKLIGHT_MASK; + return mybot_aw9523b_set_levels(&s_hardware.aw9523b, mask, percent ? mask : 0); +#elif defined(MYBOT_DISPLAY_BACKLIGHT_GPIO) if (MYBOT_DISPLAY_BACKLIGHT_GPIO == GPIO_NUM_NC) { return 0; } diff --git a/components/mybot_platform/boards/common/generic_profile.cmake b/components/mybot_platform/boards/common/generic_profile.cmake index 47072dc..b604ef2 100644 --- a/components/mybot_platform/boards/common/generic_profile.cmake +++ b/components/mybot_platform/boards/common/generic_profile.cmake @@ -33,6 +33,8 @@ 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 "es8388") + 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") @@ -77,6 +79,11 @@ if(MYBOT_GENERIC_TCA9554) "${MYBOT_PLATFORM_ROOT}/src/drivers/io_expander/tca9554.c") endif() +if(MYBOT_GENERIC_AW9523B) + list(APPEND MYBOT_BOARD_SOURCES + "${MYBOT_PLATFORM_ROOT}/src/drivers/io_expander/aw9523b.c") +endif() + set(MYBOT_BOARD_REQUIRES button esp_codec_dev diff --git a/components/mybot_platform/boards/kevin-box-2/README.md b/components/mybot_platform/boards/kevin-box-2/README.md new file mode 100644 index 0000000..fee5e6b --- /dev/null +++ b/components/mybot_platform/boards/kevin-box-2/README.md @@ -0,0 +1,14 @@ +# KEVIN Box 2 + +板型 ID 为 `kevin-box-2`。当前实现为 Wi-Fi headless 子集。 + +- 显示:未启用;128×64 SSD1306 单色 UI 尚未实现。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 电源:AXP2101 `0x34`,GPIO9 speaker PA。 +- 输入:GPIO0 Boot、GPIO1 音量加、GPIO2 音量减。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 + +ML307/4G、OLED、LED、电池状态、休眠和关机未实现。AXP2101 电源时序、双 codec 路由和按键 +仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/kevin-box-2/board.c b/components/mybot_platform/boards/kevin-box-2/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/kevin-box-2/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-box-2/board.cmake b/components/mybot_platform/boards/kevin-box-2/board.cmake new file mode 100644 index 0000000..5eba0b1 --- /dev/null +++ b/components/mybot_platform/boards/kevin-box-2/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 "dual") +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 +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/kevin-box-2/board_config.h b/components/mybot_platform/boards/kevin-box-2/board_config.h new file mode 100644 index 0000000..e19a642 --- /dev/null +++ b/components/mybot_platform/boards/kevin-box-2/board_config.h @@ -0,0 +1,38 @@ +/* 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 "kevin-box-2" +#define MYBOT_BOARD_HAS_DISPLAY 0 +#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_40 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_38 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_47 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_48 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_1 +#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_9 +#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_1 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_2 +#define MYBOT_BOOT_BUTTON_LONG_PRESS_MS 3000 + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/kevin-box-2/sdkconfig.defaults b/components/mybot_platform/boards/kevin-box-2/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/kevin-box-2/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/labplus-ledong-v2/README.md b/components/mybot_platform/boards/labplus-ledong-v2/README.md new file mode 100644 index 0000000..05aa72e --- /dev/null +++ b/components/mybot_platform/boards/labplus-ledong-v2/README.md @@ -0,0 +1,15 @@ +# LabPlus 乐动掌控 V2 + +板型 ID 为 `labplus-ledong-v2`。 + +- 显示:320×172 ST7789 兼容 SPI 面板,SPI2 mode 0,20 MHz,大端像素数据。 +- 音频:ES8388,I2C0 GPIO44/43,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;第二个按键 GPIO46 未接入。 +- 存储:16 MB QIO Flash、80 MHz Quad PSRAM、双 OTA。 +- 背光:GPIO34 LEDC,默认 60%。 +- 屏幕复位:通过 I2C `0x11` 向 STM8 寄存器 `0x04` 发送一次复位命令。 + +触摸、传感器、蜂鸣器、RGB 灯、电机、摄像头、休眠和关机未实现。ES8388、面板方向、STM8 +复位和音频仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/labplus-ledong-v2/board.c b/components/mybot_platform/boards/labplus-ledong-v2/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/labplus-ledong-v2/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/labplus-ledong-v2/board.cmake b/components/mybot_platform/boards/labplus-ledong-v2/board.cmake new file mode 100644 index 0000000..29f98c2 --- /dev/null +++ b/components/mybot_platform/boards/labplus-ledong-v2/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 "es8388") +set(MYBOT_GENERIC_DISPLAY "st7789/st7789_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_EXTRA_SOURCES "${CMAKE_CURRENT_LIST_DIR}/labplus_hardware.c") +set(MYBOT_GENERIC_REQUIRED_CONFIGS + CONFIG_PARTITION_TABLE_CUSTOM + CONFIG_ESPTOOLPY_FLASHSIZE_16MB + CONFIG_ESPTOOLPY_FLASHMODE_QIO + CONFIG_SPIRAM + CONFIG_SPIRAM_MODE_QUAD + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8388_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/labplus-ledong-v2/board_config.h b/components/mybot_platform/boards/labplus-ledong-v2/board_config.h new file mode 100644 index 0000000..bb58cc7 --- /dev/null +++ b/components/mybot_platform/boards/labplus-ledong-v2/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 "labplus-ledong-v2" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 5 +#define MYBOT_GENERIC_TOUCH_KIND 0 +#define MYBOT_GENERIC_EXTRA_INIT 1 + +#define MYBOT_AUDIO_SAMPLE_RATE 16000 +#define MYBOT_AUDIO_DEFAULT_VOLUME 70 +#define MYBOT_AUDIO_I2S_MCLK GPIO_NUM_39 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_41 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_42 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_40 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_38 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_44 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_43 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_AUDIO_CODEC_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AUDIO_CODEC_I2C_ADDRESS 0x20 +#define MYBOT_AUDIO_CODEC_INPUT_GAIN_DB 24.0f +#define MYBOT_AUDIO_CODEC_HARDWARE_VOLUME_SCALE_PERCENT 100 + +#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 SPI2_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_37 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_36 +#define MYBOT_DISPLAY_DC GPIO_NUM_35 +#define MYBOT_DISPLAY_CS GPIO_NUM_NC +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_34 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 320 +#define MYBOT_DISPLAY_HEIGHT 172 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 34 +#define MYBOT_DISPLAY_SWAP_XY true +#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 (20 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define MYBOT_DISPLAY_DATA_ENDIAN LCD_RGB_DATA_ENDIAN_BIG + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/labplus-ledong-v2/labplus_hardware.c b/components/mybot_platform/boards/labplus-ledong-v2/labplus_hardware.c new file mode 100644 index 0000000..7b3e354 --- /dev/null +++ b/components/mybot_platform/boards/labplus-ledong-v2/labplus_hardware.c @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "driver/i2c_master.h" +#include "esp_err.h" +#include "esp_log.h" + +#include + +#define TAG "labplus_hardware" +#define LABPLUS_STM8_ADDRESS 0x11 +#define LABPLUS_STM8_RESET_REGISTER 0x04 +#define LABPLUS_I2C_TIMEOUT_MS 100 + +int mybot_generic_board_extra_init(i2c_master_bus_handle_t bus) { + const i2c_device_config_t config = { + .dev_addr_length = I2C_ADDR_BIT_LEN_7, + .device_address = LABPLUS_STM8_ADDRESS, + .scl_speed_hz = 400000, + }; + i2c_master_dev_handle_t device = NULL; + esp_err_t result = i2c_master_bus_add_device(bus, &config, &device); + if (result == ESP_OK) { + const uint8_t command = LABPLUS_STM8_RESET_REGISTER; + result = i2c_master_transmit(device, &command, sizeof(command), LABPLUS_I2C_TIMEOUT_MS); + } + if (device) { + const esp_err_t remove_result = i2c_master_bus_rm_device(device); + if (result == ESP_OK) { + result = remove_result; + } + } + if (result != ESP_OK) { + ESP_LOGE(TAG, "event=panel_reset action=initialize result=error code=%s", + esp_err_to_name(result)); + return -1; + } + return 0; +} diff --git a/components/mybot_platform/boards/labplus-ledong-v2/sdkconfig.defaults b/components/mybot_platform/boards/labplus-ledong-v2/sdkconfig.defaults new file mode 100644 index 0000000..dba9b50 --- /dev/null +++ b/components/mybot_platform/boards/labplus-ledong-v2/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_QUAD=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8388_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/labplus-mpython-v3/README.md b/components/mybot_platform/boards/labplus-mpython-v3/README.md new file mode 100644 index 0000000..f172505 --- /dev/null +++ b/components/mybot_platform/boards/labplus-mpython-v3/README.md @@ -0,0 +1,14 @@ +# LabPlus 掌控板 V3 + +板型 ID 为 `labplus-mpython-v3`。 + +- 显示:320×172 ST7789 兼容 SPI 面板,SPI2 mode 0,20 MHz,大端像素数据。 +- 音频:ES8388,I2C0 GPIO44/43,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot;第二个按键 GPIO46 未接入。 +- 存储:16 MB QIO Flash、80 MHz Quad PSRAM、双 OTA。 +- 背光:GPIO33 LEDC,默认 60%。 + +触摸、传感器、蜂鸣器、RGB 灯、电机、摄像头、休眠和关机未实现。ES8388、面板方向和音频 +仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/labplus-mpython-v3/board.c b/components/mybot_platform/boards/labplus-mpython-v3/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/labplus-mpython-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/labplus-mpython-v3/board.cmake b/components/mybot_platform/boards/labplus-mpython-v3/board.cmake new file mode 100644 index 0000000..2a5afb5 --- /dev/null +++ b/components/mybot_platform/boards/labplus-mpython-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 "es8388") +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_QUAD + CONFIG_SPIRAM_SPEED_80M + CONFIG_CODEC_ES8388_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/labplus-mpython-v3/board_config.h b/components/mybot_platform/boards/labplus-mpython-v3/board_config.h new file mode 100644 index 0000000..d4d8153 --- /dev/null +++ b/components/mybot_platform/boards/labplus-mpython-v3/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 "labplus-mpython-v3" +#define MYBOT_BOARD_HAS_DISPLAY 1 +#define MYBOT_BOARD_HAS_TOUCH 0 +#define MYBOT_GENERIC_AUDIO_KIND 5 +#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_39 +#define MYBOT_AUDIO_I2S_BCLK GPIO_NUM_41 +#define MYBOT_AUDIO_I2S_WS GPIO_NUM_42 +#define MYBOT_AUDIO_I2S_DIN GPIO_NUM_40 +#define MYBOT_AUDIO_I2S_DOUT GPIO_NUM_38 + +#define MYBOT_GENERIC_I2C_PORT I2C_NUM_0 +#define MYBOT_GENERIC_I2C_SDA GPIO_NUM_44 +#define MYBOT_GENERIC_I2C_SCL GPIO_NUM_43 +#define MYBOT_GENERIC_I2C_SPEED_HZ 400000 +#define MYBOT_GENERIC_PA_GPIO GPIO_NUM_NC + +#define MYBOT_AUDIO_CODEC_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AUDIO_CODEC_I2C_ADDRESS 0x20 +#define MYBOT_AUDIO_CODEC_INPUT_GAIN_DB 24.0f +#define MYBOT_AUDIO_CODEC_HARDWARE_VOLUME_SCALE_PERCENT 100 + +#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 SPI2_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_37 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_36 +#define MYBOT_DISPLAY_DC GPIO_NUM_35 +#define MYBOT_DISPLAY_CS GPIO_NUM_34 +#define MYBOT_DISPLAY_RESET GPIO_NUM_NC +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_33 +#define MYBOT_DISPLAY_BACKLIGHT_INVERT 0 +#define MYBOT_DISPLAY_BRIGHTNESS_PERCENT 60 +#define MYBOT_DISPLAY_WIDTH 320 +#define MYBOT_DISPLAY_HEIGHT 172 +#define MYBOT_DISPLAY_OFFSET_X 0 +#define MYBOT_DISPLAY_OFFSET_Y 34 +#define MYBOT_DISPLAY_SWAP_XY true +#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 (20 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define MYBOT_DISPLAY_DATA_ENDIAN LCD_RGB_DATA_ENDIAN_BIG + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/labplus-mpython-v3/sdkconfig.defaults b/components/mybot_platform/boards/labplus-mpython-v3/sdkconfig.defaults new file mode 100644 index 0000000..dba9b50 --- /dev/null +++ b/components/mybot_platform/boards/labplus-mpython-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_QUAD=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_CODEC_ES8388_SUPPORT=y +CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n diff --git a/components/mybot_platform/boards/magiclick-2p5/README.md b/components/mybot_platform/boards/magiclick-2p5/README.md new file mode 100644 index 0000000..ad8ae42 --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p5/README.md @@ -0,0 +1,14 @@ +# Magiclick 2.5 + +板型 ID 为 `magiclick-2p5`。当前实现仅使用 Wi-Fi 通路。 + +- 显示:128×128,支持 GC9107 与 ST7735 两种 PCB 版本,共享紧凑 LVGL UI。 +- 音频:ES8311,I2C0 GPIO5/6,GPIO4 speaker PA,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO21 Boot、GPIO47 音量加、GPIO0 音量减。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO13 LEDC,默认 60%。 + +ML307/4G、电池、充电、LED、休眠和关机未实现。面板版本由 GPIO3 ADC 选择,显示方向、 +音频、PA、按键和电源仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/magiclick-2p5/board.c b/components/mybot_platform/boards/magiclick-2p5/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p5/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-2p5/board.cmake b/components/mybot_platform/boards/magiclick-2p5/board.cmake new file mode 100644 index 0000000..0fb073a --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p5/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 "compact/compact_128_panel.c") +set(MYBOT_GENERIC_TOUCH OFF) +set(MYBOT_GENERIC_EXTRA_SOURCES "${CMAKE_CURRENT_LIST_DIR}/magiclick_2p5_hardware.c") +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_adc esp_lcd_gc9a01) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/magiclick-2p5/board_config.h b/components/mybot_platform/boards/magiclick-2p5/board_config.h new file mode 100644 index 0000000..a5cf153 --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p5/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 "magiclick-2p5" +#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_DISPLAY_SPI_HOST SPI3_HOST +#define MYBOT_DISPLAY_MOSI GPIO_NUM_16 +#define MYBOT_DISPLAY_SCLK GPIO_NUM_15 +#define MYBOT_DISPLAY_DC GPIO_NUM_18 +#define MYBOT_DISPLAY_CS GPIO_NUM_14 +#define MYBOT_DISPLAY_RESET GPIO_NUM_17 +#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 false +#define MYBOT_DISPLAY_INVERT_COLOR false +#define MYBOT_DISPLAY_SPI_MODE 0 +#define MYBOT_DISPLAY_PIXEL_CLOCK_HZ (20 * 1000 * 1000) +#define MYBOT_DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB + +#define MYBOT_COMPACT_GC9107_OFFSET_X 0 +#define MYBOT_COMPACT_GC9107_OFFSET_Y 0 +#define MYBOT_COMPACT_ST7735_OFFSET_X 2 +#define MYBOT_COMPACT_ST7735_OFFSET_Y 3 +#define MYBOT_COMPACT_GC9107_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#define MYBOT_COMPACT_ST7735_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR + +#endif /* MYBOT_BOARD_CONFIG_H_ */ diff --git a/components/mybot_platform/boards/magiclick-2p5/magiclick_2p5_hardware.c b/components/mybot_platform/boards/magiclick-2p5/magiclick_2p5_hardware.c new file mode 100644 index 0000000..51d088b --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p5/magiclick_2p5_hardware.c @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "board_config.h" + +#include "esp_adc/adc_oneshot.h" +#include "esp_log.h" + +#define TAG "magiclick_2p5" +#define MYBOT_COMPACT_PANEL_GC9107 1 +#define MYBOT_COMPACT_PANEL_ST7735 2 + +int mybot_board_compact_panel_profile(void) { + adc_oneshot_unit_handle_t adc = NULL; + const adc_oneshot_unit_init_cfg_t unit_config = { + .unit_id = ADC_UNIT_1, + }; + if (adc_oneshot_new_unit(&unit_config, &adc) != ESP_OK) { + ESP_LOGW(TAG, "event=panel action=detect result=fallback reason=adc_init"); + return MYBOT_COMPACT_PANEL_ST7735; + } + const adc_oneshot_chan_cfg_t channel_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + if (adc_oneshot_config_channel(adc, ADC_CHANNEL_2, &channel_config) != ESP_OK) { + (void)adc_oneshot_del_unit(adc); + ESP_LOGW(TAG, "event=panel action=detect result=fallback reason=adc_channel"); + return MYBOT_COMPACT_PANEL_ST7735; + } + + int total = 0; + int raw = 0; + for (int i = 0; i < 10; ++i) { + if (adc_oneshot_read(adc, ADC_CHANNEL_2, &raw) != ESP_OK) { + (void)adc_oneshot_del_unit(adc); + return MYBOT_COMPACT_PANEL_ST7735; + } + total += raw; + } + (void)adc_oneshot_del_unit(adc); + + const int millivolts = (total / 10) * 3300 / 4095; + if (millivolts < 100) { + ESP_LOGI(TAG, "event=panel action=detect profile=gc9107 millivolts=%d", millivolts); + return MYBOT_COMPACT_PANEL_GC9107; + } + ESP_LOGI(TAG, "event=panel action=detect profile=st7735 millivolts=%d", millivolts); + return MYBOT_COMPACT_PANEL_ST7735; +} diff --git a/components/mybot_platform/boards/magiclick-2p5/sdkconfig.defaults b/components/mybot_platform/boards/magiclick-2p5/sdkconfig.defaults new file mode 100644 index 0000000..bc4a897 --- /dev/null +++ b/components/mybot_platform/boards/magiclick-2p5/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/minsi-k08-dual/README.md b/components/mybot_platform/boards/minsi-k08-dual/README.md new file mode 100644 index 0000000..6654390 --- /dev/null +++ b/components/mybot_platform/boards/minsi-k08-dual/README.md @@ -0,0 +1,14 @@ +# Minsi K08 Dual + +板型 ID 为 `minsi-k08-dual`。当前实现仅使用 Wi-Fi 通路。 + +- 显示:240×240 ST7789,SPI3 mode 3,40 MHz,X 偏移 80,共享 LVUI。 +- 音频:独立麦克风与扬声器 raw I2S,16 kHz 单声道 signed-16 PCM。 +- 输入:GPIO0 Boot、GPIO47 音量加、GPIO46 音量减。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO7 LEDC,默认 60%。 + +ML307/4G、电池、充电、LED、测试灯、休眠和关机未实现。GPIO18 同时被上游代码定义为 +麦克风 DIN 和测试灯,本 profile 不启用测试灯。显示方向、音频和电源仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/minsi-k08-dual/board.c b/components/mybot_platform/boards/minsi-k08-dual/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/minsi-k08-dual/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/minsi-k08-dual/board.cmake b/components/mybot_platform/boards/minsi-k08-dual/board.cmake new file mode 100644 index 0000000..ee77d8a --- /dev/null +++ b/components/mybot_platform/boards/minsi-k08-dual/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/minsi-k08-dual/board_config.h b/components/mybot_platform/boards/minsi-k08-dual/board_config.h new file mode 100644 index 0000000..9395b9e --- /dev/null +++ b/components/mybot_platform/boards/minsi-k08-dual/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 "minsi-k08-dual" +#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_10 +#define MYBOT_AUDIO_I2S_MIC_WS GPIO_NUM_9 +#define MYBOT_AUDIO_I2S_MIC_DIN GPIO_NUM_18 +#define MYBOT_AUDIO_I2S_SPK_BCLK GPIO_NUM_5 +#define MYBOT_AUDIO_I2S_SPK_WS GPIO_NUM_6 +#define MYBOT_AUDIO_I2S_SPK_DOUT GPIO_NUM_4 + +#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_47 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_46 +#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_8 +#define MYBOT_DISPLAY_DC GPIO_NUM_15 +#define MYBOT_DISPLAY_CS GPIO_NUM_NC +#define MYBOT_DISPLAY_RESET GPIO_NUM_16 +#define MYBOT_DISPLAY_BACKLIGHT_GPIO GPIO_NUM_7 +#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 80 +#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 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/minsi-k08-dual/sdkconfig.defaults b/components/mybot_platform/boards/minsi-k08-dual/sdkconfig.defaults new file mode 100644 index 0000000..214c8f7 --- /dev/null +++ b/components/mybot_platform/boards/minsi-k08-dual/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/zhengchen-cam/README.md b/components/mybot_platform/boards/zhengchen-cam/README.md new file mode 100644 index 0000000..96bdd8f --- /dev/null +++ b/components/mybot_platform/boards/zhengchen-cam/README.md @@ -0,0 +1,15 @@ +# 征辰 Camera + +板型 ID 为 `zhengchen-cam`。 + +- 显示:320×240 ST7789,SPI3 mode 0,80 MHz,固定横屏,共享 LVGL UI。 +- 音频:ES8311 输出与 ES7210 采集,16 kHz 单声道 signed-16 PCM。 +- 扩展器:PCA9557 `0x19` 控制 speaker PA。 +- 输入:GPIO0 Boot、GPIO3 音量加、GPIO46 音量减。 +- 存储:16 MB QIO Flash、80 MHz Octal PSRAM、双 OTA。 +- 背光:GPIO42 LEDC,低电平有效,默认 60%。 + +摄像头、竖屏切换、电池、温度、休眠和关机未实现。PCA9557 位映射、面板方向、双 codec +路由、PA 和音频仍需真机验证。 + +公共构建见[板卡索引](../README.md),验证状态见[验证矩阵](../../../../docs/VALIDATION.md)。 diff --git a/components/mybot_platform/boards/zhengchen-cam/board.c b/components/mybot_platform/boards/zhengchen-cam/board.c new file mode 100644 index 0000000..dca1106 --- /dev/null +++ b/components/mybot_platform/boards/zhengchen-cam/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/zhengchen-cam/board.cmake b/components/mybot_platform/boards/zhengchen-cam/board.cmake new file mode 100644 index 0000000..d679902 --- /dev/null +++ b/components/mybot_platform/boards/zhengchen-cam/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 "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 + CONFIG_CODEC_ES7210_SUPPORT +) +include("${CMAKE_CURRENT_LIST_DIR}/../common/generic_profile.cmake") diff --git a/components/mybot_platform/boards/zhengchen-cam/board_config.h b/components/mybot_platform/boards/zhengchen-cam/board_config.h new file mode 100644 index 0000000..7f8f7f2 --- /dev/null +++ b/components/mybot_platform/boards/zhengchen-cam/board_config.h @@ -0,0 +1,64 @@ +/* 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 "zhengchen-cam" +#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_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_AMOLED175_I2C_PORT MYBOT_GENERIC_I2C_PORT +#define MYBOT_AMOLED175_ES8311_ADDRESS 0x30 +#define MYBOT_AMOLED175_ES7210_ADDRESS 0x80 + +#define MYBOT_GENERIC_PCA9557_ADDRESS 0x19 +#define MYBOT_GENERIC_PCA9557_OUTPUT_MASK 0x07 +#define MYBOT_GENERIC_PCA9557_HIGH_MASK 0x02 +#define MYBOT_GENERIC_PCA9557_PA_MASK 0x02 + +#define MYBOT_BOOT_BUTTON_GPIO GPIO_NUM_0 +#define MYBOT_VOLUME_UP_BUTTON_GPIO GPIO_NUM_3 +#define MYBOT_VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_46 +#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_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 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/zhengchen-cam/sdkconfig.defaults b/components/mybot_platform/boards/zhengchen-cam/sdkconfig.defaults new file mode 100644 index 0000000..597a395 --- /dev/null +++ b/components/mybot_platform/boards/zhengchen-cam/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/src/drivers/audio/es8311_codec_audio.c b/components/mybot_platform/src/drivers/audio/es8311_codec_audio.c index 6b0b352..74286e9 100644 --- a/components/mybot_platform/src/drivers/audio/es8311_codec_audio.c +++ b/components/mybot_platform/src/drivers/audio/es8311_codec_audio.c @@ -10,6 +10,8 @@ #include "driver/i2s_std.h" #if MYBOT_GENERIC_AUDIO_KIND == 4 #include "audio/es8374_codec.h" +#elif MYBOT_GENERIC_AUDIO_KIND == 5 +#include "es8388_codec.h" #else #include "es8311_codec.h" #endif @@ -34,7 +36,27 @@ #define AUDIO_CAPTURE_CHANNEL 0 #define AUDIO_VOLUME_NVS_KEY "output_volume" #define AUDIO_VOLUME_NVS_NAMESPACE "audio" + +#ifndef MYBOT_AUDIO_CODEC_I2C_PORT +#define MYBOT_AUDIO_CODEC_I2C_PORT MYBOT_ES8311_I2C_PORT +#endif +#ifndef MYBOT_AUDIO_CODEC_I2C_ADDRESS +#define MYBOT_AUDIO_CODEC_I2C_ADDRESS MYBOT_ES8311_I2C_ADDRESS +#endif +#ifndef MYBOT_AUDIO_CODEC_INPUT_GAIN_DB +#define MYBOT_AUDIO_CODEC_INPUT_GAIN_DB MYBOT_ES8311_INPUT_GAIN_DB +#endif +#ifndef MYBOT_AUDIO_CODEC_HARDWARE_VOLUME_SCALE_PERCENT +#define MYBOT_AUDIO_CODEC_HARDWARE_VOLUME_SCALE_PERCENT MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT +#endif + +#if MYBOT_GENERIC_AUDIO_KIND == 5 +#define TAG "es8388_audio" +#define MYBOT_AUDIO_CODEC_NAME "es8388" +#else #define TAG "es8311_audio" +#define MYBOT_AUDIO_CODEC_NAME "es8311" +#endif #if MYBOT_AUDIO_SAMPLE_RATE != 16000 #error "Onboard codec audio must expose 16 kHz PCM to mybot" @@ -45,9 +67,9 @@ #error "MYBOT_AUDIO_DEFAULT_VOLUME must be between 0 and 100" #endif -#if MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT < 0 || \ - MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT > 100 -#error "MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT must be between 0 and 100" +#if MYBOT_AUDIO_CODEC_HARDWARE_VOLUME_SCALE_PERCENT < 0 || \ + MYBOT_AUDIO_CODEC_HARDWARE_VOLUME_SCALE_PERCENT > 100 +#error "MYBOT_AUDIO_CODEC_HARDWARE_VOLUME_SCALE_PERCENT must be between 0 and 100" #endif typedef enum { @@ -169,7 +191,7 @@ static int validate_format(int rate, int channels, int bits) { } static int hardware_volume_from_logical(int volume) { - return (volume * MYBOT_ES8311_HARDWARE_VOLUME_SCALE_PERCENT + MYBOT_AUDIO_VOLUME_MAX / 2) / + return (volume * MYBOT_AUDIO_CODEC_HARDWARE_VOLUME_SCALE_PERCENT + MYBOT_AUDIO_VOLUME_MAX / 2) / MYBOT_AUDIO_VOLUME_MAX; } @@ -367,8 +389,8 @@ static int initialize_codec_locked(void) { } audio_codec_i2c_cfg_t i2c_config = { - .port = MYBOT_ES8311_I2C_PORT, - .addr = MYBOT_ES8311_I2C_ADDRESS, + .port = MYBOT_AUDIO_CODEC_I2C_PORT, + .addr = MYBOT_AUDIO_CODEC_I2C_ADDRESS, .bus_handle = i2c_bus, }; s_audio.ctrl_if = audio_codec_new_i2c_ctrl(&i2c_config); @@ -385,6 +407,20 @@ static int initialize_codec_locked(void) { .pa_pin = GPIO_NUM_NC, .pa_reverted = false, }; +#elif MYBOT_GENERIC_AUDIO_KIND == 5 + es8388_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 = true, + .pa_pin = GPIO_NUM_NC, + .pa_reverted = false, + .hw_gain = + { + .pa_voltage = 5.0f, + .codec_dac_voltage = 3.3f, + }, + }; #else es8311_codec_cfg_t codec_config = { .ctrl_if = s_audio.ctrl_if, @@ -405,6 +441,8 @@ static int initialize_codec_locked(void) { #endif #if MYBOT_GENERIC_AUDIO_KIND == 4 s_audio.codec_if = es8374_codec_new(&codec_config); +#elif MYBOT_GENERIC_AUDIO_KIND == 5 + s_audio.codec_if = es8388_codec_new(&codec_config); #else s_audio.codec_if = es8311_codec_new(&codec_config); #endif @@ -573,19 +611,33 @@ static int open_codec_locked(void) { .mclk_multiple = I2S_MCLK_MULTIPLE_256, }; if (esp_codec_dev_open(s_audio.device, &format) != ESP_CODEC_DEV_OK) { - ESP_LOGE(TAG, "event=audio_device component=es8311 action=open result=error"); + ESP_LOGE(TAG, "event=audio_device component=%s action=open result=error", + MYBOT_AUDIO_CODEC_NAME); (void)esp_codec_dev_close(s_audio.device); return -1; } s_audio.codec_open = true; int hardware_volume = hardware_volume_from_logical(s_output_volume); - if (esp_codec_dev_set_in_gain(s_audio.device, MYBOT_ES8311_INPUT_GAIN_DB) != ESP_CODEC_DEV_OK || + if (esp_codec_dev_set_in_gain(s_audio.device, MYBOT_AUDIO_CODEC_INPUT_GAIN_DB) != + ESP_CODEC_DEV_OK || esp_codec_dev_set_out_vol(s_audio.device, hardware_volume) != ESP_CODEC_DEV_OK) { - ESP_LOGE(TAG, "event=audio_device component=es8311 action=configure result=error"); + ESP_LOGE(TAG, "event=audio_device component=%s action=configure result=error", + MYBOT_AUDIO_CODEC_NAME); (void)close_codec_locked(); return -1; } +#if MYBOT_GENERIC_AUDIO_KIND == 5 + uint8_t analog_volume = 30; + for (uint8_t reg = 0x2e; reg <= 0x31; ++reg) { + if (s_audio.ctrl_if->write_reg(s_audio.ctrl_if, reg, 1, &analog_volume, 1) != + ESP_CODEC_DEV_OK) { + ESP_LOGE(TAG, "event=audio_device component=es8388 action=analog_volume result=error"); + (void)close_codec_locked(); + return -1; + } + } +#endif return 0; } diff --git a/components/mybot_platform/src/drivers/display/panels/compact/compact_128_panel.c b/components/mybot_platform/src/drivers/display/panels/compact/compact_128_panel.c new file mode 100644 index 0000000..c701e9d --- /dev/null +++ b/components/mybot_platform/src/drivers/display/panels/compact/compact_128_panel.c @@ -0,0 +1,208 @@ +/* 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_gc9a01.h" +#include "esp_lcd_io_spi.h" +#include "esp_lcd_panel_vendor.h" +#include "esp_log.h" + +#include +#include + +#define TAG "compact_128_panel" +#define MYBOT_COMPACT_PANEL_GC9107 1 +#define MYBOT_COMPACT_PANEL_ST7735 2 + +#ifndef MYBOT_COMPACT_GC9107_OFFSET_X +#define MYBOT_COMPACT_GC9107_OFFSET_X 0 +#endif +#ifndef MYBOT_COMPACT_GC9107_OFFSET_Y +#define MYBOT_COMPACT_GC9107_OFFSET_Y 0 +#endif +#ifndef MYBOT_COMPACT_ST7735_OFFSET_X +#define MYBOT_COMPACT_ST7735_OFFSET_X 2 +#endif +#ifndef MYBOT_COMPACT_ST7735_OFFSET_Y +#define MYBOT_COMPACT_ST7735_OFFSET_Y 3 +#endif +#ifndef MYBOT_COMPACT_GC9107_RGB_ORDER +#define MYBOT_COMPACT_GC9107_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB +#endif +#ifndef MYBOT_COMPACT_ST7735_RGB_ORDER +#define MYBOT_COMPACT_ST7735_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR +#endif + +int mybot_board_compact_panel_profile(void); + +static const gc9a01_lcd_init_cmd_t s_gc9107_init[] = { + {0xfe, (uint8_t[]){0x00}, 0, 0}, + {0xef, (uint8_t[]){0x00}, 0, 0}, + {0xb0, (uint8_t[]){0xc0}, 1, 0}, + {0xb1, (uint8_t[]){0x80}, 1, 0}, + {0xb2, (uint8_t[]){0x27}, 1, 0}, + {0xb3, (uint8_t[]){0x13}, 1, 0}, + {0xb6, (uint8_t[]){0x19}, 1, 0}, + {0xb7, (uint8_t[]){0x05}, 1, 0}, + {0xac, (uint8_t[]){0xc8}, 1, 0}, + {0xab, (uint8_t[]){0x0f}, 1, 0}, + {0x3a, (uint8_t[]){0x05}, 1, 0}, + {0xb4, (uint8_t[]){0x04}, 1, 0}, + {0xa8, (uint8_t[]){0x08}, 1, 0}, + {0xb8, (uint8_t[]){0x08}, 1, 0}, + {0xea, (uint8_t[]){0x02}, 1, 0}, + {0xe8, (uint8_t[]){0x2a}, 1, 0}, + {0xe9, (uint8_t[]){0x47}, 1, 0}, + {0xe7, (uint8_t[]){0x5f}, 1, 0}, + {0xc6, (uint8_t[]){0x21}, 1, 0}, + {0xc7, (uint8_t[]){0x15}, 1, 0}, + {0xf0, + (uint8_t[]){0x1d, 0x38, 0x09, 0x4d, 0x92, 0x2f, 0x35, 0x52, 0x1e, 0x0c, 0x04, 0x12, 0x14, + 0x1f}, + 14, 0}, + {0xf1, + (uint8_t[]){0x16, 0x40, 0x1c, 0x54, 0xa9, 0x2d, 0x2e, 0x56, 0x10, 0x0d, 0x0c, 0x1a, 0x14, + 0x1e}, + 14, 0}, + {0xf4, (uint8_t[]){0x00, 0x00, 0xff}, 3, 0}, + {0xba, (uint8_t[]){0xff, 0xff}, 2, 0}, +}; + +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 int profile = mybot_board_compact_panel_profile(); + if (profile != MYBOT_COMPACT_PANEL_GC9107 && profile != MYBOT_COMPACT_PANEL_ST7735) { + result = ESP_ERR_INVALID_STATE; + goto fail; + } + + 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; + } + + const bool gc9107 = profile == MYBOT_COMPACT_PANEL_GC9107; + const gc9a01_vendor_config_t vendor_config = { + .init_cmds = s_gc9107_init, + .init_cmds_size = sizeof(s_gc9107_init) / sizeof(s_gc9107_init[0]), + }; + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = MYBOT_DISPLAY_RESET, + .rgb_ele_order = gc9107 ? MYBOT_COMPACT_GC9107_RGB_ORDER : MYBOT_COMPACT_ST7735_RGB_ORDER, + .bits_per_pixel = 16, + }; + if (gc9107) { + panel_config.vendor_config = (void *)&vendor_config; + result = esp_lcd_new_panel_gc9a01(panel->io, &panel_config, &panel->panel); + } else { + result = esp_lcd_new_panel_st7789(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_set_gap( + panel->panel, gc9107 ? MYBOT_COMPACT_GC9107_OFFSET_X : MYBOT_COMPACT_ST7735_OFFSET_X, + gc9107 ? MYBOT_COMPACT_GC9107_OFFSET_Y : MYBOT_COMPACT_ST7735_OFFSET_Y); + } + if (result == ESP_OK) { + result = esp_lcd_panel_invert_color(panel->panel, !gc9107); + } + if (result == ESP_OK) { + result = esp_lcd_panel_swap_xy(panel->panel, false); + } + if (result == ESP_OK) { + result = esp_lcd_panel_mirror(panel->panel, !gc9107, !gc9107); + } + 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; + ESP_LOGI(TAG, "event=panel action=initialize profile=%s result=ok", + gc9107 ? "gc9107" : "st7735"); + 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/st7789/st7789_panel.c b/components/mybot_platform/src/drivers/display/panels/st7789/st7789_panel.c index db1ebe9..f9dd8ee 100644 --- a/components/mybot_platform/src/drivers/display/panels/st7789/st7789_panel.c +++ b/components/mybot_platform/src/drivers/display/panels/st7789/st7789_panel.c @@ -94,11 +94,14 @@ int mybot_display_panel_open(mybot_display_panel_t *panel) { goto fail; } - const esp_lcd_panel_dev_config_t panel_config = { + 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, }; +#ifdef MYBOT_DISPLAY_DATA_ENDIAN + panel_config.data_endian = MYBOT_DISPLAY_DATA_ENDIAN; +#endif result = esp_lcd_new_panel_st7789(panel->io, &panel_config, &panel->panel); if (result == ESP_OK) { result = esp_lcd_panel_reset(panel->panel); diff --git a/components/mybot_platform/src/drivers/io_expander/aw9523b.c b/components/mybot_platform/src/drivers/io_expander/aw9523b.c new file mode 100644 index 0000000..475a808 --- /dev/null +++ b/components/mybot_platform/src/drivers/io_expander/aw9523b.c @@ -0,0 +1,85 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "io_expander/aw9523b.h" + +#include "esp_err.h" +#include "esp_log.h" + +#define TAG "aw9523b" +#define AW9523B_I2C_TIMEOUT_MS 100 +#define AW9523B_REG_OUTPUT 0x02 +#define AW9523B_REG_CONFIG 0x04 + +static int write_pair(mybot_aw9523b_t *expander, uint8_t reg, uint16_t value) { + const uint8_t command[] = {reg, (uint8_t)(value & 0xff), (uint8_t)(value >> 8)}; + esp_err_t result = + i2c_master_transmit(expander->device, command, sizeof(command), AW9523B_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_aw9523b_init(mybot_aw9523b_t *expander, i2c_master_bus_handle_t bus, uint8_t address, + uint32_t speed_hz) { + if (!expander || !bus || expander->device) { + return -1; + } + *expander = (mybot_aw9523b_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_aw9523b_deinit(mybot_aw9523b_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_aw9523b_configure_outputs(mybot_aw9523b_t *expander, uint16_t output_mask, + uint16_t high_mask) { + if (!expander || !expander->device) { + return -1; + } + const uint16_t output_state = high_mask & output_mask; + if (write_pair(expander, AW9523B_REG_OUTPUT, output_state) < 0 || + write_pair(expander, AW9523B_REG_CONFIG, (uint16_t)~output_mask) < 0) { + return -1; + } + expander->output_state = output_state; + return 0; +} + +int mybot_aw9523b_set_levels(mybot_aw9523b_t *expander, uint16_t mask, uint16_t high_mask) { + if (!expander || !expander->device) { + return -1; + } + const uint16_t output_state = (uint16_t)((expander->output_state & ~mask) | (high_mask & mask)); + if (write_pair(expander, AW9523B_REG_OUTPUT, output_state) < 0) { + return -1; + } + expander->output_state = output_state; + return 0; +} diff --git a/components/mybot_platform/src/internal/io_expander/aw9523b.h b/components/mybot_platform/src/internal/io_expander/aw9523b.h new file mode 100644 index 0000000..c259cd0 --- /dev/null +++ b/components/mybot_platform/src/internal/io_expander/aw9523b.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#ifndef MYBOT_AW9523B_H_ +#define MYBOT_AW9523B_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; + uint16_t output_state; +} mybot_aw9523b_t; + +int mybot_aw9523b_init(mybot_aw9523b_t *expander, i2c_master_bus_handle_t bus, uint8_t address, + uint32_t speed_hz); +int mybot_aw9523b_deinit(mybot_aw9523b_t *expander); +int mybot_aw9523b_configure_outputs(mybot_aw9523b_t *expander, uint16_t output_mask, + uint16_t high_mask); +int mybot_aw9523b_set_levels(mybot_aw9523b_t *expander, uint16_t mask, uint16_t high_mask); + +#ifdef __cplusplus +} +#endif + +#endif /* MYBOT_AW9523B_H_ */ diff --git a/docs/BOARD_PORTING.md b/docs/BOARD_PORTING.md index 366f41f..c275f56 100644 --- a/docs/BOARD_PORTING.md +++ b/docs/BOARD_PORTING.md @@ -191,9 +191,12 @@ fixed N16R8 profile has been validated with ESP-IDF v5.5.2; real-device validati `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. +The generic path supports ES8311, ES8311+ES7210, ES8388, 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. `MYBOT_GENERIC_EXTRA_INIT` optionally runs a board callback on the +shared audio I2C bus before display registration; that callback owns bounded timeouts and device +cleanup. Ledong V2 uses it to send the STM8 panel-reset command, while Mpython V3 uses the same +display/audio family without that external reset. `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 diff --git a/docs/BOARD_PORTING.zh-CN.md b/docs/BOARD_PORTING.zh-CN.md index cfc0599..aaaff25 100644 --- a/docs/BOARD_PORTING.zh-CN.md +++ b/docs/BOARD_PORTING.zh-CN.md @@ -120,8 +120,10 @@ RTSA 包前,SDK 构建封装会拒绝这些配置。[CI 工作流](../.github/ `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。会改变启动顺序的板级 -电源或扩展器逻辑不应塞入公共路径。 +ES8311+ES7210、ES8388、ES8374 和原始 I2S;原始采集可使用标准 I2S 或 PDM。会改变启动顺序的 +板级电源或扩展器逻辑不应塞入公共路径。`MYBOT_GENERIC_EXTRA_INIT` 可在显示注册前对共享 +音频 I2C 总线执行一次板级回调,回调必须使用有界超时并清理设备句柄。乐动掌控 V2 用它发送 +STM8 面板复位命令;掌控板 V3 使用同系列显示与音频,但没有该外部复位步骤。 `M5Stack StopWatch` 在显示和 codec 启动前初始化 M5PM1 与 M5IOE1:打开 LCD/codec 电源, 复位 LCD/触摸,控制扬声器 PA,并默认关闭振动电机。共享圆屏布局把控件收窄到中央安全 diff --git a/docs/PLATFORM_UI.md b/docs/PLATFORM_UI.md index e285613..42b6ac8 100644 --- a/docs/PLATFORM_UI.md +++ b/docs/PLATFORM_UI.md @@ -25,13 +25,15 @@ paths and touch/key ownership retain their existing board behavior. | `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 | +| `zhengchen-cam` | ST7789, 320 × 240 | shared ST7789 adapter | Boot and volume buttons | +| `labplus-ledong-v2`, `labplus-mpython-v3` | ST7789-compatible, 320 × 172 | shared ST7789 adapter | Boot | | `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 | +| `esp32-s3-touch-amoled-1.32`, `esp32-s3-touch-amoled-1.43c`, `esp32-s3-touch-amoled-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 | @@ -50,6 +52,9 @@ paths and touch/key ownership retain their existing board behavior. | `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 | +| `minsi-k08-dual` | ST7789, 240 × 240 | shared ST7789 adapter | Boot and volume buttons | +| `magiclick-2p5` | GC9107 or ST7735, 128 × 128 | shared compact panel adapter | three buttons | +| `atk-dnesp32s3-box3` | ST7789, 320 × 240 | shared ST7789 adapter | 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 @@ -67,9 +72,17 @@ Chinese and English 2.16 build support has been validated; real-device testing r 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%. +Zhengchen Cam uses ST7789 SPI3 mode 0 at 80 MHz with a fixed 320 × 240 landscape layout and an +active-low GPIO42 backlight. Both LabPlus profiles use a 320 × 172 big-endian panel on SPI2 at +20 MHz; Mpython V3 drives CS on GPIO34 and the backlight on GPIO33, while Ledong V2 uses no CS +GPIO, backlight GPIO34, and an STM8-held panel reset. 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. +Minsi K08 uses ST7789 SPI3 mode 3 at 40 MHz, an 80-pixel X offset, swapped axes and a GPIO7 LEDC +backlight. Magiclick 2.5 selects GC9107 or ST7735 from GPIO3 ADC and uses a GPIO13 LEDC +backlight. ATK Box 3 uses ST7789 on SPI2 at 60 MHz with big-endian pixel data and AW9523B +bit 8 as a binary backlight enable. ## CoreS3 pixel-glasses view diff --git a/docs/PLATFORM_UI.zh-CN.md b/docs/PLATFORM_UI.zh-CN.md index 122feb8..10ba4a7 100644 --- a/docs/PLATFORM_UI.zh-CN.md +++ b/docs/PLATFORM_UI.zh-CN.md @@ -24,13 +24,15 @@ | `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 与音量按键 | +| `zhengchen-cam` | ST7789,320 × 240 | 通用 ST7789 | Boot 与音量按键 | +| `labplus-ledong-v2`、`labplus-mpython-v3` | ST7789 兼容,320 × 172 | 通用 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 | +| `esp32-s3-touch-amoled-1.32`、`esp32-s3-touch-amoled-1.43c`、`esp32-s3-touch-amoled-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;未实现触摸 | @@ -49,6 +51,9 @@ | `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 | +| `minsi-k08-dual` | ST7789,240 × 240 | 通用 ST7789 | Boot 与音量键 | +| `magiclick-2p5` | GC9107 或 ST7735,128 × 128 | 通用紧凑面板适配器 | 三个按键 | +| `atk-dnesp32s3-box3` | ST7789,320 × 240 | 通用 ST7789 | Boot | AtomEchoS3R、DOIT S3 AI Box、Bread Compact Wi-Fi、HU-087 与 ReSpeaker Flex 没有 LCD, 不编入 LVGL。ML307、Wi-Fi 及音视频协议与显示后端 @@ -65,9 +70,15 @@ 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%)。 +征辰 Camera 使用 ST7789 SPI3 mode 0 / 80 MHz、固定 320 × 240 横屏与低电平有效的 GPIO42 +背光。两款 LabPlus 使用 SPI2 / 20 MHz 的 320 × 172 大端面板;掌控板 V3 的 CS 为 GPIO34、 +背光为 GPIO33,乐动掌控 V2 不使用 CS 引脚、背光为 GPIO34,并通过 STM8 保持面板复位时序。 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 背光。 +Minsi K08 使用 ST7789 SPI3 mode 3 / 40 MHz、X 偏移 80、交换 X/Y 与 GPIO7 LEDC 背光。 +Magiclick 2.5 通过 GPIO3 ADC 选择 GC9107 或 ST7735,并使用 GPIO13 LEDC 背光。 +ATK Box 3 使用 SPI2 / 60 MHz 的 ST7789 与大端像素数据,并由 AW9523B bit 8 二值开关背光。 ## CoreS3像素眼镜视图 diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index 4d6546e..dba7f14 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -77,6 +77,13 @@ unsupported areas. Board READMEs describe hardware and operation only; the | `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. | +| `minsi-k08-dual` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Physical Flash/PSRAM, ST7789 orientation, raw audio slots and three keys require device validation; Wi-Fi-only subset has no ML307, battery, LED, lamp, sleep or shutdown. | +| `kevin-box-2` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | Headless subset; AXP2101, ES8311/ES7210 routing and keys require validation. SSD1306 UI, ML307, LED, battery, sleep and shutdown are not implemented. | +| `magiclick-2p5` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | GC9107/ST7735 ADC selection, ES8311, PA, keys and power require validation; Wi-Fi-only subset has no battery, LED, sleep or shutdown. | +| `atk-dnesp32s3-box3` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | AW9523B power/backlight/PA sequencing, ST7789 endian/orientation and dual codec require validation; K1/K2, camera, battery, sleep and shutdown are not implemented. | +| `zhengchen-cam` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | PCA9557 PA routing, 320×240 ST7789 orientation, ES8311/ES7210 slots and buttons require device validation; camera, portrait mode, battery, temperature, sleep and shutdown are not implemented. | +| `labplus-ledong-v2` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | STM8 display reset, 320×172 panel endian/orientation, ES8388, backlight and Boot input require device validation; touch and board peripherals are not implemented. | +| `labplus-mpython-v3` | CI: zh-CN, en-US; local merged build | Not completed / 未完成 | 320×172 panel endian/orientation, GPIO34 CS, GPIO33 backlight, ES8388 and Boot input require device validation; touch and board peripherals are not implemented. | ## Global boundaries / 全局边界 diff --git a/tests/aw9523b/mocks/driver/i2c_master.h b/tests/aw9523b/mocks/driver/i2c_master.h new file mode 100644 index 0000000..83a6150 --- /dev/null +++ b/tests/aw9523b/mocks/driver/i2c_master.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef AW9523B_I2C_MASTER_MOCK_H_ +#define AW9523B_I2C_MASTER_MOCK_H_ + +#include +#include + +typedef int esp_err_t; +#define ESP_OK 0 +#define ESP_FAIL -1 +#define I2C_ADDR_BIT_LEN_7 0 + +typedef void *i2c_master_bus_handle_t; +typedef void *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); +const char *esp_err_to_name(esp_err_t error); + +#endif /* AW9523B_I2C_MASTER_MOCK_H_ */ diff --git a/tests/aw9523b/mocks/esp_err.h b/tests/aw9523b/mocks/esp_err.h new file mode 100644 index 0000000..bc3b792 --- /dev/null +++ b/tests/aw9523b/mocks/esp_err.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "driver/i2c_master.h" diff --git a/tests/aw9523b/mocks/esp_log.h b/tests/aw9523b/mocks/esp_log.h new file mode 100644 index 0000000..e8a566b --- /dev/null +++ b/tests/aw9523b/mocks/esp_log.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#define ESP_LOGE(...) ((void)0) diff --git a/tests/aw9523b/run.sh b/tests/aw9523b/run.sh new file mode 100644 index 0000000..76c06bf --- /dev/null +++ b/tests/aw9523b/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/aw9523b" +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_aw9523b.c" \ + "$repo_root/components/mybot_platform/src/drivers/io_expander/aw9523b.c" \ + -o "$test_build_dir/test_aw9523b" + +if command -v timeout >/dev/null 2>&1; then + timeout 20 "$test_build_dir/test_aw9523b" +else + "$test_build_dir/test_aw9523b" +fi diff --git a/tests/aw9523b/test_aw9523b.c b/tests/aw9523b/test_aw9523b.c new file mode 100644 index 0000000..be89ddd --- /dev/null +++ b/tests/aw9523b/test_aw9523b.c @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: MIT */ +#include "io_expander/aw9523b.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 uint16_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 == 0x59 && 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 && g_device.open); + CHECK(data && size == 3 && timeout_ms == 100); + if (g_fail_write) { + g_fail_write = false; + return ESP_FAIL; + } + g_register = data[0]; + g_value = (uint16_t)data[1] | ((uint16_t)data[2] << 8); + ++g_writes; + return ESP_OK; +} + +int main(void) { + mybot_aw9523b_t expander = {0}; + CHECK(mybot_aw9523b_init(NULL, &g_bus, 0x59, 400000) < 0); + CHECK(mybot_aw9523b_init(&expander, NULL, 0x59, 400000) < 0); + g_fail_add = true; + CHECK(mybot_aw9523b_init(&expander, &g_bus, 0x59, 400000) < 0); + CHECK(!expander.device); + + CHECK(mybot_aw9523b_init(&expander, &g_bus, 0x59, 400000) == 0); + CHECK(expander.device == &g_device && expander.output_state == 0); + CHECK(mybot_aw9523b_init(&expander, &g_bus, 0x59, 400000) < 0); + + CHECK(mybot_aw9523b_configure_outputs(&expander, 0xfffc, 0x7830) == 0); + CHECK(g_register == 0x04 && g_value == 0x0003); + CHECK(g_writes == 2 && expander.output_state == 0x7830); + CHECK(mybot_aw9523b_set_levels(&expander, 0x0100, 0x0100) == 0); + CHECK(g_register == 0x02 && g_value == 0x7930); + CHECK(mybot_aw9523b_set_levels(&expander, 0x0120, 0) == 0); + CHECK(g_value == 0x7810 && expander.output_state == 0x7810); + + g_fail_write = true; + CHECK(mybot_aw9523b_set_levels(&expander, 0x0001, 0x0001) < 0); + CHECK(expander.output_state == 0x7810); + CHECK(mybot_aw9523b_set_levels(&expander, 0x0001, 0x0001) == 0); + CHECK(expander.output_state == 0x7811); + + g_fail_remove = true; + CHECK(mybot_aw9523b_deinit(&expander) < 0); + CHECK(expander.device == &g_device); + CHECK(mybot_aw9523b_deinit(&expander) == 0); + CHECK(!expander.device && !expander.bus && expander.output_state == 0); + CHECK(mybot_aw9523b_deinit(&expander) == 0); + puts("AW9523B masked update host tests passed"); + return 0; +} diff --git a/tests/compact_panel/run.sh b/tests/compact_panel/run.sh new file mode 100644 index 0000000..be0caab --- /dev/null +++ b/tests/compact_panel/run.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# SPDX-License-Identifier: MIT +set -eu + +repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) +test_dir="$repo_root/tests/compact_panel" +mock_dir="$repo_root/tests/st7789_lifecycle/mocks" +board_dir="$repo_root/components/mybot_platform/boards/magiclick-2p5" +test_build_dir=$(mktemp -d) +trap 'rm -r -- "$test_build_dir"' EXIT HUP INT TERM + +"${CC:-cc}" -std=c11 -Wall -Wextra -Werror \ + -I"$mock_dir" -I"$board_dir" \ + -I"$repo_root/components/mybot_platform/src/internal" \ + -c "$repo_root/components/mybot_platform/src/drivers/display/panels/compact/compact_128_panel.c" \ + -o "$test_build_dir/compact_128_panel.o" +"${CXX:-c++}" -std=c++17 -Wall -Wextra -Werror \ + -I"$mock_dir" -I"$board_dir" \ + -I"$repo_root/components/mybot_platform/src/internal" \ + "$test_dir/test_compact_panel.cc" "$test_build_dir/compact_128_panel.o" \ + -o "$test_build_dir/test_compact_panel" + +if command -v timeout >/dev/null 2>&1; then + timeout 20 "$test_build_dir/test_compact_panel" +else + "$test_build_dir/test_compact_panel" +fi diff --git a/tests/compact_panel/test_compact_panel.cc b/tests/compact_panel/test_compact_panel.cc new file mode 100644 index 0000000..cb3a381 --- /dev/null +++ b/tests/compact_panel/test_compact_panel.cc @@ -0,0 +1,204 @@ +/* SPDX-License-Identifier: MIT */ +#include "board_config.h" +#include "display/display_panel.h" + +#include +#include + +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \ + std::exit(1); \ + } \ + } while (0) + +struct host_panel_io { + bool open = false; +}; +struct host_panel { + bool open = false; + bool gc9107 = false; + int gap_x = -1; + int gap_y = -1; + bool invert = false; + bool mirror_x = false; + bool mirror_y = false; +}; + +static host_panel_io io; +static host_panel panel; +static bool spi_open; +static int profile; +static int backlight; +static unsigned int panel_deletes; +static unsigned int io_deletes; +static unsigned int spi_frees; +static bool fail_backlight; +static bool fail_panel_new; + +extern "C" { +const char *esp_err_to_name(esp_err_t error) { + return error == ESP_OK ? "ESP_OK" : "ESP_FAIL"; +} + +int mybot_board_compact_panel_profile(void) { + return profile; +} + +int mybot_board_set_display_backlight(unsigned int percent) { + CHECK(percent <= 100); + if (fail_backlight) { + fail_backlight = false; + return -1; + } + backlight = static_cast(percent); + return 0; +} + +esp_err_t spi_bus_initialize(int host, const spi_bus_config_t *config, int dma) { + CHECK(host == MYBOT_DISPLAY_SPI_HOST && dma == SPI_DMA_CH_AUTO && !spi_open); + CHECK(config->mosi_io_num == MYBOT_DISPLAY_MOSI); + CHECK(config->sclk_io_num == MYBOT_DISPLAY_SCLK); + CHECK(config->miso_io_num == GPIO_NUM_NC); + spi_open = true; + return ESP_OK; +} + +esp_err_t spi_bus_free(int host) { + CHECK(host == MYBOT_DISPLAY_SPI_HOST && spi_open && !io.open && !panel.open); + spi_open = false; + ++spi_frees; + return ESP_OK; +} + +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_DISPLAY_SPI_HOST && spi_open && !io.open); + CHECK(config->cs_gpio_num == MYBOT_DISPLAY_CS); + CHECK(config->dc_gpio_num == MYBOT_DISPLAY_DC); + CHECK(config->pclk_hz == MYBOT_DISPLAY_PIXEL_CLOCK_HZ); + io = {}; + io.open = true; + *out = &io; + return ESP_OK; +} + +esp_err_t esp_lcd_new_panel_gc9a01(esp_lcd_panel_io_handle_t handle, + const esp_lcd_panel_dev_config_t *config, + esp_lcd_panel_handle_t *out) { + CHECK(handle == &io && io.open && !panel.open); + CHECK(config->vendor_config); + CHECK(config->rgb_ele_order == MYBOT_COMPACT_GC9107_RGB_ORDER); + if (fail_panel_new) { + fail_panel_new = false; + return ESP_FAIL; + } + panel = {}; + panel.open = true; + panel.gc9107 = true; + *out = &panel; + return ESP_OK; +} + +esp_err_t esp_lcd_new_panel_st7789(esp_lcd_panel_io_handle_t handle, + const esp_lcd_panel_dev_config_t *config, + esp_lcd_panel_handle_t *out) { + CHECK(handle == &io && io.open && !panel.open); + CHECK(config->rgb_ele_order == MYBOT_COMPACT_ST7735_RGB_ORDER); + if (fail_panel_new) { + fail_panel_new = false; + return ESP_FAIL; + } + panel = {}; + panel.open = true; + *out = &panel; + return ESP_OK; +} + +esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t handle) { + CHECK(handle == &panel && panel.open); + return ESP_OK; +} +esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t handle) { + CHECK(handle == &panel && panel.open); + return ESP_OK; +} +esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t handle, int x, int y) { + CHECK(handle == &panel && panel.open); + panel.gap_x = x; + panel.gap_y = y; + return ESP_OK; +} +esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t handle, bool invert) { + CHECK(handle == &panel && panel.open); + panel.invert = invert; + return ESP_OK; +} +esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t handle, bool swap) { + CHECK(handle == &panel && panel.open && !swap); + return ESP_OK; +} +esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t handle, bool x, bool y) { + CHECK(handle == &panel && panel.open); + panel.mirror_x = x; + panel.mirror_y = y; + return ESP_OK; +} +esp_err_t esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t handle, bool on) { + CHECK(handle == &panel && panel.open); + CHECK(on == false || on == true); + return ESP_OK; +} +esp_err_t esp_lcd_panel_del(esp_lcd_panel_handle_t handle) { + CHECK(handle == &panel && panel.open && io.open && backlight == 0); + panel = {}; + ++panel_deletes; + return ESP_OK; +} +esp_err_t esp_lcd_panel_io_del(esp_lcd_panel_io_handle_t handle) { + CHECK(handle == &io && io.open && !panel.open); + io = {}; + ++io_deletes; + return ESP_OK; +} +} + +int main(void) { + mybot_display_panel_t display{}; + + profile = 1; + CHECK(mybot_display_panel_open(&display) == 0); + CHECK(display.ready && display.spi_ready && display.backlight_ready); + CHECK(panel.gc9107 && panel.gap_x == MYBOT_COMPACT_GC9107_OFFSET_X); + CHECK(panel.gap_y == MYBOT_COMPACT_GC9107_OFFSET_Y); + CHECK(!panel.invert && !panel.mirror_x && !panel.mirror_y); + CHECK(backlight == MYBOT_DISPLAY_BRIGHTNESS_PERCENT); + CHECK(mybot_display_panel_close(&display) == 0); + CHECK(!display.ready && !display.spi_ready && !display.backlight_ready); + CHECK(panel_deletes == 1 && io_deletes == 1 && spi_frees == 1); + CHECK(mybot_display_panel_close(&display) == 0); + + profile = 2; + CHECK(mybot_display_panel_open(&display) == 0); + CHECK(!panel.gc9107 && panel.gap_x == MYBOT_COMPACT_ST7735_OFFSET_X); + CHECK(panel.gap_y == MYBOT_COMPACT_ST7735_OFFSET_Y); + CHECK(panel.invert && panel.mirror_x && panel.mirror_y); + CHECK(mybot_display_panel_close(&display) == 0); + + profile = 0; + CHECK(mybot_display_panel_open(&display) < 0); + CHECK(!display.ready && !spi_open); + + profile = 2; + fail_panel_new = true; + CHECK(mybot_display_panel_open(&display) < 0); + CHECK(!display.ready && !spi_open); + + fail_backlight = true; + CHECK(mybot_display_panel_open(&display) < 0); + CHECK(!display.ready && !spi_open); + std::puts("Compact GC9107/ST7735 panel lifecycle tests passed"); + return 0; +} diff --git a/tests/labplus_stm8_reset/mocks/driver/i2c_master.h b/tests/labplus_stm8_reset/mocks/driver/i2c_master.h new file mode 100644 index 0000000..2e600b2 --- /dev/null +++ b/tests/labplus_stm8_reset/mocks/driver/i2c_master.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: MIT */ +#ifndef LABPLUS_STM8_MOCK_I2C_H_ +#define LABPLUS_STM8_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 /* LABPLUS_STM8_MOCK_I2C_H_ */ diff --git a/tests/labplus_stm8_reset/mocks/esp_err.h b/tests/labplus_stm8_reset/mocks/esp_err.h new file mode 100644 index 0000000..bc3b792 --- /dev/null +++ b/tests/labplus_stm8_reset/mocks/esp_err.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "driver/i2c_master.h" diff --git a/tests/labplus_stm8_reset/mocks/esp_log.h b/tests/labplus_stm8_reset/mocks/esp_log.h new file mode 100644 index 0000000..e8a566b --- /dev/null +++ b/tests/labplus_stm8_reset/mocks/esp_log.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#define ESP_LOGE(...) ((void)0) diff --git a/tests/labplus_stm8_reset/run.sh b/tests/labplus_stm8_reset/run.sh new file mode 100644 index 0000000..c02cf06 --- /dev/null +++ b/tests/labplus_stm8_reset/run.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# SPDX-License-Identifier: MIT +set -eu + +repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd) +test_dir="$repo_root/tests/labplus_stm8_reset" +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" \ + "$test_dir/test_labplus_stm8_reset.c" \ + "$repo_root/components/mybot_platform/boards/labplus-ledong-v2/labplus_hardware.c" \ + -o "$test_build_dir/test_labplus_stm8_reset" + +if command -v timeout >/dev/null 2>&1; then + timeout 20 "$test_build_dir/test_labplus_stm8_reset" +else + "$test_build_dir/test_labplus_stm8_reset" +fi diff --git a/tests/labplus_stm8_reset/test_labplus_stm8_reset.c b/tests/labplus_stm8_reset/test_labplus_stm8_reset.c new file mode 100644 index 0000000..6d866cf --- /dev/null +++ b/tests/labplus_stm8_reset/test_labplus_stm8_reset.c @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: MIT */ +#include "driver/i2c_master.h" + +#include +#include +#include + +int mybot_generic_board_extra_init(i2c_master_bus_handle_t bus); + +#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 bool g_fail_add; +static bool g_fail_transmit; +static bool g_fail_remove; +static unsigned int g_adds; +static unsigned int g_removes; +static unsigned int g_transmits; +static uint8_t g_command; +static size_t g_command_size; +static int g_timeout_ms; + +static void reset_mock(void) { + g_device = (struct mock_i2c_device){0}; + g_fail_add = false; + g_fail_transmit = false; + g_fail_remove = false; + g_adds = 0; + g_removes = 0; + g_transmits = 0; + g_command = 0; + g_command_size = 0; + g_timeout_ms = 0; +} + +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 && config && out_device && !g_device.open); + CHECK(config->dev_addr_length == I2C_ADDR_BIT_LEN_7); + CHECK(config->device_address == 0x11 && config->scl_speed_hz == 400000); + ++g_adds; + if (g_fail_add) { + 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 && device->open); + ++g_removes; + g_device.open = false; + return g_fail_remove ? ESP_FAIL : 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 && data); + ++g_transmits; + g_command = data[0]; + g_command_size = size; + g_timeout_ms = timeout_ms; + return g_fail_transmit ? ESP_FAIL : ESP_OK; +} + +static void expect_command(void) { + CHECK(g_adds == 1 && g_transmits == 1 && g_removes == 1); + CHECK(g_command == 0x04 && g_command_size == 1 && g_timeout_ms == 100); + CHECK(!g_device.open); +} + +int main(void) { + reset_mock(); + CHECK(mybot_generic_board_extra_init(&g_bus) == 0); + expect_command(); + + reset_mock(); + g_fail_transmit = true; + CHECK(mybot_generic_board_extra_init(&g_bus) < 0); + expect_command(); + + reset_mock(); + g_fail_remove = true; + CHECK(mybot_generic_board_extra_init(&g_bus) < 0); + expect_command(); + + reset_mock(); + g_fail_add = true; + CHECK(mybot_generic_board_extra_init(&g_bus) < 0); + CHECK(g_adds == 1 && g_transmits == 0 && g_removes == 0); + + puts("LabPlus STM8 reset host tests passed"); + return 0; +} diff --git a/tests/st7789_lifecycle/mocks/esp_lcd_gc9a01.h b/tests/st7789_lifecycle/mocks/esp_lcd_gc9a01.h new file mode 100644 index 0000000..3f0f9ba --- /dev/null +++ b/tests/st7789_lifecycle/mocks/esp_lcd_gc9a01.h @@ -0,0 +1,2 @@ +/* SPDX-License-Identifier: MIT */ +#include "host_platform.h" diff --git a/tests/st7789_lifecycle/mocks/host_platform.h b/tests/st7789_lifecycle/mocks/host_platform.h index 27f990e..5b47ffe 100644 --- a/tests/st7789_lifecycle/mocks/host_platform.h +++ b/tests/st7789_lifecycle/mocks/host_platform.h @@ -15,6 +15,7 @@ typedef int esp_err_t; #define ESP_OK 0 #define ESP_FAIL -1 #define ESP_ERR_TIMEOUT 0x107 +#define ESP_ERR_INVALID_STATE 0x103 const char *esp_err_to_name(esp_err_t error); #define ESP_LOGI(...) ((void)0) #define ESP_LOGW(...) ((void)0) @@ -51,6 +52,7 @@ esp_err_t rtc_gpio_deinit(gpio_num_t gpio); #define GPIO_NUM_14 14 #define GPIO_NUM_15 15 #define GPIO_NUM_16 16 +#define GPIO_NUM_17 17 #define GPIO_NUM_18 18 #define GPIO_NUM_21 21 #define GPIO_NUM_38 38 @@ -156,6 +158,19 @@ esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t host, esp_err_t esp_lcd_new_panel_st7789(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *config, esp_lcd_panel_handle_t *out); +typedef struct { + uint8_t cmd; + const uint8_t *data; + uint8_t data_bytes; + uint16_t delay_ms; +} gc9a01_lcd_init_cmd_t; +typedef struct { + const gc9a01_lcd_init_cmd_t *init_cmds; + size_t init_cmds_size; +} gc9a01_vendor_config_t; +esp_err_t esp_lcd_new_panel_gc9a01(esp_lcd_panel_io_handle_t io, + const esp_lcd_panel_dev_config_t *config, + esp_lcd_panel_handle_t *out); esp_err_t esp_lcd_panel_io_del(esp_lcd_panel_io_handle_t io); esp_err_t esp_lcd_panel_del(esp_lcd_panel_handle_t panel); esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel);