基礎知識之I2C總線
—— 基礎知識之串行總線
I2C - 集成電路之間的同步、半雙工數據傳輸
I2C總線特性
但是:
一個I2C總線需要至少一個I2C主和I2C從. I2C“主”即可以向“從”寫也可以從“從”設備中讀取
I2C波形
這個圖示為向地址為0x51的EEPROM進行寫2個字節的數據0x50和0x0F.
一個I2C過程由“起始”開始, 接著是我們要通信的設備的地址,有一位標記此操作是“讀”還是“寫”;要讀取或寫入的“數據”,最后是個“終止”位。
還有其它的一些細節,比如在每個字節傳輸以后需要一個”應答“位,參看波形圖。
在FPGA或CPLD中有兩種方式創建一個I2C從功能:
第一種方法設計比較緊湊,但不如第二種方法可靠,在這里我們簡單講一下第一種方法的實現過程。
目標:通過I2C進行IO端口擴展,SCL在FPGA/CPLD中作為時鐘信號
I2C從模塊連接到一個小的8位存儲器,這個存儲器可以通過I2C總線進行讀寫,這8位為FPGA/CPLD的外部連線,這樣就實現了一個I2C的IO端口擴展
第一步,先定義模塊
module I2CslaveWith8bitsIO(SDA, SCL, IOout);inout SDA;input SCL;output [7:0] IOout;
接著是我們需要的I2C“從”設備的7位地址
parameter I2C_ADR = 7'h27;
接著是“起始”和“終止”檢測邏輯,這也是本設計中最神秘的部分。
// We use two wires with a combinatorial loop to detect the start and stop conditions// ... making sure these two wires don't get optimized awaywire 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 bitwire bit_DATA = ~bitcnt[3]; // the DATA bits are the first 8 bits sentwire bit_ACK = bitcnt[3]; // the ACK bit is the 9th bit sentreg data_phase; always @(negedge SCL or negedge incycle)if(~incycle)begin bitcnt <= 4'h7; // the bit 7 is received first data_phase <= 0;endelsebegin 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 negedgereg 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;endelsebegin 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 writeend
如有需要驅動=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和Altera的多個器件上進行過測試,能夠同硬化的I2C主進行通信,在這里可以下載完整的代碼
不過此代碼有兩個缺點::
如果你能夠容忍這些缺點,這應該是I2C從模式非常簡潔的設計,否則你只能用外部時鐘對SDA和SCL進行過取樣,通過數字濾波器將毛刺給濾除掉,“起始”和“終止”的檢測也變得比較容易,當然代價就是設計變得更復雜。
評論