第四十八章 音乐播放器实验
[mw_shl_code=c,true]1.硬件平台:正点原子探索者STM32F407开发板
2.软件平台:MDK5.1
3.固件库版本:V1.4.0[/mw_shl_code]
ALIENTEK探索者
STM32F4开发板拥有全双工
I2S,且外扩了一颗
HIFI级
CODEC芯片:
WM8978G,支持最高
192K 24BIT的音频播放,并且支持录音(下一章介绍)。本章,我们将利用探索者
STM32F4开发板实现一个简单的音乐播放器(仅支持
WAV播放)。本章分为如下几个部:
48.1 WAV&WM8978&I2S简介
48.2 硬件设计
48.3 软件设计
48.4 下载验证
48.1 WAV&WM8978&I2S简介
本章新知识点比较多,包括:
WAV、
WM8978和
I2S等三个知识点。下面我们将分别向大家介绍。
48.1.1 WAV简介
WAV即
WAVE文件,
WAV是计算机领域最常用的数字化声音文件格式之一,它是微软专门为Windows系统定义的波形文件格式(Waveform Audio),由于其扩展名为"*.wav"。它符合
RIFF(Resource Interchange File Format)文件规范,用于保存
Windows平台的音频信息资源,被
Windows平台及其应用程序所广泛支持,该格式也支持
MSADPCM,
CCITT 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结构示意图
其中块标识符由
4个
ASCII码构成,数据大小则标出紧跟其后的数据的长度(单位为字节),注意这个长度不包含块标识符和数据大小的长度,即不包含最前面的
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 ; //子集合大小
(不包括
ID和
Size);这里为
:20.
u16
AudioFormat; //音频格式
;0X10,表示线性
PCM;0X11表示
IMA ADPCM
u16
NumOfChannels; //通道数量
;1,表示单声道
;2,表示双声道
;
u32
SampleRate; //采样率
;0X1F40,表示
8Khz
u32
ByteRate; //字节速率
;
u16
BlockAlign; //块对齐
(字节
);
u16
BitsPerSample; //单个采样数据大小
;4位
ADPCM,设置为
4
}ChunkFMT;
接下来,我们再看看Fact块(Fact Chunk),该块为可选块,以“fact”作为标示,不是每个WAV文件都有,在非PCM格式的文件中,一般会在Format结构后面加入一个Fact块,该块Chunk结构如下:
//fact块
typedef __packed struct
{
u32
ChunkID; //chunk id;这里固定为
"fact",即
0X74636166;
u32
ChunkSize ; //子集合大小
(不包括
ID和
Size);这里为
: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 ; //子集合大小
(不包括
ID和
Size);文件大小
-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信噪比
98dB;
ADC信噪比
90dB
●支持无电容耳机驱动(提供
40mW@16Ω的输出能力)
●支持扬声器输出(提供
0.9W@8Ω的驱动能力)
●支持立体声差分输入
/麦克风输入
●支持左右声道音量独立调节
●支持
3D效果,支持
5路
EQ调节
WM8978的控制通过
I2S接口(即数字音频接口)同
MCU进行音频数据传输(支持音频接收和发送),通过两线(
MODE=0,即
IIC接口)或三线(
MODE=1)接口进行配置。
WM8978的
I2S接口,由
4个引脚组成:
1,
ADCDAT:ADC数据输出
2,
DACDAT:
DAC数据输入
3,
LRC:数据左
/右对齐时钟
4,
BCLK:位时钟,用于同步
WM8978可作为
I2S主机,输出
LRC和
BLCK时钟,不过我们一般使用
WM8978作为从机,接收
LRC和
BLCK。另外,
WM8978的
I2S接口支持
5中不同的音频数据模式:左(
MSB)对齐标准、右(
LSB)对齐标准、飞利浦(
I2S)标准、
DSP模式
A和
DSP模式
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,不过
WM8978的
IIC接口比较特殊:
1,只支持写,不支持读数据;
2,寄存器长度为
7位,数据长度为
9位。
3,寄存器字节的最低位用于传输数据的最高位(也就是
9位数据的最高位,
7位寄存器的最低位)。
WM8978的
IIC地址固定为:
0X1A。关于
WM8978的
IIC详细介绍,请看其数据手册第
77页。
这里我们简单介绍一下要正常使用
WM8978来播放音乐,应该执行哪些配置。
1,寄存器
R0(
00h),该寄存器用于控制
WM8978的软复位,写任意值到该寄存器地址,即可实现软复位
WM8978。
2,寄存器
R1(
01h),该寄存器主要要设置
BIASEN(
bit3),该位设置为
1,模拟部分的放大器才会工作,才可以听到声音。
3,寄存器
R2(
02h),该寄存器要设置
ROUT1EN(bit8),
LOUT1EN(bit7)和
SLEEP(bit6)等三个位,
ROUT1EN和
LOUT1EN,设置为
1,使能耳机输出,
SLEEP设置为
0,进入正常工作模式。
4,寄存器
R3(
03h),该寄存器要设置
LOUT2EN(bit6),
ROUT2EN(bit5),
RMIXER(bit3),
LMIXER(bit2),
DACENR(bit1)和
DACENL(bit0)等
6个位。
LOUT2EN和
ROUT2EN,设置为
1,使能喇叭输出;
LMIXER和
RMIXER设置为
1,使能左右声道混合器;
DACENL和
DACENR则是使能左右声道的
DAC了,必须设置为
1。
5,寄存器
R4(
04h),该寄存器要设置
WL(bit6:5)和
FMT(bit4:3)等
4个位。
WL(bit6:5)用于设置字长(即设置音频数据有效位数),
00表示
16位音频,
10表示
24位音频;
FMT(bit4:3)用于设置
I2S音频数据格式(模式),我们一般设置为
10,表示
I2S格式,即飞利浦模式。
6,寄存器
R6(
06h),该寄存器我们直接全部设置为
0即可,设置
MCLK和
BCLK都来自外部,即由
STM32F4提供。
7,寄存器
R10(
0Ah),该寄存器我们要设置
SOFTMUTE(bit6)和
DACOSR128(bit3)等两个位,
SOFTMUTE设置为
0,关闭软件静音;
DACOSR128设置为
1,
DAC得到最好的
SNR。
8,寄存器
R43(
2Bh),该寄存器我们只需要设置
INVROUT2为
1即可,反转
ROUT2输出,更好的驱动喇叭。
9,寄存器
R49(
31h),该寄存器我们要设置
SPKBOOST(bit2)和
TSDEN(bit1)这两个位。
SPKBOOST用于设置喇叭的增益,我们默认设置为
0就好了(
gain=-1),如想获得更大的声音,设置为
1(
gain=+1.5)即可;
TSDEN用于设置过热保护,设置为
1(开启)即可。
10,寄存器
R50(
32h)和
R51(
33h),这两个寄存器设置类似,一个用于设置左声道(
R50),另外一个用于设置右声道(
R51)。我们只需要设置这两个寄存器的最低位为
1即可,将左右声道的
DAC输出接入左右声道混合器里面,才能在耳机
/喇叭听到音乐。
11,寄存器
R52(
34h)和
R53(
35h),这两个寄存器用于设置耳机音量,同样一个用于设置左声道(
R52),另外一个用于设置右声道(
R53)。这两个寄存器的最高位(
HPVU)用于设置是否更新左右声道的音量,最低
6位用于设置左右声道的音量,我们可以先设置好两个寄存器的音量值,最后设置其中一个寄存器最高位为
1,即可更新音量设置。
12,寄存器
R54(
36h)和
R55(
37h),这两个寄存器用于设置喇叭音量,同
R52,
R53设置一模一样,这里就不细说了。
以上,就是我们用
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*fs,
fs即音频采样率)
STM32F4的
I2S框图如图
48.1.3.1所示:
图
48.1.3.1 I2S框图
STM32F4的
I2S是与
SPI部分共用的,通过设置
SPI_I2SCFGR寄存器的
I2SMOD位即可开启
I2S功能,
I2S接口使用了几乎与
SPI相同的引脚、标志和中断。
I2S用到的信号有:
1,
SD:串行数据(映射到
MOSI 引脚),用于发送或接收两个时分复用的数据通道上的数据(仅半双工模式)。
2,
WS:字选择(映射到
NSS引脚),即帧时钟,用于切换左右声道的数据。
WS频率等于音频信号采样率(
fs)。
3,
CK:串行时钟(映射到
SCK引脚),即位时钟,是主模式下的串行时钟输出以及从模式下的串行时钟输入。
CK频率
=WS频率(
fs)
*2*16(
16位宽),如果是
32位宽,则是:
CK频率
=WS频率(
fs)
*2*32(
32位宽)
4,
I2S2ext_SD和
I2S3ext_SD:用于控制
I2S全双工模式的附加引脚(映射到
MISO引脚)。
5,
MCK:即主时钟输出,当
I2S配置为主模式(并且
SPI_I2SPR寄存器中的
MCKOE位置
1)时,使用此时钟,该时钟输出频率
256×
fs,
fs即音频信号采样频率(
fs)。
为支持
I2S全双工模式,除了
I2S2和
I2S3,还可以使用两个额外的
I2S,它们称为扩展
I2S(
I2S2_ext、
I2S3_ext),如图
48.1.3.2:
图
48.1.3.2
I2S全双工框图
因此,第一个
I2S全双工接口基于
I2S2和
I2S2_ext,第二个基于
I2S3和
I2S3_ext。注意:
I2S2_ext和
I2S3_ext仅用于全双工模式。
I2Sx可以在主模式下工作。因此:
1,只有
I2Sx可在半双工模式下输出
SCK和
WS
2,只有
I2Sx可在全双工模式下向
I2S2_ext和
I2S3_ext提供
SCK和
WS。
扩展
I2S (I2Sx_ext)只能用于全双工模式。
I2Sx_ext始终在从模式下工作。
I2Sx和
I2Sx_ext 均可用于发送和接收。
STM32F4的
I2S支持
4种数据和帧格式组合,分别是:
1,将
16位数据封装在
16位帧中;
2,将
16位数据封装在
32位帧中;
3,将
24位数据封装在
32位帧中;
4,将
32位数据封装在
32位帧中。
将
16位数据封装在
32位帧中时,前
16位
(MSB)为有效位,
16位
LSB被强制清零,无需任何软件操作或
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,第二次写入
0X33xx(
xx可以为任意数值),这样就把
0X8EAA33发送出去了。
顺便说一下
SD卡读取到的
24位
WAV数据流,是低字节在前,高字节在后的,比如,我们读到一个声道的数据(
24bit),存储在
buf[3]里面,那么要通过
SPI_DR发送这个
24位数据,过程如下:
SPI_DR=((u16)buf[2]<<8)+buf[1];
SPI_DR=(u16)buf[0]<<8;
这样,第一次发送高
16为数据,第二次发送低
8位数据,完成一次
24bit数据的发送。
接下来,我们介绍下
STM32F4的
I2S时钟发生器,其架构如图
48.1.3.4所示:
图
48.1.3.4 I2S时钟发生器架构
图中
I2SxCLK可以来自
PLLI2S输出(通过
R系数分频)或者来自外部时钟(
I2S_CKIN引脚),一般我们使用前者作为
I2SxCLK输入时钟。
一般我们需要根据音频采样率(
fs,即
CK的频率)来计算各个分频器的值,常用的音频采样率有:
22.05Khz、
44.1Khz、
48Khz、
96Khz、
196Khz等。
根据是否使能
MCK输出,
fs频率的计算公式有
2种情况。不过,本章只考虑
MCK输出使能时的情况,当
MCK输出使能时,
fs频率计算公式如下:
fs=I2SxCLK/[256*(2*I2SDIV+ODD)]
其中:
I2SxCLK=(HSE/pllm)*PLLI2SN/PLLI2SR。
HSE我们是
8Mhz,而
pllm在系统时钟初始化就确定了,是
8,这样结合以上
2式,可得计算公式如下:
fs= (1000*PLLI2SN/PLLI2SR )/[256*(2*I2SDIV+ODD)]
fs单位是:
Khz。其中:
PLL2SN取值范围:
192~432;
PLLI2SR取值范围:
2~7;
I2SDIV取值范围:
2~255;
ODD取值范围:
0/1。根据以上约束条件,我们便可以根据
fs来设置各个系数的值了,不过很多时候,并不能取得和
fs一模一样的频率,只能近似等于
fs,比如
44.1Khz采样率,我们设置
PLL2SN=271,
PLL2SR=2,
I2SDIV=6,
ODD=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各位描述
本章我们设置
MCKOE为
1,开启
MCK输出,
ODD和
I2SDIV则根据不同的
fs,查表进行设置。
第三个是
PLLI2S配置寄存器:
RCC_PLLI2SCFGR,该寄存器各位描述如图
48.1.3.7所示:
图
48.1.3.7 寄存器
RCC_
PLLI2SCFGR各位描述
该寄存器用于配置
PLLI2SR和
PLLI2SN两个系数,
PLLI2SR的取值范围是:
2~7,
PLLI2SN的取值范围是:
192~432。同样,这两个也是根据
fs的值来设置的。
此外,还要用到
SPI_CR2寄存器的
bit1位,设置
I2S TX DMA数据传输,
SPI_DR寄存器用于传输数据,本章用
DMA来传输,所以直接设置
DMA的外设地址位
SPI_DR即可。
最后,我们看看要通过
STM32F4的
I2S,驱动
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_MSB,
LSB对齐标准
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来传输的。并且,
STM32F4的
DMA具有双缓冲机制,这样可以提高效率,大大方便了我们的数据传输,本章将
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
这些硬件我们都已经介绍过了,不过
WM8978和
STM32F4的连接,还没有介绍,连接如图
48.2.1所示:
图
48.2.1
WM8978与
STM32F4连接原理图
图中,
PHONE接口,可以用来插耳机,
P1接口,可以外接喇叭(
1W@8Ω,需自备)。硬件上,
IIC接口和
24C02,
MPU6050等共用,另外
I2S_MCLK和
DCMI_D0共用,所以
I2S和
DCMI不可以同时使用。
本实验,大家需要准备
1个
SD卡(在里面新建一个
MUSIC文件夹,并存放一些
wav歌曲在
MUSIC文件夹下)和一个耳机(或喇叭),分别插入
SD卡接口和耳机接口(喇叭接
P1接口),然后下载本实验就可以通过耳机来听歌了。
48.3 软件设计
打开本章实验工程目录可以看到,我们在工程根目录文件夹下新建
APP和
AUDIOCODEC两个文件夹。在
APP文件夹里面新建了
audioplay.c和
audioplay.h两个文件。在
AUDIOCODEC文件夹里面新建了
wav文件夹,然后在其中新建了
wavplay.c和
wavplay.h两个文件。同时,我们把相关的源文件引入工程相应分组,同时将
APP和
wav文件夹加入头文件包含路径。
然后,我们在
HARDWARE文件夹下新建了
WM8978和
I2S两个文件夹,在
WM8978文件夹里面新建了
wm8978.c和
wm8978.h两个文件,在
I2S文件夹里面新建了
i2s.c和
i2s.h两个文件。 最后将
wm8978.c和
i2s.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
---------------------------------
找到问题的原因了,感谢你的回答。
1,24bit,192k卡的原因是407的速度跟不上,板上的例子是按库开发的。换成寄存器操作就可以了,跑系统应该也可以解决。
2,纠结的看了好久代码,终于发现代码为什么要按你那么写。因为数据是按DMA直接到I2S的,DMA配制成是16bit传输。所以数据在DMA传输时因该是
中字节+高字节(共16bit),然后再发 无效字节+低字节,之后I2S再把数据解析为 高字节+中字节+低字节(24bit), 无效字节(24bit模式时不发送)。如果不参考你的代码真的不知道怎么写这段代码了。
一周热门 更多>