【正点原子探索者STM32F407开发板例程连载+教学】第48章 音乐播放器实验

2019-07-20 06:31发布

第四十八章 音乐播放器实验

[mw_shl_code=c,true]1.硬件平台:正点原子探索者STM32F407开发板 2.软件平台:MDK5.1 3.固件库版本:V1.4.0[/mw_shl_code]
ALIENTEK探索者STM32F4开发板拥有全双工I2S,且外扩了一颗HIFICODEC芯片:WM8978G,支持最高192K 24BIT的音频播放,并且支持录音(下一章介绍)。本章,我们将利用探索者STM32F4开发板实现一个简单的音乐播放器(仅支持WAV播放)。本章分为如下几个部: 48.1 WAV&WM8978&I2S简介 48.2 硬件设计 48.3 软件设计 48.4 下载验证  

48.1 WAV&WM8978&I2S简介 

本章新知识点比较多,包括:WAVWM8978I2S等三个知识点。下面我们将分别向大家介绍。

48.1.1 WAV简介

WAVWAVE文件,WAV是计算机领域最常用的数字化声音文件格式之一,它是微软专门为Windows系统定义的波形文件格式(Waveform Audio),由于其扩展名为"*.wav"。它符合RIFF(Resource Interchange File Format)文件规范,用于保存Windows平台的音频信息资源,被Windows平台及其应用程序所广泛支持,该格式也支持MSADPCMCCITT A LAW等多种压缩运算法,支持多种音频数字,取样频率和声道,标准格式化的WAV文件和CD格式一样,也是44.1K的取样频率,16位量化数字,因此在声音文件质量和CD相差无几! WAV一般采用线性PCM(脉冲编码调制)编码,本章,我们也主要讨论PCM的播放,因为这个最简单。 WAV是由若干个Chunk组成的。按照在文件中的出现位置包括:RIFF WAVE Chunk、 Format Chunk、 Fact Chunk(可选)和 Data Chunk。每个Chunk由块标识符、数据大小和数据三部分组成,如图48.1.1.1所示:
 图48.1.1.1 Chunk结构示意图        其中块标识符由4ASCII码构成,数据大小则标出紧跟其后的数据的长度(单位为字节),注意这个长度不包含块标识符和数据大小的长度,即不包含最前面的8个字节。所以实际Chunk的大小为数据大小加8 首先,我们来看看RIFF块(RIFF WAVE Chunk),该块以“RIFF”作为标示,紧跟wav文件大小(该大小是wav文件的总大小-8),然后数据段为“WAVE”,表示是wav文件。RIFF块的Chunk结构如下: //RIFF typedef __packed struct {     u32 ChunkID;             //chunk id;这里固定为"RIFF",0X46464952     u32 ChunkSize ;            //集合大小;文件总大小-8     u32 Format;                //格式;WAVE,0X45564157 }ChunkRIFF ; 接着,我们看看Format块(Format Chunk),该块以“fmt ”作为标示(注意有个空格!),一般情况下,该段的大小为16个字节,但是有些软件生成的wav格式,该部分可能有18个字节,含有2个字节的附加信息。Format块的Chunk结构如下: //fmt typedef __packed struct {     u32 ChunkID;             //chunk id;这里固定为"fmt ",0X20746D66     u32 ChunkSize ;            //子集合大小(不包括IDSize);这里为:20.     u16 AudioFormat;        //音频格式;0X10,表示线性PCM;0X11表示IMA ADPCM        u16 NumOfChannels;    //通道数量;1,表示单声道;2,表示双声道;        u32 SampleRate;           //采样率;0X1F40,表示8Khz        u32 ByteRate;               //字节速率;        u16 BlockAlign;            //块对齐(字节);        u16 BitsPerSample;              //单个采样数据大小;4ADPCM,设置为4 }ChunkFMT;  接下来,我们再看看Fact块(Fact Chunk),该块为可选块,以“fact”作为标示,不是每个WAV文件都有,在非PCM格式的文件中,一般会在Format结构后面加入一个Fact块,该块Chunk结构如下: //fact typedef __packed struct {     u32 ChunkID;                    //chunk id;这里固定为"fact",0X74636166;     u32 ChunkSize ;                 //子集合大小(不包括IDSize);这里为:4.     u32 DataFactSize;               //数据转换为PCM格式后的大小 }ChunkFACT; DataFactSize是这个Chunk中最重要的数据,如果这是某种压缩格式的声音文件,那么从这里就可以知道他解压缩后的大小。对于解压时的计算会有很大的好处!不过本章我们使用的是PCM格式,所以不存在这个块。 最后,我们来看看数据块(Data Chunk),该块是真正保存wav数据的地方,以“data'作为该Chunk的标示,然后是数据的大小。数据块的Chunk结构如下: //data typedef __packed struct {     u32 ChunkID;             //chunk id;这里固定为"data",0X61746164     u32 ChunkSize ;            //子集合大小(不包括IDSize);文件大小-60. }ChunkDATA; ChunkSize后紧接着就是wav数据。根据Format Chunk中的声道数以及采样bit数,wav数据的bit位置可以分成如表48.1.1.1所示的几种形式: 单声道 取样1 取样2 取样3 取样4 取样5 取样6 8位量化 声道0 声道0 声道0 声道0 声道0 声道0 双声道 取样1 取样2 取样3 8位量化 声道0() 声道1() 声道0() 声道1() 声道0() 声道1() 单声道 取样1 取样2 取样3 16位量化 声道0
(
低字节)
声道0
(
高字节)
声道0
(
低字节)
声道0
(
高字节)
声道0
(
低字节)
声道0
(
高字节)
双声道 取样1 取样2 16位量化 声道0 声道0 声道1 声道1 声道0 声道0 (低字节) (高字节) (低字节) (高字节) (低字节) (高字节) 单声道 取样1 取样2 24位量化 声道0
(
低字节)
声道0
(
中字节)
声道0
(
高字节)
声道0
(
低字节)
声道0
(
中字节)
声道0
(
高字节)
双声道 取样1 24位量化 声道0 声道0 声道0 声道1 声道1 声道1 (低字节) (中字节) (高字节) (低字节) (中字节) (高字节)48.1.1.1 WAVE文件数据采样格式        本章,我们播放的音频支持:16位和24位,立体声,所以每个取样为4/6个字节,低字节在前,高字节在后。在得到这些wav数据以后,通过I2S丢给WM8978,就可以欣赏音乐了。

48.1.2 WM8978简介

WM8978是欧胜(Wolfson)推出的一款全功能音频处理器。它带有一个HI-FI级数字信号处理内核,支持增强3D硬件环绕音效,以及5频段的硬件均衡器,可以有效改善音质;并有一个可编程的陷波滤波器,用以去除屏幕开、切换等噪音。 WM8978同样集成了对麦克风的支持,以及用于一个强悍的扬声器功放,可提供高达900mW的高质量音响效果扬声器功率。 一个数字回放限制器可防止扬声器声音过载。WM8978进一步提升了耳机放大器输出功率,在推动16欧姆耳机的时候,每声道最大输出功率高达40毫瓦!可以连接市面上绝大多数适合随身听的高端HI-FI耳机。 WM8988的主要特性有:I2S接口,支持最高192K,24bit音频播放DAC信噪比98dBADC信噪比90dB ●支持无电容耳机驱动(提供40mW@16Ω的输出能力) ●支持扬声器输出(提供0.9W@8Ω的驱动能力) ●支持立体声差分输入/麦克风输入 ●支持左右声道音量独立调节 ●支持3D效果,支持5EQ调节 WM8978的控制通过I2S接口(即数字音频接口)同MCU进行音频数据传输(支持音频接收和发送),通过两线(MODE=0,即IIC接口)或三线(MODE=1)接口进行配置。WM8978I2S接口,由4个引脚组成: 1,ADCDAT:ADC数据输出 2,DACDATDAC数据输入 3,LRC:数据左/右对齐时钟 4,BCLK:位时钟,用于同步 WM8978可作为I2S主机,输出LRCBLCK时钟,不过我们一般使用WM8978作为从机,接收LRCBLCK。另外,WM8978I2S接口支持5中不同的音频数据模式:左(MSB)对齐标准、右(LSB)对齐标准、飞利浦(I2S)标准、DSP模式ADSP模式B。本章,我们用飞利浦标准来传输I2S数据。 飞利浦(I2S)标准模式,数据在跟随LRC传输的BCLK的第二个上升沿时传输MSB,其他位一直到LSB按顺序传输。传输依赖于字长、BCLK频率和采样率,在每个采样的LSB和下一个采样的MSB之间都应该有未用的BCLK周期。飞利浦标准模式的I2S数据传输协议如图48.1.2.1所示:  48.1.2.1 飞利浦标准模式I2S数据传输图     图中,fs即音频信号的采样率,比如44.1Khz,因此可以知道,LRC的频率就是音频信号的采样率。另外,WM8978还需要一个MCLK,本章我们采用STM32F4为其提供MCLK时钟,MCLK的频率必须等于256fs,也就是音频采样率的256倍。     WM8978的框图如图48.1.2.2所示:  48.1.2.2 WM8978框图 从上图可以看出,WM8978内部有很多的模拟开关,用来选择通道,同时还有很多调节器,用来设置增益和音量。 本章,我们通过IIC接口(MODE=0)连接WM8978,不过WM8978IIC接口比较特殊:1,只支持写,不支持读数据;2,寄存器长度为7位,数据长度为9位。3,寄存器字节的最低位用于传输数据的最高位(也就是9位数据的最高位,7位寄存器的最低位)。WM8978IIC地址固定为:0X1A。关于WM8978IIC详细介绍,请看其数据手册第77页。 这里我们简单介绍一下要正常使用WM8978来播放音乐,应该执行哪些配置。 1,寄存器R000h),该寄存器用于控制WM8978的软复位,写任意值到该寄存器地址,即可实现软复位WM8978 2,寄存器R101h),该寄存器主要要设置BIASENbit3),该位设置为1,模拟部分的放大器才会工作,才可以听到声音。 3,寄存器R202h),该寄存器要设置ROUT1EN(bit8)LOUT1EN(bit7)SLEEP(bit6)等三个位,ROUT1ENLOUT1EN,设置为1,使能耳机输出,SLEEP设置为0,进入正常工作模式。 4,寄存器R303h),该寄存器要设置LOUT2EN(bit6)ROUT2EN(bit5)RMIXER(bit3)LMIXER(bit2)DACENR(bit1)DACENL(bit0)6个位。LOUT2ENROUT2EN,设置为1,使能喇叭输出;LMIXERRMIXER设置为1,使能左右声道混合器;DACENLDACENR则是使能左右声道的DAC了,必须设置为1 5,寄存器R404h),该寄存器要设置WL(bit6:5)FMT(bit4:3)4个位。WL(bit6:5)用于设置字长(即设置音频数据有效位数),00表示16位音频,10表示24位音频;FMT(bit4:3)用于设置I2S音频数据格式(模式),我们一般设置为10,表示I2S格式,即飞利浦模式。 6,寄存器R606h),该寄存器我们直接全部设置为0即可,设置MCLKBCLK都来自外部,即由STM32F4提供。 7,寄存器R100Ah),该寄存器我们要设置SOFTMUTE(bit6)DACOSR128(bit3)等两个位,SOFTMUTE设置为0,关闭软件静音;DACOSR128设置为1DAC得到最好的SNR 8,寄存器R432Bh),该寄存器我们只需要设置INVROUT21即可,反转ROUT2输出,更好的驱动喇叭。 9,寄存器R4931h),该寄存器我们要设置SPKBOOST(bit2)TSDEN(bit1)这两个位。SPKBOOST用于设置喇叭的增益,我们默认设置为0就好了(gain=-1),如想获得更大的声音,设置为1gain=+1.5)即可;TSDEN用于设置过热保护,设置为1(开启)即可。 10,寄存器R5032h)和R5133h),这两个寄存器设置类似,一个用于设置左声道(R50),另外一个用于设置右声道(R51)。我们只需要设置这两个寄存器的最低位为1即可,将左右声道的DAC输出接入左右声道混合器里面,才能在耳机/喇叭听到音乐。 11,寄存器R5234h)和R5335h),这两个寄存器用于设置耳机音量,同样一个用于设置左声道(R52),另外一个用于设置右声道(R53)。这两个寄存器的最高位(HPVU)用于设置是否更新左右声道的音量,最低6位用于设置左右声道的音量,我们可以先设置好两个寄存器的音量值,最后设置其中一个寄存器最高位为1,即可更新音量设置。 12,寄存器R5436h)和R5537h),这两个寄存器用于设置喇叭音量,同R52R53设置一模一样,这里就不细说了。 以上,就是我们用WM8978播放音乐时的设置,按照以上所述,对各个寄存器进行相应的配置,即可使用WM8978正常播放音乐了。还有其他一些3D设置,EQ设置等,我们这里就不再介绍了,大家参考WM8978的数据手册自行研究下即可。

48.1.3 I2S简介

I2S(Inter IC Sound)总线, 又称集成电路内置音频总线,是飞利浦公司为数字音频设备之间的音频数据传输而制定的一种总线标准,该总线专责于音频设备之间的数据传输,广泛应用于各种多媒体系统。它采用了沿独立的导线传输时钟与数据信号的设计,通过将数据和时钟信号分离,避免了因时差诱发的失真,为用户节省了购买抵抗音频抖动的专业设备的费用。 STM32F4自带了2个全双工I2S接口,其特点包括: ●支持全双工/半双工通信 ●主持主/从模式设置8位可编程线性预分频器,可实现精确的音频采样频率(8~192Khz) ●支持16/24/32位数据格式 ●数据包帧固定为16位(仅16位数据帧)或32位(可容纳16/24/32位数据帧) ●可编程时钟极性 ●支持MSB对齐(左对齐)、LSB对齐(右对齐)、飞利浦标准和PCM标准等I2S协议 ●支持DMA数据传输(16位宽) ●数据方向固定位MSB在前 ●支持主时钟输出(固定为256*fsfs即音频采样率) STM32F4的I2S框图如图48.1.3.1所示:  48.1.3.1 I2S框图 STM32F4I2S是与SPI部分共用的,通过设置SPI_I2SCFGR寄存器的I2SMOD位即可开启I2S功能,I2S接口使用了几乎与SPI相同的引脚、标志和中断。 I2S用到的信号有: 1SD:串行数据(映射到 MOSI 引脚),用于发送或接收两个时分复用的数据通道上的数据(仅半双工模式)。 2WS:字选择(映射到NSS引脚),即帧时钟,用于切换左右声道的数据。WS频率等于音频信号采样率(fs)。 3CK:串行时钟(映射到SCK引脚),即位时钟,是主模式下的串行时钟输出以及从模式下的串行时钟输入。CK频率=WS频率(fs*2*1616位宽),如果是32位宽,则是:CK频率=WS频率(fs*2*3232位宽) 4I2S2ext_SDI2S3ext_SD:用于控制I2S全双工模式的附加引脚(映射到MISO引脚)。 5MCK:即主时钟输出,当I2S配置为主模式(并且SPI_I2SPR寄存器中的MCKOE位置1)时,使用此时钟,该时钟输出频率 256×fsfs即音频信号采样频率(fs)。 为支持I2S全双工模式,除了I2S2I2S3,还可以使用两个额外的I2S,它们称为扩展I2SI2S2_extI2S3_ext),如图48.1.3.2  48.1.3.2 I2S全双工框图 因此,第一个I2S全双工接口基于I2S2I2S2_ext,第二个基于I2S3I2S3_ext。注意:I2S2_extI2S3_ext仅用于全双工模式。 I2Sx可以在主模式下工作。因此: 1,只有I2Sx可在半双工模式下输出SCKWS 2,只有I2Sx可在全双工模式下向I2S2_extI2S3_ext提供SCKWS 扩展I2S (I2Sx_ext)只能用于全双工模式。I2Sx_ext始终在从模式下工作。I2SxI2Sx_ext 均可用于发送和接收。 STM32F4I2S支持4种数据和帧格式组合,分别是:1,将16位数据封装在16位帧中;2,将16位数据封装在32位帧中;3,将24位数据封装在32位帧中;4,将32位数据封装在32位帧中。16位数据封装在32位帧中时,前16(MSB)为有效位,16LSB被强制清零,无需任何软件操作或DMA请求(只需一个读/写操作)。如果应用程序首选DMA,则24位和32位数据帧需要对SPI_DR执行两次CPU读取或写入操作,或者需要两次DMA操作。24位的数据帧,硬件会将8位非有效位扩展到带有0位的32位。 对于所有数据格式和通信标准而言,始终会先发送最高有效位(MSB优先)。 STM32F4的I2S支持:MSB对齐(左对齐)标准、LSB对齐(右对齐)标准、飞利浦标准和PCM标准等4种音频标准,本章我们用飞利浦标准,仅针对该标准进行介绍,其他的请大家参考《STM32F4xx中文参考手册》第27.4节。 I2S飞利浦标准,使用WS信号来指示当前正在发送的数据所属的通道。该信号从当前通道数据的第一个位(MSB)之前的一个时钟开始有效。发送方在时钟信号(CK)的下降沿改变数据,接收方在上升沿读取数据。WS信号也在CK的下降沿变化。这和我们48.1.2节介绍的是一样的。 本章我们使用16/24位数据格式,16位时采用扩展帧格式(即将16位数据封装在32位帧中),以24位帧为例,I2S波形(飞利浦标准)如图48.1.3.3所示:  48.1.3.3 I2S飞利浦标准24位帧格式波形 这个图和图48.1.2.1是一样的时序,在24位模式下数据传输,需要对SPI_DR执行两次读取或写入操作。比如我们要发送0X8EAA33这个数据,就要分两次写入SPI_DR,第一次写入:0X8EAA,第二次写入0X33xxxx可以为任意数值),这样就把0X8EAA33发送出去了。 顺便说一下SD卡读取到的24WAV数据流,是低字节在前,高字节在后的,比如,我们读到一个声道的数据(24bit),存储在buf[3]里面,那么要通过SPI_DR发送这个24位数据,过程如下: SPI_DR=((u16)buf[2]<<8)+buf[1]; SPI_DR=(u16)buf[0]<<8;     这样,第一次发送高16为数据,第二次发送低8位数据,完成一次24bit数据的发送。     接下来,我们介绍下STM32F4I2S时钟发生器,其架构如图48.1.3.4所示:  48.1.3.4 I2S时钟发生器架构 图中I2SxCLK可以来自PLLI2S输出(通过R系数分频)或者来自外部时钟(I2S_CKIN引脚),一般我们使用前者作为I2SxCLK输入时钟。        一般我们需要根据音频采样率(fs,即CK的频率)来计算各个分频器的值,常用的音频采样率有:22.05Khz44.1Khz48Khz96Khz196Khz等。        根据是否使能MCK输出,fs频率的计算公式有2种情况。不过,本章只考虑MCK输出使能时的情况,当MCK输出使能时,fs频率计算公式如下: fs=I2SxCLK/[256*(2*I2SDIV+ODD)]        其中:I2SxCLK=(HSE/pllm)*PLLI2SN/PLLI2SRHSE我们是8Mhz,而pllm在系统时钟初始化就确定了,是8,这样结合以上2式,可得计算公式如下: fs= (1000*PLLI2SN/PLLI2SR )/[256*(2*I2SDIV+ODD)]        fs单位是:Khz。其中:PLL2SN取值范围:192~432PLLI2SR取值范围:2~7I2SDIV取值范围:2~255ODD取值范围:0/1。根据以上约束条件,我们便可以根据fs来设置各个系数的值了,不过很多时候,并不能取得和fs一模一样的频率,只能近似等于fs,比如44.1Khz采样率,我们设置PLL2SN=271PLL2SR=2I2SDIV=6ODD=0,得到fs=44.108073Khz,误差为:0.0183%。晶振频率决定了有时无法通过分频得到我们所要的fs,所以,某些fs如果要实现0误差,大家必须得选用外部时钟才可以。 如果要通过程序去计算这些系数的值,是比较麻烦的,所以,我们事先计算好常用fs对应的系数值,建立一个表,这样,用的时候,只需要查表取值就可以了,大大简化了代码,常用fs对应系数表如下: //表格式:采样率/10,PLLI2SN,PLLI2SR,I2SDIV,ODD const u16 I2S_PSC_TBL[][5]= {        {800 ,256,5,12,1},        //8Khz采样率        {1102,429,4,19,0},        //11.025Khz采样率        {1600,213,2,13,0},              //16Khz采样率        {2205,429,4, 9,1},        //22.05Khz采样率        {3200,213,2, 6,1},        //32Khz采样率        {4410,271,2, 6,0},        //44.1Khz采样率        {4800,258,3, 3,1},        //48Khz采样率        {8820,316,2, 3,1},        //88.2Khz采样率        {9600,344,2, 3,1},       //96Khz采样率        {17640,361,2,2,0},      //176.4Khz采样率        {19200,393,2,2,0},      //192Khz采样率 };        有了上面的fs-系数对应表,我们可以很方便的完成I2S的时钟配置。        接下来,我们看看本章需要用到的一些相关寄存器。        首先,是SPI_I2S配置寄存器:SPI_I2SCFGR,该寄存器各位描述如图48.1.3.5所示:  48.1.3.5 寄存器SPI_I2SCFGR各位描述 I2SMOD位,设置为1,选择I2S模式,注意,必须在I2S/SPI禁止的时候,设置该位。 I2SE位,设置为1,使能I2S外设,该位必须在I2SMOD位设置之后再设置。 I2SCFG[1:0]位, 这两个位用于配置I2S模式,设置为10,选择主模式(发送)。 I2SSTD[1:0]位,这两个位用于选择I2S标准,设置为00,选择飞利浦模式。 CKPOL位,用于设置空闲时时钟电平,设置为0,空闲时时钟低电平。 DATLEN[1:0]位,用于设置数据长度,00,表示16位数据;01表示24位数据。 CHLEN位,用于设置通道长度,即帧长度,0,表示16位;1,表示32位。 第二个是SPI_I2S预分配器寄存器:SPI_I2SPR,该寄存器各位描述如图48.1.3.6所示:  48.1.3.6 寄存器SPI_ I2SPR各位描述 本章我们设置MCKOE1,开启MCK输出,ODDI2SDIV则根据不同的fs,查表进行设置。 第三个是PLLI2S配置寄存器:RCC_PLLI2SCFGR,该寄存器各位描述如图48.1.3.7所示:  48.1.3.7 寄存器RCC_ PLLI2SCFGR各位描述 该寄存器用于配置PLLI2SRPLLI2SN两个系数,PLLI2SR的取值范围是:2~7PLLI2SN的取值范围是:192~432。同样,这两个也是根据fs的值来设置的。 此外,还要用到SPI_CR2寄存器的bit1位,设置I2S TX DMA数据传输,SPI_DR寄存器用于传输数据,本章用DMA来传输,所以直接设置DMA的外设地址位SPI_DR即可。 最后,我们看看要通过STM32F4I2S,驱动WM8978播放音乐的简要步骤。这里需要说明一下,I2S相关的库函数申明和定义跟SPI是同文件的,在stm32f4xx_spi.c以及头文件stm32f4xx_spi.h中。具体步骤如下: 1)初始化WM8978 这个过程就是在48.1.2节最后那十几个寄存器的配置,包括软复位、DAC设置、输出设置和音量设置等。在我们实验工程中是在文件wm8978.c中,大家可以打开实验工程参考。 2)初始化I2S       此过程主要设置SPI_I2SCFGR寄存器,设置I2S模式、I2S标准、时钟空闲电平和数据帧长等,最后开启I2S TX DMA,使能I2S外设。        在库函数中初始化I2S调用的函数为: void I2S_Init(SPI_TypeDef* SPIx, I2S_InitTypeDef* I2S_InitStruct);        第一个参数比较好理解,我们来着重看下第二个参数,这里我们主要讲解结构体I2S_InitTypeDef各个成员变量的含义。结构体I2S_InitTypeDef的定义为:   typedef struct {   uint16_t I2S_Mode;      uint16_t I2S_Standard;     uint16_t I2S_DataFormat;    uint16_t I2S_MCLKOutput;     uint32_t I2S_AudioFreq;   uint16_t I2S_CPOL; }I2S_InitTypeDef; 第一个参数用来设置I2S的模式,也就是设置SPI_I2SCFGR寄存器的I2SCFG相关位。可以配置为主模式发送I2S_Mode_MasterTx,主模式接受I2S_Mode_MasterRx,从模式发送I2S_Mode_SlaveTx以及从模式接受I2S_Mode_SlaveRx四种模式。 第二个参数I2S_Standard用来设置I2S标准,这个前面已经讲解过。可以设置为:飞利浦标准I2S_Standard_Phillips,MSB对齐标准I2S_Standard_MSBLSB对齐标准I2S_Standard_LSB以及PCM标准I2S_Standard_PCMShort 第三个参数I2S_DataFormat用来设置I2S的数据通信格式。这里实际包含设置SPI_I2SCFGR寄存器的HCLEN位(通道长度)以及DATLEN位(传输的数据长度)。当我们设置为16位标准格式I2S_DataFormat_16b的时候,实际上传输的数据长度为16位,通道长度为16位。当我们设置为其他值的时候,通道长度都为32位。        第四个参数I2S_MCLKOutput用来设置是否使能主时钟输出。我们实验会使能主时钟输出。        第五个参数I2S_AudioFreq用来设置I2S频率。实际根据输入的频率值,会来计算SPI预分频寄存器SPI_I2SPR的预分频奇数因子以及I2S线性预分频器的值。这里支持10中频率: #define I2S_AudioFreq_192k               ((uint32_t)192000) #define I2S_AudioFreq_96k                ((uint32_t)96000) #define I2S_AudioFreq_48k                ((uint32_t)48000) #define I2S_AudioFreq_44k                ((uint32_t)44100) #define I2S_AudioFreq_32k                ((uint32_t)32000) #define I2S_AudioFreq_22k                ((uint32_t)22050) #define I2S_AudioFreq_16k                ((uint32_t)16000) #define I2S_AudioFreq_11k                ((uint32_t)11025) #define I2S_AudioFreq_8k                 ((uint32_t)8000) #define I2S_AudioFreq_Default            ((uint32_t)2)        第六个参数I2S_CPOL用来设置空闲状态时钟电平,这个比较好理解。取值为高电平I2S_CPOL_High以及低电平I2S_CPOL_Low 3)解析WAV文件,获取音频信号采样率和位数并设置I2S时钟分频器        这里,要先解析WAV文件,取得音频信号的采样率(fs)和位数(16位或32位),根据这两个参数,来设置I2S的时钟分频,这里我们用前面介绍的查表法来设置即可。这是我们单独写了一个设置频率的函数为I2S2_SampleRate_Set,我们后面程序章节会讲解。 4)设置DMA        I2S播放音频的时候,一般都是通过DMA来传输数据的,所以必须配置DMA,本章我们用I2S2,其TX是使用的DMA1数据流4的通道0来传输的。并且,STM32F4DMA具有双缓冲机制,这样可以提高效率,大大方便了我们的数据传输,本章将DMA1数据流4设置为:双缓冲循环模式,外设和存储器都是16位宽,并开启DMA传输完成中断(方便填充数据)。DMA具体配置过程请参考我们光盘工程代码,前面DMA实验我们已经讲解过DMA相关配置过程。 5)编写DMA传输完成中断服务函数        为了方便填充音频数据,我们使用DMA传输完成中断,每当一个缓冲数据发送完后,硬件自动切换为下一个缓冲,同时进入中断服务函数,填充数据到发送完的这个缓冲。过程如图48.1.3.8所示:
48.1.3.8 DMA双缓冲发送音频数据流框图 6)开启DMA传输,填充数据        最后,我们就只需要开启DMA传输,然后及时填充WAV数据到DMA的两个缓存区即可。此时,就可以在WM8978的耳机和喇叭通道听到所播放音乐了。操作方法为: DMA_Cmd(DMA1_Stream4,ENABLE);//开启DMA TX传输,开始播放 

48.2 硬件设计

本章实验功能简介:开机后,先初始化各外设,然后检测字库是否存在,如果检测无问题,则开始循环播放SD卡MUSIC文件夹里面的歌曲(必须在SD卡根目录建立一个MUSIC文件夹,并存放歌曲(仅支持wav格式)在里面),在TFTLCD上显示歌曲名字、播放时间、歌曲总时间、歌曲总数目、当前歌曲的编号等信息。KEY0用于选择下一曲,KEY2用于选择上一曲,KEY_UP用来控制暂停/继续播放。DS0还是用于指示程序运行状态。 本实验用到的资源如下: 1)  指示灯DS0 2)  三个按键(KEY_UP/KEY0/KEY1 3)  串口 4)  TFTLCD模块 5)  SD 6)  SPI FLASH 7)  WM8978 8)  I2S2   这些硬件我们都已经介绍过了,不过WM8978STM32F4的连接,还没有介绍,连接如图48.2.1所示:
48.2.1 WM8978STM32F4连接原理图        图中,PHONE接口,可以用来插耳机,P1接口,可以外接喇叭(1W@8Ω,需自备)。硬件上,IIC接口和24C02MPU6050等共用,另外I2S_MCLKDCMI_D0共用,所以I2SDCMI不可以同时使用。 本实验,大家需要准备1SD卡(在里面新建一个MUSIC文件夹,并存放一些wav歌曲在MUSIC文件夹下)和一个耳机(或喇叭),分别插入SD卡接口和耳机接口(喇叭接P1接口),然后下载本实验就可以通过耳机来听歌了。

48.3 软件设计

打开本章实验工程目录可以看到,我们在工程根目录文件夹下新建APPAUDIOCODEC两个文件夹。在APP文件夹里面新建了audioplay.caudioplay.h两个文件。在AUDIOCODEC文件夹里面新建了wav文件夹,然后在其中新建了wavplay.cwavplay.h两个文件。同时,我们把相关的源文件引入工程相应分组,同时将APPwav文件夹加入头文件包含路径。 然后,我们在HARDWARE文件夹下新建了WM8978I2S两个文件夹,在WM8978文件夹里面新建了wm8978.cwm8978.h两个文件,在I2S文件夹里面新建了i2s.ci2s.h两个文件。 最后将wm8978.ci2s.c添加到工程HARDWARE组下。同时相应的头文件加入到PATH中。 本章代码比较多,我们就不全部贴出来给大家介绍了,这里仅挑一些重点函数给大家介绍下。首先是i2s.c里面,重点函数代码如下: //I2S2初始化 //参数I2S_Standard: @ref SPI_I2S_Standard  I2S标准, //参数I2S_Mode: @ref SPI_I2S_Mode //参数I2S_Clock_Polarity    @ref SPI_I2S_Clock_Polarity: //参数I2S_DataFormat@ref SPI_I2S_Data_Format : void I2S2_Init(u16 I2S_Standard,u16 I2S_Mode,u16 I2S_Clock_Polarity,u16 I2S_DataFormat) {     I2S_InitTypeDef I2S_InitStructure;               RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//使能SPI2时钟        RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,ENABLE); //复位SPI2        RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,DISABLE);//结束复位          I2S_InitStructure.I2S_Mode=I2S_Mode;//IIS模式        I2S_InitStructure.I2S_Standard=I2S_Standard;//IIS标准        I2S_InitStructure.I2S_DataFormat=I2S_DataFormat;//IIS数据长度        I2S_InitStructure.I2S_MCLKOutput=I2S_MCLKOutput_Disable;//主时钟输出禁止        I2S_InitStructure.I2S_AudioFreq=I2S_AudioFreq_Default;//IIS频率设置        I2S_InitStructure.I2S_CPOL=I2S_Clock_Polarity;//空闲状态时钟电平        I2S_Init(SPI2,&I2S_InitStructure);//初始化IIS          SPI_I2S_DMACmd(SPI2,SPI_I2S_DMAReq_Tx,ENABLE);//SPI2 TX DMA请求使能.     I2S_Cmd(SPI2,ENABLE);//SPI2 I2S EN使能 } //采样率计算公式:Fs=I2SxCLK/[256*(2*I2SDIV+ODD)] //I2SxCLK=(HSE/pllm)*PLLI2SN/PLLI2SR //一般HSE=8Mhz //pllm:Sys_Clock_Set设置的时候确定,一般是8 //PLLI2SN:一般是192~432 //PLLI2SR:2~7 //I2SDIV:2~255 //ODD:0/1 //I2S分频系数表@pllm=8,HSE=8Mhz,vco输入频率为1Mhz //表格式:采样率/10,PLLI2SN,PLLI2SR,I2SDIV,ODD const u16 I2S_PSC_TBL[][5]= { ……//省略部分代码,见48.1.3节介绍  };  //设置IIS的采样率(@MCKEN) //samplerate:采样率,单位:Hz //返回值:0,设置成功;1,无法设置. u8 I2S2_SampleRate_Set(u32 samplerate) {        u8 i=0;        u32 tempreg=0;        samplerate/=10;//缩小10                 for(i=0;i<(sizeof(I2S_PSC_TBL)/10);i++)//看看改采样率是否可以支持        {               if(samplerate==I2S_PSC_TBL[0])break;        }        RCC_PLLI2SCmd(DISABLE);//先关闭PLLI2S        if(i==(sizeof(I2S_PSC_TBL)/10))return 1;//搜遍了也找不到        RCC_PLLI2SConfig((u32)I2S_PSC_TBL[1],(u32)I2S_PSC_TBL[2]); //设置I2SxCLK的频率(x=2)  设置PLLI2SN PLLI2SR        RCC->CR|=1<<26;                             //开启I2S时钟        while((RCC->CR&1<<27)==0);          //等待I2S时钟开启成功.        tempreg=I2S_PSC_TBL[3]<<0;       //设置I2SDIV        tempreg|=I2S_PSC_TBL[3]<<8;      //设置ODD        tempreg|=1<<9;                                 //使能MCKOE,输出MCK        SPI2->I2SPR=tempreg;               //设置I2SPR寄存器        return 0; //I2S2 TX DMA配置 //设置为双缓冲模式,并开启DMA传输完成中断 //buf0:M0AR地址. //buf1:M1AR地址. //num:每次传输数据量 void I2S2_TX_DMA_Init(u8* buf0,u8 *buf1,u16 num) NVIC_InitTypeDef   NVIC_InitStructure; DMA_InitTypeDef  DMA_InitStructure;          RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1时钟使能        DMA_DeInit(DMA1_Stream4); while (DMA_GetCmdStatus(DMA1_Stream4) != DISABLE){}//等待可配置                 /* 配置 DMA Stream */   DMA_InitStructure.DMA_Channel = DMA_Channel_0;  //通道0 SPI2_TX通道   DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI2->DR;//外设地址   DMA_InitStructure.DMA_Memory0BaseAddr = (u32)buf0;//DMA 存储器0地址   DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存储器到外设模式   DMA_InitStructure.DMA_BufferSize = num;//数据传输量   DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设非增量模式   DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式   DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; //外设数据长度:16   DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; //存储器数据长度:16   DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;// 使用循环模式   DMA_InitStructure.DMA_Priority = DMA_Priority_High;//高优先级   DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //不使用FIFO模式          DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;   DMA_InitStructure.DMA_MemoryBu
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
36条回答
gongrong816
2019-07-21 08:09
原子哥,最近在使用探索者开发板的音乐播放器例程,发现两个问题。
1,我试了16bit、24bit和采样率在32k~192k的各种wav音乐。发现播放24bit,192k的音乐有杂音很大,24bit,96k也有但少一点。其他bit和采样率的组合暂时没发现有音质上的问题(32bit没试)。用示波器看采样率、比特率、MCK有一定误差,但应该问题不大。虽说STM32f407最大支持192k,32bit,但是否对一些数据量比较大的时候407I2S处理会有问题,比如192k,24bit的wav音乐。
2,对24bit数据流处理的一段代码不理解。
        for(i=0;i<size;)

    {

         buf[i++]=p[1]; buf=p[2];

         i+=2; buf[i++]=p[0];  

         p+=3;

     }
    看了下wav的存储格式,24bit是低字节+中字节+高字节吗,为什么发送数据时不能直接发送高字节+中字节+低字节。即代码改成:
     for(i=0;i<size;)

    {
        //按照I2S协议,先发MSB

         buf=p[2];
         buf[i+1]=p[1];
         buf[i+2]=p[0];

         i+=3;  

         p+=3;

     }


一周热门 更多>