diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index ec36261..f64701d 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -592,6 +592,11 @@ elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_4G) set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) set(BUILTIN_ICON_FONT font_awesome_16_4) set(DEFAULT_EMOJI_COLLECTION twemoji_32) +elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX3) + set(BOARD_TYPE "atk-dnesp32s3-box3") + set(BUILTIN_TEXT_FONT font_puhui_basic_20_4) + set(BUILTIN_ICON_FONT font_awesome_20_4) + set(DEFAULT_EMOJI_COLLECTION twemoji_64) elseif(CONFIG_BOARD_TYPE_DU_CHATX) set(BOARD_TYPE "du-chatx") set(BUILTIN_TEXT_FONT font_puhui_basic_16_4) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index fc1869b..6e2f37f 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -437,6 +437,9 @@ choice BOARD_TYPE config BOARD_TYPE_ATK_DNESP32S3M_4G bool "正点原子DNESP32S3M-4G" depends on IDF_TARGET_ESP32S3 + config BOARD_TYPE_ATK_DNESP32S3_BOX3 + bool "正点原子DNESP32S3-BOX3" + depends on IDF_TARGET_ESP32S3 config BOARD_TYPE_DU_CHATX bool "嘟嘟开发板CHATX(wifi)" depends on IDF_TARGET_ESP32S3 diff --git a/main/boards/atk-dnesp32s3-box3/atk_dnesp32s3_box3.cc b/main/boards/atk-dnesp32s3-box3/atk_dnesp32s3_box3.cc new file mode 100644 index 0000000..7bb1064 --- /dev/null +++ b/main/boards/atk-dnesp32s3-box3/atk_dnesp32s3_box3.cc @@ -0,0 +1,471 @@ +#include "wifi_board.h" +#include "audio_codec.h" +#include "codecs/es8311_audio_codec.h" +#include "codecs/box_audio_codec.h" +#include "codecs/no_audio_codec.h" +#include "display/lcd_display.h" +#include "application.h" +#include "button.h" +#include "config.h" +#include "led/single_led.h" + +#include "i2c_device.h" +#include "esp_video.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "power_save_timer.h" +#include "power_manager.h" +#include +#include +#include "esp_io_expander_tca95xx_16bit.h" +#include "assets/lang_config.h" +#include + +#define TAG "atk_dnesp32s3_box3" + +LV_FONT_DECLARE(font_puhui_20_4); +LV_FONT_DECLARE(font_awesome_20_4); + +class atk_dnesp32s3_box3 : public WifiBoard { +private: + i2c_master_bus_handle_t i2c_bus_; + LcdDisplay* display_; + EspVideo* camera_; + static atk_dnesp32s3_box3* instance_; + esp_io_expander_handle_t io_exp_handle; + button_handle_t btns; + button_driver_t* btn_driver_ = nullptr; + PowerSaveTimer* power_save_timer_; + PowerManager* power_manager_; + PowerSupply power_status_; + esp_timer_handle_t wake_timer_handle_; + int ticks_ = 0; + const int kChgCtrlInterval = 5; + + void InitializeBoardPowerManager() { + instance_ = this; + + if (IoExpanderGetLevel(XIO_BAT_CHRG) == 0) { + power_status_ = kDeviceTypecSupply; + } else { + power_status_ = kDeviceBatterySupply; + } + + esp_timer_create_args_t wake_display_timer_args = { + .callback = [](void *arg) { + atk_dnesp32s3_box3* self = static_cast(arg); + + self->ticks_ ++; + if (self->ticks_ % self->kChgCtrlInterval == 0) { + if (self->IoExpanderGetLevel(XIO_BAT_CHRG) == 0) { + self->power_status_ = kDeviceTypecSupply; + } else { + self->power_status_ = kDeviceBatterySupply; + } + + /* 低于某个电量,会自动关机 */ + if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) { + esp_timer_stop(self->power_manager_->timer_handle_); + + esp_io_expander_set_dir(self->io_exp_handle, XIO_BAT_CHRG_EN, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(self->io_exp_handle, XIO_BAT_CHRG_EN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + + esp_io_expander_set_dir(self->io_exp_handle, XIO_BAT_CHRG_EN, IO_EXPANDER_INPUT); + esp_io_expander_set_level(self->io_exp_handle, XIO_BAT_CHRG_EN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + } + } + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "wake_update_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000)); + } + + void InitializePowerManager() { + power_manager_ = new PowerManager(io_exp_handle); + power_manager_->OnChargingStatusChanged([this](bool is_charging) { + if (is_charging) { + power_save_timer_->SetEnabled(false); + } else { + power_save_timer_->SetEnabled(true); + } + }); + } + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, -1, -1); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + if (power_status_ == kDeviceBatterySupply) { + GetBacklight()->SetBrightness(0); + esp_timer_stop(power_manager_ ->timer_handle_); + esp_io_expander_set_dir(io_exp_handle, XIO_BAT_CHRG_EN, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(io_exp_handle, XIO_BAT_CHRG_EN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + esp_io_expander_set_level(io_exp_handle, XIO_VDD_3V3_EN, 0); + } + }); + + power_save_timer_->SetEnabled(true); + } + + void audio_volume_change(bool direction) { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume(); + + if (direction) { + volume += 10; + if (volume > 100) { + volume = 100; + } + codec->SetOutputVolume(volume); + } else { + volume -= 10; + if (volume < 0) { + volume = 0; + } + codec->SetOutputVolume(volume); + } + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); + } + + void audio_volume_minimum(){ + GetAudioCodec()->SetOutputVolume(0); + GetDisplay()->ShowNotification(Lang::Strings::MUTED); + } + + void audio_volume_maxmum(){ + GetAudioCodec()->SetOutputVolume(100); + GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); + } + + esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) { + return esp_io_expander_set_level(io_exp_handle, pin_mask, level); + } + + uint8_t IoExpanderGetLevel(uint16_t pin_mask) { + uint32_t pin_val = 0; + esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val); + pin_mask &= DRV_IO_EXP_INPUT_MASK; + return (uint8_t)((pin_val & pin_mask) ? 1 : 0); + } + + void InitializeIoExpander() { + esp_err_t ret = ESP_OK; + esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, AW9523B_ADDR, &io_exp_handle); + + // ret |= esp_io_expander_set_pullupdown(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_PULL_NONE); + + ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT); + ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT); + + ret |= esp_io_expander_set_level(io_exp_handle, XIO_VDD_2V8_EN, 1); /* 0308 */ + ret |= esp_io_expander_set_level(io_exp_handle, XIO_VDD_3V3_EN, 1); /* 电源 */ + ret |= esp_io_expander_set_level(io_exp_handle, XIO_ESP_ADC_SEL, 1); /* ADC */ + ret |= esp_io_expander_set_level(io_exp_handle, XIO_VDDA_3V3_EN, 1);/* 音频电源 */ + ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBAT_EN, 1); /* 音频 */ + ret |= esp_io_expander_set_level(io_exp_handle, XIO_PA_CTRL, 1); /* 音频功放 */ + ret |= esp_io_expander_set_level(io_exp_handle, XIO_LCD_BL, 0); /* LCD背光 */ + + assert(ret == ESP_OK); + } + + void InitializeI2c() { + // Initialize I2C peripheral + i2c_master_bus_config_t i2c_bus_cfg = { + .i2c_port = (i2c_port_t)I2C_NUM_0, + .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, + .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); + } + + // Initialize spi peripheral + void InitializeSpi() { + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = LCD_MOSI_PIN; + buscfg.miso_io_num = LCD_MISO_PIN; + buscfg.sclk_io_num = LCD_SCLK_PIN; + buscfg.quadwp_io_num = GPIO_NUM_NC; + buscfg.quadhd_io_num = GPIO_NUM_NC; + buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); + } + + void InitializeSt7789Display() { + esp_lcd_panel_io_handle_t panel_io = nullptr; + esp_lcd_panel_handle_t panel = nullptr; + ESP_LOGD(TAG, "Install panel IO"); + // 液晶屏控制IO初始化 + esp_lcd_panel_io_spi_config_t io_config = {}; + io_config.cs_gpio_num = LCD_CS_PIN; + io_config.dc_gpio_num = LCD_DC_PIN; + io_config.spi_mode = 0; + io_config.pclk_hz = 60 * 1000 * 1000; + io_config.trans_queue_depth = 7; + io_config.lcd_cmd_bits = 8; + io_config.lcd_param_bits = 8; + esp_lcd_new_panel_io_spi(SPI2_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; + panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG, + esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel); + esp_lcd_panel_reset(panel); + + esp_lcd_panel_init(panel); + esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); + esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + + display_ = new SpiLcdDisplay(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + } + + void InitializeButtons() { + instance_ = this; + + button_config_t bo_btn_cfg = { + .long_press_time = 800, + .short_press_time = 500 + }; + + button_config_t k1_btn_cfg = { + .long_press_time = 800, + .short_press_time = 500 + }; + + button_config_t k2_btn_cfg = { + .long_press_time = 800, + .short_press_time = 500 + }; + + button_driver_t* xio_k1_btn_driver_ = nullptr; + button_driver_t* xio_k2_btn_driver_ = nullptr; + + button_handle_t bo_btn_handle = NULL; + button_handle_t k1_btn_handle = NULL; + button_handle_t k2_btn_handle = NULL; + + xio_k1_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + xio_k1_btn_driver_->enable_power_save = false; + xio_k1_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return !instance_->IoExpanderGetLevel(XIO_KEY_K1); + }; + ESP_ERROR_CHECK(iot_button_create(&k1_btn_cfg, xio_k1_btn_driver_, &k1_btn_handle)); + + xio_k2_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); + xio_k2_btn_driver_->enable_power_save = false; + xio_k2_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { + return instance_->IoExpanderGetLevel(XIO_KEY_K2); + }; + ESP_ERROR_CHECK(iot_button_create(&k2_btn_cfg, xio_k2_btn_driver_, &k2_btn_handle)); + + button_gpio_config_t bo_cfg = { + .gpio_num = BOOT_BUTTON_GPIO, + .active_level = BUTTON_INACTIVE, + .enable_power_save = false, + .disable_pull = false + }; + ESP_ERROR_CHECK(iot_button_new_gpio_device(&bo_btn_cfg, &bo_cfg, &bo_btn_handle)); + + iot_button_register_cb(k1_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_change(false); + }, this); + + iot_button_register_cb(k1_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_minimum(); + }, this); + + iot_button_register_cb(k2_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + app.ToggleChatState(); + }, this); + + iot_button_register_cb(k2_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting) { + self->EnterWifiConfigMode(); + return; + } + + if (self->power_status_ == kDeviceBatterySupply) { + auto backlight = Board::GetInstance().GetBacklight(); + backlight->SetBrightness(0); + esp_timer_stop(self->power_manager_->timer_handle_); + esp_io_expander_set_dir(self->io_exp_handle, XIO_BAT_CHRG_EN, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(self->io_exp_handle, XIO_BAT_CHRG_EN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + esp_io_expander_set_level(self->io_exp_handle, XIO_VDD_3V3_EN, 0); + vTaskDelay(pdMS_TO_TICKS(100)); + } + }, this); + + iot_button_register_cb(bo_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_change(true); + }, this); + + iot_button_register_cb(bo_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { + auto self = static_cast(usr_data); + self->power_save_timer_->WakeUp(); + self->audio_volume_maxmum(); + }, this); + } + + /* 初始化摄像头:GC0308; */ + /* 根据正点原子官方示例参数 */ + void InitializeCamera() { + esp_io_expander_set_level(io_exp_handle, XIO_TP_CAM_RESET, 0); /* 确保复位 */ + vTaskDelay(pdMS_TO_TICKS(50)); /* 延长复位保持时间 */ + esp_io_expander_set_level(io_exp_handle, XIO_TP_CAM_RESET, 1); /* 释放复位 */ + vTaskDelay(pdMS_TO_TICKS(50)); /* 延长 50ms */ + + /* DVP pin configuration */ + static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = { + .data_width = CAM_CTLR_DATA_WIDTH_8, + .data_io = { + [0] = CAM_PIN_D0, + [1] = CAM_PIN_D1, + [2] = CAM_PIN_D2, + [3] = CAM_PIN_D3, + [4] = CAM_PIN_D4, + [5] = CAM_PIN_D5, + [6] = CAM_PIN_D6, + [7] = CAM_PIN_D7, + }, + .vsync_io = CAM_PIN_VSYNC, + .de_io = CAM_PIN_LREF, + .pclk_io = CAM_PIN_PCLK, + .xclk_io = CAM_PIN_XCLK, + }; + + /* 复用 I2C 总线 */ + esp_video_init_sccb_config_t sccb_config = { + .init_sccb = false, /* 不初始化新的 SCCB,使用现有的 I2C 总线 */ + .i2c_handle = i2c_bus_, /* 使用现有的 I2C 总线句柄 */ + .freq = 100000, /* SCCB 通信频率,通常为 100kHz */ + }; + + esp_video_init_dvp_config_t dvp_config = { + .sccb_config = sccb_config, + .reset_pin = CAM_PIN_RESET, + .pwdn_pin = CAM_PIN_PWDN, + .dvp_pin = dvp_pin_config, + .xclk_freq = 24000000, + }; + + esp_video_init_config_t video_config = { + .dvp = &dvp_config, + }; + + camera_ = new EspVideo(video_config); + } + +public: + atk_dnesp32s3_box3(){ + InitializeI2c(); + InitializeSpi(); + InitializeIoExpander(); + InitializePowerSaveTimer(); + //InitializePowerManager(); + InitializeSt7789Display(); + InitializeButtons(); + //GetBacklight()->RestoreBrightness(); + //InitializeBoardPowerManager(); + InitializeCamera(); + } + + virtual AudioCodec* GetAudioCodec() override { + static BoxAudioCodec audio_codec( + i2c_bus_, + AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_GPIO_MCLK, + AUDIO_I2S_GPIO_BCLK, + AUDIO_I2S_GPIO_WS, + AUDIO_I2S_GPIO_DOUT, + AUDIO_I2S_GPIO_DIN, + AUDIO_CODEC_PA_PIN, + AUDIO_CODEC_ES8311_ADDR, + AUDIO_CODEC_ES7210_ADDR, + AUDIO_INPUT_REFERENCE); + return &audio_codec; + } + + virtual Display* GetDisplay() override { + return display_; + } + + // virtual Backlight* GetBacklight() override { + // static PwmBacklight backlight(GPIO_NUM_0, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + // return &backlight; + // } + + // virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + // static bool last_discharging = false; + // charging = power_manager_->IsCharging(); + // discharging = power_manager_->IsDischarging(); + // if (discharging != last_discharging) { + // power_save_timer_->SetEnabled(discharging); + // last_discharging = discharging; + // } + // level = power_manager_->GetBatteryLevel(); + // return true; + // } + + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveLevel(level); + } + + virtual Camera* GetCamera() override { + return camera_; + } +}; + +DECLARE_BOARD(atk_dnesp32s3_box3); + +// 定义静态成员变量 +atk_dnesp32s3_box3* atk_dnesp32s3_box3::instance_ = nullptr; diff --git a/main/boards/atk-dnesp32s3-box3/config.h b/main/boards/atk-dnesp32s3-box3/config.h new file mode 100644 index 0000000..e418590 --- /dev/null +++ b/main/boards/atk-dnesp32s3-box3/config.h @@ -0,0 +1,94 @@ +#ifndef _BOARD_CONFIG_H_ +#define _BOARD_CONFIG_H_ + +#include + +enum PowerSupply { + kDeviceTypecSupply, + kDeviceBatterySupply, +}; + +#define AUDIO_INPUT_SAMPLE_RATE 24000 +#define AUDIO_OUTPUT_SAMPLE_RATE 24000 + +#define AUDIO_INPUT_REFERENCE true + +#define AUDIO_I2S_GPIO_WS GPIO_NUM_39 +#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_38 +#define AUDIO_I2S_GPIO_DIN GPIO_NUM_41 +#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_40 +#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_21 + +#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC +#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3 +#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 +#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR +#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR + +#define BUILTIN_LED_GPIO GPIO_NUM_NC +#define BOOT_BUTTON_GPIO GPIO_NUM_0 + +#define DISPLAY_OFFSET_X 0 +#define DISPLAY_OFFSET_Y 0 + +#define DISPLAY_WIDTH 320 +#define DISPLAY_HEIGHT 240 +#define DISPLAY_SWAP_XY true +#define DISPLAY_MIRROR_X true +#define DISPLAY_MIRROR_Y false + +#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC +#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true + +// Pin Definitions +#define LCD_SCLK_PIN GPIO_NUM_15 +#define LCD_MOSI_PIN GPIO_NUM_16 +#define LCD_MISO_PIN GPIO_NUM_17 +#define LCD_DC_PIN GPIO_NUM_48 +#define LCD_CS_PIN GPIO_NUM_47 + +/* IO扩展 */ +#define AW9523B_ADDR 0x59 +#define AW9523B_INT_GPIO GPIO_NUM_42 +#define XIO_KEY_K1 (IO_EXPANDER_PIN_NUM_0) +#define XIO_KEY_K2 (IO_EXPANDER_PIN_NUM_1) +#define XIO_BAT_CHRG_EN (IO_EXPANDER_PIN_NUM_2) +#define XIO_BAT_CHRG (IO_EXPANDER_PIN_NUM_3) +#define XIO_ESP_ADC_SEL (IO_EXPANDER_PIN_NUM_4) +#define XIO_PA_CTRL (IO_EXPANDER_PIN_NUM_5) +#define XIO_EXT_GPIO0 (IO_EXPANDER_PIN_NUM_6) +#define XIO_EXT_GPIO1 (IO_EXPANDER_PIN_NUM_7) +#define XIO_LCD_BL (IO_EXPANDER_PIN_NUM_8) +#define XIO_LED_RED (IO_EXPANDER_PIN_NUM_9) +#define XIO_LED_BLUE (IO_EXPANDER_PIN_NUM_10) +#define XIO_VDD_3V3_EN (IO_EXPANDER_PIN_NUM_11) +#define XIO_VBAT_EN (IO_EXPANDER_PIN_NUM_12) +#define XIO_VDDA_3V3_EN (IO_EXPANDER_PIN_NUM_13) +#define XIO_VDD_2V8_EN (IO_EXPANDER_PIN_NUM_14) +#define XIO_TP_CAM_RESET (IO_EXPANDER_PIN_NUM_15) + +#define DRV_IO_EXP_OUTPUT_MASK 0XFFFC +#define DRV_IO_EXP_INPUT_MASK 0x0003 + +/* 相机引脚配置 */ +#define CAM_PIN_PWDN GPIO_NUM_NC +#define CAM_PIN_RESET GPIO_NUM_NC +#define CAM_PIN_VSYNC GPIO_NUM_6 +#define CAM_PIN_LREF GPIO_NUM_46 +#define CAM_PIN_PCLK GPIO_NUM_45 +#define CAM_PIN_XCLK GPIO_NUM_NC +#define CAM_PIN_SIOD GPIO_NUM_NC +#define CAM_PIN_SIOC GPIO_NUM_NC +#define CAM_PIN_D0 GPIO_NUM_7 +#define CAM_PIN_D1 GPIO_NUM_8 +#define CAM_PIN_D2 GPIO_NUM_9 +#define CAM_PIN_D3 GPIO_NUM_10 +#define CAM_PIN_D4 GPIO_NUM_11 +#define CAM_PIN_D5 GPIO_NUM_12 +#define CAM_PIN_D6 GPIO_NUM_4 +#define CAM_PIN_D7 GPIO_NUM_5 +#define CAM_2V8_EN 14 +#define CAM_RST 15 + + +#endif // _BOARD_CONFIG_H_ diff --git a/main/boards/atk-dnesp32s3-box3/config.json b/main/boards/atk-dnesp32s3-box3/config.json new file mode 100644 index 0000000..2db50c3 --- /dev/null +++ b/main/boards/atk-dnesp32s3-box3/config.json @@ -0,0 +1,9 @@ +{ + "target": "esp32s3", + "builds": [ + { + "name": "atk-dnesp32s3-box3", + "sdkconfig_append": [] + } + ] +} \ No newline at end of file diff --git a/main/boards/atk-dnesp32s3-box3/power_manager.h b/main/boards/atk-dnesp32s3-box3/power_manager.h new file mode 100644 index 0000000..65cc3e4 --- /dev/null +++ b/main/boards/atk-dnesp32s3-box3/power_manager.h @@ -0,0 +1,195 @@ +#pragma once +#include +#include +#include "esp_io_expander_tca95xx_16bit.h" +#include +#include +#include + + +class PowerManager { +private: + std::function on_charging_status_changed_; + std::function on_low_battery_status_changed_; + esp_io_expander_handle_t aw9523b_; + uint32_t pin_val = 0; + 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 kBatteryAdcInterval = 60; + const int kBatteryAdcDataCount = 3; + const int kLowBatteryLevel = 20; + + adc_oneshot_unit_handle_t adc_handle_; + + void CheckBatteryStatus() { + // Get charging status + esp_io_expander_get_level(aw9523b_, DRV_IO_EXP_INPUT_MASK, &pin_val); + bool new_charging_status = ((uint8_t)((pin_val & XIO_BAT_CHRG) ? 1 : 0)) == 0; + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据不足,则读取电池电量数据 + if (adc_values_.size() < kBatteryAdcDataCount) { + ReadBatteryAdcData(); + return; + } + + // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 + ticks_++; + if (ticks_ % kBatteryAdcInterval == 0) { + ReadBatteryAdcData(); + } + } + + void ReadBatteryAdcData() { + int adc_value; + uint32_t temp_val = 0; + + esp_io_expander_set_dir(aw9523b_, XIO_BAT_CHRG_EN, IO_EXPANDER_OUTPUT); + esp_io_expander_set_level(aw9523b_, XIO_BAT_CHRG_EN, 0); + vTaskDelay(pdMS_TO_TICKS(500)); + + for(int t = 0; t < 10; t ++) { + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); + temp_val += adc_value; + } + + esp_io_expander_set_dir(aw9523b_, XIO_BAT_CHRG_EN, IO_EXPANDER_INPUT); + + adc_value = temp_val / 10; + + // 将 ADC 值添加到队列中 + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {2696, 0}, /* 3.48V -屏幕闪屏 */ + {2724, 20}, /* 3.53V */ + {2861, 40}, /* 3.7V */ + {3038, 60}, /* 3.90V */ + {3150, 80}, /* 4.02V */ + {3280, 100} /* 4.14V */ + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + + // 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_); + } + } + } + + low_voltage_ = adc_value; + + ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_); + } + +public: + esp_timer_handle_t timer_handle_; + uint16_t low_voltage_ = 2630; + PowerManager(esp_io_expander_handle_t aw9523b) : aw9523b_(aw9523b) { + // 创建电池电量检查定时器 + 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_1, + .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_0, &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() { + // 如果电量已经满了,则不再显示充电中 + if (battery_level_ == 100) { + return false; + } + return is_charging_; + } + + bool IsDischarging() { + // 没有区分充电和放电,所以直接返回相反状态 + 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; + } +}; diff --git a/main/idf_component.yml b/main/idf_component.yml index 5699e5c..9fbbb35 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -35,7 +35,7 @@ dependencies: espressif/button: ~4.1.5 espressif/knob: ^1.0.0 espressif/esp32-camera: - version: ^2.1.5 + version: ^2.1.6 rules: - if: target in [esp32s3] espressif/esp_video: