最近遇到一個BUG,跟IIC通訊有關,因此借這個機會總結一下IIC總線協議緩存
1.引腳接口介紹ide
1.A0,A1,A2爲24LC64的片選信號,IIC總線最多能夠掛載8個IIC接口器件,經過對A0,A1,A2尋址,能夠實現對不一樣的EEPROM操做spa
2.WP爲讀寫使能信號,當WP懸空或者接地,EEPROM可讀可寫,當WP接電源,EEPROM只能讀不能寫。由於咱們要對EEPROM寫,因此這裏WP信號懸空code
3.SCL爲時鐘信號線,最高頻率400Khzblog
4.SDA爲數據線,雙向線(inout),當爲in時,數據經過SDA寫到EEPROM。爲out時,eeprom讀出來的數據經過SDA傳到外面接口
2.接口時序input
IIC讀寫時序分爲隨機讀寫和頁讀寫,這裏只研究隨機讀寫flash
2.1 寫時序it
寫操做步驟event
1.發送啓動信號
2.發送控制字寫(1010_A0A1A2_0 )
3.EEPROM發送應答信號ACK
4.發送高字節寫地址
5.EEPROM發送應答信號ACK
6.發送低字節寫地址
7.EEPROM發送應答信號ACK
8.發送8bit寫數據
9.EEPROM發送應答信號ACK
10.發送中止信號
2.2 讀時序
讀操做信號
1.發送啓動信號
2.發送控制字寫(1010_A0A1A2_0)
3.EEPROM發送應答信號ACK
4.發送高字節讀地址
5.EEPROM發送應答信號ACK
6.發送低字節讀地址
7.EEPROM發送應答信號ACK
8.發送啓動信號
9.發送控制字讀(1010_A0A1A2_1)
10.EEPROM發送應答信號ACK
11.讀取一個8bit數據
12..EEPROM發送NO ACK信號
13.發送中止信號
3.操做步驟解析
3.1啓動信號
SCL 保持高電平期間 ,若是 SDA 出現由高到低的跳變,表明啓動信號
3.2控制字
1010_A0A1A2X,
1.1010爲EEPROM信號標識,爲一組固定的序列
2.A0A1A2爲片選信號,因爲只有一個flash,因此A0A1A2在這裏全爲0
3.最後一個bit X,爲0時表明寫,爲1時表明讀。
3.3地址
24LC64表示有64Kbit的存儲空間,須要13位地址線尋址。可是IIC是以字節的實行操做的,因此須要13位地址線擴展成16位,高3位隨意填0或者1,習慣填0
3.4應答信號與非應答信號
應答信號和非應答信號都是由數據接收方(EEPROM)發出的,當SCL爲高電平時候,若是檢測到SDA爲低電平,說明有應答信號。若是檢測到SDA爲高電平,說明有非應答信號。因此在應答時鐘週期的時候,咱們要釋放SDA信號線,讓EEPROM經過SDA發送一個低電平或者高電平過來。
3.5中止信號
SCL 保持高電平期間 ,若是 SDA 出現由低到高的跳變,表明中止信號
3.6 數據傳輸
因爲IIC總線協議的啓動和中止信號都是在SCL高電平期間發生跳變,這就決定了其數據只能在SCL低電平期間發生改變,否則會被當作啓動或者中止信號處理。在SCL爲高電平期間,數據必須保持穩定。即在SCL低電平的時候改變數據,高電平的時候採集數據
4關鍵代碼解析
4.1狀態機設置
4.2 sda信號線控制
因爲sda是inout型,讀寫都是有這根線控制。因此咱們要有一個信號,來指示sda信號線何時寫,何時是讀。
當link_sda信號爲1的時候,指示sda信號寫。這時候咱們把須要寫的數據一個bit一個bit的賦給中間變量sda_buf信號,該信號通過sda信號線把數據寫進flash
當link_sda信號爲0的時候,指示sda信號讀。
完整代碼以下
module iic_control( input wire sclk, input wire reset, input wire key_wr, input wire key_rd, output reg scl, inout wire sda, output wire[7:0] dataout, output reg led ); parameter IDLE = 14'b00_0000_0000_0000, start1 = 14'b00_0000_0000_0001, control_byte1 = 14'b00_0000_0000_0010, ack1 = 14'b00_0000_0000_0100, high_addr_byte = 14'b00_0000_0000_1000, ack2 = 14'b00_0000_0001_0000, low_addr_byte = 14'b00_0000_0010_0000, ack3 = 14'b00_0000_0100_0000, start2 = 14'b00_0000_1000_0000, control_byte2 = 14'b00_0001_0000_0000, ack4 = 14'b00_0010_0000_0000, transfer_data = 14'b00_0100_0000_0000, ack5 = 14'b00_1000_0000_0000, no_ack = 14'b01_0000_0000_0000, stop = 14'b10_0000_0000_0000; reg[13:0] state; reg[6:0] cnt; //分頻計數 reg link_sda; //總線開關 reg sda_buf; //總線數據緩存器 reg wr; //寫使能 reg rd; //讀使能 reg[7:0] data; reg[3:0] cnt_num; reg[7:0] result; assign sda=(link_sda)?sda_buf:1'hz; assign dataout = result; always@(posedge sclk or negedge reset) if(!reset) cnt <= 7'd0; else if(cnt==7'd124) cnt <= 7'd0; else cnt <= cnt + 1'b1; always@(posedge sclk or negedge reset) if(!reset) scl <= 1'b0; else if(cnt==7'd30) scl <= 1'b1; else if(cnt==8'd93) scl <= 1'b0; always@(posedge sclk or negedge reset) if(!reset==1) begin state <= IDLE; link_sda <= 0; sda_buf <= 0; data <= 0; cnt_num <= 4'd0; result <= 8'd0; led <= 1; end else case(state) IDLE: begin if(!key_wr) wr <= 1; if(!key_rd) rd <= 1; if((wr==1)||(rd==1)&&(!scl==1)) begin state <= start1; link_sda <= 1; sda_buf <= 1; data <= 8'b10100000; //寫控制字準備 end end start1: begin if((scl==1)&&(cnt==7'd61)) begin state <= control_byte1; link_sda <= 1; sda_buf <= 0; end end control_byte1: begin if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; sda_buf <= data[7]; data <= {data[6:0],data[7]}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= ack1; link_sda <= 0; cnt_num <= 0; end end ack1: begin // if((scl==1)&&(cnt==7'd61)&&(sda==1'b0)) // begin // state <= high_addr_byte; // data <= 8'b00000000; //高字節地址準備 // link_sda <= 1; // end if((scl==1)&&(cnt==7'd61)) begin state <= high_addr_byte; data <= 8'b00000000; //高字節地址準備 // link_sda <= 1; end end high_addr_byte: begin if(cnt==7'd124) begin link_sda <= 1; end if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; sda_buf <= data[7]; data <= {data[6:0],data[7]}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= ack2; link_sda <= 0; cnt_num <= 0; end end ack2: begin // if((scl==1)&&(cnt==7'd61)&&(sda==1'b0)) // begin // state <= low_addr_byte; // data <= 8'b00000000; //低字節地址準備 // link_sda <= 1; // end if((scl==1)&&(cnt==7'd61)) begin state <= low_addr_byte; data <= 8'b00000000; //低字節地址準備 // link_sda <= 1; end end low_addr_byte: begin if(cnt==7'd124) begin link_sda <= 1; end if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; sda_buf <= data[7]; data <= {data[6:0],data[7]}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= ack3; link_sda <= 0; cnt_num <= 0; end end ack3: begin // if((scl==1)&&(cnt==7'd61)&&(sda==1'b0)) // begin // link_sda <= 1; // if(wr==1) // begin // state <= transfer_data; // data <= 8'b10101010;//準備想要寫入的數據 // end // if(rd==1) // begin // state <= start2; // sda_buf <= 1;//準備再次發啓動信號 // end // end if((scl==1)&&(cnt==7'd61)) begin // link_sda <= 1; if(wr==1) begin state <= transfer_data; data <= 8'b10101010;//準備想要寫入的數據 end if(rd==1) begin state <= start2; sda_buf <= 1;//準備再次發啓動信號 end end end start2: begin if(cnt==7'd124) begin link_sda <= 1; end if((scl==1)&&(cnt==7'd61)) begin state <= control_byte2; sda_buf <= 0; data <= 8'b10100001; //讀控制字準備 end end control_byte2: begin if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; sda_buf <= data[7]; data <= {data[6:0],data[7]}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= ack4; link_sda <= 0; cnt_num <= 0; end end ack4: begin // if((scl==1)&&(cnt==7'd61)&&(sda==1'b0)) // begin // state <= transfer_data; // data <= 8'b00000000; //低字節地址準備 // link_sda <= 0; // end if((scl==1)&&(cnt==7'd61)) begin state <= transfer_data; data <= 8'b00000000; //低字節地址準備 // link_sda <= 0; end end transfer_data: begin if(wr==1) begin if(cnt==7'd124) begin link_sda <= 1; end if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; sda_buf <= data[7]; data <= {data[6:0],data[7]}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= ack5; link_sda <= 0; cnt_num <= 0; wr <= 0; led <= 0; end end if(rd==1) begin if(cnt==7'd124) begin link_sda <= 0; end if((cnt_num<4'd8)&&(cnt==7'd124)) begin cnt_num <= cnt_num + 1; result <= {result[6:0],sda}; end else if((cnt_num==4'd8)&&(cnt==7'd124)) begin state <= no_ack; link_sda <= 1; cnt_num <= 0; sda_buf <= 1; rd <= 0; end end end ack5: begin // if((scl==1)&&(cnt==7'd61)&&(sda==1'b0)) // begin // state <= stop; // link_sda <= 1; // end if((scl==1)&&(cnt==7'd61)) begin state <= stop; // link_sda <= 1; end end no_ack: begin if(cnt==7'd124) begin state <= stop; link_sda <= 1; sda_buf <= 0; end end stop: begin if(cnt==7'd124) begin link_sda <= 1; end if((scl==1)&&(cnt==7'd61)) begin sda_buf <= 1; state <= IDLE; end end default:state<=0; endcase endmodule
說明:因爲仿真中沒有嵌入EEPROM仿真模型,所以,沒法給出ACK應答信號,沒有應答信號,狀態機就沒辦法繼續向下跳轉。因此爲了完成仿真,就在代碼中屏蔽了全部的ACK檢測。在仿真中,只要看本該出現ACK信號的時候,sda信號是否是藍色高組態。若是是高組態就表示仿真沒有問題。在實際工程中,只須要把代碼中屏蔽掉的if語句放開,把對應的if語句(仿真用的)屏蔽掉就能夠直接用了