STM32F4_音乐播放器

目录

前言

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”,即0x46464952u32 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 ” ,即0x20746D66u32 ChunkSize;  //子集合大小(不包括 ID 和 Size);这里为:20u16 AudioFormat; //音频格式;0x10,表示线性PCM;0x11,表示 IMA ADPCMu16 NumOfChannels;  //通道数量;1,表示单声道;2,表示双声道u32 SampleRate;   //采样率;0x1F40,表示8Khzu32 ByteRate;     //字节速率=采样率*通道数*(ADC位数/8)u16 BlockAlign;   //块对齐(字节)=通道数*(ADC位数/8)u16 BitsPerSample;  //单个采样数据大小;16位 ADPCM,设置为16u16 ByteExtraData;  //附加的数据字节;2个;线性PCM,没有这个参数
}ChunkFMT;

        Fact 块(Fact Chunk),该块为可选块,以 “fact” 作为标示,不是每个 WAV文件 都有,在 非PCM格式 的文件中,一般会在Format 结构后面加入一个Fact 块,该块 Chunk 结构如下:

        

//fact块typedef__packed struct
{u32 ChunkID;  //chunk id;这里固定为 “fact” ,即0x74636166u32 ChunkSize; //子集合大小(不包括ID 和 Size);这里为4u32 DataFactSize; //数据转换为 PCM 格式后的大小
}ChunkFACT;//DataFactSize 是这个Chunk中最重要的数据,如果这是某种压缩格式的声音文件,那么从这里就可以知道它解压缩后的大小。

        最后是数据块(Data Chunk),该块是真正保存 wav 数据的地方,以 “data” 作为该 Chunk 的标示,然后是数据的大小。数据块的 Chunk 结构如下:

//data块typedef__packed struct
{u32 ChunkID; //chunk id;这里固定为 “data” ,即0x61746164u32 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个引脚组成:

  1.         ADCDAT:ADC数据输出
  2.         DACDAT:DAC数据输入
  3.         LRC:数据左 / 右对齐时钟
  4.         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接口比较特殊:

  1.         只支持写,不支持读数据
  2.         寄存器长度为7位,数据长度为9位
  3.         寄存器字节的最低位用于传输数据最高位(也就是 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布线;

  1. LIP:模拟输入,左麦克风前置放大同相输入
  2. LIN:模拟输入,左麦克风前置放大反相输入
  3. L2/GPIO2:模拟输入,左通道线输入/次要的麦克风前置放大同相输入/GPIO引脚
  4. RIP:模拟输入,右麦克风前置放大同相输入
  5. RIN:模拟输入,右麦克风前置放大反相输入
  6. R2/GPIO3:模拟输入,右通道线输入/次要的麦克风前置放大同相输入/GPIO引脚
  7. LRC:数字输入/输出,DAC和ADC的采样率时钟
  8. BCLK:数字输入/输出,数字音频位时钟
  9. ADCDAT:数字输出,ADC数字音频数据输出
  10. DACDAT:数字输入,DAC数据音频数据输入
  11. MCLK:数字输入,主时钟输入
  12. DGND:电源,数字地
  13. DCVDD:电源,数字核心逻辑电源
  14. DBVDD:电源,数字缓冲器电源
  15. CSB/GPIO1:数字输入/输出,3线微处理器片选/通用输入/输出1
  16. SCLK:数字输入/输出,3线/2线微处理器时钟输入
  17. SDIN:数字输入/输出,3线/2线微处理器数据输入
  18. MODE:数字输入,控制接口选择
  19. AUXL:模拟输入,左辅助输入
  20. AUXR:模拟输入,右辅助输入
  21. OUT4:模拟输出,中轨耳机伪地缓冲或者右线输出或者单声道混合输出
  22. OUT3:模拟输出,中轨耳机伪地缓冲或者左线输出
  23. ROUT2:模拟输出,第二右输出或者BTL扬声器同相输出驱动
  24. SPKGND:电源,扬声器地(供给供给扬声器放大器和OUT3/OUT4)
  25. LOUT2:模拟输出,第二左输出或者BTL扬声器反相输出
  26. SPKVDD:电源,扬声器电源(只供给扬声器放大器)
  27. VMID:参考,解耦ADC和DAC的参考电压
  28. AGND:电源,模拟地(供给ADC和DAC)
  29. ROUT1:模拟输出,耳机右输出
  30. LOUT1:模拟输出,耳机左输出
  31. AVDD:电源,模拟电源(供给ADC和DAC)
  32. 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,ODDconst 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);  //初始化USMARTLCD_Init();Key_Init();W25Q128_Init();WM8978_Init();				//初始化WM8978WM8978_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); //复位SPI2RCC_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);//初始化IISSPI_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);//先关闭PLLI2Sif(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 PLLI2SRRCC->CR|=1<<26;					//开启I2S时钟  操作RCC控制寄存器while((RCC->CR&1<<27)==0);		//等待I2S时钟开启成功. tempreg=I2S_PSC_TBL[i][3]<<0;	//设置I2SDIVtempreg|=I2S_PSC_TBL[i][4]<<8;	//设置ODD位tempreg|=1<<9;					//使能MCKOE位,输出MCKSPI2->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->DRDMA_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 StreamDMA_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;//抢占优先级0NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;//子优先级0NVIC_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;//100MHzGPIO_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;//100MHzGPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化GPIO_PinAFConfig(GPIOB,GPIO_PinSource12,GPIO_AF_SPI2); //PB12,AF5  I2S_LRCKGPIO_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_MCKGPIO_PinAFConfig(GPIOC,GPIO_PinSource2,GPIO_AF6_SPI2);	//PC2 ,AF6  I2S_ADCDATA  I2S2ext_SD是AF6!!!IIC_Init();//初始化IIC接口res=WM8978_Write_Reg(0,0);	//软复位WM8978if(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);	//读取R3if(DACEN)regval|=3<<0;		//R3最低2个位设置为1,开启DACR&DACLelse regval&=~(3<<0);		//R3最低2个位清零,关闭DACR&DACL.WM8978_Write_Reg(3,regval);	//设置R3regval=WM8978_Read_Reg(2);	//读取R2if(ADCEN)regval|=3<<0;		//R2最低2个位设置为1,开启ADCR&ADCLelse 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);	//读取R2if(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);	//读取R44if(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);//设置R44if(lineinen)WM8978_LINEIN_Gain(5);//LINE IN 0dB增益else WM8978_LINEIN_Gain(0);	//关闭LINE INif(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);	//读取R47regval&=~(7<<4);			//清除原来的设置 WM8978_Write_Reg(47,regval|gain<<4);//设置R47regval=WM8978_Read_Reg(48);	//读取R48regval&=~(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);	//读取R47regval&=~(7<<0);			//清除原来的设置 WM8978_Write_Reg(47,regval|gain<<0);//设置R47regval=WM8978_Read_Reg(48);	//读取R48regval&=~(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时,直接muteif(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		0X03u8 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/167582.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Openssl数据安全传输平台007:共享内存及代码的实现 ——待完善项目具体代码和逻辑

文章目录 0. 代码仓库1. 使用流程案例代码&#xff1a; 2. API解析2.1 创建或打开一块共享内存区2.2 将当前进程和共享内存关联到一起2.3 将共享内存和当前进程分离2.4 共享内存操作 -&#xff08; 删除共享内存 &#xff09; 3. 思考问题3. ftok函数4. 共享内存API封装-以本项…

链表的中间结点-力扣

1、题目描述 给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海…

在 Python 3 中释放 LightGBM 的力量:您的机器学习大师之路

机器学习是 Python 占据主导地位的领域,它一直在给全球各行各业带来革命性的变化。要在这个不断变化的环境中脱颖而出,掌握正确的工具是关键。LightGBM 就是这样一个工具,它是一个强大且快速的梯度提升框架。在这份综合指南中,我们将通过实际示例和示例数据集从基础知识到高…

大语言模型(LLM)综述(二):开发大语言模型的公开可用资源

A Survey of Large Language Models 前言3. RESOURCES OF LLMS3.1 公开可用的模型CheckPoints或 API3.2 常用语料库3.3 库资源 前言 随着人工智能和机器学习领域的迅速发展&#xff0c;语言模型已经从简单的词袋模型&#xff08;Bag-of-Words&#xff09;和N-gram模型演变为更…

简单说明反射和new的区别和反射的使用代码展示

目录 1.反射的认识 2.反射和new的区别 3.反射的使用代码展示 4.反射优点和缺点 1.反射的认识 反射是Java语言的一种机制&#xff0c;它允许程序在运行时检查和操作类、方法、字段等信息&#xff0c;而不需要提前知道它们的具体定义。通过反射&#xff0c;我们可以在运行时动…

Redis缓存(缓存预热,缓存穿透,缓存雪崩,缓存击穿)

目录 一, 缓存 1, 什么是缓存 2, 什么是热点数据(热词) 3, 缓存更新策略 3.1 定期生成 3.2 实时生成 二, Redis缓存可能出现的问题 1, 缓存预热 1.1 什么是缓存预热 1.2 缓存预热的过程 2, 缓存穿透 2.1 什么是缓存穿透 2.2 缓存穿透产生的原因 2.3 缓存穿透的解…

图论03-【无权无向】-图的深度优先DFS遍历-路径问题/检测环/二分图

文章目录 1. 代码仓库2. 单源路径2.1 思路2.2 主要代码 3. 所有点对路径3.1 思路3.2 主要代码 4. 路径问题的优化-提前结束递归4.1 思路4.2 主要代码 5. 检测环5.1 思路5.2 主要代码 6. 二分图6.1 思路6.2 主要代码6.2.1 遍历每个联通分量6.2.2 递归判断相邻两点的颜色是否一致…

概念解析 | 毫米波雷达与计算机视觉的融合

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:毫米波雷达与计算机视觉的融合。 毫米波雷达与计算机视觉的融合 Sensors | Free Full-Text | MmWave Radar and Vision Fusion for Object Detection in Autonomous Driving: A …

最详细STM32,cubeMX串口发送,接收数据

这篇文章将详细介绍 串口 发送数据&#xff0c;接受数据。 文章目录 前言一、串口的基础知识二、cubeMX 配置三、自动生成代码解析四、串口发送数据函数五、使用串口收发数据点亮 led重定向函数&#xff1a; 总结 前言 实验开发板&#xff1a;STM32F103C8T6。所需软件&#xf…

pycharm操作git、前后端项目上传到gitee

pycharm操作git 之前用命令做的所有操作&#xff0c;使用pychrm点点就可以完成 克隆代码 上方工具栏Git ⇢ \dashrightarrow ⇢ Clone ⇢ \dashrightarrow ⇢ 填写地址&#xff08;http、ssh&#xff09; 提交到暂存区&#xff0c;提交到版本库&#xff0c;推送到远程 直接…

rust学习——函数返回值

概念 Rust 中的函数定义以 fn 开始&#xff0c;后跟着函数名和一对圆括号。大括号告诉编译器函数体在哪里开始和结束。 特殊的地方——函数返回值 错误的写法 正解1 去掉分号 fn main() {let x plus_one(5);println!("The value of x is: {}", x); }fn plus_…

【小白专用 已验证】PHP连接SQLServer数据库

PHP是一门强大的服务器端脚本语言&#xff0c;而SQL Server是Microsoft开发的一款关系型数据库管理系统。为了在PHP中直接操纵SQL Server数据库&#xff0c;需要通过安装SQL Server扩展来实现。这篇文章将详细介绍如何在PHP中使用SQL Server扩展来操作数据库。 首先&#xff0…

linux/kali2023.1工具集合()

1 系统硬件信息查询 arch 显示机器的处理器架构(1) uname -m 显示机器的处理器架构(2) uname -r 显示正在使用的内核版本 dmidecode -q 显示硬件系统部件 - (SMBIOS / DMI) hdparm -i /dev/hda 罗列一个磁盘的架构特性 hdparm -tT /dev/sda 在磁盘上执行测试性读取操作 cat /p…

使用 Rust 和 cURL 库下载程序

以下是一个使用 Rust 和 cURL 库的下载器程序&#xff0c;用于下载 图像。此程序使用了 https://www.duoip.cn/get_proxy 的代码。 extern crate curl; ​ use std::io::{self, Read}; use std::error::Error; ​ fn main() {let url "https://www.baidu.com";let …

Access,Trunk,Hybrid的一些接触知识以及实验

VLAN基本配置 一、实验目的 1.掌握VLAN基础配置原理&#xff1b; 2.掌握Access接口工作原理及配置&#xff1b; 3.掌握Trunk接口工作原理及配置&#xff1b; 4.掌握Hybrid接口工作原理及配置。 二、实验设备 1.电脑1台&#xff1b; 2.ENSP仿真软件。 三、实验内容及步骤 VLAN( …

论文阅读:Efficient Point Cloud Segmentation with Geometry-Aware Sparse Networks

来源&#xff1a;ECCV2022 链接&#xff1a;Efficient Point Cloud Segmentation with Geometry-Aware Sparse Networks | SpringerLink 0、Abstract 在点云学习中&#xff0c;稀疏性和几何性是两个核心特性。近年来&#xff0c;为了提高点云语义分割的性能&#xff0c;人们提…

【网络编程】应用层——HTTP协议

文章目录 一、HTTP协议简介二、认识URL三、HTTP协议格式1. HTTP请求协议格式2. HTTP响应协议格式 三、构建HTTP请求和响应四、HTTP的方法五、HTTP的状态码六、HTTP常见的Header七、Cookie和Session 一、HTTP协议简介 HTTP 协议 是 Hyper Text Transfer Protocol&#xff08;超文…

给Windows文件夹添加备注信息

自己的电脑中文件夹为了安装各种开发环境&#xff0c;基本都是英文字母命名&#xff0c;就导致好多东西猛地一看找不着。此时加个备注会不会就好很多呢&#xff1f;就如以下这种 设置方法&#xff1a; 1、展示备注 右键展示的列表头部&#xff0c;会出现展示项&#xff0c;一…

AD9371 官方例程HDL详解之JESD204B TX_CLK生成 (二)

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 AD9371 官方例程 时钟间的关系与生成 &#xff1a; AD9371 官方…

Metabase:简单快捷的商业智能与数据分析工具 | 开源日报 No.61

moby/moby Stars: 66.8k License: Apache-2.0 Moby 是一个由 Docker 创建的开源项目&#xff0c;旨在实现和加速软件容器化。它提供了工具包组件的“乐高集”&#xff0c;可以将它们组装成基于容器的自定义系统的框架。组件包括容器生成工具、容器注册表、业务流程工具、运行时…