ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于STM32的智能花卉浇水系统完整设计与实现

基于STM32的智能花卉浇水系统完整设计与实现

家里养了几盆花,出一趟差回来总有那么一盆叶子耷拉到底的,另一个托盘积水根系发黄。于是我去做了套智能花卉浇水系统,从需求、硬件选型、代码到交付资料完整走了一遍。这个项目看起来不难——传感器测湿度、单片机控水泵,真正把它做到能长期稳定运行、不误浇不漏浇,还能整理成一份别人照着手册就能复刻的完整资料,里面要补的功课比想象中多。

如果你是做课程设计、毕业设计,或者只是想在阳台养几盆花又不想天天记得浇水,这篇文章的内容可以直接拿来用。我会把选型逻辑、电路连接、控制策略、实测踩坑和资料结构全部铺开讲,全程照着做就行。

1. 动手之前,先把需求和硬件选型定死在纸面上

1.1 这个浇水系统到底要"智能"在哪里

很多第一次做这类项目的人,第一反应就是"传感器检测湿度,低于阈值就浇水"。这个思路对了一半,但会把问题想窄了。真实场景下,一个拿得出手的智能花卉浇水系统,至少要解决下面这几件事:

需求点具体表现验收标准
自动判断不是定时浇,而是土壤真的缺水才浇土壤湿度低于下限后自动开启水泵
浇水适量浇到"刚好"就停,不把托盘灌满湿度回到上限后自动停止
缺水保护水桶空了不能干抽空桶报警并强制停泵
异常兜底传感器漂移、管路问题不能无限浇水单次浇水时长上限保护
手动介入换盆、施肥时能切手动拨杆开关切换手动/自动
成本可控整套硬件不超过一百元按BOM表采购,总价可控

把这些写清楚之后,"智能"两个字就不再是噱头,而是变成了一个个可验证的功能点。

用户能不能接受这个系统,另一层是交互要直观。OLED屏上直接显示"当前湿度百分比、目前状态(自动/浇水/报警)、水桶水位"这三项,比任何花哨的App首屏都实在。别一上来就想着手机远程控制,先把本地的自动闭环做稳,远程只是这个项目的第二版扩展方向。

1.2 主控、传感器、水泵、电源的四件套选择

本着"好用、便宜、资料多"的原则,我选择了这几个核心器件:

  • 主控:STM32F103C8T6 最小系统板。网上俗称"蓝板",价格在十块左右。72MHz主频、64KB Flash、3个ADC、足够多的GPIO,做这个项目绰绰有余。相比Arduino,它的资料同样非常多,课程设计和毕业设计里也常见,后续要扩展RTOS或者加WiFi模块都有空间。
  • 传感器:电容式土壤湿度传感器。重点在"电容式"这三个字,具体原因后面专门讲。一块板子集成探头和信号调理电路,输出模拟量AO,接单片机ADC引脚。
  • 水泵:12V直流微型潜水泵。注意是"潜水泵"而不是"自吸泵",潜水泵直接丢进水桶里就能抽水,扬程30厘米到1米左右足够把水从桶里送到花盆。
  • 驱动:NMOS管AO3400。水泵工作电流大概在300到500mA,STM32的GPIO最多只能提供几个毫安,直接驱动肯定不行。用AO3400做低端开关,简单可靠,比继电器安静,也没有机械触点寿命问题。
  • 电源:12V 2A电源适配器 + LM2596S降压模块。适配器给水泵供电,LM2596S把12V降到5V给单片机和传感器使用。

如果手头已经有Arduino Nano,主控换成它也可以,程序逻辑完全一致,只是引脚编号改一下。我自己还是选了STM32,理由是这个项目要写状态机、要做ADC滤波,用STM32的标准外设库顺手,顺便把串口打印调试信息也做了,方便很多。

1.3 给系统画一张通信与供电的全局图纸

很多教程一上来就给你接线图,结果新手照着接完,芯片烧了都不知道怎么回事。正确的顺序是先画一张全局关系图,搞清楚哪些是信号线、哪些是电源线。

系统的信号链路是这样的:

  • 土壤湿度传感器 AO 引脚 → STM32 PA0(ADC1 通道0)
  • OLED I2C 数据脚 SDA → PB7,时钟脚 SCL → PB6(用软件I2C,引脚可以自定义)
  • 水泵控制引脚 → PA1,输出高电平时NMOS导通,水泵启动
  • 船型开关选择手动/自动 → PB3、PB4 两个引脚检测电平
  • 液位传感器(光电式低液位)输出引脚 → PB5,低电平表示水桶空

电源链路则是:

  • 12V适配器正极 → 水泵正极
  • 12V适配器正极 → LM2596S输入正极
  • LM2596S输出5V → STM32板5V引脚、OLED VCC、传感器VCC
  • 所有GND汇到一点,再接到电源适配器负极

有了这张图,再去买元件、接线、查故障,心里就有数了。引脚分配也可以写死在程序头文件里,方便后续改板子时快速调整。

2. 传感器不是插上就能用:土壤湿度的测量与校准

2.1 为什么我最终选了电容式土壤湿度传感器

市面上最常见的土壤湿度传感器有两种:电阻式(两根金属叉子)和电容式(PCB板子加覆铜电极)。我第一版图便宜买了电阻式,用了大概两个星期,问题就出来了。

电阻式的原理是两根金属电极插入土壤,土壤含水量不同,电极之间的等效电阻不同,通过分压电路测出电压变化。问题在于电极直接接触土壤和水分,通的是直流电,在电场作用下土壤里的盐分和水分会让金属电极发生电解腐蚀。用了一个多月,电极表面明显发暗,同一盆土、同样湿润程度下测出来的ADC值每周都在漂。这就没法用于长期自动控制。

电容式传感器用覆铜板做电极,测的是土壤这个介质插在两块极板之间时的电容变化,金属不直接接触土壤里的电解质,腐蚀问题大大缓解。用生活里的话说:电阻式像拿湿毛巾当可变电阻,毛巾本身会受潮、会烂,读数自然不稳定;电容式像用一对极板的电容变化来判断中间的夹层是什么材料,稳定又可靠。

选型时注意一个细节:模块上一般有AO和DO两个输出,DO是数字量,里面已经用电位器设好了一个固定阈值,只能输出高或低。这个版本不适合做策略控制,一定要选带AO模拟量输出的,把0到3.3V的电压送给单片机ADC,程序里才方便做阈值区间、校准、滤波这些处理。

2.2 从ADC原始值到"土壤干湿等级"的换算

STM32的ADC是12位分辨率,参考电压3.3V,读出来的原始值范围是0到4095。

我用的这块电容式传感器,模块AO输出电压的特点是:土壤越湿润,输出电压越低。也就是说,湿度升高时ADC读数变小。这个方向不同厂家可能不一样,买回来第一步是实测确认,别默认。我程序里先做了一次反转,把"数值越大代表越湿"统一起来:

uint16_t raw = read_adc(); // 0~4095, 土壤越湿数值越小 uint16_t moisture_raw = 4095 - raw; // 反转:土壤越湿数值越大 float percent = (float)moisture_raw * 100.0f / 4095.0f;

这样得到的是0到100的湿度百分比。为什么要换算成百分比而不是直接用原始值?因为阈值设置更直观——"低于30%浇水"比"低于某个1220的ADC原始值"好理解得多,以后换传感器或者调整供电电压,也只需要重新做一次校准,不用改逻辑。

必须承认,单次ADC采样的数值抖动相当明显。干燥土上,我连续读10次,原始值波动范围可以达到±200。这个抖动来自几个方面:传感器供电噪声、土壤颗粒接触状态、探头附近微小水膜。直接拿单次值去判断阈值,水泵会频繁误动作。后面会讲软件滤波怎么处理。

2.3 三点式校准,把判据写进程序

"百分比"是相对量,但如果直接用4095做分母,有个问题:不同土壤、不同传感器、不同供电电压下,同一个百分比代表的实际干湿程度完全不同。所以必须做校准。

我做了一个简单的三步校准流程,通过长按按键进入校准模式,OLED屏一步步提示:

  1. DRY校准:把传感器插在已经完全干透的土壤里,静置一分钟,按一下按键,程序记录干燥基准值 dry_raw。
  2. WET校准:把土壤浇透水,等托盘不再滴水、土壤达到"握紧能成团但不出水"的状态,再把传感器插进去,按一下按键,记录湿润基准值 wet_raw。
  3. NORMAL确认:校准结束后重新回到自动模式,用当前实时值验证一下百分比是否合理。

校准完成后,程序里用的是两档映射而不是刚才那个固定4095分母:

float percent = (moisture_raw - dry_raw) * 100.0f / (wet_raw - dry_raw); if (percent < 0.0f) percent = 0.0f; if (percent > 100.0f) percent = 100.0f;

这样"0%"表示完全干燥,"100%"表示土壤刚好饱和,中间值就直观多了。我的实际经验是:大部分室内绿植适合把浇水下限设在25%到35%,停止浇水上限设在55%到60%。下限和上限之间留出缓冲区间,避免踩到临界点时反复启停水泵。这个区间设计对水泵寿命和花盆土壤状态都更友好。

3. 执行端设计:水泵驱动、供电隔离与控制状态机

3.1 一个小水泵背后的NMOS驱动电路

水泵不能直接接在GPIO上,这是很多人第一版烧板子时学到的血泪教训。

STM32的GPIO输出电压只有3.3V,输出电流能力在几个毫安量级;而水泵是12V供电、工作电流几百毫安的感性负载。中间必须加一级驱动电路。我选的是最简单也最实用的NMOS低端驱动方案:用AO3400这个增强型N沟道MOS管,配合几个电阻搭成开关。

电路连接方式:

  • 单片机PA1引脚先串一个1kΩ电阻,接到AO3400的栅极(G极)
  • 栅极到地再并一个10kΩ下拉电阻,确保单片机复位期间GPIO处于高阻态时,MOS管不会意外导通
  • 源极(S极)接地
  • 漏极(D极)接水泵的负极线
  • 水泵正极接12V电源正极
  • 水泵两个引脚之间反向并联一个1N5819肖特基二极管,用来吸收断电瞬间感性负载产生的反向尖峰电压

这个方案叫低端开关,因为开关放在负载和地之间。相比高端开关要驱动P管或者自举电路,NMOS低端开关电路极其简单,控制逻辑也直观:GPIO输出高电平,MOS管导通,水泵得电;输出低电平,水泵失电。

实测AO3400在这个场景下的发热可以忽略不计。电流不到1A,导通阻抗只有几十毫欧,算下来的管压降几乎为零。

3.2 供电回路必须共地,降压模块别省

我这块板子遇到的第一个奇怪现象是:单独给12V水泵供电能转,单独给STM32供电程序能跑,但把两个系统接到一起后,水泵怎么都不转。

排查到最后,原因就是两个字:没共地。

水泵的负极接在NMOS漏极,NMOS源极接地,但这个"地"指的一定要是STM32板上的GND。如果12V电源的地和STM32板的地没有连在一起,PA1输出的3.3V高电平相对于水泵驱动回路的参考地来说是"悬空"的,MOS管栅源电压达不到开启条件,自然不会导通。

连接正确后,第二件事是把12V降到5V给单片机供电。如果你直接用USB线给STM32供电,又拿12V单独给水泵供电,两个系统虽然共地了,但USB那边的5V地线很细,水泵一启动,瞬间大电流通过地线回流,STM32的ADC参考电压会抖动,直接污染湿度采样。我这里用了LM2596S降压模块,12V统一进来,5V和12V两根电源线都用较粗的导线走,模拟地和功率地在主板上星型汇接,问题就消失了。

星型接地可以这样理解:所有GND线不要串成一根链,而是像蜘蛛网一样,每一路单独拉回电源负极这个"中心点"。水泵启动时的大电流不会流过传感器ADC的参考地路径,模拟采样就干净很多。这个习惯在做混合供电系统时非常管用。

3.3 用状态机写浇水策略,而不是堆一堆if

控制逻辑是整个系统的大脑。如果只用"湿度低于30就开泵"这一句if,实际运行起来会非常难受:传感器数值在阈值附近抖来抖去,水泵像抽风一样频繁启停。

我用了一个简单的状态机来管理浇水流程,状态定义如下:

typedef enum { ST_IDLE, // 空闲:周期检测 ST_WATERING, // 浇水:保持水泵开启,持续监测 ST_ALARM, // 报警:停止水泵并提示 ST_MANUAL // 手动模式:仅响应人为操作 } SysState;

各状态之间按规则迁移,核心逻辑是:

  • ST_IDLE:每10秒采样一次湿度,如果湿度连续3次低于下限,才切到ST_WATERING。连续3次确认是为了滤掉偶发抖动,不是一看到数值低就动。
  • ST_WATERING:打开水泵,每2秒采样一次湿度。如果湿度达到上限,立刻关泵回ST_IDLE;如果连续浇水超过300秒水桶还没检测到低液位,也强制停止,并进入ST_ALARM。
  • ST_ALARM:关闭水泵,OLED显示故障代码E01(湿度无明显上升)、E02(水桶空)、E03(传感器异常),必须人为处理后按键复位。
  • ST_MANUAL:自动逻辑完全失效,只响应"手动浇水5分钟"按键。这个状态的优先级最高,切换开关一拨过去就进入。

使用状态机的好处是每个"动作"都有明确的进入条件和退出条件,不会出现"还在浇水但湿度又低于下限"这种互相冲突的情况。调试的时候,我在串口上打印状态切换的时间点和触发原因,系统跑了一个月,日志一目了然。

4. 挡在"自动浇水"前面的那些坑:实测与调试记录

4.1 ADC数据抖动与软件滤波

前面说到MCU测到的ADC原始值波动很大,这一点在实时系统里会直接影响判断。我实测在干燥土壤里,同一位置、同一时刻,ADC原始值前后能差200左右。这个波动如果直接用于阈值判断,下限设在30%的话,系统一天可能误动作十几次。

解决方案分三层,每一层都有自己的目的:

  1. 硬件层:传感器VCC和GND之间并一个100nF陶瓷电容,滤掉高频噪声;供电走线尽量短。
  2. 采样层:每轮采样连续读取8次,去掉最大值和最小值,剩下的6次取平均。这样单轮采样的抗脉冲干扰能力就很强了。
  3. 确认层:状态机里"连续3次低于下限"才动作,等于又加了一道软件确认。

另外特别注意一个容易被忽略的情况:浇水刚刚结束的时候,传感器探头周围会有一层水膜,此时读数会接近饱和值,看起来土壤"很湿",但实际土壤深处可能还没渗透到位。如果在这个时间段继续检测,系统会误以为水分充足,导致下一次浇水推迟过久。我在代码里写了一个"浇水后稳定期":每次水泵停止后,15分钟内不判断是否需再次浇水,让水分充分渗透扩散后再恢复检测。

4.2 水桶干转,是水泵最常见的死法

微型潜水泵没有散热设计,靠水流动带走电机热量。一旦水桶空了,水泵还在运行,干转十几分钟电机就会严重发热,甚至直接烧毁。

我的保护方案做了三层:硬件、逻辑、超时。

  • 硬件层:在水桶外壁贴一个非接触式低液位传感器,当水位低于设定值时,传感器输出低电平给PB5引脚。光电式的优点是不接触液体,不会因为水垢、青苔造成误判。
  • 逻辑层:ST_WATERING状态下,每当读取到低液位信号,立即停止浇水并切到ST_ALARM,OLED显示E02。
  • 超时层:不管传感器是否正常,任何一次浇水最多持续300秒,到时间强制停止。这个是最后的兜底,防止"传感器坏了所以永远浇不够"的极端情况。

实测中我发现,如果完全依赖液位传感器,一旦它接线松动或者模块位置被碰歪,保护就失效了。所以超时保护不是可有可无,而是必须存在。你可以理解为:液位传感器负责"看到水桶空了",超时定时器负责"就算没看到也不能无限浇下去"。

4.3 手动自动优先级怎么设计

自动系统做得再好,真正使用的时候人总有干预需求:换盆、换土、施肥、给叶片喷水,这里面的土壤湿度变化很复杂,自动逻辑可能做出和预期相反的动作。

我在外壳上装了一个三脚船型开关,用来切换手动/自动。自动挡下状态机全权接管;手动挡下自动检测和自动浇水全部失效,但保留OLED显示功能,可以随时看到当前湿度。另外在手动模式下,短按一次按键,水泵工作固定5分钟后自动停止,防止人忘了关泵。

这个设计看着简单,但实际价值很高。我朋友家里用的时候,就靠这个开关在阳台大扫除时切到手动,不然自动模式会以为花盆彻底干透,疯狂补水。手动模式下还保留了超时保护,也就是说不存在"切到手动就能一直开着泵"的隐患,这也算是安全设计的一部分。

还有一个小功能是假期提醒:如果连续3天系统都检测到湿度低于下限,但触发不了正常浇水流程,OLED会闪烁显示"CHECK",提示出门的人检查水桶和管路。这个功能不是必需,但加上了之后整个系统显得相当成熟。

5. 从能跑到能交付:完整资料应该包含什么东西

5.1 设计文档的写法与结构化思路

项目写完后要交给别人复刻,"资料完整"绝不是丢一堆代码和一张电路图那么简单。资料的价值在于:让一个没参与设计的人,从零开始也能成功组装、烧录、校准并让它稳定运行。

我的资料包按顺序分了这几层:

  • 需求说明:用一页纸说清楚系统做什么、有什么功能、用户怎么操作。这是给非技术角色看的。
  • 系统框图:用文字和箭头的形式画出信号流向和电源流向。这是给人建立整体认知的。
  • 接线表:写明每个模块的哪根线接到STM32的哪个引脚、什么颜色、去向哪里。这是给动手组装的人看的。
  • 原理图细节说明:标注每个电阻、电容在电路中的作用,特别是NMOS驱动和保护二极管。
  • BOM清单:所有元件的名称、型号、数量、参考价格。
  • 烧录与校准说明:从下载程序到完成三步校准的完整步骤。
  • 故障排查表:把我在调试中遇到的现象和解决办法整理成表格,后续维护者可以直接查。

这个顺序其实是模仿专业嵌入式项目的交付结构,但又比公司里的文档更精简。所有内容加起来十几页,信息密度足够,别人照着做一天内就能复刻出来。

5.2 BOM清单与预算参考

元件清单列表如下,价格是以网购平台普通零售价估算的:

类别元件数量参考单价作用
主控STM32F103C8T6最小系统板1约12元系统核心
传感器电容式土壤湿度传感器(带AO输出)1约15元检测湿度
执行12V微型潜水泵1约8元抽水浇水
驱动AO3400 NMOS管2约1元低端开关
电源12V 2A适配器1约15元系统供电
电源LM2596S降压模块1约5元12V转5V
显示0.96寸OLED SSD1306 I2C1约12元状态显示
检测光电式低液位传感器1约8元空桶检测
控制三脚船型开关、按键各1约3元模式切换
接线杜邦线、导线、二极管、电阻、电容若干约10元电路连接

整套下来不到90元。如果不需要OLED和液位检测,还能再压缩十元。这个成本在同类成品自动浇水器里已经算很低了,更重要的是自己做的系统逻辑透明、出了问题能自己修。

5.3 代码工程与模块化注释思路

代码结构上,我把整个工程拆成几个模块,每个模块只负责一件事:

  • bsp_moisture.c:湿度传感器的ADC初始化、采样、滤波、校准参数存储
  • bsp_pump.c:水泵控制引脚、开泵、关泵、超时计时
  • bsp_oled.c:OLED初始化和界面刷新
  • app_state_machine.c:状态机迁移逻辑、按键处理、报警处理

这样分模块的好处是查问题快。比如有一次OLED显示乱码,我只需要去查OLED的I2C时序和刷新频率,完全不用碰状态机代码。对想拿这个项目做课设或毕设的人来说,这种模块划分本身也是答辩时的加分项。

代码注释我坚持写"为什么",不写"是什么"。例如:

// 这里必须等待300ms再读ADC // 因为传感器上电稳定需要时间,过早读取会拿到初始化噪声

这类注释对后来维护的人帮助巨大,两个月后你自己回来看代码也容易恢复上下文。我还习惯在每个文件头部用几行注释说明这个模块的对外接口和依赖关系,这样别人拿到源码后不用挨个文件读就能定位入口。

5.4 故障排查表:把我踩过的坑提前写给你们

为了让资料能真正上手可用,我把调试时遇到的高频问题整理成了一张速查表:

问题现象可能原因排查方法
OLED不显示I2C地址不对、供电不足、SDA/SCL接反先查供电再扫描地址,用软件I2C换引脚测试
水泵不转未共地、NMOS栅极无下拉、GPIO未初始化用万用表测MOS管G-S电压是否超过2V
湿度读数乱跳传感器供电噪声、采样间隔太短加100nF电容,按8次去最值平均采样
浇水永远停不下来湿土上限设置太高、传感器位置靠近滴水点重新校准,把传感器插在根系区而非正对滴头
拔掉USB后程序不走供电电压不足、降压模块输出电流不够检查LM2596S输出电压是否稳定在5V

这张表在交付资料里的价值极高。很多人拿到资料跑不通就放弃了,但有了这张表,十个常见问题里八个能自己定位解决。

最后再分享一个实际体会

整套系统做完以后我最大的体会是:这类物联网小项目的技术门槛真的不高,难点全在稳定性。土壤湿度传感器必须校准、水泵必须做保护、电源必须考虑共地、状态机必须处理异常——这些边边角角处理完,系统才算真的"智能",否则它只是另一个定时开关。

如果你打算复刻这套资料,我的建议顺序是:先把器件买齐搭裸板、把校准流程跑通,再去做OLED显示和状态机完善,最后才考虑手机远程监控这种扩展功能。本地闭环不稳,远程控制只会放大问题。等第一版稳定运行两周以上,再给它加WiFi模组或者联网上报,那时候你手里已经有了一台可以依赖的设备,而不是一个永远在调试的demo。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进