目录
前言
1. WAV简介
1.1 WAVE文件的内部结构
2. WM8978简介
3. I2S简介
4. 硬件设计
5. 实验程序
5.1 main.c
5.2 I2S.c
5.3 I2S.h
5.4 WM8978.c
5.5 WM8978.h
前言
STM32F4开发板拥有全双工I2S(也就是可以同时双向进行传输,A到B传输信息的同时B也可以向A进行信息传输),并且开发板外扩了一颗HIFI级CODEC芯片:WM8978G,支持最高192K 24BIT的音频播放,并且支持录音。STM32F4开发板的音乐播放器仅支持WAV播放。
1. WAV简介
WAV文件是计算机领域最常用的数字化声音文件格式之一,它是微软专门为Windows系统定义的波形文件格式(Waveform Audio),由于其扩展名为 “*. wav” 。wave文件有很多不同的压缩格式,现在一些程序生成的wave文件都或多或少的含有一些错误,这些错误很大程度是因为在压缩和解压缩后没有正确地组织好文件的内部结构。最基本的WAVE文件是PCM(脉冲编码调制)格式的,这种文件直接存储采样的声音数据没有经过任何的压缩,是声卡直接支持的数据格式,要让声卡正确播放其它被压缩的声音数据,就应该先把压缩的数据解压缩成PCM格式,然后再让声卡来播放。
1.1 WAVE文件的内部结构
WAVE文件是以RIFF(Resource Interchange File Format,资源交互文件格式)格式来组织内部结构的。RIFF文件结构可以看作是树状结构,其基本构成是称为 “块(Chunk)”的单元,最顶端是一个 “RIFF块” ,下面的每个块由 “类型块标识”、“标志符”、“数据大小” 及 “数据” 等项所组成。
上面提及的 “类型块标识” 只在部分chunk中用到,如 “WAVE chunk” 中,它表示的意义是下面嵌套有别的chunk,当使用了 “类型块标识” 时,该chunk中就没有别的项(如块标志等,数据大小等),它只作为文件读取时的一个标识(类似于链表中的指针域,指向下一个数据域,起一个导向的作用)。先找到这个 “类型块标识”,再以它为起点读取它下面嵌套的其他chunk。
//Chunk结构示意图:
WAV是由若干个Chunk组成的。按照在文件中出现的位置包括:RIFF WAVE Chunk、Format Chunk、Fact Chunk(可选)、Data Chunk。每个Chunk由块标识符、数据大小和数据三部分组成,
其中块标识符由4个ASCII码构成,数据大小则标出紧跟其后的数据的长度(单位为字节),注意这个长度不包含块标识符和数据大小的长度,即不包含最前面的8个字节(这8个字节是块标识符和数据大小总共的字节数)。所以实际 Chunk 的大小为数据大小加8。(也就是说一个Chunk是刚开始是块标识符,占4个字节;紧接着是数据大小,占4个字节;最后才是真正意义上的数据。)
首先,先来看 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 减8 是因为整个数据是放在块标志符和数据大小之后的,块标志符和数据大小各占4个字节 u32 Format; //格式;WAVE,即0x45564157 }ChunkRIFF;
Format 块(Format Chunk),该块以 “fmt ”作为标示(注意有个空格!),一般情况下,该段的大小为16个字节,但是有些软件生成的wav格式,该部分可能有18个字节,含有两个字节的附加信息。Format 块的 Chunk 结构如下:
//fmt块 (Format块的Chunk 结构) 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; //字节速率=采样率*通道数*(ADC位数/8) u16 BlockAlign; //块对齐(字节)=通道数*(ADC位数/8) u16 BitsPerSample; //单个采样数据大小;16位 ADPCM,设置为16 u16 ByteExtraData; //附加的数据字节;2个;线性PCM,没有这个参数 }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中最重要的数据,如果这是某种压缩格式的声音文件,那么从这里就可以知道它解压缩后的大小。
最后是数据块(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 位置可以分为如下几种形式:
本章,我们播放的音频支持:16位和24位,立体声,所以每个取样为 4/6 个字节,低字节在前,高字节在后。在得到这些 wav数据 以后,通过 I2S 丢给WM8978,就可以欣赏音乐啦。
只要依循此结构的文件,我们称之为RIFF档。此种结构提供了一种系统化的分类。如果和 MS、DOS 文件系统作比较,"RIFF"chunk 就好比是一台硬盘的根目录,其格式辨别码 便是此硬盘的逻辑代码(C:或 D:),而"L1ST"chunk 即为其下的子目录,其他的 chunk 则为一 般的文件。至于在 RIFF 文件的处理方面,微软提供了相关的函数。视窗下的各种多媒体文件 格式就如同在磁盘机下规定仅能放怎样的目录,而在该目录下仅能放何种数据。
每个文件最前端写入的是RIFF块,每个文件只有一个RIFF块。
非PCM格式的文件会至少多加入一个 “fact”块,它用来记录数据解压缩后的大小。这个 “fact” 块一般加在 “data” 块的前面。
WAVE文件是非常简单的一种RIFF文件,它的格式类型为 “WAVE”。RIFF块 包含两个子块,这两个子块的ID分别为 “fmt” 和 “data” ,其中 “fmt” 子块由结构 PCMWAVEFORMAT 所组成 ,其子块的大小就是 sizeof(PCMWAVEFORMAT),数据组成就是 PCMWAVEFORMAT 结构中的数据。
// PCMWAVEFORMAT 结构定义如下:
Typedef struct
{
WAVEFORMAT wf; //波形格式
WORD wBitsPerSample; //WAVE文件的采样大小
}PCMWAVEFORMAT;
//WAVEFORMAT结构定义如下:
typedef struct
{
WORD wFormatag; //编码格式,包括WAVE_FORMAT_PCM,WAVEFORMAT_ADPCM等
WORD nChannls; //声道数,单声道为1,双声道为2;
DWORD nSamplesPerSec; //采样频率
DWORD nAvgBytesperSec; //每秒的数据量
WORD nBlockAlign; //块对齐
}WAVEFORMAT;
“data” 子块 包含 WAVE 文件的数字化波形声音数据,其存放格式依赖于 “fmt” 子块中wFormatTag 成员指定的格式种类,在多声道WAVE文件中,样本是交替出现的。
2. WM8978简介
WM8978 是一个低功耗、高质量的立体声多媒体数字信号编译码器。是欧胜(Wolfson)推出的一款全功能音频处理器。它带有一个 HI-FI 级数字信号处理内核,支持增强 3D 硬件环绕音效,以及 5 频段的硬件均衡器,可以有效改善音质;并有一个可编程的陷波滤波器,用以去除屏幕开、切换等噪音。
WM8978 集成了对麦克风的支持,用于一个强悍的扬声器功放,可提供高达 900mW 的高质量音响效果扬声器功率。
WM8978 拥有一个数字回放限制器,可防止扬声器声音过载。进一步提升了耳机放大器输出功率,在推动 16Ω 耳机的时候,每声道最大输出功率高达 40毫瓦 !可以连接市面上绝大多数适合随身听的高端 HI-FI 耳机。
WM8978的主要特性有:
- 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个引脚组成:
- ADCDAT:ADC数据输出
- DACDAT:DAC数据输入
- LRC:数据左 / 右对齐时钟
- BCLK:位时钟,用于同步
WM8978可作为 I2S 主机,输出 LRC 和 BLCK 时钟,不过我们一般使用 WM8978 作为从机,接收LRC 和 BLCK 时钟。另外,WM8978的 I2S 接口支持 5 种不同的音频数据模式:左(MSB)对齐标准、右(LSB)对齐标准、飞利浦(I2S)标准(本节我们使用飞利浦标准来传输I2S数据)、DSP模式 A 和DSP模式 B。
飞利浦(I2S)标准模式,数据在跟随 LRC 传输的 BCLK 的第二个上升沿时传输MSB,其他位一直到LSB按顺序传输。传输依赖于字长、BCLK频率和采样率,在每个采样的 LSB 和下一个 MSB 之间都应该有未用的 BCLK 周期。飞利浦标准模式的 I2S 数据传输协议如下:
fs 表示音频信号的采样率;LRC的频率就是音频信号的采样率。另外,STM32F4提供MCLK时钟,MCLK的频率必须等于256 fs,也就是音频采样率的256倍;
WM8978 的框图如下:
WM8978内部有很多的模拟开关,用来选择通道,同时还有很多调节器,用来设置增益和音量。
本节,我们通过 IIC接口 (MODE=0)连接WM8978,不过需要注意,WM8978 的IIC接口比较特殊:
- 只支持写,不支持读数据
- 寄存器长度为7位,数据长度为9位
- 寄存器字节的最低位用于传输数据最高位(也就是 9 位数据的最高位,7位寄存器的最低位)。
WM8978 的 IIC 地址固定为:0x1A
正常使用WM8978来播放音乐的相关配置:
寄存器R0(00h),该寄存器用于控制WM8978的软复位,写任意值到该寄存器地址,即可实现软复位WM8978。
寄存器R1(01h),该寄存器主要设置BIASEN(bit3),该位设置为 1,模拟部分的放大器才会工作,才可以听到声音。
寄存器R2(02h),该寄存器要设置ROUT1EN(bit8),LOUT1EN(bit7)和SLEEP(bit6)等三个位,ROUT1EN 和 LOUT1EN,设置为 1,使能耳机输出,SLEEP 设置为 0,进入正 常工作模式。
寄存器 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。
寄存器 R4(04h),该寄存器要设置 WL(bit6:5)和 FMT(bit4:3)等 4 个位。WL(bit6:5)用 于设置字长(即设置音频数据有效位数),00 表示 16 位音频,10 表示 24 位音频;FMT(bit4:3)用于设置 I2S 音频数据格式(模式),我们一般设置为 10,表示 I2S 格式,即飞利浦模式。
寄存器 R6(06h),该寄存器我们直接全部设置为 0 即可,设置 MCLK 和 BCLK 都来 自外部,即由 STM32F4 提供。
寄存器 R10(0Ah),该寄存器我们要设置 SOFTMUTE(bit6)和 DACOSR128(bit3)等两个位,SOFTMUTE 设置为 0,关闭软件静音;DACOSR128 设置为 1,DAC 得到最好的 SNR。
寄存器 R43(2Bh),该寄存器我们只需要设置 INVROUT2 为 1 即可,反转 ROUT2 输 出,更好的驱动喇叭。
寄存器 R49(31h),该寄存器我们要设置 SPKBOOST(bit2)和 TSDEN(bit1)这两个位。SPKBOOST 用于设置喇叭的增益,我们默认设置为 0 就好了(gain=-1),如想获得更大的 声音,设置为 1(gain=+1.5)即可;TSDEN 用于设置过热保护,设置为 1(开启)即可。
寄存器 R50(32h)和 R51(33h),这两个寄存器设置类似,一个用于设置左声道(R50), 另外一个用于设置右声道(R51)。我们只需要设置这两个寄存器的最低位为 1 即可,将 左右声道的 DAC 输出接入左右声道混合器里面,才能在耳机/喇叭听到音乐。
寄存器 R52(34h)和 R53(35h),这两个寄存器用于设置耳机音量,同样一个用于 设置左声道(R52),另外一个用于设置右声道(R53)。这两个寄存器的最高位(HPVU) 用于设置是否更新左右声道的音量,最低 6 位用于设置左右声道的音量,我们可以先设置 好两个寄存器的音量值,最后设置其中一个寄存器最高位为 1,即可更新音量设置。
寄存器 R54(36h)和 R55(37h),这两个寄存器用于设置喇叭音量,同 R52,R53设置一模一样,这里就不细说了。
开发板原理图:
WM8978的引脚分布不是按照正确顺序来分布的,一定程度上也是为了保证有利于PCB布线;
- LIP:模拟输入,左麦克风前置放大同相输入
- LIN:模拟输入,左麦克风前置放大反相输入
- L2/GPIO2:模拟输入,左通道线输入/次要的麦克风前置放大同相输入/GPIO引脚
- RIP:模拟输入,右麦克风前置放大同相输入
- RIN:模拟输入,右麦克风前置放大反相输入
- R2/GPIO3:模拟输入,右通道线输入/次要的麦克风前置放大同相输入/GPIO引脚
- LRC:数字输入/输出,DAC和ADC的采样率时钟
- BCLK:数字输入/输出,数字音频位时钟
- ADCDAT:数字输出,ADC数字音频数据输出
- DACDAT:数字输入,DAC数据音频数据输入
- MCLK:数字输入,主时钟输入
- DGND:电源,数字地
- DCVDD:电源,数字核心逻辑电源
- DBVDD:电源,数字缓冲器电源
- CSB/GPIO1:数字输入/输出,3线微处理器片选/通用输入/输出1
- SCLK:数字输入/输出,3线/2线微处理器时钟输入
- SDIN:数字输入/输出,3线/2线微处理器数据输入
- MODE:数字输入,控制接口选择
- AUXL:模拟输入,左辅助输入
- AUXR:模拟输入,右辅助输入
- OUT4:模拟输出,中轨耳机伪地缓冲或者右线输出或者单声道混合输出
- OUT3:模拟输出,中轨耳机伪地缓冲或者左线输出
- ROUT2:模拟输出,第二右输出或者BTL扬声器同相输出驱动
- SPKGND:电源,扬声器地(供给供给扬声器放大器和OUT3/OUT4)
- LOUT2:模拟输出,第二左输出或者BTL扬声器反相输出
- SPKVDD:电源,扬声器电源(只供给扬声器放大器)
- VMID:参考,解耦ADC和DAC的参考电压
- AGND:电源,模拟地(供给ADC和DAC)
- ROUT1:模拟输出,耳机右输出
- LOUT1:模拟输出,耳机左输出
- AVDD:电源,模拟电源(供给ADC和DAC)
- MICBIAS:模拟输出,麦克风偏置
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即音频采样率)
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):
第一个I2S全双工接口基于I2S2和I2S2_ext,第二个基于I2S3和I2S3_ext。
注意:I2S2_ext 和 I2S3_ext 仅用于全双工模式。
I2Sx可以在主模式下工作。因此:
1. I2S只有在半双工模式下可以输出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位帧中
对应所有数据格式和通信标准而言,始终会先发送最高有效位(MSB优先)
I2S飞利浦标准,使用 WS信号 来指示当前正在发送的数据所属的通道。该信号从当前通道数据的第一个位(MSB)之前的一个时钟开始有效。发送方在时钟信号(CK)的下降沿改变数据,接收方在上升沿读取数据。WS信号 也在CK的下降沿变化。
本节我们使用16位/24位数据格式,16位时采用扩展帧格式(即将16位数据封装在32位帧中)
在24位模式下数据传输,需要对SPI_DR执行两次读取或写入操作。比如我们要发送0x8EAA33这个数据,就要分两次写入SPI_DR,第一次写入:0x8EAA,第二次写入0x33xx(xx可以为任意数值),这样就把0x8EAA33发送出去了。
SD卡读取到的 24位 WAV 数据流,是低字节在前,高字节在后的,比如,我们读到一个声道的数据(24bit),存储在buf[3] 里面,通过SPI_DR发送这个24位数据,过程如下:
SPI_DR=((u16)buf[2]; //第一次发送高16位数据
SPI_DR=(u16)buf[0]; //第二次发送低 8 位数据,完成一次24 bit 数据的发送
I2S时钟发生器:
图中I2SxCLK可以来自PLLI2S输出(通过R系数分频)或者来自外部时钟(I2S_CKIN引脚),一般我们使用前者作为I2SxCLK输入时钟。
一般我们需要根据音频采样率(fs,即CK的频率)来计算各个分频器的值,常用的音频采样率有:22.05Khz、44.1Khz、48Khz、96Khz、196Khz等。
当MCK输出使能时,fs 频率计算公式如下:
fs=I2SxCLK/[256*(2*I2SDIV+ODD)]
其中,I2SxCLK=(HSE/pllm)*PLLI2SN/PLLI2SR。HSE我们是8Mhz,pllm在系统时钟初始化时确定为8,综合:
fs= (1000*PLLI2SN/PLLI2SR )/[256*(2*I2SDIV+ODD)]
fs 单位是:Khz。其中:PLL2SN 取值范围:192~432;PLLI2SR 取值范围:2~7;I2SDIV取值范围:2~255;ODD 取值范围:0/1 。
//常用 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 采样率
};
I2S常用相关寄存器:
SPI_I2S配置寄存器:SPI_I2SCFGR
位11:I2SMOD 位,设置为 1,选择 I2S 模式,注意,必须在 I2S/SPI 禁止的时候,设置该位。
位10:I2SE 位,设置为 1,使能 I2S 外设,该位必须在 I2SMOD 位设置之后再设置。
位9:8 I2SCFG[1:0]位, 这两个位用于配置 I2S 模式,设置为 10,选择主模式(发送)。
位5:4 I2SSTD[1:0]位,这两个位用于选择 I2S 标准,设置为 00,选择飞利浦模式。
位3:CKPOL 位,用于设置空闲时时钟电平,设置为 0,空闲时时钟低电平。
位2:1 DATLEN[1:0]位,用于设置数据长度,00,表示 16 位数据;01 表示 24 位数据。
位0 CHLEN 位,用于设置通道长度,即帧长度,0,表示 16 位;1,表示 32 位。
SPI_I2S预分频器寄存器:SPI_I2SPR
位15:10 保留,必须保持复位值
位9 MCKOE:主时钟输出使能
0:禁止主时钟输出
1:使能主时钟输出
位8 ODD:预分频器的奇数因子
0:实际分频值为=I2SDIV*2
1:实际分频值为=(I2SDIV*2)+1
位7:0 I2SDIV:I2S线性预分频器
I2SDIV[7:0]=0 或 I2SDIV[7:0]=1 为禁用值
PLLI2S配置寄存器:RCC_PLLI2SCFGR
该寄存器用于配置PLLI2SR和PLLI2SN两个系数,PLLI2SR的取值范围是:2~7,PLLI2SN的取值范围是:192~432。
通过 STM32F4 的 I2S 驱动WM8978播放音乐的步骤:
1. 初始化WM8978
配置寄存器,包括软复位、DAC设置、输出设置和音量设置等。
2. 初始化I2S
void I2S_Init(SPI_TypeDef* SPIx, I2S_InitTypeDef* I2S_InitStruct); //初始化 I2S 函数
其中第二个参数是通过结构体 I2S_InitTypeDef 来定义的:
typedef struct
{
uint16_t I2S_Mode;
//第一个参数用来设置 I2S 的模式,也就是设置 SPI_I2SCFGR 寄存器的 I2SCFG 相关位。可以配置为主模式发送 I2S_Mode_MasterTx,主模式接受 I2S_Mode_MasterRx,从模式发送I2S_Mode_SlaveTx 以及从模式接受 I2S_Mode_SlaveRx 四种模式。
uint16_t I2S_Standard;
//第二个参数 I2S_Standard 用来设置 I2S 标准,可以设置为:飞利浦标准 I2S_Standard_Phillips,MSB 对齐标准 I2S_Standard_MSB,LSB 对齐标准 I2S_Standard_LSB以及 PCM 标准 I2S_Standard_PCMShort。
uint16_t I2S_DataFormat;
//第三个参数 I2S_DataFormat 用来设置 I2S 的数据通信格式。这里实际包含设置SPI_I2SCFGR 寄存器的 HCLEN 位(通道长度)以及 DATLEN 位(传输的数据长度)。当我们设置为 16 位标准格式I2S_DataFormat_16b 的时候,实际上传输的数据长度为 16 位,通道长度为 16 位。当我们设置为其他值的时候,通道长度都为 32 位。
uint16_t I2S_MCLKOutput;
//第四个参数 I2S_MCLKOutput 用来设置是否使能主时钟输出。我们实验会使能主时钟输出。
uint32_t I2S_AudioFreq;
//第五个参数 I2S_AudioFreq 用来设置 I2S 频率。实际根据输入的频率值,会来计算 SPI 预分频寄存器 SPI_I2SPR 的预分频奇数因子以及 I2S 线性预分频器的值。
//#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)
uint16_t I2S_CPOL;
//六个参数 I2S_CPOL 用来设置空闲状态时钟电平,这个比较好理解。取值为高电平I2S_CPOL_High 以及低电平 I2S_CPOL_Low。
}I2S_InitTypeDef;
3. 解析WAV文件,获取音频信号采样率和位数并设置I2S时钟分频器
这里,解析WAV文件,取得音频信号的采样率(fs)和位数(16位或32位),根据这两个参数,来设置 I2S 的时钟分频,
4. 设置DMA
I2S播放音频的时候,一般都是通过DMA来传输数据的,所以必须配置DMA。本节我们使用I2S2,其TX是使用DMA1数据流4的通道0来传输的。并且,STM32F4 的 DMA 具有双 缓冲机制,这样可以提高效率,大大方便了我们的数据传输,本章将 DMA1 数据流 4 设置为: 双缓冲循环模式,外设和存储器都是 16 位宽,并开启 DMA 传输完成中断(方便填充数据)。
5. 编写DMA传输完成中断服务函数
为了方便填充音频数据,我们使用DMA传输完成中断,每当一个缓冲数据发送完后,硬件自动切换为下一个缓冲,同时进入中断服务函数,填充数据到发送完的这个缓冲。
6. 开启DMA传输,填充数据
我们只需要开启DMA传输,然后及时填充WAV数据到DMA的两个缓存区即可。此时,就可以在WM8978的耳机和喇叭通道听到所播放的音乐了。
DMA_Cmd(DMA1_Stream4,ENABLE); //开启 DMA TX 传输,开始播放
4. 硬件设计
实验功能:
开机后,先初始化各外设,然后检测字库是否存在,如果检测无问题, 则开始循环播放 SD 卡 MUSIC 文件夹里面的歌曲(必须在 SD 卡根目录建立一个 MUSIC 文件 夹,并存放歌曲(仅支持 wav 格式)在里面),在 TFTLCD 上显示歌曲名字、播放时间、歌 曲总时间、歌曲总数目、当前歌曲的编号等信息。KEY0 用于选择下一曲,KEY2 用于选择上 一曲,KEY_UP 用来控制暂停/继续播放。DS0 还是用于指示程序运行状态。
图中,PHONE接口,可以用来插耳机。
5. 实验程序
5.1 main.c
#include "stm32f4xx.h"
#include "delay.h"
#include "usart.h"
#include "LED.h"
#include "lcd.h"
#include "Key.h"
#include "usmart.h"
#include "SRAM.h"
#include "Malloc.h"
#include "SDIO_Card.h"
#include "W25Q128.h"
#include "ff.h"
#include "exfuns.h"
#include "fontupd.h"
#include "text.h"
#include "WM8978.h"
#include "AudioPlay.h"
//LCD状态设置函数
void led_set(u8 sta)//只要工程目录下有usmart调试函数,主函数就必须调用这两个函数
{
LED1=sta;
}
//函数参数调用测试函数
void test_fun(void(*ledset)(u8),u8 sta)
{
led_set(sta);
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(168);
uart_init(115200);
LED_Init();
usmart_dev.init(84); //初始化USMART
LCD_Init();
Key_Init();
W25Q128_Init();
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,"2023年20月23日",16,0);
Show_Str(60,130,200,16,"KEY0:NEXT KEY2:PREV",16,0);
Show_Str(60,150,200,16,"KEY_UP:PAUSE/PLAY",16,0);
while(1)
{
audio_play();
}
}
5.2 I2S.c
#include "I2S.h"
#include "usart.h"
//I2S初始化
//参数I2S_Standard: @ref SPI_I2S_Standard I2S标准,
//I2S_Standard_Phillips,飞利浦标准;
//I2S_Standard_MSB,MSB对齐标准(右对齐);
//I2S_Standard_LSB,LSB对齐标准(左对齐);
//I2S_Standard_PCMShort,I2S_Standard_PCMLong:PCM标准
//参数I2S_Mode: 可以选择 I2S_Mode_SlaveTx:从机发送;I2S_Mode_SlaveRx:从机接收;I2S_Mode_MasterTx:主机发送;I2S_Mode_MasterRx:主机接收;
//参数I2S_Clock_Polarity I2S极性 I2S_CPOL_Low,时钟低电平有效;I2S_CPOL_High,时钟高电平有效
//参数I2S_DataFormat: 数据长度,I2S_DataFormat_16b,16位标准;I2S_DataFormat_16bextended,16位扩展(frame=32bit);I2S_DataFormat_24b,24位;I2S_DataFormat_32b,32位.
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
//注意是根据所要设置的I2SxCLK 反向设置公式中每一个参数
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采样率
};
//设置I2S的采样率
//SampleRate:采样率
//返回值:0,设置成功;1,无法设置
u8 I2S2_SampleRate_Set(u32 SampleRate)
{
u8 i=0;
u32 tempreg=0;
SampleRate=SampleRate/10;//缩小10倍
for(i=0;i<(sizeof(I2S_PSC_TBL)/10);i++)//看看改采样率是否可以支持
{
if(SampleRate==I2S_PSC_TBL[i][0])
break;
}
RCC_PLLI2SCmd(DISABLE);//先关闭PLLI2S
if(i==(sizeof(I2S_PSC_TBL)/10))
return 1;//搜遍了也找不到
RCC_PLLI2SConfig((u32)I2S_PSC_TBL[i][1],(u32)I2S_PSC_TBL[i][2]);//设置I2SxCLK的频率(x=2) 设置PLLI2SN PLLI2SR
RCC->CR|=1<<26; //开启I2S时钟 操作RCC控制寄存器
while((RCC->CR&1<<27)==0); //等待I2S时钟开启成功.
tempreg=I2S_PSC_TBL[i][3]<<0; //设置I2SDIV
tempreg|=I2S_PSC_TBL[i][4]<<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){}//等待DMA1_Stream1可配置
/* 配置 DMA Stream */
DMA_InitStructure.DMA_Channel = DMA_Channel_0; //通道0 SPI2_TX通道
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&SPI2->DR;//外设地址为:(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)DMA1_Stream4,传输完成标志
{
DMA_ClearITPendingBit(DMA1_Stream4,DMA_IT_TCIF4);
i2s_tx_callback(); //执行回调函数,读取数据等操作在这里面处理
}
}
//I2S开始播放
void I2S_Play_Start(void)
{
DMA_Cmd(DMA1_Stream4,ENABLE);//开启DMA TX传输,开始播放
}
//关闭I2S播放
void I2S_Play_Stop(void)
{
DMA_Cmd(DMA1_Stream4,DISABLE);//关闭DMA,结束播放
}
5.3 I2S.h
#ifndef __I2S_H
#define __I2S_H
#include "sys.h"
extern void (*i2s_tx_callback)(void); //IIS TX回调函数指针
void I2S2_Init(u16 I2S_Standard ,u16 I2S_Mode,u16 I2S_Clock_Polarity,u16 I2S_DataFormat);
u8 I2S2_SampleRate_Set(u32 samplerate);
void I2S2_TX_DMA_Init(u8* buf0,u8 *buf1,u16 num);
void I2S_Play_Start(void);
void I2S_Play_Stop(void);
#endif
5.4 WM8978.c
#include "WM8978.h"
#include "MyI2C.h"
#include "delay.h"
//WM8978寄存器值缓存区(总共58个寄存器,0~57),占用116字节内存
//因为WM8978的IIC操作不支持读操作,所以在本地保存所有寄存器值
//写WM8978寄存器时,同步更新到本地寄存器值,读寄存器时,直接返回本地保存的寄存器值.
//注意:WM8978的寄存器值是9位的,所以要用u16来存储.
static u16 WM8978_REGVAL_TBL[58]=
{
0X0000,0X0000,0X0000,0X0000,0X0050,0X0000,0X0140,0X0000,
0X0000,0X0000,0X0000,0X00FF,0X00FF,0X0000,0X0100,0X00FF,
0X00FF,0X0000,0X012C,0X002C,0X002C,0X002C,0X002C,0X0000,
0X0032,0X0000,0X0000,0X0000,0X0000,0X0000,0X0000,0X0000,
0X0038,0X000B,0X0032,0X0000,0X0008,0X000C,0X0093,0X00E9,
0X0000,0X0000,0X0000,0X0000,0X0003,0X0010,0X0010,0X0100,
0X0100,0X0002,0X0001,0X0001,0X0039,0X0039,0X0039,0X0039,
0X0001,0X0001
};
//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,AF5 I2S_LRCK
GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_SPI2); //PB13,AF5 I2S_SCLK
GPIO_PinAFConfig(GPIOC,GPIO_PinSource3,GPIO_AF_SPI2); //PC3 ,AF5 I2S_DACDATA
GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_SPI2); //PC6 ,AF5 I2S_MCK
GPIO_PinAFConfig(GPIOC,GPIO_PinSource2,GPIO_AF6_SPI2); //PC2 ,AF6 I2S_ADCDATA I2S2ext_SD是AF6!!!
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写寄存器
//RegisterAddress:寄存器地址
//Value:要写入寄存器的值
//返回值:0,成功;
// 其他,错误代码
u8 WM8978_Write_Reg(u8 RegisterAddress,u16 Value)
{
//WM8978遵循IIC时序
IIC_Start(); //开始信号
IIC_Send_Byte((WM8978_ADDR<<1)|0);//发送器件地址(7位)+写命令(最低位是读写命令位)
if(IIC_Wait_Ack()) //IIC_Wait_Ack函数返回1表示接收应答失败
return 1; //等待应答(成功?/失败?)
IIC_Send_Byte((RegisterAddress<<1)|((Value>>8)&0X01));//写寄存器地址(写命令是零)+数据的最高位
if(IIC_Wait_Ack())
return 2; //等待应答(成功?/失败?)
IIC_Send_Byte(Value&0XFF); //发送数据
if(IIC_Wait_Ack())
return 3; //等待应答(成功?/失败?)
IIC_Stop();
WM8978_REGVAL_TBL[RegisterAddress]=Value; //保存寄存器值到本地
return 0;
}
//WM8978读寄存器
//就是读取本地寄存器值缓冲区内的对应值
//RegisterAddress:寄存器地址
//返回值:寄存器值
u16 WM8978_Read_Reg(u8 RegisterAddress)
{
return WM8978_REGVAL_TBL[RegisterAddress];
}
//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 输入通道配置
//micen:MIC开启(1)/关闭(0)
//lineinen:Line In开启(1)/关闭(0)
//auxen:aux开启(1)/关闭(0)
void WM8978_Input_Cfg(u8 micen,u8 lineinen,u8 auxen)
{
u16 regval;
regval=WM8978_Read_Reg(2); //读取R2
if(micen)
regval|=3<<2; //开启INPPGAENR,INPPGAENL(MIC的PGA放大)
else
regval&=~(3<<2); //关闭INPPGAENR,INPPGAENL.
WM8978_Write_Reg(2,regval); //设置R2
regval=WM8978_Read_Reg(44); //读取R44
if(micen)
regval|=3<<4|3<<0; //开启LIN2INPPGA,LIP2INPGA,RIN2INPPGA,RIP2INPGA.
else
regval&=~(3<<4|3<<0); //关闭LIN2INPPGA,LIP2INPGA,RIN2INPPGA,RIP2INPGA.
WM8978_Write_Reg(44,regval);//设置R44
if(lineinen)
WM8978_LINEIN_Gain(5);//LINE IN 0dB增益
else
WM8978_LINEIN_Gain(0); //关闭LINE IN
if(auxen)
WM8978_AUX_Gain(7);//AUX 6dB增益
else
WM8978_AUX_Gain(0); //关闭AUX输入
}
//WM8978 输出配置
//dacen:DAC输出(放音)开启(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设置
}
//WM8978 MIC增益设置(不包括BOOST的20dB,MIC-->ADC输入部分的增益)
//gain:0~63,对应-12dB~35.25dB,0.75dB/Step
void WM8978_MIC_Gain(u8 gain)
{
gain&=0X3F;
WM8978_Write_Reg(45,gain); //R45,左通道PGA设置
WM8978_Write_Reg(46,gain|1<<8); //R46,右通道PGA设置
}
//WM8978 L2/R2(也就是Line In)增益设置(L2/R2-->ADC输入部分的增益)
//gain:0~7,0表示通道禁止,1~7,对应-12dB~6dB,3dB/Step
void WM8978_LINEIN_Gain(u8 gain)
{
u16 regval;
gain&=0X07;
regval=WM8978_Read_Reg(47); //读取R47
regval&=~(7<<4); //清除原来的设置
WM8978_Write_Reg(47,regval|gain<<4);//设置R47
regval=WM8978_Read_Reg(48); //读取R48
regval&=~(7<<4); //清除原来的设置
WM8978_Write_Reg(48,regval|gain<<4);//设置R48
}
//WM8978 AUXR,AUXL(PWM音频部分)增益设置(AUXR/L-->ADC输入部分的增益)
//gain:0~7,0表示通道禁止,1~7,对应-12dB~6dB,3dB/Step
void WM8978_AUX_Gain(u8 gain)
{
u16 regval;
gain&=0X07;
regval=WM8978_Read_Reg(47); //读取R47
regval&=~(7<<0); //清除原来的设置
WM8978_Write_Reg(47,regval|gain<<0);//设置R47
regval=WM8978_Read_Reg(48); //读取R48
regval&=~(7<<0); //清除原来的设置
WM8978_Write_Reg(48,regval|gain<<0);//设置R48
}
//设置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工作模式设置
}
//设置耳机左右声道音量
//voll:左声道音量(0~63)
//volr:右声道音量(0~63)
void WM8978_HPvol_Set(u8 voll,u8 volr)
{
voll&=0X3F;
volr&=0X3F;//限定范围
if(voll==0)voll|=1<<6;//音量为0时,直接mute
if(volr==0)volr|=1<<6;//音量为0时,直接mute
WM8978_Write_Reg(52,voll); //R52,耳机左声道音量设置
WM8978_Write_Reg(53,volr|(1<<8)); //R53,耳机右声道音量设置,同步更新(HPVU=1)
}
//设置喇叭音量
//voll:左声道音量(0~63)
void WM8978_SPKvol_Set(u8 volx)
{
volx&=0X3F;//限定范围
if(volx==0)volx|=1<<6;//音量为0时,直接mute
WM8978_Write_Reg(54,volx); //R54,喇叭左声道音量设置
WM8978_Write_Reg(55,volx|(1<<8)); //R55,喇叭右声道音量设置,同步更新(SPKVU=1)
}
//设置3D环绕声
//depth:0~15(3D强度,0最弱,15最强)
void WM8978_3D_Set(u8 depth)
{
depth&=0XF;//限定范围
WM8978_Write_Reg(41,depth); //R41,3D环绕设置
}
//设置EQ/3D作用方向
//dir:0,在ADC起作用
// 1,在DAC起作用(默认)
void WM8978_EQ_3D_Dir(u8 dir)
{
u16 regval;
regval=WM8978_Read_Reg(0X12);
if(dir)regval|=1<<8;
else regval&=~(1<<8);
WM8978_Write_Reg(18,regval);//R18,EQ1的第9位控制EQ/3D方向
}
//设置EQ1
//cfreq:截止频率,0~3,分别对应:80/105/135/175Hz
//gain:增益,0~24,对应-12~+12dB
void WM8978_EQ1_Set(u8 cfreq,u8 gain)
{
u16 regval;
cfreq&=0X3;//限定范围
if(gain>24)gain=24;
gain=24-gain;
regval=WM8978_Read_Reg(18);
regval&=0X100;
regval|=cfreq<<5; //设置截止频率
regval|=gain; //设置增益
WM8978_Write_Reg(18,regval);//R18,EQ1设置
}
//设置EQ2
//cfreq:中心频率,0~3,分别对应:230/300/385/500Hz
//gain:增益,0~24,对应-12~+12dB
void WM8978_EQ2_Set(u8 cfreq,u8 gain)
{
u16 regval=0;
cfreq&=0X3;//限定范围
if(gain>24)gain=24;
gain=24-gain;
regval|=cfreq<<5; //设置截止频率
regval|=gain; //设置增益
WM8978_Write_Reg(19,regval);//R19,EQ2设置
}
//设置EQ3
//cfreq:中心频率,0~3,分别对应:650/850/1100/1400Hz
//gain:增益,0~24,对应-12~+12dB
void WM8978_EQ3_Set(u8 cfreq,u8 gain)
{
u16 regval=0;
cfreq&=0X3;//限定范围
if(gain>24)gain=24;
gain=24-gain;
regval|=cfreq<<5; //设置截止频率
regval|=gain; //设置增益
WM8978_Write_Reg(20,regval);//R20,EQ3设置
}
//设置EQ4
//cfreq:中心频率,0~3,分别对应:1800/2400/3200/4100Hz
//gain:增益,0~24,对应-12~+12dB
void WM8978_EQ4_Set(u8 cfreq,u8 gain)
{
u16 regval=0;
cfreq&=0X3;//限定范围
if(gain>24)gain=24;
gain=24-gain;
regval|=cfreq<<5; //设置截止频率
regval|=gain; //设置增益
WM8978_Write_Reg(21,regval);//R21,EQ4设置
}
//设置EQ5
//cfreq:中心频率,0~3,分别对应:5300/6900/9000/11700Hz
//gain:增益,0~24,对应-12~+12dB
void WM8978_EQ5_Set(u8 cfreq,u8 gain)
{
u16 regval=0;
cfreq&=0X3;//限定范围
if(gain>24)gain=24;
gain=24-gain;
regval|=cfreq<<5; //设置截止频率
regval|=gain; //设置增益
WM8978_Write_Reg(22,regval);//R22,EQ5设置
}
5.5 WM8978.h
#ifndef __WM8978_H
#define __WM8978_H
#include "sys.h"
#define WM8978_ADDR 0X1A //WM8978的器件地址,固定为0X1A
#define EQ1_80Hz 0X00
#define EQ1_105Hz 0X01
#define EQ1_135Hz 0X02
#define EQ1_175Hz 0X03
#define EQ2_230Hz 0X00
#define EQ2_300Hz 0X01
#define EQ2_385Hz 0X02
#define EQ2_500Hz 0X03
#define EQ3_650Hz 0X00
#define EQ3_850Hz 0X01
#define EQ3_1100Hz 0X02
#define EQ3_14000Hz 0X03
#define EQ4_1800Hz 0X00
#define EQ4_2400Hz 0X01
#define EQ4_3200Hz 0X02
#define EQ4_4100Hz 0X03
#define EQ5_5300Hz 0X00
#define EQ5_6900Hz 0X01
#define EQ5_9000Hz 0X02
#define EQ5_11700Hz 0X03
u8 WM8978_Init(void);
void WM8978_ADDA_Cfg(u8 dacen,u8 adcen);
void WM8978_Input_Cfg(u8 micen,u8 lineinen,u8 auxen);
void WM8978_Output_Cfg(u8 dacen,u8 bpsen);
void WM8978_MIC_Gain(u8 gain);
void WM8978_LINEIN_Gain(u8 gain);
void WM8978_AUX_Gain(u8 gain);
u8 WM8978_Write_Reg(u8 reg,u16 val);
u16 WM8978_Read_Reg(u8 reg);
void WM8978_HPvol_Set(u8 voll,u8 volr);
void WM8978_SPKvol_Set(u8 volx);
void WM8978_I2S_Cfg(u8 fmt,u8 len);
void WM8978_3D_Set(u8 depth);
void WM8978_EQ_3D_Dir(u8 dir);
void WM8978_EQ1_Set(u8 cfreq,u8 gain);
void WM8978_EQ2_Set(u8 cfreq,u8 gain);
void WM8978_EQ3_Set(u8 cfreq,u8 gain);
void WM8978_EQ4_Set(u8 cfreq,u8 gain);
void WM8978_EQ5_Set(u8 cfreq,u8 gain);
#endif