我们在前文中讲过一个关于audio的例程,它主要完成了录制播放功能(直接从SDRAM中写/读数据),而本文相较之前的例程有两处不同,第一处:本例程是从SD卡中读取数据;第二处:本例程添加了文件检索功能(但是并没有添加文件系统)。关于第一处我们没有太多问题,因为在之前关于SD卡的读写已经详细讲解过了,那么第二处的文件检索就成为了重中之重,在没有文件系统的情况下如何完成文件检索?在已知文件格式为WAV的条件下(大部分的文件都有文件头),我们需要做的是分析SD卡一个扇区的前几个字节,判断这个文件是否为WAV文件。WAV文件作为多媒体中使用的声波文件格式之一,它是以RIFF格式为标准的。RIFF是英文Resource Interchange File Format的缩写,每个WAV文件的头四个字节便是”RIFF”,所以本实验就简单的以每个扇区的前4个字节是否为”RIFF”判断文件是否为WAV文件,紧接着4个字节表示文件的大小,这样我们就可以确定要读取的数据量。WAV文件头是88字节,在播放时要把前88个字节的文件头去掉。
sd_card_audio.v RTL视图
从上面的RTL视图中我们可以发现,有五个模块构成,但是在功能上有三部分组成
①:sd_card_audio,完成SD卡与audio之间的数据交互,并将检索出来的wave文件通过发送给WM8731音频芯片完成DA转换。
②:i2c_config/lut_wm8731,完成WM8731的配置。
③:seg_decoder/seg_scan,完成数码管的显示。
由上图可以看出sd_card_audio_m0由哪几个模块组成,模块之间是怎样连接的,一目了然。
由于其他子模块我们之前有讲过,我们下面详细讲述wav_read这个子模块,该模块主要完成了对WAVE文件的检索,以及将SD卡中读到的数据传给FIFO。
wav_read.v
可以看出该模块总共由五个状态,我之前一再强调,分析任何逻辑代码,不要一上来就从头看到尾,去分析这个信号干什么的,那个信号又是干什么的。我们要先去分析状态机。
通过观察这段状态机,发现了与以往我们设计状态机不一样的地方,之前在进行状态机设计的时候我们会默认state==IDLE,而在这里我们通过判断sd_init_done的值来决定state是否进入IDLE状态,那么有人会问sd_init_done是个什么信号,怎么既熟悉又陌生?我只能说你是具备了一个渣男的属性(见一个爱一个),在SD卡的设计文档中我们有讲过sd_init_done是SD卡是否完成初始化的标记信号。如果完成初始化,进入S_IDLE,映入眼帘的是find信号,通过RTL视图,我们有了解,find信号与negedge信号相连,也就是说当有检测到键按下,系统进入S_FIND状态。sd_sec_read_addr <= {sd_sec_read_addr[31:3],3'd0};在IDLE状态里的这条逻辑语句我一直没有看懂,我不清楚为什么要在这里对地址减8?
我们先抛开这个问题,往下看,当系统进入S_FIND状态后,我们会看到几个信号,如:sd_sec_read_end;sd_sec_read;渣男本性的你估计又该问这几个是什么信号呢?这两个信号在RTL视图我们可以看出它与sdcard_top相连,关于这部分我们之前有讲过,sd_sec_read_end是读完一个扇区(即512bytes)的标志信号;而sd_sec_read是一个控制信号,它决定了sd_card_sec_read_write这个模块在S_WAIT_READ_WRITE这个状态里是进入S_CMD24还是S_CMD17,也就是在SD卡初始化后,是进行读操作还是写操作。接下来我们继续看这段逻辑,进入到S_FIND中后,sd_sec_read就被置1,开始读对应地址(sd_sec_read_addr <= {sd_sec_read_addr[31:3],3'd0})中的数据,以达到搜索的目的。如果读完该扇区所有的数据,sd_sec_read_end置1,这里开始判断found信号,我们开始搜索前面的程序,来找寻found信号是个什么东东?我们就要看以下两端逻辑来进行分析。
rd_cnt是用来对该扇区读到的数记录顺序,rd_cnt==0;即读到的第一个数。rd_cnt==1;即读到的第二个数。。。在前面我们有讲过wav的文件头,匹配的过程,就是看你对应位置读到的数据与文件头是否一致。匹配一致,found置1。file_len指的是在头文件中表示的文件长度。
回到上面那段状态机,如果匹配一致,系统进入S_PLAY_WAIT,并将sd_sec_read置0,否则sd_sec_read_addr<=sd_sec_read_addr+32’d8;最开始我也好奇为什么每次+8,也就是每隔8*512bytes=4K的空间进行匹配。这里我给大家分享一个有意思的事情:
我们发现分配单元也是4K,我估计应该是按照分配单元进行检索。
当系统检测到WAV的文件头,即匹配成功,进入S_PLAY_WAIT状态,这又是一个什么状态呢?通过判断语句我们可以看出这个与我们之前check fifo功能上是大体一致的啊,他们都是检查fifo里的空间够不够缓存一个扇区的数据。fifo_wr_cnt连接的正是fifo的wrusedw。
如果fifo中的空间足够缓存扇区的数据,系统进入S_PLAY状态,也就是播放该WAV格式的文件,在这个状态中,我们看到了一个play_cnt,翻找前文,我们找到了它的轨迹:
每读完一个数据,注意这里不是读完一个扇区,而是读完一个字节,play_cnt+1。也就是说play_cnt起到了一个记录数据长度的作用。
回到S_PLAY状态,当play_cnt>=file_len也就是读完该文件的所有数据后,进入S_END状态,否则回到S_PLAY_WAIT,继续检测fifo的空间够不够缓存一个扇区的数据(因为sd_sec_read_addr+1,开始读下一个扇区的数据)。
接下来肯定会有同学产生疑问,我们播放的应该是音频的数据,那么整个文件既包括音频又包括头文件,我们如何剔除这些无效信息呢?下面出现的这段代码就帮我们做了这样的事情
既然要控制音频芯片播放有效的数据,那么我们缓存在fifo中的数据应该是剔除头文件的有效音频数据。在这里我们通过控制wav_data_wr_en(连接在fifo的wr_req)来完成这一功能。
已知头文件的长度为88bytes,所以要在S_PLAY状态选择性的读取SD卡中的数据,并将数据送至FIFO(if(sd_sec_read_data_valid==1’b1&&play_cnt>32’d87&&play_cnt wav_data_wr_en<=1’b1; wav_data<=sd_sec_read_data;)。