一、项目介绍
本实验通过 DHT11 模块测量大气中温湿度,利用树莓派多功能的用途,将温湿度测量结果显示到网页,并包含历史数据、实时折线图表示。
二、 实验介绍
1. 实验原材料
树莓派 Raspberry 4 Model B,DHT11 v1.3,电阻,杜邦线,面包板。
2. 流程图
三、硬件部分
DHT11 传感器实验相关知识
1.1 DHT11引脚说明 下图为GPIO引脚对照表 本次实验使用的DHT11有3个引脚:VCC、DATA、GND。 VCC接 5V PIN 02;DATA接 PIN 08;GND接地 PIN 06。 硬件连接图如下:右上角40引脚的2、6、8所连的三条线为DHT11所连的线。下面的13、14引脚所连的线为温度阈值报警小灯所用(此为附加功能所用)。 1.2 外设读取步骤 主机和从机之间的通信可通过如下几个步骤完成((外设(如微处理器)读取DHT11的数据的步骤)。 步骤一: DHT11上电后(DHT11上电后要等待1S以越过不稳定状态在此期间不能发送任何指令),测试环境温湿度数据,并记录数据,同时DHT11的DATA数据线由上拉电阻拉高一直保持高电平;此时DHT11的DATA引脚处于输入状态,时刻检测外部信号。 步骤二: 微处理器的I/O设置为输出同时输出低电平,且低电平保持时间不能小于18ms(最大不得超过30ms),然后微处理器的I/O设置为输入状态,由于上拉电阻,微处理器的I/O即DHT11的DATA数据线也随之变高,等待DHT11作出回答信号。发送信号如下图所示: 步骤三: DHT11的DATA引脚检测到外部信号有低电平时,等待外部信号低电平结束,延迟后DHT11的 DATA引脚处于输出状态,输出83微秒的低电平作为应答信号,紧接着输出87微秒的高电平通知 外设准备接收数据,微处理器的I/O此时处于输入状态,检测到I/O有低电平(DHT11回应信号) 后,等待87微秒的高电平后的数据接收,发送信号下图所示: 步骤四: 由DHT11的DATA引脚输出40位数据,微处理器根据I/O电平的变化接收40位数据,位数据 “0”的格式为:54微秒的低电平和23-27微秒的高电平,位数据“1”的格式为:54微秒的低 电平加68-74微秒的高电平。位数据“0”、“1”格式信号如下图所示: 结束信号: DHT11的DATA引脚输出40位数据后,继续输出低电平54微秒后转为输入状态,由于上拉电 阻随之变为高电平。但DHT11内部重测环境温湿度数据,并记录数据,等待外部信号的到来。
1.3. DHT11单总线传送数据位定义 DATA 用于微处理器与 DHT11 之间的通讯和同步,采用单总线数据格式,一次传送 40 位数据,高位先出。 数据格式:
8bit 湿度整数数据+8bit 湿度小数数据+8bit 温度整数数据+8bit 温度小数数据+8bit 校验位,其中湿度小数部分为 0
示例一:接收到的40位数据为: 0011 0101 / 0000 0000 / 0001 1000 / 0000 0100 / 0101 0001 湿度高8位 / 湿度低8位 / 温度高8位 / 温度低8位 / 校验位 计算: 00110101+00000000+00011000+00000100=01010001 接收数据正确: 湿度:00110101(整数)=35H=53%RH00000000(小数)=00H=0.0%RH=>53%RH+0.0%RH=53.0%RH 温度:00011000(整数)=18H=24℃00000100(小数)=04H=0.4℃=>24℃+0.4℃=24.4℃
实验中dht11.py所读取的温度显示为整数,欲将其变为带有一位小数的输出,只需将return值处理一下,将温度的小数部分the_bytes[3]加上就可以了
return DHT11Result(DHT11Result.ERR_NO_ERROR, the_bytes[2]+float(the_bytes[3])/10, the_bytes[0])
四、软件部分
1. 设计流程
相关参数调用关系
本实验中参数调用主要围绕app.py文件进行,app.py运行后,首先读取dht11读取到的当前温湿度参数,把参数存到数据库中,创建数组调用数据生成折线图。生成的图片通过存到对应路径上。html文件调用路径上存的文件显示到网页上,同时调用style.css文件进行网页美化。
文件夹路径关系: 主文件app.py位于WebServer文件夹 第三级目录中: templates用于放html文件; statics用于存放,css文件和其他的图片(可自选,例如背景图、温度湿度曲线图等便于调用); history用于放dht11.py文件(因为我的树莓派B4无法使用Adafruit库,所以是使用硬件直接读取温湿度数据的。)
2. 树莓派学习笔记
树莓派相关下载过程此处不再赘述,这里只总结一些我在实验中遇到的问题以及解决的方法。
- SSH 远程连接树莓派 使用SSH远程连接后,可以在另外一台PC机上操控树莓派
这个问题的出现的很普遍,因为大家对这个操作平台都还不是很熟悉,这个问题出现的原因是树莓派 SSH 服务没有打开,在树莓派桌面左上角树莓派图标上单击-> Preferences-> Raspberry Pi Configuration-> Interfaces -> SSH Enabled 选中 -> OK,重启PUTTY就可以解决了。 - 树莓派远程桌面 在实现远程连接时,要保证两个设备处在同一个网络中。 而且要确认好目前设备开的端口是哪一个,树莓派的IP地址是哪一个,确定好了才可以连接。
3. 代码程序报错记录
在运行这几个py文件时,遇到的几个主要的报错以及出现原因、处理方法如下:
- 报错:SyntaxError: invalid syntax
这句话的意思是Python代码语法有问题。Python对语法的要求非常严格,有时候代码是网上直接找来复制粘贴的,就有可能导致明明看上去没错,但就一直报语法错误,可以尝试清空格式后重新输入一遍。 另外需要注意的是Python2和Python3的语法有区别,比如Python2的print后面不用加括号,而Python3中就会报错。 - 报错:ModuleNotFoundError: No module named 'Adafruit_DHT’
“import Adafruit_DHT ”语句错误,提示找不到Adafruit_DHT模块,但是在terminal里输入“import Adafruit_DHT”不报错,并且已经下载了相关库。可能是python版本的问题,先明确你运行程序时使用的是哪个Python版本。 使用的是Python2使用的语句是:sudo pip install Adafruit_Python_DHT; sudo python setup.py install, 但使用的是Python3使用的语句是:sudo pip3 install Adafruit_Python_DHT; sudo python3 setup.py install 注意一定要用pip3! - 报错:ImportError: cannot import name ‘Beaglebone_Black_Driver’ from 'Adafruit_DHT’
通过报错信息,发现是库中的common.py里引用get_platform有问题,再去platform_detect.py里查看发现树莓派的版本定义里没有4B,由于我们使用的是4B树莓派,所以导致返回了None 解决:出现这个原因是AdaFruit不再更新这个旧的Adafruit_DHT库,所以里面没有更新树莓派4B对应的cpu。有两种解决方法,使用这个传感器的新库“Adafruit_CircuitPython_DHT”,或者在你原来的Adadruit_DHT库中自己手动更新,具体操作如下: (1)找到Adafruit_DHT文件夹, “/usr/local/lib/python3.7/dist-packages/Adafruit_DHT/”, (2)打开文件夹里的platform_detect.py文件,可以看到把下面这两行代码添加到最后。 其中BCM2711是树莓派4B的CPU,它是四核Cortex-A72 64位。其它的BCMxxxx是其它树莓派版本的CPU。 - Adafruit库无法使用
在根据讲义下载DHT库Adafruit库时,下载到的库中缺少Raspberry_Pi_2driver.py文件,导致执行DHT11_test测试文件时就没能读出数据,经过原因排查和多次下载,我们推断猜测可能是因为树莓派的问题,因为我们使用的是树莓派B4,可能是版本更新了的问题。 对此我们的解决方案是:不使用Adafruit读取温湿度数据,而是用我们之前用过的DHT11.py文件进行读取,这个文件是通过GPIO端口直接读取数据的,并具有CRC纠检错功能,足够实验的功能实现。 实际操作:WebServer文件夹下添加新文件夹:library,将dht11.py文件放在其中作调用功能,并在app.py文件加调用语言from library import dht11,将接下来的变量名重置。
4. 代码
基础功能的代码:使用dht11.py读取温度后,将温湿度显示到网页上。
(1)数据读取部分dht11.py
用于通过传感器读取数据的文件
import time
import RPi
class DHT11Result:
'DHT11 sensor result returned by DHT11.read() method'
ERR_NO_ERROR = 0
ERR_MISSING_DATA = 1
ERR_CRC = 2
error_code = ERR_NO_ERROR
temperature = -1
humidity = -1
def __init__(self, error_code, temperature, humidity):
self.error_code = error_code
self.temperature = temperature
self.humidity = humidity
def is_valid(self):
return self.error_code == DHT11Result.ERR_NO_ERROR
class DHT11:
'DHT11 sensor reader class for Raspberry'
__pin = 0
def __init__(self, pin):
self.__pin = pin
def read(self):
RPi.GPIO.setup(self.__pin, RPi.GPIO.OUT)
self.__send_and_sleep(RPi.GPIO.HIGH, 0.05)
self.__send_and_sleep(RPi.GPIO.LOW, 0.02)
RPi.GPIO.setup(self.__pin, RPi.GPIO.IN, RPi.GPIO.PUD_UP)
data = self.__collect_input()
pull_up_lengths = self.__parse_data_pull_up_lengths(data)
if len(pull_up_lengths) != 40:
return DHT11Result(DHT11Result.ERR_MISSING_DATA, 0, 0)
bits = self.__calculate_bits(pull_up_lengths)
the_bytes = self.__bits_to_bytes(bits)
checksum = self.__calculate_checksum(the_bytes)
if the_bytes[4] != checksum:
return DHT11Result(DHT11Result.ERR_CRC, 0, 0)
return DHT11Result(DHT11Result.ERR_NO_ERROR, the_bytes[2]+float(the_bytes[3])/10, the_bytes[0]+float(the_bytes[1])/10)
def __send_and_sleep(self, output, sleep):
RPi.GPIO.output(self.__pin, output)
time.sleep(sleep)
def __collect_input(self):
unchanged_count = 0
max_unchanged_count = 100
last = -1
data = []
while True:
current = RPi.GPIO.input(self.__pin)
data.append(current)
if last != current:
unchanged_count = 0
last = current
else:
unchanged_count += 1
if unchanged_count > max_unchanged_count:
break
return data
def __parse_data_pull_up_lengths(self, data):
STATE_INIT_PULL_DOWN = 1
STATE_INIT_PULL_UP = 2
STATE_DATA_FIRST_PULL_DOWN = 3
STATE_DATA_PULL_UP = 4
STATE_DATA_PULL_DOWN = 5
state = STATE_INIT_PULL_DOWN
lengths = []
current_length = 0
for i in range(len(data)):
current = data[i]
current_length += 1
if state == STATE_INIT_PULL_DOWN:
if current == RPi.GPIO.LOW:
state = STATE_INIT_PULL_UP
continue
else:
continue
if state == STATE_INIT_PULL_UP:
if current == RPi.GPIO.HIGH:
state = STATE_DATA_FIRST_PULL_DOWN
continue
else:
continue
if state == STATE_DATA_FIRST_PULL_DOWN:
if current == RPi.GPIO.LOW:
state = STATE_DATA_PULL_UP
continue
else:
continue
if state == STATE_DATA_PULL_UP:
if current == RPi.GPIO.HIGH:
current_length = 0
state = STATE_DATA_PULL_DOWN
continue
else:
continue
if state == STATE_DATA_PULL_DOWN:
if current == RPi.GPIO.LOW:
lengths.append(current_length)
state = STATE_DATA_PULL_UP
continue
else:
continue
return lengths
def __calculate_bits(self, pull_up_lengths):
shortest_pull_up = 1000
longest_pull_up = 0
for i in range(0, len(pull_up_lengths)):
length = pull_up_lengths[i]
if length < shortest_pull_up:
shortest_pull_up = length
if length > longest_pull_up:
longest_pull_up = length
halfway = shortest_pull_up + (longest_pull_up - shortest_pull_up) / 2
bits = []
for i in range(0, len(pull_up_lengths)):
bit = False
if pull_up_lengths[i] > halfway:
bit = True
bits.append(bit)
return bits
def __bits_to_bytes(self, bits):
the_bytes = []
byte = 0
for i in range(0, len(bits)):
byte = byte << 1
if (bits[i]):
byte = byte | 1
else:
byte = byte | 0
if ((i + 1) % 8 == 0):
the_bytes.append(byte)
byte = 0
return the_bytes
def __calculate_checksum(self, the_bytes):
return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255
(2)主文件app.py
- 这里要注意地址需要修改成自己主机的地址,查看树莓派地址的方式:在终端输入ifconfig然后回车。
- from-import是用来引用文件中的文件内容的。 title中的内容"Sensor Data"和html中的代码</>Sensor Data</>是对应的,如果想要修改,需要两边同时修改。
- time.sleep(1)的用处是让程序间隔一秒读取数据。这里的timesleep是python自带的一个函数,在编写程序时候,我们有时需要将程序短暂的停顿一下,这个时候就需要用到time包下面的sleep。
- 有的时候运行报错可能是因为端口的问题,尝试把port=50改成80/5080/8080之类的就好了。
from flask import Flask ,render_template
from library import dht11
import RPi.GPIO as GPIO
app = Flask(__name__)
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()
instance = dht11.DHT11(pin=14)
@app.route("/")
def hello():
result = instance.read()
time.sleep(1)
temperature = result.temperature
humidity = result.humidity
templateData={
'title': "Sensor Data",
'temp': temperature,
'hum': humidity,
}
return render_template("index.html", **templateData)
if __name__ == "__main__":
app.run(host="192.168.1.xxx", port=50, debug=True)
(3)index.html
<!DOCTYPE html>
<head>
<html>
<head>
<title>{{title}}</title>
<link rel="stylesheet" href='../static/style.css'/>
</head>
<body>
<h1>Sensor Data</h1>
<h2>TEMPERATURE ==> {{ temp }} oC</h2>
<h3>HUMIDITY (Rel.) ==> {{ hum }} %</h3>
</body>
</html>
(4)style.css:用于美化html背景和字体颜色
body{
background: blue;
color: yellow;
padding:1%
}
.button {
font: bold 15px Arial;
text-decoration: none;
background-color: #EEEEEE;
color: #333333;
padding: 2px 6px 2px 6px;
border-top: 1px solid #CCCCCC;
border-right: 1px solid #333333;
border-bottom: 1px solid #333333;
border-left: 1px solid #CCCCCC;
}
|