Update to 2.0.1
This commit is contained in:
@@ -2,19 +2,21 @@
|
||||
#include "system_info.h"
|
||||
#include "settings.h"
|
||||
#include "display/display.h"
|
||||
#include "display/oled_display.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include "esp32_music.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_ota_ops.h>
|
||||
#include <esp_chip_info.h>
|
||||
#include <esp_random.h>
|
||||
|
||||
#include "esp32_music.h"
|
||||
|
||||
#define TAG "Board"
|
||||
|
||||
Board::Board() {
|
||||
music_ = nullptr; // 先初始化为空指针
|
||||
|
||||
|
||||
Settings settings("board", true);
|
||||
uuid_ = settings.GetString("uuid");
|
||||
if (uuid_.empty()) {
|
||||
@@ -22,10 +24,6 @@ Board::Board() {
|
||||
settings.SetString("uuid", uuid_);
|
||||
}
|
||||
ESP_LOGI(TAG, "UUID=%s SKU=%s", uuid_.c_str(), BOARD_NAME);
|
||||
|
||||
// 初始化音乐播放器
|
||||
music_ = new Esp32Music();
|
||||
ESP_LOGI(TAG, "Music player initialized for all boards");
|
||||
}
|
||||
|
||||
Board::~Board() {
|
||||
@@ -173,6 +171,21 @@ std::string Board::GetSystemInfoJson() {
|
||||
json += R"("label":")" + std::string(ota_partition->label) + R"(")";
|
||||
json += R"(},)";
|
||||
|
||||
// Append display info
|
||||
auto display = GetDisplay();
|
||||
if (display) {
|
||||
json += R"("display":{)";
|
||||
if (dynamic_cast<OledDisplay*>(display)) {
|
||||
json += R"("monochrome":)" + std::string("true") + R"(,)";
|
||||
} else {
|
||||
json += R"("monochrome":)" + std::string("false") + R"(,)";
|
||||
}
|
||||
json += R"("width":)" + std::to_string(display->width()) + R"(,)";
|
||||
json += R"("height":)" + std::to_string(display->height()) + R"(,)";
|
||||
json.pop_back(); // Remove the last comma
|
||||
}
|
||||
json += R"(},)";
|
||||
|
||||
json += R"("board":)" + GetBoardJson();
|
||||
|
||||
// Close the JSON object
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
#include "led/led.h"
|
||||
#include "backlight.h"
|
||||
#include "camera.h"
|
||||
#include "music.h"
|
||||
#include "assets.h"
|
||||
|
||||
#include "music.h"
|
||||
|
||||
|
||||
void* create_board();
|
||||
class AudioCodec;
|
||||
@@ -32,6 +33,7 @@ protected:
|
||||
|
||||
// 音乐播放器实例
|
||||
Music* music_;
|
||||
|
||||
public:
|
||||
static Board& GetInstance() {
|
||||
static Board* instance = static_cast<Board*>(create_board());
|
||||
@@ -64,4 +66,4 @@ void* create_board() { \
|
||||
return new BOARD_CLASS_NAME(); \
|
||||
}
|
||||
|
||||
#endif // BOARD_H
|
||||
#endif // BOARD_H
|
||||
|
||||
@@ -63,33 +63,26 @@ bool Esp32Camera::Capture() {
|
||||
// 显示预览图片
|
||||
auto display = dynamic_cast<LvglDisplay*>(Board::GetInstance().GetDisplay());
|
||||
if (display != nullptr) {
|
||||
// Create a new preview image
|
||||
auto img_dsc = (lv_img_dsc_t*)heap_caps_calloc(1, sizeof(lv_img_dsc_t), MALLOC_CAP_8BIT);
|
||||
img_dsc->header.magic = LV_IMAGE_HEADER_MAGIC;
|
||||
img_dsc->header.cf = LV_COLOR_FORMAT_RGB565;
|
||||
img_dsc->header.flags = 0;
|
||||
img_dsc->header.w = fb_->width;
|
||||
img_dsc->header.h = fb_->height;
|
||||
img_dsc->header.stride = fb_->width * 2;
|
||||
img_dsc->data_size = fb_->width * fb_->height * 2;
|
||||
img_dsc->data = (uint8_t*)heap_caps_malloc(img_dsc->data_size, MALLOC_CAP_SPIRAM);
|
||||
if (img_dsc->data == nullptr) {
|
||||
auto data = (uint8_t*)heap_caps_malloc(fb_->len, MALLOC_CAP_SPIRAM);
|
||||
if (data == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for preview image");
|
||||
heap_caps_free(img_dsc);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto src = (uint16_t*)fb_->buf;
|
||||
auto dst = (uint16_t*)img_dsc->data;
|
||||
auto dst = (uint16_t*)data;
|
||||
size_t pixel_count = fb_->len / 2;
|
||||
for (size_t i = 0; i < pixel_count; i++) {
|
||||
// 交换每个16位字内的字节
|
||||
dst[i] = __builtin_bswap16(src[i]);
|
||||
}
|
||||
display->SetPreviewImage(img_dsc);
|
||||
|
||||
auto image = std::make_unique<LvglAllocatedImage>(data, fb_->len, fb_->width, fb_->height, fb_->width * 2, LV_COLOR_FORMAT_RGB565);
|
||||
display->SetPreviewImage(std::move(image));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Esp32Camera::SetHMirror(bool enabled) {
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (s == nullptr) {
|
||||
|
||||
@@ -166,11 +166,12 @@ Esp32Music::Esp32Music() : last_downloaded_data_(), current_music_url_(), curren
|
||||
song_name_displayed_(false), current_lyric_url_(), lyrics_(),
|
||||
current_lyric_index_(-1), lyric_thread_(), is_lyric_running_(false),
|
||||
display_mode_(DISPLAY_MODE_LYRICS), is_playing_(false), is_downloading_(false),
|
||||
play_thread_(), download_thread_(), audio_buffer_(), buffer_mutex_(),
|
||||
is_paused_(false), play_thread_(), download_thread_(), audio_buffer_(), buffer_mutex_(),
|
||||
buffer_cv_(), buffer_size_(0), mp3_decoder_(nullptr), mp3_frame_info_(),
|
||||
mp3_decoder_initialized_(false) {
|
||||
ESP_LOGI(TAG, "Music player initialized with default spectrum display mode");
|
||||
InitializeMp3Decoder();
|
||||
// 延迟MP3解码器初始化,避免在构造函数中初始化导致的问题
|
||||
// InitializeMp3Decoder();
|
||||
}
|
||||
|
||||
Esp32Music::~Esp32Music() {
|
||||
@@ -282,9 +283,9 @@ Esp32Music::~Esp32Music() {
|
||||
}
|
||||
|
||||
bool Esp32Music::Download(const std::string& song_name, const std::string& artist_name) {
|
||||
ESP_LOGI(TAG, "Starting to get music details for: %s", song_name.c_str());
|
||||
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
|
||||
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
|
||||
ESP_LOGI(TAG, "Starting to get music details for: %s", song_name.c_str());
|
||||
|
||||
// 清空之前的下载数据
|
||||
last_downloaded_data_.clear();
|
||||
@@ -357,21 +358,17 @@ bool Esp32Music::Download(const std::string& song_name, const std::string& artis
|
||||
if (cJSON_IsString(audio_url) && audio_url->valuestring && strlen(audio_url->valuestring) > 0) {
|
||||
ESP_LOGI(TAG, "Audio URL path: %s", audio_url->valuestring);
|
||||
|
||||
// 第二步:直接使用audio_url播放音乐
|
||||
std::string audio_path = audio_url->valuestring;
|
||||
current_music_url_ = audio_path;
|
||||
// 第二步:直接使用音频URL开始流式播放
|
||||
std::string current_music_url_ = audio_url->valuestring;
|
||||
|
||||
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
|
||||
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
|
||||
ESP_LOGI(TAG, "Starting streaming playback for: %s", song_name.c_str());
|
||||
song_name_displayed_ = false; // 重置歌名显示标志
|
||||
StartStreaming(current_music_url_);
|
||||
|
||||
// 处理歌词URL - 只有在歌词显示模式下才启动歌词
|
||||
if (cJSON_IsString(lyric_url) && lyric_url->valuestring && strlen(lyric_url->valuestring) > 0) {
|
||||
// 直接使用歌词URL
|
||||
std::string lyric_path = lyric_url->valuestring;
|
||||
current_lyric_url_ = lyric_path;
|
||||
// 使用歌词URL获取歌词
|
||||
std::string current_lyric_url_ = lyric_url->valuestring;
|
||||
|
||||
// 根据显示模式决定是否启动歌词
|
||||
if (display_mode_ == DISPLAY_MODE_LYRICS) {
|
||||
@@ -431,6 +428,14 @@ bool Esp32Music::StartStreaming(const std::string& music_url) {
|
||||
|
||||
ESP_LOGD(TAG, "Starting streaming for URL: %s", music_url.c_str());
|
||||
|
||||
// 确保MP3解码器已初始化
|
||||
if (!mp3_decoder_initialized_) {
|
||||
if (!InitializeMp3Decoder()) {
|
||||
ESP_LOGE(TAG, "Failed to initialize MP3 decoder");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 停止之前的播放和下载
|
||||
is_downloading_ = false;
|
||||
is_playing_ = false;
|
||||
@@ -491,6 +496,7 @@ bool Esp32Music::StopStreaming() {
|
||||
// 停止下载和播放标志
|
||||
is_downloading_ = false;
|
||||
is_playing_ = false;
|
||||
is_paused_ = false; // 重置暂停状态
|
||||
|
||||
// 清空歌名显示
|
||||
auto& board = Board::GetInstance();
|
||||
@@ -723,8 +729,6 @@ void Esp32Music::PlayAudioStream() {
|
||||
});
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
|
||||
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
|
||||
ESP_LOGI(TAG, "Starting playback with buffer size: %d", buffer_size_);
|
||||
|
||||
size_t total_played = 0;
|
||||
@@ -744,18 +748,20 @@ void Esp32Music::PlayAudioStream() {
|
||||
bool id3_processed = false;
|
||||
|
||||
while (is_playing_) {
|
||||
// 检查是否被暂停
|
||||
if (is_paused_) {
|
||||
ESP_LOGD(TAG, "Music playback paused, waiting...");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查设备状态,只有在空闲状态才播放音乐
|
||||
auto& app = Application::GetInstance();
|
||||
DeviceState current_state = app.GetDeviceState();
|
||||
|
||||
// 状态转换:说话中-》聆听中-》待机状态-》播放音乐
|
||||
if (current_state == kDeviceStateListening || current_state == kDeviceStateSpeaking) {
|
||||
if (current_state == kDeviceStateSpeaking) {
|
||||
ESP_LOGI(TAG, "Device is in speaking state, switching to listening state for music playback");
|
||||
}
|
||||
if (current_state == kDeviceStateListening) {
|
||||
ESP_LOGI(TAG, "Device is in listening state, switching to idle state for music playback");
|
||||
}
|
||||
// 等小智把话说完了,变成聆听状态之后,马上转成待机状态,进入音乐播放
|
||||
if (current_state == kDeviceStateListening) {
|
||||
ESP_LOGI(TAG, "Device is in listening state, switching to idle state for music playback");
|
||||
// 切换状态
|
||||
app.ToggleChatState(); // 变成待机状态
|
||||
vTaskDelay(pdMS_TO_TICKS(300));
|
||||
@@ -1133,8 +1139,6 @@ bool Esp32Music::DownloadLyrics(const std::string& lyric_url) {
|
||||
add_auth_headers(http.get());
|
||||
|
||||
// 打开GET连接
|
||||
ESP_LOGI(TAG, "云端由MeowEmbeddedMusicServer喵波音律嵌入式提供");
|
||||
ESP_LOGI(TAG, "喵波音律QQ交流群:865754861");
|
||||
if (!http->Open("GET", current_url)) {
|
||||
ESP_LOGE(TAG, "Failed to open HTTP connection for lyrics");
|
||||
// 移除delete http; 因为unique_ptr会自动管理内存
|
||||
@@ -1426,4 +1430,99 @@ void Esp32Music::SetDisplayMode(DisplayMode mode) {
|
||||
ESP_LOGI(TAG, "Display mode changed from %s to %s",
|
||||
(old_mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS",
|
||||
(mode == DISPLAY_MODE_SPECTRUM) ? "SPECTRUM" : "LYRICS");
|
||||
}
|
||||
|
||||
// MCP工具需要的方法实现
|
||||
bool Esp32Music::SetVolume(int volume) {
|
||||
ESP_LOGI(TAG, "SetVolume called with volume: %d", volume);
|
||||
|
||||
// 验证音量范围
|
||||
if (volume < 0 || volume > 100) {
|
||||
ESP_LOGW(TAG, "Invalid volume level: %d, must be between 0-100", volume);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 通过Board获取AudioCodec并设置音量
|
||||
auto& board = Board::GetInstance();
|
||||
auto codec = board.GetAudioCodec();
|
||||
if (codec) {
|
||||
codec->SetOutputVolume(volume);
|
||||
ESP_LOGI(TAG, "Volume set to %d%%", volume);
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "No audio codec available");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Esp32Music::PlaySong() {
|
||||
ESP_LOGI(TAG, "PlaySong called");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Esp32Music::StopSong() {
|
||||
ESP_LOGI(TAG, "StopSong called");
|
||||
return StopStreaming();
|
||||
}
|
||||
|
||||
bool Esp32Music::PauseSong() {
|
||||
ESP_LOGI(TAG, "PauseSong called");
|
||||
|
||||
// 检查是否正在播放
|
||||
if (!is_playing_) {
|
||||
ESP_LOGW(TAG, "No music is currently playing");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否已经暂停
|
||||
if (is_paused_) {
|
||||
ESP_LOGW(TAG, "Music is already paused");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 设置暂停标志
|
||||
is_paused_ = true;
|
||||
ESP_LOGI(TAG, "Music playback paused");
|
||||
|
||||
// 更新显示状态
|
||||
auto& board = Board::GetInstance();
|
||||
auto display = board.GetDisplay();
|
||||
if (display && !current_song_name_.empty()) {
|
||||
std::string formatted_song_name = "《" + current_song_name_ + "》已暂停";
|
||||
display->SetMusicInfo(formatted_song_name.c_str());
|
||||
ESP_LOGI(TAG, "Updated display: %s", formatted_song_name.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Esp32Music::ResumeSong() {
|
||||
ESP_LOGI(TAG, "ResumeSong called");
|
||||
|
||||
// 检查是否正在播放
|
||||
if (!is_playing_) {
|
||||
ESP_LOGW(TAG, "No music is currently playing");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否已经恢复
|
||||
if (!is_paused_) {
|
||||
ESP_LOGW(TAG, "Music is not paused");
|
||||
return true;
|
||||
}
|
||||
|
||||
// 清除暂停标志
|
||||
is_paused_ = false;
|
||||
ESP_LOGI(TAG, "Music playback resumed");
|
||||
|
||||
// 更新显示状态
|
||||
auto& board = Board::GetInstance();
|
||||
auto display = board.GetDisplay();
|
||||
if (display && !current_song_name_.empty()) {
|
||||
std::string formatted_song_name = "《" + current_song_name_ + "》播放中...";
|
||||
display->SetMusicInfo(formatted_song_name.c_str());
|
||||
ESP_LOGI(TAG, "Updated display: %s", formatted_song_name.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -38,7 +38,6 @@ private:
|
||||
std::string current_music_url_;
|
||||
std::string current_song_name_;
|
||||
bool song_name_displayed_;
|
||||
std::atomic<bool> stop_flag_{false}; // 停止播放标志位
|
||||
|
||||
// 歌词相关
|
||||
std::string current_lyric_url_;
|
||||
@@ -51,6 +50,7 @@ private:
|
||||
std::atomic<DisplayMode> display_mode_;
|
||||
std::atomic<bool> is_playing_;
|
||||
std::atomic<bool> is_downloading_;
|
||||
std::atomic<bool> is_paused_;
|
||||
std::thread play_thread_;
|
||||
std::thread download_thread_;
|
||||
int64_t current_play_time_ms_; // 当前播放时间(毫秒)
|
||||
@@ -102,11 +102,20 @@ public:
|
||||
virtual bool StopStreaming() override; // 停止流式播放
|
||||
virtual size_t GetBufferSize() const override { return buffer_size_; }
|
||||
virtual bool IsDownloading() const override { return is_downloading_; }
|
||||
virtual bool IsPlaying() const override { return is_playing_; }
|
||||
virtual bool IsPaused() const override { return is_paused_; }
|
||||
virtual int16_t* GetAudioData() override { return final_pcm_data_fft; }
|
||||
|
||||
// 显示模式控制方法
|
||||
void SetDisplayMode(DisplayMode mode);
|
||||
DisplayMode GetDisplayMode() const { return display_mode_.load(); }
|
||||
|
||||
// MCP工具需要的方法
|
||||
virtual bool PlaySong() override;
|
||||
virtual bool SetVolume(int volume) override;
|
||||
virtual bool StopSong() override;
|
||||
virtual bool PauseSong() override;
|
||||
virtual bool ResumeSong() override;
|
||||
};
|
||||
|
||||
#endif // ESP32_MUSIC_H
|
||||
@@ -15,7 +15,16 @@ public:
|
||||
virtual bool StopStreaming() = 0; // 停止流式播放
|
||||
virtual size_t GetBufferSize() const = 0;
|
||||
virtual bool IsDownloading() const = 0;
|
||||
virtual bool IsPlaying() const = 0;
|
||||
virtual bool IsPaused() const = 0;
|
||||
virtual int16_t* GetAudioData() = 0;
|
||||
|
||||
// MCP工具需要的方法
|
||||
virtual bool PlaySong() = 0;
|
||||
virtual bool SetVolume(int volume) = 0;
|
||||
virtual bool StopSong() = 0;
|
||||
virtual bool PauseSong() = 0;
|
||||
virtual bool ResumeSong() = 0;
|
||||
};
|
||||
|
||||
#endif // MUSIC_H
|
||||
211
main/boards/esp32s3-smart-speaker/README_MPU6050.md
Normal file
211
main/boards/esp32s3-smart-speaker/README_MPU6050.md
Normal file
@@ -0,0 +1,211 @@
|
||||
# MPU6050传感器集成说明
|
||||
|
||||
## 概述
|
||||
|
||||
本项目为ESP32-S3智能音箱开发板集成了MPU6050六轴传感器支持,提供了现代化的C++封装接口。
|
||||
|
||||
## 文件结构
|
||||
|
||||
- `mpu6050_sensor.h` - MPU6050传感器封装类的头文件
|
||||
- `mpu6050_sensor.cc` - MPU6050传感器封装类的实现文件
|
||||
- `mpu6050_test.cc` - MPU6050传感器测试程序(可选)
|
||||
- `esp32s3_smart_speaker.cc` - 已集成MPU6050支持的板子实现
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 传感器支持
|
||||
- **加速度计**: 支持±2g, ±4g, ±8g, ±16g量程
|
||||
- **陀螺仪**: 支持±250°/s, ±500°/s, ±1000°/s, ±2000°/s量程
|
||||
- **温度传感器**: 内置温度传感器
|
||||
- **姿态角计算**: 使用互补滤波算法计算俯仰角、横滚角和偏航角
|
||||
|
||||
### 2. 技术特点
|
||||
- 使用ESP-IDF的现代I2C Master API
|
||||
- 支持多量程配置
|
||||
- 内置数字低通滤波器
|
||||
- 可配置采样率
|
||||
- 互补滤波姿态解算
|
||||
- 完整的错误处理机制
|
||||
|
||||
## 硬件连接
|
||||
|
||||
根据`config.h`中的定义:
|
||||
|
||||
```c
|
||||
// IMU传感器 (I2C接口)
|
||||
#define IMU_I2C_SDA_PIN GPIO_NUM_21
|
||||
#define IMU_I2C_SCL_PIN GPIO_NUM_20
|
||||
#define IMU_INT_PIN GPIO_NUM_19
|
||||
```
|
||||
|
||||
### 连接方式
|
||||
- **VCC**: 3.3V
|
||||
- **GND**: 地
|
||||
- **SDA**: GPIO21
|
||||
- **SCL**: GPIO20
|
||||
- **INT**: GPIO19(中断引脚,可选)
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 1. 基本使用
|
||||
|
||||
```cpp
|
||||
#include "mpu6050_sensor.h"
|
||||
|
||||
// 创建传感器实例
|
||||
auto sensor = std::make_unique<Mpu6050Sensor>(i2c_bus_handle);
|
||||
|
||||
// 初始化传感器
|
||||
if (sensor->Initialize(ACCE_FS_4G, GYRO_FS_500DPS)) {
|
||||
// 唤醒传感器
|
||||
if (sensor->WakeUp()) {
|
||||
// 验证设备ID
|
||||
uint8_t device_id;
|
||||
if (sensor->GetDeviceId(&device_id)) {
|
||||
ESP_LOGI(TAG, "MPU6050 initialized, ID: 0x%02X", device_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
### 2. 读取传感器数据
|
||||
```cpp
|
||||
mpu6050_acce_value_t acce;
|
||||
mpu6050_gyro_value_t gyro;
|
||||
mpu6050_temp_value_t temp;
|
||||
complimentary_angle_t angle;
|
||||
// 读取加速度计数据
|
||||
if (sensor->GetAccelerometer(&acce)) {
|
||||
ESP_LOGI(TAG, "Accelerometer - X:%.2f, Y:%.2f, Z:%.2f",
|
||||
acce.acce_x, acce.acce_y, acce.acce_z);
|
||||
}
|
||||
// 读取陀螺仪数据
|
||||
if (sensor->GetGyroscope(&gyro)) {
|
||||
ESP_LOGI(TAG, "Gyroscope - X:%.2f, Y:%.2f, Z:%.2f",
|
||||
gyro.gyro_x, gyro.gyro_y, gyro.gyro_z);
|
||||
}
|
||||
// 读取温度数据
|
||||
if (sensor->GetTemperature(&temp)) {
|
||||
ESP_LOGI(TAG, "Temperature: %.2f°C", temp.temp);
|
||||
}
|
||||
// 计算姿态角
|
||||
if (sensor->ComplimentaryFilter(&acce, &gyro, &angle)) {
|
||||
ESP_LOGI(TAG, "Attitude - Pitch:%.2f°, Roll:%.2f°, Yaw:%.2f°",
|
||||
angle.pitch, angle.roll, angle.yaw);
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 获取传感器状态
|
||||
|
||||
```cpp
|
||||
// 检查是否已初始化
|
||||
if (sensor->IsInitialized()) {
|
||||
// 获取状态信息
|
||||
std::string status = sensor->GetStatusJson();
|
||||
ESP_LOGI(TAG, "Sensor status: %s", status.c_str());
|
||||
}
|
||||
```
|
||||
|
||||
## 配置参数
|
||||
|
||||
### 加速度计量程
|
||||
- `ACCE_FS_2G`: ±2g (16384 LSB/g)
|
||||
- `ACCE_FS_4G`: ±4g (8192 LSB/g)
|
||||
- `ACCE_FS_8G`: ±8g (4096 LSB/g)
|
||||
- `ACCE_FS_16G`: ±16g (2048 LSB/g)
|
||||
|
||||
### 陀螺仪量程
|
||||
- `GYRO_FS_250DPS`: ±250°/s (131 LSB/°/s)
|
||||
- `GYRO_FS_500DPS`: ±500°/s (65.5 LSB/°/s)
|
||||
- `GYRO_FS_1000DPS`: ±1000°/s (32.8 LSB/°/s)
|
||||
- `GYRO_FS_2000DPS`: ±2000°/s (16.4 LSB/°/s)
|
||||
|
||||
### 互补滤波参数
|
||||
- **alpha**: 0.98 (默认值,表示更信任陀螺仪)
|
||||
- **采样率**: 125Hz
|
||||
- **数字低通滤波器**: 5Hz
|
||||
|
||||
## 集成到板子
|
||||
|
||||
MPU6050已经集成到`Esp32s3SmartSpeaker`类中:
|
||||
|
||||
1. **自动初始化**: 在板子构造函数中自动初始化MPU6050
|
||||
2. **后台任务**: 自动创建后台任务持续读取传感器数据
|
||||
3. **状态报告**: 在`GetBoardJson()`中报告传感器状态
|
||||
|
||||
## 日志输出
|
||||
|
||||
传感器会输出以下日志信息:
|
||||
|
||||
```
|
||||
I (1234) SmartSpeaker: MPU6050 sensor initialized successfully (ID: 0x68)
|
||||
I (1235) SmartSpeaker: IMU data task created successfully
|
||||
I (1236) SmartSpeaker: IMU data task started
|
||||
I (1237) SmartSpeaker: Accelerometer - X:0.12, Y:-0.05, Z:0.98
|
||||
I (1238) SmartSpeaker: Gyroscope - X:0.15, Y:-0.02, Z:0.08
|
||||
I (1239) SmartSpeaker: Temperature: 25.3°C
|
||||
I (1240) SmartSpeaker: Attitude - Pitch:2.1°, Roll:-1.5°, Yaw:0.3°
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **设备ID不匹配**
|
||||
- 检查I2C连接
|
||||
- 确认设备地址是否正确
|
||||
- 检查电源供应
|
||||
|
||||
2. **初始化失败**
|
||||
- 检查I2C总线配置
|
||||
- 确认GPIO引脚配置正确
|
||||
- 检查上拉电阻
|
||||
|
||||
3. **数据读取失败**
|
||||
- 检查I2C通信
|
||||
- 确认传感器已唤醒
|
||||
- 检查采样率配置
|
||||
|
||||
### 调试建议
|
||||
|
||||
1. 启用I2C调试日志
|
||||
2. 检查硬件连接
|
||||
3. 使用示波器检查I2C信号
|
||||
4. 验证电源电压稳定性
|
||||
|
||||
## 技术细节
|
||||
|
||||
### I2C配置
|
||||
- **时钟频率**: 100kHz
|
||||
- **地址**: 0x68 (7位地址)
|
||||
- **上拉电阻**: 内部使能
|
||||
|
||||
### 寄存器配置
|
||||
- **加速度计量程**: 寄存器0x1C
|
||||
- **陀螺仪量程**: 寄存器0x1B
|
||||
- **数字低通滤波器**: 寄存器0x1A
|
||||
- **采样率**: 寄存器0x19
|
||||
- **电源管理**: 寄存器0x6B
|
||||
|
||||
### 数据格式
|
||||
- **加速度计**: 16位有符号整数,转换为g值
|
||||
- **陀螺仪**: 16位有符号整数,转换为度/秒
|
||||
- **温度**: 16位有符号整数,转换为摄氏度
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 可扩展的功能
|
||||
1. **中断支持**: 可以配置数据就绪中断
|
||||
2. **运动检测**: 可以配置运动检测中断
|
||||
3. **自由落体检测**: 可以配置自由落体检测
|
||||
4. **FIFO支持**: 可以使用FIFO缓冲区
|
||||
5. **DMP支持**: 可以使用数字运动处理器
|
||||
|
||||
### 性能优化
|
||||
1. **降低采样率**: 减少功耗
|
||||
2. **使用中断**: 避免轮询
|
||||
3. **FIFO缓冲**: 批量读取数据
|
||||
4. **休眠模式**: 不使用时进入低功耗模式
|
||||
|
||||
## 许可证
|
||||
|
||||
本代码遵循项目的许可证要求。
|
||||
376
main/boards/esp32s3-smart-speaker/adc_manager.cc
Normal file
376
main/boards/esp32s3-smart-speaker/adc_manager.cc
Normal file
@@ -0,0 +1,376 @@
|
||||
#include "adc_manager.h"
|
||||
#include "application.h"
|
||||
#include <board.h>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <esp_adc/adc_cali.h>
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_timer.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
#define TAG "AdcManager"
|
||||
|
||||
// 检测时间定义(毫秒)
|
||||
#define PRESSURE_DETECTION_TIME_MS 2000 // 压力检测持续时间:2秒
|
||||
#define LOW_VALUE_DETECTION_TIME_MS 2000 // 低值检测持续时间:2秒
|
||||
|
||||
AdcManager &AdcManager::GetInstance() {
|
||||
static AdcManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool AdcManager::Initialize() {
|
||||
if (initialized_) {
|
||||
ESP_LOGW(TAG, "AdcManager already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing AdcManager...");
|
||||
|
||||
// 初始化ADC数组
|
||||
memset(pressure_adc_values_, 0, sizeof(pressure_adc_values_));
|
||||
|
||||
InitializeAdc();
|
||||
// InitializeDigitalOutput(); // 暂时注释掉DO初始化
|
||||
|
||||
// 先设置初始化状态,再启动任务
|
||||
initialized_ = true;
|
||||
|
||||
// 初始化后立刻读取一次,便于快速确认链路
|
||||
int init_read_raw = -1;
|
||||
esp_err_t init_read_ret = adc_oneshot_read(
|
||||
adc1_handle_, PRESSURE_SENSOR_ADC_CHANNEL, &init_read_raw);
|
||||
if (init_read_ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Initial ADC read failed: %s",
|
||||
esp_err_to_name(init_read_ret));
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Initial ADC read ok: Raw=%d", init_read_raw);
|
||||
}
|
||||
|
||||
// 启动ADC任务
|
||||
StartAdcTask();
|
||||
|
||||
ESP_LOGI(TAG, "AdcManager initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void AdcManager::InitializeAdc() {
|
||||
ESP_LOGI(TAG, "Initializing ADC for pressure sensor on GPIO4 (ADC1_CH3)...");
|
||||
|
||||
// 初始化ADC驱动
|
||||
adc_oneshot_unit_init_cfg_t init_config1 = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
};
|
||||
esp_err_t ret = adc_oneshot_new_unit(&init_config1, &adc1_handle_);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize ADC unit: %s", esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "ADC unit initialized successfully");
|
||||
|
||||
// 配置ADC通道
|
||||
adc_oneshot_chan_cfg_t chan_config = {
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ret = adc_oneshot_config_channel(adc1_handle_, PRESSURE_SENSOR_ADC_CHANNEL,
|
||||
&chan_config);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure ADC channel %d: %s",
|
||||
PRESSURE_SENSOR_ADC_CHANNEL, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "ADC channel %d configured successfully",
|
||||
PRESSURE_SENSOR_ADC_CHANNEL);
|
||||
|
||||
// 初始化ADC校准
|
||||
adc_cali_curve_fitting_config_t cali_config = {
|
||||
.unit_id = ADC_UNIT_1,
|
||||
.atten = ADC_ATTEN_DB_12,
|
||||
.bitwidth = ADC_BITWIDTH_12,
|
||||
};
|
||||
ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc1_cali_handle_);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "ADC calibration not available: %s", esp_err_to_name(ret));
|
||||
adc1_cali_handle_ = NULL;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "ADC calibration initialized successfully");
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "ADC initialized for pressure sensor monitoring on GPIO4");
|
||||
}
|
||||
|
||||
void AdcManager::ReadPressureSensorData() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
int adc_value;
|
||||
esp_err_t ret =
|
||||
adc_oneshot_read(adc1_handle_, PRESSURE_SENSOR_ADC_CHANNEL, &adc_value);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read pressure sensor ADC: %s",
|
||||
esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
// 直接使用原始ADC值,不进行电压转换
|
||||
current_pressure_value_ = adc_value;
|
||||
|
||||
// 压力检测触发音乐播放
|
||||
static bool last_pressure_state = false;
|
||||
static int64_t pressure_start_time = 0;
|
||||
static bool pressure_triggered = false;
|
||||
|
||||
if (last_pressure_state) {
|
||||
// 长时间不动检测逻辑
|
||||
CheckLongTimeNoMovement(current_pressure_value_);
|
||||
}
|
||||
|
||||
// 每隔5次打印一次详细日志(便于定位问题)
|
||||
static int adc_log_counter = 0;
|
||||
adc_log_counter++;
|
||||
if (adc_log_counter >= 10) {
|
||||
ESP_LOGI(TAG, "ADC read: Raw=%d", adc_value);
|
||||
adc_log_counter = 0;
|
||||
}
|
||||
|
||||
bool current_pressure_state = IsPressureDetected();
|
||||
|
||||
if (current_pressure_state && !last_pressure_state) {
|
||||
// 压力开始检测,记录开始时间
|
||||
pressure_start_time = esp_timer_get_time();
|
||||
pressure_triggered = false;
|
||||
ESP_LOGI(TAG, "Pressure detection started");
|
||||
} else if (current_pressure_state && last_pressure_state) {
|
||||
// 压力持续检测中,检查是否超过2秒
|
||||
int64_t current_time = esp_timer_get_time();
|
||||
int64_t duration_ms =
|
||||
(current_time - pressure_start_time) / 1000; // 转换为毫秒
|
||||
|
||||
if (duration_ms >= PRESSURE_DETECTION_TIME_MS && !pressure_triggered) {
|
||||
ESP_LOGI(TAG,
|
||||
"Pressure detected for %ld ms! Triggering music playback...",
|
||||
(long)duration_ms);
|
||||
// 触发音乐播放
|
||||
TriggerMusicPlayback();
|
||||
pressure_triggered = true; // 防止重复触发
|
||||
}
|
||||
} else if (!current_pressure_state && last_pressure_state) {
|
||||
// 压力结束检测
|
||||
ESP_LOGI(TAG, "Pressure detection ended");
|
||||
pressure_triggered = false;
|
||||
}
|
||||
|
||||
last_pressure_state = current_pressure_state;
|
||||
|
||||
// ADC值小于100时的暂停检测(也需要持续2秒)
|
||||
static int64_t low_value_start_time = 0;
|
||||
static bool low_value_triggered = false;
|
||||
|
||||
if (adc_value < 100) {
|
||||
if (low_value_start_time == 0) {
|
||||
// 第一次检测到小于100的值,记录开始时间
|
||||
low_value_start_time = esp_timer_get_time();
|
||||
low_value_triggered = false;
|
||||
ESP_LOGI(TAG, "ADC low value detection started (value: %d)", adc_value);
|
||||
} else {
|
||||
// 持续检测小于100的值,检查是否超过2秒
|
||||
int64_t current_time = esp_timer_get_time();
|
||||
int64_t duration_ms =
|
||||
(current_time - low_value_start_time) / 1000; // 转换为毫秒
|
||||
|
||||
if (duration_ms >= LOW_VALUE_DETECTION_TIME_MS && !low_value_triggered) {
|
||||
ESP_LOGI(TAG,
|
||||
"ADC low value detected for %ld ms! (value: %d) Triggering music pause...",
|
||||
(long)duration_ms, adc_value);
|
||||
TriggerMusicPauseback();
|
||||
low_value_triggered = true; // 防止重复触发
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ADC值大于等于100,重置低值检测
|
||||
if (low_value_start_time != 0) {
|
||||
ESP_LOGI(TAG, "ADC low value detection ended (value: %d)", adc_value);
|
||||
low_value_start_time = 0;
|
||||
low_value_triggered = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AdcManager::StartAdcTask() {
|
||||
if (!initialized_) {
|
||||
ESP_LOGE(TAG, "AdcManager not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
BaseType_t ret =
|
||||
xTaskCreate(AdcTask, "adc_task", 4096, this, 2, &adc_task_handle_);
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create ADC task");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "ADC task created successfully");
|
||||
}
|
||||
}
|
||||
|
||||
void AdcManager::StopAdcTask() {
|
||||
if (adc_task_handle_) {
|
||||
vTaskDelete(adc_task_handle_);
|
||||
adc_task_handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void AdcManager::AdcTask(void *pvParameters) {
|
||||
AdcManager *manager = static_cast<AdcManager *>(pvParameters);
|
||||
ESP_LOGI(TAG, "ADC task started");
|
||||
|
||||
while (true) {
|
||||
if (manager->initialized_) {
|
||||
manager->ReadPressureSensorData();
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100)); // 100ms间隔
|
||||
}
|
||||
}
|
||||
|
||||
int AdcManager::GetCurrentPressureValue() const {
|
||||
return current_pressure_value_;
|
||||
}
|
||||
|
||||
const int *AdcManager::GetPressureAdcValues() const {
|
||||
return pressure_adc_values_;
|
||||
}
|
||||
|
||||
size_t AdcManager::GetPressureSampleCount() const {
|
||||
return (pressure_data_index_ == 0) ? kPressureAdcDataCount
|
||||
: pressure_data_index_;
|
||||
}
|
||||
|
||||
bool AdcManager::IsPressureDetected() const {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
return current_pressure_value_ > 1000; // 压力阈值:100
|
||||
}
|
||||
|
||||
bool AdcManager::IsLightPressure() const {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
return current_pressure_value_ > 500; // 轻压阈值:500
|
||||
}
|
||||
|
||||
void AdcManager::CheckLongTimeNoMovement(int adc_value) {
|
||||
uint32_t current_time = esp_timer_get_time() / 1000000; // 转换为秒
|
||||
|
||||
// 计算ADC值变化
|
||||
int adc_change = abs(adc_value - last_stable_value_);
|
||||
|
||||
if (adc_change > kMovementThreshold) {
|
||||
// 有显著变化,重置不动检测
|
||||
last_stable_value_ = adc_value;
|
||||
no_movement_start_time_ = current_time;
|
||||
is_no_movement_detected_ = false;
|
||||
} else {
|
||||
// 变化很小,检查是否长时间不动
|
||||
if (no_movement_start_time_ == 0) {
|
||||
no_movement_start_time_ = current_time;
|
||||
}
|
||||
|
||||
uint32_t no_movement_duration = current_time - no_movement_start_time_;
|
||||
if (no_movement_duration >= kLongTimeThreshold &&
|
||||
!is_no_movement_detected_) {
|
||||
is_no_movement_detected_ = true;
|
||||
ESP_LOGW(TAG,
|
||||
"Long time no movement detected! Duration: %lu seconds, ADC: %d",
|
||||
no_movement_duration, adc_value);
|
||||
|
||||
// 停止音乐播放和语音交互
|
||||
TriggerMusicPauseback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AdcManager::IsLongTimeNoMovement() const {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
return is_no_movement_detected_;
|
||||
}
|
||||
|
||||
uint32_t AdcManager::GetNoMovementDuration() const {
|
||||
if (!initialized_ || no_movement_start_time_ == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t current_time = esp_timer_get_time() / 1000000;
|
||||
return current_time - no_movement_start_time_;
|
||||
}
|
||||
|
||||
void AdcManager::TriggerMusicPauseback() {
|
||||
ESP_LOGI(TAG, "Triggering music pauseback");
|
||||
auto music = Board::GetInstance().GetMusic();
|
||||
if (!music) {
|
||||
ESP_LOGI(TAG, "No music player found");
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查音乐状态,避免重复操作
|
||||
if (!music->IsPlaying() && !music->IsPaused()) {
|
||||
ESP_LOGI(TAG, "Music is not playing or paused, skipping pause operation");
|
||||
return;
|
||||
}
|
||||
|
||||
music->PauseSong();
|
||||
|
||||
// 停止语音交互
|
||||
auto &app = Application::GetInstance();
|
||||
app.GetAudioService().EnableWakeWordDetection(false);
|
||||
ESP_LOGI(TAG, "Stopped wake word detection due to long time no movement");
|
||||
}
|
||||
|
||||
void AdcManager::TriggerMusicPlayback() {
|
||||
ESP_LOGI(TAG, "Triggering music playback");
|
||||
|
||||
// 确保音频输出已启用
|
||||
auto& board = Board::GetInstance();
|
||||
auto codec = board.GetAudioCodec();
|
||||
if (!codec) {
|
||||
ESP_LOGE(TAG, "Audio codec not available");
|
||||
return;
|
||||
}
|
||||
|
||||
codec->EnableOutput(true);
|
||||
ESP_LOGI(TAG, "Audio output enabled");
|
||||
|
||||
// 通过Board接口获取音乐播放器并触发播放
|
||||
auto music = board.GetMusic();
|
||||
if (!music) {
|
||||
ESP_LOGI(TAG, "No music player found");
|
||||
return;
|
||||
}
|
||||
if (music->IsPlaying()) {
|
||||
ESP_LOGI(TAG, "Music is already playing");
|
||||
return;
|
||||
}
|
||||
if (music->IsDownloading()) {
|
||||
ESP_LOGI(TAG, "Music is already downloading");
|
||||
return;
|
||||
}
|
||||
if (music->IsPaused()) {
|
||||
ESP_LOGI(TAG, "Music is already paused");
|
||||
music->ResumeSong();
|
||||
return;
|
||||
}
|
||||
auto song_name = "稻香";
|
||||
auto artist_name = "";
|
||||
if (!music->Download(song_name, artist_name)) {
|
||||
ESP_LOGI(TAG, "获取音乐资源失败");
|
||||
return;
|
||||
}
|
||||
|
||||
auto download_result = music->GetDownloadResult();
|
||||
ESP_LOGI(TAG, "Music details result: %s", download_result.c_str());
|
||||
}
|
||||
80
main/boards/esp32s3-smart-speaker/adc_manager.h
Normal file
80
main/boards/esp32s3-smart-speaker/adc_manager.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#ifndef ADC_MANAGER_H
|
||||
#define ADC_MANAGER_H
|
||||
|
||||
#include "config.h"
|
||||
#include <esp_adc/adc_oneshot.h>
|
||||
#include <esp_adc/adc_cali.h>
|
||||
#include <esp_adc/adc_cali_scheme.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
|
||||
class AdcManager {
|
||||
public:
|
||||
static AdcManager& GetInstance();
|
||||
|
||||
// 初始化ADC系统
|
||||
bool Initialize();
|
||||
|
||||
// 读取压感传感器数据
|
||||
void ReadPressureSensorData();
|
||||
|
||||
// 获取当前压感值
|
||||
int GetCurrentPressureValue() const;
|
||||
|
||||
// 获取ADC原始值数组
|
||||
const int* GetPressureAdcValues() const;
|
||||
|
||||
// 获取有效样本数量
|
||||
size_t GetPressureSampleCount() const;
|
||||
|
||||
// 启动/停止ADC任务
|
||||
void StartAdcTask();
|
||||
void StopAdcTask();
|
||||
|
||||
// 检查是否已初始化
|
||||
bool IsInitialized() const { return initialized_; }
|
||||
|
||||
// 基于ADC值判断压力状态
|
||||
bool IsPressureDetected() const;
|
||||
bool IsLightPressure() const;
|
||||
|
||||
// 长时间不动检测
|
||||
bool IsLongTimeNoMovement() const;
|
||||
uint32_t GetNoMovementDuration() const; // 返回不动持续时间(秒)
|
||||
|
||||
// 压力检测触发音乐播放
|
||||
void TriggerMusicPlayback();
|
||||
void TriggerMusicPauseback();
|
||||
|
||||
private:
|
||||
AdcManager() = default;
|
||||
~AdcManager() = default;
|
||||
AdcManager(const AdcManager&) = delete;
|
||||
AdcManager& operator=(const AdcManager&) = delete;
|
||||
|
||||
void InitializeAdc();
|
||||
void CheckLongTimeNoMovement(int adc_value);
|
||||
static void AdcTask(void *pvParameters);
|
||||
|
||||
bool initialized_ = false;
|
||||
adc_oneshot_unit_handle_t adc1_handle_;
|
||||
adc_cali_handle_t adc1_cali_handle_;
|
||||
|
||||
// 压感传感器数据
|
||||
static constexpr size_t kPressureAdcDataCount = 10;
|
||||
int pressure_adc_values_[kPressureAdcDataCount];
|
||||
size_t pressure_data_index_ = 0;
|
||||
int current_pressure_value_ = 0;
|
||||
|
||||
// 长时间不动检测相关变量
|
||||
int last_stable_value_ = 0;
|
||||
uint32_t no_movement_start_time_ = 0;
|
||||
bool is_no_movement_detected_ = false;
|
||||
static constexpr int kMovementThreshold = 50; // ADC变化阈值
|
||||
static constexpr uint32_t kLongTimeThreshold = 30; // 长时间阈值(秒)
|
||||
|
||||
// 任务句柄
|
||||
TaskHandle_t adc_task_handle_ = nullptr;
|
||||
};
|
||||
|
||||
#endif // ADC_MANAGER_H
|
||||
131
main/boards/esp32s3-smart-speaker/button_manager.cc
Normal file
131
main/boards/esp32s3-smart-speaker/button_manager.cc
Normal file
@@ -0,0 +1,131 @@
|
||||
#include "button_manager.h"
|
||||
#include "application.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "ButtonManager"
|
||||
|
||||
ButtonManager& ButtonManager::GetInstance() {
|
||||
static ButtonManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
ButtonManager::ButtonManager()
|
||||
: boot_button_(BOOT_BUTTON_GPIO),
|
||||
volume_up_button_(VOLUME_UP_BUTTON_GPIO),
|
||||
volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
|
||||
}
|
||||
|
||||
bool ButtonManager::Initialize() {
|
||||
if (initialized_) {
|
||||
ESP_LOGW(TAG, "ButtonManager already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing ButtonManager...");
|
||||
|
||||
// 设置按钮回调
|
||||
SetupButtonCallbacks();
|
||||
|
||||
initialized_ = true;
|
||||
ESP_LOGI(TAG, "ButtonManager initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ButtonManager::SetupButtonCallbacks() {
|
||||
ESP_LOGI(TAG, "Setting up button callbacks...");
|
||||
|
||||
// BOOT按钮回调
|
||||
boot_button_.OnClick([]() {
|
||||
ESP_LOGI(TAG, "Boot button clicked");
|
||||
});
|
||||
|
||||
boot_button_.OnLongPress([]() {
|
||||
ESP_LOGI(TAG, "BOOT long pressed: play boot tone");
|
||||
|
||||
// 确保音频输出已启用
|
||||
auto& board = Board::GetInstance();
|
||||
auto codec = board.GetAudioCodec();
|
||||
if (!codec) {
|
||||
ESP_LOGE(TAG, "Audio codec not available");
|
||||
return;
|
||||
}
|
||||
|
||||
codec->EnableOutput(true);
|
||||
codec->SetOutputVolume(10);
|
||||
|
||||
auto music = Board::GetInstance().GetMusic();
|
||||
if (!music) {
|
||||
ESP_LOGE(TAG, "Music player not available");
|
||||
return;
|
||||
}
|
||||
|
||||
auto song_name = "稻香";
|
||||
auto artist_name = "";
|
||||
if (!music->Download(song_name, artist_name)) {
|
||||
ESP_LOGI(TAG, "获取音乐资源失败");
|
||||
return;
|
||||
}
|
||||
|
||||
auto download_result = music->GetDownloadResult();
|
||||
ESP_LOGI(TAG, "Music details result: %s", download_result.c_str());
|
||||
});
|
||||
|
||||
// 音量上按钮回调
|
||||
volume_up_button_.OnClick([]() {
|
||||
ESP_LOGI(TAG, "Volume up button clicked");
|
||||
// 通过AudioService间接控制音量
|
||||
auto& board = Board::GetInstance();
|
||||
auto codec = board.GetAudioCodec();
|
||||
codec->SetOutputVolume(codec->output_volume() + 10);
|
||||
ESP_LOGI(TAG, "Volume up requested");
|
||||
});
|
||||
|
||||
volume_up_button_.OnLongPress([]() {
|
||||
ESP_LOGI(TAG, "Volume up long pressed: switching to voice interaction mode");
|
||||
|
||||
// 播放进入语音交互模式的提示音
|
||||
auto& app = Application::GetInstance();
|
||||
app.PlaySound("success"); // 播放成功提示音
|
||||
|
||||
// 暂停音乐播放
|
||||
auto music = Board::GetInstance().GetMusic();
|
||||
if (music && music->IsPlaying()) {
|
||||
music->PauseSong();
|
||||
ESP_LOGI(TAG, "Music paused for voice interaction");
|
||||
}
|
||||
|
||||
// 切换到语音交互模式
|
||||
app.GetAudioService().EnableWakeWordDetection(true);
|
||||
app.GetAudioService().EnableVoiceProcessing(true);
|
||||
ESP_LOGI(TAG, "Switched to voice interaction mode - waiting for user voice input");
|
||||
});
|
||||
|
||||
// 音量下按钮回调
|
||||
volume_down_button_.OnClick([]() {
|
||||
ESP_LOGI(TAG, "Volume down button clicked");
|
||||
auto& board = Board::GetInstance();
|
||||
auto codec = board.GetAudioCodec();
|
||||
codec->SetOutputVolume(codec->output_volume() - 10);
|
||||
ESP_LOGI(TAG, "Volume down requested");
|
||||
});
|
||||
|
||||
volume_down_button_.OnLongPress([]() {
|
||||
ESP_LOGI(TAG, "Volume down long pressed: stopping audio playback and voice interaction");
|
||||
|
||||
// 播放停止提示音
|
||||
auto& app = Application::GetInstance();
|
||||
app.PlaySound("exclamation"); // 播放感叹号提示音
|
||||
|
||||
// 停止音乐播放
|
||||
auto music = Board::GetInstance().GetMusic();
|
||||
if (music && music->IsPlaying()) {
|
||||
music->PauseSong();
|
||||
ESP_LOGI(TAG, "Music playback stopped");
|
||||
}
|
||||
|
||||
// 停止语音交互
|
||||
app.GetAudioService().EnableWakeWordDetection(false);
|
||||
app.GetAudioService().EnableVoiceProcessing(false);
|
||||
ESP_LOGI(TAG, "Voice interaction stopped");
|
||||
});
|
||||
}
|
||||
31
main/boards/esp32s3-smart-speaker/button_manager.h
Normal file
31
main/boards/esp32s3-smart-speaker/button_manager.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef BUTTON_MANAGER_H
|
||||
#define BUTTON_MANAGER_H
|
||||
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
|
||||
class ButtonManager {
|
||||
public:
|
||||
static ButtonManager& GetInstance();
|
||||
|
||||
// 初始化按钮系统
|
||||
bool Initialize();
|
||||
|
||||
// 检查是否已初始化
|
||||
bool IsInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
ButtonManager();
|
||||
~ButtonManager() = default;
|
||||
ButtonManager(const ButtonManager&) = delete;
|
||||
ButtonManager& operator=(const ButtonManager&) = delete;
|
||||
|
||||
void SetupButtonCallbacks();
|
||||
|
||||
bool initialized_ = false;
|
||||
Button boot_button_;
|
||||
Button volume_up_button_;
|
||||
Button volume_down_button_;
|
||||
};
|
||||
|
||||
#endif // BUTTON_MANAGER_H
|
||||
77
main/boards/esp32s3-smart-speaker/config.h
Normal file
77
main/boards/esp32s3-smart-speaker/config.h
Normal file
@@ -0,0 +1,77 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
// ESP32-S3 智能音箱开发板配置
|
||||
|
||||
// 音频配置
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 16000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 16000
|
||||
|
||||
// 扬声器I2S配置
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_47 // 扬声器Word Select
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 // 扬声器Bit Clock
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 // 扬声器数据输出
|
||||
|
||||
// INMP441 I2S麦克风配置 (BCLK/WS/DIN)
|
||||
// 说明: INMP441 是 I2S 数字麦,需要标准 I2S 三线
|
||||
// 建议映射: BCLK=GPIO14, WS=GPIO38, DIN=GPIO16(可按需要调整)
|
||||
#define AUDIO_MIC_I2S_BCLK GPIO_NUM_14
|
||||
#define AUDIO_MIC_I2S_WS GPIO_NUM_38
|
||||
#define AUDIO_MIC_I2S_DIN GPIO_NUM_16
|
||||
|
||||
// 用户交互
|
||||
#define BUILTIN_LED_GPIO GPIO_NUM_48
|
||||
#define BOOT_BUTTON_GPIO GPIO_NUM_0
|
||||
#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
|
||||
#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_41
|
||||
|
||||
// IMU传感器 (I2C接口)
|
||||
#define IMU_I2C_SDA_PIN GPIO_NUM_21
|
||||
#define IMU_I2C_SCL_PIN GPIO_NUM_20
|
||||
#define IMU_INT_PIN GPIO_NUM_13
|
||||
|
||||
// 压感传感器 (ADC接口)
|
||||
#define PRESSURE_SENSOR_ADC_CHANNEL ADC_CHANNEL_3 // GPIO4 (ADC1_CHANNEL_3)
|
||||
|
||||
// 功能IO定义
|
||||
// LED控制
|
||||
#define LED_RING_GPIO GPIO_NUM_6 // LED灯环控制
|
||||
#define STATUS_LED_GPIO GPIO_NUM_10 // 状态指示灯
|
||||
|
||||
// 无显示配置
|
||||
#define DISPLAY_SDA_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_SCL_PIN GPIO_NUM_NC
|
||||
#define DISPLAY_WIDTH 0
|
||||
#define DISPLAY_HEIGHT 0
|
||||
#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
|
||||
|
||||
// 无摄像头配置
|
||||
#define CAMERA_PIN_PWDN GPIO_NUM_NC
|
||||
#define CAMERA_PIN_RESET GPIO_NUM_NC
|
||||
#define CAMERA_PIN_XCLK GPIO_NUM_NC
|
||||
#define CAMERA_PIN_SIOD GPIO_NUM_NC
|
||||
#define CAMERA_PIN_SIOC GPIO_NUM_NC
|
||||
#define CAMERA_PIN_D0 GPIO_NUM_NC
|
||||
#define CAMERA_PIN_D1 GPIO_NUM_NC
|
||||
#define CAMERA_PIN_D2 GPIO_NUM_NC
|
||||
#define CAMERA_PIN_D3 GPIO_NUM_NC
|
||||
#define CAMERA_PIN_D4 GPIO_NUM_NC
|
||||
#define CAMERA_PIN_D5 GPIO_NUM_NC
|
||||
#define CAMERA_PIN_D6 GPIO_NUM_NC
|
||||
#define CAMERA_PIN_D7 GPIO_NUM_NC
|
||||
#define CAMERA_PIN_VSYNC GPIO_NUM_NC
|
||||
#define CAMERA_PIN_HREF GPIO_NUM_NC
|
||||
#define CAMERA_PIN_PCLK GPIO_NUM_NC
|
||||
|
||||
// 开发板版本
|
||||
#define SMART_SPEAKER_VERSION "1.0.0"
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
15
main/boards/esp32s3-smart-speaker/config.json
Normal file
15
main/boards/esp32s3-smart-speaker/config.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "esp32s3-smart-speaker",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
|
||||
"CONFIG_LWIP_IPV6=n",
|
||||
"CONFIG_USE_ESP_WAKE_WORD=y",
|
||||
"CONFIG_USE_AUDIO_DEBUGGER=y",
|
||||
"CONFIG_AUDIO_DEBUG_UDP_SERVER=\"192.168.122.143:8000\""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
151
main/boards/esp32s3-smart-speaker/esp32s3_smart_speaker.cc
Normal file
151
main/boards/esp32s3-smart-speaker/esp32s3_smart_speaker.cc
Normal file
@@ -0,0 +1,151 @@
|
||||
#include "assets.h"
|
||||
#include "adc_manager.h"
|
||||
#include "button_manager.h"
|
||||
#include "codecs/no_audio_codec.h"
|
||||
#include "config.h"
|
||||
#include "esplog_display.h"
|
||||
#include "esp32_music.h"
|
||||
#include "gpio_manager.h"
|
||||
#include "imu_manager.h"
|
||||
#include "led/single_led.h"
|
||||
#include "tools_manager.h"
|
||||
#include "wifi_board.h"
|
||||
#include "wifi_manager.h"
|
||||
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_log.h>
|
||||
#include <string>
|
||||
|
||||
#define TAG "SmartSpeaker"
|
||||
|
||||
class Esp32s3SmartSpeaker : public WifiBoard {
|
||||
private:
|
||||
// I2C总线句柄
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
|
||||
void InitializeManagers() {
|
||||
ESP_LOGI(TAG, "Initializing managers...");
|
||||
|
||||
// 初始化各个管理器(Initialize内部会自动启动任务)
|
||||
AdcManager::GetInstance().Initialize();
|
||||
ImuManager::GetInstance().Initialize();
|
||||
ButtonManager::GetInstance().Initialize();
|
||||
GpioManager::GetInstance().Initialize();
|
||||
ToolsManager::GetInstance().Initialize();
|
||||
WifiManager::GetInstance().Initialize();
|
||||
|
||||
ESP_LOGI(TAG, "All managers initialized successfully");
|
||||
}
|
||||
|
||||
void InitializeCodecI2c() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
public:
|
||||
Esp32s3SmartSpeaker() {
|
||||
ESP_LOGI(TAG, "Initializing ESP32-S3 Smart Speaker");
|
||||
|
||||
// 初始化音乐播放器
|
||||
music_ = new Esp32Music();
|
||||
ESP_LOGI(TAG, "Music player initialized");
|
||||
|
||||
// 初始化I2C总线
|
||||
InitializeCodecI2c();
|
||||
|
||||
// 初始化各个管理器
|
||||
InitializeManagers();
|
||||
|
||||
ESP_LOGI(TAG, "ESP32-S3 Smart Speaker initialized successfully");
|
||||
}
|
||||
|
||||
virtual ~Esp32s3SmartSpeaker() = default;
|
||||
|
||||
virtual std::string GetBoardType() override {
|
||||
return std::string("esp32s3-smart-speaker");
|
||||
}
|
||||
|
||||
virtual AudioCodec *GetAudioCodec() override {
|
||||
static NoAudioCodecSimplex audio_codec(
|
||||
AUDIO_INPUT_SAMPLE_RATE,
|
||||
AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
// 扬声器(标准 I2S 输出)
|
||||
AUDIO_I2S_GPIO_BCLK,
|
||||
AUDIO_I2S_GPIO_WS,
|
||||
AUDIO_I2S_GPIO_DOUT,
|
||||
// 麦克风(标准 I2S 输入,单声道)
|
||||
AUDIO_MIC_I2S_BCLK,
|
||||
AUDIO_MIC_I2S_WS,
|
||||
AUDIO_MIC_I2S_DIN);
|
||||
return &audio_codec;
|
||||
}
|
||||
|
||||
virtual Display *GetDisplay() override {
|
||||
static EspLogDisplay display;
|
||||
return &display;
|
||||
}
|
||||
|
||||
virtual Led *GetLed() override {
|
||||
static SingleLed led(BUILTIN_LED_GPIO);
|
||||
return &led;
|
||||
}
|
||||
|
||||
virtual std::string GetBoardJson() override {
|
||||
char json_buffer[2048];
|
||||
|
||||
// 安全地获取管理器状态,避免在初始化过程中访问
|
||||
bool imu_initialized = false;
|
||||
bool adc_initialized = false;
|
||||
int pressure_value = 0;
|
||||
size_t pressure_sample_count = 0;
|
||||
bool imu_sensor_initialized = false;
|
||||
|
||||
try {
|
||||
auto& imu_manager = ImuManager::GetInstance();
|
||||
imu_initialized = imu_manager.IsInitialized();
|
||||
|
||||
auto& adc_manager = AdcManager::GetInstance();
|
||||
adc_initialized = adc_manager.IsInitialized();
|
||||
pressure_value = adc_manager.GetCurrentPressureValue();
|
||||
pressure_sample_count = adc_manager.GetPressureSampleCount();
|
||||
|
||||
auto imu_sensor = imu_manager.GetImuSensor();
|
||||
imu_sensor_initialized = imu_sensor && imu_sensor->IsInitialized();
|
||||
} catch (...) {
|
||||
ESP_LOGW(TAG, "Error accessing managers in GetBoardJson, using default values");
|
||||
}
|
||||
|
||||
snprintf(json_buffer, sizeof(json_buffer),
|
||||
"{"
|
||||
"\"board_type\":\"esp32s3-smart-speaker\","
|
||||
"\"version\":\"%s\","
|
||||
"\"features\":[\"audio\",\"imu\",\"pressure\",\"led_ring\",\"fan\",\"relay\",\"status_led\"],"
|
||||
"\"audio_codec\":\"NoAudioCodecSimplex\","
|
||||
"\"audio_method\":\"i2s_standard\","
|
||||
"\"microphone\":\"INMP441_I2S\","
|
||||
"\"speaker\":\"NoAudioCodec\","
|
||||
"\"imu_initialized\":%s,"
|
||||
"\"pressure_sensor_initialized\":%s,"
|
||||
"\"pressure_sensor\":{\"current_value\":%d,\"adc_channel\":%d,\"sample_count\":%u},"
|
||||
"\"imu_sensor\":{\"type\":\"MPU6050\",\"initialized\":%s,\"status\":\"unknown\"}"
|
||||
"}",
|
||||
SMART_SPEAKER_VERSION,
|
||||
imu_initialized ? "true" : "false",
|
||||
adc_initialized ? "true" : "false",
|
||||
pressure_value,
|
||||
PRESSURE_SENSOR_ADC_CHANNEL,
|
||||
(unsigned int)pressure_sample_count,
|
||||
imu_sensor_initialized ? "true" : "false"
|
||||
);
|
||||
|
||||
ESP_LOGI(TAG, "GetBoardJson completed successfully");
|
||||
return std::string(json_buffer);
|
||||
}
|
||||
|
||||
virtual Assets *GetAssets() override {
|
||||
static Assets assets(std::string(ASSETS_XIAOZHI_WAKENET_SMALL));
|
||||
return &assets;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(Esp32s3SmartSpeaker);
|
||||
53
main/boards/esp32s3-smart-speaker/gpio_manager.cc
Normal file
53
main/boards/esp32s3-smart-speaker/gpio_manager.cc
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "gpio_manager.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "GpioManager"
|
||||
|
||||
GpioManager& GpioManager::GetInstance() {
|
||||
static GpioManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool GpioManager::Initialize() {
|
||||
if (initialized_) {
|
||||
ESP_LOGW(TAG, "GpioManager already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing GpioManager...");
|
||||
|
||||
// 初始化GPIO输出
|
||||
gpio_config_t io_conf = {};
|
||||
|
||||
// LED灯环控制
|
||||
io_conf.intr_type = GPIO_INTR_DISABLE;
|
||||
io_conf.mode = GPIO_MODE_OUTPUT;
|
||||
io_conf.pin_bit_mask = (1ULL << LED_RING_GPIO);
|
||||
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
gpio_config(&io_conf);
|
||||
|
||||
// 状态指示灯
|
||||
io_conf.pin_bit_mask = (1ULL << STATUS_LED_GPIO);
|
||||
gpio_config(&io_conf);
|
||||
|
||||
initialized_ = true;
|
||||
ESP_LOGI(TAG, "GpioManager initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpioManager::SetLedRing(bool state) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGE(TAG, "GpioManager not initialized");
|
||||
return;
|
||||
}
|
||||
gpio_set_level(LED_RING_GPIO, state ? 1 : 0);
|
||||
}
|
||||
|
||||
void GpioManager::SetStatusLed(bool state) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGE(TAG, "GpioManager not initialized");
|
||||
return;
|
||||
}
|
||||
gpio_set_level(STATUS_LED_GPIO, state ? 1 : 0);
|
||||
}
|
||||
30
main/boards/esp32s3-smart-speaker/gpio_manager.h
Normal file
30
main/boards/esp32s3-smart-speaker/gpio_manager.h
Normal file
@@ -0,0 +1,30 @@
|
||||
#ifndef GPIO_MANAGER_H
|
||||
#define GPIO_MANAGER_H
|
||||
|
||||
#include "config.h"
|
||||
#include <driver/gpio.h>
|
||||
|
||||
class GpioManager {
|
||||
public:
|
||||
static GpioManager& GetInstance();
|
||||
|
||||
// 初始化GPIO系统
|
||||
bool Initialize();
|
||||
|
||||
// GPIO控制方法
|
||||
void SetLedRing(bool state);
|
||||
void SetStatusLed(bool state);
|
||||
|
||||
// 检查是否已初始化
|
||||
bool IsInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
GpioManager() = default;
|
||||
~GpioManager() = default;
|
||||
GpioManager(const GpioManager&) = delete;
|
||||
GpioManager& operator=(const GpioManager&) = delete;
|
||||
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
#endif // GPIO_MANAGER_H
|
||||
139
main/boards/esp32s3-smart-speaker/imu_manager.cc
Normal file
139
main/boards/esp32s3-smart-speaker/imu_manager.cc
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "imu_manager.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "ImuManager"
|
||||
|
||||
ImuManager& ImuManager::GetInstance() {
|
||||
static ImuManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool ImuManager::Initialize() {
|
||||
if (initialized_) {
|
||||
ESP_LOGW(TAG, "ImuManager already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing ImuManager...");
|
||||
|
||||
InitializeImu();
|
||||
|
||||
// 启动IMU任务
|
||||
StartImuTask();
|
||||
|
||||
initialized_ = true;
|
||||
ESP_LOGI(TAG, "ImuManager initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImuManager::InitializeImu() {
|
||||
ESP_LOGI(TAG, "Initializing MPU6050 IMU sensor...");
|
||||
|
||||
// IMU传感器I2C总线
|
||||
i2c_master_bus_config_t imu_i2c_cfg = {
|
||||
.i2c_port = I2C_NUM_1,
|
||||
.sda_io_num = IMU_I2C_SDA_PIN,
|
||||
.scl_io_num = IMU_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,
|
||||
.allow_pd = false,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&imu_i2c_cfg, &imu_i2c_bus_));
|
||||
|
||||
// 初始化MPU6050传感器
|
||||
mpu6050_sensor_ = std::make_unique<Mpu6050Sensor>(imu_i2c_bus_);
|
||||
|
||||
if (mpu6050_sensor_) {
|
||||
uint8_t device_id;
|
||||
if (mpu6050_sensor_->GetDeviceId(&device_id)) {
|
||||
ESP_LOGI(TAG, "MPU6050 device ID: 0x%02X", device_id);
|
||||
if (device_id == MPU6050_WHO_AM_I_VAL) {
|
||||
if (mpu6050_sensor_->Initialize(ACCE_FS_4G, GYRO_FS_500DPS)) {
|
||||
if (mpu6050_sensor_->WakeUp()) {
|
||||
initialized_ = true;
|
||||
ESP_LOGI(TAG, "MPU6050 sensor initialized successfully");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to wake up MPU6050");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize MPU6050");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "MPU6050 device ID mismatch: expected 0x%02X, got 0x%02X",
|
||||
MPU6050_WHO_AM_I_VAL, device_id);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to read MPU6050 device ID");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to create MPU6050 sensor instance");
|
||||
}
|
||||
|
||||
if (!initialized_) {
|
||||
ESP_LOGW(TAG, "IMU sensor initialization failed - continuing without IMU");
|
||||
}
|
||||
}
|
||||
|
||||
void ImuManager::StartImuTask() {
|
||||
if (!initialized_) {
|
||||
ESP_LOGW(TAG, "ImuManager not initialized, skipping IMU task creation");
|
||||
return;
|
||||
}
|
||||
|
||||
BaseType_t ret = xTaskCreate(ImuDataTask, "imu_data_task", 4096, this, 5, &imu_task_handle_);
|
||||
if (ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create IMU data task");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "IMU data task created successfully");
|
||||
}
|
||||
}
|
||||
|
||||
void ImuManager::StopImuTask() {
|
||||
if (imu_task_handle_) {
|
||||
vTaskDelete(imu_task_handle_);
|
||||
imu_task_handle_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ImuManager::ImuDataTask(void *pvParameters) {
|
||||
ImuManager *manager = static_cast<ImuManager *>(pvParameters);
|
||||
ESP_LOGI(TAG, "IMU data task started");
|
||||
|
||||
mpu6050_acce_value_t acce;
|
||||
mpu6050_gyro_value_t gyro;
|
||||
mpu6050_temp_value_t temp;
|
||||
complimentary_angle_t angle;
|
||||
|
||||
while (true) {
|
||||
if (manager->mpu6050_sensor_ && manager->initialized_) {
|
||||
// 读取加速度计数据
|
||||
if (manager->mpu6050_sensor_->GetAccelerometer(&acce)) {
|
||||
ESP_LOGI(TAG, "Accelerometer - X:%.2f, Y:%.2f, Z:%.2f", acce.acce_x,
|
||||
acce.acce_y, acce.acce_z);
|
||||
}
|
||||
|
||||
// 读取陀螺仪数据
|
||||
if (manager->mpu6050_sensor_->GetGyroscope(&gyro)) {
|
||||
ESP_LOGI(TAG, "Gyroscope - X:%.2f, Y:%.2f, Z:%.2f", gyro.gyro_x,
|
||||
gyro.gyro_y, gyro.gyro_z);
|
||||
}
|
||||
|
||||
// 读取温度数据
|
||||
if (manager->mpu6050_sensor_->GetTemperature(&temp)) {
|
||||
ESP_LOGI(TAG, "Temperature: %.2f°C", temp.temp);
|
||||
}
|
||||
|
||||
// 计算姿态角
|
||||
if (manager->mpu6050_sensor_->ComplimentaryFilter(&acce, &gyro, &angle)) {
|
||||
ESP_LOGI(TAG, "Attitude - Pitch:%.2f°, Roll:%.2f°, Yaw:%.2f°",
|
||||
angle.pitch, angle.roll, angle.yaw);
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
}
|
||||
}
|
||||
43
main/boards/esp32s3-smart-speaker/imu_manager.h
Normal file
43
main/boards/esp32s3-smart-speaker/imu_manager.h
Normal file
@@ -0,0 +1,43 @@
|
||||
#ifndef IMU_MANAGER_H
|
||||
#define IMU_MANAGER_H
|
||||
|
||||
#include "config.h"
|
||||
#include "mpu6050_sensor.h"
|
||||
#include <driver/i2c_master.h>
|
||||
#include <memory>
|
||||
|
||||
class ImuManager {
|
||||
public:
|
||||
static ImuManager& GetInstance();
|
||||
|
||||
// 初始化IMU系统
|
||||
bool Initialize();
|
||||
|
||||
// 启动/停止IMU任务
|
||||
void StartImuTask();
|
||||
void StopImuTask();
|
||||
|
||||
// 获取IMU传感器实例
|
||||
Mpu6050Sensor* GetImuSensor() const { return mpu6050_sensor_.get(); }
|
||||
|
||||
// 检查是否已初始化
|
||||
bool IsInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
ImuManager() = default;
|
||||
~ImuManager() = default;
|
||||
ImuManager(const ImuManager&) = delete;
|
||||
ImuManager& operator=(const ImuManager&) = delete;
|
||||
|
||||
void InitializeImu();
|
||||
static void ImuDataTask(void *pvParameters);
|
||||
|
||||
bool initialized_ = false;
|
||||
i2c_master_bus_handle_t imu_i2c_bus_;
|
||||
std::unique_ptr<Mpu6050Sensor> mpu6050_sensor_;
|
||||
|
||||
// 任务句柄
|
||||
TaskHandle_t imu_task_handle_ = nullptr;
|
||||
};
|
||||
|
||||
#endif // IMU_MANAGER_H
|
||||
280
main/boards/esp32s3-smart-speaker/mpu6050_sensor.cc
Normal file
280
main/boards/esp32s3-smart-speaker/mpu6050_sensor.cc
Normal file
@@ -0,0 +1,280 @@
|
||||
#include "mpu6050_sensor.h"
|
||||
#include <esp_timer.h>
|
||||
#include <cmath>
|
||||
|
||||
const char* Mpu6050Sensor::TAG = "MPU6050";
|
||||
|
||||
Mpu6050Sensor::Mpu6050Sensor(i2c_master_bus_handle_t i2c_bus, uint8_t device_addr)
|
||||
: i2c_bus_(i2c_bus), device_addr_(device_addr), initialized_(false),
|
||||
acce_fs_(ACCE_FS_4G), gyro_fs_(GYRO_FS_500DPS), dt_(0.0f), alpha_(0.98f) {
|
||||
|
||||
// 初始化设备句柄
|
||||
i2c_device_config_t dev_cfg = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = device_addr_,
|
||||
.scl_speed_hz = 100000,
|
||||
};
|
||||
|
||||
esp_err_t ret = i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &device_handle_);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to add MPU6050 device to I2C bus: %s", esp_err_to_name(ret));
|
||||
device_handle_ = nullptr;
|
||||
}
|
||||
|
||||
// 初始化互补滤波
|
||||
InitializeComplimentaryFilter();
|
||||
}
|
||||
|
||||
Mpu6050Sensor::~Mpu6050Sensor() {
|
||||
if (device_handle_) {
|
||||
i2c_master_bus_rm_device(device_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
bool Mpu6050Sensor::Initialize(mpu6050_acce_fs_t acce_fs, mpu6050_gyro_fs_t gyro_fs) {
|
||||
if (!device_handle_) {
|
||||
ESP_LOGE(TAG, "Device handle is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
acce_fs_ = acce_fs;
|
||||
gyro_fs_ = gyro_fs;
|
||||
|
||||
// 配置加速度计量程
|
||||
uint8_t acce_config = (acce_fs << 3) & 0x18;
|
||||
if (!WriteRegister(0x1C, acce_config)) {
|
||||
ESP_LOGE(TAG, "Failed to configure accelerometer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 配置陀螺仪量程
|
||||
uint8_t gyro_config = (gyro_fs << 3) & 0x18;
|
||||
if (!WriteRegister(0x1B, gyro_config)) {
|
||||
ESP_LOGE(TAG, "Failed to configure gyroscope");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 配置数字低通滤波器
|
||||
if (!WriteRegister(0x1A, 0x06)) { // DLPF_CFG = 6 (5Hz)
|
||||
ESP_LOGE(TAG, "Failed to configure DLPF");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 配置采样率 (1kHz / (1 + 7) = 125Hz)
|
||||
if (!WriteRegister(0x19, 0x07)) {
|
||||
ESP_LOGE(TAG, "Failed to configure sample rate");
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
ESP_LOGI(TAG, "MPU6050 initialized successfully");
|
||||
ESP_LOGI(TAG, "Accelerometer range: %d, Gyroscope range: %d", acce_fs, gyro_fs);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mpu6050Sensor::WakeUp() {
|
||||
if (!device_handle_) {
|
||||
ESP_LOGE(TAG, "Device handle is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 清除睡眠模式位
|
||||
if (!WriteRegister(0x6B, 0x00)) {
|
||||
ESP_LOGE(TAG, "Failed to wake up MPU6050");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 等待传感器稳定
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
ESP_LOGI(TAG, "MPU6050 woken up");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mpu6050Sensor::GetDeviceId(uint8_t* device_id) {
|
||||
if (!device_id) {
|
||||
ESP_LOGE(TAG, "Device ID pointer is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReadRegister(MPU6050_WHO_AM_I_REG, device_id, 1);
|
||||
}
|
||||
|
||||
bool Mpu6050Sensor::GetAccelerometer(mpu6050_acce_value_t* acce) {
|
||||
if (!acce) {
|
||||
ESP_LOGE(TAG, "Accelerometer data pointer is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t data[6];
|
||||
if (!ReadRegister(0x3B, data, 6)) {
|
||||
ESP_LOGE(TAG, "Failed to read accelerometer data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 组合16位数据
|
||||
int16_t raw_x = (data[0] << 8) | data[1];
|
||||
int16_t raw_y = (data[2] << 8) | data[3];
|
||||
int16_t raw_z = (data[4] << 8) | data[5];
|
||||
|
||||
// 根据量程转换为g值
|
||||
float scale_factor;
|
||||
switch (acce_fs_) {
|
||||
case ACCE_FS_2G: scale_factor = 16384.0f; break;
|
||||
case ACCE_FS_4G: scale_factor = 8192.0f; break;
|
||||
case ACCE_FS_8G: scale_factor = 4096.0f; break;
|
||||
case ACCE_FS_16G: scale_factor = 2048.0f; break;
|
||||
default: scale_factor = 8192.0f; break;
|
||||
}
|
||||
|
||||
acce->acce_x = raw_x / scale_factor;
|
||||
acce->acce_y = raw_y / scale_factor;
|
||||
acce->acce_z = raw_z / scale_factor;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mpu6050Sensor::GetGyroscope(mpu6050_gyro_value_t* gyro) {
|
||||
if (!gyro) {
|
||||
ESP_LOGE(TAG, "Gyroscope data pointer is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t data[6];
|
||||
if (!ReadRegister(0x43, data, 6)) {
|
||||
ESP_LOGE(TAG, "Failed to read gyroscope data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 组合16位数据
|
||||
int16_t raw_x = (data[0] << 8) | data[1];
|
||||
int16_t raw_y = (data[2] << 8) | data[3];
|
||||
int16_t raw_z = (data[4] << 8) | data[5];
|
||||
|
||||
// 根据量程转换为度/秒
|
||||
float scale_factor;
|
||||
switch (gyro_fs_) {
|
||||
case GYRO_FS_250DPS: scale_factor = 131.0f; break;
|
||||
case GYRO_FS_500DPS: scale_factor = 65.5f; break;
|
||||
case GYRO_FS_1000DPS: scale_factor = 32.8f; break;
|
||||
case GYRO_FS_2000DPS: scale_factor = 16.4f; break;
|
||||
default: scale_factor = 65.5f; break;
|
||||
}
|
||||
|
||||
gyro->gyro_x = raw_x / scale_factor;
|
||||
gyro->gyro_y = raw_y / scale_factor;
|
||||
gyro->gyro_z = raw_z / scale_factor;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mpu6050Sensor::GetTemperature(mpu6050_temp_value_t* temp) {
|
||||
if (!temp) {
|
||||
ESP_LOGE(TAG, "Temperature data pointer is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t data[2];
|
||||
if (!ReadRegister(0x41, data, 2)) {
|
||||
ESP_LOGE(TAG, "Failed to read temperature data");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 组合16位数据
|
||||
int16_t raw_temp = (data[0] << 8) | data[1];
|
||||
|
||||
// 转换为摄氏度: T = (TEMP_OUT / 340) + 36.53
|
||||
temp->temp = raw_temp / 340.0f + 36.53f;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mpu6050Sensor::ComplimentaryFilter(const mpu6050_acce_value_t* acce,
|
||||
const mpu6050_gyro_value_t* gyro,
|
||||
complimentary_angle_t* angle) {
|
||||
if (!acce || !gyro || !angle) {
|
||||
ESP_LOGE(TAG, "Input pointers are null");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t current_time = GetCurrentTimeUs();
|
||||
|
||||
// 计算时间间隔
|
||||
if (last_time_ > 0) {
|
||||
dt_ = (current_time - last_time_) / 1000000.0f; // 转换为秒
|
||||
} else {
|
||||
dt_ = 0.01f; // 默认10ms
|
||||
}
|
||||
|
||||
// 从加速度计计算俯仰角和横滚角
|
||||
float accel_pitch = atan2f(acce->acce_y, sqrtf(acce->acce_x * acce->acce_x + acce->acce_z * acce->acce_z)) * 180.0f / M_PI;
|
||||
float accel_roll = atan2f(-acce->acce_x, acce->acce_z) * 180.0f / M_PI;
|
||||
|
||||
// 互补滤波
|
||||
angle->pitch = alpha_ * (last_angle_.pitch + gyro->gyro_x * dt_) + (1.0f - alpha_) * accel_pitch;
|
||||
angle->roll = alpha_ * (last_angle_.roll + gyro->gyro_y * dt_) + (1.0f - alpha_) * accel_roll;
|
||||
angle->yaw = last_angle_.yaw + gyro->gyro_z * dt_; // 偏航角只能通过陀螺仪积分
|
||||
|
||||
// 更新上次的角度和时间
|
||||
last_angle_ = *angle;
|
||||
last_time_ = current_time;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Mpu6050Sensor::GetStatusJson() const {
|
||||
std::string json = "{";
|
||||
json += "\"initialized\":" + std::string(initialized_ ? "true" : "false") + ",";
|
||||
json += "\"device_address\":" + std::to_string(device_addr_) + ",";
|
||||
json += "\"accelerometer_range\":" + std::to_string(static_cast<int>(acce_fs_)) + ",";
|
||||
json += "\"gyroscope_range\":" + std::to_string(static_cast<int>(gyro_fs_)) + ",";
|
||||
json += "\"filter_alpha\":" + std::to_string(alpha_) + ",";
|
||||
json += "\"sample_rate\":125";
|
||||
json += "}";
|
||||
return json;
|
||||
}
|
||||
|
||||
bool Mpu6050Sensor::WriteRegister(uint8_t reg_addr, uint8_t data) {
|
||||
if (!device_handle_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t write_buf[2] = {reg_addr, data};
|
||||
esp_err_t ret = i2c_master_transmit(device_handle_, write_buf, 2, 1000);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write register 0x%02X: %s", reg_addr, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mpu6050Sensor::ReadRegister(uint8_t reg_addr, uint8_t* data, size_t len) {
|
||||
if (!device_handle_ || !data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = i2c_master_transmit_receive(device_handle_, ®_addr, 1, data, len, 1000);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read register 0x%02X: %s", reg_addr, esp_err_to_name(ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t Mpu6050Sensor::GetCurrentTimeUs() {
|
||||
return esp_timer_get_time();
|
||||
}
|
||||
|
||||
void Mpu6050Sensor::InitializeComplimentaryFilter() {
|
||||
last_angle_.pitch = 0.0f;
|
||||
last_angle_.roll = 0.0f;
|
||||
last_angle_.yaw = 0.0f;
|
||||
last_time_ = 0;
|
||||
dt_ = 0.01f;
|
||||
alpha_ = 0.98f; // 互补滤波系数,0.98表示更信任陀螺仪
|
||||
}
|
||||
184
main/boards/esp32s3-smart-speaker/mpu6050_sensor.h
Normal file
184
main/boards/esp32s3-smart-speaker/mpu6050_sensor.h
Normal file
@@ -0,0 +1,184 @@
|
||||
#ifndef MPU6050_SENSOR_H
|
||||
#define MPU6050_SENSOR_H
|
||||
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <string>
|
||||
|
||||
// MPU6050相关定义
|
||||
#define MPU6050_I2C_ADDRESS 0x68
|
||||
#define MPU6050_WHO_AM_I_REG 0x75
|
||||
#define MPU6050_WHO_AM_I_VAL 0x68
|
||||
|
||||
// 加速度计量程
|
||||
typedef enum {
|
||||
ACCE_FS_2G = 0, // ±2g
|
||||
ACCE_FS_4G = 1, // ±4g
|
||||
ACCE_FS_8G = 2, // ±8g
|
||||
ACCE_FS_16G = 3 // ±16g
|
||||
} mpu6050_acce_fs_t;
|
||||
|
||||
// 陀螺仪量程
|
||||
typedef enum {
|
||||
GYRO_FS_250DPS = 0, // ±250°/s
|
||||
GYRO_FS_500DPS = 1, // ±500°/s
|
||||
GYRO_FS_1000DPS = 2, // ±1000°/s
|
||||
GYRO_FS_2000DPS = 3 // ±2000°/s
|
||||
} mpu6050_gyro_fs_t;
|
||||
|
||||
// 传感器数据结构
|
||||
typedef struct {
|
||||
float acce_x;
|
||||
float acce_y;
|
||||
float acce_z;
|
||||
} mpu6050_acce_value_t;
|
||||
|
||||
typedef struct {
|
||||
float gyro_x;
|
||||
float gyro_y;
|
||||
float gyro_z;
|
||||
} mpu6050_gyro_value_t;
|
||||
|
||||
typedef struct {
|
||||
float temp;
|
||||
} mpu6050_temp_value_t;
|
||||
|
||||
typedef struct {
|
||||
float pitch;
|
||||
float roll;
|
||||
float yaw;
|
||||
} complimentary_angle_t;
|
||||
|
||||
/**
|
||||
* @brief MPU6050传感器封装类
|
||||
*
|
||||
* 提供现代化的C++接口来操作MPU6050六轴传感器
|
||||
* 支持加速度计、陀螺仪、温度传感器和互补滤波
|
||||
*/
|
||||
class Mpu6050Sensor {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param i2c_bus I2C总线句柄
|
||||
* @param device_addr 设备地址,默认为0x68
|
||||
*/
|
||||
explicit Mpu6050Sensor(i2c_master_bus_handle_t i2c_bus, uint8_t device_addr = MPU6050_I2C_ADDRESS);
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~Mpu6050Sensor();
|
||||
|
||||
/**
|
||||
* @brief 初始化传感器
|
||||
* @param acce_fs 加速度计量程
|
||||
* @param gyro_fs 陀螺仪量程
|
||||
* @return true表示初始化成功,false表示失败
|
||||
*/
|
||||
bool Initialize(mpu6050_acce_fs_t acce_fs = ACCE_FS_4G, mpu6050_gyro_fs_t gyro_fs = GYRO_FS_500DPS);
|
||||
|
||||
/**
|
||||
* @brief 唤醒传感器
|
||||
* @return true表示成功,false表示失败
|
||||
*/
|
||||
bool WakeUp();
|
||||
|
||||
/**
|
||||
* @brief 获取设备ID
|
||||
* @param device_id 输出设备ID
|
||||
* @return true表示成功,false表示失败
|
||||
*/
|
||||
bool GetDeviceId(uint8_t* device_id);
|
||||
|
||||
/**
|
||||
* @brief 获取加速度计数据
|
||||
* @param acce 输出加速度计数据
|
||||
* @return true表示成功,false表示失败
|
||||
*/
|
||||
bool GetAccelerometer(mpu6050_acce_value_t* acce);
|
||||
|
||||
/**
|
||||
* @brief 获取陀螺仪数据
|
||||
* @param gyro 输出陀螺仪数据
|
||||
* @return true表示成功,false表示失败
|
||||
*/
|
||||
bool GetGyroscope(mpu6050_gyro_value_t* gyro);
|
||||
|
||||
/**
|
||||
* @brief 获取温度数据
|
||||
* @param temp 输出温度数据
|
||||
* @return true表示成功,false表示失败
|
||||
*/
|
||||
bool GetTemperature(mpu6050_temp_value_t* temp);
|
||||
|
||||
/**
|
||||
* @brief 互补滤波计算姿态角
|
||||
* @param acce 加速度计数据
|
||||
* @param gyro 陀螺仪数据
|
||||
* @param angle 输出姿态角
|
||||
* @return true表示成功,false表示失败
|
||||
*/
|
||||
bool ComplimentaryFilter(const mpu6050_acce_value_t* acce,
|
||||
const mpu6050_gyro_value_t* gyro,
|
||||
complimentary_angle_t* angle);
|
||||
|
||||
/**
|
||||
* @brief 检查传感器是否已初始化
|
||||
* @return true表示已初始化,false表示未初始化
|
||||
*/
|
||||
bool IsInitialized() const { return initialized_; }
|
||||
|
||||
/**
|
||||
* @brief 获取传感器状态信息
|
||||
* @return JSON格式的状态信息
|
||||
*/
|
||||
std::string GetStatusJson() const;
|
||||
|
||||
private:
|
||||
i2c_master_bus_handle_t i2c_bus_;
|
||||
i2c_master_dev_handle_t device_handle_;
|
||||
uint8_t device_addr_;
|
||||
bool initialized_;
|
||||
mpu6050_acce_fs_t acce_fs_;
|
||||
mpu6050_gyro_fs_t gyro_fs_;
|
||||
|
||||
// 互补滤波相关
|
||||
float dt_;
|
||||
float alpha_;
|
||||
complimentary_angle_t last_angle_;
|
||||
uint64_t last_time_;
|
||||
|
||||
static const char* TAG;
|
||||
|
||||
/**
|
||||
* @brief 写入寄存器
|
||||
* @param reg_addr 寄存器地址
|
||||
* @param data 数据
|
||||
* @return true表示成功,false表示失败
|
||||
*/
|
||||
bool WriteRegister(uint8_t reg_addr, uint8_t data);
|
||||
|
||||
/**
|
||||
* @brief 读取寄存器
|
||||
* @param reg_addr 寄存器地址
|
||||
* @param data 输出数据
|
||||
* @param len 数据长度
|
||||
* @return true表示成功,false表示失败
|
||||
*/
|
||||
bool ReadRegister(uint8_t reg_addr, uint8_t* data, size_t len);
|
||||
|
||||
/**
|
||||
* @brief 获取当前时间戳(微秒)
|
||||
* @return 时间戳
|
||||
*/
|
||||
uint64_t GetCurrentTimeUs();
|
||||
|
||||
/**
|
||||
* @brief 初始化互补滤波
|
||||
*/
|
||||
void InitializeComplimentaryFilter();
|
||||
};
|
||||
|
||||
#endif // MPU6050_SENSOR_H
|
||||
199
main/boards/esp32s3-smart-speaker/tools_manager.cc
Normal file
199
main/boards/esp32s3-smart-speaker/tools_manager.cc
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "tools_manager.h"
|
||||
#include "application.h"
|
||||
#include "board.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "ToolsManager"
|
||||
|
||||
ToolsManager& ToolsManager::GetInstance() {
|
||||
static ToolsManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool ToolsManager::Initialize() {
|
||||
if (initialized_) {
|
||||
ESP_LOGW(TAG, "ToolsManager already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing ToolsManager...");
|
||||
|
||||
// 注册各种工具
|
||||
RegisterMcpTools();
|
||||
RegisterSystemTools();
|
||||
RegisterAudioTools();
|
||||
RegisterSensorTools();
|
||||
|
||||
initialized_ = true;
|
||||
ESP_LOGI(TAG, "ToolsManager initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void ToolsManager::RegisterMcpTools() {
|
||||
ESP_LOGI(TAG, "Registering MCP tools...");
|
||||
|
||||
auto& mcp_server = McpServer::GetInstance();
|
||||
|
||||
// 系统信息查询工具
|
||||
mcp_server.AddTool(
|
||||
"self.smart_speaker.get_system_info",
|
||||
"获取智能音箱系统信息,包括板卡类型、版本、功能特性等",
|
||||
PropertyList(),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
auto& board = Board::GetInstance();
|
||||
return board.GetBoardJson();
|
||||
}
|
||||
);
|
||||
|
||||
// 设备状态查询工具
|
||||
mcp_server.AddTool(
|
||||
"self.smart_speaker.get_device_state",
|
||||
"获取设备当前状态,包括启动状态、连接状态等",
|
||||
PropertyList(),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
auto& app = Application::GetInstance();
|
||||
DeviceState state = app.GetDeviceState();
|
||||
const char* state_str = "unknown";
|
||||
switch (state) {
|
||||
case kDeviceStateStarting: state_str = "starting"; break;
|
||||
case kDeviceStateWifiConfiguring: state_str = "configuring"; break;
|
||||
case kDeviceStateIdle: state_str = "idle"; break;
|
||||
case kDeviceStateConnecting: state_str = "connecting"; break;
|
||||
case kDeviceStateListening: state_str = "listening"; break;
|
||||
case kDeviceStateSpeaking: state_str = "speaking"; break;
|
||||
case kDeviceStateUpgrading: state_str = "upgrading"; break;
|
||||
case kDeviceStateActivating: state_str = "activating"; break;
|
||||
case kDeviceStateAudioTesting: state_str = "audio_testing"; break;
|
||||
case kDeviceStateFatalError: state_str = "fatal_error"; break;
|
||||
default: state_str = "unknown"; break;
|
||||
}
|
||||
return std::string("{\"state\":\"") + state_str + "\"}";
|
||||
}
|
||||
);
|
||||
|
||||
ESP_LOGI(TAG, "MCP tools registered successfully");
|
||||
}
|
||||
|
||||
void ToolsManager::RegisterSystemTools() {
|
||||
ESP_LOGI(TAG, "Registering system tools...");
|
||||
|
||||
auto& mcp_server = McpServer::GetInstance();
|
||||
|
||||
// 系统重启工具
|
||||
mcp_server.AddTool(
|
||||
"self.smart_speaker.reboot",
|
||||
"重启智能音箱系统",
|
||||
PropertyList(),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
auto& app = Application::GetInstance();
|
||||
app.Reboot();
|
||||
return "{\"message\":\"System reboot initiated\"}";
|
||||
}
|
||||
);
|
||||
|
||||
// 设备控制工具
|
||||
mcp_server.AddTool(
|
||||
"self.smart_speaker.start_listening",
|
||||
"开始语音监听",
|
||||
PropertyList(),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
auto& app = Application::GetInstance();
|
||||
app.StartListening();
|
||||
return "{\"message\":\"Started listening\"}";
|
||||
}
|
||||
);
|
||||
|
||||
mcp_server.AddTool(
|
||||
"self.smart_speaker.stop_listening",
|
||||
"停止语音监听",
|
||||
PropertyList(),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
auto& app = Application::GetInstance();
|
||||
app.StopListening();
|
||||
return "{\"message\":\"Stopped listening\"}";
|
||||
}
|
||||
);
|
||||
|
||||
ESP_LOGI(TAG, "System tools registered successfully");
|
||||
}
|
||||
|
||||
void ToolsManager::RegisterAudioTools() {
|
||||
ESP_LOGI(TAG, "Registering audio tools...");
|
||||
|
||||
auto& mcp_server = McpServer::GetInstance();
|
||||
|
||||
// 音频播放工具
|
||||
mcp_server.AddTool(
|
||||
"self.smart_speaker.play_sound",
|
||||
"播放指定音效。sound: 音效名称(activation, welcome, upgrade, wificonfig等)",
|
||||
PropertyList({Property("sound", kPropertyTypeString, "activation")}),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
auto& app = Application::GetInstance();
|
||||
std::string sound = properties["sound"].value<std::string>();
|
||||
app.PlaySound(sound);
|
||||
return "{\"message\":\"Playing sound: " + sound + "\"}";
|
||||
}
|
||||
);
|
||||
|
||||
// 语音检测状态工具
|
||||
mcp_server.AddTool(
|
||||
"self.smart_speaker.is_voice_detected",
|
||||
"检查是否检测到语音",
|
||||
PropertyList(),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
auto& app = Application::GetInstance();
|
||||
bool voice_detected = app.IsVoiceDetected();
|
||||
return "{\"voice_detected\":" + std::string(voice_detected ? "true" : "false") + "}";
|
||||
}
|
||||
);
|
||||
|
||||
ESP_LOGI(TAG, "Audio tools registered successfully");
|
||||
}
|
||||
|
||||
void ToolsManager::RegisterSensorTools() {
|
||||
ESP_LOGI(TAG, "Registering sensor tools...");
|
||||
|
||||
auto& mcp_server = McpServer::GetInstance();
|
||||
|
||||
// 压感传感器读取工具
|
||||
mcp_server.AddTool(
|
||||
"self.smart_speaker.get_pressure_sensor",
|
||||
"获取压感传感器数据,包括当前值、ADC通道、样本数量等",
|
||||
PropertyList(),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
auto& board = Board::GetInstance();
|
||||
std::string board_json = board.GetBoardJson();
|
||||
|
||||
// 从board JSON中提取压感传感器信息
|
||||
// 这里简化处理,直接返回board信息中包含的传感器数据
|
||||
return board_json;
|
||||
}
|
||||
);
|
||||
|
||||
// IMU传感器状态工具
|
||||
mcp_server.AddTool(
|
||||
"self.smart_speaker.get_imu_status",
|
||||
"获取IMU传感器状态信息",
|
||||
PropertyList(),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
auto& board = Board::GetInstance();
|
||||
std::string board_json = board.GetBoardJson();
|
||||
|
||||
// 从board JSON中提取IMU信息
|
||||
return board_json;
|
||||
}
|
||||
);
|
||||
|
||||
// 传感器数据重置工具
|
||||
mcp_server.AddTool(
|
||||
"self.smart_speaker.reset_sensor_data",
|
||||
"重置传感器数据缓冲区",
|
||||
PropertyList(),
|
||||
[](const PropertyList& properties) -> ReturnValue {
|
||||
// TODO: 实现传感器数据重置
|
||||
return "{\"message\":\"Sensor data reset requested\"}";
|
||||
}
|
||||
);
|
||||
|
||||
ESP_LOGI(TAG, "Sensor tools registered successfully");
|
||||
}
|
||||
32
main/boards/esp32s3-smart-speaker/tools_manager.h
Normal file
32
main/boards/esp32s3-smart-speaker/tools_manager.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#ifndef TOOLS_MANAGER_H
|
||||
#define TOOLS_MANAGER_H
|
||||
|
||||
#include <string>
|
||||
#include "mcp_server.h"
|
||||
|
||||
class ToolsManager {
|
||||
public:
|
||||
static ToolsManager& GetInstance();
|
||||
|
||||
// 初始化工具系统
|
||||
bool Initialize();
|
||||
|
||||
// 工具注册方法
|
||||
void RegisterMcpTools();
|
||||
void RegisterSystemTools();
|
||||
void RegisterAudioTools();
|
||||
void RegisterSensorTools();
|
||||
|
||||
// 检查是否已初始化
|
||||
bool IsInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
ToolsManager() = default;
|
||||
~ToolsManager() = default;
|
||||
ToolsManager(const ToolsManager&) = delete;
|
||||
ToolsManager& operator=(const ToolsManager&) = delete;
|
||||
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
#endif // TOOLS_MANAGER_H
|
||||
55
main/boards/esp32s3-smart-speaker/wifi_manager.cc
Normal file
55
main/boards/esp32s3-smart-speaker/wifi_manager.cc
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "wifi_manager.h"
|
||||
#include "settings.h"
|
||||
#include "wifi_station.h"
|
||||
#include <esp_log.h>
|
||||
|
||||
#define TAG "WifiManager"
|
||||
|
||||
WifiManager& WifiManager::GetInstance() {
|
||||
static WifiManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool WifiManager::Initialize() {
|
||||
if (initialized_) {
|
||||
ESP_LOGW(TAG, "WifiManager already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing WifiManager...");
|
||||
|
||||
// 配置WiFi设置
|
||||
ConfigureWifiSettings();
|
||||
|
||||
// 设置默认凭据
|
||||
SetDefaultCredentials();
|
||||
|
||||
initialized_ = true;
|
||||
ESP_LOGI(TAG, "WifiManager initialized successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void WifiManager::ConfigureWifiSettings() {
|
||||
ESP_LOGI(TAG, "Configuring WiFi settings...");
|
||||
|
||||
// 配置WiFi参数到NVS
|
||||
Settings wifi_settings("wifi", true);
|
||||
|
||||
// 设置不记住BSSID (不区分MAC地址)
|
||||
wifi_settings.SetInt("remember_bssid", 0);
|
||||
|
||||
// 设置最大发射功率
|
||||
wifi_settings.SetInt("max_tx_power", 0);
|
||||
|
||||
ESP_LOGI(TAG, "WiFi settings configured");
|
||||
}
|
||||
|
||||
void WifiManager::SetDefaultCredentials() {
|
||||
ESP_LOGI(TAG, "Setting default WiFi credentials...");
|
||||
|
||||
// 添加默认WiFi配置
|
||||
auto &wifi_station = WifiStation::GetInstance();
|
||||
wifi_station.AddAuth("xoxo", "12340000");
|
||||
|
||||
ESP_LOGI(TAG, "Default WiFi credentials added: SSID=xoxo, Password=12340000");
|
||||
}
|
||||
30
main/boards/esp32s3-smart-speaker/wifi_manager.h
Normal file
30
main/boards/esp32s3-smart-speaker/wifi_manager.h
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
#ifndef WIFI_MANAGER_H
|
||||
#define WIFI_MANAGER_H
|
||||
|
||||
#include <string>
|
||||
|
||||
class WifiManager {
|
||||
public:
|
||||
static WifiManager& GetInstance();
|
||||
|
||||
// 初始化WiFi系统
|
||||
bool Initialize();
|
||||
|
||||
// WiFi配置方法
|
||||
void SetDefaultCredentials();
|
||||
void ConfigureWifiSettings();
|
||||
|
||||
// 检查是否已初始化
|
||||
bool IsInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
WifiManager() = default;
|
||||
~WifiManager() = default;
|
||||
WifiManager(const WifiManager&) = delete;
|
||||
WifiManager& operator=(const WifiManager&) = delete;
|
||||
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
#endif // WIFI_MANAGER_H
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "sscma_camera.h"
|
||||
#include "mcp_server.h"
|
||||
#include "lvgl_display.h"
|
||||
#include "lvgl_image.h"
|
||||
#include "board.h"
|
||||
#include "system_info.h"
|
||||
#include "config.h"
|
||||
@@ -245,7 +246,8 @@ bool SscmaCamera::Capture() {
|
||||
// 显示预览图片
|
||||
auto display = dynamic_cast<LvglDisplay*>(Board::GetInstance().GetDisplay());
|
||||
if (display != nullptr) {
|
||||
display->SetPreviewImage(&preview_image_);
|
||||
auto image = std::make_unique<LvglSourceImage>(&preview_image_);
|
||||
display->SetPreviewImage(std::move(image));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 由于原来的麦克风型号停产,2025年7月之后的太极派(JC3636W518)更换了麦克风,并且更换了屏幕玻璃,所以在产品标签上批次号大于2528的用户请选择I2S Type PDM,
|
||||
|
||||
# 新增双声道配置
|
||||
# 编译配置命令
|
||||
|
||||
**配置编译目标为 ESP32S3:**
|
||||
@@ -19,7 +19,12 @@ idf.py menuconfig
|
||||
```
|
||||
Xiaozhi Assistant -> Board Type -> 太极小派esp32s3
|
||||
|
||||
Xiaozhi Assistant -> taiji-pi-S3 I2S Type -> I2S Type PDM
|
||||
Xiaozhi Assistant -> TAIJIPAI_S3_CONFIG -> taiji-pi-S3 I2S Type -> I2S Type PDM
|
||||
```
|
||||
|
||||
**如果需要选择双声道:**
|
||||
```
|
||||
Xiaozhi Assistant -> TAIJIPAI_S3_CONFIG -> Enabel use 2 slot
|
||||
```
|
||||
|
||||
**修改PSRAM配置:**
|
||||
|
||||
@@ -618,9 +618,17 @@ public:
|
||||
AUDIO_I2S_GPIO_BCLK,
|
||||
AUDIO_I2S_GPIO_WS,
|
||||
AUDIO_I2S_GPIO_DOUT,
|
||||
#ifdef CONFIG_I2S_USE_2SLOT
|
||||
I2S_STD_SLOT_BOTH,
|
||||
#endif
|
||||
AUDIO_MIC_SCK_PIN,
|
||||
AUDIO_MIC_WS_PIN,
|
||||
#ifdef CONFIG_I2S_USE_2SLOT
|
||||
AUDIO_MIC_SD_PIN,
|
||||
I2S_STD_SLOT_LEFT
|
||||
#else
|
||||
AUDIO_MIC_SD_PIN
|
||||
#endif
|
||||
);
|
||||
#else
|
||||
static NoAudioCodecSimplexPdm audio_codec(
|
||||
@@ -629,6 +637,9 @@ public:
|
||||
AUDIO_I2S_GPIO_BCLK,
|
||||
AUDIO_I2S_GPIO_WS,
|
||||
AUDIO_I2S_GPIO_DOUT,
|
||||
#ifdef CONFIG_I2S_USE_2SLOT
|
||||
I2S_STD_SLOT_BOTH,
|
||||
#endif
|
||||
AUDIO_MIC_WS_PIN,
|
||||
AUDIO_MIC_SD_PIN
|
||||
);
|
||||
|
||||
88
main/boards/yunliao-s3/README.md
Normal file
88
main/boards/yunliao-s3/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# 小智云聊S3
|
||||
|
||||
## 简介
|
||||
小智云聊S3是小智AI的魔改项目,是首个2.8寸护眼大屏+大字体+2000mah大电池的量产成品,做了大量创新和优化。
|
||||
|
||||
## 合并版
|
||||
合并版代码在小智AI主项目中维护,跟随主项目的一起版本更新,便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G自由切换等功能。
|
||||
|
||||
>### 按键操作
|
||||
>- **开机**: 关机状态,长按1秒后释放按键,自动开机
|
||||
>- **关机**: 开机状态,长按1秒后释放按键,标题栏会显示'请稍候',再等2秒自动关机
|
||||
>- **唤醒/打断**: 正常通话环境下,单击按键
|
||||
>- **切换4G/Wifi**: 启动过程或者配网界面,1秒钟内双击按键(需安装4G模块)
|
||||
>- **重新配网**: 开机状态,1秒钟内三击按键,会自动重启并进入配网界面
|
||||
|
||||
## 魔改版
|
||||
魔改版由于底层改动太大,代码单独维护,定期合并主项目代码。
|
||||
|
||||
>### 为什么是魔改
|
||||
>- 首个实现微信二维码配网。
|
||||
>- 首个支持单手机配网。
|
||||
>- 首个支持扫二维码访问控制台。
|
||||
>- 首发支持繁体、日文、英文版界面
|
||||
>- 首个全语音操控模式
|
||||
>- 独家提供一键刷机脚本等多种刷机方式
|
||||
|
||||
## 版本区别
|
||||
>| 特性 | 合并版 | 魔改版 |
|
||||
>| --- | --- | --- |
|
||||
>| 语音打断 | ✓ | ✓ |
|
||||
>| 4G功能 | ✓ | ✓ |
|
||||
>| 自动更新固件 | ✓ | X |
|
||||
>| 第三方固件支持 | ✓ | X |
|
||||
>| 天气待机界面 | X | ✓ |
|
||||
>| 闹钟提醒 | X | ✓ |
|
||||
>| 网络音乐播放 | X | ✓ |
|
||||
>| 微信扫码配网 | X | ✓ |
|
||||
>| 单手机配网 | X | ✓ |
|
||||
>| 扫码访问控制台 | X | ✓ |
|
||||
>| 繁日英文界面 | X | ✓ |
|
||||
>| 多语言支持 | X | ✓ |
|
||||
>| 外接蓝牙音箱 | X | ✓ |
|
||||
|
||||
|
||||
# 编译配置命令
|
||||
|
||||
**克隆工程**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/78/xiaozhi-esp32.git
|
||||
```
|
||||
|
||||
**进入工程**
|
||||
|
||||
```bash
|
||||
cd xiaozhi-esp32
|
||||
```
|
||||
|
||||
**配置编译目标为 ESP32S3**
|
||||
|
||||
```bash
|
||||
idf.py set-target esp32s3
|
||||
```
|
||||
|
||||
**打开 menuconfig**
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
**选择板子**
|
||||
|
||||
```bash
|
||||
- `Xiaozhi Assistant` → `Board Type` → 选择 `小智云聊-S3` → 选择 `Enable Device-Side AEC`
|
||||
```
|
||||
|
||||
**编译**
|
||||
|
||||
```ba
|
||||
idf.py build
|
||||
```
|
||||
|
||||
**下载并打开串口终端**
|
||||
|
||||
```bash
|
||||
idf.py build flash monitor
|
||||
```
|
||||
|
||||
59
main/boards/yunliao-s3/config.h
Normal file
59
main/boards/yunliao-s3/config.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#ifndef _BOARD_CONFIG_H_
|
||||
#define _BOARD_CONFIG_H_
|
||||
|
||||
#include <driver/gpio.h>
|
||||
|
||||
#define AUDIO_INPUT_REFERENCE true
|
||||
|
||||
#define AUDIO_INPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
|
||||
#define AUDIO_DEFAULT_OUTPUT_VOLUME 70
|
||||
|
||||
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14
|
||||
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
|
||||
#define AUDIO_I2S_GPIO_WS GPIO_NUM_11
|
||||
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12
|
||||
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
|
||||
|
||||
#define AUDIO_CODEC_PA_PIN GPIO_NUM_17
|
||||
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21
|
||||
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_18
|
||||
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
|
||||
|
||||
#define BOOT_BUTTON_PIN GPIO_NUM_2
|
||||
#define BOOT_5V_PIN GPIO_NUM_3 //5V升压输出
|
||||
#define BOOT_4G_PIN GPIO_NUM_5 //4G模块使能
|
||||
#define MON_BATT_PIN GPIO_NUM_43 //检测PMU电池指示
|
||||
#define MON_BATT_CNT 70 //检测PMU电池秒数
|
||||
#define MON_USB_PIN GPIO_NUM_47 //检测USB插入
|
||||
|
||||
|
||||
#define ML307_RX_PIN GPIO_NUM_16
|
||||
#define ML307_TX_PIN GPIO_NUM_15
|
||||
|
||||
#define DISPLAY_SPI_LCD_HOST SPI2_HOST
|
||||
#define DISPLAY_SPI_CLOCK_HZ (40 * 1000 * 1000)
|
||||
#define DISPLAY_SPI_PIN_SCLK 42
|
||||
#define DISPLAY_SPI_PIN_MOSI 40
|
||||
#define DISPLAY_SPI_PIN_MISO -1
|
||||
#define DISPLAY_SPI_PIN_LCD_DC 41
|
||||
#define DISPLAY_SPI_PIN_LCD_RST 45
|
||||
#define DISPLAY_SPI_PIN_LCD_CS -1
|
||||
#define DISPLAY_PIN_TOUCH_CS -1
|
||||
|
||||
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46
|
||||
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
|
||||
|
||||
#define DISPLAY_WIDTH 320
|
||||
#define DISPLAY_HEIGHT 240
|
||||
#define DISPLAY_SWAP_XY true
|
||||
#define DISPLAY_MIRROR_X false
|
||||
#define DISPLAY_MIRROR_Y true
|
||||
#define DISPLAY_INVERT_COLOR false
|
||||
#define DISPLAY_RGB_ORDER_COLOR LCD_RGB_ELEMENT_ORDER_RGB
|
||||
|
||||
#define DISPLAY_OFFSET_X 0
|
||||
#define DISPLAY_OFFSET_Y 0
|
||||
#define KEY_EXPIRE_MS 800
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
11
main/boards/yunliao-s3/config.json
Normal file
11
main/boards/yunliao-s3/config.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"target": "esp32s3",
|
||||
"builds": [
|
||||
{
|
||||
"name": "yunliao-s3",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_USE_DEVICE_AEC=y"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
203
main/boards/yunliao-s3/power_manager.cc
Normal file
203
main/boards/yunliao-s3/power_manager.cc
Normal file
@@ -0,0 +1,203 @@
|
||||
#include "power_manager.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "esp_log.h"
|
||||
#include "config.h"
|
||||
#include <esp_sleep.h>
|
||||
#include "esp_log.h"
|
||||
#include "settings.h"
|
||||
|
||||
#define TAG "PowerManager"
|
||||
|
||||
static QueueHandle_t gpio_evt_queue = NULL;
|
||||
uint16_t battCnt;//闪灯次数
|
||||
int battLife = -1; //电量
|
||||
|
||||
// 中断服务程序
|
||||
static void IRAM_ATTR batt_mon_isr_handler(void* arg) {
|
||||
uint32_t gpio_num = (uint32_t) arg;
|
||||
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
|
||||
}
|
||||
|
||||
// 添加任务处理函数
|
||||
static void batt_mon_task(void* arg) {
|
||||
uint32_t io_num;
|
||||
while(1) {
|
||||
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
|
||||
battCnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void calBattLife() {
|
||||
// 计算电量
|
||||
battLife = battCnt;
|
||||
|
||||
if (battLife > 100){
|
||||
battLife = 100;
|
||||
}
|
||||
// ESP_LOGI(TAG, "Battery life:%d", (int)battLife);
|
||||
// 重置计数器
|
||||
battCnt = 0;
|
||||
}
|
||||
|
||||
PowerManager::PowerManager(){
|
||||
}
|
||||
|
||||
void PowerManager::Initialize(){
|
||||
// 初始化5V控制引脚
|
||||
gpio_config_t io_conf_5v = {
|
||||
.pin_bit_mask = 1<<BOOT_5V_PIN,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf_5v));
|
||||
|
||||
// 初始化4G控制引脚
|
||||
gpio_config_t io_conf_4g = {
|
||||
.pin_bit_mask = 1<<BOOT_4G_PIN,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_ENABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf_4g));
|
||||
|
||||
// 电池电量监测引脚配置
|
||||
gpio_config_t io_conf_batt_mon = {
|
||||
.pin_bit_mask = 1ull<<MON_BATT_PIN,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_POSEDGE,
|
||||
};
|
||||
ESP_ERROR_CHECK(gpio_config(&io_conf_batt_mon));
|
||||
// 创建电量GPIO事件队列
|
||||
gpio_evt_queue = xQueueCreate(2, sizeof(uint32_t));
|
||||
// 安装电量GPIO ISR服务
|
||||
ESP_ERROR_CHECK(gpio_install_isr_service(0));
|
||||
// 添加中断处理
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_add(MON_BATT_PIN, batt_mon_isr_handler, (void*)MON_BATT_PIN));
|
||||
// 创建监控任务
|
||||
xTaskCreate(&batt_mon_task, "batt_mon_task", 1024, NULL, 10, NULL);
|
||||
|
||||
// 初始化监测引脚
|
||||
gpio_config_t mon_conf = {};
|
||||
mon_conf.pin_bit_mask = 1ULL << MON_USB_PIN;
|
||||
mon_conf.mode = GPIO_MODE_INPUT;
|
||||
mon_conf.pull_up_en = GPIO_PULLUP_DISABLE;
|
||||
mon_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
|
||||
gpio_config(&mon_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));
|
||||
}
|
||||
|
||||
void PowerManager::CheckBatteryStatus(){
|
||||
call_count_++;
|
||||
if(call_count_ >= MON_BATT_CNT) {
|
||||
calBattLife();
|
||||
call_count_ = 0;
|
||||
}
|
||||
|
||||
bool new_charging_status = IsCharging();
|
||||
if (new_charging_status != is_charging_) {
|
||||
is_charging_ = new_charging_status;
|
||||
if (charging_callback_) {
|
||||
charging_callback_(is_charging_);
|
||||
}
|
||||
}
|
||||
|
||||
bool new_discharging_status = IsDischarging();
|
||||
if (new_discharging_status != is_discharging_) {
|
||||
is_discharging_ = new_discharging_status;
|
||||
if (discharging_callback_) {
|
||||
discharging_callback_(is_discharging_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PowerManager::IsCharging() {
|
||||
return gpio_get_level(MON_USB_PIN) == 1 && !IsChargingDone();
|
||||
}
|
||||
|
||||
bool PowerManager::IsDischarging() {
|
||||
return gpio_get_level(MON_USB_PIN) == 0;
|
||||
}
|
||||
|
||||
bool PowerManager::IsChargingDone() {
|
||||
return battLife >= 95;
|
||||
}
|
||||
|
||||
int PowerManager::GetBatteryLevel() {
|
||||
return battLife;
|
||||
}
|
||||
|
||||
void PowerManager::OnChargingStatusChanged(std::function<void(bool)> callback) {
|
||||
charging_callback_ = callback;
|
||||
}
|
||||
|
||||
void PowerManager::OnChargingStatusDisChanged(std::function<void(bool)> callback) {
|
||||
discharging_callback_ = callback;
|
||||
}
|
||||
|
||||
void PowerManager::CheckStartup() {
|
||||
Settings settings1("board", true);
|
||||
if(settings1.GetInt("sleep_flag", 0) > 0){
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
if( gpio_get_level(BOOT_BUTTON_PIN) == 1) {
|
||||
Sleep(); //进入休眠模式
|
||||
}else{
|
||||
settings1.SetInt("sleep_flag", 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PowerManager::Start5V() {
|
||||
gpio_set_level(BOOT_5V_PIN, 1);
|
||||
}
|
||||
|
||||
void PowerManager::Shutdown5V() {
|
||||
gpio_set_level(BOOT_5V_PIN, 0);
|
||||
}
|
||||
|
||||
void PowerManager::Start4G() {
|
||||
gpio_set_level(BOOT_4G_PIN, 1);
|
||||
}
|
||||
|
||||
void PowerManager::Shutdown4G() {
|
||||
gpio_set_level(BOOT_4G_PIN, 0);
|
||||
gpio_set_level(ML307_RX_PIN,1);
|
||||
gpio_set_level(ML307_TX_PIN,1);
|
||||
}
|
||||
|
||||
void PowerManager::Sleep() {
|
||||
ESP_LOGI(TAG, "Entering deep sleep");
|
||||
Settings settings("board", true);
|
||||
settings.SetInt("sleep_flag", 1);
|
||||
Shutdown4G();
|
||||
Shutdown5V();
|
||||
|
||||
if(gpio_evt_queue) {
|
||||
vQueueDelete(gpio_evt_queue);
|
||||
gpio_evt_queue = NULL;
|
||||
}
|
||||
ESP_ERROR_CHECK(gpio_isr_handler_remove(BOOT_BUTTON_PIN));
|
||||
ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(BOOT_BUTTON_PIN, 0));
|
||||
ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(BOOT_BUTTON_PIN));
|
||||
ESP_ERROR_CHECK(rtc_gpio_pullup_en(BOOT_BUTTON_PIN));
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
37
main/boards/yunliao-s3/power_manager.h
Normal file
37
main/boards/yunliao-s3/power_manager.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#ifndef __POWERMANAGER_H__
|
||||
#define __POWERMANAGER_H__
|
||||
|
||||
#include <functional>
|
||||
#include "driver/gpio.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/timers.h"
|
||||
|
||||
class PowerManager{
|
||||
public:
|
||||
PowerManager();
|
||||
void Initialize();
|
||||
bool IsCharging();
|
||||
bool IsDischarging();
|
||||
bool IsChargingDone();
|
||||
int GetBatteryLevel();
|
||||
void CheckStartup();
|
||||
void Start5V();
|
||||
void Shutdown5V();
|
||||
void Start4G();
|
||||
void Shutdown4G();
|
||||
void Sleep();
|
||||
void CheckBatteryStatus();
|
||||
void OnChargingStatusChanged(std::function<void(bool)> callback);
|
||||
void OnChargingStatusDisChanged(std::function<void(bool)> callback);
|
||||
private:
|
||||
esp_timer_handle_t timer_handle_;
|
||||
std::function<void(bool)> charging_callback_;
|
||||
std::function<void(bool)> discharging_callback_;
|
||||
int is_charging_ = -1;
|
||||
int is_discharging_ = -1;
|
||||
int call_count_ = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
207
main/boards/yunliao-s3/yunliao_s3.cc
Normal file
207
main/boards/yunliao-s3/yunliao_s3.cc
Normal file
@@ -0,0 +1,207 @@
|
||||
#include "lvgl_theme.h"
|
||||
#include "dual_network_board.h"
|
||||
#include "codecs/es8388_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
#include "config.h"
|
||||
#include "power_save_timer.h"
|
||||
#include "power_manager.h"
|
||||
#include "assets/lang_config.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
|
||||
#define TAG "YunliaoS3"
|
||||
|
||||
class YunliaoS3 : public DualNetworkBoard {
|
||||
private:
|
||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||
Button boot_button_;
|
||||
SpiLcdDisplay* display_;
|
||||
PowerSaveTimer* power_save_timer_;
|
||||
PowerManager* power_manager_;
|
||||
|
||||
void InitializePowerSaveTimer() {
|
||||
power_save_timer_ = new PowerSaveTimer(-1, 60, 600);
|
||||
power_save_timer_->OnEnterSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(true);
|
||||
GetBacklight()->SetBrightness(10);
|
||||
});
|
||||
power_save_timer_->OnExitSleepMode([this]() {
|
||||
GetDisplay()->SetPowerSaveMode(false);
|
||||
GetBacklight()->RestoreBrightness();
|
||||
});
|
||||
power_save_timer_->OnShutdownRequest([this]() {
|
||||
ESP_LOGI(TAG, "Shutting down");
|
||||
power_manager_->Sleep();
|
||||
});
|
||||
power_save_timer_->SetEnabled(true);
|
||||
}
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
i2c_master_bus_config_t i2c_bus_cfg = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
|
||||
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.intr_priority = 0,
|
||||
.trans_queue_depth = 0,
|
||||
.flags = {
|
||||
.enable_internal_pullup = 1,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
|
||||
}
|
||||
|
||||
void InitializeSpi() {
|
||||
spi_bus_config_t buscfg = {};
|
||||
buscfg.mosi_io_num = DISPLAY_SPI_PIN_MOSI;
|
||||
buscfg.miso_io_num = DISPLAY_SPI_PIN_MISO;
|
||||
buscfg.sclk_io_num = DISPLAY_SPI_PIN_SCLK;
|
||||
buscfg.quadwp_io_num = GPIO_NUM_NC;
|
||||
buscfg.quadhd_io_num = GPIO_NUM_NC;
|
||||
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
}
|
||||
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
app.ToggleChatState();
|
||||
});
|
||||
boot_button_.OnDoubleClick([this]() {
|
||||
ESP_LOGI(TAG, "Button OnDoubleClick");
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
SwitchNetworkType();
|
||||
}
|
||||
});
|
||||
boot_button_.OnMultipleClick([this]() {
|
||||
ESP_LOGI(TAG, "Button OnThreeClick");
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
}
|
||||
},3);
|
||||
boot_button_.OnLongPress([this]() {
|
||||
ESP_LOGI(TAG, "Button LongPress to Sleep");
|
||||
display_->SetStatus(Lang::Strings::PLEASE_WAIT);
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
power_manager_->Sleep();
|
||||
});
|
||||
}
|
||||
void InitializeSt7789Display() {
|
||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||
esp_lcd_panel_handle_t panel = nullptr;
|
||||
// 液晶屏控制IO初始化
|
||||
ESP_LOGD(TAG, "Install panel IO");
|
||||
esp_lcd_panel_io_spi_config_t io_config = {};
|
||||
io_config.cs_gpio_num = DISPLAY_SPI_PIN_LCD_CS;
|
||||
io_config.dc_gpio_num = DISPLAY_SPI_PIN_LCD_DC;
|
||||
io_config.spi_mode = 3;
|
||||
io_config.pclk_hz = DISPLAY_SPI_CLOCK_HZ;
|
||||
io_config.trans_queue_depth = 10;
|
||||
io_config.lcd_cmd_bits = 8;
|
||||
io_config.lcd_param_bits = 8;
|
||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(DISPLAY_SPI_LCD_HOST, &io_config, &panel_io));
|
||||
|
||||
// 初始化液晶屏驱动芯片ST7789
|
||||
ESP_LOGD(TAG, "Install LCD driver");
|
||||
esp_lcd_panel_dev_config_t panel_config = {};
|
||||
panel_config.reset_gpio_num = DISPLAY_SPI_PIN_LCD_RST;
|
||||
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER_COLOR;
|
||||
panel_config.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, DISPLAY_INVERT_COLOR);
|
||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||
display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_WIDTH,
|
||||
DISPLAY_HEIGHT, DISPLAY_OFFSET_X,
|
||||
DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
|
||||
DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
|
||||
auto& theme_manager = LvglThemeManager::GetInstance();
|
||||
auto theme = theme_manager.GetTheme("dark");
|
||||
if (theme != nullptr) {
|
||||
display_->SetTheme(theme);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
YunliaoS3() :
|
||||
DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0),
|
||||
boot_button_(BOOT_BUTTON_PIN),
|
||||
power_manager_(new PowerManager()){
|
||||
power_manager_->Start5V();
|
||||
power_manager_->Initialize();
|
||||
InitializeI2c();
|
||||
power_manager_->CheckStartup();
|
||||
InitializePowerSaveTimer();
|
||||
InitializeSpi();
|
||||
InitializeButtons();
|
||||
InitializeSt7789Display();
|
||||
power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) {
|
||||
if(power_save_timer_){
|
||||
if (is_discharging) {
|
||||
power_save_timer_->SetEnabled(true);
|
||||
} else {
|
||||
power_save_timer_->SetEnabled(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
if(GetNetworkType() == NetworkType::WIFI){
|
||||
power_manager_->Shutdown4G();
|
||||
}
|
||||
GetBacklight()->RestoreBrightness();
|
||||
}
|
||||
|
||||
virtual AudioCodec* GetAudioCodec() override {
|
||||
static Es8388AudioCodec audio_codec(
|
||||
codec_i2c_bus_,
|
||||
I2C_NUM_0,
|
||||
AUDIO_INPUT_SAMPLE_RATE,
|
||||
AUDIO_OUTPUT_SAMPLE_RATE,
|
||||
AUDIO_I2S_GPIO_MCLK,
|
||||
AUDIO_I2S_GPIO_BCLK,
|
||||
AUDIO_I2S_GPIO_WS,
|
||||
AUDIO_I2S_GPIO_DOUT,
|
||||
AUDIO_I2S_GPIO_DIN,
|
||||
AUDIO_CODEC_PA_PIN,
|
||||
AUDIO_CODEC_ES8388_ADDR,
|
||||
AUDIO_INPUT_REFERENCE
|
||||
);
|
||||
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 {
|
||||
level = power_manager_->GetBatteryLevel();
|
||||
charging = power_manager_->IsCharging();
|
||||
discharging = power_manager_->IsDischarging();
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
DualNetworkBoard::SetPowerSaveMode(enabled);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(YunliaoS3);
|
||||
Reference in New Issue
Block a user