第七章 实战项目提升,完善简历
18.SD卡存放音频WAV播放(中)
如图1所示是WM8731中11个寄存器功能说明概况图,我们需要对照手册,再去深入了解WM8731中的11个寄存器,怎么去配置这些寄存器达到预期的效果,起初笔者在刚接触这款芯片时,猛地一看这么多个寄存器,那么为了保险起见,就想着把所有的寄存器都按照顺序配置一遍,实际在这里走了一些弯路并且反复排查耽误了两天时间。
注意到复位寄存器R15,其相当于之前PHY芯片对MDIO接口的软复位,当我们写入这个复位命令后实际上证明是WM8731就会将所有的控制寄存器都写入0,本来开发板一上电WM8731会自己复位,当然这里是写入默认值的设置,而软复位后等于清零所有寄存器了,就导致前面的操作都白干了。
图1 WM8731中11个寄存器功能说明概况图
关于这11个寄存器应该如何去配置,因为数据手册上也已经写得非常清楚了,所以在这里笔者不想过多赘述,直接给出这个例程中的WM8731配置说明:
1. 8'h00 Left Line In寄存器配置成0x97,即为默认值
2. 8'h02 Right Line In寄存器配置成0x97,即为默认值
3. 8'h04 Left Headphone out寄存器配置成0x7f,即左耳机输出为+6dB分贝
4. 8'h06 Right Headphone out寄存器配置成0x7f,即右耳机输出为+6dB分贝
5. 8'h08 Analogue audio path control寄存器配置成0x15,即把MIC选择为DAC输出
6. 8'h0a Digital Audio path control寄存器配置成0x06,即选择了48Khz的采样频率
7. 8'h0c Power down control寄存器配置成0x00,即为默认值
8. 8'h0e Digital Audio interface format寄存器配置成0x40,即选择了Right justified模式,主时钟模式,默认的32bit模式
9. 8'h10 Sampling control寄存器配置成0x00,即为默认值
10. 8'h12 Active control寄存器配置成0x01,即为默认值
介绍完WM8731上关键的设计要点后,回归主题在这个例程,我们去实现SD卡存放音频WAV播放的目的,如图7-12所示是整个例程的设计示意图,和上一个例程可能有些类似的地方也有可以借鉴的地方,在程序设计方面,笔者个人认为有几点需要注意:
1. 通过对WM8731芯片手册的阅读,明白了需要先初始化WM8731芯片,再配置好相关寄存器后才能正常工作,所以需要i2c_wm8731和config_wm8731两个模块进行IIC初始化配置;
2. 对于FPGA端来说需要去按照顺序读出每个扇区512字节的数据,再检查每个扇区是否存在WAV格式的文件头,所以需要wav_query模块去查找并判断当前扇区是不是文件头,如果不是就跳转到下一个扇区继续查找,如果是则提取出WAV格式文件大小用来判断整首音乐是否播放完全,有关WAV文件头会在下面详细展开介绍;
3. 在整个设计中数据的产生是由SD卡而来,数据的消耗则是WM8731数字转模拟后通过耳机音频座子外放出去的,显然这里两个模块存在大量跨时钟域的数据,所以需要两个模块即wav_query查找并用FIFO缓存SD卡中的WAV音频数据,wav_play模块用来按照WM8731的音频格式播放WAV音频数据。
4. 通过对WM8731芯片手册的阅读,了解到了WM8731有不同的工作模式,而在这里使用了Right justified模式,主时钟模式和32bit模式,对应的时序逻辑后续会展开说明。
图2 SD卡存放音频WAV播放整体设计示意图
如图3所示是SD卡存放音频WAV播放例程的功能框图,朋友们可以对照图7-12和图7-13再去构想下整个程序的设计思路,这里不妨去想想看数据来源、数据流向、数据缓存以及各个模块之间应该怎么去关联起来。
图3 SD卡存放音频WAV播放功能框图
在阅读了WM8731芯片数据手册和梳理了例程的整体设计框图后,在模块设计之前,简单穿插介绍下WAV的文件头格式,结合WM8731配置,这里有些需要注意的地方,实话实说,笔者在最初设计这个例程时候也走错路,踩过坑,一点点摸索边调边试,最终才把一些细节琢磨清楚,在这里分享给大家。
如图4所示是WAV文件头格式,起初在程序设计当中笔者只关心到了两点即如何判断文件报头和如何提取文件长度,仔细观察不难发现文件头前12个字节包含了核心信息,因为WAV文件是基于RIFF格式标准的,所以文件报头的第一个4字节是“RIFF”,第二个4字节是整个文件大小这里以字节为单位,第三个4字节则是“WAVE”。
图4 WAV文件头格式
实际上对于wav_query模块的程序设计了解到这里就够了,在这个模块里我们只需要实现核心的4个功能:
1. 遍历地查找并判断当前扇区是不是WAV文件头(WAV文件头的第一个4字节是“RIFF”和第三个4字节是“WAVE”),如果不是就跳转到下一个扇区继续查找,如果是则提取出WAV格式文件大小(WAV文件头的第二个4字节)用来判断整首音乐是否播放完全;
2. 缓存从SD卡扇区中读到的WAV格式数据,合理地把上游SD卡中存储的音频数据送到下游WM8731的DACDAT引脚,下游的wav_play模块再通过芯片内部的数模转换把音乐外放到音频耳机座子上。这也是整个模块设计中最为关键的部分,因为读SD扇区音频数据的速度要快于WM8731数模转换播放的速度,简而言之数据的产生速度要大于数据的处理速度,所以我们用FIFO中的写入数据量作为标志,使用一个读写端口均是1024的FIFO作为缓存,当写入端口侧的数据量少于512个就继续读取下个扇区512字节数据,这样就非常合理地解决了数据产生和数据处理速度上不一致的问题;
3. 计数每首WAV歌曲的音频数据量,这里注意到WAV文件头大小固定是88字节,所以在播放时需要把前88个字节的文件头去掉;
4. 把从上游模块中SD卡扇区中读到的1字节8比特数据拼接成4字节32比特数据写入FIFO中,下游模块再从FIFO中读取数据,当然具体的原因会在wav_play模块中再进行详细讲解。
如下表1所示是wav_query模块的信号列表,如图7-15所示是WAV音频查找模块的代码设计,和上个例程BMP图片查找模块的设计思想大同小异,但是在这个模块中需要注意下状态机的跳转,根据实际需求把模块划分为5个状态即WAV_IDLE、WAV_QUERY、WAV_CHECK_FIFO、WAV_READ_SEC、WAV_END,这里单独分开了一个WAV_CHECK_FIFO状态用来判断FIFO中写端口的写入数据量是否小于512,如果小于512跳到WAV_READ_SEC状态继续读取SD卡下个扇区的512字节数据,如果大于512则等待下游wav_play模块把FIFO中缓存的音频数据消耗了再去读取下个扇区。
表1 wav_query模块信号列表
图5 WAV音频查找模块的代码设计
对于wav_play即WAV音频播放模块的设计,实际上笔者在这里也花费了很多精力和时间,不断调试磨合,最终才发现自己对WM8731手册或者说对WAV文件头的研究还不够细致。
刚开始因为笔者对左声道和右声道这个概念没有太多理解,所以对照WM8731手册看得也是似懂非懂,更不知道怎么把SD卡中读到的WAV格式音频数据映射到左右声道,后来上板调试均失败后,静下心来把WM8731手册和WAV编码格式又反反复复地研究了几遍,最后才慢慢想明白了整个原理,下面就为大家逐一道来。
为了更加直观地理解,如下图6所示是WinHex下查看Counting Stars的WAV格式二级制文件,这里方便大家查看,笔者也用红色方框把重要的内容都框了出来。
图6 WinHex下查看Counting Stars的WAV格式二级制文件
“52 49 46 46”即ASCII字符“RIFF”,固定格式表明这是一个WAV的文件头。
“58 7B B4 02”即WAV文件的所有数据大小,16进制的“58 7B B4 02”对应是十进制的45382488,这里注意到读取的方式应是低位在前高位在后,也就是“02 B4 7B 58”所对应的十进制数值。
“57 41 56 45”即ASCII字符“WAVE”,也是固定格式表明这是一个WAV的文件头。
“66 6D 74 20”即表示标识符“fmt”。
“10 00 00 00”即对应数字“16”,表示后面有一段的数据长度是16个字节,即偏移量从14H~23H。
“01 00” 即对应数字“1”,表示该数据以PCM方式进行编码。
“02 00” 即对应数字“2”,表示该文件是双声道文件。
“44 AC 00 00” 即对应数字“44100”,表示采样频率为44100Hz,表示每个通道的播放速度。
“10 B1 02 00” 即对应数字“176400”,表示波形音频数据传输速率为176400(2*44100*16/8)。
“04 00” 即对应数字“4”,表示块对其单位,表明WAV文件一次需要处理多个4字节大小的数据(2*16/8)。
“10 00” 即对应数字“16”,表示采样大小为16Bits,是每样本的数据位数。高八位表示左声道,低八位表示右声道。
通过上面对WAV文件头进一步的深入分析,大家可以清楚地看到这首WAV格式的Counting Stars歌曲实际上是16 Bit双声道的,即对应表2的编码格式。
表2 WAV下16 Bit双声道的编码格式