RGB转HDMI方案、MS7210驱动——FPGA学习笔记20

一、简介

HDMI彩条显示------FPGA学习笔记12-CSDN博客

二、TMDS编码原理

HDMI 采用 TMDS (Time Minimized Differential Signal) 最小化传输差分信号传输技术, 是美国 Silicon Image 公司开发的一项高速数据传输技术, 将视频、 音频、 控制信号进行编码并串转换后发送。 TMDS 是一种微分信号机制,采用的是差分传动方式。 利用 2 个引脚间电压差来传送信号, 由两脚间电压正负极性和大小决定传送"0" 还是"1"。采用 2 根线来传输信号, 一根线上传输原来的信号, 另一根线上传输与原来信号相反的信号, 接收端就可以通过让一根线上的信号减去另一根线上的信号的方式来屏蔽电磁干扰, 从而得到正确的信号。

(1) 使用 channel0 的 D[1:0]传输 HSYNC, VSYNC, 占用 2bit, 控制信号被编码成 10 位传输, 00、 01、 10、 11 编码后分别是 10'b1101010100, 10'b0010101011, 10'b0101010100, 和 10'b1010101011。

( 2) Preamble 控制信息, 图中的 CTLx, 可用来表示后面传输的是 data island 还是 video data。 通过 channel1 和 2的 D[1:0]传输, 占用 4bit, 控制信号被编码成 10 位传输。

三、代码实现

Haskell 复制代码
`timescale 1ns / 1ps

module HDMI_top(
    input           I_sysclk        ,
    input           I_rst_n         ,
    output          O_hdmi_clk_p    ,
    output          O_hdmi_clk_n    ,
    output  [2:0]   O_hdmi_tx_p     ,
    output  [2:0]   O_hdmi_tx_n    
);

wire [7:0]  rgb_r   ;
wire [7:0]  rgb_g   ;
wire [7:0]  rgb_b   ;
wire        lcd_hs  ;
wire        lcd_vs  ;
wire        lcd_de  ;



//LCD驱动时钟
clk_wiz_0 u_clk_wiz_0
(
    .clk_75M    (clk_40M    )   ,     
    .clk_375M   (clk_200M   )   ,     
    .clk_in1    (I_sysclk   )
);  


video_lcd u_video_lcd(
    .I_vid_clk   (clk_40M   )   ,   //系统时钟
    .I_vid_rstn  (I_rst_n   )   ,   //系统复位输入
    .O_vid_hs    (lcd_hs    )   ,   //hs信号
    .O_vid_vs    (lcd_vs    )   ,   //vs信号
    .O_vid_de    (lcd_de    )   ,   //视频数据有效信号
    .O_rgb_r     (rgb_r     )   ,   // RGB-红
    .O_rgb_g     (rgb_g     )   ,   // RGB-绿
    .O_rgb_b     (rgb_b     )       // RGB-蓝
);

hdmitx#
(
    .FAMILY ("7FAMILY")			
)
u_hdmitx
(
.I_rstn             (I_rst_n            )   ,  //复位
.I_hs               (lcd_hs             )   ,  //hs信号
.I_vs               (lcd_vs             )   ,  //vs信号
.I_de               (lcd_de             )   ,  //de信号
.I_rgb              ({rgb_r,rgb_g,rgb_b})   ,  //RGB数据
.I_pclkx1           (clk_40M            )   ,  //像素时钟
.I_pclkx2_5         (1'b0               )   ,  //2.5倍像素时钟,只有UFAMILY需要
.I_pclkx5           (clk_200M           )   ,  //5倍像素时钟
.O_hdmi_tx_clk_p    (O_hdmi_clk_p       )   ,  //HDMI时钟输出P端
.O_hdmi_tx_clk_n    (O_hdmi_clk_n       )   ,  //HDMI时钟输出N端
.O_hdmi_tx_p        (O_hdmi_tx_p        )   ,  //HDMI输出数据P端
.O_hdmi_tx_n        (O_hdmi_tx_n        )      //HDMI输出数据N端
);

endmodule
Haskell 复制代码
module hdmitx#
(
parameter  FAMILY = "ULTRASCALE"			
)
(
    input I_rstn,
    input I_vs,
    input I_hs,
    input I_de,
    input [23:0] I_rgb,
    input I_pclkx1,
    input I_pclkx2_5,
    input I_pclkx5,
    output O_hdmi_tx_clk_p,
    output O_hdmi_tx_clk_n,
    output [2:0]O_hdmi_tx_p,
    output [2:0]O_hdmi_tx_n
);

wire [7:0] RED = I_rgb[23:16];
wire [7:0] GREEN = I_rgb[15:8];
wire [7:0] BLUE = I_rgb[7:0];  
wire [9:0] intTmdsRed;
wire [9:0] intTmdsGreen;
wire [9:0] intTmdsBlue;
    
wire intRst = !I_rstn;
    //----------------------------------------------------------------------------------
    //-- DVI Encoder; DVI 1.0 Specifications
    //-- This component encodes 24-bit RGB video frames with sync signals into 10-bit
    //-- TMDS characters.
    //----------------------------------------------------------------------------------
TMDSEncoder Inst_TMDSEncoder_red
     (
     .D_I(RED),
     .C0_I(1'b0),
     .C1_I(1'b0),
     .DE_I(I_de),
     .CLK_I(I_pclkx1),
     .D_O(intTmdsRed)
     );
TMDSEncoder Inst_TMDSEncoder_green
     (
     .D_I(GREEN),
     .C0_I(1'b0),
     .C1_I(1'b0),
     .DE_I(I_de),
     .CLK_I(I_pclkx1),
     .D_O(intTmdsGreen)
     );
TMDSEncoder Inst_TMDSEncoder_blue(
     .D_I(BLUE),
     .C0_I(I_hs),
     .C1_I(I_vs),
     .DE_I(I_de),
     .CLK_I(I_pclkx1),
     .D_O(intTmdsBlue)
    );
//----------------------------------------------------------------------------------
//-- TMDS serializer; ratio of 10:1; 3 data & 1 clock channel
// -- Since the TMDS clock's period is character-long (10-bit periods), the
// -- serialization of "1111100000" will result in a 10-bit long clock period.
//----------------------------------------------------------------------------------
generate  if(FAMILY == "ULTRASCALE" || FAMILY == "ULTRASCALE_PLUS")begin : ULTRASCALE_FAMILY 

    oserdese3_10to1 #
    (
        .FAMILY(FAMILY)
    )
    Inst_clk_oserdese3_10to1
    (
        .txdata("1111100000"),
        .txrst(intRst),
        .pclk(I_pclkx1),
        .clkdiv2(I_pclkx5),
        .clkdiv4(I_pclkx2_5),
        .tx_p(O_hdmi_tx_clk_p),
        .tx_n(O_hdmi_tx_clk_n)
    );

    oserdese3_10to1#
    (
        .FAMILY(FAMILY)
    )
    Inst_d2_serializer_10_1
    (
        .txdata(intTmdsRed),
        .txrst(intRst),
        .pclk(I_pclkx1),
        .clkdiv2(I_pclkx5),
        .clkdiv4(I_pclkx2_5),
        .tx_p(O_hdmi_tx_p[2]),
        .tx_n(O_hdmi_tx_n[2])
    );

    oserdese3_10to1#
    (
        .FAMILY(FAMILY)
    )
    Inst_d1_serializer_10_1
    (
        .txdata(intTmdsGreen),
        .txrst(intRst),
        .pclk(I_pclkx1),
        .clkdiv2(I_pclkx5),
        .clkdiv4(I_pclkx2_5),
        .tx_p(O_hdmi_tx_p[1]),
        .tx_n(O_hdmi_tx_n[1])
    );

    oserdese3_10to1#
    (
        .FAMILY(FAMILY)
    )
    Inst_d0_serializer_10_1
    (
        .txdata(intTmdsBlue),
        .txrst(intRst),
        .pclk(I_pclkx1),
        .clkdiv2(I_pclkx5),
        .clkdiv4(I_pclkx2_5),
        .tx_p(O_hdmi_tx_p[0]),
        .tx_n(O_hdmi_tx_n[0])
    );
end

else if(FAMILY == "7FAMILY")begin : family_7 

    oserdese2_10to1 Inst_clk_oserdese2_10to1
    (
        .txdata("1111100000"),
        .txrst(intRst),
        .pclk(I_pclkx1),
        .clkdiv2(I_pclkx5),
        .tx_p(O_hdmi_tx_clk_p),
        .tx_n(O_hdmi_tx_clk_n)
    ); 

    oserdese2_10to1 Inst_d2_serializer_10_1
    (
        .txdata(intTmdsRed),
        .txrst(intRst),
        .pclk(I_pclkx1),
        .clkdiv2(I_pclkx5),
        .tx_p(O_hdmi_tx_p[2]),
        .tx_n(O_hdmi_tx_n[2])
    );

    oserdese2_10to1 Inst_d1_serializer_10_1
    (
        .txdata(intTmdsGreen),
        .txrst(intRst),
        .pclk(I_pclkx1),
        .clkdiv2(I_pclkx5),
        .tx_p(O_hdmi_tx_p[1]),
        .tx_n(O_hdmi_tx_n[1])
    );

    oserdese2_10to1 Inst_d0_serializer_10_1
    (
        .txdata(intTmdsBlue),
        .txrst(intRst),
        .pclk(I_pclkx1),
        .clkdiv2(I_pclkx5),
        .tx_p(O_hdmi_tx_p[0]),
        .tx_n(O_hdmi_tx_n[0])
    );
    end
endgenerate
endmodule

四、上板验证

五、MS7210驱动方案

Haskell 复制代码
`timescale 1ns / 1ps

module HDMI_top(
    input           sys_clk_p   ,
    input           sys_clk_n   ,
    input           I_rst_n     ,
    
    output          lcd_clk     ,
    output          lcd_hs      ,
    output          lcd_vs      ,
    // output          lcd_bl      ,
    output          lcd_de      ,
    output          lcd_rst     ,
    inout  [23:0]   lcd_rgb     ,
    output          iic_scl     ,
    inout           iic_sda     //
);

//转换差分信号
IBUFDS diff_clock (
    .O          (I_sysclk   )   ,   //输出系统时钟
    .I          (sys_clk_p  )   ,   //系统差分输入时钟P端
    .IB         (sys_clk_n  )       //系统差分输入时钟N端
);

clk_wiz_0 u_clk_wiz_0
(
    .clk_out1   (clk_10M    )   ,     
    .clk_out2   (clk_40M    )   ,     
    .locked     (locked     )   ,      
    .reset      (~I_rst_n   )   ,

    .clk_in1    (I_sysclk   )  
);

assign  rst_n =  I_rst_n & locked;

// wire    rstn_out    ;

LCD u_LCD(
    .I_lcd_clk   (clk_40M   )   ,
    .I_rst_n     (rst_n     )   ,
    .lcd_clk     (lcd_clk   )   ,
    .lcd_hs      (lcd_hs    )   ,
    .lcd_vs      (lcd_vs    )   ,
    .lcd_bl      (lcd_bl    )   ,
    .lcd_de      (lcd_de    )   ,
    .lcd_rst     (   )   ,
    .lcd_rgb     (lcd_rgb   )      
);

ms72xx_ctl u_ms72xx_ctl(
    .clk         (clk_10M   )   ,   
    .rst_n       (rst_n     )   ,   
    .rstn_out    (lcd_rst   )   ,   //芯片复位信号,低有效
    .int_over    (init_over )   ,   //配置全部完成标志
    .iic_scl     (iic_scl   )   ,      
    .iic_sda     (iic_sda   )                 
);

endmodule
Haskell 复制代码
module ms72xx_ctl(
    input       clk,
    input       rst_n,
    
    output      rstn_out,    //芯片复位信号,低有效
    output      int_over,   //配置全部完成标志
    output      iic_scl ,   
    inout       iic_sda         //
);


//parameter define
parameter SLAVE_ADDR = 7'h2b          ; //器件地址
parameter BIT_CTRL   = 1'b1           ; //字节地址为16位  0:8位 1:16位
parameter CLK_FREQ   = 27'd10_000_000 ; //i2c_dri模块的驱动时钟频率 
parameter I2C_FREQ   = 18'd250_000    ; //I2C的SCL时钟频率,不超过400KHz

//reg define


//wire define
wire        i2c_exec       ;  //I2C触发执行信号
wire [23:0] i2c_data       ;  //I2C要配置的地址与数据(高16位地址,低8位数据)          
wire        i2c_done       ;  //I2C寄存器配置完成信号
wire        i2c_dri_clk    ;  //I2C操作时钟
wire [ 7:0] i2c_data_r     ;  //I2C读出的数据
wire        i2c_rh_wl      ;  //I2C读写控制信号

//*****************************************************
//**                    main code
//*****************************************************


//I2C配置模块
i2c_ms7210_cfg u_i2c_ms7210_cfg(
    .clk                (i2c_dri_clk),
    .rst_n              (rst_n),
            
    .i2c_exec           (i2c_exec),
    .i2c_data           (i2c_data),
    .i2c_rh_wl          (i2c_rh_wl),        //I2C读写控制信号
    .i2c_done           (i2c_done), 
    .i2c_data_r         (i2c_data_r),   
    .rstn_out           (rstn_out),       
    .init_done          (init_over)         //配置全部完成标志
    );    

//I2C驱动模块
i2c_dri #(
    .SLAVE_ADDR         (SLAVE_ADDR),       //参数传递
    .CLK_FREQ           (CLK_FREQ  ),              
    .I2C_FREQ           (I2C_FREQ  ) 
    )
u_i2c_dri(
    .clk                (clk),
    .rst_n              (rst_n     ),

    .i2c_exec           (i2c_exec  ),   
    .bit_ctrl           (BIT_CTRL ),   
    .i2c_rh_wl          (i2c_rh_wl),        //固定为0,只用到了IIC驱动的写操作   
    .i2c_addr           ({i2c_data[15:8],i2c_data[23:16]}),   
    .i2c_data_w         (i2c_data[7:0]),   
    .i2c_data_r         (i2c_data_r),   
    .i2c_done           (i2c_done  ),
    
    .scl                (iic_scl   ),   
    .sda                (iic_sda   ),   

    .dri_clk            (i2c_dri_clk)       //I2C操作时钟
    );	

    
endmodule
Haskell 复制代码
module i2c_ms7210_cfg
   (  
    input                clk      ,     //时钟信号
    input                rst_n    ,     //复位信号,低电平有效
    
    input        [7:0]   i2c_data_r,    //I2C读出的数据
    input                i2c_done ,     //I2C寄存器配置完成信号
    output  reg          i2c_exec ,     //I2C触发执行信号   
    output  reg  [23:0]  i2c_data ,     //I2C要配置的地址与数据(高16位地址,低8位数据)
    output  reg          i2c_rh_wl,     //I2C读写控制信号
    output                rstn_out,     //芯片复位信号,低有效
    output  reg          init_done      //初始化完成信号
    );

//parameter define
localparam  REG_NUM = 8'd50  ;       //总共需要配置的寄存器个数

//reg define
reg    [12:0]   start_init_cnt;        //等待延时计数器
reg    [7:0]    init_reg_cnt  ;        //寄存器配置个数计数器
reg    [13:0]   cfg_delay_cnt  ;       //寄存器配置之间延迟计数器
reg    [15:0]   rstn_1ms  = 16'd0     ;

//*****************************************************
//**                    main code
//*****************************************************

assign rstn_out = (rstn_1ms == 16'd1000) && rst_n;

//产生芯片复位信号的延迟计数器
always @(posedge clk)begin
    if(!rst_n)
    	 rstn_1ms <= 16'd0;
    else begin
    	if(rstn_1ms == 16'd1000)
    		rstn_1ms <= rstn_1ms;
        else
    		rstn_1ms <= rstn_1ms + 1'b1;
    end
end

//clk时钟配置成1MHZ,周期为1us 5000*1us = 5ms
//上电到开始配置IIC等待5ms
always @(posedge clk or negedge rstn_out) begin
    if(!rstn_out)
        start_init_cnt <= 13'b0;
    else if(start_init_cnt < 13'd5000) begin
        start_init_cnt <= start_init_cnt + 1'b1;                    
    end
end

//每个寄存器配置之间延迟20us    
always @(posedge clk or negedge rstn_out) begin
    if(!rstn_out)
        cfg_delay_cnt <= 8'd0;
    else if(i2c_done)   
        cfg_delay_cnt <= 8'd0;
	else if(cfg_delay_cnt >6000)
        cfg_delay_cnt <= cfg_delay_cnt ;		
	else
        cfg_delay_cnt <= cfg_delay_cnt + 8'b1;	
		
end

//寄存器配置个数计数    
always @(posedge clk or negedge rstn_out) begin
    if(!rstn_out)
        init_reg_cnt <= 8'd0;
    else if(i2c_exec)   
        init_reg_cnt <= init_reg_cnt + 8'b1;
end

//i2c触发执行信号   
always @(posedge clk or negedge rstn_out) begin
    if(!rstn_out)
        i2c_exec <= 1'b0;
    else if(start_init_cnt == 13'd4999)
        i2c_exec <= 1'b1;
    else if(i2c_done && (init_reg_cnt < REG_NUM) && (init_reg_cnt != 21))
        i2c_exec <= 1'b1;
    else if((cfg_delay_cnt == 5999) && (init_reg_cnt == 21))
        i2c_exec <= 1'b1;		
    else
        i2c_exec <= 1'b0;
end 

//配置I2C读写控制信号
always @(posedge clk or negedge rstn_out) begin
    if(!rstn_out)
        i2c_rh_wl <= 1'b1;
    else 
        i2c_rh_wl <= 1'b0;  
end

//初始化完成信号
always @(posedge clk or negedge rstn_out) begin
    if(!rstn_out)
        init_done <= 1'b0;
    else if((init_reg_cnt == REG_NUM) && i2c_done)  
        init_done <= 1'b1;  
end

//配置寄存器地址与数据
always @(posedge clk or negedge rstn_out) begin
    if(!rstn_out)
        i2c_data <= 24'b0;
    else begin
        case(init_reg_cnt)
            8'd0  : i2c_data <= {16'h0003,8'h5a}; 
            8'd1  : i2c_data <= {16'h1281,8'h04}; 
            8'd2  : i2c_data <= {16'h0016,8'h04}; 
            8'd3  : i2c_data <= {16'h0009,8'h01}; 
            8'd4  : i2c_data <= {16'h0007,8'h09}; 
            8'd5  : i2c_data <= {16'h0008,8'hF0};
            8'd6  : i2c_data <= {16'h000A,8'hF0};
            8'd7  : i2c_data <= {16'h0006,8'h11}; 
            8'd8  : i2c_data <= {16'h0531,8'h84}; 
            8'd9  : i2c_data <= {16'h0900,8'h20};
            8'd10 : i2c_data <= {16'h0901,8'h47};
            8'd11 : i2c_data <= {16'h0904,8'h09};
            8'd12 : i2c_data <= {16'h0923,8'h07};
            8'd13 : i2c_data <= {16'h0924,8'h44};
            8'd14 : i2c_data <= {16'h0925,8'h44};
            8'd15 : i2c_data <= {16'h090F,8'h80};
            8'd16 : i2c_data <= {16'h091F,8'h07};
            8'd17 : i2c_data <= {16'h0920,8'h1E};
            8'd18 : i2c_data <= {16'h0018,8'h20};
            8'd19 : i2c_data <= {16'h05c0,8'hFE};
            8'd20 : i2c_data <= {16'h000B,8'h00};
            8'd21 : i2c_data <= {16'h0507,8'h06};
            8'd22 : i2c_data <= {16'h0906,8'h04};
            8'd23 : i2c_data <= {16'h0920,8'h5E};
            8'd24 : i2c_data <= {16'h0926,8'hDD}; 
            8'd25 : i2c_data <= {16'h0927,8'h0D}; 
            8'd26 : i2c_data <= {16'h0928,8'h88}; 
            8'd27 : i2c_data <= {16'h0929,8'h08};
            8'd28 : i2c_data <= {16'h0910,8'h01};
            8'd29 : i2c_data <= {16'h000B,8'h11};
            8'd30 : i2c_data <= {16'h050E,8'h00}; 
            8'd31 : i2c_data <= {16'h050A,8'h82}; 
            8'd32 : i2c_data <= {16'h0509,8'h02}; 
            8'd33 : i2c_data <= {16'h050B,8'h0D};
            8'd34 : i2c_data <= {16'h050D,8'h06};
            8'd35 : i2c_data <= {16'h050D,8'h11};
            8'd36 : i2c_data <= {16'h050D,8'h58};
            8'd37 : i2c_data <= {16'h050D,8'h00};
            8'd38 : i2c_data <= {16'h050D,8'h00};
            8'd39 : i2c_data <= {16'h050D,8'h00};
            8'd40 : i2c_data <= {16'h050D,8'h00}; 
            8'd41 : i2c_data <= {16'h050D,8'h00}; 
            8'd42 : i2c_data <= {16'h050D,8'h00}; 
            8'd43 : i2c_data <= {16'h050D,8'h00}; 
            8'd44 : i2c_data <= {16'h050D,8'h00}; 
            8'd45 : i2c_data <= {16'h050D,8'h00}; 
            8'd46 : i2c_data <= {16'h050D,8'h00}; 
            8'd47 : i2c_data <= {16'h050D,8'h00}; 
            8'd48 : i2c_data <= {16'h050E,8'h40}; 
            8'd49 : i2c_data <= {16'h0507,8'h00};
            default : i2c_data <= {16'h0003,8'h5a}; 
        endcase
    end
end

endmodule
Haskell 复制代码
module i2c_dri
    #(
      parameter   SLAVE_ADDR = 7'h2b        ,  //器件地址
      parameter   CLK_FREQ   = 26'd10_000_000, //模块输入的时钟频率
      parameter   I2C_FREQ   = 18'd250_000     //IIC_SCL的时钟频率
    )
   (                                                            
    input                clk        ,    
    input                rst_n      ,   
                                         
    //i2c interface                      
    input                i2c_exec   ,  //I2C触发执行信号
    input                bit_ctrl   ,  //字地址位控制(16b/8b)
    input                i2c_rh_wl  ,  //I2C读写控制信号
    input        [15:0]  i2c_addr   ,  //I2C器件内地址
    input        [7:0]   i2c_data_w ,  //I2C要写的数据
    output  reg  [7:0]   i2c_data_r ,  //I2C读出的数据
    output  reg          i2c_done   ,  //I2C一次操作完成
    output  reg          i2c_ack    ,  //I2C应答标志 0:应答 1:未应答
    output  reg          scl        ,  //I2C的SCL时钟信号
    inout                sda        ,  //I2C的SDA信号
//    input                 sda_in,
//    output                sda_out,
//    output                sda_dir,    


                                       
    //user interface                   
    output  reg          dri_clk       //驱动I2C操作的驱动时钟
     );

//localparam define
localparam  st_idle     = 8'b0000_0001; //空闲状态
localparam  st_sladdr   = 8'b0000_0010; //发送器件地址(slave address)
localparam  st_addr16   = 8'b0000_0100; //发送16位字地址
localparam  st_addr8    = 8'b0000_1000; //发送8位字地址
localparam  st_data_wr  = 8'b0001_0000; //写数据(8 bit)
localparam  st_addr_rd  = 8'b0010_0000; //发送器件地址读
localparam  st_data_rd  = 8'b0100_0000; //读数据(8 bit)
localparam  st_stop     = 8'b1000_0000; //结束I2C操作

//reg define
reg            sda_dir   ; //I2C数据(SDA)方向控制
reg            sda_out   ; //SDA输出信号
reg            st_done   ; //状态结束
reg            wr_flag   ; //写标志
reg    [ 6:0]  cnt       ; //计数
reg    [ 7:0]  cur_state ; //状态机当前状态
reg    [ 7:0]  next_state; //状态机下一状态
reg    [15:0]  addr_t    ; //地址
reg    [ 7:0]  data_r    ; //读取的数据
reg    [ 7:0]  data_wr_t ; //I2C需写的数据的临时寄存
reg    [ 9:0]  clk_cnt   ; //分频时钟计数

//wire define
wire          sda_in     ; //SDA输入信号
wire   [8:0]  clk_divide ; //模块驱动时钟的分频系数

//*****************************************************
//**                    main code
//*****************************************************

//SDA控制
assign  sda     = sda_dir ?  sda_out : 1'bz;     //SDA数据输出或高阻
assign  sda_in  = sda ;                          //SDA数据输入
assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd2;//模块驱动时钟的分频系数

//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        dri_clk <=  1'b0;
        clk_cnt <= 10'd0;
    end
    else if(clk_cnt == clk_divide[8:1] - 1'd1) begin
        clk_cnt <= 10'd0;
        dri_clk <= ~dri_clk;
    end
    else
        clk_cnt <= clk_cnt + 1'b1;
end

//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge rst_n) begin
    if(!rst_n)
        cur_state <= st_idle;
    else
        cur_state <= next_state;
end

//组合逻辑判断状态转移条件
always @(*) begin
    next_state = st_idle;
    case(cur_state)
        st_idle: begin                          //空闲状态
           if(i2c_exec) begin
               next_state = st_sladdr;
           end
           else
               next_state = st_idle;
        end
        st_sladdr: begin
            if(st_done) begin
                if(bit_ctrl)                    //判断是16位还是8位字地址
                   next_state = st_addr16;
                else
                   next_state = st_addr8 ;
            end
            else
                next_state = st_sladdr;
        end
        st_addr16: begin                        //写16位字地址
            if(st_done) begin
                next_state = st_addr8;
            end
            else begin
                next_state = st_addr16;
            end
        end
        st_addr8: begin                         //8位字地址
            if(st_done) begin
                if(wr_flag==1'b0)               //读写判断
                    next_state = st_data_wr;
                else
                    next_state = st_addr_rd;
            end
            else begin
                next_state = st_addr8;
            end
        end
        st_data_wr: begin                       //写数据(8 bit)
            if(st_done)
                next_state = st_stop;
            else
                next_state = st_data_wr;
        end
        st_addr_rd: begin                       //写地址以进行读数据
            if(st_done) begin
                next_state = st_data_rd;
            end
            else begin
                next_state = st_addr_rd;
            end
        end
        st_data_rd: begin                       //读取数据(8 bit)
            if(st_done)
                next_state = st_stop;
            else
                next_state = st_data_rd;
        end
        st_stop: begin                          //结束I2C操作
            if(st_done)
                next_state = st_idle;
            else
                next_state = st_stop ;
        end
        default: next_state= st_idle;
    endcase
end

//时序电路描述状态输出
always @(posedge dri_clk or negedge rst_n) begin
    //复位初始化
    if(!rst_n) begin
        scl       <= 1'b1;
        sda_out   <= 1'b1;
        sda_dir   <= 1'b1;                          
        i2c_done  <= 1'b0;                          
        i2c_ack   <= 1'b0;                          
        cnt       <= 1'b0;                          
        st_done   <= 1'b0;                          
        data_r    <= 1'b0;                          
        i2c_data_r<= 1'b0;                          
        wr_flag   <= 1'b0;                          
        addr_t    <= 1'b0;                          
        data_wr_t <= 1'b0;                          
    end                                              
    else begin                                       
        st_done <= 1'b0 ;                            
        cnt     <= cnt +1'b1 ;                       
        case(cur_state)                              
             st_idle: begin                          //空闲状态
                scl     <= 1'b1;                     
                sda_out <= 1'b1;                     
                sda_dir <= 1'b1;                     
                i2c_done<= 1'b0;                     
                cnt     <= 7'b0;               
                if(i2c_exec) begin                   
                    wr_flag   <= i2c_rh_wl ;         
                    addr_t    <= i2c_addr  ;         
                    data_wr_t <= i2c_data_w;  
                    i2c_ack <= 1'b0;                      
                end                                  
            end                                      
            st_sladdr: begin                         //写地址(器件地址和字地址)
                case(cnt)                            
                    7'd1 : sda_out <= 1'b0;          //开始I2C
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= SLAVE_ADDR[5]; 
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= SLAVE_ADDR[4]; 
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= SLAVE_ADDR[3]; 
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= SLAVE_ADDR[2]; 
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= SLAVE_ADDR[1]; 
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= SLAVE_ADDR[0]; 
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: sda_out <= 1'b0;          //0:写
                    7'd33: scl <= 1'b1;              
                    7'd35: scl <= 1'b0;              
                    7'd36: begin                     
                        sda_dir <= 1'b0;             
                        sda_out <= 1'b1;                         
                    end                              
                    7'd37: scl     <= 1'b1;            
                    7'd38: begin                     //从机应答 
                        st_done <= 1'b1;
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位     
                    end                                          
                    7'd39: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            st_addr16: begin                         
                case(cnt)                            
                    7'd0 : begin                     
                        sda_dir <= 1'b1 ;            
                        sda_out <= addr_t[15];       //传送字地址
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= addr_t[14];    
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= addr_t[13];    
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= addr_t[12];    
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= addr_t[11];    
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= addr_t[10];    
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= addr_t[9];     
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= addr_t[8];     
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;             
                        sda_out <= 1'b1;   
                    end                              
                    7'd33: scl  <= 1'b1;             
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end        
                    7'd35: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            st_addr8: begin                          
                case(cnt)                            
                    7'd0: begin                      
                       sda_dir <= 1'b1 ;             
                       sda_out <= addr_t[7];         //字地址
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= addr_t[6];     
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= addr_t[5];     
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= addr_t[4];     
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= addr_t[3];     
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= addr_t[2];     
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= addr_t[1];     
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= addr_t[0];     
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;         
                        sda_out <= 1'b1;                    
                    end                              
                    7'd33: scl     <= 1'b1;          
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end   
                    7'd35: begin                     
                        scl <= 1'b0;                 
                        cnt <= 1'b0;                 
                    end                              
                    default :  ;                     
                endcase                              
            end                                      
            st_data_wr: begin                        //写数据(8 bit)
                case(cnt)                            
                    7'd0: begin                      
                        sda_out <= data_wr_t[7];     //I2C写8位数据
                        sda_dir <= 1'b1;             
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= data_wr_t[6];  
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= data_wr_t[5];  
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= data_wr_t[4];  
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= data_wr_t[3];  
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= data_wr_t[2];  
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= data_wr_t[1];  
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= data_wr_t[0];  
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: begin                     
                        sda_dir <= 1'b0;           
                        sda_out <= 1'b1;                              
                    end                              
                    7'd33: scl <= 1'b1;              
                    7'd34: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end          
                    7'd35: begin                     
                        scl  <= 1'b0;                
                        cnt  <= 1'b0;                
                    end                              
                    default  :  ;                    
                endcase                              
            end                                      
            st_addr_rd: begin                        //写地址以进行读数据
                case(cnt)                            
                    7'd0 : begin                     
                        sda_dir <= 1'b1;             
                        sda_out <= 1'b1;             
                    end                              
                    7'd1 : scl <= 1'b1;              
                    7'd2 : sda_out <= 1'b0;          //重新开始
                    7'd3 : scl <= 1'b0;              
                    7'd4 : sda_out <= SLAVE_ADDR[6]; //传送器件地址
                    7'd5 : scl <= 1'b1;              
                    7'd7 : scl <= 1'b0;              
                    7'd8 : sda_out <= SLAVE_ADDR[5]; 
                    7'd9 : scl <= 1'b1;              
                    7'd11: scl <= 1'b0;              
                    7'd12: sda_out <= SLAVE_ADDR[4]; 
                    7'd13: scl <= 1'b1;              
                    7'd15: scl <= 1'b0;              
                    7'd16: sda_out <= SLAVE_ADDR[3]; 
                    7'd17: scl <= 1'b1;              
                    7'd19: scl <= 1'b0;              
                    7'd20: sda_out <= SLAVE_ADDR[2]; 
                    7'd21: scl <= 1'b1;              
                    7'd23: scl <= 1'b0;              
                    7'd24: sda_out <= SLAVE_ADDR[1]; 
                    7'd25: scl <= 1'b1;              
                    7'd27: scl <= 1'b0;              
                    7'd28: sda_out <= SLAVE_ADDR[0]; 
                    7'd29: scl <= 1'b1;              
                    7'd31: scl <= 1'b0;              
                    7'd32: sda_out <= 1'b1;          //1:读
                    7'd33: scl <= 1'b1;              
                    7'd35: scl <= 1'b0;              
                    7'd36: begin                     
                        sda_dir <= 1'b0;            
                        sda_out <= 1'b1;                    
                    end
                    7'd37: scl     <= 1'b1;
                    7'd38: begin                     //从机应答
                        st_done <= 1'b1;     
                        if(sda_in == 1'b1)           //高电平表示未应答
                            i2c_ack <= 1'b1;         //拉高应答标志位    
                    end   
                    7'd39: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                    end
                    default : ;
                endcase
            end
            st_data_rd: begin                        //读取数据(8 bit)
                case(cnt)
                    7'd0: sda_dir <= 1'b0;
                    7'd1: begin
                        data_r[7] <= sda_in;
                        scl       <= 1'b1;
                    end
                    7'd3: scl  <= 1'b0;
                    7'd5: begin
                        data_r[6] <= sda_in ;
                        scl       <= 1'b1   ;
                    end
                    7'd7: scl  <= 1'b0;
                    7'd9: begin
                        data_r[5] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd11: scl  <= 1'b0;
                    7'd13: begin
                        data_r[4] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd15: scl  <= 1'b0;
                    7'd17: begin
                        data_r[3] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd19: scl  <= 1'b0;
                    7'd21: begin
                        data_r[2] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd23: scl  <= 1'b0;
                    7'd25: begin
                        data_r[1] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd27: scl  <= 1'b0;
                    7'd29: begin
                        data_r[0] <= sda_in;
                        scl       <= 1'b1  ;
                    end
                    7'd31: scl  <= 1'b0;
                    7'd32: begin
                        sda_dir <= 1'b1;             
                        sda_out <= 1'b1;
                    end
                    7'd33: scl     <= 1'b1;
                    7'd34: st_done <= 1'b1;          //非应答
                    7'd35: begin
                        scl <= 1'b0;
                        cnt <= 1'b0;
                        i2c_data_r <= data_r;
                    end
                    default  :  ;
                endcase
            end
            st_stop: begin                           //结束I2C操作
                case(cnt)
                    7'd0: begin
                        sda_dir <= 1'b1;             //结束I2C
                        sda_out <= 1'b0;
                    end
                    7'd1 : scl     <= 1'b1;
                    7'd3 : sda_out <= 1'b1;
                    7'd15: st_done <= 1'b1;
                    7'd16: begin
                        cnt      <= 1'b0;
                        i2c_done <= 1'b1;            //向上层模块传递I2C结束信号
                    end
                    default  : ;
                endcase
            end
        endcase
    end
end

endmodule

六、上版验证

相关推荐
kinl20184 小时前
cs2385_note1 (lec6-lec8)
笔记
FatHonor4 小时前
【golang学习之旅】使用VScode安装配置Go开发环境
vscode·学习·golang
Edward111111115 小时前
3月24 内部类
学习
我不是程序猿儿5 小时前
【嵌入式】适合 STM32 初学者BootLoader 入门学习心得
linux·stm32·单片机·嵌入式硬件·学习
中屹指纹浏览器5 小时前
2026住宅IP网络环境下指纹浏览器稳定性优化与工程实践
经验分享·笔记
液态不合群5 小时前
一文学习 Spring 声明式事务源码全流程总结
java·学习·spring
云边散步5 小时前
godot2D游戏教程系列二(20)
笔记·学习·音视频
CyanMind6 小时前
IsaacLab 训练范式探索(二):从“上帝视角”到实机落地的蒸馏学习
学习
吃个糖糖6 小时前
Open3D学习点云读取与显示
学习
DANGAOGAO6 小时前
Transformer学习
深度学习·学习·transformer