基于FPGA的I2C协议——以EEPROM为例

基于FPGA的I2C协议------以EEPROM为例



一、I2C硬件层

1、I2C为双线总线接口,仅有SCL(时钟线)、SDA(数据线)两根线。
2、其中两根线均为开漏输出, 均无输出高电平的能力,需要外界上拉电阻来输出高电平,SCL、SDA在空闲状态为高阻态。
3、在一个I2C通讯总线中,可连接多个I2C通讯设备,支持多个通讯主机及多个通讯从机。每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。
4、传输速率标准模式下可以达到100kb/s,快速模式下可以达到400kb/s,高速模式下可达 3.4Mbit/s。

I2C硬件原理图
【图为I2C硬件原理图】


二、I2C协议简介

1、在时钟(SCL)为高电平的时候,数据总线(SDA)必须保持稳定,所以数据总线(SDA),在时钟(SCL)为低电平的时候才能改变。
2、当SCL为高电平时,SDA产生下降沿为起始状态。
3、当SCL为高电平时,SDA产生上升沿为停止状态,同时转为空闲状态。
起始状态、停止状态
4、当一个完整字节的指令或数据传输完成,从机设备正确接收到指令或数据后,会通过拉低SDA为低电平,向主机设备发送单比特的应答信号,表示数据或指令写入成功。若从机正确应答,可以结束或开始下一字节数据或指令的传输,否则表明数据或指令写入失败,主机就可以决定是否放弃写入或者重新发起写入。
5、每个I2C 器件都有一个器件地址,有的器件地址在出厂时地址就设置好了,用户不可以更改(例如 OV7670 器件地址为固定的 0x42),有的确定了几位,剩下几位由硬件确定(比如常见的I2C 接口的 EEPROM 存储器,留有 3 个控地址的引脚,由用户自己在硬件设计时确定)。(本帖以器件地址为A0H为例。)
6、IIC读操作
在这里插入图片描述
单字节写操作时序图 (单字节存储地址 )
在这里插入图片描述
单字节写操作时序图 (双字节存储地址 )
7、IIC写操作
在这里插入图片描述

关于时序的详细解释资料很多,这里就不过多解释

三、程序讲解

1.程序目标

本程序使用EEPROM单字节写和读实现连续写入六个数据并读出。
按下按键1进行写,按下按键2进行读。
RTL视图如下:
在这里插入图片描述

2.状态机图示

有点丑

在这里插入图片描述

3.代码讲解

为了提高可移植性,将I2C控制协议单独做了一个模块。

开发平台:Vivado 2018.3
仿真平台:Modusim SE-64 10.5
开发平台:ALINX AX7Z020

模块列表如下

module i2c_ctrl
#(
      parameter   SLAVE_ADDR = 7'b1010000   ,  //EEPROM从机地址
      parameter   CLK_FREQ   = 26'd50_000_000, //模块输入的时钟频率
      parameter   I2C_FREQ   = 18'd250_000     //IIC_SCL的时钟频率ss
)
(
    input       wire                sys_clk     ,
    input       wire                sys_rst_n   ,
    input       wire                i2c_wr_en   ,//写操作使能端口
    input       wire                i2c_rd_en   ,//读操作使能端口
    input       wire                i2c_start   , //产生一个高脉冲进行I2C开始
    input       wire    [1:0]       addr_num    , //存储地址字节选择,1为单字节,2为双个字节
    
    input       wire    [15:0]      byte_addr   ,//读或者写字节地址
    input       wire    [7:0]       i2c_data_w  ,//预写入的数据
  
    output      reg     [7:0]       i2c_data_r  ,//读出的数据
    output      reg                 i2c_done    ,//读写操作完成信号
    output      reg                 i2c_clk     ,//由于IIC写于运行速率过慢,故先分频为1MHz进行处理
    output      reg                 i2c_ack     ,//若读写失败产生的非应答信号,将其拉高
    
    output      reg                 i2c_scl     ,//I2C_SCL信号
    inout       wire                i2c_sda      //I2C_SDA信号

);

注:
由于SDA需要进行输入输出,且要求连接到总线上的输出端必须是开漏输出结构,给不了高电平,所以总线上所有的高电平应该是由上拉电阻上拉达到效果的,而不是由主机直接给总线赋值 1就能实现,所以我们在写这个逻辑的时候也应该遵循这个标准,当总线上要输出低电平的时候,我们就直接给总线赋值 0,要输出高电平的时候,只能将总线设置成高阻态,这样再由外部上拉电阻来上拉成高电平。
用三态门很好实现。

assign  i2c_sda = (sda_en)?(sda_out?1'bZ:1'b0):1'bZ; 
assign  sda_in  = i2c_sda;
//sda_en为1时为输入,0为输出状态

IIC控制代码较长只给出状态机第二段的写法(文末附全部代码链接)
其中用clk_bit进行时序计数器,建议结合仿真一起查看

always@(posedge i2c_clk or negedge sys_rst_n)   begin
    if(sys_rst_n == 1'b0)   begin
            i2c_scl <= 1'b1;
            sda_out <= 1'b1;
            sda_en  <= 1'b1;
            st_done <= 1'b0;
            clk_bit <= 8'd0;
            i2c_ack <= 1'd0;
            i2c_done <= 1'b0;
            addr_num_r <= 2'b0;
            i2c_wr_en_r  <= 1'd0;
            i2c_rd_en_r  <= 1'd0;
            i2c_data_r <= 1'b0;
            i2c_data_w_r <= 8'b0;
            byte_addr_r  <= 16'b0;
            i2c_read_data_r <= 8'b0;
    end
    else    begin
        st_done <= 1'b0;
        case(state) 
            IDLE: begin 
                    sda_out <= 1'b1;
                    i2c_done <= 1'b0;
                    clk_bit <= 8'd0;
                    if(i2c_start == 1'b1)   begin
                        addr_num_r   <=  addr_num;
                        i2c_wr_en_r  <=  i2c_wr_en;
                        i2c_rd_en_r  <=  i2c_rd_en;
                        i2c_data_w_r <=  i2c_data_w;
                        byte_addr_r  <=  byte_addr;
                    end
            end
            START: begin   
                    clk_bit <= clk_bit + 1'b1;
                    case(clk_bit)  
                    0                               :begin 
                                                           sda_out <= 1'b0; 
                                                           st_done <= 1'b1;
                                                     end
                    1                               :clk_bit <= 1'b0;
                      
                    endcase
                
            end
            SEND_SLADDR_W:  begin
                    clk_bit <= clk_bit + 1'b1;
                    case(clk_bit)   
                        0,4,8,12,16,20,24,28,32     :i2c_scl <= 1'b0;
                        2,6,10,14,18,22,26,30,34    :i2c_scl <= 1'b1;
                        1                           :sda_out <= SLAVE_ADDR[6];
                        5                           :sda_out <= SLAVE_ADDR[5];
                        9                           :sda_out <= SLAVE_ADDR[4];
                        13                          :sda_out <= SLAVE_ADDR[3];
                        17                          :sda_out <= SLAVE_ADDR[2];
                        21                          :sda_out <= SLAVE_ADDR[1];
                        25                          :sda_out <= SLAVE_ADDR[0];
                        29                          :sda_out <= 0;
                        33                          :sda_en <=1'b0;
                        35                          :st_done <= 1'b1;
                        36                          :begin
                                                        i2c_scl <= 1'b0;
                                                        if(sda_in == 1'b1)  begin
                                                            i2c_ack <= 1'b1;
                                                        end 
                                                        clk_bit <= 1'b0;
                                                        
                        end    
                        default                     : ;
                        
                    endcase   
                             
            end
            SEND_ADDR16:    begin
                    clk_bit <= clk_bit + 1'b1;
                    case(clk_bit)   
                        0                           :begin
                                                        sda_en <=1'b1;
                                                        sda_out <= byte_addr_r[15];
                        end  
                        3,7,11,15,19,23,27,31       : i2c_scl <= 1'b0;
                        1,5,9,13,17,21,25,29,33     : i2c_scl <= 1'b1;
                                                    
                        4                           :sda_out <= byte_addr_r[14];
                        8                           :sda_out <= byte_addr_r[13];
                        12                          :sda_out <= byte_addr_r[12];
                        16                          :sda_out <= byte_addr_r[11];
                        20                          :sda_out <= byte_addr_r[10];
                        24                          :sda_out <= byte_addr_r[9];
                        28                          :sda_out <= byte_addr_r[8];
                        32                          :sda_en <=1'b0;
                        34                          :st_done <= 1'b1;
                        35                          :begin
                                                        i2c_scl <= 1'b0;
                                                        if(sda_in == 1'b1)  begin
                                                            i2c_ack <= 1'b1;
                                                        end 
                                                        clk_bit <= 1'b0;                                     
                        end                                
                        default                     : ;   
                    endcase
            end
            SEND_ADDR8:begin
                    clk_bit <= clk_bit + 1'b1;
                    case(clk_bit)   
                        0                           :begin
                                                        sda_en <=1'b1;
                                                        sda_out <= byte_addr_r[7];
                        end  
                        3,7,11,15,19,23,27,31       : i2c_scl <= 1'b0;
                        1,5,9,13,17,21,25,29,33     : i2c_scl <= 1'b1;

                        4                           :sda_out <= byte_addr_r[6];
                        8                           :sda_out <= byte_addr_r[5];
                        12                          :sda_out <= byte_addr_r[4];
                        16                          :sda_out <= byte_addr_r[3];
                        20                          :sda_out <= byte_addr_r[2];
                        24                          :sda_out <= byte_addr_r[1];
                        28                          :sda_out <= byte_addr_r[0];
                        32                          :sda_en <=1'b0;
                        34                          :st_done <= 1'b1;
                        35                          :begin
                                                        i2c_scl <= 1'b0;
                                                        if(sda_in == 1'b1)  begin
                                                            i2c_ack <= 1'b1;
                                                        end 
                                                        clk_bit <= 1'b0;                                     
                        end                                
                        default                     : ;  
                    endcase
            end
            SEND_DATA:begin
                    clk_bit <= clk_bit + 1'b1;
                    case(clk_bit)   
                        0                           :begin
                                                        sda_en <=1'b1;
                                                        sda_out <= i2c_data_w_r[7];
                        end  
                        3,7,11,15,19,23,27,31       : i2c_scl <= 1'b0;
                        1,5,9,13,17,21,25,29,33     : i2c_scl <= 1'b1;

                        4                           :sda_out <= i2c_data_w_r[6];
                        8                           :sda_out <= i2c_data_w_r[5];
                        12                          :sda_out <= i2c_data_w_r[4];
                        16                          :sda_out <= i2c_data_w_r[3];
                        20                          :sda_out <= i2c_data_w_r[2];
                        24                          :sda_out <= i2c_data_w_r[1];
                        28                          :sda_out <= i2c_data_w_r[0];
                        32                          :sda_en <=1'b0;
                        34                          :st_done <= 1'b1;
                        35                          :begin
                                                        i2c_scl <= 1'b0;
                                                        if(sda_in == 1'b1)  begin
                                                            i2c_ack <= 1'b1;
                                                        end 
                                                        clk_bit <= 1'b0;                                     
                        end                                
                        default                     : ;  
                    endcase
            end  
            STOP:begin
                    clk_bit <= clk_bit + 1'b1;
                    case(clk_bit)   
                        0                           :begin
                                                        sda_en <=1'b1;
                                                        sda_out <= 1'b0;
                        end
                        1                           :i2c_scl <= 1'b1;
                        2                           :sda_out <= 1'b1;
                        8                           :st_done <= 1'b1;
                        9                           :begin
                                                        clk_bit <= 1'b0; 
                                                        i2c_done <= 1'b1;        
                        end                                
                        default                     : ;  
                    endcase
            end
            START_2: begin   
                    clk_bit <= clk_bit + 1'b1;
                    case(clk_bit)  
                    0                               :sda_en <=1'b1;
                    1                               :begin 
                                                           sda_out <= 1'b1;  
                                                           i2c_scl <= 1'b1;                
                                                           st_done <= 1'b1;
                    end
                    2                               :begin 
                                                           sda_out <= 1'b0;     
                                                           clk_bit <= 1'b0;
                    end  
                    endcase
            end
         
            SEND_SLADDR_R:begin
                    clk_bit <= clk_bit + 1'b1;
                    case(clk_bit)   
                        0                           :begin
                                                        i2c_scl <= 1'b0;
                                                        sda_en <=1'b1;
                        end  
                        4,8,12,16,20,24,28,32     :i2c_scl <= 1'b0;
                        2,6,10,14,18,22,26,30,34    :i2c_scl <= 1'b1;
                                                    
                        1                           :sda_out <= SLAVE_ADDR[6];
                        5                           :sda_out <= SLAVE_ADDR[5];
                        9                           :sda_out <= SLAVE_ADDR[4];
                        13                          :sda_out <= SLAVE_ADDR[3];
                        17                          :sda_out <= SLAVE_ADDR[2];
                        21                          :sda_out <= SLAVE_ADDR[1];
                        25                          :sda_out <= SLAVE_ADDR[0];
                        29                          :sda_out <= 1;
                        33                          :sda_en <=1'b0;
                        35                          :st_done <= 1'b1;
                        36                          :begin
                                                        i2c_scl <= 1'b0;
                                                        if(sda_in == 1'b1)  begin
                                                            i2c_ack <= 1'b1;
                                                        end 
                                                        clk_bit <= 1'b0;                                  
                        end                                
                        default                     : ;  
                    endcase
            end
            READ_DATA:begin
                    clk_bit <= clk_bit + 1'b1;
                    case(clk_bit)   
                        0                           :sda_en <=1'b0;
                        3,7,11,15,19,23,27,31       :i2c_scl <= 1'b0;
                        1,5,9,13,17,21,25,29,33     :i2c_scl <= 1'b1;
                             
                        2                           :i2c_read_data_r[7]<= sda_in;     
                        6                           :i2c_read_data_r[6]<= sda_in;
                        10                          :i2c_read_data_r[5]<= sda_in;
                        14                          :i2c_read_data_r[4]<= sda_in;
                        18                          :i2c_read_data_r[3]<= sda_in;
                        22                          :i2c_read_data_r[2]<= sda_in;
                        26                          :i2c_read_data_r[1]<= sda_in;
                        30                          :i2c_read_data_r[0]<= sda_in;
                        32                          :sda_en  <= 1'b1;
                        33                          :sda_out <= 1'b1;
                        34                          :st_done <= 1'b1;
                        35                          :begin
                                                        i2c_scl <= 1'b0;                  
                                                        clk_bit <= 1'b0;  
                                                        i2c_data_r <= i2c_read_data_r;                                                        
                        end                                
                        default                     : ;   
                    endcase
            end
        endcase    
    end        
end

仿真如下
EEPROM_AT24C64 为原子哥提供的仿真模型

module tb_i2c_ctrl();

reg     sys_clk;
reg     sys_rst_n;
reg     i2c_wr_en ;
reg     i2c_rd_en ;
reg     i2c_start ;
reg     [1:0]    addr_num  ;
reg     [15:0]   byte_addr ;
reg     [7:0]    i2c_data_w;

wire    [7:0]    i2c_data_r  ;
wire             i2c_done    ;
wire             i2c_clk     ;
wire             i2c_ack     ;
wire             i2c_scl     ;
wire             i2c_sda     ;

pullup(i2c_sda);
//给输入信号初始值
initial 
    begin
        sys_clk            = 1'b0;
        sys_rst_n          = 1'b0;     //复位
        addr_num <= 2'd2;
        i2c_wr_en <= 1'b1;
        i2c_rd_en <= 1'b0;
        byte_addr <= 16'hA5A5;
        i2c_data_w <= 8'hAA;
        #21;  
        sys_rst_n  = 1'b1;     //在第21ns的时候复位信号信号拉高
        i2c_start <= 1'b1;
        #1000;
        i2c_start <= 1'b0;
        #500000;
        i2c_wr_en <= 1'b0;
        i2c_rd_en <= 1'b1;
        byte_addr <= 16'hA5A5;
        #1000;
        i2c_start <= 1'b1;
        #1000;
        i2c_start <= 1'b0;
        #500000;

        i2c_wr_en <= 1'b1;
        i2c_rd_en <= 1'b0;
        byte_addr <= 16'h5AA5;
        i2c_data_w <= 8'h55;
        i2c_start <= 1'b1;
        #1000;
        i2c_start <= 1'b0;
        #500000;
        i2c_wr_en <= 1'b0;
        i2c_rd_en <= 1'b1;
        byte_addr <= 16'h5AA5;
        i2c_start <= 1'b1;
        #1000;
        i2c_start <= 1'b0;
        
        #500000;
        i2c_wr_en <= 1'b0;
        i2c_rd_en <= 1'b1;
        byte_addr <= 16'hA5A5;
        i2c_start <= 1'b1;
        #1000;
        i2c_start <= 1'b0;
        
    end

//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #10  sys_clk = ~sys_clk;

i2c_ctrl
#(
    .SLAVE_ADDR(7'b1010000     ) ,  //EEPROM从机地址
    .CLK_FREQ  (26'd50_000_000 ) , //模块输入的时钟频率
    .I2C_FREQ  (18'd250_000    )   //IIC_SCL的时钟频率ss
)
i2c_ctrl_inst
(
    .sys_clk     (sys_clk   ),
    .sys_rst_n   (sys_rst_n ),
    .i2c_wr_en   (i2c_wr_en ),
    .i2c_rd_en   (i2c_rd_en ),
    .i2c_start   (i2c_start ), 
    .addr_num    (addr_num  ), 

    .byte_addr   (byte_addr ),
    .i2c_data_w  (i2c_data_w),
 
    .i2c_data_r  (i2c_data_r),
    .i2c_done    (i2c_done  ),
    .i2c_clk     (i2c_clk   ),
    .i2c_ack     (i2c_ack   ),

    .i2c_scl     (i2c_scl   ),
    .i2c_sda     (i2c_sda   )
);
EEPROM_AT24C64 u_EEPROM_AT24C64(
    .scl         (i2c_scl  ),
    .sda         (i2c_sda  )
    );
    
endmodule

顶层EEPROM的读写就很轻松了,这里仅给出仿真结果(文末附全部文件)

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

读写的数据一致,仿真通过。
黑金的EEPROM竟然接在了PS段,上不了板了,呜呜呜

总结

文件链接在这里,嘿咻
提取码:4ik6

IIC协议在FPGA中算是比较难实现的协议了.孩子写了好久,呜呜呜
文中I2C控制的代码参考了原子哥的程序(还得是原子哥)。
协议方面个人认为小梅哥的线下班讲的很棒。
在写代码之前一定要理清协议的原理,最好是画一下波形图,写代码的时候如果觉得还是有点乱可以边仿真变写代码,个人认为这是比较快的方法。>o<

此工程可以应用于实际项目,如果认为文章写的不错请点赞支持。
FPGA初学者,欢迎交流鸭>o<

  • 11
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值