上一篇我們設計了 I2C Master 的狀態機,那麼我們今天要來引用上次完成的狀態機模塊來實現 I2C Master 模塊,但我們先一步一步來,先來完成 write 的部分,也就是先可以把輸入的值成功以 I2C 協定傳出去,再來完成讀入的部分
先來看看這個 I2C Master write 模塊該有哪些輸入輸出腳吧:
輸入:
輸出:
輸入+輸出:
module I2C_write_R_W(
  clk_sys, 
  rst_n, 
  en,
  data_R,
  data_W,
  ACK, 
  ACK1,
  ACK2,
  ACK3,
  rstACK, 
  SCLK,
  SDA, 
  ldnACK1,
  ldnACK2, 
  ldnACK3,
  R_W, 
  tick_I2C_neg
);
/*---------ports declaration---------*/
input        clk_sys;
input        rst_n; 
input        en;
input        R_W;
input [26:0] data_R;//read  27bit
input [17:0] data_W;//write 18bit
output       ACK1;
output       ACK2;
output       ACK3;
output       tick_I2C_neg;
output       ACK; 
output       rstACK;
output       SCLK;
output       ldnACK1;
output       ldnACK2;
output       ldnACK3;
reg          tick_I2C_neg;    
reg          ACK1;
reg          ACK2;
reg          ACK3;
wire         ACK; 
wire         rstACK;
wire         SCLK;
wire         ldnACK1;
wire         ldnACK2;
wire         ldnACK3;
inout        SDA;
宣告變數:
需要用到的變數有:
/*---------variables---------*/
reg  [4:0] count;
reg  [8:0] cnt_I2C;
reg        tick_I2C;
reg        SCLK_100k;
reg [26:0] regdata_R;
reg [17:0] regdata_W;
wire       SCLK_temp;
wire       SHEN;
wire       LDEN;
wire       SDO;
wire       countEN;
wire       rstcount;
wire       SEL;
/*---------assign wire---------*/
assign SEL = (SHEN)?((!R_W)?(regdata_W[17]):(regdata_R[26])):(SDO);
assign SDA = (SEL)?(1'bz):(1'b0);
assign ACK = ACK1|ACK2|ACK3;
引用狀態機模組
/*---------module instantiate---------*/
I2C_control_R_W U0(
  .clk_sys(clk_sys),
  .SCLK_100k(SCLK_100k),
  .tick_I2C(tick_I2C),
  .rst_n(rst_n),
  .en(en),
  .count(count),
  .countEN(countEN),
  .rstcount(rstcount),
  .ACK1(ldnACK1),
  .ACK2(ldnACK2),
  .ACK3(ldnACK3),
  .rstACK(rstACK),
  .SCLK(SCLK),
  .SCLK_temp(SCLK_temp),
  .SHEN(SHEN),
  .LDEN(LDEN),
  .SDO(SDO),
  .R_W(R_W)
);
cnt_I2C
/*---------100k counter---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)cnt_I2C <= 9'd0;
  else begin
    if(cnt_I2C<9'd499)cnt_I2C <= cnt_I2C + 9'd1;//0-499
    else              cnt_I2C <= 9'd0;
  end
end
tick_I2C
/*---------I2C tick---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)tick_I2C <= 1'b0;
  else begin
    if(cnt_I2C==9'd498)tick_I2C <= 1'b1;
    else               tick_I2C <= 1'b0;
  end
end
/*---------I2C tick_neg---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)tick_I2C_neg <= 1'b0;
  else begin
    if(cnt_I2C==9'd249)tick_I2C_neg <= 1'b1;
    else               tick_I2C_neg <= 1'b0;
  end
end
SCLK_100k
/*---------SCLK_100k---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)SCLK_100k <= 1'b0;
  else begin
    if(cnt_I2C==9'd100)     SCLK_100k <= 1'b0;
    else if(cnt_I2C==9'd400)SCLK_100k <= 1'b1;
    else                    SCLK_100k <= SCLK_100k;
  end
end
這裡來解釋一下為什麼是 100 跟 400:
還記得前幾天的 I2C 的介紹內有提到,SDA 只能在 SCLK 為 "0" 時更動值,因此我們的 SCLK 高電位的部分要抓短一點,所以才會出現 100 時下去 400 上來的情況(咦?這樣不是反過來嗎?因為我狀態機模組裡面有反向了,你們也可以 100 上去 400 下來,然後不要反向,當然你想用其他的高電位寬度也都可以!),而 SDA 只會在 cnt_I2C 等於 498 時才更動值,如此一來就可以達到 SDA 只會在 SCLK 等於 "0" 時才更動值的效果!
藉由 rstcount&countEN 來控制 count
/*---------count---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    count <= 5'd0;
  end
  else begin
    if(tick_I2C)begin
      if(rstcount)    count <= 5'd0;
      else if(countEN)count <= count + 5'd1;
      else            count <= count;
    end
    else count <= count;
  end
end
藉由 LDEN&SHEN&R_W 來控制我們的 regdata_W®data_R
/*---------load data---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    regdata_R <= 27'd0;
    regdata_W <= 18'd0;
  end
  else begin
    if(!R_W)begin
      if(tick_I2C)begin
        if(LDEN)     regdata_W <= data_W;
        else if(SHEN)regdata_W <= {regdata_W[16:0],1'b0};
        else         regdata_W <= regdata_W;
      end
      else regdata_W <= regdata_W;
    end 
    else begin
      if(tick_I2C)begin
        if(LDEN)     regdata_R <= data_R;
        else if(SHEN)regdata_R <= {regdata_R[25:0],1'b0};
        else         regdata_R <= regdata_R;
      end
      else regdata_R <= regdata_R;
    end
  end
end
讀取 BUS 上的 ACK
/*---------ACK---------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    ACK1 <= 1'b0;
    ACK2 <= 1'b0;
    ACK3 <= 1'b0;
  end
  else if(rstACK&&tick_I2C)begin
    ACK1 <= 1'b0;
    ACK2 <= 1'b0;
    ACK3 <= 1'b0;
  end
  else begin
    if(ldnACK1&&tick_I2C)ACK1 <= SDA;
    else                 ACK1 <= ACK1;
    if(ldnACK2&&tick_I2C)ACK2 <= SDA;
    else                 ACK2 <= ACK2;
    if(ldnACK3&&tick_I2C)ACK3 <= SDA;
    else                 ACK3 <= ACK3;
  end
end
endmodule
由於這篇的內容也算偏多,可以先讓你們消化一下,下一篇我們會繼續完成 Read 的部分,並且撰寫 TestBench 來驗證電路的正確性~~