写DS1820的时候发现网上的都是一段式状态机,就想写一个两段式的,可是花了一个星期才写好,编译的时候总是有组合环合锁存器的警告,怎么都去不掉,一直在想是不是两段式根本写不了,都快放弃了,可是书上说没有什么是两段式写不了的。
搞了好久,后来看了特权的《两段式不可能完成的状态机》,才知道问题出在哪,就是敏感变量不能出现作为输出出现在第二段的always段中,也不能改变其值,如果要改变,必须设一个中间变量,最后在板子上调通了,下来是代码,我的注释应该还算是详细吧,我是菜鸟,有问题帮我指出来吧.
module DS1820_ctrl (
CLK,RSTn,
DQ,
readtemp,
clk1us
);
input CLK; //50MHz
input RSTn; //复位信号,低有效
inout DQ; //一线数据信号
output[7:0] readtemp; //读出的温度并送给7段数码管
output clk1us;
reg[7:0] counter_1us;
reg clk_1us;
parameter DELAY_1US = 49;
parameter DELAY_800MS = 800000;
parameter DELAY_1MS = 1000;
parameter DELAY_2MS = 2000;
parameter DELAY_10MS = 10000;
parameter DELAY_20MS = 20000;
parameter DELAY_30MS = 30000;
parameter DELAY_40MS = 40000;
parameter DELAY_50MS = 50000;
parameter DELAY_100MS = 100000;
parameter DELAY_200MS = 200000;
parameter DELAY_300MS = 300000;
parameter DELAY_400MS = 400000;
parameter DELAY_500MS = 500000;
parameter DELAY_600MS = 600000;
parameter DELAY_1S = 1000000;
parameter DELAY_2S = 2000000;
parameter DELAY_10S = 10000000;
parameter DELAY_100US = 100;
parameter DELAY_200US = 200;
parameter DELAY_300US = 300;
parameter DELAY_400US = 400;
parameter DELAY_500US = 500;
parameter DELAY_600US = 600;
parameter DELAY_550US = 550;
parameter DELAY_2US = 2;
parameter DELAY_4US = 4;
parameter DELAY_6US = 6;
parameter DELAY_8US = 8;
parameter DELAY_12US = 12;
parameter DELAY_15US = 15;
parameter DELAY_20US = 20;
parameter DELAY_25US = 25;
parameter DELAY_30US = 30;
parameter DELAY_40US = 40;
parameter DELAY_70US = 70;
parameter DELAY_850US = 850;
always@(posedge CLK or negedge RSTn) begin
if(!RSTn) counter_1us<=8'd0;
else if(counter_1us==DELAY_1US) begin
counter_1us<=8'd0;
clk_1us<=1'b1;
end
else begin
counter_1us<=counter_1us+1'b1;
clk_1us<=1'b0;
end
end
reg[23:0] counter; //
reg[23:0] counter1;
reg clk1us_start;
always@(posedge CLK or negedge RSTn) begin
if(!RSTn) counter<=24'd0;
else if(!clk1us_start) begin
counter<=24'd0; //只要允许位置0则counter归0
end
else if(clk_1us) begin
counter<=counter1+1'b1;
end
end
parameter CMD_SKIP = 8'b1100_1100; //
parameter CMD_READ = 8'b1011_1110;
parameter CMD_CONVERT = 8'b0100_0100;
parameter RESET1 = 3'b000;
parameter WRITE_BYTE1 = 3'b001;
parameter WAIT_CONVERT = 3'b010;
parameter RESET2 = 3'b011;
parameter WRITE_BYTE2 = 3'b100;
parameter READ_BYTE = 3'b101;
parameter WAIT1S = 3'b110;
//第一段:状态转移
reg[2:0] cstate; //current state
reg[2:0] nstate; //next state
reg[7:0] temp_data; //
reg[3:0] data_bit;
//reg[7:0] read_temp; //
//reg[7:0] read_temp1; //
reg[7:0] temp_data1; //
reg[3:0] data_bit1;
reg dq_ctrl; //dq_ctrl=1'b1;output dq_ctrl=1'b0;high Z
reg dq_r;
reg dq_ctrl1; //dq_ctrl=1'b1;output dq_ctrl=1'b0;high Z
reg dq_r1;
always @ (posedge CLK or negedge RSTn) //异步复位
if(!RSTn) begin
cstate <= RESET1;
end
else begin
cstate <= nstate;//注意,使用的是非阻塞赋值
temp_data<=temp_data1;
data_bit<=data_bit1;
dq_ctrl<=dq_ctrl1;
dq_r<=dq_r1;
end
//第二段;状态逻辑判断
always@(cstate or DQ or counter or data_bit or temp_data or dq_r or dq_ctrl) begin //任何一个敏感变量变化都引起变化才不会产生锁存器
nstate=cstate;
temp_data1=temp_data;
data_bit1=data_bit;
dq_ctrl1=dq_ctrl;
dq_r1=dq_r;
counter1=counter;
case(cstate)
//=========================================================
/*主机拉低DQ到0至少480US,然后释放DQ,这时DQ会被上拉电阻从0上拉到1,一旦DS1820检测到
DQ引脚从0->1这个上升沿,DS1820就会等待15-60US然后发送应答脉冲(一个60-240US的低电平)*/
RESET1: begin
clk1us_start=1'b1; //启动1us时钟计数
case(counter)
DELAY_2US: begin
dq_ctrl1=1'b1; //set to output
dq_r1=1'b0; //pull down to 0 for at least 480us
nstate=RESET1;
end
DELAY_500US: begin//拉低数据线至少480US,这里设为500US
dq_ctrl1=1'b0; //enable input
dq_r1=1'b1;
nstate=RESET1;
end
DELAY_550US: begin//DS1820在0->1上升沿等待15-60US再应答,这里设为50US
if(DQ==1'b1) begin
nstate=WAIT1S;//如果接收到的数据线电平是1,则复位错误
//nstate=RESET1;
end
else if(DQ==1'b0) begin
nstate=RESET1;
end
end
DELAY_850US: begin//收到应答信号后等待270US才进入发数据状态
temp_data1=CMD_SKIP;
nstate=WRITE_BYTE1;
clk1us_start=1'b0; //停止1us时钟计数
dq_ctrl1=1'b1; //set to output
dq_r1=1'b1;
data_bit1=0;
end
default: nstate=RESET1;
endcase
end
//=========================================================
//写1要先拉低1-15us,写0要拉低一直到写0结束,每一位的1到0作为读周期的开始
//写0写1都要保持60us
//2个写时序之间至少1us恢复期
//主机把数据线从1拉到0告诉DS1820写数据开始
WRITE_BYTE1: begin
clk1us_start=1'b1; //启动1us时钟计数
case(counter)
DELAY_20US: begin
dq_r1=1'b0; //不论写0写1都要拉低数据
nstate=WRITE_BYTE1;
end
DELAY_30US: begin
case(data_bit)
4'd0: dq_r1=temp_data[0]; //from LSB
4'd1: dq_r1=temp_data[1];
4'd2: dq_r1=temp_data[2];
4'd3: dq_r1=temp_data[3];
4'd4: dq_r1=temp_data[4];
4'd5: dq_r1=temp_data[5];
4'd6: dq_r1=temp_data[6];
4'd7: dq_r1=temp_data[7];
default: ;
endcase
nstate=WRITE_BYTE1;
end
DELAY_100US: begin //写0写1都要保持60us
clk1us_start=1'b0;//counter清0
if(data_bit<7) begin
data_bit1=data_bit+1'b1;
dq_r1=1'b1; //写完1位拉高数据
nstate=WRITE_BYTE1;
end
else if(temp_data==CMD_SKIP) begin
data_bit1=0;
dq_r1=1'b1; //写完1位拉高数据,第7位虽然写完,但在循环语句中没有拉高
temp_data1=CMD_CONVERT;
nstate=WRITE_BYTE1;
end
else if(temp_data==CMD_CONVERT) begin
data_bit1=0;
dq_r1=1'b1; //写完1位拉高数据,第7位虽然写完,但在循环语句中没有拉高
nstate=WAIT_CONVERT;
end
end
default: nstate=WRITE_BYTE1;
endcase
end
//=========================================================
WAIT_CONVERT: begin
clk1us_start=1'b1; //启动1us时钟计数
if(counter==DELAY_800MS) begin
clk1us_start=1'b0;
nstate=RESET2;
end
else begin
nstate=WAIT_CONVERT;
end
end
//=========================================================
RESET2: begin
clk1us_start=1'b1; //启动1us时钟计数
case(counter)
DELAY_2US: begin
dq_ctrl1=1'b1; //set to output
dq_r1=1'b0; //pull down to 0 for at least 480us
nstate=RESET2;
end
DELAY_500US: begin//拉低数据线至少480US,这里设为500US
dq_ctrl1=1'b0; //set to input
nstate=RESET2;
end
DELAY_550US: begin//DS1820在0->1上升沿等待15-60US再应答,这里设为50US
if(DQ==1'b1) begin
nstate=WAIT1S;//如果接收到的数据线电平是1,则复位错误
//nstate=RESET2;
end
else if(DQ==1'b0) begin
nstate=RESET2;
end
end
DELAY_850US: begin//收到应答信号后等待270US才进入发数据状态
temp_data1=CMD_SKIP;
nstate=WRITE_BYTE2;
clk1us_start=1'b0; //停止1us时钟计数
dq_ctrl1=1'b1; //set to output
dq_r1=1'b1;
data_bit1=0;
end
default: nstate=RESET2;
endcase
end
//=========================================================
WRITE_BYTE2: begin
clk1us_start=1'b1; //启动1us时钟计数
case(counter)
DELAY_20US: begin
dq_r1=1'b0; //不论写0写1都要拉低数据
nstate=WRITE_BYTE2;
end
DELAY_30US: begin
case(data_bit)
4'd0: dq_r1=temp_data[0]; //from LSB
4'd1: dq_r1=temp_data[1];
4'd2: dq_r1=temp_data[2];
4'd3: dq_r1=temp_data[3];
4'd4: dq_r1=temp_data[4];
4'd5: dq_r1=temp_data[5];
4'd6: dq_r1=temp_data[6];
4'd7: dq_r1=temp_data[7];
default: ;
endcase
nstate=WRITE_BYTE2;
end
DELAY_100US: begin //写0写1都要保持60us
//DELAY_6US: begin //写0写1都要保持60us
clk1us_start=1'b0;//counter清0
if(data_bit<7) begin
data_bit1=data_bit+1'b1;
dq_r1=1'b1; //写完1位拉高数据
nstate=WRITE_BYTE2;
end
else if(temp_data==CMD_SKIP) begin
data_bit1=0;
dq_r1=1'b1; //写完1位拉高数据
temp_data1=CMD_READ;
nstate=WRITE_BYTE2;
end
else if(temp_data==CMD_READ) begin
data_bit1=0;
dq_r1=1'b1; //写完1位拉高数据
nstate=READ_BYTE;
end
end
default: nstate=WRITE_BYTE2;
endcase
end
//=========================================================
//每一位的1到0作为读周期的开始
//读完1位数至少要延迟60us
//要右移9位
READ_BYTE: begin
clk1us_start=1'b1; //启动1us时钟计数
case(counter)
DELAY_12US: begin
dq_r1=1'b0; //不论写0写1都要拉低数据
nstate=READ_BYTE;
end
DELAY_15US: begin //主机拉低数据后至少要保持1US的低电平
dq_ctrl1=1'b0;//set to high Z
nstate=READ_BYTE;
end
DELAY_25US: begin//DS1820在主机拉低后开始的15US内都保持数据有效
case(data_bit)
//4'd0: temp_data1[0]=DQ; //from LSB
4'd1: temp_data1[0]=DQ;
4'd2: temp_data1[1]=DQ;
4'd3: temp_data1[2]=DQ;
4'd4: temp_data1[3]=DQ;
4'd5: temp_data1[4]=DQ;
4'd6: temp_data1[5]=DQ;
4'd7: temp_data1[6]=DQ;
4'd8: temp_data1[7]=DQ;
default: ;
endcase
nstate=READ_BYTE;
end
DELAY_100US: begin
if(data_bit<8) begin
data_bit1=data_bit+1'b1;
dq_ctrl1=1'b1; //set to output
clk1us_start=1'b0;//counter清0,开始下一位
nstate=READ_BYTE;
end
else begin
data_bit1=0;
dq_ctrl1=1'b1; //set to output
dq_r1=1'b1;
clk1us_start=1'b0;
nstate=WAIT1S;
end
end
default: nstate=READ_BYTE;
endcase
end
//=========================================================
WAIT1S: begin
clk1us_start=1'b1; //启动1us时钟计数
if(counter==DELAY_800MS) begin
clk1us_start=1'b0;//此处少了一句清0,致使在RESET时候要等counter计数溢出才会执行
nstate=RESET1;
end
else begin
nstate=WAIT1S;
end
end
//=========================================================
default: nstate=RESET1;
endcase
end
assign DQ = dq_ctrl ? dq_r:1'bz;
assign readtemp = temp_data;
assign clk1us = clk_1us;
endmodule
文章评论(0条评论)
登录后参与讨论