测试功能描述:
程序会先测量 2 秒环境音量作为基准,然后开始实时显示音量柱状图,并在 30 秒后自动结束,当检测到音量超过阈值时会显示提示并打开led灯
一,硬件准备:
1.ESP32 CH3 USB开发板1块
2.INMP441 麦克风1个
3.LED灯一个
二,连线
1.麦克风与esp32连接
- SCK -> GPIO14
- WS -> GPIO15
- SD -> GPIO2
- VDD -> 3.3V
- GND -> GND
2.麦克风与esp32连接
- 正极 -> GPIO25
- 负极 -> GND
三,测试代码
import machine
import ustruct
import utime
import math# 硬件配置 - INMP441麦克风连接到I2S0
i2s = machine.I2S(0,sck=machine.Pin(14), # 时钟引脚ws=machine.Pin(15), # 字选择引脚sd=machine.Pin(2), # 数据引脚mode=machine.I2S.RX, # 接收模式bits=16, # 16位采样深度format=machine.I2S.MONO, # 单声道rate=16000, # 采样率16kHzibuf=40000 # 内部缓冲区大小
)# LED配置
led = machine.Pin(25, machine.Pin.OUT) # LED连接到GPIO25# 采样参数
SAMPLE_RATE = 16000
BUFFER_SIZE = 1024 # 每次读取的样本数
FFT_SIZE = 256 # FFT大小(必须是2的幂,降低以提高性能)
DETECTION_THRESHOLD = 20000 # 语音检测阈值
COMMAND_TIMEOUT = 1 # 命令持续时间(秒)
LED_ON_DURATION = 3 # LED亮起持续时间(秒)
VOLUME_BAR_LENGTH = 40 # 音量条长度# 汉宁窗函数
def hanning_window(size):"""生成汉宁窗函数"""window = []for i in range(size):window.append(0.5 * (1 - math.cos(2 * math.pi * i / (size - 1))))return window# 简易FFT实现(Cooley-Tukey算法)
def fft(samples):"""简易FFT实现"""n = len(samples)if n <= 1:return sampleseven = fft(samples[0::2])odd = fft(samples[1::2])result = [0j] * n # 使用复数列表for k in range(n//2):angle = -2 * math.pi * k / nt = odd[k] * (math.cos(angle) + 1j * math.sin(angle))result[k] = even[k] + tresult[k + n//2] = even[k] - treturn result# 计算频谱能量
def calculate_spectrum(samples):"""计算信号的频谱能量"""# 应用汉宁窗window = hanning_window(len(samples))windowed_samples = [samples[i] * window[i] for i in range(len(samples))]# 计算FFTfft_data = fft(windowed_samples)# 计算幅度谱(能量)magnitudes = [math.sqrt(fft_data[i].real**2 + fft_data[i].imag**2) for i in range(len(fft_data)//2)]return magnitudes# 提取语音特征
def extract_features(spectrum):"""从频谱中提取语音相关特征"""# 人类语音主要集中在300-3400Hz# 计算这个范围内的能量low_freq = int(300 * FFT_SIZE / SAMPLE_RATE)high_freq = int(3400 * FFT_SIZE / SAMPLE_RATE)# 确保索引在有效范围内low_freq = max(0, min(low_freq, len(spectrum)-1))high_freq = max(0, min(high_freq, len(spectrum)-1))# 计算语音频率范围内的能量voice_energy = sum(spectrum[low_freq:high_freq])return voice_energy# 环境特征校准
def calibrate_environment(duration=2):"""测量环境特征,返回建议的阈值"""print(f"正在校准环境特征 ({duration}秒)...")mic_samples = bytearray(BUFFER_SIZE * 2)samples_array = [0] * BUFFER_SIZEmax_feature = 0avg_feature = 0samples_count = 0for _ in range(int(SAMPLE_RATE / BUFFER_SIZE * duration)):# 从I2S读取数据i2s.readinto(mic_samples)# 转换为整数数组for i in range(BUFFER_SIZE):samples_array[i] = ustruct.unpack_from('<h', mic_samples, i*2)[0]# 计算频谱和特征spectrum = calculate_spectrum(samples_array[:FFT_SIZE])feature = extract_features(spectrum)avg_feature += featuresamples_count += 1if feature > max_feature:max_feature = feature# 显示实时特征normalized = min(1.0, feature / 100000)bar_length = int(normalized * VOLUME_BAR_LENGTH)feature_bar = '█' * bar_length + '-' * (VOLUME_BAR_LENGTH - bar_length)print(f"\r环境特征: [{feature_bar}] {feature:.1f}", end='')utime.sleep_ms(50)if samples_count > 0:avg_feature /= samples_count# 建议阈值:环境最大特征的1.5倍suggested_threshold = max_feature * 1.5# 确保阈值不低于默认值suggested_threshold = max(DETECTION_THRESHOLD, suggested_threshold)print(f"\n环境校准完成: 平均={avg_feature:.1f}, 最大={max_feature:.1f}")print(f"建议阈值: {suggested_threshold:.1f}")return suggested_threshold# 语音命令识别
def detect_command(buffer, threshold=DETECTION_THRESHOLD):"""检测语音命令"""samples_array = [0] * BUFFER_SIZE# 转换为整数数组for i in range(BUFFER_SIZE):samples_array[i] = ustruct.unpack_from('<h', buffer, i*2)[0]# 计算频谱spectrum = calculate_spectrum(samples_array[:FFT_SIZE])# 提取特征feature = extract_features(spectrum)# 计算音量(用于显示)volume = sum(abs(samples_array[i]) for i in range(BUFFER_SIZE)) // BUFFER_SIZEreturn feature > threshold, feature, volume# 主控制循环
def voice_control_loop(threshold=DETECTION_THRESHOLD):print("语音控制LED系统已启动")print(f"语音检测阈值: {threshold}")# 创建缓冲区mic_samples = bytearray(BUFFER_SIZE * 2)led_state = Falselast_command_time = 0command_active = Falsetry:while True:# 从I2S读取数据i2s.readinto(mic_samples)# 检测命令command_detected, feature_value, volume = detect_command(mic_samples, threshold)current_time = utime.ticks_ms()# 状态显示status = "ON " if led_state else "OFF"cmd_status = "CMD" if command_detected else " "# 显示特征值柱状图normalized = min(1.0, feature_value / 100000)bar_length = int(normalized * VOLUME_BAR_LENGTH)feature_bar = '█' * bar_length + '-' * (VOLUME_BAR_LENGTH - bar_length)print(f"\r特征: [{feature_bar}] {feature_value:.1f} | 音量: {volume:4d} | 状态: {status} | {cmd_status}", end='')# 命令逻辑if command_detected and not command_active:command_active = Trueprint("\n命令开始检测")if command_detected:last_command_time = current_time# 如果命令持续时间超过阈值,执行动作if command_active and utime.ticks_diff(current_time, last_command_time) > COMMAND_TIMEOUT * 1000:command_active = False# 如果LED关闭,则打开if not led_state:led.on()led_state = Trueprint("\nLED已点亮")else:# 如果LED已打开,则关闭led.off()led_state = Falseprint("\nLED已关闭")# 如果LED已打开且超时,则关闭if led_state and utime.ticks_diff(current_time, last_command_time) > LED_ON_DURATION * 1000:led.off()led_state = Falseprint("\nLED已关闭(超时)")# 短暂延迟utime.sleep_ms(50)except KeyboardInterrupt:print("\n程序已停止")finally:# 关闭资源i2s.deinit()led.off()# 运行语音控制程序
def main():# 执行环境校准threshold = calibrate_environment()# 使用校准后的阈值voice_control_loop(threshold)# 启动程序
main()
四,运行效果截图
- 代码中的引脚定义可以根据实际接线情况调整
- 内部缓冲区大小 (ibuf) 可能需要根据 ESP32 的内存情况调整
- 采样率和位深度可以根据需要修改,但会影响音频质量
- 如果出现内存不足错误,尝试减小 BUFFER_SIZE 值
- 麦克风的灵敏度可以通过修改 INMP442 的增益设置来调整