2021-08-21

FPGA IIC 总线实现及仿真(一)


IIC总线简介

IIC(Inter-Integrated Circuit)总线是由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信。IIC一共有只有两个总线: 一条是双向的串行数据线SDA(Serial data),一条是串行时钟线SCL(Serial clock line)

IIC总线上必须有一个主设备来控制数据的读写,I2C总线上的每个设备都自己一个唯一的地址。

IIC总线连接

一、IIC协议

I2C 总线在传送数据过程中共有三种类型信号:开始信号、数据/地址信号、应答信号和结束信号,如下图所示。

开始(Start)信号:SCL 为高电平时,SDA 由高电平向低电平跳变。
数据(Data)/地址(Addr)信号:SDA线上的数据在SCL时钟“高”期间必须是稳定的,只有当SCL线上的时钟信号为低时,数据线上状态才可以改变。输出到SDA线上的每个字节必须是8位,数据传送时,先传送最高位(MSB)。
应答(ACK)信号:接收数据设备 在接收到 8bit 数据后,向发送数据的 设备发出低电平脉冲,表示已收到数据。发送数据的设备在收到应答信号后决定是否继续传输数据。
非应答(NOP)信号:由主机发送,当主机收到数据后,后续不在接收数据,则发送非应答信号,通知从机不在发送数据。非应答信号是主机在应答信号周期内发送高电平脉冲,结束数据传输。
结束(Stop)信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。

IIC总线传输

二、IIC读写数据

1.写数据时序

IIC写数据时序如下图所示:

IIC写数据时序

a)主机首先产生起始(START)信号
b)然后发送从机设备地址(Addr),这个地址共有7位,紧接着的第8位是读写位(R/W),0表示主机发送数据(写),1表示主机接收数据(读)。主机发送地址时,总线上的从机将这7位地址码与自己的地址进行比较,若相同,则认为自己正在被主机寻址,根据R/T位将自己确定为发送器和接收器
c)地址发送完毕后,主机等待从机的应答(ACK)信号
d)当主机收到应答(ACK)信号后,发送要访问从机的数据(DataAddr)地址, 再等待从机的应答信号
e)当主机收到应答信号后,发送数据(Data),每次发送完8bit数据,都要等待从机的应答信号
f)主机写数据完毕,产生停止信号,结束传送过程。

2.读数据时序

IIC读数据时序如下图所示:

IIC读数据时序

a)主机首先产生起始(START)信号
b)然后发送从机设备地址(Addr),此时该地址的第8位为0,表明是向从机写命令
c)地址发送完毕后,主机等待从机的应答(ACK)信号
d)当主机收到应答(ACK)信号后,发送要访问从机的数据(DataAddr)地址, 再等待从机的应答信号
e)当主机收到应答信号后,主机重新发送一个开始Restart信号,然后紧跟着发送一个从机设备地址,注意此时该地址的第8位为1,表明将主机设 置成接收模式开始读取数据。
f)这时候主机等待从机的应答信号,当主机收到应答信号时,就可以接收1个字节的数据,当接收完成后,主机发送非应答(NOP)信号,表示不在接收数据
g)主机进而产生停止信号,结束传送过程。

三、AT24C02

以读写EEPROM AT24C02为例,说明IIC读写信号时序。
AT24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。如下图所示。
A0,A1,A2:硬件地址引脚
WP:写保护引脚,接高电平只读,接地允许读和写
SCL和SDA:IIC总线
AT24C02器件图

AT24C02设备地址前四位固定为1010,A2~A0为由管脚电平(接地),最后一位表示读写操作,所以AT24C02的读地址为0xA1,写地址为0xA0(A2、A1和A0接地)。

AT24C02总共256字节数据,其寻址范围为00~FF,使用8bit数据地址可对内部256B中的任一个进行读/写操作。当地址大于2K时,需要使用页寻址,例如4k总共有512字节数据,使用P0作为页地址:
当P0=0时,可操作0~255地址的字节数据;
当P0=1时,可操作256~511地址的字节数据。

AT24C02系列EEPROM设备地址如下图所示。
在这里插入图片描述

四、FPGA IIC总线实现代码

IIC模块的输入输出,由外部给IIC模块读写指令及要写入的数据

module IIC_Master(
    input clk,     //工作时钟  
    input rst_p,   //复位  1:复位 0:正常工作
    
    output SCL,    //IIC 时钟信号
    inout  SDA,    //IIC 数据信号    
    
    input   WR,            //WR=1时  从EEPRO读数据   WR=0时   向EEPROM写数据
    input   WR_start,     //读写数据起始信号
    
    input   [7:0]  data_in,  //需要写入的数据
    output  Write_ready,     //写数据有效信号    
    output  Write_done,      //写数据结束信号
    //input   [7:0]  Write_num, //写数据长度
    
    output  [7:0]   data_out,//读出的数据
    output  Read_ready,       //读出数据有效信号
    output  Read_done       //读数据结束信号
    //input   [7:0]  Read_num  //读数据长度    
    );
    
    
    parameter   write_addr      =   8'b1010_000_0;  //IIC 从设备的读地址
    parameter   read_addr       =   8'b1010_000_1;  //IIC 从设备的写地址 
    parameter   DataAddr_start  =   8'h00;          //数据起始地址
    
    parameter   Baud_clk       =    16'd480;  //IIC 时钟速率=clk/Baud_clk 
    
    parameter   Send_num       =    8'd10;    //一次发送的字节数
    parameter   Recv_num       =    8'd10;    //一次接收的字节数
      

IIC总线状态机


    reg     IIC_SCL    = 0;
    reg     IIC_SDAin  = 0;
    reg     IIC_SDAout = 0;
    reg     is_out     = 0;
    
    reg     WR_bit     = 0;   //0:写数据 1:读数据
    
    reg  [15:0]   clk_cnt   = 0;//时钟分频计数器
    reg  [3:0]    Bit_cnt   = 0;//接收bit数 计数器
    reg  [7:0]    WRdata_cnt   = 0;//发送字节数计数器
    reg  [7:0]    RDdata_cnt   = 0;//接收字节数计数器
    reg  [7:0]    Write_cnt   = 0;
    reg  [7:0]    Read_cnt   = 0;
    reg  [7:0]    data_addr   = 0;
    reg  [7:0]    send_data  = 0;//写入的数据
    reg  [7:0]    recv_data  = 0;//读出的数据
    
    reg  [3:0]    IIC_state   = 0;
    reg  [3:0]    IIC_next_state   = 0;
    localparam  [3:0]  Idle_state     = 0,
                       Start_state    = 1,//start信号                       
                       WrAddr_state   = 2,//设备写地址
                       DataAddr_state = 3,//设备读地址
                       ReStart_state  = 4,//读数据时,重新发送start信号
                       RdAddr_state   = 5,//数据地址
                       WrData_state   = 6,//写数据
                       RdData_state   = 7,//读数据
                       sACK_state     = 8,//从设备响应
                       mACK_state     = 9,//主设备响应
                       NOP_state      = 10,//主设备非应答信号
                       Stop_state     = 11;//stop信号
 assign   SCL = IIC_SCL;  
 assign   SDA = (is_out==0) ? IIC_SDAout : 1'bz;  //is_out=0 时发送,is_out=1时 时输入
 //assign   IIC_SDAin =  (is_out==1) ? SDA : 0;
/IIC 状态机///    
    always @ (posedge clk)  begin
    case(IIC_state)
        Idle_state:begin
            
            if(WR_start==1)
               IIC_state<= Start_state;
            else
               IIC_state<= Idle_state; 
        end
        
        Start_state:begin
            if(clk_cnt==Baud_clk-1 )
                IIC_state<= WrAddr_state;
            else
                IIC_state<= Start_state;        
        end
        
        WrAddr_state:begin    //写8bit设备写地址
            if(Bit_cnt==7 && clk_cnt==Baud_clk-1)
            begin
                IIC_state<= sACK_state;
                IIC_next_state<=DataAddr_state;
            end
            else
                IIC_state<= WrAddr_state;        
        end
        
        DataAddr_state:begin    //写8bit设备读地址
            if(Bit_cnt==7 && clk_cnt==Baud_clk-1)
            begin  
                IIC_state<= sACK_state;
                if( WR_bit==1)   //如果读数据,则重新发送start信号
                    IIC_next_state<=ReStart_state;
                else            //如果写数据,则开始发送数据
                    IIC_next_state<=WrData_state;  
            end           
            else
                IIC_state<= DataAddr_state;          
        end
        
         ReStart_state:begin    
            if(clk_cnt==Baud_clk-1 )
                IIC_state<= RdAddr_state;
            else
                IIC_state<= ReStart_state;          
        end
        
        RdAddr_state:begin    //写8bit设备读地址
            if(Bit_cnt==7 && clk_cnt==Baud_clk-1)
            begin
                IIC_state<= sACK_state;
                IIC_next_state<=RdData_state;
            end
            else
                IIC_state<= RdAddr_state;        
        end
        
        WrData_state:begin    //读数据
            if(Bit_cnt==7 && clk_cnt==Baud_clk-1)
            begin  
                IIC_state<= sACK_state;
                if( WRdata_cnt==Write_cnt-1)
                    IIC_next_state<=Stop_state;
                else            
                    IIC_next_state<=WrData_state;  
            end           
            else
                IIC_state<= WrData_state;          
        end
        
        RdData_state:begin    //读数据
                if(clk_cnt==Baud_clk/2)
                    IIC_SDAin<=SDA;
                //IIC_SDAin<=0;
                
            if(Bit_cnt==7 && clk_cnt==Baud_clk-1)
            begin                  
                if( RDdata_cnt==Read_cnt-1)   
                    IIC_state<=NOP_state;
                else
                begin
                    IIC_state<= mACK_state;            
                    IIC_next_state<=RdData_state; 
                end 
            end           
            else
                IIC_state<= RdData_state;          
        end
        
        sACK_state:begin    //等待响应
            if(clk_cnt==Baud_clk/2)
                    IIC_SDAin<=SDA;
                //IIC_SDAin<=0;
                
            if(clk_cnt==Baud_clk-1 ) begin  
                if( IIC_SDAin==0 )               //如果响应,则发送读数据地址
                    IIC_state<= IIC_next_state;
                else
                    IIC_state<= Stop_state;    //如果未响应,则结束此次读写操作      
             end
        end
        
        mACK_state:begin    //等待响应
           if(clk_cnt==Baud_clk-1 )  //如果响应,则发送读数据地址
                IIC_state<= IIC_next_state;
            else
                IIC_state<= mACK_state;    //如果未响应,则结束此次读写操作      
        end
              
        NOP_state:begin    
            if(clk_cnt==Baud_clk-1 )
                IIC_state<= Stop_state;
            else
                IIC_state<= NOP_state;          
        end
        
        Stop_state:begin    
            if(clk_cnt==Baud_clk-1 )
                IIC_state<= Idle_state;
            else
                IIC_state<= Stop_state;          
        end        
    endcase
    end

SCL时序及SDA时序控制

   always @ (posedge clk)  begin
        if(IIC_state== Idle_state)
            clk_cnt<=0;
        else begin
            if(clk_cnt==Baud_clk-1)
                clk_cnt<=0;
            else
                clk_cnt<=clk_cnt+1;
        end
    end
SCL 时序/    
  always @ (posedge clk)  begin
        if(IIC_state== Idle_state || IIC_state== Start_state  || IIC_state== ReStart_state)
            IIC_SCL<=1;
        else begin
            if(clk_cnt<=Baud_clk/2)
                IIC_SCL<=0;
            else
                IIC_SCL<=1;
        end
    end
 SDA 时序/    
   
    always @ (posedge clk)  begin
        if(IIC_state==sACK_state || IIC_state==RdData_state)
                is_out<=1;
        else
                is_out<=0;
    end
   
    
     always @ (posedge clk)  begin
        case(IIC_state)
        Idle_state:
            IIC_SDAout<=1;
        Start_state,ReStart_state:begin
            if(clk_cnt<=Baud_clk/2)
                IIC_SDAout<=1;
            else
                IIC_SDAout<=0;
        end
        mACK_state:
            IIC_SDAout<=0;
        NOP_state:
            IIC_SDAout<=1;
        Stop_state:begin
            if(clk_cnt<=Baud_clk/4*3)
                IIC_SDAout<=0;
            else
                IIC_SDAout<=1;
        end
        WrAddr_state:begin
         if(clk_cnt<=Baud_clk/4)
         begin
         case(Bit_cnt)
         0:IIC_SDAout<=write_addr[7];  //先发高位
         1:IIC_SDAout<=write_addr[6];  
         2:IIC_SDAout<=write_addr[5];  
         3:IIC_SDAout<=write_addr[4];  
         4:IIC_SDAout<=write_addr[3];  
         5:IIC_SDAout<=write_addr[2];
         6:IIC_SDAout<=write_addr[1];
         7:IIC_SDAout<=write_addr[0]; 
         endcase      
        end
        end
        RdAddr_state:begin
         if(clk_cnt<=Baud_clk/4)
         begin
         case(Bit_cnt)
         0:IIC_SDAout<=read_addr[7];  //先发高位
         1:IIC_SDAout<=read_addr[6];  
         2:IIC_SDAout<=read_addr[5];  
         3:IIC_SDAout<=read_addr[4];  
         4:IIC_SDAout<=read_addr[3];  
         5:IIC_SDAout<=read_addr[2];
         6:IIC_SDAout<=read_addr[1];
         7:IIC_SDAout<=read_addr[0]; 
         endcase      
        end
        end
        DataAddr_state:begin
         if(clk_cnt<=Baud_clk/4)
         begin
         case(Bit_cnt)
         0:IIC_SDAout<=DataAddr_start[7];  //先发高位
         1:IIC_SDAout<=DataAddr_start[6];  
         2:IIC_SDAout<=DataAddr_start[5];  
         3:IIC_SDAout<=DataAddr_start[4];  
         4:IIC_SDAout<=DataAddr_start[3];  
         5:IIC_SDAout<=DataAddr_start[2];
         6:IIC_SDAout<=DataAddr_start[1];
         7:IIC_SDAout<=DataAddr_start[0]; 
         endcase      
        end
        end
        WrData_state:begin
         send_data<=data_in;
         if(clk_cnt<=Baud_clk/4)
         begin
         case(Bit_cnt)
         0:IIC_SDAout<=send_data[7];  //先发高位
         1:IIC_SDAout<=send_data[6];  
         2:IIC_SDAout<=send_data[5];  
         3:IIC_SDAout<=send_data[4];  
         4:IIC_SDAout<=send_data[3];  
         5:IIC_SDAout<=send_data[2];
         6:IIC_SDAout<=send_data[1];
         7:IIC_SDAout<=send_data[0]; 
         endcase      
        end
        end

        default:
            IIC_SDAout<=1;
        endcase
     end

数据的读写时序

      reg   WR_start_r = 0;
      reg   Write_ready_r=0;
      reg   Read_ready_r =0;
      always @ (posedge clk)  begin
        WR_start_r<=WR_start;
        if(WR_start==1 && WR_start_r==0)
            WR_bit<=WR;
            Write_cnt<=Send_num;
            Read_cnt<=Recv_num;
      end
      
      assign Write_ready = ( Bit_cnt==7 && clk_cnt==Baud_clk-1 && WR_bit==0 && IIC_state==WrData_state)  ?  1 :0;
      assign Write_done =  ( IIC_state==Stop_state && WR_bit==0)  ?  1 :0;

      
      always @ (posedge clk)  begin
        if(IIC_state==WrAddr_state||IIC_state==DataAddr_state
            ||IIC_state==RdAddr_state||IIC_state==WrData_state||IIC_state==RdData_state)
        begin
            if(clk_cnt==Baud_clk-1)
                Bit_cnt<=Bit_cnt+1;
        end
        else
                Bit_cnt<=0; 
      end
      
      
      always @ (posedge clk)  begin
         Write_ready_r<=Write_ready;
         if(Write_ready_r==0 && Write_ready==1 )begin
            if( WRdata_cnt==Write_cnt-1 && WR_bit==0 )
                WRdata_cnt<=0;
            else
                WRdata_cnt<=WRdata_cnt+1; 
         end
      end  
        
     读数据 时序/ 
      assign Read_ready = ( Bit_cnt==7 && clk_cnt==Baud_clk-1 &&  WR_bit==1 && IIC_state==RdData_state)  ?  1 :0;
      assign Read_done =  ( IIC_state==Stop_state &&  WR_bit==1)  ?  1 :0;
      assign data_out   = recv_data;
        
    always @ (posedge clk)  begin        if(IIC_state==RdData_state)
        begin
        //if(clk_cnt==Baud_clk/2)
        //begin
         case(Bit_cnt)
         0:recv_data[7]<=IIC_SDAin;  //先收高位
         1:recv_data[6]<=IIC_SDAin;  
         2:recv_data[5]<=IIC_SDAin;  
         3:recv_data[4]<=IIC_SDAin;  
         4:recv_data[3]<=IIC_SDAin;
         5:recv_data[2]<=IIC_SDAin;
         6:recv_data[1]<=IIC_SDAin;
         7:recv_data[0]<=IIC_SDAin; 
         endcase      
        //end
        end
      end
      
      
      always @ (posedge clk)  begin
         Read_ready_r<=Read_ready;
         if(Read_ready_r==0 && Read_ready==1 )begin
            if( RDdata_cnt==Read_cnt-1 && WR_bit==1 )
                RDdata_cnt<=0;
            else
                RDdata_cnt<=RDdata_cnt+1; 
         end
      end 
         
endmodule

下篇讲解IIC总线的时序仿真


IIC原理超详细讲解

FPGA IIC 总线实现及仿真(二)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
FPGA读写EEPROM芯片AT24C02实验Verilog逻辑源码Quartus11.0工程文件, FPGA型号为CYCLONE4E系列中的EP4CE6E22C8,可以做为你的学习设计参考。 module iic_com( clk,rst_n, sw1,sw2, scl,sda, dis_data ); input clk; // 50MHz input rst_n; //复位信号,低有效 input sw1,sw2; //按键1、2,(1按下执行写入操作,2按下执行读操作) output scl; // 24C02的时钟端口 inout sda; // 24C02的数据端口 output[7:0] dis_data; //数码管显示的数据 //按键检测 reg sw1_r,sw2_r; //键值锁存寄存器,每20ms检测一次键值 reg[19:0] cnt_20ms; //20ms计数寄存器 always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt_20ms <= 20'd0; else cnt_20ms <= cnt_20ms+1'b1; //不断计数 end always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin sw1_r <= 1'b1; //键值寄存器复位,没有键盘按下时键值都为1 sw2_r <= 1'b1; end else if(cnt_20ms == 20'hfffff) begin sw1_r <= sw1; //按键1值锁存 sw2_r <= sw2; //按键2值锁存 end end //--------------------------------------------- //分频部分 reg[2:0] cnt; // cnt=0:scl上升沿,cnt=1:scl高电平中间,cnt=2:scl下降沿,cnt=3:scl低电平中间 reg[8:0] cnt_delay; //500循环计数,产生iic所需要的时钟 reg scl_r; //时钟脉冲寄存器 always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt_delay <= 9'd0; else if(cnt_delay == 9'd499) cnt_delay <= 9'd0; //计数到10us为scl的周期,即100KHz else cnt_delay <= cnt_delay+1'b1; //时钟计数 end always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt <= 3'd5; else begin case (cnt_delay) 9'd124: cnt <= 3'd1; //cnt=1:scl高电平中间,用于数据采样 9'd249: cnt <= 3'd2; //cnt=2:scl下降沿 9'd374: cnt <= 3'd3; //cnt=3:scl低电平中间,用于数据变化 9'd499: cnt <= 3'd0; //cnt=0:scl上升沿 default: cnt <= 3'd5; endcase end end `define SCL_POS (cnt==3'd0) //cnt=0:scl上升沿 `define SCL_HIG (cnt==3'd1) //cnt=1:scl高电平中间,用于数据采样 `define SCL_NEG (cnt==3'd2) //cnt=2:scl下降沿 `define SCL_LOW (cnt==3'd3) //cnt=3:scl低电平中间,用于数据变化 always @ (posedge clk or negedge rst_n) begin if(!rst_n) scl_r <= 1'b0; else if(cnt==3'd0) scl_r <= 1'b1; //scl信号上升沿
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

mhlicq

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

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

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

打赏作者

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

抵扣说明:

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

余额充值