数字麦克风PDM信号采集与STM32 I2S接口应用(三)

  本文是数字麦克风笔记文章的数据处理篇。

  读取数字麦克风的信号,需要嵌入式驱动和PC应用的结合,驱动负责信号采集,应用代码负责声音分析。

  一般而言,在完成特征分析和实验之后,把优化过的代码固化到嵌入式端,实现目标应用。本文记录了分析过程的一些基本步骤。

1、ARM驱动

使用STM32F4芯片,驱动使用ST-CUBE MX生成,节约了大量的时间。

1)GPIO

2)I2S配置

目标是16khz音频采样,这里选择为32khz的I2S频率,原因上一篇文章已经阐述了,计算方法为32khz*2*16/64=16khz。

3)DMA配置

 

4)系统时钟配置

 

 5)I2S驱动编写

复制代码
/* I2S2 init function */
void MX_I2S2_Init(void)
{

  hi2s2.Instance = SPI2;
  hi2s2.Init.Mode = I2S_MODE_MASTER_RX;
  hi2s2.Init.Standard = I2S_STANDARD_PHILIPS;
  hi2s2.Init.DataFormat = I2S_DATAFORMAT_16B;
  hi2s2.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
  hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_32K;
  hi2s2.Init.CPOL = I2S_CPOL_HIGH;//I2S_CPOL_LOW;
  hi2s2.Init.ClockSource = I2S_CLOCK_PLL;
  hi2s2.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
  if (HAL_I2S_Init(&hi2s2) != HAL_OK)
  {
    Error_Handler();
  }
}
复制代码

6)PDM2PCM配置

  ST公司提供PDM2PCM的解码包,在cube下载驱动即可,使用方式参考UM2372。抽样因子选择64。

  由于PDM的采样率为32khz*2=64khz,而目标PCM信号的频率设定目标为16khz,所以4个PDM数据将产生1个PCM数据,所以申请内存空间时候需要注意这个比例关系。

  首先配置PDM函数的参数。

复制代码
PDMFilter_InitStruct Filter;
#define PDM_SAM_POINTS  (640) 
#define PCM_SAM_POINTS  (160) // 16khz频率,10ms数据160个采样点

/* Filter LP & HP Init */
Filter.LP_HZ = 7500;
Filter.HP_HZ = 100;
Filter.Fs = 16000;
Filter.Out_MicChannels = 1;
Filter.In_MicChannels = 1;
PDM_Filter_Init((PDMFilter_InitStruct *)&Filter);
复制代码

PDM函数驱动原型为:

int32_t PDM_Filter_64_LSB(uint8_t* data, uint16_t* dataOut, uint16_t MicGain,  PDMFilter_InitStruct * Filter);

其中因为PDM处理的是按byte处理,而STM32 DMA接收的数据是u16类型,所有需要颠倒下字节顺序。

for(int i=0; i<PDM_SAM_POINTS; i++)
{
  uint16_t a = srcBuf[i];
  srcBuf[i] = HTONS(a);
}

代码中调用驱动函数。

PDM_Filter_64_LSB((uint8_t *)&srcBuf[i], (uint16_t *)&dstBuf[i/4], volumeGain , (PDMFilter_InitStruct *)&Filter);

7)数据发送

if(USBD_OK != CDC_Transmit_FS((uint8_t *)dstBuf,  2*PCM_SAM_POINTS) )
{
  UsbTxErr++;
}

使用DMA接收数据,使用USB CDC传输。PC端接收到的文件为:

 

 (传输附件很不方便,直接复制数据到这里了,拷贝到本地重命名之后就可以运行后面的分析程序,听一听声音,保真度还是很高的,音频是一句海阔天空的歌)

2、声音播放

关键点有3:第一是接收的数据是byte型,恢复为short型。第二是sounddevice播放声音。第三是播放时候需要做归一化。

复制代码
# -*- coding:utf-8 -*-
from scipy import signal
import numpy as np
import matplotlib.pyplot  as plt

import matplotlib
myfont = matplotlib.font_manager.FontProperties(fname='C:\Windows\Fonts\simsun.ttc')

filename = r'16khz采样1.txt'
filepath = r'C:\Users\pwplu\Music'

f = open(filepath + '\\' + filename)
if 0:  # 这是ascii保存的数据格式
    line = f.read()
    data = [l.split(' ')[-1] for l in line.split('\n') if l]
    data = [int(x) for x in data]
else: # 这是HEX保存的数据格式
    line = f.read()[:-1]
    print("buffer count", len(line))
    data0 = np.array([int(x, base=16) for x in line.split(' ')])
    data_1 = data0[0:len(data0):2]
    data_2 = data0[1:len(data0):2]
    data3 = data_2 * 256 + data_1
    # data = np.frombuffer(data3, dtype=np.int16)
data = data3.astype(np.int16)

filename2 = filename + "_ascii.TXT"
f=open(filepath + '\\' + filename2, 'w')
np.savetxt(f, data, fmt="%i", newline='\n')

import sounddevice as sd
fs = 16000
src = data
sd.play(1*src/np.max(src), fs, blocking=True)
复制代码

3、滤波器和FFT频谱

数据源是上一节读取的data。使用高通滤波器滤波,然后对比滤波前后的频谱。

关键点是信号处理,滤波器的带宽选择,需要根据应用场景来实验后定。频谱显示只是为了直观反映处理结果。

在PC上作分析的目的是找到合适的算法,快速验证算法,搭建testbench反复测试,然后使用ST的math库实现信号检测。

复制代码
fc1 = 6000
fc2 = 0
wn1 = 2.0 * fc1 / fs
b, a = signal.butter(8, wn1, 'highpass')  # 配置滤波器 8 表示滤波器的阶数
filtedData = signal.filtfilt(b, a, data)  # data为要过滤的信号
filter_type = "高通 " + str(fc1) + "hz"

n = len(filtedData) # length of the signal
k = np.arange(n)
T = n/fs
frq = k/T # two sides frequency range
frq1 = frq[range(int(n/2))] # one side frequency range

# calc FFT, raw data and filter data
Y_raw = np.fft.fft(data)/n # fft computing and normalization 归一化
Y1_raw = Y_raw[range(int(n/2))] # fft computing and normalization 归一化
Y_filter = np.fft.fft(filtedData)/n # fft computing and normalization 归一化
Y1_filter = Y_filter[range(int(n/2))]
fig, ax = plt.subplots(4, 1)

ax[0].set_title("滤波-"+ filter_type + " " + filename.replace(".TXT", "") , fontproperties = myfont)
ax[0].plot(k, data, 'R', label = "raw data") # plotting the spectrum
ax[0].set_xlabel('time')
ax[0].set_ylabel('|raw Mag|')
ax[0].legend()
ax[0].legend(loc='upper right')

ax[1].plot(k, filtedData, 'R', label = "filter data") # plotting the spectrum
ax[1].set_xlabel('time')
ax[1].set_ylabel('|filter Mag|')
ax[1].legend()
ax[1].legend(loc='upper right')

Y1_raw = Y1_raw[1:n//2]
ax[2].plot(frq1[1:n//2], abs(Y1_raw),'G', label = "raw fft") # plotting the spectrum
ax[2].set_xlabel('Y1_raw Freq (Hz)')
ax[2].set_ylabel('|raw Y(freq)|')
ax[2].legend()
ax[2].legend(loc='upper right')

Y1_filter = Y1_filter[1:n//2]
ax[3].plot(frq1[1:n//2], abs(Y1_filter),'G', label = "filter fft") # plotting the spectrum
ax[3].set_xlabel('Freq (Hz)')
ax[3].set_ylabel('|filter Y(freq)|')
ax[3].legend()
ax[3].legend(loc='upper right')

plt.legend()
plt.show()
print('finished')
复制代码

 

参考文档:

1、Probing PDM: MP45DT02 and STM32F407

https://www.theunterminatedstring.com/probing-pdm/

 

 


数字麦克风PDM信号采集与STM32 I2S接口应用--笔记目录:

数字麦克风PDM信号采集与STM32 I2S接口应用(一)

https://www.cnblogs.com/pingwen/p/11298675.html

数字麦克风PDM信号采集与STM32 I2S接口应用(二)

https://www.cnblogs.com/pingwen/p/11301935.html

数字麦克风PDM信号采集与STM32 I2S接口应用(三)

https://www.cnblogs.com/pingwen/p/11794081.html

数字麦克风PDM转PCM与STM32 I2S接口应用----重要文档列表

https://www.cnblogs.com/pingwen/p/11302452.html

数字麦克风PDM信号采集与STM32 I2S接口应用(四)--单片机源码

https://www.cnblogs.com/pingwen/p/13371144.html

尊重原创技术文章,转载请注明。

https://www.cnblogs.com/pingwen/p/11794081.html

 

posted on   啊哈彭  阅读(11682)  评论(5编辑  收藏  举报

编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示