1.DHT11工作流程
DHT11采用了简化的单总线通讯。当DHT11在上电一秒后收到来自控制器(FPGA)发出的起始信号后,会向控制器发送一个响应信号,随后便会发送40位的数据。
起始信号:一个时长大于18ms小于30ms的低电平
响应信号:
数据格式:
工作时序图:
数据格式:
校验位=湿度高8位+湿度低8位+温度高8位+温度低8位;
湿度高8位对应湿度的整数部分,湿度低8位对应湿度的小数部分;
温度高8位对应温度的整数部分,温度低8位对应温度的小数部分(当温度的低8位的最高位为1时表示此时测量到的温度为0下);
!!! 主机从DHT11读取的温湿度数据总是前一次的测量值,如两次测间隔时间很长,请连 续读两次以第二次获得的值为实时温湿度值。
2.代码详解
2.1起始信号的发送
1.对使能信号en进行两次缓存,data_reg在输入时钟的上升沿对来自DHT11的数据进行采样;
always @(posedge clk or negedge rstn )
begin
if (!rstn)
begin
en_reg0<=1'b0;
en_reg1<=1'b0;
end
else
begin
en_reg0<=en;
en_reg1<=en_reg0;
end
end
always @(posedge clk )
begin
data_reg<=data;
end
2.当起始信号发送标志位send_flag有效时,对输入的50Mhz时钟clk进行分频产生一个周期为1ms的时钟clk_1ms,在clk时钟的上升沿对clk_1ms进行采样,采样结果放在寄存器clk_reg0和clk_reg1中(clk_reg1滞后于clk_reg0一个clk周期)。当~clk_reg1&clk_reg0(clk_1ms出现一个上升沿)时cnt_19ms+1;
为什么不直接用产生的周期为1ms的时钟clk_1ms直接进行计数,而是用对clk_reg0和clk_reg1来判断clk_1ms是否出现上升沿从而进行计数?
这是因为send_flag是在一个以clk为时钟的状态机中产生的,如果用clk_1ms直接进行计数,当cnt_19ms计到特定时,状态机在clk的时钟控制下跳转到下一个状态使send_flag无效,此时clk_1ms为低电平且不会在发生向高电平的跳变,此时就会导致寄存器cnt_19ms无法被正确的置0,导致下次测量使能en信号来临时模块无法正常工作;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
clk_1ms<=1'b0;
cnt_1ms<=15'd0;
end
else if (send_flag)
begin
cnt_1ms<=cnt_1ms==num_1ms ? 15'd0 : cnt_1ms+15'd1;
clk_1ms<=cnt_1ms==num_1ms ? ~clk_1ms : clk_1ms;
end
else
begin
clk_1ms<=1'b0;
cnt_1ms<=15'd0;
end
end
reg clk_reg0,clk_reg1;
always @(posedge clk or negedge rstn)
begin
if (!rstn)
begin
clk_reg0<=0;
clk_reg1<=0;
end
else if (send_flag)
begin
clk_reg0<=clk_1ms;
clk_reg1<=clk_reg0;
end
else
begin
clk_reg0<=0;
clk_reg1<=0;
end
end
wire cnt_en=~clk_reg1&clk_reg0;
always@(posedge clk or negedge rstn)
begin
if (!rstn)
begin
cnt_19ms<=5'd0;
end
else if (cnt_en)
begin
cnt_19ms<=cnt_19ms+5'd1;
end
else if (!send_flag)
begin
cnt_19ms<=5'd0;
end
else
begin
cnt_19ms<=cnt_19ms;
end
end
3.由于DHT11采用的是单总线控制,只有一条数据线,来自控制器(FPGA)和DHT11的数据都在这条数据线上传输。所以在FPGA中需要设置一个三态门来应对这种情况,在verlog中三态门对于的端口类型为inout。
当dir_reg为1时三态门为输出模式,此时FPGA可以通过该端口向外发送数据,当dir_reg为0时三态门为输入模式,此时FPGA可以通过该端口接收来自DHT11的数据。
状态机:当检测到en_reg1滞后en_reg0一个周期,当检测到测量控制en出现下降沿后send_flag为1,三态门设置为输出模式并向外输出电平直至cnt_19ms为20(定时19.5ms)。当cnt_19ms为20时三态门三态门配置为输入模式,flag_rce置1准备开始接收来自DHT11的40位数据。当完成数据的接收后状态机回到初始状态准备接收下一次的测量开始信号(en出现下降沿)
为什么要在接收完数据后才跳转到初始状态?
为了避免在数据接收的过程中,en有效从而打断一次正常的数据接收。
always @(posedge clk or negedge rstn)
begin
if (!rstn)
begin
st0<=s0;
send_flag<=1'b0;
dir_reg<=1'b0;
out_2_dht<=1'b1;
flag_rce<=1'b0;
end
else
begin
case(st0)
s0:begin
st0<=(en_reg1&~en_reg0) ? s1 : s0;
end
s1:begin
send_flag<=1'b1;
st0<= cnt_19ms==20?s2:s1;
dir_reg<=1'b1;
out_2_dht<=1'b0;
end
s2:
begin
dir_reg<=1'b0;
out_2_dht<=1'b1;
send_flag<=1'b0;
flag_rce<=1'b1;
st0<=s3;
end
s3:
begin
flag_rce<=1'b0;
st0<= done_reg==1'b1 ? s0 : s3;
end
endcase
end
end
assign io_dir=dir_reg;
assign data_2_dht11=out_2_dht;
2.2数据的接收
当检测到数据接收有效信号flag_rce为1时跳转到下一个状态等待数据总线被释放,当数据总线被释放后开始接收来自DHT11的响应信号(一个83us的低电平和一个87us的高电平),若接收到的响应信号不满足用户手册中给出的最小值则认为数据传输有误跳转到s_erro,结束数据的接收并输出一个clk时钟高电平的error信号。若接收到的响应信号符合要求则进行来自DHT11的40位数据的接收。通过观察DHT11工作流程中的数据格式我们可以发现,数据“0”和数据“1”只有高电平的时候是有差异的,所以我采用了一个比较偷懒的办法,只对数据的高电平进行计数,如果计数结果小于用户手册数据“0”的最大时间,则认为该数据为0反之为1。
在接收完40个数据后,根据DHT11工作流程中检验位的产生方法,生成校验位与接收到的数据的低8位进行比对,若相等则此次数据有效,进行数据的输出并结束,若不相等则跳转到s_erro.
always @(posedge clk or negedge rstn)
begin
if (!rstn)
begin
bit_cnt<=6'd39;
st<=s0;
cnt<=13'd0;
error_reg<=1'b0;
d_reg<=39'd0;
done_reg<=1'b0;
parity_bit<=8'd0;
end
else
begin
case(st)
s0: begin
st<= flag_rce ? s1: s0;
end
s1: begin
st<= data_reg==1'b1 ? s2 :s1;
end
s2:begin
st<= data_reg==1'b0 ? s3: s2;
cnt<=13'd1;
end
s3:begin
st<= data_reg==1'b1 ? s4 :s3;
cnt<=cnt+13'd1;
end
s4:begin
cnt<=13'd2;
st<= cnt>13'd4000 ? s5 : s_erro;
end
s5:begin
st<= data_reg==1'b0 ? s6 :s5;
cnt<=cnt+13'd1;
end
s6:begin
cnt<=13'd1;
st<= cnt>13'd4200 ? s7 : s_erro;
end
s7:begin
st<= data_reg==1'b1 ? s8 :s7;
cnt<=13'd1;
end
s8:begin
cnt<=cnt+13'd1;
st<= data_reg==1'b0 ? s9 : s8;
end
s9:begin
d_reg[bit_cnt]<=cnt<13'd1400 ? 1'b0:1'b1;
bit_cnt<= bit_cnt==6'd0 ? 6'd0 :bit_cnt-6'd1;
st<= bit_cnt==6'd0 ? s10 : s7;
end
s10:begin
parity_bit=d_reg[39:32]+d_reg[31:24]+d_reg[23:16]+d_reg[15:8];
st<=s11;
end
s11:begin
done_reg<=parity_bit==d_reg[7:0] ? 1'b1: 1'b0;
d0<=parity_bit==d_reg[7:0] ? d_reg: 40'd0;
st<=parity_bit==d_reg[7:0] ? s12:s_erro;
end
s12:begin
bit_cnt<=6'd39;
st<=s0;
cnt<=13'd0;
error_reg<=1'b0;
d_reg<=39'd0;
done_reg<=1'b0;
end
s_erro:begin
done_reg<=1'b1;
error_reg<=1'b1;
d0<=40'd0;
st<=s12;
end
endcase
end
end
3.整体代码
3.1DHT11完整驱动代码
module DHT11(
input clk,rstn,
data,en,
output reg [39:0] d0,
output done0,erro,
output io_dir,data_2_dht11
);
parameter num_1ms=15'd24_999;
localparam s0=4'b0000,s1=4'b0001,s2=4'b0011,s3=4'b0010,s4=4'b0110,s5=4'b0111,s6=4'b0101,s7=4'b0100,s8=4'b1100,s9=4'b1101,s10=4'b1111,s11=4'b1110,s12=4'b1010, s_erro=4'b1000;
reg en_reg0,en_reg1;
reg send_flag,flag_rce,done_reg,data_reg;
reg [3:0]st0;
reg [4:0]cnt_19ms;
reg dir_reg,out_2_dht;
always @(posedge clk or negedge rstn )
begin
if (!rstn)
begin
en_reg0<=1'b0;
en_reg1<=1'b0;
end
else
begin
en_reg0<=en;
en_reg1<=en_reg0;
end
end
always @(posedge clk )
begin
data_reg<=data;
end
always @(posedge clk or negedge rstn)
begin
if (!rstn)
begin
st0<=s0;
send_flag<=1'b0;
dir_reg<=1'b0;
out_2_dht<=1'b1;
flag_rce<=1'b0;
end
else
begin
case(st0)
s0:begin
st0<=(en_reg1&~en_reg0) ? s1 : s0;
end
s1:begin
send_flag<=1'b1;
st0<= cnt_19ms==20?s2:s1;
dir_reg<=1'b1;
out_2_dht<=1'b0;
end
s2:
begin
dir_reg<=1'b0;
out_2_dht<=1'b1;
send_flag<=1'b0;
flag_rce<=1'b1;
st0<=s3;
end
s3:
begin
flag_rce<=1'b0;
st0<= done_reg==1'b1 ? s0 : s3;
end
endcase
end
end
assign io_dir=dir_reg;
assign data_2_dht11=out_2_dht;
reg [14:0]cnt_1ms;
reg clk_1ms;
always @(posedge clk or negedge rstn)
begin
if(!rstn)
begin
clk_1ms<=1'b0;
cnt_1ms<=15'd0;
end
else if (send_flag)
begin
cnt_1ms<=cnt_1ms==num_1ms ? 15'd0 : cnt_1ms+15'd1;
clk_1ms<=cnt_1ms==num_1ms ? ~clk_1ms : clk_1ms;
end
else
begin
clk_1ms<=1'b0;
cnt_1ms<=15'd0;
end
end
reg clk_reg0,clk_reg1;
always @(posedge clk or negedge rstn)
begin
if (!rstn)
begin
clk_reg0<=0;
clk_reg1<=0;
end
else if (send_flag)
begin
clk_reg0<=clk_1ms;
clk_reg1<=clk_reg0;
end
else
begin
clk_reg0<=0;
clk_reg1<=0;
end
end
wire cnt_en=~clk_reg1&clk_reg0;
always@(posedge clk or negedge rstn)
begin
if (!rstn)
begin
cnt_19ms<=5'd0;
end
else if (cnt_en)
begin
cnt_19ms<=cnt_19ms+5'd1;
end
else if (!send_flag)
begin
cnt_19ms<=5'd0;
end
else
begin
cnt_19ms<=cnt_19ms;
end
end
reg [39:0]d_reg;
reg [12:0]cnt;
reg [3:0]st;
reg error_reg;
reg [5:0] bit_cnt;
reg [7:0] parity_bit;
always @(posedge clk or negedge rstn)
begin
if (!rstn)
begin
bit_cnt<=6'd39;
st<=s0;
cnt<=13'd0;
error_reg<=1'b0;
d_reg<=39'd0;
done_reg<=1'b0;
parity_bit<=8'd0;
end
else
begin
case(st)
s0: begin
st<= flag_rce ? s1: s0;
end
s1: begin
st<= data_reg==1'b1 ? s2 :s1;
end
s2:begin
st<= data_reg==1'b0 ? s3: s2;
cnt<=13'd1;
end
s3:begin
st<= data_reg==1'b1 ? s4 :s3;
cnt<=cnt+13'd1;
end
s4:begin
cnt<=13'd2;
st<= cnt>13'd4000 ? s5 : s_erro;
end
s5:begin
st<= data_reg==1'b0 ? s6 :s5;
cnt<=cnt+13'd1;
end
s6:begin
cnt<=13'd1;
st<= cnt>13'd4200 ? s7 : s_erro;
end
s7:begin
st<= data_reg==1'b1 ? s8 :s7;
cnt<=13'd1;
end
s8:begin
cnt<=cnt+13'd1;
st<= data_reg==1'b0 ? s9 : s8;
end
s9:begin
d_reg[bit_cnt]<=cnt<13'd1400 ? 1'b0:1'b1;
bit_cnt<= bit_cnt==6'd0 ? 6'd0 :bit_cnt-6'd1;
st<= bit_cnt==6'd0 ? s10 : s7;
end
s10:begin
parity_bit=d_reg[39:32]+d_reg[31:24]+d_reg[23:16]+d_reg[15:8];
st<=s11;
end
s11:begin
done_reg<=parity_bit==d_reg[7:0] ? 1'b1: 1'b0;
d0<=parity_bit==d_reg[7:0] ? d_reg: 40'd0;
st<=parity_bit==d_reg[7:0] ? s12:s_erro;
end
s12:begin
bit_cnt<=6'd39;
st<=s0;
cnt<=13'd0;
error_reg<=1'b0;
d_reg<=39'd0;
done_reg<=1'b0;
end
s_erro:begin
done_reg<=1'b1;
error_reg<=1'b1;
d0<=40'd0;
st<=s12;
end
endcase
end
end
assign done0=done_reg;
assign erro=error_reg;
endmodule
3.2调用示例
当复位后1s,led灯亮起此时按下按键en开始进行一次温湿度数据采集,若采集到的数据有误则会重新进行一次采集直至采集到正确数据。采集到的数据通过sender模块由通过串口发送到上位机。
module DHT11_code (
input clk ,rstn,en,
inout data_dht11,
output tx,
output led,test
);
reg[26:0] cnt;
reg ready;
always @ (posedge clk or negedge rstn)
begin
if (!rstn)
begin
cnt<=27'd0;
ready<=1'b0;
end
else
begin
cnt<=cnt==27'd50_000_000 ? 27'd50_000_000:cnt+27'd1;
ready<= cnt==27'd50_000_000 ? 1'b1:1'b0;
end
end
assign led=~ready;
reg clk_us;
reg [4:0] cnt_num;
always @(posedge clk or negedge rstn)
begin
if (!rstn)
begin
clk_us<=1'b0;
cnt_num<=5'd0;
end
else
begin
cnt_num<=cnt_num==5'd25 ? 5'd0:cnt_num+5'd1;
clk_us<=cnt_num==5'd25 ? ~clk_us:clk_us;
end
end
reg key_buff0,key_buff1;
always @(posedge clk_us or negedge rstn)
begin
if(!rstn)
begin
key_buff0<=1'b1;
key_buff1<=1'b1;
end
else
begin
key_buff0<=en;
key_buff1<=key_buff0;
end
end
wire en_flag,erro;
assign en_flag=key_buff1&~key_buff0&ready|erro;
wire out_2_dht11;
wire io_ctrl;
assign data_dht11 = io_ctrl==1'b1 ? out_2_dht11 : 1'bz;
wire [39:0]dht11_data;
wire done_2_uart;
DHT11 U0(
.clk(clk),.rstn(rstn),
.data(data_dht11),.en(en_flag),
.d0(dht11_data),
.done0(done_2_uart),.erro(erro),
.io_dir(io_ctrl),.data_2_dht11(out_2_dht11)
);
wire send_done;
sender U1(.clk(clk),.rstn(rstn),.data_flag(done_2_uart),
.data(data_dht11),
.done(send_done),.tx_d(tx)
);
endmodule
用signaltap II抓到的DHT11 模块的输出结果。文章来源:https://www.toymoban.com/news/detail-786338.html
文章来源地址https://www.toymoban.com/news/detail-786338.html
到了这里,关于温湿度测量模块DHT11使用方法(FPGA)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!