From d0ba3a923c67ead1e0037331e32630b79d135786 Mon Sep 17 00:00:00 2001 From: zczc365 <158059821+Zc365@users.noreply.github.com> Date: Sat, 13 Sep 2025 14:38:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=B0=8F=E6=99=BA=E4=BA=91?= =?UTF-8?q?=E8=81=8A-S3=E5=B9=B6=E4=BF=AE=E6=94=B98388=E6=94=AF=E6=8C=81AE?= =?UTF-8?q?C=20(#1179)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/CMakeLists.txt | 5 + main/Kconfig.projbuild | 5 +- main/audio/codecs/es8388_audio_codec.cc | 28 +++- main/audio/codecs/es8388_audio_codec.h | 2 +- main/boards/yunliao-s3/README.md | 88 ++++++++++ main/boards/yunliao-s3/config.h | 59 +++++++ main/boards/yunliao-s3/config.json | 11 ++ main/boards/yunliao-s3/power_manager.cc | 203 ++++++++++++++++++++++++ main/boards/yunliao-s3/power_manager.h | 37 +++++ main/boards/yunliao-s3/yunliao_s3.cc | 203 ++++++++++++++++++++++++ 10 files changed, 631 insertions(+), 10 deletions(-) create mode 100644 main/boards/yunliao-s3/README.md create mode 100644 main/boards/yunliao-s3/config.h create mode 100644 main/boards/yunliao-s3/config.json create mode 100644 main/boards/yunliao-s3/power_manager.cc create mode 100644 main/boards/yunliao-s3/power_manager.h create mode 100644 main/boards/yunliao-s3/yunliao_s3.cc diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 90471d1e..f57f1a0a 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -516,6 +516,11 @@ elseif(CONFIG_BOARD_TYPE_SURFER_C3_1_14TFT) set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_S_PUHUI_COMMON_20_4_EMOJI_32}) +elseif(CONFIG_BOARD_TYPE_YUNLIAO_S3) + set(BOARD_TYPE "yunliao-s3") + set(LVGL_TEXT_FONT ${FONT_PUHUI_BASIC_20_4}) + set(LVGL_ICON_FONT ${FONT_AWESOME_20_4}) + set(DEFAULT_ASSETS ${ASSETS_XIAOZHI_PUHUI_COMMON_20_4_EMOJI_64}) endif() file(GLOB BOARD_SOURCES diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index cd774615..11275d30 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -370,6 +370,9 @@ choice BOARD_TYPE config BOARD_TYPE_SURFER_C3_1_14TFT bool "Surfer-C3-1-14TFT" depends on IDF_TARGET_ESP32C3 + config BOARD_TYPE_YUNLIAO_S3 + bool "小智云聊-S3" + depends on IDF_TARGET_ESP32S3 endchoice choice ESP_S3_LCD_EV_Board_Version_TYPE @@ -529,7 +532,7 @@ config USE_AUDIO_PROCESSOR config USE_DEVICE_AEC bool "Enable Device-Side AEC" default n - depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE || BOARD_TYPE_LICHUANG_DEV || BOARD_TYPE_ESP32S3_KORVO2_V3 || BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 || BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06 || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2) + depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE || BOARD_TYPE_LICHUANG_DEV || BOARD_TYPE_ESP32S3_KORVO2_V3 || BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 || BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06 || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 || BOARD_TYPE_YUNLIAO_S3) help 因为性能不够,不建议和微信聊天界面风格同时开启 diff --git a/main/audio/codecs/es8388_audio_codec.cc b/main/audio/codecs/es8388_audio_codec.cc index 4129fe99..3fd327bd 100644 --- a/main/audio/codecs/es8388_audio_codec.cc +++ b/main/audio/codecs/es8388_audio_codec.cc @@ -6,13 +6,14 @@ Es8388AudioCodec::Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8388_addr) { + gpio_num_t pa_pin, uint8_t es8388_addr, bool input_reference) { duplex_ = true; // 是否双工 - input_reference_ = false; // 是否使用参考输入,实现回声消除 - input_channels_ = 1; // 输入通道数 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 input_sample_rate_ = input_sample_rate; output_sample_rate_ = output_sample_rate; - pa_pin_ = pa_pin; CreateDuplexChannels(mclk, bclk, ws, dout, din); + pa_pin_ = pa_pin; + CreateDuplexChannels(mclk, bclk, ws, dout, din); // Do initialize of related interface: data_if, ctrl_if and gpio_if audio_codec_i2s_cfg_t i2s_cfg = { @@ -144,13 +145,21 @@ void Es8388AudioCodec::EnableInput(bool enable) { if (enable) { esp_codec_dev_sample_info_t fs = { .bits_per_sample = 16, - .channel = 1, - .channel_mask = 0, + .channel = (uint8_t) input_channels_, + .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), .sample_rate = (uint32_t)input_sample_rate_, .mclk_multiple = 0, }; + if (input_reference_) { + fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1); + } ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs)); - ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 24.0)); + if (input_reference_) { + uint8_t gain = (11 << 4) + 0; + ctrl_if_->write_reg(ctrl_if_, 0x09, 1, &gain, 1); + }else{ + ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 24.0)); + } } else { ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); } @@ -175,6 +184,9 @@ void Es8388AudioCodec::EnableOutput(bool enable) { // Set analog output volume to 0dB, default is -45dB uint8_t reg_val = 30; // 0dB + if(input_reference_){ + reg_val = 27; + } uint8_t regs[] = { 46, 47, 48, 49 }; // HP_LVOL, HP_RVOL, SPK_LVOL, SPK_RVOL for (uint8_t reg : regs) { ctrl_if_->write_reg(ctrl_if_, reg, 1, ®_val, 1); @@ -200,7 +212,7 @@ int Es8388AudioCodec::Read(int16_t* dest, int samples) { } int Es8388AudioCodec::Write(const int16_t* data, int samples) { - if (output_enabled_) { + if (output_enabled_ && output_dev_ && data != nullptr) { ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); } return samples; diff --git a/main/audio/codecs/es8388_audio_codec.h b/main/audio/codecs/es8388_audio_codec.h index d81f49d1..316dfce5 100644 --- a/main/audio/codecs/es8388_audio_codec.h +++ b/main/audio/codecs/es8388_audio_codec.h @@ -29,7 +29,7 @@ private: public: Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, - gpio_num_t pa_pin, uint8_t es8388_addr); + gpio_num_t pa_pin, uint8_t es8388_addr, bool input_reference = false); virtual ~Es8388AudioCodec(); virtual void SetOutputVolume(int volume) override; diff --git a/main/boards/yunliao-s3/README.md b/main/boards/yunliao-s3/README.md new file mode 100644 index 00000000..011cc549 --- /dev/null +++ b/main/boards/yunliao-s3/README.md @@ -0,0 +1,88 @@ +# 小智云聊S3 + +## 简介 +小智云聊S3是小智AI的魔改项目,是首个2.8寸护眼大屏+大字体+2000mah大电池的量产成品,做了大量创新和优化。 + +## 合并版 +合并版代码在小智AI主项目中维护,跟随主项目的一起版本更新,便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G自由切换等功能。 + +>### 按键操作 +>- **开机**: 关机状态,长按1秒后释放按键,自动开机 +>- **关机**: 开机状态,长按1秒后释放按键,标题栏会显示'请稍候',再等2秒自动关机 +>- **唤醒/打断**: 正常通话环境下,单击按键 +>- **切换4G/Wifi**: 启动过程或者配网界面,1秒钟内双击按键(需安装4G模块) +>- **重新配网**: 开机状态,1秒钟内三击按键,会自动重启并进入配网界面 + +## 魔改版 +魔改版由于底层改动太大,代码单独维护,定期合并主项目代码。 + +>### 为什么是魔改 +>- 首个实现微信二维码配网。 +>- 首个支持单手机配网。 +>- 首个支持扫二维码访问控制台。 +>- 首发支持繁体、日文、英文版界面 +>- 首个全语音操控模式 +>- 独家提供一键刷机脚本等多种刷机方式 + +## 版本区别 +>| 特性 | 合并版 | 魔改版 | +>| --- | --- | --- | +>| 语音打断 | ✓ | ✓ | +>| 4G功能 | ✓ | ✓ | +>| 自动更新固件 | ✓ | X | +>| 第三方固件支持 | ✓ | X | +>| 天气待机界面 | X | ✓ | +>| 闹钟提醒 | X | ✓ | +>| 网络音乐播放 | X | ✓ | +>| 微信扫码配网 | X | ✓ | +>| 单手机配网 | X | ✓ | +>| 扫码访问控制台 | X | ✓ | +>| 繁日英文界面 | X | ✓ | +>| 多语言支持 | X | ✓ | +>| 外接蓝牙音箱 | X | ✓ | + + +# 编译配置命令 + +**克隆工程** + +```bash +git clone https://github.com/78/xiaozhi-esp32.git +``` + +**进入工程** + +```bash +cd xiaozhi-esp32 +``` + +**配置编译目标为 ESP32S3** + +```bash +idf.py set-target esp32s3 +``` + +**打开 menuconfig** + +```bash +idf.py menuconfig +``` + +**选择板子** + +```bash +- `Xiaozhi Assistant` → `Board Type` → 选择 `小智云聊-S3` → 选择 `Enable Device-Side AEC` +``` + +**编译** + +```ba +idf.py build +``` + +**下载并打开串口终端** + +```bash +idf.py build flash monitor +``` + diff --git a/main/boards/yunliao-s3/config.h b/main/boards/yunliao-s3/config.h new file mode 100644 index 00000000..be740022 --- /dev/null +++ b/main/boards/yunliao-s3/config.h @@ -0,0 +1,59 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 +#define AUDIO_DEFAULT_OUTPUT_VOLUME 70 + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_11 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_17 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_18 +#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR + +#define BOOT_BUTTON_PIN GPIO_NUM_2 +#define BOOT_5V_PIN GPIO_NUM_3 //5V升压输出 +#define BOOT_4G_PIN GPIO_NUM_5 //4G模块使能 +#define MON_BATT_PIN GPIO_NUM_43 //检测PMU电池指示 +#define MON_BATT_CNT 70 //检测PMU电池秒数 +#define MON_USB_PIN GPIO_NUM_47 //检测USB插入 + + +#define ML307_RX_PIN GPIO_NUM_16 +#define ML307_TX_PIN GPIO_NUM_15 + +#define DISPLAY_SPI_LCD_HOST SPI2_HOST +#define DISPLAY_SPI_CLOCK_HZ (40 * 1000 * 1000) +#define DISPLAY_SPI_PIN_SCLK 42 +#define DISPLAY_SPI_PIN_MOSI 40 +#define DISPLAY_SPI_PIN_MISO -1 +#define DISPLAY_SPI_PIN_LCD_DC 41 +#define DISPLAY_SPI_PIN_LCD_RST 45 +#define DISPLAY_SPI_PIN_LCD_CS -1 +#define DISPLAY_PIN_TOUCH_CS -1 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SWAP_XY true +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_INVERT_COLOR false +#define DISPLAY_RGB_ORDER_COLOR LCD_RGB_ELEMENT_ORDER_RGB + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 +#define KEY_EXPIRE_MS 800 + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/yunliao-s3/config.json b/main/boards/yunliao-s3/config.json new file mode 100644 index 00000000..1261a683 --- /dev/null +++ b/main/boards/yunliao-s3/config.json @@ -0,0 +1,11 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "yunliao-s3", + "sdkconfig_append": [ + "CONFIG_USE_DEVICE_AEC=y" + ] + } + ] +} \ No newline at end of file diff --git a/main/boards/yunliao-s3/power_manager.cc b/main/boards/yunliao-s3/power_manager.cc new file mode 100644 index 00000000..19999e6c --- /dev/null +++ b/main/boards/yunliao-s3/power_manager.cc @@ -0,0 +1,203 @@ +#include "power_manager.h" +#include "esp_sleep.h" +#include "driver/rtc_io.h" +#include "esp_log.h" +#include "config.h" +#include +#include "esp_log.h" +#include "settings.h" + +#define TAG "PowerManager" + +static QueueHandle_t gpio_evt_queue = NULL; +uint16_t battCnt;//闪灯次数 +int battLife = -1; //电量 + +// 中断服务程序 +static void IRAM_ATTR batt_mon_isr_handler(void* arg) { + uint32_t gpio_num = (uint32_t) arg; + xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL); +} + +// 添加任务处理函数 +static void batt_mon_task(void* arg) { + uint32_t io_num; + while(1) { + if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) { + battCnt++; + } + } +} + +static void calBattLife() { + // 计算电量 + battLife = battCnt; + + if (battLife > 100){ + battLife = 100; + } + // ESP_LOGI(TAG, "Battery life:%d", (int)battLife); + // 重置计数器 + battCnt = 0; +} + +PowerManager::PowerManager(){ +} + +void PowerManager::Initialize(){ + // 初始化5V控制引脚 + gpio_config_t io_conf_5v = { + .pin_bit_mask = 1<(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); +} + +void PowerManager::CheckBatteryStatus(){ + call_count_++; + if(call_count_ >= MON_BATT_CNT) { + calBattLife(); + call_count_ = 0; + } + + bool new_charging_status = IsCharging(); + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (charging_callback_) { + charging_callback_(is_charging_); + } + } + + bool new_discharging_status = IsDischarging(); + if (new_discharging_status != is_discharging_) { + is_discharging_ = new_discharging_status; + if (discharging_callback_) { + discharging_callback_(is_discharging_); + } + } +} + +bool PowerManager::IsCharging() { + return gpio_get_level(MON_USB_PIN) == 1 && !IsChargingDone(); +} + +bool PowerManager::IsDischarging() { + return gpio_get_level(MON_USB_PIN) == 0; +} + +bool PowerManager::IsChargingDone() { + return battLife >= 95; +} + +int PowerManager::GetBatteryLevel() { + return battLife; +} + +void PowerManager::OnChargingStatusChanged(std::function callback) { + charging_callback_ = callback; +} + +void PowerManager::OnChargingStatusDisChanged(std::function callback) { + discharging_callback_ = callback; +} + +void PowerManager::CheckStartup() { + Settings settings1("board", true); + if(settings1.GetInt("sleep_flag", 0) > 0){ + vTaskDelay(pdMS_TO_TICKS(1000)); + if( gpio_get_level(BOOT_BUTTON_PIN) == 1) { + Sleep(); //进入休眠模式 + }else{ + settings1.SetInt("sleep_flag", 0); + } + } +} + +void PowerManager::Start5V() { + gpio_set_level(BOOT_5V_PIN, 1); +} + +void PowerManager::Shutdown5V() { + gpio_set_level(BOOT_5V_PIN, 0); +} + +void PowerManager::Start4G() { + gpio_set_level(BOOT_4G_PIN, 1); +} + +void PowerManager::Shutdown4G() { + gpio_set_level(BOOT_4G_PIN, 0); + gpio_set_level(ML307_RX_PIN,1); + gpio_set_level(ML307_TX_PIN,1); +} + +void PowerManager::Sleep() { + ESP_LOGI(TAG, "Entering deep sleep"); + Settings settings("board", true); + settings.SetInt("sleep_flag", 1); + Shutdown4G(); + Shutdown5V(); + + if(gpio_evt_queue) { + vQueueDelete(gpio_evt_queue); + gpio_evt_queue = NULL; + } + ESP_ERROR_CHECK(gpio_isr_handler_remove(BOOT_BUTTON_PIN)); + ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(BOOT_BUTTON_PIN, 0)); + ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(BOOT_BUTTON_PIN)); + ESP_ERROR_CHECK(rtc_gpio_pullup_en(BOOT_BUTTON_PIN)); + esp_deep_sleep_start(); +} \ No newline at end of file diff --git a/main/boards/yunliao-s3/power_manager.h b/main/boards/yunliao-s3/power_manager.h new file mode 100644 index 00000000..921cf47a --- /dev/null +++ b/main/boards/yunliao-s3/power_manager.h @@ -0,0 +1,37 @@ +#ifndef __POWERMANAGER_H__ +#define __POWERMANAGER_H__ + +#include +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/timers.h" + +class PowerManager{ +public: + PowerManager(); + void Initialize(); + bool IsCharging(); + bool IsDischarging(); + bool IsChargingDone(); + int GetBatteryLevel(); + void CheckStartup(); + void Start5V(); + void Shutdown5V(); + void Start4G(); + void Shutdown4G(); + void Sleep(); + void CheckBatteryStatus(); + void OnChargingStatusChanged(std::function callback); + void OnChargingStatusDisChanged(std::function callback); +private: + esp_timer_handle_t timer_handle_; + std::function charging_callback_; + std::function discharging_callback_; + int is_charging_ = -1; + int is_discharging_ = -1; + int call_count_ = 0; +}; + +#endif \ No newline at end of file diff --git a/main/boards/yunliao-s3/yunliao_s3.cc b/main/boards/yunliao-s3/yunliao_s3.cc new file mode 100644 index 00000000..786e5b4d --- /dev/null +++ b/main/boards/yunliao-s3/yunliao_s3.cc @@ -0,0 +1,203 @@ +#include "dual_network_board.h" +#include "codecs/es8388_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "power_save_timer.h" +#include "power_manager.h" +#include "assets/lang_config.h" +#include +#include +#include + + +#define TAG "YunliaoS3" + +class YunliaoS3 : public DualNetworkBoard { +private: + i2c_master_bus_handle_t codec_i2c_bus_; + Button boot_button_; + SpiLcdDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 600); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + power_manager_->Sleep(); + }); + power_save_timer_->SetEnabled(true); + } + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = DISPLAY_SPI_PIN_MOSI; + buscfg.miso_io_num = DISPLAY_SPI_PIN_MISO; + buscfg.sclk_io_num = DISPLAY_SPI_PIN_SCLK; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeButtons() { + boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + app.ToggleChatState(); + }); + boot_button_.OnDoubleClick([this]() { + ESP_LOGI(TAG, "Button OnDoubleClick"); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + SwitchNetworkType(); + } + }); + boot_button_.OnMultipleClick([this]() { + ESP_LOGI(TAG, "Button OnThreeClick"); + auto& app = Application::GetInstance(); + if (GetNetworkType() == NetworkType::WIFI) { + auto& wifi_board = static_cast(GetCurrentBoard()); + wifi_board.ResetWifiConfiguration(); + } + },3); + boot_button_.OnLongPress([this]() { + ESP_LOGI(TAG, "Button LongPress to Sleep"); + display_->SetStatus(Lang::Strings::PLEASE_WAIT); + vTaskDelay(pdMS_TO_TICKS(2000)); + power_manager_->Sleep(); + }); + } + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + // 液晶屏控制IO初始化 + ESP_LOGD(TAG, "Install panel IO"); + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = DISPLAY_SPI_PIN_LCD_CS; + io_config.dc_gpio_num = DISPLAY_SPI_PIN_LCD_DC; + io_config.spi_mode = 3; + io_config.pclk_hz = DISPLAY_SPI_CLOCK_HZ; + io_config.trans_queue_depth = 10; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(DISPLAY_SPI_LCD_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = DISPLAY_SPI_PIN_LCD_RST; + panel_config.rgb_ele_order = DISPLAY_RGB_ORDER_COLOR; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_WIDTH, + DISPLAY_HEIGHT, DISPLAY_OFFSET_X, + DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, + DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + display_->SetTheme("dark"); + } + +public: + YunliaoS3() : + DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0), + boot_button_(BOOT_BUTTON_PIN), + power_manager_(new PowerManager()){ + power_manager_->Start5V(); + power_manager_->Initialize(); + InitializeI2c(); + power_manager_->CheckStartup(); + InitializePowerSaveTimer(); + InitializeSpi(); + InitializeButtons(); + InitializeSt7789Display(); + power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) { + if(power_save_timer_){ + if (is_discharging) { + power_save_timer_->SetEnabled(true); + } else { + power_save_timer_->SetEnabled(false); + } + } + }); + if(GetNetworkType() == NetworkType::WIFI){ + power_manager_->Shutdown4G(); + } + GetBacklight()->RestoreBrightness(); + } + + virtual AudioCodec* GetAudioCodec() override { + static Es8388AudioCodec audio_codec( + codec_i2c_bus_, + I2C_NUM_0, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8388_ADDR, + AUDIO_INPUT_REFERENCE + ); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } + + virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + level = power_manager_->GetBatteryLevel(); + charging = power_manager_->IsCharging(); + discharging = power_manager_->IsDischarging(); + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + DualNetworkBoard::SetPowerSaveMode(enabled); + } +}; + +DECLARE_BOARD(YunliaoS3);