第一次接触SIPEED麦克风阵列时,最让我头疼的就是硬件连接问题。这个阵列包含4个数字麦克风,采用MSM261S4030H0芯片,支持标准的I2S接口。但STM32F407的硬件I2S外设资源有限,我们需要用GPIO模拟I2S时序。
接线时要注意几个关键点:
具体引脚配置如下表示:
| SIPEED引脚 | STM32F407引脚 | 功能说明 |
|---|---|---|
| LED_CA | GPIOB7 | LED时钟 |
| LED_DA | GPIOB8 | LED数据 |
| MIC_D0 | GPIOB3 | 麦克风0数据 |
| MIC_D1 | GPIOB4 | 麦克风1数据 |
| MIC_D2 | GPIOB5 | 麦克风2数据 |
| MIC_D3 | GPIOB6 | 麦克风3数据 |
| MIC_WS | GPIOG14 | 声道选择 |
| MIC_CK | GPIOG15 | 时钟信号 |
| VIN | 3.3V | 电源输入 |
| GND | GND | 地线 |
实际调试中发现,如果时钟信号质量不好,会导致数据采样错位。我最初用杜邦线连接时经常出现数据异常,后来改用屏蔽线后稳定性大幅提升。另外,GPIOG14和GPIOG15最好配置为推挽输出模式,驱动能力更强。
硬件I2S外设用起来方便,但引脚固定且资源有限。当需要同时驱动多个设备时,软件模拟是更灵活的选择。飞利浦标准的I2S协议主要有三个信号:
对于24位音频数据,每个声道需要32个时钟周期(24位数据+8位填充)。模拟时序时要注意:
以下是关键代码实现:
c复制// 读取左声道数据
u32 Sipeed_read_left(u8 DMIC) {
u32 data = 0;
for(u8 i=1; i<=32; i++) {
SIPEED_MIC_SCK = 0;
SIPEED_MIC_WS = 0;
delay_us(1); // 保持低电平时间
if(i>=2 && i<=25) { // 有效数据位
if(DMIC == 0) data |= SIPEED_MIC_D0 << (25-i);
else if(DMIC == 1) data |= SIPEED_MIC_D1 << (25-i);
else if(DMIC == 2) data |= SIPEED_MIC_D2 << (25-i);
}
SIPEED_MIC_SCK = 1;
delay_us(1); // 保持高电平时间
}
return data;
}
实测中发现,delay_us(1)产生的时序在168MHz主频下实际约为1.2μs。如果需要更精确的时序,可以考虑使用定时器中断或者DMA方式。另外,24位数据在STM32中是按有符号数处理的,最高位为符号位,这点在后续MATLAB处理时要特别注意。
MSM261S4030H0输出的24位数据让很多初学者困惑。经过反复测试,我总结了几个关键点:
在STM32端,可以这样判断声音强度:
c复制if(data > 0xF72000) { // 阈值判断
Sipeed_Led_SendData(1, 255, 0, 0, 10); // 红色LED
} else {
Sipeed_Led_SendData(1, 0, 255, 0, 10); // 绿色LED
}
传输到MATLAB时,要注意串口发送的是十进制字符串。我最初直接发送二进制数据,导致MATLAB解析错误。正确的做法是:
c复制printf("%d\n", data); // 以十进制格式发送
在MATLAB端,需要将接收到的数据还原为24位有符号数。这里有个坑:MATLAB默认处理的是双精度浮点数,直接转换会导致精度丢失。我的解决方案是:
matlab复制function data = B2Qw(~, X)
len = strlength(X);
p = str2double(X(1)) * (-1) * power(2, len-1);
for i=2:len
p = p + str2double(X(i)) * power(2, len-i);
end
data = p;
end
对于声强显示,我建议先对原始数据做归一化处理,将24位有符号数映射到[-1,1]范围,这样波形显示会更直观。
MATLAB上位机开发主要分为三部分:串口通信、数据处理和实时显示。经过多次迭代,我总结出一个稳定的实现方案。
首先创建App Designer应用,主要组件包括:
串口通信的关键代码如下:
matlab复制function uart_reciveButtonPushed(app, event)
app.plot_flag = 1;
while app.plot_flag
temp = read(app.uart_scom, 1, "char");
if temp ~= 10 // 不是换行符
data = data * 10 + temp - 48; // 拼接数字
else // 收到完整数据帧
if data > 0x800000
data = B2Qw(app, dec2bin(data)); // 负数处理
end
app.uart_recieve_data = [app.uart_recieve_data; data];
app.x_tick = [app.x_tick; app.x_tick(end)+0.01];
// 更新波形显示
plot(app.UIAxes, app.x_tick(2:end), app.uart_recieve_data);
// 计算分贝值
db_value = 20*log10(abs(data));
app.uart_recieve_db = [app.uart_recieve_db; db_value];
plot(app.UIAxes_2, app.x_tick(2:end), app.uart_recieve_db);
data = 0; // 重置数据缓存
end
pause(0.001); // 防止CPU占用过高
end
end
实际调试中遇到几个典型问题:
对于分贝计算,标准的公式是20×log10(振幅/参考值)。参考值可以根据环境噪声水平调整,我一般取0x100000作为基准。如果想更专业,可以加入A计权滤波,模拟人耳频率响应。