iT邦幫忙

2021 iThome 鐵人賽

DAY 25
1

One-Wire

One-Wire是一種只需要一條線即可傳輸資料的傳輸協定,而通常這種傳輸協定會用於與小型裝置溝通,例如數位溫度、濕度感測器。

那我們在這邊會以 AM2302 的規格為主去撰寫我們的程式


AM2302 Datasheet

注意:Tbe 為 master 拉低

圖片出處:AM2302 Datasheet

這邊我們由圖一及圖二可以看到在接收 AM2302 的資料時,一共會有一連串的 Start Signal 以及 40 bit 的 data,分別是濕度的前 8 bit、後 8 bit,溫度的前 8 bit、後 8 bit,還有最後的 8 bit 較驗位(較驗位 = 前面四筆 8 bit 資料做相加)

而圖三及圖四則告訴了我們各種信號所會花費的時間長

而 datasheet 內容也有提到這裡的 SDA 線也是有 pull-up 的~

先來第一步吧!

先定義狀態機

//===== state machine =====//
localparam IDLE     = 3'd0;
localparam Be       = 3'd1;
localparam GO       = 3'd2;
localparam REL      = 3'd3;
localparam REH      = 3'd4;
localparam DATA     = 3'd5;
localparam CHECKOUT = 3'd6;

定義每個間隔時間

//===== during time =====//
localparam durBe       = 16'd50000;//1ms
localparam durGo       = 16'd1500; //30us
localparam durRel      = 16'd2555; //80us
localparam durReh      = 16'd2560; //80us
localparam durDataHIGH = 16'd2580; //70us
localparam durDataMax  = 16'd5000; //100us

這裡的間隔周期以 datasheet 的 typical 為準,而這裡的時脈是以 50M 為主,舉個例子:如果要 1ms 那麼 counter 就要數 1ms/20ns = 50000,其他以此類推。

再來比較有趣的是這裡可以不用精準定義 "0" 訊號以及 "1" 訊號的 HIGH 時間長,因為只要在資料線一變 LOW 時去檢查在 HIGH 時數到的數字有沒有超過一定的值就可以視為訊號 "1" 了(這裡是抓 70us,因為 "0" HIGH 的 typical 值為 30us,"1" HIGH 的 typical 值為 75us)

再來定義輸入輸出
輸入:

  • clk_sys
  • rst_n
  • en(狀態機 enable)

輸出:

  • dataReady(資料正確時升為一,表示資料可讀取走)
  • data_o

inout

  • SDA
module AM2302_controller(
  clkSys, 
  en, 
  rst_n, 
  SDA, 
  dataReady, 
  data_o
);
/*---------Ports Declarations---------*/
input         clkSys;
input         en;
input         rst_n;
inout         SDA;
output        dataReady;
output [39:0] data_o;
reg           dataReady;
reg    [39:0] data_o;

宣告變數:

  • fstate(狀態機用)
  • counter(計數用)
  • databuf(蒐集的資料暫存的地方)
  • counterEN(counter 的致能)
  • flag_get(以此 flag 抓負緣)
  • sdaReg(sda 的暫存)
/*---------variables---------*/
reg     [2:0] fstate;
reg    [15:0] counter;
reg    [39:0] databuf;
reg           counterEN;
reg           flag_get;
reg           sdaReg;

SDA 輸出

/*---------assign wire---------*/
assign SDA = (sdaReg)?(1'bz):(1'b0);

counter 致能控制 counter 的計數

/*---------counter---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)        counter <= 16'd0;
  else if(counterEN)counter <= counter + 16'd1;
  else              counter <= 16'd0;
end

狀態機狀態邏輯

  • 每個狀態都數滿週期後才往下個狀態跑。
  • 特別的是,我這邊的 DATA 狀態是讓它數到超過,才往下狀態走(因為 "1" 的 HIGH 時間不會到 100us 這麼長,如果超過代表資料傳送完畢了),如此一來可以省掉許多判斷。
  • 最後一個狀態是來檢查校驗碼的。
/*---------fstate_state---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)fstate <= IDLE;
  else begin
    case(fstate)
      IDLE:begin
        if(en)fstate <= Be;
        else  fstate <= IDLE;
      end
      Be:begin
        if(counter == durBe)fstate <= GO;
        else                fstate <= Be;
      end
      GO:begin
        if(counter == durGo)fstate <= REL;
        else                fstate <= GO;
      end
      REL:begin
        if(counter == durRel)fstate <= REH;
        else                 fstate <= REL;
      end
      REH:begin
        if(counter == durReh)fstate <= DATA;
        else                 fstate <= REH;
      end
      DATA:begin
        if(counter == durDataMax)fstate <= CHECKOUT;
        else                     fstate <= DATA;
      end
      CHECKOUT:fstate <= IDLE;
      default: fstate <= IDLE;
    endcase
  end
end

狀態機輸出邏輯

  • sda 只有在 be 需要拉低(datasheet 的 Tbe 為 master 拉低)。
  • 當 counter 小於該狀態應計數值的值時 counterEN = 1,這樣是為了讓 counter 可以在進入下個狀態前歸零。
/*---------fstate_output---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)begin
    counterEN <= 1'b0;
    sdaReg    <= 1'b1;
  end 
  else begin
    case(fstate)
      IDLE:begin
        counterEN <= 1'b0;
        sdaReg    <= 1'b1;
      end
      Be:begin
        if(counter < durBe)counterEN  <= 1'b1;
        else               counterEN  <= 1'b0;
        sdaReg <= 1'b0;
      end
      GO:begin
        if(counter < durGo)counterEN  <= 1'b1;
        else               counterEN  <= 1'b0;
        sdaReg <= 1'b1;
      end
      REL:begin
        if(!SDA && counter < durRel)counterEN  <= 1'b1;
        else                        counterEN  <= 1'b0;
        sdaReg <= 1'b1;
      end
      REH:begin
        if(SDA && counter < durReh)counterEN  <= 1'b1;
        else                       counterEN  <= 1'b0;
        sdaReg <= 1'b1;
      end
      DATA:begin
        if(SDA && counter < durDataMax)counterEN  <= 1'b1;
        else                           counterEN  <= 1'b0;
        sdaReg <= 1'b1;
      end
      CHECKOUT:begin
        counterEN <= 1'b0;
        sdaReg    <= 1'b1;
      end 
      default:begin
        counterEN <= 1'b0;
        sdaReg    <= 1'b1;
      end 
    endcase
  end
end

flag_get

/*---------flag_get---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)flag_get <= 1'b0;
  else      flag_get <= SDA;
end

把 SDA 延後一個 clk 給 flag_get,往後只要讀到 SDA = 0;flag_get = 1,就知道 SDA 此時下降了(負緣)

蒐集資料

/*---------put data in databuf---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)databuf <= 39'd0;
  else begin
    if(fstate > REH)begin
      if(!SDA && flag_get && (counter > durDataHIGH))     databuf <= {databuf[38:0],1'b1};    
      else if(!SDA && flag_get && (counter < durDataHIGH))databuf <= {databuf[38:0],1'b0};
      else                                                databuf <= databuf;
    end
    else databuf <= 39'd0;
  end 
end

在 SDA 剛下降時去檢查剛剛在 SDA 為 HIGH 時維持了多少時間,如果大於 70us 則將 "1" 左移移入 databuf,反之則移入 "0"。

檢查較驗碼

/*---------check data and sent dataReady---------*/
always@(posedge clkSys or negedge rst_n)begin
  if(!rst_n)begin
    data_o    <= 39'd0; 
    dataReady <= 1'b0;
  end 
  else begin
    if(fstate==CHECKOUT)begin
      if(databuf[7:0]==databuf[39:32]+databuf[31:24]+databuf[23:16]+databuf[15:8])begin
        data_o    <= databuf;
        dataReady <= 1'b1;
      end 
      else begin
        data_o    <= data_o;
        dataReady <= 1'b0;
      end 
    end
    else begin
      data_o    <= data_o;
      dataReady <= 1'b0;
    end
  end
end

endmodule

在 CHECKOUT 狀態檢查較驗碼,如果正確則輸出至 data_o,並且將 dataReady 升為 "1"。

如此以來我們就完成了這個 AM2302 controller 模組瞜,是不是比上次的 I2C 更簡單了一點呢~~~~


上一篇
【Day24】I2C Master 的實現及驗證(最終章)
下一篇
【Day26】快速乘法器的實作(Booth演算法)
系列文
verilog or very lag30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言