diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f4aa676..34d022eb 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 "1.4.2") +set(PROJECT_VER "1.4.3") # Add this line to disable the specific warning add_compile_options(-Wno-missing-field-initializers) diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 1203930f..3f32affd 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -9,7 +9,7 @@ set(SOURCES "audio_codecs/audio_codec.cc" "display/display.cc" "display/no_display.cc" "display/lcd_display.cc" - "display/ssd1306_display.cc" + "display/oled_display.cc" "protocols/protocol.cc" "iot/thing.cc" "iot/thing_manager.cc" diff --git a/main/assets/common/low_battery.p3 b/main/assets/common/low_battery.p3 new file mode 100644 index 00000000..31064733 Binary files /dev/null and b/main/assets/common/low_battery.p3 differ diff --git a/main/assets/en-US/language.json b/main/assets/en-US/language.json index 50869d13..6265ae5b 100644 --- a/main/assets/en-US/language.json +++ b/main/assets/en-US/language.json @@ -43,6 +43,7 @@ "BATTERY_LOW": "Low battery", "BATTERY_CHARGING": "Charging", "BATTERY_FULL": "Battery full", + "BATTERY_NEED_CHARGE": "Low battery, please charge", "VOLUME": "Volume ", "MUTED": "Muted", diff --git a/main/assets/zh-CN/language.json b/main/assets/zh-CN/language.json index 03274ce9..d9e75cd5 100644 --- a/main/assets/zh-CN/language.json +++ b/main/assets/zh-CN/language.json @@ -42,6 +42,7 @@ "BATTERY_LOW":"电量不足", "BATTERY_CHARGING":"正在充电", "BATTERY_FULL":"电量已满", + "BATTERY_NEED_CHARGE":"电量低,请充电", "VOLUME":"音量 ", "MUTED":"已静音", diff --git a/main/boards/bread-compact-esp32/esp32_bread_board.cc b/main/boards/bread-compact-esp32/esp32_bread_board.cc index 1096b267..cedee79b 100644 --- a/main/boards/bread-compact-esp32/esp32_bread_board.cc +++ b/main/boards/bread-compact-esp32/esp32_bread_board.cc @@ -6,10 +6,13 @@ #include "config.h" #include "iot/thing_manager.h" #include "led/single_led.h" -#include "display/ssd1306_display.h" +#include "display/oled_display.h" + #include #include #include +#include +#include #define TAG "ESP32-MarsbearSupport" @@ -24,6 +27,9 @@ private: Button asr_button_; i2c_master_bus_handle_t display_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + OledDisplay* display_ = nullptr; void InitializeDisplayI2c() { i2c_master_bus_config_t bus_config = { @@ -41,6 +47,53 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); } + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + {&font_puhui_14_1, &font_awesome_14_1}); + } + void InitializeButtons() { // 配置 GPIO @@ -88,6 +141,7 @@ public: CompactWifiBoard() : boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) { InitializeDisplayI2c(); + InitializeSsd1306Display(); InitializeButtons(); InitializeIot(); } @@ -105,9 +159,7 @@ public: } virtual Display* GetDisplay() override { - static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, - &font_puhui_14_1, &font_awesome_14_1); - return &display; + return display_; } }; diff --git a/main/boards/bread-compact-ml307/compact_ml307_board.cc b/main/boards/bread-compact-ml307/compact_ml307_board.cc index c18bce13..f0ac6f52 100644 --- a/main/boards/bread-compact-ml307/compact_ml307_board.cc +++ b/main/boards/bread-compact-ml307/compact_ml307_board.cc @@ -1,6 +1,6 @@ #include "ml307_board.h" #include "audio_codecs/no_audio_codec.h" -#include "display/ssd1306_display.h" +#include "display/oled_display.h" #include "system_reset.h" #include "application.h" #include "button.h" @@ -11,6 +11,8 @@ #include #include +#include +#include #define TAG "CompactMl307Board" @@ -20,6 +22,9 @@ LV_FONT_DECLARE(font_awesome_14_1); class CompactMl307Board : public Ml307Board { private: i2c_master_bus_handle_t display_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + OledDisplay* display_ = nullptr; Button boot_button_; Button touch_button_; Button volume_up_button_; @@ -41,6 +46,53 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); } + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + {&font_puhui_14_1, &font_awesome_14_1}); + } + void InitializeButtons() { boot_button_.OnClick([this]() { Application::GetInstance().ToggleChatState(); @@ -98,6 +150,7 @@ public: volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { InitializeDisplayI2c(); + InitializeSsd1306Display(); InitializeButtons(); InitializeIot(); } @@ -119,9 +172,7 @@ public: } virtual Display* GetDisplay() override { - static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, - &font_puhui_14_1, &font_awesome_14_1); - return &display; + return display_; } }; diff --git a/main/boards/bread-compact-wifi/compact_wifi_board.cc b/main/boards/bread-compact-wifi/compact_wifi_board.cc index 9612e26e..8758609b 100644 --- a/main/boards/bread-compact-wifi/compact_wifi_board.cc +++ b/main/boards/bread-compact-wifi/compact_wifi_board.cc @@ -1,6 +1,6 @@ #include "wifi_board.h" #include "audio_codecs/no_audio_codec.h" -#include "display/ssd1306_display.h" +#include "display/oled_display.h" #include "system_reset.h" #include "application.h" #include "button.h" @@ -12,6 +12,8 @@ #include #include #include +#include +#include #define TAG "CompactWifiBoard" @@ -21,6 +23,9 @@ LV_FONT_DECLARE(font_awesome_14_1); class CompactWifiBoard : public WifiBoard { private: i2c_master_bus_handle_t display_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + OledDisplay* display_ = nullptr; Button boot_button_; Button touch_button_; Button volume_up_button_; @@ -42,6 +47,53 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); } + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + {&font_puhui_14_1, &font_awesome_14_1}); + } + void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); @@ -102,6 +154,7 @@ public: volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { InitializeDisplayI2c(); + InitializeSsd1306Display(); InitializeButtons(); InitializeIot(); } @@ -123,9 +176,7 @@ public: } virtual Display* GetDisplay() override { - static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, - &font_puhui_14_1, &font_awesome_14_1); - return &display; + return display_; } }; diff --git a/main/boards/common/axp2101.cc b/main/boards/common/axp2101.cc new file mode 100644 index 00000000..c040576f --- /dev/null +++ b/main/boards/common/axp2101.cc @@ -0,0 +1,37 @@ +#include "axp2101.h" +#include "board.h" +#include "display.h" + +#include + +#define TAG "Axp2101" + +Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { +} + +int Axp2101::GetBatteryCurrentDirection() { + return (ReadReg(0x01) & 0b01100000) >> 5; +} + +bool Axp2101::IsCharging() { + return GetBatteryCurrentDirection() == 1; +} + +bool Axp2101::IsDischarging() { + return GetBatteryCurrentDirection() == 2; +} + +bool Axp2101::IsChargingDone() { + uint8_t value = ReadReg(0x01); + return (value & 0b00000111) == 0b00000100; +} + +int Axp2101::GetBatteryLevel() { + return ReadReg(0xA4); +} + +void Axp2101::PowerOff() { + uint8_t value = ReadReg(0x10); + value = value | 0x01; + WriteReg(0x10, value); +} diff --git a/main/boards/esp32-s3-touch-amoled-1.8/axp2101.h b/main/boards/common/axp2101.h similarity index 100% rename from main/boards/esp32-s3-touch-amoled-1.8/axp2101.h rename to main/boards/common/axp2101.h diff --git a/main/boards/common/power_save_timer.cc b/main/boards/common/power_save_timer.cc index d43c872c..378827e5 100644 --- a/main/boards/common/power_save_timer.cc +++ b/main/boards/common/power_save_timer.cc @@ -31,9 +31,12 @@ void PowerSaveTimer::SetEnabled(bool enabled) { ticks_ = 0; enabled_ = enabled; ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); + ESP_LOGI(TAG, "Power save timer enabled"); } else if (!enabled && enabled_) { ESP_ERROR_CHECK(esp_timer_stop(power_save_timer_)); enabled_ = enabled; + WakeUp(); + ESP_LOGI(TAG, "Power save timer disabled"); } } diff --git a/main/boards/esp32-s3-touch-amoled-1.8/axp2101.cc b/main/boards/esp32-s3-touch-amoled-1.8/axp2101.cc deleted file mode 100644 index b905f8b3..00000000 --- a/main/boards/esp32-s3-touch-amoled-1.8/axp2101.cc +++ /dev/null @@ -1,59 +0,0 @@ -#include "axp2101.h" -#include "board.h" -#include "display.h" - -#include - -#define TAG "Axp2101" - -Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - // ** EFUSE defaults ** - WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable - WriteReg(0x27, 0x10); // hold 4s to power off - - WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V - - uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0 - value = value | 0x02; // set bit 1 (ALDO2) - WriteReg(0x90, value); // and power channels now enabled - - WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V - - WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA - WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) - WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA - - WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables - WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables - WriteReg(0x16, 0x05); // set input current limit to 2000mA - - WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery) - WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature) -} - -int Axp2101::GetBatteryCurrentDirection() { - return (ReadReg(0x01) & 0b01100000) >> 5; -} - -bool Axp2101::IsCharging() { - return GetBatteryCurrentDirection() == 1; -} - -bool Axp2101::IsDischarging() { - return GetBatteryCurrentDirection() == 2; -} - -bool Axp2101::IsChargingDone() { - uint8_t value = ReadReg(0x01); - return (value & 0b00000111) == 0b00000100; -} - -int Axp2101::GetBatteryLevel() { - return ReadReg(0xA4); -} - -void Axp2101::PowerOff() { - uint8_t value = ReadReg(0x10); - value = value | 0x01; - WriteReg(0x10, value); -} diff --git a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc index 8c438b38..21a02637 100644 --- a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc +++ b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc @@ -9,6 +9,7 @@ #include "led/single_led.h" #include "iot/thing_manager.h" #include "config.h" +#include "power_save_timer.h" #include "axp2101.h" #include "i2c_device.h" #include @@ -25,6 +26,13 @@ LV_FONT_DECLARE(font_puhui_30_4); LV_FONT_DECLARE(font_awesome_30_4); +class Pmic : public Axp2101 { +public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + // TODO: Configure the power management IC here... + } +}; + #define LCD_OPCODE_WRITE_CMD (0x02ULL) #define LCD_OPCODE_READ_CMD (0x03ULL) #define LCD_OPCODE_WRITE_COLOR (0x32ULL) @@ -86,13 +94,33 @@ protected: class waveshare_amoled_1_8 : public WifiBoard { private: i2c_master_bus_handle_t codec_i2c_bus_; - Axp2101* axp2101_ = nullptr; - esp_timer_handle_t power_save_timer_ = nullptr; - + Pmic* pmic_ = nullptr; Button boot_button_; CustomLcdDisplay* display_; CustomBacklight* backlight_; esp_io_expander_handle_t io_expander = NULL; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + ESP_LOGI(TAG, "Enabling sleep mode"); + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("sleepy"); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("neutral"); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + pmic_->PowerOff(); + }); + power_save_timer_->SetEnabled(true); + } void InitializeCodecI2c() { // Initialize I2C peripheral @@ -129,46 +157,10 @@ private: } void InitializeAxp2101() { - // 使用 ESP_LOGI 宏记录信息日志,TAG 是日志标签,"Init AXP2101" 是日志信息 ESP_LOGI(TAG, "Init AXP2101"); - // 创建一个新的 Axp2101 对象,该对象通过 I2C 总线 i2c_bus_ 和设备地址 0x34 进行初始化 - // axp2101_ 是一个指向 Axp2101 对象的指针 - axp2101_ = new Axp2101(codec_i2c_bus_, 0x34); + pmic_ = new Pmic(codec_i2c_bus_, 0x34); } - void InitializePowerSaveTimer() { - esp_timer_create_args_t power_save_timer_args = { - .callback = [](void *arg) { - auto board = static_cast(arg); - board->PowerSaveCheck(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "Power Save Timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(&power_save_timer_args, &power_save_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); - } - - void PowerSaveCheck() { - // 电池放电模式下,如果待机超过一定时间,则自动关机 - const int seconds_to_shutdown = 600; - static int seconds = 0; - if (Application::GetInstance().GetDeviceState() != kDeviceStateIdle) { - seconds = 0; - return; - } - if (!axp2101_->IsDischarging()) { - seconds = 0; - return; - } - - seconds++; - if (seconds >= seconds_to_shutdown) { - axp2101_->PowerOff(); - } - } void InitializeSpi() { spi_bus_config_t buscfg = {}; buscfg.sclk_io_num = GPIO_NUM_11; @@ -246,13 +238,13 @@ private: public: waveshare_amoled_1_8() : boot_button_(BOOT_BUTTON_GPIO) { + InitializePowerSaveTimer(); InitializeCodecI2c(); InitializeTca9554(); InitializeAxp2101(); InitializeSpi(); InitializeSH8601Display(); InitializeButtons(); - InitializePowerSaveTimer(); InitializeIot(); } @@ -267,21 +259,33 @@ public: return display_; } + virtual Backlight* GetBacklight() override { + return backlight_; + } + virtual bool GetBatteryLevel(int &level, bool& charging) override { - static int last_level = 0; static bool last_charging = false; - level = axp2101_->GetBatteryLevel(); - charging = axp2101_->IsCharging(); - if (level != last_level || charging != last_charging) { - last_level = level; + charging = pmic_->IsCharging(); + if (charging != last_charging) { + power_save_timer_->WakeUp(); last_charging = charging; - ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); + } + + level = pmic_->GetBatteryLevel(); + + if (pmic_->IsDischarging()) { + power_save_timer_->SetEnabled(true); + } else { + power_save_timer_->SetEnabled(false); } return true; } - virtual Backlight* GetBacklight() override { - return backlight_; + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); } }; diff --git a/main/boards/kevin-box-1/kevin_box_board.cc b/main/boards/kevin-box-1/kevin_box_board.cc index 04b1cb14..33d451d1 100644 --- a/main/boards/kevin-box-1/kevin_box_board.cc +++ b/main/boards/kevin-box-1/kevin_box_board.cc @@ -1,6 +1,6 @@ #include "ml307_board.h" #include "audio_codecs/box_audio_codec.h" -#include "display/ssd1306_display.h" +#include "display/oled_display.h" #include "application.h" #include "button.h" #include "config.h" @@ -12,6 +12,8 @@ #include #include #include +#include +#include #define TAG "KevinBoxBoard" @@ -22,6 +24,9 @@ class KevinBoxBoard : public Ml307Board { private: i2c_master_bus_handle_t display_i2c_bus_; i2c_master_bus_handle_t codec_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + OledDisplay* display_ = nullptr; Button boot_button_; Button volume_up_button_; Button volume_down_button_; @@ -67,6 +72,53 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); } + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + {&font_puhui_14_1, &font_awesome_14_1}); + } + void InitializeCodecI2c() { // Initialize I2C peripheral i2c_master_bus_config_t i2c_bus_cfg = { @@ -135,6 +187,7 @@ public: volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { InitializeDisplayI2c(); + InitializeSsd1306Display(); InitializeCodecI2c(); MountStorage(); Enable4GModule(); @@ -156,9 +209,7 @@ public: } virtual Display* GetDisplay() override { - static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, - &font_puhui_14_1, &font_awesome_14_1); - return &display; + return display_; } }; diff --git a/main/boards/kevin-box-2/axp2101.cc b/main/boards/kevin-box-2/axp2101.cc deleted file mode 100644 index b905f8b3..00000000 --- a/main/boards/kevin-box-2/axp2101.cc +++ /dev/null @@ -1,59 +0,0 @@ -#include "axp2101.h" -#include "board.h" -#include "display.h" - -#include - -#define TAG "Axp2101" - -Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - // ** EFUSE defaults ** - WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable - WriteReg(0x27, 0x10); // hold 4s to power off - - WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V - - uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0 - value = value | 0x02; // set bit 1 (ALDO2) - WriteReg(0x90, value); // and power channels now enabled - - WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V - - WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA - WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) - WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA - - WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables - WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables - WriteReg(0x16, 0x05); // set input current limit to 2000mA - - WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery) - WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature) -} - -int Axp2101::GetBatteryCurrentDirection() { - return (ReadReg(0x01) & 0b01100000) >> 5; -} - -bool Axp2101::IsCharging() { - return GetBatteryCurrentDirection() == 1; -} - -bool Axp2101::IsDischarging() { - return GetBatteryCurrentDirection() == 2; -} - -bool Axp2101::IsChargingDone() { - uint8_t value = ReadReg(0x01); - return (value & 0b00000111) == 0b00000100; -} - -int Axp2101::GetBatteryLevel() { - return ReadReg(0xA4); -} - -void Axp2101::PowerOff() { - uint8_t value = ReadReg(0x10); - value = value | 0x01; - WriteReg(0x10, value); -} diff --git a/main/boards/kevin-box-2/axp2101.h b/main/boards/kevin-box-2/axp2101.h deleted file mode 100644 index db9a497e..00000000 --- a/main/boards/kevin-box-2/axp2101.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef __AXP2101_H__ -#define __AXP2101_H__ - -#include "i2c_device.h" - -class Axp2101 : public I2cDevice { -public: - Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr); - bool IsCharging(); - bool IsDischarging(); - bool IsChargingDone(); - int GetBatteryLevel(); - void PowerOff(); - -private: - int GetBatteryCurrentDirection(); -}; - -#endif diff --git a/main/boards/kevin-box-2/kevin_box_board.cc b/main/boards/kevin-box-2/kevin_box_board.cc index 61a6d267..32a2630e 100644 --- a/main/boards/kevin-box-2/kevin_box_board.cc +++ b/main/boards/kevin-box-2/kevin_box_board.cc @@ -1,6 +1,6 @@ #include "ml307_board.h" #include "audio_codecs/box_audio_codec.h" -#include "display/ssd1306_display.h" +#include "display/oled_display.h" #include "application.h" #include "button.h" #include "led/single_led.h" @@ -13,26 +13,61 @@ #include #include #include +#include +#include #define TAG "KevinBoxBoard" LV_FONT_DECLARE(font_puhui_14_1); LV_FONT_DECLARE(font_awesome_14_1); + +class Pmic : public Axp2101 { +public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + // ** EFUSE defaults ** + WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable + WriteReg(0x27, 0x10); // hold 4s to power off + + WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V + + uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0 + value = value | 0x02; // set bit 1 (ALDO2) + WriteReg(0x90, value); // and power channels now enabled + + WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V + + WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA + WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) + WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA + + WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables + WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables + WriteReg(0x16, 0x05); // set input current limit to 2000mA + + WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery) + WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature) + } +}; + + class KevinBoxBoard : public Ml307Board { private: i2c_master_bus_handle_t display_i2c_bus_; i2c_master_bus_handle_t codec_i2c_bus_; - Axp2101* axp2101_ = nullptr; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + OledDisplay* display_ = nullptr; + Pmic* pmic_ = nullptr; Button boot_button_; Button volume_up_button_; Button volume_down_button_; PowerSaveTimer* power_save_timer_; void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(240, -1, 600); + power_save_timer_ = new PowerSaveTimer(-1, -1, 600); power_save_timer_->OnShutdownRequest([this]() { - axp2101_->PowerOff(); + pmic_->PowerOff(); }); power_save_timer_->SetEnabled(true); } @@ -66,6 +101,53 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); } + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + {&font_puhui_14_1, &font_awesome_14_1}); + } + void InitializeCodecI2c() { // Initialize I2C peripheral i2c_master_bus_config_t i2c_bus_cfg = { @@ -140,8 +222,9 @@ public: volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { InitializeDisplayI2c(); + InitializeSsd1306Display(); InitializeCodecI2c(); - axp2101_ = new Axp2101(codec_i2c_bus_, AXP2101_I2C_ADDR); + pmic_ = new Pmic(codec_i2c_bus_, AXP2101_I2C_ADDR); Enable4GModule(); @@ -163,42 +246,22 @@ public: } virtual Display* GetDisplay() override { - static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, - &font_puhui_14_1, &font_awesome_14_1); - return &display; + return display_; } virtual bool GetBatteryLevel(int &level, bool& charging) override { - static int last_level = 0; static bool last_charging = false; - - charging = axp2101_->IsCharging(); + charging = pmic_->IsCharging(); if (charging != last_charging) { power_save_timer_->WakeUp(); - } - - level = axp2101_->GetBatteryLevel(); - if (level != last_level || charging != last_charging) { - last_level = level; last_charging = charging; - ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); } - static bool show_low_power_warning_ = false; - if (axp2101_->IsDischarging()) { - // 电量低于 10% 时,显示低电量警告 - if (!show_low_power_warning_ && level <= 10) { - auto& app = Application::GetInstance(); - app.Alert(Lang::Strings::WARNING, Lang::Strings::BATTERY_LOW, "sad", Lang::Sounds::P3_VIBRATION); - show_low_power_warning_ = true; - } + level = pmic_->GetBatteryLevel(); + + if (pmic_->IsDischarging()) { power_save_timer_->SetEnabled(true); } else { - if (show_low_power_warning_) { - auto& app = Application::GetInstance(); - app.DismissAlert(); - show_low_power_warning_ = false; - } power_save_timer_->SetEnabled(false); } return true; diff --git a/main/boards/m5stack-core-s3/m5stack_core_s3.cc b/main/boards/m5stack-core-s3/m5stack_core_s3.cc index 1215f274..ae4da1df 100644 --- a/main/boards/m5stack-core-s3/m5stack_core_s3.cc +++ b/main/boards/m5stack-core-s3/m5stack_core_s3.cc @@ -3,8 +3,10 @@ #include "display/lcd_display.h" #include "application.h" #include "config.h" +#include "power_save_timer.h" #include "i2c_device.h" #include "iot/thing_manager.h" +#include "axp2101.h" #include #include @@ -19,10 +21,10 @@ LV_FONT_DECLARE(font_puhui_20_4); LV_FONT_DECLARE(font_awesome_20_4); -class Axp2101 : public I2cDevice { +class Pmic : public Axp2101 { public: // Power Init - Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { uint8_t data = ReadReg(0x90); data |= 0b10110100; WriteReg(0x90, data); @@ -35,39 +37,26 @@ public: WriteReg(0x95, 33 - 5); } - int GetBatteryCurrentDirection() { - return (ReadReg(0x01) & 0b01100000) >> 5; - } - - bool IsCharging() { - return GetBatteryCurrentDirection() == 1; - } - - int GetBatteryLevel() { - return ReadReg(0xA4); - } - void SetBrightness(uint8_t brightness) { brightness = ((brightness + 641) >> 5); WriteReg(0x99, brightness); } }; -class Axp2101Backlight : public Backlight { + +class CustomBacklight : public Backlight { public: - Axp2101Backlight(Axp2101 *axp2101) : axp2101_(axp2101) {} + CustomBacklight(Pmic *pmic) : pmic_(pmic) {} - ~Axp2101Backlight() { ESP_LOGI(TAG, "Destroy Axp2101Backlight"); } - - void SetBrightnessImpl(uint8_t brightness) override; + void SetBrightnessImpl(uint8_t brightness) override { + pmic_->SetBrightness(target_brightness_); + brightness_ = target_brightness_; + } private: - Axp2101 *axp2101_; + Pmic *pmic_; }; - -void Axp2101Backlight::SetBrightnessImpl(uint8_t brightness) { - axp2101_->SetBrightness(brightness); -} + class Aw9523 : public I2cDevice { public: @@ -133,14 +122,37 @@ private: TouchPoint_t tp_; }; + class M5StackCoreS3Board : public WifiBoard { private: i2c_master_bus_handle_t i2c_bus_; - Axp2101* axp2101_; + Pmic* pmic_; Aw9523* aw9523_; Ft6336* ft6336_; LcdDisplay* display_; esp_timer_handle_t touchpad_timer_; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + ESP_LOGI(TAG, "Enabling sleep mode"); + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("sleepy"); + GetBacklight()->SetBrightness(10); + }); + power_save_timer_->OnExitSleepMode([this]() { + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("neutral"); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + pmic_->PowerOff(); + }); + power_save_timer_->SetEnabled(true); + } void InitializeI2c() { // Initialize I2C peripheral @@ -182,7 +194,7 @@ private: void InitializeAxp2101() { ESP_LOGI(TAG, "Init AXP2101"); - axp2101_ = new Axp2101(i2c_bus_, 0x34); + pmic_ = new Pmic(i2c_bus_, 0x34); } void InitializeAw9523() { @@ -227,7 +239,7 @@ private: ESP_LOGI(TAG, "Init FT6336"); ft6336_ = new Ft6336(i2c_bus_, 0x38); - // 创建定时器,10ms 间隔 + // 创建定时器,20ms 间隔 esp_timer_create_args_t timer_args = { .callback = touchpad_timer_callback, .arg = NULL, @@ -237,7 +249,7 @@ private: }; ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 10 * 1000)); // 10ms = 10000us + ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 20 * 1000)); } void InitializeSpi() { @@ -302,6 +314,7 @@ private: public: M5StackCoreS3Board() { + InitializePowerSaveTimer(); InitializeI2c(); InitializeAxp2101(); InitializeAw9523(); @@ -310,7 +323,7 @@ public: InitializeIli9342Display(); InitializeIot(); InitializeFt6336TouchPad(); - GetBacklight()->SetBrightness(100); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -333,20 +346,32 @@ public: } virtual bool GetBatteryLevel(int &level, bool& charging) override { - static int last_level = 0; static bool last_charging = false; - level = axp2101_->GetBatteryLevel(); - charging = axp2101_->IsCharging(); - if (level != last_level || charging != last_charging) { - last_level = level; + charging = pmic_->IsCharging(); + if (charging != last_charging) { + power_save_timer_->WakeUp(); last_charging = charging; - ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); + } + + level = pmic_->GetBatteryLevel(); + + if (pmic_->IsDischarging()) { + power_save_timer_->SetEnabled(true); + } else { + power_save_timer_->SetEnabled(false); } return true; } + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } + virtual Backlight *GetBacklight() override { - static Axp2101Backlight backlight(axp2101_); + static CustomBacklight backlight(pmic_); return &backlight; } diff --git a/main/boards/tudouzi/axp2101.cc b/main/boards/tudouzi/axp2101.cc deleted file mode 100644 index b905f8b3..00000000 --- a/main/boards/tudouzi/axp2101.cc +++ /dev/null @@ -1,59 +0,0 @@ -#include "axp2101.h" -#include "board.h" -#include "display.h" - -#include - -#define TAG "Axp2101" - -Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { - // ** EFUSE defaults ** - WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable - WriteReg(0x27, 0x10); // hold 4s to power off - - WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V - - uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0 - value = value | 0x02; // set bit 1 (ALDO2) - WriteReg(0x90, value); // and power channels now enabled - - WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V - - WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA - WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) - WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA - - WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables - WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables - WriteReg(0x16, 0x05); // set input current limit to 2000mA - - WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery) - WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature) -} - -int Axp2101::GetBatteryCurrentDirection() { - return (ReadReg(0x01) & 0b01100000) >> 5; -} - -bool Axp2101::IsCharging() { - return GetBatteryCurrentDirection() == 1; -} - -bool Axp2101::IsDischarging() { - return GetBatteryCurrentDirection() == 2; -} - -bool Axp2101::IsChargingDone() { - uint8_t value = ReadReg(0x01); - return (value & 0b00000111) == 0b00000100; -} - -int Axp2101::GetBatteryLevel() { - return ReadReg(0xA4); -} - -void Axp2101::PowerOff() { - uint8_t value = ReadReg(0x10); - value = value | 0x01; - WriteReg(0x10, value); -} diff --git a/main/boards/tudouzi/axp2101.h b/main/boards/tudouzi/axp2101.h deleted file mode 100644 index db9a497e..00000000 --- a/main/boards/tudouzi/axp2101.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef __AXP2101_H__ -#define __AXP2101_H__ - -#include "i2c_device.h" - -class Axp2101 : public I2cDevice { -public: - Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr); - bool IsCharging(); - bool IsDischarging(); - bool IsChargingDone(); - int GetBatteryLevel(); - void PowerOff(); - -private: - int GetBatteryCurrentDirection(); -}; - -#endif diff --git a/main/boards/tudouzi/kevin_box_board.cc b/main/boards/tudouzi/kevin_box_board.cc index e1528269..eeac0e27 100644 --- a/main/boards/tudouzi/kevin_box_board.cc +++ b/main/boards/tudouzi/kevin_box_board.cc @@ -1,6 +1,6 @@ #include "ml307_board.h" #include "audio_codecs/box_audio_codec.h" -#include "display/ssd1306_display.h" +#include "display/oled_display.h" #include "application.h" #include "button.h" #include "led/single_led.h" @@ -14,17 +14,52 @@ #include #include #include +#include +#include #define TAG "KevinBoxBoard" LV_FONT_DECLARE(font_puhui_14_1); LV_FONT_DECLARE(font_awesome_14_1); + +class Pmic : public Axp2101 { +public: + Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) { + // ** EFUSE defaults ** + WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable + WriteReg(0x27, 0x10); // hold 4s to power off + + WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V + + uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0 + value = value | 0x02; // set bit 1 (ALDO2) + WriteReg(0x90, value); // and power channels now enabled + + WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V + + WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA + WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) + WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA + + WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables + WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables + WriteReg(0x16, 0x05); // set input current limit to 2000mA + + WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery) + WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature) + } +}; + + class KevinBoxBoard : public Ml307Board { private: i2c_master_bus_handle_t display_i2c_bus_; i2c_master_bus_handle_t codec_i2c_bus_; - Axp2101* axp2101_ = nullptr; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + OledDisplay* display_ = nullptr; + Pmic* pmic_ = nullptr; Button boot_button_; Button volume_up_button_; Button volume_down_button_; @@ -85,6 +120,53 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); } + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + {&font_puhui_14_1, &font_awesome_14_1}); + } + void InitializeCodecI2c() { // Initialize I2C peripheral i2c_master_bus_config_t i2c_bus_cfg = { @@ -159,8 +241,9 @@ public: volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { InitializeDisplayI2c(); + InitializeSsd1306Display(); InitializeCodecI2c(); - axp2101_ = new Axp2101(codec_i2c_bus_, AXP2101_I2C_ADDR); + pmic_ = new Pmic(codec_i2c_bus_, AXP2101_I2C_ADDR); Enable4GModule(); @@ -182,42 +265,22 @@ public: } virtual Display* GetDisplay() override { - static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, - &font_puhui_14_1, &font_awesome_14_1); - return &display; + return display_; } virtual bool GetBatteryLevel(int &level, bool& charging) override { - static int last_level = 0; static bool last_charging = false; - - charging = axp2101_->IsCharging(); + charging = pmic_->IsCharging(); if (charging != last_charging) { power_save_timer_->WakeUp(); - } - - level = axp2101_->GetBatteryLevel(); - if (level != last_level || charging != last_charging) { - last_level = level; last_charging = charging; - ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); } - static bool show_low_power_warning_ = false; - if (axp2101_->IsDischarging()) { - // 电量低于 10% 时,显示低电量警告 - if (!show_low_power_warning_ && level <= 10) { - auto& app = Application::GetInstance(); - app.Alert(Lang::Strings::WARNING, Lang::Strings::BATTERY_LOW, "sad", Lang::Sounds::P3_VIBRATION); - show_low_power_warning_ = true; - } + level = pmic_->GetBatteryLevel(); + + if (pmic_->IsDischarging()) { power_save_timer_->SetEnabled(true); } else { - if (show_low_power_warning_) { - auto& app = Application::GetInstance(); - app.DismissAlert(); - show_low_power_warning_ = false; - } power_save_timer_->SetEnabled(false); } return true; diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc index 4360eae3..de7791b8 100644 --- a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc +++ b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc @@ -1,6 +1,6 @@ #include "ml307_board.h" #include "audio_codecs/no_audio_codec.h" -#include "display/ssd1306_display.h" +#include "display/oled_display.h" #include "system_reset.h" #include "application.h" #include "button.h" @@ -15,6 +15,8 @@ #include #include #include +#include +#include #define TAG "XINGZHI_CUBE_0_96OLED_ML307" @@ -22,59 +24,29 @@ LV_FONT_DECLARE(font_puhui_14_1); LV_FONT_DECLARE(font_awesome_14_1); -class CustomDisplay : public Ssd1306Display { -private: - lv_obj_t* low_battery_popup_ = nullptr; - -public: - CustomDisplay(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, - const lv_font_t* text_font, const lv_font_t* icon_font) - : Ssd1306Display(i2c_master_handle, width, height, mirror_x, mirror_y, text_font, icon_font) { - } - - void ShowLowBatteryPopup() { - DisplayLockGuard lock(this); - - if (low_battery_popup_ == nullptr) { - // 创建弹出窗口 - low_battery_popup_ = lv_obj_create(lv_scr_act()); - lv_obj_set_size(low_battery_popup_, 120, 30); - lv_obj_center(low_battery_popup_); - lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); - lv_obj_set_style_radius(low_battery_popup_, 10, 0); - - // 创建提示文本标签 - lv_obj_t* label = lv_label_create(low_battery_popup_); - lv_label_set_text(label, "电量过低,请充电"); - lv_obj_set_style_text_color(label, lv_color_white(), 0); - lv_obj_center(label); - } - - // 显示弹出窗口 - lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - - void HideLowBatteryPopup() { - DisplayLockGuard lock(this); - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } -}; - - class XINGZHI_CUBE_0_96OLED_ML307 : public Ml307Board { private: i2c_master_bus_handle_t display_i2c_bus_; Button boot_button_; Button volume_up_button_; Button volume_down_button_; - CustomDisplay* display_; + OledDisplay* display_; PowerSaveTimer* power_save_timer_; - PowerManager power_manager_; + PowerManager* power_manager_; esp_lcd_panel_io_handle_t panel_io_ = nullptr; esp_lcd_panel_handle_t panel_ = nullptr; + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + void InitializePowerSaveTimer() { rtc_gpio_init(GPIO_NUM_21); rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); @@ -119,6 +91,53 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); } + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + {&font_puhui_14_1, &font_awesome_14_1}); + } + void InitializeButtons() { boot_button_.OnClick([this]() { power_save_timer_->WakeUp(); @@ -171,10 +190,11 @@ public: XINGZHI_CUBE_0_96OLED_ML307() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096), boot_button_(BOOT_BUTTON_GPIO), volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), - power_manager_(GPIO_NUM_38) { + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); InitializePowerSaveTimer(); InitializeDisplayI2c(); + InitializeSsd1306Display(); InitializeButtons(); InitializeIot(); } @@ -191,44 +211,12 @@ public: } virtual Display* GetDisplay() override { - static CustomDisplay display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, - &font_puhui_14_1, &font_awesome_14_1); - return &display; + return display_; } virtual bool GetBatteryLevel(int& level, bool& charging) override { - static int last_level = 0; - static bool last_charging = false; - - charging = power_manager_.IsCharging(); - if (charging != last_charging) { - power_save_timer_->WakeUp(); - } - - level = power_manager_.ReadBatteryLevel(charging != last_charging); - if (level != last_level || charging != last_charging) { - last_level = level; - last_charging = charging; - ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); - } - - static bool show_low_power_warning_ = false; - if (power_manager_.IsBatteryLevelSteady()) { - if (!charging) { - // 电量低于 15% 时,显示低电量警告 - if (!show_low_power_warning_ && level <= 15) { - display_->ShowLowBatteryPopup(); - show_low_power_warning_ = true; - } - power_save_timer_->SetEnabled(true); - } else { - if (show_low_power_warning_) { - display_->HideLowBatteryPopup(); - show_low_power_warning_ = false; - } - power_save_timer_->SetEnabled(false); - } - } + charging = power_manager_->IsCharging(); + level = power_manager_->GetBatteryLevel(); return true; } diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc index 5f5b89d7..66d1c84c 100644 --- a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc +++ b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc @@ -1,6 +1,6 @@ #include "wifi_board.h" #include "audio_codecs/no_audio_codec.h" -#include "display/ssd1306_display.h" +#include "display/oled_display.h" #include "system_reset.h" #include "application.h" #include "button.h" @@ -17,6 +17,8 @@ #include #include #include +#include +#include #define TAG "XINGZHI_CUBE_0_96OLED_WIFI" @@ -24,59 +26,29 @@ LV_FONT_DECLARE(font_puhui_14_1); LV_FONT_DECLARE(font_awesome_14_1); -class CustomDisplay : public Ssd1306Display { -private: - lv_obj_t* low_battery_popup_ = nullptr; - -public: - CustomDisplay(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, - const lv_font_t* text_font, const lv_font_t* icon_font) - : Ssd1306Display(i2c_master_handle, width, height, mirror_x, mirror_y, text_font, icon_font) { - } - - void ShowLowBatteryPopup() { - DisplayLockGuard lock(this); - - if (low_battery_popup_ == nullptr) { - // 创建弹出窗口 - low_battery_popup_ = lv_obj_create(lv_scr_act()); - lv_obj_set_size(low_battery_popup_, 120, 30); - lv_obj_center(low_battery_popup_); - lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); - lv_obj_set_style_radius(low_battery_popup_, 10, 0); - - // 创建提示文本标签 - lv_obj_t* label = lv_label_create(low_battery_popup_); - lv_label_set_text(label, "电量过低,请充电"); - lv_obj_set_style_text_color(label, lv_color_white(), 0); - lv_obj_center(label); - } - - // 显示弹出窗口 - lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - - void HideLowBatteryPopup() { - DisplayLockGuard lock(this); - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } -}; - - class XINGZHI_CUBE_0_96OLED_WIFI : public WifiBoard { private: i2c_master_bus_handle_t display_i2c_bus_; Button boot_button_; Button volume_up_button_; Button volume_down_button_; - CustomDisplay* display_; + OledDisplay* display_; PowerSaveTimer* power_save_timer_; - PowerManager power_manager_; + PowerManager* power_manager_; esp_lcd_panel_io_handle_t panel_io_ = nullptr; esp_lcd_panel_handle_t panel_ = nullptr; + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + void InitializePowerSaveTimer() { rtc_gpio_init(GPIO_NUM_21); rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); @@ -121,6 +93,53 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); } + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + {&font_puhui_14_1, &font_awesome_14_1}); + } + void InitializeButtons() { boot_button_.OnClick([this]() { power_save_timer_->WakeUp(); @@ -176,10 +195,11 @@ public: XINGZHI_CUBE_0_96OLED_WIFI() : boot_button_(BOOT_BUTTON_GPIO), volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), - power_manager_(GPIO_NUM_38) { + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); InitializePowerSaveTimer(); InitializeDisplayI2c(); + InitializeSsd1306Display(); InitializeButtons(); InitializeIot(); } @@ -196,44 +216,12 @@ public: } virtual Display* GetDisplay() override { - static CustomDisplay display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, - &font_puhui_14_1, &font_awesome_14_1); - return &display; + return display_; } virtual bool GetBatteryLevel(int& level, bool& charging) override { - static int last_level = 0; - static bool last_charging = false; - - charging = power_manager_.IsCharging(); - if (charging != last_charging) { - power_save_timer_->WakeUp(); - } - - level = power_manager_.ReadBatteryLevel(charging != last_charging); - if (level != last_level || charging != last_charging) { - last_level = level; - last_charging = charging; - ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); - } - - static bool show_low_power_warning_ = false; - if (power_manager_.IsBatteryLevelSteady()) { - if (!charging) { - // 电量低于 15% 时,显示低电量警告 - if (!show_low_power_warning_ && level <= 15) { - display_->ShowLowBatteryPopup(); - show_low_power_warning_ = true; - } - power_save_timer_->SetEnabled(true); - } else { - if (show_low_power_warning_) { - display_->HideLowBatteryPopup(); - show_low_power_warning_ = false; - } - power_save_timer_->SetEnabled(false); - } - } + charging = power_manager_->IsCharging(); + level = power_manager_->GetBatteryLevel(); return true; } diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc index a0f9e22a..dbb7b919 100644 --- a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc +++ b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc @@ -23,58 +23,28 @@ LV_FONT_DECLARE(font_puhui_20_4); LV_FONT_DECLARE(font_awesome_20_4); -class CustomDisplay : public SpiLcdDisplay { -private: - lv_obj_t* low_battery_popup_ = nullptr; - -public: - CustomDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, - { - .text_font = &font_puhui_20_4, - .icon_font = &font_awesome_20_4, - .emoji_font = font_emoji_64_init(), - }) { - } - - void ShowLowBatteryPopup() { - DisplayLockGuard lock(this); - if (low_battery_popup_ == nullptr) { - low_battery_popup_ = lv_obj_create(lv_screen_active()); - lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, LV_VER_RES * 0.5); - lv_obj_center(low_battery_popup_); - lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); - lv_obj_set_style_radius(low_battery_popup_, 10, 0); - - lv_obj_t* label = lv_label_create(low_battery_popup_); - lv_label_set_text(label, "电量过低,请充电"); - lv_obj_set_style_text_color(label, lv_color_white(), 0); - lv_obj_center(label); - } - lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - - void HideLowBatteryPopup() { - DisplayLockGuard lock(this); - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } -}; - - class XINGZHI_CUBE_1_54TFT_ML307 : public Ml307Board { private: Button boot_button_; Button volume_up_button_; Button volume_down_button_; - CustomDisplay* display_; + SpiLcdDisplay* display_; PowerSaveTimer* power_save_timer_; - PowerManager power_manager_; + PowerManager* power_manager_; esp_lcd_panel_io_handle_t panel_io_ = nullptr; esp_lcd_panel_handle_t panel_ = nullptr; + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + void InitializePowerSaveTimer() { rtc_gpio_init(GPIO_NUM_21); rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); @@ -83,15 +53,13 @@ private: power_save_timer_ = new PowerSaveTimer(-1, 60, 300); power_save_timer_->OnEnterSleepMode([this]() { ESP_LOGI(TAG, "Enabling sleep mode"); - auto display = GetDisplay(); - display->SetChatMessage("system", ""); - display->SetEmotion("sleepy"); + display_->SetChatMessage("system", ""); + display_->SetEmotion("sleepy"); GetBacklight()->SetBrightness(1); }); power_save_timer_->OnExitSleepMode([this]() { - auto display = GetDisplay(); - display->SetChatMessage("system", ""); - display->SetEmotion("neutral"); + display_->SetChatMessage("system", ""); + display_->SetEmotion("neutral"); GetBacklight()->RestoreBrightness(); }); power_save_timer_->OnShutdownRequest([this]() { @@ -182,8 +150,13 @@ private: ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); - display_ = new CustomDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + 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(), + }); } void InitializeIot() { @@ -198,8 +171,8 @@ public: Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096), boot_button_(BOOT_BUTTON_GPIO), volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), - power_manager_(GPIO_NUM_38) { + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); InitializePowerSaveTimer(); InitializeSpi(); InitializeButtons(); @@ -224,38 +197,8 @@ public: } virtual bool GetBatteryLevel(int& level, bool& charging) override { - static int last_level = 0; - static bool last_charging = false; - - charging = power_manager_.IsCharging(); - if (charging != last_charging) { - power_save_timer_->WakeUp(); - } - - level = power_manager_.ReadBatteryLevel(charging != last_charging); - if (level != last_level || charging != last_charging) { - last_level = level; - last_charging = charging; - ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); - } - - static bool show_low_power_warning_ = false; - if (power_manager_.IsBatteryLevelSteady()) { - if (!charging) { - // 电量低于 15% 时,显示低电量警告 - if (!show_low_power_warning_ && level <= 15) { - display_->ShowLowBatteryPopup(); - show_low_power_warning_ = true; - } - power_save_timer_->SetEnabled(true); - } else { - if (show_low_power_warning_) { - display_->HideLowBatteryPopup(); - show_low_power_warning_ = false; - } - power_save_timer_->SetEnabled(false); - } - } + charging = power_manager_->IsCharging(); + level = power_manager_->GetBatteryLevel(); return true; } diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h b/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h index 6f32f06f..3c1bfaa1 100644 --- a/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h +++ b/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h @@ -1,66 +1,60 @@ #pragma once #include +#include +#include #include #include class PowerManager { private: + esp_timer_handle_t timer_handle_; + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + gpio_num_t charging_pin_ = GPIO_NUM_NC; std::vector adc_values_; uint32_t battery_level_ = 0; + bool is_charging_ = false; + bool is_low_battery_ = false; int ticks_ = 0; - const int kBatteryCheckInterval = 60; + const int kBatteryAdcInterval = 60; const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - } + adc_oneshot_unit_handle_t adc_handle_; - uint16_t ReadBatteryAdcData() { - adc_oneshot_unit_handle_t adc_handle; - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_2, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - // 初始化 ADC 单元 - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - // 配置 ADC 通道 - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_6, &chan_config)); - int adc_value; - // 读取 ADC 值 - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_6, &adc_value)); - adc_oneshot_del_unit(adc_handle); - return adc_value; - } - - bool IsBatteryLevelSteady() { - return adc_values_.size() >= kBatteryAdcDataCount; - } - - uint8_t ReadBatteryLevel(bool update_immediately = false) { - ticks_++; - if (!update_immediately && adc_values_.size() >= kBatteryAdcDataCount) { - // 每隔一段时间检查一次电量 - if (ticks_ % kBatteryCheckInterval != 0) { - return battery_level_; + void CheckBatteryStatus() { + // Get charging status + bool new_charging_status = gpio_get_level(charging_pin_) == 1; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); } + ReadBatteryAdcData(); + return; } - uint16_t adc_value = ReadBatteryAdcData(); + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_6, &adc_value)); + + // 将 ADC 值添加到队列中 adc_values_.push_back(adc_value); if (adc_values_.size() > kBatteryAdcDataCount) { adc_values_.erase(adc_values_.begin()); @@ -76,12 +70,12 @@ public: uint16_t adc; uint8_t level; } levels[] = { - {1900, 0}, // 小于1900时为0% - {2000, 20}, // 1970起点为20% - {2100, 40}, // 2100为40% - {2200, 60}, // 2200为60% - {2300, 80}, // 2300为80% - {2400, 100} // 2400及以上为100% + {1970, 0}, + {2062, 20}, + {2154, 40}, + {2246, 60}, + {2338, 80}, + {2430, 100} }; // 低于最低值时 @@ -101,12 +95,87 @@ public: } } } - return battery_level_; + + // Check low battery status + if (adc_values_.size() >= kBatteryAdcDataCount) { + bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; + if (new_low_battery_status != is_low_battery_) { + is_low_battery_ = new_low_battery_status; + if (on_low_battery_status_changed_) { + on_low_battery_status_changed_(is_low_battery_); + } + } + } + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + + // 创建电池电量检查定时器 + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + PowerManager* self = static_cast(arg); + self->CheckBatteryStatus(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "battery_check_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); + + // 初始化 ADC + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_2, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); + + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_6, &chan_config)); + } + + ~PowerManager() { + if (timer_handle_) { + esp_timer_stop(timer_handle_); + esp_timer_delete(timer_handle_); + } + if (adc_handle_) { + adc_oneshot_del_unit(adc_handle_); + } } bool IsCharging() { - int charging_level = gpio_get_level(charging_pin_); - return (charging_level == 1); + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + uint8_t GetBatteryLevel() { + return battery_level_; + } + + void OnLowBatteryStatusChanged(std::function callback) { + on_low_battery_status_changed_ = callback; + } + + void OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; } }; - \ No newline at end of file diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc index e3c07d68..c49f8db9 100644 --- a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc +++ b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc @@ -24,58 +24,28 @@ LV_FONT_DECLARE(font_puhui_20_4); LV_FONT_DECLARE(font_awesome_20_4); -class CustomDisplay : public SpiLcdDisplay { -private: - lv_obj_t* low_battery_popup_ = nullptr; - -public: - CustomDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, - { - .text_font = &font_puhui_20_4, - .icon_font = &font_awesome_20_4, - .emoji_font = font_emoji_64_init(), - }) { - } - - void ShowLowBatteryPopup() { - DisplayLockGuard lock(this); - if (low_battery_popup_ == nullptr) { - low_battery_popup_ = lv_obj_create(lv_screen_active()); - lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, LV_VER_RES * 0.5); - lv_obj_center(low_battery_popup_); - lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); - lv_obj_set_style_radius(low_battery_popup_, 10, 0); - - lv_obj_t* label = lv_label_create(low_battery_popup_); - lv_label_set_text(label, "电量过低,请充电"); - lv_obj_set_style_text_color(label, lv_color_white(), 0); - lv_obj_center(label); - } - lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - - void HideLowBatteryPopup() { - DisplayLockGuard lock(this); - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } -}; - - class XINGZHI_CUBE_1_54TFT_WIFI : public WifiBoard { private: Button boot_button_; Button volume_up_button_; Button volume_down_button_; - CustomDisplay* display_; + SpiLcdDisplay* display_; PowerSaveTimer* power_save_timer_; - PowerManager power_manager_; + PowerManager* power_manager_; esp_lcd_panel_io_handle_t panel_io_ = nullptr; esp_lcd_panel_handle_t panel_ = nullptr; + void InitializePowerManager() { + power_manager_ = new PowerManager(GPIO_NUM_38); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + void InitializePowerSaveTimer() { rtc_gpio_init(GPIO_NUM_21); rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); @@ -84,15 +54,13 @@ private: power_save_timer_ = new PowerSaveTimer(-1, 60, 300); power_save_timer_->OnEnterSleepMode([this]() { ESP_LOGI(TAG, "Enabling sleep mode"); - auto display = GetDisplay(); - display->SetChatMessage("system", ""); - display->SetEmotion("sleepy"); + display_->SetChatMessage("system", ""); + display_->SetEmotion("sleepy"); GetBacklight()->SetBrightness(1); }); power_save_timer_->OnExitSleepMode([this]() { - auto display = GetDisplay(); - display->SetChatMessage("system", ""); - display->SetEmotion("neutral"); + display_->SetChatMessage("system", ""); + display_->SetEmotion("neutral"); GetBacklight()->RestoreBrightness(); }); power_save_timer_->OnShutdownRequest([this]() { @@ -186,8 +154,13 @@ private: ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); - display_ = new CustomDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + 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(), + }); } void InitializeIot() { @@ -201,8 +174,8 @@ public: XINGZHI_CUBE_1_54TFT_WIFI() : boot_button_(BOOT_BUTTON_GPIO), volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), - power_manager_(GPIO_NUM_38) { + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + InitializePowerManager(); InitializePowerSaveTimer(); InitializeSpi(); InitializeButtons(); @@ -227,38 +200,8 @@ public: } virtual bool GetBatteryLevel(int& level, bool& charging) override { - static int last_level = 0; - static bool last_charging = false; - - charging = power_manager_.IsCharging(); - if (charging != last_charging) { - power_save_timer_->WakeUp(); - } - - level = power_manager_.ReadBatteryLevel(charging != last_charging); - if (level != last_level || charging != last_charging) { - last_level = level; - last_charging = charging; - ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); - } - - static bool show_low_power_warning_ = false; - if (power_manager_.IsBatteryLevelSteady()) { - if (!charging) { - // 电量低于 15% 时,显示低电量警告 - if (!show_low_power_warning_ && level <= 15) { - display_->ShowLowBatteryPopup(); - show_low_power_warning_ = true; - } - power_save_timer_->SetEnabled(true); - } else { - if (show_low_power_warning_) { - display_->HideLowBatteryPopup(); - show_low_power_warning_ = false; - } - power_save_timer_->SetEnabled(false); - } - } + charging = power_manager_->IsCharging(); + level = power_manager_->GetBatteryLevel(); return true; } diff --git a/main/boards/xmini-c3/xmini_c3_board.cc b/main/boards/xmini-c3/xmini_c3_board.cc index 01fdc22c..156dce27 100644 --- a/main/boards/xmini-c3/xmini_c3_board.cc +++ b/main/boards/xmini-c3/xmini_c3_board.cc @@ -1,6 +1,6 @@ #include "wifi_board.h" #include "audio_codecs/es8311_audio_codec.h" -#include "display/ssd1306_display.h" +#include "display/oled_display.h" #include "application.h" #include "button.h" #include "led/single_led.h" @@ -14,6 +14,8 @@ #include #include #include +#include +#include #define TAG "XminiC3Board" @@ -23,6 +25,9 @@ LV_FONT_DECLARE(font_awesome_14_1); class XminiC3Board : public WifiBoard { private: i2c_master_bus_handle_t codec_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + OledDisplay* display_ = nullptr; Button boot_button_; bool press_to_talk_enabled_ = false; PowerSaveTimer* power_save_timer_; @@ -66,6 +71,53 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); } + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; + + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_)); + + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + {&font_puhui_14_1, &font_awesome_14_1}); + } + void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); @@ -105,6 +157,7 @@ public: esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); InitializeCodecI2c(); + InitializeSsd1306Display(); InitializeButtons(); InitializePowerSaveTimer(); InitializeIot(); @@ -116,9 +169,7 @@ public: } virtual Display* GetDisplay() override { - static Ssd1306Display display(codec_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, - &font_puhui_14_1, &font_awesome_14_1); - return &display; + return display_; } virtual AudioCodec* GetAudioCodec() override { diff --git a/main/display/display.cc b/main/display/display.cc index 20de5f84..7acc3a52 100644 --- a/main/display/display.cc +++ b/main/display/display.cc @@ -2,6 +2,7 @@ #include #include #include +#include #include "display.h" #include "board.h" @@ -9,6 +10,7 @@ #include "font_awesome_symbols.h" #include "audio_codec.h" #include "settings.h" +#include "assets/lang_config.h" #define TAG "Display" @@ -146,6 +148,21 @@ void Display::Update() { battery_icon_ = icon; lv_label_set_text(battery_label_, battery_icon_); } + + if (low_battery_popup_ != nullptr) { + if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0) { + if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { + lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + auto& app = Application::GetInstance(); + app.PlaySound(Lang::Sounds::P3_LOW_BATTERY); + } + } else { + // Hide the low battery popup when the battery is not empty + if (!lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } + } } // 升级固件时,不读取 4G 网络状态,避免占用 UART 资源 diff --git a/main/display/display.h b/main/display/display.h index 86ffe34f..3e816e19 100644 --- a/main/display/display.h +++ b/main/display/display.h @@ -43,6 +43,8 @@ protected: lv_obj_t *mute_label_ = nullptr; lv_obj_t *battery_label_ = nullptr; lv_obj_t* chat_message_label_ = nullptr; + lv_obj_t* low_battery_popup_ = nullptr; + const char* battery_icon_ = nullptr; const char* network_icon_ = nullptr; bool muted_ = false; diff --git a/main/display/lcd_display.cc b/main/display/lcd_display.cc index 4b00b6d7..9cb4dba7 100644 --- a/main/display/lcd_display.cc +++ b/main/display/lcd_display.cc @@ -246,6 +246,18 @@ void LcdDisplay::SetupUI() { battery_label_ = lv_label_create(status_bar_); lv_label_set_text(battery_label_, ""); lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); + + low_battery_popup_ = lv_obj_create(screen); + lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, fonts_.text_font->line_height * 2); + lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + lv_obj_t* low_battery_label = lv_label_create(low_battery_popup_); + lv_label_set_text(low_battery_label, Lang::Strings::BATTERY_NEED_CHARGE); + lv_obj_set_style_text_color(low_battery_label, lv_color_white(), 0); + lv_obj_center(low_battery_label); + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); } void LcdDisplay::SetEmotion(const char* emotion) { diff --git a/main/display/ssd1306_display.cc b/main/display/oled_display.cc similarity index 78% rename from main/display/ssd1306_display.cc rename to main/display/oled_display.cc index 13c77882..4ec21bd3 100644 --- a/main/display/ssd1306_display.cc +++ b/main/display/oled_display.cc @@ -1,20 +1,18 @@ -#include "ssd1306_display.h" +#include "oled_display.h" #include "font_awesome_symbols.h" #include #include -#include -#include #include #include "assets/lang_config.h" -#define TAG "Ssd1306Display" +#define TAG "OledDisplay" LV_FONT_DECLARE(font_awesome_30_1); -Ssd1306Display::Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, - const lv_font_t* text_font, const lv_font_t* icon_font) - : text_font_(text_font), icon_font_(icon_font) { +OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, bool mirror_x, bool mirror_y, DisplayFonts fonts) + : panel_io_(panel_io), panel_(panel), fonts_(fonts) { width_ = width; height_ = height; @@ -23,48 +21,6 @@ Ssd1306Display::Ssd1306Display(void* i2c_master_handle, int width, int height, b port_cfg.task_priority = 1; lvgl_port_init(&port_cfg); - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2((i2c_master_bus_t*)i2c_master_handle, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(height_), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - return; - } - - // 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_, @@ -103,7 +59,7 @@ Ssd1306Display::Ssd1306Display(void* i2c_master_handle, int width, int height, b } } -Ssd1306Display::~Ssd1306Display() { +OledDisplay::~OledDisplay() { if (content_ != nullptr) { lv_obj_del(content_); } @@ -126,15 +82,15 @@ Ssd1306Display::~Ssd1306Display() { lvgl_port_deinit(); } -bool Ssd1306Display::Lock(int timeout_ms) { +bool OledDisplay::Lock(int timeout_ms) { return lvgl_port_lock(timeout_ms); } -void Ssd1306Display::Unlock() { +void OledDisplay::Unlock() { lvgl_port_unlock(); } -void Ssd1306Display::SetChatMessage(const char* role, const char* content) { +void OledDisplay::SetChatMessage(const char* role, const char* content) { DisplayLockGuard lock(this); if (chat_message_label_ == nullptr) { return; @@ -151,11 +107,11 @@ void Ssd1306Display::SetChatMessage(const char* role, const char* content) { } } -void Ssd1306Display::SetupUI_128x64() { +void OledDisplay::SetupUI_128x64() { DisplayLockGuard lock(this); auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font_, 0); + lv_obj_set_style_text_font(screen, fonts_.text_font, 0); lv_obj_set_style_text_color(screen, lv_color_black(), 0); /* Container */ @@ -226,7 +182,7 @@ void Ssd1306Display::SetupUI_128x64() { network_label_ = lv_label_create(status_bar_); lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font_, 0); + lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0); notification_label_ = lv_label_create(status_bar_); lv_obj_set_flex_grow(notification_label_, 1); @@ -241,18 +197,30 @@ void Ssd1306Display::SetupUI_128x64() { mute_label_ = lv_label_create(status_bar_); lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font_, 0); + lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0); battery_label_ = lv_label_create(status_bar_); lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, icon_font_, 0); + lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); + + low_battery_popup_ = lv_obj_create(screen); + lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF); + lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, fonts_.text_font->line_height * 2); + lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + lv_obj_t* low_battery_label = lv_label_create(low_battery_popup_); + lv_label_set_text(low_battery_label, Lang::Strings::BATTERY_NEED_CHARGE); + lv_obj_set_style_text_color(low_battery_label, lv_color_white(), 0); + lv_obj_center(low_battery_label); + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); } -void Ssd1306Display::SetupUI_128x32() { +void OledDisplay::SetupUI_128x32() { DisplayLockGuard lock(this); auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font_, 0); + lv_obj_set_style_text_font(screen, fonts_.text_font, 0); /* Container */ container_ = lv_obj_create(screen); @@ -294,15 +262,15 @@ void Ssd1306Display::SetupUI_128x32() { network_label_ = lv_label_create(status_bar_); lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font_, 0); + lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0); mute_label_ = lv_label_create(status_bar_); lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font_, 0); + lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0); battery_label_ = lv_label_create(status_bar_); lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, icon_font_, 0); + lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); status_label_ = lv_label_create(status_bar_); lv_obj_set_style_pad_left(status_label_, 2, 0); diff --git a/main/display/ssd1306_display.h b/main/display/oled_display.h similarity index 61% rename from main/display/ssd1306_display.h rename to main/display/oled_display.h index 047f7ece..f6053728 100644 --- a/main/display/ssd1306_display.h +++ b/main/display/oled_display.h @@ -1,12 +1,12 @@ -#ifndef SSD1306_DISPLAY_H -#define SSD1306_DISPLAY_H +#ifndef OLED_DISPLAY_H +#define OLED_DISPLAY_H #include "display.h" #include #include -class Ssd1306Display : public Display { +class OledDisplay : public Display { private: esp_lcd_panel_io_handle_t panel_io_ = nullptr; esp_lcd_panel_handle_t panel_ = nullptr; @@ -18,8 +18,7 @@ private: lv_obj_t* container_ = nullptr; lv_obj_t* side_bar_ = nullptr; - const lv_font_t* text_font_ = nullptr; - const lv_font_t* icon_font_ = nullptr; + DisplayFonts fonts_; virtual bool Lock(int timeout_ms = 0) override; virtual void Unlock() override; @@ -28,11 +27,11 @@ private: void SetupUI_128x32(); public: - Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, - const lv_font_t* text_font, const lv_font_t* icon_font); - ~Ssd1306Display(); + OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, bool mirror_x, bool mirror_y, + DisplayFonts fonts); + ~OledDisplay(); virtual void SetChatMessage(const char* role, const char* content) override; }; -#endif // SSD1306_DISPLAY_H +#endif // OLED_DISPLAY_H diff --git a/scripts/convert_audio_to_p3.py b/scripts/convert_audio_to_p3.py index 1d38d6dd..5a92f829 100644 --- a/scripts/convert_audio_to_p3.py +++ b/scripts/convert_audio_to_p3.py @@ -24,7 +24,7 @@ def encode_audio_to_opus(input_file, output_file): audio = (audio * 32767).astype(np.int16) # Initialize Opus encoder - encoder = opuslib.Encoder(sample_rate, 1, opuslib.APPLICATION_VOIP) + encoder = opuslib.Encoder(sample_rate, 1, opuslib.APPLICATION_AUDIO) # Encode audio data to Opus packets # Save encoded data to file