☰
树莓派Python自动浇水系统:土壤湿度传感器+继电器闭环控制
2026/10/18 16:57:30 网站建设 项目流程

1. 项目缘起与整体设计思路

1.1 为什么我会盯上“自动浇水”这件事

养植物这件事,说难不难,说简单也不简单。出差三天回来,阳台那盆养了两年的琴叶榕叶子全耷拉下来;加班一周忘了管,窗台上的多肉直接化水。我试过定时器插座加小水泵的方案,结果夏天蒸发快、冬天蒸发慢,固定时间浇水要么涝要么旱。后来我干脆用树莓派加Python搭了一套“看土浇水”的系统,核心逻辑很简单:土壤湿度低于阈值就浇水,高于阈值就停,再配合温湿度传感器做辅助判断。

这套方案适合谁?如果你手头有一块吃灰的树莓派(3B、4B、Zero W都行),家里养了几盆需要规律浇水的植物,又不想买那种几十块钱但只能定时、不能看土壤状态的成品浇水器,那这套方案就是为你准备的。整套硬件成本大概一百出头,软件全用Python写,代码量不大,但涉及传感器读取、继电器控制、定时任务、数据记录这几个嵌入式开发的典型环节,拿来练手非常合适。

1.2 方案选型:为什么是“土壤湿度触发”而不是“定时浇水”

先说结论:定时浇水是开环控制,土壤湿度触发是闭环控制。开环的问题在于它假设环境不变,但实际环境一直在变。我做过一个粗略统计,同一盆绿萝在梅雨季和干燥的暖气房里,土壤水分蒸发速度能差三倍以上。定时器每天浇200毫升,梅雨季必然烂根,暖气房必然不够。

闭环控制的核心是反馈。土壤湿度传感器实时告诉系统“现在土里还有多少水”,系统据此决定要不要开泵。这就像你渴不渴自己知道,而不是别人规定你每小时必须喝一杯水。

那为什么不用更高级的方案,比如称重法(通过花盆重量判断含水量)或者图像识别(看叶子是否萎蔫)?称重法精度高,但需要高精度称重传感器,成本上去了,而且换盆、植物长大都会导致基准重量变化,标定麻烦。图像识别更复杂,需要摄像头、训练数据,而且叶子萎蔫时往往已经缺水一段时间了,属于“事后补救”。土壤湿度传感器直接测根区水分,响应快、成本低、标定简单,是性价比最高的选择。

1.3 系统架构:从传感器到水泵的完整链路

整个系统的数据流是这样的:

土壤湿度传感器 → 模数转换(ADS1115或MCP3008)→ 树莓派Python程序 → 判断逻辑 → GPIO输出 → 继电器模块 → 水泵 → 出水

这里有个关键点:树莓派的GPIO是数字接口,只能读高电平或低电平,而土壤湿度传感器输出的是模拟电压信号。所以必须加一块模数转换芯片。我选的是ADS1115,16位精度,I2C接口,接线简单,比MCP3008的SPI接口少几根线。如果你用的是树莓派Pico,它自带ADC,可以省掉这块芯片,但Pico没有完整的Linux系统,跑定时任务和日志记录不如树莓派方便。

继电器模块的作用是隔离。树莓派GPIO输出3.3V、电流只有十几毫安,带不动水泵。继电器用3.3V信号控制,触点端接水泵的独立电源(通常是12V或220V),这样强弱电分离,保护树莓派不被水泵启动时的反向电动势打坏。我一开始图省事直接用GPIO驱动小水泵,结果水泵启动瞬间树莓派直接重启,后来加了继电器和续流二极管才稳定。

2. 硬件清单与接线实操

2.1 物料准备:别买错型号

下面是我实际用到的物料清单,附上选型理由和避坑提示:

物料推荐型号数量备注
树莓派3B+/4B/Zero W1任意带40针GPIO的型号均可
土壤湿度传感器电容式(如SEN0193类)1-4电阻式易腐蚀,电容式寿命长
模数转换模块ADS111514通道,可接4个传感器
继电器模块5V低电平触发1注意触发电平,高/低电平要匹配
微型水泵5V或12V隔膜泵112V扬程更高,适合多盆
电源12V 2A适配器1给水泵供电,树莓派单独供电
硅胶管内径6mm若干配合水泵出水口
杜邦线母对母、公对母若干建议买彩色,方便区分

注意:土壤湿度传感器一定要选电容式。电阻式传感器靠裸露电极测导电率,通电后电极会电解腐蚀,用不了几个月就报废,而且腐蚀产物会污染土壤。电容式传感器表面有阻焊层,不与土壤直接发生电化学反应,寿命长得多。我第一套用的电阻式,三个月后读数飘得没法看,拆开一看电极已经绿了。

2.2 接线图与关键引脚说明

接线分三部分:传感器到ADS1115、ADS1115到树莓派、继电器和水泵。

传感器到ADS1115:

  • 传感器VCC → ADS1115 VDD(3.3V)
  • 传感器GND → ADS1115 GND
  • 传感器AO → ADS1115 A0(第一路)

ADS1115到树莓派:

  • VDD → 树莓派3.3V(物理引脚1)
  • GND → 树莓派GND(物理引脚6)
  • SCL → 树莓派GPIO3(物理引脚5)
  • SDA → 树莓派GPIO2(物理引脚3)

继电器到树莓派:

  • 继电器VCC → 树莓派5V(物理引脚2)
  • 继电器GND → 树莓派GND(物理引脚9)
  • 继电器IN → 树莓派GPIO17(物理引脚11)

水泵到继电器:

  • 水泵正极 → 12V电源正极
  • 水泵负极 → 继电器常开端(NO)
  • 继电器公共端(COM)→ 12V电源负极

提示:继电器模块的触发电平一定要确认。市面上有高电平触发和低电平触发两种。我买的第一块是高电平触发,代码里写GPIO.output(17, True)才开泵,后来换了一块低电平触发的,逻辑正好反过来,排查了半天才发现是模块类型不同。买之前看商品页面的参数,或者用万用表测一下IN脚悬空时继电器是否吸合。

2.3 供电与防水:两个容易翻车的点

供电方面,树莓派和水泵必须分开供电。树莓派用5V 2.5A以上的适配器,水泵用12V 2A适配器。共地是必须的,也就是两个电源的负极要连在一起,否则继电器控制信号没有参考电平,会乱动作。

防水方面,传感器探头可以插进土里,但传感器电路板部分不能沾水。我一般用热缩管把电路板包起来,只留探头部分。水泵放在水桶里,出水口接硅胶管引到花盆。如果多盆植物共用一个水泵,需要在出水端加分流阀,但更简单的做法是每盆配一个小水泵,用继电器分别控制。我目前是四盆植物配四个小水泵,每个水泵单独一个继电器通道,代码里循环检测四路传感器,哪盆缺水就开对应的泵。

3. Python程序核心逻辑与代码实现

3.1 环境搭建:I2C和依赖库

树莓派系统默认关闭I2C,需要手动开启。在终端输入:

sudo raspi-config

进入Interface Options → I2C → Yes,然后重启。重启后用以下命令检查I2C设备是否被识别:

sudo apt-get install -y i2c-tools i2cdetect -y 1

如果看到地址0x48(ADS1115默认地址),说明接线正确。如果没看到,检查SDA和SCL是否接反、电源是否接好。

安装Python库:

pip3 install adafruit-circuitpython-ads1x15 RPi.GPIO

如果你用的是较新的树莓派系统(Bookworm),GPIO库可能需要换成rpi-lgpio或gpiozero。我实测下来RPi.GPIO在Bookworm上会有兼容性警告,但功能正常。如果追求稳定,可以用gpiozero重写继电器控制部分。

3.2 读取土壤湿度的完整代码

下面是我实际在用的读取脚本,加了多次采样取中值滤波,避免单次读数跳变:

import time import board import busio import adafruit_ads1x15.ads1115 as ADS from adafruit_ads1x15.analog_in import AnalogIn # 初始化I2C和ADS1115 i2c = busio.I2C(board.SCL, board.SDA) ads = ADS.ADS1115(i2c) ads.gain = 1 # 增益设为1,量程±4.096V # 四个通道对应四盆植物 channels = [ AnalogIn(ads, ADS.P0), AnalogIn(ads, ADS.P1), AnalogIn(ads, ADS.P2), AnalogIn(ads, ADS.P3), ] def read_moisture(channel, samples=11): """读取土壤湿度,返回电压值(伏特)""" values = [] for _ in range(samples): values.append(channel.voltage) time.sleep(0.05) values.sort() return values[len(values) // 2] # 取中值 # 测试读取 for i, ch in enumerate(channels): v = read_moisture(ch) print(f"通道{i} 电压: {v:.3f} V")

这里解释几个关键点。增益设为1是因为ADS1115在增益1时量程±4.096V,而传感器输出范围通常在0-3.3V,正好覆盖。如果增益设大了,比如±0.512V,传感器输出超过量程会饱和,读数一直是最大值。采样11次取中值是为了滤掉偶然的尖峰干扰,比如水泵启动瞬间的电磁干扰。中值比平均值更抗脉冲噪声,计算量也小。

3.3 标定:把电压转换成“干”和“湿”

传感器输出的电压本身没有意义,需要标定。标定方法很简单:

  1. 把传感器插进完全干燥的土里,记录电压,记为V_dry。
  2. 把传感器插进刚浇透的土里,等几分钟稳定后记录电压,记为V_wet。
  3. 之后读到的电压V,湿度百分比可以近似为:
moisture_percent = (V_dry - V) / (V_dry - V_wet) * 100

注意电容式传感器的特性:越湿电压越低。我实测干燥土电压约2.6V,浇透后约1.2V。所以公式里是V_dry - V,别搞反了。

注意:标定不是一劳永逸的。不同土壤类型(泥炭土、椰糠、园土)的介电常数不同,换土后要重新标定。另外温度也会影响读数,冬天和夏天同一盆土的电压可能差0.1-0.2V。如果追求精度,可以加一个DS18B20温度传感器做补偿,但一般家庭养花没必要,设阈值时留点余量就行。

3.4 浇水控制逻辑与防抖动

核心控制逻辑不复杂,但有几个细节决定系统稳不稳定:

import RPi.GPIO as GPIO import time RELAY_PIN = 17 GPIO.setmode(GPIO.BCM) GPIO.setup(RELAY_PIN, GPIO.OUT) GPIO.output(RELAY_PIN, GPIO.HIGH) # 初始关闭,假设低电平触发 # 阈值设定 THRESHOLD_ON = 30 # 湿度低于30%开始浇水 THRESHOLD_OFF = 50 # 湿度高于50%停止浇水 MAX_WATER_TIME = 30 # 单次最长浇水30秒,防止传感器故障导致淹水 def water_plant(channel_index): """给指定植物浇水""" start_time = time.time() GPIO.output(RELAY_PIN, GPIO.LOW) # 开泵 while True: v = read_moisture(channels[channel_index]) moisture = (V_dry[channel_index] - v) / (V_dry[channel_index] - V_wet[channel_index]) * 100 if moisture >= THRESHOLD_OFF: break if time.time() - start_time > MAX_WATER_TIME: print(f"通道{channel_index} 超时保护触发") break time.sleep(1) GPIO.output(RELAY_PIN, GPIO.HIGH) # 关泵 time.sleep(2) # 等水渗下去再测

这里有两个保护机制。超时保护是防止传感器脱落或损坏导致读数一直很低,水泵不停抽水把花淹了。浇水后等待2秒是让水在土壤里扩散一下,否则刚浇的水还没渗到传感器位置,读数没变化,会误判为没浇够继续浇。

另外,阈值不能设得太窄。比如低于30%开、高于35%关,这样浇水几秒就停了,但水还没渗开,过一会儿湿度又掉下去,系统频繁启停,继电器咔咔响。我建议开和关之间至少差15-20个百分点,给水留出扩散时间。

4. 定时任务、数据记录与远程查看

4.1 用cron做定时检测

树莓派上最简单的定时方案是cron。我设置每30分钟检测一次:

crontab -e

添加一行:

*/30 * * * * /usr/bin/python3 /home/pi/plant_water.py >> /home/pi/plant_log.txt 2>&1

这样每半小时跑一次脚本,检测四盆植物的湿度,需要浇水的就浇。日志追加到plant_log.txt,方便回溯。

提示:cron的环境变量和登录shell不同,脚本里用到的Python库路径可能找不到。保险做法是在脚本开头加上#!/usr/bin/env python3,并且用绝对路径调用Python。如果还是报模块找不到,在crontab里加一行PATH=/usr/local/bin:/usr/bin:/bin。

4.2 数据记录与趋势观察

光浇水不够,我还想知道土壤湿度随时间的变化趋势,用来调整阈值。脚本每次运行会把读数追加到CSV文件:

import csv from datetime import datetime def log_data(moistures): with open('/home/pi/moisture_log.csv', 'a', newline='') as f: writer = csv.writer(f) row = [datetime.now().strftime('%Y-%m-%d %H:%M:%S')] + [f"{m:.1f}" for m in moistures] writer.writerow(row)

跑了一周后,把CSV拉到电脑上用Excel画个折线图,就能看出每盆植物的“干湿循环”周期。比如我发现朝南那盆绿萝大概5天从50%降到30%,而朝北那盆要8天。根据这个周期,我把朝南的阈值调高了一点,让它更早浇水,因为南向蒸发快,等降到30%可能已经有点晚了。

4.3 远程查看:用Flask做个极简页面

有时候出差在外想看看植物状态,我用Flask写了个单页应用,树莓派上跑一个轻量HTTP服务,手机浏览器打开就能看:

from flask import Flask, jsonify app = Flask(__name__) @app.route('/status') def status(): data = {} for i, ch in enumerate(channels): v = read_moisture(ch) m = (V_dry[i] - v) / (V_dry[i] - V_wet[i]) * 100 data[f'plant_{i}'] = round(m, 1) return jsonify(data) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)

这个页面只返回JSON数据,不涉及任何外部服务,纯局域网访问。如果你想让外网也能看,那是另一个话题了,涉及网络配置,这里不展开。局域网内手机连同一个WiFi就能访问http://树莓派IP:5000/status,够用了。

5. 常见问题与排查技巧实录

5.1 读数飘忽不定怎么办

这是最常见的问题。排查顺序如下:

现象可能原因解决方法
读数跳变超过0.5V电源干扰传感器和树莓派用同一电源,或加滤波电容
读数缓慢漂移土壤温度变化正常现象,阈值留余量
读数一直最大值传感器未插好检查杜邦线是否松动
读数一直最小值传感器短路检查探头是否进水短路
某个通道异常ADS1115通道坏换一个通道测试

我遇到过一次读数疯狂跳变,换了传感器、换了ADS1115都没用,最后发现是水泵电源线和传感器线捆在一起,水泵启动时的电磁干扰串进来了。把线分开走,问题消失。强电和弱电信号线一定要分开走线,这是硬件调试的基本功。

5.2 水泵不启动或一直转

水泵不启动,先看继电器指示灯。如果继电器灯亮但泵不转,说明继电器触点没闭合,可能是触发电平搞反了。如果继电器灯不亮,用万用表测GPIO输出,看代码里控制的引脚号对不对。

水泵一直转,最常见的原因是传感器读数一直低于阈值。拔出来放在空气中,看读数是否上升。如果空气中读数还是低,传感器坏了。另一个原因是程序卡在while循环里,超时保护没生效。检查MAX_WATER_TIME变量是否被正确比较,time.time()的单位是秒,别和毫秒搞混。

5.3 土壤发霉与水泵空转

土壤长期过湿会发霉,这是浇水过量导致的。除了调低阈值,还可以在代码里加一个最小浇水间隔,比如两次浇水至少间隔6小时,防止传感器误触发导致频繁浇水。

水泵空转是指水桶没水了,泵还在转。这个靠软件很难检测,最简单的办法是在水桶里加一个浮球开关,水位低于阈值时断开继电器电源。浮球开关就是一个干簧管,水位下降时磁铁靠近,触点闭合或断开,接在继电器电源线上即可。我加了一个之后,再也没烧过泵。

5.4 系统稳定性经验汇总

最后分享几条我踩坑后总结的经验:

  • 树莓派要加散热片。长时间跑Flask和cron,CPU温度能到70度以上,夏天会降频。加个铝散热片,温度能压到55度左右。
  • SD卡容易坏。日志频繁写入会加速SD卡磨损。我把日志写到外接U盘,或者用log2ram把日志先写到内存,定期同步到SD卡。
  • 代码要加异常捕获。传感器偶尔读失败会抛异常,整个脚本崩掉。用try...except包住读取逻辑,失败时跳过这次检测,记录错误日志,下次再试。
  • 继电器选固态继电器更好。机械继电器有触点寿命,频繁开关会坏。固态继电器无触点,寿命长,但价格贵一点。如果浇水频率不高,机械继电器也够用。

这套系统我跑了大半年,四盆植物都活得挺好。最冷的那几天暖气房湿度掉得快,系统自动多浇了几次;梅雨季几乎没怎么浇过。代码不长,硬件也不复杂,但把传感器、执行器、控制逻辑、数据记录串起来,是一个很完整的嵌入式小项目。如果你手头有树莓派,不妨试试,比买成品浇水器有意思得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询