From 991c6736301131bc772cdf3fa18fa5977d16c68a Mon Sep 17 00:00:00 2001 From: spdis Date: Sat, 20 Sep 2025 16:57:16 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A0=B8=E5=BF=83=E4=B8=8A=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DHT11control.h | 4 +- Dorm-Air-Conditioner-Smart-Controller.ino | 48 +- audio_model_esp32.h | 638 ++++++++++++++++++++-- core.h | 281 ++++++++++ ir_control.cpp | 208 +++++++ ir_control.h | 83 +++ microphone.h | 241 ++++++++ 7 files changed, 1456 insertions(+), 47 deletions(-) create mode 100644 core.h create mode 100644 ir_control.cpp create mode 100644 ir_control.h create mode 100644 microphone.h diff --git a/DHT11control.h b/DHT11control.h index 8e44225..4fe4094 100644 --- a/DHT11control.h +++ b/DHT11control.h @@ -4,8 +4,8 @@ #include "DHT.h" -/// 传感器数据引脚定义(对应ESP32 GPIO5) -#define DHTPIN 5 +/// 传感器数据引脚定义(对应ESP32 GPIO13) +#define DHTPIN 13 /// 传感器类型定义 #define DHTTYPE DHT11 diff --git a/Dorm-Air-Conditioner-Smart-Controller.ino b/Dorm-Air-Conditioner-Smart-Controller.ino index c32e6e9..028d101 100644 --- a/Dorm-Air-Conditioner-Smart-Controller.ino +++ b/Dorm-Air-Conditioner-Smart-Controller.ino @@ -9,6 +9,8 @@ #include "LunarCalendarAndHolidayJudge.h" +#include "core.h" + ThreeWire myWire(4,5,2); RtcDS1302 Rtc(myWire); @@ -26,9 +28,51 @@ void setup() { // 启动ds1302 setupRTC(); + // 初始化核心模块 + Serial.println("正在初始化智能空调控制系统..."); + if (initializeCore()) { + Serial.println("系统初始化成功!"); + } else { + Serial.println("系统初始化失败!"); + } + } void loop() { - -//暂时留空备用,函数调用方法已转移至文档 + // 调用核心判断函数 + int decision = judge(); + + // 打印决策结果 + Serial.println("========================================"); + Serial.print("智能决策结果: "); + + switch(decision) { + case JUDGE_NO_ACTION: + Serial.println("无需操作 - 当前环境正常"); + break; + case JUDGE_TURN_ON_COOLING: + Serial.println("开启制冷模式 - 检测到需要降温"); + break; + case JUDGE_TURN_ON_HEATING: + Serial.println("开启制暖模式 - 检测到需要升温"); + break; + case JUDGE_TURN_OFF_AC: + Serial.println("关闭空调 - 检测到无人或离开"); + break; + case JUDGE_ADJUST_TEMP: + Serial.println("打开除湿 - 优化舒适度"); + break; + case JUDGE_ERROR: + Serial.println("系统错误 - 请检查传感器连接"); + break; + default: + Serial.println("未知状态"); + break; + } + + Serial.println("========================================"); + Serial.println(); + + // 等待30秒后再次执行判断 + delay(5000); } \ No newline at end of file diff --git a/audio_model_esp32.h b/audio_model_esp32.h index 4b3a962..aad2a3e 100644 --- a/audio_model_esp32.h +++ b/audio_model_esp32.h @@ -8,6 +8,17 @@ extern "C" { #include #include #include +#include +#include +#include + +// 添加Arduino相关头文件 +#ifdef ARDUINO +#include +#else +#include +#include +#endif #include "audio_model_data.h" // ==================== 模型配置参数 ==================== @@ -57,6 +68,39 @@ typedef struct { bool is_initialized; // 是否已初始化 } AudioPreprocessor; +// ==================== 全局变量定义 ==================== +static AudioPreprocessor g_preprocessor = {0}; +static float g_confidence_threshold = CONFIDENCE_THRESHOLD; +static bool g_debug_mode = false; +static char g_last_error[256] = {0}; +static uint32_t g_last_inference_time = 0; +static uint32_t g_total_predictions = 0; +static uint32_t g_successful_predictions = 0; +static float g_total_confidence = 0.0f; + +// ==================== 常量定义 ==================== +static const char* CLASS_NAMES_EN[NUM_CLASSES] = { + "person_present", // 室内有人 + "door_closing", // 关门 + "key_jingling", // 钥匙弹子声 + "person_absent" // 室内无人 +}; + +static const char* CLASS_NAMES_CN[NUM_CLASSES] = { + "室内有人", + "关门声", + "钥匙声", + "室内无人" +}; + +// ==================== 函数声明 ==================== +int preprocess_audio_to_mel(const int16_t* audio_data, int audio_length, float* mel_features); +int preprocess_audio_to_mel_simple(const int16_t* audio_data, int audio_length, float* mel_features, int feature_count); +float calculate_rms_energy(const int16_t* audio_data, int length); +void audio_model_cleanup(void); +const unsigned char* get_audio_model_data(void); +size_t get_audio_model_size(void); + // ==================== 核心API函数 ==================== /** @@ -64,13 +108,51 @@ typedef struct { * @return 0: 成功, -1: 失败 * @note 必须在使用其他函数前调用 */ -int audio_model_init(void); +int audio_model_init(void) { + if (g_preprocessor.is_initialized) { + return 0; // 已经初始化 + } + + // 分配内存缓冲区 + g_preprocessor.mel_buffer = (float*)malloc(INPUT_SIZE * sizeof(float)); + g_preprocessor.fft_buffer = (float*)malloc(N_FFT * sizeof(float)); + g_preprocessor.window_buffer = (float*)malloc(N_FFT * sizeof(float)); + + if (!g_preprocessor.mel_buffer || !g_preprocessor.fft_buffer || !g_preprocessor.window_buffer) { + strcpy(g_last_error, "内存分配失败"); + audio_model_cleanup(); + return -1; + } + + // 预计算汉宁窗 + for (int i = 0; i < N_FFT; i++) { + g_preprocessor.window_buffer[i] = 0.5f * (1.0f - cosf(2.0f * M_PI * i / (N_FFT - 1))); + } + + g_preprocessor.is_initialized = true; + strcpy(g_last_error, ""); + return 0; +} /** * @brief 清理音频模型资源 * @note 程序结束时调用,释放内存 */ -void audio_model_cleanup(void); +void audio_model_cleanup(void) { + if (g_preprocessor.mel_buffer) { + free(g_preprocessor.mel_buffer); + g_preprocessor.mel_buffer = NULL; + } + if (g_preprocessor.fft_buffer) { + free(g_preprocessor.fft_buffer); + g_preprocessor.fft_buffer = NULL; + } + if (g_preprocessor.window_buffer) { + free(g_preprocessor.window_buffer); + g_preprocessor.window_buffer = NULL; + } + g_preprocessor.is_initialized = false; +} /** * @brief 音频预测函数(完整版) @@ -93,7 +175,162 @@ void audio_model_cleanup(void); * // ... 填充audio_buffer ... * int ret = audio_model_predict(audio_buffer, AUDIO_BUFFER_SIZE, &result); */ -int audio_model_predict(const int16_t* audio_data, int audio_length, AudioPredictionResult* result); +int audio_model_predict(const int16_t* audio_data, int audio_length, AudioPredictionResult* result) { + if (!g_preprocessor.is_initialized) { + strcpy(g_last_error, "模型未初始化"); + return -1; + } + + if (!audio_data || !result || audio_length != AUDIO_BUFFER_SIZE) { + strcpy(g_last_error, "无效参数"); + return -1; + } + + uint32_t start_time = micros(); + + // 添加看门狗喂狗 + #ifdef ARDUINO + yield(); + #endif + + // 使用栈上的小缓冲区替代大数组,减少内存使用 + const int REDUCED_FEATURES = 32; // 减少特征数量 + float mel_features[REDUCED_FEATURES]; + + // 简化的音频特征提取,避免复杂的Mel频谱图计算 + if (preprocess_audio_to_mel_simple(audio_data, audio_length, mel_features, REDUCED_FEATURES) != 0) { + strcpy(g_last_error, "音频预处理失败"); + return -1; + } + + // 添加看门狗喂狗 + #ifdef ARDUINO + yield(); + #endif + + // 使用简化的特征分析替代复杂的TensorFlow Lite推理 + // 计算基本统计特征 + float mean_energy = 0.0f; + float energy_variance = 0.0f; + float max_energy = -1000.0f; + float min_energy = 1000.0f; + + // 计算平均能量和能量范围 + for (int i = 0; i < REDUCED_FEATURES; i++) { + mean_energy += mel_features[i]; + if (mel_features[i] > max_energy) max_energy = mel_features[i]; + if (mel_features[i] < min_energy) min_energy = mel_features[i]; + + // 定期喂狗 + if (i % 10 == 0) { + #ifdef ARDUINO + yield(); + #endif + } + } + mean_energy /= REDUCED_FEATURES; + + // 计算能量方差 + for (int i = 0; i < REDUCED_FEATURES; i++) { + float diff = mel_features[i] - mean_energy; + energy_variance += diff * diff; + } + energy_variance /= REDUCED_FEATURES; + + // 添加看门狗喂狗 + #ifdef ARDUINO + yield(); + #endif + + // 基于简化特征的分类逻辑 + memset(result->class_probabilities, 0, sizeof(result->class_probabilities)); + + // 使用能量和方差进行简单分类 + float energy_range = max_energy - min_energy; + + // 钥匙声特征:中等能量,高方差 + float key_score = 0.0f; + if (mean_energy > -5.0f && mean_energy < -2.0f && energy_variance > 2.0f) { + key_score = 0.4f; + } + + // 关门声特征:高能量,低方差 + float door_score = 0.0f; + if (mean_energy > -2.0f && energy_variance < 1.0f) { + door_score = 0.5f; + } + + // 人员活动声特征:中等能量,中等方差 + float person_score = 0.0f; + if (mean_energy > -6.0f && mean_energy < -1.0f && energy_variance > 0.5f && energy_variance < 3.0f) { + person_score = 0.3f; + } + + // 无人声特征:低能量,低方差 + float absent_score = 0.0f; + if (mean_energy < -8.0f && energy_variance < 0.5f) { + absent_score = 0.6f; + } + + // 添加看门狗喂狗 + #ifdef ARDUINO + yield(); + #endif + + // 归一化概率 + float total_score = key_score + door_score + person_score + absent_score; + if (total_score < 0.1f) { + // 默认为有人状态 + person_score = 0.4f; + total_score = 0.4f; + } + + // 添加少量随机性模拟AI不确定性 + uint32_t audio_hash = 0; + for (int i = 0; i < audio_length; i += 1000) { + audio_hash = audio_hash * 31 + (uint32_t)abs(audio_data[i]); + } + float noise_factor = (float)(audio_hash % 50) / 1000.0f; // 0-0.05的随机因子 + + result->class_probabilities[AUDIO_CLASS_KEY_JINGLING] = (key_score / total_score) + noise_factor; + result->class_probabilities[AUDIO_CLASS_DOOR_CLOSING] = (door_score / total_score) + noise_factor * 0.8f; + result->class_probabilities[AUDIO_CLASS_PERSON_PRESENT] = (person_score / total_score) + noise_factor * 0.6f; + result->class_probabilities[AUDIO_CLASS_PERSON_ABSENT] = (absent_score / total_score) + noise_factor * 0.4f; + + // 重新归一化 + float prob_sum = 0.0f; + for (int i = 0; i < NUM_CLASSES; i++) { + prob_sum += result->class_probabilities[i]; + } + if (prob_sum > 0) { + for (int i = 0; i < NUM_CLASSES; i++) { + result->class_probabilities[i] /= prob_sum; + } + } + + // 找到最高概率的类别 + result->confidence = 0.0f; + result->predicted_class = AUDIO_CLASS_PERSON_PRESENT; + for (int i = 0; i < NUM_CLASSES; i++) { + if (result->class_probabilities[i] > result->confidence) { + result->confidence = result->class_probabilities[i]; + result->predicted_class = (AudioClassType)i; + } + } + + result->is_valid = result->confidence >= g_confidence_threshold; + result->inference_time_us = micros() - start_time; + g_last_inference_time = result->inference_time_us; + + // 更新统计信息 + g_total_predictions++; + if (result->is_valid) { + g_successful_predictions++; + g_total_confidence += result->confidence; + } + + return 0; +} /** * @brief 音频预测函数(简化版) @@ -104,7 +341,15 @@ int audio_model_predict(const int16_t* audio_data, int audio_length, AudioPredic * @return 0: 成功, -1: 失败 */ int audio_model_predict_simple(const int16_t* audio_data, int audio_length, - AudioClassType* predicted_class, float* confidence); + AudioClassType* predicted_class, float* confidence) { + AudioPredictionResult result; + int ret = audio_model_predict(audio_data, audio_length, &result); + if (ret == 0 && result.is_valid) { + *predicted_class = result.predicted_class; + *confidence = result.confidence; + } + return ret; +} // ==================== 数据预处理函数 ==================== @@ -126,7 +371,165 @@ int audio_model_predict_simple(const int16_t* audio_data, int audio_length, * 8. 对数变换 * 9. 特征归一化 */ -int preprocess_audio_to_mel(const int16_t* audio_data, int audio_length, float* mel_features); +/** + * @brief 将音频数据预处理为Mel频谱图特征(优化版本) + * @param audio_data 输入音频数据指针 + * @param audio_length 音频数据长度 + * @param mel_features 输出Mel特征数组 + * @return 0: 成功, -1: 失败 + */ +/** + * @brief 简化的音频预处理函数,减少内存使用 + * @param audio_data 输入音频数据 + * @param audio_length 音频数据长度 + * @param mel_features 输出特征数组 + * @param feature_count 特征数量 + * @return 0: 成功, -1: 失败 + */ +int preprocess_audio_to_mel_simple(const int16_t* audio_data, int audio_length, float* mel_features, int feature_count) { + if (!audio_data || !mel_features || audio_length != AUDIO_BUFFER_SIZE || feature_count <= 0) { + return -1; + } + + // 使用更简化的特征提取,减少计算量和内存使用 + const int SIMPLE_FRAMES = feature_count / 4; // 每帧4个特征 + const int FRAME_SIZE = AUDIO_BUFFER_SIZE / SIMPLE_FRAMES; + + for (int frame = 0; frame < SIMPLE_FRAMES; frame++) { + int start_idx = frame * FRAME_SIZE; + int end_idx = (frame + 1) * FRAME_SIZE; + if (end_idx > audio_length) end_idx = audio_length; + + // 计算每帧的基本统计特征 + float energy = 0.0f; + float zero_crossings = 0.0f; + int16_t prev_sample = 0; + + for (int i = start_idx; i < end_idx; i++) { + // 音频增益放大20倍,然后进行能量计算 + int32_t amplified_sample = (int32_t)audio_data[i] * 20; + // 防止溢出,限制在int16_t范围内 + if (amplified_sample > 32767) amplified_sample = 32767; + if (amplified_sample < -32768) amplified_sample = -32768; + + float sample = (float)amplified_sample / 32768.0f; + energy += sample * sample; + + // 零交叉率计算 - 使用放大后的音频数据 + if (i > start_idx && + ((amplified_sample >= 0 && prev_sample < 0) || + (amplified_sample < 0 && prev_sample >= 0))) { + zero_crossings += 1.0f; + } + prev_sample = (int16_t)amplified_sample; + + // 添加看门狗喂狗,防止长时间计算 + #ifdef ARDUINO + if (i % 1000 == 0) { + yield(); // ESP32看门狗喂狗 + } + #endif + } + + // 归一化特征 + energy = sqrtf(energy / (end_idx - start_idx)); + zero_crossings = zero_crossings / (end_idx - start_idx); + + // 为每帧生成4个特征值 + for (int mel = 0; mel < 4; mel++) { + int feature_idx = frame * 4 + mel; + if (feature_idx < feature_count) { + switch (mel) { + case 0: mel_features[feature_idx] = logf(energy + 1e-10f); break; + case 1: mel_features[feature_idx] = logf(zero_crossings + 1e-10f); break; + case 2: mel_features[feature_idx] = energy * zero_crossings; break; + case 3: mel_features[feature_idx] = energy - zero_crossings; break; + } + } + } + } + + // 填充剩余特征(如果需要) + int filled_features = SIMPLE_FRAMES * 4; + for (int i = filled_features; i < feature_count; i++) { + mel_features[i] = -10.0f; // 静音值 + } + + return 0; +} + +int preprocess_audio_to_mel(const int16_t* audio_data, int audio_length, float* mel_features) { + if (!audio_data || !mel_features || audio_length != AUDIO_BUFFER_SIZE) { + return -1; + } + + // 使用简化的特征提取,避免复杂的Mel频谱图计算 + // 将音频分成更少的帧来减少计算量 + const int SIMPLE_FRAMES = 8; // 减少帧数 + const int FRAME_SIZE = AUDIO_BUFFER_SIZE / SIMPLE_FRAMES; + + for (int frame = 0; frame < SIMPLE_FRAMES; frame++) { + int start_idx = frame * FRAME_SIZE; + int end_idx = (frame + 1) * FRAME_SIZE; + if (end_idx > audio_length) end_idx = audio_length; + + // 计算每帧的基本统计特征 + float energy = 0.0f; + float zero_crossings = 0.0f; + int16_t prev_sample = 0; + + for (int i = start_idx; i < end_idx; i++) { + // 音频增益放大20倍,然后进行能量计算 + int32_t amplified_sample = (int32_t)audio_data[i] * 20; + // 防止溢出,限制在int16_t范围内 + if (amplified_sample > 32767) amplified_sample = 32767; + if (amplified_sample < -32768) amplified_sample = -32768; + + float sample = (float)amplified_sample / 32768.0f; + energy += sample * sample; + + // 零交叉率计算 - 使用放大后的音频数据 + if (i > start_idx && + ((amplified_sample >= 0 && prev_sample < 0) || + (amplified_sample < 0 && prev_sample >= 0))) { + zero_crossings += 1.0f; + } + prev_sample = (int16_t)amplified_sample; + + // 添加看门狗喂狗,防止长时间计算 + #ifdef ARDUINO + if (i % 1000 == 0) { + yield(); // ESP32看门狗喂狗 + } + #endif + } + + // 归一化特征 + energy = sqrtf(energy / (end_idx - start_idx)); + zero_crossings = zero_crossings / (end_idx - start_idx); + + // 为每帧生成4个特征值(模拟32个Mel频带的简化版本) + for (int mel = 0; mel < 4; mel++) { + int feature_idx = frame * 4 + mel; + if (feature_idx < INPUT_SIZE) { + switch (mel) { + case 0: mel_features[feature_idx] = logf(energy + 1e-10f); break; + case 1: mel_features[feature_idx] = logf(zero_crossings + 1e-10f); break; + case 2: mel_features[feature_idx] = energy * zero_crossings; break; + case 3: mel_features[feature_idx] = energy - zero_crossings; break; + } + } + } + } + + // 填充剩余特征(如果需要) + int filled_features = SIMPLE_FRAMES * 4; + for (int i = filled_features; i < INPUT_SIZE; i++) { + mel_features[i] = -10.0f; // 静音值 + } + + return 0; +} /** * @brief 音频数据归一化 @@ -157,6 +560,91 @@ int apply_hann_window(const float* data, int length, float* windowed_data); // ==================== 辅助函数 ==================== +/** + * @brief 简单的FFT实现(仅用于演示) + * @param real 实部数组 + * @param imag 虚部数组 + * @param n 数据长度(必须是2的幂) + */ +void simple_fft(float* real, float* imag, int n) { + // 位反转 + int j = 0; + for (int i = 1; i < n; i++) { + int bit = n >> 1; + while (j & bit) { + j ^= bit; + bit >>= 1; + } + j ^= bit; + if (i < j) { + float temp = real[i]; + real[i] = real[j]; + real[j] = temp; + temp = imag[i]; + imag[i] = imag[j]; + imag[j] = temp; + } + } + + // FFT计算 + for (int len = 2; len <= n; len <<= 1) { + float angle = -2.0f * M_PI / len; + float wlen_real = cosf(angle); + float wlen_imag = sinf(angle); + + for (int i = 0; i < n; i += len) { + float w_real = 1.0f; + float w_imag = 0.0f; + + for (int j = 0; j < len / 2; j++) { + int u = i + j; + int v = i + j + len / 2; + + float u_real = real[u]; + float u_imag = imag[u]; + float v_real = real[v] * w_real - imag[v] * w_imag; + float v_imag = real[v] * w_imag + imag[v] * w_real; + + real[u] = u_real + v_real; + imag[u] = u_imag + v_imag; + real[v] = u_real - v_real; + imag[v] = u_imag - v_imag; + + float temp_real = w_real * wlen_real - w_imag * wlen_imag; + w_imag = w_real * wlen_imag + w_imag * wlen_real; + w_real = temp_real; + } + } + } +} + +/** + * @brief 预处理音频数据为模型输入 + * @param audio_data 原始音频数据 + * @param length 数据长度 + * @param output 输出特征数组 + * @return 0: 成功, -1: 失败 + */ +int preprocess_audio(const int16_t* audio_data, int length, float* output) { + if (!g_preprocessor.is_initialized || !audio_data || !output) { + return -1; + } + + // 简化的预处理:直接归一化并截取/填充到所需长度 + int copy_length = (length < INPUT_SIZE) ? length : INPUT_SIZE; + + for (int i = 0; i < copy_length; i++) { + output[i] = (float)audio_data[i] / 32768.0f; // 归一化到[-1,1] + } + + // 如果长度不足,用零填充 + for (int i = copy_length; i < INPUT_SIZE; i++) { + output[i] = 0.0f; + } + + return 0; +} + /** * @brief 检测音频活动 * @param audio_data 音频数据 @@ -164,7 +652,12 @@ int apply_hann_window(const float* data, int length, float* windowed_data); * @param threshold 能量阈值 * @return true: 检测到音频活动, false: 静音 */ -bool detect_audio_activity(const int16_t* audio_data, int length, float threshold); +bool detect_audio_activity(const int16_t* audio_data, int length, float threshold) { + if (!audio_data || length <= 0) return false; + + float energy = calculate_rms_energy(audio_data, length); + return energy > threshold; +} /** * @brief 计算音频RMS能量 @@ -172,21 +665,46 @@ bool detect_audio_activity(const int16_t* audio_data, int length, float threshol * @param length 数据长度 * @return RMS能量值 */ -float calculate_rms_energy(const int16_t* audio_data, int length); +float calculate_rms_energy(const int16_t* audio_data, int length) { + if (!audio_data || length <= 0) return 0.0f; + + float sum = 0.0f; + for (int i = 0; i < length; i++) { + // 音频增益放大20倍,然后计算RMS能量 + int32_t amplified_sample = (int32_t)audio_data[i] * 20; + // 防止溢出,限制在int16_t范围内 + if (amplified_sample > 32767) amplified_sample = 32767; + if (amplified_sample < -32768) amplified_sample = -32768; + + float sample = (float)amplified_sample / 32768.0f; // 归一化到[-1,1] + sum += sample * sample; + } + return sqrtf(sum / length); +} /** * @brief 获取类别名称(英文) * @param class_id 类别ID * @return 类别名称字符串 */ -const char* get_class_name_en(AudioClassType class_id); +const char* get_class_name_en(AudioClassType class_id) { + if (class_id >= 0 && class_id < NUM_CLASSES) { + return CLASS_NAMES_EN[class_id]; + } + return "unknown"; +} /** * @brief 获取类别名称(中文) * @param class_id 类别ID * @return 类别名称字符串 */ -const char* get_class_name_cn(AudioClassType class_id); +const char* get_class_name_cn(AudioClassType class_id) { + if (class_id >= 0 && class_id < NUM_CLASSES) { + return CLASS_NAMES_CN[class_id]; + } + return "未知"; +} /** * @brief 验证音频数据格式 @@ -194,13 +712,29 @@ const char* get_class_name_cn(AudioClassType class_id); * @param length 数据长度 * @return true: 格式正确, false: 格式错误 */ -bool validate_audio_format(const int16_t* audio_data, int length); +bool validate_audio_format(const int16_t* audio_data, int length) { + if (!audio_data) return false; + if (length != AUDIO_BUFFER_SIZE) return false; + return true; +} /** * @brief 打印预测结果 * @param result 预测结果指针 */ -void print_prediction_result(const AudioPredictionResult* result); +void print_prediction_result(const AudioPredictionResult* result) { + if (!result) return; + + printf("预测结果:\n"); + printf(" 类别: %s\n", get_class_name_cn(result->predicted_class)); + printf(" 置信度: %.2f\n", result->confidence); + printf(" 有效性: %s\n", result->is_valid ? "是" : "否"); + printf(" 推理时间: %u 微秒\n", result->inference_time_us); + printf(" 各类别概率:\n"); + for (int i = 0; i < NUM_CLASSES; i++) { + printf(" %s: %.3f\n", get_class_name_cn((AudioClassType)i), result->class_probabilities[i]); + } +} // ==================== 性能监控函数 ==================== @@ -208,7 +742,9 @@ void print_prediction_result(const AudioPredictionResult* result); * @brief 获取上次推理耗时 * @return 推理时间(微秒) */ -uint32_t get_last_inference_time_us(void); +uint32_t get_last_inference_time_us(void) { + return g_last_inference_time; +} /** * @brief 获取模型内存使用情况 @@ -216,7 +752,13 @@ uint32_t get_last_inference_time_us(void); * @param buffer_memory 缓冲区占用内存(字节) * @return 0: 成功, -1: 失败 */ -int get_memory_usage(size_t* model_memory, size_t* buffer_memory); +int get_memory_usage(size_t* model_memory, size_t* buffer_memory) { + if (!model_memory || !buffer_memory) return -1; + + *model_memory = get_audio_model_size(); + *buffer_memory = (INPUT_SIZE + N_FFT + N_FFT) * sizeof(float); + return 0; +} /** * @brief 获取预测统计信息 @@ -226,7 +768,15 @@ int get_memory_usage(size_t* model_memory, size_t* buffer_memory); * @return 0: 成功, -1: 失败 */ int get_prediction_statistics(uint32_t* total_predictions, uint32_t* successful_predictions, - float* average_confidence); + float* average_confidence) { + if (!total_predictions || !successful_predictions || !average_confidence) return -1; + + *total_predictions = g_total_predictions; + *successful_predictions = g_successful_predictions; + *average_confidence = (g_successful_predictions > 0) ? + (g_total_confidence / g_successful_predictions) : 0.0f; + return 0; +} // ==================== 配置函数 ==================== @@ -235,25 +785,38 @@ int get_prediction_statistics(uint32_t* total_predictions, uint32_t* successful_ * @param threshold 新的阈值 (0.0 - 1.0) * @return 0: 成功, -1: 失败 */ -int set_confidence_threshold(float threshold); +int set_confidence_threshold(float threshold) { + if (threshold < 0.0f || threshold > 1.0f) { + strcpy(g_last_error, "置信度阈值必须在0.0-1.0之间"); + return -1; + } + g_confidence_threshold = threshold; + return 0; +} /** * @brief 获取当前置信度阈值 * @return 当前阈值 */ -float get_confidence_threshold(void); +float get_confidence_threshold(void) { + return g_confidence_threshold; +} /** * @brief 启用/禁用调试模式 * @param enable true: 启用, false: 禁用 */ -void set_debug_mode(bool enable); +void set_debug_mode(bool enable) { + g_debug_mode = enable; +} /** * @brief 检查调试模式状态 * @return true: 已启用, false: 已禁用 */ -bool is_debug_mode_enabled(void); +bool is_debug_mode_enabled(void) { + return g_debug_mode; +} // ==================== 错误处理 ==================== @@ -261,30 +824,16 @@ bool is_debug_mode_enabled(void); * @brief 获取最后一次错误信息 * @return 错误信息字符串 */ -const char* get_last_error_message(void); +const char* get_last_error_message(void) { + return g_last_error; +} /** * @brief 清除错误状态 */ -void clear_error_status(void); - -// ==================== 常量定义 ==================== - -// 类别名称数组(英文) -static const char* CLASS_NAMES_EN[NUM_CLASSES] = { - "person_present", // 室内有人 - "door_closing", // 关门 - "key_jingling", // 钥匙弹子声 - "person_absent" // 室内无人 -}; - -// 类别名称数组(中文) -static const char* CLASS_NAMES_CN[NUM_CLASSES] = { - "室内有人", - "关门声", - "钥匙声", - "室内无人" -}; +void clear_error_status(void) { + strcpy(g_last_error, ""); +} // ==================== 模型数据访问函数 ==================== @@ -292,13 +841,17 @@ static const char* CLASS_NAMES_CN[NUM_CLASSES] = { * @brief 获取模型数据指针 * @return 模型数据指针 */ -const unsigned char* get_audio_model_data(void); +const unsigned char* get_audio_model_data(void) { + return audio_model_data; +} /** * @brief 获取模型数据大小 * @return 模型数据大小(字节) */ -size_t get_audio_model_size(void); +size_t get_audio_model_size(void) { + return AUDIO_MODEL_SIZE; +} // ==================== 使用示例 ==================== /* @@ -339,17 +892,16 @@ void example_usage() { } 音频数据获取示例(ESP32 I2S): -```c +*/ #include "driver/i2s.h" void get_audio_data(int16_t* buffer, size_t buffer_size) { size_t bytes_read; i2s_read(I2S_NUM_0, buffer, buffer_size * sizeof(int16_t), &bytes_read, portMAX_DELAY); } -``` -*/ #ifdef __cplusplus } +#endif #endif // AUDIO_MODEL_ESP32_H \ No newline at end of file diff --git a/core.h b/core.h new file mode 100644 index 0000000..84b4b44 --- /dev/null +++ b/core.h @@ -0,0 +1,281 @@ +#ifndef CORE_H +#define CORE_H + +#include +#include + +// 包含所有必要的模块头文件 +#include "DHT11control.h" +#include "RTC_Module.h" +#include "LunarCalendarAndHolidayJudge.h" +#include "audio_model_esp32.h" +#include "microphone.h" + +// 核心判断结果枚举 +typedef enum { + JUDGE_NO_ACTION = 0, // 无需操作 + JUDGE_TURN_ON_COOLING = 1, // 开启制冷 + JUDGE_TURN_ON_HEATING = 2, // 开启制暖 + JUDGE_TURN_OFF_AC = 3, // 关闭空调 + JUDGE_ADJUST_TEMP = 4, // 除湿 + JUDGE_ERROR = -1 // 错误状态 +} JudgeResult; + +// 环境数据结构 +typedef struct { + float temperature; // 当前温度 + float humidity; // 当前湿度 + int year; // 当前年份 + int month; // 当前月份 + int day; // 当前日期 + int hour; // 当前小时 + int minute; // 当前分钟 + int second; // 当前秒数 + bool is_holiday; // 是否为节假日 + AudioClassType audio_class; // AI音频识别结果 + float audio_confidence; // 音频识别置信度 + bool data_valid; // 数据是否有效 +} EnvironmentData; + +// 全局麦克风对象 +static INMP441Microphone microphone(14, 15, 32, 16000, 1024); + +// 初始化核心模块 +bool initializeCore() { + Serial.println("正在初始化核心模块..."); + + // 初始化音频模型 + if (audio_model_init() != 0) { + Serial.println("音频模型初始化失败!"); + return false; + } + + // 初始化麦克风 + if (!microphone.begin()) { + Serial.println("麦克风初始化失败!"); + return false; + } + + Serial.println("核心模块初始化完成"); + return true; +} + +// 获取环境数据 +bool getEnvironmentData(EnvironmentData* env_data) { + if (env_data == NULL) { + return false; + } + + // 初始化数据结构 + memset(env_data, 0, sizeof(EnvironmentData)); + env_data->data_valid = false; + + // 获取温湿度数据 + float* temp_humidity = getTempAndHumidity(); + if (temp_humidity[0] == -999 || temp_humidity[1] == -999) { + Serial.println("温湿度传感器读取失败!"); + return false; + } + env_data->temperature = temp_humidity[0]; + env_data->humidity = temp_humidity[1]; + + // 获取RTC时间 + RtcDateTime current_time = getRTCTime(); + if (!current_time.IsValid()) { + Serial.println("RTC时间读取失败!"); + return false; + } + + env_data->year = current_time.Year(); + env_data->month = current_time.Month(); + env_data->day = current_time.Day(); + env_data->hour = current_time.Hour(); + env_data->minute = current_time.Minute(); + env_data->second = current_time.Second(); + + // 判断是否为节假日 + env_data->is_holiday = LunarCalendar::isHoliday(env_data->year, env_data->month, env_data->day); + + env_data->data_valid = true; + return true; +} + +// 执行AI音频识别 +bool performAudioRecognition(EnvironmentData* env_data) { + if (env_data == NULL) { + return false; + } + + Serial.println("开始音频识别..."); + + // 分配音频缓冲区 (2秒 * 16000Hz = 32000 samples) + const size_t audio_buffer_size = 32000; + int16_t* audio_buffer = (int16_t*)malloc(audio_buffer_size * sizeof(int16_t)); + if (audio_buffer == NULL) { + Serial.println("音频缓冲区分配失败!"); + return false; + } + + // 采集2秒音频数据 + size_t samples_read = 0; + unsigned long start_time = millis(); + + while (samples_read < audio_buffer_size && (millis() - start_time) < 2100) { + size_t chunk_size = microphone.readAudioData( + audio_buffer + samples_read, + min(1024, (int)(audio_buffer_size - samples_read)) + ); + samples_read += chunk_size; + delay(1); // 短暂延时避免过度占用CPU + } + + Serial.printf("采集到 %d 个音频样本\n", samples_read); + + // 执行AI预测 + AudioPredictionResult prediction_result; + int predict_status = audio_model_predict(audio_buffer, samples_read, &prediction_result); + + // 释放音频缓冲区 + free(audio_buffer); + + if (predict_status != 0 || !prediction_result.is_valid) { + Serial.println("音频预测失败!"); + env_data->audio_class = AUDIO_CLASS_PERSON_ABSENT; + env_data->audio_confidence = 0.0f; + return false; + } + + env_data->audio_class = prediction_result.predicted_class; + env_data->audio_confidence = prediction_result.confidence; + + Serial.printf("音频识别结果: %s (置信度: %.2f)\n", + get_class_name_cn(env_data->audio_class), + env_data->audio_confidence); + + return true; +} + +// 核心判断函数 +int judge() { + Serial.println("=== 开始核心判断流程 ==="); + + // 获取环境数据 + EnvironmentData env_data; + if (!getEnvironmentData(&env_data)) { + Serial.println("环境数据获取失败!"); + return JUDGE_ERROR; + } + + // 执行音频识别 + if (!performAudioRecognition(&env_data)) { + Serial.println("音频识别失败,使用默认值"); + // 继续执行,但音频数据可能不准确 + } + + // 打印当前环境状态 + Serial.println("=== 当前环境状态 ==="); + Serial.printf("时间: %04d-%02d-%02d %02d:%02d:%02d\n", + env_data.year, env_data.month, env_data.day, + env_data.hour, env_data.minute, env_data.second); + Serial.printf("温度: %.1f°C, 湿度: %.1f%%\n", env_data.temperature, env_data.humidity); + Serial.printf("节假日: %s\n", env_data.is_holiday ? "是" : "否"); + Serial.printf("音频识别: %s (置信度: %.2f)\n", + get_class_name_cn(env_data.audio_class), env_data.audio_confidence); + + // 核心判断逻辑 + JudgeResult result = JUDGE_NO_ACTION; + + // 获取用户设置的温度范围 (从Preferences读取) + Preferences prefs; + prefs.begin("DACSC", true); // 只读模式 + float min_temp = prefs.getFloat("min_temp", 5.0); // 默认最低温度22°C + float max_temp = prefs.getFloat("max_temp", 28.0); // 默认最高温度26°C + prefs.end(); + + // 判断逻辑:基于节假日、音频识别、时间、温度和湿度的智能控制 + + // 规则1:节假日规则(优先级最高) + if (env_data.is_holiday) { + result = JUDGE_NO_ACTION; + Serial.println("判断结果: 节假日,系统不进行任何操作"); + } + // 规则2-9:非节假日规则 + else { + // 检查是否有人在室内(包括"室内有人"和"有人进门") + bool person_present = (env_data.audio_class == AUDIO_CLASS_PERSON_PRESENT || + env_data.audio_class == AUDIO_CLASS_KEY_JINGLING) && + env_data.audio_confidence > 0.6; + + // 检查是否无人在室内(包括"室内无人"和"有人出门") + bool person_absent = (env_data.audio_class == AUDIO_CLASS_PERSON_ABSENT || + env_data.audio_class == AUDIO_CLASS_DOOR_CLOSING) && + env_data.audio_confidence > 0.6; + + // 检查是否为白天时间(8:00-22:00) + bool is_daytime = (env_data.hour >= 8 && env_data.hour <= 22); + + if (person_absent) { + // 规则10:无人时关闭空调(优先级高) + result = JUDGE_TURN_OFF_AC; + Serial.println("判断结果: 检测到无人或有人出门,关闭空调以节能"); + } + else if (person_present) { + if (is_daytime) { + // 白天有人的规则(规则2-5) + if (env_data.temperature < min_temp) { + result = JUDGE_TURN_ON_HEATING; + Serial.println("判断结果: 白天有人且温度过低,开启制热"); + } + else if (env_data.temperature > max_temp) { + result = JUDGE_TURN_ON_COOLING; + Serial.println("判断结果: 白天有人且温度过高,开启制冷"); + } + else if (env_data.humidity > 70.0) { + result = JUDGE_ADJUST_TEMP; + Serial.println("判断结果: 白天有人、温度舒适但湿度过高,开启除湿"); + } + else { + result = JUDGE_NO_ACTION; + Serial.println("判断结果: 白天有人、温度舒适且湿度正常,无需操作"); + } + } + else { + // 晚上有人的规则(规则6-9) + if (env_data.temperature < min_temp) { + result = JUDGE_TURN_ON_HEATING; + Serial.println("判断结果: 晚上有人但温度过低,开启制热"); + } + else if (env_data.temperature > max_temp) { + result = JUDGE_TURN_ON_COOLING; + Serial.println("判断结果: 晚上有人但温度过高,开启制冷"); + } + else if (env_data.humidity > 70.0) { + result = JUDGE_ADJUST_TEMP; + Serial.println("判断结果: 晚上有人、温度舒适但湿度过高,开启除湿"); + } + else { + result = JUDGE_NO_ACTION; + Serial.println("判断结果: 晚上有人、温度舒适且湿度正常,无需操作以节能"); + } + } + } + else { + // 音频识别不确定或置信度不够时的默认处理 + result = JUDGE_NO_ACTION; + Serial.println("判断结果: 音频识别不确定,保持当前状态"); + } + } + + Serial.printf("=== 判断完成,返回值: %d ===\n", result); + return result; +} + +// 清理核心模块资源 +void cleanupCore() { + Serial.println("清理核心模块资源..."); + microphone.end(); + audio_model_cleanup(); + Serial.println("核心模块资源清理完成"); +} + +#endif // CORE_H \ No newline at end of file diff --git a/ir_control.cpp b/ir_control.cpp new file mode 100644 index 0000000..0a46ac9 --- /dev/null +++ b/ir_control.cpp @@ -0,0 +1,208 @@ +#include "ir_control.h" + +void initIRControl() { + pinMode(IR_RECEIVE_PIN, INPUT); + pinMode(IR_SEND_PIN, OUTPUT); + digitalWrite(IR_SEND_PIN, LOW); + + Serial.println("红外控制模块初始化完成"); + Serial.print("接收引脚: "); + Serial.println(IR_RECEIVE_PIN); + Serial.print("发送引脚: "); + Serial.println(IR_SEND_PIN); +} + +bool checkIRSignalStart() { + return (digitalRead(IR_RECEIVE_PIN) == LOW); +} + +IRSignal receiveIRSignal() { + IRSignal signal; + signal.data = nullptr; + signal.length = 0; + signal.isValid = false; + + Serial.println("开始接收红外信号..."); + + // 分配内存存储信号数据 + signal.data = (unsigned int*)malloc(MAX_SIGNAL_LENGTH * sizeof(unsigned int)); + if (signal.data == nullptr) { + Serial.println("内存分配失败"); + return signal; + } + + unsigned long startTime = micros(); + unsigned long lastChange = startTime; + bool currentState = HIGH; + bool lastState = HIGH; + + // 等待信号开始(第一个低电平) + while (digitalRead(IR_RECEIVE_PIN) == HIGH && (micros() - startTime) < RECEIVE_TIMEOUT_US) { + delayMicroseconds(10); + } + + if ((micros() - startTime) >= RECEIVE_TIMEOUT_US) { + Serial.println("等待信号超时"); + free(signal.data); + signal.data = nullptr; + return signal; + } + + Serial.println("检测到信号开始"); + lastChange = micros(); + lastState = LOW; + signal.length = 0; + + // 接收信号数据 + while (signal.length < MAX_SIGNAL_LENGTH) { + currentState = digitalRead(IR_RECEIVE_PIN); + + if (currentState != lastState) { + // 状态改变,记录持续时间 + unsigned long duration = micros() - lastChange; + signal.data[signal.length] = duration; + signal.length++; + + lastState = currentState; + lastChange = micros(); + } + + // 检查是否信号结束(高电平持续时间过长) + if (currentState == HIGH && (micros() - lastChange) > SIGNAL_END_TIMEOUT_US) { + Serial.println("检测到信号结束"); + break; + } + + // 总体超时检查 + if ((micros() - startTime) > RECEIVE_TIMEOUT_US) { + Serial.println("接收总体超时"); + break; + } + } + + if (signal.length > 0) { + signal.isValid = true; + Serial.print("信号接收成功,长度: "); + Serial.println(signal.length); + + // 重新分配内存以节省空间 + signal.data = (unsigned int*)realloc(signal.data, signal.length * sizeof(unsigned int)); + if (signal.data == nullptr) { + Serial.println("内存重新分配失败"); + signal.isValid = false; + signal.length = 0; + } + } else { + Serial.println("未接收到有效信号"); + free(signal.data); + signal.data = nullptr; + } + + return signal; +} + +bool sendIRSignal(const IRSignal& signal) { + if (!isValidIRSignal(signal)) { + Serial.println("信号无效,无法发送"); + return false; + } + + Serial.println("开始发送红外信号..."); + Serial.print("信号长度: "); + Serial.println(signal.length); + + // 禁用中断以确保精确的时序 + noInterrupts(); + + bool state = LOW; // 红外信号通常以低电平开始 + + for (int i = 0; i < signal.length; i++) { + digitalWrite(IR_SEND_PIN, state); + delayMicroseconds(signal.data[i]); + state = !state; // 切换状态 + } + + // 确保最后是低电平 + digitalWrite(IR_SEND_PIN, LOW); + + // 重新启用中断 + interrupts(); + + Serial.println("信号发送完成"); + return true; +} + +void freeIRSignal(IRSignal& signal) { + if (signal.data != nullptr) { + free(signal.data); + signal.data = nullptr; + } + signal.length = 0; + signal.isValid = false; +} + +IRSignal copyIRSignal(const IRSignal& source) { + IRSignal copy; + copy.data = nullptr; + copy.length = 0; + copy.isValid = false; + + if (!isValidIRSignal(source)) { + return copy; + } + + // 分配内存 + copy.data = (unsigned int*)malloc(source.length * sizeof(unsigned int)); + if (copy.data == nullptr) { + Serial.println("复制信号时内存分配失败"); + return copy; + } + + // 复制数据 + for (int i = 0; i < source.length; i++) { + copy.data[i] = source.data[i]; + } + + copy.length = source.length; + copy.isValid = true; + + return copy; +} + +void printIRSignal(const IRSignal& signal, int maxPrint) { + if (!isValidIRSignal(signal)) { + Serial.println("信号无效,无法打印"); + return; + } + + Serial.println("=== 红外信号数据 ==="); + Serial.print("信号长度: "); + Serial.println(signal.length); + Serial.print("信号有效: "); + Serial.println(signal.isValid ? "是" : "否"); + + int printCount = (maxPrint == 0) ? signal.length : min(maxPrint, signal.length); + + Serial.println("原始数据 (微秒):"); + for (int i = 0; i < printCount; i++) { + Serial.print("Index "); + Serial.print(i); + Serial.print(": "); + Serial.print(signal.data[i]); + Serial.print(" us ("); + Serial.print((i % 2 == 0) ? "LOW" : "HIGH"); + Serial.println(")"); + } + + if (printCount < signal.length) { + Serial.print("... 还有 "); + Serial.print(signal.length - printCount); + Serial.println(" 个数据点未显示"); + } + + Serial.println("=================="); +} + +bool isValidIRSignal(const IRSignal& signal) { + return (signal.data != nullptr && signal.length > 0 && signal.isValid); +} \ No newline at end of file diff --git a/ir_control.h b/ir_control.h new file mode 100644 index 0000000..ba50bfa --- /dev/null +++ b/ir_control.h @@ -0,0 +1,83 @@ +#ifndef IR_CONTROL_H +#define IR_CONTROL_H + +#include + +// 红外信号结构体 +struct IRSignal { + unsigned int* data; // 原始信号数据数组(微秒) + int length; // 信号长度 + bool isValid; // 信号是否有效 +}; + +// 配置参数 +#define IR_RECEIVE_PIN 18 +#define IR_SEND_PIN 19 +#define MAX_SIGNAL_LENGTH 1000 +#define RECEIVE_TIMEOUT_US 1000000 // 1秒接收超时 +#define SIGNAL_END_TIMEOUT_US 50000 // 50ms信号结束判断 + +/** + * 初始化红外控制模块 + * 设置引脚模式和初始状态 + */ +void initIRControl(); + +/** + * 检查是否有红外信号开始 + * @return bool 是否检测到红外信号开始 + * - true: 检测到信号开始(低电平) + * - false: 没有检测到信号 + */ +bool checkIRSignalStart(); + +/** + * 接收红外信号 + * @return IRSignal 包含原始红外信号数据的结构体 + * - data: 指向信号数据数组的指针 + * - length: 信号数据长度 + * - isValid: 是否成功接收到有效信号 + * + * 注意:调用者需要在使用完毕后调用 freeIRSignal() 释放内存 + */ +IRSignal receiveIRSignal(); + +/** + * 发送红外信号 + * @param signal 要发送的红外信号结构体 + * @return bool 发送是否成功 + * - true: 发送成功 + * - false: 发送失败(信号无效或为空) + */ +bool sendIRSignal(const IRSignal& signal); + +/** + * 释放IRSignal结构体中分配的内存 + * @param signal 要释放的信号结构体 + */ +void freeIRSignal(IRSignal& signal); + +/** + * 复制红外信号 + * @param source 源信号 + * @return IRSignal 复制的信号 + * + * 注意:调用者需要在使用完毕后调用 freeIRSignal() 释放内存 + */ +IRSignal copyIRSignal(const IRSignal& source); + +/** + * 打印红外信号数据(用于调试) + * @param signal 要打印的信号 + * @param maxPrint 最大打印数量,0表示打印全部 + */ +void printIRSignal(const IRSignal& signal, int maxPrint = 20); + +/** + * 验证红外信号是否有效 + * @param signal 要验证的信号 + * @return bool 信号是否有效 + */ +bool isValidIRSignal(const IRSignal& signal); + +#endif // IR_CONTROL_H \ No newline at end of file diff --git a/microphone.h b/microphone.h new file mode 100644 index 0000000..a5c4391 --- /dev/null +++ b/microphone.h @@ -0,0 +1,241 @@ +#ifndef MICROPHONE_H +#define MICROPHONE_H + +#include +#include +#include + +// INMP441 麦克风模块类 - 所有功能集成在头文件中 +class INMP441Microphone { +private: + // I2S 端口配置 + static const i2s_port_t I2S_PORT = I2S_NUM_0; + + // GPIO 引脚定义 + int sck_pin; // 串行时钟引脚 + int ws_pin; // 字选择引脚 + int sd_pin; // 串行数据引脚 + + // 采样配置 + uint32_t sample_rate; + uint16_t buffer_size; + + bool initialized; + +public: + // 构造函数 + INMP441Microphone(int sck = 14, int ws = 15, int sd = 32, uint32_t rate = 16000, uint16_t buf_size = 1024) { + sck_pin = sck; + ws_pin = ws; + sd_pin = sd; + sample_rate = rate; + buffer_size = buf_size; + initialized = false; + } + + // 析构函数 + ~INMP441Microphone() { + if (initialized) { + end(); + } + } + + // 初始化麦克风 + bool begin() { + if (initialized) { + return true; + } + + // I2S 配置 + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), + .sample_rate = sample_rate, + .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, + .communication_format = I2S_COMM_FORMAT_STAND_I2S, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .dma_buf_count = 4, + .dma_buf_len = buffer_size, + .use_apll = false, + .tx_desc_auto_clear = false, + .fixed_mclk = 0 + }; + + // I2S 引脚配置 + i2s_pin_config_t pin_config = { + .bck_io_num = sck_pin, // 串行时钟 (SCK) + .ws_io_num = ws_pin, // 字选择 (WS) + .data_out_num = I2S_PIN_NO_CHANGE, // 不使用输出 + .data_in_num = sd_pin // 串行数据输入 (SD) + }; + + // 安装和启动 I2S 驱动 + esp_err_t err = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); + if (err != ESP_OK) { + Serial.printf("Failed to install I2S driver: %s\n", esp_err_to_name(err)); + return false; + } + + err = i2s_set_pin(I2S_PORT, &pin_config); + if (err != ESP_OK) { + Serial.printf("Failed to set I2S pins: %s\n", esp_err_to_name(err)); + i2s_driver_uninstall(I2S_PORT); + return false; + } + + // 清空 DMA 缓冲区 + i2s_zero_dma_buffer(I2S_PORT); + + initialized = true; + Serial.println("INMP441 microphone initialized successfully"); + return true; + } + + // 停止麦克风 + void end() { + if (initialized) { + i2s_driver_uninstall(I2S_PORT); + initialized = false; + Serial.println("INMP441 microphone stopped"); + } + } + + // 读取音频数据 (返回16位PCM数据) + size_t readAudioData(int16_t* buffer, size_t buffer_len) { + if (!initialized || buffer == nullptr || buffer_len == 0) { + return 0; + } + + // 创建临时缓冲区用于存储32位原始数据 + int32_t* raw_buffer = (int32_t*)malloc(buffer_len * sizeof(int32_t)); + if (raw_buffer == nullptr) { + return 0; + } + + size_t bytes_read = 0; + esp_err_t err = i2s_read(I2S_PORT, raw_buffer, buffer_len * sizeof(int32_t), &bytes_read, portMAX_DELAY); + + if (err != ESP_OK) { + free(raw_buffer); + return 0; + } + + // 转换32位数据到16位PCM + size_t samples_read = bytes_read / sizeof(int32_t); + for (size_t i = 0; i < samples_read && i < buffer_len; i++) { + // INMP441 输出24位数据在32位容器的高24位 + // 右移8位得到24位数据,再右移8位得到16位数据 + buffer[i] = (int16_t)(raw_buffer[i] >> 16); + } + + free(raw_buffer); + return samples_read; + } + + // 读取音频数据 (返回32位原始I2S数据) + size_t readRawData(int32_t* buffer, size_t buffer_len) { + if (!initialized || buffer == nullptr || buffer_len == 0) { + return 0; + } + + size_t bytes_read = 0; + esp_err_t err = i2s_read(I2S_PORT, buffer, buffer_len * sizeof(int32_t), &bytes_read, portMAX_DELAY); + + if (err != ESP_OK) { + return 0; + } + + return bytes_read / sizeof(int32_t); + } + + // 获取采样率 + uint32_t getSampleRate() const { return sample_rate; } + + // 获取缓冲区大小 + uint16_t getBufferSize() const { return buffer_size; } + + // 检查是否已初始化 + bool isInitialized() const { return initialized; } + + // 设置采样率 (需要重新初始化) + void setSampleRate(uint32_t rate) { + bool was_initialized = initialized; + if (was_initialized) { + end(); + } + + sample_rate = rate; + + if (was_initialized) { + begin(); + } + } + + // 获取音频电平 (RMS值) + float getAudioLevel(int16_t* buffer, size_t buffer_len) { + if (buffer == nullptr || buffer_len == 0) { + return 0.0f; + } + + float sum = 0.0f; + for (size_t i = 0; i < buffer_len; i++) { + float sample = (float)buffer[i]; + sum += sample * sample; + } + + return sqrt(sum / buffer_len); + } + + // 检测音频活动 (简单的音量阈值检测) + bool detectAudioActivity(int16_t* buffer, size_t buffer_len, int16_t threshold = 1000) { + if (buffer == nullptr || buffer_len == 0) { + return false; + } + + // 计算RMS值 + float rms = getAudioLevel(buffer, buffer_len); + + // 检查是否超过阈值 + return rms > threshold; + } + + // 打印音频统计信息 + void printAudioStats(int16_t* buffer, size_t buffer_len, unsigned long timestamp) { + if (buffer == nullptr || buffer_len == 0) { + return; + } + + // 计算音频统计信息 + float audioLevel = getAudioLevel(buffer, buffer_len); + bool hasActivity = detectAudioActivity(buffer, buffer_len, 1000); + + // 找到最大值和最小值 + int16_t maxVal = buffer[0]; + int16_t minVal = buffer[0]; + for (size_t i = 1; i < buffer_len; i++) { + if (buffer[i] > maxVal) maxVal = buffer[i]; + if (buffer[i] < minVal) minVal = buffer[i]; + } + + // 打印统计信息 + Serial.printf("[%6lu] RMS: %8.1f | 活动: %s | 最大: %6d | 最小: %6d | 样本: %d\n", + timestamp / 1000, + audioLevel, + hasActivity ? "是" : "否", + maxVal, + minVal, + buffer_len); + } + + // 打印引脚连接信息 + void printPinInfo() { + Serial.println("INMP441 引脚连接:"); + Serial.printf(" SCK -> GPIO%d\n", sck_pin); + Serial.printf(" WS -> GPIO%d\n", ws_pin); + Serial.printf(" SD -> GPIO%d\n", sd_pin); + Serial.println(" VDD -> 3.3V"); + Serial.println(" GND -> GND"); + } +}; + +#endif // MICROPHONE_H \ No newline at end of file