One-Wire是一種只需要一條線即可傳輸資料的傳輸協定,而通常這種傳輸協定會用於與小型裝置溝通,例如數位溫度、濕度感測器。
那我們在這邊會以 AM2302 的規格為主去撰寫我們的程式
注意: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)
再來定義輸入輸出
輸入:
輸出:
inout
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;
宣告變數:
/*---------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
狀態機狀態邏輯
/*---------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
狀態機輸出邏輯
/*---------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 更簡單了一點呢~~~~