Compare commits

..

8 Commits

Author SHA1 Message Date
Terrence
4aef3d2a4e update version to 1.1.2 2025-02-08 13:56:36 +08:00
dujianmin
bc800921cf 添加了嘟嘟电路板chatx (#128)
* 添加了嘟嘟电路板chatx

* 多写了一个空格。修改了一下
2025-02-06 23:24:39 +08:00
Terrence
256a16e2fe remove the lvgl draw thread to save 8kb sram 2025-02-05 14:57:34 +08:00
Terrence
554152cd00 fix es8388 input/output volume 2025-02-04 23:49:58 +08:00
SunnyBoy-y
503c7d8a2a 正点原子esp32s3开发板音量问题修正 (#125)
* Update config.h

添加音量增益设置

* Update config.h

音量增益设置

* Update atk_dnesp32s3.cc

音量增益设置

* Update es8388_audio_codec.h

音量增益设置

* Update es8388_audio_codec.cc

音量增益设置
2025-02-04 23:10:49 +08:00
Terrence
84c932da4a CPU usage depending on boards 2025-02-04 14:37:11 +08:00
Terrence
a0adbfd774 fix display issues 2025-02-04 14:37:11 +08:00
MOV
252755f615 Moji LCD configuration (#122)
* fix:Modify the README and add Moji images

* fix: Moji LCD initialization configuration.

* fix: DISPLAY_MIRROR_X false >> true
2025-02-04 12:33:06 +08:00
31 changed files with 437 additions and 219 deletions

View File

@@ -4,7 +4,7 @@
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(PROJECT_VER "1.1.0")
set(PROJECT_VER "1.1.2")
# Add this line to disable the specific warning
add_compile_options(-Wno-missing-field-initializers)

View File

@@ -85,11 +85,13 @@ elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3)
set(BOARD_TYPE "atk-dnesp32s3")
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX)
set(BOARD_TYPE "atk-dnesp32s3-box")
elseif(CONFIG_BOARD_TYPE_DU_CHATX)
set(BOARD_TYPE "du-chatx")
endif()
file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc)
list(APPEND SOURCES ${BOARD_SOURCES})
if(CONFIG_IDF_TARGET_ESP32S3)
if(CONFIG_USE_AUDIO_PROCESSING)
list(APPEND SOURCES "audio_processing/audio_processor.cc" "audio_processing/wake_word_detect.cc")
endif()

View File

@@ -84,6 +84,8 @@ choice BOARD_TYPE
bool "正点原子DNESP32S3开发板"
config BOARD_TYPE_ATK_DNESP32S3_BOX
bool "正点原子DNESP32S3-BOX"
config BOARD_TYPE_DU_CHATX
bool "嘟嘟开发板CHATX(wifi)"
endchoice
choice DISPLAY_LCD_TYPE
@@ -116,6 +118,12 @@ choice DISPLAY_LCD_TYPE
bool "ILI9341, 分辨率240*320, 非IPS"
config LCD_CUSTOM
bool "自定义屏幕参数"
endchoice
endchoice
config USE_AUDIO_PROCESSING
bool "启用语音唤醒与音频处理"
default y
depends on IDF_TARGET_ESP32S3 && USE_AFE
help
需要 ESP32 S3 与 AFE 支持
endmenu

View File

@@ -77,7 +77,7 @@ void Application::CheckNewVersion() {
display->SetStatus("新版本 " + ota_.GetFirmwareVersion());
board.SetPowerSaveMode(false);
#if CONFIG_IDF_TARGET_ESP32S3
#if CONFIG_USE_AUDIO_PROCESSING
wake_word_detect_.StopDetection();
#endif
// 预先关闭音频输出,避免升级过程有音频操作
@@ -225,6 +225,16 @@ void Application::Start() {
opus_decode_sample_rate_ = codec->output_sample_rate();
opus_decoder_ = std::make_unique<OpusDecoderWrapper>(opus_decode_sample_rate_, 1);
opus_encoder_ = std::make_unique<OpusEncoderWrapper>(16000, 1, OPUS_FRAME_DURATION_MS);
// For ML307 boards, we use complexity 5 to save bandwidth
// For other boards, we use complexity 3 to save CPU
if (board.GetBoardType() == "ml307") {
ESP_LOGI(TAG, "ML307 board detected, setting opus encoder complexity to 5");
opus_encoder_->SetComplexity(5);
} else {
ESP_LOGI(TAG, "WiFi board detected, setting opus encoder complexity to 3");
opus_encoder_->SetComplexity(3);
}
if (codec->input_sample_rate() != 16000) {
input_resampler_.Configure(codec->input_sample_rate(), 16000);
reference_resampler_.Configure(codec->input_sample_rate(), 16000);
@@ -355,7 +365,7 @@ void Application::Start() {
}, "check_new_version", 4096 * 2, this, 1, nullptr);
#if CONFIG_IDF_TARGET_ESP32S3
#if CONFIG_USE_AUDIO_PROCESSING
audio_processor_.Initialize(codec->input_channels(), codec->input_reference());
audio_processor_.OnOutput([this](std::vector<int16_t>&& data) {
background_task_->Schedule([this, data = std::move(data)]() mutable {
@@ -541,7 +551,7 @@ void Application::InputAudio() {
}
}
#if CONFIG_IDF_TARGET_ESP32S3
#if CONFIG_USE_AUDIO_PROCESSING
if (audio_processor_.IsRunning()) {
audio_processor_.Input(data);
}
@@ -585,7 +595,7 @@ void Application::SetDeviceState(DeviceState state) {
case kDeviceStateIdle:
display->SetStatus("待命");
display->SetEmotion("neutral");
#ifdef CONFIG_IDF_TARGET_ESP32S3
#ifdef CONFIG_USE_AUDIO_PROCESSING
audio_processor_.Stop();
#endif
break;
@@ -597,7 +607,7 @@ void Application::SetDeviceState(DeviceState state) {
display->SetEmotion("neutral");
ResetDecoder();
opus_encoder_->ResetState();
#if CONFIG_IDF_TARGET_ESP32S3
#if CONFIG_USE_AUDIO_PROCESSING
audio_processor_.Start();
#endif
UpdateIotStates();
@@ -605,7 +615,7 @@ void Application::SetDeviceState(DeviceState state) {
case kDeviceStateSpeaking:
display->SetStatus("说话中...");
ResetDecoder();
#if CONFIG_IDF_TARGET_ESP32S3
#if CONFIG_USE_AUDIO_PROCESSING
audio_processor_.Stop();
#endif
break;

View File

@@ -17,7 +17,7 @@
#include "ota.h"
#include "background_task.h"
#if CONFIG_IDF_TARGET_ESP32S3
#if CONFIG_USE_AUDIO_PROCESSING
#include "wake_word_detect.h"
#include "audio_processor.h"
#endif
@@ -66,7 +66,7 @@ private:
Application();
~Application();
#if CONFIG_IDF_TARGET_ESP32S3
#if CONFIG_USE_AUDIO_PROCESSING
WakeWordDetect wake_word_detect_;
AudioProcessor audio_processor_;
#endif

View File

@@ -149,7 +149,7 @@ void Es8388AudioCodec::EnableInput(bool enable) {
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 40.0));
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 24.0));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}
@@ -170,6 +170,14 @@ void Es8388AudioCodec::EnableOutput(bool enable) {
};
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
// Set analog output volume to 0dB, default is -45dB
uint8_t reg_val = 30; // 0dB
uint8_t regs[] = { 46, 47, 48, 49 }; // HP_LVOL, HP_RVOL, SPK_LVOL, SPK_RVOL
for (uint8_t reg : regs) {
ctrl_if_->write_reg(ctrl_if_, reg, 1, &reg_val, 1);
}
if (pa_pin_ != GPIO_NUM_NC) {
gpio_set_level(pa_pin_, 1);
}
@@ -194,4 +202,4 @@ int Es8388AudioCodec::Write(const int16_t* data, int samples) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
}
return samples;
}
}

View File

@@ -5,27 +5,16 @@
#define TAG "BackgroundTask"
BackgroundTask::BackgroundTask(uint32_t stack_size) {
#if CONFIG_IDF_TARGET_ESP32S3
task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
background_task_handle_ = xTaskCreateStatic([](void* arg) {
BackgroundTask* task = (BackgroundTask*)arg;
task->BackgroundTaskLoop();
}, "background_task", stack_size, this, 1, task_stack_, &task_buffer_);
#else
xTaskCreate([](void* arg) {
BackgroundTask* task = (BackgroundTask*)arg;
task->BackgroundTaskLoop();
}, "background_task", stack_size, this, 1, &background_task_handle_);
#endif
}
BackgroundTask::~BackgroundTask() {
if (background_task_handle_ != nullptr) {
vTaskDelete(background_task_handle_);
}
if (task_stack_ != nullptr) {
heap_caps_free(task_stack_);
}
}
void BackgroundTask::Schedule(std::function<void()> callback) {

View File

@@ -23,10 +23,6 @@ private:
TaskHandle_t background_task_handle_ = nullptr;
std::atomic<size_t> active_tasks_{0};
TaskHandle_t task_ = nullptr;
StaticTask_t task_buffer_;
StackType_t* task_stack_ = nullptr;
void BackgroundTaskLoop();
};

View File

@@ -162,15 +162,20 @@ public:
}
virtual AudioCodec* GetAudioCodec() override {
static Es8388AudioCodec* audio_codec = nullptr;
if (audio_codec == nullptr) {
audio_codec = new Es8388AudioCodec(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,
GPIO_NUM_NC, AUDIO_CODEC_ES8388_ADDR);
audio_codec->SetOutputVolume(AUDIO_DEFAULT_OUTPUT_VOLUME); //设置默认音量
}
return audio_codec;
static Es8388AudioCodec audio_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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8388_ADDR
);
return &audio_codec;
}
virtual Display* GetDisplay() override {
@@ -178,4 +183,4 @@ public:
}
};
DECLARE_BOARD(atk_dnesp32s3);
DECLARE_BOARD(atk_dnesp32s3);

View File

@@ -2,11 +2,11 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_DEFAULT_OUTPUT_VOLUME 90
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_3
#define AUDIO_I2S_GPIO_WS GPIO_NUM_9

View File

@@ -122,13 +122,20 @@ public:
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec* audio_codec = nullptr;
if (audio_codec == nullptr) {
audio_codec = new Es8311AudioCodec(i2c_bus_, I2C_NUM_1, 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_GPIO_PA, AUDIO_CODEC_ES8311_ADDR, false);
}
return audio_codec;
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_1,
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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
};

View File

@@ -233,13 +233,20 @@ public:
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec* audio_codec = nullptr;
if (audio_codec == nullptr) {
audio_codec = new Es8311AudioCodec(i2c_bus_, I2C_NUM_1, 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_GPIO_PA, AUDIO_CODEC_ES8311_ADDR, false);
}
return audio_codec;
static Es8311AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_1,
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_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
virtual Display* GetDisplay() override {

View File

@@ -23,15 +23,12 @@ protected:
public:
static Board& GetInstance() {
static Board* instance = nullptr;
if (nullptr == instance) {
instance = static_cast<Board*>(create_board());
}
static Board* instance = static_cast<Board*>(create_board());
return *instance;
}
virtual void StartNetwork() = 0;
virtual ~Board() = default;
virtual std::string GetBoardType() = 0;
virtual Led* GetLed();
virtual AudioCodec* GetAudioCodec() = 0;
virtual Display* GetDisplay();
@@ -39,7 +36,7 @@ public:
virtual WebSocket* CreateWebSocket() = 0;
virtual Mqtt* CreateMqtt() = 0;
virtual Udp* CreateUdp() = 0;
virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) = 0;
virtual void StartNetwork() = 0;
virtual const char* GetNetworkStateIcon() = 0;
virtual bool GetBatteryLevel(int &level, bool& charging);
virtual std::string GetJson();

View File

@@ -11,28 +11,15 @@
#include <web_socket.h>
#include <ml307_mqtt.h>
#include <ml307_udp.h>
#include <opus_encoder.h>
static const char *TAG = "Ml307Board";
static std::string csq_to_string(int csq) {
if (csq == -1) {
return "No network";
} else if (csq >= 0 && csq <= 9) {
return "Very bad";
} else if (csq >= 10 && csq <= 14) {
return "Bad";
} else if (csq >= 15 && csq <= 19) {
return "Fair";
} else if (csq >= 20 && csq <= 24) {
return "Good";
} else if (csq >= 25 && csq <= 31) {
return "Very good";
}
return "Invalid";
Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, size_t rx_buffer_size) : modem_(tx_pin, rx_pin, rx_buffer_size) {
}
Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, size_t rx_buffer_size) : modem_(tx_pin, rx_pin, rx_buffer_size) {
std::string Ml307Board::GetBoardType() {
return "ml307";
}
void Ml307Board::StartNetwork() {
@@ -95,16 +82,6 @@ Udp* Ml307Board::CreateUdp() {
return new Ml307Udp(modem_, 0);
}
bool Ml307Board::GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) {
if (!modem_.network_ready()) {
return false;
}
network_name = modem_.GetCarrierName();
signal_quality = modem_.GetCsq();
signal_quality_text = csq_to_string(signal_quality);
return signal_quality != -1;
}
const char* Ml307Board::GetNetworkStateIcon() {
if (!modem_.network_ready()) {
return FONT_AWESOME_SIGNAL_OFF;

View File

@@ -13,12 +13,12 @@ protected:
public:
Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, size_t rx_buffer_size = 4096);
virtual std::string GetBoardType() override;
virtual void StartNetwork() override;
virtual Http* CreateHttp() override;
virtual WebSocket* CreateWebSocket() override;
virtual Mqtt* CreateMqtt() override;
virtual Udp* CreateUdp() override;
virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) override;
virtual const char* GetNetworkStateIcon() override;
virtual void SetPowerSaveMode(bool enabled) override;
};

View File

@@ -22,20 +22,6 @@
static const char *TAG = "WifiBoard";
static std::string rssi_to_string(int rssi) {
if (rssi >= -55) {
return "Very good";
} else if (rssi >= -65) {
return "Good";
} else if (rssi >= -75) {
return "Fair";
} else if (rssi >= -85) {
return "Poor";
} else {
return "No network";
}
}
WifiBoard::WifiBoard() {
Settings settings("wifi", true);
wifi_config_mode_ = settings.GetInt("force_ap") == 1;
@@ -45,6 +31,10 @@ WifiBoard::WifiBoard() {
}
}
std::string WifiBoard::GetBoardType() {
return "wifi";
}
void WifiBoard::EnterWifiConfigMode() {
auto& application = Application::GetInstance();
auto display = Board::GetInstance().GetDisplay();
@@ -138,24 +128,6 @@ Udp* WifiBoard::CreateUdp() {
return new EspUdp();
}
bool WifiBoard::GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) {
if (wifi_config_mode_) {
auto& wifi_ap = WifiConfigurationAp::GetInstance();
network_name = wifi_ap.GetSsid();
signal_quality = -99;
signal_quality_text = wifi_ap.GetWebServerUrl();
return true;
}
auto& wifi_station = WifiStation::GetInstance();
if (!wifi_station.IsConnected()) {
return false;
}
network_name = wifi_station.GetSsid();
signal_quality = wifi_station.GetRssi();
signal_quality_text = rssi_to_string(signal_quality);
return signal_quality != -1;
}
const char* WifiBoard::GetNetworkStateIcon() {
if (wifi_config_mode_) {
return FONT_AWESOME_WIFI;

View File

@@ -12,12 +12,12 @@ protected:
virtual std::string GetBoardJson() override;
public:
virtual std::string GetBoardType() override;
virtual void StartNetwork() override;
virtual Http* CreateHttp() override;
virtual WebSocket* CreateWebSocket() override;
virtual Mqtt* CreateMqtt() override;
virtual Udp* CreateUdp() override;
virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) override;
virtual const char* GetNetworkStateIcon() override;
virtual void SetPowerSaveMode(bool enabled) override;
virtual void ResetWifiConfiguration();

View File

@@ -0,0 +1,54 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
// 如果使用 Duplex I2S 模式,请注释下面一行
#define AUDIO_I2S_METHOD_SIMPLEX
#ifdef AUDIO_I2S_METHOD_SIMPLEX
#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_39
#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_38
#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_7
#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_40
#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_42
#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_2
#else
#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
#endif
#define BUILTIN_LED_GPIO GPIO_NUM_48
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define TOUCH_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_9
#define DISPLAY_MOSI_PIN GPIO_NUM_18
#define DISPLAY_CLK_PIN GPIO_NUM_17
#define DISPLAY_DC_PIN GPIO_NUM_8
#define DISPLAY_RST_PIN GPIO_NUM_20
#define DISPLAY_CS_PIN GPIO_NUM_16
#define DISPLAY_WIDTH 128
#define DISPLAY_HEIGHT 160
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_SWAP_XY false
#define DISPLAY_INVERT_COLOR false
#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,134 @@
#include "wifi_board.h"
#include "audio_codecs/no_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "iot/thing_manager.h"
#include "led/single_led.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/spi_common.h>
#if defined(LCD_ILI9341_240X320) || defined(LCD_ILI9341_240X320_NO_IPS)
#include "esp_lcd_ili9341.h"
#endif
#define TAG "DuChatX"
LV_FONT_DECLARE(font_puhui_16_4);
LV_FONT_DECLARE(font_awesome_16_4);
class DuChatX : public WifiBoard {
private:
Button boot_button_;
LcdDisplay* display_;
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
buscfg.miso_io_num = GPIO_NUM_NC;
buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeLcdDisplay() {
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(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 = DISPLAY_RST_PIN;
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
panel_config.bits_per_pixel = 16;
#if defined(LCD_ILI9341_240X320) || defined(LCD_ILI9341_240X320_NO_IPS)
ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
#else
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
#endif
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
display_ = new LcdDisplay(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,
{
.text_font = &font_puhui_16_4,
.icon_font = &font_awesome_16_4,
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
});
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
// 物联网初始化,添加对 AI 可见设备
void InitializeIot() {
auto& thing_manager = iot::ThingManager::GetInstance();
thing_manager.AddThing(iot::CreateThing("Speaker"));
}
public:
DuChatX() :
boot_button_(BOOT_BUTTON_GPIO) {
InitializeSpi();
InitializeLcdDisplay();
InitializeButtons();
InitializeIot();
}
virtual Led* GetLed() override {
static SingleLed led(BUILTIN_LED_GPIO);
return &led;
}
virtual AudioCodec* GetAudioCodec() override {
#ifdef AUDIO_I2S_METHOD_SIMPLEX
static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
#else
static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
#endif
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
};
DECLARE_BOARD(DuChatX);

View File

@@ -146,13 +146,20 @@ public:
}
virtual AudioCodec* GetAudioCodec() override {
static BoxAudioCodec* audio_codec = nullptr;
if (audio_codec == nullptr) {
audio_codec = new BoxAudioCodec(i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
}
return audio_codec;
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 {

View File

@@ -26,8 +26,8 @@
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y true
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0

View File

@@ -125,13 +125,20 @@ public:
}
virtual AudioCodec* GetAudioCodec() override {
static BoxAudioCodec* audio_codec = nullptr;
if (audio_codec == nullptr) {
audio_codec = new BoxAudioCodec(i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
}
return audio_codec;
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

View File

@@ -116,13 +116,19 @@ public:
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec* audio_codec = nullptr;
if (audio_codec == nullptr) {
audio_codec = new Es8311AudioCodec(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;
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 {

View File

@@ -142,13 +142,20 @@ public:
}
virtual AudioCodec* GetAudioCodec() override {
static BoxAudioCodec* audio_codec = nullptr;
if (audio_codec == nullptr) {
audio_codec = new BoxAudioCodec(i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
GPIO_NUM_NC, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
}
return audio_codec;
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,
GPIO_NUM_NC,
AUDIO_CODEC_ES8311_ADDR,
AUDIO_CODEC_ES7210_ADDR,
AUDIO_INPUT_REFERENCE);
return &audio_codec;
}
virtual Display* GetDisplay() override {

View File

@@ -219,14 +219,17 @@ public:
}
virtual AudioCodec *GetAudioCodec() override {
static Tcircles3AudioCodec *audio_codec = nullptr;
if (audio_codec == nullptr){
audio_codec = new Tcircles3AudioCodec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_MIC_I2S_GPIO_BCLK, AUDIO_MIC_I2S_GPIO_WS, AUDIO_MIC_I2S_GPIO_DATA,
AUDIO_SPKR_I2S_GPIO_BCLK, AUDIO_SPKR_I2S_GPIO_LRCLK, AUDIO_SPKR_I2S_GPIO_DATA,
AUDIO_INPUT_REFERENCE);
}
return audio_codec;
static Tcircles3AudioCodec audio_codec(
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_MIC_I2S_GPIO_BCLK,
AUDIO_MIC_I2S_GPIO_WS,
AUDIO_MIC_I2S_GPIO_DATA,
AUDIO_SPKR_I2S_GPIO_BCLK,
AUDIO_SPKR_I2S_GPIO_LRCLK,
AUDIO_SPKR_I2S_GPIO_DATA,
AUDIO_INPUT_REFERENCE);
return &audio_codec;
}
virtual Display *GetDisplay() override{

View File

@@ -12,7 +12,8 @@
#include <wifi_station.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include "esp_lcd_ili9341.h"
#include <esp_lcd_ili9341.h>
#include <esp_timer.h>
#define TAG "M5StackCoreS3Board"
@@ -120,6 +121,7 @@ private:
Ft6336* ft6336_;
LcdDisplay* display_;
Button boot_button_;
esp_timer_handle_t touchpad_timer_;
void InitializeI2c() {
// Initialize I2C peripheral
@@ -170,37 +172,53 @@ private:
vTaskDelay(pdMS_TO_TICKS(50));
}
static void touchpad_daemon(void* param) {
vTaskDelay(pdMS_TO_TICKS(2000));
static void touchpad_timer_callback(void* arg) {
auto& board = (M5StackCoreS3Board&)Board::GetInstance();
auto touchpad = board.GetTouchpad();
bool was_touched = false;
while (1) {
touchpad->UpdateTouchPoint();
if (touchpad->GetTouchPoint().num > 0) {
// On press
if (!was_touched) {
was_touched = true;
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
board.ResetWifiConfiguration();
}
app.ToggleChatState();
static bool was_touched = false;
static int64_t touch_start_time = 0;
const int64_t TOUCH_THRESHOLD_MS = 500; // 触摸时长阈值超过500ms视为长按
touchpad->UpdateTouchPoint();
auto touch_point = touchpad->GetTouchPoint();
// 检测触摸开始
if (touch_point.num > 0 && !was_touched) {
was_touched = true;
touch_start_time = esp_timer_get_time() / 1000; // 转换为毫秒
}
// 检测触摸释放
else if (touch_point.num == 0 && was_touched) {
was_touched = false;
int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time;
// 只有短触才触发
if (touch_duration < TOUCH_THRESHOLD_MS) {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting &&
!WifiStation::GetInstance().IsConnected()) {
board.ResetWifiConfiguration();
}
app.ToggleChatState();
}
// On release
else if (was_touched) {
was_touched = false;
}
vTaskDelay(pdMS_TO_TICKS(50));
}
vTaskDelete(NULL);
}
void InitializeFt6336TouchPad() {
ESP_LOGI(TAG, "Init FT6336");
ft6336_ = new Ft6336(i2c_bus_, 0x38);
xTaskCreate(touchpad_daemon, "tp", 2048, NULL, 5, NULL);
// 创建定时器10ms 间隔
esp_timer_create_args_t timer_args = {
.callback = touchpad_timer_callback,
.arg = NULL,
.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_, 10 * 1000)); // 10ms = 10000us
}
void InitializeSpi() {
@@ -276,23 +294,27 @@ public:
InitializeI2c();
InitializeAxp2101();
InitializeAw9523();
InitializeFt6336TouchPad();
I2cDetect();
InitializeSpi();
InitializeIli9342Display();
InitializeButtons();
InitializeIot();
InitializeFt6336TouchPad();
}
virtual AudioCodec* GetAudioCodec() override {
static CoreS3AudioCodec* audio_codec = nullptr;
if (audio_codec == nullptr) {
aw9523_->ResetAw88298();
audio_codec = new CoreS3AudioCodec(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;
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 {

View File

@@ -24,7 +24,7 @@
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 240
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false

View File

@@ -63,20 +63,20 @@ Display::~Display() {
}
void Display::SetStatus(const std::string &status) {
DisplayLockGuard lock(this);
if (status_label_ == nullptr) {
return;
}
DisplayLockGuard lock(this);
lv_label_set_text(status_label_, status.c_str());
lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
}
void Display::ShowNotification(const std::string &notification, int duration_ms) {
DisplayLockGuard lock(this);
if (notification_label_ == nullptr) {
return;
}
DisplayLockGuard lock(this);
lv_label_set_text(notification_label_, notification.c_str());
lv_obj_clear_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
@@ -86,15 +86,15 @@ void Display::ShowNotification(const std::string &notification, int duration_ms)
}
void Display::Update() {
if (mute_label_ == nullptr) {
return;
}
auto& board = Board::GetInstance();
auto codec = board.GetAudioCodec();
{
DisplayLockGuard lock(this);
if (mute_label_ == nullptr) {
return;
}
// 如果静音状态改变,则更新图标
if (codec->output_volume() == 0 && !muted_) {
muted_ = true;
@@ -123,8 +123,8 @@ void Display::Update() {
};
icon = levels[battery_level / 20];
}
if (battery_icon_ != icon) {
DisplayLockGuard lock(this);
DisplayLockGuard lock(this);
if (battery_label_ != nullptr && battery_icon_ != icon) {
battery_icon_ = icon;
lv_label_set_text(battery_label_, battery_icon_);
}
@@ -140,7 +140,7 @@ void Display::Update() {
};
if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end()) {
icon = board.GetNetworkStateIcon();
if (network_icon_ != icon) {
if (network_label_ != nullptr && network_icon_ != icon) {
DisplayLockGuard lock(this);
network_icon_ = icon;
lv_label_set_text(network_label_, network_icon_);
@@ -150,10 +150,6 @@ void Display::Update() {
void Display::SetEmotion(const std::string &emotion) {
if (emotion_label_ == nullptr) {
return;
}
struct Emotion {
const char* icon;
const char* text;
@@ -188,6 +184,10 @@ void Display::SetEmotion(const std::string &emotion) {
[&emotion](const Emotion& e) { return e.text == emotion; });
DisplayLockGuard lock(this);
if (emotion_label_ == nullptr) {
return;
}
// 如果找到匹配的表情就显示对应图标否则显示默认的neutral表情
if (it != emotions.end()) {
lv_label_set_text(emotion_label_, it->icon);
@@ -197,10 +197,10 @@ void Display::SetEmotion(const std::string &emotion) {
}
void Display::SetIcon(const char* icon) {
DisplayLockGuard lock(this);
if (emotion_label_ == nullptr) {
return;
}
DisplayLockGuard lock(this);
lv_label_set_text(emotion_label_, icon);
}

View File

@@ -48,7 +48,7 @@ LcdDisplay::LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_
.panel_handle = panel_,
.control_handle = nullptr,
.buffer_size = static_cast<uint32_t>(width_ * 10),
.double_buffer = true,
.double_buffer = false,
.trans_size = 0,
.hres = static_cast<uint32_t>(width_),
.vres = static_cast<uint32_t>(height_),
@@ -75,7 +75,9 @@ LcdDisplay::LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_
return;
}
lv_display_set_offset(display_, offset_x, offset_y);
if (offset_x != 0 || offset_y != 0) {
lv_display_set_offset(display_, offset_x, offset_y);
}
SetBacklight(100);
@@ -166,7 +168,7 @@ void LcdDisplay::Unlock() {
void LcdDisplay::SetupUI() {
DisplayLockGuard lock(this);
auto screen = lv_disp_get_scr_act(lv_disp_get_default());
auto screen = lv_screen_active();
lv_obj_set_style_text_font(screen, fonts_.text_font, 0);
lv_obj_set_style_text_color(screen, lv_color_black(), 0);
@@ -237,19 +239,14 @@ void LcdDisplay::SetupUI() {
}
void LcdDisplay::SetChatMessage(const std::string &role, const std::string &content) {
DisplayLockGuard lock(this);
if (chat_message_label_ == nullptr) {
return;
}
DisplayLockGuard lock(this);
lv_label_set_text(chat_message_label_, content.c_str());
}
void LcdDisplay::SetEmotion(const std::string &emotion) {
if (emotion_label_ == nullptr) {
return;
}
struct Emotion {
const char* icon;
const char* text;
@@ -284,6 +281,10 @@ void LcdDisplay::SetEmotion(const std::string &emotion) {
[&emotion](const Emotion& e) { return e.text == emotion; });
DisplayLockGuard lock(this);
if (emotion_label_ == nullptr) {
return;
}
// 如果找到匹配的表情就显示对应图标否则显示默认的neutral表情
lv_obj_set_style_text_font(emotion_label_, fonts_.emoji_font, 0);
if (it != emotions.end()) {
@@ -294,10 +295,10 @@ void LcdDisplay::SetEmotion(const std::string &emotion) {
}
void LcdDisplay::SetIcon(const char* icon) {
DisplayLockGuard lock(this);
if (emotion_label_ == nullptr) {
return;
}
DisplayLockGuard lock(this);
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
lv_label_set_text(emotion_label_, icon);
}

View File

@@ -135,7 +135,7 @@ void Ssd1306Display::Unlock() {
void Ssd1306Display::SetupUI_128x64() {
DisplayLockGuard lock(this);
auto screen = lv_disp_get_scr_act(display_);
auto screen = lv_screen_active();
lv_obj_set_style_text_font(screen, text_font_, 0);
lv_obj_set_style_text_color(screen, lv_color_black(), 0);
@@ -197,7 +197,7 @@ void Ssd1306Display::SetupUI_128x64() {
void Ssd1306Display::SetupUI_128x32() {
DisplayLockGuard lock(this);
auto screen = lv_disp_get_scr_act(display_);
auto screen = lv_screen_active();
lv_obj_set_style_text_font(screen, text_font_, 0);
/* Container */

View File

@@ -25,9 +25,8 @@ CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n
# LVGL 9.2.2
CONFIG_LV_OS_FREERTOS=y
CONFIG_LV_USE_OS=2
CONFIG_LV_USE_FREERTOS_TASK_NOTIFY=y
CONFIG_LV_OS_NONE=y
CONFIG_LV_USE_OS=0
CONFIG_LV_USE_CLIB_MALLOC=y
CONFIG_LV_USE_CLIB_STRING=y
CONFIG_LV_USE_CLIB_SPRINTF=y