STM32F4_音乐播放器

news2024/11/16 17:44:07

目录

前言

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个引脚组成:

  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,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


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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

强化学习问题(7)--- Python和Pytorch,Tensorflow的版本对应

1.问题 之前下载的python3.8&#xff0c;在对应Pytorch和Tensorflow时没太在意版本&#xff0c;在运行一些代码时&#xff0c;提示Pytorch和Tensorflow版本过高&#xff0c;直接降下来&#xff0c;有时候又和Python3.8不兼容&#xff0c;所以又在虚拟环境搞一个Pyhon3.7&#x…

Brachistochrone:使用变分法找到最快下降曲线

一、说明 对于任何对数学和科学感兴趣的人&#xff0c;您可能已经知道了急速线&#xff0c;因为它经常在各种流行的教学频道&#xff08;例如 Vsauce 和 3Blue1Brown&#xff09;上谈论。虽然有多种方法可以解决急速线问题&#xff0c;但在这篇文章中&#xff0c;本文将使用变分…

【C++】哈希应用——海量数据面试题

哈希应用——海量数据面试题 一、位图应用1、给定100亿个整数&#xff0c;设计算法找到只出现一次的整数&#xff1f;2、给两个文件&#xff0c;分别有100亿个整数&#xff0c;我们只有1G内存&#xff0c;如何找到两个文件交集&#xff1f;&#xff08;1&#xff09;用一个位图…

Mac安装nginx(Homebrew)

查看需要安装 nginx 的信息 brew info nginxDocroot 默认为 /usr/local/var/www 在 /opt/homebrew/etc/nginx/nginx.conf 配置文件中默认端口被配置为8080&#xff0c;从而使 nginx 运行时不需要加 sudo nginx将在 /opt/homebrew//etc/nginx/servers/ 目录中加载所有文件 …

Springboot+vue的学生考勤管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的学生考勤管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的学生考勤管理系统&#xff0c;采用M&#xff08;model&a…

Gin框架--参数接收函数

1.get 请求 穿参数 #根据返回的bool 判断是否正确传值 _p, err : c.GetQuery("pkg") if !err {p.ReError(c, http.StatusBadRequest, "params pkg empty", "")return} #默认值接收方法_p : c.DefaultQuery("pkg", "hmf") …

【java计算机毕设】高校奖学金管理系统 java springmvc vue mysql 送文档+ppt

目录 1.项目功能截图 2.项目简介 3.源码下载地址 1.项目功能截图 2.项目简介 ssm奖学金系统 医院系统 idea mysql5.7/8 tomcat8 html jdk1.8 奖学金管理系统。基于SpringBootVue框架开发&#xff0c;方便学生直观得查看学校奖学金的评选时间、评选资格和评选内容&#xff0…

C语言学习系列->动态内存管理

文章目录 前言概述&#x1f6a9;malloc and free&#x1f51c;malloc&#x1f51c;free &#x1f6a9;calloc and realloc&#x1f51c;calloc&#x1f51c;realloc 前言 要想学好数据结构&#xff0c;在C语言学习过程中就需要把指针、结构体和动态内存管理学好。在前面的文章&…

2006-2019年全国30省绿色创新效率、绿色投资效率:基于SBM-DEA测算面板数据(数据+Stata代码)

1、来源&#xff1a;各省年鉴、统计局、科技年鉴 2、时间&#xff1a;2006-2019 3、范围&#xff1a;全国 30 个省份 4、指标&#xff1a; 原始数据指标&#xff1a;R&D 全时人员当量 (万人年)、R&D 资本存量 (亿元&#xff1b;利用以 1999 年为初期永续存量法&a…

css之Flex弹性布局(父项常见属性)

文章目录 &#x1f415;前言&#xff1a;&#x1f3e8;定义flex容器 display:flex&#x1f3e8;在flex容器中子组件进行排列&#x1fa82;行排列 flex-direction: row&#x1fa82;将行排列进行翻转排列 flex-direction: row-reverse&#x1f3c5;按列排列 flex-direction: col…

postman自动化运行接口测试用例

做过接口测试的人&#xff0c;应该都知道postman &#xff0c;我们在日常的时候都可以利用postman做接口测试&#xff0c;我们可以把接口的case保存下来在collection里面&#xff0c;那么可能会有这样的需求&#xff0c;我们怎么把collection的用例放到jenkins中定时执行呢&…

C++STL的迭代器(iterator)

一、定义 迭代器是一种检查容器内元素并且遍历容器内元素的数据类型。 【引用自&#xff1a;C迭代器&#xff08;iterator&#xff09;_c iterator_NiUoW的博客-CSDN博客】迭代器是一个变量&#xff0c;相当于容器和操纵容器的算法之间的中介。C更趋向于使用迭代器而不是数组下…

【GESP】2023年06月图形化三级 -- 自幂数判断

文章目录 自幂数判断【题目描述】【输入描述】【输出描述】【参考答案】其他测试用例 自幂数判断 【题目描述】 自幂数是指N位数各位数字N次方之和是本身&#xff0c;如153是3位数&#xff0c;其每位数的3次方之和是153本身&#xff0c;因此153是自幂数&#xff0c;1634是4位数…

STM32进行LVGL裸机移植

本文的移植参考的是正点原子的课程《手把手教你学LVGL图形界面编程》 基于该课程和《LVGL开发指南_V1.3》“第二章 LVGL 无操作系统移植”&#xff0c;然后结合自身的实际情况进行整理。 先根据自己的习惯&#xff0c;创建基础的单片机工程&#xff0c;然后在APP业务层和DRIVE…

cdm解决‘ping‘ 或者nslookup不是内部或外部命令,也不是可运行的程序或批处理文件的问题

当我们在执行cmd时&#xff0c;会出现不是内部或外部命令&#xff0c;也不是可运行的程序的提示。 搜索环境变量 点开高级 >> 环境变量 打开Path&#xff0c;看是否在Path变量值中存在以下项目&#xff1a; %SystemRoot%/system32; %SystemRoot%; %SystemRoot%/Syste…

【JVM系列】- 探索·运行时数据区的私有结构

探索运行时数据区的私有结构 文章目录 探索运行时数据区的私有结构运行时数据区的结构与概念认识线程了解守护线程和普通线程JVM系统线程 程序计数器&#xff08;PC寄存器&#xff09;概述PC寄存器的特点PC寄存器的作用 透过案例了解寄存器为什么需要用PC寄存器来存放字节码的指…

C语言基础-循环与数组

目录 循环 while 循环&#xff1a; for 循环&#xff1a; do while 循环&#xff1a; 中断循环&#xff1a; break continue&#xff1a; 数组 数组&#xff1a;用来装一组数的类型。声明形式如下&#xff1a; 定义数组类型变量&#xff1a; 下标&#xff1a;即各元素…

初出茅庐的小李博客之ESP8266获取自己B站粉丝数据

获取方式 ESP8266发起HTTP请求解析json数据 获取粉丝API: https://api.bilibili.com/x/relation/stat?vmid349513188API浏览器测试返回结果 {"code": 0,"message": "0","ttl": 1,"data": {"mid": 349513188, …

Python+Appium实现自动化测试

一、环境准备 1.脚本语言&#xff1a;Python3.x IDE&#xff1a;安装Pycharm 2.安装Java JDK 、Android SDK 3.adb环境&#xff0c;path添加E:\Software\Android_SDK\platform-tools 4.安装Appium for windows&#xff0c;官网地址 Redirecting 点击下载按钮会到GitHub的…

【safetensor】介绍和基础代码

Hugging Face, EleutherAI, StabilityAI 用的多 介绍 文件形式 header&#xff0c;体现其特性。如果强行将pickle或者空软连接 打开&#xff0c;会出现报错。解决详见&#xff1a;debug 连接到其他教程结构和参数 安装 with pip:Copied pip install safetensors with con…