1、实验目的
使用逻辑派Z1开发板(基于紫光同创 compa 系列 PGC4KD-6ILPG144 芯片)编写DHT11温湿度传奇模块驱动代码,使用按键触发对环境温度、湿度的测量并使用串口向上位机发送当前测量到的温度、湿度。
2、实验原理
2.1、DHT11模块介绍
DHT11是一种数字温湿度传感器,集成了温度和湿度测量功能。它采用电容式感湿元件和负温度系数电阻测温元件,并结合高性能8位单片机进行信号的采集处理和输出。DHT11通过单线数字接口与微处理器通信,输出经过校准的数字信号,温度测量范围为0至50摄氏度,精度为±2℃;湿度测量范围为20%至90%RH,精度为±5%RH。因其良好的稳定性和性价比,被广泛应用于各种环境监测设备、智能家居系统和自动化控制领域。其模块图如下:
模块接口描述如下表:
2.2、DHT11模块通讯协议以及数据格式
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的表示方法示意图如下:
数字1表示方法示意图
数字0表示方法示意图
由示意图可知“0”的高电平持续 26~28us,“1”的高电平持续70us,每一位数据前都有 50us 的起始时隙。到此DHT11的通讯协议以及数据格式就介绍完了。
3、代码设计
DHT11驱动模块端口描述如下表:
DHT11驱动模块代码如下:
`timescale 1ns / 1ps
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
模块使用一个三段式状态机处理板卡与DHT11的通讯逻辑。我们可以通过状态机的跳转过程来理解模块的工作逻辑。这个状态机总共有如下状态:WAIT_1S(上电延迟状态)、WAIT_TRI(等待触发状态)、START(发送触发信号状态)、DELAY_30US(主机释放总线30us)、BUS_REPLY_LOW(接收DHT11低电平响应)、BUS_REPLY_HIGH(接收DHT11高电平响应)、REV_data(解析DHT11数据)。下面我们挨个解释这些状态:
- WAIT_1S:上电后等待1秒的时间,确保DHT11传感器稳定工作。
- WAIT_TRI:等待外部触发信号cap_start_flag,用于启动温湿度数据采集。cap_start_flag在顶层模块由按键产生。
- START:发送触发信号,拉低总线18毫秒,使DHT11传感器进入响应模式。
- DELAY_30US:主机释放总线30微秒,等待DHT11传感器的响应信号。
- BUS_REPLY_LOW:检测DHT11传感器的低电平响应信号,低电平持续时间由us_cnt计数,需要满足低电平持续时间在70~100us。
- BUS_REPLY_HIGH:检测DHT11传感器的高电平响应信号,低电平持续时间由us_cnt计数,需要满足高电平持续时间大于在70us。
- REV_data:当响应信号接收无误之后开始,接收DHT11传感器发送的40位数据,并根据接收到的数据位更新内部数据存储,直到接收完所有数据。
- 这里需要注意一下,代码中:assign dht11 = dht11_en ? dht11_out : 1'bz;
通过dht11_en信号来控制双向端口dht11。当dht11_en为1时双向端口dht11为输出,当dht11_en为0时双向端口dht11为输入。这是一种常用的控制双向端口的方式。
模块顶层代码端口描述如下表:
顶层模块代码如下:
`timescale 1ns / 1ps
module DHT11_top(
input clk ,
input 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值
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
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 clk or negedge rst_n)
if(rst_n == 1'b0)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
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 clk or negedge rst_n) begin
if (!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
// 串口发送数据控制
always @(posedge clk or negedge rst_n) begin
if (!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'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'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: uart_tx_data <= ascii_table[temperature_data[31:28]]; // 湿度高字节的 ASCII
7'd19: uart_tx_data <= ascii_table[temperature_data[27:24]]; // 湿度高字节的 ASCII
7'd20: uart_tx_data <= 8'h2e; // 小数点
7'd21: uart_tx_data <= ascii_table[temperature_data[23:20]]; // 湿度低字节的 ASCII
7'd22: uart_tx_data <= ascii_table[temperature_data[19:16]]; // 湿度低字节的 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 (clk ),
.rst_n (rst_n ),
.cap_start_flag (key_flag ),
.dht11 (dht11 ),// 单总线(双向信号)
.data_vld (data_vld ),
.temperature_data (temperature_data ) // 输出的有效数据
);
uart_tx u_uart_tx(
.clk (clk ),
.rst_n (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
我们在顶层模块接收用户输入的按键信号key_in使用一个20ms计数器对其进行消抖产生温湿度驱动模块的采样触发信号cap_start_flag。将DHT11得到的数据通过串口发送到上位机进行查看,汉字使用GB2312编码,模块中初始化了一个ascii_table用来储存阿拉伯数字0~9的ASCII值,将DHT11采样得到的温湿度数据在ascii_table索引到对应的ASCII值进行发送。其中串口发送部分的知识在之前的章节已有介绍,读者若对这部分不熟悉,可到串口回环章节进行查看。
约束代码如下:
define_attribute {p:dht11} {PAP_IO_DIRECTION} {INOUT}
define_attribute {p:dht11} {PAP_IO_LOC} {92}
define_attribute {p:dht11} {PAP_IO_VCCIO} {1.2}
define_attribute {p:dht11} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:dht11} {PAP_IO_DRIVE} {2}
define_attribute {p:dht11} {PAP_IO_NONE} {TRUE}
define_attribute {p:dht11} {PAP_IO_SLEW} {SLOW}
define_attribute {p:uart_txd} {PAP_IO_DIRECTION} {OUTPUT}
define_attribute {p:uart_txd} {PAP_IO_LOC} {33}
define_attribute {p:uart_txd} {PAP_IO_VCCIO} {1.2}
define_attribute {p:uart_txd} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:uart_txd} {PAP_IO_DRIVE} {2}
define_attribute {p:uart_txd} {PAP_IO_NONE} {TRUE}
define_attribute {p:uart_txd} {PAP_IO_SLEW} {SLOW}
define_attribute {p:clk} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:clk} {PAP_IO_LOC} {5}
define_attribute {p:clk} {PAP_IO_VCCIO} {1.2}
define_attribute {p:clk} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:clk} {PAP_IO_PULLUP} {TRUE}
define_attribute {p:key_in} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:key_in} {PAP_IO_LOC} {20}
define_attribute {p:key_in} {PAP_IO_VCCIO} {1.2}
define_attribute {p:key_in} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:key_in} {PAP_IO_PULLUP} {TRUE}
define_attribute {p:rst_n} {PAP_IO_DIRECTION} {INPUT}
define_attribute {p:rst_n} {PAP_IO_LOC} {19}
define_attribute {p:rst_n} {PAP_IO_VCCIO} {1.2}
define_attribute {p:rst_n} {PAP_IO_STANDARD} {LVCMOS12}
define_attribute {p:rst_n} {PAP_IO_PULLUP} {TRUE}
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
4、代码仿真
由于本实验需要与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数据,至此,模块功能验证成功。
5、实验现象
将程序下载进入板卡后,将DHT11按照约束文件正确连接,打开上位机串口助手,按下板卡按键k0,可得到如下结果。