Files
xiaozhi-esp32/main/boards/magiclick-c3/magiclick_c3_board.cc
2025-03-05 09:27:43 +08:00

257 lines
9.5 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "wifi_board.h"
#include "display/lcd_display.h"
#include "audio_codecs/es8311_audio_codec.h"
#include "application.h"
#include "button.h"
#include "led/single_led.h"
#include "iot/thing_manager.h"
#include "config.h"
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#include "esp_lcd_nv3023.h"
#include "font_awesome_symbols.h"
#include <esp_efuse_table.h>
#include <esp_timer.h>
#include <esp_pm.h>
#define TAG "magiclick_c3"
LV_FONT_DECLARE(font_puhui_16_4);
LV_FONT_DECLARE(font_awesome_16_4);
class NV3023Display : public SpiLcdDisplay {
public:
NV3023Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
gpio_num_t backlight_pin, bool backlight_output_invert,
int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy)
: SpiLcdDisplay(panel_io, panel, backlight_pin, backlight_output_invert,
width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy,
{
.text_font = &font_puhui_16_4,
.icon_font = &font_awesome_16_4,
.emoji_font = font_emoji_32_init(),
}) {
DisplayLockGuard lock(this);
// 只需要覆盖颜色相关的样式
auto screen = lv_disp_get_scr_act(lv_disp_get_default());
lv_obj_set_style_text_color(screen, lv_color_black(), 0);
// 设置容器背景色
lv_obj_set_style_bg_color(container_, lv_color_black(), 0);
// 设置状态栏背景色和文本颜色
lv_obj_set_style_bg_color(status_bar_, lv_color_white(), 0);
lv_obj_set_style_text_color(network_label_, lv_color_black(), 0);
lv_obj_set_style_text_color(notification_label_, lv_color_black(), 0);
lv_obj_set_style_text_color(status_label_, lv_color_black(), 0);
lv_obj_set_style_text_color(mute_label_, lv_color_black(), 0);
lv_obj_set_style_text_color(battery_label_, lv_color_black(), 0);
// 设置内容区背景色和文本颜色
lv_obj_set_style_bg_color(content_, lv_color_black(), 0);
lv_obj_set_style_border_width(content_, 0, 0);
lv_obj_set_style_text_color(emotion_label_, lv_color_white(), 0);
lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0);
}
};
class magiclick_c3 : public WifiBoard {
private:
i2c_master_bus_handle_t codec_i2c_bus_;
Button boot_button_;
NV3023Display* display_;
esp_timer_handle_t power_save_timer_ = nullptr;
bool sleep_mode_enabled_ = false;
int power_save_ticks_ = 0;
void InitializePowerSaveTimer() {
esp_timer_create_args_t power_save_timer_args = {
.callback = [](void *arg) {
auto board = static_cast<magiclick_c3*>(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_sleep = 120;
auto& app = Application::GetInstance();
if (app.GetDeviceState() != kDeviceStateIdle) {
power_save_ticks_ = 0;
return;
}
power_save_ticks_++;
if (power_save_ticks_ >= seconds_to_sleep) {
EnableSleepMode(true);
}
}
void EnableSleepMode(bool enable) {
power_save_ticks_ = 0;
if (!sleep_mode_enabled_ && enable) {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
// 如果是LCD还可以调节屏幕亮度
display->SetBacklight(1);
auto codec = GetAudioCodec();
codec->EnableInput(false);
esp_pm_config_t pm_config = {
.max_freq_mhz = 160,
.min_freq_mhz = 40,
.light_sleep_enable = true,
};
esp_pm_configure(&pm_config);
sleep_mode_enabled_ = true;
} else if (sleep_mode_enabled_ && !enable) {
esp_pm_config_t pm_config = {
.max_freq_mhz = 160,
.min_freq_mhz = 160,
.light_sleep_enable = false,
};
esp_pm_configure(&pm_config);
ESP_LOGI(TAG, "Disabling sleep mode");
auto codec = GetAudioCodec();
codec->EnableInput(true);
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("happy");
// 如果是LCD还可以调节屏幕亮度
display->SetBacklight(display->brightness());
sleep_mode_enabled_ = false;
}
}
void InitializeCodecI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = 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, &codec_i2c_bus_));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
});
boot_button_.OnPressDown([this]() {
EnableSleepMode(false);
Application::GetInstance().StartListening();
});
boot_button_.OnPressUp([this]() {
Application::GetInstance().StopListening();
});
}
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SDA_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_SCL_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 InitializeNv3023Display(){
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_CS_PIN;
io_config.dc_gpio_num = DISPLAY_DC_PIN;
io_config.spi_mode = 0;
io_config.pclk_hz = 40 * 1000 * 1000;
io_config.trans_queue_depth = 10;
io_config.lcd_cmd_bits = 8;
io_config.lcd_param_bits = 8;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片NV3023
ESP_LOGD(TAG, "Install LCD driver");
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = DISPLAY_RST_PIN;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
panel_config.bits_per_pixel = 16;
ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, false);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
esp_lcd_panel_disp_on_off(panel, true);
display_ = new NV3023Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
thing_manager.AddThing(iot::CreateThing("Backlight"));
}
public:
magiclick_c3() : boot_button_(BOOT_BUTTON_GPIO) {
// 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用
esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO);
InitializeCodecI2c();
InitializeButtons();
InitializePowerSaveTimer();
InitializeSpi();
InitializeNv3023Display();
InitializeIot();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, 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);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(magiclick_c3);