FPGA-音频模块开发(二)

audio_record_play_ctrl模块

该模块主要完成通过I2S完成与WM8731的通信(发送和接收数据),信号包括bclk、adclrc、daclrc、adcdat、dacdat。该系统还完成了播放和录制的功能,为此在该模块中添加了audio_key子模块,主要通过key的按下和释放发出record和play的命令,将WM8731输出的数字信号写到SDRAM中(达到录制的功能),而播放是读出相应地址的SDRAM中的数据。并且同frame_read_write模块进行信息交互(其中包括write_data、read_data、write_en、read_en、read_req_ack、write_req_ack、write_req、read_req)。

audio_record_play_ctrl.v(模块顶层文件)

 

该段代码是audio_record_play_ctrl模块的顶层文件,它主要描述该模块是由三个子模块

audio_key、audio_tx、_audio_rx逻辑组合的。

audio_tx.v

 

首先,对外部的输入信号sck_bclk、ws_lrc进行锁存,达到与系统时钟同步的目的,避免紊乱。 

 

 

与WM8731通信,我们需要遵从约定好的通信协议,即I2S。根据时序图可以知道WM8731发送一次数据或者接收一次数据(左右声道),即一个周期(1/fs),开始于lrc信号的上升沿。在lrc的上升沿,我们将frame_read_write模块中fifo里读出的64位数据赋给两个移位寄存器,left_data_shift以及right_data_shift(分别用来储存左声道以及右声道的数字信 号)。同样根据时序图,在bclk的下降沿,WM8731发送一位数据,那么FPGA也应该在下降沿发送数据,lrc==1时,发送的是左声道的数据,所以在每个bclk的下降沿,left_data_shift左移一位,lrc==0时,发送的是右声道的数据,所以在每个bclk的下降沿,right_data_shift左移一位。我们会在下面的always块里看到sdata<=left_data_shift[31],或者sdata<=right_data_shift[31]最初我比较困惑按照通信协议里规定的,要在BCLK的下降沿发送数据,这里同步的时钟是系统时钟,这样会不会破坏协议,oh,my god!bclk_d0/bclk_d1

每次发生改变都是在clk的上升沿!!!豁然开朗!!!

在这里我们仔细分析这个子模块的逻辑,当采集到lrc的跳变(上升沿)的下一个CLK的上升沿,left_data/right_data赋给left_data_shift/right_data_shift,而此时left_data/right_data的数据是上一个(1/fs)周期从fifo中读到的。为什么是这样呢?原因是在lrc跳变沿触发后(也就是lrc上升沿的下一个CLK时钟),read_data_en置1,才会给frame_read_write这个模块中的fifo提供读使能,q[63...0]({left_data,right,data})的输出滞后读使能一个CLK时钟。所以此时left_data/right_data为上一个(1/fs)周期从fifo中读到的数据。

在audio_record_play_ctrl这个模块中我们可以看到assign read_en = read_data_en & play;

read_en这个信号是连接到fifo中的读使能的,达到连续发送数据的目的。

这里延伸一下关于fifo的知识:

关于读使能与data的输出由两种模式可以选择:

第一种是Normal synchronous FIFO mode

该模式,数据输出滞后读使能一个时钟周期(The data becomes available after rdreq is asserted)。

第二种是Show-ahead synchronous FIFO mode(The data becomes available before rdreq is asserted)。

笔者在网络这个浩瀚的宇宙苦苦寻觅,终于找到了这组时序图,帮我解决了许久的困惑,在学习音频模块的时候,发现了一个问题,CHECK FIFO这个状态中,设计中通过判断rdusedw与BURST_SIZE之间的大小,即(可读数据长度是否大于一次突发写的数据长度)来决定是否进入下个状态。当时笔者在想,rdusedw和BURST_SIZE的单位是否一致?BURST_SIZE是指一次突发读写多少组数据,它的单位是16位的数据。最初我在想rdusedw和wrusedw的计数方式是一致的,会以同一位宽为基准,就是说如果fifo里输入端口的位宽是64,输出端口的位宽是16,在写使能有效的时候,写进一个64位的数据,wrusedw+1,那么在读使能有效的时候,读出4个16位的数据,rdusedw+1,那么黑金写的音频模块就是错误的。但是看了这个时序笔者恍然大悟,rdusedw/wrusedw的计数方式是根据自己输入输出数据端口进出数据计数的,如上图在wrreq有效时,在第一个时钟写入0xFF01,在第二个时钟的上升沿后wrusedw==01,在第二个时钟写入0x0002,在第三个时钟的上升沿后wrusedw==02,当rdreq为1,在下一个rdclk的上升沿有效,并输出第一个数据0x01,在之后的下一个时钟的上升沿输出第二个数据0xFF,同时rdusedw-1,从上面的逻辑我们可以知道wrusedw和rdusedw的计数原则。

 

audio_rx.v

 

接收模块和发送模块逻辑设计上大体一致,区别在于,接收模块中BCLK上升沿采集数据,发送模块中BCLK下降沿发送数据。还有需要注意的是,每次LRC的上升沿(完成一个周期的数据接收,左右声道),right_data_shift/left_data_shift赋给right_data,left_data,然后复位清0,并将data_valid置1,标志着完成了一次数据接收。在这里大家可能会有疑问,为什么会先赋值,再清0,FPGA不是并行的吗?怎么会有先后顺序呢?逻辑中不都是在判断语句中判断了LRC的上升沿吗?有这样的疑问说明了大家对时序逻辑没有深刻的认识,首先大家要根据逻辑关系在脑海里有一个大体的输入输出逻辑门关系,在该判断逻辑中left_data/right_data这两个寄存器的输入是left_data_shift/right_data_shift,而left_data_shift/right_data_shift的输入是0,而这四个寄存器的时钟输入都是CLK,那么在CLK的上升沿,left_data_shift/right_data_shift的值并没有改变,而是在CLK上升沿之后,left_data_shift/right_data_shift才被清0。在这里再延伸一下,关于数据采样的问题,我们发现在FPGA逻辑中,时钟的上升沿伴随着数据的更新,数据采样如果采用系统的时钟,那么会出现一个问题,没有办法在数据的中端位置采样,这样就无法保证数据的有效性。所以我们会常常用PLL分频出一个相位差180°的时钟,这样数据的中端位置恰巧在时钟的上升沿,方便数据的采样。言归正传,现在我们看data_valid这个信号,它和read_data_en一样都是与record/play组合输出并连接在fifo上的,data_valid标志着接收了一次数据(由WM8731发出的左右声道数据)。 

 audio_key.v

 

 

首先在程序底部,我们看到了一段例化描述,它主要是添加了按键去抖,因为我们需要捕捉按键的下降沿/上升沿用来完成record/play的功能。接下来我们来看这个子模块的状态机,S_IDLE、S_RECORD、S_PLAY,初始化/录制/播放。其他没什么特别的难点,唯一我们需要注意的是record_cnt和play_cnt,这两个寄存器的目的,是保证录制时间和播放时间的一致。 

 

 

 

  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值