DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。其成本低、长期稳定、可以测量相对湿度和温度测量,并可以只使用一根数据线进行温湿度采集。
1、模块来源
2、参数
工作电压:3.3 - 5.5V
工作电流:1mA
测量分辨率:8 bit
湿度量程: 5 - 95 %RH
湿度精度:25℃ ±5 %RH
温度量程: -20 - 60 ℃
温度精度:25℃ ±2 ℃
通信协议:单总线
3、时序说明
DHT11 是一种数字温湿度传感器, 它通过单总线协议与微处理器通信。 正常情况下, DHT11 处于低功耗模式。 当单片机需要读取温湿度数据时, 首先发送一次复位信号。 DHT11 接收到复位信号后, 从低功耗模式转换到高速模式, 开始执行一次温湿度采集过程, 完成后通过单总线发送响应信号, 并将总线拉高, 准备传输数据。
一次完整的数据传输包括 40 位(5 字节) 的数据, 具体格式为: 8 位湿度整数数据 + 8 位湿度小数数据 + 8 位温度整数数据 + 8 位温度小数数据 + 8 位校验和。由于 DHT11 的分辨率限制, 湿度和温度的小数部分数据始终为 0。 校验和是前 4 个字节数据的累加和, 用于验证数据传输的准确性, 确保接收端接收到的数据没有错误。
DHT11 只有在接收到开始信号后才会触发温湿度采集, 如果没有接收到复位信号, 它将保持在低功耗模式, 不会主动进行数据采集。 当数据采集和传输完成后,DHT11 会自动切换回低功耗模式, 等待下一次触发信号。
通讯过程示意图如下:
总线空闲状态为高电平,主机把总线拉低等待DHT11响应,主机把总线拉低必须大于18毫秒,保证DHT11能检测到起始信号,然后拉高等待20-40us, 读取DHT11的响应信号,此时主机释放总线。DHT11接收到主机的开始信号后,发送(70~100)us低电平响应信号。然后然后 DHT11 拉高总线 (70~100)us,开始传输数据。
主机发送触发信号与DHT11响应的过程示意图如下:
由该图可知触发信号由主机发送,主机需要拉低总线至少 18ms,然后再拉高总线,延时 20~40us。DHT11 检测到触发信号后,开始一次采样,并拉低总线 80us 表示响应信号,告诉主机数据已经准备好了。然后 DHT11 拉高总线 80us,之后开始传输数据。
数字1表示方法示意图如下:
数字0表示方法示意图如下:
由示意图可知“0”的高电平持续 26~28us,“1”的高电平持续70us,每一位数据前都有 50us 的起始时隙。到此DHT11的通讯协议以及数据格式就介绍完了。
4、实战任务
使用逻辑派FPGA-G1开发板(编写 DHT11 温湿度传奇模块驱动代码, 使用按键触发对环境温度、 湿度的测量并使用串口向上位机发送当前测量到的温度、 湿度。
5、系统框架
6、程序编写
注: 单总线的时序要求较为严格,不同厂家的时序可能有所不同。如果无法正常使用,建议自行核对并调整时序设置。
module DHT11_top(
input sys_clk ,
input sys_rst_n ,
inout dht11 ,
input key_in ,
output uart_txd
);
parameter CNT_MAX = 20'd999_999; //消抖计数器
parameter WENDU_STR = 48'hce_c2_b6_c8_a3_ba;//“温度:”的GB2312编码
parameter SHIDU_STR = 48'hca_aa_b6_c8_a3_ba;//“湿度:”的GB2312编码
//信号定义
wire data_vld ;
wire [ 31: 0] temperature_data ;
wire uart_tx_busy ;
wire uart_tx_done ;
reg [ 7: 0] uart_tx_data ;
reg uart_tx_req ;
reg work_en ;
reg [ 6: 0] tx_byte_cnt ;
reg [ 47: 0] wendu_str ;
reg [ 47: 0] shidu_str ;
reg [ 19: 0] cnt_20ms ;//消抖计数器
reg key_flag ;
reg [ 7: 0] ascii_table [0:9] ;//储存0~9的ASCII值
reg [64:0] bcd_data_temperature ;//温度BCD码转换寄存器
reg [64:0] bcd_data_humidness ;//湿度BCD码转换寄存器
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
cnt_20ms <= 20'b0;
else if(key_in == 1'b1)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX && key_in == 1'b0)
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
work_en <= 1'b0;
else if(data_vld == 1'b1)
work_en <= 1'b1;
else if(tx_byte_cnt== 7'd23&&uart_tx_done)
work_en <= 1'b0;
else
work_en <= work_en;
//串口发送逻辑
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
tx_byte_cnt <= 7'b0;
end else if (work_en) begin
if (tx_byte_cnt == 7'd23&&uart_tx_done) // 总共发送 24 字节
tx_byte_cnt <= 7'b0;
else if(uart_tx_done)
tx_byte_cnt <= tx_byte_cnt + 1'b1;
end
end
reg [31:0] dec_data;
// 串口发送数据控制
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_tx_req <= 1'b0;
uart_tx_data <= 8'b0;
wendu_str <= WENDU_STR;
shidu_str <= SHIDU_STR;
ascii_table[0] = 8'd48; //'0'
ascii_table[1] = 8'd49; //'1'
ascii_table[2] = 8'd50; //'2'
ascii_table[3] = 8'd51; //'3'
ascii_table[4] = 8'd52; //'4'
ascii_table[5] = 8'd53; //'5'
ascii_table[6] = 8'd54; //'6'
ascii_table[7] = 8'd55; //'7'
ascii_table[8] = 8'd56; //'8'
ascii_table[9] = 8'd57; //'9'
end else if (work_en && !uart_tx_busy) begin
case (tx_byte_cnt) // 根据当前字节计数器发送数据
7'd0: uart_tx_data <= wendu_str[47:40]; // "温"
7'd1: uart_tx_data <= wendu_str[39:32]; // "温"
7'd2: uart_tx_data <= wendu_str[31:24]; // "度"
7'd3: uart_tx_data <= wendu_str[23:16]; // "度"
7'd4: uart_tx_data <= wendu_str[15:8]; // ":"
7'd5: uart_tx_data <= wendu_str[7:0]; // ":"
// 7'd6: uart_tx_data <= ascii_table[temperature_data[15:12]]; // 温度高字节的 ASCII
// 7'd7: uart_tx_data <= ascii_table[temperature_data[11:8]]; // 温度低字节的 ASCII
7'd6: begin
bcd_data_temperature[63:32] <= (temperature_data[15:12] << 4) + temperature_data[11:8];
uart_tx_data <= ascii_table[bcd_data_temperature[63:32]/10]; // 温度高字节的 ASCII
end
7'd7:
uart_tx_data <= ascii_table[bcd_data_temperature[63:32]%10]; // 温度低字节的 ASCII
7'd8: uart_tx_data <= 8'h2e; // 小数点
// 7'd9: uart_tx_data <= ascii_table[temperature_data[7:4]]; // 温度高字节的 ASCII
// 7'd10: uart_tx_data <= ascii_table[temperature_data[3:0]]; // 温度低字节的 ASCII
7'd9: begin
bcd_data_temperature[31:0] <= (temperature_data[7:4] << 4) + temperature_data[3:0];
uart_tx_data <= ascii_table[bcd_data_temperature[31:0]/10]; // 温度高字节的 ASCII
end
7'd10: uart_tx_data <= ascii_table[bcd_data_temperature[31:0]%10]; // 温度低字节的 ASCII
7'd11: uart_tx_data <= 8'h20; // 空格
7'd12: uart_tx_data <= shidu_str[47:40]; // "湿"
7'd13: uart_tx_data <= shidu_str[39:32]; // "湿"
7'd14: uart_tx_data <= shidu_str[31:24]; // "度"
7'd15: uart_tx_data <= shidu_str[23:16]; // "度"
7'd16: uart_tx_data <= shidu_str[15:8]; // ":"
7'd17: uart_tx_data <= shidu_str[7:0]; // ":"
7'd18: begin
bcd_data_humidness[64:32] <= (temperature_data[31:28] << 4) + temperature_data[27:24];
uart_tx_data <= ascii_table[bcd_data_humidness[64:32]/10]; // 湿度高字节的 ASCII
end
7'd19: uart_tx_data <= ascii_table[bcd_data_humidness[64:32]%10]; // 湿度高字节的 ASCII
7'd20: uart_tx_data <= 8'h2e; // 小数点
7'd21: begin
bcd_data_humidness[31:0] <= (temperature_data[23:20] << 4) + temperature_data[19:16];
uart_tx_data <= ascii_table[bcd_data_humidness[31:0]/10]; // 湿度低字节的 ASCII
end
7'd22: uart_tx_data <= ascii_table[bcd_data_humidness[31:0]%10]; // 湿度低字节的 ASCII
7'd23: uart_tx_data <= 8'h0A; // 换行符
default: uart_tx_data <= 8'b0;
endcase
uart_tx_req <= 1'b1; // 拉高请求信号
end else begin
uart_tx_req <= 1'b0; // 请求信号拉高仅一个时钟周期
end
end
DHT11_drive u_DHT11_drive(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.cap_start_flag (key_flag ),
.dht11 (dht11 ),// 单总线(双向信号)
.data_vld (data_vld ),
.temperature_data (temperature_data ) // 输出的有效数据
);
uart_tx u_uart_tx(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.uart_tx_req (uart_tx_req&work_en ),// 发送使能请求
.uart_tx_data (uart_tx_data ),// UART要发送的数据
.uart_txd (uart_txd ),// UART发送端口
.uart_tx_done (uart_tx_done ),//8bit数据发送完成
.uart_tx_busy (uart_tx_busy ) // 发送忙
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
module DHT11_drive(
input clk ,
input rst_n ,
input cap_start_flag /* synthesis PAP_MARK_DEBUG="true" */,//触发采样
inout dht11 /* synthesis PAP_MARK_DEBUG="true" */,//单总线(双向信号)
output reg data_vld ,
output reg [ 31: 0] temperature_data //输出的有效数据
);
//参数定义
localparam WAIT_1S = 7'b0000001,
WAIT_TRI = 7'b0000010 ,
START = 7'b0000100 ,
DELAY_30US = 7'b0001000 ,
BUS_REPLY_LOW = 7'b0010000 ,
BUS_REPLY_HIGH = 7'b0100000 ,
REV_data = 7'b1000000 ;
localparam TIME_1S = 999_999, //上电1s延时计数,单位us
TIME_BE = 17_999 , //主机起始信号拉低时间,单位us
TIME_GO = 30 ; //主机释放总线时间,单位us
//信号申明
reg [ 6: 0] cur_state /* synthesis PAP_MARK_DEBUG="true" */ ;//现态
reg [ 6: 0] next_state ;//次态
reg [ 4: 0] cnt ;//50分频计数器,1Mhz(1us)
reg dht11_out ;//双向总线输出
reg dht11_en ;//双向总线输出使能,1则输出,0则高阻态
reg dht11_d1 ;//总线信号打1拍
reg dht11_d2 ;//总线信号打2拍
reg clk_1us ;//us时钟
reg [ 21: 0] us_cnt /* synthesis PAP_MARK_DEBUG="true" */;//us计数器,最大可表示4.2s
reg [ 5: 0] bit_cnt /* synthesis PAP_MARK_DEBUG="true" */;//接收数据计数器,最大可以表示64位
reg [ 39: 0] data_temp ;//包含校验的40位输出
wire dht11_in ;//双向总线输入
wire dht11_rise ;//上升沿
wire dht11_fall ;//下降沿
// 脉冲扩展
reg [ 6: 0] counter ;
reg cap_start ;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cap_start <= 1'b0;
counter <= 0;
end else begin
if (cap_start_flag) begin
cap_start <= 1'b1;
counter <= 200;
end else if (counter > 0) begin
cap_start <= 1'b1; // 计数器递减,维持高电平
counter <= counter - 1;
end else begin
cap_start <= 1'b0; // 计数结束,输出恢复低电平
end
end
end
//双向端口使用方式
assign dht11_in = dht11;//高阻态的话,则把总线上的数据赋给dht11_in
assign dht11 = dht11_en ? dht11_out : 1'bz;//使能1则输出,0则高阻态
//us时钟生成,因为时序都是以us为单位,所以生成一个1us的时钟会比较方便
//50分频计数
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 5'd0;
else if(cnt == 5'd24) //每25个时钟500ns清零
cnt <= 5'd0;
else
cnt <= cnt + 1'd1;
end
//生成1us时钟
always @(posedge clk or negedge rst_n)begin
if(!rst_n)
clk_1us <= 1'b0;
else if(cnt == 5'd24) //每500ns
clk_1us <= ~clk_1us; //时钟反转
else
clk_1us <= clk_1us;
end
//检测总线上的上升沿和下降沿
assign dht11_rise = ~dht11_d2 && dht11_d1;//上升沿
assign dht11_fall = ~dht11_d1 && dht11_d2;//下降沿
always @(posedge clk_1us or negedge rst_n)begin
if(!rst_n)begin
dht11_d1 <= 1'b0;
dht11_d2 <= 1'b0;
end
else begin
dht11_d1 <= dht11;
dht11_d2 <= dht11_d1;
end
end
//同步时序描述状态转移
always @(posedge clk_1us or negedge rst_n)begin
if(!rst_n)
cur_state <= WAIT_1S;
else
cur_state <= next_state;
end
//组合逻辑判断状态转移条件,描述状态转移规律以及输出
always @(*)begin
next_state = WAIT_1S;
case(cur_state)
WAIT_1S :begin
if(us_cnt == TIME_1S) //满足上电延时的时间
next_state = WAIT_TRI;
else
next_state = WAIT_1S;
end
WAIT_TRI :begin
if(cap_start)
next_state = START;
else
next_state = WAIT_TRI;
end
START :begin
if(us_cnt == TIME_BE) //满足拉低总线的时间
next_state = DELAY_30US;
else
next_state = START;
end
DELAY_30US :begin
if(us_cnt == TIME_GO) //满足主机释放总线时间
next_state = BUS_REPLY_LOW;
else
next_state = DELAY_30US;
end
BUS_REPLY_LOW :begin
if(us_cnt <= 'd500)begin //不到500us
if(dht11_rise && us_cnt >= 'd70
&& us_cnt <= 'd100) //上升沿响应,且低电平时间介于70~100us
next_state = BUS_REPLY_HIGH; //跳转到DELAY_75us
else
next_state = BUS_REPLY_LOW; //条件不满足状态不变
end
else
next_state = START; //超过500us仍没有上升沿响应则跳转到START
end
BUS_REPLY_HIGH :begin
if(dht11_fall && us_cnt >= 'd70) //上升沿响应,且低电平时间大于70us
next_state = REV_data; //跳转到REV_data
else
next_state = BUS_REPLY_HIGH; //条件不满足状态不变
end
REV_data :begin
if(dht11_rise && bit_cnt == 'd40) //接收完了所有40个数据后会拉低一段时间作为结束
//捕捉到上升沿且接收数据个数为40
next_state = WAIT_TRI; //状态跳转到START,重新开始新一轮采集
else
next_state = REV_data; //条件不满足状态不变
end
default:next_state = START; //默认状态为START
endcase
end
//时序逻辑描述输出
always @(posedge clk_1us or negedge rst_n)begin
if(!rst_n)begin //复位状态下输出如下
dht11_en <= 1'b0;
dht11_out <= 1'b0;
us_cnt <= 22'd0;
bit_cnt <= 6'd0;
data_temp <= 40'd0;
end
else
case(cur_state)
WAIT_1S :begin
dht11_en <= 1'b0; //释放总线,由外部电阻拉高
if(us_cnt == TIME_1S)
us_cnt <= 22'd0;
else
us_cnt <= us_cnt + 1'd1;
end
WAIT_TRI :begin
dht11_en <= 1'b0;
end
START :begin
dht11_en <= 1'b1; //占用总线
dht11_out <= 1'b0; //输出低电平
if(us_cnt == TIME_BE)
us_cnt <= 22'd0;
else
us_cnt <= us_cnt + 1'd1;
end
DELAY_30US :begin
dht11_en <= 1'b0; //释放总线,由外部电阻拉高//这里是否要拉高
if(us_cnt == TIME_GO)
us_cnt <= 22'd0;
else
us_cnt <= us_cnt + 1'd1;
end
BUS_REPLY_LOW :begin
dht11_en <= 1'b0; //释放总线,由外部电阻拉高
if(us_cnt <= 'd500)begin //计时不到500us
if(dht11_rise && us_cnt >= 'd70
&& us_cnt <= 'd100) //上升沿响应,且低电平时间介于70~100us
us_cnt <= 22'd0; //计时清零
else
us_cnt <= us_cnt + 1'd1;
end
else
us_cnt <= 22'd0; //超过500us仍没有上升沿响应,则计数清零
end
BUS_REPLY_HIGH :begin
dht11_en <= 1'b0; //释放总线,由外部电阻拉高
if(dht11_fall && us_cnt >= 'd70) //上升沿响应,且低电平时间大于70us
us_cnt <= 22'd0; //计时清零
else
us_cnt <= us_cnt + 1'd1;
end
REV_data :begin
dht11_en <= 1'b0; //释放总线,由外部电阻拉高,进入读取状态
if(dht11_rise && bit_cnt == 'd40)begin //数据接收完毕
bit_cnt <= 6'd0;
us_cnt <= 22'd0;
end
else if(dht11_fall)begin //检测到低电平,则说明接收到一个数据
bit_cnt <= bit_cnt + 1'd1; //数据接收计数器+1
us_cnt <= 22'd0;
if(us_cnt <= 'd100)
data_temp[39-bit_cnt] <= 1'b0; //总共所有的时间少于100us,则说明接收到“0”
else
data_temp[39-bit_cnt] <= 1'b1; //总共所有的时间大于100us,则说明接收到“1”
end
else begin //所有数据没有接收完,且正处于1个数据的接收进程中
bit_cnt <= bit_cnt;
data_temp <= data_temp;
us_cnt <= us_cnt + 1'd1;
end
end
default:;
endcase
end
//校验读取的数据是否符合校验规则
always @(posedge clk_1us or negedge rst_n)begin
if(!rst_n)
temperature_data <= 32'd0;
else if((data_temp[7:0] == data_temp[39:32] + data_temp[31:24] +
data_temp[23:16] + data_temp[15:8]))
temperature_data <= data_temp[39:8]; //符合规则,则把有效数据赋值给输出
else
temperature_data <= temperature_data; //不符合规则,则舍弃这次读取的数据,输出仍保持上次的状态不变
end
always @(posedge clk_1us or negedge rst_n)begin
if(!rst_n)
data_vld <= 1'b0;
else if(dht11_rise && bit_cnt == 'd40)
data_vld <= 1'b1;
else
data_vld <= 1'b0;
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
module uart_tx(
input clk ,
input rst_n ,
input uart_tx_req ,//发送使能请求
input [ 7: 0] uart_tx_data ,//UART要发送的数据
output reg uart_txd ,//UART发送端口
output reg uart_tx_done,
output reg uart_tx_busy //发送忙
);
//parameter define
parameter SYS_CLK_FREQ = 50000000;
parameter UART_BPS = 115200;
localparam BAUD_CNT_MAX = SYS_CLK_FREQ/UART_BPS; //根据时钟和波特率计算波特计数器的值
//reg define
reg [ 7: 0] tx_data_reg ;//发送数据寄存器
reg [ 3: 0] tx_data_cnt ;//发送数据计数器
reg [ 15: 0] baud_cnt ;//波特率计数器
//当uart_tx_en为高时,寄存输入的并行数据,并拉高发送忙信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
tx_data_reg <= 8'b0;
uart_tx_busy <= 1'b0;
end
else if(uart_tx_req == 'd1) begin
tx_data_reg <= uart_tx_data;
uart_tx_busy <= 1'b1;
end
else if(tx_data_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - 1) begin
tx_data_reg <= 8'b0;
uart_tx_busy <= 1'b0;
end
else begin
tx_data_reg <= tx_data_reg;
uart_tx_busy <= uart_tx_busy;
end
end
always @(posedge clk or negedge rst_n)
if(!rst_n)
uart_tx_done<=1'b0;
else if(tx_data_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - 1)
uart_tx_done<=1'b1;
else
uart_tx_done<=1'b0;
//波特率计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
baud_cnt <= 16'd0;
else if(uart_tx_req)
baud_cnt <= 16'd0;
else if(uart_tx_busy) begin
if(baud_cnt < BAUD_CNT_MAX - 1'b1)
baud_cnt <= baud_cnt + 16'b1;
else
baud_cnt <= 16'd0;
end
else
baud_cnt <= 16'd0;
end
//发送位计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
tx_data_cnt <= 4'd0;
else if(uart_tx_req)
tx_data_cnt <= 16'd0;
else if(uart_tx_busy) begin
if(baud_cnt == BAUD_CNT_MAX - 1'b1)
tx_data_cnt <= tx_data_cnt + 1'b1;
else
tx_data_cnt <= tx_data_cnt;
end
else
tx_data_cnt <= 4'd0; //发送完成清零
end
//使用case给发送端口赋值
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
uart_txd <= 1'b1;
else if(uart_tx_busy) begin
case(tx_data_cnt)
4'd0 : uart_txd <= 1'b0 ; //起始位
4'd1 : uart_txd <= tx_data_reg[0];
4'd2 : uart_txd <= tx_data_reg[1];
4'd3 : uart_txd <= tx_data_reg[2];
4'd4 : uart_txd <= tx_data_reg[3];
4'd5 : uart_txd <= tx_data_reg[4];
4'd6 : uart_txd <= tx_data_reg[5];
4'd7 : uart_txd <= tx_data_reg[6];
4'd8 : uart_txd <= tx_data_reg[7];
4'd9 : uart_txd <= 1'b1 ; //停止位
default : uart_txd <= 1'b1;
endcase
end
else
uart_txd <= 1'b1; //否则为空闲
end
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
`timescale 1ns / 1ps
module DHT11_mod();
reg sys_clk ;
reg sys_rst_n ;
wire dht11 ;
reg key_in ;
wire uart_txd ;
always #10 sys_clk = ~sys_clk ;
initial
begin
#2
sys_rst_n = 0 ;
sys_clk = 0 ;
#10
sys_rst_n = 1 ;
end
DHT11_top u_DHT11_top(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.dht11 (dht11 ),
.key_in (1'd0 ),
.uart_txd (uart_txd )
);
endmodule
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
7、仿真
本实验需要与DHT11通过总线交互信息,在测试文件中编写DHT11的应答逻辑比较麻烦,我们可以将DHT11驱动模块中的关键信号打上debug标记,分析DHT11驱动模块是否正确工作,同时检查DHT11是否正确应答我们通过总线发送的信息。
DHT11的控制流程大致为主机触发、DHT11应答、DHT11发送温度数据主机接收温度数据。我们将DHT11驱动代码中的状态机cur_stste、顶层模块发送的采样触发信号cap_start_flag、DHT11总线信号dht11、微秒计数器us_cnt、接收数据比特计数器bit_cnt添加上debug标记。
将触发模式设置为cap_start_flag高电平触发,按下板卡按键k0,捕捉到的波形如下图所示,总线信号dht11在空闲状态为高电平,在顶层发送触发信号后状态机由等待触发状态(WAIT_TIR)跳转至START状态,此时FPGA占用总线并拉低总线18ms,向DHT11发送开始信号。
将触发模式设置为(cur_state = 7’b0010000&&us_cnt = 70)(状态机在接收来自DHT11的低电平响应状态),按下按键,观察捕获到的波形图,如果状态机正确跳转,那么说明我们向DHT11发送的触发信号被回应。
由上图可知我们状态机在状态机在接收来自DHT11的低电平响应状态时,dht11为低电平,并且us_cnt成功计数到70以上,满足响应时间在(70~100)us的要求,说明来自DHT11的低电平响应是正确的。
同样的我们将触发条件设置为(cur_state = 7’b0100000&&us_cnt = 70)(状态机在接收来自DHT11的高电平响应状态)按下按键,观察捕获到的波形图如下:
结合波形图可以发现状态机在接收高电平响应的状态时,us_cnt也成功计数到70us以上,说明来自DHT11的高电平响应也是正确的。至此DHT11应答成功,开始发送温度数据。
我们知道,DHT11发送的温度数据是40bit的8位湿度整数数据 + 8位湿度小数数据 + 8位温度整数数据 + 8位温度小数数据 + 8位校验和。我们将触发状态设置为(cur_state = 7’b1000000&&bit_cnt = 40)(状态机在接收来自DHT11的数据并且接收完成),再次按下按键,观察捕获到的波形图如下:
由捕获到的波形可知,状态机在接收数据状态时,成功接收数据,并且接收bit计数器成功计数到40,说明接收到了来自DHT11的完整40bit数据,至此,模块功能验证成功。
8、IO 绑定
接下来使用 Gowin 对引脚进行分配并上板验证。管脚分配如下表所示:
信号 | 方向 | 引脚 | 端口作用 | 电平标准 |
---|---|---|---|---|
sys_clk | input | T7 | 时钟 | LVCMOS33 |
sys_rst_n | input | D11 | 复位 | LVCMOS33 |
key_in | input | F10 | 按键 | LVCMOS33 |
dq | inout | N14 | 单总线 | LVCMOS33 |
9、程序下载
连接开发板的下载器,下载码流文件到开发板里面,下载完成之后,使用下载器FPGA端的性能串口,打开串口助手, 按下开发板按键 F10 按键, 等待温湿度采样完成之后, 开发板将会返回当前环境温度的测量值。 具体现象如下: