OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 31942|回复: 39

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

[复制链接]

230

主题

1950

帖子

10

精华

论坛元老

Rank: 8Rank: 8

积分
4562
金钱
4562
注册时间
2010-12-14
在线时间
32 小时
发表于 2014-12-9 12:25:29 | 显示全部楼层 |阅读模式

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

[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_MemoryBurst = DMA_MemoryBurst_Single;

//外设突发单次传输

  DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;

//存储器突发单次传输

  DMA_Init(DMA1_Stream4, &DMA_InitStructure);//初始化DMA Stream

      

DMA_DoubleBufferModeConfig(DMA1_Stream4,(u32)buf1,DMA_Memory_0);

//双缓冲模式配置

  DMA_DoubleBufferModeCmd(DMA1_Stream4,ENABLE);//双缓冲模式开启

  DMA_ITConfig(DMA1_Stream4,DMA_IT_TC,ENABLE);//开启传输完成中断

      

NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream4_IRQn;

  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;//抢占优先级0

  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;//响应优先级0

  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能外部中断通道

  NVIC_Init(&NVIC_InitStructure);//配置

//I2S DMA回调函数指针

void (*i2s_tx_callback)(void);      //TX回调函数

//DMA1_Stream4中断服务函数

void DMA1_Stream4_IRQHandler(void)

{     

       if(DMA_GetITStatus(DMA1_Stream4,DMA_IT_TCIF4)==SET)//传输完成标志

       {

              DMA_ClearITPendingBit(DMA1_Stream4,DMA_IT_TCIF4);

          i2s_tx_callback();   //执行回调函数,读取数据等操作在这里面处理 

       }                                                                                              

    其中,I2S2_Init完成I2S2的初始化,通过4个参数设置I2S2的详细配置信息。另外一个函数:I2S2_SampleRate_Set,则是用前面介绍的查表法,根据音频采样率来设置I2S的时钟部分。函数I2S2_TX_DMA_Init,用于设置I2S2DMA发送,使用双缓冲循环模式,发送数据给WM8978,并开启了发送完成中断。而DMA1_Stream4_IRQHandler函数,则是DMA1数据流4发送完成中断的服务函数,该函数调用i2s_tx_callback函数(函数指针,使用前需指向特定函数)实现DMA数据填充。在i2s.c里面,还有2个函数:I2S_Play_StartI2S_Play_Stop,用于开启和关闭DMA传输,这里我们没贴出来了,请大家参考光盘本例程源码。

       再来看wm8978.c里面的几个函数,代码如下:

//WM8978初始化

//返回值:0,初始化正常

//    其他,错误代码

u8 WM8978_Init(void)

{

       u8 res;

       GPIO_InitTypeDef  GPIO_InitStructure;

      

       RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB|RCC_AHB1Periph_GPIOC,

ENABLE);  //使能外设GPIOB,GPIOC时钟

       //PB12/13 复用功能输出

       GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 | GPIO_Pin_13;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能

    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz

    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉

    GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化

      

       //PC2/PC3/PC6复用功能输出

       GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3|GPIO_Pin_6;

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能

    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz

    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉

    GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化

      

       GPIO_PinAFConfig(GPIOB,GPIO_PinSource12,GPIO_AF_SPI2);//PB12, I2S_LRCK

       GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_SPI2);//PB13 I2S_SCLK

       GPIO_PinAFConfig(GPIOC,GPIO_PinSource3,GPIO_AF_SPI2);//PC3,I2S_DACDATA

       GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_SPI2);//PC6,AF5 I2S_MCK

       GPIO_PinAFConfig(GPIOC,GPIO_PinSource2,GPIO_AF6_SPI2);//PC2,I2S_ADCDATA 

IIC_Init();//初始化IIC接口

       res=WM8978_Write_Reg(0,0);     //软复位WM8978

       if(res)return 1;                                   //发送指令失败,WM8978异常

       //以下为通用设置

       WM8978_Write_Reg(1,0X1B);     //R1,MICEN设置为1(MIC使能),BIASEN设置为1

//(模拟器工作),VMIDSEL[1:0]设置为:11(5K)

       WM8978_Write_Reg(2,0X1B0);   //R2,ROUT1,LOUT1输出使能(耳机可以工作)

//,BOOSTENR,BOOSTENL使能

       WM8978_Write_Reg(3,0X6C);     //R3,LOUT2,ROUT2,喇叭输出,RMIX,LMIX使能    

       WM8978_Write_Reg(6,0);           //R6,MCLK由外部提供

       WM8978_Write_Reg(43,1<<4);    //R43,INVROUT2反向,驱动喇叭

       WM8978_Write_Reg(47,1<<8);    //R47设置,PGABOOSTL,左通道MIC获得20倍增益

       WM8978_Write_Reg(48,1<<8);    //R48设置,PGABOOSTR,右通道MIC获得20倍增益

       WM8978_Write_Reg(49,1<<1);    //R49,TSDEN,开启过热保护

       WM8978_Write_Reg(10,1<<3);    //R10,SOFTMUTE关闭,128x采样,最佳SNR

       WM8978_Write_Reg(14,1<<3);    //R14,ADC 128x采样率

       return 0;

}

//WM8978 DAC/ADC配置

//adcen:adc使能(1)/关闭(0)

//dacen:dac使能(1)/关闭(0)

void WM8978_ADDA_Cfg(u8 dacen,u8 adcen)

{

       u16 regval;

       regval=WM8978_Read_Reg(3);    //读取R3

       if(dacen)regval|=3<<0;                //R3最低2个位设置为1,开启DACR&DACL

       else regval&=~(3<<0);                //R3最低2个位清零,关闭DACR&DACL.

       WM8978_Write_Reg(3,regval);    //设置R3

       regval=WM8978_Read_Reg(2);    //读取R2

       if(adcen)regval|=3<<0;                //R2最低2个位设置为1,开启ADCR&ADCL

       else regval&=~(3<<0);                //R2最低2个位清零,关闭ADCR&ADCL.

       WM8978_Write_Reg(2,regval);    //设置R2      

}

//WM8978 输出配置

//dacenAC输出(放音)开启(1)/关闭(0)

//bpsen:Bypass输出(录音,包括MIC,LINE IN,AUX)开启(1)/关闭(0)

void WM8978_Output_Cfg(u8 dacen,u8 bpsen)

{

       u16 regval=0;

       if(dacen)regval|=1<<0;  //DAC输出使能

       if(bpsen)

       {

              regval|=1<<1;        //BYPASS使能

              regval|=5<<2;        //0dB增益

       }

       WM8978_Write_Reg(50,regval);//R50设置

       WM8978_Write_Reg(51,regval);//R51设置

}

//设置I2S工作模式

//fmt:0,LSB(右对齐);1,MSB(左对齐);2,飞利浦标准I2S;3,PCM/DSP;

//len:0,16;1,20;2,24;3,32

void WM8978_I2S_Cfg(u8 fmt,u8 len)

{

       fmt&=0X03;

       len&=0X03;//限定范围

       WM8978_Write_Reg(4,(fmt<<3)|(len<<5));       //R4,WM8978工作模式设置      

}    

以上代码WM8978_Init用于初始化WM8978,这里只是通用配置(ADC&DAC),初始化之后,并不能正常播放音乐,还需要通过WM8978_ADDA_Cfg函数,使能DAC,然后通过WM8978_Output_Cfg选择DAC输出,通过WM8978_I2S_Cfg配置I2S工作模式,最后设置音量才可以接收I2S音频数据,实现音乐播放。这里设置音量、EQ、音效等函数,没有贴出了,请大家参考光盘本例程源码。

接下来,看看wavplay.c里面的几个函数,代码如下:

__wavctrl wavctrl;         //WAV控制结构体

vu8 wavtransferend=0;   //i2s传输完成标志

vu8 wavwitchbuf=0;      //i2sbufx指示标志

//WAV解析初始化

//fname:文件路径+文件名

//wavx:wav 信息存放结构体指针

//返回值:0,成功;1,打开文件失败;2,WAV文件;3,DATA区域未找到.

u8 wav_decode_init(u8* fname,__wavctrl* wavx)

{

       FIL*ftemp; u32 br=0;

       u8 *buf; u8 res=0;  

       ChunkRIFF *riff; ChunkFMT *fmt;

       ChunkFACT *fact; ChunkDATA *data;

       ftemp=(FIL*)mymalloc(SRAMIN,sizeof(FIL));

       buf=mymalloc(SRAMIN,512);

       if(ftemp&&buf)     //内存申请成功

       {

              res=f_open(ftemp,(TCHAR*)fname,FA_READ);//打开文件

              if(res==FR_OK)

              {

                     f_read(ftemp,buf,512,&br);   //读取512字节在数据

                     riff=(ChunkRIFF *)buf;        //获取RIFF

                     if(riff->Format==0X45564157)//WAV文件

                     {

                            fmt=(ChunkFMT *)(buf+12);       //获取FMT

                            fact=(ChunkFACT *)(buf+12+8+fmt->ChunkSize);//读取FACT

                            if(fact->ChunkID==0X74636166||fact->ChunkID==0X5453494C)

wavx->datastart=12+8+fmt->ChunkSize+8+fact->ChunkSize;

//具有fact/LIST块的时候(未测试)

                            else wavx->datastart=12+8+fmt->ChunkSize; 

                            data=(ChunkDATA *)(buf+wavx->datastart);       //读取DATA

                            if(data->ChunkID==0X61746164)//解析成功!

                            {

                                   wavx->audioformat=fmt->AudioFormat;     //音频格式

                                   wavx->nchannels=fmt->NumOfChannels;    //通道数

                                   wavx->samplerate=fmt->SampleRate;         //采样率

                                   wavx->bitrate=fmt->ByteRate*8;                //得到位速

                                   wavx->blockalign=fmt->BlockAlign;          //块对齐

                                   wavx->bps=fmt->BitsPerSample;                //位数,16/24/32       

                                   wavx->datasize=data->ChunkSize;              //数据块大小

                                   wavx->datastart=wavx->datastart+8;           //数据流开始的地方.

                            }else res=3;//data区域未找到.

                     }else res=2;//wav文件

              }else res=1;//打开文件错误

       }

       f_close(ftemp);

       myfree(SRAMIN,ftemp); myfree(SRAMIN,buf); //释放内存

       return 0;

}

//填充buf

//buf:数据区

//size:填充数据量

//bits:位数(16/24)

//返回值:读到的数据个数

u32 wav_buffill(u8 *buf,u16 size,u8 bits)

{

       u16 readlen=0; u32 bread;

       u16 i; u8 *p;

       if(bits==24)//24bit音频,需要处理一下

       {

              readlen=(size/4)*3;                                           //此次要读取的字节数

              f_read(audiodev.file,audiodev.tbuf,readlen,(UINT*)&bread);     //读取数据

              p=audiodev.tbuf;

              for(i=0;i<size;)

              {

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

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

                     p+=3;

              }

              bread=(bread*4)/3;        //填充后的大小.

       }else

       {

              f_read(audiodev.file,buf,size,(UINT*)&bread);//16bit音频,直接读取数据 

              if(bread<size) for(i=bread;i<size-bread;i++)buf=0;//不够数据了,补充0

       }

       return bread;

//WAV播放时,I2S DMA传输回调函数

void wav_i2s_dma_tx_callback(void)

{  

       u16 i;

       if(DMA1_Stream4->CR&(1<<19))

       {

              wavwitchbuf=0;

              if((audiodev.status&0X01)==0) //暂停

              for(i=0;i<WAV_I2S_TX_DMA_BUFSIZE;i++)audiodev.i2sbuf1=0;//0

       }else

       {

              wavwitchbuf=1;

              if((audiodev.status&0X01)==0) //暂停

              for(i=0;i<WAV_I2S_TX_DMA_BUFSIZE;i++)audiodev.i2sbuf2=0;//0

       }

       wavtransferend=1;

}

//播放某个WAV文件

//fname:wav文件路径.

//返回值:

//KEY0_PRES:下一曲

//KEY1_PRES:上一曲

//其他:错误

u8 wav_play_song(u8* fname)

{

       u8 key; u8 t=0; u8 res; u32 fillnum;   

       audiodev.file=(FIL*)mymalloc(SRAMIN,sizeof(FIL));

       audiodev.i2sbuf1=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);

       audiodev.i2sbuf2=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);

       audiodev.tbuf=mymalloc(SRAMIN,WAV_I2S_TX_DMA_BUFSIZE);

       if(audiodev.file&&audiodev.i2sbuf1&&audiodev.i2sbuf2&&audiodev.tbuf)

       {

              res=wav_decode_init(fname,&wavctrl);//得到文件的信息

              if(res==0)//解析文件成功

              {

                     if(wavctrl.bps==16)

                     {

                            WM8978_I2S_Cfg(2,0);       //飞利浦标准,16位数据长度

I2S2_Init(I2S_Standard_Phillips,I2S_Mode_MasterTx,I2S_CPOL_Low,

I2S_DataFormat_16bextended);

//飞利浦标准,主机发送,时钟低电平,16位扩展帧长度

                     }else if(wavctrl.bps==24)

                     {

                            WM8978_I2S_Cfg(2,2);       //飞利浦标准,24位数据长度

                            I2S2_Init(I2S_Standard_Phillips,I2S_Mode_MasterTx,I2S_CPOL_Low,

I2S_DataFormat_24b);//飞利浦标准,主机发送,时钟低,24位扩展帧长度

                     }

                     I2S2_SampleRate_Set(wavctrl.samplerate);//设置采样率

                     I2S2_TX_DMA_Init(audiodev.i2sbuf1,audiodev.i2sbuf2,

WAV_I2S_TX_DMA_BUFSIZE/2); //配置TX DMA

                  i2s_tx_callback=wav_i2s_dma_tx_callback;//回调函数指wav_i2s_dma_callback

                     audio_stop();

                     res=f_open(audiodev.file,(TCHAR*)fname,FA_READ);//打开文件

                     if(res==0)

                     {

                            f_lseek(audiodev.file, wavctrl.datastart);//跳过文件头

fillnum=wav_buffill(audiodev.i2sbuf1,WAV_I2S_TX_DMA_BUFSIZE,

wavctrl.bps);                   

fillnum=wav_buffill(audiodev.i2sbuf2,WAV_I2S_TX_DMA_BUFSIZE,

wavctrl.bps);

                            audio_start(); 

                            while(res==0)

                            {

                                   while(wavtransferend==0);//等待wav传输完成;

                                   wavtransferend=0;

                                   if(fillnum!=WAV_I2S_TX_DMA_BUFSIZE)//播放结束?

                                   { res=KEY0_PRES; break; }

     if(wavwitchbuf)fillnum=wav_buffill(audiodev.i2sbuf2,

WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf2

                                   else fillnum=wav_buffill(audiodev.i2sbuf1,

WAV_I2S_TX_DMA_BUFSIZE,wavctrl.bps);//填充buf1

                                   while(1)

                                   {

                                          key=KEY_Scan(0);

                                          if(key==WKUP_PRES)//暂停

                                          {

                                                 if(audiodev.status&0X01)audiodev.status&=~(1<<0);

                                                 else audiodev.status|=0X01; 

                                          }

                                          if(key==KEY2_PRES||key==KEY0_PRES)//下一曲/上一曲

                                          { res=key; break; }

                                          wav_get_curtime(audiodev.file,&wavctrl);//得到播放和总时间

                                          audio_msg_show(wavctrl.totsec,wavctrl.cursec,wavctrl.bitrate);

                                          t++;

                                          if(t==20) { t=0; LED0=!LED0; }

                                          if((audiodev.status&0X01)==0)delay_ms(10);

                                          else break;

                                   }

                            }

                            audio_stop();

                     }else res=0XFF;

              }else res=0XFF;

       }else res=0XFF;

       myfree(SRAMIN,audiodev.tbuf); myfree(SRAMIN,audiodev.file);           //释放内存

       myfree(SRAMIN,audiodev.i2sbuf1); myfree(SRAMIN,audiodev.i2sbuf2);       //释放内存

       return res;

}

以上,wav_decode_init函数,用来对wav文件进行解析,得到wav的详细信息(音频采样率,位数,数据流起始位置等);wav_buffill函数,用f_read读取数据,填充数据到buf里面,注意24位音频的时候,读出的数据需要经过转换后才填充到bufwav_i2s_dma_tx_callback函数,则是DMA发送完成的回调函数(i2s_tx_callback函数指针指向该函数),这里面,我们并没有对数据进行填充处理(暂停时进行了填0处理),而是采用2个标志量:wavtransferendwavwitchbuf,来告诉wav_play_song函数是否传输完成,以及应该填充哪个数据bufi2sbuf1i2sbuf2);

最后,wav_play_song函数,是播放WAV的最终执行函数,该函数解析完WAV文件后,设置WM8978I2S的参数(采样率,位数等),并开启DMA,然后不停填充数据,实现WAV播放,该函数还进行了按键扫描控制,实现上下取切换和暂停/播放等操作。该函数通过判断wavtransferend是否为1来处理是否应该填充数据,而到底填充到哪个bufi2sbuf1i2sbuf2),则是通过wavwitchbuf标志来确定的,当wavwitchbuf=0时,说明DMA正在使用i2sbuf2,程序应该填充i2sbuf1;当wavwitchbuf=1时,说明DMA正在使用i2sbuf1,程序应该填充i2sbuf2

接下来,看看audioplay.c里面的几个函数,代码如下:

//播放音乐

void audio_play(void)

{

       u8 res; u8 key;       u16 temp;

      DIR wavdir;               //目录

       FILINFO wavfileinfo;   //文件信息

       u8 *fn;                       //长文件名

       u8 *pname;                   //带路径的文件名

       u16 totwavnum;           //音乐文件总数

       u16 curindex;                //图片当前索引           

       u16 *wavindextbl;         //音乐索引表

       WM8978_ADDA_Cfg(1,0);  //开启DAC

       WM8978_Input_Cfg(0,0,0);//关闭输入通道

       WM8978_Output_Cfg(1,0);  //开启DAC输出    

      while(f_opendir(&wavdir,"0:/MUSIC"))//打开音乐文件夹

      {        

              Show_Str(60,190,240,16,"MUSIC文件夹错误!",16,0); delay_ms(200);

              LCD_Fill(60,190,240,206,WHITE); delay_ms(200);//清除显示  

       }                                                               

       totwavnum=audio_get_tnum("0:/MUSIC"); //得到总有效文件数

      while(totwavnum==NULL)//音乐文件总数为0        

      {        

              Show_Str(60,190,240,16,"没有音乐文件!",16,0); delay_ms(200);   

              LCD_Fill(60,190,240,146,WHITE); delay_ms(200);   //清除显示    

       }                                                                      

      wavfileinfo.lfsize=_MAX_LFN*2+1;                                      //长文件名最大长度

       wavfileinfo.lfname=mymalloc(SRAMIN,wavfileinfo.lfsize);//为长文件缓存区分配内存

      pname=mymalloc(SRAMIN,wavfileinfo.lfsize);         //为带路径的文件名分配内存

      wavindextbl=mymalloc(SRAMIN,2*totwavnum);       //申请内存,用于存放音乐文件索引

      while(wavfileinfo.lfname==NULL||pname==NULL||wavindextbl==NULL)//内存分配出错

      {        

              Show_Str(60,190,240,16,"内存分配失败!",16,0); delay_ms(200);

              LCD_Fill(60,190,240,146,WHITE); delay_ms(200);//清除显示                             

       }     

      //记录索引

    res=f_opendir(&wavdir,"0:/MUSIC"); //打开目录

       if(res==FR_OK)

       {

              curindex=0;//当前索引为0

              while(1)//全部查询一遍

              {

                     temp=wavdir.index;                                          //记录当前index

               res=f_readdir(&wavdir,&wavfileinfo);          //读取目录下的一个文件

               if(res!=FR_OK||wavfileinfo.fname[0]==0)break; //错误了/到末尾了,退出        

                 fn=(u8*)(*wavfileinfo.lfname?wavfileinfo.lfname:wavfileinfo.fname);    

                     res=f_typetell(fn); 

                     if((res&0XF0)==0X40)//取高四位,看看是不是音乐文件 

                     {

                            wavindextbl[curindex]=temp;//记录索引

                            curindex++;

                     }        

              }

       }  

     curindex=0;                                                                          //0开始显示

     res=f_opendir(&wavdir,(const TCHAR*)"0:/MUSIC");     //打开目录

       while(res==FR_OK)//打开成功

       {    

              dir_sdi(&wavdir,wavindextbl[curindex]);                   //改变当前目录索引       

        res=f_readdir(&wavdir,&wavfileinfo);                 //读取目录下的一个文件

        if(res!=FR_OK||wavfileinfo.fname[0]==0)break; //错误了/到末尾了,退出

          fn=(u8*)(*wavfileinfo.lfname?wavfileinfo.lfname:wavfileinfo.fname);                  

              strcpy((char*)pname,"0:/MUSIC/");                          //复制路径(目录)

              strcat((char*)pname,(const char*)fn);                       //将文件名接在后面

             LCD_Fill(60,190,240,190+16,WHITE);                           //清除之前的显示

              Show_Str(60,190,240-60,16,fn,16,0);                       //显示歌曲名字

              audio_index_show(curindex+1,totwavnum);

              key=audio_play_song(pname);                                //播放这个音频文件

              if(key==KEY2_PRES)         //上一曲

              {

                     if(curindex)curindex--;

                     else curindex=totwavnum-1;

             }else if(key==KEY0_PRES)//下一曲

              {

                     curindex++;               

                     if(curindex>=totwavnum)curindex=0;//到末尾的时候,自动从头开始

             }else break;    //产生了错误         

       }                                                                             

       myfree(SRAMIN,wavfileinfo.lfname); //释放内存                      

       myfree(SRAMIN,pname);                          //释放内存                      

       myfree(SRAMIN,wavindextbl);                  //释放内存    

}

//播放某个音频文件

u8 audio_play_song(u8* fname)

{

       u8 res; 

       res=f_typetell(fname);

       switch(res)

       {

              case T_WAV:

                     res=wav_play_song(fname); break;                   

              default://其他文件,自动跳转到下一曲

                     printf("can't play:%s\r\n",fname);

                     res=KEY0_PRES; break;

       }

       return res;

}

这里,audio_play函数在main函数里面被调用,该函数首先设置WM8978相关配置,然后查找SD卡里面的MUSIC文件夹,并统计该文件夹里面总共有多少音频文件(统计包括:WAV/MP3/APE/FLAC等),然后,该函数调用audio_play_song函数,按顺序播放这些音频文件。

audio_play_song函数里面,通过判断文件类型,调用不同的解码函数,本章,只支持WAV文件,通过wav_play_song函数实现WAV解码。其他格式:MP3/APE/FLAC等,在综合实验我们会实现其解码函数,大家可以参考综合实验代码,这里就不做介绍了。

最后,我们看看主函数代码:

int main(void)

{       

 

       NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2

       delay_init(168);  //初始化延时函数

       uart_init(115200);         //初始化串口波特率为115200

       LED_Init();                                //初始化LED

       usmart_dev.init(84);             //初始化USMART

      LCD_Init();                                //LCD初始化 

      KEY_Init();                                //按键初始化 

       W25QXX_Init();                         //初始化W25Q128

       WM8978_Init();                          //初始化WM8978

       WM8978_HPvol_Set(40,40);       //耳机音量设置

       WM8978_SPKvol_Set(50);          //喇叭音量设置

      

       my_mem_init(SRAMIN);            //初始化内部内存池

       my_mem_init(SRAMCCM);         //初始化CCM内存池

       exfuns_init();                       //fatfs相关变量申请内存 

      f_mount(fs[0],"0:",1);          //挂载SD 

       POINT_COLOR=RED;     

       while(font_init())                //检查字库

       {        

              LCD_ShowString(30,50,200,16,16,"Font Error!");

              delay_ms(200);                            

              LCD_Fill(30,50,240,66,WHITE);//清除显示          

              delay_ms(200);                            

       }     

       POINT_COLOR=RED;     

      Show_Str(60,50,200,16,"Explorer STM32F4开发板",16,0);                                      

       Show_Str(60,70,200,16,"音乐播放器实验",16,0);                                      

       Show_Str(60,90,200,16,"正点原子@ALIENTEK",16,0);                                   

       Show_Str(60,110,200,16,"2014524",16,0);

       Show_Str(60,130,200,16,"KEY0:NEXT   KEY2REV",16,0);

       Show_Str(60,150,200,16,"KEY_UPAUSE/PLAY",16,0);

       while(1)

       {

              audio_play();

       }

}

    该函数就相对简单了,在初始化各个外设后,通过audio_play函数,开始音频播放。软件部分就介绍到这里,其他未贴出代码,请参考光盘本例程源码。

48.4 下载验证

在代码编译成功之后,我们下载代码到ALIENTEK探索者STM32F4开发板上,程序先执行字库检测,然后当检测到SD卡根目录的MUSIC文件夹有有效音频文件(WAV格式音频)的时候,就开始自动播放歌曲了,如图48.4.1所示:

说明: D:\My Documents\Tencent Files\389063473\Image\2GSBB}PC(CA6MY0PVUUTKMA.jpg

48.4.1 MP3播放中

从上图可以看出,当前正在播放第4首歌曲,总共4首歌曲,歌曲名、播放时间、总时长、码率、音量等信息等也都有显示。此时DS0会随着音乐的播放而闪烁。

只要我们在开发板的PHONE端子插入耳机(或者在P1接口插入喇叭),就能听到歌曲的声音了。同时,我们可以通过按KEY0KEY2来切换下一曲和上一曲,通过KEY_UP控制暂停和继续播放。

本实验,我们还可以通过USMART来测试WM8978的其他功能,通过将wm8978.c里面的部分函数加入USMART管理,我们可以很方便的设置wm8978的各种参数(音量、3DEQ等都可以设置),达到验证测试的目的。有兴趣的朋友,可以实验测试一下。

至此,我们就完成了一个简单的音乐播放器了,虽然只支持WAV文件,但是大家可以在此基础上,增加其他音频格式解码器(可参考综合实验),便可实现其他音频格式解码了。

 


 

实验详细手册和源码下载地址:http://www.openedv.com/posts/list/41586.htm 

正点原子探索者STM32F407开发板购买地址http://item.taobao.com/item.htm?id=41855882779

  

实验43 音乐播放器实验.zip

1.2 MB, 下载次数: 2245

第四十八章 音乐播放器实验-STM32F4开发指南-正点原子探索者STM32开发板.pdf

1.45 MB, 下载次数: 1909

我是开源电子网?网站管理员,对网站有任何问题,请与我联系!QQ:389063473Email:389063473@qq.com
正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

6

主题

33

帖子

0

精华

初级会员

Rank: 2

积分
121
金钱
121
注册时间
2013-10-24
在线时间
6 小时
发表于 2014-12-11 22:13:09 | 显示全部楼层
支持   播放器好复杂的说
回复 支持 反对

使用道具 举报

13

主题

123

帖子

0

精华

初级会员

Rank: 2

积分
197
金钱
197
注册时间
2013-8-3
在线时间
0 小时
发表于 2014-12-13 09:39:46 | 显示全部楼层
为什么加了解码芯片,还叫软件解码呢?
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2014-12-14 11:59:38 | 显示全部楼层
回复【3楼】SCMartian:
---------------------------------
WM8978是dac,不带解码功能。。。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

12

主题

60

帖子

0

精华

初级会员

Rank: 2

积分
159
金钱
159
注册时间
2013-8-18
在线时间
7 小时
发表于 2015-2-4 17:02:18 | 显示全部楼层
回复【4楼】正点原子:
---------------------------------
请问原子哥,在音乐播放器试验中,会用到W25Q64这个8M的flash吗?如果用到,那么有什么作用呢?谢谢。
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-2-4 22:49:38 | 显示全部楼层
回复【5楼】海阔天kongcyc:
---------------------------------
字库。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

6

主题

73

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
291
金钱
291
注册时间
2014-8-12
在线时间
32 小时
发表于 2015-3-12 17:07:34 | 显示全部楼层
原子哥,播放时喇叭和耳机都有声音,是可以通过软件控制来切换吗?
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-3-12 22:45:13 | 显示全部楼层
回复【7楼】heitu103:
---------------------------------
可以控制的,你控制WM8978的寄存器即可.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

2

主题

4

帖子

0

精华

新手入门

积分
32
金钱
32
注册时间
2013-7-31
在线时间
0 小时
发表于 2015-3-13 09:43:49 | 显示全部楼层
原子哥,最近在使用探索者开发板的音乐播放器例程,发现两个问题。
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;

     }


大法
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-3-13 12:05:05 | 显示全部楼层
回复【9楼】gongrong816:
---------------------------------
1,WM8978,官方宣传是48Khz的,能放192K,你就偷着乐吧.真要192K,可以换其他DAC,比如CS4398,PCM1794.
2,这个处理你可以自己试试,我当时这里被折腾了好久.把几乎所有组合都试了一遍才搞出来的.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

2

主题

4

帖子

0

精华

新手入门

积分
32
金钱
32
注册时间
2013-7-31
在线时间
0 小时
发表于 2015-3-14 10:03:24 | 显示全部楼层
回复【10楼】正点原子:
---------------------------------
找到问题的原因了,感谢你的回答。
1,24bit,192k卡的原因是407的速度跟不上,板上的例子是按库开发的。换成寄存器操作就可以了,跑系统应该也可以解决。
2,纠结的看了好久代码,终于发现代码为什么要按你那么写。因为数据是按DMA直接到I2S的,DMA配制成是16bit传输。所以数据在DMA传输时因该是
中字节+高字节(共16bit),然后再发 无效字节+低字节,之后I2S再把数据解析为 高字节+中字节+低字节(24bit), 无效字节(24bit模式时不发送)。如果不参考你的代码真的不知道怎么写这段代码了。
大法
回复 支持 反对

使用道具 举报

6

主题

73

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
291
金钱
291
注册时间
2014-8-12
在线时间
32 小时
发表于 2015-3-14 20:06:11 | 显示全部楼层
回复【8楼】正点原子:
---------------------------------
原子哥,我问问WM8978播放时会有沙沙的声音,据您的经验可能是软件哪里的问题
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-3-14 23:13:16 | 显示全部楼层
回复【11楼】gongrong816:
---------------------------------
1,192K,24bit,就算寄存器,也会有沙沙声的。

回复【12楼】heitu103:
---------------------------------
是不是码率太高了?
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

0

主题

2

帖子

0

精华

新手入门

积分
22
金钱
22
注册时间
2015-3-17
在线时间
0 小时
发表于 2015-3-17 10:38:53 | 显示全部楼层
回复【13楼】正点原子:
----------------------------
原子哥,STM32与WM8978播放音频时,如何配置I2S播放单声道的wav文件?播放立体声都可以,一旦播放单声道文件,音频语速就快很多
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-3-17 23:33:47 | 显示全部楼层
回复【14楼】compass2008:
---------------------------------
修改代码,把单声道数据复制一份,成为双声道就可以了.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

0

主题

2

帖子

0

精华

新手入门

积分
22
金钱
22
注册时间
2015-3-17
在线时间
0 小时
发表于 2015-3-18 09:44:16 | 显示全部楼层
回复【15楼】正点原子:
---------------------------------
本不想这么麻烦的做来,看别型号的单片机直接有单声道立体声的寄存器可选。STM32只能在数据上做文章了啊。谢谢原子哥
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
16
金钱
16
注册时间
2020-5-8
在线时间
5 小时
发表于 2015-3-19 14:29:35 | 显示全部楼层

“本章我们必须向WM8978提供WS,CK和MCK等时钟,同时又要录音,所以只能使用全双工模式。主I2Sx 循环发送数据0X0000,给WM8978,以产生CK、WS和MCK等信号,从I2Sx_ext,则接收来自WM8978的ADC数据(I2Sxext_SD),并保存到SD卡,实现录音。”,
原子哥,这个怎么理解?
要是像stm32f207,要是只设置为主机接收模式,是不是提供不了WS,CK和MCK等时钟了?

回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-3-19 22:59:25 | 显示全部楼层
回复【17楼】烟灰2008:
---------------------------------
就是这么理解啊.
要全双工,必须这么做.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-3-19 22:59:40 | 显示全部楼层
回复【17楼】烟灰2008:
---------------------------------
207没用过.
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

1

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
52
金钱
52
注册时间
2015-4-14
在线时间
2 小时
发表于 2015-4-14 22:11:27 | 显示全部楼层
回复【12楼】heitu103:
---------------------------------
请问问题解决了吗,我的wm8978也有沙沙音,码率低的时候也有,一直有
回复 支持 反对

使用道具 举报

1

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
52
金钱
52
注册时间
2015-4-14
在线时间
2 小时
发表于 2015-4-14 22:16:31 | 显示全部楼层
回复【19楼】正点原子:
-------------------------
请问原子哥:我的wm8978一直有杂音,沙沙的,根据你的经验可能出现什么问题昵,换了低码率的wav还是不行
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-4-14 22:41:20 | 显示全部楼层
回复【21楼】niu14789:
---------------------------------
多高码率?
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

1

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
52
金钱
52
注册时间
2015-4-14
在线时间
2 小时
发表于 2015-4-14 22:44:57 | 显示全部楼层
回复【22楼】正点原子:
---------------------------------
24bit 44.1KHz
回复 支持 反对

使用道具 举报

1

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
52
金钱
52
注册时间
2015-4-14
在线时间
2 小时
发表于 2015-4-14 22:49:42 | 显示全部楼层
回复【22楼】正点原子:
---------------------------------
感觉码率是对的,可以听到唱的是什么歌,只是有很大的杂音
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-4-14 23:44:22 | 显示全部楼层
回复【24楼】niu14789:
---------------------------------
   试试16位的
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

1

主题

18

帖子

0

精华

初级会员

Rank: 2

积分
52
金钱
52
注册时间
2015-4-14
在线时间
2 小时
发表于 2015-4-15 11:12:05 | 显示全部楼层
回复【25楼】正点原子:
---------------------------------
16位的也试了  都不行 还是有杂音,我怀疑是不是WM8978坏了?
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-4-15 23:39:32 | 显示全部楼层
回复【26楼】niu14789:
---------------------------------
换个试试吧。
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

6

主题

73

帖子

0

精华

中级会员

Rank: 3Rank: 3

积分
291
金钱
291
注册时间
2014-8-12
在线时间
32 小时
发表于 2015-4-16 16:28:31 | 显示全部楼层
回复【20楼】niu14789:
---------------------------------
没有啊,搁那儿了,没时间搞
回复 支持 反对

使用道具 举报

2

主题

13

帖子

0

精华

新手上路

积分
44
金钱
44
注册时间
2014-5-24
在线时间
1 小时
发表于 2015-4-21 09:42:31 | 显示全部楼层
原子哥, 为啥选wm8978呢, wm8960不好么?这个输出还1w的.
回复 支持 反对

使用道具 举报

2

主题

18

帖子

0

精华

新手上路

积分
47
金钱
47
注册时间
2015-2-2
在线时间
0 小时
发表于 2015-7-7 13:35:03 | 显示全部楼层

 
原子哥,为什么我把例程下载到开发板后,一直就
上面的界面,任何相关操作都无效呢?
回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-7-7 22:53:28 | 显示全部楼层
回复【30楼】YouDream:
---------------------------------
开发板的音乐播放器实验,只支持wav的音频文件
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

2

主题

18

帖子

0

精华

新手上路

积分
47
金钱
47
注册时间
2015-2-2
在线时间
0 小时
发表于 2015-7-8 10:16:07 | 显示全部楼层
回复【31楼】正点原子:
---------------------------------
实在是恍然大悟啊,就说怎么一直没有用呢,还搞了这么久,谢了。(~ …… ~)
回复 支持 反对

使用道具 举报

2

主题

18

帖子

0

精华

新手上路

积分
47
金钱
47
注册时间
2015-2-2
在线时间
0 小时
发表于 2015-7-8 10:26:37 | 显示全部楼层
原子哥,还有个问题请教。我的工程中,像下图能够把代码收缩起来的按钮不见了,改怎么调回来呢?keil使用的是开发板附带的,V5.1,谢谢

回复 支持 反对

使用道具 举报

530

主题

11万

帖子

34

精华

管理员

Rank: 12Rank: 12Rank: 12

积分
165540
金钱
165540
注册时间
2010-12-1
在线时间
2117 小时
发表于 2015-7-8 11:06:29 | 显示全部楼层
回复【33楼】YouDream:
---------------------------------
这个我也不知道,关闭工程重新打开试试
我是开源电子网www.openedv.com站长,有关站务问题请与我联系。
正点原子STM32开发板购买店铺http://openedv.taobao.com
正点原子官方微信公众平台,点击这里关注“正点原子”
回复 支持 反对

使用道具 举报

4

主题

12

帖子

0

精华

初级会员

Rank: 2

积分
80
金钱
80
注册时间
2014-3-29
在线时间
11 小时
发表于 2016-6-14 23:33:14 | 显示全部楼层
死活不进dma中断。。。
回复 支持 反对

使用道具 举报

5

主题

24

帖子

0

精华

初级会员

Rank: 2

积分
195
金钱
195
注册时间
2016-7-18
在线时间
32 小时
发表于 2016-11-23 16:58:45 | 显示全部楼层
原子哥,我想问一句,关于音频播放可不可以通过自己控制,直接选择所需要的WAV文件进行播放,最近一直在试,看了原子程序,但不知道从何下手
回复 支持 反对

使用道具 举报

5

主题

11

帖子

0

精华

初级会员

Rank: 2

积分
146
金钱
146
注册时间
2016-10-3
在线时间
52 小时
发表于 2017-1-14 22:17:08 | 显示全部楼层
请问原子哥 有什么好的办法可以退出        audio_play();这个函数吗  自己试了老出问题
回复 支持 反对

使用道具 举报

10

主题

21

帖子

0

精华

初级会员

Rank: 2

积分
65
金钱
65
注册时间
2017-11-8
在线时间
10 小时
发表于 2018-8-23 17:42:11 | 显示全部楼层
STM32F4开发指南V1.1-库函数版本中第四十八章 音乐播放器实验
书中P660页写到“SD卡读取到的24位WAV数据流,是低字节在前,高字节在后,比如,我们读到一个声道的数据(24位),存储在buf[3]里面,那么要通过SPI-DR发送这个24位数据,过程如下:SPI_DR=((u16)buf[2]<<8+buf[1];SPI_DR=((u16)buf[0]<<8;然而在P672页的wav_buffill填充函数中对于24位数据转换处理后的结果却是buf[0]=p[1],buf[1]=p[2],buf[3]=p[0]......
我的问题是:
(1)对于24位的WAV数据流,wav_buffill函数并没有将WAV数据流的高字节p[2]赋值给缓冲区的首地址buf[0],这是为什么?
(2)为何 是低字节在前,高字节在后的,什么原则导致的?
(3)加入SD卡读取到的是16位WAV数据流,是否也是低字节在前,高字节在后的?如果是,为何wav_buffill函数并没有对16位WAV数据流进行转换处理?
(4)该开发板的下一个实验(第四十九章 录音机实验)进行了WAV录音并保存为SD卡内的WAV数据流文件,然而该实验里并看不出WAV数据流是低字节在前,高字节在后的,那么问题(2)是什么原因导致的?
感谢原子哥及各位学友指导。
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
13
金钱
13
注册时间
2019-8-30
在线时间
4 小时
发表于 2019-12-7 13:48:55 | 显示全部楼层
正点原子 发表于 2015-3-17 23:33
回复【14楼】compass2008:
---------------------------------
修改代码,把单声道数据复制一份,成为双声道 ...

原子哥,具体的代码怎么改呢,还是不太懂
回复 支持 反对

使用道具 举报

0

主题

1

帖子

0

精华

新手入门

积分
2
金钱
2
注册时间
2019-12-8
在线时间
0 小时
发表于 2019-12-8 14:40:49 | 显示全部楼层
6pbs65XirD+7UblPYhXSW6sZMIwCPnEHCyDqksxniFMmTvBfCJXExUD/LKWShc/P
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-7-6 01:57

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表