Mega2560 + 行空板 M10 双主控智慧农场:10 类传感器如何实现精准灌溉与多级安全保护
简介
这是一座会自己照顾自己的微缩农场。它以 Arduino Mega2560 做实时控制、以行空板 M10 做数据展示与网络服务,用 10 类传感器把"土壤干不干、水干不干净、天会不会下雨、光够不够"这些原本靠经验判断的事,变成可以量化的判断条件:土壤湿度低于阈值才开泵,水质浊度超标就禁止灌溉,大雨暂停浇水,光照过强自动熄灯、回落后再恢复原状态;温度、空气质量、紫外线异常时蜂鸣器分级报警并与风扇联动。所有数据通过串口送给行空板,再由它推送到自建网页、SIoT 与巴法云,手机或电脑随时可看、可控。

(系统架构框图)

(作品全景)
一、我们想解决什么
做这个项目的起因很朴素。我们观察真实的农业生产,发现了三个具体问题:
- 浇水靠猜。 浇多了烂根,浇少了减产,而"该不该浇、浇多少"其实是可以算出来的——土壤湿度、水质、雨量都是可测量的量。
- 极端天气靠扛。 高温、暴雨、强紫外线来临时,人在现场还好,没人在就只能听天由命。
水安全没人管。 用脏水浇菜,问题不在当天,而在长期累积。这件事需要在源头就拦住,而不是事后补救。
……
于是目标定得很明确:让传感器当"眼睛和皮肤",程序当"大脑",水泵、风扇、灯带当"手脚",做一座能看、能判断、能动手的农场。
二、硬件清单与软件环境
2.1 硬件清单
| 类别 | 名称 | 型号 / 规格 | 数量 | 作用 |
| 主控器 | Arduino 主控板 | Mega2560 | 1 | 实时控制:采集、判断、驱动执行器 |
| 主控器 | 行空板 | UNIHIKER M10 | 1 | 数据展示、网页服务、云端接入 |
| 传感器 | 温湿度传感器 | DHT11 | 1 | 监测空气温湿度,高温联动风扇 |
| 传感器 | 土壤湿度传感器 | 电阻式 | 1 | 检测土壤含水量,决定是否灌溉 |
| 传感器 | 光照传感器 | 光敏模块 | 1 | 光照过强时强制熄灯 |
| 传感器 | 空气质量传感器 | DFRobot ENS160 | 1 | 监测空气质量,超标自动通风 |
| 传感器 | 气压传感器 | DFRobot BMP388(SEN0251) | 1 | 辅助判断天气趋势 |
| 传感器 | 浊度传感器 | DFRobot SEN0189 | 1 | 检测水质,超标禁灌 |
| 传感器 | 水位传感器 | 电阻式 | 1 | 监测水箱水位,防水泵空转 |
| 传感器 | 雨滴传感器 | 雨滴模块 | 1 | 分级判断小雨 / 大雨 |
| 传感器 | 紫外线传感器 | DFRobot LTR390(SEN0540) | 1 | 紫外线超标报警 |
| 传感器 | 流量传感器 | YF-S401 | 1 | 累计灌溉用水量 |
| 输出模块 | RGB 灯带 | WS2812,64 灯 | 1 | 补光,支持调色与调亮度 |
| 输出模块 | 显示屏 | SSD1306 OLED 128×64 | 1 | 本地四页数据显示 |
| 输出模块 | 直流水泵 | 微型潜水泵 | 1 | 灌溉执行 |
| 输出模块 | 继电器 | 单路 5V | 1 | 驱动水泵 |
| 输出模块 | 调速风扇 | 5V PWM 风扇 | 1 | 通风降温 |
| 输出模块 | 蜂鸣器 | 有源低电平触发 | 1 | 三路分级报警 |
| 交互模块 | 离线语音识别 | DFRobot DF2301Q | 1 | 19 条语音指令控制 |
| 交互模块 | 翻页按键 | 轻触按键 | 2 | OLED 翻页、双键清零水量 |
| 物联网 | 物联网模块 | DFRobot OBLOQ(TEL0118) | 1 | 数据上报 EasyIoT 平台 |
| 电源 | 太阳能板 | 5V / 6V 太阳能电池板 | 1 | 绿色供电 |
| 电源 | 充放电模块 | 太阳能充电管理 + 锂电池 | 1 组 | 储能与续航 |
| 结构材料 | 木板、亚克力板、喷漆 | — | 若干 | 场景与棚体 |
wb-table-cont
| 类别 | 名称 | 型号 / 规格 | 数量 | 作用 |
| 连接线材 | 杜邦线、传感器延长线 | — | 若干 | 接线 |
2.2 软件环境
| 项目 | 说明 |
| Mega2560 开发 | Arduino IDE,使用 Wire、EEPROM、Adafruit_NeoPixel、Adafruit_SSD1306、DHT、DFRobot_ENS160、DFRobot_DF2301Q、DFRobot_BMP3XX、DFRobot_LTR390UV 等库 |
| 行空板 M10 | Mind+ Python 模式,使用 unihiker(GUI)、pyserial、pinpong、mqtt(SIoT / 巴法云)、内置 http.server |
| 物联网平台 | EasyIoT(OBLOQ 直连)、行空板自带 SIoT(MQTT 1883)、巴法云(mqtt.bemfa.com:9501) |
| 本地网页 | 行空板自建 Web 服务,端口 9000 |
三、系统架构:为什么用两块主控
这是整个项目最关键的一次取舍。一开始我们只想用一块板,但做着做着发现两件事很难在一块板上同时做好:实时控制要求"不被打断",而网络服务要求"慢慢等"。
| 分工 | Arduino Mega2560 | 行空板 M10 |
| 职责 | 采集传感器、执行控制逻辑、驱动执行器 | 展示数据、提供网页、接入云端 |
| 时间尺度 | 毫秒级(流量计中断、灯带刷新) | 秒级(上报、刷新页面) |
| 通信 | USB 串口,115200 | 同上 |
| 好处 | 网络卡顿不影响灌溉 | 算力富余,可跑网页服务 |
两块板之间只约定了最简单的协议:
- 上行:Mega 每秒打印一行 JSON,包含全部状态;
- 下行:M10 发一行文本命令,如
CMD PUMP ON、CMD FAN OFF、CMD LIGHT ON、CMD STATUS; - 开机标记:Mega 上电时先发
{"boot":1},M10 收到后重建界面。
这个约定的好处是任何一端重启,另一端都能自动恢复:Mega 重启,M10 收到 boot 标记重建界面;M10 重启,只要重新打开串口就能继续收 JSON。调试时把 Mega 接到电脑,用串口监视器直接看 JSON,也能立刻判断是哪一端的问题。

(硬件接线全景(杂乱版)(俯视))
四、制作过程
步骤一:需求调研与方案设计
我们先把需求收敛成四个词——灌溉、环境、安全、交互,然后画系统框图,确定"双主控 + 多传感器 + 语音 + 物联网"的总体方案。

(木板结构搭建(非本人出镜))
步骤二:传感器选型与电路搭建
这一步我们做了一件后来被证明非常值得的事:每接一个传感器,先用串口把它的原始读数打出来,看清楚数值范围,再定阈值。比如土壤湿度,干燥时 ADC 约 1023、浸水时接近 0;雨滴传感器干燥时读数高、沾水后下降。有了实测数据,阈值才不是拍脑袋定的。
十路传感器的接口分配如下(部分):
| 接口 | 器件 | 接口 | 器件 |
| A6 | 土壤湿度 | D2 | 流量计(中断脚) |
| A7 | 光照 | D4 | 风扇(PWM) |
| A8 | 水位 | D6 | 水泵继电器 |
| A9 | 旋钮电位器 | D7 | 蜂鸣器 |
| A10 | 浊度(SEN0189) | D8 | 雨滴探头供电控制 |
| A11 | 雨滴(AOUT) | D14 / D15 | OLED 翻页按键 K1 / K2 |
| D22 | DHT11 | D23 | WS2812 灯带 |
| I2C 0x3C | OLED | I2C 0x64 | DF2301Q 语音 |
| I2C 0x53 | ENS160 | I2C 0x77 | BMP388 |
| I2C 0x1C | LTR390 | Serial2 | OBLOQ(D16/D17) |


(装配与调试)
步骤三:核心控制程序编写
全部功能采用非阻塞架构:主循环里没有 delay(),所有定时任务靠 millis() 判断时间差。这一点看似只是写法区别,实际决定了系统能不能同时做多件事——后面"屏幕响应迟钝"那一节会讲到我们为此付出的代价。
本阶段完成的功能模块:自动灌溉(湿度阈值 + 迟滞 + 时序保护)、水质禁灌、雨量分级、光照自适应灯带、三路蜂鸣器分级报警、流量累计与 EEPROM 保存。

(e.g.灌溉决策代码)
步骤四:语音控制中心与物联网接入
语音模块是固件内置命令词,不需要训练,但内置编号必须自己实测一遍。我们的方法是:说一条指令,看串口打印的 CMDID,再对照动作,把"编号—动作"做成常量表,并在串口回显动作名。这样即使某条指令厂家固件改了编号,也只需改一个数字。
物联网部分做了三层:OBLOQ 直连 EasyIoT;行空板自带 SIoT 供局域网内控制;巴法云用于外网手机查看。三层互不依赖,任何一层断线不影响本地逻辑。
步骤五:场景搭建与联调迭代
木板棚体完成后进入连续运行联调。这一阶段记录并修复了问题。
五、技术原理
5.1 精准灌溉:一个带时序保护的状态机
灌溉不是"湿度低就开泵"这么简单。真实场景里有四个必须处理的问题:水渗下去需要时间、传感器读数会抖、水泵不能频繁启停、水泵空转会烧线圈。我们把灌溉逻辑写成一个两状态机,每个状态有自己的进入条件:
停机状态下的开泵条件(五个条件同时满足):土壤湿度 < 30%、距上次停泵已超过 10 秒、水箱有水、水质未超标、雨量未达大雨级。
抽水状态下的停泵条件:至少抽满 5 秒后,土壤湿度 > 50% 或单次已超 60 秒;而缺水、水质超标、大雨这三条立即停泵,不受最小时长限制。
三个时间常数的作用分别是:最短运行 5 秒保证水真正渗下去;最短间隔 10 秒避免频繁启停;最长 60 秒防止传感器卡死导致淹水。
if (!pumpState) {
// 停机:土够干 + 歇够 + 有水 + 水质达标 + 没下大雨 → 开泵
if (soilPercent < SOIL_DRY_THRESHOLD
&& (now - pumpOffAt) >= PUMP_MIN_OFF_MS
&& tankHasWater && !turbBad && rainLevel < 2) {
pumpState = true; pumpOnAt = now; setPumpRelay(true);
}
} else {
unsigned long runMs = now - pumpOnAt;
bool soaked = (soilPercent > SOIL_WET_THRESHOLD);
bool timeout = (runMs >= PUMP_MAX_ON_MS);
// 缺水/水质超标/大雨立即停;其余需满 MIN_ON 才允许停
if (!tankHasWater || turbBad || rainLevel >= 2
|| (runMs >= PUMP_MIN_ON_MS && (soaked || timeout))) {
pumpState = false; pumpOffAt = now; setPumpRelay(false);
saveFlowTotal(); // 停泵瞬间把累计水量写入 EEPROM
}
}
5.2 迟滞阈值:让设备不再"打摆子"
所有阈值都成对出现,一个触发、一个恢复,中间留出缓冲带:
| 项目 | 触发阈值 | 恢复阈值 | 缓冲带 |
| 土壤湿度 | < 30% 开泵 | > 50% 关泵 | 20% |
| 水质浊度 | > 800 NTU 禁灌 | < 600 NTU 解禁 | 200 NTU |
| 雨量(小雨) | 读数 < 550 | 读数 > 650 | 100 |
| 雨量(大雨) | 读数 < 300 | 读数 > 350 | 50 |
| 光照 | > 700 熄灯 | < 500 恢复 | 200 |
| 温度 | > 30 ℃ 报警 | < 28 ℃ 恢复 | 2 ℃ |
| 空气质量 | AQI > 3 | AQI ≤ 1 | 2 |
没有缓冲带会发生什么?土壤湿度在 30% 附近抖动时,水泵会在一秒内反复开停,继电器咔咔响,电机和继电器都容易坏。加了迟滞之后,设备只在"明确越界"时才动作。
5.3 光照自适应灯带:把"用户意图"和"实际输出"分开
这是我们在联调后期加的一个功能,思路值得单独说。灯带的开关有两类来源:用户(语音 / 网页)想开或想关,和环境(光照过强)要求必须关。如果只用一个变量 ledOn,强光把灯关掉后,用户原本"开灯"的意图就被抹掉了,天黑后灯不会自己亮回来。
我们的做法是拆成两个变量:
ledOn:用户意图,语音和网页只改它,不受环境影响;lightForceOff:环境压制标志,光照 > 700 时置位,< 500 且持续满 3 秒才清除;- 实际输出 =
ledOn && !lightForceOff,统一由applyLed()一个出口刷新。
那个 3 秒是必需的。64 颗灯珠本身就会照亮光敏传感器,如果没有最小保持时间,系统会在"关灯→变暗→开灯→变亮→关灯"之间高频自激闪烁。
5.4 语音控制中心:先建映射表,再谈控制
DF2301Q 的命令词是固件内置的,编号需要通过实测确定。我们建立了这样的常量表,并在串口回显动作名:
| 编号 | 动作 | 编号 | 动作 |
| 85 / 86 | 风扇开 / 关 | 117 / 118 | 灯光开 / 关 |
| 87 / 88 / 89 | 风速一 / 二 / 三挡 | 119 / 120 | 亮度调高 / 调低 |
| — | — | 121 / 122 | 亮度最大 / 最小 |
| — | — | 130–137 | 红橙黄绿青蓝紫白八色 |
这样设计的好处是:万一固件版本不同导致编号变化,只需改表里的数字,控制逻辑一行不用动。
5.5 三路蜂鸣器:非阻塞节奏发生器
三种报警音要能区分,又不能用 delay() 卡住主循环。我们把每种声音定义成"响 / 停"交替的时间序列,用 millis() 推进步进:
| 报警 | 节奏(毫秒,偶数位响、奇数位停) | 听感 |
| 水位过低 | 200, 800 | 慢长鸣,约 1 秒一声 |
| 水质过差 | 100, 100, 100, 700 | 急促双鸣 |
| 下雨 | 100, 100, 100, 100, 100, 900 | 三短鸣 |
优先级:水位过低 > 水质过差 > 下雨。
5.6 双主控串口桥
Mega 每秒上报一行 JSON(节选):
{"temp":22.2,"hum":64.0,"soil":100,"lux":11,"aqi":0,"water":293,
"ntu":1989,"rain":0,"pump":0,"used":0.00,"fan":0,"fanmode":0,
"led":1,"ledforce":0,"press":965.7,"remote":0,"turbBad":1,"uv":0}其中 ledforce 是"是否被强光压制",turbBad 是"水质是否超标被禁灌"。这两个字段让 M10 端能显示真实原因,而不是笼统的"已关闭"。
M10 侧收到命令后,转成 CMD XXX 发给 Mega,所有安全保护仍在 Mega 端把关:水质超标时即使远程下发开泵,Mega 也会拒绝执行。
5.7 行空板 M10:三种联网方式并存
| 方式 | 地址 | 用途 | 特点 |
| 自建网页 | http://板子IP:9000 | 实时数据、历史曲线、远程控制 | 局域网内最快,曲线支持 10 分钟 / 1 小时 / 6 小时 / 24 小时四档 |
| SIoT | 板子IP:1883(网页 8080) | 局域网内下发指令 | 行空板自带 MQTT 服务器 |
| 巴法云 | mqtt.bemfa.com:9501 | 外网手机查看与控制 | 需要私钥,注意保密 |


六、关键代码说明
完整代码见附件(v2.6_no_servo.ino 约 1530 行,main.py 约 1190 行),代码中每一处阈值都有中文注释。这里只说明几个容易看不懂的设计:
setPumpRelay()统一处理继电器极性。 高电平触发和低电平触发的继电器在市场上都有,把极性判断收在一个函数里,换模块时只改一个常量。saveFlowTotal()与 EEPROM。 累计水量写入时用 magic 值0xA5做有效性校验,断电重启后先校验再读取,避免读到乱码导致水量跳变。抽水过程中每 30 秒存一次,停泵瞬间再存一次。medianRead()中值滤波。 模拟量传感器读数偶尔会跳,连续采 5 次取中值,比平均值更能抵抗单点干扰。- 雨滴探头间歇供电。 探头长期通电会电解腐蚀,代码里每 6 秒才通电 1 秒采样。
- 流量标定。 YF-S401 按官方公式约 480 脉冲/升,代码里留了常量
PULSES_PER_LITER,用量杯实测后可替换以提高精度。

注:①代码目前已参加某比赛,完整版将于比赛结果“出炉”后公布;
②代码原版有舵机控制可开合顶棚与自动开合的大门(使用手势传感器、超声波传感器、按键传感器等综合判定状态),NFC Module刷卡才上电保证安全性等功能,但因框架原因最后删去了,若想要此部分代码亦可留言,若有新的创新建议,也欢迎积极留言哦~;
③木板结构外框架的制作在此处略去(此部分因人而异,想了解的可以留言);
④应该就这些了吧?




