#include #include #include #include #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 "mcp_server.h" #include #include #include #include #include #include #include "led/single_led.h" #include "system_reset.h" #include "esp_lcd_ili9341.h" #define TAG "FreenoveESP32S3Display" class TouchDriver { public: TouchDriver() : dev_(nullptr) {} bool Init(i2c_master_bus_handle_t bus, uint8_t addr) { i2c_device_config_t cfg = { .device_address = addr, .scl_speed_hz = 400000, .scl_wait_us = 0, }; return i2c_master_bus_add_device(bus, &cfg, &dev_) == ESP_OK; } bool Read(bool &touched, uint16_t &x, uint16_t &y) { touched = false; x = y = 0; if (!dev_) return false; uint8_t reg = 0x02; uint8_t buf[5]; if (i2c_master_transmit_receive(dev_, ®, 1, buf, 5, 50) != ESP_OK) return false; uint8_t points = buf[0] & 0x0F; if (points == 0) return true; touched = true; x = ((buf[1] & 0x0F) << 8) | buf[2]; y = ((buf[3] & 0x0F) << 8) | buf[4]; return true; } private: i2c_master_dev_handle_t dev_; }; class FreenoveESP32S3Display : public WifiBoard { private: Button boot_button_; LcdDisplay *display_; i2c_master_bus_handle_t codec_i2c_bus_; TouchDriver touch_; static void TouchTask(void *arg) { auto *self = static_cast(arg); auto &app = Application::GetInstance(); uint32_t last_tap = 0; uint32_t down_start = 0; bool down = false; while (true) { bool t; uint16_t x, y; self->touch_.Read(t, x, y); uint32_t now = esp_timer_get_time() / 1000; if (t) { if (!down) { down = true; down_start = now; } } if (!t && down) { down = false; uint32_t press = now - down_start; // long tap if (press > 3000) { self->EnterWifiConfigMode(); } else { // double tap if (now - last_tap < 250) { app.StartListening(); last_tap = 0; } else { // single tap app.ToggleChatState(); last_tap = now; } } } vTaskDelay(pdMS_TO_TICKS(50)); } } void InitializeTouch() { if (!touch_.Init(codec_i2c_bus_, 0x38)) return; xTaskCreatePinnedToCore(TouchTask, "touch_task", 4096, this, 5, nullptr, 0); } void InitializeI2c() { i2c_master_bus_config_t i2c_bus_cfg = { .i2c_port = AUDIO_CODEC_I2C_NUM, .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_MOSI_PIN; buscfg.miso_io_num = DISPLAY_MIS0_PIN; buscfg.sclk_io_num = DISPLAY_SCK_PIN; 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(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO)); } void InitializeButtons() { boot_button_.OnClick([this]() { auto &app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateStarting) { EnterWifiConfigMode(); } app.ToggleChatState(); }); } void InitializeLcdDisplay() { 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_CS_PIN; io_config.dc_gpio_num = DISPLAY_DC_PIN; io_config.spi_mode = DISPLAY_SPI_MODE; io_config.pclk_hz = DISPLAY_SPI_SCLK_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(LCD_SPI_HOST, &io_config, &panel_io)); // 初始化液晶屏驱动芯片 ESP_LOGD(TAG, "Install LCD driver"); esp_lcd_panel_dev_config_t panel_config = {}; panel_config.reset_gpio_num = DISPLAY_RST_PIN; panel_config.rgb_ele_order = DISPLAY_RGB_ORDER; panel_config.bits_per_pixel = 16; ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); ESP_LOGI(TAG, "Install LCD driver ILI9341"); 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); } void InitializeTools() { } public: FreenoveESP32S3Display(): boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); InitializeSpi(); InitializeLcdDisplay(); InitializeTouch(); InitializeButtons(); InitializeTools(); GetBacklight()->SetBrightness(100); } virtual Led *GetLed() override { static SingleLed led(BUILTIN_LED_GPIO); return &led; } virtual AudioCodec* GetAudioCodec() override { static Es8311AudioCodec audio_codec(codec_i2c_bus_, AUDIO_CODEC_I2C_NUM, 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, true, true); 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; } }; DECLARE_BOARD(FreenoveESP32S3Display);