IIC读写EEPROM

一、IIC协议简介

1.IIC简介

        IIC即Inter-Integrated Circuit(集成电路总线),它是一种同步串行半双工通信总线,使用多主从架构,由飞利浦公司在1980年代设计出来的一种简单、双向、二线制总线标准。多用于主机和从机在数据量不大且传输距离短的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。

        IIC总线由数据线SDA和时钟线SCL构成通信线路,既可用于发送数据,也可接收数据。在主控与被控IC之间可进行双向数据传送,数据的传输速率在标准模式下可达100kbit/s,在快速模式下可达400kbit/s,在高速模式下可达3.4Mbit/s,各种被控器件均并联在总线上,通过器件地址(SLAVE ADDR,具体可查器件手册)识别。

注意:

1.IIC总线通过上拉电阻接高电平。即空闲状态为高,输出低电平时总线拉低。

2.主机通过识别器件地址ID建立多地址通信机制。

3.从机与从机之间不能进行数据交换。注意:IIC上的设备既可以是主机也能是从机,这取决于我们具体设备所需要实现的功能。

4.IIC支持多主多从。但同一时刻,只有一个主机能发起传输。若多个主机发起输,由于总线仲裁机制,只能给一个主机授权。IIC总线只能由总机发起传输。

总线仲裁机制

(1)仲裁过程:当主设备A准备占用I2C时,需要在SCL为高时将SDA拉高,再拉低,满足一个启动条件。当主设备A将SDA拉高后,需检查SDA的电平:

        如果此时SDA电平为高,说明主设备可以占用总线,然后主设备A会将SDA拉低,一次满足启动条件,开始传输。

如果此时SDA电平为低,说明总线已经被其他设备占用,主设备A会退出。

(2)为什么SDA为低,就是被其他设备占用了呢?

        因为线与逻辑的存在。只有总线上有其他的设备将SDA置为0,线与后,SDA线的电平为0。主设备A检查SDA线的电平时,会发现为低电平。所以仲裁时,哪个设备更早地将SDA线拉低,谁就抢占了优先权。

2.IIC通信原理

2.1 IIC传输时序

(1)起始信号,SCL高电平期间,SDA拉低;

(2)数据信号,SCL高电平期间,从机采样数据;SCL低电平期间,主机发送改变数据。

(3)应答信号,IIC总线传输数据时,每传输1个字节后都必须有一个SCL的有效应答信号。(可强制在第8周期低电平期间将SDA强制拉高,以保证能检测到有效应答)。

        应答信号有两种状态:应答(ACK)、非应答 (NACK) ,接收端在应答位期间输出低电平则为应答,输出高电平则为非应答。

        当由于某种原因,从机设备不产生应答时,如从机在进行其它处理而无法接收总线上的数据时,必须释放SDA总线,然后由主机产生一个停止信号以终止数据传输。

        当主机在接收数据时,接收到最后一个字节后,必须给从机发送一个非应答信号,使从机释放总线以便主机可以发送停止信号,终止数据传输。

(4)停止信号,SCL高电平期间,SDA拉高;

2.2 IIC总线数据传输

        IIC总线协议规定:起始信号表明一次传输开始,其后为寻址字节(高7位为从机地址、最低1位为方向位,方向位表明全机与从机之间的数据传输方向,0:主机发送数据给从机,1:从机发送数据给主机);在寻址字节后是对应的读、写操作的数据字节和应答位;在数据传输完成后主机必须发送停止信号

        IIC总线的数据传输方式有许多读、写组合方式: 主机写操作、主机读操作、主机读写操作

图2.2 传输器件地址格式

二、工程实践

        本次对IIC协议的功能探究采用的是“利用IIC控制EEPROM读写”,以此来加强对IIC协议的了解。详细参考:EEPROM手册解读

1.系统框图

2.代码设计

2.1 IIC接口模块设计
2.1.1 IIC状态转移图

2.1.2 IIC接口代码
module i2c_intf (
    input           clk         ,
    input           rst_n       ,
    input   [3:0]   cmd         ,
    input           req         ,
    input   [7:0]   wr_data     ,
    output  [7:0]   dout        ,
    output          done        ,
    output  reg     scl         ,
    inout           sda                
);

parameter   CMD_START   = 4'b0001,
            CMD_WITER   = 4'b0010,
            CMD_READ    = 4'b0100,
            CMD_STOP    = 4'b1000;

parameter   SCL_MAX     = 50_000_000 / 100_000 ,// 500分频-->100k的时钟频率
            SCL_LOW     = 125,//低电平的中间时刻,发送 1/4
            SCL_HIGHT   = 375;//高电平的中间时刻,采样 3/4


//wr_data
reg     [7:0]   wr_data_r;
reg     [7:0]   rd_data  ;
//cmd寄存
reg     [3:0]   cmd_r;
//ack响应
reg             rx_ack;   
//scl计数器
reg	    [8:0]   cnt_scl     ;
wire		    add_cnt_scl ;
wire            end_cnt_scl ;
//bit计数器
reg     [3:0]   cnt_bit     ;
wire		    add_cnt_bit ;
wire            end_cnt_bit ;
reg     [3:0]   bit_max     ;//bit最大计数复用

//状态转移条件
reg [3:0]   state_c;
reg [3:0]   state_n;
wire        idle2start  ;
wire        idle2witer  ;
wire        idle2read   ;
wire        start2witer ;
wire        witer2rack  ;
wire        rack2idle   ;
wire        rack2stop   ;
wire        read2sack   ;
wire        sack2idle   ;
wire        sack2stop   ;
wire        stop2idle   ;
//三态门
reg         sda_en        ; // 设置SDA模式,1位输出,0为输入
reg         sda_out       ; // SDA寄存器
wire        sda_in;
/**************************************************************
                         状态机        
**************************************************************/
parameter   IDLE    =   0,
            START   =   1,
            WITER   =   2,
            RACK    =   3,
            READ    =   4,
            SACK    =   5,
            STOP    =   6;
                                
//第一段状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
    state_c <= IDLE;
end
else begin
    state_c <= state_n;
end
end
                
//第二段状态机
always @(*)begin
case(state_c)
    IDLE    :	if(idle2start)
                    state_n = START;
                else if(idle2witer)
                    state_n = WITER;
                else if(idle2read)
                    state_n = READ;
                else 
                    state_n = state_c;
    START    :	if(start2witer)
                    state_n = WITER;
                else 
                    state_n = state_c;
    WITER    :	if(witer2rack)
                    state_n = RACK;
                else 
                    state_n = state_c;                                   
    RACK    :	if(rack2idle)
                    state_n = IDLE;
                else if(rack2stop)
                    state_n = STOP;
                else 
                    state_n = state_c;
    READ    :	if(read2sack)
                    state_n = SACK;
                else 
                    state_n = state_c;
    SACK    :	if(sack2idle)
                    state_n = IDLE;
                else if(sack2stop)
                    state_n = STOP;
                else 
                    state_n = state_c;                                                                         
    STOP    :   if(stop2idle)
                    state_n = IDLE;
                else
                    state_n = state_c;            
    default : state_n = state_c;
    endcase
end	
                               
//状态跳转条件
assign 	   idle2start  = state_c == IDLE  && req && (cmd & CMD_START)	; 
assign 	   idle2witer  = state_c == IDLE  && req && (cmd & CMD_WITER)	;
assign 	   idle2read   = state_c == IDLE  && req && (cmd & CMD_READ )	;
assign 	   start2witer = state_c == START && end_cnt_bit ;
assign 	   witer2rack  = state_c == WITER && end_cnt_bit ;
assign 	   rack2idle   = state_c == RACK  && end_cnt_bit && (!(cmd_r & CMD_STOP));
assign 	   rack2stop   = state_c == RACK  && end_cnt_bit && ((cmd_r & CMD_STOP) || rx_ack);
assign 	   read2sack   = state_c == READ  && end_cnt_bit ;
assign 	   sack2idle   = state_c == SACK  && end_cnt_bit && (!(cmd_r & CMD_STOP));
assign 	   sack2stop   = state_c == SACK  && end_cnt_bit && (cmd_r & CMD_STOP);
assign     stop2idle   = state_c == STOP  && end_cnt_bit ;
/**************************************************************
                    时序约束            
**************************************************************/
//cmd寄存
always@(posedge clk or negedge rst_n)
    if(!rst_n)
        cmd_r <= 4'b0000;
    else if(req)
        cmd_r <= cmd;

//接收从机回应的ack
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            rx_ack <= 1'b1;
        end 
        else if(state_c == RACK && cnt_scl == SCL_HIGHT)begin 
            rx_ack <= sda_in;
        end 
    end

//写入的数据 
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            wr_data_r <= 0;
        end 
        else if(req)begin 
            wr_data_r <= wr_data;
        end 
    end
//接收读取的数据
//rd_data       接收读入的数据
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            rd_data <= 0;
        end
        else if(state_c == READ && cnt_scl == SCL_HIGHT)begin
            rd_data[7-cnt_bit] <= sda_in;    //将接收到的SDA信号串并转换发送到eeprom_rw模块
        end
    end
/**************************************************************
                    双向端口sda的使用方式                   
**************************************************************/
assign	sda_in = sda;					 //高阻态的话,则把总线上的数据赋给sda_in
assign	sda =  sda_en ? sda_out : 1'bz;//使能1则输出,0则高阻态                    

//sda_en
always@(posedge clk or negedge rst_n)
    if(!rst_n)
        sda_en <= 1'b0;
    else if(state_c == READ | state_c == RACK)   
        sda_en <= 1'b0;     
    else 
        sda_en <= 1'b1; 
           
//sda_out
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            sda_out <= 1;
        end 
        else begin 
            case (state_c)
                START : if(cnt_scl == SCL_LOW)
                            sda_out <= 1'b1;
                        else if(cnt_scl == SCL_HIGHT)
                            sda_out <= 1'b0;
                WITER : if(cnt_scl == SCL_LOW)
                            sda_out <= wr_data_r[7-cnt_bit];//MSB
                STOP  : if(cnt_scl == SCL_LOW)
                            sda_out <= 1'b0;
                        else if(cnt_scl == SCL_HIGHT)
                            sda_out <= 1'b1;
                SACK  : if(cnt_scl == SCL_LOW)
                            sda_out <= (cmd & CMD_STOP)?1'b1:1'b0;
                default: sda_out <= 1'bz;
            endcase
        end 
    end
/**************************************************************
                    系统时钟降频模块             
**************************************************************/
//cnt_scl
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt_scl <= 0;
        end 
        else if(add_cnt_scl)begin 
            if(end_cnt_scl)begin 
                cnt_scl <= 0;
            end
            else begin 
                cnt_scl <= cnt_scl + 1;
            end 
        end
        else begin
            cnt_scl <= cnt_scl;
        end
    end 

    assign add_cnt_scl = state_c != IDLE ;
    assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_MAX - 1;

//scl
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            scl <= 1'b1;
        end 
        else if(cnt_scl <= (SCL_MAX>>1))begin 
            scl <= 1'b0;
        end 
        else begin 
            scl <= 1'b1;
        end 
    end

/**************************************************************
                    bit计数器              
**************************************************************/
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt_bit <= 0;
        end 
        else if(add_cnt_bit)begin 
            if(end_cnt_bit)begin 
                cnt_bit <= 0;
            end
            else begin 
                cnt_bit <= cnt_bit + 1;
            end 
        end
        else begin
            cnt_bit <= cnt_bit;
        end
    end 
    
    assign add_cnt_bit = end_cnt_scl ;
    assign end_cnt_bit = add_cnt_bit && cnt_bit == bit_max - 1;

//bit_max
    always@(*)
        if(state_c == WITER || state_c == READ)
            bit_max = 8; 
        else 
            bit_max = 1; 
/**************************************************************
                   输出信号              
**************************************************************/
assign  dout = rd_data;
assign  done = rack2idle | sack2idle | stop2idle; 

endmodule
2.2 eeprom读写控制模块设计
2.2.1 EEPROM读写控制模块状态转移图

2.2.2 EEPROM读写控制模块代码
module eeprom_rw_ctrl #( parameter wr_byte_num = 3 , 
                             rd_byte_num = 4
)(    
    input                   clk         ,
    input                   rst_n       ,
    output         [3:0]    cmd         ,
    output                  req         ,
    output         [7:0]    wr_data     ,   //写入eeprom的数据
    input          [7:0]    dout        ,   //从eeprom读取的数据
    input                   done        ,   //传输完成标识
    input                   busy        ,
    //key
    input                   rd_en       ,
    //rx
    input                   rx_data_vld ,
    input          [7:0]    rx_data     ,
    //tx
    output         [7:0]    tx_din      ,
    output                  tx_din_vld   
);

parameter   CMD_START   = 4'b0001,
            CMD_WITER   = 4'b0010,
            CMD_READ    = 4'b0100,
            CMD_STOP    = 4'b1000;

//byte计数器
reg     [4:0]   cnt_byte     ;
wire		    add_cnt_byte ;
wire            end_cnt_byte ;
reg     [4:0]   byte_max     ;//byte最大计数复用
//wr_addr
reg 	[8:0]	cnt_wr_addr;    //地址计数器,最高位用于选择black
wire		  	add_cnt_wr_addr;
wire		  	end_cnt_wr_addr;
//rd_addr
reg 	[8:0]	cnt_rd_addr;
wire		  	add_cnt_rd_addr;
wire		  	end_cnt_rd_addr;
//STATE
reg     [3:0]   state_c;
reg     [3:0]   state_n;
wire            idle2witer   ;
wire            idle2read    ;
wire            witer2wait_wr;
wire            wait_wr2done ;
wire            wait_wr2witer;
wire            read2wait_rd ;
wire            wait_rd2done ;
wire            wait_rd2read ;
wire            done2idle    ;
//FIFO
wire     [7:0]  wr_fifo_data ;
wire            fifo_wr_rdreq;
wire            fifo_wr_wrreq;
wire            fifo_wr_empty;
wire            fifo_wr_full ;
wire     [7:0]  wr_usedw     ; 

wire            fifo_rd_rdreq;
wire            fifo_rd_wrreq;
wire            fifo_rd_empty;
wire            fifo_rd_full ;
wire     [7:0]  rd_usedw     ;
wire     [7:0]  tx_data      ; 
reg      [7:0]  tx_data_r    ;
reg             tx_data_vld  ;
//
reg      [7:0]  wr_data_r    ;
reg             tx_req       ;
reg      [3:0]  tx_cmd       ;
/**************************************************************
                     状态机            
**************************************************************/

parameter   IDLE     =   0,
            WITER    =   1,
            WAIT_WR  =   2,
            READ     =   3,
            WAIT_RD  =   4,
            DONE     =   5;
                               
//第一段状态机
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
    state_c <= IDLE;
end
else begin
    state_c <= state_n;
end
end
               
//第二段状态机
always @(*)begin
case(state_c)
    IDLE        :	if(idle2witer)
                       state_n = WITER;
                    else if(idle2read)
                        state_n = READ;
                    else 
                       state_n = state_c;
    WITER       :	if(witer2wait_wr)
                      state_n = WAIT_WR;
                   else 
                      state_n = state_c;                   
    WAIT_WR     :	if(wait_wr2done)
                     state_n = DONE;
                     else if(wait_wr2witer)
                     state_n = WITER;
                     else 
                    state_n = state_c;
    READ        :	if(read2wait_rd)
                       state_n = WAIT_RD;
                    else 
                   state_n = state_c;               
    WAIT_RD     :	if(wait_rd2done)
                     state_n = DONE;
                     else if(wait_rd2read)
                     state_n = READ; 
                    else 
                    state_n = state_c;               
    DONE        :	if(done2idle)
                       state_n = IDLE;
                    else 
                       state_n = state_c;
   default      :   state_n = state_c;
   endcase
end	
                          
//状态跳转条件
assign 	   idle2witer    = state_c == IDLE    && (wr_usedw >= wr_byte_num - 2); 
assign 	   idle2read     = state_c == IDLE    && rd_en	; 
assign 	   witer2wait_wr = state_c == WITER   && 1'b1	; 
assign 	   wait_wr2done  = state_c == WAIT_WR && end_cnt_byte	; 
assign 	   wait_wr2witer = state_c == WAIT_WR && (done & cnt_byte < wr_byte_num - 1)	; 
assign 	   read2wait_rd  = state_c == READ    && 1'b1	; 
assign 	   wait_rd2done  = state_c == WAIT_RD && end_cnt_byte	; 
assign 	   wait_rd2read  = state_c == WAIT_RD && (done & cnt_byte < rd_byte_num - 1)	; 
assign 	   done2idle     = state_c == DONE    && 1'b1	;     

/**************************************************************
                    byte计数器              
**************************************************************/
    always @(posedge clk or negedge rst_n)begin 
        if(!rst_n)begin
            cnt_byte <= 0;
        end 
        else if(add_cnt_byte)begin 
            if(end_cnt_byte)begin 
                cnt_byte <= 0;
            end
            else begin 
                cnt_byte <= cnt_byte + 1;
            end 
        end
        else begin
            cnt_byte <= cnt_byte;
        end
    end 
    
    assign add_cnt_byte = (state_c == WAIT_RD | state_c == WAIT_WR) & done ;
    assign end_cnt_byte = add_cnt_byte && cnt_byte == byte_max - 1;

//byte_max
    always@(*)
        if(state_c == WAIT_WR)
            byte_max = wr_byte_num; 
        else 
            byte_max = rd_byte_num; 

always@(posedge clk or negedge rst_n)
    if(!rst_n)
        SEND(1'b0,4'b0,8'b0);
    else if(state_c == WITER)
        case (cnt_byte)
            0                :   SEND(1'b1,(CMD_START|CMD_WITER),{6'b101000,cnt_wr_addr[8],1'b0});  //START+WITER 写控制字(写设备地址)
            1                :   SEND(1'b1,CMD_WITER,cnt_wr_addr[7:0]);                             //WITER(写字节地址)
            wr_byte_num - 1  :   SEND(1'b1,(CMD_WITER|CMD_STOP),wr_fifo_data );                           //WITER(写数据)
            default          :   SEND(1'b1,CMD_WITER,wr_fifo_data );
        endcase
    else if(state_c == READ)
        case (cnt_byte)
            0                :   SEND(1'b1,(CMD_START|CMD_WITER),{6'b101000,cnt_rd_addr[8],1'b0});  //START+WITER(虚写过程)
            1                :   SEND(1'b1,CMD_WITER,cnt_rd_addr[7:0]);                             //WITER(写字节地址)
            2                :   SEND(1'b1,(CMD_START|CMD_WITER),{6'b101000,cnt_rd_addr[8],1'b1});  //START+读控制字(写读设备地址)
            rd_byte_num - 1  :   SEND(1'b1,(CMD_READ |CMD_STOP),8'b0);                              //READ+STOP(读数据)
            default          :   SEND(1'b1,CMD_READ,8'b0);                                          //READ(如果数据没读完,继续读数据)
        endcase
    else
        SEND(1'b0,cmd,wr_data_r);

//用task发送请求、命令、数据(地址+数据)
    task SEND;   //任务 任务名
        input                   req     ;//任务输入
        input       [3:0]       command ;
        input       [7:0]       data    ;
        begin //顺序执行
            tx_req  = req;     //任务的内部逻辑
            tx_cmd  = command;
            wr_data_r  = data;
        end 
    endtask //任务结束

/**************************************************************
                       读写地址计数器          
**************************************************************/
//wr_addr		
always@(posedge clk or negedge rst_n)	
    if(!rst_n)								
        cnt_wr_addr <= 'd0;						
    else    if(add_cnt_wr_addr) begin				
        if(end_cnt_wr_addr)						
            cnt_wr_addr <= 'd0;  				
        else									
            cnt_wr_addr <= cnt_wr_addr + (wr_byte_num - 2);	//wr_byte_num - 2 写状态有一个设备地址,一个字节地址	
    end											
assign add_cnt_wr_addr = wait_wr2done ;
assign end_cnt_wr_addr = add_cnt_wr_addr && cnt_wr_addr == 256 - 1 ;

//rd_addr		
always@(posedge clk or negedge rst_n)	
    if(!rst_n)								
        cnt_rd_addr <= 'd0;						
    else    if(add_cnt_rd_addr) begin				
        if(end_cnt_rd_addr)						
            cnt_rd_addr <= 'd0;  				
        else									
            cnt_rd_addr <= cnt_rd_addr + (rd_byte_num - 3);	//rd_byte_num - 3 随机读过程,有一个虚写状态(2byte),一个写读地址(1byte)
    end											
assign add_cnt_rd_addr = wait_rd2done ;
assign end_cnt_rd_addr = add_cnt_rd_addr && cnt_rd_addr == 256 - 1 ;

/**************************************************************
                        FIFO模块         
**************************************************************/
//fifo_wr
fifo	fifo_wr (
	.aclr  ( ~rst_n    ),
	.clock ( clk       ),
	.data  ( rx_data   ),
	.rdreq ( fifo_wr_rdreq ),
	.wrreq ( fifo_wr_wrreq ),
	.empty ( fifo_wr_empty ),
	.full  ( fifo_wr_full  ),
	.q     ( wr_fifo_data  ),
	.usedw ( wr_usedw  )
);

assign fifo_wr_rdreq = state_c== WAIT_WR && done && cnt_byte > 1;
assign fifo_wr_wrreq = ~fifo_wr_full & rx_data_vld; 

//fifo_rd
fifo	fifo_rd (
	.aclr  ( ~rst_n    ),
	.clock ( clk       ),
	.data  ( dout      ),
	.rdreq ( fifo_rd_rdreq ),
	.wrreq ( fifo_rd_wrreq ),
	.empty ( fifo_rd_empty ),
	.full  ( fifo_rd_full  ),
	.q     ( tx_data   ),
	.usedw ( rd_usedw  )
);
assign fifo_rd_rdreq = ~fifo_rd_empty && busy;
assign fifo_rd_wrreq = ~fifo_rd_full && state_c == WAIT_RD && cnt_byte > 2 && done;


//fifo数据缓存
always@(posedge clk or negedge rst_n)
    if(!rst_n)  begin
        tx_data_r   <= 8'b0;
        tx_data_vld <= 1'b0;
    end
    else begin
        tx_data_r   <= tx_data;
        tx_data_vld <= fifo_rd_rdreq;
    end

//输出端口
assign  req = tx_req;  
assign  cmd = tx_cmd;
assign  wr_data    = wr_data_r;
assign  tx_din     = tx_data_r;
assign  tx_din_vld = tx_data_vld;

endmodule
2.3 顶层模块
module top (
    input                  clk         ,
    input                  rst_n       ,
    input                  key_in      ,
    input                  rx_din      ,
    output                 tx_dout     ,
    output                 scl         ,
    inout                  sda        
);
//模块连接
wire                    key_flag;
wire                    rx_data_vld;
wire    [7:0]           rx_data    ;
wire    [7:0]           tx_din     ;
wire                    tx_din_vld ;
wire    [3:0]           cmd    ;
wire                    req    ;
wire    [7:0]           wr_data;
wire    [7:0]           dout   ;
wire                    done   ;
key_filter#(
    .CNT_MAX(20'd999)
)key_filter_inst(
    /* input       */     .clk      (clk     )   ,  
    /* input       */     .rst_n    (rst_n   )   ,  
    /* input       */     .key_in   (key_in  )   ,  
    /* output  reg */     .key_flag (key_flag)                                        
);

uart_rx #(
    .CLK_FREQ(50_000_000),
    .BPS     (115200    ),
    .CHECK   ("NONE"    )   
)uart_rx_inst(
    /* input                 */  .clk        (clk        ) ,
    /* input                 */  .rst_n      (rst_n      ) ,
    /* input                 */  .rx_din     (rx_din     ) ,
    /* output                */  .rx_data_vld(rx_data_vld) ,
    /* output   reg    [7:0] */  .rx_data    (rx_data    )  
);

uart_tx #(
    .CLK_FREQ(50_000_000),
    .BPS     (115200    ),
    .CHECK   ("NONE"    ) 
)uart_tx_inst(
    /* input          */  .clk       (clk       )  ,
    /* input          */  .rst_n     (rst_n     )  ,
    /* input    [7:0] */  .tx_din    (tx_din    )  ,
    /* input          */  .tx_din_vld(tx_din_vld)  ,
    /* output         */  .tx_ready  (tx_ready  )  ,
    /* output   reg   */  .tx_dout   (tx_dout   )  
);


eeprom_rw_ctrl #(.wr_byte_num(18) , 
           .rd_byte_num(19)
) eeprom_rw_ctrl_inst(    
    /* input                */    .clk    (clk    )     ,
    /* input                */    .rst_n  (rst_n  )     ,
    /* output   reg   [3:0] */    .cmd    (cmd    )     ,
    /* output               */    .req    (req    )     ,
    /* output   reg   [7:0] */    .wr_data(wr_data)     ,   //写入eeprom的数据
    /* input          [7:0] */    .dout   (dout   )     ,   //从eeprom读取的数据
    /* input                */    .done   (done   )     ,   //传输完成标识
    /* input                */    .busy   (tx_ready)     ,
    /* //key */
    /* input                */    .rd_en  (key_flag  )     ,
    /* //rx */
    /* input                */    .rx_data_vld (rx_data_vld),
    /* input   reg    [7:0] */    .rx_data     (rx_data    ),
    /* //tx */
    /* output         [7:0] */    .tx_din      (tx_din     ),
    /* output               */    .tx_din_vld  (tx_din_vld )
);

i2c_intf i2c_intf_inst(
    /* input         */   .clk    (clk    )     ,
    /* input         */   .rst_n  (rst_n  )     ,
    /* input   [3:0] */   .cmd    (cmd    )     ,
    /* input         */   .req    (req    )     ,
    /* input   [7:0] */   .wr_data(wr_data)     ,
    /* output  [7:0] */   .dout   (dout   )     ,
    /* output        */   .done   (done   )     ,
    /* output  reg   */   .scl    (scl  	)     ,
    /* inout         */   .sda    (sda  	)       
);
endmodule
2.4 其余模块

uart_rx、uart_tx、key_filter模块,略。详细见之前的博客。

3.仿真测试

3.1仿真测试代码
`timescale 1ns/1ps
module  i2c_ctrl_rw_tb();

parameter CLK_CYCLE = 20;
reg	sys_clk,sys_rst_n;
reg  [3:0] cmd;
reg  [7:0] wr_data;
reg req;

wire       m_scl;
wire       m_sda;

pullup(m_sda);//拉高sda,上拉
always #(CLK_CYCLE/2) sys_clk = ~sys_clk;
initial begin
    sys_clk = 1'b1;  
    sys_rst_n = 1'b0;
    #(CLK_CYCLE*2);
    sys_rst_n = 1'b1;
end

i2c_intf i2c_intf(
    /* input         */   .clk    (sys_clk)     ,
    /* input         */   .rst_n  (sys_rst_n)     ,
    /* input   [3:0] */   .cmd    (cmd)     ,
    /* input         */   .req    (req)     ,
    /* input   [7:0] */   .wr_data(wr_data)     ,
    /* output  [7:0] */   .dout   ()     ,
    /* output        */   .done   ()     ,
    /* output  reg   */   .m_scl  (m_scl)     ,
    /* inout         */   .m_sda  (m_sda)      
);

M24LC04B M24LC04B(1'b0, 1'b0, 1'b0, 1'b0, m_sda, m_scl, ~sys_rst_n);

initial begin
        req = 1'b0;
        cmd = 4'b0;
        wr_data = 8'b0;
        #(CLK_CYCLE*100);

        req = 1'b1;
        my_cmd(4'b0011,8'ha0);//start+write 
        my_cmd(4'b0010,8'h02); //write
        my_cmd(4'b0010,8'ha3);
        my_cmd(4'b1000,8'h00); //stop

        my_cmd(4'b0011,8'ha0);//start+write
        my_cmd(4'b0010,8'h02);//write
        my_cmd(4'b0011,8'ha1);//start+write
        #(CLK_CYCLE*500);
        my_cmd(4'b0100,8'ha3);//read
        my_cmd(4'b1000,8'h00);//stop

        req = 1'b0;
        #(CLK_CYCLE*100);
        $stop;
    end

    task my_cmd;        
        input   [3:0]   cmd_in; 
        input   [7:0]   data_in;
        begin
            cmd = cmd_in;
            wr_data = data_in;
            #(CLK_CYCLE*500);   //延时包括应答周期
            #(CLK_CYCLE*4000 +2);
        end
    endtask


endmodule
`timescale 1ns/1ps
module  top_tb();

parameter CLK_CYCLE = 20;
reg	sys_clk,sys_rst_n;
reg key_in,rx_din;
wire    m_scl,m_sda;

pullup(m_sda);
always #(CLK_CYCLE/2) sys_clk = ~sys_clk;
initial begin
    sys_clk = 1'b1;
    sys_rst_n = 1'b0;
    #(CLK_CYCLE*2);
    sys_rst_n = 1'b1;
end

top top_tb(
    /* input   */                .clk    (sys_clk)     ,
    /* input   */                .rst_n  (sys_rst_n)     ,
    /* input   */                .key_in (key_in)     ,
    /* input   */                .rx_din (rx_din)     ,
    /* output  */                .tx_dout()     ,
    /* output  */                .m_scl  (m_scl)     ,
    /* inout   */                .m_sda  (m_sda)      
);

M24LC04B M24LC04B(1'b0, 1'b0, 1'b0, 1'b0, m_sda, m_scl, ~sys_rst_n);
initial begin
rx_din = 1;
sys_clk = 1'b1;
key_in = 1'b1;
sys_rst_n = 1'b0;
#(CLK_CYCLE*2);
sys_rst_n = 1'b1;
#(CLK_CYCLE*20);

    repeat(5)  begin
    my_uart_tx(8'ha0,1'b1); 
    #(CLK_CYCLE*10000);
    key_in = 1'b0;
    #(CLK_CYCLE*40000);
    key_in = 1'b1;
    end

    repeat(5)  begin
    my_uart_tx(8'ha0,1'b1);
    #(CLK_CYCLE*10000);
    key_in = 1'b0;
    #(CLK_CYCLE*40000);
    key_in = 1'b1;
    end
    #(CLK_CYCLE*434);
    $stop;
end

integer i;
task    my_uart_tx;
input   [7:0]   data_in;
input           key;
begin
    rx_din = 0;
    #(CLK_CYCLE*434);
    //数据位
    for(i=0;i<=7;i=i+1)begin
        rx_din = data_in[i];
        #(CLK_CYCLE*434);
    end
    //停止位
    rx_din = 1;
    #(CLK_CYCLE*434);
    #(CLK_CYCLE*10000);
end
endtask

endmodule
3.2 仿真测试图

图3.1 IIC接口模块仿真测试图

图3.2 数据存入的memory

图3.3 数据存入的block地址

仿真测试分析:

如图3.1,写数据时,当仿真输入cmd = 4’b0011时,执行写设备地址+写控制字功能,可以写入设备地址1010000;当仿真输入cmd = 4’b0010时,执行写功能,可以写入字节地址;当仿真输入cmd = 4’b0010时,执行写功能,可以写入数据;当仿真输入cmd = 4’b1000时,执行停止功能,停止写入;

读数据时,当仿真输入cmd = 4’b0011时,执行写设备地址功能,可以写入设备地址1010000;当仿真输入cmd = 4’b0010时,执行写功能,可以写入字节地址;当仿真输入cmd = 4’b0011时,执行写设备地址+读控制字功能,可以写入读的设备地址;当仿真输入cmd = 4’b0100时,执行读数据功能,读取数据并输出;最后,当仿真输入cmd = 4’b1000时,执行停止功能,停止读取

  • 18
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值