FPGA-based I2C communication (two)

table of Contents

Three, FPGA implementation of I2C protocol

1. I2C interface design

2. Simulation verification


 The project download link for the overall function realization of the EEPROM reading and writing system (explained in the next blog, including the content of this article) of this topic is as follows:
 https://download.csdn.net/download/qq_33231534/12503289


 

Three, FPGA implementation of I2C protocol

The previous blog gave a general explanation of the I2C bus protocol, as well as a detailed explanation of the read and write timing of the I2C bus device EEPROM (AT24C64). The following is how to read and write the EEPROM device on the FPGA, as well as the interface design and debugging system Give a specific description. This experimental platform uses Xiaomeige’s AC620 development board, and the FPGA chip is cyclone IV EP4CE10F17C8N.

1. I2C (EEPROM) interface design

 

Figure 1: Byte write timing diagram
Figure 2: Page write timing diagram
Figure 3: Random read timing diagram
Figure 4: Continuous reading shix

 According to the four read and write timing diagrams above, write the I2C interface timing and I2C protocol interface design. Here, the state machine is used to design the entire design into 9 states, namely IDLE, WR_START, WR_DEVICE, WR_ADDR, WR_DATA, RD_START, RD_DEVICE, RD_DATA, STOP. The state transition diagram is as follows:

 The meaning of each state and the name and function of the module interface signal are as follows:

Write the interface signals in a table here to make it clearer. as follows:

Table: I2C interface module control signal description
Signal name I / O Digits Function description
clk I 1 System clock 50MHz
rst_n I 1 System reset
rd_data_num   I 6 Number of read data, maximum 32
wr_data_num   I 6 Number of data to be written, maximum 32
word_addr I 16 Data word address, AT24C64 is a 16-bit address
device_addr   I 3 The variable address of the device is determined by the hardware connection. The experimental platform is 3'b001
wr_en   I 1 Write data enable
wr_data   I 8 Write data
rd_en I 1 Read data enable
wr_data_vaild THE 1 Write data valid bit, read multiple data is to generate a valid bit when each data is read
rd_data THE 8 Read data
rd_data_vaild THE 1 Read data valid bit, write multiple data is to generate a valid bit when each data is written
done THE 1 Read and write operation end flag
scl THE 1 Serial clock line
sda I 1 Serial data line

Directly on the code:

//-------------------------------------------------------------------
//https://blog.csdn.net/qq_33231534 PHF的CSDN   
//File name:           I2C_ctrl.v
//Last modified Date:  2020/6/6
//Last Version:        
//Descriptions:        EEPROM(AT24C64)接口设计
//-------------------------------------------------------------------

module I2C_ctrl(
    input                   clk             ,//系统时钟50MHz
    input                   rst_n           ,//系统复位
    input   [ 5: 0]         rd_data_num     ,//读数据个数,最大32
    input   [ 5: 0]         wr_data_num     ,//写数据个数,最大32
    input   [15: 0]         word_addr       ,//数据字地址,AT24C64是16位地址
    input   [ 2: 0]         device_addr     ,//设备可变地址,由硬件连接决定,本实验平台为3‘b001
    input                   wr_en           ,//写数据使能
    input   [ 7: 0]         wr_data         ,//写数据
    input                   rd_en           ,//读数据使能

    output reg              wr_data_vaild   ,//写数据有效位,读多个数据就是读完每个数据时产生一个有效位
    output reg [7:0]        rd_data         ,//读数据
    output reg              rd_data_vaild   ,//读数据有效位,写多个数据就是写完每个数据时产生一个有效位
    output reg              done            ,//读写操作结束标志
    output reg              scl             ,//串行时钟线

    inout                   sda              //串行数据线
);

parameter SYS_CLK   = 50_000_000  ;//系统时钟频率
parameter SCLK      = 100_000     ;//串行时钟线时钟频率

localparam SCLK_CNT = SYS_CLK/SCLK;//产生时钟线的时钟计数次数

//状态机状态定义
parameter IDLE      = 4'd0      ;//空闲状态
parameter WR_START  = 4'd1      ;//写数据启动状态
parameter WR_DEVICE = 4'd2      ;//发送设备地址状态
parameter WR_ADDR   = 4'd3      ;//发送数据字地址状态
parameter WR_DATA   = 4'd4      ;//发送数据状态
parameter RD_START  = 4'd5      ;//读数据启动
parameter RD_DEVICE = 4'd6      ;//读操作设备地址状态
parameter RD_DATA   = 4'd7      ;//读数据状态
parameter STOP      = 4'd8      ;//停止状态

reg   [  3: 0]         state_c  ;//状态机改变后的状态,时序逻辑
reg   [  3: 0]         state_n  ;//状态机当前状态,组合逻辑

reg                    add_flag      ;//计数条件标志
reg   [  8: 0]         sclk_cnt      ;//sclk时钟信号计数
reg                    scl_high      ;//scl信号高电平中间信号,1个时钟周期
reg                    scl_low       ;//scl信号低电平中间信号,1个时钟周期
reg                    wr_flag       ;//写标志
reg                    rd_flag       ;//读标志
reg                    ack           ;//响应信号
reg                    waddr_cnt     ;//数据字地址字节计数,共2字节
reg   [  5: 0]         wdata_cnt     ;//写数据字节计数,最大32个
reg   [  5: 0]         rdata_cnt     ;//读数据字节计数,最大32个
reg   [  4: 0]         bit_cnt       ;//对传输的8位数据中的scl_highhe scl_low信号计数
reg                    sda_en        ;//三态信号sda使能
reg                    sda_reg       ;//三态信号sda寄存数据
reg   [  7: 0]         device_addr_a ;//传输的8位设备地址
reg   [  2: 0]         sda_reg_cnt   ;//传输的8位数据传递数
wire  [  7: 0]         word_addr_high;//数据字地址高8位
wire  [  7: 0]         word_addr_low ;//数据字地址低8位

assign word_addr_high = word_addr[15:8];
assign word_addr_low  = word_addr[ 7:0];

assign sda = sda_en ? sda_reg : 1'bz;

//sclk计数器计数标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        add_flag <= 0;
    end
    else if(wr_en || rd_en) begin
        add_flag <= 1;
    end
    else if(done)begin
        add_flag <= 0;
    end
end

//scl信号计数器
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        sclk_cnt <= 0;
    end
    else if(add_flag) begin
        if((add_flag && sclk_cnt==SCLK_CNT-1) || done)
            sclk_cnt <= 0;
        else
            sclk_cnt <= sclk_cnt + 1'b1;
    end
end

//scl信号生成
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        scl <= 1;
    end
    else if(sclk_cnt==SCLK_CNT/2-1) begin
        scl <= 0;
    end
    else if(sclk_cnt==1'b0)begin
        scl<= 1;
    end
    else begin
        scl <= scl;
    end
end

//scl_high信号:scl信号高电平正中间脉冲
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        scl_high <= 0;
    end
    else if(sclk_cnt==SCLK_CNT*1/4-1) begin
        scl_high <= 1;
    end
    else begin
        scl_high <= 0;
    end
end

//scl_low信号:scl信号低电平正中间脉冲
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        scl_low <= 0;
    end
    else if(sclk_cnt==SCLK_CNT*3/4-1) begin
        scl_low <= 1;
    end
    else begin
        scl_low <= 0;
    end
end

//写信号标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wr_flag <= 0;
    end
    else if(wr_en) begin
        wr_flag <= 1;
    end
    else if(done) begin
        wr_flag <= 0;
    end
end

//读信号标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rd_flag <= 0;
    end
    else if(rd_en) begin
        rd_flag <= 1;
    end
    else if(done) begin
        rd_flag <= 0;
    end
end

//状态机第一段
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        state_c <= IDLE;
    end
    else begin
        state_c <= state_n;
    end
end

//状态机第二段
always  @(*)begin
    case(state_c)
        IDLE      :begin
                       if(wr_en || rd_en)
                           state_n = WR_START;
                       else
                           state_n = state_c;
                   end
        WR_START  :begin
                       if(scl==0 && scl_low)
                           state_n = WR_DEVICE;
                       else
                           state_n = state_c;
                   end
        WR_DEVICE :begin
                       if(ack==1 && scl_low)
                           state_n = WR_ADDR;
                       else if(ack==0 && scl_low && bit_cnt==17)
                           state_n = IDLE;
                       else
                           state_n = state_c;
                    end
        WR_ADDR   :begin
                       if(wr_flag && ack==1 && waddr_cnt==1 && scl_low)
                           state_n = WR_DATA;
                       else if(rd_flag && ack==1 && waddr_cnt==1 &&scl_low)
                           state_n = RD_START;
                       else if(ack==0 && scl_low && bit_cnt==17)
                           state_n = IDLE;
                       else
                           state_n = state_c;
                   end
        WR_DATA   :begin
                       if(wdata_cnt==wr_data_num-1 && ack==1 && wr_flag==1 && scl_low)
                           state_n = STOP;
                       else if(ack==0 && scl_low && bit_cnt==17)
                           state_n = IDLE;
                       else
                           state_n = state_c;
                   end
        RD_START  :begin
                       if(scl==0 && scl_low)
                           state_n = RD_DEVICE;
                       else
                           state_n = state_c;
                   end
        RD_DEVICE :begin
                       if(ack==1 && scl_low)
                           state_n = RD_DATA;
                       else if(ack==0 && scl_low && bit_cnt==17)
                           state_n = IDLE;
                       else
                           state_n = state_c;
                   end

        RD_DATA   :begin
                       if(rdata_cnt==rd_data_num-1 && rd_flag==1 && bit_cnt==17 && scl_low)
                           state_n = STOP;
                       else 
                           state_n = state_c;
                   end
        STOP      :begin
                       if(scl_high)
                           state_n = IDLE;
                       else
                           state_n = state_c;
                    end
    endcase
end

//在传输设备地址、字地址和数据时要传输8位数据,这里对其计数,计数scl_low和scl_high
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        bit_cnt <= 0;
    end
    else if(state_c==WR_DEVICE || state_c==WR_ADDR || state_c==WR_DATA ||state_c==RD_DEVICE || state_c==RD_DATA) begin
        if(scl_high || scl_low)begin
            if(bit_cnt==17 && scl_low)
                bit_cnt <= 0;
            else
                bit_cnt <= bit_cnt + 1'b1;
        end
    end
    else begin
        bit_cnt <= bit_cnt;
    end
end

//ack响应信号
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        ack <= 0;
    end
    else if(scl_high && bit_cnt==16 && sda==0) begin
        ack <= 1;
    end
    else if(bit_cnt==17 && scl_low)begin
        ack <= 0;
    end
    else begin
        ack <= ack;
    end
end

//2个字地址计数器
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        waddr_cnt <= 0;
    end
    else if(state_c==WR_ADDR && bit_cnt==17 && scl_low) begin
        if(waddr_cnt==1)
            waddr_cnt <= 0;
        else
            waddr_cnt <= waddr_cnt + 1'b1;
    end
    else begin
        waddr_cnt <= waddr_cnt;
    end
end

//写数据个数计数
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wdata_cnt <= 0;
    end
    else if(state_c==WR_DATA && bit_cnt==17 && scl_low) begin
        if(wdata_cnt==wr_data_num-1)
            wdata_cnt <= 0;
        else
            wdata_cnt <= wdata_cnt + 1'b1;
    end
    else begin
        wdata_cnt <= wdata_cnt;
    end
end

//读数据个数计数
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rdata_cnt <= 0;
    end
    else if(state_c==RD_DATA && bit_cnt==17 && scl_low) begin
        if(rdata_cnt==rd_data_num-1)
            rdata_cnt <= 0;
        else
            rdata_cnt <= rdata_cnt + 1'b1;
    end
    else begin
        rdata_cnt <= rdata_cnt;
    end
end

//sda_en信号输出
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        sda_en <= 0;
    end
    else begin
        case(state_c)
            IDLE:sda_en <= 0;
            WR_START,RD_START,STOP:sda_en <= 1;
            WR_DEVICE,WR_ADDR,WR_DATA,RD_DEVICE:
                begin
                    if(bit_cnt<16)
                        sda_en <= 1;
                    else
                        sda_en <= 0;
                end
            RD_DATA:
                //sda_en <= 0;
                begin
                    if(bit_cnt<16)
                        sda_en <= 0;
                    else
                        sda_en <= 1;
                end
            default:sda_en <= 0;
        endcase
    end
end

//wr_data_vaild信号:写数据有效信号标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        wr_data_vaild <= 0;
    end
    else if(state_c==WR_DATA && ack==1 && scl_low && bit_cnt==17) begin
        wr_data_vaild <= 1;
    end
    else begin
        wr_data_vaild <= 0;
    end
end

//rd_data信号:读出8位数据
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rd_data <= 0;
    end
    else if(state_c==RD_DATA && bit_cnt<15 && scl_high) begin
        rd_data <= {rd_data[6:0],sda};
    end
    else begin
        rd_data <= rd_data;
    end
end

//rd_data_vaild信号:读出8位数据有效信号
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        rd_data_vaild <= 0;
    end
    else if(state_c==RD_DATA && bit_cnt==15 && scl_low) begin
        rd_data_vaild <= 1;
    end
    else begin
        rd_data_vaild <= 0;
    end
end

//done:I2C工作结束标志
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        done <= 0;
    end
    else if(state_c==STOP && scl_high) begin
        done <= 1;
    end
    else begin
        done <= 0;
    end
end

//设备地址
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        device_addr_a <= 0;
    end
    else if(rd_flag && (state_c==RD_DEVICE || state_c==RD_START || state_c==RD_DATA))begin
        device_addr_a <= {4'b1010,device_addr,1'b1};
    end
    else begin
        device_addr_a <= {4'b1010,device_addr,1'b0};
    end
end

//sda_reg_cnt
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        sda_reg_cnt <= 7;
    end
    else if(state_c==WR_DEVICE || state_c==WR_ADDR || state_c==WR_DATA ||state_c==RD_DEVICE || state_c==RD_DATA) begin
        if(bit_cnt<16 && scl_low)begin
            if(sda_reg_cnt==0)
                sda_reg_cnt <= 7;
            else
                sda_reg_cnt <= sda_reg_cnt - 1'b1;
        end
    end
    else begin
        sda_reg_cnt <= sda_reg_cnt;
    end
end


//sda_reg
always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        sda_reg <= 1;
    end
    else begin
        case(state_c)
            WR_START:begin
                        if(scl_high)
                            sda_reg <= 0;
                        else
                            sda_reg <= sda_reg;
                    end
            WR_DEVICE:begin
                         sda_reg <= device_addr_a[sda_reg_cnt];
                      end
            WR_ADDR:begin
                       if(waddr_cnt==0)
                           sda_reg <= word_addr_high[sda_reg_cnt];
                       else if(waddr_cnt==1)
                           if(bit_cnt<16)
                               sda_reg <= word_addr_low[sda_reg_cnt]; 
                           else
                               sda_reg <= 1;
                    end
            WR_DATA:begin
                        sda_reg <= wr_data[sda_reg_cnt];   //输入信号wr_data新数据应该wr_data_vaild时给出
                    end          
            RD_START:begin
                        if(scl_high)
                            sda_reg <= 0;
                        else
                            sda_reg <= sda_reg;
                    end
            RD_DEVICE:begin
                         sda_reg <= device_addr_a[sda_reg_cnt];
                      end
            RD_DATA:/*begin
                        if(bit_cnt==16 || bit_cnt==17)
                            sda_reg <= 0;
                        else
                            sda_reg <= sda_reg;
                    end*/
                   if(rdata_cnt==rd_data_num-1 && bit_cnt>15)
                       sda_reg <= 1;
                   else
                       sda_reg <= 0;
            STOP:begin
                        sda_reg <= 0;
                        /*if(scl_high)
                            sda_reg <= 1;
                        else
                            sda_reg <= sda_reg;*/
                    end
            default: sda_reg <= 1;
        endcase
    end
end
endmodule

2. Simulation verification

The EEPROM simulation model used here is used for simulation verification, and Micron's EEPROM simulation model is used. Those who need it can download it from my project. See the download link above for the complete project source code. I won’t go into details here. The test code is as follows:

`timescale 1 ns/ 1 ns
module I2C_ctrl_tb();
// test vector input registers
reg clk;
//reg [2:0] device_addr;
//reg [5:0] rd_data_num;
reg rd_en;
reg rst_n;
//reg treg_sda;
reg [15:0] word_addr;
reg [7:0] wr_data;
//reg [5:0] wr_data_num;
reg wr_en;
// wires                                               
wire done;
wire [7:0]  rd_data;
wire rd_data_vaild;
wire scl;
wire sda;
wire wr_data_vaild;

pullup(sda);
// assign statements (if any)                          
//assign sda = treg_sda;
parameter clk_period = 20;

I2C_ctrl i1 (
// port map - connection between master ports and signals/registers   
	.clk(clk),
	.device_addr(3'b000),
	.done(done),
	.rd_data(rd_data),
	.rd_data_num(6'd4),
	.rd_data_vaild(rd_data_vaild),
	.rd_en(rd_en),
	.rst_n(rst_n),
	.scl(scl),
	.sda(sda),
	.word_addr(word_addr),
	.wr_data(wr_data),
	.wr_data_num(6'd4),
	.wr_data_vaild(wr_data_vaild),
	.wr_en(wr_en)
);

M24LC64 u_M24LC64(
    .A0(1'b0), 
    .A1(1'b0), 
    .A2(1'b0), 
    .WP(1'b0), 
    .SDA(sda), 
    .SCL(scl), 
    .RESET(!rst_n)
);

initial clk = 0;
always #(clk_period/2) clk=~clk;

initial begin
    #2;
    rst_n = 0;
    word_addr = 0;
    wr_en = 0;
    wr_data = 0;
    rd_en = 0;

    #(clk_period*200);
    rst_n = 1;
    #(clk_period*20);

    //写入20组数据
    word_addr = 0;
    wr_data = 0;
    repeat(20)begin
        wr_en = 1;
        #(clk_period);
        wr_en = 0;

        repeat(4)begin
            @(posedge wr_data_vaild)
            wr_data = wr_data + 1;
        end

        @(posedge done);
        #(clk_period*200);
        word_addr = word_addr + 4;
    end

    #(clk_period*500);

    //读出刚写入的20组数据
    word_addr = 0;
    repeat(20)begin
        rd_en = 1;
        #(clk_period);
        rd_en = 0;

        @(posedge done);
        #(clk_period*200);
        word_addr = word_addr + 4;
    end

    #(clk_period*500);
    $stop;
end

endmodule

The following figure is the simulation result of AT24C64 EEPROM:

The following figure is the simulation result of writing sequence to AT24C64 EEPROM:

The following figure is the simulation result of read sequence of AT24C64 EEPROM:

The simulation results meet the expected results. The next article will conduct board-level debugging on the I2C interface module, and design an EEPROM read-write system experiment to verify the correctness of the design.

Guess you like

Origin blog.csdn.net/qq_33231534/article/details/106589690