diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bd6997f..3cd46db1 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) -set(PROJECT_VER "0.7.0") +set(PROJECT_VER "0.7.1") include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(xiaozhi) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 7917587a..ec389be3 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -2,11 +2,12 @@ set(SOURCES "audio_codec.cc" "audio_codecs/no_audio_codec.cc" "audio_codecs/box_audio_codec.cc" "display.cc" - "display/ssd1306_display.cc" "display/no_display.cc" + "display/st7789_display.cc" + "display/ssd1306_display.cc" "board.cc" - "boards/ml307_board.cc" "boards/wifi_board.cc" + "boards/ml307_board.cc" "system_info.cc" "system_reset.cc" "application.cc" diff --git a/main/application.cc b/main/application.cc index ee746c04..a7036aa4 100644 --- a/main/application.cc +++ b/main/application.cc @@ -356,23 +356,28 @@ void Application::SetChatState(ChatState state) { chat_state_ = state; ESP_LOGI(TAG, "STATE: %s", state_str[chat_state_]); + auto display = Board::GetInstance().GetDisplay(); auto builtin_led = Board::GetInstance().GetBuiltinLed(); switch (chat_state_) { case kChatStateUnknown: case kChatStateIdle: builtin_led->TurnOff(); + display->SetText("I'm\nIdle."); break; case kChatStateConnecting: builtin_led->SetBlue(); builtin_led->TurnOn(); + display->SetText("I'm\nConnecting..."); break; case kChatStateListening: builtin_led->SetRed(); builtin_led->TurnOn(); + display->SetText("I'm\nListening..."); break; case kChatStateSpeaking: builtin_led->SetGreen(); builtin_led->TurnOn(); + display->SetText("I'm\nSpeaking..."); break; case kChatStateWakeWordDetected: builtin_led->SetBlue(); diff --git a/main/audio_codecs/box_audio_codec.cc b/main/audio_codecs/box_audio_codec.cc index 4e26f835..bd0a7f1b 100644 --- a/main/audio_codecs/box_audio_codec.cc +++ b/main/audio_codecs/box_audio_codec.cc @@ -5,7 +5,8 @@ static const char TAG[] = "BoxAudioCodec"; -BoxAudioCodec::BoxAudioCodec(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, +BoxAudioCodec::BoxAudioCodec(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, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference) { duplex_ = true; // 是否双工 input_reference_ = input_reference; // 是否使用参考输入,实现回声消除 diff --git a/main/audio_codecs/box_audio_codec.h b/main/audio_codecs/box_audio_codec.h index 173dac52..3c793ad4 100644 --- a/main/audio_codecs/box_audio_codec.h +++ b/main/audio_codecs/box_audio_codec.h @@ -29,7 +29,8 @@ private: virtual int Write(const int16_t* data, int samples) override; public: - BoxAudioCodec(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, + BoxAudioCodec(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, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference); virtual ~BoxAudioCodec(); diff --git a/main/boards/kevin-box-0/kevin_box_board.cc b/main/boards/kevin-box-0/kevin_box_board.cc index a57952af..a2cdc751 100644 --- a/main/boards/kevin-box-0/kevin_box_board.cc +++ b/main/boards/kevin-box-0/kevin_box_board.cc @@ -181,6 +181,7 @@ public: virtual bool GetBatteryVoltage(int &voltage, bool& charging) override { ESP_ERROR_CHECK(adc_oneshot_get_calibrated_result(adc1_handle_, adc1_cali_handle_, ADC_CHANNEL_0, &voltage)); + voltage *= 3; charging = false; ESP_LOGI(TAG, "Battery voltage: %d, Charging: %d", voltage, charging); return true; diff --git a/main/boards/kevin-box-1/kevin_box_board.cc b/main/boards/kevin-box-1/kevin_box_board.cc index d8629f5b..03b386ae 100644 --- a/main/boards/kevin-box-1/kevin_box_board.cc +++ b/main/boards/kevin-box-1/kevin_box_board.cc @@ -191,6 +191,7 @@ public: virtual bool GetBatteryVoltage(int &voltage, bool& charging) override { ESP_ERROR_CHECK(adc_oneshot_get_calibrated_result(adc1_handle_, adc1_cali_handle_, ADC_CHANNEL_0, &voltage)); + voltage *= 3; charging = gpio_get_level(GPIO_NUM_2) == 0; ESP_LOGI(TAG, "Battery voltage: %d, Charging: %d", voltage, charging); return true; diff --git a/main/boards/lichuang-dev/config.h b/main/boards/lichuang-dev/config.h index 7896ac2f..57581bef 100644 --- a/main/boards/lichuang-dev/config.h +++ b/main/boards/lichuang-dev/config.h @@ -16,7 +16,6 @@ #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45 #define AUDIO_CODEC_USE_PCA9557 -#define AUDIO_CODEC_PA_PIN GPIO_NUM_40 #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1 #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR @@ -27,11 +26,9 @@ #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC -#define DISPLAY_SDA_PIN GPIO_NUM_NC -#define DISPLAY_SCL_PIN GPIO_NUM_NC -#define DISPLAY_WIDTH 128 -#define DISPLAY_HEIGHT 64 -#define DISPLAY_MIRROR_X false +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_MIRROR_X true #define DISPLAY_MIRROR_Y false diff --git a/main/boards/lichuang-dev/lichuang_dev_board.cc b/main/boards/lichuang-dev/lichuang_dev_board.cc index 30321100..e133f435 100644 --- a/main/boards/lichuang-dev/lichuang_dev_board.cc +++ b/main/boards/lichuang-dev/lichuang_dev_board.cc @@ -1,19 +1,21 @@ #include "boards/wifi_board.h" #include "audio_codecs/box_audio_codec.h" -#include "display/no_display.h" +#include "display/st7789_display.h" #include "application.h" #include "button.h" #include "led.h" #include "config.h" #include +#include #include - +#include #define TAG "LichuangDevBoard" class LichuangDevBoard : public WifiBoard { private: i2c_master_bus_handle_t i2c_bus_; + i2c_master_dev_handle_t pca9557_handle_; Button boot_button_; void InitializeI2c() { @@ -33,25 +35,48 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); } + void Pca9557ReadRegister(uint8_t addr, uint8_t* data) { + uint8_t tmp[1] = {addr}; + ESP_ERROR_CHECK(i2c_master_transmit_receive(pca9557_handle_, tmp, 1, data, 1, 100)); + } + + void Pca9557WriteRegister(uint8_t addr, uint8_t data) { + uint8_t tmp[2] = {addr, data}; + ESP_ERROR_CHECK(i2c_master_transmit(pca9557_handle_, tmp, 2, 100)); + } + + void Pca9557SetOutputState(uint8_t bit, uint8_t level) { + uint8_t data; + Pca9557ReadRegister(0x01, &data); + data = (data & ~(1 << bit)) | (level << bit); + Pca9557WriteRegister(0x01, data); + } + void InitializePca9557() { i2c_device_config_t pca9557_cfg = { .dev_addr_length = I2C_ADDR_BIT_LEN_7, .device_address = 0x19, - .scl_speed_hz = 400000, + .scl_speed_hz = 100000, .scl_wait_us = 0, .flags = { .disable_ack_check = 0, }, }; - i2c_master_dev_handle_t pca9557_handle; - ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus_, &pca9557_cfg, &pca9557_handle)); - assert(pca9557_handle != NULL); - auto pca9557_set_register = [](i2c_master_dev_handle_t pca9557_handle, uint8_t data_addr, uint8_t data) { - uint8_t data_[2] = {data_addr, data}; - ESP_ERROR_CHECK(i2c_master_transmit(pca9557_handle, data_, 2, 50)); - }; - pca9557_set_register(pca9557_handle, 0x03, 0xfd); - pca9557_set_register(pca9557_handle, 0x01, 0x02); + ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus_, &pca9557_cfg, &pca9557_handle_)); + assert(pca9557_handle_ != NULL); + Pca9557WriteRegister(0x01, 0x03); + Pca9557WriteRegister(0x03, 0xf8); + } + + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = GPIO_NUM_40; + buscfg.miso_io_num = GPIO_NUM_NC; + buscfg.sclk_io_num = GPIO_NUM_41; + 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 InitializeButtons() { @@ -68,6 +93,7 @@ public: ESP_LOGI(TAG, "Initializing LichuangDevBoard"); InitializeI2c(); InitializePca9557(); + InitializeSpi(); InitializeButtons(); WifiBoard::Initialize(); } @@ -82,15 +108,47 @@ public: if (audio_codec == nullptr) { audio_codec = new BoxAudioCodec(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); - audio_codec->SetOutputVolume(AUDIO_DEFAULT_OUTPUT_VOLUME); + GPIO_NUM_NC, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); + // audio_codec->SetOutputVolume(AUDIO_DEFAULT_OUTPUT_VOLUME); } return audio_codec; } virtual Display* GetDisplay() override { - static NoDisplay display; - return &display; + static St7789Display* display = nullptr; + if (display == nullptr) { + 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_NC; + io_config.dc_gpio_num = GPIO_NUM_39; + io_config.spi_mode = 2; + io_config.pclk_hz = 80 * 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)); + + // 初始化液晶屏驱动芯片ST7789 + ESP_LOGD(TAG, "Install LCD driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = GPIO_NUM_NC; + panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; + panel_config.bits_per_pixel = 16; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); + + esp_lcd_panel_reset(panel); + Pca9557SetOutputState(0, 0); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, true); + esp_lcd_panel_swap_xy(panel, true); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + display = new St7789Display(panel_io, panel, GPIO_NUM_42, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + return display; } }; diff --git a/main/display.cc b/main/display.cc index 51a1a72d..93a9a1da 100644 --- a/main/display.cc +++ b/main/display.cc @@ -18,12 +18,14 @@ void Display::SetupUI() { Lock(); label_ = lv_label_create(lv_disp_get_scr_act(disp_)); // lv_obj_set_style_text_font(label_, font_, 0); + lv_obj_set_style_text_color(label_, lv_color_black(), 0); lv_label_set_text(label_, "Initializing..."); lv_obj_set_width(label_, disp_->driver->hor_res); lv_obj_set_height(label_, disp_->driver->ver_res); notification_ = lv_label_create(lv_disp_get_scr_act(disp_)); // lv_obj_set_style_text_font(notification_, font_, 0); + lv_obj_set_style_text_color(notification_, lv_color_black(), 0); lv_label_set_text(notification_, "Notification\nTest"); lv_obj_set_width(notification_, disp_->driver->hor_res); lv_obj_set_height(notification_, disp_->driver->ver_res); @@ -106,7 +108,7 @@ void Display::ShowNotification(const std::string &text) { void Display::UpdateDisplay() { auto chat_state = Application::GetInstance().GetChatState(); - if (chat_state == kChatStateIdle || chat_state == kChatStateConnecting || chat_state == kChatStateListening) { + if (chat_state == kChatStateIdle) { std::string text; auto& board = Board::GetInstance(); std::string network_name; diff --git a/main/display/st7789_display.cc b/main/display/st7789_display.cc new file mode 100644 index 00000000..e38574e7 --- /dev/null +++ b/main/display/st7789_display.cc @@ -0,0 +1,123 @@ +#include "st7789_display.h" + +#include +#include +#include +#include +#include + +#define TAG "St7789Display" +#define LCD_LEDC_CH LEDC_CHANNEL_0 + +St7789Display::St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, gpio_num_t backlight_pin, + int width, int height, bool mirror_x, bool mirror_y) + : panel_io_(panel_io), panel_(panel), mirror_x_(mirror_x), mirror_y_(mirror_y) { + width_ = width; + height_ = height; + + ESP_LOGI(TAG, "Initialize LVGL"); + lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + lvgl_port_init(&port_cfg); + + InitializeBacklight(backlight_pin); + + // draw white + std::vector buffer(width_, 0xFFFF); + for (int y = 0; y < height_; y++) { + esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + ESP_LOGI(TAG, "Adding LCD screen"); + const lvgl_port_display_cfg_t display_cfg = { + .io_handle = panel_io_, + .panel_handle = panel_, + .control_handle = nullptr, + .buffer_size = static_cast(width_ * 20), + .double_buffer = false, + .trans_size = 0, + .hres = static_cast(width_), + .vres = static_cast(height_), + .monochrome = false, + .rotation = { + .swap_xy = true, + .mirror_x = mirror_x_, + .mirror_y = mirror_y_, + }, + .flags = { + .buff_dma = 0, + .buff_spiram = 1, + .sw_rotate = 0, + .full_refresh = 0, + .direct_mode = 0, + }, + }; + + disp_ = lvgl_port_add_disp(&display_cfg); + + SetBacklight(100); +} + +St7789Display::~St7789Display() { + if (panel_ != nullptr) { + esp_lcd_panel_del(panel_); + } + if (panel_io_ != nullptr) { + esp_lcd_panel_io_del(panel_io_); + } + lvgl_port_deinit(); +} + +void St7789Display::InitializeBacklight(gpio_num_t backlight_pin) { + if (backlight_pin == GPIO_NUM_NC) { + return; + } + + // Setup LEDC peripheral for PWM backlight control + const ledc_channel_config_t backlight_channel = { + .gpio_num = backlight_pin, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = LCD_LEDC_CH, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LEDC_TIMER_0, + .duty = 0, + .hpoint = 0, + .flags = { + .output_invert = true + } + }; + const ledc_timer_config_t backlight_timer = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LEDC_TIMER_10_BIT, + .timer_num = LEDC_TIMER_0, + .freq_hz = 5000, + .clk_cfg = LEDC_AUTO_CLK, + .deconfigure = false + }; + + ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer)); + ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); +} + +void St7789Display::SetBacklight(uint8_t brightness) { + if (brightness > 100) { + brightness = 100; + } + + ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness); + // LEDC resolution set to 10bits, thus: 100% = 1023 + uint32_t duty_cycle = (1023 * brightness) / 100; + ESP_ERROR_CHECK(ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle)); + ESP_ERROR_CHECK(ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH)); +} + +void St7789Display::Lock() { + lvgl_port_lock(0); +} + +void St7789Display::Unlock() { + lvgl_port_unlock(); +} diff --git a/main/display/st7789_display.h b/main/display/st7789_display.h new file mode 100644 index 00000000..cef6a073 --- /dev/null +++ b/main/display/st7789_display.h @@ -0,0 +1,29 @@ +#ifndef ST7789_DISPLAY_H +#define ST7789_DISPLAY_H + +#include "display.h" + +#include +#include +#include + +class St7789Display : public Display { +private: + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + bool mirror_x_ = false; + bool mirror_y_ = false; + + void InitializeBacklight(gpio_num_t backlight_pin); + void SetBacklight(uint8_t brightness); + + virtual void Lock() override; + virtual void Unlock() override; + +public: + St7789Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, gpio_num_t backlight_pin, + int width, int height, bool mirror_x, bool mirror_y); + ~St7789Display(); +}; + +#endif // ST7789_DISPLAY_H diff --git a/sdkconfig.defaults b/sdkconfig.defaults index 46f0bf70..d4889182 100644 --- a/sdkconfig.defaults +++ b/sdkconfig.defaults @@ -16,3 +16,6 @@ CONFIG_USE_MULTINET=n CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y + +CONFIG_LV_COLOR_16_SWAP=y +CONFIG_LV_MEM_CUSTOM=y