IIC驱动学习(AT24C64)

本文学习的主要是使用FPGA作为主机实现I2C协议,用来和EEPROM芯片AT24C64通信。

简介

        I2C 即 Inter-Integrated Circuit(集成电路总线),是由 Philips 半导体公司(现在的 NXP 半导体公司)在 八十年代初设计出来的一种简单、双向、二线制总线标准。多用于主机和从机在数据量不大且传输距离短 的场合下的主从通信。主机启动总线,并产生时钟用于传送数据,此时任何接收数据的器件均被认为是从机。
        AT24C64 存储容量为 64Kbit,内部分成 256 页,每页 32 字节,共有 8192 个字节,且其读写操作都是以字节为基本单位。可 以把 AT24C64 看作一本书,那么这本书有 256 页,每页有 32 行,每行有 8 个字,总共有 256*32*8=65536 个字,对应着 AT24C64 的 64*1024=65536 个 bit。
AT24C64 具有高可靠性,可对所存数据保存 100 年,并可多次擦写,擦写次数达一百万次。

I2C时序

        数据传输过程中SDA必须在SCL高电平的时候保持高电平,在SCL上升沿前拉高SDA,SCL下降沿后拉低SDA,时序图如下所示:
        除了数据信号,I2C还有开始信号和结束信号,SDA和SCL空闲状态处于高电平,开始信号需要SDA在SCL高电平期间拉低,结束信号需要SDA在SCL高电平期间拉高,时序图如下所示:
        还有一个比较重要的信号是ACK响应信号,他在写入8bit信号之后拉低一个SCL时钟,也就是在第9bit的时候从机需要拉低SDA,时序图如下所示:

AT24C64读写时序

字节写的逻辑

        开始位;
        写入设备地址和读写控制位;
        从机响应;
        写入数据地址高字节
        从机响应;
        写入数据地址低字节;
        从机响应;
        写入一字节数据;
        从机响应;
        停止位;
时序图如下所示:

页写的逻辑

        开始位;
        写入设备地址和读写控制位;
        从机响应;
        写入数据地址高字节
        从机响应;
        写入数据地址低字节;
        从机响应;
        写入第1个字节数据;
        从机响应;
        写入第2个字节数据;
        从机响应;
        写入第n个字节数据;
        从机响应;
        停止位;
时序图如下所示:

随机读逻辑

        开始位;
        写入设备地址和读写控制位;
        从机响应;
        写入数据地址高字节;
        从机响应;
        写入数据地址低字节;
        开始位;
        写入设备地址和读写控制位;
        从机响应;
        读取1个字节数据;
        从机响应;
        停止位;
时序图如下所示:

顺序读逻辑

        开始位;
        写入设备地址和读写控制位;
        从机响应;
        写入数据地址高字节;
        从机响应;
        写入数据地址低字节;
        开始位;
        写入设备地址和读写控制位;
        从机响应;
        读取第1个字节数据;
        从机响应;
        读取第2个字节数据;
        从机响应;
        读取第n个字节数据;
        从机响应;
        停止位;
时序图如下所示:

源码

驱动文件i2c_driver.v,对EEPROM进行读写,只使用了字节写和随机读。

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2025/04/02 15:02:54
// Design Name: 
// Module Name: i2c_driver
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module i2c_driver(
    input                   i_clk               ,
    input                   i_rst               ,
    input                   i_start             ,
    input                   i_dir               ,//0:write, 1:read
    input       [6:0]       i_device_addr       ,
    input       [15:0]      i_data_addr         ,
    input       [7:0]       i_data              ,
    output                  o_req               ,
    output                  o_data_valid        ,
    output      [7:0]       o_data              ,
    output                  o_i2c_ack           ,
    output                  o_i2c_done          ,

    // input                   sda_in              ,
    // output  reg             sda_out             ,
    // output reg [3:0]        bit_cnt,

    output                  i2c_scl             ,
    inout                   i2c_sda
);

parameter DIV_CNT = 4;
reg         i2c_dri_clk =1'b0;
reg [3:0]   i2c_dri_cnt =4'd0;
reg         i2c_dri_rst ;

always@(posedge i_clk)begin
    i2c_dri_rst <= i_rst;
    if(i2c_dri_cnt>=(DIV_CNT-1))begin
        i2c_dri_clk <= ~i2c_dri_clk;
        i2c_dri_cnt <= 4'd0;
    end
    else begin
        i2c_dri_cnt <= i2c_dri_cnt + 1'b1;
    end
end

reg     scl     ;
reg     sda_dir ;
wire    sda_in  ;
reg     sda_out ;

reg [7:0]rec_data;

assign  sda_in = (sda_dir==1'b1)?i2c_sda:1'b1;
assign  i2c_sda = (sda_dir==1'b0)?sda_out:1'bz;
assign  i2c_scl = scl;

reg [3:0]state;

reg [7:0]device_addr;


localparam IDLE                 = 4'd0;
localparam START                = 4'd1;
localparam WRITE_DEVICE_ADDR    = 4'd2;
localparam WRITE_DATA_HIGH_ADDR = 4'd3;
localparam WRITE_DATA_LOW_ADDR  = 4'd4;
localparam WRITE_DATA           = 4'd5;
localparam RESTART              = 4'd6;
localparam READ_DEVICE_ADDR     = 4'd7;
localparam READ_DATA            = 4'd8;
localparam STOP                 = 4'd14;
localparam DONE                 = 4'd15;

reg [3:0]clk_cnt;
reg [3:0]bit_cnt;

reg start_d0,start_d1;
wire start_pdge;
always@(posedge i2c_dri_clk)begin
    start_d0 <= i_start;
    start_d1 <= start_d0;
end
assign start_pdge = (!start_d1)&start_d0;

reg ack_sig;




always@(*)begin
    case(state)
        IDLE                :device_addr = {i_device_addr, i_dir};
        START               :device_addr = {i_device_addr, 1'b0};
        WRITE_DEVICE_ADDR   :device_addr = {i_device_addr, 1'b0};
        READ_DEVICE_ADDR    :device_addr = {i_device_addr, 1'b1};
        DONE                :device_addr = {i_device_addr, i_dir};
        default             :device_addr = {i_device_addr, i_dir};
    endcase
end

always @(posedge i2c_dri_clk) begin
    if(i2c_dri_rst)begin
        state <= 4'd0;
        scl <= 1'b1;
        sda_out <= 1'b1;
        sda_dir <= 1'b0;
        ack_sig <= 1'b0;
    end
    else begin
        case(state)
            IDLE                :begin
                scl <= 1'b1;
                sda_out <= 1'b1;
                sda_dir <= 1'b0;
                ack_sig <= 1'b0;
                if(start_pdge)begin
                    state <= START;
                end
                else begin
                    state <= IDLE;
                end
            end
            START               :begin
                sda_out <= 1'b0;
                sda_dir <= 1'b0;
                if(sda_out == 1'b0)begin
                    scl <= 1'b0;
                    state <= WRITE_DEVICE_ADDR;
                end
                else begin
                    state <= START;
                end
            end
            WRITE_DEVICE_ADDR   :begin
                if(clk_cnt == 4'd1)
                    scl <= 1'b1;
                else if(clk_cnt == 4'd3)
                    scl <= 1'b0;
                
                if((clk_cnt == 4'd0)&&(bit_cnt < 4'd8))
                    sda_out <= device_addr[7-bit_cnt];
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd8))begin
                    sda_out <= 1'b0;
                end
                
                if((bit_cnt == 4'd8)&&(sda_in == 1'b0))begin
                    ack_sig <= 1'b1;
                end
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd0))begin
                    ack_sig <= 1'b0;
                end

                if((bit_cnt == 4'd8)&&(clk_cnt == 4'd0))begin
                    sda_dir <= 1'b1;
                end
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd0))begin
                    sda_dir <= 1'b0;
                end

                if((clk_cnt == 4'd3)&&(bit_cnt == 4'd8))begin
                    if(ack_sig == 1'b1)begin
                        state <= WRITE_DATA_HIGH_ADDR;
                    end
                    else begin
                        state <= IDLE;
                    end
                end
                else begin
                    state <= WRITE_DEVICE_ADDR;
                end

            end
            WRITE_DATA_HIGH_ADDR:begin
                if(clk_cnt == 4'd1)
                    scl <= 1'b1;
                else if(clk_cnt == 4'd3)
                    scl <= 1'b0;
                
                if((clk_cnt == 4'd0)&&(bit_cnt < 4'd8))
                    sda_out <= i_data_addr[15-bit_cnt];
                 else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd8))begin
                    sda_out <= 1'b0;
                end
                
                if((bit_cnt == 4'd8)&&(sda_in == 1'b0))begin
                    ack_sig <= 1'b1;
                end
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd0))begin
                    ack_sig <= 1'b0;
                end

                if((bit_cnt == 4'd8)&&(clk_cnt == 4'd0))begin
                    sda_dir <= 1'b1;
                end
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd0))begin
                    sda_dir <= 1'b0;
                end

                if((clk_cnt == 4'd3)&&(bit_cnt == 4'd8))begin
                    if(ack_sig == 1'b1)begin
                        state <= WRITE_DATA_LOW_ADDR;
                    end
                    else begin
                        state <= IDLE;
                    end
                end
                else begin
                    state <= WRITE_DATA_HIGH_ADDR;
                end

            end
            WRITE_DATA_LOW_ADDR :begin
                if(clk_cnt == 4'd1)
                    scl <= 1'b1;
                else if(clk_cnt == 4'd3)
                    scl <= 1'b0;
                
                if((clk_cnt == 4'd0)&&(bit_cnt < 4'd8))
                    sda_out <= i_data_addr[7-bit_cnt];
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd8))begin
                    sda_out <= 1'b0;
                end
                
                if((bit_cnt == 4'd8)&&(sda_in == 1'b0))begin
                    ack_sig <= 1'b1;
                end
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd0))begin
                    ack_sig <= 1'b0;
                end

                if((bit_cnt == 4'd8)&&(clk_cnt == 4'd0))begin
                    sda_dir <= 1'b1;
                end
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd0))begin
                    sda_dir <= 1'b0;
                end

                if((clk_cnt == 4'd3)&&(bit_cnt == 4'd8))begin
                    if(ack_sig == 1'b1)begin
                        state <= (i_dir==1'b1)?RESTART:WRITE_DATA;
                    end
                    else begin
                        state <= IDLE;
                    end
                end
                else begin
                    state <= WRITE_DATA_LOW_ADDR;
                end
            end
            WRITE_DATA          :begin
                if(clk_cnt == 4'd1)
                    scl <= 1'b1;
                else if(clk_cnt == 4'd3)
                    scl <= 1'b0;
                
                if((clk_cnt == 4'd0)&&(bit_cnt < 4'd8))
                    sda_out <= i_data[7-bit_cnt];
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd8))begin
                    sda_out <= 1'b0;
                end
                
                if((bit_cnt == 4'd8)&&(sda_in == 1'b0))begin
                    ack_sig <= 1'b1;
                end
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd0))begin
                    ack_sig <= 1'b0;
                end

                if((bit_cnt == 4'd8)&&(clk_cnt == 4'd0))begin
                    sda_dir <= 1'b1;
                end
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd0))begin
                    sda_dir <= 1'b0;
                end

                if((clk_cnt == 4'd3)&&(bit_cnt == 4'd8))begin
                    if(ack_sig == 1'b1)begin
                        state <= STOP;
                    end
                    else begin
                        state <= IDLE;
                    end
                end
                else begin
                    state <= WRITE_DATA;
                end
            end
            RESTART               :begin
                sda_dir <= 1'b0;
                ack_sig <= 1'b0;

                if(clk_cnt == 4'd1)
                    scl <= 1'b1;
                else if(clk_cnt == 4'd3)
                    scl <= 1'b0;
                
                if(clk_cnt == 4'd0)
                    sda_out <= 1'b1;
                else if(clk_cnt == 4'd2)
                    sda_out <= 1'b0;

                if(clk_cnt == 4'd3)begin
                    state <= READ_DEVICE_ADDR;
                end
                else begin
                    state <= RESTART;
                end
            end
            READ_DEVICE_ADDR      :begin
                if(clk_cnt == 4'd1)
                    scl <= 1'b1;
                else if(clk_cnt == 4'd3)
                    scl <= 1'b0;
                
                if((clk_cnt == 4'd0)&&(bit_cnt < 4'd8))
                    sda_out <= device_addr[7-bit_cnt];
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd8))begin
                    sda_out <= 1'b0;
                end
                
                if((bit_cnt == 4'd8)&&(sda_in == 1'b0))begin
                    ack_sig <= 1'b1;
                end
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd0))begin
                    ack_sig <= 1'b0;
                end

                if((bit_cnt == 4'd8)&&(clk_cnt == 4'd0))begin
                    sda_dir <= 1'b1;
                end
                else if((clk_cnt == 4'd0)&&(bit_cnt == 4'd0))begin
                    sda_dir <= 1'b0;
                end

                if((clk_cnt == 4'd3)&&(bit_cnt == 4'd8))begin
                    if(ack_sig == 1'b1)begin
                        state <= READ_DATA;
                        sda_dir <= 1'b1;
                    end
                    else begin
                        state <= IDLE;
                    end
                end
                else begin
                    state <= READ_DEVICE_ADDR;
                end
            end
            READ_DATA           :begin
                if(clk_cnt == 4'd1)
                    scl <= 1'b1;
                else if(clk_cnt == 4'd3)
                    scl <= 1'b0;

                if((clk_cnt == 4'd3)&&(bit_cnt == 4'd8))begin
                    sda_dir <= 1'b0;
                end

                if((clk_cnt == 4'd3)&&(bit_cnt <= 4'd7))begin
                    rec_data[7-bit_cnt] <= sda_in;
                end

                if((clk_cnt == 4'd3)&&(bit_cnt == 4'd8))begin
                    if(ack_sig == 1'b1)begin
                        state <= STOP;
                    end
                    else begin
                        state <= IDLE;
                    end
                end
                else begin
                    state <= READ_DATA;
                end
            end
            STOP                :begin
                sda_dir <= 1'b0;
                ack_sig <= 1'b0;
                // if(clk_cnt == 4'd0)begin
                    
                // end

                if(clk_cnt == 4'd1)begin
                    scl <= 1'b1;
                end

                if(clk_cnt == 4'd2)begin
                    sda_out <= 1'b1;
                end

                if(clk_cnt == 4'd3)begin
                    state <= DONE;
                end
                else begin
                    state <= STOP;
                end
            end
            DONE                :begin
                scl <= 1'b1;
                sda_out <= 1'b1;
                sda_dir <= 1'b0;
                ack_sig <= 1'b0;
                state <= IDLE;
            end
        endcase
    end
end

always@(posedge i2c_dri_clk)begin
    if(i2c_dri_rst)begin
        clk_cnt <= 4'd0;
        bit_cnt <= 4'd0;
    end
    else begin
        case(state)
            IDLE:begin
                clk_cnt <= 4'd0;
                bit_cnt <= 4'd0;
            end
            START:begin
                clk_cnt <= 4'd0;
                bit_cnt <= 4'd0;
            end
            WRITE_DEVICE_ADDR:begin
                if(clk_cnt>= 4'd3)begin
                    clk_cnt <= 4'd0;
                    if(bit_cnt>= 4'd8)
                        bit_cnt <= 4'd0;
                    else 
                        bit_cnt <= bit_cnt + 1'b1;
                end
                else begin
                    clk_cnt <= clk_cnt + 1'b1;
                end
            end
            WRITE_DATA_HIGH_ADDR, WRITE_DATA_LOW_ADDR, WRITE_DATA, READ_DEVICE_ADDR, READ_DATA:begin
                if(clk_cnt>= 4'd3)begin
                    clk_cnt <= 4'd0;
                    if(bit_cnt>= 4'd8)
                        bit_cnt <= 4'd0;
                    else 
                        bit_cnt <= bit_cnt + 1'b1;
                end
                else begin
                    clk_cnt <= clk_cnt + 1'b1;
                end
            end
            RESTART             :begin
                if(clk_cnt>= 4'd3)begin
                    clk_cnt <= 4'd0;
                end
                else begin
                    clk_cnt <= clk_cnt + 1'b1;
                end
            end
            STOP                :begin
                if(clk_cnt>= 4'd3)begin
                    clk_cnt <= 4'd0;
                end
                else begin
                    clk_cnt <= clk_cnt + 1'b1;
                end
            end
            DONE                :begin
                clk_cnt <= 4'd0;
                bit_cnt <= 4'd0;
            end
        endcase
    end
end


ila_0 u_ila_0 (
	.clk(i_clk), // input wire clk


	.probe0({
            state,
            sda_in ,
            scl,
            bit_cnt,
            clk_cnt,
            rec_data
    }) // input wire [127:0] probe0
);

endmodule

仿真文件i2c_dri_tb.v,对驱动进行仿真。

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2025/04/07 17:24:44
// Design Name: 
// Module Name: i2c_dri_tb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module i2c_dri_tb(

    );

reg                   clk               ;
reg                   rst               ;
reg                   start             ;
reg                   dir               ;//0:write, 1:read
reg       [6:0]       device_addr       ;
reg       [15:0]      data_addr         ;
reg       [7:0]       data              ;
wire                  o_req               ;
wire                  o_data_valid        ;
wire      [7:0]       o_data              ;
wire                  o_i2c_ack           ;
wire                  o_i2c_done          ;
wire                  i2c_scl             ;
wire                  i2c_sda             ;
wire [3:0] bit_cnt;

wire sda_in;
wire sda_out;

initial clk = 0;
always#5 clk = ~clk;

initial begin
    rst = 1;
    start = 0;
    #1000;
    rst = 0;
    #1000;
    start = 1;
    #100;
    start = 0;
end

assign sda_in = (bit_cnt==4'd8)?1'b0:1'b1;

i2c_driver u_i2c_driver(
    .i_clk               (clk),
    .i_rst               (rst),
    .i_start             (start),
    .i_dir               (1),//0:write, 1:read
    .i_device_addr       (7'h13),
    .i_data_addr         (16'h0506),
    .i_data              (8'hC6),
    .o_req               (o_req),
    .o_data_valid        (o_data_valid),
    .o_data              (o_data),
    .o_i2c_ack           (o_i2c_ack),
    .o_i2c_done          (o_i2c_done),

    .sda_in              (sda_in),
    .sda_out             (sda_out),
    .bit_cnt             (bit_cnt),

    .i2c_scl             (),
    .i2c_sda             ()
);

endmodule

顶层文件I2C_TOP.v,用于下板测试,手动控制读写,需要添加xdc文件。

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2025/04/08 21:40:50
// Design Name: 
// Module Name: I2C_TOP
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module I2C_TOP(
        input   sys_clk,
        input   sys_rst_n,
        output  iic_scl,
        inout   iic_sda
);

wire clk;
wire start;
wire rw_ctrl;

wire [15:0]data_addr;
wire [7:0]data;

clk_wiz u_clk_wiz(
    // Clock out ports
    .clk_out1   (clk        ), // output clk_out1
    // Status and control signals
    .resetn     (sys_rst_n  ), // input resetn
    .locked     (locked     ), // output locked
   // Clock in ports
    .clk_in1    (sys_clk    )  // input clk_in1
);     

vio_0 u_vio_0 (
  .clk          (sys_clk        ),                // input wire clk
  .probe_out0   (start      ),  // output wire [0 : 0] probe_out0
  .probe_out1   (rw_ctrl),
  .probe_out2   (data_addr),
  .probe_out3   (data)
);

ila_0 u1_ila_0 (
	.clk(sys_clk), // input wire clk


	.probe0({
    sys_rst_n,
    locked,
    clk
    }) // input wire [127:0] probe0
);





i2c_driver u_i2c_driver(
    .i_clk               (clk),
    .i_rst               (!locked),
    .i_start             (start),
    .i_dir               (rw_ctrl),//0:write, 1:read
    .i_device_addr       (7'b1010000),
    .i_data_addr         (data_addr),
    .i_data              (data),
    .o_req               (o_req),
    .o_data_valid        (o_data_valid),
    .o_data              (o_data),
    .o_i2c_ack           (o_i2c_ack),
    .o_i2c_done          (o_i2c_done),

    // .sda_in              (),
    // .sda_out             (),
    // .bit_cnt             (bit_cnt),

    .i2c_scl             (iic_scl),
    .i2c_sda             (iic_sda)
);


endmodule

下板测试

使用vio写入03之后读该地址的数据,读取成功。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值