#include "wifi_board.h" #include "cores3_audio_codec.h" #include "display/lcd_display.h" #include "stackchan_display.h" #include "application.h" #include "config.h" #include "power_save_timer.h" #include "i2c_device.h" #include "axp2101.h" #include "settings.h" #include #include #include #include #include #include #include #include "stackchan_camera.h" #include "hal_bridge.h" #define TAG "M5Stack-StackChan-Board" #define XPOWERS_AXP2101_ICC_CHG_SET (0x62) class Pmic : public Axp2101 { public: /** * @brief axp2101 charge currnet voltage parameters. */ typedef enum __xpowers_axp2101_chg_curr { XPOWERS_AXP2101_CHG_CUR_0MA, XPOWERS_AXP2101_CHG_CUR_100MA = 4, XPOWERS_AXP2101_CHG_CUR_125MA, XPOWERS_AXP2101_CHG_CUR_150MA, XPOWERS_AXP2101_CHG_CUR_175MA, XPOWERS_AXP2101_CHG_CUR_200MA, XPOWERS_AXP2101_CHG_CUR_300MA, XPOWERS_AXP2101_CHG_CUR_400MA, XPOWERS_AXP2101_CHG_CUR_500MA, XPOWERS_AXP2101_CHG_CUR_600MA, XPOWERS_AXP2101_CHG_CUR_700MA, XPOWERS_AXP2101_CHG_CUR_800MA, XPOWERS_AXP2101_CHG_CUR_900MA, XPOWERS_AXP2101_CHG_CUR_1000MA, } xpowers_axp2101_chg_curr_t; // Power Init 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); // WriteReg(0x99, (0b11110 - 5)); WriteReg(0x97, (0b11110 - 2)); WriteReg(0x69, 0b00110101); WriteReg(0x30, 0b111111); WriteReg(0x90, 0xBF); WriteReg(0x94, 33 - 5); WriteReg(0x95, 33 - 5); WriteReg(0x27, 0x00); auto ret = setChargerConstantCurr(XPOWERS_AXP2101_CHG_CUR_700MA); if (!ret) { ESP_LOGE(TAG, "Set charge current failed"); } else { ESP_LOGI(TAG, "Set charge current success"); } SetBrightness(0); } void SetBrightness(uint8_t brightness) { if (brightness == 0) { // DLDO1 off uint8_t val = ReadReg(0x90); WriteReg(0x90, val & 0x7F); } else { // 映射计算:将 1~100 映射到 寄存器值 20~28 // 公式:MinReg + (input * (MaxReg - MinReg) / MaxInput) // 20 + (brightness * 8 / 100) if (brightness > 100) { brightness = 100; } uint8_t reg_val = 20 + ((uint16_t)brightness * 8 / 100); WriteReg(0x99, reg_val); // Make sure DLDO1 on uint8_t val = ReadReg(0x90); if (!(val & 0x80)) { WriteReg(0x90, val | 0x80); } } } /** * @brief Set charge current. * @param opt: See xpowers_axp2101_chg_curr_t enum for details. * @retval */ bool setChargerConstantCurr(uint8_t opt) { if (opt > XPOWERS_AXP2101_CHG_CUR_1000MA) { return false; } int val = ReadReg(XPOWERS_AXP2101_ICC_CHG_SET); if (val == -1) { return false; } val &= 0xE0; WriteReg(XPOWERS_AXP2101_ICC_CHG_SET, val | opt); return true; } }; class CustomBacklight : public Backlight { public: CustomBacklight(Pmic* pmic) : pmic_(pmic) { } void SetBrightnessImpl(uint8_t brightness) override { pmic_->SetBrightness(target_brightness_); brightness_ = target_brightness_; } private: Pmic* pmic_; }; class Aw9523 : public I2cDevice { public: // Exanpd IO Init Aw9523(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { WriteReg(0x02, 0b00000111); // P0 WriteReg(0x03, 0b10001111); // P1 WriteReg(0x04, 0b00011000); // CONFIG_P0 WriteReg(0x05, 0b00001100); // CONFIG_P1 WriteReg(0x11, 0b00010000); // GCR P0 port is Push-Pull mode. WriteReg(0x12, 0b11111111); // LEDMODE_P0 WriteReg(0x13, 0b11111111); // LEDMODE_P1 } void ResetAw88298() { ESP_LOGI(TAG, "Reset AW88298"); WriteReg(0x02, 0b00000011); vTaskDelay(pdMS_TO_TICKS(10)); WriteReg(0x02, 0b00000111); vTaskDelay(pdMS_TO_TICKS(50)); } void ResetIli9342() { ESP_LOGI(TAG, "Reset IlI9342"); WriteReg(0x03, 0b10000001); vTaskDelay(pdMS_TO_TICKS(20)); WriteReg(0x03, 0b10000011); vTaskDelay(pdMS_TO_TICKS(10)); } }; class Ft6336 : public I2cDevice { public: struct TouchPoint_t { int num = 0; int x = -1; int y = -1; }; Ft6336(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { uint8_t chip_id = ReadReg(0xA3); ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id); read_buffer_ = new uint8_t[6]; } ~Ft6336() { delete[] read_buffer_; } void UpdateTouchPoint() { ReadRegs(0x02, read_buffer_, 6); tp_.num = read_buffer_[0] & 0x0F; tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2]; tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4]; } inline const TouchPoint_t& GetTouchPoint() { return tp_; } private: uint8_t* read_buffer_ = nullptr; TouchPoint_t tp_; }; class M5StackCoreS3Board : public WifiBoard { private: i2c_master_bus_handle_t i2c_bus_; Pmic* pmic_; Aw9523* aw9523_; Ft6336* ft6336_; LvglDisplay* display_; StackChanCamera* camera_; esp_timer_handle_t touchpad_timer_; PowerSaveTimer* power_save_timer_; void InitializePowerSaveTimer() { power_save_timer_ = new PowerSaveTimer(-1, 300, 600); power_save_timer_->OnEnterSleepMode([this]() { GetDisplay()->SetPowerSaveMode(true); // GetBacklight()->SetBrightness(10); }); power_save_timer_->OnExitSleepMode([this]() { GetDisplay()->SetPowerSaveMode(false); GetBacklight()->RestoreBrightness(); }); power_save_timer_->OnShutdownRequest([this]() { pmic_->PowerOff(); }); power_save_timer_->SetEnabled(true); } void InitializeI2c() { // Initialize I2C peripheral i2c_master_bus_config_t i2c_bus_cfg = { .i2c_port = (i2c_port_t)1, .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, .clk_source = I2C_CLK_SRC_DEFAULT, .glitch_ignore_cnt = 7, .intr_priority = 0, .trans_queue_depth = 0, .flags = { .enable_internal_pullup = 1, }, }; ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); } void I2cDetect() { uint8_t address; printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n"); for (int i = 0; i < 128; i += 16) { printf("%02x: ", i); for (int j = 0; j < 16; j++) { fflush(stdout); address = i + j; esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200)); if (ret == ESP_OK) { printf("%02x ", address); } else if (ret == ESP_ERR_TIMEOUT) { printf("UU "); } else { printf("-- "); } } printf("\r\n"); } } void InitializeAxp2101() { ESP_LOGI(TAG, "Init AXP2101"); pmic_ = new Pmic(i2c_bus_, 0x34); } void InitializeAw9523() { ESP_LOGI(TAG, "Init AW9523"); aw9523_ = new Aw9523(i2c_bus_, 0x58); vTaskDelay(pdMS_TO_TICKS(50)); } void PollTouchpad() { ft6336_->UpdateTouchPoint(); auto& touch_point = ft6336_->GetTouchPoint(); // Update hal touch point hal_bridge::set_touch_point(touch_point.num, touch_point.x, touch_point.y); } void InitializeFt6336TouchPad() { ESP_LOGI(TAG, "Init FT6336"); ft6336_ = new Ft6336(i2c_bus_, 0x38); // 创建定时器,20ms 间隔 esp_timer_create_args_t timer_args = { .callback = [](void* arg) { M5StackCoreS3Board* board = (M5StackCoreS3Board*)arg; board->PollTouchpad(); }, .arg = this, .dispatch_method = ESP_TIMER_TASK, .name = "touchpad_timer", .skip_unhandled_events = true, }; ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_)); ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 20 * 1000)); } void InitializeSpi() { spi_bus_config_t buscfg = {}; buscfg.mosi_io_num = GPIO_NUM_37; buscfg.miso_io_num = GPIO_NUM_NC; buscfg.sclk_io_num = GPIO_NUM_36; buscfg.quadwp_io_num = GPIO_NUM_NC; buscfg.quadhd_io_num = GPIO_NUM_NC; buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); } void InitializeIli9342Display() { ESP_LOGI(TAG, "Init IlI9342"); esp_lcd_panel_io_handle_t panel_io = nullptr; esp_lcd_panel_handle_t panel = nullptr; ESP_LOGD(TAG, "Install panel IO"); esp_lcd_panel_io_spi_config_t io_config = {}; io_config.cs_gpio_num = GPIO_NUM_3; io_config.dc_gpio_num = GPIO_NUM_35; io_config.spi_mode = 2; io_config.pclk_hz = 40 * 1000 * 1000; io_config.trans_queue_depth = 10; io_config.lcd_cmd_bits = 8; io_config.lcd_param_bits = 8; ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); ESP_LOGD(TAG, "Install LCD driver"); 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_BGR; panel_config.bits_per_pixel = 16; ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel)); esp_lcd_panel_reset(panel); aw9523_->ResetIli9342(); esp_lcd_panel_init(panel); esp_lcd_panel_invert_color(panel, true); esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); // display_ = new StackChanLcdDisplay(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, // DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); display_ = new StackChanAvatarDisplay(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } void InitializeCamera() { ESP_LOGI(TAG, "Init Camera"); static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = { .data_width = CAM_CTLR_DATA_WIDTH_8, .data_io = { [0] = CAMERA_PIN_D0, [1] = CAMERA_PIN_D1, [2] = CAMERA_PIN_D2, [3] = CAMERA_PIN_D3, [4] = CAMERA_PIN_D4, [5] = CAMERA_PIN_D5, [6] = CAMERA_PIN_D6, [7] = CAMERA_PIN_D7, }, .vsync_io = CAMERA_PIN_VSYNC, .de_io = CAMERA_PIN_HREF, .pclk_io = CAMERA_PIN_PCLK, .xclk_io = CAMERA_PIN_XCLK, }; esp_video_init_sccb_config_t sccb_config = { .init_sccb = false, .i2c_handle = i2c_bus_, .freq = 100000, }; esp_video_init_dvp_config_t dvp_config = { .sccb_config = sccb_config, .reset_pin = CAMERA_PIN_RESET, .pwdn_pin = CAMERA_PIN_PWDN, .dvp_pin = dvp_pin_config, .xclk_freq = XCLK_FREQ_HZ, }; esp_video_init_config_t video_config = { .dvp = &dvp_config, }; camera_ = new StackChanCamera(video_config); camera_->SetHMirror(false); } public: M5StackCoreS3Board() { InitializePowerSaveTimer(); InitializeI2c(); InitializeAxp2101(); InitializeAw9523(); I2cDetect(); InitializeSpi(); InitializeIli9342Display(); InitializeCamera(); InitializeFt6336TouchPad(); GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { static CoreS3AudioCodec 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_AW88298_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); return &audio_codec; } virtual Display* GetDisplay() override { return display_; } virtual Camera* GetCamera() override { return camera_; } virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { static bool last_discharging = false; charging = pmic_->IsCharging(); discharging = pmic_->IsDischarging(); if (discharging != last_discharging) { power_save_timer_->SetEnabled(discharging); last_discharging = discharging; } level = pmic_->GetBatteryLevel(); return true; } virtual void SetPowerSaveLevel(PowerSaveLevel level) override { if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } WifiBoard::SetPowerSaveLevel(level); } virtual Backlight* GetBacklight() override { static CustomBacklight backlight(pmic_); return &backlight; } i2c_master_bus_handle_t GetI2cBus() { return i2c_bus_; } }; DECLARE_BOARD(M5StackCoreS3Board); i2c_master_bus_handle_t hal_bridge::board_get_i2c_bus() { auto& board = (M5StackCoreS3Board&)Board::GetInstance(); return board.GetI2cBus(); } StackChanCamera* hal_bridge::board_get_camera() { auto& board = Board::GetInstance(); auto camera = (StackChanCamera*)board.GetCamera(); return camera; } int hal_bridge::board_get_battery_level() { auto& board = Board::GetInstance(); int level = 0; bool charging = false; bool discharging = false; if (board.GetBatteryLevel(level, charging, discharging)) { return level; } else { return 100; } } bool hal_bridge::board_is_battery_charging() { auto& board = Board::GetInstance(); int level = 0; bool charging = false; bool discharging = false; if (board.GetBatteryLevel(level, charging, discharging)) { return charging; } else { return false; } } void hal_bridge::board_set_backlight_brightness(uint8_t brightness, bool permanent) { auto& board = Board::GetInstance(); auto backlight = board.GetBacklight(); if (backlight) { backlight->SetBrightness(brightness, false); if (permanent) { Settings settings("display", true); settings.SetInt("brightness", brightness); } } } uint8_t hal_bridge::board_get_backlight_brightness() { auto& board = Board::GetInstance(); auto backlight = board.GetBacklight(); if (backlight) { return backlight->brightness(); } else { return 0; } } void hal_bridge::board_set_speaker_volume(uint8_t volume, bool permanent) { auto& board = Board::GetInstance(); auto audio_codec = board.GetAudioCodec(); if (audio_codec) { if (permanent) { audio_codec->SetOutputVolume(volume); } } } uint8_t hal_bridge::board_get_speaker_volume() { int volume = 70; Settings settings("audio", false); volume = settings.GetInt("output_volume", volume); if (volume <= 0) { volume = 10; } return volume; } void hal_bridge::toggle_xiaozhi_chat_state() { auto& app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateStarting) { // EnterWifiConfigMode(); return; } app.ToggleChatState(); }