Compare commits

...

11 Commits

Author SHA1 Message Date
Terrence
b031a829c0 Bump to 1.8.2 2025-07-22 22:43:41 +08:00
Xiaoxia
3c11cceb43 增加自定义唤醒词启动失败的提升 (#965) 2025-07-22 18:57:25 +08:00
Y1hsiaochunnn
15f233e773 Add compatibility for Waveshare ESP32-S3-Touch-AMOLED-2.06 (#960)
* Add Waveshare ESP32-S3-Touch-AMOLED-2.06

* Update some configuration settings

* Add configuration to the configuration file

* Fix the abnormal areas
2025-07-22 18:02:31 +08:00
Forairaaaaa
721b58f8c7 Fix atoms3 backlight control (#959) 2025-07-22 17:55:33 +08:00
Xiaoxia
eb0bba2c89 Update LICENSE 2025-07-22 10:06:11 +08:00
Surfer
0d45c636a8 Surfer-C3-1.14tft 支持默认离线语音唤醒配置等 (#951)
* 添加 Surfer-C3-1.14TFT开发板支持

添加 Surfer-C3-1.14TFT开发板,基于C3的1.14寸LCD开发板,支持GPIO2  ADC 电池电量检测,应用于wifi Modem-Sleep 和低电量提醒。

* 修改surfer-c3-1.14tff电池电量适配值

修改surfer-c3-1.14tff电池电量适配值,电池容量400ma

* 修改支持默认离线语音唤醒配置等

1. 增加 config.json 文件默认支持离线语音唤醒配置和USB_SERIAL_JTAG
2. 微调电池电量值显示范围参数
2025-07-21 22:13:56 +08:00
Wiking_Xu
3e2bc9ee74 新增正点原子BOX2板子,有WIFI版本和4G版本 (#954) 2025-07-21 22:13:12 +08:00
Terrence
6a7a403117 修复C3唤醒提示音 2025-07-21 14:40:35 +08:00
Terrence
d1c047d060 Update to esp-ml307@3.1.1 2025-07-21 05:46:05 +08:00
Terrence
a35a344f42 Fix frame samples for server AEC 2025-07-20 07:53:52 +08:00
Terrence
efc6f238e7 Fix Server AEC 2025-07-20 03:57:36 +08:00
48 changed files with 2407 additions and 125 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.0")
set(PROJECT_VER "1.8.2")
# Add this line to disable the specific warning
add_compile_options(-Wno-missing-field-initializers)

View File

@@ -1,6 +1,7 @@
MIT License
Copyright (c) 2024 Xiaoxia
Copyright (c) 2025 Shenzhen Xinzhi Future Technology Co., Ltd.
Copyright (c) 2025 Project Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -5,6 +5,7 @@ set(SOURCES "audio/audio_codec.cc"
"audio/codecs/es8311_audio_codec.cc"
"audio/codecs/es8374_audio_codec.cc"
"audio/codecs/es8388_audio_codec.cc"
"audio/codecs/es8389_audio_codec.cc"
"audio/codecs/dummy_audio_codec.cc"
"audio/processors/audio_debugger.cc"
"led/single_led.cc"
@@ -105,6 +106,8 @@ elseif(CONFIG_BOARD_TYPE_ECHOEAR)
set(BOARD_TYPE "echoear")
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)
set(BOARD_TYPE "waveshare-s3-touch-amoled-2.06")
elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75)
set(BOARD_TYPE "waveshare-s3-touch-amoled-1.75")
elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_1_85C)
@@ -151,6 +154,10 @@ elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX)
set(BOARD_TYPE "atk-dnesp32s3-box")
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX0)
set(BOARD_TYPE "atk-dnesp32s3-box0")
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI)
set(BOARD_TYPE "atk-dnesp32s3-box2-wifi")
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_4G)
set(BOARD_TYPE "atk-dnesp32s3-box2-4g")
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_WIFI)
set(BOARD_TYPE "atk-dnesp32s3m-wifi")
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_4G)

View File

@@ -148,6 +148,8 @@ choice BOARD_TYPE
config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8
bool "Waveshare ESP32-S3-Touch-AMOLED-1.8"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ESP32S3_Touch_AMOLED_2_06
bool "Waveshare ESP32-S3-Touch-AMOLED-2.06"
config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_75
bool "Waveshare ESP32-S3-Touch-AMOLED-1.75"
depends on IDF_TARGET_ESP32S3
@@ -214,6 +216,12 @@ choice BOARD_TYPE
config BOARD_TYPE_ATK_DNESP32S3_BOX0
bool "正点原子DNESP32S3-BOX0"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI
bool "正点原子DNESP32S3-BOX2-WIFI"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ATK_DNESP32S3_BOX2_4G
bool "正点原子DNESP32S3-BOX2-4G"
depends on IDF_TARGET_ESP32S3
config BOARD_TYPE_ATK_DNESP32S3M_WIFI
bool "正点原子DNESP32S3M-WIFI"
depends on IDF_TARGET_ESP32S3
@@ -436,7 +444,7 @@ 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_ESP32P4_WIFI6_Touch_LCD_4B || BOARD_TYPE_ESP32P4_WIFI6_Touch_LCD_XC)
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)
help
因为性能不够,不建议和微信聊天界面风格同时开启

View File

@@ -36,7 +36,9 @@ static const char* const STATE_STRINGS[] = {
Application::Application() {
event_group_ = xEventGroupCreate();
#if CONFIG_USE_DEVICE_AEC
#if CONFIG_USE_DEVICE_AEC && CONFIG_USE_SERVER_AEC
#error "CONFIG_USE_DEVICE_AEC and CONFIG_USE_SERVER_AEC cannot be enabled at the same time"
#elif CONFIG_USE_DEVICE_AEC
aec_mode_ = kAecOnDeviceSide;
#elif CONFIG_USE_SERVER_AEC
aec_mode_ = kAecOnServerSide;
@@ -603,11 +605,12 @@ void Application::OnWakeWordDetected() {
}
// Set the chat state to wake word detected
protocol_->SendWakeWordDetected(wake_word);
SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime);
#else
SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime);
// Play the pop up sound to indicate the wake word is detected
audio_service_.PlaySound(Lang::Sounds::P3_POPUP);
#endif
SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime);
} else if (device_state_ == kDeviceStateSpeaking) {
AbortSpeaking(kAbortReasonWakeWordDetected);
} else if (device_state_ == kDeviceStateActivating) {

View File

@@ -11,8 +11,8 @@ class AudioProcessor {
public:
virtual ~AudioProcessor() = default;
virtual void Initialize(AudioCodec* codec) = 0;
virtual void Feed(const std::vector<int16_t>& data) = 0;
virtual void Initialize(AudioCodec* codec, int frame_duration_ms) = 0;
virtual void Feed(std::vector<int16_t>&& data) = 0;
virtual void Start() = 0;
virtual void Stop() = 0;
virtual bool IsRunning() = 0;

View File

@@ -43,7 +43,6 @@ void AudioService::Initialize(AudioCodec* codec) {
reference_resampler_.Configure(codec->input_sample_rate(), 16000);
}
audio_debugger_ = std::make_unique<AudioDebugger>();
#if CONFIG_USE_AUDIO_PROCESSOR
audio_processor_ = std::make_unique<AfeAudioProcessor>();
#else
@@ -118,7 +117,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", 4096, this, 3, &audio_output_task_handle_);
/* Start the opus codec task */
xTaskCreate([](void* arg) {
@@ -185,12 +184,15 @@ bool AudioService::ReadAudioData(std::vector<int16_t>& data, int sample_rate, in
/* Update the last input time */
last_input_time_ = std::chrono::steady_clock::now();
debug_statistics_.input_count++;
#if CONFIG_USE_AUDIO_DEBUGGER
// 音频调试:发送原始音频数据
if (audio_debugger_) {
audio_debugger_->Feed(data);
if (audio_debugger_ == nullptr) {
audio_debugger_ = std::make_unique<AudioDebugger>();
}
audio_debugger_->Feed(data);
#endif
return true;
}
@@ -250,7 +252,7 @@ void AudioService::AudioInputTask() {
int samples = audio_processor_->GetFeedSize();
if (samples > 0) {
if (ReadAudioData(data, 16000, samples)) {
audio_processor_->Feed(data);
audio_processor_->Feed(std::move(data));
continue;
}
}
@@ -285,6 +287,14 @@ void AudioService::AudioOutputTask() {
/* Update the last output time */
last_output_time_ = std::chrono::steady_clock::now();
debug_statistics_.playback_count++;
#if CONFIG_USE_SERVER_AEC
/* Record the timestamp for server AEC */
if (task->timestamp > 0) {
lock.lock();
timestamp_queue_.push_back(task->timestamp);
}
#endif
}
ESP_LOGW(TAG, "Audio output task stopped");
@@ -311,6 +321,7 @@ void AudioService::OpusCodecTask() {
auto task = std::make_unique<AudioTask>();
task->type = kAudioTaskTypeDecodeToPlaybackQueue;
task->timestamp = packet->timestamp;
SetDecodeSampleRate(packet->sample_rate, packet->frame_duration);
if (opus_decoder_->Decode(std::move(packet->payload), task->pcm)) {
@@ -338,25 +349,28 @@ void AudioService::OpusCodecTask() {
audio_encode_queue_.pop_front();
audio_queue_cv_.notify_all();
lock.unlock();
opus_encoder_->Encode(std::move(task->pcm), [this, &task](std::vector<uint8_t>&& opus) {
auto packet = std::make_unique<AudioStreamPacket>();
packet->payload = std::move(opus);
packet->frame_duration = OPUS_FRAME_DURATION_MS;
packet->sample_rate = 16000;
if (task->type == kAudioTaskTypeEncodeToSendQueue) {
{
std::lock_guard<std::mutex> lock(audio_queue_mutex_);
audio_send_queue_.push_back(std::move(packet));
}
if (callbacks_.on_send_queue_available) {
callbacks_.on_send_queue_available();
}
} else if (task->type == kAudioTaskTypeEncodeToTestingQueue) {
auto packet = std::make_unique<AudioStreamPacket>();
packet->frame_duration = OPUS_FRAME_DURATION_MS;
packet->sample_rate = 16000;
packet->timestamp = task->timestamp;
if (!opus_encoder_->Encode(std::move(task->pcm), packet->payload)) {
ESP_LOGE(TAG, "Failed to encode audio");
continue;
}
if (task->type == kAudioTaskTypeEncodeToSendQueue) {
{
std::lock_guard<std::mutex> lock(audio_queue_mutex_);
audio_testing_queue_.push_back(std::move(packet));
audio_send_queue_.push_back(std::move(packet));
}
});
if (callbacks_.on_send_queue_available) {
callbacks_.on_send_queue_available();
}
} else if (task->type == kAudioTaskTypeEncodeToTestingQueue) {
std::lock_guard<std::mutex> lock(audio_queue_mutex_);
audio_testing_queue_.push_back(std::move(packet));
}
debug_statistics_.encode_count++;
lock.lock();
}
@@ -387,6 +401,17 @@ void AudioService::PushTaskToEncodeQueue(AudioTaskType type, std::vector<int16_t
/* Push the task to the encode queue */
std::unique_lock<std::mutex> lock(audio_queue_mutex_);
/* If the task is to send queue, we need to set the timestamp */
if (type == kAudioTaskTypeEncodeToSendQueue && !timestamp_queue_.empty()) {
if (timestamp_queue_.size() <= MAX_TIMESTAMPS_IN_QUEUE) {
task->timestamp = timestamp_queue_.front();
} else {
ESP_LOGW(TAG, "Timestamp queue (%u) is full, dropping timestamp", timestamp_queue_.size());
}
timestamp_queue_.pop_front();
}
audio_queue_cv_.wait(lock, [this]() { return audio_encode_queue_.size() < MAX_ENCODE_TASKS_IN_QUEUE; });
audio_encode_queue_.push_back(std::move(task));
audio_queue_cv_.notify_all();
@@ -443,7 +468,10 @@ void AudioService::EnableWakeWordDetection(bool enable) {
ESP_LOGD(TAG, "%s wake word detection", enable ? "Enabling" : "Disabling");
if (enable) {
if (!wake_word_initialized_) {
wake_word_->Initialize(codec_);
if (!wake_word_->Initialize(codec_)) {
ESP_LOGE(TAG, "Failed to initialize wake word");
return;
}
wake_word_initialized_ = true;
}
wake_word_->Start();
@@ -458,7 +486,7 @@ void AudioService::EnableVoiceProcessing(bool enable) {
ESP_LOGD(TAG, "%s voice processing", enable ? "Enabling" : "Disabling");
if (enable) {
if (!audio_processor_initialized_) {
audio_processor_->Initialize(codec_);
audio_processor_->Initialize(codec_, OPUS_FRAME_DURATION_MS);
audio_processor_initialized_ = true;
}
@@ -522,6 +550,7 @@ bool AudioService::IsIdle() {
void AudioService::ResetDecoder() {
std::lock_guard<std::mutex> lock(audio_queue_mutex_);
opus_decoder_->ResetState();
timestamp_queue_.clear();
audio_decode_queue_.clear();
audio_playback_queue_.clear();
audio_testing_queue_.clear();

View File

@@ -40,6 +40,7 @@
#define MAX_DECODE_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS)
#define MAX_SEND_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS)
#define AUDIO_TESTING_MAX_DURATION_MS 10000
#define MAX_TIMESTAMPS_IN_QUEUE 3
#define AUDIO_POWER_TIMEOUT_MS 15000
#define AUDIO_POWER_CHECK_INTERVAL_MS 1000
@@ -67,6 +68,7 @@ enum AudioTaskType {
struct AudioTask {
AudioTaskType type;
std::vector<int16_t> pcm;
uint32_t timestamp;
};
struct DebugStatistics {

View File

@@ -0,0 +1,201 @@
#include "es8389_audio_codec.h"
#include <esp_log.h>
static const char TAG[] = "Es8389AudioCodec";
Es8389AudioCodec::Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
gpio_num_t pa_pin, uint8_t es8389_addr, bool use_mclk) {
duplex_ = true; // 是否双工
input_reference_ = false; // 是否使用参考输入,实现回声消除
input_channels_ = 1; // 输入通道数
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
pa_pin_ = pa_pin;
CreateDuplexChannels(mclk, bclk, ws, dout, din);
// Do initialize of related interface: data_if, ctrl_if and gpio_if
audio_codec_i2s_cfg_t i2s_cfg = {
.port = I2S_NUM_0,
.rx_handle = rx_handle_,
.tx_handle = tx_handle_,
};
data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
assert(data_if_ != NULL);
// Output
audio_codec_i2c_cfg_t i2c_cfg = {
.port = i2c_port,
.addr = es8389_addr,
.bus_handle = i2c_master_handle,
};
ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
assert(ctrl_if_ != NULL);
gpio_if_ = audio_codec_new_gpio();
assert(gpio_if_ != NULL);
es8389_codec_cfg_t es8389_cfg = {};
es8389_cfg.ctrl_if = ctrl_if_;
es8389_cfg.gpio_if = gpio_if_;
es8389_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
es8389_cfg.pa_pin = pa_pin;
es8389_cfg.use_mclk = use_mclk;
es8389_cfg.hw_gain.pa_voltage = 5.0;
es8389_cfg.hw_gain.codec_dac_voltage = 3.3;
codec_if_ = es8389_codec_new(&es8389_cfg);
assert(codec_if_ != NULL);
esp_codec_dev_cfg_t outdev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_OUT,
.codec_if = codec_if_,
.data_if = data_if_,
};
output_dev_ = esp_codec_dev_new(&outdev_cfg);
assert(output_dev_ != NULL);
esp_codec_dev_cfg_t indev_cfg = {
.dev_type = ESP_CODEC_DEV_TYPE_IN,
.codec_if = codec_if_,
.data_if = data_if_,
};
input_dev_ = esp_codec_dev_new(&indev_cfg);
assert(input_dev_ != NULL);
esp_codec_set_disable_when_closed(output_dev_, false);
esp_codec_set_disable_when_closed(input_dev_, false);
ESP_LOGI(TAG, "Es8389AudioCodec initialized");
}
Es8389AudioCodec::~Es8389AudioCodec() {
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
esp_codec_dev_delete(output_dev_);
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
esp_codec_dev_delete(input_dev_);
audio_codec_delete_codec_if(codec_if_);
audio_codec_delete_ctrl_if(ctrl_if_);
audio_codec_delete_gpio_if(gpio_if_);
audio_codec_delete_data_if(data_if_);
}
void Es8389AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
assert(input_sample_rate_ == output_sample_rate_);
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = 6,
.dma_frame_num = 240,
.auto_clear_after_cb = true,
.auto_clear_before_cb = false,
.intr_priority = 0,
};
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
i2s_std_config_t std_cfg = {
.clk_cfg = {
.sample_rate_hz = (uint32_t)output_sample_rate_,
.clk_src = I2S_CLK_SRC_DEFAULT,
.mclk_multiple = I2S_MCLK_MULTIPLE_256,
#ifdef I2S_HW_VERSION_2
.ext_clk_freq_hz = 0,
#endif
},
.slot_cfg = {
.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
.slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
.slot_mode = I2S_SLOT_MODE_STEREO,
.slot_mask = I2S_STD_SLOT_BOTH,
.ws_width = I2S_DATA_BIT_WIDTH_16BIT,
.ws_pol = false,
.bit_shift = true,
.left_align = true,
.big_endian = false,
.bit_order_lsb = false
},
.gpio_cfg = {
.mclk = mclk,
.bclk = bclk,
.ws = ws,
.dout = dout,
.din = din,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false
}
}
};
ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
ESP_LOGI(TAG, "Duplex channels created");
}
void Es8389AudioCodec::SetOutputVolume(int volume) {
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
AudioCodec::SetOutputVolume(volume);
}
void Es8389AudioCodec::EnableInput(bool enable) {
if (enable == input_enabled_) {
return;
}
if (enable) {
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)input_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 40.0));
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
}
AudioCodec::EnableInput(enable);
}
void Es8389AudioCodec::EnableOutput(bool enable) {
if (enable == output_enabled_) {
return;
}
if (enable) {
// Play 16bit 1 channel
esp_codec_dev_sample_info_t fs = {
.bits_per_sample = 16,
.channel = 1,
.channel_mask = 0,
.sample_rate = (uint32_t)output_sample_rate_,
.mclk_multiple = 0,
};
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
if (pa_pin_ != GPIO_NUM_NC) {
gpio_set_level(pa_pin_, 1);
}
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
if (pa_pin_ != GPIO_NUM_NC) {
gpio_set_level(pa_pin_, 0);
}
}
AudioCodec::EnableOutput(enable);
}
int Es8389AudioCodec::Read(int16_t* dest, int samples) {
if (input_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
}
return samples;
}
int Es8389AudioCodec::Write(const int16_t* data, int samples) {
if (output_enabled_) {
ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
}
return samples;
}

View File

@@ -0,0 +1,38 @@
#ifndef _ES8389_AUDIO_CODEC_H
#define _ES8389_AUDIO_CODEC_H
#include "audio_codec.h"
#include <driver/i2c.h>
#include <driver/gpio.h>
#include <esp_codec_dev.h>
#include <esp_codec_dev_defaults.h>
class Es8389AudioCodec : public AudioCodec {
private:
const audio_codec_data_if_t* data_if_ = nullptr;
const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
const audio_codec_if_t* codec_if_ = nullptr;
const audio_codec_gpio_if_t* gpio_if_ = nullptr;
esp_codec_dev_handle_t output_dev_ = nullptr;
esp_codec_dev_handle_t input_dev_ = nullptr;
gpio_num_t pa_pin_ = GPIO_NUM_NC;
void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
virtual int Read(int16_t* dest, int samples) override;
virtual int Write(const int16_t* data, int samples) override;
public:
Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
gpio_num_t pa_pin, uint8_t es8389_addr, bool use_mclk = true);
virtual ~Es8389AudioCodec();
virtual void SetOutputVolume(int volume) override;
virtual void EnableInput(bool enable) override;
virtual void EnableOutput(bool enable) override;
};
#endif // _ES8389_AUDIO_CODEC_H

View File

@@ -10,8 +10,13 @@ AfeAudioProcessor::AfeAudioProcessor()
event_group_ = xEventGroupCreate();
}
void AfeAudioProcessor::Initialize(AudioCodec* codec) {
void AfeAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms) {
codec_ = codec;
frame_samples_ = frame_duration_ms * 16000 / 1000;
// Pre-allocate output buffer capacity
output_buffer_.reserve(frame_samples_);
int ref_num = codec_->input_reference() ? 1 : 0;
std::string input_format;
@@ -79,7 +84,7 @@ size_t AfeAudioProcessor::GetFeedSize() {
return afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
}
void AfeAudioProcessor::Feed(const std::vector<int16_t>& data) {
void AfeAudioProcessor::Feed(std::vector<int16_t>&& data) {
if (afe_data_ == nullptr) {
return;
}
@@ -141,7 +146,24 @@ void AfeAudioProcessor::AudioProcessorTask() {
}
if (output_callback_) {
output_callback_(std::vector<int16_t>(res->data, res->data + res->data_size / sizeof(int16_t)));
size_t samples = res->data_size / sizeof(int16_t);
// Add data to buffer
output_buffer_.insert(output_buffer_.end(), res->data, res->data + samples);
// Output complete frames when buffer has enough data
while (output_buffer_.size() >= frame_samples_) {
if (output_buffer_.size() == frame_samples_) {
// If buffer size equals frame size, move the entire buffer
output_callback_(std::move(output_buffer_));
output_buffer_.clear();
output_buffer_.reserve(frame_samples_);
} else {
// If buffer size exceeds frame size, copy one frame and remove it
output_callback_(std::vector<int16_t>(output_buffer_.begin(), output_buffer_.begin() + frame_samples_));
output_buffer_.erase(output_buffer_.begin(), output_buffer_.begin() + frame_samples_);
}
}
}
}
}

View File

@@ -18,8 +18,8 @@ public:
AfeAudioProcessor();
~AfeAudioProcessor();
void Initialize(AudioCodec* codec) override;
void Feed(const std::vector<int16_t>& data) override;
void Initialize(AudioCodec* codec, int frame_duration_ms) override;
void Feed(std::vector<int16_t>&& data) override;
void Start() override;
void Stop() override;
bool IsRunning() override;
@@ -35,7 +35,9 @@ private:
std::function<void(std::vector<int16_t>&& data)> output_callback_;
std::function<void(bool speaking)> vad_state_change_callback_;
AudioCodec* codec_ = nullptr;
int frame_samples_ = 0;
bool is_speaking_ = false;
std::vector<int16_t> output_buffer_;
void AudioProcessorTask();
};

View File

@@ -3,16 +3,31 @@
#define TAG "NoAudioProcessor"
void NoAudioProcessor::Initialize(AudioCodec* codec) {
void NoAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms) {
codec_ = codec;
frame_samples_ = frame_duration_ms * 16000 / 1000;
}
void NoAudioProcessor::Feed(const std::vector<int16_t>& data) {
void NoAudioProcessor::Feed(std::vector<int16_t>&& data) {
if (!is_running_ || !output_callback_) {
return;
}
// 直接将输入数据传递给输出回调
output_callback_(std::vector<int16_t>(data));
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);
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
mono_data[i] = data[j];
}
output_callback_(std::move(mono_data));
} else {
output_callback_(std::move(data));
}
}
void NoAudioProcessor::Start() {
@@ -39,8 +54,7 @@ size_t NoAudioProcessor::GetFeedSize() {
if (!codec_) {
return 0;
}
// 返回一个固定的帧大小,比如 30ms 的数据
return 30 * codec_->input_sample_rate() / 1000;
return frame_samples_;
}
void NoAudioProcessor::EnableDeviceAec(bool enable) {

View File

@@ -12,8 +12,8 @@ public:
NoAudioProcessor() = default;
~NoAudioProcessor() = default;
void Initialize(AudioCodec* codec) override;
void Feed(const std::vector<int16_t>& data) override;
void Initialize(AudioCodec* codec, int frame_duration_ms) override;
void Feed(std::vector<int16_t>&& data) override;
void Start() override;
void Stop() override;
bool IsRunning() override;
@@ -24,6 +24,7 @@ public:
private:
AudioCodec* codec_ = nullptr;
int frame_samples_ = 0;
std::function<void(std::vector<int16_t>&& data)> output_callback_;
std::function<void(bool speaking)> vad_state_change_callback_;
bool is_running_ = false;

View File

@@ -11,7 +11,7 @@ class WakeWord {
public:
virtual ~WakeWord() = default;
virtual void Initialize(AudioCodec* codec) = 0;
virtual bool Initialize(AudioCodec* codec) = 0;
virtual void Feed(const std::vector<int16_t>& data) = 0;
virtual void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) = 0;
virtual void Start() = 0;

View File

@@ -30,14 +30,14 @@ AfeWakeWord::~AfeWakeWord() {
vEventGroupDelete(event_group_);
}
void AfeWakeWord::Initialize(AudioCodec* codec) {
bool AfeWakeWord::Initialize(AudioCodec* codec) {
codec_ = codec;
int ref_num = codec_->input_reference() ? 1 : 0;
srmodel_list_t *models = esp_srmodel_init("model");
if (models == nullptr || models->num == -1) {
ESP_LOGE(TAG, "Failed to initialize wakenet model");
return;
return false;
}
for (int i = 0; i < models->num; i++) {
ESP_LOGI(TAG, "Model %d: %s", i, models->model_name[i]);
@@ -75,6 +75,8 @@ void AfeWakeWord::Initialize(AudioCodec* codec) {
this_->AudioDetectionTask();
vTaskDelete(NULL);
}, "audio_detection", 4096, this, 3, nullptr);
return true;
}
void AfeWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {

View File

@@ -23,7 +23,7 @@ public:
AfeWakeWord();
~AfeWakeWord();
void Initialize(AudioCodec* codec);
bool Initialize(AudioCodec* codec);
void Feed(const std::vector<int16_t>& data);
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
void Start();

View File

@@ -31,6 +31,12 @@ CustomWakeWord::~CustomWakeWord() {
afe_iface_->destroy(afe_data_);
}
// 清理 multinet 资源
if (multinet_model_data_ != nullptr && multinet_ != nullptr) {
multinet_->destroy(multinet_model_data_);
multinet_model_data_ = nullptr;
}
if (wake_word_encode_task_stack_ != nullptr) {
heap_caps_free(wake_word_encode_task_stack_);
}
@@ -38,15 +44,37 @@ CustomWakeWord::~CustomWakeWord() {
vEventGroupDelete(event_group_);
}
void CustomWakeWord::Initialize(AudioCodec* codec) {
bool CustomWakeWord::Initialize(AudioCodec* codec) {
codec_ = codec;
int ref_num = codec_->input_reference() ? 1 : 0;
models = esp_srmodel_init("model");
if (models == nullptr || models->num == -1) {
ESP_LOGE(TAG, "Failed to initialize wakenet model");
return;
return false;
}
// 初始化 multinet (命令词识别)
mn_name_ = esp_srmodel_filter(models, ESP_MN_PREFIX, ESP_MN_CHINESE);
if (mn_name_ == nullptr) {
ESP_LOGE(TAG, "Failed to initialize multinet, mn_name is nullptr");
ESP_LOGI(TAG, "Please refer to https://pcn7cs20v8cr.feishu.cn/wiki/CpQjwQsCJiQSWSkYEvrcxcbVnwh to add custom wake word");
return false;
}
ESP_LOGI(TAG, "multinet:%s", mn_name_);
multinet_ = esp_mn_handle_from_name(mn_name_);
multinet_model_data_ = multinet_->create(mn_name_, 2000); // 2秒超时
multinet_->set_det_threshold(multinet_model_data_, 0.5);
esp_mn_commands_clear();
esp_mn_commands_add(1, CONFIG_CUSTOM_WAKE_WORD); // 添加自定义唤醒词作为命令词
esp_mn_commands_update();
// 打印所有的命令词
multinet_->print_active_speech_commands(multinet_model_data_);
ESP_LOGI(TAG, "Custom wake word: %s", CONFIG_CUSTOM_WAKE_WORD);
// 初始化 afe
int ref_num = codec_->input_reference() ? 1 : 0;
std::string input_format;
for (int i = 0; i < codec_->input_channels() - ref_num; i++) {
input_format.push_back('M');
@@ -70,6 +98,8 @@ void CustomWakeWord::Initialize(AudioCodec* codec) {
this_->AudioDetectionTask();
vTaskDelete(NULL);
}, "audio_detection", 16384, this, 3, nullptr);
return true;
}
void CustomWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
@@ -105,23 +135,16 @@ void CustomWakeWord::AudioDetectionTask() {
auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
// 初始化 multinet (命令词识别)
char *mn_name = esp_srmodel_filter(models, ESP_MN_PREFIX, ESP_MN_CHINESE);
ESP_LOGI(TAG, "multinet:%s", mn_name);
esp_mn_iface_t *multinet = esp_mn_handle_from_name(mn_name);
model_iface_data_t *model_data = multinet->create(mn_name, 2000); // 2秒超时
multinet->set_det_threshold(model_data, 0.5);
esp_mn_commands_clear();
esp_mn_commands_add(1, CONFIG_CUSTOM_WAKE_WORD); // 添加自定义唤醒词作为命令词
esp_mn_commands_update();
int mu_chunksize = multinet->get_samp_chunksize(model_data);
// 检查 multinet 是否已正确初始化
if (multinet_ == nullptr || multinet_model_data_ == nullptr) {
ESP_LOGE(TAG, "Multinet not initialized properly");
return;
}
int mu_chunksize = multinet_->get_samp_chunksize(multinet_model_data_);
assert(mu_chunksize == feed_size);
// 打印所有的命令词
multinet->print_active_speech_commands(model_data);
ESP_LOGI(TAG, "Audio detection task started, feed size: %d fetch size: %d", feed_size, fetch_size);
ESP_LOGI(TAG, "Custom wake word: %s", CONFIG_CUSTOM_WAKE_WORD);
// 禁用wakenet直接使用multinet检测自定义唤醒词
afe_iface_->disable_wakenet(afe_data_);
@@ -139,14 +162,14 @@ void CustomWakeWord::AudioDetectionTask() {
StoreWakeWordData(res->data, res->data_size / sizeof(int16_t));
// 直接使用multinet检测自定义唤醒词
esp_mn_state_t mn_state = multinet->detect(model_data, res->data);
esp_mn_state_t mn_state = multinet_->detect(multinet_model_data_, res->data);
if (mn_state == ESP_MN_STATE_DETECTING) {
// 仍在检测中,继续
continue;
} else if (mn_state == ESP_MN_STATE_DETECTED) {
// 检测到自定义唤醒词
esp_mn_results_t *mn_result = multinet->get_results(model_data);
esp_mn_results_t *mn_result = multinet_->get_results(multinet_model_data_);
ESP_LOGI(TAG, "Custom wake word detected: command_id=%d, string=%s, prob=%f",
mn_result->command_id[0], mn_result->string, mn_result->prob[0]);
@@ -163,23 +186,17 @@ void CustomWakeWord::AudioDetectionTask() {
}
// 清理multinet状态准备下次检测
multinet->clean(model_data);
multinet_->clean(multinet_model_data_);
ESP_LOGI(TAG, "Ready for next detection");
}
} else if (mn_state == ESP_MN_STATE_TIMEOUT) {
// 超时,清理状态继续检测
ESP_LOGD(TAG, "Command word detection timeout, cleaning state");
multinet->clean(model_data);
multinet_->clean(multinet_model_data_);
continue;
}
}
// 清理资源
if (model_data) {
multinet->destroy(model_data);
model_data = NULL;
}
ESP_LOGI(TAG, "Audio detection task ended");
}

View File

@@ -28,7 +28,7 @@ public:
CustomWakeWord();
~CustomWakeWord();
void Initialize(AudioCodec* codec);
bool Initialize(AudioCodec* codec);
void Feed(const std::vector<int16_t>& data);
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
void Start();
@@ -42,6 +42,11 @@ private:
esp_afe_sr_iface_t* afe_iface_ = nullptr;
esp_afe_sr_data_t* afe_data_ = nullptr;
srmodel_list_t *models = nullptr;
// multinet 相关成员变量
esp_mn_iface_t* multinet_ = nullptr;
model_iface_data_t* multinet_model_data_ = nullptr;
char* mn_name_ = nullptr;
char* wakenet_model_ = NULL;
std::vector<std::string> wake_words_;

View File

@@ -23,19 +23,19 @@ EspWakeWord::~EspWakeWord() {
vEventGroupDelete(event_group_);
}
void EspWakeWord::Initialize(AudioCodec* codec) {
bool EspWakeWord::Initialize(AudioCodec* codec) {
codec_ = codec;
wakenet_model_ = esp_srmodel_init("model");
if (wakenet_model_ == nullptr || wakenet_model_->num == -1) {
ESP_LOGE(TAG, "Failed to initialize wakenet model");
return;
return false;
}
if(wakenet_model_->num > 1) {
ESP_LOGW(TAG, "More than one model found, using the first one");
} else if (wakenet_model_->num == 0) {
ESP_LOGE(TAG, "No model found");
return;
return false;
}
char *model_name = wakenet_model_->model_name[0];
wakenet_iface_ = (esp_wn_iface_t*)esp_wn_handle_from_name(model_name);
@@ -44,6 +44,8 @@ void EspWakeWord::Initialize(AudioCodec* codec) {
int frequency = wakenet_iface_->get_samp_rate(wakenet_data_);
int audio_chunksize = wakenet_iface_->get_samp_chunksize(wakenet_data_);
ESP_LOGI(TAG, "Wake word(%s),freq: %d, chunksize: %d", model_name, frequency, audio_chunksize);
return true;
}
void EspWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {

View File

@@ -24,7 +24,7 @@ public:
EspWakeWord();
~EspWakeWord();
void Initialize(AudioCodec* codec);
bool Initialize(AudioCodec* codec);
void Feed(const std::vector<int16_t>& data);
void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
void Start();

View File

@@ -0,0 +1,487 @@
#include "dual_network_board.h"
#include "codecs/es8389_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "power_save_timer.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include "power_manager.h"
#include "i2c_device.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#include "esp_io_expander_tca95xx_16bit.h"
#define TAG "atk_dnesp32s3_box2_4g"
LV_FONT_DECLARE(font_puhui_20_4);
LV_FONT_DECLARE(font_awesome_20_4);
class atk_dnesp32s3_box2_4g : public DualNetworkBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
LcdDisplay* display_;
esp_io_expander_handle_t io_exp_handle;
button_handle_t btns;
button_driver_t* btn_driver_ = nullptr;
static atk_dnesp32s3_box2_4g* instance_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
PowerSupply power_status_;
esp_timer_handle_t wake_timer_handle_;
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
int ticks_ = 0;
const int kChgCtrlInterval = 5;
void InitializeBoardPowerManager() {
instance_ = this;
if (IoExpanderGetLevel(XIO_CHRG) == 0) {
power_status_ = kDeviceTypecSupply;
} else {
power_status_ = kDeviceBatterySupply;
}
esp_timer_create_args_t wake_display_timer_args = {
.callback = [](void *arg) {
atk_dnesp32s3_box2_4g* self = static_cast<atk_dnesp32s3_box2_4g*>(arg);
self->ticks_ ++;
if (self->ticks_ % self->kChgCtrlInterval == 0) {
if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
self->power_status_ = kDeviceTypecSupply;
} else {
self->power_status_ = kDeviceBatterySupply;
}
/* 低于某个电量,会自动关机 */
if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "wake_update_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
}
void InitializePowerManager() {
power_manager_ = new PowerManager(io_exp_handle);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
if (power_status_ == kDeviceBatterySupply) {
GetBacklight()->SetBrightness(0);
esp_timer_stop(power_manager_->timer_handle_);
esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
}
});
power_save_timer_->SetEnabled(true);
}
void audio_volume_change(bool direction) {
auto codec = GetAudioCodec();
auto volume = codec->output_volume();
if (direction) {
volume += 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
} else {
volume -= 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
}
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
}
void audio_volume_minimum(){
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
}
void audio_volume_maxmum(){
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
}
esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
}
uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
uint32_t pin_val = 0;
esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
pin_mask &= DRV_IO_EXP_INPUT_MASK;
return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
}
void InitializeIoExpander() {
esp_err_t ret = ESP_OK;
esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
assert(ret == ESP_OK);
}
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
}
void InitializeButtons() {
instance_ = this;
button_config_t l_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t m_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t r_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_driver_t* xio_l_btn_driver_ = nullptr;
button_driver_t* xio_m_btn_driver_ = nullptr;
button_handle_t l_btn_handle = NULL;
button_handle_t m_btn_handle = NULL;
button_handle_t r_btn_handle = NULL;
xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_l_btn_driver_->enable_power_save = false;
xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return !instance_->IoExpanderGetLevel(XIO_KEY_L);
};
ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_m_btn_driver_->enable_power_save = false;
xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return instance_->IoExpanderGetLevel(XIO_KEY_M);
};
ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
button_gpio_config_t r_cfg = {
.gpio_num = R_BUTTON_GPIO,
.active_level = BUTTON_INACTIVE,
.enable_power_save = false,
.disable_pull = false
};
ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(false);
}, this);
iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_minimum();
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
if (self->GetNetworkType() == NetworkType::WIFI) {
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
}
else {
app.ToggleChatState();
}
} else {
app.ToggleChatState();
}
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_DOUBLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
self->SwitchNetworkType();
}
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
auto& app = Application::GetInstance();
if (self->GetNetworkType() == NetworkType::WIFI) {
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
auto& wifi_board = static_cast<WifiBoard&>(self->GetCurrentBoard());
wifi_board.ResetWifiConfiguration();
}
}
if (self->power_status_ == kDeviceBatterySupply) {
auto backlight = Board::GetInstance().GetBacklight();
backlight->SetBrightness(0);
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(true);
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_maxmum();
}, this);
}
void InitializeSt7789Display() {
ESP_LOGI(TAG, "Install panel IO");
/*RD PIN */
gpio_config_t gpio_init_struct;
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
gpio_set_level(LCD_PIN_RD, 1);
/* BL PIN */
gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = LCD_PIN_DC,
.wr_gpio_num = LCD_PIN_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
LCD_PIN_D0,
LCD_PIN_D1,
LCD_PIN_D2,
LCD_PIN_D3,
LCD_PIN_D4,
LCD_PIN_D5,
LCD_PIN_D6,
LCD_PIN_D7,
},
.bus_width = 8,
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
.psram_trans_align = 64,
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = LCD_PIN_CS,
.pclk_hz = (20 * 1000 * 1000),
.trans_queue_depth = 7,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.dc_levels = {
.dc_idle_level = 1,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.cs_active_high = 0,
.pclk_active_neg = 0,
.pclk_idle_low = 0,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_PIN_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_set_gap(panel, 0, 0);
esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
});
}
public:
atk_dnesp32s3_box2_4g() :
DualNetworkBoard(Module_4G_TX_PIN, Module_4G_RX_PIN) {
InitializeI2c();
InitializeIoExpander();
InitializePowerSaveTimer();
InitializePowerManager();
InitializeSt7789Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
InitializeBoardPowerManager();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8389AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_0,
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN,
GPIO_NUM_NC,
AUDIO_CODEC_ES8389_ADDR
);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
static bool last_discharging = false;
charging = power_manager_->IsCharging();
discharging = power_manager_->IsDischarging();
if (discharging != last_discharging) {
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = power_manager_->GetBatteryLevel();
return true;
}
virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled) {
power_save_timer_->WakeUp();
}
DualNetworkBoard::SetPowerSaveMode(enabled);
}
};
DECLARE_BOARD(atk_dnesp32s3_box2_4g);
// 定义静态成员变量
atk_dnesp32s3_box2_4g* atk_dnesp32s3_box2_4g::instance_ = nullptr;

View File

@@ -0,0 +1,80 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
enum PowerSupply {
kDeviceTypecSupply,
kDeviceBatterySupply,
};
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
#define SPISD_PIN_MOSI GPIO_NUM_16
#define SPISD_PIN_MISO GPIO_NUM_18
#define SPISD_PIN_CLK GPIO_NUM_17
#define SPISD_PIN_TS GPIO_NUM_15
#define R_BUTTON_GPIO GPIO_NUM_0
#define XL9555_INT_GPIO GPIO_NUM_2
#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
#define DRV_IO_EXP_INPUT_MASK 0xC0E7
#define LCD_PIN_CS GPIO_NUM_14
#define LCD_PIN_DC GPIO_NUM_12
#define LCD_PIN_RD GPIO_NUM_10
#define LCD_PIN_WR GPIO_NUM_11
#define LCD_PIN_RST GPIO_NUM_NC
#define LCD_PIN_D0 GPIO_NUM_13
#define LCD_PIN_D1 GPIO_NUM_9
#define LCD_PIN_D2 GPIO_NUM_8
#define LCD_PIN_D3 GPIO_NUM_7
#define LCD_PIN_D4 GPIO_NUM_6
#define LCD_PIN_D5 GPIO_NUM_5
#define LCD_PIN_D6 GPIO_NUM_4
#define LCD_PIN_D7 GPIO_NUM_3
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define Module_4G_RX_PIN GPIO_NUM_44
#define Module_4G_TX_PIN GPIO_NUM_43
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,9 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3-box2-4g",
"sdkconfig_append": []
}
]
}

View File

@@ -0,0 +1,195 @@
#pragma once
#include <vector>
#include <functional>
#include "esp_io_expander_tca95xx_16bit.h"
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
esp_io_expander_handle_t xl9555_;
uint32_t pin_val = 0;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> adc_values_;
uint32_t battery_level_ = 0;
bool is_charging_ = false;
bool is_low_battery_ = false;
int ticks_ = 0;
const int kBatteryAdcInterval = 60;
const int kBatteryAdcDataCount = 3;
const int kLowBatteryLevel = 20;
adc_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
if (new_charging_status != is_charging_) {
is_charging_ = new_charging_status;
if (on_charging_status_changed_) {
on_charging_status_changed_(is_charging_);
}
ReadBatteryAdcData();
return;
}
// 如果电池电量数据不足,则读取电池电量数据
if (adc_values_.size() < kBatteryAdcDataCount) {
ReadBatteryAdcData();
return;
}
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
ticks_++;
if (ticks_ % kBatteryAdcInterval == 0) {
ReadBatteryAdcData();
}
}
void ReadBatteryAdcData() {
int adc_value;
uint32_t temp_val = 0;
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(500));
for(int t = 0; t < 10; t ++) {
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
temp_val += adc_value;
}
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
adc_value = temp_val / 10;
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{2696, 0}, /* 3.48V -屏幕闪屏 */
{2724, 20}, /* 3.53V */
{2861, 40}, /* 3.7V */
{3038, 60}, /* 3.90V */
{3150, 80}, /* 4.02V */
{3280, 100} /* 4.14V */
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
low_voltage_ = adc_value;
// ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
esp_timer_handle_t timer_handle_;
uint16_t low_voltage_ = 2630;
PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(arg);
self->CheckBatteryStatus();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "battery_check_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
// 初始化 ADC
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_handle_);
}
}
bool IsCharging() {
// 如果电量已经满了,则不再显示充电中
if (battery_level_ == 100) {
return false;
}
return is_charging_;
}
bool IsDischarging() {
// 没有区分充电和放电,所以直接返回相反状态
return !is_charging_;
}
uint8_t GetBatteryLevel() {
return battery_level_;
}
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};

View File

@@ -0,0 +1,466 @@
#include "wifi_board.h"
#include "codecs/es8389_audio_codec.h"
#include "display/lcd_display.h"
#include "system_reset.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "power_save_timer.h"
#include "led/single_led.h"
#include "assets/lang_config.h"
#include "power_manager.h"
#include "i2c_device.h"
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <wifi_station.h>
#include <driver/rtc_io.h>
#include <esp_sleep.h>
#include "esp_io_expander_tca95xx_16bit.h"
#define TAG "atk_dnesp32s3_box2_wifi"
LV_FONT_DECLARE(font_puhui_20_4);
LV_FONT_DECLARE(font_awesome_20_4);
class atk_dnesp32s3_box2_wifi : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
LcdDisplay* display_;
esp_io_expander_handle_t io_exp_handle;
button_handle_t btns;
button_driver_t* btn_driver_ = nullptr;
static atk_dnesp32s3_box2_wifi* instance_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
PowerSupply power_status_;
esp_timer_handle_t wake_timer_handle_;
esp_lcd_panel_io_handle_t panel_io = nullptr;
esp_lcd_panel_handle_t panel = nullptr;
int ticks_ = 0;
const int kChgCtrlInterval = 5;
void InitializeBoardPowerManager() {
instance_ = this;
if (IoExpanderGetLevel(XIO_CHRG) == 0) {
power_status_ = kDeviceTypecSupply;
} else {
power_status_ = kDeviceBatterySupply;
}
esp_timer_create_args_t wake_display_timer_args = {
.callback = [](void *arg) {
atk_dnesp32s3_box2_wifi* self = static_cast<atk_dnesp32s3_box2_wifi*>(arg);
self->ticks_ ++;
if (self->ticks_ % self->kChgCtrlInterval == 0) {
if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
self->power_status_ = kDeviceTypecSupply;
} else {
self->power_status_ = kDeviceBatterySupply;
}
/* 低于某个电量,会自动关机 */
if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "wake_update_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
}
void InitializePowerManager() {
power_manager_ = new PowerManager(io_exp_handle);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("sleepy");
GetBacklight()->SetBrightness(1);
});
power_save_timer_->OnExitSleepMode([this]() {
display_->SetChatMessage("system", "");
display_->SetEmotion("neutral");
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
if (power_status_ == kDeviceBatterySupply) {
GetBacklight()->SetBrightness(0);
esp_timer_stop(power_manager_->timer_handle_);
esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
}
});
power_save_timer_->SetEnabled(true);
}
void audio_volume_change(bool direction) {
auto codec = GetAudioCodec();
auto volume = codec->output_volume();
if (direction) {
volume += 10;
if (volume > 100) {
volume = 100;
}
codec->SetOutputVolume(volume);
} else {
volume -= 10;
if (volume < 0) {
volume = 0;
}
codec->SetOutputVolume(volume);
}
GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
}
void audio_volume_minimum(){
GetAudioCodec()->SetOutputVolume(0);
GetDisplay()->ShowNotification(Lang::Strings::MUTED);
}
void audio_volume_maxmum(){
GetAudioCodec()->SetOutputVolume(100);
GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
}
esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
}
uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
uint32_t pin_val = 0;
esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
pin_mask &= DRV_IO_EXP_INPUT_MASK;
return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
}
void InitializeIoExpander() {
esp_err_t ret = ESP_OK;
esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
assert(ret == ESP_OK);
}
// Initialize I2C peripheral
void InitializeI2c() {
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = (i2c_port_t)I2C_NUM_0,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags = {
.enable_internal_pullup = 1,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
}
void InitializeButtons() {
instance_ = this;
button_config_t l_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t m_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_config_t r_btn_cfg = {
.long_press_time = 800,
.short_press_time = 500
};
button_driver_t* xio_l_btn_driver_ = nullptr;
button_driver_t* xio_m_btn_driver_ = nullptr;
button_handle_t l_btn_handle = NULL;
button_handle_t m_btn_handle = NULL;
button_handle_t r_btn_handle = NULL;
xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_l_btn_driver_->enable_power_save = false;
xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return !instance_->IoExpanderGetLevel(XIO_KEY_L);
};
ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
xio_m_btn_driver_->enable_power_save = false;
xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
return instance_->IoExpanderGetLevel(XIO_KEY_M);
};
ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
button_gpio_config_t r_cfg = {
.gpio_num = R_BUTTON_GPIO,
.active_level = BUTTON_INACTIVE,
.enable_power_save = false,
.disable_pull = false
};
ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(false);
}, this);
iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_minimum();
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
app.ToggleChatState();
}, this);
iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
self->ResetWifiConfiguration();
}
if (self->power_status_ == kDeviceBatterySupply) {
auto backlight = Board::GetInstance().GetBacklight();
backlight->SetBrightness(0);
esp_timer_stop(self->power_manager_->timer_handle_);
esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
vTaskDelay(pdMS_TO_TICKS(100));
}
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_change(true);
}, this);
iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
self->power_save_timer_->WakeUp();
self->audio_volume_maxmum();
}, this);
}
void InitializeSt7789Display() {
ESP_LOGI(TAG, "Install panel IO");
/* RD PIN */
gpio_config_t gpio_init_struct;
gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
gpio_set_level(LCD_PIN_RD, 1);
/* BL PIN */
gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
gpio_config(&gpio_init_struct);
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = LCD_PIN_DC,
.wr_gpio_num = LCD_PIN_WR,
.clk_src = LCD_CLK_SRC_DEFAULT,
.data_gpio_nums = {
LCD_PIN_D0,
LCD_PIN_D1,
LCD_PIN_D2,
LCD_PIN_D3,
LCD_PIN_D4,
LCD_PIN_D5,
LCD_PIN_D6,
LCD_PIN_D7,
},
.bus_width = 8,
.max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
.psram_trans_align = 64,
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = LCD_PIN_CS,
.pclk_hz = (20 * 1000 * 1000),
.trans_queue_depth = 7,
.on_color_trans_done = nullptr,
.user_ctx = nullptr,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.dc_levels = {
.dc_idle_level = 1,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.cs_active_high = 0,
.pclk_active_neg = 0,
.pclk_idle_low = 0,
},
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = LCD_PIN_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, true);
esp_lcd_panel_set_gap(panel, 0, 0);
esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
display_ = new SpiLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY,
{
.text_font = &font_puhui_20_4,
.icon_font = &font_awesome_20_4,
.emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(),
});
}
public:
atk_dnesp32s3_box2_wifi() {
InitializeI2c();
InitializeIoExpander();
InitializePowerSaveTimer();
InitializePowerManager();
InitializeSt7789Display();
InitializeButtons();
GetBacklight()->RestoreBrightness();
InitializeBoardPowerManager();
}
virtual AudioCodec* GetAudioCodec() override {
static Es8389AudioCodec audio_codec(
i2c_bus_,
I2C_NUM_0,
AUDIO_INPUT_SAMPLE_RATE,
AUDIO_OUTPUT_SAMPLE_RATE,
AUDIO_I2S_GPIO_MCLK,
AUDIO_I2S_GPIO_BCLK,
AUDIO_I2S_GPIO_WS,
AUDIO_I2S_GPIO_DOUT,
AUDIO_I2S_GPIO_DIN,
GPIO_NUM_NC,
AUDIO_CODEC_ES8389_ADDR,
false);
return &audio_codec;
}
virtual Display* GetDisplay() override {
return display_;
}
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
static bool last_discharging = false;
charging = power_manager_->IsCharging();
discharging = power_manager_->IsDischarging();
if (discharging != last_discharging) {
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = power_manager_->GetBatteryLevel();
return true;
}
virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled) {
power_save_timer_->WakeUp();
}
WifiBoard::SetPowerSaveMode(enabled);
}
};
DECLARE_BOARD(atk_dnesp32s3_box2_wifi);
// 定义静态成员变量
atk_dnesp32s3_box2_wifi* atk_dnesp32s3_box2_wifi::instance_ = nullptr;

View File

@@ -0,0 +1,71 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
enum PowerSupply {
kDeviceTypecSupply,
kDeviceBatterySupply,
};
#define AUDIO_INPUT_SAMPLE_RATE 16000
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
#define R_BUTTON_GPIO GPIO_NUM_0
#define XL9555_INT_GPIO GPIO_NUM_2
#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
#define DRV_IO_EXP_INPUT_MASK 0xC0E7
#define LCD_PIN_CS GPIO_NUM_14
#define LCD_PIN_DC GPIO_NUM_12
#define LCD_PIN_RD GPIO_NUM_10
#define LCD_PIN_WR GPIO_NUM_11
#define LCD_PIN_RST GPIO_NUM_NC
#define LCD_PIN_D0 GPIO_NUM_13
#define LCD_PIN_D1 GPIO_NUM_9
#define LCD_PIN_D2 GPIO_NUM_8
#define LCD_PIN_D3 GPIO_NUM_7
#define LCD_PIN_D4 GPIO_NUM_6
#define LCD_PIN_D5 GPIO_NUM_5
#define LCD_PIN_D6 GPIO_NUM_4
#define LCD_PIN_D7 GPIO_NUM_3
#define DISPLAY_WIDTH 240
#define DISPLAY_HEIGHT 320
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,9 @@
{
"target": "esp32s3",
"builds": [
{
"name": "atk-dnesp32s3-box2-wifi",
"sdkconfig_append": []
}
]
}

View File

@@ -0,0 +1,195 @@
#pragma once
#include <vector>
#include <functional>
#include "esp_io_expander_tca95xx_16bit.h"
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
class PowerManager {
private:
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
esp_io_expander_handle_t xl9555_;
uint32_t pin_val = 0;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> adc_values_;
uint32_t battery_level_ = 0;
bool is_charging_ = false;
bool is_low_battery_ = false;
int ticks_ = 0;
const int kBatteryAdcInterval = 60;
const int kBatteryAdcDataCount = 3;
const int kLowBatteryLevel = 20;
adc_oneshot_unit_handle_t adc_handle_;
void CheckBatteryStatus() {
// Get charging status
esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
if (new_charging_status != is_charging_) {
is_charging_ = new_charging_status;
if (on_charging_status_changed_) {
on_charging_status_changed_(is_charging_);
}
ReadBatteryAdcData();
return;
}
// 如果电池电量数据不足,则读取电池电量数据
if (adc_values_.size() < kBatteryAdcDataCount) {
ReadBatteryAdcData();
return;
}
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
ticks_++;
if (ticks_ % kBatteryAdcInterval == 0) {
ReadBatteryAdcData();
}
}
void ReadBatteryAdcData() {
int adc_value;
uint32_t temp_val = 0;
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
vTaskDelay(pdMS_TO_TICKS(500));
for(int t = 0; t < 10; t ++) {
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
temp_val += adc_value;
}
esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
adc_value = temp_val / 10;
// 将 ADC 值添加到队列中
adc_values_.push_back(adc_value);
if (adc_values_.size() > kBatteryAdcDataCount) {
adc_values_.erase(adc_values_.begin());
}
uint32_t average_adc = 0;
for (auto value : adc_values_) {
average_adc += value;
}
average_adc /= adc_values_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{2696, 0}, /* 3.48V -屏幕闪屏 */
{2724, 20}, /* 3.53V */
{2861, 40}, /* 3.7V */
{3038, 60}, /* 3.90V */
{3150, 80}, /* 4.02V */
{3280, 100} /* 4.14V */
};
// 低于最低值时
if (average_adc < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_adc >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
break;
}
}
}
// Check low battery status
if (adc_values_.size() >= kBatteryAdcDataCount) {
bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
if (new_low_battery_status != is_low_battery_) {
is_low_battery_ = new_low_battery_status;
if (on_low_battery_status_changed_) {
on_low_battery_status_changed_(is_low_battery_);
}
}
}
low_voltage_ = adc_value;
// ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
}
public:
esp_timer_handle_t timer_handle_;
uint16_t low_voltage_ = 2630;
PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(arg);
self->CheckBatteryStatus();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "battery_check_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
// 初始化 ADC
adc_oneshot_unit_init_cfg_t init_config = {
.unit_id = ADC_UNIT_1,
.ulp_mode = ADC_ULP_MODE_DISABLE,
};
ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
adc_oneshot_chan_cfg_t chan_config = {
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
}
~PowerManager() {
if (timer_handle_) {
esp_timer_stop(timer_handle_);
esp_timer_delete(timer_handle_);
}
if (adc_handle_) {
adc_oneshot_del_unit(adc_handle_);
}
}
bool IsCharging() {
// 如果电量已经满了,则不再显示充电中
if (battery_level_ == 100) {
return false;
}
return is_charging_;
}
bool IsDischarging() {
// 没有区分充电和放电,所以直接返回相反状态
return !is_charging_;
}
uint8_t GetBatteryLevel() {
return battery_level_;
}
void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
on_low_battery_status_changed_ = callback;
}
void OnChargingStatusChanged(std::function<void(bool)> callback) {
on_charging_status_changed_ = callback;
}
};

View File

@@ -229,7 +229,7 @@ public:
}
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 256);
return &backlight;
}
};

View File

@@ -81,12 +81,12 @@ void Backlight::OnTransitionTimer() {
}
}
PwmBacklight::PwmBacklight(gpio_num_t pin, bool output_invert) : Backlight() {
PwmBacklight::PwmBacklight(gpio_num_t pin, bool output_invert, uint32_t freq_hz) : Backlight() {
const ledc_timer_config_t backlight_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_10_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = 25000, //背光pwm频率需要高一点防止电感啸叫
.freq_hz = freq_hz, //背光pwm频率需要高一点防止电感啸叫
.clk_cfg = LEDC_AUTO_CLK,
.deconfigure = false
};

View File

@@ -29,7 +29,7 @@ protected:
class PwmBacklight : public Backlight {
public:
PwmBacklight(gpio_num_t pin, bool output_invert = false);
PwmBacklight(gpio_num_t pin, bool output_invert = false, uint32_t freq_hz = 25000);
~PwmBacklight();
void SetBrightnessImpl(uint8_t brightness) override;

View File

@@ -210,7 +210,7 @@ std::string Esp32Camera::Explain(const std::string& question) {
});
auto network = Board::GetInstance().GetNetwork();
auto http = std::unique_ptr<Http>(network->CreateHttp(3));
auto http = network->CreateHttp(3);
// 构造multipart/form-data请求体
std::string boundary = "----ESP32_CAMERA_BOUNDARY";

View File

@@ -279,7 +279,7 @@ std::string SscmaCamera::Explain(const std::string& question) {
}
auto network = Board::GetInstance().GetNetwork();
auto http = std::unique_ptr<Http>(network->CreateHttp(3));
auto http = network->CreateHttp(3);
// 构造multipart/form-data请求体
std::string boundary = "----ESP32_CAMERA_BOUNDARY";

View File

@@ -5,7 +5,9 @@
"name": "surfer-c3-1.14tft",
"sdkconfig_append": [
"CONFIG_PM_ENABLE=y",
"CONFIG_FREERTOS_USE_TICKLESS_IDLE=y"
"CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
"CONFIG_USE_ESP_WAKE_WORD=y",
"CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y"
]
}
]

View File

@@ -75,7 +75,7 @@ private:
{3340, 40},
{3480, 60},
{3620, 80},
{3765, 100}
{3760, 100}
};
// 低于最低值时

View File

@@ -0,0 +1,11 @@
# Waveshare ESP32-S3-Touch-AMOLED-1.75
[ESP32-S3-Touch-AMOLED-2.06](https://www.waveshare.com/esp32-s3-touch-amoled-2.06.htm) is a high-performance, wearable watch-style development board designed by Waveshare. Based on the ESP32-S3R8 microcontroller, it integrates a 2.06inch AMOLED capacitive touch display, 6-axis IMU, RTC chip, audio codec chip, power management IC, and so on. Comes with a custom-designed case with a smartwatch-like appearance, making it ideal for prototyping and functional verification of wearable applications.
## images
| [ESP32-S3-Touch-AMOLED-2.06](https://www.waveshare.com/esp32-s3-touch-amoled-2.06.htm) |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| <img style="width: 150px; height: auto; display: block; margin: 0 auto;" src= "https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/e/s/esp32-s3-touch-amoled-2.06-1.jpg"> |

View File

@@ -0,0 +1,43 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16
#define AUDIO_I2S_GPIO_WS GPIO_NUM_45
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_42
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_40
#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14
#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
#define BOOT_BUTTON_GPIO GPIO_NUM_0
#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12
#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_11
#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4
#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5
#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6
#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7
#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_8
#define DISPLAY_WIDTH 410
#define DISPLAY_HEIGHT 502
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY false
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,12 @@
{
"target": "esp32s3",
"builds": [
{
"name": "waveshare-s3-touch-amoled-2.06",
"sdkconfig_append": [
"CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
"CONFIG_USE_DEVICE_AEC=y"
]
}
]
}

View File

@@ -0,0 +1,366 @@
#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"
#include "button.h"
#include "led/single_led.h"
#include "mcp_server.h"
#include "config.h"
#include "power_save_timer.h"
#include "axp2101.h"
#include "i2c_device.h"
#include <wifi_station.h>
#include <esp_log.h>
#include <esp_lcd_panel_vendor.h>
#include <driver/i2c_master.h>
#include <driver/spi_master.h>
#include "settings.h"
#include <esp_lcd_touch_ft5x06.h>
#include <esp_lvgl_port.h>
#include <lvgl.h>
#define TAG "WaveshareEsp32s3TouchAMOLED2inch06"
LV_FONT_DECLARE(font_puhui_30_4);
LV_FONT_DECLARE(font_awesome_30_4);
class Pmic : public Axp2101 {
public:
Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
WriteReg(0x27, 0x10); // hold 4s to power off
// Disable All DCs but DC1
WriteReg(0x80, 0x01);
// Disable All LDOs
WriteReg(0x90, 0x00);
WriteReg(0x91, 0x00);
// Set DC1 to 3.3V
WriteReg(0x82, (3300 - 1500) / 100);
// Set ALDO1 to 3.3V
WriteReg(0x92, (3300 - 500) / 100);
WriteReg(0x93, (3300 - 500) / 100);
// Enable ALDO1(MIC)
WriteReg(0x90, 0x03);
WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
}
};
#define LCD_OPCODE_WRITE_CMD (0x02ULL)
#define LCD_OPCODE_READ_CMD (0x03ULL)
#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
static const sh8601_lcd_init_cmd_t vendor_specific_init[] = {
// set display to qspi mode
{0x11, (uint8_t []){0x00}, 0, 120},
{0xC4, (uint8_t []){0x80}, 1, 0},
{0x44, (uint8_t []){0x01, 0xD1}, 2, 0},
{0x35, (uint8_t []){0x00}, 1, 0},
{0x53, (uint8_t []){0x20}, 1, 10},
{0x63, (uint8_t []){0xFF}, 1, 10},
{0x51, (uint8_t []){0x00}, 1, 10},
{0x2A, (uint8_t []){0x00,0x16,0x01,0xAF}, 4, 0},
{0x2B, (uint8_t []){0x00,0x00,0x01,0xF5}, 4, 0},
{0x29, (uint8_t []){0x00}, 0, 10},
{0x51, (uint8_t []){0xFF}, 1, 0},
};
// 在waveshare_amoled_2_06类之前添加新的显示类
class CustomLcdDisplay : public SpiLcdDisplay {
public:
static void rounder_event_cb(lv_event_t* e) {
lv_area_t* area = (lv_area_t* )lv_event_get_param(e);
uint16_t x1 = area->x1;
uint16_t x2 = area->x2;
uint16_t y1 = area->y1;
uint16_t y2 = area->y2;
// round the start of coordinate down to the nearest 2M number
area->x1 = (x1 >> 1) << 1;
area->y1 = (y1 >> 1) << 1;
// round the end of coordinate up to the nearest 2N+1 number
area->x2 = ((x2 >> 1) << 1) + 1;
area->y2 = ((y2 >> 1) << 1) + 1;
}
CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
esp_lcd_panel_handle_t panel_handle,
int width,
int height,
int offset_x,
int offset_y,
bool mirror_x,
bool mirror_y,
bool swap_xy)
: SpiLcdDisplay(io_handle, panel_handle,
width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy,
{
.text_font = &font_puhui_30_4,
.icon_font = &font_awesome_30_4,
#if CONFIG_USE_WECHAT_MESSAGE_STYLE
.emoji_font = font_emoji_32_init(),
#else
.emoji_font = font_emoji_64_init(),
#endif
})
{
DisplayLockGuard lock(this);
lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES* 0.1, 0);
lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES* 0.1, 0);
lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
}
};
class CustomBacklight : public Backlight {
public:
CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {}
protected:
esp_lcd_panel_io_handle_t panel_io_;
virtual void SetBrightnessImpl(uint8_t brightness) override {
auto display = Board::GetInstance().GetDisplay();
DisplayLockGuard lock(display);
uint8_t data[1] = {((uint8_t)((255* brightness) / 100))};
int lcd_cmd = 0x51;
lcd_cmd &= 0xff;
lcd_cmd <<= 8;
lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24;
esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data));
}
};
class WaveshareEsp32s3TouchAMOLED2inch06 : public WifiBoard {
private:
i2c_master_bus_handle_t i2c_bus_;
Pmic* pmic_ = nullptr;
Button boot_button_;
CustomLcdDisplay* display_;
CustomBacklight* backlight_;
PowerSaveTimer* power_save_timer_;
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
power_save_timer_->OnEnterSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
GetBacklight()->SetBrightness(20); });
power_save_timer_->OnExitSleepMode([this]() {
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
GetBacklight()->RestoreBrightness(); });
power_save_timer_->OnShutdownRequest([this](){
pmic_->PowerOff(); });
power_save_timer_->SetEnabled(true);
}
void InitializeCodecI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.flags = {
.enable_internal_pullup = 1,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
}
void InitializeAxp2101() {
ESP_LOGI(TAG, "Init AXP2101");
pmic_ = new Pmic(i2c_bus_, 0x34);
}
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.sclk_io_num = EXAMPLE_PIN_NUM_LCD_PCLK;
buscfg.data0_io_num = EXAMPLE_PIN_NUM_LCD_DATA0;
buscfg.data1_io_num = EXAMPLE_PIN_NUM_LCD_DATA1;
buscfg.data2_io_num = EXAMPLE_PIN_NUM_LCD_DATA2;
buscfg.data3_io_num = EXAMPLE_PIN_NUM_LCD_DATA3;
buscfg.max_transfer_sz = DISPLAY_WIDTH* DISPLAY_HEIGHT* sizeof(uint16_t);
buscfg.flags = SPICOMMON_BUSFLAG_QUAD;
ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
ResetWifiConfiguration();
}
app.ToggleChatState();
});
#if CONFIG_USE_DEVICE_AEC
boot_button_.OnDoubleClick([this]() {
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateIdle) {
app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
}
});
#endif
}
void InitializeSH8601Display() {
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 = SH8601_PANEL_IO_QSPI_CONFIG(
EXAMPLE_PIN_NUM_LCD_CS,
nullptr,
nullptr);
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
// 初始化液晶屏驱动芯片
ESP_LOGD(TAG, "Install LCD driver");
const sh8601_vendor_config_t vendor_config = {
.init_cmds = &vendor_specific_init[0],
.init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t),
.flags = {
.use_qspi_interface = 1,
}};
esp_lcd_panel_dev_config_t panel_config = {};
panel_config.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST;
panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
panel_config.bits_per_pixel = 16;
panel_config.vendor_config = (void* )&vendor_config;
ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel));
esp_lcd_panel_set_gap(panel, 0x16, 0);
esp_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
esp_lcd_panel_invert_color(panel, false);
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
esp_lcd_panel_disp_on_off(panel, true);
display_ = new CustomLcdDisplay(panel_io, panel,
DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
backlight_ = new CustomBacklight(panel_io);
backlight_->RestoreBrightness();
}
void InitializeTouch() {
esp_lcd_touch_handle_t tp;
esp_lcd_touch_config_t tp_cfg = {
.x_max = DISPLAY_WIDTH - 1,
.y_max = DISPLAY_HEIGHT - 1,
.rst_gpio_num = GPIO_NUM_9,
.int_gpio_num = GPIO_NUM_38,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 0,
.mirror_y = 0,
},
};
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG();
tp_io_config.scl_speed_hz = 400* 1000;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
ESP_LOGI(TAG, "Initialize touch controller");
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp));
const lvgl_port_touch_cfg_t touch_cfg = {
.disp = lv_display_get_default(),
.handle = tp,
};
lvgl_port_add_touch(&touch_cfg);
ESP_LOGI(TAG, "Touch panel initialized successfully");
}
// 初始化工具
void InitializeTools() {
auto &mcp_server = McpServer::GetInstance();
mcp_server.AddTool("self.system.reconfigure_wifi",
"Reboot the device and enter WiFi configuration mode.\n"
"**CAUTION** You must ask the user to confirm this action.",
PropertyList(), [this](const PropertyList& properties) {
ResetWifiConfiguration();
return true;
});
}
public:
WaveshareEsp32s3TouchAMOLED2inch06() : boot_button_(BOOT_BUTTON_GPIO) {
InitializePowerSaveTimer();
InitializeCodecI2c();
InitializeAxp2101();
InitializeSpi();
InitializeSH8601Display();
InitializeTouch();
InitializeButtons();
InitializeTools();
}
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 {
return backlight_;
}
virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override {
static bool last_discharging = false;
charging = pmic_->IsCharging();
discharging = pmic_->IsDischarging();
if (discharging != last_discharging)
{
power_save_timer_->SetEnabled(discharging);
last_discharging = discharging;
}
level = pmic_->GetBatteryLevel();
return true;
}
virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled)
{
power_save_timer_->WakeUp();
}
WifiBoard::SetPowerSaveMode(enabled);
}
};
DECLARE_BOARD(WaveshareEsp32s3TouchAMOLED2inch06);

View File

@@ -6,6 +6,7 @@
"sdkconfig_append": [
"CONFIG_PM_ENABLE=y",
"CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
"CONFIG_USE_ESP_WAKE_WORD=y",
"CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y"
]
}

View File

@@ -14,8 +14,8 @@ dependencies:
espressif/esp_lcd_panel_io_additions: ^1.0.1
78/esp_lcd_nv3023: ~1.0.0
78/esp-wifi-connect: ~2.4.3
78/esp-opus-encoder: ~2.3.3
78/esp-ml307: ~3.0.2
78/esp-opus-encoder: ~2.4.0
78/esp-ml307: ~3.2.0
78/xiaozhi-fonts: ~1.3.2
espressif/led_strip: ^2.5.5
espressif/esp_codec_dev: ~1.3.2

View File

@@ -49,7 +49,7 @@ std::string Ota::GetCheckVersionUrl() {
return url;
}
Http* Ota::SetupHttp() {
std::unique_ptr<Http> Ota::SetupHttp() {
auto& board = Board::GetInstance();
auto app_desc = esp_app_get_description();
@@ -85,7 +85,7 @@ bool Ota::CheckVersion() {
return false;
}
auto http = std::unique_ptr<Http>(SetupHttp());
auto http = SetupHttp();
std::string data = board.GetJson();
std::string method = data.length() > 0 ? "POST" : "GET";
@@ -274,7 +274,7 @@ bool Ota::Upgrade(const std::string& firmware_url) {
std::string image_header;
auto network = Board::GetInstance().GetNetwork();
auto http = std::unique_ptr<Http>(network->CreateHttp(0));
auto http = network->CreateHttp(0);
if (!http->Open("GET", firmware_url)) {
ESP_LOGE(TAG, "Failed to open HTTP connection");
return false;
@@ -452,7 +452,7 @@ esp_err_t Ota::Activate() {
url += "activate";
}
auto http = std::unique_ptr<Http>(SetupHttp());
auto http = SetupHttp();
std::string data = GetActivationPayload();
http->SetContent(std::move(data));

View File

@@ -51,7 +51,7 @@ private:
std::vector<int> ParseVersion(const std::string& version);
bool IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion);
std::string GetActivationPayload();
Http* SetupHttp();
std::unique_ptr<Http> SetupHttp();
};
#endif // _OTA_H

View File

@@ -16,12 +16,6 @@ MqttProtocol::MqttProtocol() {
MqttProtocol::~MqttProtocol() {
ESP_LOGI(TAG, "MqttProtocol deinit");
if (udp_ != nullptr) {
delete udp_;
}
if (mqtt_ != nullptr) {
delete mqtt_;
}
vEventGroupDelete(event_group_handle_);
}
@@ -32,7 +26,7 @@ bool MqttProtocol::Start() {
bool MqttProtocol::StartMqttClient(bool report_error) {
if (mqtt_ != nullptr) {
ESP_LOGW(TAG, "Mqtt client already started");
delete mqtt_;
mqtt_.reset();
}
Settings settings("mqtt", false);
@@ -150,10 +144,7 @@ bool MqttProtocol::SendAudio(std::unique_ptr<AudioStreamPacket> packet) {
void MqttProtocol::CloseAudioChannel() {
{
std::lock_guard<std::mutex> lock(channel_mutex_);
if (udp_ != nullptr) {
delete udp_;
udp_ = nullptr;
}
udp_.reset();
}
std::string message = "{";
@@ -193,10 +184,6 @@ bool MqttProtocol::OpenAudioChannel() {
}
std::lock_guard<std::mutex> lock(channel_mutex_);
if (udp_ != nullptr) {
delete udp_;
}
auto network = Board::GetInstance().GetNetwork();
udp_ = network->CreateUdp(2);
udp_->OnMessage([this](const std::string& data) {

View File

@@ -37,8 +37,8 @@ private:
std::string publish_topic_;
std::mutex channel_mutex_;
Mqtt* mqtt_ = nullptr;
Udp* udp_ = nullptr;
std::unique_ptr<Mqtt> mqtt_;
std::unique_ptr<Udp> udp_;
mbedtls_aes_context aes_ctx_;
std::string aes_nonce_;
std::string udp_server_;

View File

@@ -17,9 +17,6 @@ WebsocketProtocol::WebsocketProtocol() {
}
WebsocketProtocol::~WebsocketProtocol() {
if (websocket_ != nullptr) {
delete websocket_;
}
vEventGroupDelete(event_group_handle_);
}
@@ -79,17 +76,10 @@ bool WebsocketProtocol::IsAudioChannelOpened() const {
}
void WebsocketProtocol::CloseAudioChannel() {
if (websocket_ != nullptr) {
delete websocket_;
websocket_ = nullptr;
}
websocket_.reset();
}
bool WebsocketProtocol::OpenAudioChannel() {
if (websocket_ != nullptr) {
delete websocket_;
}
Settings settings("websocket", false);
std::string url = settings.GetString("url");
std::string token = settings.GetString("token");
@@ -102,6 +92,10 @@ bool WebsocketProtocol::OpenAudioChannel() {
auto network = Board::GetInstance().GetNetwork();
websocket_ = network->CreateWebSocket(1);
if (websocket_ == nullptr) {
ESP_LOGE(TAG, "Failed to create websocket");
return false;
}
if (!token.empty()) {
// If token not has a space, add "Bearer " prefix

View File

@@ -23,7 +23,7 @@ public:
private:
EventGroupHandle_t event_group_handle_;
WebSocket* websocket_ = nullptr;
std::unique_ptr<WebSocket> websocket_;
int version_ = 1;
void ParseServerHello(const cJSON* root);