基於FPGA的DS18B20控制程序設計及其Verilog實現 (一)

一,總體介紹

DS18B20是一個1-wire總線,12bit的數字溫度傳感器,其詳細的參數這裏不做具體的介紹,只討論其基於Verilog的控制程序的設計。

實際上,對DS18B20的控制,主要是實現1-wire總線的初始化,讀,寫等操作,然後再根據DS18B20的控制要求,實現對其控制的verilog邏輯。

在1-Wire總線上,有一個master,可以有1個或者多個slave。而對於FPGA+DS18B20的溫度測試設計來講,需要在FPGA上實現一個1-Wire總線的master。 DS18B20作爲1-wire總線的slave設備存在,可以有一個或者多個,不過爲了簡化程序,例程裏假定只存在一個DS18B2020。

1-Wire總線的操作形式上相對簡單,但操作本身相對卻又比較複雜。用Verilog做控制程序設計時,可以採用多層次嵌套的狀態機來實現。

二,FPGA + DS18B20的硬件設計

硬件的設計非常簡單,只需要將DS18B20的DQ與FPGA的一個IO連接,並加4.7K左右的上拉電阻就可以了。VDD和VPU可以爲3.0~5.0V。這裏我們參照FPGA本身的IO電壓,選擇3.3V。

另外要注意的一點是,由於DQ的數據是雙向的,所以FPGA的該IO要設定爲inout類型。

clip_image002

三,1-Wire總線的基本操作及Verilog實現。

根據1-Wire總線的特點,可以把1-Wire總線的操作歸結爲初始化,單bit讀操作,單bit寫操作等最基礎的幾種。下面分別是幾種基本操作的介紹和verilog實現。由於DS18B20的時序操作的最小單位基本上是1us,所以在該設計中,全部採用1MHz的時鐘。

1. 初始化

初始化實際上就是1-wire總線上的Reset操作。由master發出一定長度的初始化信號。Slave看到該初始化信號後,在一定時間內發出規定長度的響應信號,然後初始化操作就結束了。下圖是DS18B20的datasheet上給出的初始化的時序要求圖示。

clip_image004

我們用一個簡單的狀態機來實現對DS18B20初始化的操作。根據初始化的時序要求,設計一個有3個狀態的簡單的狀態機,這三個狀態分別是RST_IDLE,RST_MINIT和RST_SINIT。系統初始化時,處於RST_IDLE狀態,當RST_EN信號有效時,進入RST_MINIT狀態,由master發出初始化信號。當master的初始化信號發出一定時間以後,直接進入RST_SINIT狀態。在RST_SINIT狀態時,master去觀察slave是否輸出了正確的狀態:如果slave沒有輸出正確的狀態,則狀態機重新回到RST_MINIT狀態,由master重新發出初始化信號;如果slave輸出了正確的狀態,則意味着初始化正確完成,狀態機回到RST_IDLE狀態,整個初始化過程完成(這個文章裏涉及到比較多的狀態機,但狀態機的轉換都很簡單,所以不會給出狀態機的狀態轉換圖,僅僅會用文字做簡單敘述,有疑問的地方,可以仔細閱讀相關代碼)。

wire RST_EN;

wire RST_OVER;

parameter RST_IDLE = 3'b001, //IDLE 狀態

RST_MINIT = 3'b010, //master 初始化操作

RST_SINIT = 3'b100; //slave 初始化應答

reg [2:0] RSTSM, RSTSMNXT;

wire PHASE_RST_IDLE = RSTSM[0];

wire PHASE_RST_MINIT = RSTSM[1];

wire PHASE_RST_SINIT = RSTSM[2];

wire PHASENXT_RST_IDLE = RSTSMNXT[0];

always @(posedge CLK1MHZ or negedge RESET)

begin

if(~RESET)

RSTSM <= RST_IDLE;

else

RSTSM <= RSTSMNXT;

end

reg [9:0] MASTER_CNT; //用來控制master發出初始化信號的長度

always @(posedge CLK1MHZ or negedge RESET)

begin

if(~RESET)

MASTER_CNT <= 10'b0;

else if(~PHASE_RST_MINIT)

MASTER_CNT <= 10'b0;

else

MASTER_CNT <= MASTER_CNT + 10'b1;

end

reg [9:0] SLAVE_CNT; //用來判斷slave是否在恰當時間返回初始化結束的信號

always @(posedge CLK1MHZ or negedge RESET)

begin

if(~RESET)

SLAVE_CNT <= 10'b0;

else if(~PHASE_RST_SINIT)

SLAVE_CNT <= 10'b0;

else

SLAVE_CNT <= SLAVE_CNT + 10'b1;

end

reg SLAVE_IS_INIT; //採集並保存slave發出的初始化結束信號

always @(posedge CLK1MHZ or negedge RESET)

begin

if(~RESET)

SLAVE_IS_INIT <= 1'b1;

else if(SLAVE_CNT == 10'd70)

SLAVE_IS_INIT <= DQ_IN;

else if(PHASE_RST_MINIT)

SLAVE_IS_INIT <= 1'b1;

else

SLAVE_IS_INIT <= SLAVE_IS_INIT;

end

always @(RSTSM or RST_EN or MASTER_CNT or SLAVE_CNT or SLAVE_IS_INIT ) begin

case(RSTSM)

RST_IDLE:

if(RST_EN)

RSTSMNXT = RST_MINIT;

else

RSTSMNXT = RST_IDLE;

RST_MINIT:

if(MASTER_CNT == 10'd500)

RSTSMNXT = RST_SINIT;

else

RSTSMNXT = RST_MINIT;

RST_SINIT:

if( (SLAVE_CNT == 10'd500) & ~SLAVE_IS_INIT)

RSTSMNXT = RST_IDLE;

else if((SLAVE_CNT == 10'd500) & SLAVE_IS_INIT)

RSTSMNXT = RST_MINIT;

else

RSTSMNXT = RST_SINIT;

default:

RSTSMNXT = RST_IDLE;

endcase

end

assign RST_OVER = PHASE_RST_SINIT & PHASENXT_RST_IDLE; //初始化完成標誌信號

下圖是用示波器抓出的初始化過程DQ信號的波形(紅色)。

clip_image006

2. 單bit讀操作

在1-wire總線上,讀數據的操作實際上是按bit來完成的。每次master可以從slave讀回一個bit的數據。讀回的數據可能是1或者0。下圖是DS18B20的datasheet上給出的單bit讀操作的時序要求圖示。

clip_image008

需要注意的是,對於master來講,無論讀回來的數據是1還是0,其本身的操作及時序都是一樣的,沒有差異。

仍然用一個簡單的狀態機來實現對DS18B20的單bit讀操作。設計一個有5個狀態的簡單的狀態機,這五個狀態分別是RD_IDLE,RD_MPL,RD_MSAP,RD_WAIT和RD_OVER。系統初始化時,處於RD_IDLE狀態,當RDBEGIN信號有效時,進入RD_MPL狀態,由master發出讀信號。 3us以後,進入RD_MSAP狀態(master在該狀態結束的前一個us讀取DQ上的值作爲讀bit的結果),在11us以後,進入RD_WAIT狀態,而在讀bit開始後的59us,系統進入RD_OVER狀態,意味着讀bit操作結束。RD_OVER狀態是爲了符合1-Wire總線的操作規範(在每個操作之間至少有1us的總線空閒時間)而存在的。

wire RDBEGIN ;

parameter RD_IDLE = 5'b00001, //resister pullup, larger than 1us

RD_MPL = 5'b00010, //master pull low, larger than 1us

RD_MSAP = 5'b00100, //ds18b20 pull low(read 0) or resister pullup(read 1), master sample data, near 15us

RD_WAIT = 5'b01000, //ds18b20 pull low(read 0) or resister pullup(read 1)

RD_OVER = 5'b10000; //resister pullup, larger than 1us

reg [4:0] RDSM, RDSMNXT;

wire PHASE_RD_IDLE = RDSM[0];

wire PHASE_RD_MPL = RDSM[1];

wire PHASE_RD_MSAP = RDSM[2];

wire PHASE_RD_OVER = RDSM[4];

reg [5:0] RD_CNT;

always @(posedge CLK1MHZ or negedge RESET)

begin

if(~RESET)

RD_CNT <= 6'b0;

else if(~PHASE_RD_IDLE)

RD_CNT <= RD_CNT + 6'b1;

else

RD_CNT <= 6'b0;

end

always @(posedge CLK1MHZ or negedge RESET)

begin

if(~RESET)

RDSM <= RD_IDLE;

else

RDSM <= RDSMNXT;

end

always @(RDSM or RDBEGIN or RD_CNT) begin

case(RDSM)

RD_IDLE:

if(RDBEGIN)

RDSMNXT = RD_MPL;

else

RDSMNXT = RD_IDLE;

RD_MPL:

if(RD_CNT == 6'd3)

RDSMNXT = RD_MSAP;

else

RDSMNXT = RD_MPL;

RD_MSAP:

if(RD_CNT == 6'd14)

RDSMNXT = RD_WAIT;

else

RDSMNXT = RD_MSAP;

RD_WAIT:

if(RD_CNT == 6'd59)

RDSMNXT = RD_OVER;

else

RDSMNXT = RD_WAIT;

RD_OVER:

if(RD_CNT == 6'd61)

RDSMNXT = RD_IDLE;

else

RDSMNXT = RD_OVER;

default:

RDSMNXT = RD_IDLE;

endcase

end

reg RD_BIT_DATA; //讀bit操作獲得的數據

always @(posedge CLK1MHZ or negedge RESET)

begin

if(~RESET)

RD_BIT_DATA <= 1'b0;

else if( PHASE_RD_MSAP & (RD_CNT == 6'd13) )

RD_BIT_DATA <= DQ;

else if(PHASE_RD_IDLE)

RD_BIT_DATA <= 1'b0;

else

RD_BIT_DATA <= RD_BIT_DATA;

end

3. 單bit寫操作

在1-Wire總線上,寫數據的操作也是按bit來完成的。每次master可以向slave寫入一個bit的數據。寫數據可能是1或者0。下圖是DS18B20的datasheet上給出的單bit寫操作的時序要求圖示:

clip_image010

需要注意的是,對於master來講,寫數據不同的時候(1或者0),其本身的操作及時序是有差別的。

對DS18B20的單bit寫操作可以用一個有4個狀態的簡單的狀態機來實現。這三個狀態分別是WD_IDLE,WD_MPL,WD_OUT,和RD_OVER。系統初始化時,處於WD_IDLE狀態,當WDBEGIN信號有效時,進入WD_MPL狀態,由master發出寫信號。 9us以後,進入WD_MOUT狀態(master將要寫到slave的數據放到DQ上),而在寫bit開始後的59us,系統進入RD_OVER狀態,意味着寫bit操作結束。WD_OVER狀態是爲了符合1-Wire總線的操作規範(在每個操作之間至少有1us的總線空閒時間)而存在的。

wire WDBEGIN; //單bit寫操作開始信號

wire WD_DATA_OUT; //要寫入到slave的值

parameter WD_IDLE = 4'b0001, //resister pullup, no time request

WD_MPL = 4'b0010, //master pull low, larger than 1us, use 10us.

WD_MOUT = 4'b0100, //master pull low(write 0) or resister pull up(write 1), use 50us.

WD_OVER = 4'b1000; //resister pullup, larger than 1us

reg [3:0] WDSM, WDSMNXT;

wire PHASE_WD_IDLE = WDSM[0];

wire PHASE_WD_MPL = WDSM[1];

wire PHASE_WD_MOUT = WDSM[2];

wire PHASE_WD_OVER = WDSM[3];

wire PHASENXT_WD_MOUT = WDSMNXT[2];

reg [5:0] WD_CNT;

always @(posedge CLK1MHZ or negedge RESET)

begin

if(~RESET)

WD_CNT <= 6'b0;

else if(~PHASE_WD_IDLE)

WD_CNT <= WD_CNT + 6'b1;

else

WD_CNT <= 6'b0;

end

always @(posedge CLK1MHZ or negedge RESET)

begin

if(~RESET)

WDSM <= WD_IDLE;

else

WDSM <= WDSMNXT;

end

always @(WDSM or WDBEGIN or WD_CNT) begin

case(WDSM)

WD_IDLE:

if(WDBEGIN)

WDSMNXT = WD_MPL;

else

WDSMNXT = WD_IDLE;

WD_MPL:

if(WD_CNT == 6'd9)

WDSMNXT = WD_MOUT;

else

WDSMNXT = WD_MPL;

WD_MOUT:

if(WD_CNT == 6'd59)

WDSMNXT = WD_OVER;

else

WDSMNXT = WD_MOUT;

WD_OVER:

if(WD_CNT == 6'd61)

WDSMNXT = WD_IDLE;

else

WDSMNXT = WD_OVER;

default:

WDSMNXT = WD_IDLE;

endcase

end

always @(posedge CLK1MHZ or negedge RESET)

begin

if(~RESET)

DQ_OUT <= 1'b0;

else if(PHASENXT_WD_MOUT )

begin

DQ_OUT <= WD_DATA_OUT;

else if(PHASE_WD_IDLE)

DQ_OUT <= 1'b1;

else

DQ_OUT <= DQ_OUT;

end

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章