温湿度测量模块DHT11使用方法(FPGA)

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 模块的输出结果。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值