简介:基于STC89C52单片机与YL-69湿度传感器构建的土壤温湿度检测计设计文档,适合电子、嵌入式及农业自动化方向的初学者和课程设计参考。内容从选题背景、方案对比到单元模块电路、软件流程和系统调试均有完整说明,并给出了硬件调试、软件调试及传感器漂移等问题的处理思路,可直接作为毕业设计或创新项目的文档范本。这份PDF共1个文件,压缩包大小1.56MB,内容预览显示目录涵盖绪论、总体方案、时钟/复位/报警/显示模块、LCD1602与蜂鸣器介绍、软件设计及系统功能结论,整体结构完整、章节清晰。已有208人学习下载,可作为低成本土壤湿度监测系统设计的入门参考资料,便于快速理解系统框架并复用关键电路与程序逻辑。 前阵子给阳台蔬菜箱做自动浇水,最开始靠手指摸土判断干湿,结果不同深度的土手感完全不一样,浅层干了深层还湿着,几次浇水要么太多要么太少。后来干脆自己动手做了一台基于单片机的土壤温湿度检测计:一块STC8系列单片机做主控,配上土壤湿度探头和DS18B20温度传感器,OLED屏实时显示,做完了放在菜箱里插着,干湿一目了然。
这篇文章把整台检测计的硬件选型、信号调理、软件逻辑、标定方法和实测坑点都梳理一遍。如果你正准备做单片机毕设,或者想给家里搞一套土壤监测小设备,这套方案可以直接抄作业。
1. 为什么土壤湿度检测计值得自己动手做一版
1.1 先想清楚:这颗检测计到底要解决什么问题
很多人一上来就直接买成品土壤传感器,插土里接个显示屏就完事。市面确实有Modbus输出的工业级土壤温湿度传感器,接个串口屏就能读数据,但两三百起步,而且内部标定曲线是黑盒,换了一种土就只能看个趋势。
自己做检测计的核心价值不在省那点钱,而在于把"电极读数怎么变成含水率"这个链路彻底打通。土壤湿度测量本质上是一个复杂的电学问题:探头的输出电压受土壤含水率、紧实度、盐分、温度等多因素影响,同一块探头插在不同土里,读数差异能超过30%。只有自己掌握信号调理和标定方法,才能根据实际土壤修正结果,而不是永远依赖厂家的出厂曲线。
我建议目标明确一点:不追求实验室级的精度,但要做到同一块地、长期插着、读数稳定可重复,这就赢了大多数DIY方案。
1.2 器件选型:主控、探头、显示与电源定案
主控方面我最初纠结过STM32F103和STC8系列。STM32外设丰富,但如果是刚入门或者做课程设计,STC8H8K64U是51内核,Keil环境熟悉,内置12位ADC,一路ADC采湿度,一路单总线读DS18B20,IO完全够用,而且成本不到5块钱,直插封装也好焊接。所以最终选了STC8H系列,整个设计思路对STC89C52也基本适用,只是89C52的ADC是外部扩展的,需要另接。
探头是这台设备的核心。常见的有三种选择:电容式FDR探头、电阻式电导探头和工业频域反射探头。三种差异后面专门讲,我这边方案里用的是电容式土壤湿度探头模块(输出电压模拟量),因为它没有裸露金属电极,长期埋在土里不容易电解腐蚀,输出受盐分影响也比电阻式小。
显示用0.96寸I2C接口的OLED,功耗低、显示内容直观。DS18B20测温防水探头单独引出,埋在探头旁边。电源用一块18650锂电池加LDO稳压到3.3V,如果要长期插着,可以再加一块TP4056充电板,整个系统待机电流控制在一二十毫安内,一颗电池撑一周没问题。
1.3 整机框图与工作流程
整机结构并不复杂:电池经过LDO后给主控、传感器和OLED供电;土壤湿度探头输出0到3V左右的模拟电压,经RC滤波后进主控的ADC引脚;DS18B20走单总线接普通IO口;I2C总线接OLED。
工作流程是每两秒启动一次采集,先给湿度探头上电,等100毫秒让输出稳定,连续采8次做去极值平均滤波,再读DS18B20温度,最后刷新OLED显示。温度不仅用于显示,还能对湿度读数做温度补偿。整个循环跑完大约300毫秒,剩下时间单片机进IDLE模式省电。
如果需要,再接一个蜂鸣器或LED做阈值报警,按键可以调整报警点。这部分后面在软件里说。
2. 探头选型与信号调理电路的关键参数计算
2.1 电阻式探头和电容式探头的实测差异
先说我踩过的坑。第一批样机贪便宜买了一对电阻式土壤湿度探头模块,就是两片裸露铜箔插在土里的那种。刚插下去读数还挺正常,干燥时输出电压高、浇水后电压低,趋势没问题。但用了三天,同一盆土、同一位置,干燥时读数漂了差不多一倍,问题就出在直流极化上。
电阻式探头本质是测两点之间的导电率,可土壤不是纯电阻,是复杂的电阻电容网络。探针上加直流电压,离子会向两极迁移形成极化层,等效阻抗随时间变化,读数自然漂。电容式探头则不同,它把土壤作为介质的一部分,测量高频激励下的介电常数变化,水分子极性大、介电常数约80,干土只有3到7,湿度一变电容值变化很明显,所以读数稳定得多。
如果你手头只有电阻式探头,也不是不能用,但必须做间歇供电——每次采样前才上电,读取后立即断电,把通电时间压到100毫秒以内,极化影响能小很多。下面表格是我几种探头实测下来的感受:
| 探头类型 | 输出信号 | 长期稳定性 | 盐分敏感度 | 单片成本 |
|---|---|---|---|---|
| 电阻式/电导式 | 模拟电压/频率 | 差,易极化腐蚀 | 高 | 3-5元 |
| 电容式/FDR | 模拟电压/频率 | 好,无裸露金属 | 低 | 8-20元 |
| 工业频域反射式 | RS485/4-20mA | 好,可长期埋土 | 低 | 200元以上 |
建议直接选电容式模块,贵几块钱省掉一堆麻烦。
2.2 信号调理电路设计:分压、跟随、滤波与参考电压
电容式探头模块的输出范围因型号而异,常见的是0到3.0V或0到3.3V,供电电压不同输出上限也不同。这里最容易翻车的点,是ADC参考电压选不对。
STC8H内置12位ADC,参考电压可以选内部2.5V基准,也可以直接用VCC当参考。如果你用3.3V供电、探头输出最高3.0V,直接采样的话3.0V已经超过2.5V内部参考,顶部会被削掉。我第一版就是栽在这儿,饱和土读数直接顶到4095,怎么变化都看不出来。
解决办法是加一级分压。在探头输出和ADC引脚之间用一个2:1电阻分压,把0到3.0V映射到0到1.5V,再进ADC。公式很简单:
输出电压 = 探头电压 × R2 / (R1 + R2)
R1取10k,R2取10k,正好对半分。分压后信号源内阻变高,直接接ADC会影响采样电容充电,中间加一个运放电压跟随器缓冲。LMV321这类单电源轨到轨运放就够了,一个SOT23封装非常小。如果手头没有运放,可以把分压电阻总值降到1k到2k之间,也勉强能用,但会额外消耗探头模块驱动能力。
滤波方面,ADC引脚前加一个一阶RC低通,R取1k,C取0.1uF,截止频率约1.6kHz。这个位置不需要很高的截止频率,因为土壤湿度变化本来就慢,反而是滤掉探头上电瞬间的尖峰更重要。
2.3 DS18B20温度采集:单总线的时序与上拉
DS18B20是几乎所有单片机项目都用过的老熟人,但每次做还是有人栽在时序上。它的单总线协议要求很严格:初始化时主机拉低至少480us再释放,然后等60到240us检测从机的存在脉冲;写一个bit需要60到120us的时隙,读一个bit则主机要先拉低1us再释放,然后快速采样。
在51内核上最容易出的问题是延时不准。不要直接用空延时函数凭感觉调,用逻辑分析仪看波形最靠谱。我的经验是:初始化延时拉低用500us左右,写0时拉低50us再释放,写1时只拉低10us就释放,读时序时拉低1us后必须马上切输入模式,再延时10us采样。时序优化后再没出过错。
还有两个细节很多人忽略。一是VS18B20的数据线必须接一个4.7k上拉电阻到VCC,否则寄生供电模式下通信不稳定。二是注意供电电压,DS18B20在3.3V下工作正常,但最大测温精度对应的电源范围建议按手册来,3.0V到5.5V都没问题。读取时建议做CRC校验,数据线受干扰时能直接丢弃坏帧,避免显示一个完全不靠谱的-55度或者125度。
3. 软件逻辑:采样滤波、温度读取与显示刷新
3.1 ADC采样驱动与间歇供电控制
软件这块从ADC驱动说起。STC8H的ADC配置比较简单:开启ADC电源,选择采样通道,置位启动标志,等待转换结束标志置位,然后读结果寄存器。一次转换大约十个微秒级别。
但土壤湿度探头不能一直通电,前面说了要间歇工作。所以在主控和探头模块之间加了一颗MOS管或三极管做电源开关。采样流程是这样:
SOIL_PWR = 1; // 探头供电 delay_ms(100); // 等待输出稳定 adc_val = ADC_Read(SOIL_CH); // 采集 SOIL_PWR = 0; // 立即断电这段代码看起来简单,但100毫秒延时是有讲究的。电容式探头内部有振荡电路,上电瞬间要经历一个从0到稳定的过程,示波器上看大约要几十毫秒。延时太短读到的电压偏低,延时太长又增加极化和功耗。我实测100毫秒是比较平衡的值。
3.2 滑动平均滤波与异常值剔除
土壤环境不像干净的电路板,土壤颗粒、水分、根系都会让探头输出带上干扰。单次采样直接换算,数值可能跳来跳去,连植物都觉得你数据胡闹。
我用的滤波算法是"去极值滑动平均":连续采8次,每次间隔5毫秒,然后对8个数排序,去掉一个最大值和一个最小值,剩下6个取平均。这样做能把偶然的尖峰干扰滤掉,又不会像单纯一阶低通那样引入明显的滞后。
#define SAMPLE_NUM 8 uint16_t soil_filter_value(void) { uint16_t buf[SAMPLE_NUM]; uint8_t i, j; uint16_t tmp; uint32_t sum = 0; for (i = 0; i < SAMPLE_NUM; i++) { buf[i] = adc_read(SOIL_CH); delay_ms(5); } // 冒泡排序,简单可靠 for (i = 0; i < SAMPLE_NUM - 1; i++) { for (j = 0; j < SAMPLE_NUM - 1 - i; j++) { if (buf[j] > buf[j + 1]) { tmp = buf[j]; buf[j] = buf[j + 1]; buf[j + 1] = tmp; } } } sum = 0; for (i = 1; i < SAMPLE_NUM - 1; i++) { sum += buf[i]; } return (uint16_t)(sum / (SAMPLE_NUM - 2)); }这里排序用了最土的冒泡,因为SAMPLE_NUM只有8,完全够用,而且代码一眼能看懂。不用纠结算法复杂度。
3.3 温湿度合并处理与OLED显示刷新策略
主循环的逻辑可以做得非常简洁。温度读取放在湿度采集之前,因为读DS18B20本身要花不少时间,正好利用这段时间让湿度探头完成上电稳定过程。两个数据都拿到后,先做温度补偿,再换算含水率:
moisture_raw = soil_filter_value(); temp = ds18b20_read(); // 温度补偿:探头输出会随温度漂移,以25摄氏度为基准 voltage = (float)moisture_raw * REF_VOLTAGE / 4095.0f * 2.0f; // 乘2还原分压 voltage += (25.0f - temp) * 0.002f; moisture_percent = calibration_map(voltage);OLED显示这块有个性能问题值得提醒:SSD1306驱动IC的I2C传输一帧全屏数据要不少时间,如果每秒全屏刷新,单片机会一直忙于刷屏,主循环被卡得很死。我采用局部刷新策略——湿度百分比和温度值单独切窗口更新,每秒只刷新变化区域,整屏只在开机时刷一次。实测主循环负载从80%降到20%左右,按键响应也跟手了。
报警逻辑更简单:含水率低于下限或者高于上限,对应IO口拉高,驱动有源蜂鸣器响两声。阈值用两个按键调整,一个切换项目,一个加减数值,长按加速。E2PROM里存三组阈值,断电不丢。
4. 标定实测:把ADC读数翻译成“土壤含水率”
4.1 烘干称重法实测标定的完整步骤
程序里最终要显示"含水率",而ADC读数是电压,两者之间必须建立映射关系。这一步很多人直接移植网上的公式就完事,但不同土壤、不同探头,实际特性曲线差很多。正确的做法是烘干称重标定法,过程不难但需要耐心。
最简标定流程分四步:
- 取目标土壤(比如你实际要种的营养土),过2mm筛去掉石子杂物,分成五份,分别加入不同重量的水,配成大致5%、15%、25%、35%、45%含水率的土样,装入密封袋闷放24小时,让水分分布均匀。
- 每种土样取一部分压实到花盆或塑料杯里,插入探头,待读数稳定后记录ADC值或者换算后的电压值。每次插入深度和压实程度保持一致。
- 同时从每种土样中取约100克土,放入105度烘箱烘8小时以上,称干重。用公式算真实含水率:含水率% = (湿重 - 干重) / 干重 × 100。
- 把电压和真实含水率一一对应记录下来。
这个办法的优点是每个数据都能追溯,写论文或做报告时有据可查,不是拍脑袋编的。
4.2 标定曲线的拟合与嵌入程序
测完五组数据,接下来就是拟合。土壤含水率和探头输出电压的关系不是纯线性的,但在一段范围内用分段线性足够。我实际测到的一组数据如下:
| 真实含水率(烘干法) | 探头输出电压(V) |
|---|---|
| 3.2% | 1.86 |
| 12.8% | 1.49 |
| 23.5% | 1.12 |
| 34.1% | 0.78 |
| 45.6% | 0.52 |
可以看到电压随含水率升高而下降,基本接近线性。把程序里的calibration_map写成分段查表加线性插值:
float calibration_map(float voltage) { // 表项: {电压, 含水率} const float table[5][2] = { {1.86f, 3.2f}, {1.49f, 12.8f}, {1.12f, 23.5f}, {0.78f, 34.1f}, {0.52f, 45.6f} }; if (voltage >= table[0][0]) return table[0][1]; if (voltage <= table[4][0]) return table[4][1]; for (uint8_t i = 0; i < 4; i++) { if (voltage <= table[i][0] && voltage >= table[i + 1][0]) { float ratio = (table[i][0] - voltage) / (table[i][0] - table[i + 1][0]); return table[i][1] + ratio * (table[i + 1][1] - table[i][1]); } } return 0.0f; }这样在区间内是线性插值,超出边界就饱和,显示器不会出现负数。如果你换了一种探头或者换了土,只需要更新这张表,程序其他部分不用动,这个设计值得保留。
4.3 实测数据与误差来源分析
标定完成后我做了对比验证,发现几个不可避免的误差来源。
第一个是插入压实度。同一个杯子同一块土,松插和压实,电压能差15%左右。因为探头电极和土壤颗粒之间的接触紧密度直接影响等效介电常数,土壤压得越实,读数越接近真实含水率。所以每次测量尽量用同样的力度压实。
第二个是土壤紧邻探头区域的局部含水率与整体含水率可能不一致。比如表面浇了水但深层还没渗透,探头插在表层读数高,实际根系层可能很干。解决方法是探头插深一点,或者做成多层探头。
第三个是温度影响。虽然做了补偿,但补偿系数是经验值,温度剧烈变化时仍会有1%到2%的含水率偏差。对浇花而言完全够用,不需要过度纠结。
5. 连续运行一周后暴露的三个真实问题
5.1 探头极化导致读数漂移:症状、根因与修复
第一批样机拿到阳台连续运行第七天,同事反馈OLED上湿度值明显偏高,拔出探头一看,电极附近土有点发黑。我用万用表量探头输出,开路状态下电压已经不对了。
根因就是直流极化。虽然是电容式探头,但长期通电,内部还是会有微弱电解反应,电极表面状态慢慢变化,输出基准电压跟着漂移。修复方法就是我前面提到过的间歇供电:从"一直通电"改成"每2秒只通电100毫秒"。改完后连续跑了三周,干燥状态下的读数漂移从原来的0.3V降到0.02V以内,几乎可以忽略。
这个教训很重要:土壤湿度探头的电源一定要做成可控的,不要为了方便直接接VCC。
5.2 同土壤、不同探头与校准批次差异
第二个有意思的问题是我做了两台一模一样的设备,插在同一盆土里,一台显示25.1%,另一台显示22.8%。硬件相同、程序相同,差别来自探头个体差异和标定误差。
电容式探头的核心是高频振荡电路,不同批次器件的电阻电容容差会导致振荡频率和输出幅值有偏差,直接套同一套标定表就会有2到3个百分点的偏差。
解决思路不是追求硬件一致性,而是给每台设备做独立标定。量大的话可以在产线上做个简化流程:准备一个标准湿度的土壤样本,把每台设备的探头插进去,测出电压后把修正偏移量存进该设备的E2PROM,出厂时参数各不相同,但显示结果一致。这种做法和工业传感器厂家出厂前逐台校准是一个道理。
个人用户做单台的话问题不大,但如果做课程设计要展示几台样机,这个细节值得提前说明。
5.3 电源纹波让ADC末位跳变:滤波和板级布线补救
最后一坑是ADC末位跳动。用锂电池供电时读数基本稳定,但用USB供电时就出现湿度值每次刷新跳一两个数。用示波器量3.3V引脚,纹波有40多毫伏。ADC参考电压如果是VCC,参考都不干净,采集结果自然跟着抖。
补救做了三件事:LDO输入输出各加一颗10uF钽电容和0.1uF陶瓷电容;ADC采样通道对地并一颗1uF电容,和前面的一阶RC滤波配合;软件上把参考电压从VCC改成内部2.5V基准。改完再测,末位跳动从±3降到±1以内。
PCB布线时也给后来者提个醒:探头模拟信号线尽量短,避开晶振和电源走线;如果不方便铺地,信号线两边放地线隔离;土壤探头出来的线如果比较长,用双绞线加屏蔽层,屏蔽层单端接地。这些招数别等到干扰了再补,画板子的时候就规划好,能省掉后面一大半调试时间。
这次做下来,最大的体会是土壤湿度检测计看着简单,真正做稳需要兼顾探头供电策略、信号调理、标定方法和抗干扰设计,缺一环都会在长期运行中暴露问题。如果让我重新设计第二版,我会加一个湿敏电容式的深层探头,用两路ADC同时采表层和根系层湿度,再配上低功耗休眠模式,充电一次用一个月。不过那是后话了,这套方案跑通之后,先给阳台西红柿用上,浇水踏实多了。
本文还有配套的精品资源,点击获取