植物環(huán)境監(jiān)測(cè)器
這個(gè) Pocket Beagle 設(shè)備測(cè)量并顯示你的室內(nèi)植物正在茁壯成長(zhǎng)或勉強(qiáng)存活的環(huán)境條件。
故事
背景故事
這個(gè)項(xiàng)目是為萊斯大學(xué)(Rice University)的 EDES 301:實(shí)用電氣工程導(dǎo)論 課程開(kāi)發(fā)的。
如這篇帖子所述,我設(shè)計(jì)并制作了一個(gè)基于 PocketBeagle 的植物環(huán)境監(jiān)測(cè)器,它測(cè)量土壤濕度和溫度等環(huán)境條件,在 LCD 屏幕上顯示它們,并通過(guò) LED 提供視覺(jué)反饋。該系統(tǒng)旨在幫助防止常見(jiàn)的植物養(yǎng)護(hù)錯(cuò)誤,例如過(guò)度澆水或疏于照料——而我過(guò)去確實(shí)犯過(guò)這些錯(cuò)誤。我想找到一種方法,在植物枯萎死亡之前,跟蹤并改善我的植物所處的環(huán)境條件。
設(shè)備概述
這是一個(gè)嵌入式系統(tǒng),旨在通過(guò)持續(xù)監(jiān)測(cè)環(huán)境條件來(lái)幫助用戶保持室內(nèi)植物健康。該設(shè)備使用 PocketBeagle 微控制器測(cè)量土壤濕度和溫度,在 LCD 屏幕上顯示實(shí)時(shí)數(shù)據(jù),并通過(guò) LED 提供視覺(jué)反饋。一個(gè)由按鈕控制的界面允許用戶循環(huán)切換顯示模式。該設(shè)備支持自動(dòng)啟動(dòng)(auto-boot),并被設(shè)計(jì)為嵌入式系統(tǒng)與電氣工程概念的一個(gè)實(shí)用且簡(jiǎn)單的應(yīng)用。
在我的系統(tǒng)中,土壤濕度以百分比測(cè)量,我將這個(gè)條件劃分為四個(gè)類別:土壤濕(≥70%)、最佳(40–69%)、干燥(20–39%)以及非常干燥(<20%)。溫度以攝氏度監(jiān)測(cè),以確保安全的工作條件并提供環(huán)境背景,幫助我理解熱量如何影響土壤干燥/植物健康。
硬件
在我的項(xiàng)目中,我使用 PocketBeagle 作為微控制器。下面是它的引腳圖。

所有 PocketBeagle 引腳(P1 和 P2)到組件引腳(組件上的引腳或面包板上的引腳)的對(duì)應(yīng)關(guān)系如下所示,便于參考。這些引腳是在我測(cè)試系統(tǒng)時(shí),根據(jù) PocketBeagle 哪些引腳有響應(yīng)而決定的。
我開(kāi)始這個(gè)項(xiàng)目時(shí),首先將 PB(P1_14)的 3.3 V 連接到我的面包板正電源軌。我也將 PB(P1_15)的 GND 連接到負(fù)電源軌。這樣所有傳感器、LED、按鈕、電位器和其他組件就可以共享公共地,并且都能獲得 3.3 V 供電。

LCD 屏幕(HD44780,16x2)
我按照?qǐng)D 2 所示的連接方式將 LCD 屏幕連接到 PB。我使用 GPIO 引腳以 4 位模式接線:RS、Enable(E)以及數(shù)據(jù)線(D4-D7)連接到 PB GPIO(P2_24、P2_22、P2_18、P2_20、P2_17、P2_10)。然后我將 BLK、RW 和地引腳連接到地(面包板地軌)。在 VO 引腳上使用一個(gè)電位器來(lái)控制對(duì)比度。LCD 屏幕本身(VDD)使用 5 V 供電(USB 的 5 V,P1_5)。此外,背光(BLA)通過(guò)一個(gè) 220 歐姆電阻連接到 5 V。
我使用一個(gè)電位器作為分壓器來(lái)控制 LCD 對(duì)比度,中間引腳連接到 LCD 的 VO 引腳,一側(cè)接 3.3 V,另一側(cè)接地。然后在 LCD 由我的電腦供電的情況下,通過(guò)旋轉(zhuǎn)電位器手動(dòng)調(diào)整對(duì)比度。接線方式類似于下面的圖 3(注意:由于初步引腳連接沒(méi)有響應(yīng),后續(xù)迭代中接線發(fā)生了變化,不過(guò)該圖用于視覺(jué)輔助)。

土壤濕度傳感器(STEMMA I2C)

參考圖 2 并使用隨附的 JST PH 2mm 4 針轉(zhuǎn)公排線纜,我將傳感器 SDA 連接到 P1_26,將 SCL 連接到 P1_28。GND 連接到地,VIN 連接到 3.3 V 電源軌。這些連接可在下面的圖 4 中看到。
該土壤濕度傳感器在我的設(shè)備中通過(guò) PB 上的 I2C 總線 2(地址 0x36)進(jìn)行通信。
溫度傳感器(BMP280)
使用公對(duì)公跳線,我將 SDA 連接到 P2_11,將 SCL 連接到 P2_09,將 GND 和 SDO 連接到地,將 VCC 連接到 3.3 V。該溫度傳感器通過(guò) I2C 總線 1(地址 0x76)通信。我原本打算使用 BME280 溫濕度傳感器,但在我燒壞原來(lái)的 BME280 之后,BMP280(溫度和氣壓)是唯一可用的傳感器。
LEDs
每個(gè) LED 的陽(yáng)極(長(zhǎng)腳)串聯(lián)一個(gè) 220 歐姆電阻。然后從電阻出發(fā),紅色 LED 連接到 P2_19,黃色 LED 連接到 P2_25,綠色 LED 連接到 P2_29。LED 的陰極(短腳)接地。接線在下面的圖 5 中示意。
按鈕

使用公對(duì)公跳線,我以一個(gè)下拉(pull-down)配置連接了一個(gè)瞬時(shí)按鍵。按鈕的一端連接到 P2_27(GPIO 輸入),另一端連接到 3.3 V 電源軌。同一個(gè) GPIO 節(jié)點(diǎn)(P2_27)通過(guò)一個(gè) 10K 歐姆下拉電阻連接到地,以確保按鈕未按下時(shí)為一個(gè)確定的 LOW 狀態(tài)。按下按鈕時(shí),按鈕將 P2_27 拉為 HIGH,使 PocketBeagle 能夠檢測(cè)到一個(gè)干凈的上升沿輸入用于屏幕切換。
代碼
為了使系統(tǒng)具備功能,我為 LCD、土壤濕度傳感器、BMP280、LED 和按鈕編寫(xiě)了 python 驅(qū)動(dòng)。一個(gè)主驅(qū)動(dòng)隨后讀取土壤濕度,計(jì)算狀態(tài) + 干燥等級(jí)(使用簡(jiǎn)單計(jì)算器),更新 LED(基于濕度),讀取溫度并更新 LCD 屏幕。按鈕代碼用于允許在 4 個(gè)顯示屏之間切換/循環(huán):
屏幕 0 - 顯示土壤濕度百分比和整體狀態(tài)。
屏幕 1 - 顯示簡(jiǎn)化的干燥等級(jí)和狀態(tài)。
屏幕 2 - 提供一個(gè)簡(jiǎn)短的項(xiàng)目標(biāo)題和提示用戶按鍵進(jìn)入下一屏。
屏幕 3 - 顯示溫度以及土壤濕度,用于組合監(jiān)測(cè)。
我首先使用一個(gè)自定義的 configure_pins.sh 腳本來(lái)配置 PocketBeagle 引腳,在啟動(dòng)時(shí)將所有 GPIO、I2C 和外設(shè)引腳設(shè)置到正確模式。然后我創(chuàng)建了一個(gè) run.sh 腳本,它切換到項(xiàng)目目錄,運(yùn)行引腳配置,等待電源和 I2C 設(shè)備穩(wěn)定,然后啟動(dòng)主 python 程序。最后,我通過(guò)創(chuàng)建一個(gè) systemd 服務(wù)來(lái)啟用自動(dòng)啟動(dòng),該服務(wù)在啟動(dòng)時(shí)調(diào)用 run.sh,確保植物監(jiān)測(cè)器在 PocketBeagle 上電(使用 5 伏 USB 充電適配器)時(shí)自動(dòng)運(yùn)行,而無(wú)需連接電腦。或者,你也可以使用日志和一個(gè) cronlog 文件來(lái)設(shè)置自動(dòng)啟動(dòng),并使用命令 sudo crontab -e 來(lái)編輯 cron。
實(shí)現(xiàn)
只需通過(guò) Micro USB 轉(zhuǎn) USB-A 線纜和 5 伏 USB 充電適配器將 PocketBeagle 連接到 5V 為設(shè)備供電。30 秒后,設(shè)備將啟動(dòng)并如下面的視頻所示運(yùn)行。
未來(lái)工作
還有很大的改進(jìn)空間,包括修復(fù) LCD 的電氣穩(wěn)定性和初始化,以消除長(zhǎng)時(shí)間運(yùn)行后偶爾出現(xiàn)的顯示故障。我還想增加一個(gè)搖桿輸入來(lái)替代單個(gè)按鈕,使在不同屏幕之間的導(dǎo)航更平滑、更直觀。擴(kuò)展傳感器套件以包括濕度、環(huán)境光和額外的溫度探頭,也將為植物健康提供更完整的圖景。加上 Wi-Fi 模塊后,系統(tǒng)可以在條件變得不利時(shí)向手機(jī)發(fā)送實(shí)時(shí)警報(bào)或通知。最后,我會(huì)激光切割或 3D 打印一個(gè)外殼,以提高耐用性、整理性和設(shè)備整體展示效果。
代碼(原文未提供,我按描述“自己生成”的可用示例)
依賴:PocketBeagle 常用
Adafruit_BBIO(GPIO)+smbus2(I2C)
安裝示例:sudo apt-get update && sudo apt-get install -y python3-pippip3 install smbus2
目錄結(jié)構(gòu)
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穩(wěn)定 sleep 2 python3 main.py
3) systemd 服務(wù) 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 驅(qū)動(dòng))
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) # 進(jìn)入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) # 功能設(shè)置:4bit, 2行, 5x8 self.cmd(0x28) # 顯示開(kāi),光標(biāo)關(guān) 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: """ 說(shuō)明:不同版本STEMMA土壤傳感器寄存器可能不同。 這里給出“可用模板”:如果你確認(rèn)寄存器,再替換 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): # 常見(jiàn)做法:某些STEMMA土壤傳感器濕度寄存器為 0x0F(示例) return self.read_u16(0x0F) def moisture_percent(self): raw = self.read_moisture_raw() # 需要標(biāo)定:這里給出一個(gè)經(jīng)驗(yàn)映射(示例),請(qǐng)按你的實(shí)測(cè)干/濕點(diǎn)修正 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): # 簡(jiǎn)易輪詢?nèi)ザ? 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屏循環(huán)顯示 + 濕度分級(jí) + 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:
# 簡(jiǎn)化干燥等級(jí)與狀態(tài)
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()











評(píng)論