用Verilog代码实现一个简易的I2C从机接口

I2C总线用于连接多个芯片,特别是在FPGA/CPLD中使用更是非常简便。

I2C项目

  1. I2C概述
  2. 一个例子I2C  slave(方法1)
     
  3. I2C slave 实例(方法2)
  4. I2C主机的一个例子
  5. 逻辑分析器,用于捕获实时I2C事务并在总线上进行监视。
  6. .还没准备好

链接

  • The I2C specification.
  • An application note from Philips discussing in depth multiple aspects of I2C.
  • An I2C FAQ page.
  • A page about the differences between I2C and SMBus (the SMBus was developed by Intel and is mostly compatible with I2C)

 

I2C概述

 

I2C总线特性

  • 除了电源和地线外,只使用两根线缆(命名为“sda”和“scl”)。
  • 可以在同一总线上支持100多个设备(总线上的每个设备都有一个可单独访问的地址)
  • 多主机(例如,两个CPU可以轻松地共享相同的I2C设备)
  • 行业标准(飞利浦提出,为许多其他制造商所采用)
  • 到处使用(电视,个人电脑.),但相对较慢(100 Kbps基本速度,扩展可达3.4Mbps)
  • 不是即插即用

它是如何工作的

I2C总线至少需要I2C  master和I2C  slave。

I2C  master是事务发起者(master主设备可以从slave从设备写入或读取)。

I2C  slave是事务接收方(slave从设备可以被master主设备写入或读取)。

I2C波形

这是总线上的样子。

这是对地址0x51的EEPROM的写入,有2个数据字节0X50和0x0F。

 

I2C事务以“start”条件开始,然后依次是我们希望与之通信的从设备的地址,读或写标志,写或读的数据,最后是“stop”。

还有其他细节,比如在每个字节传输后需要有一个acknowledge位,见上面的波形。

 

I2C  slave(方法1)

 

在FPGA或CPLD中创建I2C从机有两种方法。

  1. 在您的FPGA/CPLD中直接使用SCL线作为时钟信号
  2. 使用高速时钟对SDA和SCL信号进行过采样

第一种方法允许创建紧凑的设计。但它不像第二种方法那么可靠。

I2C slave:IO扩展,使用方法1(SCL作为FPGA/CPLD中的时钟)

这是我们的IO扩展程序的一个视图。

I2C从模块连接到可从I2C总线读取和写入的小型8位存储器。8位还在FPGA/CPLD外输

出.它创建了一个I2C IO扩展程序。

首先是模块声明。

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

然后,7位地址,我们想为我们的I2C  slave。

parameter I2C_ADR = 7'h27;

然后是启动和停止条件检测逻辑。

// 我们使用带有组合回路的两根线来检测启动和停止条件
//  ... 确保这两根电线没有被优化掉
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;

现在我们已经准备好计算I2C到来的位数了。

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 

并检测I2C地址是否与我们自己的地址匹配。

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

必要时驱动SDA线路。

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 FPGA、Altera FPGA/CPLD)上进行了测试。

完整的代码是可用的。这里.

此代码有两个缺点:

  • FPGA/CPLD采用SCL信号作为时钟。强烈建议使用一个施密特触发器SCL输入引脚上避免发生异常(如果没有Schmitt触发器,SCL线路上的任何噪声或毛刺都会引入额外的时钟周期,从而破坏功能)
  • 开始和停止条件检测逻辑使用组合反馈循环,这不是推荐的做法。在“InCycle”信号上也是一样的,它被逻辑的其余部分用作异步重置。这是为了避免使用过采样时钟而付出的代价。

如果你能忍受这些缺点,你会得到一个非常紧凑的I2C从设计。

否则,使用外部时钟过采样SDA和SCL(方法2)。通过这种方式,可以使用数字滤波器过滤出故障,并且启动和停止条件可以很容易地检测到(以更复杂的设计为代价)。

你可能感兴趣的:(通信接口)