最近在做一个DHT11相关的东西,写了一个DHT11控制模块。参考了正点原子和野火电子的文档资料后总觉得又乱又杂,所以自己跟着数据手册写了一遍,当然了也很感谢正点原子和野火文档的帮助。
DHT11是一种数字温湿度传感器,有4个引脚但只有三个有效引脚,分别是VDD,DATA和GND,如图1所示,使用的是典型的单总线通信,即一根数据线。这根数据线需要通过上拉电阻连接到电源,使得数据线在空闲时保持高电平。数据手册建议每2秒种对数据进行一次读取,来保证数据的稳定性。
单总线数据传输位定义:一次传输40位数据,高位先出。
数据格式:8bit湿度整数+8bit湿度小数+8bit温度整数+8bit温度小数+8bit校验位。
示例数据:
00110101 00000000 00011000 00000100 01010001
湿度高8位 湿度低8位 温度高8位 温度低8位 校验位8bit
计算:
00110101+00000000+00011000+00000100=01010001
湿度:00110101 = 35H = 53
00000000 = 00H = 0 湿度为53.0%
温度:00011000 = 18H = 24
00000100 = 04H = 0.4 温度为24.4℃
并且当温度低于0时温度数据的低8位的最高位置1。由于一般不会出现负数,所以此次设计不关心此处。
时序图如图2:
外设读取步骤:
①设备上电,等待1s越过不稳定状态,由于2秒读取一次数据较稳定,所以我们等待2s。
②主机发送开始信号,数据线拉低至少18ms。此时总线由FPGA控制。
③主机将数据线拉高后释放总线等待从机响应,时间为13us。
④从机相应低电平,时间为83us。
⑤从机相应高电平,时间为87us。
⑥FPGA读取DHT11的温湿度数据。
图3为时间参数表。
由此我们可以确定使用状态机实现该设计,很轻松便可以画出状态转换图,如图4,图中状态分别对应上述外设读取的6个步骤。
接下来就根据状态转换图来进行verilog代码的编写,代码如下。
/*
description : dht11 control
*/
module dht11(
input sys_clk , //system clock
input sys_rst_n , //system reset negedge
inout dht11_data , //dht11 inout port
output reg [39:0] t_h_data
);
//---------state code
parameter WAIT = 6'b000_001,//wait state 2s
START = 6'b000_010,//make bus low 20ms
WAIT_RES = 6'b000_100,//wait respond
RES_LOW = 6'b001_000,//respond low
RES_HIGH = 6'b010_000,//respong high
REC_DATA = 6'b100_000;//receive datas
//---------time parameter
//parameter CNT_2S_MAX = 100 ,
// CNT_20MS_MAX = 1_0 ,
// CNT_1US_MAX = 50 ;
parameter CNT_2S_MAX = 100_000_000 ,
CNT_20MS_MAX = 1_000_000 ,
CNT_1US_MAX = 50 ;
//---------state define
reg [5:0] state_cur;//current state
reg [5:0] state_nex;//next state
//---------flag define
wire end_2s ; //wait 2s end
wire end_20ms ; //wait 20ms end
wire res_ok ; //respond ok
wire res_no ; //no respond
wire end_res_low ; //wait respond low end 83us
wire end_res_high; //wait respond high end 87us
wire end_rec ; //data receive end 40bits
//---------dht11_data regist
reg dht11_data_r1;
reg dht11_data_r2;
wire dht11_posedge;
wire dht11_negedge;
reg data;
reg output_en;
wire check; //the datas is correct or wrong ?
reg [39:0] t_h_data_temp;//temperature and huminity data
//---------counter define
reg [26:0] cnt_2s;
reg [19:0] cnt_20ms;
reg [6:0] cnt_nus;
reg [5:0] cnt_1us;
reg cnt_us_rst;
reg [5:0] cnt_bit;
//---------flag assignments
assign end_2s = (state_cur == WAIT && cnt_2s == CNT_2S_MAX - 1'b1) ? 1'b1 : 1'b0;
assign end_20ms = (state_cur == START && cnt_20ms == CNT_20MS_MAX - 1'b1) ? 1'b1 : 1'b0;
assign res_ok = (state_cur == WAIT_RES && cnt_nus < 20 && dht11_negedge) ? 1'b1 : 1'b0;
assign res_no = (state_cur == WAIT_RES && cnt_nus > 20) ? 1'b1 : 1'b0;
assign end_res_low = (state_cur == RES_LOW && cnt_nus > 70 && dht11_posedge) ? 1'b1 : 1'b0;
assign end_res_high = (state_cur == RES_HIGH && cnt_nus > 70 && dht11_negedge) ? 1'b1 : 1'b0;
assign end_rec = (state_cur == REC_DATA && cnt_bit >= 40) ? 1'b1 : 1'b0;
//---------dht11 assignments
assign dht11_posedge = dht11_data_r1 & ~dht11_data_r2;
assign dht11_negedge = ~dht11_data_r1 & dht11_data_r2;
assign dht11_data = output_en ? data : 1'bz;
assign check = (t_h_data_temp[39:32]+t_h_data_temp[31:24]+
t_h_data_temp[23:16]+t_h_data_temp[15:8] == t_h_data_temp[7:0])
? 1'b1 : 1'b0;//the datas is correct or wrong ?
//*********posedge and negedge detect
always@(posedge sys_clk or negedge sys_rst_n)begin
if(~sys_rst_n)begin
dht11_data_r1 <= 1'b0;
dht11_data_r2 <= 1'b0;
end
else begin
dht11_data_r1 <= dht11_data;
dht11_data_r2 <= dht11_data_r1;
end
end
//*********counter
always@(*)begin
case(state_cur)
WAIT : cnt_us_rst = 1'b1;
START : cnt_us_rst = 1'b1;
WAIT_RES : begin
if(res_ok)
cnt_us_rst = 1'b1;
else
cnt_us_rst = 1'b0;
end
RES_LOW : begin
if(end_res_low)
cnt_us_rst = 1'b1;
else
cnt_us_rst = 1'b0;
end
RES_HIGH : begin
if(end_res_high)
cnt_us_rst = 1'b1;
else
cnt_us_rst = 1'b0;
end
REC_DATA : begin
if(dht11_posedge || dht11_negedge)
cnt_us_rst = 1'b1;
else
cnt_us_rst = 1'b0;
end
default :cnt_us_rst = 1'b1;
endcase
end
//---------cnt_2s
always@(posedge sys_clk or negedge sys_rst_n)begin
if(~sys_rst_n)begin
cnt_2s <= 27'd0;
end
else begin
if(state_cur == WAIT)begin
if(cnt_2s <= CNT_2S_MAX - 1'b1)
cnt_2s <= cnt_2s + 1'b1;
else
cnt_2s <= cnt_2s;
end
else if(state_cur == REC_DATA)begin
cnt_2s <= 27'd0;
end
else begin
cnt_2s <= cnt_2s;
end
end
end
//---------cnt_20ms
always@(posedge sys_clk or negedge sys_rst_n)begin
if(~sys_rst_n)begin
cnt_20ms <= 20'd0;
end
else begin
if(state_cur == START)begin
if(cnt_20ms <= CNT_20MS_MAX - 1'b1)
cnt_20ms <= cnt_20ms + 1'b1;
else
cnt_20ms <= cnt_20ms;
end
else if(state_cur == REC_DATA)begin
cnt_20ms <= 20'd0;
end
else begin
cnt_20ms <= cnt_20ms;
end
end
end
//---------cnt_1us
always@(posedge sys_clk or negedge sys_rst_n)begin
if(~sys_rst_n)begin
cnt_1us <= 6'd0;
end
else begin
if(cnt_1us == CNT_1US_MAX - 1'b1)
cnt_1us <= 6'd0;
else if(cnt_us_rst)
cnt_1us <= 6'd0;
else
cnt_1us <= cnt_1us + 1'b1;
end
end
//---------cnt_nus
always@(posedge sys_clk or negedge sys_rst_n)begin
if(~sys_rst_n)begin
cnt_nus <= 7'd0;
end
else begin
if(cnt_us_rst)
cnt_nus <= 7'd0;
else if(cnt_1us == CNT_1US_MAX - 1'b1)
cnt_nus <= cnt_nus + 1'b1;
else
cnt_nus <= cnt_nus;
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(~sys_rst_n)begin
cnt_bit <= 6'd0;
end
else begin
if(state_cur == REC_DATA)begin
if(dht11_negedge)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
end
else begin
cnt_bit <= 6'd0;
end
end
end
//*********three stages state machine
//---------the first stage : state transmission
always@(posedge sys_clk or negedge sys_rst_n)begin
if(~sys_rst_n)
state_cur <= WAIT;
else
state_cur <= state_nex;
end
//---------the second stage : conditions
always@(*)begin
case(state_cur)
WAIT :begin
if(end_2s)
state_nex = START; //count 2s finish
else
state_nex = WAIT;
end
START :begin
if(end_20ms)
state_nex = WAIT_RES;//count 20ms finish
else
state_nex = START;
end
WAIT_RES:begin
if(res_ok) //respond
state_nex = RES_LOW;
else if(res_no) //no respond
state_nex = WAIT;
else
state_nex = WAIT_RES;
end
RES_LOW :begin
if(end_res_low)
state_nex = RES_HIGH;
else
state_nex = RES_LOW;
end
RES_HIGH:begin
if(end_res_high)
state_nex = REC_DATA;
else
state_nex = RES_HIGH;
end
REC_DATA:begin
if(end_rec)
state_nex = WAIT;
else
state_nex = REC_DATA;
end
default :begin
state_nex = WAIT;
end
endcase
end
//---------the third stage : outputs
always@(posedge sys_clk or negedge sys_rst_n)begin
if(~sys_rst_n)begin
output_en <= 1'b0;
data <= 1'b0;
end
else begin
case(state_cur)
WAIT :begin
output_en <= 1'b1;//output
data <= 1'b1;
end
START :begin
output_en <= 1'b1;//output
data <= 1'b0;
if(end_20ms)
data <= 1'b1;
end
WAIT_RES :begin
output_en <= 1'b0;//input
data <= 1'b0;
end
RES_LOW :begin
output_en <= 1'b0;//input
data <= 1'b0;
end
RES_HIGH :begin
output_en <= 1'b0;//input
data <= 1'b0;
end
REC_DATA :begin
output_en <= 1'b0;//input
data <= 1'b0;
end
default :begin
output_en <= 1'b0;//input
data <= 1'b0;
end
endcase
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(~sys_rst_n)begin
t_h_data_temp <= 40'd0;
end
else begin
if(state_cur == REC_DATA)begin
if(cnt_nus > 50 && dht11_negedge)
t_h_data_temp[39 - cnt_bit] <= 1'b1;
else if(cnt_nus < 50 && dht11_negedge)
t_h_data_temp[39 - cnt_bit] <= 1'b0;
else
t_h_data_temp <= t_h_data_temp;
end
else begin
t_h_data_temp <= t_h_data_temp;
end
end
end
always@(posedge sys_clk or negedge sys_rst_n)begin
if(~sys_rst_n)begin
t_h_data <= 40'd0;
end
else begin
if(state_cur == REC_DATA)begin
if(end_rec && check)
t_h_data <= t_h_data_temp;
else
t_h_data <= t_h_data;
end
else begin
t_h_data <= t_h_data;
end
end
end
endmodule
完成代码后仿真观察前三个状态,modelsim仿真结果如图5,可以清晰地观察到状态的转换以及转换条件。
由于没有dht11仿真模型,所以只好使用signaltap直接对实际波形进行抓取,在抓取了几次之后并且更改代码后得到了最终的正确结果,如图6,读取波形数据后可得湿度为18,温度为25,并且校验通过,数据读取正确。
随后通过数码管进行显示,数码管模块的代码非常简单,这里就不再贴出,直接查看最后的上板实验效果,只显示了温湿度的整数位,如图7,左侧为湿度,右侧为温度。
参考资料:
1.dht11数据手册
2.征途Mini《FPGA Verilog开发实战指南——基于Altera EP4CE10》
3.新起点FPGA开发指南