显示页面 讨论 修订记录 反向链接 本页面只读。您可以查看源文件,但不能更改它。如果您觉得这是系统错误,请联系管理员。 I2C是一种简单地连接多个芯片的总线方式,尤其是在[[FPGA]]s/[[CPLD]]s中. {{ ::i2cslave.gif |}} ====== 概述 ====== ===== I2C总线特性 ===== * 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). ===== I2C波型 ===== Here's how it looks on the bus. This a write to an EEPROM at address 0x51, with 2 data bytes 0x50 and 0x0F. {{ ::i2c_writetoeeprom.gif |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从功能: * 直接使用你FPGA/CPLD中的SCL信号线作为时钟信号 * 使用更快的时钟信号过取样你的SDA和SCL信号 第一种方法设计比较紧凑,但不如第二种方法可靠,在这里我们简单讲一下第一种方法的实现过程。 目标:通过I2C进行IO端口扩展,SCL在FPGA/CPLD中作为时钟信号 {{ ::i2cslave.gif |}} I2C从模块连接到一个小的8位存储器,这个存储器可以通过I2C总线进行读写,这8位为FPGA/CPLD的外部连线,这样就实现了一个I2C的IO端口扩展 第一步,先定义模块 <code verilog> module I2CslaveWith8bitsIO(SDA, SCL, IOout); inout SDA; input SCL; output [7:0] IOout; </code> Then the 7-bits address that we want for our I2C slave. <code verilog> parameter I2C_ADR = 7'h27; </code> Then the start and stop conditions detection logic. That's the "black magic" part of this design... <code verilog> // 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; </code> Now we are ready to count the I2C bits coming in <code verilog> 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 </code> and detect if the I2C address matches our own <code verilog> 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 </code> and drive the SDA line when necessary. <code verilog> 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 </code> 结果如何? 此代码已经在Xilinx和Altera的多个器件上进行过测试,能够同硬化的I2C主进行通信,在这里可以下载[[http://www.fpga4fun.com/files/I2Cslave1.zip|完整的代码]] 不过此代码有两个缺点:: * 在FPGA/CPLD中SCL被用做了时钟信号,强烈建议在SCL输入端加入施密特触发器以避免错误的行为发生,如果不加施密特触发器,在SCL线上的任何噪声或振铃都会导致多余的时钟周期,导致功能失效 * “起始”和“终止”的条件检查逻辑采用的是组合反馈环路,这种方式是不建议的。同样其它电路的异步复位也不建议采用“周期内信号” 如果你能够容忍这些缺点,这应该是I2C从模式非常简洁的设计,否则你只能用外部时钟对SDA和SCL进行过取样,通过数字滤波器将毛刺给滤除掉,“起始”和“终止”的检测也变得比较容易,当然代价就是设计变得更复杂。 ====== 资源链接 ====== * [[http://www.nxp.com/documents/user_manual/UM10204.pdf|I2C规范]]. * Philips多方面深度讨论I2C的[[http://www.nxp.com/documents/application_note/AN10216.pdf|应用指南]] * [[http://www.esacademy.com/faq/i2c/|I2C常见问题]] * [[http://www.totalphase.com/support/kb/10040/|关于I2C和SMBus区别的文章]](SMBus是由Intel公司开发的,基本跟I2C兼容) 更多信息参见[[wikipedia上关于i2c的介绍]]