资讯动态

STM32F103+CubeMX实现土壤湿度监测:从ADC采样到自动灌溉

发布时间:2026/10/24 5:48:59 来源:尧图企业网站定制
刚把土壤湿度监测这块跑通的时候我其实有点意外整个项目从零到出数据比预想的要快太多了。项目标题就是“土壤(STM32F103CubeMX)”说白了就是用STM32F103这颗老将配合STM32CubeMX图形化配置工具做一套能实时读取土壤湿度、把数据显示出来甚至能触发外部动作的小系统。为什么选这个组合理由很直接第一STM32F103是市面上最普及、资料最多的ARM Cortex-M3单片机几乎每个嵌入式开发者手里都有一块拿来当土壤监测主控再合适不过第二CubeMX可以帮我们自动生成初始化代码不用再手动敲一堆寄存器配置把精力全部集中在业务逻辑上非常适合从零快速搭一个原型。这篇文章我会从整体设计思路、传感器原理、CubeMX配置、代码实现到调试排坑把整条链路讲透同样想上手土壤检测项目的朋友可以直接照抄。1. 项目整体设计思路从“测个湿度”到一套完整监测方案1.1 需求拆解这个项目到底在解决什么问题先想清楚一件事我们要的不是“测一下土壤湿不湿”而是“持续、稳定、可量化地知道土壤湿度是多少”。这一字之差背后的差别很大。前者可能拿个简易湿度计插土里看一眼就行但后者的场景是无人值守的可能要接一个水泵或者把数据传到云端用来做植物自动灌溉。我当时给自己定的目标很简单一块STM32F103最小系统板一个土壤湿度传感器一段能把ADC采样结果换算成湿度百分比并显示出来的代码外加串口输出方便上位机看数据。这个系统要跑在5V或者3.3V下功耗不能太离谱代码结构要清楚方便后续再接继电器、OLED、ESP8266之类的模块。拆解之后系统的数据链路就清晰了土壤湿度传感器把土壤的介电常数变化转成电压信号STM32的ADC外设把这个模拟电压采样成数字量然后单片机内部把数字量映射成0%到100%的湿度值最后通过串口或者显示屏输出。1.2 为什么选STM32F103看似保守其实是最稳的选择你可能觉得STM32F103有点“老”都2025年了还推这个但我得说在做土壤监测这类对实时性要求不高、对成本敏感、对稳定性要求较高的应用中F103依然是性价比之王。几个实际原因第一F103的ADC精度是12位采样电平0到3.3V土壤湿度传感器输出的模拟电压普遍在1V到3V之间这个分辨率完全够用能把湿度变化分辨到接近0.1%的刻度第二F103有多个UART、I2C、SPI外设后续想接ESP8266传数据、接OLED屏显示、接继电器控制水泵都不用换主控第三它的HAL库和生态成熟度非常高CubeMX生成的工程模板稳定网上能参考的案例极多遇到问题搜一圈基本都能解决。我记得最初也纠结过要不要用ESP32毕竟自带WiFi直接把数据传云端省事。但后来一想这个项目里WiFi不是刚需而且ESP32的ADC线性度在某些封装下并不好采样土壤湿度这种缓慢变化的信号反而会有噪声干扰。相比之下F103的ADC配合简单的软件滤波数据稳的一批。1.3 CubeMX带来的效率提升一半代码不需要手写了STM32CubeMX这个工具很多人对它又爱又恨。爱的是它把时钟树、引脚复用、外设参数全可视化点几下鼠标就能生成完整的初始化工程恨的是生成代码的目录结构复杂看起来有点吓人。但实际用下来对于F103这种HAL库的芯片CubeMX生成的代码质量很高大多数情况下你只需要在main.c里补业务逻辑。我现在的习惯是所有外设初始化全部交给CubeMX包括时钟、ADC、UART、GPIO然后自己在main.c或者单独的应用文件里写读ADC、滤波、换算、输出的逻辑。这套流程最大的好处是可复现性极强换了项目改一下CubeMX配置代码主体能直接复用省下了大量调初始化参数的时间。2. 土壤湿度传感器原理与选型测量值不是玄学2.1 电阻式与电容式到底怎么选一个被腐蚀一个不会市面上最常见的土壤湿度传感器有两类电阻式也叫张力式和电容式。电阻式的原理非常简单两片金属探针插入土壤土壤里的水分越多导电性越好探针之间的电阻越小。配合一个LM393比较器模块输出模拟电压和数字开关信号。探针插入土中相当于一个可变电阻和模块上的固定电阻分压ADC读到的电压就会随湿度变化。这种传感器便宜几块钱一个但有一个致命缺陷金属探针插入潮湿土壤后在直流电流作用下会发生电解反应铜探针很快被腐蚀。我实测过连续泡在水里一周探针表面明显变黑灵敏度显著下降。电容式传感器则是利用土壤的介电常数变化来检测湿度。水的介电常数大约在80左右而干土的介电常数在2到7之间湿度大的土壤介电常数明显更高。传感器内部是一组PCB板上做的叉指电容土壤作为电介质的一部分电容值随湿度变化再通过NE555之类的振荡电路把电容变化转换成频率或电压输出。电容式探头的表面是防腐蚀涂层不会被电解寿命长很多价格也贵一些目前是植物智能化种植的主流选择。我的建议是如果是做验证性实验、短期测量、课程设计电阻式就够了几块钱的成本挂个ADC就能采样不用纠结。如果是打算长期放在花盆、田地里做准自动化控制直接上电容式省得三个月后探针烂掉导致数据全部失真。2.2 传感器与STM32的接口怎么接分清模拟和数字不管选哪类传感器市面上大多数模块都做成四根线VCC、GND、AO模拟输出、DO数字输出。AO直接接到STM32的ADC输入引脚DO信号是一个由电位器调阈值的比较器输出当湿度超过某个设置值时DO输出会翻转可以用来直接触发外部中断但实际应用中大多数情况宁愿用AO读模拟量在软件里做判断阈值方便随时改。接线方式很简单VCC接3.3V或5V注意传感器模块上是否有板载稳压芯片如果没有且STM32的系统电压是3.3V建议先查一下模块对供电的要求。我测过几款常见的模块2.5V到5V都能工作输出AO范围一般是0到供电电压。GND共地这个必须接ADC是相对参考电压测量的不共地读数会飘。AO接STM32的ADC通道引脚比如PA0对应ADC1的通道0。DO不用可以不接。还有一个容易忽略的细节部分传感器模块在3.3V供电时的模拟输出最高只有3.3V但有些老模块在5V供电时会输出接近5V的电平如果直接接到STM32的PA0上会超出ADC的参考电压范围损坏芯片。稳妥的做法是让传感器和STM32统一用3.3V供电如果必须用5V供电要确认模块输出是否在3.3V以内或者串联一个电阻做分压。2.3 ADC采样原理从泥土里的电阻到数字量STM32F103的ADC是逐次逼近型SAR ADC核心思路可以类比成用天平称重量先用一个较大的砝码如果放上去发现超重就换小砝码不断二分逼近直到精度满足为止。F103的ADC是12位的意味着它可以输出0到4095之间的整数。对于3.3V的参考电压每个最小量化单位对应的电压是3.3V除以4096大约0.8mV。实际采集到的数字量N与电压V的关系是V N × 3.3 / 4095。然后这个电压怎么映射成湿度最常用的方法是“标定两点”把传感器完全插入干燥土壤中记下ADC读数记为dryVal大概在2500到3000左右。把传感器完全插入水中注意只有探头部分不要泡到电路板记下ADC读数记为wetVal大概在1200到1800左右。湿度百分比 (dryVal - currentVal) / (dryVal - wetVal) × 100%。为什么要用值相减而不是直接把电压拿来用因为传感器的绝对输出电压受供电电压影响而同一次测量中的差值比绝对值稳定得多。上面只是个参考公式实际曲线不一定是线性但对于大多数场景线性逼近已经够用了。想要更准可以采用三点标定甚至查表法后面我会在故障排查部分细聊校准的方法。3. CubeMX工程搭建实操从新建项目到代码生成3.1 时钟树配置把系统跑在72MHz打开CubeMX新建工程后第一件事就是配时钟。F103的时钟树看起来复杂但掌握核心逻辑之后其实就那么几个关键节点。开发板上一般都有8MHz的外部晶振HSE我们把它作为系统时钟的源头通过PLL锁相环倍频到72MHz。在CubeMX的Clock Configuration页面里我通常这样操作输入HSE为8MHz。把PLL源选为HSE倍频倍数设为9这样HSE进入PLL后PLL输出就是8MHz × 9 72MHz。系统时钟源选择PLL的CLK输出。把APB1分频设为2得到36MHzAPB2分频设为1得到72MHz。这里注意ADC挂在APB2上它的时钟源是72MHz但ADC本身最高只能跑14MHz所以ADC分频系数要设成6得到12MHz的ADC时钟刚好在安全范围内。这个配置方法对于绝大多数F103项目都通用。如果用的是HIL或内部RC振荡器代码也能跑但ADC采样精度和串口波特率准确性都会打折扣我强烈建议优先用开发板上的外部晶振如果板子上没有晶振那就得用内部HSI但届时串口的波特率误差会偏大通信质量不好保证。3.2 ADC外设配置采样速度、分辨率、扫描模式在Categories列表里找到ADC1启用它。我常用的配置是ModeIndependent mode也就是独立模式只用一个ADC不搞双ADC同步采样。Number of Conversion1个转换如果你挂了多个传感器可以增加但要记得配置扫描顺序。ADC 12-bit分辨率这是F103默认的能力。Scan Conversion Mode如果只有一个通道转换保持Disabled如果多路采集开启这个并把Number of Conversion设成对应数量。Continuous Conversion Mode如果希望ADC自己一直采把缓冲区数据不断更新可以开启否则用单次转换。我一般开Discontinuous因为软件里用一个稍大的延时手动启动转换读回来逻辑更直观。采样时间Sample TimeF103的采样时间有几种可选常见是55.5周期或者239.5周期。我实测下来采样时间越长ADC输入源的内阻影响越小读数越稳尤其是接土壤湿度传感器这种输出阻抗偏高的设备建议直接选239.5周期。ADC时钟分频设为6这样ADCCLK72MHz/612MHz不超过14MHz的规格上限采样率还是能达到1MHz级别对土壤湿度这种低速信号来说绰绰有余。3.3 GPIO与串口配置给数据找一个出口土壤湿度数据读出来总要给人看或者传给上位机最省事的方式就是串口。在CubeMX里点击USART1Mode选Asynchronous异步收发波特率设115200数据位8无校验停止位1其他保持默认。引脚会自动复用成TXPA9和RXPA10。如果你要接OLED屏走I2C的话还要额外配置I2C1把SCL、SDA分配到PB6和PB7速率一般设400kHz。不过第一次做的时候我建议先把串口打通哪怕只是printf一个Hello from soil monitor也能确认整个系统的基础没问题。数据显示和显示器的内容等ADC采样功能做出来再加这样出问题的时候排查范围小。4. 核心代码与数据处理让测量值变得可信4.1 ADC采样代码从HAL库到实际读值CubeMX生成的代码已经把ADC初始化好了我们只需要在main函数的主循环里加上采样逻辑。核心代码如下/* 读取土壤湿度ADC值 */ uint32_t getSoilADC(void) { uint32_t adcValue 0; HAL_ADC_Start(hadc1); if (HAL_ADC_PollForConversion(hadc1, 100) HAL_OK) { adcValue HAL_ADC_GetValue(hadc1); } HAL_ADC_Stop(hadc1); return adcValue; }这段代码的思路就是启动一次ADC转换等待转换完成把结果读出来然后关闭ADC。有人会问为什么要先Start再Stop不能一直开着吗当然可以但如果多个传感器共用一个ADC就需要重新选择通道并触发转换每次采样完成就Stop相当于每次采样都从一个干净的状态开始逻辑更安全。调试时发现的问题PollForConversion里的超时参数如果设成100ms整个循环会阻塞100ms。土壤湿度信号变化本身是秒级的所以就算阻塞100ms也不影响业务但如果你在同一循环里还要做显示刷新、按键扫描这个阻塞就不能太长建议把超时改成1ms然后靠循环频率来保证数据连续性。这里还有一个很多人会犯的错ADC转换后的值直接拿来用不够平滑。传感器在土里会因为根部的空气、水分分布的微观差异导致读数上下跳幅度能有几百个ADC单位。这时候需要加滤波。4.2 电压换算与土壤湿度映射拿到ADC原始值后第一步是换算成电压方便在上位机调试的时候确认传感器工作状态float voltage (float)adcValue * 3.3f / 4095.0f;如果在空气中测试湿度基本是0电压应该在2.8V左右插在水里湿度接近100%电压会降到1.5V以下。如果读数不符合这个规律先查供电和接线。然后做湿度映射。因为前面已经标定了dryVal和wetVal直接套公式int soilPercent (int)((dryVal - adcValue) * 100.0f / (dryVal - wetVal)); if (soilPercent 0) soilPercent 0; if (soilPercent 100) soilPercent 100;注意两个边界处理如果adcValue比wetVal还低说明传感器泡水里值可能超出100%要限幅如果adcValue比dryVal还高说明探头完全干透或者悬空值会是负数也要限幅。4.3 滑动平均滤波让数据不再跳变最直接有效的滤波方式是滑动平均。在内存里维护一个长度为10的数组每采样一次就丢进去把最新10次取平均。这个方法简单粗暴性能开销小适用于F103这种主频72MHz、RAM不算富裕的场景。#define FILTER_SIZE 10 uint32_t filterBuffer[FILTER_SIZE]; uint8_t filterIndex 0; uint32_t getFilteredADC(void) { uint32_t sum 0; uint8_t i 0; filterBuffer[filterIndex] getSoilADC(); filterIndex (filterIndex 1) % FILTER_SIZE; for (i 0; i FILTER_SIZE; i) { sum filterBuffer[i]; } return sum / FILTER_SIZE; }这里有点绕的地方是环形缓冲区的实现但效果很好每次新值进来覆盖最旧的那个值平均结果始终反映最近一段时间的中位数趋势。实测下来原本上下跳300单位的原始数据经过10次滑动平均后波动被压到20单位以内完全够用。如果想更省RAM还可以用一阶低通滤波用如下公式out out (newSample - out) / 8效果类似但需要针对传感器的响应速度调系数。对土壤湿度这种本身变化就慢的信号两种滤波方式我推荐滑动平均逻辑线性容易理解出bug的概率低。4.4 阈值判断与自动灌溉扩展接上继电器就算完成了闭环硬件上GPIOC13之类的普通引脚接一个继电器模块就能控制水泵或电磁阀。软件上在滤波之后加一个逻辑if (soilPercent 20 !pumpStatus) { HAL_GPIO_WritePin(PUMP_GPIO_Port, PUMP_Pin, GPIO_PIN_SET); pumpStatus 1; } else if (soilPercent 60 pumpStatus) { HAL_GPIO_WritePin(PUMP_GPIO_Port, PUMP_Pin, GPIO_PIN_RESET); pumpStatus 0; }注意这里用了滞回判断启动阈值设成20%停止阈值设成60%而不是用一个单一阈值。原因很简单如果单一阈值设在40%湿度在40附近波动时水泵会频繁启停继电器寿命骤减。滞回区间让水泵在“低于20%开始浇高于60%才停”中间区域保持状态这是工业控制里最常见的防抖思路。额外提醒一点继电器模块和STM32之间务必做好隔离至少用光耦隔离模块否则继电器线圈的反向电动势和触点切换的浪涌很容易把单片机干复位甚至烧GPIO口。5. 调试过程中踩过的坑与排查速查表5.1 常见问题清单与解决思路整个项目做完我觉得最花时间的反而是各种看起来不起眼的坑。下面这张表是我实测过程中遇到的典型问题。现象可能原因排查步骤与解决办法ADC读数始终接近4095AO引脚悬空或接触不良用万用表量AO对GND电压确认传感器是否上电检查杜邦线连接读数一直在跳幅度超过200电源纹波大或采样时间不足采样时间设为239.5周期供电加10uF0.1uF电容滤波检查共地湿土读数反而不变传感器探针被腐蚀或结垢用软布清洁探头表面换电容式传感器在水中读数不饱和水没有接触到探针敏感区确认探针插入深度泡水时水面要没过探针大部分面积注意不要越过模块的PVC防水线湿度百分比计算为负数dryVal标定值偏小重新在干燥土壤中标定确保探头完全在干土里而不是悬空串口输出乱码波特率配置不对或时钟不准检查CubeMX串口波特率确认时钟树PLL配置正确排查晶振是否损坏5.2 校准建议别信那种“拿起来就准”的标定我在第一节留下了校准的坑现在详细讲讲。拿一杯干土、一杯水来标定是正常的思路但实际操作中要注意第一干土要自然风干不要在太阳下暴晒到绝对干燥实际农业环境中也没有绝对干燥标定基准应该对应用途。如果你打算检测花盆是否缺水干燥基准可以用“放置一周不浇水的土”如果你要做高精度农业监测干燥基准需要在105度烘箱里烘干至少8小时然后拿出来在干燥器里冷却。第二水中标定时不要用自来水管子里直接冲出来的水里面气泡多而且水温和土温不同介电常数有一定的温度系数。最好用静置一段时间的室温水把传感器插进去后静置一两分钟再读数让水膜稳定。第三标定不是标一次就永远不变。传感器的老化、探头表面附着物、土壤类型不同都会改变绝对ADC值。正确做法是每换一批土或者每隔几周重新标定一次把dryVal和wetVal更新到Flash里这样数据长期稳定。5.3 提升系统可靠性的几个细节这些都是被项目逼出来的经验分享给大家串口调试助手用的线质量很重要。便宜的USB转TTL模块在长时间传输时容易丢字节我遇到过几次换了CH340模块后彻底消失。如果你发现数据时断时续先怀疑USB转串口线。土壤传感器的线不要和继电器线并在一起走。继电器通断瞬间会有较大的电流变化会在连接传感器的线束上感应出噪声导致ADC值瞬间跳变。解决办法是让模拟信号线和功率线分开布线实在避不开就在ADC引脚附近并联一个小电容到地。电源方面STM32和继电器、水泵最好分开供电。如果共用一个5V电源继电器吸合瞬间电压跌落可能让STM32复位。我用过一个12V水泵启动电流接近2A直接把5V拉垮了单片机反复重启后来换独立电源问题消失。加入看门狗。F103有内置IWDGCubeMX里配置一下喂狗时间设2秒。如果程序因为传感器短路、外部干扰跑飞看门狗能自动复位让系统回到正常状态在地里没人管的时候这个功能非常关键。5.4 后续扩展思路往联网和低功耗方向走这个项目做完后我其实挺后悔没在一开始就留好扩展接口的。如果你也打算继续往深了做有两条路可以参考。第一条是联网化在USART2上接一个ESP-01S或者ESP8266模块把温湿度数据通过AT指令POST到本地服务器就能在手机端实时看到土壤状态。资料已经非常成熟但要注意ESP8266是5V容忍和STM32的3.3V电平匹配问题最好用模块的3.3V供电TX接个1K电阻分压再连STM32的RX。第二条是低功耗化F103支持STOP模式可以在CubeMX里配置EXTI唤醒然后利用RTC定时唤醒采集。系统休眠时电流能压到20uA以内如果配合太阳能电池就完全可以做成一个放置在田间的独立监测节点一节18650电池能撑几个月。网上有人把整套系统做成了烟盒大小非常精致思路基本都是这个路子。我在实际使用中还有一个体会做这种偏物理量的测量项目最难的不是代码而是对传感器机理和数据分析的理解。土壤湿度传感器出来的原始数值在没有标定、不理解滤波的时候看起来就是一堆跳动的数字毫无价值可当你把校准、滤波、标定、阈值判断这套流程捋顺之后它能变成一个可靠的数据源。这个项目做下来最有收获的部分就是把从传感器到决策的整条链路完整打通了以后再接其他传感器比如土壤温度、光照强度套路几乎是复制粘贴。如果你想继续折腾我最后再分享一个小技巧把所有传感器的原始值、换算后的物理量、滤波后的结果都通过串口输出来格式用逗号分隔就像3240,1.25V,0.36,36.2%。因为这样可以直接在TSV/CSV里打开并发散分析一组对比数据放一起会节省非常多调试时间。这个小习惯让我在后续几个项目里少掉了不少头发。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑