基于FPGA的I2C通信(二)

目录

三、I2C协议的FPGA实现

1. I2C接口设计

2. 仿真验证


 本专题EEPROM读写系统(在下一篇博客讲解,包含本篇内容)整体功能实现的工程下载链接如下:
 https://download.csdn.net/download/qq_33231534/12503289


三、I2C协议的FPGA实现

上一篇博客对I2C总线协议进行了大体的讲解,以及对I2C总线器件EEPROM(AT24C64)读写时序进行详细阐述,下边就要对EEPROM器件在FPGA上如何进行读写,以及接口设计和调试系统进行具体叙述。本实验平台使用的是小梅哥的AC620开发板,FPGA芯片是cyclone IV EP4CE10F17C8N。

1. I2C(EEPROM)接口设计

 

图1:字节写时序图
图2:页写时序图
图3:随机读时序图
图4:连续读shix

 根据上方4个读写时序图,编写I2C接口时序,I2C协议接口设计,这里采用状态机的方式,将整个设计为9个状态,分别为IDLE 、WR_START  、WR_DEVICE 、WR_ADDR  、WR_DATA  、RD_START 、RD_DEVICE、RD_DATA  、STOP 。其状态转移图如下:

 其各个状态表示的含义以及该模块接口信号的名称及功能如下思维导图:

这里将接口信号用表格写一下,看的更加清晰一点。如下:

表:I2C接口模块控制信号说明
信号名称 I/O 位数 功能描述
clk I 1 系统时钟50MHz
rst_n I 1 系统复位
rd_data_num   I 6 读数据个数,最大32
wr_data_num   I 6 写数据个数,最大32
word_addr I 16 数据字地址,AT24C64是16位地址
device_addr   I 3 设备可变地址,由硬件连接决定,本实验平台为3‘b001
wr_en   I 1 写数据使能
wr_data   I 8 写数据
rd_en I 1 读数据使能
wr_data_vaild O 1 写数据有效位,读多个数据就是读完每个数据时产生一个有效位
rd_data O 8 读数据
rd_data_vaild O 1 读数据有效位,写多个数据就是写完每个数据时产生一个有效位
done O 1 读写操作结束标志
scl O 1 串行时钟线
sda IO 1 串行数据线

直接上代码:

//-------------------------------------------------------------------
//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. 仿真验证

这里采用的EEPROM仿真模型进行仿真验证,使用的是镁光的EEPROM仿真模型。需要的人可以从我的工程里边下载,完整工程源码看上方下载链接,这里不多赘述。测试代码如下:

`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

下图是对AT24C64型号EEPROM仿真结果:

下图是对AT24C64型号EEPROM写时序仿真结果:

下图是对AT24C64型号EEPROM读时序仿真结果:

仿真结果符合预期效果。下一篇将会在对I2C接口模块进行板级调试,设计了一个EEPROM读写系统实验,以验证设计的正确性。

猜你喜欢

转载自blog.csdn.net/qq_33231534/article/details/106589690
I2C