#include "wifi_board.h" #include "display/lcd_display.h" #include "esp_lcd_st7701.h" #include "codecs/box_audio_codec.h" #include "application.h" #include "button.h" #include "led/single_led.h" #include "mcp_server.h" #include "config.h" #include "power_save_timer.h" #include "axp2101.h" #include "i2c_device.h" #include #include #include #include #include #include "esp_io_expander_tca9554.h" #include "settings.h" #include #include #include #include #include #include #include #define TAG "WaveshareEsp32s3TouchLCD4b" class Pmic : public Axp2101 { public: Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable WriteReg(0x27, 0x10); // hold 4s to power off // Disable All DCs but DC1 WriteReg(0x80, 0x01); // Disable All LDOs WriteReg(0x90, 0x00); WriteReg(0x91, 0x00); // Set DC1 to 3.3V WriteReg(0x82, (3300 - 1500) / 100); // Set ALDO1 to 3.3V WriteReg(0x92, (3300 - 500) / 100); // Enable ALDO1(MIC) WriteReg(0x90, 0x01); WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA } }; #define LCD_OPCODE_WRITE_CMD (0x02ULL) #define LCD_OPCODE_READ_CMD (0x03ULL) #define LCD_OPCODE_WRITE_COLOR (0x32ULL) static const st7701_lcd_init_cmd_t lcd_init_cmds[] = { // {cmd, { data }, data_size, delay_ms} {0x11, (uint8_t[]){0x00}, 0, 120}, {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, {0xC0, (uint8_t[]){0x3B, 0x00}, 2, 0}, {0xC1, (uint8_t[]){0x0D, 0x02}, 2, 0}, {0xC2, (uint8_t[]){0x21, 0x08}, 2, 0}, {0xCD, (uint8_t[]){0x08}, 1, 0}, {0xB0, (uint8_t[]){0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18}, 16, 0}, {0xB1, (uint8_t[]){0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18}, 16, 0}, {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0}, {0xB0, (uint8_t[]){0x60}, 1, 0}, {0xB1, (uint8_t[]){0x30}, 1, 0}, {0xB2, (uint8_t[]){0x87}, 1, 0}, {0xB3, (uint8_t[]){0x80}, 1, 0}, {0xB5, (uint8_t[]){0x49}, 1, 0}, {0xB7, (uint8_t[]){0x85}, 1, 0}, {0xB8, (uint8_t[]){0x21}, 1, 0}, {0xC1, (uint8_t[]){0x78}, 1, 0}, {0xC2, (uint8_t[]){0x78}, 1, 20}, {0xE0, (uint8_t[]){0x00, 0x1B, 0x02}, 3, 0}, {0xE1, (uint8_t[]){0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44}, 11, 0}, {0xE2, (uint8_t[]){0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00}, 12, 0}, {0xE3, (uint8_t[]){0x00, 0x00, 0x11, 0x11}, 4, 0}, {0xE4, (uint8_t[]){0x44, 0x44}, 2, 0}, {0xE5, (uint8_t[]){0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0}, 16, 0}, {0xE6, (uint8_t[]){0x00, 0x00, 0x11, 0x11}, 4, 0}, {0xE7, (uint8_t[]){0x44, 0x44}, 2, 0}, {0xE8, (uint8_t[]){0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0}, 16, 0}, {0xEB, (uint8_t[]){0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40}, 7, 0}, {0xEC, (uint8_t[]){0x3C, 0x00}, 2, 0}, {0xED, (uint8_t[]){0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA}, 16, 0}, {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0}, {0x36, (uint8_t[]){0x00}, 1, 0}, {0x3A, (uint8_t[]){0x66}, 1, 0}, {0x21, (uint8_t[]){0x00}, 0, 120}, {0x29, (uint8_t[]){0x00}, 0, 0}, }; class WaveshareEsp32s3TouchLCD4b : public WifiBoard { private: i2c_master_bus_handle_t i2c_bus_; Pmic* pmic_ = nullptr; Button boot_button_; LcdDisplay* display_; esp_io_expander_handle_t io_expander = NULL; PowerSaveTimer* power_save_timer_; uint32_t key_press_start; bool key_pressed; bool key_handled; void InitializePowerSaveTimer() { power_save_timer_ = new PowerSaveTimer(-1, 60, 300); power_save_timer_->OnEnterSleepMode([this]() { GetDisplay()->SetPowerSaveMode(true); GetBacklight()->SetBrightness(70); }); power_save_timer_->OnExitSleepMode([this]() { GetDisplay()->SetPowerSaveMode(false); GetBacklight()->RestoreBrightness(); }); power_save_timer_->OnShutdownRequest([this](){ pmic_->PowerOff(); }); 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, .trans_queue_depth = 0, .flags = { .enable_internal_pullup = 1, }, }; ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); } void InitializeTca9554(void) { esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_3|IO_EXPANDER_PIN_NUM_5 | IO_EXPANDER_PIN_NUM_6 , IO_EXPANDER_OUTPUT); esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_3, 1); esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, 0); vTaskDelay(pdMS_TO_TICKS(200)); esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, 0); vTaskDelay(pdMS_TO_TICKS(200)); esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, 1); vTaskDelay(pdMS_TO_TICKS(200)); esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_4|IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_INPUT); } void InitializeAxp2101() { ESP_LOGI(TAG, "Init AXP2101"); pmic_ = new Pmic(i2c_bus_, 0x34); } void InitializeRGB() { esp_lcd_panel_io_handle_t panel_io = nullptr; spi_line_config_t line_config = { .cs_io_type = IO_TYPE_EXPANDER, .cs_expander_pin = BSP_LCD_IO_SPI_CS, .scl_io_type = IO_TYPE_EXPANDER, .scl_expander_pin = BSP_LCD_IO_SPI_SCL, .sda_io_type = IO_TYPE_EXPANDER, .sda_expander_pin = BSP_LCD_IO_SPI_SDA, .io_expander = io_expander, }; esp_lcd_panel_io_3wire_spi_config_t io_config = ST7701_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0); ESP_ERROR_CHECK(esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io)); esp_lcd_panel_handle_t panel_handle = NULL; esp_lcd_rgb_panel_config_t rgb_config = { .clk_src = LCD_CLK_SRC_DEFAULT, .timings = { .pclk_hz = 16 * 1000 * 1000, .h_res = DISPLAY_WIDTH, .v_res = DISPLAY_HEIGHT, .hsync_pulse_width = 10, .hsync_back_porch = 10, .hsync_front_porch = 20, .vsync_pulse_width = 10, .vsync_back_porch = 10, .vsync_front_porch = 10, .flags = { .pclk_active_neg = false } }, .data_width = 16, .bits_per_pixel = 16, .num_fbs = 2, .bounce_buffer_size_px = 480 * 20, .psram_trans_align = 64, .hsync_gpio_num = BSP_LCD_HSYNC, .vsync_gpio_num = BSP_LCD_VSYNC, .de_gpio_num = BSP_LCD_DE, .pclk_gpio_num = BSP_LCD_PCLK, .disp_gpio_num = BSP_LCD_DISP, .data_gpio_nums = { BSP_LCD_DATA0, BSP_LCD_DATA1, BSP_LCD_DATA2, BSP_LCD_DATA3, BSP_LCD_DATA4, BSP_LCD_DATA5, BSP_LCD_DATA6, BSP_LCD_DATA7, BSP_LCD_DATA8, BSP_LCD_DATA9, BSP_LCD_DATA10, BSP_LCD_DATA11, BSP_LCD_DATA12, BSP_LCD_DATA13, BSP_LCD_DATA14, BSP_LCD_DATA15 }, .flags = { .fb_in_psram = 1, }, }; rgb_config.timings.h_res = DISPLAY_WIDTH; rgb_config.timings.v_res = DISPLAY_HEIGHT; st7701_vendor_config_t vendor_config = { .init_cmds = lcd_init_cmds, .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), .rgb_config = &rgb_config, .flags = { .mirror_by_cmd = 0, .auto_del_panel_io = 1, } }; const esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = GPIO_NUM_NC, .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, .bits_per_pixel = 18, .vendor_config = &vendor_config, }; ESP_ERROR_CHECK(esp_lcd_new_panel_st7701(panel_io, &panel_config, &panel_handle)); esp_lcd_panel_init(panel_handle); display_ = new RgbLcdDisplay(panel_io, 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(); }); #if CONFIG_USE_DEVICE_AEC boot_button_.OnDoubleClick([this]() { auto& app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateIdle) { app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff); } }); #endif } void InitializeTouch() { esp_lcd_touch_handle_t tp; esp_lcd_touch_config_t tp_cfg = { .x_max = DISPLAY_WIDTH - 1, .y_max = DISPLAY_HEIGHT - 1, .rst_gpio_num = GPIO_NUM_NC, .int_gpio_num = GPIO_NUM_NC, .levels = { .reset = 0, .interrupt = 0, }, .flags = { .swap_xy = 0, .mirror_x = 0, .mirror_y = 0, }, }; esp_lcd_panel_io_handle_t tp_io_handle = NULL; esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); tp_io_config.scl_speed_hz = 400* 1000; ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle)); ESP_LOGI(TAG, "Initialize touch controller"); ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp)); const lvgl_port_touch_cfg_t touch_cfg = { .disp = lv_display_get_default(), .handle = tp, }; lvgl_port_add_touch(&touch_cfg); ESP_LOGI(TAG, "Touch panel initialized successfully"); } void InitializeTools() { auto &mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.system.reconfigure_wifi", "Reboot the device and enter WiFi configuration mode.\n" "**CAUTION** You must ask the user to confirm this action.", PropertyList(), [this](const PropertyList& properties) { ResetWifiConfiguration(); return true; }); } void CheckKeyState() { if (!io_expander) return; uint32_t current_level; esp_err_t ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_4, ¤t_level); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to read IO_EXPANDER_PIN_NUM_4 level"); return; } static uint32_t last_level = 0; static uint64_t press_start_time_ms = 0; if (current_level != last_level) { last_level = current_level; if (current_level > 0) { press_start_time_ms = esp_timer_get_time() / 1000; ESP_LOGD(TAG, "Button pressed, start time recorded"); } else { uint64_t press_duration = (esp_timer_get_time() / 1000) - press_start_time_ms; ESP_LOGI(TAG, "Button released after %llums", press_duration); if (press_duration < 1000) { ESP_LOGI(TAG, "Short press detected, switching to factory partition"); const esp_partition_t* factory_partition = esp_partition_find_first( ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_FACTORY, nullptr ); if (factory_partition) { ESP_LOGI(TAG, "Found factory partition: %s", factory_partition->label); ESP_ERROR_CHECK(esp_ota_set_boot_partition(factory_partition)); esp_restart(); } else { ESP_LOGE(TAG, "Factory partition not found"); } } else { ESP_LOGI(TAG, "Long press detected (>1000ms), no action"); } } } } void InitializeKeyMonitor() { key_press_start = 0; key_pressed = false; key_handled = false; xTaskCreatePinnedToCore( [](void* arg) { auto* board = static_cast(arg); while (true) { board->CheckKeyState(); vTaskDelay(pdMS_TO_TICKS(20)); } }, "key_monitor_task", 4096, this, 5, nullptr, 0 ); } public: WaveshareEsp32s3TouchLCD4b() : boot_button_(BOOT_BUTTON_GPIO) { InitializePowerSaveTimer(); InitializeCodecI2c(); InitializeTca9554(); InitializeAxp2101(); InitializeRGB(); InitializeTouch(); InitializeButtons(); InitializeTools(); InitializeKeyMonitor(); // 启动按键监听 GetBacklight()->SetBrightness(100); } virtual AudioCodec* GetAudioCodec() override { static BoxAudioCodec audio_codec( i2c_bus_, 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, AUDIO_CODEC_ES7210_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 { static bool last_discharging = false; charging = pmic_->IsCharging(); discharging = pmic_->IsDischarging(); if (discharging != last_discharging) { power_save_timer_->SetEnabled(discharging); last_discharging = discharging; } level = pmic_->GetBatteryLevel(); return true; } virtual void SetPowerSaveMode(bool enabled) override { if (!enabled) { power_save_timer_->WakeUp(); } WifiBoard::SetPowerSaveMode(enabled); } }; DECLARE_BOARD(WaveshareEsp32s3TouchLCD4b);