FPGA——驱动DHT11温湿度模块

    科技2022-09-01  99

    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。

    Processed: 0.013, SQL: 9