v1.8.0: Audio 代码重构与低功耗优化 (#943)

* Reconstruct Audio Code

* Remove old IoT implementation

* Add MQTT-UDP documentation

* OTA升级失败时,可以继续使用
This commit is contained in:
Xiaoxia
2025-07-19 22:45:22 +08:00
committed by GitHub
parent 0621578f55
commit 3c71558a5f
173 changed files with 2099 additions and 3265 deletions

View File

@@ -0,0 +1,4 @@
# 开源地址
https://oshwhub.com/tenclass01/xmini_c3_4g

View File

@@ -1,200 +0,0 @@
#pragma once
#include <vector>
#include <functional>
#include <esp_timer.h>
#include <driver/gpio.h>
#include <esp_adc/adc_oneshot.h>
#include <esp_adc/adc_cali.h>
#include <esp_adc/adc_cali_scheme.h>
class PowerManager {
private:
esp_timer_handle_t timer_handle_;
std::function<void(bool)> on_charging_status_changed_;
std::function<void(bool)> on_low_battery_status_changed_;
gpio_num_t charging_pin_ = GPIO_NUM_NC;
std::vector<uint16_t> voltages_;
uint32_t battery_level_ = 0;
bool is_charging_ = false;
bool is_low_battery_ = false;
int ticks_ = 0;
const int kBatteryAdcInterval = 1;
const int kBatteryAdcDataCount = 3;
const int kLowBatteryLevel = 20;
adc_oneshot_unit_handle_t adc_handle_;
adc_cali_handle_t adc_cali_handle_;
void CheckBatteryStatus() {
// Get charging status
bool new_charging_status = gpio_get_level(charging_pin_) == 1;
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 (voltages_.size() < kBatteryAdcDataCount) {
ReadBatteryAdcData();
return;
}
// 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据
ticks_++;
if (ticks_ % kBatteryAdcInterval == 0) {
ReadBatteryAdcData();
}
}
void ReadBatteryAdcData() {
int adc_value, voltage;
ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_4, &adc_value));
if (adc_value == 0) {
return;
}
ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_value, &voltage));
// 将 ADC 值添加到队列中
voltages_.push_back(voltage);
if (voltages_.size() > kBatteryAdcDataCount) {
voltages_.erase(voltages_.begin());
}
uint32_t average_voltage = 0;
for (auto value : voltages_) {
average_voltage += value;
}
average_voltage /= voltages_.size();
// 定义电池电量区间
const struct {
uint16_t adc;
uint8_t level;
} levels[] = {
{1600, 0},
{1700, 20},
{1800, 40},
{1900, 60},
{2000, 80},
{2100, 100}
};
// 低于最低值时
if (average_voltage < levels[0].adc) {
battery_level_ = 0;
}
// 高于最高值时
else if (average_voltage >= levels[5].adc) {
battery_level_ = 100;
} else {
// 线性插值计算中间值
for (int i = 0; i < 5; i++) {
if (average_voltage >= levels[i].adc && average_voltage < levels[i+1].adc) {
float ratio = static_cast<float>(average_voltage - 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 (voltages_.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_);
}
}
}
}
public:
PowerManager(gpio_num_t pin) : charging_pin_(pin) {
// 初始化充电引脚
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pin_bit_mask = (1ULL << charging_pin_);
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
gpio_config(&io_conf);
// 创建电池电量检查定时器
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_4, &chan_config));
adc_cali_curve_fitting_config_t cali_config = {
.unit_id = ADC_UNIT_1,
.atten = ADC_ATTEN_DB_12,
.bitwidth = ADC_BITWIDTH_12,
};
ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle_));
}
~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

@@ -1,5 +1,5 @@
#include "ml307_board.h"
#include "audio_codecs/es8311_audio_codec.h"
#include "codecs/es8311_audio_codec.h"
#include "display/oled_display.h"
#include "application.h"
#include "button.h"
@@ -7,9 +7,9 @@
#include "mcp_server.h"
#include "settings.h"
#include "config.h"
#include "power_save_timer.h"
#include "sleep_timer.h"
#include "font_awesome_symbols.h"
#include "power_manager.h"
#include "adc_battery_monitor.h"
#include <wifi_station.h>
#include <esp_log.h>
@@ -32,44 +32,42 @@ private:
Display* display_ = nullptr;
Button boot_button_;
bool press_to_talk_enabled_ = false;
PowerSaveTimer* power_save_timer_ = nullptr;
PowerManager* power_manager_ = nullptr;
SleepTimer* sleep_timer_ = nullptr;
AdcBatteryMonitor* adc_battery_monitor_ = nullptr;
void InitializePowerManager() {
power_manager_ = new PowerManager(GPIO_NUM_12);
power_manager_->OnChargingStatusChanged([this](bool is_charging) {
void InitializeBatteryMonitor() {
adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_4, 100000, 100000, GPIO_NUM_12);
adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) {
if (is_charging) {
power_save_timer_->SetEnabled(false);
sleep_timer_->SetEnabled(false);
} else {
power_save_timer_->SetEnabled(true);
sleep_timer_->SetEnabled(true);
}
});
}
void InitializePowerSaveTimer() {
#if CONFIG_USE_ESP_WAKE_WORD
power_save_timer_ = new PowerSaveTimer(160, 600);
sleep_timer_ = new SleepTimer(600);
#else
power_save_timer_ = new PowerSaveTimer(160, 60);
sleep_timer_ = new SleepTimer(30);
#endif
power_save_timer_->OnEnterSleepMode([this]() {
sleep_timer_->OnEnterLightSleepMode([this]() {
ESP_LOGI(TAG, "Enabling sleep mode");
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("sleepy");
auto codec = GetAudioCodec();
codec->EnableInput(false);
// Show the standby screen
GetDisplay()->ShowStandbyScreen(true);
// Enable sleep mode, and sleep in 1 second after DTR is set to high
modem_->SetSleepMode(true, 1);
// Set the DTR pin to high to make the modem enter sleep mode
modem_->GetAtUart()->SetDtrPin(true);
});
power_save_timer_->OnExitSleepMode([this]() {
auto codec = GetAudioCodec();
codec->EnableInput(true);
auto display = GetDisplay();
display->SetChatMessage("system", "");
display->SetEmotion("neutral");
sleep_timer_->OnExitLightSleepMode([this]() {
// Set the DTR pin to low to make the modem wake up
modem_->GetAtUart()->SetDtrPin(false);
// Hide the standby screen
GetDisplay()->ShowStandbyScreen(false);
});
power_save_timer_->SetEnabled(true);
sleep_timer_->SetEnabled(true);
}
void InitializeCodecI2c() {
@@ -152,9 +150,6 @@ private:
}
});
boot_button_.OnPressDown([this]() {
if (power_save_timer_) {
power_save_timer_->WakeUp();
}
if (press_to_talk_enabled_) {
Application::GetInstance().StartListening();
}
@@ -170,9 +165,6 @@ private:
Settings settings("vendor");
press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0;
#if CONFIG_IOT_PROTOCOL_XIAOZHI
#error "XiaoZhi 协议不支持"
#elif CONFIG_IOT_PROTOCOL_MCP
auto& mcp_server = McpServer::GetInstance();
mcp_server.AddTool("self.set_press_to_talk",
"Switch between press to talk mode (长按说话) and click to talk mode (单击说话).\n"
@@ -191,14 +183,13 @@ private:
}
throw std::runtime_error("Invalid mode: " + mode);
});
#endif
}
public:
XminiC3Board() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, ML307_DTR_PIN),
boot_button_(BOOT_BUTTON_GPIO) {
boot_button_(BOOT_BUTTON_GPIO, false, 0, 0, true) {
InitializePowerManager();
InitializeBatteryMonitor();
InitializePowerSaveTimer();
InitializeCodecI2c();
InitializeSsd1306Display();
@@ -223,14 +214,9 @@ public:
}
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();
charging = adc_battery_monitor_->IsCharging();
discharging = adc_battery_monitor_->IsDischarging();
level = adc_battery_monitor_->GetBatteryLevel();
return true;
}
@@ -248,7 +234,7 @@ public:
virtual void SetPowerSaveMode(bool enabled) override {
if (!enabled) {
power_save_timer_->WakeUp();
sleep_timer_->WakeUp();
}
Ml307Board::SetPowerSaveMode(enabled);
}