基于MicroPython与LoRa的智能物联网种植系统实战 📅 发布时间:2026/9/17 21:57:28 👁 浏览次数: 简介这是一份面向物联网与Python开发学习者的实战项目资料围绕「智能物联网种植系统」展开适合农场、大棚等农业场景的开发者、学生及嵌入式爱好者参考。文档以Python为主要开发语言完整讲解由终端设备、网关和后台服务器三部分构成的系统架构涵盖环境监测、滴灌控制、安防报警、灯光控制与设备管理五大功能模块并涉及DHT11温湿度采集、LoRa通信、STM32主控、Web远程配置等具体实现细节。压缩包内共1个PDF文件即完整课程教程文档约3.93MB内容从整体架构到各模块开发逐步展开包含硬件清单、程序流程与数据库同步等说明。目前已有1079人学习下载读者可借此理解传感器数据采集、自动滴灌判定、设备心跳与电量预警、故障设备无缝替换等设计思路掌握将Python与物联网技术落地到实际农业项目的完整方法提升复杂系统开发与调试能力。1. 一个把 Python 写进大棚的项目难的不是代码很多人第一次听说「智能物联网种植系统」脑子里浮现的是传感器插土里、数据上云、手机点一下浇水。真动手拆开这个基于 TPYBoard 与 MicroPython 的实战项目会发现难度并不在业务逻辑而在通信时序、字节解析和低功耗管理这些「脏活」。它由终端设备、网关、后台服务器三层组成终端用 STM32 主控跑 MicroPython挂载 DHT11、GY-30、FC-37、水位与土壤湿度传感器靠 LoRa 与网关对话网关汇总数据并同步到服务器服务器把环境数据画成图表同时处理滴灌、灯光、安防报警与设备心跳。对刚接触 MicroPython 的开发者它是一套能跑通的完整链路对做过几年嵌入式的老手它把单总线时序、I2C 寄存器读、ADC 采样精度这些平时被库函数遮住的细节全摊开了。往下看我按「架构怎么切、驱动怎么写、数据怎么解析、系统怎么连起来」的顺序拆一遍。2. 智能物联网种植系统的三层架构与硬件选型2.1 终端、网关、服务器各自负责什么项目从功能上切出五个模块环境监测、滴灌控制、安防报警、灯光控制、设备管理。这五个模块不是并列的软件功能而是被三层架构重新分配过的。终端设备贴着真实世界负责采集和驱动外设芯片选 STM32 跑 MicroPython外设包括舵机控制的水泵、继电器控制的 LED、SPI 液晶屏和人体红外传感器网关是数据中转与本地存储负责聚合多个终端的数据、维护数据库、定期把数据同步给服务器服务器承担可视化、策略下发和长期存档。这样切的好处在于终端可以只做「采集执行」逻辑越薄越省电。终端用电池供电靠 ADC 采电池电压估算剩余电量一旦把判断逻辑都堆到终端功耗和固件复杂度都会失控。网关掉线时终端仍能按最后收到的策略执行这是我比较认可的设计取舍。2.2 为什么空气温湿度选 DHT11、光照选 GY-30选型上有明确的性价比取向。空气温湿度用 DHT11它是已校准的数字输出复合传感器有单总线和标准 I2C 两种通信方式可自由切换项目走单总线只需一根数据线。它成本低、功耗小缺点是采样间隔不能太快且时序必须严格。光照强度用 GY-30 模块核心是 BH1750FVI 芯片量程 065535 Lx内置 16bit AD 转换器直接数字输出接近人眼灵敏度能测到 1 Lx 精度走标准 I2C。雨量用 FC-37同时给数字和模拟两路输出水位和土壤湿度都是模拟量输出直接接 MCU 的 ADC 口。这套组合覆盖了种植场景里最关键的几个变量价格又压得住批量部署。2.3 传感器到核心板的引脚映射引脚不是随便分配的要避开被占用的功能口同时把模拟量集中到 ADC 通道上。常见接线如下传感器模块型号核心板引脚接口类型空气温湿度DHT11X8单总线 GPIO光照强度GY-30I2C(1) SDA/SCLI2C雨滴FC-37X11(模拟) / X12(数字)ADC / GPIO水位水位传感器A7ADC土壤湿度土壤湿度模块ADC 口ADC电池电量分压电路ADC 口ADC提示GY-30 的 ADO 引脚接地时设备地址是 0x23接 VCC 时是 0x5c写驱动时必须和实际接线一致否则 I2C 扫描不到设备。3. DHT11 单总线驱动与时序解析3.1 单总线通信时序拆解DHT11 和核心板之间是主从关系只有主机呼叫时传感器才应答所以主机访问期间必须严格遵守单总线时序时序一乱读回来的 40 位数据就是垃圾。整个过程分三段主机先把数据总线拉低一段时间作为起始信号通知传感器准备数据传感器检测到后把数据线拉低再拉高各一段时间作为响应信号随后传感器一次性从数据线读出 40 位数据高位先出。这 40 位按顺序分别是湿度整数、湿度小数、温度整数、温度小数和一个 8 位校验位。校验规则是前四个字节相加取低 8 位是否等于校验位。温度小数的最高位是符号位为 1 表示负温度。项目里把主机拉低时间设成 18 毫秒、响应阶段各 80 微秒是靠 GPIO 的忙等和time.sleep拼出来的不能用普通定时器替代。3.2 可复用的 DHT11.py 驱动下面是驱动核心逻辑是拉低 18ms 发起始信号切输入等待响应的两个电平跳变再逐位采样 40 次。import pyb from pyb import Pin import time class DHT11: def __init__(self, pin_): self.PinName pin_ time.sleep(1) self.gpio_pin Pin(pin_, Pin.OUT_PP) def read_temp_hum(self): data [] j 0 gpio_pin Pin(self.PinName, Pin.OUT_PP) # 起始信号阶段必须切回输出 gpio_pin.low() time.sleep(0.018) # 拉低 18ms gpio_pin.high() # 等待传感器响应 gpio_pin Pin(self.PinName, Pin.IN) while gpio_pin.value() 1: continue # 等低电平 while gpio_pin.value() 0: continue # 等高电平 while gpio_pin.value() 1: continue # 响应结束 # 逐位读取 40 位数据 while j 40: k 0 while gpio_pin.value() 0: continue while gpio_pin.value() 1: k 1 if k 100: # 防止死循环 break if k 3: data.append(0) # 高电平持续时间短判为 0 else: data.append(1) j j 1 humidity_bit data[0:8] humidity_point_bit data[8:16] temperature_bit data[16:24] temperature_point_bit data[24:32] check_bit data[32:40] humidity humidity_point 0 temperature temperature_point 0 check 0 temp_negative 0 if data[24] 1: # 小数最高位为符号位 data[24] 0 # 屏蔽符号位再算绝对值 temp_negative 1 for i in range(8): humidity humidity_bit[i] * 2 ** (7 - i) humidity_point humidity_point_bit[i] * 2 ** (7 - i) temperature temperature_bit[i] * 2 ** (7 - i) temperature_point temperature_point_bit[i] * 2 ** (7 - i) check check_bit[i] * 2 ** (7 - i) tmp humidity humidity_point temperature temperature_point if check tmp: # 校验通过才返回 if temp_negative 1: return -(temperature temperature_point / 10), humidity humidity_point / 10 else: return temperature temperature_point / 10, humidity humidity_point / 10 else: print(checksum ERROR) return 0, 0代码里有几个点值得说明。Pin(self.PinName, Pin.OUT_PP)在起始阶段重新实例化是因为发完信号后引脚要切到输入去读响应同一个对象不能直接改模式。k 3这个判据把「高电平持续时间短」判为 0因为 DHT11 用高电平的持续时间区分 0 和 10 大概 2628 微秒1 大概 70 微秒忙等循环的计数就是时长的粗糙度量。k 100是防死循环保护传感器没接好时不会卡住主循环。校验失败返回 0, 0调用方直接判这个值决定是否丢弃本次采样。3.3 main.py 验证与常见读数为 0 的排查写完驱动接好线用主程序循环打印验证import pyb import micropython import time from DHT11 import DHT11 if __name__ __main__: S DHT11(X8) # 数据接口定义在 X8 while True: print(----------------------------) temp 0 hum 0 temp, hum S.read_temp_hum() print(Temperature: %s % temp) print(Humidity: %s % hum) time.sleep(3) # DHT11 采样间隔不宜小于 2s把DHT11.py和main.py一起放进核心板映射盘启动后会每 3 秒输出一组温湿度。正常输出形如Temperature: 16.3、Humidity: 77.0。如果一直读 0按这个顺序查先确认上拉电阻是否装了DHT11 的数据线需要上拉再确认引脚号与代码里DHT11(X8)一致然后用示波器或逻辑分析仪看起始信号有没有真的拉低 18ms。最常见的坑是time.sleep(0.018)在部分固件里精度不够微秒级的响应判断又依赖忙等一旦固件开了调度或中断时序就会抖读数随即变成校验失败。4. GY-30 光照、FC-37 雨量与 ADC 类传感器驱动4.1 GY-30 的 I2C 寄存器读写与量程换算GY-30 走标准 I2C项目在 I2C(1) 上初始化为主机模式然后往设备寄存器写命令、从数据寄存器读两字节。驱动里定义了一组关键常量代表不同的测量模式from pyb import I2C import time DEVICE 0x23 # ADO 接地为 0x23接 VCC 为 0x5c POWER_DOWN 0x00 POWER_ON 0x01 RESET 0x07 CONTINUOUS_HIGH_RES_MODE_1 0x10 # 1lx 分辨率约 120ms CONTINUOUS_LOW_RES_MODE 0x13 # 4lx 分辨率约 16ms ONE_TIME_HIGH_RES_MODE_1 0x20 # 单次测量后自动进入掉电 i2c I2C(1, I2C.MASTER) # 创建并初始化为主机 def convertToNumber(data): # BH1750 返回两字节高字节在前除 1.2 得到 lx return int((data[1] (256 * data[0])) / 1.2) def readLight(addrDEVICE): i2c.send(CONTINUOUS_HIGH_RES_MODE_1, DEVICE) # 下发持续高分辨率测量命令 time.sleep(0.2) # 等待转换完成 data i2c.mem_read(2, DEVICE, 2) # 从地址 2 读 2 字节 return convertToNumber(data)convertToNumber里data[1] (256 * data[0])是把两个字节拼成 16 位整数除以 1.2 是芯片的灵敏度换算系数手册里给的公式就是 lx 原始值 / 1.2。这里用mem_read(2, DEVICE, 2)从从机地址 2 读 2 个字节是因为测量结果就存在数据寄存器里。用手机手电筒照模块读数应该明显上升如果一直是 0 或固定值先确认 ADO 接线和DEVICE常量是否匹配——这是 GY-30 项目里最高频的报错源。4.2 FC-37 雨量数字量与模拟量一起用FC-37 模块同时输出数字和模拟两路信号没雨时数字口输出高电平有雨时输出低电平模拟口输出与雨量相关的电压值直接进 ADC。驱动同时暴露两个函数import pyb from pyb import Pin p_in Pin(X12, Pin.IN, Pin.PULL_UP) # 数字量输入内部上拉 adc pyb.ADC(Pin(X11)) # 模拟量建立 ADC 对象 def getRainAo(): return adc.read() # 返回 0~4095 的 ADC 值 def getRainDo(): return p_in.value() # 返回 0 或 1数字量适合做「有没有下雨」的开关判断模拟量适合做雨量大小的连续估计。我一般把模拟值和数字值一起上报用数字量做快速触发用模拟量做趋势记录避免单看数字量的跳变误报。注意模拟口的 ADC 是 12 位读到的是 04095要换算成电压得乘参考电压再除 4096项目里直接用原始值做相对比较。4.3 水位与土壤湿度的 ADC 采样水位和土壤湿度都是纯模拟量输出驱动极简import pyb from pyb import Pin adc pyb.ADC(Pin(A7)) # 水位传感器接 A7 def getWaterLevel(): return adc.read() # 水位越深读数越大水位传感器的平行导线浸水越深导通电阻越低分压后的 ADC 值越大所以「读数随水位加深而增大」这个方向不能反。土壤湿度模块同理湿度越高读数越低或越高取决于模块的电压分压方式接线后必须先用干燥土和浇过水的土各测一组标定才能真正拿来触发滴灌逻辑光看手册给的区间容易翻车。ADC 读数抖动很常见实际项目里通常连续采 510 次取中位数再上判断。5. 终端到网关的联调与设备管理落地5.1 LoRa 串口收发与数据帧格式终端和网关之间用 LoRa 通信MCU 通过串口控制 LoRa 模块把采集到的温湿度、光照、雨量、水位、土壤湿度和电池电量打包成一帧发出去。常见做法是用一个定长结构帧头 设备 ID 各字段 校验。串口配置大致如下from pyb import UART uart UART(3, 115200) # LoRa 模块挂在 UART3 uart.init(115200, bits8, parityNone, stop1) def send_frame(dev_id, temp, hum, light, battery): payload %s,%s,%s,%s,%s\n % (dev_id, temp, hum, light, battery) uart.write(payload) # 以换行结尾便于网关切分用文本帧而不是二进制帧调试时可以直接在串口助手看到内容代价是带宽略高。LoRa 本身速率低帧不能太长所以把不重要的字段做取舍比如只在变化超过阈值时才上报这样既省电又降低冲突概率。5.2 心跳、剩余电量与故障设备无缝替换设备管理模块的核心是心跳机制每个终端周期性向服务器发心跳附带自身在线状态和剩余电量。服务器端统计在线、离线状态和电量电量低于阈值就提前提示换电池避免设备停下才发现。网关侧维护一个数据库存终端运行状态、设备信息和网关自身状态并定期把库同步给服务器。这套设计有个很实用的小技巧当任何设备故障时把新设备的 ID 写成和故障设备一致就能从服务器备份的数据库里拿回故障前该设备的全部运行状态与设备信息实现完整修复和无缝替换。也就是说设备 ID 是逻辑身份硬件只是载体。落地时要注意心跳周期和设备数量之间的关系——终端数量一多如果心跳都挤在同一时刻网关串口和服务器接口会被瞬时打满通常会给每个设备的心跳周期加一个基于 ID 的随机偏移。5.3 滴灌与灯光的三种控制模式如何落地滴灌和灯光都提供手动、定时、自动三种模式通过 Web 页面配置。手动模式是页面上点一下按钮服务器下发指令终端收到后驱动继电器定时模式是服务器按配置的时间表触发自动模式则是把阈值下发到终端或网关由本地判断。以自动滴灌为例逻辑是「土壤湿度低于设定值就开泵」判断放在哪里很关键判断位置延迟断网可用性适合场景服务器判断高不可用网络稳定、策略需频繁调整网关判断中网关在线即可多终端统一策略终端判断低完全可用网络差、要求实时灯光控制里的自动模式根据环境光照强度开关灯本质和自动滴灌一样都是拿传感器读数和阈值比较只是执行器换成继电器控制的 LED。项目支持批量控制和单个控制批量靠设备分组实现下发时按组广播避免逐个点。6. 让这套 MicroPython 种植系统真正稳定跑起来的几个技巧先说校验和这件事。DHT11 的校验、LoRa 帧的校验、网关入库前的字段校验三层里任何一层缺失最后都会变成图表上的毛刺。我一般会在网关侧加一个滑动窗口连续三次校验失败才标记设备异常单次失败只丢帧不报警避免传感器偶发抖动引发误告警。再说 ADC 的稳定性。水位、土壤湿度、电池电量都走 ADC而 ADC 读数受电源波动影响明显。一个低成本但有效的做法是每次采样连续读 7 次去掉最大最小后取平均再和上一次的差值做限幅超过合理变化范围就丢弃。这样在电池供电、电压缓慢下降的场景下尤其管用。关于低功耗终端用电池供电最耗电的其实是 DHT11 的忙等和 LoRa 的发射。合理的节奏是大部分时间让 MCU 进停止模式靠定时唤醒采样采样完立刻回睡LoRa 发完就关模块电源。心跳周期没必要太密几分钟一次足够反映在线状态电量字段也顺带带上。最后是替换和恢复。设备 ID 是逻辑身份这一点在部署时就要固化下来写进配置文件而不是烧死在固件里否则换硬件就得重新编译。网关数据库定期同步到服务器同步失败要有重试队列别让一次网络抖动丢掉一整段运行数据。真正把这几点做扎实这套系统才不只是跑通 demo而是能在大棚里连轴转的工程。本文还有配套的精品资源点击获取