4、传感器数据采集实战:使用Python与树莓派/Jetson Nano读取DHT22数据
好,咱们进入实战环节。
前面讲了那么多理论,什么PID啊、前馈啊,最终都得落到传感器数据上。数据不准,后面全是白搭。我个人习惯,做温室项目第一步,先把传感器调通,数据能稳定读上来,心里才有底。
4.1 为什么选DHT22?
市面上温湿度传感器很多,DHT11、DHT22、SHT30、BME280……我为什么推荐DHT22?
说白了,性价比。DHT11太粗糙,精度±2°C,做温室调控根本不够用。SHT30精度高,但贵,而且I2C通信对新手不太友好。DHT22精度±0.5°C,湿度±2%RH,单总线协议,一根线就能搞定。我在项目中遇到过用DHT11做育苗温室,结果温度偏差大,苗子全徒长了……后来全换成DHT22,再没出过类似问题。
| 参数 | DHT11 | DHT22 |
|---|---|---|
| 温度精度 | ±2°C | ±0.5°C |
| 湿度精度 | ±5%RH | ±2%RH |
| 采样周期 | 1秒 | 2秒 |
| 通信协议 | 单总线 | 单总线 |
| 价格 | 约5元 | 约15元 |
4.2 硬件接线
接线其实很简单。DHT22有四个引脚,但实际只用三个:VCC、DATA、GND。有个NC脚是空脚,不用管。
你想想看,树莓派和Jetson Nano的GPIO都是3.3V逻辑电平,而DHT22工作电压是3.3-5V。这里有个坑——数据引脚必须接上拉电阻。我曾经第一次调DHT22时,没接上拉电阻,数据死活读不出来,折腾了一下午才发现是这个问题。
具体接线:
- VCC → 3.3V(树莓派Pin1,Jetson Nano Pin1)
- DATA → GPIO4(树莓派Pin7,Jetson Nano Pin7)
- GND → GND(树莓派Pin6,Jetson Nano Pin6)
- DATA与VCC之间接一个4.7kΩ上拉电阻
4.3 安装依赖库
Python读取DHT22,最常用的库是Adafruit CircuitPython DHT。为什么不用C语言?嗯,Python开发快,调试方便,原型验证阶段效率高。等产品定型了,再考虑用C重写底层驱动。
安装步骤:
# 更新系统包
sudo apt update
sudo apt upgrade -y
# 安装Python3和pip(一般树莓派系统自带)
sudo apt install python3-pip
# 安装Adafruit CircuitPython库
pip3 install adafruit-circuitpython-dht
# 如果使用树莓派,还需要安装libgpiod
sudo apt install libgpiod2
Jetson Nano上稍微有点不同。我记得第一次在Jetson Nano上跑DHT22,直接报错说找不到设备。后来发现需要手动安装Jetson GPIO库:
# Jetson Nano额外步骤
pip3 install Jetson.GPIO
sudo groupadd -f -r gpio
sudo usermod -a -G gpio $USER
# 然后重启
4.4 编写读取代码
好,硬件接好了,库装好了,咱们写代码。
核心逻辑其实就三步:初始化传感器、循环读取、处理异常。DHT22的采样周期是2秒,你读太快它会报错。我刚开始写的时候没注意这个,循环里设了0.5秒间隔,结果全是CRC校验错误。
import time
import board
import adafruit_dht
# 初始化DHT22,连接到GPIO4
dht_device = adafruit_dht.DHT22(board.D4)
while True:
try:
# 读取温湿度
temperature = dht_device.temperature
humidity = dht_device.humidity
# 检查数据是否有效
if temperature is not None and humidity is not None:
print(f"温度: {temperature:.1f}°C, 湿度: {humidity:.1f}%")
else:
print("读取失败,重试中...")
except RuntimeError as error:
# DHT22偶尔会通信失败,这是正常的
print(f"读取异常: {error.args[0]}")
except Exception as error:
# 其他异常,比如传感器断开
print(f"严重错误: {error}")
dht_device.exit()
raise
# 必须等待至少2秒
time.sleep(2.0)
4.5 数据校验与滤波
原始数据读上来了,但直接用吗?不行。你想想看,温室里风机一开,温度传感器数值会剧烈跳动。直接拿这个值去控制设备,执行器会频繁启停,寿命大打折扣。
我常用的方法:滑动平均滤波。取最近5次数据求平均,能有效抑制噪声。
class SensorFilter:
def __init__(self, window_size=5):
self.window_size = window_size
self.temp_buffer = []
self.humid_buffer = []
def add_reading(self, temp, humid):
self.temp_buffer.append(temp)
self.humid_buffer.append(humid)
# 只保留最近N个数据
if len(self.temp_buffer) > self.window_size:
self.temp_buffer.pop(0)
if len(self.humid_buffer) > self.window_size:
self.humid_buffer.pop(0)
def get_filtered(self):
if len(self.temp_buffer) == 0:
return None, None
avg_temp = sum(self.temp_buffer) / len(self.temp_buffer)
avg_humid = sum(self.humid_buffer) / len(self.humid_buffer)
return avg_temp, avg_humid
# 使用示例
filter = SensorFilter(window_size=5)
while True:
try:
temp = dht_device.temperature
humid = dht_device.humidity
if temp is not None and humid is not None:
filter.add_reading(temp, humid)
f_temp, f_humid = filter.get_filtered()
print(f"原始: {temp:.1f}°C, 滤波后: {f_temp:.1f}°C")
except RuntimeError:
pass
time.sleep(2.0)
4.6 常见问题与避坑指南
做这个实验,你大概率会遇到下面几个问题。我一个个说:
- 读不到数据:先检查接线,上拉电阻接了没?GPIO号对不对?我曾经犯过一个低级错误,把DATA线插到了3.3V上,传感器直接冒烟了……
- CRC校验错误:这是DHT22的老毛病。单总线对时序敏感,树莓派后台跑的任务多了就会丢数据。解决方案:加重试机制,连续3次失败才报错。
- 数值跳变:比如温度突然从25°C跳到50°C。这通常是电磁干扰。温室里变频器、水泵启动时会产生强干扰。解决办法:数据线用屏蔽线,或者加软件滤波。
- 传感器发热:DHT22工作时会自发热,如果通风不好,测出来的温度会比实际高1-2°C。我建议传感器不要紧贴外壳,留点空隙。
4.7 数据落盘与可视化
数据读出来了,总得存下来吧?调试阶段,我习惯把数据同时打印到终端和写入CSV文件。这样既能实时看,又能事后分析。
import csv
from datetime import datetime
csv_file = open('greenhouse_data.csv', 'a', newline='')
csv_writer = csv.writer(csv_file)
# 写入表头
csv_writer.writerow(['时间', '温度(°C)', '湿度(%)'])
while True:
try:
temp = dht_device.temperature
humid = dht_device.humidity
if temp is not None:
now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
csv_writer.writerow([now, f"{temp:.1f}", f"{humid:.1f}"])
csv_file.flush() # 立即写入磁盘
print(f"[{now}] {temp:.1f}°C, {humid:.1f}%")
except RuntimeError:
pass
time.sleep(2.0)
嗯,这里要注意:flush()一定要加。Python的文件写入是有缓冲的,不加flush的话,程序崩溃时最后几条数据就丢了。我在现场调试时吃过这个亏,数据丢了半小时,找原因找了半天。
好了,DHT22的数据采集就讲到这里。下一章咱们会把这些数据送到数据库里,然后开始做真正的闭环控制。你先把这段代码跑通,有问题随时翻回来看看。