FPGA的I2S采集数据处理

0

由于项目需要,需要由FPGA把4路由ADC芯片采集的I2S数据进行合并成1路I2S,最后输出到主控CPU芯片,FPGA在这里起到数据中转的作用。项目的整体结构如下图

(待画)

1.I2S协议

参考文档:I2S bus specification.pdf  见下面博客

参考博客:I2S接口规范时序以及其同DSP的连接

I2S音频总线学习中的一、二、四

采样频率、时间、声道、量化与储存大学的关系

2.实现方案

1.方案一:FIFO的思想

即采用8个FIFO,分成两组,组1的4个FIFO负责存储4路采样数据的左声道的32bit,组二的4个FIFO负责存储4路采样数据的左声道的32bit;然后使用乒乓操作进行2个组的读和写的切换,即读组1的同时对组2进行写,读组2的同时对组1进行写,读时钟的频率是写时钟的4倍,这样可以保证写完4路数据的时间和读完4路并输出成1路的数据相同。

2.方案二:RAM的思想

采用2个32X4(地址深度32,数据宽度为4,)的RAM分别存储4路采样数据的左右声道数据(32bit)。其中RAM1的地址n中存储的是4路左声道数据的第n位,正好4位,其中RAM2的地址n中存储的是4路右声道数据的第n位。写时钟是 Fbclk1=32X2X16K(bit时钟), Flsclk1=16k(采样频率=帧时钟);读时钟Fbclk0=32X2X4X16K(主控CPU的bit时钟), Flsclk0=4X16k(主控CPU帧时钟)。读和写RAM采用乒乓操作进行切换。其中RAM采用SDP(伪双端口RAM),这里写代码需要参考SDP的具体手册。

主要参数的时序:Fbclk0、Flsclk0、Fbclk1、Flsclk1、data0、data1、data2、data3、data4(待续)

3.硬件实现

1.FPGA最小系统

1.Micro USB 供电
2.LDO生成核心电压VCC=1.2V,所有的VCCIO=3.3V
3.JTAG下载电路,复位引脚

2.I2S输入和输出接口

1.主控CPU输入Fbclk0、Flsclk0;FPGA输出到主控时钟CPU:data0;
2.FPGA输出到ADC芯片:Fbclk1、Flsclk1、Fmclk;ADC输入到FPGA:data1、data2、data3、data4

3.I2C接口

1.FPGA留出I2C接口,用来进行通过FPGA对ADC进行配置

4.代码结构及实现

1.整体端口

module I2S_64K
(
    //main controller
	input   i_rst,
    input   i_bit_clk,                        
    input   i_fs_clk,
    output	o_data,

    //adc 
    output	reg o_bit_clk,
    output 	reg o_fs_clk,
    output 	 o_main_clk,  //---------------------------------->now, it have not be usedd
    input   i_data0,
    input   i_data1,
    input   i_data2,
    input   i_data3,
    //input   i_data4,
    //input   i_data5

    //i2c_config_done
    //input   i2c_config_done
);

2.生成ADC芯片的Fbclk1和Flsclk1

对输入的主控CPU的I2S时钟(Fbclk0、Flsclk0)进行4分频生成ADC的Fbclk1、Flsclk1
参见(奇偶分频、小数分频),这里计数器分频

parameter DIVIDE=4;                 //共4路数据,所以4分频
//**********************************************************************************************************************
//************************   o_bit_clk = i_bit_clk / DIVIDE ;   o_fs_clk = i_fs_clk / DIVIDE   *************************
//**********************************************************************************************************************
//1.o_bit_clk = i_bit_clk / DIVIDE      
reg [1:0] cnt_bit_divide;

always@(negedge i_bit_clk or negedge i_rst)     //下降沿时钟触发是为了下降沿和o_fs_clk对齐
if(!i_rst)
    begin
        cnt_bit_divide <= 2'd0;
        o_bit_clk <= 1'b0;
    end
else if(cnt_bit_divide == (DIVIDE/2-1))     
    begin
        cnt_bit_divide <= 2'd0;
        o_bit_clk <= ~o_bit_clk;
    end
else
    begin
        cnt_bit_divide <= cnt_bit_divide + 1'b1;
        o_bit_clk <= o_bit_clk;
    end


//**********************************************
//2.o_fs_clk = i_fs_clk / DIVIDE        
reg [1:0] cnt_fs_divide;

always@(negedge i_fs_clk or negedge i_rst)
if(!i_rst)
    begin
        cnt_fs_divide <= 2'd0;
        o_fs_clk <= 1'b1;                        
    end
else if(cnt_fs_divide == (DIVIDE/2-1))           
    begin
        cnt_fs_divide <= 2'd0;
        o_fs_clk <= ~o_fs_clk;
    end
else
    begin
        cnt_fs_divide <= cnt_fs_divide + 1'b1;
        o_fs_clk <= o_fs_clk;
    end

3.生成ADC芯片的Fmclk

用PLL对主控时钟Fbclk0进行倍频生成Fmclk

wire clkout_o,lock_o;
 GW_PLL PLL_3(
        .clkout(clkout_o), //output clkout
        .lock(lock_o), //output lock
        .clkin(i_bit_clk) //input clkin
    );
assign o_main_clk = (lock_o == 1'b1)    ?   clkout_o    :   1'b0;




4.I2S接收电路和4路并行数据写入RAM(写入时钟Fbclk1)

接收电路参考文档:I2S bus specification.pdf。这个文档的figure5、figure6和figure7有详细的RTL图,这个是我写这段代码的基础。下面的是上升沿(时钟)边沿检测,并触发了下降沿(时钟)的数据发送就是从这里参考的 

1.接收电路上升沿时钟便要检测和下降沿时钟开始接收数据


// dectect o_fs_clk  edge change    
reg o_fs_clk1,o_fs_clk2;

always@(posedge o_bit_clk or negedge i_rst)
if(!i_rst)
    begin
        o_fs_clk1 <= 1'b0;
        o_fs_clk2 <= 1'b0;
    end
else 
    begin
        o_fs_clk1 <= o_fs_clk;
        o_fs_clk2 <= o_fs_clk1;
    end

wire wsp = o_fs_clk1 ^ o_fs_clk2;     // edge detect

2.接收数据写入RAM

这里需要计数器对32bit的写入进行计数(即计数最大值31)

//*******************************************
// SRAM store count register     
reg [4:0] cnt_rec; //SRAM store count register 

always@(negedge o_bit_clk or negedge i_rst)    //下降沿时钟,wsp上升沿开始计时(see the I2S bus specification.pdf,the last two figure)
if(!i_rst)
    begin
        cnt_rec <= 5'd0;           		
    end    
else if(wsp)                        //
	begin
        cnt_rec <= 5'd1;           		
    end
else
    begin
        cnt_rec <= cnt_rec + 1'b1;     
    end

RAM写入使能,RAM写入就是一个周期,即立马写入

//*******************************************	
// SRAM write enable signal
wire cea1 = o_fs_clk;
wire cea2 = ~ o_fs_clk;

RAM数据写入并编通道号

//*******************************************
//ADC data receive ,then store the data in SRAM ,   
wire [3:0] data_adc;
//assign data_adc = {i_data0,i_data1,i_data2,i_data3};

assign data_adc = 	((cnt_rec >= 5'd25) | (cnt_rec <= 5'd20))	?	{i_data0,i_data1,i_data2,i_data3}	:
					((cea1 == 1'b1) & (cnt_rec == 5'd21))	?	(4'b0000)	:
					((cea1 == 1'b1) & (cnt_rec == 5'd22))	?	(4'b0001)	:
					((cea1 == 1'b1) & (cnt_rec == 5'd23))	?	(4'b0110)	:
					((cea1 == 1'b1) & (cnt_rec == 5'd24))	?	(4'b1010)	:
					((cea1 == 1'b0) & (cnt_rec == 5'd21))	?	(4'b0001)	:
					((cea1 == 1'b0) & (cnt_rec == 5'd22))	?	(4'b1110)	:
					((cea1 == 1'b0) & (cnt_rec == 5'd23))	?	(4'b0110)	:
					((cea1 == 1'b0) & (cnt_rec == 5'd24))	?	(4'b1010)	:	(4'b0000);	

5.主控CPU读出RAM(读RAM时钟Fbclk0)

1.读时钟边沿检测

reg i_fs_clk1,i_fs_clk2;

always@(posedge i_bit_clk or negedge i_rst)
if(!i_rst)
    begin
        i_fs_clk1 <= 1'b0;
        i_fs_clk2 <= 1'b0;
    end
else 
    begin
        i_fs_clk1 <= i_fs_clk;
        i_fs_clk2 <= i_fs_clk1;
    end

wire wsp3 = i_fs_clk1 ^ i_fs_clk2; //边沿检测

2.读RAM的计数器

reg [4:0] cnt_read; //计数器只是用来计数,当o_bit_clk变化时它就清零,然后依次加一
always@(negedge i_bit_clk or negedge i_rst)    //the same resson as 99 line    
if(!i_rst)
    begin
        cnt_read <= 5'd0;
    end
else if(wsp3)                    //i_fs_clk edge detect                                      
    begin
        cnt_read <= 5'd3;        // attention !!!  here is important,  to keep same with the left-justified audio data   
    end
else 
    begin     
        cnt_read <= cnt_read + 1'b1;
    end

3.读出的4个通道数据区分

因为RAM的bypass输出需要两个周期,并且需要左对齐输出,

reg [3:0] cnt_read_cnt;  //这个累加器是为了把o_fs_clk分成四份,用来区别通道数的
always@(negedge i_bit_clk or negedge i_rst)    //the same resson as 99 line 
if(!i_rst)
    begin
        cnt_read_cnt <= 4'b0000;
    end
else if((cnt_read == 5'd0)  & (cnt_rec != 5'd31))      //
    begin     
        cnt_read_cnt <= cnt_read_cnt << 1;
    end
else if((cnt_read == 5'd0)  & (cnt_rec == 5'd31))  //
    begin     
        cnt_read_cnt <= 4'b0001;              //attention !!!    here is delay for a i_bit_clk period !
    end
else
    begin
        cnt_read_cnt <= cnt_read_cnt;
    end

4.读RAM使能

因为RAM的bypass输出需要两个周期,并且需要左对齐输出,

reg ceb11,ceb22;


always@(negedge i_bit_clk or negedge i_rst)    //the same resson as 99 line 
if(!i_rst)
    begin
        ceb11 <= 1'b0;
		ceb22 <= 1'b0;
    end
else if((cnt_read == 5'd30) & (cnt_rec == 5'd31) & (o_fs_clk == 1'b1) )      // here is good ,and there are no delay ,
    begin     
        ceb11 <= 1'b1;
        ceb22 <= 1'b0;
    end
else if((cnt_read == 5'd30) & (cnt_rec == 5'd31) & (o_fs_clk == 1'b0) )  //
    begin     
        ceb11 <= 1'b0;
        ceb22 <= 1'b1;
    end
else
    begin
        ceb11 <= ceb11;
        ceb22 <= ceb22;
    end

6.两个RAM例化

RAM1存储左声道数据,RAM2储存右声道数据

 GW_SDP sram_left(
        .dout(data_sram1), //output [3:0] dout                                read
        .clka(o_bit_clk), //input clka              write
        .cea(cea1), //input cea                     write
        .reseta(!i_rst), //input reseta             write
        .clkb(!i_bit_clk), //input clkb                              read
        .ceb(ceb11), //input ceb                                         read
        .resetb(!i_rst), //input resetb                             read
        .oce(1'b0), //input oce                                                         
        .ada(cnt_rec), //input [4:0] ada            write
        .din(data_adc), //input [3:0] din           write
        .adb(cnt_read) //input [4:0] adb                                    read
    );


    GW_SDP sram_right(
        .dout(data_sram2), //output [3:0] dout                                read
        .clka(o_bit_clk), //input clka              write
        .cea(cea2), //input cea                     write
        .reseta(!i_rst), //input reseta             write
        .clkb(!i_bit_clk), //input clkb                              read
        .ceb(ceb22), //input ceb                                         read
        .resetb(!i_rst), //input resetb                             read
        .oce(1'b0), //input oce                                                        
        .ada(cnt_rec), //input [4:0] ada            write
        .din(data_adc), //input [3:0] din           write
        .adb(cnt_read) //input [4:0] adb                                    read
    );

7.I2S数据输出

assign o_data = (ceb11 & (cnt_read_cnt == 4'b0001))?  data_sram1[3] :
                (ceb11 & (cnt_read_cnt == 4'b0010))?  data_sram1[2] :
                (ceb11 & (cnt_read_cnt == 4'b0100))?  data_sram1[1] :
                (ceb11 & (cnt_read_cnt == 4'b1000))?  data_sram1[0] :
                (ceb22 & (cnt_read_cnt == 4'b0001))?  data_sram2[3] :
                (ceb22 & (cnt_read_cnt == 4'b0010))?  data_sram2[2] :
                (ceb22 & (cnt_read_cnt == 4'b0100))?  data_sram2[1] :
                (ceb22 & (cnt_read_cnt == 4'b1000))?  data_sram2[0] : 1'b0;

5.modelsim仿真

主要参数的时序:
i_bit_clk、i_fs_clk、o_bit_clk、o_fs_clk、o_data、i_data0、i_data1、i_data2、i_data3

6.总结

1.知识点

1.I2S协议:I2S bus specification.pdf 
2.modelsim独立仿真的流程
3.SDP(伪双端口RAM的具体使用细节和读写的时序查看细节)
4.本代码I2S接收是normal格式,发送时I2S的左对齐模式











你可能感兴趣的:(FPGA)