23. DHT11数字温湿度传感器¶
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有极高的可靠性与卓越的长期稳定性。具有成本低、抗干扰力强、长期稳定等优点,可应用于暖通空调、除湿器、农业、医疗等相关湿度检测控制。
本章节将带领大家学习DHT11的工作方式,以及如何控制其去进行温湿度测量并在数码管上进行显示。
23.1. 理论学习¶
23.1.1. dht11外形及参数¶
图 29‑1 DHT11实物图
如图 29‑1为DHT11的实物图,它一共有四个引脚,各引脚说明如表格 29‑1所示:
表格 29‑1 引脚说明
引脚 |
引脚描述 |
---|---|
VDD |
直流供电3.3V~5.5V |
DATA |
串行数据,单总线 |
NC |
空脚 |
GND |
接地,电源负极 |
图 29‑2 DHT11性能参数
图 29‑2为DHT11的性能参数,可以看到其湿度的量程为5%RH—95%RH,误差为±5%RH。温度的量程为-20℃—60℃,误差为±2℃。需要注意的是图上的采样周期要大于2S/次,也就是说我们要大于2S才能读取一次温湿度。
23.2. dht11通信方式¶
23.2.1. 单总线说明¶
DHT11器件采用简化的单总线通信。单总线即只有一根数据线,系统中的数据交换、控制均由单总线完成。设备(主机或从机)通过一个漏极开路或三态端口连至该数据线,以允许设备在不发送数据时能够释放总线。单总线通常要求外接一个约4.7kΩ的上拉电阻,这样,当总线闲置时,其状态为高电平。由于它们是主从结构,只有 主机呼叫从机时,从机才能应答,因此主机访问器件都必须严格遵循单总线序列,如果出现序列混乱,器件将不响应主机。
23.2.1.1. 单总线传送数据位定义¶
主机与DHT11之间的通讯和同步采用单总线数据格式,一次传送40位数据,高位先出。数据格式为:8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据+8bit校验位。(其中湿度小数部分为0)
校验位数据定义:“8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据”8bit校验位等于所得结果的末8位。
图 29‑3 信号传输定义图
如图 29‑3所示为各个信号的定义图。关于数据的格式在此做个说明,首先湿度的小数部分为0,所以输出的湿度为整数。而温度数据是可以显示小数部分的,如:接收到的40位数据为:00110101(湿度高八位)+00000000(湿度低八位)+00011000(温度高八位)+00000100(温度低八位)+0 10110001(校验位)。通过计算:00110101+00000000+00011
000+00000100=01010001与校验位相对应,结果正确。所以其湿度温度分别为:
湿度=整数+小数=00110101+0=35H=53.0%RH
温度=整数+小数=00011000+00000100=18H+04H=24℃+0.4℃=24.4℃。
当温度低于0℃时,温度数据的8bit小数数据的最高位为1。这时其最高位的数据值是不计算在显示的小数值之内的。事实上通过实际的测量发现,目前DHT11温度只能精确到0.1℃,所以温度8bit小数数据的值是小于10的,我们应用时用小数数据的低四位来表示温度的小数值,最高位表示温度的正负即可。
23.2.1.2. 数据时序图¶
主机发送一次开始信号后,DHT11从低功耗模式转换到高速模式,待主机发送的开始信号结束后,DHT11发送响应信号,送出40bit的数据。信号发送如图 29‑4所示:
图 29‑4 数据时序图
由于主机从DHT11读取的温湿度数据总是前一次的测量值,如果两次测量间隔过长,请连续两次以第二次获得的值为实时温湿度值。
读取步骤
主机和从机之间的通信可以通过以下几个步骤完成(主机读取DHT11温湿度数据的步骤)。
步骤一:
DHT11上电后(DHT11上电后要等待1S以越过不稳定状态在此期间不能发送任何指令),测试环境温湿度数据,并记录数据,同时DHT11的DATA数据线由上拉电阻拉高一直保持高电平;此时DHT11的单总线引脚处于输入状态,时刻检测外部信号。
步骤二:
主机发送开始信号:输出低电平,持续时间不小于18ms且不超过30ms。发送完之后释放总线等待DHT11应答。发送信号如图 29‑5所示:
图 29‑5 起始信号时序图
步骤三:
DHT11的DATA引脚检测到外部信号有低电平时,等待外部信号低电平结束,延迟后DHT11的DATA引脚输出83us的低电平作为应答信号,紧接着输出87us的高电平通知外设准备接收数据,发送信号如图 29‑6所示:
图 29‑6 DHT11响应信号时序图
步骤四:
由DHT11的DATA引脚输出40位数据,主机根据I/O电平的变化接收40位数据,位数据 “0” 的格式为:54us的低电平后23~27us的高电平,位数据 “1” 的格式为:54us的低电平后68~74us的高电平。位数据“0”、“1”格式信号如图 29‑7所示:
图 29‑7 数据时序图
结束信号:
DHT11的DATA引脚输出40位数据后,继续输出低电平54us后转为输入状态,由于上拉电阻随之变为高电平。DHT11内部重测环境温湿度数据,并记录数据,等待外部信号的到来。
表格 29‑2 各信号持续时间表
符号 |
参数 |
min |
type |
max |
单位 |
---|---|---|---|---|---|
Tbe |
主机起始信号拉低时间 |
18 |
20 |
30 |
ms |
Tgo |
主机释放总线时间 |
10 |
13 |
20 |
us |
Trel |
响应低电平时间 |
81 |
83 |
85 |
us |
Treh |
响应高电平时间 |
85 |
87 |
88 |
us |
TLOW |
信号“0”、“1”低电平时间 |
52 |
54 |
56 |
us |
THO |
信号“0”高电平时间 |
23 |
24 |
27 |
us |
TH1 |
信号“1”高电平时间 |
68 |
71 |
74 |
us |
Ten |
传感器释放总线时间 |
52 |
54 |
56 |
us |
表格 29‑2为各信号的电平持续时间范围,在我们设计时,按照表中的参数进行和图 29‑4的时序图设计即可。
通过上面对DHT11的了解相信大家对具体怎么去控制DHT11已经有了具体的思路,下面我们进入实战演练来加深对DHT11的了解。
23.3. 实战演练¶
23.3.1. 实验目标¶
实验目标:控制DHT11,读出湿度温度数据显示在数码管中,通过按键使湿度和温度在数码管中切换显示。
23.3.2. 硬件资源¶
本次实验我们需使用到开发板上的按键、数码管及DHT11接口,其中按键和数码管硬件资源在前面章节已有讲解,这里就不再过多描述。开发板上的DHT11接口如图 29‑8所示。
图 29‑8 DHT11接口
其原理图如图 29‑9所示,该接口是单总线接口,可接DS18B20(温度传感器),DHT11(温湿度传感器),DS18B20传感器我们在前面章节已讲解完。
图 29‑9 DHT11接口原理图
23.4. 程序设计¶
23.4.1. 整体说明¶
根据实验目标,首先需要一个数码管动态显示模块,在“数码管的动态显示”章节已详细讲述,直接调用即可。其次我们是用按键进行湿度和温度切换显示的,所以我们还需要一个按键消抖模块来产生按键标志信号。最后需要一个DHT11控制模块使DHT11读出湿度温度的值。下面给出本次实验的整体框图,如图 29‑10所示。
图 29‑10 DHT11温湿度显示实验整体框图
通过上图可以看到,该工程共分四个模块。各模块简介见表格 29‑3。
表格 29‑3 dht11工程模块简介
模块名称 |
功能描述 |
---|---|
key_filter |
按键消抖模块 |
dht11_ctrl |
dht11控制模块 |
seg_595_dynamic |
数码管动态显示模块 |
dht11 |
顶层模块 |
其中按键消抖模块和数码管动态显示模块在前面章节已经做了详细的描述,所以我们只需要写个DHT11控制模块即可。
23.4.1.1. 按键消抖模块¶
在前面按键消抖章节已经对按键消抖模块进行了详细的讲解,在此就不再进行说明,直接调用这个模块即可。
23.4.1.2. 数码管动态显示模块¶
在“数码管的动态显示”章节中我们已经对数码管动态显示模块做了详细的讲解,在此就不再讲解了,在这里我们直接调用这个模块即可。需要注意的是该模块下还有子模块我们没有在整体框图中画出,该模块框图如图 29‑11所示。
图 29‑11 数码管显示模块
23.4.1.3. DHT11控制模块¶
模块框图
本小节我们对DHT11控制模块做个详细的介绍,首先是模块框图,如图 29‑12所示。
图 29‑12 DHT11控制模块
这个模块的作用就是将实时的环境湿度,温度转换出来,然后将转换出来的温湿度读出来,通过按键切换显示在数码管上。模块各个信号的简介,如表格 29‑4所示。
表格 29‑4 DHT11控制模块输入输出信号描述
信号 |
位宽 |
类型 |
功能描述 |
---|---|---|---|
sys_clk |
1bit |
input |
系统时钟,频率50MHz |
sys_rst_n |
1bit |
input |
复位信号,低电平有效 |
key_flag |
1bit |
input |
按键有效信号 |
dht11 |
1bit |
inout |
控制总线 |
sign |
1bit |
output |
输出温度符号位 |
data_out |
20bit |
output |
输出湿度、温度值 |
系统时钟、复位信号、按键有效信号、输出湿度温度值、输出符号位、这些信号一目了然,就不过多介绍了。下面详细介绍一下dht11信号:dht11信号的类型为inout(输入输出型)既可以作为输入也可以作为输出,它是DHT11与主机之间的通信单总线,我们即可以通过单总线输入数据去控制DHT11,DHT11也 可以通过单总线向主机传出数据。所以它是输入输出型,同时它是单总线,即位宽为1bit。
状态机
根据DHT11的数据时序图可以画出状态跳转图来进一步了解如何控制DHT11,以及读出DHT11的湿度温度值。如图 29‑13所示。
图 29‑13 dht11_ctrl模块状态跳转图
表格 29‑5 状态机状态描述
状态名称 |
状态描述 |
---|---|
S_WAIT_1S |
上电等待1S状态 |
S_LOW_18MS |
发送18ms低电平状态 |
S_DLY1 |
拉高等待状态 |
S_REPLY |
DHT11回应状态 |
S_DLY2 |
拉高准备输出状态 |
S_RD_DATA |
读数据状态 |
状态机初始状态为S_WAIT_1S,上电后等待1s后跳转到S_LOW_18MS状态,在这个状态时主机向DHT11发送开始信号(18ms的低电平)。发送完开始信号后从时序图可以看到需要拉高等待,所以我们需要一个拉高等待的状态。等待10us后,跳转到S_REPLY状态,在这个状态若检测到了响应信号则跳到 下一状态,若一段时间后仍没有检测到响应信号,则跳回到S_LOW_18MS状态重新发送起始信号。为何要这么做呢?发送了起始信号DHT11不就是会响应吗?这个问题留在下一段为大家讲解。当检测到DHT11发送的响应信号后就跳转到S_DLY2状态等待一段时间后检测到下降沿到来说明DHT11开始发数据了,此时 跳转到读数据状态接收数据。当接收完最后一个数据时跳回到S_LOW_18MS状态发送开始信号,开始下一次的湿度温度检测。(各个状态的时间以表格 29‑2 各信号持续时间表为准进行设计)
在上一段中留下了一个问题,这里为大家解答。从图 29‑2中表3电气特性可以看到DHT11的采样间隔为2S/次。所以如果我们第二次发送开始信号与第一次的时间过短,DHT11是不会响应的,如果不跳回去重新开始发送开始信号,那么DHT11就只能读取一次温度湿度值。那为什么不等待2S后再发送下一次的开始信号 呢?当然这样也是可以的,这样设计的话就是2S读取一次温度湿度值。而实际上不同的DHT11,不同的环境,它的采样间隔可能并不是2S,2S只是最大值来适用于所有的DHT11和不同的环境。而如何设计才能是DHT11最快的读出温度湿度值呢?所以就有了上面的设计,当其不响应的时候我们可以回去重新发送开始信号, 直到其响应为止。这样设计的话就能使DHT11以最快的采样间隔读出数据。
从状态跳转图可以看到状态机的状态跳转条件,下面再通过波形图来看看具体如何去实现。
波形图绘制
图 29‑14 时钟分频波形图
在整个时序过程中无论是发送开始信号,还是“1”,“0”的时序组成我们都需要用到时间信号,而这些时间信号的最小单位为us,所以我们先产生单位时钟为1us的时钟来作为DHT11的控制时钟。如上波形图所示,产生方法在“分频器”章节已有详细的讲解,在此就不再过多介绍,不明白的读者可以回到“分频器”章节进行了 解。
图 29‑15 信号沿获取波形图
通过DHT11的信号时序图可以看到信号的变化很多都需要借助其上升沿和下降沿来进行判断的,所以在这里我们先产生dht11单总线的信号沿,其波形图如上所示。前面触摸按键控制led灯我们已经讲到过产生信号上升沿的方法,产生下降沿的方法也是一样的。对需要采沿的信号打拍后经过取反相与操作即可获得上升沿或下降沿 标志信号,不能理解的可以去触摸按键控制led灯章节详细了解。
通过上面的讲解我们知道我们需要借助状态机来设计整个控制过程 。所以波形图我们将围绕状态机的跳转去绘制。如图 29‑16所示。
图 29‑16 DHT11控制波形图(一)
dht11:单总线,其类型为输入输出型,定义为wire型变量,所以我们将借助dht11_out(总线输入)和dht11_en(总线使能信号)来给dht11赋值。由时序图可知,我们只需要控制总线发送开始信号(18ms的低电平),其他状态释放即可。如图中的dht11_out与dht11_en信号所示,当 dht11_en信号为1时dht11的值等于dht11_out的值,当dht11_en为0时释放总线。
state:状态机的状态。初始状态为S_WAIT_1S,上电后需要等待1S以越过不稳定状态。所以我们需要一个计数器(cnt_us)去进行产生1S的时间信号。当计数器计到1S时跳到下一个状态。
S_LOW_18MS:发送开始信号状态。开始信号是由18ms的低电平组成,所以这里我们又需要用到计数器,而且在发送时需要从0开始计数。所以在上一状态计数器计到1S后我们需要对计数器其清0,清零后开始18ms的计数。当状态机跳转到S_LOW_18MS状态时,我们需要拉低总线维持18ms。当计数器计到1 8ms时(0-17999),状态机跳转计数器清零。
S_DLY1:拉高等待状态。等待10us后跳转到下一状态,等待回应信号的到来。
S_REPLY:DHT11回应状态(DHT11响应低电平81~85us)。跳到这个状态后计数器开始计数,但是我们并不知道回应信号有没有开始发送,而cnt_us计数器是对该状态的持续时间进行计数,所以其并不能计到响应信号的持续时间,这时我们需要一个新的计数器(cnt_low)进行对响应信号计数,当检测 到dht11为低电平时,说明响应信号开始发送了,我们就可以让cnt_low计数器开始计数。同时dht11的上升沿到来时说明响应信号发送完毕。若此时cnt_low计数器大于等于70(因为DHT11响应低电平81~85us,这里当我们检测到低电平持续时间大于等于70时视为响应正确。响应这个值大家可以视情 况而设,使有些没有严格按照协议发送的DHT11也可以兼容)则视为发送的数据正确,同时跳转到下一状态。若在这个状态一直没有检测到响应信号,则在cnt_us计到1ms时跳回S_LOW_18MS状态,重新发送开始信号,波形图如图 29‑17所示:
图 29‑17 DHT11无回应波形图
当DHT11有响应跳到下一状态时,波形图如图 29‑17所示:
图 29‑18 DHT11控制波形图(二)
S_DLY2:DHT11拉高等待输出状态。当下降沿到来时,若此时的值满足其发送时间则跳转到下一状态。
S_RD_DATA:DHT11输出数据状态。从时序图可以看到,数据“0”和数据“1”的时序都是由低电平开始的,而且持续时间相同,不同的地方在于高电平的持续时间。所以我们只需要判断高电平的持续时间即可判断其输出的是0还是1。当上升沿到来时计数器清零开始计数,当下降沿到来时说明1bit的数据发送完毕,此 时对计数器采样。我们知道数据“0”是维持23-27us的高电平,数据“1”是维持68-74us的高电平。我们取一个中间值(50us)去判断输出的是“0”还是“1”,若下降沿到来cnt_us小于等于50则判断为0,若大于50则判断为1。为了判断我们采了几个数据,我们还需要一个bit计数器(bit_cn t)。其初始值为0,当下降沿到来时bit_cnt加1,当其加到39时说明发送到了最后一个数据。所以当其为40的时候表示数据发送完毕,同时dht11会发送结束信号(拉低一段时间)。当dht11上升沿到来时表示整个时序结束。状态跳回S_LOW_18MS状态发送开始信号开始新一轮的数据读取,同时bit_c nt清零。
图 29‑19 DHT11控制波形图(三)
图 29‑19为数据采集波形图。当在S_RD_DATA状态时,每来一个下降沿,采集一次数据。因为数据是从高位开始发送的,所以第一个采集的数据放在数据寄存器(data_tmp)的最高位即data_tmp[39],依次类推最后一位放在最低位即data_tmp[0]。可以看到下降沿状态到来时bit_cnt 的值应为上一状态的值,所以bit_cnt与寄存器赋值位数的关系为data_tmp[39-cnt]。当40位数据发送完之后发现检验位与8bit湿度整数数据+8bit湿度小数数据+8bit温度整数数据+8bit温度小数数据”所得结果的末8位相等时,则将湿度温度数据值赋给data。
图 29‑20 按键切换显示湿度温度波形图
在理论部分已经说到,湿度小数位是为0的,而温度小数实际上只能精确到0.1,所以我们只需要三个数码管显示即可。当按键没有按下时,data_flag的值为0,此时显示湿度的值,显示在第二第三个数码管,而第二三个数码管显示的是十分位和百分位,所以读出的整数值要乘以10。当按键按下时,data_flag取反 为1,此时显示的是温度值,温度小数部分用低四位计算即可。需要注意的是我们需使用系统时钟对key_flag进行采样,而不能使用DHT11的控制时钟clk_1us,因为我们产生key_flag用的是系统时钟,而且key_flag只维持了一个系统时钟的高电平,所以如果我们用分频的时钟去采的话是几乎采不到这 个信号的。
代码编写
参照绘制波形图,编写模块代码。模块参考代码,具体见代码清单 29‑1。
代码清单 29‑1 DHT11控制模块参考代码(dht11_ctrl.v)
1 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 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 | module dht11_ctrl
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire key_flag , //按键消抖后标志信号
inout wire dht11 , //控制总线
output reg [19:0] data_out , //输出显示的数据
output reg sign //输出符号位,高电平显示负号
);
////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter S_WAIT_1S = 3'd1 , //上电等待1s状态
S_LOW_18MS = 3'd2 , //主机拉低18ms,发送开始信号状态
S_DLY1 = 3'd3 , //等待20-40us状态
S_REPLY = 3'd4 , //DHT11响应80us状态
S_DLY2 = 3'd5 , //拉高等待80us状态
S_RD_DATA = 3'd6 ; //接收数据状态
parameter T_1S_DATA = 999999 ; //1s时间计数值
parameter T_18MS_DATA = 17999 ; //18ms时间计数值
//reg define
reg clk_1us ; //1us时钟,用于驱动整个模块
reg [4:0] cnt ; //时钟分频计数器
reg [2:0] state ; //状态机状态
reg [20:0] cnt_us ; //us计数器
reg dht11_out ; //总线输出数据
reg dht11_en ; //总线输出使能信号
reg [5:0] bit_cnt ; //字节计数器
reg [39:0] data_tmp ; //读出数据寄存器
reg data_flag ; //数据切换标志信号
reg dht11_d1 ; //总线信号打一拍
reg dht11_d2 ; //总线信号打两拍
reg [31:0] data ; //除校验位数据
reg [6:0] cnt_low ; //低电平计数器
//wire define
wire dht11_fall; //总线下降沿
wire dht11_rise; //总线上升沿
////
//\* Main Code \//
////
//当使能信号为1是总线的值为DATA_out的值,为0时值为高阻态
assign dht11 = (dht11_en == 1 ) ? dht11_out : 1'bz;
//检测总线信号的上升沿下降沿
assign dht11_rise = (~dht11_d2) & (dht11_d1) ;
assign dht11_fall = (dht11_d2) & (~dht11_d1) ;
//对dht11信号打拍
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
dht11_d1 <= 1'b0 ;
dht11_d2 <= 1'b0 ;
end
else
begin
dht11_d1 <= dht11 ;
dht11_d2 <= dht11_d1 ;
end
//cnt:分频计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 5'b0;
else if(cnt == 5'd24)
cnt <= 5'b0;
else
cnt <= cnt + 1'b1;
//clk_1us:产生单位时钟为1us的时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
clk_1us <= 1'b0;
else if(cnt == 5'd24)
clk_1us <= ~clk_1us;
else
clk_1us <= clk_1us;
//bit_cnt:读出数据bit位数计数器
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 6'b0;
else if(bit_cnt == 40 && dht11_rise == 1'b1)
bit_cnt <= 6'b0;
else if(dht11_fall == 1'b1 && state == S_RD_DATA)
bit_cnt <= bit_cnt + 1'b1;
//data_flag:数据变换标志信号的产生,按一次键变换一次
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_flag <= 1'b0;
else if(key_flag == 1'b1)
data_flag <= ~data_flag;
else
data_flag <= data_flag;
//状态机状态跳转
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= S_WAIT_1S ;
else
case(state)
S_WAIT_1S:
if(cnt_us == T_1S_DATA) //上电1s后跳入起始状态
state <= S_LOW_18MS ;
else
state <= S_WAIT_1S ;
S_LOW_18MS:
if(cnt_us == T_18MS_DATA)
state <= S_DLY1 ;
else
state <= S_LOW_18MS ;
S_DLY1:
if(cnt_us == 10) //等待10us后进入下一状态
state <= S_REPLY ;
else
state <= S_DLY1 ;
S_REPLY: //上升沿到来且低电平保持时间大于70us,则跳转到下一状态
if(dht11_rise == 1'b1 && cnt_low >= 70)
state <= S_DLY2 ;
//若1ms后,dht11还没响应,则回去继续发送起始信号
else if(cnt_us >= 1000)
state <= S_LOW_18MS ;
else
state <= S_REPLY ;
S_DLY2: //下降沿到来且计数器值大于70us,则跳转到下一状态
if(dht11_fall == 1'b1 && cnt_us >= 70)
state <= S_RD_DATA ;
else
state <= S_DLY2 ;
S_RD_DATA: //读完数据后,回到起始状态
if(bit_cnt == 40 && dht11_rise == 1'b1)
state <= S_LOW_18MS ;
else
state <= S_RD_DATA ;
default:
state <= S_WAIT_1S ;
endcase
//各状态下的计数器赋值
//cnt_us:每到一个新的状态就让该计数器重新计数
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
cnt_low <= 7'd0 ;
cnt_us <= 21'd0 ;
end
else
case(state)
S_WAIT_1S:
if(cnt_us == T_1S_DATA)
cnt_us <= 21'd0 ;
else
cnt_us <= cnt_us + 1'b1;
S_LOW_18MS:
if(cnt_us == T_18MS_DATA)
cnt_us <= 21'd0 ;
else
cnt_us <= cnt_us + 1'b1;
S_DLY1:
if(cnt_us == 10)
cnt_us <= 21'd0 ;
else
cnt_us <= cnt_us + 1'b1;
S_REPLY:
if(dht11_rise == 1'b1 && cnt_low >= 70)
begin
cnt_low <= 7'd0 ;
cnt_us <= 21'd0 ;
end
//当dht11发送低电平回应时,计算其低电平的持续时间
else if(dht11 == 1'b0)
begin
cnt_low <= cnt_low + 1'b1 ;
cnt_us <= cnt_us + 1'b1 ;
end
//若1ms后,dht11还没响应,则回去继续发送起始信号
else if(cnt_us >= 1000)
begin
cnt_low <= 7'd0 ;
cnt_us <= 21'd0 ;
end
else
begin
cnt_low <= cnt_low ;
cnt_us <= cnt_us + 1'b1 ;
end
S_DLY2:
if(dht11_fall == 1'b1 && cnt_us >= 70)
cnt_us <= 21'd0 ;
else
cnt_us <= cnt_us + 1'b1;
S_RD_DATA:
if(dht11_fall == 1'b1 \|\| dht11_rise == 1'b1)
cnt_us <= 21'd0 ;
else
cnt_us <= cnt_us + 1'b1;
default:
begin
cnt_low <= 7'd0 ;
cnt_us <= 21'd0 ;
end
endcase
//各状态下的单总线赋值
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
dht11_out <= 1'b0 ;
dht11_en <= 1'b0 ;
end
else
case(state)
S_WAIT_1S:
begin
dht11_out <= 1'b0 ;
dht11_en <= 1'b0 ;
end
S_LOW_18MS: //拉低总线18ms
begin
dht11_out <= 1'b0 ;
dht11_en <= 1'b1 ;
end
//后面状态释放总线即可,由DHT11操控总线
S_DLY1:
begin
dht11_out <= 1'b0 ;
dht11_en <= 1'b0 ;
end
S_REPLY:
begin
dht11_out <= 1'b0 ;
dht11_en <= 1'b0 ;
end
S_DLY2:
begin
dht11_out <= 1'b0 ;
dht11_en <= 1'b0 ;
end
S_RD_DATA:
begin
dht11_out <= 1'b0 ;
dht11_en <= 1'b0 ;
end
default:;
endcase
//data_tmp:将读出的数据寄存在data_tmp中
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_tmp <= 40'b0;
else if(state == S_RD_DATA && dht11_fall == 1'b1 && cnt_us<=50)
data_tmp[39-bit_cnt] <= 1'b0;
else if(state == S_RD_DATA && dht11_fall == 1'b1 && cnt_us>50)
data_tmp[39-bit_cnt] <= 1'b1;
else
data_tmp <= data_tmp;
//data_out:输出数据显示,按一次按键切换一次数据
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data <= 32'b0;
else if(data_tmp[7:0] == data_tmp[39:32] + data_tmp[31:24] +
data_tmp[23:16] + data_tmp[15:8])
data <= data_tmp[39:8]; //若检验位正确,则数据值有效
else
data <= data;
//data_out:对数码管显示的湿度和温度进行赋值
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_out <= 20'b0;
else if(data_flag == 1'b0 )
data_out <= data[31:24] \* 10; //湿度小数位为0
else if(data_flag == 1'b1)
//温度低四位显示温度小数数据
data_out <= data[15:8] \* 10 + data[3:0];
//sign:符号位的显示
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sign <= 1'b0;
else if(data[7] == 1'b1 && data_flag == 1'b1)
//当温度低八位最高位为1时,显示负号
sign <= 1'b1;
else
sign <= 1'b0;
endmodule
|
模块参考代码是参照绘制波形图进行编写的,在波形图绘制小节已经对模块各信号有了详细的说明,本小节不再过多叙述。
23.4.1.4. 顶层模块¶
模块框图
dht11顶层模块主要是对各个子功能模块的实例化,以及对应信号的连接,模块框图如图 29‑21所示。
图 29‑21 dht11顶层模块框图
模块各输入输出信号描述如表格 29‑6所示。
表格 29‑6 dht11顶层模块输入输出信号描述
信号 |
位宽 |
类型 |
功能描述 |
---|---|---|---|
sys_clk |
1bit |
input |
系统时钟,频率50MHz |
sys_rst_n |
1bit |
input |
系统复位,低有效 |
key_in |
1bit |
input |
按键输入信号 |
dht11 |
1bit |
inout |
数据总线 |
stcp |
1bit |
output |
存储寄存器时钟 |
shcp |
1bit |
output |
移位寄存器时钟 |
ds |
1bit |
output |
串行数据 |
oe |
1bit |
output |
输出使能,低有效 |
代码编写
顶层模块代码编写较为容易,无需波形图的绘制。顶层参考代码,具体见代码清单 29‑2。
代码清单 29‑2 顶层模块参考代码(dht11.v)
1 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 | module dht11
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire key_in , //按键信号
inout wire dht11 , //数据总线
output wire stcp , //输出数据存储寄时钟
output wire shcp , //移位寄存器的时钟输入
output wire ds , //串行数据输入
output wire oe //输出使能信号
);
////
//\* Parameter and Internal Signal \//
////
//wire define
wire [19:0] data_out; //需要显示的数据
wire key_flag; //按键消抖后输出信号
wire sign ; //输出符号
////
//\* Main Code \//
////
//-------------dht11_ctrl_inst--------------
dht11_ctrl dht11_ctrl_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.key_flag (key_flag ), //按键消抖后标志信号
.dht11 (dht11 ), //控制总线
.data_out (data_out ), //输出显示的数据
.sign (sign ) //输出符号
);
//-------------key_fifter_inst--------------
key_filter key_filter_inst
(
.sys_clk (sys_clk ), //系统时钟50MHz
.sys_rst_n (sys_rst_n), //全局复位
.key_in (key_in ), //按键输入信号
.key_flag (key_flag ) //按键消抖后输出信号
);
//-------------seg_595_dynamic_inst--------------
seg_595_dynamic seg_595_dynamic_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低有效
.data (data_out ), //数码管要显示的值
.point (6'b000010), //小数点显示,高电平有效
.seg_en (1'b1 ), //数码管使能信号,高电平有效
.sign (sign ), //符号位,高电平显示负号
.stcp (stcp ), //输出数据存储寄时钟
.shcp (shcp ), //移位寄存器的时钟输入
.ds (ds ), //串行数据输入
.oe (oe ) //输出使能信号
);
endmodule
|
代码第58行是对小数点进行赋值,因为显示的是1位小数,所以给让第2位的数码管显示小数点。在数码管动态显示模块对point的定义是高电平有效,所以给第四位为1即可。
23.4.1.5. RTL视图¶
实验工程代码编写完之后,使用Quartus软件对实验工程进行编译,编译完成后,我们查看一下RTL视图,RTL视图展示信息与顶层模块框图一致,具体见图 29‑22。
图 29‑22 实验工程RTL视图
23.4.1.6. SignalTap波形抓取¶
由于控制DHT11我们FPGA只发送了一个开始信号,而总线的其他时序都是由DHT11控制的,而DHT11发送的实际信号和理想信号有偏差的话,我们产生模拟信号去仿真效果也不是很好。所以在此我们就利用quartus软件自带的SignalTap工具对波形进行抓取,如图 29‑23所示。
图 29‑23 SignalTap抓取波形图
根据抓取波形图可以看到,DHT11输出的时序波形与实际DHT11响应的波形图是相符合的,这说明我们的设计是正确的。
23.5. 上板调试¶
23.5.1. 引脚约束¶
仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 29‑7所示。
表格 29‑7 引脚分配表
信号名 |
信号类型 |
对应引脚 |
备注 |
---|---|---|---|
sys_clk |
input |
E1 |
时钟 |
sys_rst_n |
input |
M15 |
复位 |
key_in |
input |
M2 |
按键 |
dht11 |
inout |
L14 |
dht11单总线 |
stcp |
output |
K9 |
存储寄存器时钟 |
shcp |
output |
B1 |
移位寄存器时钟 |
ds |
output |
R1 |
串行数据 |
oe |
output |
L11 |
输出使能,低有效 |
下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 29‑24所示。
图 29‑24 管脚分配
23.5.1.1. 结果验证¶
管脚配置完成之后重新进行编译,编译完之后就可以进行下载验证了,在下载之前首先将电源与下载线与开发板连接好,然后再接上DS18B20温度传感器。连接好后上电,如图 29‑25、图 29‑26所示。
图 29‑25 下载连线图
图 29‑26 DHT11温湿度传感器连接图
这里需要注意的是:DHT11温湿度传感器不要接反了,需严格按照上图进行连接。
打开下载界面后,当检测到下载器(USB-Blaster)已连接之后,即可点击“Add File…”添加sof文件,添加好后点击“start”开始下载,随后界面会显示下载成功,如图 29‑27所示。
图 29‑27 下载成功界面
下载成功后即可以开始验证了。如果看到数码管显示了当前环境的湿度值(一位小数),同时显示的数值会随湿度的变化而变化。当按下按键KEY1时数码管会显示当前环境的温度值(一位小数),同时显示的数值会随温度的变化而变化。则说明验证正确。
23.6. 章末总结¶
到这里,本章节讲解完毕,通过实验相信大家已经学会怎么去控制DHT11。需要注意的就是要严格按照手册上说描述的时序去进行设计,这样出错的概率就会大大降低。