iT邦幫忙

2021 iThome 鐵人賽

DAY 23
1
Software Development

verilog or very lag系列 第 23

【Day23】I2C Master(Write)的實現

上一篇我們設計了 I2C Master 的狀態機,那麼我們今天要來引用上次完成的狀態機模塊來實現 I2C Master 模塊,但我們先一步一步來,先來完成 write 的部分,也就是先可以把輸入的值成功以 I2C 協定傳出去,再來完成讀入的部分

先來看看這個 I2C Master write 模塊該有哪些輸入輸出腳吧:

輸入:

  • clk_sys
  • rst_n
  • en(外部給此模組的驅動信號(enable))
  • data_R(27 bit)
  • data_W(18 bit)
  • R_W(Read/Write)

輸出:

  • ACK1, ACK2, ACK3(BUS 上的 ACK 接收後輸出)
  • ACK(ACK = ACK1|ACK2|ACK3)
  • rstACK(連接狀態機模組的腳)
  • SCLK
  • ldnACK1, ldnACK2, ldnACK3(連接狀態機模組 ACK1, ACK2, ACK3 的腳 )
  • tick_I2C_neg(由於我讀資料又會包在外面的模塊,這個訊號是要傳到外面並要在此訊號為"1"時取樣資料)

輸入+輸出:

  • SDA
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;

宣告變數:
需要用到的變數有:

  • count(計數 18 or 27 個 bit 用)
  • cnt_I2C(計數用(0~499),要做 tick_I2C)
  • regdata_R(load 進來的資料先暫存在這裡(for Read))
  • regdata_W(load 進來的資料先暫存在這裡(for Write))
  • 要做tick_I2C(100KHZ 的 tick)
  • 其他與狀態機模組連接的變數
  • SEL(SDA 的暫存值(而 SDA 又是由 regdata 的 LSB 以及 SD0 做切換的),再多一層是因為,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&regdata_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 來驗證電路的正確性~~


上一篇
【Day22】I2C Master 狀態機的實現
下一篇
【Day24】I2C Master 的實現及驗證(最終章)
系列文
verilog or very lag30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言