Inter-IntegratedCircuit(集成電路總線)
IIC是一種多向控制總線,由飛利浦半導體公司在八十度年代初設計,主要是用來連接整體電路(ICS)。在IIC中多個芯片可以連接到同一總線結構下,同時每個芯片都可以作爲實施數據傳輸的控制源,這種方式簡化了信號傳容輸總線。
-
IIC的物理層
典型電路如下:
-
IIC協議層
每當主機向從機發送完一個字節的數據,主機總是需要等待從機給出一個應答信號,以確認從機是否成功接收到了數據,從機應答主機所需要的時鐘仍是主機提供的,應答出現在每一次主機完成8個數據位傳輸後緊跟着的時鐘週期,低電平0表示應答,1表示非應答。 -
數據幀格式
每次數據傳送總是由主機產生的終止信號結束。但是,若主機希望繼續佔用總線進行新的數據傳送,則可以不產生終止信號,馬上再次發出起始信號對另一從機進行尋址。
-
傳輸信號時序圖及時序參數
-
AT24CXX系列的介紹
(1)寫操作
AT24CXX系列的EEPROM爲了提高寫效率,提供了頁寫功能,內部有個一頁大小的寫緩衝RAM,地址範圍當然就是從00到一頁大小,發生寫操作時,開始送入的地址對應的頁被選中,並將其內容映像到緩衝RAM,數據從低端地址對應的緩衝RAM地址開始修改,超過這個地址範圍就回到00,寫完後,就會把開始確定的EEPROM頁擦除,再把一整頁RAM數據寫入。所有寫數據都發生在開始寫地址時確定的頁上。
24C01、24C02 這兩個型號是 8 個字節一個頁,而 24C04、24C08、24C16 是 16 個字節一頁。以頁容量128爲例,一頁都是從00開始按128字節分成一個個的頁,0頁就是0—7F,1頁就是80—FF,類推,邊界就是128字節的整數倍地址。頁RAM的地址範圍爲7位00—7F,寫入時高端地址就是頁號。發生寫操作,開始送入的地址對應的頁被鎖存,後續不論寫多少,都在這個頁中,只是一個頁內的地址進行加一,超過就歸零開始。從F0開始寫32個字節,那麼開始送入的地址爲F0,就會鎖定在1號頁(第2個頁)上,底端7位頁內部地址開始從70H開始寫,到達7F時回到00再到10H,也就是寫在了F0—FF,80—8F。也就是,從01開始寫也只能到7F,再往80寫就跑到00上去了,這就是寫操作的翻卷,datasheet上都有說明。就是從邊界前寫兩個字節也要分兩次寫。頁是絕對的,按整頁大小排列,不是從開始寫入的地址開始算。
(2)讀操作
讀沒有頁的問題,可以從任意地址開始讀取任意大小數據,只是超過整個存儲器容量時地址纔回卷。但一次性訪問的數據長度也不要太大。 -
Verliog代碼設計
在正點原子的三段式IIC驅動代碼的基礎上進行修改,實現對AT24C64的多字節讀寫操作。這裏需要格外注意的有兩點:
(1)寫操作完成最後一個字節數據後是從機產生應答;讀操作完成最後一個字節數據後是主機產生應答。
(2)分頁的存儲器要做好存儲器管理,儘量將同時讀寫的數據放在一個頁上。
代碼如下:`timescale 1ns/1ps module iic_driver #(// slave address(器件地址),放此處方便參數傳遞 parameter SLAVE_ADDR = 7'b1010000 , parameter CLK_FREQ = 26'd50_000_000, // iic_dri模塊的驅動時鐘頻率(CLK_FREQ) parameter IIC_FREQ = 18'd250_000 // IIC的SCL時鐘頻率 ) ( input CLK, // iic_dri模塊的驅動時鐘(CLK_FREQ) input RST_N, // 復位信號 input iic_exec, // IIC觸發執行信號 input bit_ctrl, // 字地址位控制(16b/8b) input iic_rh_wl, // IIC讀寫控制信號(1:read 0:write) input [15:0] iic_addr, // IIC器件內地址 input [ 7:0] iic_data_w, // IIC要寫的數據 output reg [ 7:0] iic_data_r, // IIC讀出的數據 output wire done, // IIC一次操作完成 output reg SCL, // IIC的SCL時鐘信號 inout SDA, // IIC的SDA信號 input [ 7:0] wdata_num, // IIC要寫數據的個數 input [ 7:0] rdata_num, // IIC要讀數據的個數 output wire wr_data_vaild, // IIC寫入數據有效 output wire rd_data_vaild, // IIC讀出數據有效 output reg dri_clk // 驅動IIC操作的驅動時鐘 ); //localparam define localparam st_idle = 8'b0000_0001; // 空閒狀態 1 localparam st_sladdr = 8'b0000_0010; // 發送器件地址(slave address) 2 localparam st_addr16 = 8'b0000_0100; // 發送16位字地址 4 localparam st_addr8 = 8'b0000_1000; // 發送8位字地址 8 localparam st_data_wr = 8'b0001_0000; // 寫數據(8 bit) 16 localparam st_addr_rd = 8'b0010_0000; // 發送器件地址讀 32 localparam st_data_rd = 8'b0100_0000; // 讀數據(8 bit) 64 localparam st_stop = 8'b1000_0000; // 結束IIC操作 128 //reg define reg sda_dir; // I2C數據(SDA)方向控制 reg sda_out; // SDA輸出信號 reg st_done; // 狀態結束 reg iic_exec_r; // 起始信號寄存器 reg wr_flag; // 寫標誌 reg [6:0] cnt; // 計數 reg [7:0] cur_state; // 狀態機當前狀態 reg [7:0] next_state; // 狀態機下一狀態 reg [15:0] addr_t; // 地址 reg [7:0] data_r; // 讀取的數據 reg [9:0] clk_cnt; // 分頻時鐘計數 reg ack; // SDA應答 reg [7:0] wdata_cnt; // 寫數據計數器 reg [7:0] rdata_cnt; // 讀數據計數器 reg [1:0] wr_data_vaild_r; // IIC寫入數據有效 reg [1:0] rd_data_vaild_r; // IIC寫入數據有效 reg iic_done; reg [1:0] iic_done_r; //wire define wire sda_in; // SDA輸入信號 wire [7:0] data_wr_t; // IIC需寫的數據保存 wire [8:0] clk_divide; // 模塊驅動時鐘的分頻係數 //SDA控制 assign SDA = sda_dir ? (sda_out ? 1'bz :1'b0 ) : 1'bz; assign sda_in = SDA ; // SDA數據輸入 assign clk_divide = (CLK_FREQ/IIC_FREQ) >> 3; // 模塊驅動時鐘的分頻係數 //寄存1個時鐘週期的起始信號 always @(posedge CLK or negedge RST_N) begin if(!RST_N) iic_exec_r <= 1'b0; else if(iic_exec) iic_exec_r <= 1'b1; else if(iic_done) iic_exec_r <= 1'b0; else iic_exec_r <= iic_exec_r; end //生成IIC的SCL的四倍頻率(1M)的驅動時鐘用於驅動i2c的操作 always @(posedge CLK or negedge RST_N) begin if(!RST_N) begin dri_clk <= 1'b1; clk_cnt <= 10'd0; end else if(clk_cnt == clk_divide - 1'd1) // F=50M/(25*2)=1M begin clk_cnt <= 10'd0; dri_clk <= ~dri_clk; end else clk_cnt <= clk_cnt + 1'b1; end //(三段式狀態機)同步時序描述狀態轉移 always @(posedge dri_clk or negedge RST_N) begin if(!RST_N) cur_state <= st_idle; else cur_state <= next_state; end //組合邏輯判斷狀態轉移條件 always @( * ) begin case(cur_state) st_idle: begin // 空閒狀態 if(iic_exec_r) begin next_state = st_sladdr; end else next_state = st_idle; end st_sladdr: begin if(st_done) begin if(bit_ctrl) // 判斷是16位還是8位字地址 next_state = st_addr16; else next_state = st_addr8 ; end else next_state = st_sladdr; end st_addr16: begin // 寫16位字地址 if(st_done) begin next_state = st_addr8; end else begin next_state = st_addr16; end end st_addr8: begin // 8位字地址 if(st_done) begin if(wr_flag==1'b0) // 讀寫判斷 next_state = st_data_wr; else next_state = st_addr_rd; end else begin next_state = st_addr8; end end st_data_wr: begin // 寫數據(8 bit) if(st_done) begin next_state = st_stop; end else next_state = st_data_wr; end st_addr_rd: begin // 寫地址以進行讀數據 if(st_done) begin next_state = st_data_rd; end else next_state = st_addr_rd; end st_data_rd: begin // 讀取數據(8 bit) if(st_done) next_state = st_stop; else next_state = st_data_rd; end st_stop: begin // 結束IIC操作 if(st_done) next_state = st_idle; else next_state = st_stop ; end default: next_state = st_idle; endcase end //時序電路描述狀態輸出 always @(posedge dri_clk or negedge RST_N) begin //復位初始化 if(!RST_N) begin SCL <= 1'b1; sda_out <= 1'b1; sda_dir <= 1'b1; iic_done <= 1'b0; cnt <= 1'b0; st_done <= 1'b0; data_r <= 1'b0; iic_data_r<= 1'b0; wr_flag <= 1'b0; addr_t <= 1'b0; ack <= 1'b1; wdata_cnt <= 1'b0; rdata_cnt <= 1'b0; end else begin st_done <= 1'b0 ; cnt <= cnt +1'b1 ; case(cur_state) st_idle: begin // 空閒狀態 SCL <= 1'b1; sda_out <= 1'b1; sda_dir <= 1'b1; iic_done<= 1'b0; cnt <= 7'b0; if(iic_exec_r) begin wr_flag <= iic_rh_wl ; addr_t <= iic_addr ; end end st_sladdr: begin // 寫地址(器件地址和字地址) case(cnt) 7'd1 : sda_out <= 1'b0; // 開始IIC 起始位 7'd3 : SCL <= 1'b0; 7'd4 : sda_out <= SLAVE_ADDR[6]; // 傳送器件地址 SCL低電平中間改變數據 7'd5 : SCL <= 1'b1; 7'd7 : SCL <= 1'b0; 7'd8 : sda_out <= SLAVE_ADDR[5]; 7'd9 : SCL <= 1'b1; 7'd11: SCL <= 1'b0; 7'd12: sda_out <= SLAVE_ADDR[4]; 7'd13: SCL <= 1'b1; 7'd15: SCL <= 1'b0; 7'd16: sda_out <= SLAVE_ADDR[3]; 7'd17: SCL <= 1'b1; 7'd19: SCL <= 1'b0; 7'd20: sda_out <= SLAVE_ADDR[2]; 7'd21: SCL <= 1'b1; 7'd23: SCL <= 1'b0; 7'd24: sda_out <= SLAVE_ADDR[1]; 7'd25: SCL <= 1'b1; 7'd27: SCL <= 1'b0; 7'd28: sda_out <= SLAVE_ADDR[0]; 7'd29: SCL <= 1'b1; 7'd31: SCL <= 1'b0; 7'd32: sda_out <= 1'b0; // 0:寫 7'd33: SCL <= 1'b1; 7'd35: SCL <= 1'b0; 7'd36: begin sda_dir <= 1'b0; ack <= sda_in; // 從機應答 end 7'd37: SCL <= 1'b1; 7'd38: st_done <= 1'b1; 7'd39: begin SCL <= 1'b0; cnt <= 1'b0; end default : ; endcase end st_addr16: begin case(cnt) 7'd0 : begin sda_dir <= 1'b1; sda_out <= addr_t[15]; // 傳送字地址 end 7'd1 : SCL <= 1'b1; 7'd3 : SCL <= 1'b0; 7'd4 : sda_out <= addr_t[14]; 7'd5 : SCL <= 1'b1; 7'd7 : SCL <= 1'b0; 7'd8 : sda_out <= addr_t[13]; 7'd9 : SCL <= 1'b1; 7'd11: SCL <= 1'b0; 7'd12: sda_out <= addr_t[12]; 7'd13: SCL <= 1'b1; 7'd15: SCL <= 1'b0; 7'd16: sda_out <= addr_t[11]; 7'd17: SCL <= 1'b1; 7'd19: SCL <= 1'b0; 7'd20: sda_out <= addr_t[10]; 7'd21: SCL <= 1'b1; 7'd23: SCL <= 1'b0; 7'd24: sda_out <= addr_t[9]; 7'd25: SCL <= 1'b1; 7'd27: SCL <= 1'b0; 7'd28: sda_out <= addr_t[8]; 7'd29: SCL <= 1'b1; 7'd31: SCL <= 1'b0; 7'd32: begin sda_dir <= 1'b0; ack <= sda_in ; // 從機應答 end 7'd33: SCL <= 1'b1; 7'd34: st_done <= 1'b1; 7'd35: begin SCL <= 1'b0; cnt <= 1'b0; end default : ; endcase end st_addr8: begin case(cnt) 7'd0: begin sda_dir <= 1'b1; sda_out <= addr_t[7]; // 字地址 end 7'd1 : SCL <= 1'b1; 7'd3 : SCL <= 1'b0; 7'd4 : sda_out <= addr_t[6]; 7'd5 : SCL <= 1'b1; 7'd7 : SCL <= 1'b0; 7'd8 : sda_out <= addr_t[5]; 7'd9 : SCL <= 1'b1; 7'd11: SCL <= 1'b0; 7'd12: sda_out <= addr_t[4]; 7'd13: SCL <= 1'b1; 7'd15: SCL <= 1'b0; 7'd16: sda_out <= addr_t[3]; 7'd17: SCL <= 1'b1; 7'd19: SCL <= 1'b0; 7'd20: sda_out <= addr_t[2]; 7'd21: SCL <= 1'b1; 7'd23: SCL <= 1'b0; 7'd24: sda_out <= addr_t[1]; 7'd25: SCL <= 1'b1; 7'd27: SCL <= 1'b0; 7'd28: sda_out <= addr_t[0]; 7'd29: SCL <= 1'b1; 7'd31: SCL <= 1'b0; 7'd32: begin sda_dir <= 1'b0; ack <= sda_in ; // 從機應答 end 7'd33: SCL <= 1'b1; 7'd34: st_done <= 1'b1; 7'd35: begin SCL <= 1'b0; cnt <= 1'b0; end default : ; endcase end st_data_wr: begin // 寫數據(8 bit) case(cnt) 7'd0 :begin sda_dir <= 1'b1; sda_out <= iic_data_w[7]; end 7'd1 : SCL <= 1'b1; 7'd3 : SCL <= 1'b0; 7'd4 : sda_out <= iic_data_w[6]; 7'd5 : SCL <= 1'b1; 7'd7 : SCL <= 1'b0; 7'd8 : sda_out <= iic_data_w[5]; 7'd9 : SCL <= 1'b1; 7'd11: SCL <= 1'b0; 7'd12: sda_out <= iic_data_w[4]; 7'd13: SCL <= 1'b1; 7'd15: SCL <= 1'b0; 7'd16: sda_out <= iic_data_w[3]; 7'd17: SCL <= 1'b1; 7'd19: SCL <= 1'b0; 7'd20: sda_out <= iic_data_w[2]; 7'd21: SCL <= 1'b1; 7'd23: SCL <= 1'b0; 7'd24: sda_out <= iic_data_w[1]; 7'd25: SCL <= 1'b1; 7'd27: SCL <= 1'b0; 7'd28: sda_out <= iic_data_w[0]; 7'd29: SCL <= 1'b1; 7'd31: SCL <= 1'b0; 7'd32: begin sda_dir <= 1'b0; ack <= sda_in ; // 從機應答 wdata_cnt <= wdata_cnt + 1; end 7'd33: SCL <= 1'b1; 7'd34: begin if(wdata_cnt == wdata_num)begin st_done <= 1'b1; wdata_cnt <= 0; end end 7'd35: begin SCL <= 1'b0; cnt <= 1'b0; end default:; endcase end st_addr_rd: begin // 寫地址以進行讀數據 case(cnt) 7'd0 : begin sda_dir <= 1'b1; sda_out <= 1'b1; end 7'd1 : SCL <= 1'b1; 7'd2 : sda_out <= 1'b0; // 重新開始 7'd3 : SCL <= 1'b0; 7'd4 : sda_out <= SLAVE_ADDR[6]; // 傳送器件地址 7'd5 : SCL <= 1'b1; 7'd7 : SCL <= 1'b0; 7'd8 : sda_out <= SLAVE_ADDR[5]; 7'd9 : SCL <= 1'b1; 7'd11: SCL <= 1'b0; 7'd12: sda_out <= SLAVE_ADDR[4]; 7'd13: SCL <= 1'b1; 7'd15: SCL <= 1'b0; 7'd16: sda_out <= SLAVE_ADDR[3]; 7'd17: SCL <= 1'b1; 7'd19: SCL <= 1'b0; 7'd20: sda_out <= SLAVE_ADDR[2]; 7'd21: SCL <= 1'b1; 7'd23: SCL <= 1'b0; 7'd24: sda_out <= SLAVE_ADDR[1]; 7'd25: SCL <= 1'b1; 7'd27: SCL <= 1'b0; 7'd28: sda_out <= SLAVE_ADDR[0]; 7'd29: SCL <= 1'b1; 7'd31: SCL <= 1'b0; 7'd32: sda_out <= 1'b1; // 1:讀 7'd33: SCL <= 1'b1; 7'd35: SCL <= 1'b0; 7'd36: begin sda_dir <= 1'b0; ack <= sda_in ; // 從機應答 end 7'd37: SCL <= 1'b1; 7'd38: st_done <= 1'b1; 7'd39: begin SCL <= 1'b0; cnt <= 1'b0; end default : ; endcase end st_data_rd: begin // 讀取數據(8 bit) case(cnt) 7'd0: sda_dir <= 1'b0; 7'd1: begin SCL <= 1'b1; data_r[7] <= sda_in; end 7'd3: SCL <= 1'b0; 7'd5: begin SCL <= 1'b1; data_r[6] <= sda_in ; end 7'd7: SCL <= 1'b0; 7'd9: begin SCL <= 1'b1; data_r[5] <= sda_in; end 7'd11: SCL <= 1'b0; 7'd13: begin SCL <= 1'b1; data_r[4] <= sda_in; end 7'd15:SCL <= 1'b0; 7'd17: begin SCL <= 1'b1; data_r[3] <= sda_in; end 7'd19: SCL <= 1'b0; 7'd21: begin SCL <= 1'b1; data_r[2] <= sda_in; end 7'd23: SCL <= 1'b0; 7'd25: begin SCL <= 1'b1; data_r[1] <= sda_in; end 7'd27: SCL <= 1'b0; 7'd29: begin SCL <= 1'b1; data_r[0] <= sda_in; end 7'd31: begin SCL <= 1'b0; rdata_cnt <= rdata_cnt + 1; iic_data_r <= data_r; end 7'd32: begin if(rdata_cnt < rdata_num)begin sda_dir <= 1'b1; sda_out <= 1'b0; //應答 end else begin sda_dir <= 1'b1; sda_out <= 1'b1; //非應答 end end 7'd33: SCL <= 1'b1; 7'd35: begin SCL <= 1'b0; if(rdata_cnt < rdata_num) cnt <= 1'b0; end 7'd36: st_done <= 1'b1; 7'd37: begin SCL <= 1'b1; cnt <= 1'b0; if(rdata_cnt == rdata_num) rdata_cnt <= 0; end default : ; endcase end st_stop: begin // 結束IIC操作 case(cnt) 7'd0: begin sda_dir <= 1'b1; // 結束IIC sda_out <= 1'b0; end 7'd1 : SCL <= 1'b1; 7'd3 : sda_out <= 1'b1; 7'd15: st_done <= 1'b1; 7'd16: begin cnt <= 1'b0; iic_done <= 1'b1; // 向上層模塊傳遞IIC結束信號 end default : ; endcase end endcase end end always @(posedge dri_clk or negedge RST_N) begin if(!RST_N) wr_data_vaild_r[0] <= 0; else if(((cur_state == st_data_wr)&&(cnt == 29))||((cur_state == st_addr8)&&(cnt == 29)&&(wr_flag==1'b0))) wr_data_vaild_r[0] <= 1; else wr_data_vaild_r[0] <= 0; end always @(posedge dri_clk or negedge RST_N) begin if(!RST_N) rd_data_vaild_r[0] <= 0; else if((cur_state == st_data_rd)&&(cnt == 31)) rd_data_vaild_r[0] <= 1; else rd_data_vaild_r[0] <= 0; end always @(posedge CLK or negedge RST_N) begin if(!RST_N)begin iic_done_r[0] <= 0; wr_data_vaild_r[1] <= 0; rd_data_vaild_r[1] <= 0; end else begin iic_done_r[0] <= iic_done; wr_data_vaild_r[1] <= wr_data_vaild_r[0]; rd_data_vaild_r[1] <= rd_data_vaild_r[0]; end end assign wr_data_vaild = ({wr_data_vaild_r[1],wr_data_vaild_r[0]}==2'b01); assign rd_data_vaild = ({rd_data_vaild_r[1],rd_data_vaild_r[0]}==2'b01); assign done = ({iic_done_r[0],iic_done}== 2'b01); endmodule