数字麦克风PDM信号采集与STM32 I2S接口应用 您所在的位置:网站首页 pdm调制器 数字麦克风PDM信号采集与STM32 I2S接口应用

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

2024-07-17 06:19| 来源: 网络整理| 查看: 265

 数字麦克风采用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> 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; iCR &= (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 * * © Copyright (c) 2019 STMicroelectronics. * All rights reserved. * * 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****/

复制代码



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

      专题文章
        CopyRight 2018-2019 实验室设备网 版权所有