开篇第一步
在上一篇教程中,创建了一个 I2S 发送器用来发送来从FPGA内部 ROM 的音频数据。下一步,我们向该 I2S 发送器添加 AXI-Stream 接口,这样我们就可以将发送器与 ZYNQ 的处理系统连接,还可以从 SD 卡读取音频数据。
为此,创建一个新的top设计。本设计应具有以下接口:

该块设计产生以下代码:
entityAXIS_I2Sis Generic(RATIO:INTEGER:=8; WIDTH:INTEGER:=16 ); Port(MCLK:inSTD_LOGIC; nReset:inSTD_LOGIC; LRCLK:outSTD_LOGIC; SCLK:outSTD_LOGIC; SD:outSTD_LOGIC; ACLK:inSTD_LOGIC; ARESETn:inSTD_LOGIC; TDATA_RXD:inSTD_LOGIC_VECTOR(31downto0); TREADY_RXD:outSTD_LOGIC; TVALID_RXD:inSTD_LOGIC ); endAXIS_I2S;
SCLK与MCKL的比率通过RATIO参数定义,每个通道的数据字宽度通过WIDTH参数定义。
PS:此实现仅支持每个通道 16 位数据字(即立体声 32 位)。
设计中必须实现以下组件:
用于为 I2S 发送器创建输入时钟的时钟预分频器
AXI-Stream 从接口
I2S发送器的控制逻辑
为分频器创建了一个过程,该过程在MCLK时钟上升沿对计数器进行计数,并在半个周期后切换信号SCLK_Int。
process variableCounter:INTEGER:=0; begin waituntilrising_edge(MCLK); if(Counter< ((RATIO / 2) - 1)) then Counter := Counter + 1; else Counter := 0; SCLK_Int <= not SCLK_Int; end if; if(nReset = '0') then Counter := 0; SCLK_Int <= '0'; end if; end process;
下一步是实现 AXI-Stream 接口。为此使用状态机:
process begin waituntilrising_edge(ACLK); caseCurrentStateis whenState_Reset=> Tx_AXI<= (others =>'0'); CurrentState<= State_WaitForTransmitterReady; when State_WaitForTransmitterReady => if(Ready_AXI='1')then TREADY_RXD<= '1'; CurrentState <= State_WaitForValid; else TREADY_RXD <= '0'; CurrentState <= State_WaitForTransmitterReady; end if; when State_WaitForValid => if(TVALID_RXD='1')then TREADY_RXD<= '0'; Tx_AXI <= TDATA_RXD; CurrentState <= State_WaitForTransmitterBusy; else TREADY_RXD <= '1'; CurrentState <= State_WaitForValid; end if; when State_WaitForTransmitterBusy => if(Ready_AXI='0')then CurrentState<= State_WaitForTransmitterReady; else CurrentState <= State_WaitForTransmitterBusy; end if; end case; if(ARESETn = '0') then CurrentState <= State_Reset; end if; end process;
复位后,机器从State_Reset状态变为State_WaitForTransmitter等待I2S 发送器发出就绪Ready信号的状态。一旦发送器准备好,TREADY_RXD就会设置 AXI-Stream 接口的信号,通知主机从机已准备好接收数据。然后从机改变为State_WaitForValid状态。
在此状态下,从机等待主机置位信号TVALID_RXD标记有效数据。一旦置位了信号,数据就会写入内部 FIFO。然后机器改变到State_WaitForTransmitterBusy状态。
现在状态机等待I2S发送器开始发送数据并“删除”就绪信号。一旦完成,状态机就会切换回State_WaitForTransmitterReady状态并再次等待,直到 I2S 发送器准备就绪。
这样,理论上 AXI-Stream 接口就完成了。不幸的是,最后变得有点棘手,因为当前的电路设计使用两个不同的时钟域:
ACLK的时钟域
MCLK的时钟域
一般来说,这两个时钟信号不能从时钟源生成(例如通过时钟分频器),因为 AXI 接口通常以 100 MHz 运行,而音频接口需要可以整齐地分频至采样频率的时钟速率,例如 12.288 MHz。因此,由于最差负裕量 (WNS) 和总负裕量 (TNS) 过多,在实现过程中会出现时序错误:

此外,由于触发器在不同时钟域中发生亚稳态而导致数据不正确的风险非常高。
因此,各个时钟域所使用的信号必须在每种情况下经由相应的电路传送到另一时钟域。Xilinx 在文档UG953(https://www.xilinx.com/support/documentation/sw_manuals/xilinx2018_3/ug953-vivado-7series-libraries.pdf)中描述了可用于此目的的相应宏。
xpm_cdc_gray - 该功能块使用格雷码将数据总线从一个时钟域 (src) 传输到另一个时钟域 (dest)。
xpm_cdc_single - 将单个信号从一个时钟域 (src) 转换到另一个时钟域 (dest)。
宏的示例可以直接用于 VHDL 代码:
xpm_cdc_Data:xpm_cdc_handshakegenericmap(DEST_EXT_HSK=>0, DEST_SYNC_FF=>4, INIT_SYNC_FF=>0, SIM_ASSERT_CHK=>0, SRC_SYNC_FF=>4, WIDTH=>(2*WIDTH) ) portmap(src_clk=>ACLK, src_in=>Data_Fast, dest_clk=>MCLK, dest_out=>Data_Slow, dest_ack=>'0', src_send=>src_send, src_rcv=>src_rcv, dest_req=>dest_req ); xpm_cdc_Ready:xpm_cdc_singlegenericmap(DEST_SYNC_FF=>4, SRC_INPUT_REG=>1 ) portmap(src_clk=>MCLK, src_in=>Ready_Transmitter, dest_clk=>ACLK, dest_out=>Ready_AXI );
最后,必须插入 I2S 发送器并传递生成的信号。
Transmitter:I2S_Transmittergenericmap(WIDTH=>WIDTH ) portmap(Clock=>SCLK_Int, nReset=>nReset, Ready=>Ready_Transmitter, Tx=>Tx_Transmitter, LRCLK=>LRCLK, SCLK=>SCLK, SD=>SD );
I2S 发送器的 AXI-Stream 接口现已准备就绪并可供使用。完整的代码如下所示:
libraryIEEE; useIEEE.STD_LOGIC_1164.ALL; libraryxpm; usexpm.vcomponents.all; entityAXIS_I2Sis Generic(RATIO:INTEGER:=8; WIDTH:INTEGER:=16 ); Port(MCLK:inSTD_LOGIC; nReset:inSTD_LOGIC; LRCLK:outSTD_LOGIC; SCLK:outSTD_LOGIC; SD:outSTD_LOGIC; ACLK:inSTD_LOGIC; ARESETn:inSTD_LOGIC; TDATA_RXD:inSTD_LOGIC_VECTOR(31downto0); TREADY_RXD:outSTD_LOGIC; TVALID_RXD:inSTD_LOGIC ); endAXIS_I2S; architectureAXIS_I2S_ArchofAXIS_I2Sis typeAXIS_State_tis(State_Reset,State_WaitForTransmitterReady,State_WaitForValid,State_WaitForTransmitterBusy); signalCurrentState:AXIS_State_t:=State_Reset; signalTx_AXI:STD_LOGIC_VECTOR(((2*WIDTH)-1)downto0):=(others=>'0'); signalReady_AXI:STD_LOGIC; signalTx_Transmitter:STD_LOGIC_VECTOR(((2*WIDTH)-1)downto0):=(others=>'0'); signalReady_Transmitter:STD_LOGIC; signalSCLK_Int:STD_LOGIC:='0'; componentI2S_Transmitteris Generic(WIDTH:INTEGER:=16 ); Port(Clock:inSTD_LOGIC; nReset:inSTD_LOGIC; Ready:outSTD_LOGIC; Tx:inSTD_LOGIC_VECTOR(((2*WIDTH)-1)downto0); LRCLK:outSTD_LOGIC; SCLK:outSTD_LOGIC; SD:outSTD_LOGIC ); endcomponent; begin Transmitter:I2S_Transmittergenericmap(WIDTH=>WIDTH ) portmap(Clock=>SCLK_Int, nReset=>nReset, Ready=>Ready_Transmitter, Tx=>Tx_Transmitter, LRCLK=>LRCLK, SCLK=>SCLK, SD=>SD ); xpm_cdc_Data:xpm_cdc_graygenericmap(DEST_SYNC_FF=>4, SIM_ASSERT_CHK=>0, SIM_LOSSLESS_GRAY_CHK=>0, WIDTH=>(2*WIDTH) ) portmap(src_clk=>ACLK, src_in_bin=>Tx_AXI, dest_clk=>MCLK, dest_out_bin=>Tx_Transmitter ); xpm_cdc_Ready:xpm_cdc_singlegenericmap(DEST_SYNC_FF=>4, SRC_INPUT_REG=>1 ) portmap(src_clk=>MCLK, src_in=>Ready_Transmitter, dest_clk=>ACLK, dest_out=>Ready_AXI ); process variableCounter:INTEGER:=0; begin waituntilrising_edge(MCLK); if(Counter< ((RATIO / 2) - 1)) then Counter := Counter + 1; else Counter := 0; SCLK_Int <= not SCLK_Int; end if; if(nReset = '0') then Counter := 0; SCLK_Int <= '0'; end if; end process; process begin wait until rising_edge(ACLK); case CurrentState is when State_Reset => Tx_AXI<= (others =>'0'); CurrentState<= State_WaitForTransmitterReady; when State_WaitForTransmitterReady => if(Ready_AXI='1')then TREADY_RXD<= '1'; CurrentState <= State_WaitForValid; else TREADY_RXD <= '0'; CurrentState <= State_WaitForTransmitterReady; end if; when State_WaitForValid => if(TVALID_RXD='1')then TREADY_RXD<= '0'; Tx_AXI <= TDATA_RXD; CurrentState <= State_WaitForTransmitterBusy; else TREADY_RXD <= '1'; CurrentState <= State_WaitForValid; end if; when State_WaitForTransmitterBusy => if(Ready_AXI='0')then CurrentState<= State_WaitForTransmitterReady; else CurrentState <= State_WaitForTransmitterBusy; end if; end case; if(ARESETn = '0') then CurrentState <= State_Reset; end if; end process; end AXIS_I2S_Arch;
接下来,我们希望使用该接口从 SD 卡读取波形文件,并使用 CS4344 D/A 转换器通过连接的扬声器输出音乐。
该项目需要以下IP核:
具有 AXI-Stream 接口的 I2S 发送器
处理系统从 SD 卡读取数据并将其写入 FIFO
AXI-Stream FIFO
用于生成音频时钟的PLL

时钟向导生成时钟,然后将其用作 CS4344 的主时钟。输出时钟可以通过 AXI-Lite 接口适应音频文件的采样率。

AXI-Stream FIFO 充当处理系统和 I2S 发送器之间的链接。处理系统通过 AXI-Lite(或 AXI)接口将数据写入 FIFO,然后将数据传输至 I2S 发送器。

根据设计创建比特流,然后可以开发软件。
读取 SD 卡需要 Xilinx 的 xilffs FAT 库,该库必须集成到 Vitis 项目的板级支持包中(不要忘记启用LFN支持大文件名的选项):

第一步,软件使用该AudioPlayer_Init函数初始化音频播放器,从而初始化 FIFO、GIC 和中断处理程序,以及时钟向导和 SD 卡。
u32AudioPlayer_Init(void)
{
xil_printf("[INFO]LookingforFIFOconfiguration...
");
_Fifo_ConfigPtr=XLlFfio_LookupConfig(XPAR_FIFO_DEVICE_ID);
if(_Fifo_ConfigPtr==NULL)
{
xil_printf("[ERROR]InvalidFIFOconfiguration!
");
returnXST_FAILURE;
}
xil_printf("[INFO]InitializeFIFO...
");
if(XLlFifo_CfgInitialize(&_Fifo,_Fifo_ConfigPtr,_Fifo_ConfigPtr->BaseAddress)!=XST_SUCCESS)
{
xil_printf("[ERROR]FIFOinitializationfailed!
");
returnXST_FAILURE;
}
xil_printf("[INFO]LookingforGICconfiguration...
");
_GIC_ConfigPtr=XScuGic_LookupConfig(XPAR_PS7_SCUGIC_0_DEVICE_ID);
if(_GIC_ConfigPtr==NULL)
{
xil_printf("[ERROR]InvalidGICconfiguration!
");
returnXST_FAILURE;
}
xil_printf("[INFO]InitializeGIC...
");
if(XScuGic_CfgInitialize(&_GIC,_GIC_ConfigPtr,_GIC_ConfigPtr->CpuBaseAddress)!=XST_SUCCESS)
{
xil_printf("[ERROR]GICinitializationfailed!
");
returnXST_FAILURE;
}
xil_printf("[INFO]Setupinterrupthandler...
");
XScuGic_SetPriorityTriggerType(&_GIC,XPAR_FABRIC_FIFO_INTERRUPT_INTR,0xA0,0x03);
if(XScuGic_Connect(&_GIC,XPAR_FABRIC_FIFO_INTERRUPT_INTR,(Xil_ExceptionHandler)AudioPlayer_FifoHandler,&_Fifo)!=XST_SUCCESS)
{
xil_printf("[ERROR]Cannotconnectinterrupthandler!
");
returnXST_FAILURE;
}
XScuGic_Enable(&_GIC,XPAR_FABRIC_FIFO_INTERRUPT_INTR);
xil_printf("[INFO]Enableexceptions...
");
Xil_ExceptionInit();
Xil_ExceptionRegisterHandler(XIL_EXCEPTION_ID_INT,(Xil_ExceptionHandler)XScuGic_InterruptHandler,&_GIC);
Xil_ExceptionEnable();
xil_printf("[INFO]EnableFIFOinterrupts...
");
XLlFifo_IntClear(&_Fifo,XLLF_INT_ALL_MASK);
xil_printf("[INFO]InitializeClockingWizard...
");
if((ClockingWizard_Init(&_ClkWiz,XPAR_CLOCKINGWIZARD_BASEADDR)||ClockingWizard_GetOutput(&_ClkWiz,&_AudioClock))!=XST_SUCCESS)
{
xil_printf("[ERROR]ClockingWizardinitializationfailed!
");
returnXST_FAILURE;
}
xil_printf("[INFO]MountSDcard...
");
if(SD_Init())
{
xil_printf("[ERROR]CannotinitializeSDcard!
");
returnXST_FAILURE;
}
returnXST_SUCCESS;
}
一旦初始化完成,就会调用AudioPlayer_LoadFile函数从 SD 卡加载Audio.wav文件 。
if(AudioPlayer_LoadFile("Audio.wav"))
{
xil_printf("[ERROR]CannotopenAudiofile!
");
returnXST_FAILURE;
}
u32AudioPlayer_LoadFile(char*File)
{
if(SD_LoadFileFromCard(File,&_File))
{
xil_printf("[ERROR]CannotopenAudiofile!
");
returnXST_FAILURE;
}
xil_printf("Filesize:%lubytes
",_File.Header.ChunkSize+8);
xil_printf("Fileformat:%lu
",_File.Format.AudioFormat);
xil_printf("Channels:%lu
",_File.Format.NumChannels);
xil_printf("Samplerate:%luHz
",_File.Format.SampleRate);
xil_printf("Bitspersample:%lubits
",_File.Format.BitsPerSample);
xil_printf("Blockalign:%lubytes
",_File.Format.BlockAlign);
xil_printf("Databytes:%lubytes
",_File.Header.ChunkSize/_File.Format.NumChannels);
xil_printf("Samples:%lu
",8*_File.Header.ChunkSize/_File.Format.NumChannels/_File.Format.BitsPerSample);
if((_File.Format.BitsPerSample!=16)||(_File.Format.NumChannels>2))
{
xil_printf("[ERROR]Invalidfileformat!
");
returnXST_FAILURE;
}
AudioPlayer_ChangeFreq(_File.Format.SampleRate);
XLlFifo_TxReset(&_Fifo);
XLlFifo_IntEnable(&_Fifo,XLLF_INT_ALL_MASK);
SD_CopyDataIntoBuffer(_FifoBuffer,256);
AudioPlayer_CopyBuffer();
returnXST_SUCCESS;
}
该函数AudioPlayer_LoadFile调用函数SD_LoadFileFromCard从SD卡加载波形文件。
u32SD_LoadFileFromCard(constchar*FileName,Wave_t*File)
{
xil_printf("[INFO]Openingfile:%s...
",FileName);
if(f_open(&_FileHandle,FileName,FA_READ))
{
xil_printf("[ERROR]Cannotopenaudiofile!
");
returnXST_FAILURE;
}
if(f_read(&_FileHandle,&File->RIFF,sizeof(Wave_RIFF_t),&_BytesRead)||f_read(&_FileHandle,&File->Format,sizeof(Wave_Format_t),&_BytesRead))
{
xil_printf("[ERROR]CannotreadSDcard!
");
returnXST_FAILURE;
}
Wave_Header_tHeader;
uint32_tOffset=sizeof(Wave_RIFF_t)+sizeof(Wave_Format_t);
if(f_read(&_FileHandle,Header.ChunkID,sizeof(Wave_Header_t),&_BytesRead)||f_lseek(&_FileHandle,Offset))
{
xil_printf("[ERROR]CannotreadSDcard!
");
returnXST_FAILURE;
}
if(strncmp("LIST",Header.ChunkID,4)==0)
{
Offset+=Header.ChunkSize+sizeof(Wave_Header_t);
if(f_read(&_FileHandle,&File->ListHeader,sizeof(Wave_Header_t),&_BytesRead)||f_lseek(&_FileHandle,Offset))
{
xil_printf("[ERROR]CannotplaceSDcardpointer!
");
returnXST_FAILURE;
}
}
if(f_read(&_FileHandle,&File->DataHeader,sizeof(Wave_Header_t),&_BytesRead))
{
xil_printf("[ERROR]CannotreadSDcard!
");
returnXST_FAILURE;
}
if(File->Format.AudioFormat!=WAVE_FORMAT_PCM)
{
xil_printf("[ERROR]Audioformatnotsupported!KeepsurethatthefileusethePCMformat!
");
returnXST_FAILURE;
}
_RemainingBytes=File->DataHeader.ChunkSize;
_IsBusy=true;
returnXST_SUCCESS;
}
在下一步中,根据使用的采样频率从波形文件中设置时钟向导的输出频率:
staticvoidAudioPlayer_ChangeFreq(constu32SampleRate)
{
if(SampleRate==44100)
{
xil_printf("Useclocksetting1...
");
_ClkWiz.DIVCLK_DIVIDE=5;
_ClkWiz.CLKFBOUT_MULT=42;
_ClkWiz.CLKFBOUT_Frac_Multiply=0;
_AudioClock.DIVIDE=93;
_AudioClock.FRAC_Divide=0;
}
elseif(SampleRate==48000)
{
xil_printf("Useclocksetting2...
");
_ClkWiz.DIVCLK_DIVIDE=3;
_ClkWiz.CLKFBOUT_MULT=23;
_ClkWiz.CLKFBOUT_Frac_Multiply=0;
_AudioClock.DIVIDE=78;
_AudioClock.FRAC_Divide=0;
}
elseif(SampleRate==96000)
{
xil_printf("Useclocksetting3...
");
_ClkWiz.DIVCLK_DIVIDE=3;
_ClkWiz.CLKFBOUT_MULT=23;
_ClkWiz.CLKFBOUT_Frac_Multiply=0;
_AudioClock.DIVIDE=39;
_AudioClock.FRAC_Divide=0;
}
ClockingWizard_SetClockBuffer(&_ClkWiz);
ClockingWizard_SetOutput(&_ClkWiz,&_AudioClock);
}
加载音频文件并且调整时钟向导的输出频率后,将从波形文件中读取第一个数据块并将其复制到 FIFO:
u32SD_CopyDataIntoBuffer(u8*Buffer,constu32Length)
{
if(_RemainingBytes>=Length)
{
if(f_read(&_FileHandle,Buffer,Length,&_BytesRead))
{
returnXST_FAILURE;
}
_RemainingBytes-=_BytesRead;
}
else
{
if(f_read(&_FileHandle,Buffer,_RemainingBytes,&_BytesRead))
{
returnXST_FAILURE;
}
if(f_close(&_FileHandle))
{
xil_printf("[ERROR]Cannotcloseaudiofile!
");
returnXST_FAILURE;
}
_IsBusy=false;
}
returnXST_SUCCESS;
}
程序流程的其余部分在 FIFO 的回调中进行:
staticvoidAudioPlayer_FifoHandler(void*CallbackRef)
{
XLlFifo*InstancePtr=(XLlFifo*)CallbackRef;
u32Pending=XLlFifo_IntPending(InstancePtr);
while(Pending)
{
if(Pending&XLLF_INT_TC_MASK)
{
SD_CopyDataIntoBuffer(_FifoBuffer,AUDIOPLAYER_FIFO_BUFFER_SIZE);
XLlFifo_IntClear(InstancePtr,XLLF_INT_TC_MASK);
}
elseif(Pending&XLLF_INT_TFPE_MASK)
{
AudioPlayer_CopyBuffer();
if(!SD_IsBusy())
{
XLlFifo_IntDisable(&_Fifo,XLLF_INT_ALL_MASK);
}
XLlFifo_IntClear(InstancePtr,XLLF_INT_TFPE_MASK);
}
elseif(Pending&XLLF_INT_ERROR_MASK)
{
xil_printf("Error:%lu!
",Pending);
XLlFifo_IntClear(InstancePtr,XLLF_INT_ERROR_MASK);
}
else
{
XLlFifo_IntClear(InstancePtr,Pending);
}
Pending=XLlFifo_IntPending(InstancePtr);
}
}
一旦 FIFO 触发TFPE中断(发送 FIFO 可编程空),FIFO 就会被来自内部缓冲区的新数据填充。当从处理系统到 FIFO 的传输完成时,会触发TC中断(传输完成),并从 SD 卡读取下一个数据块。之后重复进行上面步骤,直到文件完全播放。
staticvoidAudioPlayer_CopyBuffer(void)
{
u32Bytes=0x00;
for(u32i=0x00;i< AUDIOPLAYER_FIFO_BUFFER_SIZE; i += _File.Format.BlockAlign)
{
u32 Word = 0x00;
for(u8 Byte = 0x00; Byte < _File.Format.BlockAlign; Byte++)
{
Word |= _FifoBuffer[i + Byte];
Word <<= 0x08;
}
if(XLlFifo_iTxVacancy(&_Fifo))
{
XLlFifo_TxPutWord(&_Fifo, Word);
Bytes += sizeof(u32);
}
}
XLlFifo_iTxSetLen(&_Fifo, Bytes);
}
现在需要一个波形文件。简单的测试信号可以wavtones.com上生成(https://www.wavtones.com/functiongenerator.php)。
然后,只需将相应的文件以Audio.wav名称复制到 SD 卡上,即可开始使用。
-----------I2SAudioplayer----------- [INFO]LookingforFIFOconfiguration... [INFO]InitializeFIFO... [INFO]LookingforGICconfiguration... [INFO]InitializeGIC... [INFO]Setupinterrupthandler... [INFO]Enableexceptions... [INFO]EnableFIFOinterrupts... [INFO]InitializeClockingWizard... [INFO]MountSDcard... [INFO]Openingfile:Single.wav... Filesize:264610bytes Fileformat:1 Channels:1 Samplerate:48000Hz Bitspersample:16bits Databytes:264602bytes Samples:132301 Useclocksetting2... [INFO]Finished!
或者使用立体声音频:

-----------I2SAudioplayer----------- [INFO]LookingforFIFOconfiguration... [INFO]InitializeFIFO... [INFO]LookingforGICconfiguration... [INFO]InitializeGIC... [INFO]Setupinterrupthandler... [INFO]Enableexceptions... [INFO]EnableFIFOinterrupts... [INFO]InitializeClockingWizard... [INFO]MountSDcard... [INFO]Openingfile:Dual.wav... Filesize:529208bytes Fileformat:1 Channels:2 Samplerate:44100Hz Bitspersample:16bits Blockalign:4bytes Databytes:264600bytes Samples:132300 Useclocksetting1... [INFO]Finished!

审核编辑:刘清
-
音频
+关注
关注
31文章
3134浏览量
84918 -
扬声器
+关注
关注
29文章
1349浏览量
65485 -
SD卡
+关注
关注
2文章
583浏览量
67755 -
分频器
+关注
关注
43文章
536浏览量
52258 -
fifo
+关注
关注
3文章
406浏览量
45485 -
发送器
+关注
关注
1文章
261浏览量
27641 -
时钟信号
+关注
关注
4文章
495浏览量
29693
原文标题:使用 FPGA 播放 SD 卡中的音频文件
文章出处:【微信号:Open_FPGA,微信公众号:OpenFPGA】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录

如何从SD卡读取音频文件并将其输出到扬声器上?
评论