DHT11模块
DHT11是比较简单的一个模块,一共三个引脚:VCC、GND和双向数据线DATA,所以DHT11有主从之分。我们用FPGA当作主机,控制DATA总线向从机DHT11模块发送起始信号去采集温度湿度数据,然后DATA总线控制权交给DHT11用来传输数据给FPGA,就完成一次温湿度检测。
具体的控制过程还要参考DHT11模块的时序图:

首先在给模块上电以后要等待至少1S的时间,越过不稳定期,然后就可以发送开始信号,开始信号是一个不低于18ms的低电平信号,其实发送完这个开始信号以后,总线控制权就可以交给DHT11了,时序图中说要主机拉高20-40us,其实经过实测并没有必要,直接去检测DATA的下降沿即可,然后就是DHT的响应信号,DHT11会先拉低总线80us,然后再拉高80us,为了方便,这边也是直接检测上升沿和下降沿即可。在DHT响应过后就是数据的传输。
这里数据传输有点特殊,每一个有效bit位的传输过程是:DHT11先拉低总线50us,然后拉高总线,如果拉高的时间在26us-28us之间,则表示这个bit位为0
而如果拉高的时间为70us,则这个bit位为1。
看着有点麻烦,其实FPGA处理起来非常方便,我们只要检测每一次上升沿,在这个上升沿过后就开启一个计数器,计到30us停止,这时再去看现在总线上的值是0还是1,如果是0则表明总线已经被拉低,这个bit位为0,而如果是1则这个bit位为1。
在发送完最后一位数据后,DHT11会最后一次拉低总线50us,接着把总线控制权交还给FPGA,这时我们可以设置一个时间间隔,在这个时间之后,再次发出开始检测信号,开启下一次的检测。
我们还需要注意数据部分约定的格式,如图所示:
要注意这40bit的数据都是高位先出,也就是最先输入给FPGA的是湿度整数的最高位,最后输入的是校验和的最低位。
这里的校验和计算方法是将前四个字节的温湿度数据依次相加,然后取这个和的低八位。如果计算出来的校验和和收到的校验和一致,即为有效数据,否则为无效,将其丢弃。
verilog代码
遵循上面所述,结合状态机、计数器等手段就可以很容易写出驱动代码:
module temperature_capture#(
parameter TIME_1S = 50_000_000 ,
parameter TIME_20MS = 1_000_000 ,
parameter TIME_30US = 1500 ,
parameter TIME_0P5S = 25_000_000 ,
parameter BIT_NUM = 40
)
(
input clk ,
input reset ,
inout wire dht11 ,
output logic [31:0] dout ,
output logic dout_vld
);
parameter INIT = 7'b000_0001 ;
parameter HOST_LOW = 7'b000_0010 ;
parameter HOST_HIGH = 7'b000_0100 ;
parameter DHT_ACK_LOW = 7'b000_1000 ;
parameter DHT_ACK_HIGH = 7'b001_0000 ;
parameter DHT_DATA = 7'b010_0000 ;
parameter WAIT_0P5S = 7'b100_0000 ;
logic [ 6:0] state_c ;
logic [ 6:0] state_n ;
logic [31:0] cnt ;
logic init_end ;
logic host_low_end ;
logic host_high_end ;
logic dht_low_end ;
logic dht_ack_end ;
logic dht_data_end ;
logic wait_0p5s_end ;
logic dht_ctrl_flag ;
logic dht11_temp1 ;
logic dht11_temp2 ;
logic dht_pos ;
logic dht_neg ;
logic dht11_buffer ;
logic [10:0] cnt_dht ;
logic add_cnt_dht ;
logic end_add_cnt_dht ;
logic [ 5:0] dht_bit_cnt ;
logic add_dht_bit_cnt ;
logic end_cnt_bit ;
logic end_cnt_bit_temp;
logic dht_bit_end_flag;
logic dht_data ;
logic dout_vld_temp ;
logic [39:0] dht_data_temp ;
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
dht_ctrl_flag <= 0 ;
else if(dht_data_end)
dht_ctrl_flag <= 0 ;
else if(host_low_end)
dht_ctrl_flag <= 1 ;
end
always @(posedge clk or negedge reset)begin
if(reset==1'b0)begin
dht11_temp1 <= 0 ;
dht11_temp2 <= 0 ;
end
else if(dht_ctrl_flag)begin
dht11_temp1 <= dht11 ;
dht11_temp2 <= dht11_temp1 ;
end
end
assign dht_pos = dht11_temp1==1 && dht11_temp2==0 ;
assign dht_neg = dht11_temp1==0 && dht11_temp2==1 ;
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
state_c <= INIT ;
else
state_c <= state_n ;
end
always @(*)begin
case(state_c)
INIT:begin
if(init_end)
state_n = HOST_LOW ;
else
state_n = state_c ;
end
HOST_LOW:begin
if(host_low_end)
state_n = HOST_HIGH ;
else
state_n = state_c ;
end
HOST_HIGH:begin
if(host_high_end)
state_n = DHT_ACK_LOW ;
else
state_n = state_c ;
end
DHT_ACK_LOW:begin
if(dht_low_end)
state_n = DHT_ACK_HIGH ;
else
state_n = state_c ;
end
DHT_ACK_HIGH:begin
if(dht_ack_end)
state_n = DHT_DATA ;
else
state_n = state_c ;
end
DHT_DATA:begin
if(dht_data_end)
state_n = WAIT_0P5S ;
else
state_n = state_c ;
end
WAIT_0P5S:begin
if(wait_0p5s_end)
state_n = HOST_LOW ;
else
state_n = state_c ;
end
default:begin
state_n = INIT ;
end
endcase
end
assign init_end = state_c==INIT && cnt==0 ;
assign host_low_end = state_c==HOST_LOW && cnt==0 ;
assign host_high_end = state_c==HOST_HIGH && dht_neg ;
assign dht_low_end = state_c==DHT_ACK_LOW && dht_pos ;
assign dht_ack_end = state_c==DHT_ACK_HIGH && dht_neg ;
assign dht_data_end = state_c==DHT_DATA && dht_bit_end_flag&& dht_pos ;
assign wait_0p5s_end = state_c==WAIT_0P5S && cnt==0 ;
assign dht11_buffer = (state_c==INIT || state_c==HOST_LOW || state_c==WAIT_0P5S) ? (state_c==HOST_LOW ? 0 : 1) : 1'bz ;
assign dht11 = dht11_buffer ;
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
cnt <= TIME_1S-1 ;
else if(init_end || wait_0p5s_end)
cnt <= TIME_20MS-1 ;
else if(dht_data_end)
cnt <= TIME_0P5S-1 ;
else if(cnt!=0)
cnt <= cnt-1 ;
end
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
cnt_dht <= 0 ;
else if(add_cnt_dht)begin
if(end_add_cnt_dht)
cnt_dht <= 0 ;
else
cnt_dht <= cnt_dht+1 ;
end
end
assign end_add_cnt_dht = add_cnt_dht && cnt_dht==TIME_30US-1 ;
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
add_cnt_dht <= 0 ;
else if(end_add_cnt_dht)
add_cnt_dht <= 0 ;
else if(state_n==DHT_DATA && dht_pos)
add_cnt_dht <= 1 ;
end
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
dht_bit_cnt <= 0 ;
else if(add_dht_bit_cnt)begin
if(end_cnt_bit)
dht_bit_cnt <= 0 ;
else
dht_bit_cnt <= dht_bit_cnt+1 ;
end
end
assign add_dht_bit_cnt = end_add_cnt_dht;
assign end_cnt_bit = add_dht_bit_cnt && dht_bit_cnt==BIT_NUM-1 ;
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
end_cnt_bit_temp <= 0 ;
else
end_cnt_bit_temp <= end_cnt_bit ;
end
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
dht_bit_end_flag <= 0 ;
else if(dht_data_end)
dht_bit_end_flag <= 0 ;
else if(end_cnt_bit)
dht_bit_end_flag <= 1 ;
end
assign dht_data = (add_dht_bit_cnt && dht11_temp2==1) ? 1 : 0 ;
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
dht_data_temp <= 0 ;
else if(add_dht_bit_cnt)
dht_data_temp <= {dht_data_temp[38:0],dht_data} ;
end
always @(posedge clk or negedge reset)begin
if(reset==0)
dout_vld_temp <= 0 ;
else if(end_cnt_bit_temp && (dht_data_temp[7:0]==dht_data_temp[39:32]+dht_data_temp[31:24]+dht_data_temp[23:16]+dht_data_temp[15:8]))
dout_vld_temp <= 1 ;
else
dout_vld_temp <= 0 ;
end
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
dout_vld <= 0 ;
else
dout_vld <= dout_vld_temp ;
end
always @(posedge clk or negedge reset)begin
if(reset==1'b0)
dout <= 0 ;
else if(dout_vld_temp)
dout <= dht_data_temp[39:8] ;
else
dout <= 0 ;
end
endmodule
仿真验证
箭头标出的两个地方表示模块的最终输出:32位的dout数据和数据有效标志dout_vld,传给下游模块去处理。
RTL图
这是整个工程的RTL图,后面两个模块就是提取温湿度数据,并把它们显示到数码管上。
上板验证
左边是当前湿度,右边是当前温度,后面的00是小数部分,因为DHT11的灵敏度不高,小数部分都是输出的0。