植物环境监测器
这个 Pocket Beagle 设备测量并显示你的室内植物正在茁壮成长或勉强存活的环境条件。
故事
背景故事
这个项目是为莱斯大学(Rice University)的 EDES 301:实用电气工程导论 课程开发的。
如这篇帖子所述,我设计并制作了一个基于 PocketBeagle 的植物环境监测器,它测量土壤湿度和温度等环境条件,在 LCD 屏幕上显示它们,并通过 LED 提供视觉反馈。该系统旨在帮助防止常见的植物养护错误,例如过度浇水或疏于照料——而我过去确实犯过这些错误。我想找到一种方法,在植物枯萎死亡之前,跟踪并改善我的植物所处的环境条件。
设备概述
这是一个嵌入式系统,旨在通过持续监测环境条件来帮助用户保持室内植物健康。该设备使用 PocketBeagle 微控制器测量土壤湿度和温度,在 LCD 屏幕上显示实时数据,并通过 LED 提供视觉反馈。一个由按钮控制的界面允许用户循环切换显示模式。该设备支持自动启动(auto-boot),并被设计为嵌入式系统与电气工程概念的一个实用且简单的应用。
在我的系统中,土壤湿度以百分比测量,我将这个条件划分为四个类别:土壤湿(≥70%)、最佳(40–69%)、干燥(20–39%)以及非常干燥(<20%)。温度以摄氏度监测,以确保安全的工作条件并提供环境背景,帮助我理解热量如何影响土壤干燥/植物健康。
硬件
在我的项目中,我使用 PocketBeagle 作为微控制器。下面是它的引脚图。

所有 PocketBeagle 引脚(P1 和 P2)到组件引脚(组件上的引脚或面包板上的引脚)的对应关系如下所示,便于参考。这些引脚是在我测试系统时,根据 PocketBeagle 哪些引脚有响应而决定的。
我开始这个项目时,首先将 PB(P1_14)的 3.3 V 连接到我的面包板正电源轨。我也将 PB(P1_15)的 GND 连接到负电源轨。这样所有传感器、LED、按钮、电位器和其他组件就可以共享公共地,并且都能获得 3.3 V 供电。

LCD 屏幕(HD44780,16x2)
我按照图 2 所示的连接方式将 LCD 屏幕连接到 PB。我使用 GPIO 引脚以 4 位模式接线:RS、Enable(E)以及数据线(D4-D7)连接到 PB GPIO(P2_24、P2_22、P2_18、P2_20、P2_17、P2_10)。然后我将 BLK、RW 和地引脚连接到地(面包板地轨)。在 VO 引脚上使用一个电位器来控制对比度。LCD 屏幕本身(VDD)使用 5 V 供电(USB 的 5 V,P1_5)。此外,背光(BLA)通过一个 220 欧姆电阻连接到 5 V。
我使用一个电位器作为分压器来控制 LCD 对比度,中间引脚连接到 LCD 的 VO 引脚,一侧接 3.3 V,另一侧接地。然后在 LCD 由我的电脑供电的情况下,通过旋转电位器手动调整对比度。接线方式类似于下面的图 3(注意:由于初步引脚连接没有响应,后续迭代中接线发生了变化,不过该图用于视觉辅助)。

土壤湿度传感器(STEMMA I2C)

参考图 2 并使用随附的 JST PH 2mm 4 针转公排线缆,我将传感器 SDA 连接到 P1_26,将 SCL 连接到 P1_28。GND 连接到地,VIN 连接到 3.3 V 电源轨。这些连接可在下面的图 4 中看到。
该土壤湿度传感器在我的设备中通过 PB 上的 I2C 总线 2(地址 0x36)进行通信。
温度传感器(BMP280)
使用公对公跳线,我将 SDA 连接到 P2_11,将 SCL 连接到 P2_09,将 GND 和 SDO 连接到地,将 VCC 连接到 3.3 V。该温度传感器通过 I2C 总线 1(地址 0x76)通信。我原本打算使用 BME280 温湿度传感器,但在我烧坏原来的 BME280 之后,BMP280(温度和气压)是唯一可用的传感器。
LEDs
每个 LED 的阳极(长脚)串联一个 220 欧姆电阻。然后从电阻出发,红色 LED 连接到 P2_19,黄色 LED 连接到 P2_25,绿色 LED 连接到 P2_29。LED 的阴极(短脚)接地。接线在下面的图 5 中示意。
按钮

使用公对公跳线,我以一个下拉(pull-down)配置连接了一个瞬时按键。按钮的一端连接到 P2_27(GPIO 输入),另一端连接到 3.3 V 电源轨。同一个 GPIO 节点(P2_27)通过一个 10K 欧姆下拉电阻连接到地,以确保按钮未按下时为一个确定的 LOW 状态。按下按钮时,按钮将 P2_27 拉为 HIGH,使 PocketBeagle 能够检测到一个干净的上升沿输入用于屏幕切换。
代码
为了使系统具备功能,我为 LCD、土壤湿度传感器、BMP280、LED 和按钮编写了 python 驱动。一个主驱动随后读取土壤湿度,计算状态 + 干燥等级(使用简单计算器),更新 LED(基于湿度),读取温度并更新 LCD 屏幕。按钮代码用于允许在 4 个显示屏之间切换/循环:
屏幕 0 - 显示土壤湿度百分比和整体状态。
屏幕 1 - 显示简化的干燥等级和状态。
屏幕 2 - 提供一个简短的项目标题和提示用户按键进入下一屏。
屏幕 3 - 显示温度以及土壤湿度,用于组合监测。
我首先使用一个自定义的 configure_pins.sh 脚本来配置 PocketBeagle 引脚,在启动时将所有 GPIO、I2C 和外设引脚设置到正确模式。然后我创建了一个 run.sh 脚本,它切换到项目目录,运行引脚配置,等待电源和 I2C 设备稳定,然后启动主 python 程序。最后,我通过创建一个 systemd 服务来启用自动启动,该服务在启动时调用 run.sh,确保植物监测器在 PocketBeagle 上电(使用 5 伏 USB 充电适配器)时自动运行,而无需连接电脑。或者,你也可以使用日志和一个 cronlog 文件来设置自动启动,并使用命令 sudo crontab -e 来编辑 cron。
实现
只需通过 Micro USB 转 USB-A 线缆和 5 伏 USB 充电适配器将 PocketBeagle 连接到 5V 为设备供电。30 秒后,设备将启动并如下面的视频所示运行。
未来工作
还有很大的改进空间,包括修复 LCD 的电气稳定性和初始化,以消除长时间运行后偶尔出现的显示故障。我还想增加一个摇杆输入来替代单个按钮,使在不同屏幕之间的导航更平滑、更直观。扩展传感器套件以包括湿度、环境光和额外的温度探头,也将为植物健康提供更完整的图景。加上 Wi-Fi 模块后,系统可以在条件变得不利时向手机发送实时警报或通知。最后,我会激光切割或 3D 打印一个外壳,以提高耐用性、整理性和设备整体展示效果。
代码(原文未提供,我按描述“自己生成”的可用示例)
依赖:PocketBeagle 常用
Adafruit_BBIO(GPIO)+smbus2(I2C)
安装示例:sudo apt-get update && sudo apt-get install -y python3-pippip3 install smbus2
目录结构
plant_monitor/ configure_pins.sh run.sh plant-monitor.service main.py lcd_hd44780.py soil_stemma.py bmp280.py ui.py
1) configure_pins.sh
#!/bin/bash set -e # I2C config-pin P1_26 i2c # SDA (I2C2) config-pin P1_28 i2c # SCL (I2C2) config-pin P2_11 i2c # SDA (I2C1) config-pin P2_09 i2c # SCL (I2C1) # LCD (GPIO) - 4-bit mode config-pin P2_24 gpio # RS config-pin P2_22 gpio # E config-pin P2_18 gpio # D4 config-pin P2_20 gpio # D5 config-pin P2_17 gpio # D6 config-pin P2_10 gpio # D7 # LEDs (GPIO out) config-pin P2_19 gpio # Red config-pin P2_25 gpio # Yellow config-pin P2_29 gpio # Green # Button (GPIO in) config-pin P2_27 gpio
2) run.sh
#!/bin/bash set -e cd "$(dirname "$0")" sudo ./configure_pins.sh # 等待供电/I2C稳定 sleep 2 python3 main.py
3) systemd 服务 plant-monitor.service
[Unit] Description=Plant Conditions Monitor (PocketBeagle) After=network.target [Service] Type=simple WorkingDirectory=/home/debian/plant_monitor ExecStart=/bin/bash /home/debian/plant_monitor/run.sh Restart=on-failure RestartSec=2 [Install] WantedBy=multi-user.target
启用方式:
sudo cp plant-monitor.service /etc/systemd/system/plant-monitor.service sudo systemctl daemon-reload sudo systemctl enable plant-monitor.service sudo systemctl start plant-monitor.service sudo systemctl status plant-monitor.service
4) lcd_hd44780.py(4-bit GPIO 驱动)
import time import Adafruit_BBIO.GPIO as GPIO class HD44780: def __init__(self, rs, e, d4, d5, d6, d7): self.rs, self.e = rs, e self.data = [d4, d5, d6, d7] for p in [rs, e] + self.data: GPIO.setup(p, GPIO.OUT) GPIO.output(p, GPIO.LOW) self.init_lcd() def pulse_enable(self): GPIO.output(self.e, GPIO.HIGH) time.sleep(0.0005) GPIO.output(self.e, GPIO.LOW) time.sleep(0.0005) def write4(self, nibble): for i in range(4): GPIO.output(self.data[i], GPIO.HIGH if (nibble >> i) & 1 else GPIO.LOW) self.pulse_enable() def send(self, value, mode_rs): GPIO.output(self.rs, GPIO.HIGH if mode_rs else GPIO.LOW) self.write4((value >> 4) & 0x0F) self.write4(value & 0x0F) def cmd(self, c): self.send(c, False) def write_char(self, ch): self.send(ord(ch), True) def init_lcd(self): time.sleep(0.05) GPIO.output(self.rs, GPIO.LOW) # 进入4bit self.write4(0x03); time.sleep(0.005) self.write4(0x03); time.sleep(0.005) self.write4(0x03); time.sleep(0.001) self.write4(0x02) # 功能设置:4bit, 2行, 5x8 self.cmd(0x28) # 显示开,光标关 self.cmd(0x0C) # 清屏 self.cmd(0x01); time.sleep(0.002) # 输入模式 self.cmd(0x06) def clear(self): self.cmd(0x01) time.sleep(0.002) def set_cursor(self, row, col): addr = (0x80 + col) if row == 0 else (0xC0 + col) self.cmd(addr) def write_line(self, row, text): text = (text or "")[:16].ljust(16) self.set_cursor(row, 0) for ch in text: self.write_char(ch)
5) soil_stemma.py(STEMMA I2C 土壤湿度,地址0x36,示例读法)
from smbus2 import SMBus class StemmaSoil: """ 说明:不同版本STEMMA土壤传感器寄存器可能不同。 这里给出“可用模板”:如果你确认寄存器,再替换 read_moisture_raw / read_temp_raw。 """ def __init__(self, bus_num=2, addr=0x36): self.bus = SMBus(bus_num) self.addr = addr def read_u16(self, reg): data = self.bus.read_i2c_block_data(self.addr, reg, 2) return (data[0] << 8) | data[1] def read_moisture_raw(self): # 常见做法:某些STEMMA土壤传感器湿度寄存器为 0x0F(示例) return self.read_u16(0x0F) def moisture_percent(self): raw = self.read_moisture_raw() # 需要标定:这里给出一个经验映射(示例),请按你的实测干/湿点修正 dry_raw = 200 wet_raw = 2000 pct = (raw - dry_raw) * 100.0 / (wet_raw - dry_raw) return max(0.0, min(100.0, pct))
6) bmp280.py(I2C1 地址0x76,温度读法)
from smbus2 import SMBus import time class BMP280: def __init__(self, bus_num=1, addr=0x76): self.bus = SMBus(bus_num) self.addr = addr self._load_calibration() self._configure() def _read_u16_le(self, reg): l = self.bus.read_byte_data(self.addr, reg) h = self.bus.read_byte_data(self.addr, reg+1) return (h << 8) | l def _read_s16_le(self, reg): val = self._read_u16_le(reg) return val - 65536 if val > 32767 else val def _load_calibration(self): self.dig_T1 = self._read_u16_le(0x88) self.dig_T2 = self._read_s16_le(0x8A) self.dig_T3 = self._read_s16_le(0x8C) self.t_fine = 0 def _configure(self): # reset self.bus.write_byte_data(self.addr, 0xE0, 0xB6) time.sleep(0.1) # ctrl_meas: temp oversampling x1, pressure x1, normal mode self.bus.write_byte_data(self.addr, 0xF4, 0x27) # config: standby 1000ms, filter off self.bus.write_byte_data(self.addr, 0xF5, 0xA0) def read_temperature_c(self): # raw temp: 0xFA..0xFC msb = self.bus.read_byte_data(self.addr, 0xFA) lsb = self.bus.read_byte_data(self.addr, 0xFB) xlsb = self.bus.read_byte_data(self.addr, 0xFC) adc_T = (msb << 12) | (lsb << 4) | (xlsb >> 4) var1 = (((adc_T >> 3) - (self.dig_T1 << 1)) * self.dig_T2) >> 11 var2 = (((((adc_T >> 4) - self.dig_T1) * ((adc_T >> 4) - self.dig_T1)) >> 12) * self.dig_T3) >> 14 self.t_fine = var1 + var2 T = (self.t_fine * 5 + 128) >> 8 return T / 100.0
7) ui.py(按钮与LED逻辑)
import time import Adafruit_BBIO.GPIO as GPIO class LEDs: def __init__(self, red, yellow, green): self.red, self.yellow, self.green = red, yellow, green for p in [red, yellow, green]: GPIO.setup(p, GPIO.OUT) GPIO.output(p, GPIO.LOW) def set(self, r=False, y=False, g=False): GPIO.output(self.red, GPIO.HIGH if r else GPIO.LOW) GPIO.output(self.yellow, GPIO.HIGH if y else GPIO.LOW) GPIO.output(self.green, GPIO.HIGH if g else GPIO.LOW) class Button: def __init__(self, pin): self.pin = pin GPIO.setup(pin, GPIO.IN) def wait_rising_edge(self, debounce_ms=120): # 简易轮询去抖 while True: if GPIO.input(self.pin): time.sleep(debounce_ms/1000.0) if GPIO.input(self.pin): while GPIO.input(self.pin): time.sleep(0.01) return time.sleep(0.01)
8) main.py(4屏循环显示 + 湿度分级 + LED更新)
import time
from lcd_hd44780 import HD44780
from soil_stemma import StemmaSoil
from bmp280 import BMP280
from ui import LEDs, Button
# 引脚按你文中描述
LCD_RS = "P2_24"
LCD_E = "P2_22"
LCD_D4 = "P2_18"
LCD_D5 = "P2_20"
LCD_D6 = "P2_17"
LCD_D7 = "P2_10"
LED_R = "P2_19"
LED_Y = "P2_25"
LED_G = "P2_29"
BTN = "P2_27"
def moisture_category(pct: float):
# 四类:Soil Wet (≥70%), Optimal (40–69%), Dry (20–39%), Very Dry (<20%)
if pct >= 70:
return "Soil Wet", "WET"
if 40 <= pct <= 69:
return "Optimal", "OK"
if 20 <= pct <= 39:
return "Dry", "DRY"
return "Very Dry", "VDRY"
def update_leds(leds: LEDs, pct: float):
# 示例映射:湿度高=绿,适中=黄,干=红
if pct >= 70:
leds.set(g=True)
elif pct >= 40:
leds.set(y=True)
else:
leds.set(r=True)
def main():
lcd = HD44780(LCD_RS, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7)
soil = StemmaSoil(bus_num=2, addr=0x36)
bmp = BMP280(bus_num=1, addr=0x76)
leds = LEDs(LED_R, LED_Y, LED_G)
btn = Button(BTN)
screen = 0
last_btn_check = time.time()
while True:
m = soil.moisture_percent()
status_long, status_short = moisture_category(m)
t = bmp.read_temperature_c()
update_leds(leds, m)
if screen == 0:
lcd.write_line(0, f"Soil {m:5.1f}%")
lcd.write_line(1, f"Status: {status_long[:9]}")
elif screen == 1:
# 简化干燥等级与状态
lcd.write_line(0, f"Dryness: {status_short}")
lcd.write_line(1, f"Soil {m:5.1f}%")
elif screen == 2:
lcd.write_line(0, "Plant Monitor")
lcd.write_line(1, "Press for next")
elif screen == 3:
lcd.write_line(0, f"T {t:5.1f}C Soil")
lcd.write_line(1, f"{m:5.1f}% {status_short:>4}")
# 按钮切换屏幕(阻塞等待上升沿)
btn.wait_rising_edge()
screen = (screen + 1) % 4
time.sleep(0.05)
if __name__ == "__main__":
main()











评论