iT邦幫忙

2021 iThome 鐵人賽

DAY 24
2
Software Development

verilog or very lag系列 第 24

【Day24】I2C Master 的實現及驗證(最終章)

  • 分享至 

  • xImage
  •  

今天,我們要來完成整個 I2C 的最後一個部份了!
先來看看這個 I2C Master write 模塊該有哪些輸入輸出腳吧:

輸入:

  • clk_sys
  • rst_n
  • en(外部給此模組的驅動信號(enable))
  • addr(8 bit)
  • data_i(8 bit)

(如果是 write 模式,那前 8 bit 傳 address,而後 8 bit 傳 data_i)

輸出:

  • data_o(16 bit)
  • SCLK

(如果是 read 模式,那前 8 bit 傳 address,而後面都傳 "1" (高阻抗),因為要接收 data 所以斷開,由 slave 去給信號)

輸入+輸出:

  • SDA
module usage_I2C_write_R_W(
  en, 
  clk_sys, 
  rst_n, 
  addr, 
  data_i, 
  SCLK, 
  SDA, 
  data_o
);
/*------ports declaration------*/
input         clk_sys;
input         rst_n;
input         en;
input   [7:0] addr;
input   [7:0] data_i;
inout         SDA;
output        SCLK;
output [15:0] data_o;
reg    [15:0] data_o;
wire          SCLK;

再來,因為我打算收資料也做個狀態機,先來定義三個狀態:

/*------parameter------*/
parameter IDLE  = 2'd0;
parameter SHIFT = 2'd1;
parameter STOP  = 2'd2;

宣告變數:

  • data_temp(蒐集 read data,於狀態為 STOP 時輸出給 data_o)
  • fstate(狀態機變數)
  • count(計數 16 bit 的 data 的 counter)
  • 其他與狀態機模組連接的變數

引用上次寫好的模組

/*------module I2C_write instantiate------*/
I2C_write_R_W U0(
  .clk_sys(clk_sys),
  .rst_n(rst_n),
  .en(en), 
  .data_R({addr, 1'b1, 8'hff, 1'b0, 8'hff, 1'b1}),//27bit
  .data_W({addr, 1'b1, data_i, 1'b1}),//18bit
  .ACK(ACK),
  .ACK1(),
  .ACK2(),
  .ACK3(), 
  .rstACK(rstACK),
  .SCLK(SCLK),
  .SDA(SDA),
  .ldnACK1(ldnACK1), 
  .ldnACK2(ldnACK2),
  .ldnACK3(ldnACK3),
  .R_W(addr[0]),
  .tick_I2C_neg(tick_I2C_neg)
);

狀態邏輯:

  • 重置時要能回到 IDLE 狀態。
  • IDLE( ldnACK1 && (addr[0]) && tick_I2C_neg 訊號後開始到下一個狀態)(代表第九 bit 以及目前是 read 模式並且在訊號正中間開始)
  • SHIFT(蒐集完 16 bit 資料後結束)
  • STOP(回到 IDLE)
/*------fstate state------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)fstate <= IDLE;
  else begin
    case(fstate)
      IDLE:begin
        if(ldnACK1&&(addr[0])&&tick_I2C_neg)fstate <= SHIFT;
        else                                fstate <= IDLE;
      end
      SHIFT:begin
        if(count==4'd15&&tick_I2C_neg)fstate <= STOP;
        else                          fstate <= SHIFT;
      end
      STOP:begin
        fstate <= IDLE;
      end
      default:begin
        fstate <= IDLE;
      end
    endcase
  end
end

輸出邏輯:

  • IDLE(變數都為 0)
  • SHIFT( !ldnACK2&&tick_I2C_neg 此條件成立才 count 加一以及存入資料)(第 18 bit 資料不收,因為是 ACK,而 tick_I2C_neg 則是為了在資料正中間取樣,確保資料正確)
  • STOP(將蒐集完的 16 bit 資料輸出給 data_o,並將 count 歸零)
/*------fstate output------*/
always@(posedge clk_sys or negedge rst_n)begin
  if(!rst_n)begin
    count     <= 4'd0;
    data_temp <= 16'd0;
    data_o    <= 16'd0;
  end
  else begin
    case(fstate)
      IDLE:begin
        count     <= 4'd0;
        data_temp <= data_temp;
      end
      SHIFT:begin
        if(!ldnACK2&&tick_I2C_neg)begin
          count     <= count + 4'd1;
          data_temp <= {data_temp[14:0], SDA};
        end 
        else begin
          count     <= count;
          data_temp <= data_temp;
        end
      end
      STOP:begin
        count  <= 4'd0;
        data_o <= data_temp;
      end
      default:begin
        count     <= 4'dx;
        data_temp <= 16'hxx;
        data_o    <= 16'hxx;
      end
    endcase
  end
end

endmodule

這樣就結束了我們的 I2C 整個模塊

再來是 TestBench 的部分~~~~


TestBench

`timescale 1ns / 10ps

module i2c_tb();

localparam durTime = 5*1000;
reg sysClk, rst_n, tick_tx, sda_i;
reg [7:0]addr_i, data_i;
wire [15:0]data_o;
wire SCL;
wire SDA;
integer i;
assign SDA = (sda_i==1'b1)?1'bz:1'b0;
pullup(SDA);

usage_I2C_write_R_W UUT(
  .en(tick_tx), 
  .clk_sys(sysClk), 
  .rst_n(rst_n), 
  .addr(addr_i),
  .data_i(data_i),
  .SCLK(SCL), 
  .SDA(SDA),
  .data_o(data_o)
);

always@(negedge SCL, negedge rst_n)begin
  if(!rst_n)sda_i <= 1'b1;
  else if(i==8||i==17||i==27||i==28||i==36||i==35||i==43)#2000 sda_i <= 1'b0;//<27 for write
  else#2000 sda_i <= 1'b1;
end

always@(posedge SCL, negedge rst_n)begin
  if(!rst_n)i <= 0;
  else i <= i + 1;
end

always #10 sysClk = ~sysClk;

initial begin
  sysClk  = 0;
  rst_n   = 1;
  tick_tx = 0;
  sda_i   = 1'b1;
  addr_i  = 8'hA6;
  data_i  = 8'h81;
  #50 rst_n   = 0;
  #50 rst_n   = 1;

  #10 tick_tx = 1;
  #10 tick_tx = 0;

  repeat(50) #durTime;

  addr_i  = 8'hA7;
  #10 tick_tx = 1;
  #10 tick_tx = 0;

  repeat(100) #durTime;
  
  $stop;

end

endmodule 

值得注意的是 Verilog 本身就有 "pullup" 語法可以模擬上拉電阻的效果

Wave

可以看到上半部的部分:

前半部是 write 模式,先傳送 addr_i 的 10100110(最後一 bit 是 "0" ,所以是 write)再來是 ACK,再接著 data_i 的 10000001,最後也是 1 bit 的 ACK。

後半部是 read 模式,先傳送 addr_i 的 10100111(最後一 bit 是 "1" ,所以是 read ),再來是 ACK,接著的 16 bit 是由 tb 發送的 01111110 和 11111101,而最後再搭配一個 master 端的 non-ACK 完成通訊。

而由此波形也可以看見 SDA 確實有在 SCLK 為 LOW 時才改變值。

註:虛線是因為 pullup 的關係,所以 HIGH 才變成虛線

而這裡可以看到 tick_I2C_neg(紅色那條)位於 SDA 的正中間,以此來取樣 SDA 信號可以確保值的正確性~~~~

來看看 State Machine Viewer 是否如預期

狀態確實都有照著我們的想法去跑呢~

那麼 I2C 的教學也到這邊結束嘍~~~~


上一篇
【Day23】I2C Master(Write)的實現
下一篇
【Day25】One-Wire protocol
系列文
verilog or very lag30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
m1095509
iT邦新手 5 級 ‧ 2024-04-24 16:52:25

很詳盡的解說,獲益良多,另外想詢問一下,於宣告變數的部分,是否缺少了code呢?

我要留言

立即登入留言