diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 79ab171f..7430a1ec 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES "audio_codecs/audio_codec.cc" "audio_codecs/no_audio_codec.cc" "audio_codecs/box_audio_codec.cc" + "audio_codecs/box_audio_codec_lite.cc" "audio_codecs/es8311_audio_codec.cc" "audio_codecs/es8388_audio_codec.cc" "audio_codecs/cores3_audio_codec.cc" @@ -44,6 +45,8 @@ elseif(CONFIG_BOARD_TYPE_ESP_BOX_3) set(BOARD_TYPE "esp-box-3") elseif(CONFIG_BOARD_TYPE_ESP_BOX) set(BOARD_TYPE "esp-box") +elseif(CONFIG_BOARD_TYPE_ESP_BOX_LITE) + set(BOARD_TYPE "esp-box-lite") elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_1) set(BOARD_TYPE "kevin-box-1") elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_2) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index 77e0efd7..b0bfefed 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -62,6 +62,8 @@ choice BOARD_TYPE bool "ESP BOX 3" config BOARD_TYPE_ESP_BOX bool "ESP BOX" + config BOARD_TYPE_ESP_BOX_LITE + bool "ESP BOX Lite" config BOARD_TYPE_KEVIN_BOX_1 bool "Kevin Box 1" config BOARD_TYPE_KEVIN_BOX_2 diff --git a/main/audio_codecs/box_audio_codec_lite.cc b/main/audio_codecs/box_audio_codec_lite.cc new file mode 100644 index 00000000..579cd358 --- /dev/null +++ b/main/audio_codecs/box_audio_codec_lite.cc @@ -0,0 +1,240 @@ +#include "box_audio_codec_lite.h" + +#include +#include +#include + +static const char TAG[] = "BoxAudioCodecLite"; + +BoxAudioCodecLite::BoxAudioCodecLite(void* i2c_master_handle, 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, bool input_reference) { + duplex_ = true; // 是否双工 + input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 + input_channels_ = input_reference_ ? 2 : 1; // 输入通道数 + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + 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 = { + .port = I2S_NUM_0, + .rx_handle = rx_handle_, + .tx_handle = tx_handle_, + }; + data_if_ = audio_codec_new_i2s_data(&i2s_cfg); + assert(data_if_ != NULL); + + // Output + audio_codec_i2c_cfg_t i2c_cfg = { + .port = (i2c_port_t)1, + .addr = ES8156_CODEC_DEFAULT_ADDR, + .bus_handle = i2c_master_handle, + }; + out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(out_ctrl_if_ != NULL); + + gpio_if_ = audio_codec_new_gpio(); + assert(gpio_if_ != NULL); + + es8156_codec_cfg_t cfg = {}; + cfg.ctrl_if = out_ctrl_if_; + cfg.gpio_if = gpio_if_; + cfg.pa_pin = pa_pin; + cfg.hw_gain.pa_voltage = 5.0; + cfg.hw_gain.codec_dac_voltage = 3.3; + out_codec_if_ = es8156_codec_new(&cfg); + assert(out_codec_if_ != NULL); + + esp_codec_dev_cfg_t dev_cfg = { + .dev_type = ESP_CODEC_DEV_TYPE_OUT, + .codec_if = out_codec_if_, + .data_if = data_if_, + }; + output_dev_ = esp_codec_dev_new(&dev_cfg); + assert(output_dev_ != NULL); + + // Input + i2c_cfg.addr = ES7243E_CODEC_DEFAULT_ADDR; + in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg); + assert(in_ctrl_if_ != NULL); + + es7243e_codec_cfg_t es7243_cfg = {}; + es7243_cfg.ctrl_if = in_ctrl_if_; + in_codec_if_ = es7243e_codec_new(&es7243_cfg); + assert(in_codec_if_ != NULL); + + dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN; + dev_cfg.codec_if = in_codec_if_; + input_dev_ = esp_codec_dev_new(&dev_cfg); + assert(input_dev_ != NULL); + + ESP_LOGI(TAG, "BoxAudioDevice initialized"); +} + +BoxAudioCodecLite::~BoxAudioCodecLite() { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + esp_codec_dev_delete(output_dev_); + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + esp_codec_dev_delete(input_dev_); + + audio_codec_delete_codec_if(in_codec_if_); + audio_codec_delete_ctrl_if(in_ctrl_if_); + audio_codec_delete_codec_if(out_codec_if_); + audio_codec_delete_ctrl_if(out_ctrl_if_); + audio_codec_delete_gpio_if(gpio_if_); + audio_codec_delete_data_if(data_if_); +} + +void BoxAudioCodecLite::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + assert(input_sample_rate_ == output_sample_rate_); + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = 6, + .dma_frame_num = 240, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256 + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = I2S_GPIO_UNUSED, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + i2s_tdm_config_t tdm_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)input_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + .bclk_div = 8, + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = i2s_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3), + .ws_width = I2S_TDM_AUTO_WS_WIDTH, + .ws_pol = false, + .bit_shift = true, + .left_align = false, + .big_endian = false, + .bit_order_lsb = false, + .skip_mask = false, + .total_slot = I2S_TDM_AUTO_SLOT_NUM + }, + .gpio_cfg = { + .mclk = mclk, + .bclk = bclk, + .ws = ws, + .dout = I2S_GPIO_UNUSED, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(rx_handle_, &tdm_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); +} + +void BoxAudioCodecLite::SetOutputVolume(int volume) { + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume)); + AudioCodec::SetOutputVolume(volume); +} + +void BoxAudioCodecLite::EnableInput(bool enable) { + if (enable == input_enabled_) { + return; + } + if (enable) { + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 4, + .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), + .sample_rate = (uint32_t)output_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)); + // 不支持设置gain,暂时注释 + // ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), 40.0)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_)); + } + AudioCodec::EnableInput(enable); +} + +void BoxAudioCodecLite::EnableOutput(bool enable) { + if (enable == output_enabled_) { + return; + } + if (enable) { + // Play 16bit 1 channel + esp_codec_dev_sample_info_t fs = { + .bits_per_sample = 16, + .channel = 1, + .channel_mask = 0, + .sample_rate = (uint32_t)output_sample_rate_, + .mclk_multiple = 0, + }; + ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs)); + ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_)); + } else { + ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_)); + } + AudioCodec::EnableOutput(enable); +} + +int BoxAudioCodecLite::Read(int16_t* dest, int samples) { + if (input_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t))); + } + return samples; +} + +int BoxAudioCodecLite::Write(const int16_t* data, int samples) { + if (output_enabled_) { + ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t))); + } + return samples; +} \ No newline at end of file diff --git a/main/audio_codecs/box_audio_codec_lite.h b/main/audio_codecs/box_audio_codec_lite.h new file mode 100644 index 00000000..646f3985 --- /dev/null +++ b/main/audio_codecs/box_audio_codec_lite.h @@ -0,0 +1,37 @@ +#ifndef _BOX_AUDIO_CODEC_LITE_H +#define _BOX_AUDIO_CODEC_LITE_H + +#include "audio_codec.h" + +#include +#include + +class BoxAudioCodecLite : public AudioCodec { +private: + const audio_codec_data_if_t* data_if_ = nullptr; + const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; + const audio_codec_if_t* out_codec_if_ = nullptr; + const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; + const audio_codec_if_t* in_codec_if_ = nullptr; + const audio_codec_gpio_if_t* gpio_if_ = nullptr; + + esp_codec_dev_handle_t output_dev_ = nullptr; + esp_codec_dev_handle_t input_dev_ = nullptr; + + void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); + + virtual int Read(int16_t* dest, int samples) override; + virtual int Write(const int16_t* data, int samples) override; + +public: + BoxAudioCodecLite(void* i2c_master_handle, 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, bool input_reference); + virtual ~BoxAudioCodecLite(); + + virtual void SetOutputVolume(int volume) override; + virtual void EnableInput(bool enable) override; + virtual void EnableOutput(bool enable) override; +}; + +#endif // _BOX_AUDIO_CODEC_LITE_H diff --git a/main/boards/common/button.cc b/main/boards/common/button.cc index d111c912..feb1ce34 100644 --- a/main/boards/common/button.cc +++ b/main/boards/common/button.cc @@ -3,6 +3,21 @@ #include static const char* TAG = "Button"; +#if CONFIG_SOC_ADC_SUPPORTED +Button::Button(const button_adc_config_t& adc_cfg) { + button_config_t button_config = { + .type = BUTTON_TYPE_ADC, + .long_press_time = 1000, + .short_press_time = 50, + .adc_button_config = adc_cfg + }; + button_handle_ = iot_button_create(&button_config); + if (button_handle_ == NULL) { + ESP_LOGE(TAG, "Failed to create button handle"); + return; + } +} +#endif Button::Button(gpio_num_t gpio_num, bool active_high) : gpio_num_(gpio_num) { if (gpio_num == GPIO_NUM_NC) { diff --git a/main/boards/common/button.h b/main/boards/common/button.h index b342ddec..d2e44fd3 100644 --- a/main/boards/common/button.h +++ b/main/boards/common/button.h @@ -7,6 +7,9 @@ class Button { public: +#if CONFIG_SOC_ADC_SUPPORTED + Button(const button_adc_config_t& cfg); +#endif Button(gpio_num_t gpio_num, bool active_high = false); ~Button(); diff --git a/main/boards/esp-box-lite/config.h b/main/boards/esp-box-lite/config.h new file mode 100644 index 00000000..82cde9c6 --- /dev/null +++ b/main/boards/esp-box-lite/config.h @@ -0,0 +1,39 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2 +#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_46 +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 +#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC +#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X false +#define DISPLAY_MIRROR_Y true +#define DISPLAY_SWAP_XY false + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_45 +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/esp-box-lite/config.json b/main/boards/esp-box-lite/config.json new file mode 100644 index 00000000..a3004371 --- /dev/null +++ b/main/boards/esp-box-lite/config.json @@ -0,0 +1,9 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "esp-box-lite", + "sdkconfig_append": ["CONFIG_SOC_ADC_SUPPORTED=y"] + } + ] +} \ No newline at end of file diff --git a/main/boards/esp-box-lite/esp_box_lite_board.cc b/main/boards/esp-box-lite/esp_box_lite_board.cc new file mode 100644 index 00000000..7be4ee9f --- /dev/null +++ b/main/boards/esp-box-lite/esp_box_lite_board.cc @@ -0,0 +1,253 @@ +#include "wifi_board.h" +#include "audio_codecs/box_audio_codec_lite.h" +#include "display/lcd_display.h" +#include "esp_lcd_ili9341.h" +#include "font_awesome_symbols.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "iot/thing_manager.h" +#include "assets/lang_config.h" +#include +#include +#include +#include +#include + +#define TAG "EspBoxBoardLite" + +LV_FONT_DECLARE(font_puhui_20_4); +LV_FONT_DECLARE(font_awesome_20_4); + +/* ADC Buttons */ +typedef enum { + BSP_ADC_BUTTON_PREV, + BSP_ADC_BUTTON_ENTER, + BSP_ADC_BUTTON_NEXT, + BSP_ADC_BUTTON_NUM +} bsp_adc_button_t; + +// Init ili9341 by custom cmd +static const ili9341_lcd_init_cmd_t vendor_specific_init[] = { + {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0}, + {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0}, + {0xC5, (uint8_t []){0xD0}, 1, 0}, + {0xC1, (uint8_t []){0x02}, 1, 0}, + {0xB4, (uint8_t []){0x02}, 1, 0}, + {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0}, + {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0}, + + {0xB1, (uint8_t []){00, 0x1B}, 2, 0}, + {0x36, (uint8_t []){0x08}, 1, 0}, + {0x3A, (uint8_t []){0x55}, 1, 0}, + {0xB7, (uint8_t []){0x06}, 1, 0}, + + {0x11, (uint8_t []){0}, 0x80, 0}, + {0x29, (uint8_t []){0}, 0x80, 0}, + + {0, (uint8_t []){0}, 0xff, 0}, +}; + +class EspBoxBoardLite : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + Button boot_button_; + Button* adc_button_[BSP_ADC_BUTTON_NUM]; +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + adc_oneshot_unit_handle_t bsp_adc_handle = NULL; +#endif + LcdDisplay* display_; + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)1, + .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, &i2c_bus_)); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_6; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_7; + 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void changeVol(int val) { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + val; + if (volume > 100) { + volume = 100; + } + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + } + + void TogleState() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + ResetWifiConfiguration(); + } + app.ToggleChatState(); + } + + void InitializeButtons() { + /* Initialize ADC esp-box lite的前三个按钮采用是的adc按钮,而非gpio */ + button_adc_config_t adc_cfg; + adc_cfg.adc_channel = ADC_CHANNEL_0; // ADC1 channel 0 is GPIO1 +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0) + const adc_oneshot_unit_init_cfg_t init_config1 = { + .unit_id = ADC_UNIT_1, + }; + adc_oneshot_new_unit(&init_config1, &bsp_adc_handle); + adc_cfg.adc_handle = &bsp_adc_handle; +#endif + adc_cfg.button_index = BSP_ADC_BUTTON_PREV; + adc_cfg.min = 2310; // middle is 2410mV + adc_cfg.max = 2510; + adc_button_[0] = new Button(adc_cfg); + + adc_cfg.button_index = BSP_ADC_BUTTON_ENTER; + adc_cfg.min = 1880; // middle is 1980mV + adc_cfg.max = 2080; + adc_button_[1] = new Button(adc_cfg); + + adc_cfg.button_index = BSP_ADC_BUTTON_NEXT; + adc_cfg.min = 720; // middle is 820mV + adc_cfg.max = 920; + adc_button_[2] = new Button(adc_cfg); + + auto volume_up_button = adc_button_[BSP_ADC_BUTTON_NEXT]; + volume_up_button->OnClick([this]() {changeVol(10);}); + volume_up_button->OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + }); + + auto volume_down_button = adc_button_[BSP_ADC_BUTTON_PREV]; + volume_down_button->OnClick([this]() {changeVol(-10);}); + volume_down_button->OnLongPress([this]() { + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + }); + + auto break_button = adc_button_[BSP_ADC_BUTTON_ENTER]; + break_button->OnClick([this]() {TogleState();}); + boot_button_.OnClick([this]() {TogleState();}); + } + + void InitializeIli9341Display() { + 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 = GPIO_NUM_5; + io_config.dc_gpio_num = GPIO_NUM_4; + io_config.spi_mode = 0; + io_config.pclk_hz = 40 * 1000 * 1000; + 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(SPI3_HOST, &io_config, &panel_io)); + + // 初始化液晶屏驱动芯片 + ESP_LOGD(TAG, "Install LCD driver"); + const ili9341_vendor_config_t vendor_config = { + .init_cmds = &vendor_specific_init[0], + .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t), + }; + + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_48; + panel_config.flags.reset_active_high = 0, + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + panel_config.vendor_config = (void *)&vendor_config; + ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, false); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + esp_lcd_panel_disp_on_off(panel, true); + esp_lcd_panel_invert_color(panel, true); + 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, + { + .text_font = &font_puhui_20_4, + .icon_font = &font_awesome_20_4, + .emoji_font = font_emoji_64_init(), + }); + } + + // 物联网初始化,添加对 AI 可见设备 + void InitializeIot() { + auto& thing_manager = iot::ThingManager::GetInstance(); + thing_manager.AddThing(iot::CreateThing("Speaker")); + thing_manager.AddThing(iot::CreateThing("Backlight")); + } + +public: + EspBoxBoardLite() : boot_button_(BOOT_BUTTON_GPIO) { + InitializeI2c(); + InitializeSpi(); + InitializeIli9341Display(); + InitializeButtons(); + InitializeIot(); + GetBacklight()->RestoreBrightness(); + } + + ~EspBoxBoardLite() { + for (int i =0; i