ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于STC51单片机的自动灌溉系统设计:从土壤湿度传感器到继电器控制

基于STC51单片机的自动灌溉系统设计:从土壤湿度传感器到继电器控制 简介基于STC51单片机的自动灌溉系统设计文档面向电子工程、嵌入式系统及农业自动化领域的初学者和开发者可有效解决传统人工灌溉效率低、水资源浪费严重的问题。文档以STC12C5A60S2单片机为核心完整呈现从硬件电路到软件程序的系统设计思路。资源包含一个docx文档大小约十二KB内容精炼无冗余适合作为课程设计、毕业设计或项目立项参考。目前已有九十八人学习下载被不少学习者用于方案比对与设计入门。文档详细介绍了系统整体设计框架、土壤湿度信息采集电路、水泵驱动电路、报警显示电路以及按键输入调节电路并给出了软件主程序流程和温湿度数据处理方法同时涵盖LCD1602液晶显示模块的应用、掉电保存功能与系统仿真说明文末所附参考文献也有助于进一步拓展研究。读者可据此快速掌握自动灌溉系统的设计要点、器件选型和编程逻辑具备较强的实操指导价值。1. 用一颗STC51做自动灌溉先想清楚它该管什么自动灌溉听起来是个物联网课题但真正在菜地、温室、盆栽里稳定跑三五年的设备很多用的还是STC51。原因很直接51内核的中断响应和IO操作足够硬一颗带ADC的STC15系列芯片批量价不到五块钱外围元件一只手数得过来坏了随手换。设计这个系统核心不是把代码写得多漂亮而是先把三件事定下来土壤湿度用什么传感器采、水泵什么时候开什么时候停、人在现场怎么调参数。本文就按这个顺序把基于STC51单片机的自动灌溉系统从控制策略到电路再到代码完整过一遍。2. 自动灌溉的控制逻辑土壤湿度怎么变成水泵开关2.1 先分清两种土壤湿度传感器电导式还是电容式市面上最常见的土壤湿度传感器有两类第一类是镀镍探针电导式两根探针插进土里靠测量土壤电阻率间接推算含水量。土壤越湿电阻越小模块输出的模拟电压越低。这类传感器便宜单探头几块钱但有两个从设计阶段就得接受的缺点探针一直通电会发生电化学极化读数慢慢漂移长期埋在潮湿土壤里镀层会被电解腐蚀几个月后标定关系就变了。第二类是电容式探头是一对电极构成的平板电容土壤作为介质含水量越高电容越大通过振荡电路或专用芯片把电容变化转成电压。电容式贵一些但不会极化、不易腐蚀读数稳定深埋寿命明显更长。选型结论在项目一开始就要定下来如果只是课程设计或短期实验电导式够用省下的钱干别的如果是打算在真实环境里连续运行一个生长季以上直接电容式。本设计后续的ADC采集、滤波和阈值标定对两种传感器都适用只差在输出范围和高低关系上。2.2 采样策略传感器不要一直通电无论哪种传感头都不建议让它在系统上电期间持续通电。电导式探针通电越久极化越严重读数会像“温水煮青蛙”一样慢慢飘电容式虽然没那么敏感但持续供电对长期埋在地下的电路也没有好处。常见做法是每隔一段时间测量一次每次测量前先给传感器单独供电等几十毫秒让输出稳定再连续采样多次做一次去极值平均然后立刻断电。下面是一段基于STC15系列内置ADC的采样代码电导式和电容式都按这个流程处理void SensorPower(unsigned char on) // 传感器电源由P3.0控制 { SEN_PWR on; DelayMs(20); // 上电稳定时间 } unsigned int ReadSingleAdc(void) { unsigned int result; ADC_CONTR | 0x08; // 启动一次ADC转换 while (!(ADC_CONTR 0x10)); // 等ADC_FLAG置位 ADC_CONTR ~0x10; // 清转换完成标志 result ADC_RES; // 高8位结果 result (result 8) | ADC_RESL; // 加上低2位拼成10位 return result; } unsigned int ReadFilteredAdc(void) { unsigned char i, j; unsigned int buf[8]; unsigned int tmp, sum 0; SensorPower(1); // 测量前先上电 for (i 0; i 8; i) { buf[i] ReadSingleAdc(); } SensorPower(0); // 采完立即断电 for (i 0; i 7; i) { // 冒泡排序准备掐头去尾 for (j 0; j 7 - i; j) { if (buf[j] buf[j 1]) { tmp buf[j]; buf[j] buf[j 1]; buf[j 1] tmp; } } } for (i 1; i 7; i) { // 去掉最大值和最小值 sum buf[i]; } return sum / 6; // 取中间6个的平均值 }逻辑说明采集前先给传感器通电20ms是为了等输出端稳定跳过最大值和最小值再平均是为了滤掉土壤颗粒接触不良造成的瞬时尖峰。SEN_PWR和ADC_CONTR等寄存器需要按具体芯片头文件来定义上面代码重点在采样策略本身。参数上DelayMs(20)对电导式是底线值碰到线缆长、容性负载大的场景可以加到50ms滤波次数取8次采样快而且排序成本低如果土壤极其不均匀可以加到16次但要注意每次测量间隔会变长。2.3 阈值要成对出现低于下限开高于上限停很多刚从51单片机入门过来的设计会写“湿度大于阈值就浇”写完后发现水泵在阈值附近反复启停。问题出在只设了一个触发点水浇下去湿度越过阈值立刻停停完水分下渗读数又掉回阈值之下又开。继电器跟着折腾触点寿命和电磁干扰都上来了。正确的做法是双阈值滞回控制也叫施密特触发逻辑。设定一个下限HUMIDITY_LOW和一个上限HUMIDITY_HIGH控制规则是当前湿度低于下限才开泵之后一直灌到高于上限才停。这两个阈值之间的区间就是滞回区专门用来吸收测量波动和水土下渗带来的反复跳变。下面是把原始ADC值换算成湿度百分比并执行滞回控制的代码// soil_adc_dry 是干土状态下标定的ADC值 // soil_adc_wet 是浸水状态下标定的ADC值 unsigned char SoilPercent(unsigned int adc) { unsigned long p; if (adc soil_adc_dry) return 0; // 比干土还干按0%处理 if (adc soil_adc_wet) return 100; // 比水里还湿按100%处理 p (unsigned long)(soil_adc_dry - adc) * 100; return (unsigned char)(p / (soil_adc_dry - soil_adc_wet)); } #define P_LOW 30 // 土壤湿度低于30%时开泵 #define P_HIGH 60 // 土壤湿度高于60%时停泵 static bit pumpOn; // 当前水泵运行状态 void UpdatePump(unsigned char humidity) { if (pumpOn) { if (humidity P_HIGH) { // 已经在浇只看是否达到上限 pumpOn 0; RELAY 0; // 关泵 } } else { if (humidity P_LOW) { // 没在浇只看是否跌破下限 pumpOn 1; RELAY 1; // 开泵 } } }逻辑说明SoilPercent先把电导式传感器的反比关系转成“越大越湿”的百分比便于人脑理解和调参。pumpOn记住了当前水泵状态注意它只在状态切换时改变而不是每次采样都用同一组条件去判断。这套逻辑最大的好处是只要P_LOW和P_HIGH拉开距离继电器动作次数就和采样频率基本无关。参数上P_LOW30、P_HIGH60适合多数叶菜和草皮花卉、多肉这类耐干怕涝的植物可以改成20和50让灌溉周期拉长。需要确认的是STC51的bit类型用起来方便但pumpOn必须初始化为0否则上电瞬间可能出现误开泵。3. 硬件电路怎么选件ADC、继电器、电源与抗干扰3.1 主控选型STC89C52要外挂ADCSTC15直接内置标题写STC51实际落地时里面有两拨选择。经典的STC89C52RC是教科书常客几乎所有课程设计和Proteus仿真例子都以它为模板但它的短板是内部不带ADC要采集土壤湿度模拟量就得外挂PCF8591或ADC0809。PCF8591是I2C接口8位ADC连线只有两根信号线网上例程也不少但I2C通信本身要写时序8位分辨率对从干透到饱和的湿度跨度来说一个ADC字大约对应0.4%的含水量勉强够用误差偏大。另一拨是STC15系列比如STC15W408AS同样是51内核指令集完全兼容但内部已经集成了10位ADC、高精度内部振荡器和内部复位电路连最小系统用的晶振和复位电容都省了。10位ADC对应0.1%级别手工标定场景下够用。这个选型差异直接决定了整个硬件系统的体积和成本。对比项STC89C52RC PCF8591STC15W408ASADC8位I2C外挂10位内置时钟外部晶振典型12MHz内置IRC几MHz到二十几MHz可调复位外部复位电路内部上电复位IO驱动标准51推挽/开漏四模式可配能直接带继电器驱动系统构成晶振复位ADC模块主控主控单芯片退耦电容结论很直接能用STC15就不用STC89C52少一个外设就少一个故障点。但如果你手头只有STC89C52或者Proteus仿真库里只有AT89C52也可以照做代码层面只需把ADC读取部分换成I2C时序。3.2 控制水泵输出ULN2003驱动继电器的参数计算水泵属于感性负载启动瞬间的电流可以达到稳态的5倍以上控制它的阀门或泵体通常用继电器隔离而不是让51单片机的IO直接驱动。STC51的IO口灌电流大约20mA而一个5V继电器线圈的吸合电流在70mA到100mA之间直接驱动肯定带不动。常见做法是加一级驱动方案有两个单个NPN三极管S8050或者ULN2003达林顿管阵列。S8050的方式很经典基极串一个1k电阻接单片机IO发射极接地集电极接继电器线圈另一端。计算例子IO输出5V三极管基射结压降0.7V基极电流约4.3mAS8050放大倍数在100左右集电极电流能做到400mA上下带一个80mA线圈绰绰有余。缺点是必须自己算电阻和接续流二极管。ULN2003方案更适合多路输出内部是7路达林顿管每路能吸500mA还内置了钳位二极管。接线是单片机IO直接进输入端输出端接继电器线圈到5V电源之间公共端COM接电源正。很多工程师习惯在继电器线圈两端再并一个1N4007方向是阴极接电源正虽然ULN2003内部已经有钳位但外部再并一颗能缩短电流泄放路径让继电器释放得更干脆。这块的坑在接地和去耦上。水泵启停瞬间几十毫安的线圈电流变化会在电源线上拉出尖峰如果单片机供电和水泵驱动共用一根长线ADC参考电压会被拽得上下抖。布置上要让大电流回路单独走线在水泵供电的根部放一个1000uF电解电容加一个104陶瓷电容单片机电源处再放一个10uF加104的组合两级去耦隔开。3.3 继电器开合之后别急着采样有一类问题排查起来很隐蔽ADC读数整体正常但只要继电器一动作湿度值就跳几十个数。这不是传感器坏了而是继电器线圈吸合、断开时产生的电磁干扰通过电源或空间耦合进了模拟采样通道。解决手段分软件和硬件两层。硬件上传感器线用屏蔽线屏蔽层单端接地继电器尽量远离单片机和ADC引脚如果水泵功率再大一些用光耦把单片机电源和继电器电源作物理隔离。软件上规定一个“动作禁采区”继电器每一次改变状态之后至少等300ms再开始采样让电源和探头信号都稳定下来。下面是配合延时禁采的控制代码片段void SetRelay(unsigned char on) { static unsigned char lastState 0; if (on ! lastState) { RELAY on; lastState on; DelayMs(300); // 继电器动作后等待电源稳定 } }逻辑说明这个函数把继电器状态缓存下来只有状态真正变化时才执行输出并延时避免了每次采样都重复等待300ms。DelayMs(300)的时间参数要结合电源设计来定5V小继电器通常100ms就够带光耦隔离时150ms如果水泵是220V交流接触器等待时间建议放到500ms以上。注意这个延时只发生在状态切换那一刻不会拖慢正常的采样周期。4. 程序设计定时时基、按键调参、显示与主循环4.1 主循环按状态机组织不要用死循环裸奔灌溉系统的程序规模不大但涉及采样、比较、显示、按键四件事如果全堆在一个while(1)里任何一个阻塞都会让其他任务卡住。常见做法是拆成“时间片”用51定时器产生一个稳定的1秒时基主循环按节拍执行不同任务采样和比较放在一个时间片显示放在另一个时间片按键扫描用中断或短轮询。定时器初值计算这里有个STC51和STC89C52的差别。STC89C52是12T模式12MHz晶振下每条机器周期1us定时器方式1是16位10ms定时初值用65536减去10000STC15系列是1T模式相同12MHz下机器周期已经是83ns想定10ms就得把计数初值再除以12。烧录器选择工作频率后这个值必须对应上网上很多移植代码跑偏就是栽在这。下面是一段通用的定时器0初值计算模板// 以12MHz、STC89C52的12T模式为例单次定时10ms #define TIMER0_RELOAD (65536 - 10000) void Timer0Init(void) { TMOD 0xF0; // 只改低4位保留定时器1配置 TMOD | 0x01; // 定时器0方式116位手动重装 TH0 TIMER0_RELOAD 8; TL0 TIMER0_RELOAD 0xFF; ET0 1; // 开定时器0中断 TR0 1; // 启动定时器0 EA 1; // 开总中断 } unsigned int tick10ms; void Timer0_ISR(void) interrupt 1 { TH0 TIMER0_RELOAD 8; // 手动重装载初值 TL0 TIMER0_RELOAD 0xFF; tick10ms; }逻辑说明定时器中断里把TH0/TL0重新赋初值同时累加一个tick10ms变量主循环通过检查这个变量来判断“是不是该干活了”。中断函数要短只做计时不处理业务否则长任务会压掉下一次中断时间基准就乱了。参数上(65536 - 10000)在12MHz的12T模式下正好10ms如果换成6MHz晶振需要改成(65536 - 5000)STC15的1T模式还要把10000换成对应计数数量。主循环框架就干净了void main(void) { unsigned int adcValue; unsigned char humidity; unsigned int lastSampleTime 0; SystemInit(); while (1) { if ((unsigned int)(tick10ms - lastSampleTime) 30) { lastSampleTime tick10ms; // 每300ms采样一次 adcValue ReadFilteredAdc(); humidity SoilPercent(adcValue); UpdatePump(humidity); } KeyScan(); // 按键短轮询不阻塞 DisplayUpdate(); // 刷新显示屏 } }逻辑说明tick10ms和lastSampleTime都是无符号整数定时器只管累加主循环用差值判断是否过了300ms这种方式即使tick10ms溢出回绕也不会出错。采样、控制、按键、显示各自独立任何一个函数写得再烂也不会导致水泵失控顶多是界面不刷新。4.2 按键调参长按进设置短按跳阈值单击加一档自动灌溉系统不能只在烧录时把阈值写死人站在菜地边需要现场调整“干到什么程度开始浇”。两个按键加一个显示屏就能解决一个“设置”键一个“调节”键。长按设置键1秒进入参数设置短按设置键在P_LOW和P_HIGH之间切换调节键每次按一下数值加5%按满100%回绕到0。设置完成后不按键5秒自动退出回到正常运行。按键扫描要放在很短的时间片里消抖通过计数实现不做DelayMs阻塞式消抖unsigned char KeyScan(void) { static unsigned int pressCount 0; unsigned char ret KEY_NONE; if (KEY_SET_PIN 0) { // 设置键按下 pressCount; if (pressCount 80) { // 超过800ms判定长按 pressCount 0; ret KEY_LONG_SET; } } else { pressCount 0; } if (KEY_ADJ_PIN 0) { DelayMs(15); // 简单消抖 if (KEY_ADJ_PIN 0) { while (KEY_ADJ_PIN 0); // 等待释放防连击 ret KEY_ADJ_STEP; } } return ret; }逻辑说明设置键用计数方式实现长按识别每10ms扫描一次累计80次就是800ms数值可以根据手感调整。调节键用延时消抖加“等待释放”的方式确保一次按压只产生一次步进这对于把5%一档的调节做准很重要。参数上80次对应800ms是比较常见的值想快速进入设置就降到40次但要防止误触。4.3 显示内容怎么排把状态和数值放同一屏用1602字符屏或OLED都可以信息组织原则是一行放实时值一行放动作意图。比如第一行显示“Soil: 45% Pump: ON”第二行在设置模式下显示“L:30 H:60”。这样在PID或者调试的时候人一眼就能看出当前湿度和水泵状态是否匹配省去拿万用表量的功夫。有两点经验值得记录。第一数字转换成字符串时多位数要补零或右对齐否则刷新时屏幕会残留上一帧的尾巴简单办法是每次用空格填充整个显示字段再写数字。第二湿度百分比是0到100的整数完全没必要用浮点数一个unsigned char走天下省下的代码空间留给后续加定时灌溉功能。5. 调试收尾的三个关键细节标定、防抖、降功耗5.1 两点标定干土和水中各记一个ADC值系统装好后先别急着Set阈值。把传感器插在干燥土壤里记录SoilPercent换算前的原始ADC值记为soil_adc_dry再把探头完全浸入清水中静置两分钟记录soil_adc_wet。有了这两个值湿度百分比才是线性的。缺水条件下的控制逻辑依赖这个线性关系标定不准后面所有百分比参数都是自欺欺人。标定公式本身在前面SoilPercent函数里已经写好现场只需要改两个常量。注意电导式传感器探头要完全擦净再换介质不要在土里沾了泥直接插水里那样标出来的上下限只和泥浆有关。5.2 软件再锁一道最小启停间隔滞回控制解决了正常波动下的频繁启停但极端情况仍然存在土壤很干时开泵水刚触到传感器周边湿度瞬间冲高读数越过上限停泵水分还没扩散开传感器周围又干了于是再开。这个循环周期可能只有几秒继电器寿命撑不过一个星期。加一个“最小运行时间和最小停止时间”锁用状态记录上一次动作时刻在时限内不响应新的开/停请求#define MIN_RUN_TIME 30 // 最少运行30秒 #define MIN_STOP_TIME 60 // 最少停止60秒 void UpdatePumpLocked(unsigned char humidity) { static unsigned int runSec 0; static unsigned int stopSec 0; if (pumpOn) { runSec; stopSec 0; if (humidity P_HIGH runSec MIN_RUN_TIME) { pumpOn 0; RELAY 0; } } else { stopSec; runSec 0; if (humidity P_LOW stopSec MIN_STOP_TIME) { pumpOn 1; RELAY 1; } } }逻辑说明pumpOn为真时即使湿度已经越过上限也要等MIN_RUN_TIME秒后才实际关泵同理关闭后至少要等MIN_STOP_TIME秒才能再次开启。两个参数按实际灌溉场景调整滴灌系统水流慢最小运行时间可以放到60秒以上喷雾系统响应快30秒就够。这层锁和滞回控制不冲突一个管阈值、一个管时间两者叠加才是完整保护。5.3 低功耗方向掉电定时唤醒采样如果系统用电池供电比如野外长期监测试验可以把“定时采样”改成“睡眠为主、唤醒采样、采完再睡”。STC15的掉电模式静态电流只有微安级配合内部掉电唤醒定时器能实现真正的低功耗运行。思路是进入掉电模式前设置唤醒定时器唤醒后立即采集一次湿度和控制水泵处理完再睡。这个过程循环平均电流可以压到毫安以下一节干电池撑几个月没问题。需要注意掉电模式下IO状态会保持不变继电器状态不能依赖RAM里的pumpOn变量要在唤醒后重新读取当前输出状态再做恢复。这块做细了系统才算从“课设能演示”变成“地里能用”。本文还有配套的精品资源点击获取
返回列表