Compare commits

...

17 Commits

Author SHA1 Message Date
Terrence
38157aa180 Add device-side AEC to EchoEar 2025-09-16 00:14:01 +08:00
Terrence
1bacf40cd4 Bump to 1.9.2 2025-09-15 23:55:04 +08:00
Terrence
99aa15822b 开机启动显示开发板信息,提前启动event loop 2025-09-15 23:16:08 +08:00
Terrence
1ffc5190b6 fix: esp_emote_gfx compiling errors 2025-09-08 08:01:20 +08:00
Terrence
73dbeb4b9a update surfer-c3-1.14tft font size 2025-09-05 11:57:46 +08:00
Terrence
1e94e884b8 fix: c3 stack protection error, remove lvgl jpg library 2025-09-05 11:37:31 +08:00
Terrence
b35bf0c344 fix compiling errors 2025-09-04 13:47:02 +08:00
Xiaoxia
5d3f597137 Bump to v1.9.0 (#1157)
* update v2 partition table readme

* feat: Add user only tool

* Add image cache

* smaller cache and buffer, more heap

* use MAIN_EVENT_CLOCK_TICK to avoid audio glitches

* fix: esp_psram_get_size not found in c3

* Bump to 1.9.0
2025-09-04 12:30:26 +08:00
ggc121238
3e37551923 add waveshare-s3-audio-board (#1139)
* add waveshare-s3-audio-board

* Modify the product link in the readme of the waveshare esp32s3-audio-board

* Modify reset time
2025-09-01 10:50:39 +08:00
Xiaoxia
d09537ed5c Add V2 parition tables (#1137)
* v1.9.0: update font icons, add mqtt reconnect

* Add v2 parition tables
2025-08-29 09:04:23 +08:00
Create123
86921f4862 Add M5Stack AtomEchoS3R Board. (#1123)
Signed-off-by: hlym123 <lwylwt@qq.com>
2025-08-26 14:05:10 +08:00
Terrence
7af366b7b2 fix: ES7120_SEL_MIC1 => ES7210_SEL_MIC1 2025-08-23 16:05:49 +08:00
Xiaoxia
ddbb24942d v1.8.9: Upgrade component versions (#1118) 2025-08-23 07:12:14 +08:00
Ben
610a4a0703 Update README.md (#1115)
delete '的'
2025-08-22 18:49:26 +08:00
香草味的纳西妲喵
7cd37427b2 feat: 添加批量转换OGG音频的相关脚本,移动声波配网HTML文件到scripts文件夹下 (#1107)
* feat: 添加批量转换OGG音频的相关脚本,移动声波配网HTML文件到scripts文件夹下

* Rename

* moved README.md
2025-08-22 00:53:18 +08:00
laride
2d772dad68 fix: resolve some audio issues on esp-hi (#1027)
* fix: resolve crash when closing codec dev on esp-hi

* fix: fix incorrect status display in non-zh-CN languages

* fix: reduce noise when not in Speaking state
2025-08-19 11:50:00 +08:00
Terrence
156eb15f58 fix: dual mic without afe 2025-08-16 03:08:00 +08:00
80 changed files with 1837 additions and 225 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.8.8")
set(PROJECT_VER "1.9.2")
# Add this line to disable the specific warning
add_compile_options(-Wno-missing-field-initializers)

View File

@@ -130,7 +130,7 @@
## 大模型配置
如果你已经拥有一个小智 AI 聊天机器人设备,并且已接入官方服务器,可以登录 [xiaozhi.me](https://xiaozhi.me) 控制台进行配置。
如果你已经拥有一个小智 AI 聊天机器人设备,并且已接入官方服务器,可以登录 [xiaozhi.me](https://xiaozhi.me) 控制台进行配置。
👉 [后台操作视频教程(旧版界面)](https://www.bilibili.com/video/BV1jUCUY2EKM/)

View File

@@ -86,6 +86,8 @@ elseif(CONFIG_BOARD_TYPE_ATOMS3R_ECHO_BASE)
set(BOARD_TYPE "atoms3r-echo-base")
elseif(CONFIG_BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE)
set(BOARD_TYPE "atoms3r-cam-m12-echo-base")
elseif(CONFIG_BOARD_TYPE_ATOM_ECHOS3R)
set(BOARD_TYPE "atom-echos3r")
elseif(CONFIG_BOARD_TYPE_ATOMMATRIX_ECHO_BASE)
set(BOARD_TYPE "atommatrix-echo-base")
elseif(CONFIG_BOARD_TYPE_XMINI_C3_V3)
@@ -104,6 +106,8 @@ elseif(CONFIG_BOARD_TYPE_ESP_HI)
set(BOARD_TYPE "esp-hi")
elseif(CONFIG_BOARD_TYPE_ECHOEAR)
set(BOARD_TYPE "echoear")
elseif(CONFIG_BOARD_TYPE_ESP32S3_AUDIO_BOARD)
set(BOARD_TYPE "waveshare-s3-audio-board")
elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8)
set(BOARD_TYPE "esp32-s3-touch-amoled-1.8")
elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06)
@@ -394,4 +398,4 @@ spiffs_create_partition_assets(
MMAP_FILE_SUPPORT_FORMAT ".aaf, ttf, bin"
IMPORT_INC_PATH ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}
)
endif()
endif()

View File

@@ -178,9 +178,15 @@ choice BOARD_TYPE
config BOARD_TYPE_ATOMS3R_CAM_M12_ECHO_BASE
bool "AtomS3R CAM/M12 + Echo Base"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ATOM_ECHOS3R
bool "AtomEchoS3R"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ATOMMATRIX_ECHO_BASE
bool "AtomMatrix + Echo Base"
depends on IDF_TARGET_ESP32
config BOARD_TYPE_ESP32S3_AUDIO_BOARD
bool "Waveshare ESP32-S3-Audio-Board"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8
bool "Waveshare ESP32-S3-Touch-AMOLED-1.8"
depends on IDF_TARGET_ESP32S3
@@ -422,15 +428,27 @@ endchoice
choice DISPLAY_ESP32S3_KORVO2_V3
depends on BOARD_TYPE_ESP32S3_KORVO2_V3
prompt "ESP32S3_KORVO2_V3 LCD Type"
default LCD_ST7789
default ESP32S3_KORVO2_V3_LCD_ST7789
help
屏幕类型选择
config LCD_ST7789
config ESP32S3_KORVO2_V3_LCD_ST7789
bool "ST7789, 分辨率240*280"
config LCD_ILI9341
config ESP32S3_KORVO2_V3_LCD_ILI9341
bool "ILI9341, 分辨率240*320"
endchoice
choice DISPLAY_ESP32S3_AUDIO_BOARD
depends on BOARD_TYPE_ESP32S3_AUDIO_BOARD
prompt "ESP32S3_AUDIO_BOARD LCD Type"
default AUDIO_BOARD_LCD_JD9853
help
屏幕类型选择
config AUDIO_BOARD_LCD_JD9853
bool "JD9853, 分辨率320*172"
config AUDIO_BOARD_LCD_ST7789
bool "ST7789, 分辨率240*320"
endchoice
config USE_WECHAT_MESSAGE_STYLE
bool "Enable WeChat Message Style"
default n
@@ -490,7 +508,10 @@ config USE_AUDIO_PROCESSOR
config USE_DEVICE_AEC
bool "Enable Device-Side AEC"
default n
depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE || BOARD_TYPE_LICHUANG_DEV || BOARD_TYPE_ESP32S3_KORVO2_V3 || BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 || BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06 || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2)
depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE || BOARD_TYPE_LICHUANG_DEV \
|| BOARD_TYPE_ESP32S3_KORVO2_V3 || BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75 || BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06 \
|| BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_4B || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 \
|| BOARD_TYPE_ECHOEAR)
help
因为性能不够,不建议和微信聊天界面风格同时开启

View File

@@ -5,7 +5,6 @@
#include "audio_codec.h"
#include "mqtt_protocol.h"
#include "websocket_protocol.h"
#include "font_awesome_symbols.h"
#include "assets/lang_config.h"
#include "mcp_server.h"
@@ -14,6 +13,7 @@
#include <cJSON.h>
#include <driver/gpio.h>
#include <arpa/inet.h>
#include <font_awesome.h>
#define TAG "Application"
@@ -49,7 +49,7 @@ Application::Application() {
esp_timer_create_args_t clock_timer_args = {
.callback = [](void* arg) {
Application* app = (Application*)arg;
app->OnClockTimer();
xEventGroupSetBits(app->event_group_, MAIN_EVENT_CLOCK_TICK);
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
@@ -87,7 +87,7 @@ void Application::CheckNewVersion(Ota& ota) {
char buffer[256];
snprintf(buffer, sizeof(buffer), Lang::Strings::CHECK_NEW_VERSION_FAILED, retry_delay, ota.GetCheckVersionUrl().c_str());
Alert(Lang::Strings::ERROR, buffer, "sad", Lang::Sounds::OGG_EXCLAMATION);
Alert(Lang::Strings::ERROR, buffer, "cloud_slash", Lang::Sounds::OGG_EXCLAMATION);
ESP_LOGW(TAG, "Check new version failed, retry in %d seconds (%d/%d)", retry_delay, retry_count, MAX_RETRY);
for (int i = 0; i < retry_delay; i++) {
@@ -103,13 +103,12 @@ void Application::CheckNewVersion(Ota& ota) {
retry_delay = 10; // 重置重试延迟时间
if (ota.HasNewVersion()) {
Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "happy", Lang::Sounds::OGG_UPGRADE);
Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "download", Lang::Sounds::OGG_UPGRADE);
vTaskDelay(pdMS_TO_TICKS(3000));
SetDeviceState(kDeviceStateUpgrading);
display->SetIcon(FONT_AWESOME_DOWNLOAD);
std::string message = std::string(Lang::Strings::NEW_VERSION) + ota.GetFirmwareVersion();
display->SetChatMessage("system", message.c_str());
@@ -130,7 +129,7 @@ void Application::CheckNewVersion(Ota& ota) {
ESP_LOGE(TAG, "Firmware upgrade failed, restarting audio service and continuing operation...");
audio_service_.Start(); // Restart audio service
board.SetPowerSaveMode(true); // Restore power save mode
Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "sad", Lang::Sounds::OGG_EXCLAMATION);
Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION);
vTaskDelay(pdMS_TO_TICKS(3000));
// Continue to normal operation (don't break, just fall through)
} else {
@@ -195,7 +194,7 @@ void Application::ShowActivationCode(const std::string& code, const std::string&
}};
// This sentence uses 9KB of SRAM, so we need to wait for it to finish
Alert(Lang::Strings::ACTIVATION, message.c_str(), "happy", Lang::Sounds::OGG_ACTIVATION);
Alert(Lang::Strings::ACTIVATION, message.c_str(), "link", Lang::Sounds::OGG_ACTIVATION);
for (const auto& digit : code) {
auto it = std::find_if(digit_sounds.begin(), digit_sounds.end(),
@@ -207,7 +206,7 @@ void Application::ShowActivationCode(const std::string& code, const std::string&
}
void Application::Alert(const char* status, const char* message, const char* emotion, const std::string_view& sound) {
ESP_LOGW(TAG, "Alert %s: %s [%s]", status, message, emotion);
ESP_LOGW(TAG, "Alert [%s] %s: %s", emotion, status, message);
auto display = Board::GetInstance().GetDisplay();
display->SetStatus(status);
display->SetEmotion(emotion);
@@ -333,6 +332,9 @@ void Application::Start() {
/* Setup the display */
auto display = board.GetDisplay();
// Print board name/version info
display->SetChatMessage("system", SystemInfo::GetUserAgent().c_str());
/* Setup the audio service */
auto codec = board.GetAudioCodec();
audio_service_.Initialize(codec);
@@ -350,6 +352,12 @@ void Application::Start() {
};
audio_service_.SetCallbacks(callbacks);
// Start the main event loop task with priority 3
xTaskCreate([](void* arg) {
((Application*)arg)->MainEventLoop();
vTaskDelete(NULL);
}, "main_event_loop", 2048 * 4, this, 3, &main_event_loop_task_handle_);
/* Start the clock timer to update the status bar */
esp_timer_start_periodic(clock_timer_handle_, 1000000);
@@ -378,6 +386,10 @@ void Application::Start() {
protocol_ = std::make_unique<MqttProtocol>();
}
protocol_->OnConnected([this]() {
DismissAlert();
});
protocol_->OnNetworkError([this](const std::string& message) {
last_error_message_ = message;
xEventGroupSetBits(event_group_, MAIN_EVENT_ERROR);
@@ -493,6 +505,8 @@ void Application::Start() {
});
bool protocol_started = protocol_->Start();
// Print heap stats
SystemInfo::PrintHeapStats();
SetDeviceState(kDeviceStateIdle);
has_server_time_ = ota.HasServerTime();
@@ -503,23 +517,6 @@ void Application::Start() {
// Play the success sound to indicate the device is ready
audio_service_.PlaySound(Lang::Sounds::OGG_SUCCESS);
}
// Print heap stats
SystemInfo::PrintHeapStats();
}
void Application::OnClockTimer() {
clock_ticks_++;
auto display = Board::GetInstance().GetDisplay();
display->UpdateStatusBar();
// Print the debug info every 10 seconds
if (clock_ticks_ % 10 == 0) {
// SystemInfo::PrintTaskCpuUsage(pdMS_TO_TICKS(1000));
// SystemInfo::PrintTaskList();
SystemInfo::PrintHeapStats();
}
}
// Add a async task to MainLoop
@@ -535,18 +532,17 @@ void Application::Schedule(std::function<void()> callback) {
// If other tasks need to access the websocket or chat state,
// they should use Schedule to call this function
void Application::MainEventLoop() {
// Raise the priority of the main event loop to avoid being interrupted by background tasks (which has priority 2)
vTaskPrioritySet(NULL, 3);
while (true) {
auto bits = xEventGroupWaitBits(event_group_, MAIN_EVENT_SCHEDULE |
MAIN_EVENT_SEND_AUDIO |
MAIN_EVENT_WAKE_WORD_DETECTED |
MAIN_EVENT_VAD_CHANGE |
MAIN_EVENT_CLOCK_TICK |
MAIN_EVENT_ERROR, pdTRUE, pdFALSE, portMAX_DELAY);
if (bits & MAIN_EVENT_ERROR) {
SetDeviceState(kDeviceStateIdle);
Alert(Lang::Strings::ERROR, last_error_message_.c_str(), "sad", Lang::Sounds::OGG_EXCLAMATION);
Alert(Lang::Strings::ERROR, last_error_message_.c_str(), "circle_xmark", Lang::Sounds::OGG_EXCLAMATION);
}
if (bits & MAIN_EVENT_SEND_AUDIO) {
@@ -576,6 +572,19 @@ void Application::MainEventLoop() {
task();
}
}
if (bits & MAIN_EVENT_CLOCK_TICK) {
clock_ticks_++;
auto display = Board::GetInstance().GetDisplay();
display->UpdateStatusBar();
// Print the debug info every 10 seconds
if (clock_ticks_ % 10 == 0) {
// SystemInfo::PrintTaskCpuUsage(pdMS_TO_TICKS(1000));
// SystemInfo::PrintTaskList();
SystemInfo::PrintHeapStats();
}
}
}
}
@@ -734,11 +743,20 @@ bool Application::CanEnterSleepMode() {
}
void Application::SendMcpMessage(const std::string& payload) {
Schedule([this, payload]() {
if (protocol_) {
if (protocol_ == nullptr) {
return;
}
// Make sure you are using main thread to send MCP message
if (xTaskGetCurrentTaskHandle() == main_event_loop_task_handle_) {
ESP_LOGI(TAG, "Send MCP message in main thread");
protocol_->SendMcpMessage(payload);
} else {
ESP_LOGI(TAG, "Send MCP message in sub thread");
Schedule([this, payload = std::move(payload)]() {
protocol_->SendMcpMessage(payload);
}
});
});
}
}
void Application::SetAecMode(AecMode mode) {

View File

@@ -9,7 +9,6 @@
#include <string>
#include <mutex>
#include <deque>
#include <vector>
#include <memory>
#include "protocol.h"
@@ -17,12 +16,15 @@
#include "audio_service.h"
#include "device_state_event.h"
#define MAIN_EVENT_SCHEDULE (1 << 0)
#define MAIN_EVENT_SEND_AUDIO (1 << 1)
#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2)
#define MAIN_EVENT_VAD_CHANGE (1 << 3)
#define MAIN_EVENT_ERROR (1 << 4)
#define MAIN_EVENT_CHECK_NEW_VERSION_DONE (1 << 5)
#define MAIN_EVENT_CLOCK_TICK (1 << 6)
enum AecMode {
kAecOff,
@@ -80,12 +82,27 @@ private:
bool aborted_ = false;
int clock_ticks_ = 0;
TaskHandle_t check_new_version_task_handle_ = nullptr;
TaskHandle_t main_event_loop_task_handle_ = nullptr;
void OnWakeWordDetected();
void CheckNewVersion(Ota& ota);
void ShowActivationCode(const std::string& code, const std::string& message);
void OnClockTimer();
void SetListeningMode(ListeningMode mode);
};
class TaskPriorityReset {
public:
TaskPriorityReset(BaseType_t priority) {
original_priority_ = uxTaskPriorityGet(NULL);
vTaskPrioritySet(NULL, priority);
}
~TaskPriorityReset() {
vTaskPrioritySet(NULL, original_priority_);
}
private:
BaseType_t original_priority_;
};
#endif // _APPLICATION_H_

View File

@@ -100,18 +100,18 @@ void AudioService::Start() {
#if CONFIG_USE_AUDIO_PROCESSOR
/* Start the audio input task */
xTaskCreatePinnedToCore([](void* arg) {
xTaskCreate([](void* arg) {
AudioService* audio_service = (AudioService*)arg;
audio_service->AudioInputTask();
vTaskDelete(NULL);
}, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_, 1);
}, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_);
/* Start the audio output task */
xTaskCreate([](void* arg) {
AudioService* audio_service = (AudioService*)arg;
audio_service->AudioOutputTask();
vTaskDelete(NULL);
}, "audio_output", 2048 * 2, this, 3, &audio_output_task_handle_);
}, "audio_output", 2048 * 2, this, 4, &audio_output_task_handle_);
#else
/* Start the audio input task */
xTaskCreate([](void* arg) {
@@ -125,7 +125,7 @@ void AudioService::Start() {
AudioService* audio_service = (AudioService*)arg;
audio_service->AudioOutputTask();
vTaskDelete(NULL);
}, "audio_output", 2048, this, 3, &audio_output_task_handle_);
}, "audio_output", 2048, this, 4, &audio_output_task_handle_);
#endif
/* Start the opus codec task */
@@ -540,6 +540,12 @@ void AudioService::SetCallbacks(AudioServiceCallbacks& callbacks) {
}
void AudioService::PlaySound(const std::string_view& ogg) {
if (!codec_->output_enabled()) {
esp_timer_stop(audio_power_timer_);
esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000);
codec_->EnableOutput(true);
}
const uint8_t* buf = reinterpret_cast<const uint8_t*>(ogg.data());
size_t size = ogg.size();
size_t offset = 0;

View File

@@ -64,7 +64,7 @@ BoxAudioCodec::BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int
es7210_codec_cfg_t es7210_cfg = {};
es7210_cfg.ctrl_if = in_ctrl_if_;
es7210_cfg.mic_selected = ES7120_SEL_MIC1 | ES7120_SEL_MIC2 | ES7120_SEL_MIC3 | ES7120_SEL_MIC4;
es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4;
in_codec_if_ = es7210_codec_new(&es7210_cfg);
assert(in_codec_if_ != NULL);

View File

@@ -13,11 +13,6 @@ void NoAudioProcessor::Feed(std::vector<int16_t>&& data) {
return;
}
if (data.size() != frame_samples_) {
ESP_LOGE(TAG, "Feed data size is not equal to frame size, feed size: %u, frame size: %u", data.size(), frame_samples_);
return;
}
if (codec_->input_channels() == 2) {
// If input channels is 2, we need to fetch the left channel data
auto mono_data = std::vector<int16_t>(data.size() / 2);

View File

@@ -0,0 +1,45 @@
# AtomEchoS3R
## 简介
AtomEchoS3R 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的物联网可编程控制器,采用了 ES8311 单声道音频解码器、MEMS 麦克风和 NS4150B 功率放大器的集成方案。
开发版**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
## 配置、编译命令
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig 并配置**
```bash
idf.py menuconfig
```
分别配置如下选项:
- `Xiaozhi Assistant``Board Type` → 选择 `AtomEchoS3R`
- `Partition Table``Custom partition CSV file` → 删除原有内容,输入 `partitions/v1/8m.csv`
- `Serial flasher config``Flash size` → 选择 `8 MB`
- `Component config``ESP PSRAM``Support for external, SPI-connected RAM``SPI RAM config` → 选择 `Octal Mode PSRAM`
`S` 保存,按 `Q` 退出。
**编译**
```bash
idf.py build
```
**烧录**
将 AtomEchoS3R 连接到电脑,按住侧面 RESET 按键,直到 RESET 按键下方绿灯闪烁。
```bash
idf.py flash
```
烧录完毕后,按一下 RESET 按钮重启设备。

View File

@@ -0,0 +1,92 @@
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "i2c_device.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <wifi_station.h>
#define TAG "AtomEchoS3R"
class AtomEchoS3rBaseBoard : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Button boot_button_;
void InitializeI2c() {
// 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, &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 InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
public:
AtomEchoS3rBaseBoard() : boot_button_(USER_BUTTON_GPIO) {
InitializeI2c();
I2cDetect();
InitializeButtons();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8311AudioCodec 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,
AUDIO_CODEC_GPIO_PA,
AUDIO_CODEC_ES8311_ADDR,
false);
return &audio_codec;
}
};
DECLARE_BOARD(AtomEchoS3rBaseBoard);

View File

@@ -0,0 +1,29 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
// AtomEchoS3R Board configuration
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11
#define AUDIO_I2S_GPIO_WS GPIO_NUM_3
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_45
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_0
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_GPIO_PA GPIO_NUM_18
#define BUILTIN_LED_GPIO GPIO_NUM_NC
#define USER_BUTTON_GPIO GPIO_NUM_41
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,12 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atom-echos3r",
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/8m.csv\""
]
}
]
}

View File

@@ -111,7 +111,7 @@ private:
InitializeButtons();
GetBacklight()->SetBrightness(100);
display_->SetStatus(Lang::Strings::ERROR);
display_->SetEmotion("sad");
display_->SetEmotion("triangle_exclamation");
display_->SetChatMessage("system", "Echo Base\nnot connected");
while (1) {

View File

@@ -177,7 +177,7 @@ private:
InitializeButtons();
GetBacklight()->SetBrightness(100);
display_->SetStatus(Lang::Strings::ERROR);
display_->SetEmotion("sad");
display_->SetEmotion("triangle_exclamation");
display_->SetChatMessage("system", "Echo Base\nnot connected");
while (1) {

View File

@@ -28,7 +28,7 @@ Esp32Camera::Esp32Camera(const camera_config_t& config) {
memset(&preview_image_, 0, sizeof(preview_image_));
preview_image_.header.magic = LV_IMAGE_HEADER_MAGIC;
preview_image_.header.cf = LV_COLOR_FORMAT_RGB565;
preview_image_.header.flags = LV_IMAGE_FLAGS_ALLOCATED | LV_IMAGE_FLAGS_MODIFIABLE;
preview_image_.header.flags = 0;
switch (config.frame_size) {
case FRAMESIZE_SVGA:
@@ -89,6 +89,7 @@ bool Esp32Camera::Capture() {
encoder_thread_.join();
}
auto start_time = esp_timer_get_time();
int frames_to_get = 2;
// Try to get a stable frame
for (int i = 0; i < frames_to_get; i++) {
@@ -101,6 +102,8 @@ bool Esp32Camera::Capture() {
return false;
}
}
auto end_time = esp_timer_get_time();
ESP_LOGI(TAG, "Camera captured %d frames in %d ms", frames_to_get, int((end_time - start_time) / 1000));
// 如果预览图片 buffer 为空,则跳过预览
// 但仍返回 true因为此时图像可以上传至服务器

View File

@@ -2,11 +2,11 @@
#include "application.h"
#include "display.h"
#include "font_awesome_symbols.h"
#include "assets/lang_config.h"
#include <esp_log.h>
#include <esp_timer.h>
#include <font_awesome.h>
#include <opus_encoder.h>
static const char *TAG = "Ml307Board";
@@ -50,9 +50,9 @@ void Ml307Board::StartNetwork() {
while (true) {
auto result = modem_->WaitForNetworkReady();
if (result == NetworkStatus::ErrorInsertPin) {
application.Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "sad", Lang::Sounds::OGG_ERR_PIN);
application.Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_PIN);
} else if (result == NetworkStatus::ErrorRegistrationDenied) {
application.Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "sad", Lang::Sounds::OGG_ERR_REG);
application.Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG);
} else {
break;
}
@@ -80,13 +80,13 @@ const char* Ml307Board::GetNetworkStateIcon() {
if (csq == -1) {
return FONT_AWESOME_SIGNAL_OFF;
} else if (csq >= 0 && csq <= 14) {
return FONT_AWESOME_SIGNAL_1;
return FONT_AWESOME_SIGNAL_WEAK;
} else if (csq >= 15 && csq <= 19) {
return FONT_AWESOME_SIGNAL_2;
return FONT_AWESOME_SIGNAL_FAIR;
} else if (csq >= 20 && csq <= 24) {
return FONT_AWESOME_SIGNAL_3;
return FONT_AWESOME_SIGNAL_GOOD;
} else if (csq >= 25 && csq <= 31) {
return FONT_AWESOME_SIGNAL_4;
return FONT_AWESOME_SIGNAL_STRONG;
}
ESP_LOGW(TAG, "Invalid CSQ: %d", csq);

View File

@@ -3,7 +3,6 @@
#include "display.h"
#include "application.h"
#include "system_info.h"
#include "font_awesome_symbols.h"
#include "settings.h"
#include "assets/lang_config.h"
@@ -12,6 +11,7 @@
#include <esp_network.h>
#include <esp_log.h>
#include <font_awesome.h>
#include <wifi_station.h>
#include <wifi_configuration_ap.h>
#include <ssid_manager.h>
@@ -49,7 +49,7 @@ void WifiBoard::EnterWifiConfigMode() {
hint += "\n\n";
// 播报配置 WiFi 的提示
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "", Lang::Sounds::OGG_WIFICONFIG);
application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG);
#if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING
auto display = Board::GetInstance().GetDisplay();
@@ -124,7 +124,7 @@ const char* WifiBoard::GetNetworkStateIcon() {
}
auto& wifi_station = WifiStation::GetInstance();
if (!wifi_station.IsConnected()) {
return FONT_AWESOME_WIFI_OFF;
return FONT_AWESOME_WIFI_SLASH;
}
int8_t rssi = wifi_station.GetRssi();
if (rssi >= -60) {

View File

@@ -3,7 +3,6 @@
#include "display/lcd_display.h"
#include "esp_lcd_ili9341.h"
#include "led_control.h"
#include "font_awesome_symbols.h"
#include "application.h"
#include "button.h"
#include "config.h"

View File

@@ -4,7 +4,8 @@
{
"name": "echoear",
"sdkconfig_append": [
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m_echoear.csv\""
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m_echoear.csv\"",
"CONFIG_USE_DEVICE_AEC=y"
]
}
]

View File

@@ -1,12 +1,12 @@
#include "electron_emoji_display.h"
#include <esp_log.h>
#include <font_awesome.h>
#include <algorithm>
#include <cstring>
#include <string>
#include "font_awesome_symbols.h"
#define TAG "ElectronEmojiDisplay"
@@ -141,7 +141,7 @@ void ElectronEmojiDisplay::SetChatMessage(const char* role, const char* content)
}
lv_label_set_text(chat_message_label_, content);
lv_obj_clear_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
ESP_LOGI(TAG, "设置聊天消息 [%s]: %s", role, content);
}
@@ -163,7 +163,7 @@ void ElectronEmojiDisplay::SetIcon(const char* icon) {
}
lv_label_set_text(chat_message_label_, icon_message.c_str());
lv_obj_clear_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
ESP_LOGI(TAG, "设置图标: %s", icon);
}

View File

@@ -2,7 +2,6 @@
#include "codecs/box_audio_codec.h"
#include "display/lcd_display.h"
#include "esp_lcd_ili9341.h"
#include "font_awesome_symbols.h"
#include "application.h"
#include "button.h"
#include "config.h"

View File

@@ -2,7 +2,6 @@
#include "box_audio_codec_lite.h"
#include "display/lcd_display.h"
#include "esp_lcd_ili9341.h"
#include "font_awesome_symbols.h"
#include "application.h"
#include "button.h"
#include "config.h"

View File

@@ -2,7 +2,6 @@
#include "codecs/box_audio_codec.h"
#include "display/lcd_display.h"
#include "esp_lcd_ili9341.h"
#include "font_awesome_symbols.h"
#include "application.h"
#include "button.h"
#include "config.h"

View File

@@ -141,8 +141,7 @@ void AdcPdmAudioCodec::EnableInput(bool enable) {
};
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
} else {
// ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
return;
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}
AudioCodec::EnableInput(enable);
}

View File

@@ -3,6 +3,7 @@
#include <esp_log.h>
#include "mmap_generate_emoji.h"
#include "emoji_display.h"
#include "assets/lang_config.h"
#include <esp_lcd_panel_io.h>
#include <freertos/FreeRTOS.h>
@@ -146,9 +147,9 @@ void EmojiWidget::SetEmotion(const char* emotion)
void EmojiWidget::SetStatus(const char* status)
{
if (player_) {
if (strcmp(status, "聆听中...") == 0) {
if (strcmp(status, Lang::Strings::LISTENING) == 0) {
player_->StartPlayer(MMAP_EMOJI_ASKING_AAF, true, 15);
} else if (strcmp(status, "待命") == 0) {
} else if (strcmp(status, Lang::Strings::STANDBY) == 0) {
player_->StartPlayer(MMAP_EMOJI_WAKE_AAF, true, 15);
}
}

View File

@@ -23,6 +23,7 @@
#include "servo_dog_ctrl.h"
#include "led_strip.h"
#include "driver/rmt_tx.h"
#include "device_state_event.h"
#include "sdkconfig.h"
@@ -284,13 +285,14 @@ private:
ESP_LOGI(TAG, "Create emoji widget, panel: %p, panel_io: %p", panel, panel_io);
display_ = new anim::EmojiWidget(panel, panel_io);
#if CONFIG_ESP_CONSOLE_NONE
servo_dog_ctrl_config_t config = {
.fl_gpio_num = FL_GPIO_NUM,
.fr_gpio_num = FR_GPIO_NUM,
.bl_gpio_num = BL_GPIO_NUM,
.br_gpio_num = BR_GPIO_NUM,
};
#if CONFIG_ESP_CONSOLE_NONE
servo_dog_ctrl_init(&config);
#endif
}
@@ -378,7 +380,7 @@ private:
int r = properties["r"].value<int>();
int g = properties["g"].value<int>();
int b = properties["b"].value<int>();
led_on_ = true;
SetLedColor(r, g, b);
return true;
@@ -395,6 +397,11 @@ public:
InitializeSpi();
InitializeLcdDisplay();
InitializeTools();
DeviceStateEventManager::GetInstance().RegisterStateChangeCallback([this](DeviceState previous_state, DeviceState current_state) {
ESP_LOGD(TAG, "Device state changed from %d to %d", previous_state, current_state);
this->GetAudioCodec()->EnableOutput(current_state == kDeviceStateSpeaking);
});
}
virtual AudioCodec* GetAudioCodec() override

View File

@@ -1,7 +1,6 @@
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "font_awesome_symbols.h"
#include "application.h"
#include "button.h"
#include "config.h"

View File

@@ -1,7 +1,6 @@
#include "wifi_board.h"
#include "display/lcd_display.h"
#include "esp_lcd_sh8601.h"
#include "font_awesome_symbols.h"
#include "codecs/es8311_audio_codec.h"
#include "application.h"

View File

@@ -26,7 +26,7 @@
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
#ifdef CONFIG_LCD_ST7789
#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ST7789
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
#define DISPLAY_WIDTH 280
@@ -40,7 +40,7 @@
#define DISPLAY_OFFSET_Y 0
#endif
#ifdef CONFIG_LCD_ILI9341
#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ILI9341
#define LCD_TYPE_ILI9341_SERIAL
#define DISPLAY_SDA_PIN GPIO_NUM_NC
#define DISPLAY_SCL_PIN GPIO_NUM_NC
@@ -78,4 +78,4 @@
#define CAMERA_PIN_PCLK 11
#define XCLK_FREQ_HZ 20000000
#endif // _BOARD_CONFIG_H_
#endif // _BOARD_CONFIG_H_

View File

@@ -64,7 +64,7 @@ CoreS3AudioCodec::CoreS3AudioCodec(void* i2c_master_handle, int input_sample_rat
es7210_codec_cfg_t es7210_cfg = {};
es7210_cfg.ctrl_if = in_ctrl_if_;
es7210_cfg.mic_selected = ES7120_SEL_MIC1 | ES7120_SEL_MIC2 | ES7120_SEL_MIC3;
es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3;
in_codec_if_ = es7210_codec_new(&es7210_cfg);
assert(in_codec_if_ != NULL);

View File

@@ -2,7 +2,6 @@
#include "tab5_audio_codec.h"
#include "display/lcd_display.h"
#include "esp_lcd_ili9881c.h"
#include "font_awesome_symbols.h"
#include "font_emoji.h"
#include "application.h"
#include "button.h"

View File

@@ -66,7 +66,7 @@ Tab5AudioCodec::Tab5AudioCodec(void* i2c_master_handle, int input_sample_rate, i
es7210_codec_cfg_t es7210_cfg = {};
es7210_cfg.ctrl_if = in_ctrl_if_;
es7210_cfg.mic_selected = ES7120_SEL_MIC1 | ES7120_SEL_MIC2 | ES7120_SEL_MIC3 | ES7120_SEL_MIC4;
es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4;
in_codec_if_ = es7210_codec_new(&es7210_cfg);
assert(in_codec_if_ != NULL);

View File

@@ -5,7 +5,6 @@
#include "button.h"
#include "led/circular_strip.h"
#include "config.h"
#include "font_awesome_symbols.h"
#include "assets/lang_config.h"
#include <esp_lcd_panel_vendor.h>

View File

@@ -6,7 +6,6 @@
#include "led/circular_strip.h"
#include "config.h"
#include "assets/lang_config.h"
#include "font_awesome_symbols.h"
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>

View File

@@ -6,7 +6,6 @@
#include "led/single_led.h"
#include "config.h"
#include "power_save_timer.h"
#include "font_awesome_symbols.h"
#include <wifi_station.h>
#include <esp_log.h>

View File

@@ -6,7 +6,6 @@
#include "led/single_led.h"
#include "config.h"
#include "power_save_timer.h"
#include "font_awesome_symbols.h"
#include <wifi_station.h>
#include <esp_log.h>

View File

@@ -1,13 +1,13 @@
#include "otto_emoji_display.h"
#include <esp_log.h>
#include <font_awesome.h>
#include <algorithm>
#include <cstring>
#include <string>
#include "display/lcd_display.h"
#include "font_awesome_symbols.h"
#define TAG "OttoEmojiDisplay"
@@ -142,7 +142,7 @@ void OttoEmojiDisplay::SetChatMessage(const char* role, const char* content) {
}
lv_label_set_text(chat_message_label_, content);
lv_obj_clear_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
ESP_LOGI(TAG, "设置聊天消息 [%s]: %s", role, content);
}
@@ -164,7 +164,7 @@ void OttoEmojiDisplay::SetIcon(const char* icon) {
}
lv_label_set_text(chat_message_label_, icon_message.c_str());
lv_obj_clear_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(chat_message_label_, LV_OBJ_FLAG_HIDDEN);
ESP_LOGI(TAG, "设置图标: %s", icon);
}

View File

@@ -3,7 +3,6 @@
#include "wifi_board.h"
#include "sensecap_audio_codec.h"
#include "display/lcd_display.h"
#include "font_awesome_symbols.h"
#include "application.h"
#include "knob.h"
#include "config.h"

View File

@@ -21,8 +21,8 @@
#define TAG "SURFERC3114TFT"
LV_FONT_DECLARE(font_puhui_16_4);
LV_FONT_DECLARE(font_awesome_16_4);
LV_FONT_DECLARE(font_puhui_20_4);
LV_FONT_DECLARE(font_awesome_20_4);
class SurferC3114TFT : public WifiBoard {
private:
@@ -148,8 +148,8 @@ private:
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_16_4,
.icon_font = &font_awesome_16_4,
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
.emoji_font = font_emoji_32_init(),
});
}

View File

@@ -8,7 +8,6 @@
#include "power_save_timer.h"
#include "axp2101.h"
#include "assets/lang_config.h"
#include "font_awesome_symbols.h"
#include <esp_log.h>
#include <driver/gpio.h>

View File

@@ -0,0 +1,3 @@
新增 微雪 开发板: ESP32-S3-AUDIO-Board
产品链接:
https://www.waveshare.net/shop/ESP32-S3-AUDIO-Board.htm

View File

@@ -0,0 +1,95 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#include <driver/spi_master.h>
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define BUILTIN_LED_GPIO GPIO_NUM_38
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_12
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
#define AUDIO_I2S_GPIO_WS GPIO_NUM_14
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16
#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
#define I2C_SCL_IO GPIO_NUM_10
#define I2C_SDA_IO GPIO_NUM_11
#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000
#define DISPLAY_SDA_PIN I2C_SDA_IO
#define DISPLAY_SCL_PIN I2C_SCL_IO
#define DISPLAY_MISO_PIN GPIO_NUM_8
#define DISPLAY_MOSI_PIN GPIO_NUM_9
#define DISPLAY_SCLK_PIN GPIO_NUM_4
#define DISPLAY_CS_PIN GPIO_NUM_3
#define DISPLAY_DC_PIN GPIO_NUM_7
#define DISPLAY_RESET_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_5
#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000)
/* Camera pins */
#define CAMERA_PIN_PWDN -1
#define CAMERA_PIN_RESET -1
#define CAMERA_PIN_XCLK 43
#define CAMERA_PIN_SIOD -1
#define CAMERA_PIN_SIOC -1
#define CAMERA_PIN_D7 48
#define CAMERA_PIN_D6 47
#define CAMERA_PIN_D5 46
#define CAMERA_PIN_D4 45
#define CAMERA_PIN_D3 39
#define CAMERA_PIN_D2 18
#define CAMERA_PIN_D1 17
#define CAMERA_PIN_D0 2
#define CAMERA_PIN_VSYNC 21
#define CAMERA_PIN_HREF 1
#define CAMERA_PIN_PCLK 44
#define XCLK_FREQ_HZ 20000000
#ifdef CONFIG_AUDIO_BOARD_LCD_JD9853
#define LCD_TYPE_JD9853_SERIAL
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 172
#define DISPLAY_SWAP_XY true
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_INVERT_COLOR true
#define BACKLIGHT_INVERT false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#endif
#ifdef CONFIG_AUDIO_BOARD_LCD_ST7789
#define LCD_TYPE_ST7789_SERIAL
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_SWAP_XY false
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_INVERT_COLOR true
#define BACKLIGHT_INVERT false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#endif
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,9 @@
{
"target": "esp32s3",
"builds": [
{
"name": "waveshare-s3-audio-board",
"sdkconfig_append": []
}
]
}

View File

@@ -0,0 +1,247 @@
#include "wifi_board.h"
#include "codecs/box_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include <esp_log.h>
#include "i2c_device.h"
#include <driver/i2c_master.h>
#include <driver/ledc.h>
#include <wifi_station.h>
#include <esp_lcd_panel_io.h>
#include <esp_lcd_panel_ops.h>
#include <esp_lcd_st77916.h>
#include <esp_timer.h>
#include "esp_io_expander_tca95xx_16bit.h"
#include "esp32_camera.h"
#include "led/circular_strip.h"
#include "esp_lcd_jd9853.h"
#define TAG "waveshare_lcd_1_85c"
#define LCD_OPCODE_WRITE_CMD (0x02ULL)
#define LCD_OPCODE_READ_CMD (0x0BULL)
#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
LV_FONT_DECLARE(font_puhui_14_1);
LV_FONT_DECLARE(font_awesome_14_1);
class CustomBoard : public WifiBoard {
private:
Button boot_button_;
i2c_master_bus_handle_t i2c_bus_;
esp_io_expander_handle_t io_expander = NULL;
LcdDisplay* display_;
Esp32Camera* camera_;
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)0,
.sda_io_num = I2C_SDA_IO,
.scl_io_num = I2C_SCL_IO,
.clk_source = I2C_CLK_SRC_DEFAULT,
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
}
void InitializeTca9555(void)
{
esp_err_t ret = esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, I2C_ADDRESS, &io_expander);
if(ret != ESP_OK)
ESP_LOGE(TAG, "TCA9554 create returned error"); // 打印引脚状态
ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_8|IO_EXPANDER_PIN_NUM_5|IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT); // 设置引脚 EXIO0 和 EXIO1 模式为输出
ESP_ERROR_CHECK(ret);
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad
ESP_ERROR_CHECK(ret);
vTaskDelay(pdMS_TO_TICKS(10));
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 复位 LCD 与 TouchPad
ESP_ERROR_CHECK(ret);
vTaskDelay(pdMS_TO_TICKS(10));
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 复位 LCD 与 TouchPad
ESP_ERROR_CHECK(ret);
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_8, 1); // 启用喇叭功放
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, false); // 复位摄像头
vTaskDelay(pdMS_TO_TICKS(5));
ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, true);
vTaskDelay(pdMS_TO_TICKS(5));
ESP_ERROR_CHECK(ret);
}
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_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;
// 液晶屏控制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 = DISPLAY_SPI_SCLK_HZ;
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));
// 初始化液晶屏驱动芯片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;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR));
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_14_1,
.icon_font = &font_awesome_14_1,
.emoji_font = font_emoji_32_init(),
});
}
void InitializeJd9853Display() {
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 = DISPLAY_SPI_SCLK_HZ;
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));
// 初始化液晶屏驱动芯片JD9853
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;
//ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
ESP_ERROR_CHECK(esp_lcd_new_panel_jd9853(panel_io, &panel_config, &panel));
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 0, 34));
ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false));
ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, true));
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_14_1,
.icon_font = &font_awesome_14_1,
.emoji_font = font_emoji_32_init(),
});
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
}
void InitializeCamera() {
camera_config_t config = {};
config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用
config.ledc_timer = LEDC_TIMER_2; // LEDC timer选择 用于生成XCLK时钟 但是S3不用
config.pin_d0 = CAMERA_PIN_D0;
config.pin_d1 = CAMERA_PIN_D1;
config.pin_d2 = CAMERA_PIN_D2;
config.pin_d3 = CAMERA_PIN_D3;
config.pin_d4 = CAMERA_PIN_D4;
config.pin_d5 = CAMERA_PIN_D5;
config.pin_d6 = CAMERA_PIN_D6;
config.pin_d7 = CAMERA_PIN_D7;
config.pin_xclk = CAMERA_PIN_XCLK;
config.pin_pclk = CAMERA_PIN_PCLK;
config.pin_vsync = CAMERA_PIN_VSYNC;
config.pin_href = CAMERA_PIN_HREF;
config.pin_sccb_sda = CAMERA_PIN_SIOD; // 这里如果写-1 表示使用已经初始化的I2C接口
config.pin_sccb_scl = CAMERA_PIN_SIOC;
config.sccb_i2c_port = 0; // 这里如果写1 默认使用I2C1
config.pin_pwdn = CAMERA_PIN_PWDN;
config.pin_reset = CAMERA_PIN_RESET;
config.xclk_freq_hz = XCLK_FREQ_HZ;
config.pixel_format = PIXFORMAT_RGB565;
config.frame_size = FRAMESIZE_QVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
config.fb_location = CAMERA_FB_IN_PSRAM;
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
camera_ = new Esp32Camera(config);
camera_->SetVFlip(1);
}
public:
CustomBoard() :
boot_button_(BOOT_BUTTON_GPIO) {
InitializeI2c();
InitializeTca9555();
InitializeSpi();
InitializeButtons();
#ifdef LCD_TYPE_JD9853_SERIAL
InitializeJd9853Display();
#else
InitializeSt7789Display();
#endif
InitializeCamera();
GetBacklight()->RestoreBrightness();
}
virtual Led* GetLed() override {
static CircularStrip led(BUILTIN_LED_GPIO, 6);
return &led;
}
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(DISPLAY_BACKLIGHT_PIN, BACKLIGHT_INVERT);
return &backlight;
}
virtual Camera* GetCamera() override {
return camera_;
}
};
DECLARE_BOARD(CustomBoard);

View File

@@ -0,0 +1,460 @@
#include <stdio.h>
#include "esp_lcd_jd9853.h"
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdlib.h>
#include <sys/cdefs.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_commands.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_check.h"
static const char *TAG = "JD9853";
static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel);
static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel);
static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool off);
typedef struct
{
esp_lcd_panel_t base;
esp_lcd_panel_io_handle_t io;
int reset_gpio_num;
bool reset_level;
int x_gap;
int y_gap;
uint8_t fb_bits_per_pixel;
uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register
const jd9853_lcd_init_cmd_t *init_cmds;
uint16_t init_cmds_size;
} jd9853_panel_t;
esp_err_t esp_lcd_new_panel_jd9853(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
{
esp_err_t ret = ESP_OK;
jd9853_panel_t *jd9853 = NULL;
gpio_config_t io_conf = {0};
ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
jd9853 = (jd9853_panel_t *)calloc(1, sizeof(jd9853_panel_t));
ESP_GOTO_ON_FALSE(jd9853, ESP_ERR_NO_MEM, err, TAG, "no mem for jd9853 panel");
if (panel_dev_config->reset_gpio_num >= 0)
{
io_conf.mode = GPIO_MODE_OUTPUT;
io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num;
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
}
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
switch (panel_dev_config->color_space)
{
case ESP_LCD_COLOR_SPACE_RGB:
jd9853->madctl_val = 0;
break;
case ESP_LCD_COLOR_SPACE_BGR:
jd9853->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
break;
}
#else
switch (panel_dev_config->rgb_endian)
{
case LCD_RGB_ENDIAN_RGB:
jd9853->madctl_val = 0;
break;
case LCD_RGB_ENDIAN_BGR:
jd9853->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian");
break;
}
#endif
switch (panel_dev_config->bits_per_pixel)
{
case 16: // RGB565
jd9853->colmod_val = 0x55;
jd9853->fb_bits_per_pixel = 16;
break;
case 18: // RGB666
jd9853->colmod_val = 0x66;
// each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel
jd9853->fb_bits_per_pixel = 24;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
break;
}
jd9853->io = io;
jd9853->reset_gpio_num = panel_dev_config->reset_gpio_num;
jd9853->reset_level = panel_dev_config->flags.reset_active_high;
if (panel_dev_config->vendor_config)
{
jd9853->init_cmds = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds;
jd9853->init_cmds_size = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size;
}
jd9853->base.del = panel_jd9853_del;
jd9853->base.reset = panel_jd9853_reset;
jd9853->base.init = panel_jd9853_init;
jd9853->base.draw_bitmap = panel_jd9853_draw_bitmap;
jd9853->base.invert_color = panel_jd9853_invert_color;
jd9853->base.set_gap = panel_jd9853_set_gap;
jd9853->base.mirror = panel_jd9853_mirror;
jd9853->base.swap_xy = panel_jd9853_swap_xy;
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
jd9853->base.disp_off = panel_jd9853_disp_on_off;
#else
jd9853->base.disp_on_off = panel_jd9853_disp_on_off;
#endif
*ret_panel = &(jd9853->base);
ESP_LOGD(TAG, "new jd9853 panel @%p", jd9853);
// ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_jd9853_VER_MAJOR, ESP_LCD_jd9853_VER_MINOR,
// ESP_LCD_jd9853_VER_PATCH);
return ESP_OK;
err:
if (jd9853)
{
if (panel_dev_config->reset_gpio_num >= 0)
{
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
free(jd9853);
}
return ret;
}
static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel)
{
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
if (jd9853->reset_gpio_num >= 0)
{
gpio_reset_pin(jd9853->reset_gpio_num);
}
ESP_LOGD(TAG, "del jd9853 panel @%p", jd9853);
free(jd9853);
return ESP_OK;
}
static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel)
{
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
esp_lcd_panel_io_handle_t io = jd9853->io;
// perform hardware reset
if (jd9853->reset_gpio_num >= 0)
{
gpio_set_level(jd9853->reset_gpio_num, jd9853->reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(jd9853->reset_gpio_num, !jd9853->reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
}
else
{ // perform software reset
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command
}
return ESP_OK;
}
typedef struct
{
uint8_t cmd;
uint8_t data[16];
uint8_t data_bytes; // Length of data in above data array; 0xFF = end of cmds.
} lcd_init_cmd_t;
// static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = {
// // {cmd, { data }, data_size, delay_ms}
// /* Power contorl B, power control = 0, DC_ENA = 1 */
// {0xCF, (uint8_t []){0x00, 0xAA, 0XE0}, 3, 0},
// /* Power on sequence control,
// * cp1 keeps 1 frame, 1st frame enable
// * vcl = 0, ddvdh=3, vgh=1, vgl=2
// * DDVDH_ENH=1
// */
// {0xED, (uint8_t []){0x67, 0x03, 0X12, 0X81}, 4, 0},
// /* Driver timing control A,
// * non-overlap=default +1
// * EQ=default - 1, CR=default
// * pre-charge=default - 1
// */
// {0xE8, (uint8_t []){0x8A, 0x01, 0x78}, 3, 0},
// /* Power control A, Vcore=1.6V, DDVDH=5.6V */
// {0xCB, (uint8_t []){0x39, 0x2C, 0x00, 0x34, 0x02}, 5, 0},
// /* Pump ratio control, DDVDH=2xVCl */
// {0xF7, (uint8_t []){0x20}, 1, 0},
// {0xF7, (uint8_t []){0x20}, 1, 0},
// /* Driver timing control, all=0 unit */
// {0xEA, (uint8_t []){0x00, 0x00}, 2, 0},
// /* Power control 1, GVDD=4.75V */
// {0xC0, (uint8_t []){0x23}, 1, 0},
// /* Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 */
// {0xC1, (uint8_t []){0x11}, 1, 0},
// /* VCOM control 1, VCOMH=4.025V, VCOML=-0.950V */
// {0xC5, (uint8_t []){0x43, 0x4C}, 2, 0},
// /* VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 */
// {0xC7, (uint8_t []){0xA0}, 1, 0},
// /* Frame rate control, f=fosc, 70Hz fps */
// {0xB1, (uint8_t []){0x00, 0x1B}, 2, 0},
// /* Enable 3G, disabled */
// {0xF2, (uint8_t []){0x00}, 1, 0},
// /* Gamma set, curve 1 */
// {0x26, (uint8_t []){0x01}, 1, 0},
// /* Positive gamma correction */
// {0xE0, (uint8_t []){0x1F, 0x36, 0x36, 0x3A, 0x0C, 0x05, 0x4F, 0X87, 0x3C, 0x08, 0x11, 0x35, 0x19, 0x13, 0x00}, 15, 0},
// /* Negative gamma correction */
// {0xE1, (uint8_t []){0x00, 0x09, 0x09, 0x05, 0x13, 0x0A, 0x30, 0x78, 0x43, 0x07, 0x0E, 0x0A, 0x26, 0x2C, 0x1F}, 15, 0},
// /* Entry mode set, Low vol detect disabled, normal display */
// {0xB7, (uint8_t []){0x07}, 1, 0},
// /* Display function control */
// {0xB6, (uint8_t []){0x08, 0x82, 0x27}, 3, 0},
// };
static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = {
{0x11, (uint8_t []){ 0x00 }, 0, 120},
{0xDF, (uint8_t[]){0x98, 0x53}, 2, 0},
{0xDF, (uint8_t[]){0x98, 0x53}, 2, 0},
{0xB2, (uint8_t[]){0x23}, 1, 0},
{0xB7, (uint8_t[]){0x00, 0x47, 0x00, 0x6F}, 4, 0},
{0xBB, (uint8_t[]){0x1C, 0x1A, 0x55, 0x73, 0x63, 0xF0}, 6, 0},
{0xC0, (uint8_t[]){0x44, 0xA4}, 2, 0},
{0xC1, (uint8_t[]){0x16}, 1, 0},
{0xC3, (uint8_t[]){0x7D, 0x07, 0x14, 0x06, 0xCF, 0x71, 0x72, 0x77}, 8, 0},
{0xC4, (uint8_t[]){0x00, 0x00, 0xA0, 0x79, 0x0B, 0x0A, 0x16, 0x79, 0x0B, 0x0A, 0x16, 0x82}, 12, 0}, // 00=60Hz 06=57Hz 08=51Hz, LN=320 Line
{0xC8, (uint8_t[]){0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00, 0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00}, 32, 0}, // SET_R_GAMMA
{0xD0, (uint8_t[]){0x04, 0x06, 0x6B, 0x0F, 0x00}, 5, 0},
{0xD7, (uint8_t[]){0x00, 0x30}, 2, 0},
{0xE6, (uint8_t[]){0x14}, 1, 0},
{0xDE, (uint8_t[]){0x01}, 1, 0},
{0xB7, (uint8_t[]){0x03, 0x13, 0xEF, 0x35, 0x35}, 5, 0},
{0xC1, (uint8_t[]){0x14, 0x15, 0xC0}, 3, 0},
{0xC2, (uint8_t[]){0x06, 0x3A}, 2, 0},
{0xC4, (uint8_t[]){0x72, 0x12}, 2, 0},
{0xBE, (uint8_t[]){0x00}, 1, 0},
{0xDE, (uint8_t[]){0x02}, 1, 0},
{0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0},
{0xE5, (uint8_t[]){0x01, 0x02, 0x00}, 3, 0},
{0xDE, (uint8_t[]){0x00}, 1, 0},
{0x35, (uint8_t[]){0x00}, 1, 0},
{0x3A, (uint8_t[]){0x05}, 1, 0}, // 06=RGB66605=RGB565
{0x2A, (uint8_t[]){0x00, 0x22, 0x00, 0xCD}, 4, 0}, // Start_X=34, End_X=205
{0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x3F}, 4, 0}, // Start_Y=0, End_Y=319
{0xDE, (uint8_t[]){0x02}, 1, 0},
{0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0},
{0xDE, (uint8_t[]){0x00}, 1, 0},
{0x29, (uint8_t []){ 0x00 }, 0, 0},
};
static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel)
{
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
esp_lcd_panel_io_handle_t io = jd9853->io;
// LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed");
vTaskDelay(pdMS_TO_TICKS(100));
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){
jd9853->madctl_val,
},
1),
TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){
jd9853->colmod_val,
},
1),
TAG, "send command failed");
const jd9853_lcd_init_cmd_t *init_cmds = NULL;
uint16_t init_cmds_size = 0;
if (jd9853->init_cmds)
{
init_cmds = jd9853->init_cmds;
init_cmds_size = jd9853->init_cmds_size;
}
else
{
init_cmds = vendor_specific_init_default;
init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(jd9853_lcd_init_cmd_t);
}
bool is_cmd_overwritten = false;
for (int i = 0; i < init_cmds_size; i++)
{
// Check if the command has been used or conflicts with the internal
switch (init_cmds[i].cmd)
{
case LCD_CMD_MADCTL:
is_cmd_overwritten = true;
jd9853->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
break;
case LCD_CMD_COLMOD:
is_cmd_overwritten = true;
jd9853->colmod_val = ((uint8_t *)init_cmds[i].data)[0];
break;
default:
is_cmd_overwritten = false;
break;
}
if (is_cmd_overwritten)
{
ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd);
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed");
vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
}
ESP_LOGD(TAG, "send init commands success");
return ESP_OK;
}
static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
esp_lcd_panel_io_handle_t io = jd9853->io;
x_start += jd9853->x_gap;
x_end += jd9853->x_gap;
y_start += jd9853->y_gap;
y_end += jd9853->y_gap;
// define an area of frame memory where MCU can access
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){
(x_start >> 8) & 0xFF,
x_start & 0xFF,
((x_end - 1) >> 8) & 0xFF,
(x_end - 1) & 0xFF,
},
4),
TAG, "send command failed");
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){
(y_start >> 8) & 0xFF,
y_start & 0xFF,
((y_end - 1) >> 8) & 0xFF,
(y_end - 1) & 0xFF,
},
4),
TAG, "send command failed");
// transfer frame buffer
size_t len = (x_end - x_start) * (y_end - y_start) * jd9853->fb_bits_per_pixel / 8;
esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len);
return ESP_OK;
}
static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
esp_lcd_panel_io_handle_t io = jd9853->io;
int command = 0;
if (invert_color_data)
{
command = LCD_CMD_INVON;
}
else
{
command = LCD_CMD_INVOFF;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
return ESP_OK;
}
static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
esp_lcd_panel_io_handle_t io = jd9853->io;
if (mirror_x)
{
jd9853->madctl_val |= LCD_CMD_MX_BIT;
}
else
{
jd9853->madctl_val &= ~LCD_CMD_MX_BIT;
}
if (mirror_y)
{
jd9853->madctl_val |= LCD_CMD_MY_BIT;
}
else
{
jd9853->madctl_val &= ~LCD_CMD_MY_BIT;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed");
return ESP_OK;
}
static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
{
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
esp_lcd_panel_io_handle_t io = jd9853->io;
if (swap_axes)
{
jd9853->madctl_val |= LCD_CMD_MV_BIT;
}
else
{
jd9853->madctl_val &= ~LCD_CMD_MV_BIT;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed");
return ESP_OK;
}
static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
{
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
jd9853->x_gap = x_gap;
jd9853->y_gap = y_gap;
return ESP_OK;
}
static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
{
jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
esp_lcd_panel_io_handle_t io = jd9853->io;
int command = 0;
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
on_off = !on_off;
#endif
if (on_off)
{
command = LCD_CMD_DISPON;
}
else
{
command = LCD_CMD_DISPOFF;
}
ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
return ESP_OK;
}

View File

@@ -0,0 +1,102 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LCD: jd9853
*/
#pragma once
#include "hal/spi_ll.h"
#include "esp_lcd_panel_vendor.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LCD panel initialization commands.
*
*/
typedef struct {
int cmd; /*<! The specific LCD command */
const void *data; /*<! Buffer that holds the command specific data */
size_t data_bytes; /*<! Size of `data` in memory, in bytes */
unsigned int delay_ms; /*<! Delay in milliseconds after this command */
} jd9853_lcd_init_cmd_t;
/**
* @brief LCD panel vendor configuration.
*
* @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
*
*/
typedef struct {
const jd9853_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
* The array should be declared as `static const` and positioned outside the function.
* Please refer to `vendor_specific_init_default` in source file.
*/
uint16_t init_cmds_size; /*<! Number of commands in above array */
} jd9853_vendor_config_t;
/**
* @brief Create LCD panel for model jd9853
*
* @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config general panel device configuration
* @param[out] ret_panel Returned LCD panel handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NO_MEM if out of memory
* - ESP_OK on success
*/
esp_err_t esp_lcd_new_panel_jd9853(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
/**
* @brief LCD panel bus configuration structure
*
* @param[in] sclk SPI clock pin number
* @param[in] mosi SPI MOSI pin number
* @param[in] max_trans_sz Maximum transfer size in bytes
*
*/
#define JD9853_PANEL_BUS_SPI_CONFIG(sclk, mosi, max_trans_sz) \
{ \
.sclk_io_num = sclk, \
.mosi_io_num = mosi, \
.miso_io_num = -1, \
.quadhd_io_num = -1, \
.quadwp_io_num = -1, \
.max_transfer_sz = max_trans_sz, \
}
/**
* @brief LCD panel IO configuration structure
*
* @param[in] cs SPI chip select pin number
* @param[in] dc SPI data/command pin number
* @param[in] cb Callback function when SPI transfer is done
* @param[in] cb_ctx Callback function context
*
*/
#define JD9853_PANEL_IO_SPI_CONFIG(cs, dc, callback, callback_ctx) \
{ \
.cs_gpio_num = cs, \
.dc_gpio_num = dc, \
.spi_mode = 0, \
.pclk_hz = 40 * 1000 * 1000, \
.trans_queue_depth = 10, \
.on_color_trans_done = callback, \
.user_ctx = callback_ctx, \
.lcd_cmd_bits = 8, \
.lcd_param_bits = 8, \
}
#ifdef __cplusplus
}
#endif

View File

@@ -1,7 +1,6 @@
#include "wifi_board.h"
#include "display/lcd_display.h"
#include "esp_lcd_sh8601.h"
#include "font_awesome_symbols.h"
#include "codecs/box_audio_codec.h"
#include "application.h"

View File

@@ -1,7 +1,6 @@
#include "wifi_board.h"
#include "display/lcd_display.h"
#include "esp_lcd_sh8601.h"
#include "font_awesome_symbols.h"
#include "codecs/box_audio_codec.h"
#include "application.h"

View File

@@ -3,7 +3,6 @@
#include "lcd_display.h"
#include <vector>
#include <font_awesome_symbols.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_lvgl_port.h>
@@ -350,4 +349,4 @@ CustomLcdDisplay::CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_p
}
SetupUI();
}
}

View File

@@ -8,7 +8,6 @@
#include "settings.h"
#include "config.h"
#include "sleep_timer.h"
#include "font_awesome_symbols.h"
#include "adc_battery_monitor.h"
#include "press_to_talk_mcp_tool.h"

View File

@@ -8,7 +8,6 @@
#include "settings.h"
#include "config.h"
#include "power_save_timer.h"
#include "font_awesome_symbols.h"
#include "adc_battery_monitor.h"
#include "press_to_talk_mcp_tool.h"

View File

@@ -8,7 +8,6 @@
#include "settings.h"
#include "config.h"
#include "power_save_timer.h"
#include "font_awesome_symbols.h"
#include "press_to_talk_mcp_tool.h"
#include <wifi_station.h>

View File

@@ -15,7 +15,7 @@ public:
void SetupHighTempWarningPopup() {
// 创建高温警告弹窗
high_temp_popup_ = lv_obj_create(lv_scr_act()); // 使用当前屏幕
high_temp_popup_ = lv_obj_create(lv_screen_active()); // 使用当前屏幕
lv_obj_set_scrollbar_mode(high_temp_popup_, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_size(high_temp_popup_, LV_HOR_RES * 0.9, fonts_.text_font->line_height * 2);
lv_obj_align(high_temp_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
@@ -47,7 +47,7 @@ public:
void ShowHighTempWarning() {
if (high_temp_popup_ && lv_obj_has_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN)) {
lv_obj_clear_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN);
}
}

View File

@@ -3,11 +3,11 @@
#include <string>
#include <cstdlib>
#include <cstring>
#include <font_awesome.h>
#include "display.h"
#include "board.h"
#include "application.h"
#include "font_awesome_symbols.h"
#include "audio_codec.h"
#include "settings.h"
#include "assets/lang_config.h"
@@ -21,7 +21,7 @@ Display::Display() {
Display *display = static_cast<Display*>(arg);
DisplayLockGuard lock(display);
lv_obj_add_flag(display->notification_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_clear_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN);
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
@@ -67,7 +67,7 @@ void Display::SetStatus(const char* status) {
return;
}
lv_label_set_text(status_label_, status);
lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
last_status_update_time_ = std::chrono::system_clock::now();
@@ -83,7 +83,7 @@ void Display::ShowNotification(const char* notification, int duration_ms) {
return;
}
lv_label_set_text(notification_label_, notification);
lv_obj_clear_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_add_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
esp_timer_stop(notification_timer_);
@@ -105,7 +105,7 @@ void Display::UpdateStatusBar(bool update_all) {
// 如果静音状态改变,则更新图标
if (codec->output_volume() == 0 && !muted_) {
muted_ = true;
lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_MUTE);
lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_XMARK);
} else if (codec->output_volume() > 0 && muted_) {
muted_ = false;
lv_label_set_text(mute_label_, "");
@@ -136,13 +136,13 @@ void Display::UpdateStatusBar(bool update_all) {
const char* icon = nullptr;
if (board.GetBatteryLevel(battery_level, charging, discharging)) {
if (charging) {
icon = FONT_AWESOME_BATTERY_CHARGING;
icon = FONT_AWESOME_BATTERY_BOLT;
} else {
const char* levels[] = {
FONT_AWESOME_BATTERY_EMPTY, // 0-19%
FONT_AWESOME_BATTERY_1, // 20-39%
FONT_AWESOME_BATTERY_2, // 40-59%
FONT_AWESOME_BATTERY_3, // 60-79%
FONT_AWESOME_BATTERY_QUARTER, // 20-39%
FONT_AWESOME_BATTERY_HALF, // 40-59%
FONT_AWESOME_BATTERY_THREE_QUARTERS, // 60-79%
FONT_AWESOME_BATTERY_FULL, // 80-99%
FONT_AWESOME_BATTERY_FULL, // 100%
};
@@ -157,7 +157,7 @@ void Display::UpdateStatusBar(bool update_all) {
if (low_battery_popup_ != nullptr) {
if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0 && discharging) {
if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框隐藏,则显示
lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
app.PlaySound(Lang::Sounds::OGG_LOW_BATTERY);
}
} else {
@@ -196,50 +196,11 @@ void Display::UpdateStatusBar(bool update_all) {
void Display::SetEmotion(const char* emotion) {
struct Emotion {
const char* icon;
const char* text;
};
static const std::vector<Emotion> emotions = {
{FONT_AWESOME_EMOJI_NEUTRAL, "neutral"},
{FONT_AWESOME_EMOJI_HAPPY, "happy"},
{FONT_AWESOME_EMOJI_LAUGHING, "laughing"},
{FONT_AWESOME_EMOJI_FUNNY, "funny"},
{FONT_AWESOME_EMOJI_SAD, "sad"},
{FONT_AWESOME_EMOJI_ANGRY, "angry"},
{FONT_AWESOME_EMOJI_CRYING, "crying"},
{FONT_AWESOME_EMOJI_LOVING, "loving"},
{FONT_AWESOME_EMOJI_EMBARRASSED, "embarrassed"},
{FONT_AWESOME_EMOJI_SURPRISED, "surprised"},
{FONT_AWESOME_EMOJI_SHOCKED, "shocked"},
{FONT_AWESOME_EMOJI_THINKING, "thinking"},
{FONT_AWESOME_EMOJI_WINKING, "winking"},
{FONT_AWESOME_EMOJI_COOL, "cool"},
{FONT_AWESOME_EMOJI_RELAXED, "relaxed"},
{FONT_AWESOME_EMOJI_DELICIOUS, "delicious"},
{FONT_AWESOME_EMOJI_KISSY, "kissy"},
{FONT_AWESOME_EMOJI_CONFIDENT, "confident"},
{FONT_AWESOME_EMOJI_SLEEPY, "sleepy"},
{FONT_AWESOME_EMOJI_SILLY, "silly"},
{FONT_AWESOME_EMOJI_CONFUSED, "confused"}
};
// 查找匹配的表情
std::string_view emotion_view(emotion);
auto it = std::find_if(emotions.begin(), emotions.end(),
[&emotion_view](const Emotion& e) { return e.text == emotion_view; });
DisplayLockGuard lock(this);
if (emotion_label_ == nullptr) {
return;
}
// 如果找到匹配的表情就显示对应图标否则显示默认的neutral表情
if (it != emotions.end()) {
lv_label_set_text(emotion_label_, it->icon);
const char* utf8 = font_awesome_get_utf8(emotion);
if (utf8 != nullptr) {
SetIcon(utf8);
} else {
lv_label_set_text(emotion_label_, FONT_AWESOME_EMOJI_NEUTRAL);
SetIcon(FONT_AWESOME_NEUTRAL);
}
}

View File

@@ -1,15 +1,15 @@
#include "lcd_display.h"
#include "assets/lang_config.h"
#include "settings.h"
#include <vector>
#include <algorithm>
#include <font_awesome_symbols.h>
#include <font_awesome.h>
#include <esp_log.h>
#include <esp_err.h>
#include <esp_lvgl_port.h>
#include <esp_heap_caps.h>
#include "assets/lang_config.h"
#include <esp_psram.h>
#include <cstring>
#include "settings.h"
#include "board.h"
@@ -102,10 +102,24 @@ SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
ESP_LOGI(TAG, "Initialize LVGL library");
lv_init();
#if CONFIG_SPIRAM
// lv image cache, currently only PNG is supported
size_t psram_size_mb = esp_psram_get_size() / 1024 / 1024;
if (psram_size_mb >= 8) {
lv_image_cache_resize(2 * 1024 * 1024, true);
ESP_LOGI(TAG, "Use 2MB of PSRAM for image cache");
} else if (psram_size_mb >= 2) {
lv_image_cache_resize(512 * 1024, true);
ESP_LOGI(TAG, "Use 512KB of PSRAM for image cache");
}
#endif
ESP_LOGI(TAG, "Initialize LVGL port");
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
port_cfg.task_priority = 1;
port_cfg.timer_period_ms = 50;
#if CONFIG_SOC_CPU_CORES_NUM > 1
port_cfg.task_affinity = 1;
#endif
lvgl_port_init(&port_cfg);
ESP_LOGI(TAG, "Adding LCD display");
@@ -167,7 +181,9 @@ RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h
ESP_LOGI(TAG, "Initialize LVGL port");
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
port_cfg.task_priority = 1;
port_cfg.timer_period_ms = 50;
#if CONFIG_SOC_CPU_CORES_NUM > 1
port_cfg.task_affinity = 1;
#endif
lvgl_port_init(&port_cfg);
ESP_LOGI(TAG, "Adding LCD display");
@@ -226,6 +242,10 @@ MipiLcdDisplay::MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel
ESP_LOGI(TAG, "Initialize LVGL port");
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
port_cfg.task_priority = 1;
#if CONFIG_SOC_CPU_CORES_NUM > 1
port_cfg.task_affinity = 1;
#endif
lvgl_port_init(&port_cfg);
ESP_LOGI(TAG, "Adding LCD display");
@@ -367,7 +387,7 @@ void LcdDisplay::SetupUI() {
emotion_label_ = lv_label_create(status_bar_);
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
lv_obj_set_style_margin_right(emotion_label_, 5, 0); // 添加右边距,与后面的元素分隔
notification_label_ = lv_label_create(status_bar_);
@@ -621,7 +641,7 @@ void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
// 设置自定义属性标记气泡类型
lv_obj_set_user_data(img_bubble, (void*)"image");
// Create the image object inside the bubble
lv_obj_t* preview_image = lv_image_create(img_bubble);
@@ -744,7 +764,7 @@ void LcdDisplay::SetupUI() {
emotion_label_ = lv_label_create(content_);
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0);
lv_obj_set_style_text_color(emotion_label_, current_theme_.text, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
preview_image_ = lv_image_create(content_);
lv_obj_set_size(preview_image_, width_ * 0.5, height_ * 0.5);
@@ -814,11 +834,13 @@ void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
}
if (img_dsc != nullptr) {
// zoom factor 0.5
lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w);
// 设置图片源并显示预览图片
lv_image_set_src(preview_image_, img_dsc);
lv_obj_clear_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
if (img_dsc->header.w > 0) {
// zoom factor 0.5
lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w);
}
lv_obj_remove_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
// 隐藏emotion_label_
if (emotion_label_ != nullptr) {
lv_obj_add_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
@@ -827,7 +849,7 @@ void LcdDisplay::SetPreviewImage(const lv_img_dsc_t* img_dsc) {
// 隐藏预览图片并显示emotion_label_
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
if (emotion_label_ != nullptr) {
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
}
}
}
@@ -867,6 +889,13 @@ void LcdDisplay::SetEmotion(const char* emotion) {
std::string_view emotion_view(emotion);
auto it = std::find_if(emotions.begin(), emotions.end(),
[&emotion_view](const Emotion& e) { return e.text == emotion_view; });
if (fonts_.emoji_font == nullptr || it == emotions.end()) {
const char* utf8 = font_awesome_get_utf8(emotion);
if (utf8 != nullptr) {
SetIcon(utf8);
}
return;
}
DisplayLockGuard lock(this);
if (emotion_label_ == nullptr) {
@@ -883,7 +912,7 @@ void LcdDisplay::SetEmotion(const char* emotion) {
#if !CONFIG_USE_WECHAT_MESSAGE_STYLE
// 显示emotion_label_隐藏preview_image_
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
if (preview_image_ != nullptr) {
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
}
@@ -900,7 +929,7 @@ void LcdDisplay::SetIcon(const char* icon) {
#if !CONFIG_USE_WECHAT_MESSAGE_STYLE
// 显示emotion_label_隐藏preview_image_
lv_obj_clear_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(emotion_label_, LV_OBJ_FLAG_HIDDEN);
if (preview_image_ != nullptr) {
lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
}

View File

@@ -1,5 +1,4 @@
#include "oled_display.h"
#include "font_awesome_symbols.h"
#include "assets/lang_config.h"
#include <string>
@@ -8,6 +7,7 @@
#include <esp_log.h>
#include <esp_err.h>
#include <esp_lvgl_port.h>
#include <font_awesome.h>
#define TAG "OledDisplay"
@@ -23,7 +23,9 @@ OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handl
lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
port_cfg.task_priority = 1;
port_cfg.task_stack = 6144;
port_cfg.timer_period_ms = 50;
#if CONFIG_SOC_CPU_CORES_NUM > 1
port_cfg.task_affinity = 1;
#endif
lvgl_port_init(&port_cfg);
ESP_LOGI(TAG, "Adding OLED display");
@@ -112,7 +114,7 @@ void OledDisplay::SetChatMessage(const char* role, const char* content) {
lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
} else {
lv_label_set_text(chat_message_label_, content_str.c_str());
lv_obj_clear_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
lv_obj_remove_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
}
}
}
@@ -157,7 +159,7 @@ void OledDisplay::SetupUI_128x64() {
emotion_label_ = lv_label_create(content_left_);
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
lv_obj_center(emotion_label_);
lv_obj_set_style_pad_top(emotion_label_, 8, 0);
@@ -249,7 +251,7 @@ void OledDisplay::SetupUI_128x32() {
emotion_label_ = lv_label_create(content_);
lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0);
lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP);
lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
lv_obj_center(emotion_label_);
/* Right side */

View File

@@ -13,27 +13,27 @@ dependencies:
espressif/esp_io_expander_tca9554: ==2.0.0
espressif/esp_lcd_panel_io_additions: ^1.0.1
78/esp_lcd_nv3023: ~1.0.0
78/esp-wifi-connect: ~2.5.0
78/esp-wifi-connect: ~2.5.2
78/esp-opus-encoder: ~2.4.1
78/esp-ml307: ~3.2.6
78/xiaozhi-fonts: ~1.4.0
espressif/led_strip: ^2.5.5
espressif/esp_codec_dev: ~1.3.6
espressif/esp-sr: ==2.1.4
78/esp-ml307: ~3.3.5
78/xiaozhi-fonts: ~1.5.2
espressif/led_strip: ~3.0.1
espressif/esp_codec_dev: ~1.4.0
espressif/esp-sr: ~2.1.5
espressif/button: ~4.1.3
espressif/knob: ^1.0.0
espressif/esp32-camera: ^2.0.15
espressif/esp32-camera: ~2.1.2
espressif/esp_lcd_touch_ft5x06: ~1.0.7
espressif/esp_lcd_touch_gt911: ^1
espressif/esp_lcd_touch_gt1151: ^1
waveshare/esp_lcd_touch_cst9217: ^1.0.3
espressif/esp_lcd_touch_cst816s: ^1.0.6
lvgl/lvgl: ~9.2.2
lvgl/lvgl: ~9.3.0
esp_lvgl_port: ~2.6.0
espressif/esp_io_expander_tca95xx_16bit: ^2.0.0
espressif2022/image_player: ==1.1.0~1
espressif2022/esp_emote_gfx: ^1.0.0
espressif/adc_mic: ^0.2.0
espressif2022/esp_emote_gfx: ==1.0.0~2
espressif/adc_mic: ^0.2.1
espressif/esp_mmap_assets: '>=1.2'
txp666/otto-emoji-gif-component: ~1.0.2
espressif/adc_battery_estimation: ^0.2.0

View File

@@ -15,7 +15,7 @@ CircularStrip::CircularStrip(gpio_num_t gpio, uint8_t max_leds) : max_leds_(max_
led_strip_config_t strip_config = {};
strip_config.strip_gpio_num = gpio;
strip_config.max_leds = max_leds_;
strip_config.led_pixel_format = LED_PIXEL_FORMAT_GRB;
strip_config.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB;
strip_config.led_model = LED_MODEL_WS2812;
led_strip_rmt_config_t rmt_config = {};

View File

@@ -18,7 +18,7 @@ SingleLed::SingleLed(gpio_num_t gpio) {
led_strip_config_t strip_config = {};
strip_config.strip_gpio_num = gpio;
strip_config.max_leds = 1;
strip_config.led_pixel_format = LED_PIXEL_FORMAT_GRB;
strip_config.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB;
strip_config.led_model = LED_MODEL_WS2812;
led_strip_rmt_config_t rmt_config = {};

View File

@@ -27,5 +27,4 @@ extern "C" void app_main(void)
// Launch the application
auto& app = Application::GetInstance();
app.Start();
app.MainEventLoop();
}

View File

@@ -16,8 +16,6 @@
#define TAG "MCP"
#define DEFAULT_TOOLCALL_STACK_SIZE 6144
McpServer::McpServer() {
}
@@ -29,12 +27,17 @@ McpServer::~McpServer() {
}
void McpServer::AddCommonTools() {
// To speed up the response time, we add the common tools to the beginning of
// *Important* To speed up the response time, we add the common tools to the beginning of
// the tools list to utilize the prompt cache.
// **重要** 为了提升响应速度,我们把常用的工具放在前面,利用 prompt cache 的特性。
// Backup the original tools list and restore it after adding the common tools.
auto original_tools = std::move(tools_);
auto& board = Board::GetInstance();
// Do not add custom tools here.
// Custom tools must be added in the board's InitializeTools function.
AddTool("self.get_device_status",
"Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\n"
"Use this tool for: \n"
@@ -95,6 +98,9 @@ void McpServer::AddCommonTools() {
Property("question", kPropertyTypeString)
}),
[camera](const PropertyList& properties) -> ReturnValue {
// Lower the priority to do the camera capture
TaskPriorityReset priority_reset(1);
if (!camera->Capture()) {
return "{\"success\": false, \"message\": \"Failed to capture photo\"}";
}
@@ -122,6 +128,12 @@ void McpServer::AddTool(const std::string& name, const std::string& description,
AddTool(new McpTool(name, description, properties, callback));
}
void McpServer::AddUserOnlyTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback) {
auto tool = new McpTool(name, description, properties, callback);
tool->set_user_only(true);
AddTool(tool);
}
void McpServer::ParseMessage(const std::string& message) {
cJSON* json = cJSON_Parse(message.c_str());
if (json == nullptr) {
@@ -224,13 +236,7 @@ void McpServer::ParseMessage(const cJSON* json) {
ReplyError(id_int, "Invalid arguments");
return;
}
auto stack_size = cJSON_GetObjectItem(params, "stackSize");
if (stack_size != nullptr && !cJSON_IsNumber(stack_size)) {
ESP_LOGE(TAG, "tools/call: Invalid stackSize");
ReplyError(id_int, "Invalid stackSize");
return;
}
DoToolCall(id_int, std::string(tool_name->valuestring), tool_arguments, stack_size ? stack_size->valueint : DEFAULT_TOOLCALL_STACK_SIZE);
DoToolCall(id_int, std::string(tool_name->valuestring), tool_arguments);
} else {
ESP_LOGE(TAG, "Method not implemented: %s", method_str.c_str());
ReplyError(id_int, "Method not implemented: " + method_str);
@@ -305,7 +311,7 @@ void McpServer::GetToolsList(int id, const std::string& cursor) {
ReplyResult(id, json);
}
void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments, int stack_size) {
void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments) {
auto tool_iter = std::find_if(tools_.begin(), tools_.end(),
[&tool_name](const McpTool* tool) {
return tool->name() == tool_name;
@@ -347,15 +353,9 @@ void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* to
return;
}
// Start a task to receive data with stack size
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
cfg.thread_name = "tool_call";
cfg.stack_size = stack_size;
cfg.prio = 1;
esp_pthread_set_cfg(&cfg);
// Use a thread to call the tool to avoid blocking the main thread
tool_call_thread_ = std::thread([this, id, tool_iter, arguments = std::move(arguments)]() {
// Use main thread to call the tool
auto& app = Application::GetInstance();
app.Schedule([this, id, tool_iter, arguments = std::move(arguments)]() {
try {
ReplyResult(id, (*tool_iter)->Call(arguments));
} catch (const std::exception& e) {
@@ -363,5 +363,4 @@ void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* to
ReplyError(id, e.what());
}
});
tool_call_thread_.detach();
}

View File

@@ -177,6 +177,7 @@ private:
std::string description_;
PropertyList properties_;
std::function<ReturnValue(const PropertyList&)> callback_;
bool user_only_ = false;
public:
McpTool(const std::string& name,
@@ -188,9 +189,11 @@ public:
properties_(properties),
callback_(callback) {}
void set_user_only(bool user_only) { user_only_ = user_only; }
inline const std::string& name() const { return name_; }
inline const std::string& description() const { return description_; }
inline const PropertyList& properties() const { return properties_; }
inline bool user_only() const { return user_only_; }
std::string to_json() const {
std::vector<std::string> required = properties_.GetRequired();
@@ -214,6 +217,15 @@ public:
}
cJSON_AddItemToObject(json, "inputSchema", input_schema);
// Add audience annotation if the tool is user only (invisible to AI)
if (user_only_) {
cJSON *annotations = cJSON_CreateObject();
cJSON *audience = cJSON_CreateArray();
cJSON_AddItemToArray(audience, cJSON_CreateString("user"));
cJSON_AddItemToObject(annotations, "audience", audience);
cJSON_AddItemToObject(json, "annotations", annotations);
}
char *json_str = cJSON_PrintUnformatted(json);
std::string result(json_str);
@@ -259,6 +271,7 @@ public:
void AddCommonTools();
void AddTool(McpTool* tool);
void AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback);
void AddUserOnlyTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback);
void ParseMessage(const cJSON* json);
void ParseMessage(const std::string& message);
@@ -272,10 +285,9 @@ private:
void ReplyError(int id, const std::string& message);
void GetToolsList(int id, const std::string& cursor);
void DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments, int stack_size);
void DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments);
std::vector<McpTool*> tools_;
std::thread tool_call_thread_;
};
#endif // MCP_SERVER_H

View File

@@ -51,17 +51,17 @@ std::string Ota::GetCheckVersionUrl() {
std::unique_ptr<Http> Ota::SetupHttp() {
auto& board = Board::GetInstance();
auto app_desc = esp_app_get_description();
auto network = board.GetNetwork();
auto http = network->CreateHttp(0);
auto user_agent = SystemInfo::GetUserAgent();
http->SetHeader("Activation-Version", has_serial_number_ ? "2" : "1");
http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
http->SetHeader("Client-Id", board.GetUuid());
if (has_serial_number_) {
http->SetHeader("Serial-Number", serial_number_.c_str());
ESP_LOGI(TAG, "Setup HTTP, User-Agent: %s, Serial-Number: %s", user_agent.c_str(), serial_number_.c_str());
}
http->SetHeader("User-Agent", std::string(BOARD_NAME "/") + app_desc->version);
http->SetHeader("User-Agent", user_agent);
http->SetHeader("Accept-Language", Lang::CODE);
http->SetHeader("Content-Type", "application/json");

View File

@@ -12,11 +12,33 @@
MqttProtocol::MqttProtocol() {
event_group_handle_ = xEventGroupCreate();
// Initialize reconnect timer
esp_timer_create_args_t reconnect_timer_args = {
.callback = [](void* arg) {
MqttProtocol* protocol = (MqttProtocol*)arg;
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateIdle) {
ESP_LOGI(TAG, "Reconnecting to MQTT server");
app.Schedule([protocol]() {
protocol->StartMqttClient(false);
});
}
},
.arg = this,
};
esp_timer_create(&reconnect_timer_args, &reconnect_timer_);
}
MqttProtocol::~MqttProtocol() {
ESP_LOGI(TAG, "MqttProtocol deinit");
vEventGroupDelete(event_group_handle_);
if (reconnect_timer_ != nullptr) {
esp_timer_stop(reconnect_timer_);
esp_timer_delete(reconnect_timer_);
}
if (event_group_handle_ != nullptr) {
vEventGroupDelete(event_group_handle_);
}
}
bool MqttProtocol::Start() {
@@ -50,7 +72,18 @@ bool MqttProtocol::StartMqttClient(bool report_error) {
mqtt_->SetKeepAlive(keepalive_interval);
mqtt_->OnDisconnected([this]() {
ESP_LOGI(TAG, "Disconnected from endpoint");
if (on_disconnected_ != nullptr) {
on_disconnected_();
}
ESP_LOGI(TAG, "MQTT disconnected, schedule reconnect in %d seconds", MQTT_RECONNECT_INTERVAL_MS / 1000);
esp_timer_start_once(reconnect_timer_, MQTT_RECONNECT_INTERVAL_MS * 1000);
});
mqtt_->OnConnected([this]() {
if (on_connected_ != nullptr) {
on_connected_();
}
esp_timer_stop(reconnect_timer_);
});
mqtt_->OnMessage([this](const std::string& topic, const std::string& payload) {

View File

@@ -9,6 +9,7 @@
#include <mbedtls/aes.h>
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <esp_timer.h>
#include <functional>
#include <string>
@@ -16,7 +17,7 @@
#include <mutex>
#define MQTT_PING_INTERVAL_SECONDS 90
#define MQTT_RECONNECT_INTERVAL_MS 10000
#define MQTT_RECONNECT_INTERVAL_MS 60000
#define MQTT_PROTOCOL_SERVER_HELLO_EVENT (1 << 0)
@@ -45,6 +46,7 @@ private:
int udp_port_;
uint32_t local_sequence_;
uint32_t remote_sequence_;
esp_timer_handle_t reconnect_timer_;
bool StartMqttClient(bool report_error=false);
void ParseServerHello(const cJSON* root);

View File

@@ -24,6 +24,14 @@ void Protocol::OnNetworkError(std::function<void(const std::string& message)> ca
on_network_error_ = callback;
}
void Protocol::OnConnected(std::function<void()> callback) {
on_connected_ = callback;
}
void Protocol::OnDisconnected(std::function<void()> callback) {
on_disconnected_ = callback;
}
void Protocol::SetError(const std::string& message) {
error_occurred_ = true;
if (on_network_error_ != nullptr) {

View File

@@ -60,6 +60,8 @@ public:
void OnAudioChannelOpened(std::function<void()> callback);
void OnAudioChannelClosed(std::function<void()> callback);
void OnNetworkError(std::function<void(const std::string& message)> callback);
void OnConnected(std::function<void()> callback);
void OnDisconnected(std::function<void()> callback);
virtual bool Start() = 0;
virtual bool OpenAudioChannel() = 0;
@@ -78,6 +80,8 @@ protected:
std::function<void()> on_audio_channel_opened_;
std::function<void()> on_audio_channel_closed_;
std::function<void(const std::string& message)> on_network_error_;
std::function<void()> on_connected_;
std::function<void()> on_disconnected_;
int server_sample_rate_ = 24000;
int server_frame_duration_ = 60;

View File

@@ -47,6 +47,12 @@ std::string SystemInfo::GetChipModelName() {
return std::string(CONFIG_IDF_TARGET);
}
std::string SystemInfo::GetUserAgent() {
auto app_desc = esp_app_get_description();
auto user_agent = std::string(BOARD_NAME "/") + app_desc->version;
return user_agent;
}
esp_err_t SystemInfo::PrintTaskCpuUsage(TickType_t xTicksToWait) {
#define ARRAY_SIZE_OFFSET 5
TaskStatus_t *start_array = NULL, *end_array = NULL;

View File

@@ -13,6 +13,7 @@ public:
static size_t GetFreeHeapSize();
static std::string GetMacAddress();
static std::string GetChipModelName();
static std::string GetUserAgent();
static esp_err_t PrintTaskCpuUsage(TickType_t xTicksToWait);
static void PrintTaskList();
static void PrintHeapStats();

9
partitions/v2/16m.csv Normal file
View File

@@ -0,0 +1,9 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
model, data, spiffs, 0x10000, 0xF0000,
ota_0, app, ota_0, 0x100000, 4M,
ota_1, app, ota_1, 0x500000, 4M,
assets, data, spiffs, 0x900000, 7M
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x4000,
4 otadata, data, ota, 0xd000, 0x2000,
5 phy_init, data, phy, 0xf000, 0x1000,
6 model, data, spiffs, 0x10000, 0xF0000,
7 ota_0, app, ota_0, 0x100000, 4M,
8 ota_1, app, ota_1, 0x500000, 4M,
9 assets, data, spiffs, 0x900000, 7M

9
partitions/v2/16m_c3.csv Normal file
View File

@@ -0,0 +1,9 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
model, data, spiffs, 0x10000, 0xF0000,
ota_0, app, ota_0, 0x100000, 4M,
ota_1, app, ota_1, 0x500000, 4M,
assets, data, spiffs, 0x900000, 4000K
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x4000,
4 otadata, data, ota, 0xd000, 0x2000,
5 phy_init, data, phy, 0xf000, 0x1000,
6 model, data, spiffs, 0x10000, 0xF0000,
7 ota_0, app, ota_0, 0x100000, 4M,
8 ota_1, app, ota_1, 0x500000, 4M,
9 assets, data, spiffs, 0x900000, 4000K

10
partitions/v2/32m.csv Normal file
View File

@@ -0,0 +1,10 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvsfactory, data, nvs, , 200K,
nvs, data, nvs, , 840K,
otadata, data, ota, , 0x2000,
phy_init, data, phy, , 0x1000,
model, data, spiffs, , 0xF0000,
ota_0, app, ota_0, 0x200000, 4M,
ota_1, app, ota_1, 0x600000, 4M,
assets, data, spiffs, 0xA00000, 16M
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvsfactory, data, nvs, , 200K,
4 nvs, data, nvs, , 840K,
5 otadata, data, ota, , 0x2000,
6 phy_init, data, phy, , 0x1000,
7 model, data, spiffs, , 0xF0000,
8 ota_0, app, ota_0, 0x200000, 4M,
9 ota_1, app, ota_1, 0x600000, 4M,
10 assets, data, spiffs, 0xA00000, 16M

9
partitions/v2/8m.csv Normal file
View File

@@ -0,0 +1,9 @@
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x4000,
otadata, data, ota, 0xd000, 0x2000,
phy_init, data, phy, 0xf000, 0x1000,
model, data, spiffs, 0x10000, 0xF0000,
ota_0, app, ota_0, 0x100000, 3M,
ota_1, app, ota_1, 0x400000, 3M,
assets, data, spiffs, 0x700000, 1M
1 # ESP-IDF Partition Table
2 # Name, Type, SubType, Offset, Size, Flags
3 nvs, data, nvs, 0x9000, 0x4000,
4 otadata, data, ota, 0xd000, 0x2000,
5 phy_init, data, phy, 0xf000, 0x1000,
6 model, data, spiffs, 0x10000, 0xF0000,
7 ota_0, app, ota_0, 0x100000, 3M,
8 ota_1, app, ota_1, 0x400000, 3M,
9 assets, data, spiffs, 0x700000, 1M

107
partitions/v2/README.md Normal file
View File

@@ -0,0 +1,107 @@
# Version 2 Partition Table
This version introduces significant improvements over v1 by adding an `assets` partition to support network-loadable content and optimizing partition layouts for different flash sizes.
## Key Changes from v1
### Major Improvements
1. **Added Assets Partition**: New `assets` partition for network-loadable content
2. **Replaced Model Partition**: The old `model` partition (960KB) is replaced with a larger `assets` partition
3. **Optimized App Partitions**: Reduced application partition sizes to accommodate assets
4. **Enhanced Flexibility**: Support for dynamic content updates without reflashing
### Assets Partition Features
The `assets` partition stores:
- **Wake word models**: Customizable wake word models that can be loaded from the network
- **Theme files**: Complete theming system including:
- Fonts (text and icon fonts)
- Audio effects and sound files
- Background images and UI elements
- Custom emoji packs
- Language configuration files
- **Dynamic Content**: All content can be updated over-the-air via HTTP downloads
## Partition Layout Comparison
### v1 Layout (16MB)
- `nvs`: 16KB (non-volatile storage)
- `otadata`: 8KB (OTA data)
- `phy_init`: 4KB (PHY initialization data)
- `model`: 960KB (model storage - fixed content)
- `ota_0`: 6MB (application partition 0)
- `ota_1`: 6MB (application partition 1)
### v2 Layout (16MB)
- `nvs`: 16KB (non-volatile storage)
- `otadata`: 8KB (OTA data)
- `phy_init`: 4KB (PHY initialization data)
- `ota_0`: 4MB (application partition 0)
- `ota_1`: 4MB (application partition 1)
- `assets`: 8MB (network-loadable assets)
## Available Configurations
### 8MB Flash Devices (`8m.csv`)
- `nvs`: 16KB
- `otadata`: 8KB
- `phy_init`: 4KB
- `ota_0`: 3MB
- `ota_1`: 3MB
- `assets`: 2MB
### 16MB Flash Devices (`16m.csv`) - Standard
- `nvs`: 16KB
- `otadata`: 8KB
- `phy_init`: 4KB
- `ota_0`: 4MB
- `ota_1`: 4MB
- `assets`: 8MB
### 16MB Flash Devices (`16m_c3.csv`) - ESP32-C3 Optimized
- `nvs`: 16KB
- `otadata`: 8KB
- `phy_init`: 4KB
- `ota_0`: 4MB
- `ota_1`: 4MB
- `assets`: 4MB (4000K - limited by available mmap pages)
### 32MB Flash Devices (`32m.csv`)
- `nvsfactory`: 200KB
- `nvs`: 840KB
- `otadata`: 8KB
- `phy_init`: 4KB
- `ota_0`: 4MB
- `ota_1`: 4MB
- `assets`: 16MB
## Benefits
1. **Dynamic Content Management**: Users can download and update wake word models, themes, and other assets without reflashing the device
2. **Reduced App Size**: Application partitions are optimized, allowing more space for dynamic content
3. **Enhanced Customization**: Support for custom themes, wake words, and language packs enhances user experience
4. **Network Flexibility**: Assets can be updated independently of the main application firmware
5. **Better Resource Utilization**: Efficient use of flash memory with configurable asset storage
6. **OTA Asset Updates**: Assets can be updated over-the-air via HTTP downloads
## Technical Details
- **Partition Type**: Assets partition uses `spiffs` subtype for SPIFFS filesystem compatibility
- **Memory Mapping**: Assets are memory-mapped for efficient access during runtime
- **Checksum Validation**: Built-in integrity checking ensures asset data validity
- **Progressive Download**: Assets can be downloaded progressively with progress tracking
- **Fallback Support**: Graceful fallback to default assets if network updates fail
## Migration from v1
When upgrading from v1 to v2:
1. **Backup Important Data**: Ensure any important data in the old `model` partition is backed up
2. **Flash New Partition Table**: Use the appropriate v2 partition table for your flash size
3. **Download Assets**: The device will automatically download required assets on first boot
4. **Verify Functionality**: Ensure all features work correctly with the new partition layout
## Usage Notes
- The `assets` partition size varies by configuration to optimize for different flash sizes
- ESP32-C3 devices use a smaller assets partition (4MB) due to limited available mmap pages in the system
- 32MB devices get the largest assets partition (16MB) for maximum content storage
- All partition tables maintain proper alignment for optimal flash performance

View File

@@ -0,0 +1,29 @@
# ogg_covertor 小智AI OGG 批量转换器
本脚本为OGG批量转换工具支持将输入的音频文件转换为小智可使用的OGG格式
基于Python第三方库`ffmpeg-python`实现
支持OGG和音频之间的互转响度调节等功能
# 创建并激活虚拟环境
```bash
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境
source venv/bin/activate # Mac/Linux
venv\Scripts\activate # Windows
```
# 安装依赖
请在虚拟环境中执行
```bash
pip install ffmpeg-python
```
# 运行脚本
```bash
python ogg_covertor.py
```

View File

@@ -0,0 +1,230 @@
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import os
import threading
import sys
import ffmpeg
class AudioConverterApp:
def __init__(self, master):
self.master = master
master.title("小智AI OGG音频批量转换工具")
master.geometry("680x600") # 调整窗口高度
# 初始化变量
self.mode = tk.StringVar(value="audio_to_ogg")
self.output_dir = tk.StringVar()
self.output_dir.set(os.path.abspath("output"))
self.enable_loudnorm = tk.BooleanVar(value=True)
self.target_lufs = tk.DoubleVar(value=-16.0)
# 创建UI组件
self.create_widgets()
self.redirect_output()
def create_widgets(self):
# 模式选择
mode_frame = ttk.LabelFrame(self.master, text="转换模式")
mode_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew")
ttk.Radiobutton(mode_frame, text="音频转到OGG", variable=self.mode,
value="audio_to_ogg", command=self.toggle_settings,
width=12).grid(row=0, column=0, padx=5)
ttk.Radiobutton(mode_frame, text="OGG转回音频", variable=self.mode,
value="ogg_to_audio", command=self.toggle_settings,
width=12).grid(row=0, column=1, padx=5)
# 响度设置
self.loudnorm_frame = ttk.Frame(self.master)
self.loudnorm_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew")
ttk.Checkbutton(self.loudnorm_frame, text="启用响度调整",
variable=self.enable_loudnorm, width=15
).grid(row=0, column=0, padx=2)
ttk.Entry(self.loudnorm_frame, textvariable=self.target_lufs,
width=6).grid(row=0, column=1, padx=2)
ttk.Label(self.loudnorm_frame, text="LUFS").grid(row=0, column=2, padx=2)
# 文件选择
file_frame = ttk.LabelFrame(self.master, text="输入文件")
file_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
# 文件操作按钮
ttk.Button(file_frame, text="选择文件", command=self.select_files,
width=12).grid(row=0, column=0, padx=5, pady=2)
ttk.Button(file_frame, text="移除选中", command=self.remove_selected,
width=12).grid(row=0, column=1, padx=5, pady=2)
ttk.Button(file_frame, text="清空列表", command=self.clear_files,
width=12).grid(row=0, column=2, padx=5, pady=2)
# 文件列表使用Treeview
self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"),
show="headings", height=8)
self.tree.heading("selected", text="选中", anchor=tk.W)
self.tree.heading("filename", text="文件名", anchor=tk.W)
self.tree.column("selected", width=60, anchor=tk.W)
self.tree.column("filename", width=600, anchor=tk.W)
self.tree.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=5, pady=2)
self.tree.bind("<ButtonRelease-1>", self.on_tree_click)
# 输出目录
output_frame = ttk.LabelFrame(self.master, text="输出目录")
output_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew")
ttk.Entry(output_frame, textvariable=self.output_dir, width=60
).grid(row=0, column=0, padx=5, sticky="ew")
ttk.Button(output_frame, text="浏览", command=self.select_output_dir,
width=8).grid(row=0, column=1, padx=5)
# 转换按钮区域
button_frame = ttk.Frame(self.master)
button_frame.grid(row=4, column=0, padx=10, pady=10, sticky="ew")
ttk.Button(button_frame, text="转换全部文件", command=lambda: self.start_conversion(True),
width=15).pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="转换选中文件", command=lambda: self.start_conversion(False),
width=15).pack(side=tk.LEFT, padx=5)
# 日志区域
log_frame = ttk.LabelFrame(self.master, text="日志")
log_frame.grid(row=5, column=0, padx=10, pady=5, sticky="nsew")
self.log_text = tk.Text(log_frame, height=14, width=80)
self.log_text.pack(fill=tk.BOTH, expand=True)
# 配置布局权重
self.master.columnconfigure(0, weight=1)
self.master.rowconfigure(2, weight=1)
self.master.rowconfigure(5, weight=3)
file_frame.columnconfigure(0, weight=1)
file_frame.rowconfigure(1, weight=1)
def toggle_settings(self):
if self.mode.get() == "audio_to_ogg":
self.loudnorm_frame.grid()
else:
self.loudnorm_frame.grid_remove()
def select_files(self):
file_types = [
("音频文件", "*.wav *.mogg *.ogg *.flac") if self.mode.get() == "audio_to_ogg"
else ("ogg文件", "*.ogg")
]
files = filedialog.askopenfilenames(filetypes=file_types)
for f in files:
self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,))
def on_tree_click(self, event):
"""处理复选框点击事件"""
region = self.tree.identify("region", event.x, event.y)
if region == "cell":
col = self.tree.identify_column(event.x)
item = self.tree.identify_row(event.y)
if col == "#1": # 点击的是选中列
current_val = self.tree.item(item, "values")[0]
new_val = "[√]" if current_val == "[ ]" else "[ ]"
self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1]))
def remove_selected(self):
"""移除选中的文件"""
to_remove = []
for item in self.tree.get_children():
if self.tree.item(item, "values")[0] == "[√]":
to_remove.append(item)
for item in reversed(to_remove):
self.tree.delete(item)
def clear_files(self):
"""清空所有文件"""
for item in self.tree.get_children():
self.tree.delete(item)
def select_output_dir(self):
path = filedialog.askdirectory()
if path:
self.output_dir.set(path)
def redirect_output(self):
class StdoutRedirector:
def __init__(self, text_widget):
self.text_widget = text_widget
self.original_stdout = sys.stdout
def write(self, message):
self.text_widget.insert(tk.END, message)
self.text_widget.see(tk.END)
self.original_stdout.write(message)
def flush(self):
self.original_stdout.flush()
sys.stdout = StdoutRedirector(self.log_text)
def start_conversion(self, convert_all):
"""开始转换"""
input_files = []
for item in self.tree.get_children():
if convert_all or self.tree.item(item, "values")[0] == "[√]":
input_files.append(self.tree.item(item, "tags")[0])
if not input_files:
msg = "没有找到可转换的文件" if convert_all else "没有选中任何文件"
messagebox.showwarning("警告", msg)
return
os.makedirs(self.output_dir.get(), exist_ok=True)
try:
if self.mode.get() == "audio_to_ogg":
target_lufs = self.target_lufs.get() if self.enable_loudnorm.get() else None
thread = threading.Thread(target=self.convert_audio_to_ogg, args=(target_lufs, input_files))
else:
thread = threading.Thread(target=self.convert_ogg_to_audio, args=(input_files,))
thread.start()
except Exception as e:
print(f"转换初始化失败: {str(e)}")
def convert_audio_to_ogg(self, target_lufs, input_files):
"""音频转到ogg转换逻辑"""
for input_path in input_files:
try:
filename = os.path.basename(input_path)
base_name = os.path.splitext(filename)[0]
output_path = os.path.join(self.output_dir.get(), f"{base_name}.ogg")
print(f"正在转换: {filename}")
(
ffmpeg
.input(input_path)
.output(output_path, acodec='libopus', audio_bitrate='16k', ac=1, ar=16000, frame_duration=60)
.run(overwrite_output=True)
)
print(f"转换成功: {filename}\n")
except Exception as e:
print(f"转换失败: {str(e)}\n")
def convert_ogg_to_audio(self, input_files):
"""ogg转回音频转换逻辑"""
for input_path in input_files:
try:
filename = os.path.basename(input_path)
base_name = os.path.splitext(filename)[0]
output_path = os.path.join(self.output_dir.get(), f"{base_name}.ogg")
print(f"正在转换: {filename}")
(
ffmpeg
.input(input_path)
.output(output_path, acodec='libopus', audio_bitrate='16k', ac=1, ar=16000, frame_duration=60)
.run(overwrite_output=True)
)
print(f"转换成功: {filename}\n")
except Exception as e:
print(f"转换失败: {str(e)}\n")
if __name__ == "__main__":
root = tk.Tk()
app = AudioConverterApp(root)
root.mainloop()

View File

@@ -39,6 +39,10 @@ CONFIG_UART_ISR_IN_IRAM=y
# Fix ESP_SSL error
CONFIG_MBEDTLS_SSL_RENEGOTIATION=n
# ESP32 Camera
CONFIG_CAMERA_NO_AFFINITY=y
CONFIG_CAMERA_DMA_BUFFER_SIZE_MAX=8192
# LVGL 9.2.2
CONFIG_LV_OS_NONE=y
@@ -47,6 +51,9 @@ CONFIG_LV_USE_CLIB_MALLOC=y
CONFIG_LV_USE_CLIB_STRING=y
CONFIG_LV_USE_CLIB_SPRINTF=y
CONFIG_LV_USE_IMGFONT=y
CONFIG_LV_USE_ASSERT_STYLE=y
CONFIG_LV_USE_GIF=y
CONFIG_LV_USE_LODEPNG=y
# Use compressed font
CONFIG_LV_FONT_FMT_TXT_LARGE=y

View File

@@ -13,7 +13,6 @@ CONFIG_SPIRAM_MEMTEST=n
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
CONFIG_ESP32S3_DATA_CACHE_64KB=y
CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y