1. 项目概述
在智能硬件开发领域,声源定位技术一直是个既实用又有趣的方向。最近我用STM32F1系列单片机完成了一套声源定位系统,能够实时检测声源方位并输出角度信息。这个项目特别适合想要进阶嵌入式开发的工程师,也适合对DSP处理感兴趣的电子爱好者。
整套系统硬件成本控制在200元以内,核心算法全部自主实现,定位精度可以达到±5°(在2米范围内)。不同于市面上复杂的阵列麦克风方案,我这里采用的是最精简的三麦克风布局,通过巧妙的算法处理同样实现了不错的定位效果。
2. 系统设计与硬件选型
2.1 整体架构设计
系统采用典型的嵌入式系统三层架构:
- 传感层:三个驻极体麦克风组成的阵列
- 处理层:STM32F103C8T6最小系统板
- 输出层:0.96寸OLED显示屏+LED指示灯
特别要说明的是麦克风的布局方式。我采用的是等边三角形布局,边长8cm。这个距离选择很有讲究:太近会导致时间差分辨率不足,太远又会引入声波衍射问题。经过实测,8cm间距在1-3米范围内都能保持较好的定位精度。
2.2 关键硬件选型
-
主控芯片:STM32F103C8T6
- 选择理由:72MHz主频足够处理音频信号,内置12位ADC,性价比极高(核心板仅15元左右)
- 特别注意:要开启DMA模式采集音频,避免CPU频繁中断
-
麦克风模块:MAX9814驻极体麦克风放大器
- 关键参数:增益可调(我设置为40dB),信噪比>60dB
- 供电要求:3.3V单电源供电
-
ADC采样配置:
- 采样率:8kHz(满足语音频段需求)
- 分辨率:12位
- 触发方式:定时器触发+DMA传输
硬件设计时有个重要经验:一定要在麦克风输入端加装高通滤波器(我用的是一阶RC,截止频率300Hz),这样可以有效滤除环境低频噪声。
3. 核心算法实现
3.1 时延估计算法
声源定位的核心是计算声波到达不同麦克风的时间差(TDOA)。我对比了三种算法:
-
互相关法:
c复制void cross_correlation(int16_t *sig1, int16_t *sig2, uint32_t len, int32_t *delay) { int32_t max_corr = 0; int32_t best_delay = 0; for(int d = -MAX_DELAY; d <= MAX_DELAY; d++) { int32_t corr = 0; for(uint32_t i = 0; i < len; i++) { if((i+d) >= 0 && (i+d) < len) { corr += sig1[i] * sig2[i+d]; } } if(corr > max_corr) { max_corr = corr; best_delay = d; } } *delay = best_delay; } -
GCC-PHAT算法:
在频域实现的改进版,抗噪性更好但计算量较大 -
过零检测法:
最简单但精度较差
最终选择的是带预滤波的互相关法,在STM32上实测单次计算仅需1.2ms(开启硬件浮点加速)。
3.2 方位角计算
得到时间差Δt后,声源角度θ可通过几何关系求出:
code复制θ = arccos((c*Δt)/d)
其中:
- c = 343m/s(声速)
- d = 8cm(麦克风间距)
实际实现时要处理象限判断问题。我的做法是通过三个麦克风的两两组合(AB、BC、CA)得到三个角度估计值,然后取中位数作为最终结果。
4. 软件实现细节
4.1 实时处理流程
- ADC DMA连续采样三通道音频
- 每积累256个样本触发处理中断
- 预处理(去均值、加汉宁窗)
- 两两通道互相关计算
- 角度解算与平滑滤波
- 结果显示更新
关键点在于保证实时性。我的解决方案是:
- 使用双缓冲机制:当DMA填充一半缓冲区时触发处理
- 将FFT等运算放在空闲任务中
- 开启STM32的FPU加速
4.2 卡尔曼滤波实现
为了平滑角度输出,我实现了一个简易的卡尔曼滤波器:
c复制typedef struct {
float angle; // 当前角度估计
float bias; // 角度偏差
float P[2][2]; // 误差协方差矩阵
} KalmanFilter;
void kalman_update(KalmanFilter *kf, float new_angle, float new_rate, float dt)
{
// 预测步骤
kf->angle += dt * (new_rate - kf->bias);
kf->P[0][0] += dt * (dt*kf->P[1][1] - kf->P[0][1] - kf->P[1][0] + Q_angle);
kf->P[0][1] -= dt * kf->P[1][1];
kf->P[1][0] -= dt * kf->P[1][1];
kf->P[1][1] += Q_bias * dt;
// 更新步骤
float y = new_angle - kf->angle;
float S = kf->P[0][0] + R_measure;
float K[2];
K[0] = kf->P[0][0] / S;
K[1] = kf->P[1][0] / S;
kf->angle += K[0] * y;
kf->bias += K[1] * y;
float P00_temp = kf->P[0][0];
float P01_temp = kf->P[0][1];
kf->P[0][0] -= K[0] * P00_temp;
kf->P[0][1] -= K[0] * P01_temp;
kf->P[1][0] -= K[1] * P00_temp;
kf->P[1][1] -= K[1] * P01_temp;
}
5. 实测效果与优化
5.1 性能指标
测试环境:2m×3m的普通房间,背景噪声约45dB
| 测试项 | 指标值 |
|---|---|
| 响应时间 | <200ms |
| 角度分辨率 | 1° |
| 有效距离 | 0.5-3m |
| 静态误差 | ±3° |
| 动态跟踪误差 | ±8° |
5.2 常见问题排查
-
定位结果跳动大
- 检查麦克风固定是否牢固
- 增加卡尔曼滤波的Q_angle参数
- 确认采样时钟同步良好
-
远距离检测失效
- 调整MAX9814增益至最大
- 在代码中提高相关峰值的检测阈值
- 检查环境反射声干扰
-
计算超时
- 优化FFT计算使用STM32 DSP库
- 降低采样率到6kHz
- 减少相关计算的点数
6. 进阶改进方向
在实际使用中发现几个可以提升的点:
-
多声源识别:
通过聚类分析处理多个相关峰,目前正在尝试实现 -
距离估计:
加入声压级检测算法,结合角度信息实现二维定位 -
低功耗模式:
当检测到持续无声音时自动进入休眠状态
这套系统最让我惊喜的是它的扩展性——通过修改麦克风阵列布局和算法参数,可以适应不同场景需求。比如将麦克风间距扩大到15cm,就能提升远距离定位精度(当然要相应调整算法参数)。