#include "wifi_board.h" #include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "led/single_led.h" #include "assets/lang_config.h" #include #include #include #include #include #include #include #include "system_reset.h" #include "driver/gpio.h" #include "driver/spi_master.h" #include #include "i2c_device.h" #include #include #include "power_save_timer.h" #include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "power_manager.h" #define TAG "Spotpear_ESP32_S3_1_28_BOX" LV_FONT_DECLARE(font_puhui_16_4); LV_FONT_DECLARE(font_awesome_16_4); class Cst816d : public I2cDevice { public: struct TouchPoint_t { int num = 0; int x = -1; int y = -1; }; Cst816d(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { uint8_t chip_id = ReadReg(0xA3); ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); last_chip_id_ = chip_id; read_buffer_ = new uint8_t[6]; } ~Cst816d() { if (read_buffer_) { delete[] read_buffer_; read_buffer_ = nullptr; } } void UpdateTouchPoint() { if (!read_buffer_) return; ReadRegs(0x02, read_buffer_, 6); if (read_buffer_[0] == 0xFF) { read_buffer_[0] = 0x00; } tp_.num = read_buffer_[0] & 0x01; tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; } const TouchPoint_t& GetTouchPoint() const { return tp_; } static bool Probe(i2c_master_bus_handle_t i2c_bus, uint8_t addr, uint8_t& chip_id) { if (!i2c_bus) return false; i2c_master_dev_handle_t dev = nullptr; i2c_device_config_t cfg = { .dev_addr_length = I2C_ADDR_BIT_LEN_7, .device_address = addr, .scl_speed_hz = 400 * 1000, .scl_wait_us = 0, .flags = { .disable_ack_check = 0, }, }; esp_err_t ret = i2c_master_bus_add_device(i2c_bus, &cfg, &dev); if (ret != ESP_OK || dev == nullptr) { return false; } uint8_t reg = 0xA3; uint8_t id = 0; ret = i2c_master_transmit_receive(dev, ®, 1, &id, 1, 100); i2c_master_bus_rm_device(dev); if (ret == ESP_OK) { chip_id = id; return true; } return false; } private: uint8_t* read_buffer_ = nullptr; TouchPoint_t tp_; uint8_t last_chip_id_ = 0; }; class CustomLcdDisplay : public SpiLcdDisplay { public: CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) { DisplayLockGuard lock(this); // 由于屏幕是圆的,所以状态栏需要增加左右内边距 lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.33, 0); lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.33, 0); } }; class Spotpear_ESP32_S3_1_28_BOX : public WifiBoard { private: i2c_master_bus_handle_t codec_i2c_bus_ = nullptr; i2c_master_bus_handle_t i2c_bus_ = nullptr; Button boot_button_; Display* display_ = nullptr; esp_timer_handle_t touchpad_timer_ = nullptr; Cst816d* cst816d_ = nullptr; PowerSaveTimer* power_save_timer_ = nullptr; esp_lcd_panel_handle_t panel_ = nullptr; PowerManager* power_manager_ = nullptr; void InitializePowerSaveTimer() { rtc_gpio_init(GPIO_NUM_3); rtc_gpio_set_direction(GPIO_NUM_3, RTC_GPIO_MODE_OUTPUT_ONLY); rtc_gpio_set_level(GPIO_NUM_3, 1); power_save_timer_ = new PowerSaveTimer(-1, 60, 290); power_save_timer_->OnEnterSleepMode([this]() { GetDisplay()->SetPowerSaveMode(true); GetBacklight()->SetBrightness(1); }); power_save_timer_->OnExitSleepMode([this]() { GetDisplay()->SetPowerSaveMode(false); GetBacklight()->RestoreBrightness(); }); power_save_timer_->OnShutdownRequest([this]() { ESP_LOGI(TAG, "Shutting down"); // 关闭ES8311音频编解码器 auto codec = GetAudioCodec(); if (codec) { codec->EnableInput(false); codec->EnableOutput(false); } rtc_gpio_set_level(GPIO_NUM_3, 0); // 启用保持功能,确保睡眠期间电平不变 rtc_gpio_hold_en(GPIO_NUM_3); esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 esp_deep_sleep_start(); }); power_save_timer_->SetEnabled(true); } void InitializePowerManager() { power_manager_ = new PowerManager(BATTERY_CHARGING_PIN, ADC_CHANNEL_0); power_manager_->OnChargingStatusChanged([this](bool is_charging) { if (is_charging) { power_save_timer_->SetEnabled(false); } else { power_save_timer_->SetEnabled(true); } }); } void InitializeCodecI2c() { // 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 InitializeCodecI2c_Touch() { // Initialize I2C peripheral i2c_master_bus_config_t i2c_bus_cfg = { .i2c_port = I2C_NUM_1, .sda_io_num = TP_PIN_NUM_TP_SDA, .scl_io_num = TP_PIN_NUM_TP_SCL, .clk_source = I2C_CLK_SRC_DEFAULT, .glitch_ignore_cnt = 7, .intr_priority = 0, .trans_queue_depth = 0, .flags = { .enable_internal_pullup = 1, }, }; esp_err_t ret = i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_); if (ret != ESP_OK) { ESP_LOGE(TAG, "i2c_new_master_bus failed: %s", esp_err_to_name(ret)); i2c_bus_ = nullptr; } } static void touchpad_timer_callback(void* arg) { auto* board = static_cast(arg); if (!board || !board->cst816d_) return; static bool was_touched = false; static int64_t touch_start_time = 0; const int64_t TOUCH_THRESHOLD_MS = 500; // 触摸时长阈值,超过500ms视为长按 board->cst816d_->UpdateTouchPoint(); auto touch_point = board->cst816d_->GetTouchPoint(); // 检测触摸开始 if (touch_point.num > 0 && !was_touched) { was_touched = true; touch_start_time = esp_timer_get_time() / 1000; // 转换为毫秒 } // 检测触摸释放 else if (touch_point.num == 0 && was_touched) { was_touched = false; int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time; // 只有短触才触发 if (touch_duration < TOUCH_THRESHOLD_MS) { auto& app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { board->ResetWifiConfiguration(); } app.ToggleChatState(); } } } void InitializeCst816DTouchPad() { ESP_LOGI(TAG, "Init Cst816D"); // RST/INT 管脚初始化 gpio_config_t io_conf = {}; io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pin_bit_mask = (1ULL << TP_PIN_NUM_TP_RST); io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; io_conf.pull_up_en = GPIO_PULLUP_DISABLE; gpio_config(&io_conf); gpio_config_t int_conf = {}; int_conf.intr_type = GPIO_INTR_DISABLE; int_conf.mode = GPIO_MODE_INPUT; int_conf.pin_bit_mask = (1ULL << TP_PIN_NUM_TP_INT); int_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; int_conf.pull_up_en = GPIO_PULLUP_ENABLE; gpio_config(&int_conf); // 触摸芯片复位序列 gpio_set_level(TP_PIN_NUM_TP_RST, 0); vTaskDelay(pdMS_TO_TICKS(5)); gpio_set_level(TP_PIN_NUM_TP_RST, 1); vTaskDelay(pdMS_TO_TICKS(50)); // 探测是否存在触摸芯片 uint8_t chip_id = 0; if (!i2c_bus_) { ESP_LOGW(TAG, "Touch I2C bus not initialized, skip touch"); return; } bool touch_available = Cst816d::Probe(i2c_bus_, 0x15, chip_id); if (!touch_available) { ESP_LOGW(TAG, "CST816D not found, running in non-touch mode"); // 释放触摸I2C,避免无设备时反复报错 i2c_del_master_bus(i2c_bus_); i2c_bus_ = nullptr; return; } cst816d_ = new Cst816d(i2c_bus_, 0x15); // 创建定时器,10ms 间隔 esp_timer_create_args_t timer_args = { .callback = touchpad_timer_callback, .arg = this, .dispatch_method = ESP_TIMER_TASK, .name = "touchpad_timer", .skip_unhandled_events = true, }; if (esp_timer_create(&timer_args, &touchpad_timer_) == ESP_OK) { esp_timer_start_periodic(touchpad_timer_, 10 * 1000); // 10ms = 10000us } } // SPI初始化 void InitializeSpi() { ESP_LOGI(TAG, "Initialize SPI bus"); spi_bus_config_t buscfg = GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN, DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t)); ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); } // GC9A01初始化 void InitializeGc9a01Display() { ESP_LOGI(TAG, "Init GC9A01 display"); ESP_LOGI(TAG, "Install panel IO"); esp_lcd_panel_io_handle_t io_handle = NULL; esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, 0, NULL); io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle)); ESP_LOGI(TAG, "Install GC9A01 panel driver"); esp_lcd_panel_handle_t panel_handle = NULL; esp_lcd_panel_dev_config_t panel_config = {}; panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; //LCD_RGB_ENDIAN_RGB; panel_config.bits_per_pixel = 16; ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle)); panel_ = panel_handle; ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); uint8_t data_0x62[] = { 0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, 0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70 }; esp_lcd_panel_io_tx_param(io_handle, 0x62, data_0x62, sizeof(data_0x62)); uint8_t data_0x63[] = { 0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, 0x18, 0x13, 0x71, 0xF3, 0x70, 0x70 }; esp_lcd_panel_io_tx_param(io_handle, 0x63, data_0x63, sizeof(data_0x63)); uint8_t data_0x36[] = { 0x48}; esp_lcd_panel_io_tx_param(io_handle, 0x36, data_0x36, sizeof(data_0x36)); // uint8_t data_0x74[] = { 0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00}; // esp_lcd_panel_io_tx_param(io_handle, 0x74, data_0x74, sizeof(data_0x74)); uint8_t data_0xC3[] = { 0x1F}; esp_lcd_panel_io_tx_param(io_handle, 0xC3, data_0xC3, sizeof(data_0xC3)); uint8_t data_0xC4[] = { 0x1F}; esp_lcd_panel_io_tx_param(io_handle, 0xC4, data_0xC4, sizeof(data_0xC4)); display_ = new CustomLcdDisplay(io_handle, panel_handle, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { ResetWifiConfiguration(); } app.ToggleChatState(); }); } public: Spotpear_ESP32_S3_1_28_BOX() : boot_button_(BOOT_BUTTON_GPIO) { // 先初始化触摸的I2C并探测/初始化触摸(若无触摸则跳过) InitializeCodecI2c_Touch(); InitializeCst816DTouchPad(); // 初始化音频I2C InitializeCodecI2c(); // 显示相关先建立起来 InitializeSpi(); InitializeGc9a01Display(); InitializeButtons(); if (GetBacklight()) { GetBacklight()->RestoreBrightness(); } // 显示和背光可用后再初始化省电逻辑,避免空指针 InitializePowerSaveTimer(); InitializePowerManager(); } ~Spotpear_ESP32_S3_1_28_BOX() { if (touchpad_timer_) { esp_timer_stop(touchpad_timer_); esp_timer_delete(touchpad_timer_); touchpad_timer_ = nullptr; } if (cst816d_) { delete cst816d_; cst816d_ = nullptr; } if (power_save_timer_) { delete power_save_timer_; power_save_timer_ = nullptr; } if (power_manager_) { delete power_manager_; power_manager_ = nullptr; } if (display_) { delete display_; display_ = nullptr; } if (i2c_bus_) { i2c_del_master_bus(i2c_bus_); i2c_bus_ = nullptr; } if (codec_i2c_bus_) { i2c_del_master_bus(codec_i2c_bus_); codec_i2c_bus_ = nullptr; } } virtual Led* GetLed() override { static SingleLed led(BUILTIN_LED_GPIO); return &led; } virtual Display* GetDisplay() override { return display_; } virtual Backlight* GetBacklight() override { static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); return &backlight; } virtual AudioCodec* GetAudioCodec() override { static Es8311AudioCodec 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_ES8311_ADDR); return &audio_codec; } Cst816d* GetTouchpad() { return cst816d_; } virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { if (!power_manager_) { level = 0; charging = false; discharging = true; return false; } static bool last_discharging = false; charging = power_manager_->IsCharging(); discharging = power_manager_->IsDischarging(); if (discharging != last_discharging) { power_save_timer_->SetEnabled(discharging); last_discharging = discharging; } level = power_manager_->GetBatteryLevel(); return true; } virtual void SetPowerSaveMode(bool enabled) override { if (!enabled) { power_save_timer_->WakeUp(); } WifiBoard::SetPowerSaveMode(enabled); } }; DECLARE_BOARD(Spotpear_ESP32_S3_1_28_BOX);