FPGA基础设计(四):IIC协议实现

136 篇文章 1494 订阅

概述

  很多数字传感器、数字控制的芯片(DDS、串行ADC、串行DAC)都是通过IIC总线来和控制器通信的。不过IIC协议仍然是一种慢速的通信方式,标准IIC速率为100kbit/s,快速模式速率为400kbit/s。本文致力于讲述如何用计数器控制和分频时钟控制两种方式完成IIC的读写操作。


IIC协议

  IIC协议是一种多机通讯,由SDA数据线和SCL时钟线构成串行总线,所有的IIC设备都可以挂载到总线上,但每个设备都有唯一的设备读地址和设备写地址。在使用IIC作为数字接口的芯片datasheet中都可以看到该设备的设备读/写地址情况,并可以查找到相应的读写时序,以及对速率的要求。下图是一个通用的IIC协议时序:


  我们可以总结出五种IIC协议的时序状态:
  1. 空闲状态,当SDA和SCL两条信号线都处于高电平时总线处于空闲状态。
  2. 开始信号,SCL为高电平期间SDA信号线上产生了下降沿标志着的一次数据传输的开始。开始信号应当由主机发起。
  3. 数据传输,在SCL同步控制下SDA串行的传送每一位,因此传送8bits的数据需要8个SCL时钟。SCL为高电平时期SDA电平状态必须稳定;SCL为低电平期间才允许SDA改变状态。
  4. 应答信号,IIC总线上每传送一个8位字节,第9个脉冲期间便会释放总线,由接收器发出一个应答信号,反馈有没有成功接收。
  5. 停止信号,在SCL保持高电平期间,将SDA信号线释放恢复到高电平,标志一次数据传输的结束,IIC总线也重新回到了空闲状态。


计数器控制IIC读写

  在“FPGA基础设计(三):UART串口通信https://blog.csdn.net/FPGADesigner/article/details/75201194”中已经接触到了使用计数器控制时序的方法,这个方法在控制IIC通信时同样实用。一次完整的写入操作如下所示:

     case( i ) 
            0: // iic Start
             begin
                    isOut <= 1;                         //SDA端口输出

                    if( C1 == 0 ) rSCL <= 1'b1;
                    else if( C1 == 200 ) rSCL <= 1'b0;       //SCL由高变低

                    if( C1 == 0 ) rSDA <= 1'b1; 
                    else if( C1 == 100 ) rSDA <= 1'b0;        //SDA先由高变低 

                    if( C1 == 250 -1) begin C1 <= 9'd0; i <= i + 1'b1; end
                    else C1 <= C1 + 1'b1;
             end

             1: // Write Device Addr
             begin rData <= {4'b1010, 3'b000, 1'b0}; i <= 5'd7; Go <= i + 1'b1; end         

             2: // Wirte Word Addr
             begin rData <= Addr_Sig; i <= 5'd7; Go <= i + 1'b1; end

             3: // Write Data
             begin rData <= WrData; i <= 5'd7; Go <= i + 1'b1; end

             4: //iic Stop
             begin
                isOut <= 1'b1;

                if( C1 == 0 ) rSCL <= 1'b0;
                else if( C1 == 50 ) rSCL <= 1'b1;     //SCL先由低变高       

                 if( C1 == 0 ) rSDA <= 1'b0;
                 else if( C1 == 150 ) rSDA <= 1'b1;     //SDA由低变高  

                 if( C1 == 250 -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                 else C1 <= C1 + 1'b1; 
             end

             5:
             begin isDone <= 1'b1; i <= i + 1'b1; end       //写I2C 结束

             6: 
             begin isDone <= 1'b0; i <= 5'd0; end

             7,8,9,10,11,12,13,14:                         //发送Device Addr/Word Addr/Write Data
             begin
                 isOut <= 1'b1;
                  rSDA <= rData[14-i];                      //高位先发送

                  if( C1 == 0 ) rSCL <= 1'b0;
                 else if( C1 == 50 ) rSCL <= 1'b1;
                  else if( C1 == 150 ) rSCL <= 1'b0; 

                  if( C1 == F250K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                  else C1 <= C1 + 1'b1;
             end

             15:                                          // waiting for acknowledge
             begin
                 isOut <= 1'b0;                            //SDA端口改为输入
                 if( C1 == 100 ) isAck <= SDA;

                  if( C1 == 0 ) rSCL <= 1'b0;
                  else if( C1 == 50 ) rSCL <= 1'b1;
                  else if( C1 == 150 ) rSCL <= 1'b0;

                  if( C1 == F250K -1 ) begin C1 <= 9'd0; i <= i + 1'b1; end
                  else C1 <= C1 + 1'b1; 
             end

             16:
             if( isAck != 0 ) i <= 5'd0;
             else i <= Go; 

            endcase

  向IIC总线写数据时,需要依次写入待写入的设备写地址、设备中的写地址和待写入的数据共3个8bits字节数据。i代表总线上不同的状态,通过计数器来控制状态之间的跳转。i为0时发出开始信号;i为7~14时控制8bits数据的发送;i为1、2、3时分别为设备地址、字节地址和数据,依次调用7-14完成数据的传输;其余还有停止位、应答位、IIC通信完成置位等状态。
  从器件中读取数据的方法与此一样,只不过通常都需要先向IIC总线写入待读取的设备地址和器件地址,之后再读数据。读数据整体过程比写数据要麻烦一点,但只要控制好状态之间跳转的过程即可。


分频时钟控制IIC读写

  由计数器控制通信时序的方法优点是很灵活,几乎所有的时序方法都可以用这种方法完成;缺点就是太麻烦,需要控制好状态之间的跳转,时序越复杂使用越麻烦,其实在“FPGA综合系统设计(二):基于FPGA的温度采集和以太网传输https://blog.csdn.net/FPGADesigner/article/details/73974357”中,我对DS18B20的时序控制就是采用计数器控制的方法。DS18B20的时序要求较多,因此其中的状态跳转已经相当复杂。

  其实在控制IIC这种时钟速率固定的串行协议时,还可以在外部分频或PLL生成一个低频的通信时钟,用这个时钟来控制数据传输过程。如下所示:

    always@(posedge clock_i2c)
    begin
       if(reset_n==1'b0) begin
          tr_end<=0;
          ack1<=1;
          ack2<=1;
          ack3<=1;
          sclk<=1;
          reg_sdat<=1;
       end
       else
          case(cyc_count)
          0:begin ack1<=1;ack2<=1;tr_end<=0;sclk<=1;reg_sdat<=1;end
          1:reg_sdat<=0;                 //开始传输
          2:sclk<=0;
          3:reg_sdat<=i2c_data[23];          
          4:reg_sdat<=i2c_data[22];
          5:reg_sdat<=i2c_data[21];
          6:reg_sdat<=i2c_data[20];
          7:reg_sdat<=i2c_data[19];
          8:reg_sdat<=i2c_data[18];
          9:reg_sdat<=i2c_data[17];
          10:reg_sdat<=i2c_data[16];
          11:reg_sdat<=1;                //应答信号       
          12:begin reg_sdat<=i2c_data[15];ack1<=i2c_sdat;end
          13:reg_sdat<=i2c_data[14];
          14:reg_sdat<=i2c_data[13];
          15:reg_sdat<=i2c_data[12];
          16:reg_sdat<=i2c_data[11];
          17:reg_sdat<=i2c_data[10];
          18:reg_sdat<=i2c_data[9];
          19:reg_sdat<=i2c_data[8];
          20:reg_sdat<=1;                //应答信号       
          21:begin reg_sdat<=i2c_data[7];ack2<=i2c_sdat;end
          22:reg_sdat<=i2c_data[6];
          23:reg_sdat<=i2c_data[5];
          24:reg_sdat<=i2c_data[4];
          25:reg_sdat<=i2c_data[3];
          26:reg_sdat<=i2c_data[2];
          27:reg_sdat<=i2c_data[1];
          28:reg_sdat<=i2c_data[0];
          29:reg_sdat<=1;                //应答信号       
          30:begin ack3<=i2c_sdat;sclk<=0;reg_sdat<=0;end
          31:sclk<=1;
          32:begin reg_sdat<=1;tr_end<=1;end             //IIC传输结束
          endcase

  可以看到这个always块的敏感目标为分频后的IIC时钟信号clock_i2c,整个传输过程一目了然。这两种控制时序的方法不仅适合于IIC协议,还适合于其它的串行协议,在后面SPI协议的设计中仍将使用这种方法。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值