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

 数字麦克风采用MEMS技术,将声波信号转换为数字采样信号,由单芯片实现采样量化编码,一般而言数字麦克风的输出有PDM麦克风和PCM麦克风,由于PDM麦克风结构、工艺简单而大量应用,在使用中要注意这二者的区别,尤其是STM32 MCU的文档在I2S接口章节,对此区别含糊不清,比如采样率配置,WS管脚的用法,单声道双声道的配置,很多地方无法从文档中得到准确信息,需要一边摸索一边研究文档。本文就是对这些问题的一个研究笔记。

一、数字麦克风通信方式

数字麦克风管教很简单,如下面的图示。

    

     电源和地,时钟脚CLK,左右声道选择L/R,信号管教SD或DOUT。区别是WS管教,这个管教对数据采集和分辨率配置有很大影响。STM32芯片手册这样描述对应的这三个管教:

 

信号时序图如下:

 

   上面描述了STM32芯片关于I2S的麦克风的数据读取时序,而麦克风厂家如何处理输出信号,有自己的方式,这里就是I2S接口应用的第一个需要主要的点。

例子1:INMP441

INMP441是具有WS管脚的数字麦克风,它的时序图如下,立体声模式下,WS=0时输出左声道,WS=1时输出右声道。

单声道模式下,LR=0时一个WS周期内只有低电平输出信号,另外一半周期输出为高组,LR=1时输出时序相反。

 

例子2:MP45DT02

MP45DT02是没有WS管脚的数字麦克风,它的时序图如下,该器件根据LR电平,分别在CLK的低电平和高电平时间输出左右声道信号,实现信号的分时复用。

 

二、采样时钟配置

   如果用此类麦克风的单声道应用,就需要考虑数据采集在WS另外半休眠周期的影响。下面就是此麦克风的一个STM32F407芯片下的配置。

/* I2S2 init function */
void MX_I2S2_Init(void)
{
 hi2s2.Instance = SPI2;
 hi2s2.Init.Mode = I2S_MODE_MASTER_RX;
 hi2s2.Init.Standard = I2S_STANDARD_MSB;
 hi2s2.Init.DataFormat = I2S_DATAFORMAT_16B;
 hi2s2.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE;
 hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_16K;
 hi2s2.Init.CPOL = I2S_CPOL_LOW;
 hi2s2.Init.ClockSource = I2S_CLOCK_PLL;
 hi2s2.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_DISABLE;
 if (HAL_I2S_Init(&hi2s2) != HAL_OK)
 {
   Error_Handler();
 }
}

     该模式下,麦克风的输出信号图如下。由于LR=0,所以在WS低电平半周期输出信号,高电平半周期内无数据。可以发现CLK时钟为512khz,我们配置的是16bit数据模式,16khz采样,为什么CLK输出为512khz呢?原因是左右声道各自占用半个周期,当WS信号为16khz时,时钟频率是16bit*16khz*2=512khz,实际上单麦克风系统中有效采样时间只有一半,相当于一个采集周期内采集一半休息一半,其声音特性被这样采一半截取一半会有什么变化呢,需要试验验证。

 

    上面的问题是由WS管脚引起的,若选用无WS管脚的其他信号,比如MP34DT04,则是另一种时序,芯片手册时序图如下。

    如果LR管脚接地,则在CLK的低电平半周期输出信号,如果LR接VCC,则在CLK的高电平半周期输出信号,在一个数据线上实现双声道采集。

 

这种麦克风对于单声道应用,会在CLK一半时间内输出信号,另一半高组。在立体声应用中,使用一根数据线传输左右声道,分时复用传输信号。

 

    麦克风芯片要求的时钟频率范围一般是1—3Mhz,把上面代码的采样率设置为32Khz,则采集信号如下图。CLK时钟频率为1Mhz,为什么会出现这种情况呢?

 

     麦克风输出PDM信号,PDM调制器将缓冲模拟信号转换为串行脉冲密度调制信号。时钟输入( CLK)用于控制PDM调制器。常见的数字麦克风的时钟频率范围在1 MHz至3.25 MHz之间。该频率将定义生成离散时间表示( PDM比特流)的放大器模拟输出信号采样频率。同样的分析方法,ST芯片是按照双声道WS区分左右声道配置的芯片,那么WS低电平是左声道,高电平是右声道,因此软件配置一个16it采样的信号,一个周期需要2*16bit=32个时钟脉冲,而配置的采样率为32khz,这就使得CLK输出达到了32bit*32khz=1024khz,所以下面测量到clk为1.024Mhz。WS信号的频率为32Khz,就是采样率信号的频率。这里是数字麦克风计算PDM采样率的另一个易错点,上面的计算方法是对于有SW管脚的麦克风传感器而言的,对于没有SW管脚的麦克风芯片,则实际PDM采样率为STM32 MCU配置的分辨率的二倍。

 

 我的需求是音频特征点检测,所以把声音录制然后传输到PC上是首要任务,经过多次摸索,终于了解一二,现在分享出来方便后来者,恐水平有限有未发现的错误而误导他人,在文尾附上STM32 I2S和PDM采集相关的原厂资料以作参考。

     尽管内容庞杂,但争取化繁为简讲清楚数字麦克风单声道模式的PDM信号采集,PDM到PCM解码,以及上位机验证分析如python/matlab脚本读取录音文件和播放,频谱分析等。

1、  STM32 I2S接口标准和数据格式

      这些内容参考STM32芯片编程手册,基本上就是4个模式,以及16位32位数据格式,若使用SPI/I2S接口通信还要有MSB、LSB配置。

2、  STM32 I2S采样率的配置

    STM32的时钟配置和数据格式决定了音频信号的采样率,如下计算。在使用了ST CUBE MX后,下面的计算过程也省略了,直接配置传感器的数据格式、接口标准、采样率就可以了。

       上面是STM32芯片手册给出的信息,在做音频采集时候,发现这和实测效果相差较远,原来这组公式并不适合PDM信号的数字麦克风。PDM麦克风只是利用了STM32的I2S信号时钟和数据线,它的采样只是按照bit采样,每一个sample为1bit,要转换为类似于AD/DA的采样值,还需要进行PDM2PCM转换,ST给出了一个驱动包,可以进行类似转换,可以参考AM3998和UM2372手册。

       下面的公式才是PDM麦克风与STM32 I2S接口配合时使用的采样率计算方法,其中FS是PDM bit sample的采样率,DIV是PDM到PCM的抽样因子,经过抽样,把bit sample变为类似于模拟采样的sample。这里的Fs都是指采样sample的速率,虽然PDM输出是以u16或u32方式呈现的,但其采样率是按照bit计算的,一个sample只有1个bit。

    例如下面的配置,PDM采样率为32khz,单声道应用,那么PDM2PCM使用64抽取比,则真实的音频采样率为16*2*32khz/64 = 16khz。

复制代码

/* I2S2 init function */
void MX_I2S2_Init(void)
{
 hi2s2.Instance = SPI2;
 hi2s2.Init.Mode = I2S_MODE_MASTER_RX;
 hi2s2.Init.Standard = I2S_STANDARD_MSB;
 hi2s2.Init.DataFormat = I2S_DATAFORMAT_16B;
 hi2s2.Init.MCLKOutput = 
 I2S_MCLKOUTPUT_DISABLE;
 hi2s2.Init.AudioFreq = I2S_AUDIOFREQ_32K;
 hi2s2.Init.CPOL = I2S_CPOL_LOW;
 hi2s2.Init.ClockSource = I2S_CLOCK_PLL;
 hi2s2.Init.FullDuplexMode = 
 I2S_FULLDUPLEXMODE_DISABLE;
 if (HAL_I2S_Init(&hi2s2) != HAL_OK)
 {
    Error_Handler();
  }
}

复制代码

  此时PDM的时钟频率为32khz*16*2=1024khz,测量结果如下验证了该分析结论。

  STM32F407的I2S IP核是按照左右声道来处理音频信号的,所以其SW脚信号频率为32khz。

3、PDM和PCM信号

     PDM调制器将缓冲模拟信号转换为串行脉冲密度调制信号,时钟输入( CLK)用于控制PDM调制器。PDM信号无法直接驱动DA进行声音播放,PDM信号要变为声音信号还需要进行下采样,经过一次低通滤波和抽样,然后成为PCM信号。

     PDM是一种调制形式,用于表示数字域中的模拟信号。它是1位数字采样的高频数据流。在PDM信号中,脉冲的相对密度对应于模拟信号的幅度。大量的1s对应于高(正)幅度值,而大量的0s对应于低(负)幅度值,交替的1s和0s对应于幅度值0。

    PDM转为PCM信号,需要进行滤波和抽取。PDM信号采样率就是I2S的clk时钟频率,可见这是一个高频采样,按bit采样的信号。PCM信号是目标音频的采样率,比如高保真44khz,PDM2PCM的抽样因子M,则M=PDM频率/音频采样率。

   ST 提过了PDM2PCM的软件包,可以完成上面的工作。

   软件包源码没有开源,使用手册也简洁的让人抓狂,我觉得可能是因为ST更高级的MCU直接带了硬解码,所以对中低端MCU I2S接口的软解码关注度也不够。幸好之前做过信号处理工作,一些概念和内在逻辑能猜个八九不离十,使用起来没有任何难度就上手了,这个软件包使用时需要配置下面几个参数。

   1)初始化PDMFilter,包括采样率,低通高通滤波器截止频率,通道个数。

复制代码

typedef struct {
 uint16_t Fs;
 float LP_HZ;
 float HP_HZ;
 uint16_t Out_MicChannels;
 char InternalFilter[34];
} PDMFilter_InitStruct;

复制代码

 2)完成参数初始化后调用滤波器初始化函数。

Void PDM_Filter_Init (PDMFilter_InitStruct * Filter)

  3)最后调用下面函数完成PDM2PCM的抽取。

PDM_Filter_XX_XX(uint8_t* data, uint16_t* dataOut, uint16_t MicGain, PDMFilter_InitStruct * Filter)

   ST提供了多种PDM2PCM的抽取方法。

1

2

3

4

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

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

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

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

数据处理篇

  读取数字麦克风的信号,需要嵌入式驱动和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端接收到的文件为:

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

+ View Code

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

Probing PDM: MP45DT02 and STM32F407 - The Unterminated String

单片机程序。一些朋友私信我,调试出问题。

我的博客只是总结经验不是教程,所以不是什么都记,想起来当时我也是花了一些时间才发现问题的,可能确实有些坑。

我就把源码贴出来吧,可能主要问题是DMA的配置。尤其双DMA时候,需要手动启动I2S的接收DMA,HAL库没有这个接口,不看datasheet是找不到这个毛病的,这也是HAL库用多了引起的问题,一些特底层的问题大家都不愿意去搞了。

测试代码有点乱没整理。

复制代码

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2019 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "crc.h"
#include "dma.h"
#include "i2s.h"
//#include "pdm2pcm.h"
#include "usb_device.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usbd_cdc_if.h"
#include <stdio.h>
#include "pdm_filter.h"
#include "arm_math.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/*
---- 12khz
% Discrete-Time IIR Filter (real)                           
% -------------------------------                           
% Filter Structure    : Direct-Form I, Second-Order Sections
% Number of Sections  : 3                                   
% Stable              : Yes                                 
% Linear Phase        : No                                  

                                                           
SOS Matrix:                                                 
1  -2  1  1  1.195433962890738    0.69059892324149696       
1  -2  1  1  0.94280904158206325  0.33333333333333331       
1  -2  1  1  0.84028692165132668  0.18834516088404465       
                                                            
Scale Values:                                               
0.12379124008768976                                         
0.097631072937817504                                        
0.087014559808179473   


--- 2khz
% Generated by MATLAB(R) 8.3 and the Signal Processing Toolbox 6.21.
% Generated on: 23-Jul-2019 18:13:58

% Coefficient Format: Decimal

% Discrete-Time IIR Filter (real)                           
% -------------------------------                           
% Filter Structure    : Direct-Form I, Second-Order Sections
% Number of Sections  : 3                                   
% Stable              : Yes                                 
% Linear Phase        : No                                  

                                                           
SOS Matrix:                                                 
1  -2  1  1  -1.6812394272942188  0.81976044292731376       
1  -2  1  1  -1.454243586251585   0.57406191508395488       
1  -2  1  1  -1.349079994888392   0.46023366403769816       
                                                            
Scale Values:                                               
0.8752499675553832                                          
0.75707637533388505                                         
0.70232841473152252                                         
*/

#define numStages  3 // 6 order                 
#define FILTER_SAMPLES  2*160    
float32_t FilterDataIn[FILTER_SAMPLES]={0};
float32_t FilterDataOut[FILTER_SAMPLES]={0};

static float32_t testInput_f32_50Hz_200Hz[FILTER_SAMPLES]; /* input samping points */
static float32_t testOutput[FILTER_SAMPLES];               /* output  */
static float32_t IIRStateF32[4*numStages];                      /*  tmp buf=numTaps + blockSize - 1*/
                                                                             
#if 1 // 12khz hpf                                                                                                                                        
const float32_t IIRCoeffs32HP[5*numStages] = {
1.0f,  -2.0f,  1.0f,  -1.195433962890738f,    -0.69059892324149696f,        
1.0f,  -2.0f,  1.0f,  -0.94280904158206325f,  -0.33333333333333331f, 
1.0f,  -2.0f,  1.0f,  -0.84028692165132668f,  -0.18834516088404465f  
};
const float32_t ScaleValue  = 0.12379124008768976f  * 0.097631072937817504f * 0.087014559808179473f;
#else  // 2khz hpf, people can hear the sound, just high pitch.
const float32_t IIRCoeffs32HP[5*numStages] = {
1.0f,  -2.0f,  1.0f,  1.6812394272942188,  -0.81976044292731376,        
1.0f,  -2.0f,  1.0f,  1.454243586251585,   -0.57406191508395488, 
1.0f,  -2.0f,  1.0f,  1.349079994888392,   -0.46023366403769816  
};
const float32_t ScaleValue  = 0.8752499675553832f  * 0.75707637533388505f * 0.70232841473152252f;
#endif


void arm_copy_u162f32(
  int16_t * pSrc,
  float32_t * pDst,
  uint32_t blockSize)
{
  uint32_t blkCnt;                               /* loop counter */

#ifndef ARM_MATH_CM0

  /* Run the below code for Cortex-M4 and Cortex-M3 */

  /*loop Unrolling */
  blkCnt = blockSize >> 2u;

  /* First part of the processing with loop unrolling.  Compute 4 outputs at a time.   
   ** a second loop below computes the remaining 1 to 3 samples. */
  while(blkCnt > 0u)
  {
    /* C = A */
    /* Copy and then store the results in the destination buffer */
    *pDst++ = *pSrc++;
    *pDst++ = *pSrc++;
    *pDst++ = *pSrc++;
    *pDst++ = *pSrc++;

    /* Decrement the loop counter */
    blkCnt--;
  }

  /* If the blockSize is not a multiple of 4, compute any remaining output samples here.   
   ** No loop unrolling is used. */
  blkCnt = blockSize % 0x4u;

#else

  /* Run the below code for Cortex-M0 */

  /* Loop over blockSize number of values */
  blkCnt = blockSize;

#endif /* #ifndef ARM_MATH_CM0 */

  while(blkCnt > 0u)
  {
    /* C = A */
    /* Copy and then store the results in the destination buffer */
    *pDst++ = *pSrc++;

    /* Decrement the loop counter */
    blkCnt--;
  }
}


void arm_copy_f322u16(
  float32_t * pSrc,
  int16_t * pDst,
  uint32_t blockSize)
{
  uint32_t blkCnt;                               /* loop counter */

#ifndef ARM_MATH_CM0

  /* Run the below code for Cortex-M4 and Cortex-M3 */

  /*loop Unrolling */
  blkCnt = blockSize >> 2u;

  /* First part of the processing with loop unrolling.  Compute 4 outputs at a time.   
   ** a second loop below computes the remaining 1 to 3 samples. */
  while(blkCnt > 0u)
  {
    /* C = A */
    /* Copy and then store the results in the destination buffer */
    *pDst++ = (int16_t)*pSrc++;
    *pDst++ = (int16_t)*pSrc++;
    *pDst++ = (int16_t)*pSrc++;
    *pDst++ = (int16_t)*pSrc++;

    /* Decrement the loop counter */
    blkCnt--;
  }

  /* If the blockSize is not a multiple of 4, compute any remaining output samples here.   
   ** No loop unrolling is used. */
  blkCnt = blockSize % 0x4u;

#else

  /* Run the below code for Cortex-M0 */

  /* Loop over blockSize number of values */
  blkCnt = blockSize;

#endif /* #ifndef ARM_MATH_CM0 */

  while(blkCnt > 0u)
  {
    /* C = A */
    /* Copy and then store the results in the destination buffer */
    *pDst++ = (int16_t)*pSrc++;

    /* Decrement the loop counter */
    blkCnt--;
  }
}

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define PDM_2_PCM_SAM  (2*64)
#define PDM_SAM_POINTS  (2*640)
#define PCM_SAM_POINTS  (2*160)
typedef struct {
   int pdmIdx;
   int pcmIdx;
   uint16_t PDMBuf[2][PDM_SAM_POINTS];
   int16_t PCMBuf[PCM_SAM_POINTS];
}MicrophoneBufStruct;

MicrophoneBufStruct MicrophoreBuf = {0};
PDMFilter_InitStruct Filter;

void HAL_I2S_DMA_RxM0CpltCallback(DMA_HandleTypeDef *hdma);
void HAL_I2S_DMA_RxM1CpltCallback(DMA_HandleTypeDef *hdma);
void HAL_I2S_DMA_Rx_Error_CpltCallback(DMA_HandleTypeDef *hdma);
int UsbTxErr = 0;
uint8_t printBuf[PCM_SAM_POINTS*10]={0};
int tests = 0;


static int32_t idx = 0;
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_I2S2_Init();
  MX_USB_DEVICE_Init();
  MX_CRC_Init();
//  MX_PDM2PCM_Init();
  /* USER CODE BEGIN 2 */
   HAL_Delay(3000);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
    __HAL_RCC_GPIOB_CLK_ENABLE();
    //PB0 : FOR MEAS IIS SAMPING TIME
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_0;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    HAL_GPIO_WritePin( GPIOB, GPIO_PIN_0, 0);
  
  /* Filter LP & HP Init */ //Õâ¸öÂ˲¨Æ÷Ч¹û±È½Ï²î£¬×Ô¼ºÂ˲¨²ÅÐУ¬³öÏÖÔëÉùºÍÌø±ä¡£
    Filter.HP_HZ = 100; 
    Filter.LP_HZ = 15000;
    Filter.Fs = 32000;
    Filter.Out_MicChannels = 1;
    Filter.In_MicChannels = 1;
    PDM_Filter_Init((PDMFilter_InitStruct *)&Filter);
    
    hi2s2.hdmarx->XferCpltCallback = HAL_I2S_DMA_RxM0CpltCallback;
    hi2s2.hdmarx->XferM1CpltCallback = HAL_I2S_DMA_RxM1CpltCallback;
    hi2s2.hdmarx->XferErrorCallback = HAL_I2S_DMA_Rx_Error_CpltCallback;
    HAL_DMAEx_MultiBufferStart_IT(hi2s2.hdmarx, (uint32_t)&hi2s2.Instance->DR, 
                                  (uint32_t)&MicrophoreBuf.PDMBuf[0], (uint32_t)&MicrophoreBuf.PDMBuf[1], PDM_SAM_POINTS);
    HAL_I2S_DMAResume(&hi2s2);
    /* Enable Rx DMA Request */
    SET_BIT(hi2s2.Instance->CR2, SPI_CR2_RXDMAEN);
    
//    HAL_I2S_Receive_DMA (&hi2s2, MicrophoreBuf.PDMBuf[0], PDM_SAM_POINTS);
    static char printBuf[100]={0};
    static int cur_buf = 0;
    HAL_GPIO_WritePin( GPIOB, GPIO_PIN_0, 0);
    
    arm_biquad_casd_df1_inst_f32 S = {0};
    arm_biquad_cascade_df1_init_f32(&S, numStages, (float32_t *)&IIRCoeffs32HP[0], (float32_t *)&IIRStateF32[0]);
    while (1)
    {
        uint16_t *srcBuf = 0;
        int16_t *dstBuf = 0;
      
      
         if(MicrophoreBuf.pdmIdx == -1) continue;
          
          dstBuf = MicrophoreBuf.PCMBuf;
      
          srcBuf = MicrophoreBuf.PDMBuf[MicrophoreBuf.pdmIdx]; 
          MicrophoreBuf.pdmIdx = -1;      
          for(int i=0; i<PDM_SAM_POINTS; i++)
          {
            uint16_t a = srcBuf[i];
            srcBuf[i] = HTONS(a);
          }
          uint16_t volumeGain = 10;
          int num = 0;
          
           HAL_GPIO_WritePin( GPIOB, GPIO_PIN_0, 1);
          for(int i=0; i<PDM_SAM_POINTS; i=i+PDM_2_PCM_SAM)
          {
            num += PDM_Filter_64_LSB((uint8_t *)&srcBuf[i], (uint16_t *)&dstBuf[i/4], volumeGain , (PDMFilter_InitStruct *)&Filter);
          }
           HAL_GPIO_WritePin( GPIOB, GPIO_PIN_0, 0);

          // USE IIR FILTER HERE
       #if 1
          //HAL_GPIO_WritePin( GPIOB, GPIO_PIN_0, 1);
          arm_copy_u162f32(dstBuf, FilterDataIn, FILTER_SAMPLES);
          arm_biquad_cascade_df1_f32(&S, FilterDataIn, FilterDataOut, FILTER_SAMPLES);
          arm_scale_f32(FilterDataOut, ScaleValue, FilterDataOut, FILTER_SAMPLES);
          arm_copy_f322u16(FilterDataOut, dstBuf, FILTER_SAMPLES);
          //HAL_GPIO_WritePin( GPIOB, GPIO_PIN_0, 0);
        #endif
                  
       #if 0 // send raw data to pc
          if(USBD_OK != CDC_Transmit_FS((uint8_t *)dstBuf,  2*PCM_SAM_POINTS) )
          {
              UsbTxErr++;
//               HAL_Delay(1);
//               CDC_Transmit_FS((uint8_t *)dstBuf,  PCM_SAM_POINTS);
          }
        #endif  
          
          
        #if 1 // calc fire trigger.
          float32_t calcRst = 0.0f;
          uint8_t triggerFired = 0;
          HAL_GPIO_WritePin( GPIOB, GPIO_PIN_0, 1);
          for(int i=0; i<2; i++)
          {
            int offset = i*FILTER_SAMPLES/2;
            arm_abs_f32(FilterDataOut + offset, FilterDataOut + offset, FILTER_SAMPLES/2);
            arm_mean_f32(FilterDataOut+ offset, FILTER_SAMPLES/2, &calcRst);
            if(((int)calcRst >1700) && (triggerFired == 0))
            {
               triggerFired = 1;
               break;
            }
          }
          HAL_GPIO_WritePin( GPIOB, GPIO_PIN_0, 0);
          uint8_t cmd[] = { 2,0,0,0,  
                            0,0,0,0,  
                            3,0,0,0,  1, 6,7};
          cmd[12] = triggerFired<<7; // 0x80 null, 0x81 sound, 0x82 acc, 0x83 all.
          cmd[13] = (uint8_t)(((int)calcRst) & 0xff);    
          cmd[14] = (uint8_t)(((int)calcRst) >> 8);                              
          if(USBD_OK != CDC_Transmit_FS((uint8_t *)cmd,  sizeof(cmd) ) )
          {
              UsbTxErr++;
          }

         #endif
          

        
      }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
      
    /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

  /** Configure the main internal regulator output voltage 
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 8;
  RCC_OscInitStruct.PLL.PLLN = 336;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 7;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_I2S;
  PeriphClkInitStruct.PLLI2S.PLLI2SN = 369;
  PeriphClkInitStruct.PLLI2S.PLLI2SR = 6;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
static void DMA_SetConfig(DMA_HandleTypeDef *hdma, uint32_t SrcAddress, uint32_t DstAddress, uint32_t DataLength)
{
  /* Clear DBM bit */
  hdma->Instance->CR &= (uint32_t)(~DMA_SxCR_DBM);

  /* Configure DMA Stream data length */
  hdma->Instance->NDTR = DataLength;

  /* Memory to Peripheral */
  if((hdma->Init.Direction) == DMA_MEMORY_TO_PERIPH)
  {
    /* Configure DMA Stream destination address */
    hdma->Instance->PAR = DstAddress;

    /* Configure DMA Stream source address */
    hdma->Instance->M0AR = SrcAddress;
  }
  /* Peripheral to Memory */
  else
  {
    /* Configure DMA Stream source address */
    hdma->Instance->PAR = SrcAddress;

    /* Configure DMA Stream destination address */
    hdma->Instance->M0AR = DstAddress;
  }
}

void HAL_I2S_RxCpltCallback(I2S_HandleTypeDef *hi2s)
{
  uint16_t *srcBuf = MicrophoreBuf.PDMBuf[0];
  int16_t *dstBuf = MicrophoreBuf.PCMBuf;
  
//  HAL_I2S_Receive_DMA (&hi2s2, MicrophoreBuf.PDMBuf[0], PDM_SAM_POINTS);
  
//  if(idx == 0){
//    MicrophoreBuf.pdmIdx = 0;
//    //HAL_I2S_Receive_DMA (&hi2s2, (uint16_t*)MicrophoreBuf.PDMBuf[1], PDM_SAM_POINTS);
//  }
//  else if(idx % 2 == 1){
//    MicrophoreBuf.pdmIdx = 1;
//   // HAL_I2S_Receive_DMA (&hi2s2, (uint16_t*)MicrophoreBuf.PDMBuf[0], PDM_SAM_POINTS);
//  }
//  else{
//    MicrophoreBuf.pdmIdx = 0;
//   // HAL_I2S_Receive_DMA (&hi2s2, (uint16_t*)MicrophoreBuf.PDMBuf[1], PDM_SAM_POINTS);
//  }
//  idx++;
}

void HAL_I2S_DMA_RxM0CpltCallback(DMA_HandleTypeDef *hdma)
{
  MicrophoreBuf.pdmIdx = 0;
}


void HAL_I2S_DMA_RxM1CpltCallback(DMA_HandleTypeDef *hdma)
{
  MicrophoreBuf.pdmIdx = 1;
}

void HAL_I2S_DMA_Rx_Error_CpltCallback(DMA_HandleTypeDef *hdma)
{

}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{ 
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

复制代码

复制代码

/**
  ******************************************************************************
  * File Name          : I2S.c
  * Description        : This file provides code for the configuration
  *                      of the I2S instances.
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2019 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */

/* Includes ------------------------------------------------------------------*/
#include "i2s.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

I2S_HandleTypeDef hi2s2;
DMA_HandleTypeDef hdma_spi2_rx;

/* 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 = 64000;//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();
  }
}

void HAL_I2S_MspInit(I2S_HandleTypeDef* i2sHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(i2sHandle->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspInit 0 */

  /* USER CODE END SPI2_MspInit 0 */
    /* I2S2 clock enable */
    __HAL_RCC_SPI2_CLK_ENABLE();
  
    __HAL_RCC_GPIOC_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**I2S2 GPIO Configuration    
    PC3     ------> I2S2_SD
    PB10     ------> I2S2_CK
    PB12     ------> I2S2_WS 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /* I2S2 DMA Init */
    /* SPI2_RX Init */
    hdma_spi2_rx.Instance = DMA1_Stream3;
    hdma_spi2_rx.Init.Channel = DMA_CHANNEL_0;
    hdma_spi2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_spi2_rx.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_spi2_rx.Init.MemInc = DMA_MINC_ENABLE;
    hdma_spi2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_spi2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;//DMA_PDATAALIGN_HALFWORD;
    hdma_spi2_rx.Init.Mode =  DMA_CIRCULAR;
    //hdma_spi2_rx.Init.Mode =  DMA_NORMAL;
    hdma_spi2_rx.Init.Priority = DMA_PRIORITY_HIGH;
    hdma_spi2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_spi2_rx) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(i2sHandle,hdmarx,hdma_spi2_rx);

  /* USER CODE BEGIN SPI2_MspInit 1 */
    
  /* USER CODE END SPI2_MspInit 1 */
  }
}

void HAL_I2S_MspDeInit(I2S_HandleTypeDef* i2sHandle)
{

  if(i2sHandle->Instance==SPI2)
  {
  /* USER CODE BEGIN SPI2_MspDeInit 0 */

  /* USER CODE END SPI2_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_SPI2_CLK_DISABLE();
  
    /**I2S2 GPIO Configuration    
    PC3     ------> I2S2_SD
    PB10     ------> I2S2_CK
    PB12     ------> I2S2_WS 
    */
    HAL_GPIO_DeInit(GPIOC, GPIO_PIN_3);

    HAL_GPIO_DeInit(GPIOB, GPIO_PIN_10|GPIO_PIN_12);

    /* I2S2 DMA DeInit */
    HAL_DMA_DeInit(i2sHandle->hdmarx);
  /* USER CODE BEGIN SPI2_MspDeInit 1 */

  /* USER CODE END SPI2_MspDeInit 1 */
  }
} 

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

复制代码

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值