原创 FPGA实现I2C总线模块(转)

2010-3-16 16:47 3947 2 2 分类: FPGA/CPLD

摘要:简述了I<?xml:namespace prefix = st1 ns = "urn:schemas-microsoft-com:office:smarttags" />2C总线的特点;介绍了开发FPGAI2C总线模块的设计思想;给出并解释了用VerilogHDL实现部分I2C总线功能的程序,以及I2C总线主从模式下的仿真时序图。sO100


    关键词:I2C总线FPGAVerilogHDL时序


    开发FPGA时,利用EDA工具设计芯片实现系统功能已经成为支撑电子设计的通用平台,并逐步向支持系统级的设计方向发展。在软件设计过程中,越来越强调模块化设计。I2C总线是Philips公司推出的双向两线串行通讯标准,具有接口线少、通讯效率高等特点。把I2C总线设计成相应的模块,有利于相关FPCA的开发。目前有一些介绍相关开发的资料,但都是利用VHDL语言或AHDL语言实现的。本文给出利用VerilogHDL语言设计的I2C总线模块。


    1I2C总线概述


    I2C总线系统由两根总线即SCL(串行时钟)线和SDA(串行数据)线构成。这种总线可以设计成很多种通讯配置,但本文只讨论主从系统的应用。主器件控制总线通讯,开始/结束传送、发送信息并产生I2C系统时钟。在写操作过程中,从器件一旦被主控器件寻址,就执行特定的相应功能。在读操作过程中,主控器件从从器件那里获得数据。在整个主从传送过程中,所有的事件都通过主控器件的SCL时钟线达到同步。连到总线上的器件的接口形式必须是漏极开路或集电极开路输出状态。通过上拉电阻,使得两根总线在空闲的状态下都为高电平状态。因此I2C总线上具有线与功能,即总线上的所有器件都达到高电子状态时,I2C总线才能达到高电平状态,从而使总线上的高速器件和慢速器件工作同步。


    I2C协议中,从器件地址是一个唯一的7位地址。接下来是一个读写方向标志位,读状态是高电平、写状态是低电子。


    2I2C模块的设计与实现


    根据I2C协议中传输过程的特点,I2C模块可以划分为字节发送模块、字节接收模块、开始条件模块、停止条件模块。其中,字节发送模块、字节接收模块和停止条件模块为基本模块。在开始条件模块中,因为需要发送从器件地址,所以要调用字节发送模块。


    下面给出用VerilogHDL语言实现字节发送模块的关键程序。相关变量的声明在此略去。程序在Max+PlusII环境下编译、调试、仿真。


    assignen_sdao=tempen_sdao;//设置SDA三态输出使能


    assignsend_byte_over=tempsend_byte_over;


    assignNO_ACK=tempNO_ACK;


    assignsdao=tempsda;


    assignsclo=tempscl;


    always@(posedgesend_byte_clk)


    begin


    case(send_byte_zt)


    sendbit1:


    begin


    if(send_byte_num==0)


    begin


    shiftdata[7:0]=indata[7:0];


    end


    shiftdata=shiftdata<<1;


    tempsda=shiftdata[8];


    tempscl=1;//SCL为高电平


    send_byte_zt=delay_1;


    send_byte_num=send_byte_num+1;


    end


    delay_1://延时三个周期


    begin


    if(delay_counter>=2)


    begin


    send_byte_zt=sendbit2;


    delay_counter=0;


    end


    else


    begin


    delay_counter=delay_counter+1;


    send_byte_zt=send_byte_zt;


    end


    end


    sendbit2:


    begin


    tempsc1=0;//SCL置零


    send_byte_zt=delay_2;


    end


    delay_2://延时三个周期


    begin


    if(delay_counter>=2)


    begin


    send_byte_zt=sendbit3;


    delay_counter=0;


    end


    else


    begin


    delay_counter=delay_counter+1;


    send_byte_zt=send_byte_zt;


    end


    end


    sendbit3://判断是否字节中所有位都发送完毕


    begin


    if(send_byte_num<=8)


    begin


    send_byte_zt=sendbit1;


    end


    else


    begin


    send_byte_zt=ForACK1;


    send_byte_num=0;


    end


    end


    ForACK1:


    begin


    tempsda=1;//释放数据线,等待应答信号


    send_byte_zt=delay_ACK;


    end


    delay_ACK://延时


    begin


   &nbsp;if(delay_counter>=3)


    begin


    send_byte_zt=ForACK2;


    delay_counter=0;


    end


    else


    begin


    delay_counter=delay_counter+1;


    send_byte_zt=send_byte_zt;


    tempscl=1;


    end


    end


    ForACK2:


    begin


    send_byte_zt=AckYESNO;


    tempen_sdao=0;//输出SDA使能信号,控制sdaosdai


    end


    AckYESNO:


    begin


    if(sdai)//如果应答信号sdai1NO_ACK1


    begin


    tempNO_ACK=1;//设置未应答标志信号


    end


    tempsc1=0;//终止应答位


    send_byte_zt=Finish_delay;


    end


    Finish_delay://延时


    begin


    if(delay_counter>=2)


    begin


    tempsend_byte_over=1;


    send_byte_zt=FinishACK1;


    delay_counter=0;


    end


    else


    begin


    delay_counter=delay_counter+1


   


    send_byte_zt=send_byte_zt;


    end


    end


    FinishACK1:


    begin


    send_byte_zt=sendbit1;


    send_byte_num=0;


    end


    default:


    begin


    send_byte_zt=sendbit1;


    send_byte_num=0;


    end


    endcase


    end


    程序中sdaosclo为输出信号,sdai为应答信号,en_sdao是对sdaosdai进行切换的信号。I2C总线具有SDASCL两根信号线,所以在整个模块设计中,把sdaosclosdaiscli作为两组信号。当需要向外部SDA信号线上输出信息时,sdao连到SDA信号线上;当需要从外部SDA信号线上读入信息时,置sdao成高阻态,sdai连到SDA信号线上。en_sdao信号作为这一过程的切换信号。在程序中定义了一些状态信号:NO_ACKsend_byte_over。其中,NO_ACK信号判断从器件是否对发送的信号给予了应答。send_byte_over信号判断字节是否传输完毕。这些信号可以传递给上一层设计模块,以控制程序的流程。为了使I2C总线能够有效地通讯,必须考虑信号的建立和保持时间,所以程序中设置了相应的延时部分。另外,在以clk为触发信号的过程模块中,定义send_byte_clk信号为时钟信号的两倍频信号,并加入字节发送模块使能信号start_send_byte控制模块工作于篇幅所限,略去该过程模块。


    字节发送模块的仿真测试结果如图1所示。


    根据I2C总线标准,利用VerilogHDL很容易实现字节接收模块、开始条件模块、停止条件模块这三个模块。图2是数据发送过程的仿真测试结果。从器件的7位地址为101011,向从器件发送的数据为00010111aenscloaensdao分别是sclosclisdaosdai的切换信号。


    3是数据接收过程的仿真测试结果。从器件的7位地址为0011001,从器件发送的数据为11111111enscloensdao分别是sclosclisdaosdai的切换信号。


    将图2和图3所模拟的I2C总线时序与I2C总线协议中相关要求进行比较,满足I2C总线的时序要求。


    对各个模块进行多层次处理,形成I2C总线模块。


以该I2C总线模块为基础,编写FPGAAT24C01A(ATMEL公司生产的E2PROM)的通讯程序。然后把相关程序下载到EPF10Kl0LC84-3中,与AT24C01A进行实际通讯实验,效果良好。ImgLoad(document.getElementById(BodyLabel));


<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 


 


 


// i2c.vhd


//


//这是是能从一个外部NUMAT24C02A)读数据到一个256x8的外部SRAM块的I2C主接口程序,


//当用于写NUM的外部逻辑寄存器存取时,SRAM就从外部源读数据并且把数据写到特定的I2C地址。


//----------------------------------------------------------------------


//


//               Copyright 2004 Actel corporation


//


//----------------------------------------------------------------------


//


// Version 1.2  06/04/04 J.Vorgert - working file


//


//----------------------------------------------------------------------


 


`timescale 1ns / 1ps


 


module i2c (Reset_n, CLK, INIT, IENB, IADDR, IDATA, ICLK, UPDT,


            UENB, UADDR, UDATA, SDI, SDO, SCK);


 


input Reset_n;  /* active low reset */


input CLK    ;  /* processor clock  */


output INIT   ;  /* high during init */


output IENB   ;  /* low to enable write */


output [7:0] IADDR  ;  /* init address */


output [7:0] IDATA  ;  /* init data */


output ICLK   ;  /* init clock */


 


input UPDT   ;  /* high to trigger mirror image update */


output UENB   ;  /* low to enable fifo */


input [7:0] UADDR; /* write address */


input [7:0] UDATA;  /* write data */


 


input SDI    ;  /* serial input */


output SDO    ; /* active low open-drain drive enable - data */


output SCK    ; /* active low open-drain drive enable - clock */


 


reg IENB;


reg INIT;


reg UENB;


reg BTCK;


wire STEN;    


reg  [3:0] CSTATE;  


reg  [3:0] BCNT  ; 


reg  [7:0] CCNT  ; 


reg DLY     ;


reg D2 ;


wire D2I;


wire NKI     ;


reg NACK    ;


wire WRI     ;


wire RDI     ;


reg [8:0] BYTE   ;


reg [8:0] SDATA  ;


wire LD_BYTE  ;


reg STSP     ;


wire CTL_VAL  ;


 


always @ (posedge CLK or negedge Reset_n)


begin


  if(Reset_n == 1'b0)


     BTCK <= 1'b0;


  else


     BTCK <= #1 !BTCK;


 


end


 


// INIT is set at power-up and cleared when the state machine


// reaches state 0101.


 


always @ (negedge Reset_n or posedge CLK)


begin


  if(Reset_n == 1'b0)


     INIT <= 1'b1;


  else if(CSTATE == 4'b0101)


        INIT <= #1 1'b0 ;


end


 


// This state machine is set-up to read/write data to an AT24C02A


// serial Flash memory


 


//这个状态机是建立AT24C02A串行闪存的数据读写


 


// At power-up, the INIT bit is set, and the state machine executes


// a 'sequencial read' operation starting at address 0x000 and


// procedding until all 256 bytes have been read and forwarded into


// the internal memory block. The state machine then sends a


// stop bit to the Flash and clears the INIT control bit.


//


//在上电时,INIT被设置为高,状态机从地址0x000开始执行“连续读”操作,


//一直进行下去直到所有的256字节都被读,然后向前到内部存储区,


//状态机然后给FLASH发送一个停止位并且清除INIT控制位。


 


// The state machine then waits for updt to be set.


// When the updt bit is set, the interface asserts u_enb low on a


// falling-edge of clk and addr/data is latched  on the next falling edge


// (rd_clk should be on the rising-edge).  The state machine writes


// data to the external FLASH memory one byte at a time whenever


// updt is asserted high.  If the FIFO remains 'not empty' then this


// block will poll the NVM until it is ready, and then proceed with


// a write cycle for the next byte.


 


//状态机一直等待UPDT被置为高。当updt比特被置成高,当clk下降沿时,接口把u_enb设成低


//rd_clk应该在上升沿)。这个状态机每次updt为高时就写一个字节数据到外部闪存。


//如果FIFO保持“非空”,则这个块会一直等到它准备好,然后在接下来的比特进行一个写周期。


 


// State Machine:


//


// 0000 - reset state:   generate a start bit and load 0xA0 command


// 0001 - send byte:     then load 0x00 address


// 0010 - send byte:     generate a start bit and load 0xA1 command


// 0011 - send byte:     clear byte count


// 0100 - receive byte:  if cnt /= FF: ack, cnt++, goto 0004 else: nack


// 0101 - stop:          assert stop bit and loop until updt = 1 - then


//                       generate a start bit and load A0


// 0110 - send byte:     send byte - if nack - goto 0101, else load Address


// 0111 - send byte:     send data byte, load data


// 1000 - send byte:     goto 0101


//


// In practice, the state machine is just a counter that starts at zero


// and counts up, then jumps back to 101 and counts up again,


// returning to zero only when reset_n is asserted low.


//在练习中,状态机只是一个从0开始计数的计数器,加起来,然后跳到101,再重新计数,


//只有到reset_n被置为低时才返回到0


 


assign STEN = ( BCNT[3] == 1'b1 &&


                  (CSTATE[2] != 1'b1 || CSTATE[2:1] == 2'b11 ||


                   CSTATE[3]  == 1'b1 || (CSTATE == 4'b0100 && CCNT == 8'b11111111) ||


                  (CSTATE == 4'b0101 && UPDT == 1'b1)))?1'b1:1'b0;


 


always @(negedge Reset_n or negedge CLK)


begin


  if(Reset_n == 1'b0) 


     CSTATE <= 4'b0000;


  else


  begin


  if(STEN == 1'b1 && BTCK == 1'b0)


        begin


         if(CSTATE < 4'b0101 && NACK == 1'b1)


           CSTATE <= #1 4'b0000 ;


        end


   else


        begin


        if (CSTATE[3] == 1'b1 || NACK == 1'b1)


          CSTATE <= #1 4'b0101 ;


        else


          CSTATE <= #1 CSTATE + 1'b1 ;


        end


  end


end


 


// The bit counter (BCNT) is cleared at the state transition


// and during the first cycle of state '0011' (for start bit).


// incremented on the falling-edge of clk when BTCK is low.


 


//比特计数器在过渡状态时和在状态“0011(起始比特)第一个循环期间被清空。


//clk下降沿并且BTCK为低电平时,比特计数器增加。


 


always @ (negedge Reset_n or negedge CLK)


begin


  if(Reset_n == 1'b0)


     begin


     BCNT <= 4'b0000;


     DLY  <= 1'b0;


     end


  else


    begin


     if(BTCK == 1'b0)


       begin


       if(BCNT[3] == 1'b1 && CSTATE == 4'b0010)


          DLY <= #1 1'b1;


       else


          DLY <= 1'b0;


      


       if(BCNT[3] == 1'b1 || (CSTATE == 4'b0011 && DLY == 1'b1))


          BCNT <= #1 4'b0000;


       else


          BCNT <= #1 BCNT + 1'b1;


       end


   


    end


end


 


// The byte counter (CCNT) is cleared in state 0011.


//字节计数器在状态0011时被清零。


 


// It is incremented during the ACK bit after each


// byte transfer in state 0100 to count 0x00-0xFF bytes


// as they are read from the NVM.  ccnt is used both as


// a control signal and as the iaddr output.


 


assign D2I = (BTCK == 1'b1 && BCNT[3] == 1'b1 && CSTATE == 4'b0100)?1'b1:1'b0;


          


 


always @ (negedge Reset_n or negedge CLK)


begin


  if(Reset_n == 1'b0)


   begin


     CCNT <= 8'b0;


     D2   <= 1'b0;


   end


  else


   begin


     D2 <= #1 D2I;


     if(CSTATE == 4'b0011)


        CCNT <= #1 8'b0;


     else if(D2 == 1'b1)


        CCNT <= #1 CCNT + 1'b1;


     end


end


 


// the following logic checks the ACK bit for all states except


// states '0100' and '0101' and asserts NACK if the data pin is


// high during the 9th bit of any transfer.  This is registered


// so that the value is present during state changes.


 


 


assign NKI = (BCNT[3] == 1'b1 && CSTATE != 4'b0100 && CSTATE != 4'b0101 && SDI == 1'b1)?1'b1:1'b0;


 


always @ (negedge Reset_n or posedge CLK)


begin


  if(Reset_n == 1'b0)


     NACK <= 1'b0;


  else if(BTCK == 1'b1)


     NACK <= #1 NKI;


end


 


// Write enables are cleared to 1 at power-up and are asserted low during


// ACK in state 0100.


 


 


assign WRI = (CSTATE == 4'b0100 && BCNT[3] == 1'b1 && BTCK == 1'b1)?1'b0:1'b1;


 


always @ (negedge Reset_n or negedge CLK)


begin


  if(Reset_n == 1'b0)


     IENB <= 1'b1;


  else


     IENB <= #1 WRI;


end


 


assign IADDR = CCNT[7:0];   /* use byte count as address */


assign IDATA = SDATA[8:1];  /* account for ACK bit */


assign ICLK = !BTCK;           /* invert BTCK and use the rising-edge of this signal as */


                             /*the write clock into internal SRAM */


 


// UENB is cleared to 1 at power-up and is asserted low in state 0111


//UEUB在上电时被清到1,在0111状态当BCNT=7BTCK=1时被置为低.


 


// while BCNT="7" and BTCK="1".  It is clocked on the falling-edge


// of CLK so RD_CLK should occur on the rising-edge.


 


 


assign RDI = (CSTATE == 4'b0111 && BCNT == 4'b0111  && BTCK == 1'b1)?0:1;


 


always @ ( negedge Reset_n or negedge CLK)


begin


  if(Reset_n == 1'b0)


     UENB  <= 1'b1;


  else


     UENB <= #1 RDI;


 


end


 


// The value that gets loaded into sdata is determined


// by which state we're exiting...


 


//这个装载到sdata里的值由从哪个状态退出来决定


 


always @ (CSTATE or UDATA or UADDR)


begin


  case (CSTATE)


    4'b0000 :  BYTE = 9'b101000001; /* A0 */


    4'b0010 :  BYTE = 9'b101000011; /* A1 */


    4'b0101 :  BYTE = 9'b101000001; /* A0 */


    4'b0110 :  BYTE = {UADDR,1'b1};


    4'b0111 :  BYTE = {UDATA,1'b1};


    default :  BYTE = 9'b000000001; /* 0001,0011 */


  endcase


end


 


// The data register is 9 bits long (BYTE and ACK bit)


// It is parallel loaded during the ACK cycle in states


// 0000, 0001, 0010, 0011, 0101, 0110, and 0111;


 


//这个数据寄存器为9比特长(一个字节加一个ACK位)


//在状态000000010010010101100111状态的ACK循环时,这些都是平行加载的。


 


assign LD_BYTE = (BCNT[3] == 1'b1 && BTCK == 1'b0 && CSTATE != 4'b0100 && CSTATE[3] == 1'b0)?1'b1:1'b0;


 


always @ (negedge Reset_n or negedge CLK)


begin


  if(Reset_n == 1'b0)


     SDATA <= 9'b111111111;


  else


     begin


     if(LD_BYTE == 1'b1)


        SDATA <= #1 BYTE;


     else if((CSTATE != 4'b0101 && CSTATE != 4'b0100 && BTCK == 1'b0 && DLY == 1'b0) ||


           (CSTATE == 4'b0100 && BTCK == 1'b1))


            SDATA <= #1 {SDATA[7:0],SDI};


     end


end


 


 


// Start bits (data falling while BTCK is high) are generated as


// we exit states 0000, 0010, and 0101; stop bits (data rising


// while BTCK is high) are generated as we enter state 0101.


// This is done with the STSP signal.


 


//起始位(数据下降当BTCK为高)产生于退出状态000000100101 时;


//停止位(数据上升当BTCK为高)产生于进入状态0101时。


//这些由STSP信号完成


 


always @ (negedge Reset_n or negedge CLK)


begin


  if(Reset_n == 1'b0)


     STSP  <= 1'b1;


  else


    begin


     if(((CSTATE == 4'b0000 || CSTATE == 4'b0101) && STEN == 1'b1 && BTCK == 1'b1) || (CSTATE == 4'b0011))


        STSP <= #1 1'b0;


     else if((CSTATE == 4'b0101 && BCNT == 4'b0000 && BTCK == 1'b1) ||


           (CSTATE == 4'b0010 && BCNT[3] == 1'b1))


        STSP <= #1 1'b1;


    


    end


end


 


// The serial output is driven either by stsp when


// outen is low, or by the MSBit of the shift register


// when oten is high.


 


//outen为低时,stsp可以驱动连续的输出,


//或者当oten为高时,移位寄存器的MSbit也能驱动连续输出


 


assign CTL_VAL = (STSP == 1'b1 || (CSTATE == 4'b0100 && (BCNT[3] != 1'b1 || CCNT == 8'b11111111)))?1'b1:1'b0;


 


assign SDO = (CSTATE == 4'b0000 || DLY == 1'b1 || CSTATE == 4'b0100 || CSTATE == 4'b0101)?CTL_VAL:SDATA[8];


 


assign SCK = (BTCK == 1'b1 || (STSP == 1'b1 && (CSTATE == 4'b0000 || CSTATE == 4'b0101)))?1'b1:1'b0;


            


endmodule


 


 


 


 


 


 


 


 


 

PARTNER CONTENT

文章评论0条评论)

登录后参与讨论
EE直播间
更多
我要评论
0
2
关闭 站长推荐上一条 /3 下一条