【FPGA——Cyclone Ⅳ学习笔记】七.IIC驱动EEPROM(EP4CE6F17C8)

一.原理图

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
此开发板的24LC04芯片与之前所用的AT24C02芯片有所不同,此芯片的A2,A1,A0管脚为无效管脚,不能用于器件地址的选择!

二.IIC驱动及代码解释(i2c_dri.v)

由于黑金的代码是使用网上开源的代码,不易于理解,因此使用正点原子的代码进行解释。
IIC的驱动是使用状态机的方式进行编写。
由于iic驱动比较重要,因此单独拆开讲解。(此驱动只包含单字节读写,没有连续读写)

1.端口定义、I/O说明和变量声明

module i2c_dri
#(
	parameter   SLAVE_ADDR = 7'b1010000   ,	   // slave address(器件地址),放此处方便参数传递
	//下面两个参数用于后续对系统时钟分频从而获得I2C的SCL时钟
	parameter   CLK_FREQ   = 26'd50_000_000,   // i2c_dri模块的驱动时钟频率(CLK_FREQ),即50MHz系统时钟
	parameter   I2C_FREQ   = 18'd250_000       // 需要的I2C的SCL时钟频率(250KHz)
)
(
	//global clock
	input                clk        ,      // i2c_dri模块的驱动时钟(CLK_FREQ),系统时钟50MHz
	input                sys_rst_n  ,      // 复位信号
	
	//i2c interface
	input                i2c_exec   ,      // I2C触发执行信号,为1时代表开始和器件进行读或写
	input                bit_ctrl   ,      // 器件内地址位控制(16b/8b),=1时16位,=0时8位,
										   // 对于EEPROM即为存储地址的位数
	input                i2c_rh_wl  ,      // I2C读写控制信号,=1时读,=0时写,
	input        [15:0]  i2c_addr   ,      // I2C器件内地址,EEPROM即为芯片内部存储地址
	input        [ 7:0]  i2c_data_w ,      // I2C要写的数据
	output  reg  [ 7:0]  i2c_data_r ,      // I2C读出的数据
	output  reg          i2c_done   ,      // I2C一次操作完成(读或写)
	output  reg          scl        ,      // I2C的SCL时钟信号线
	inout                sda        ,      // I2C的SDA信号
	
	//user interface
	output  reg          dri_clk           // 驱动I2C操作的驱动时钟
);

//localparam define,状态机的8种状态
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输出信号,暂存sda的输出
reg            wr_flag     ;                     // 写标志,暂存i2c_rh_wl输入
reg            st_done     ;                     // 当前状态结束标志
reg    [ 6:0]  cnt         ;                     // 计数,后面代码出现时再解释
reg    [ 7:0]  cur_state   ;                     // 状态机当前状态
reg    [ 7:0]  next_state  ;                     // 状态机下一状态
reg    [15:0]  addr_t      ;                     // 暂存器件内地址,即i2c_addr输入
reg    [ 7:0]  data_r      ;                     // 暂存读取的数据,即i2c_data_r输出
reg    [ 7:0]  data_wr_t   ;                     // 暂存写入的数据,即i2c_data_w输入
reg    [ 9:0]  clk_cnt     ;                     // 分频时钟计数

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

2.SDA控制及IIC驱动时钟

代码中出现了三态门的使用,sda设置为高阻时,为输入模式。
详细请看:FPGA中的INOUT接口和高阻态

//SDA控制
assign  sda     = sda_dir ?  sda_out : 1'bz;     // SDA数据输出或高阻(输入)
assign  sda_in  = sda ;                          // SDA数据输入
assign  clk_divide = (CLK_FREQ/I2C_FREQ) >> 2'd3;// 模块驱动时钟的分频系数,
												 // 50_000_000/250_000=200,200/2/4=25
												 // 由于除以8,此处的分频系数输出的实际为1MHz,原因见后面代码
												 	
//生成I2C的SCL的四倍频率的驱动时钟用于驱动i2c的操作
always @(posedge clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) begin
        dri_clk <=  1'b1;
        clk_cnt <= 10'd0;
    end
    else if(clk_cnt == clk_divide - 1'd1) begin
        clk_cnt <= 10'd0;
        dri_clk <= ~dri_clk;	//此为1MHz,后面操作时还会再4分频
    end
    else
        clk_cnt <= clk_cnt + 1'b1;
end

3.状态机

数据手册的说明:
在这里插入图片描述
在这里插入图片描述
程序状态图:
与上面的两个相对应。
在这里插入图片描述

//(三段式状态机)同步时序描述状态转移
always @(posedge dri_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        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

4.时序图描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//时序电路描述状态输出
always @(posedge dri_clk or negedge sys_rst_n) begin		//1MHz的时钟频率触发
    //复位初始化
    if(sys_rst_n == 1'b0) begin
        scl        <= 1'b1;
        sda_out    <= 1'b1;
        sda_dir    <= 1'b1;
        i2c_done   <= 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;	// IIC协议规定,时钟线和数据线均为高时,代表空闲
                sda_out <= 1'b1;
                sda_dir <= 1'b1;	// sda为输出模式,
                i2c_done<= 1'b0;
                cnt     <= 7'b0;
                if(i2c_exec) begin
                    wr_flag   <= i2c_rh_wl ;			//读写信号
                    addr_t    <= i2c_addr  ;			//I2C器件内地址
                    data_wr_t <= i2c_data_w;			//需要写入的数据
                end
            end
            st_sladdr: begin                            // 写器件地址和“写”信号
                case(cnt)
                    7'd1 : sda_out <= 1'b0;             // 开始I2C,参考IIC协议时序图
                    7'd3 : scl <= 1'b0;					
                    7'd4 : sda_out <= SLAVE_ADDR[6];    // 传送器件地址
                    7'd5 : scl <= 1'b1;  
                    7'd7 : scl <= 1'b0;//此处scl的高低电平变化为每4个时钟一个周期,即250KHz,因此跳过6
                    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变为高阻态
                        sda_out <= 1'b1;				 // 拉高sda,等待从机应答
                    end
                    7'd37: scl     <= 1'b1;
                    7'd38: st_done <= 1'b1;				 // 一个状态结束信号
                    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];           // 传送内地址的高8位
                    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: st_done <= 1'b1;
                    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];            // 16位时传输内地址的低8位,8位时传送8位地址
                    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: st_done <= 1'b1;
                    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: st_done <= 1'b1;
                    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: st_done <= 1'b1;
                    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;			   // sda高阻态,等待读数据
                    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输出,将由主机给出是否应答
                        sda_out <= 1'b1;			  // 主机将sda拉高,表示非应答,结束本次读取
                    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

三.其余模块

实验效果:led灯先闪烁几秒,然后保持常亮,说明读写正确。如果一直闪烁说明读写有误。

顶层结构图:
在这里插入图片描述

1.EEPROM_rw.v

此模块先向EEPROM写入数据,然后读出,并进行对比,如果不同则输出错误信号,LED闪烁。

module EEPROM_rw
(
	input 					clk,
	input 					sys_rst_n,
	
	output					i2c_rh_wl,		//读/写标志
	output	reg				i2c_exec,		//I2C触发信号
	output	reg	  [15:0]	i2c_addr,		//器件内地址
	output	reg	  [7:0]		i2c_data_w,		//写入的数据字节
	input		  [7:0]		i2c_data_r,		//读出的数据字节
	input					i2c_done,		//操作结束标记
	
	output	reg				error_flag		//错误信号
);

parameter	WAIT	=	14'd5000;	      //等待时间
parameter	BYTE_N	=	16'd255;		  //共写入256个字节,从0开始

reg            addr_over ;                // 地址结束标志
reg            rom_w_done;                // 一个字节全部写入e2prom的标志
reg   [ 1:0]   flow_cnt  ;                // 状态流控制
reg   [13:0]   wait_cnt  ;                // 等待计数

assign i2c_rh_wl = addr_over & rom_w_done; 	//所有字节都以写入eeprom(地址结束且最后一个字节结束)

always @(posedge clk or negedge sys_rst_n) 
begin
	if(sys_rst_n == 1'b0)
	begin
		i2c_addr <= 16'd0;
		addr_over <= 1'b0;
	end
	else	if(i2c_done == 1'b1)	//I2C的一次操作完成
	begin
		if(i2c_rh_wl == 1'b1)		//如果开始读数据
		begin
			if(i2c_addr < BYTE_N)
				i2c_addr <= i2c_addr + 1'b1;
			else
				i2c_addr <= i2c_addr;
		end
		else						//还没有写完数据
		begin
			if(i2c_addr == BYTE_N)
			begin
				i2c_addr <= 16'd0;
				addr_over <= 1'b1;	//地址结束,
			end
			else
				i2c_addr <= i2c_addr + 1'd1;
		end
	end
	else
		i2c_addr <= i2c_addr;
end

always @(posedge clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) begin
        flow_cnt   <=  2'b0;
        wait_cnt   <= 14'b0;
        i2c_exec   <=  1'b0;
        i2c_data_w <=  8'd0;
        rom_w_done <=  1'b0;
        error_flag <=  1'b1;
    end
    else begin
        i2c_exec <= 1'b0;
//从eeprom的第1页的第1个字节到第8页的第32个字节(共256字节)写入数据0~255
        if(i2c_rh_wl == 1'b0) begin		//写数据
            case(flow_cnt)
                2'd0: begin
                    rom_w_done  <=  1'b0;
                    wait_cnt    <= wait_cnt + 1'b1;
                    if(wait_cnt == 14'd100) begin	//等待100*0.02=2us
                        wait_cnt <= 14'd0;
                        flow_cnt <= flow_cnt + 1'b1;
                    end
                end
                2'd1: begin
                    i2c_exec   <= 1'b1;		//触发I2C
                    i2c_data_w <= i2c_addr[7:0];	//需要写入的数据为当前写入数据的内地址
                    flow_cnt   <= flow_cnt + 1'b1;
                end
                2'd2: begin
                    if(i2c_done == 1'b1)	//I2C操作已经结束
                        flow_cnt   <= flow_cnt + 1'b1;
                end
                2'd3:begin
                    if(wait_cnt == WAIT) begin      //写间隔控制,5000*0.02us=1ms
                        flow_cnt   <=  2'b0;
                        wait_cnt   <= 14'd0;
                        rom_w_done <=  1'b1;		//一个字节完成写入
                    end
                    else
                        wait_cnt <= wait_cnt + 1'b1;
                end
            endcase
        end
//读取从eeprom的第1页的第1个字节开始的共256字节的值并判断值是否正确
        else begin		//读数据
            case(flow_cnt)
                2'd0: begin
                    wait_cnt <= wait_cnt + 1'b1;
                    if(wait_cnt == 14'd100) begin	//2us
                        flow_cnt <= flow_cnt + 1'b1;
                        wait_cnt <= 14'd0;
                    end
                end
                2'd1: begin
                    i2c_exec <= 1'b1;
                    flow_cnt <= flow_cnt + 1'b1;
                end
                2'd2: begin
                    if(i2c_done == 1'b1) begin                 // 判断I2C操作是否完成
                        if(i2c_addr[7:0] == i2c_data_r) begin  // 判断读到的值正确与否(读到的值等于当前地址)
                            error_flag   <= 1'b0;              // 读到的值正确
                            flow_cnt     <= 2'b0;              // 返回状态0
                        end
                        else begin
                            error_flag <= 1'b1;                // 读到的值错误
                        end
                    end
                end
                default: flow_cnt <= 2'b0;
            endcase
        end
    end
end

endmodule

3.led_alarm.v

module led_alarm 
#(
	parameter    L_TIME = 25'd25_000_000  // 控制led闪烁时间(此为500ms)
)
(
	//system clock
	input             clk       ,  // 时钟信号
	input             rst_n     ,  // 复位信号
	
	//led interface
	output   [3:0]    led       ,  // LED 灯
	
	//user interface
	input             error_flag   // 错误标志
);

//reg define
reg               led_t  ;             // 使用的led灯
reg    [24:0]     led_cnt;             // led计数

//led输出
assign  led = {4{led_t}};

//错误标志为1时led闪烁,否则,LED0常亮
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0) begin
        led_cnt <= 25'd0;
        led_t <= 1'b0;
    end
    else begin
        if(error_flag) begin                  // 读到的值错误
            led_cnt <= led_cnt + 25'd1;
            if(led_cnt == L_TIME) begin       // 数据错误时LED灯每隔L_TIME时间闪烁一次
                led_cnt <= 25'd0;
                led_t <= ~led_t;
            end
        end
        else begin                            // 读完且读到的值正确
            led_cnt <= 25'd0;
            led_t <= 1'b1;                    // led灯常亮
        end
    end
end

endmodule

4.I2C_EEPROM_test.v

此芯片的器件地址:
在这里插入图片描述
高四位由器件固定为“1010”,第3、2位无效,第1位选择存储块,第0位为读写控制。

module I2C_EEPROM_test(
    //system clock
    input               sys_clk    ,      // 系统时钟
    input               sys_rst_n  ,      // 系统复位
    //eeprom interface
    output              rom_scl    ,      // eeprom的时钟线scl
    inout               rom_sda    ,      // eeprom的数据线sda
    //user interface
    output   [3:0]      led               // led显示
);

//parameter define
parameter    SLAVE_ADDR = 7'b1010000   ; // 器件地址(SLAVE_ADDR)
parameter    BIT_CTRL   = 1'b0         ; // 字地址位控制参数(16b/8b)
parameter    CLK_FREQ   = 26'd50_000_000; // i2c_dri模块的驱动时钟频率(CLK_FREQ)
parameter    I2C_FREQ   = 18'd250_000   ; // I2C的SCL时钟频率
parameter    L_TIME     = 17'd125_000   ; // led闪烁时间参数

//wire define
wire           clk       ;                // I2C操作时钟
wire           i2c_exec  ;                // i2c触发控制
wire   [15:0]  i2c_addr  ;                // i2c操作地址
wire   [ 7:0]  i2c_data_w;                // i2c写入的数据
wire           i2c_done  ;                // i2c操作结束标志
wire           i2c_rh_wl ;                // i2c读写控制
wire   [ 7:0]  i2c_data_r;                // i2c读出的数据
wire           error_flag;                // 错误标志

//例化e2prom读写模块
EEPROM_rw u_eeprom_rw(
    //global clock
    .clk         (clk       ),    // 时钟信号
    .sys_rst_n       (sys_rst_n ),    // 复位信号
    //i2c interface
    .i2c_exec    (i2c_exec  ),    // I2C触发执行信号
    .i2c_rh_wl   (i2c_rh_wl ),    // I2C读写控制信号
    .i2c_addr    (i2c_addr  ),    // I2C器件内地址
    .i2c_data_w  (i2c_data_w),    // I2C要写的数据
    .i2c_data_r  (i2c_data_r),    // I2C读出的数据
    .i2c_done    (i2c_done  ),    // I2C一次操作完成
    //user interface
    .error_flag  (error_flag)     // 错误标志
);

//例化i2c_dri
i2c_dri #(
    .SLAVE_ADDR  (SLAVE_ADDR),    // slave address从机地址,放此处方便参数传递
    .CLK_FREQ    (CLK_FREQ  ),    // i2c_dri模块的驱动时钟频率(CLK_FREQ)
    .I2C_FREQ    (I2C_FREQ  )     // I2C的SCL时钟频率
) u_i2c_dri(
    //global clock
    .clk         (sys_clk   ),    // i2c_dri模块的驱动时钟(CLK_FREQ)
    .sys_rst_n   (sys_rst_n ),    // 复位信号
    //i2c interface
    .i2c_exec    (i2c_exec  ),    // I2C触发执行信号
    .bit_ctrl    (BIT_CTRL  ),    // 器件地址位控制(16b/8b)
    .i2c_rh_wl   (i2c_rh_wl ),    // I2C读写控制信号
    .i2c_addr    (i2c_addr  ),    // I2C器件内地址
    .i2c_data_w  (i2c_data_w),    // I2C要写的数据
    .i2c_data_r  (i2c_data_r),    // I2C读出的数据
    .i2c_done    (i2c_done  ),    // I 2C一次操作完成
    .scl         (rom_scl   ),    // I2C的SCL时钟信号
    .sda         (rom_sda   ),    // I2C的SDA信号
    //user interface
    .dri_clk     (clk       )     // I2C操作时钟
);

//例化led_alarm模块
led_alarm #(.L_TIME(L_TIME  )     // 控制led闪烁时间
) u_led_alarm(
    //system clock
    .clk         (clk       ),    // 时钟信号
    .rst_n       (sys_rst_n ),    // 复位信号
    //led interface
    .led         (led       ),    // LED 灯
    //user interface
    .error_flag  (error_flag)     // 错误标志
);
endmodule
  • 5
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

默默无闻小菜鸡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值