基于FPGA的DS18B20控制程序设计及其Verilog实现 (一)
(2012-05-20 22:20:41)
转载▼
标签:
杂谈
一,总体介绍
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类型。
三,1-Wire总线的基本操作及Verilog实现。
根据1-Wire总线的特点,可以把1-Wire总线的操作归结为初始化,单bit读操作,单bit写操作等最基础的几种。下面分别是几种基本操作的介绍和verilog实现。由于DS18B20的时序操作的最小单位基本上是1us,所以在该设计中,全部采用1MHz的时钟。
1. 初始化
初始化实际上就是1-wire总线上的Reset操作。由master发出一定长度的初始化信号。Slave看到该初始化信号后,在一定时间内发出规定长度的响应信号,然后初始化操作就结束了。
我们用一个简单的状态机来实现对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;
parameterRST_IDLE = 3'b001, //IDLE 状态
RST_MINIT =3'b010, //master 初始化操作
RST_SINIT =3'b100; //slave 初始化应答
reg [2:0]RSTSM, RSTSMNXT;
wirePHASE_RST_IDLE = RSTSM[0];
wirePHASE_RST_MINIT = RSTSM[1];
wirePHASE_RST_SINIT = RSTSM[2];
wirePHASENXT_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;
elseif(~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;
elseif(~PHASE_RST_SINIT)
SLAVE_CNT<= 10'b0;
else
SLAVE_CNT<= SLAVE_CNT + 10'b1;
end
regSLAVE_IS_INIT; //采集并保存slave发出的初始化结束信号
always@(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
SLAVE_IS_INIT<= 1'b1;
elseif(SLAVE_CNT == 10'd70)
SLAVE_IS_INIT<= DQ_IN;
elseif(PHASE_RST_MINIT)
SLAVE_IS_INIT<= 1'b1;
else
SLAVE_IS_INIT<= SLAVE_IS_INIT;
end
always @(RSTSMor 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;
elseif((SLAVE_CNT == 10'd500) & SLAVE_IS_INIT)
RSTSMNXT =RST_MINIT;
else
RSTSMNXT =RST_SINIT;
default:
RSTSMNXT =RST_IDLE;
endcase
end
assignRST_OVER = PHASE_RST_SINIT & PHASENXT_RST_IDLE; //初始化完成标志信号
2. 单bit读操作
在1-wire总线上,读数据的操作实际上是按bit来完成的。每次master可以从slave读回一个bit的数据。读回的数据可能是1或者0。
需要注意的是,对于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 ;
parameterRD_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 sampledata, 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;
wirePHASE_RD_IDLE = RDSM[0];
wirePHASE_RD_MPL = RDSM[1];
wirePHASE_RD_MSAP = RDSM[2];
wirePHASE_RD_OVER = RDSM[4];
reg [5:0]RD_CNT;
always@(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
RD_CNT <=6'b0;
elseif(~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 @(RDSMor 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
regRD_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;
elseif(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。
需要注意的是,对于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写操作开始信号
wireWD_DATA_OUT; //要写入到slave的值
parameterWD_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;
wirePHASE_WD_IDLE = WDSM[0];
wirePHASE_WD_MPL = WDSM[1];
wirePHASE_WD_MOUT = WDSM[2];
wirePHASE_WD_OVER = WDSM[3];
wirePHASENXT_WD_MOUT = WDSMNXT[2];
reg [5:0]WD_CNT;
always@(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
WD_CNT <=6'b0;
elseif(~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 @(WDSMor 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;
elseif(PHASENXT_WD_MOUT )
begin
DQ_OUT <=WD_DATA_OUT;
elseif(PHASE_WD_IDLE)
DQ_OUT <=1'b1;
else
DQ_OUT <=DQ_OUT;
end
三,1-Wire总线上按Byte读写的Verilog实现及DS18B20的Byte操作
上面用简单状态机实现了1-Wire总线上单bit数据的读写操作。在此基础上,可以通过状态机嵌套的方法实现按Byte的读写操作。实现Byte读写控制的状态机,控制8bit数据的读写操作,而每一bit的读写操作则通过嵌套单bit数据读写的状态机来实现。
1. 按Byte读操作
从DS18B20的datasheet上我们可以看到,1-Wire总线在发送或者接收数据的时候,LSB的数据在前,MSB的数据在后。所以对于Byte数据的读操作,只要依次在总线上读取8bit数据,并按照bit0~bit7的顺序将其存储到一个8bits的寄存器里即可。
通常情况下,设计状态机的时候,都会设计一个默认的初始状态(也可以称之为IDLE状态)。在这里每个bit数据的读操作可以设定为一个状态。这样,整个状态机就需要九个状态。在我们的设计中,九个状态分别是RBD_IDLE,RBD_BIT0 ~RBD_BIT7,其中 RBD_IDLE是IDLE状态,RBD_BIT0~RBD_BIT7分别是读取bit0~bit7数据的状态。
状态机在系统复位或者操作完成以后,进入RBD_IDLE状态。当读Byte数据的使能信号(RBDBEGIN)有效时,状态机进入RBD_BIT0,并调用1-Wire总线单bit读操作的状态机实现单bit的读操作,读取bit0的数据。为了在状态机进入RBD_BIT0的时候,正确调用单bit读操作的状态机,就要在RBD_BIT0状态下,使能RDBEGIN信号(单bit读操作的使能信号,详见上文)。在读完bit0的数据后(单bit读操作状态机回到IDLE状态),状态机需要进入RBD_BIT1状态,读取bit1的数据。也就是说,状态机从RBD_BIT0进入到RBD_BIT1的条件是bit0读操作结束(即其状态机回到IDLE状态),这个条件可以用单bit读操作状态机的PHASE_RD_OVER生成。上述的操作就实现了两个状态机的嵌套,顶层状态机(Byte读操作状态机)的信号触发底层状态机(单bit读操作状态机)进入工作状态,当底层状态机完成工作进入IDLE状态时,触发顶层状态机进入下一个状态。
从RBD_BIT0到RBD_BIT7的操作是相同的,依次读取8bit的数据。不过读完bit7的数据后,状态机要转回到RBD_IDLE状态。另外就是在单bit读的过程中,要根据Byte读状态机的状态,将读到的单bit数据写入对应的寄存器中。
下面是verilog的实现:
reg [7:0] RD_BYTE_DATA; //Byte读数据结果
wire RBDBEGIN ; // Byte读使能信号
reg PHASE_RD_OVER_Q;
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
PHASE_RD_OVER_Q <= 1'b0;
else
PHASE_RD_OVER_Q <= PHASE_RD_OVER;
end
wire RD_BIT_OVER = PHASE_RD_OVER &PHASE_RD_OVER_Q; //单bit读操作结束信号
parameter RBD_IDLE = 9'b0_0000_0001,
RBD_BIT0 = 9'b0_0000_0010,
RBD_BIT1 = 9'b0_0000_0100,
RBD_BIT2 = 9'b0_0000_1000,
RBD_BIT3 = 9'b0_0001_0000,
RBD_BIT4 = 9'b0_0010_0000,
RBD_BIT5 = 9'b0_0100_0000,
RBD_BIT6 = 9'b0_1000_0000,
RBD_BIT7 = 9'b1_0000_0000;
reg [8:0] RBDSM, RBDSMNXT;
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
RBDSM <= RBD_IDLE;
else
RBDSM <= RBDSMNXT;
end
always @(RBDSM or RBDBEGIN or RD_BIT_OVER) begin
case(RBDSM)
RBD_IDLE:
if(RBDBEGIN)
RBDSMNXT = RBD_BIT0;
else
RBDSMNXT = RBD_IDLE;
RBD_BIT0:
if(RD_BIT_OVER)
RBDSMNXT = RBD_BIT1;
else
RBDSMNXT = RBD_BIT0;
RBD_BIT1:
if(RD_BIT_OVER)
RBDSMNXT = RBD_BIT2;
else
RBDSMNXT = RBD_BIT1;
RBD_BIT2:
if(RD_BIT_OVER)
RBDSMNXT = RBD_BIT3;
else
RBDSMNXT = RBD_BIT2;
RBD_BIT3:
if(RD_BIT_OVER)
RBDSMNXT = RBD_BIT4;
else
RBDSMNXT = RBD_BIT3;
RBD_BIT4:
if(RD_BIT_OVER)
RBDSMNXT = RBD_BIT5;
else
RBDSMNXT = RBD_BIT4;
RBD_BIT5:
if(RD_BIT_OVER)
RBDSMNXT = RBD_BIT6;
else
RBDSMNXT = RBD_BIT5;
RBD_BIT6:
if(RD_BIT_OVER)
RBDSMNXT = RBD_BIT7;
else
RBDSMNXT = RBD_BIT6;
RBD_BIT7:
if(RD_BIT_OVER)
RBDSMNXT = RBD_IDLE;
else
RBDSMNXT = RBD_BIT7;
default:
RBDSMNXT = RBD_IDLE;
endcase
end
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
RD_BYTE_DATA <= 8'b0;
else if(PHASE_RD_MSAP & (RD_CNT == 6'd13) )
begin
case(RBDSM)
9'b0_0000_0010:
RD_BYTE_DATA[0] <= DQ;
9'b0_0000_0100:
RD_BYTE_DATA[1] <= DQ;
9'b0_0000_1000:
RD_BYTE_DATA[2] <= DQ;
9'b0_0001_0000:
RD_BYTE_DATA[3] <= DQ;
9'b0_0010_0000:
RD_BYTE_DATA[4] <= DQ;
9'b0_0100_0000:
RD_BYTE_DATA[5] <= DQ;
9'b0_1000_0000:
RD_BYTE_DATA[6] <= DQ;
9'b1_0000_0000:
RD_BYTE_DATA[7] <= DQ;
default:
RD_BYTE_DATA <= RD_BYTE_DATA;
endcase
end
else
RD_BYTE_DATA <= RD_BYTE_DATA;
end
2. 按Byte写操作
按Byte写操作的状态机与按Byte读操作的状态机的控制原理基本上是相同的。只不过在这里要嵌套的是单bit写操作的状态机。另外就是在单bit写操作的时候,要把对应bit的数据放到DQ总线上。
限于篇幅,状态机的实现不再给出,可自行参照Byte读操作的状态机做修改。
3. DS18B20的Byte操作
DS18B20的控制主要包括初始化,ROM命令和功能命令等。初始化的控制方式前面已经讨论过了。这里主要讨论一下ROM命令和功能命令。实际上不管是ROM命令还是功能命令,都可以归结为一个字节的命令(Byte写操作)加上0或者多个的Byte读/写操作。所以我们完全可以用上面所述的Byte读写状态机来实现ROM和功能命令。
对于只有一个单Byte写操作的命令,我们直接引用单Byte写操作的状态机就可以实现了。
而对于如读ROM数据的操作,则是一个Byte的写命令操作加上连续多个Byte的读数据的操作。在实现的时候,我们完全可以依照Byte读写操作状态机嵌套单bit读写操作状态机的方法,设计一个多BYTE读写操作的状态机,嵌套单Byte读写状态机。
下面是从DS18B20读出9Byte数据的状态机的verilog实现:
reg [7:0] TMP_LSB;
reg [7:0] TMP_MSB;
reg [7:0] USERBYTE1;
reg [7:0] USERBYTE2;
reg [7:0] CFGREG;
reg [7:0] RESERVED5;
reg [7:0] RESERVED6;
reg [7:0] RESERVED7;
reg [7:0] REGCRC;
wire RDSPAD_EN; //读DS18B20 rom数据使能信号
wire BYTE_READ_OVER = RD_BIT_OVER &PHASE_RBD_BIT7; //Byte读操作结束信号。
wire BYTE_WRITE_OVER = WD_BIT_OVER &PHASE_WBD_BIT7; //Byte 写操作结束信号。
parameter RDSPAD_IDLE = 11'b000_0000_0001,
RDSPAD_CMD = 11'b000_0000_0010, //读ROM命令
RDSPAD_BYTE1 = 11'b000_0000_0100, // 读ROM Byte 1
RDSPAD_BYTE2 = 11'b000_0000_1000,
RDSPAD_BYTE3 = 11'b000_0001_0000,
RDSPAD_BYTE4 = 11'b000_0010_0000,
RDSPAD_BYTE5 = 11'b000_0100_0000,
RDSPAD_BYTE6 = 11'b000_1000_0000,
RDSPAD_BYTE7 = 11'b001_0000_0000,
RDSPAD_BYTE8 = 11'b010_0000_0000,
RDSPAD_BYTE9 = 11'b100_0000_0000;
reg [10:0] RDSPADSM, RDSPADSMNXT;
wire PHASE_RDSPAD_IDLE = RDSPADSM[0];
wire PHASE_RDSPAD_CMD = RDSPADSM[1];
wire PHASE_RDSPAD_BYTE1 = RDSPADSM[2];
wire PHASE_RDSPAD_BYTE2 = RDSPADSM[3];
wire PHASE_RDSPAD_BYTE3 = RDSPADSM[4];
wire PHASE_RDSPAD_BYTE4 = RDSPADSM[5];
wire PHASE_RDSPAD_BYTE5 = RDSPADSM[6];
wire PHASE_RDSPAD_BYTE6 = RDSPADSM[7];
wire PHASE_RDSPAD_BYTE7 = RDSPADSM[8];
wire PHASE_RDSPAD_BYTE8 = RDSPADSM[9];
wire PHASE_RDSPAD_BYTE9 = RDSPADSM[10];
wire PHASENXT_RDSPAD_IDLE = RDSPADSMNXT[0];
wire PHASENXT_RDSPAD_CMD = RDSPADSMNXT[1];
wire PHASENXT_RDSPAD_BYTE1 = RDSPADSMNXT[2];
wire PHASENXT_RDSPAD_BYTE2 = RDSPADSMNXT[3];
wire PHASENXT_RDSPAD_BYTE3 = RDSPADSMNXT[4];
wire PHASENXT_RDSPAD_BYTE4 = RDSPADSMNXT[5];
wire PHASENXT_RDSPAD_BYTE5 = RDSPADSMNXT[6];
wire PHASENXT_RDSPAD_BYTE6 = RDSPADSMNXT[7];
wire PHASENXT_RDSPAD_BYTE7 = RDSPADSMNXT[8];
wire PHASENXT_RDSPAD_BYTE8 = RDSPADSMNXT[9];
wire PHASENXT_RDSPAD_BYTE9 = RDSPADSMNXT[10];
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
RDSPADSM <= RDSPAD_IDLE;
else
RDSPADSM <= RDSPADSMNXT;
end
always @( RDSPADSM or RDSPAD_EN or BYTE_READ_OVER orBYTE_WRITE_OVER) begin
case(RDSPADSM)
RDSPAD_IDLE:
if(RDSPAD_EN)
RDSPADSMNXT = RDSPAD_CMD;
else
RDSPADSMNXT = RDSPAD_IDLE;
RDSPAD_CMD:
if(BYTE_WRITE_OVER)
RDSPADSMNXT = RDSPAD_BYTE1;
else
RDSPADSMNXT = RDSPAD_CMD;
RDSPAD_BYTE1:
if(BYTE_READ_OVER)
RDSPADSMNXT = RDSPAD_BYTE2;
else
RDSPADSMNXT = RDSPAD_BYTE1;
RDSPAD_BYTE2:
if(BYTE_READ_OVER)
RDSPADSMNXT = RDSPAD_BYTE3;
else
RDSPADSMNXT = RDSPAD_BYTE2;
RDSPAD_BYTE3:
if(BYTE_READ_OVER)
RDSPADSMNXT = RDSPAD_BYTE4;
else
RDSPADSMNXT = RDSPAD_BYTE3;
RDSPAD_BYTE4:
if(BYTE_READ_OVER)
RDSPADSMNXT = RDSPAD_BYTE5;
else
RDSPADSMNXT = RDSPAD_BYTE4;
RDSPAD_BYTE5:
if(BYTE_READ_OVER)
RDSPADSMNXT = RDSPAD_BYTE6;
else
RDSPADSMNXT = RDSPAD_BYTE5;
RDSPAD_BYTE6:
if(BYTE_READ_OVER)
RDSPADSMNXT = RDSPAD_BYTE7;
else
RDSPADSMNXT = RDSPAD_BYTE6;
RDSPAD_BYTE7:
if(BYTE_READ_OVER)
RDSPADSMNXT = RDSPAD_BYTE8;
else
RDSPADSMNXT = RDSPAD_BYTE7;
RDSPAD_BYTE8:
if(BYTE_READ_OVER)
RDSPADSMNXT = RDSPAD_BYTE9;
else
RDSPADSMNXT = RDSPAD_BYTE8;
RDSPAD_BYTE9:
if(BYTE_READ_OVER)
RDSPADSMNXT = RDSPAD_IDLE;
else
RDSPADSMNXT = RDSPAD_BYTE9;
default:
RDSPADSMNXT = RDSPAD_IDLE;
endcase
end
//采集并保存TMP_LSB
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
TMP_LSB <= 8'b0;
else if(PHASENXT_RDSPAD_BYTE2 & BYTE_READ_OVER)
TMP_LSB <= RD_BYTE_DATA;
else
TMP_LSB <= TMP_LSB;
end
//采集并保存 TMP_MSB
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
TMP_MSB <= 8'b0;
else if(PHASENXT_RDSPAD_BYTE3 & BYTE_READ_OVER)
TMP_MSB <= RD_BYTE_DATA;
else
TMP_MSB <= TMP_MSB;
end
//采集并保存 USERBYTE1
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
USERBYTE1 <= 8'b0;
else if(PHASENXT_RDSPAD_BYTE4 & BYTE_READ_OVER)
USERBYTE1 <= RD_BYTE_DATA;
else
USERBYTE1 <= USERBYTE1;
End
//采集并保存USERBYTE2
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
USERBYTE2 <= 8'b0;
else if(PHASENXT_RDSPAD_BYTE5 & BYTE_READ_OVER)
USERBYTE2 <= RD_BYTE_DATA;
else
USERBYTE2 <= USERBYTE2;
end
//采集并保存 CFGREG
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
CFGREG <= 8'b0;
else if(PHASENXT_RDSPAD_BYTE6 & BYTE_READ_OVER)
CFGREG <= RD_BYTE_DATA;
else
CFGREG <= CFGREG;
end
//采集并保存 RESERVED5
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
RESERVED5 <= 8'b0;
else if(PHASENXT_RDSPAD_BYTE7 & BYTE_READ_OVER)
RESERVED5 <= RD_BYTE_DATA;
else
RESERVED5 <= RESERVED5;
end
//采集并保存 RESERVED6
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
RESERVED6 <= 8'b0;
else if(PHASENXT_RDSPAD_BYTE8 & BYTE_READ_OVER)
RESERVED6 <= RD_BYTE_DATA;
else
RESERVED6 <= RESERVED6;
end
//采集并保存 RESERVED7
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
RESERVED7 <= 8'b0;
else if(PHASENXT_RDSPAD_BYTE9 & BYTE_READ_OVER)
RESERVED7 <= RD_BYTE_DATA;
else
RESERVED7 <= RESERVED7;
end
//采集并保存 REGCRC
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
REGCRC <= 8'b0;
else if(PHASENXT_RDSPAD_IDLE & BYTE_READ_OVER)
REGCRC <= RD_BYTE_DATA;
else
REGCRC <= REGCRC;
end
下图是从示波器上抓出的单Byte读操作的DQ上的波形。
三,DS18B20的温度测量控制
在该系统中,1-Wire总线上只存在1个DS18B20,在控制DS18B20进行温度测量的时候,根据1-Wire总线的特性和DS18B20的控制要求,可以采用相对简单的控制流程。
我们用状态机来实现这个控制流程。从控制流程图可以看到,系统从空闲状态到读出温度数据,总共有8个大的步骤。而其中2和6,3和7是相同的,用状态机实现的时候,可以用同一个状态表示2和6,用同一个状态表示3和7。这样整个状态机就只需要6个状态。下面表格是状态机所有状态的定义:
状态名称 |
含义 |
TTST_IDLE |
系统空闲状态 |
TTST_RST |
总线初始化 |
TTST_SKPROM |
SKIP ROM 命令 |
TTST_CONVERT |
温度转换命令 |
TTST_WOVER |
等待温度转换结束 |
TTST_GETDATA |
读取温度数据 |
为了区分2和6以及3和7。用IS_CONVERT信号表示总线初始化和SKIPROM命令是用来执行CONVERT命令的。用~IS_CONVERT信号表示总线初始化和SKIPROM命令是用来读取温度数据的。在状态机进入TTST_IDLE状态时,令IS_CONVERT为1并保持,当状态机进入TTST_WOVER状态时,令IS_CONVERT为0并保持。
用TEST_EN信号作为温度测量的开始信号,这个信号至少保持1个时钟周期(CLK1MHZ),如果TEST_EN的长度超过了一次测试所需要的总的时间,则在一次测试完成以后,就直接开始进行第二次测量。
用RST_OVER信号作为总线初始化的结束和下一个状态的开始的标志信号。
用WBD_OVER信号作为命令(SKIPROM,CONVERT)结束和下一个状态开始的标志信号。
用RD_BIT_DATA & PHASE_RD_OVER表示等待温度转换结束和下一个状态开始的信号(详细的原因可参照DS18B20 datasheet的相关部分)。
用BYTE_READ_OVER &PHASENXT_RDSPAD_IDLE表示读取温度数据结束的信号(该两个信号的与表示读DS18B20 9ByteROM数据状态机完成工作并进入IDLE状态)。
下面是verilog的实现:
reg IS_CONVERT;
parameter TTST_IDLE = 6'b00_0001,
TTST_RST = 6'b00_0010,
TTST_SKPROM = 6'b00_0100,
TTST_CONVERT = 6'b00_1000,
TTST_WOVER = 6'b01_0000,
TTST_GETDATA = 6'b10_0000;
reg [5:0] TTSTSM, TTSTSMNXT;
wire PHASE_TTST_IDLE = TTSTSM[0];
wire PHASE_TTST_RST = TTSTSM[1];
wire PHASE_TTST_SKPROM = TTSTSM[2];
wire PHASE_TTST_CONVERT = TTSTSM[3];
wire PHASE_TTST_WOVER = TTSTSM[4];
wire PHASE_TTST_GETDATA = TTSTSM[5];
wire PHASENXT_TTST_IDLE = TTSTSMNXT[0];
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
TTSTSM <= TTST_IDLE;
else
TTSTSM <= TTSTSMNXT;
end
always @(posedge CLK1MHZ or negedge RESET)
begin
if(~RESET)
IS_CONVERT <= 1'b1;
else if(PHASE_TTST_IDLE)
IS_CONVERT <= 1'b1;
else if(PHASE_TTST_WOVER)
IS_CONVERT <= 1'b0;
else
IS_CONVERT <= IS_CONVERT;
end
always @(TTSTSM or TEST_EN or RST_OVER or IS_CONVERTor WBD_OVER or RD_BIT_DATA or PHASE_RD_OVER or PHASE_RD_OVER or BYTE_READ_OVERor PHASENXT_RDSPAD_IDLE ) begin
case(TTSTSM)
TTST_IDLE:
if(TEST_EN)
TTSTSMNXT = TTST_RST;
else
TTSTSMNXT = TTST_IDLE;
TTST_RST:
if(RST_OVER)
TTSTSMNXT = TTST_SKPROM;
else
TTSTSMNXT = TTST_RST;
TTST_SKPROM:
if(WBD_OVER)
if(IS_CONVERT)
TTSTSMNXT = TTST_CONVERT;
else
TTSTSMNXT = TTST_GETDATA;
else
TTSTSMNXT = TTST_SKPROM;
TTST_CONVERT:
if(WBD_OVER)
TTSTSMNXT = TTST_WOVER;
else
TTSTSMNXT = TTST_CONVERT;
TTST_WOVER:
if(RD_BIT_DATA & PHASE_RD_OVER)
TTSTSMNXT = TTST_RST;
else
TTSTSMNXT = TTST_WOVER;
TTST_GETDATA:
if(BYTE_READ_OVER & PHASENXT_RDSPAD_IDLE )
TTSTSMNXT = TTST_IDLE;
else
TTSTSMNXT = TTST_GETDATA;
default:
TTSTSMNXT = TTST_IDLE;
endcase
end
assign WD_DATA_OUT = PHASE_TTST_SKPROM ? 8'hcc :(PHASE_TTST_CONVERT ? 8'h44 : ( PHASE_RDSPAD_CMD ? 8'hbe : 8'h00) );
四,DQ总线的控制
1-Wire总线只有一根信号线,是双向的,带上拉电阻的。在FPGA中做DQ的逻辑如下,至于这么做的原因,不再做叙述。当弄懂了1-Wire总线的规范和DS18B20的基本操作的话,很容易就理解了。
wire DQ, DQ_OUT, DQ_IN, DQ_OUT_EN;
assign DQ = DQ_OUT_EN ? DQ_OUT : 1'bz;
assign DQ_IN = DQ;
wire RD_DQ_OUT = ~PHASE_RD_MPL;
wire RD_DQ_OUT_EN = PHASE_RD_MPL ;
wire WD_DQ_OUT = 1’b0;
wire WD_DQ_OUT_EN = PHASE_WD_MPL | (~WD_BIT_DATA& PHASE_WD_MOUT);
wire DQ_RST_OUT = ~ PHASE_RST_MINIT;
wire DQ_RST_EN = PHASE_RST_MINIT;
assign DQ_OUT = RD_DQ_OUT & RD_DQ_OUT_EN |WD_DQ_OUT & WD_DQ_OUT_EN | DQ_RST_OUT & DQ_RST_EN;
assign DQ_OUT_EN = RD_DQ_OUT_EN | WD_DQ_OUT_EN |DQ_RST_EN;