I2C是一种简单地连接多个芯片的总线方式,尤其是在FPGAs/CPLDs中.

概述

  • Uses only 2 wires (named “SDA” and “SCL”) in addition to power and ground
  • Can support over 100 devices on the same bus (each device on the bus has an address to be individually accessible)
  • Multi-master (for example, two CPUs can easily share the same I2C devices)
  • Industry standard (developed by Philips, adopted by many other manufacturers)
  • Used everywhere (TVs, PCs…)

但是:

  • Relatively slow (100Kbps base speed, with extensions up to 3.4Mbps)
  • Not plug-and-play

An I2C bus needs at a minimum an I2C master and an I2C slave. The I2C master is a transaction (a master can write-to or read-from a slave). The I2C slave is a transaction recipient (a slave can be written-to or read-from a master).

Here's how it looks on the bus. This a write to an EEPROM at address 0x51, with 2 data bytes 0x50 and 0x0F.

initiator

An I2C transaction begins with a “start” condition, followed by the address of the device we wish to speak to, a bit to indicate if we want to read or write, the data written or read, and finally a “stop”. There are other details, like the need to have an “acknowledge” bit after each byte transmitted… see the waveform above, and the project's links.

在FPGA或CPLD中有两种方式创建一个I2C slave

  • 直接使用你FPGA/CPLD中的SCL信号线作为时钟信号
  • 使用更快的时钟信号过取样你的SDA和SCL信号

第一种方法设计比较紧凑,但不如第二种方法可靠。

I2C从模式示例1

I2C slave示例1: 采用方法1进行IO扩展 - SCL在FPGA/CPLD中作为时钟信号

先来看一下我们的IO扩展

I2C从模块连接到一个小的8位存储器,这个存储器可以通过I2C总线进行读写,这8位为FPGA/CPLD的外部连线,这样就实现了一个I2C的IO端口扩展

第一步,先定义模块

module I2CslaveWith8bitsIO(SDA, SCL, IOout);
inout SDA;
input SCL;
output [7:0] IOout;

Then the 7-bits address that we want for our I2C slave.

parameter I2C_ADR = 7'h27;

Then the start and stop conditions detection logic. That's the “black magic” part of this design…

// We use two wires with a combinatorial loop to detect the start and stop conditions
//  ... making sure these two wires don't get optimized away
wire SDA_shadow    /* synthesis keep = 1 */;
wire start_or_stop /* synthesis keep = 1 */;
assign SDA_shadow = (~SCL | start_or_stop) ? SDA : SDA_shadow;
assign start_or_stop = ~SCL ? 1'b0 : (SDA ^ SDA_shadow);
 
reg incycle;
always @(negedge SCL or posedge start_or_stop)
if(start_or_stop) incycle <= 1'b0; else if(~SDA) incycle <= 1'b1;

Now we are ready to count the I2C bits coming in

reg [3:0] bitcnt;  // counts the I2C bits from 7 downto 0, plus an ACK bit
wire bit_DATA = ~bitcnt[3];  // the DATA bits are the first 8 bits sent
wire bit_ACK = bitcnt[3];  // the ACK bit is the 9th bit sent
reg data_phase;
 
always @(negedge SCL or negedge incycle)
if(~incycle)
begin
    bitcnt <= 4'h7;  // the bit 7 is received first
    data_phase <= 0;
end
else
begin
    if(bit_ACK)
    begin
    	bitcnt <= 4'h7;
    	data_phase <= 1;
    end
    else
    	bitcnt <= bitcnt - 4'h1;
end

and detect if the I2C address matches our own

wire adr_phase = ~data_phase;
reg adr_match, op_read, got_ACK;
// sample SDA on posedge since the I2C spec specifies as low as 0µs hold-time on negedge
reg SDAr;  always @(posedge SCL) SDAr<=SDA;
reg [7:0] mem;
wire op_write = ~op_read;
 
always @(negedge SCL or negedge incycle)
if(~incycle)
begin
    got_ACK <= 0;
    adr_match <= 1;
    op_read <= 0;
end
else
begin
    if(adr_phase & bitcnt==7 & SDAr!=I2C_ADR[6]) adr_match<=0;
    if(adr_phase & bitcnt==6 & SDAr!=I2C_ADR[5]) adr_match<=0;
    if(adr_phase & bitcnt==5 & SDAr!=I2C_ADR[4]) adr_match<=0;
    if(adr_phase & bitcnt==4 & SDAr!=I2C_ADR[3]) adr_match<=0;
    if(adr_phase & bitcnt==3 & SDAr!=I2C_ADR[2]) adr_match<=0;
    if(adr_phase & bitcnt==2 & SDAr!=I2C_ADR[1]) adr_match<=0;
    if(adr_phase & bitcnt==1 & SDAr!=I2C_ADR[0]) adr_match<=0;
    if(adr_phase & bitcnt==0) op_read <= SDAr;
    // we monitor the ACK to be able to free the bus when the master doesn't ACK during a read operation
    if(bit_ACK) got_ACK <= ~SDAr;
 
    if(adr_match & bit_DATA & data_phase & op_write) mem[bitcnt] <= SDAr;  // memory write
end

and drive the SDA line when necessary.

wire mem_bit_low = ~mem[bitcnt[2:0]];
wire SDA_assert_low = adr_match & bit_DATA & data_phase & op_read & mem_bit_low & got_ACK;
wire SDA_assert_ACK = adr_match & bit_ACK & (adr_phase | op_write);
wire SDA_low = SDA_assert_low | SDA_assert_ACK;
assign SDA = SDA_low ? 1'b0 : 1'bz;
 
assign IOout = mem;
endmodule

结果如何?

此代码已经在Xilinx和Altera的多个器件上进行过测试,能够同硬化的I2C主进行通信. 在这里可以下载完整的代码

此代码有两个缺点::

  • 在FPGA/CPLD中SCL被用做了时钟信号,强烈建议在SCL输入端加入施密特触发器以避免错误的行为发生,如果不加施密特触发器,在SCL线上的任何噪声或振铃都会导致多余的时钟周期,导致功能失效
  • “起始”和“终止”的条件检查逻辑采用的是组合反馈环路,这种方式是不建议的。同样其它电路的异步复位也不建议采用“周期内信号”

If you can live with these drawbacks, you get a very compact I2C slave design.

Otherwise, use an external clock to oversample SDA and SCL (method 2). This way, glitches can be filtered out using digital filters, and the start and stop condition detected easily (at the price of a more complex design).

External contributions

Contributed by an fpga4fun readers… I2C simulation in Verilog I2C slave in VHDL

I2C从模式示例2

An example of I2C master

A logic analyzer, to capture live I2C transaction and spy on the bus

…not ready yet

资源链接