自己做农业物联网相关项目有一阵子了,手头这个"基于STM32单片机的大棚温湿度环境参数监测无线蓝牙APP控制方案"是帮一个朋友园区改造时沉淀下来的完整思路。标题看着长,实际拆开就是三件事:传感器采集环境数据、单片机做逻辑处理、蓝牙和APP负责无线交互与控制。这套东西做完后,园区里的大棚湿度超标或者温度过高,人不用再跑现场,手机上一看就知道该不该掀膜、该不该启动风机。
先说说这套方案适合谁。如果你是要做毕业设计的学生、想搞农业智能化的嵌入式开发者、或者只是想在老家大棚里低成本实现远程监测的实操派,都可以直接参考。硬件成本控制在百元级别,全程不需要接网线和路由器,供电就靠一块电源适配器或者电池组,部署起来比较灵活。文章后面我会把原理图设计思路、每个模块为什么这么选、单片机程序怎么一个个功能拆开写、以及APP调通的完整过程都捋一遍,踩过的坑也会列出来,方便你少走弯路。
1. 整体方案设计与选型思路
1.1 需求拆解:这套系统到底解决什么问题
农业大棚最大的痛点就是环境不可控。温度高了薄膜内壁全是水珠,作物容易烂叶;湿度太大又容易爆发灰霉病;但是单纯开风口降温,又可能让温度波动太大影响生长。人工管理靠经验,看天吃饭,稍微一疏忽就是成片的损失。
基于这个背景,系统需要四个核心能力:第一,实时采集大棚内的空气温湿度和土壤湿度;第二,把数据显示在本地屏幕上,方便现场巡视的人一眼看到;第三,通过蓝牙把这些数据同步到手机APP上,人不在棚里也能看;第四,APP上可以远程控制风机、水泵、补光灯这些执行设备,不用跑回配电箱手动操作。
既然是蓝牙方案而不是WiFi方案,就要接受一个客观限制:蓝牙的有效通信距离一般在10米到20米之间,隔着一栋房子就没信号了。所以这套系统适合大棚管理房或者值班室离棚区不远、巡检半径不大的场景。对于这个前提,蓝牙的价值就很突出——功耗低、配对快、不需要网络基础设施、手机自带蓝牙功能,基本上零学习成本。
1.2 系统拓扑与数据流向设计
整个系统可以分成三段链路。第一段是感知层,温湿度传感器挂在棚内离地面1.2米左右的位置,这个高度基本是作物冠层所在区域,数据最有代表性。土壤湿度传感器插在滴灌区边缘的根系密集区,注意不要贴着滴灌头,不然测出来永远都是过饱和状态。
第二段是控制层,STM32单片机作为核心,它干的事情是定期轮询传感器、处理数据、刷新OLED屏幕,同时管理蓝牙模块的收发状态。单片机是这套系统的"调度室",所有外部设备的动作都必须经过它来决定。
第三段是交互层,手机APP通过蓝牙模块和单片机通信。我把通信内容拆成两类:一类是单向上报的环境数据帧,以字符串形式发给APP;另一类是双向的命令帧——APP下发控制指令给单片机,单片机执行完再把设备状态回传确认。
数据流的方向很明确:传感器 -> STM32 -> 本地屏幕/蓝牙 -> 手机APP;手机APP -> 蓝牙 -> STM32 -> 继电器 -> 执行设备。这个闭环一旦建立,人在办公室喝着茶就能掌握大棚里的一切。
注意:设计通信协议时,不管是上行数据还是下行指令,都要带帧头和校验位。后面程序部分我会用具体的例子说明为什么必须这么做,做嵌入式通信最忌讳的就是裸发裸收。
2. 硬件电路设计的核心要点
2.1 主控芯片选型:为什么是STM32而不是51单片机
标题里直接点名STM32,那就要说说为什么它不是51单片机。虽然51单片机做LED流水灯够用,但做带通信协议、多传感器管理、实时控制的系统时,51的短板很明显——没有硬件I2C和SPI,串口只有一个,中断资源紧张,主频也低。
我选的是STM32F103C8T6,也就是大家俗称的"蓝板"。这芯片最大优势是性价比和生态。它的资源对于一个农业监测系统来说,属于"算力富裕"的状态:64引脚封装里有足够多的GPIO、3个串口、2个I2C、2个SPI、还有多个定时器。最关键是网上资料极多,哪怕遇到疑难杂症,基本都能找到前人填过的坑。
如果开发者预算实在紧张,可以考虑用STM32F030或者国产替代芯片,但我的建议是别为了省那一两块钱折腾自己。F103的库函数和HAL库资料是最全的,遇到问题排查起来效率高好几倍。评估一个项目的整体成本,不能只算物料,也要把调试时间算进去。
2.2 传感器选型与接线细节:DHT11还是SHT30,需要认真对比
大棚环境监测最基础的两个参数就是温度和湿度。市面上常用的传感器有DHT11、DHT22、SHT30这三种。
| 传感器 | 温度精度 | 湿度精度 | 通信方式 | 单价区间 | 实测评价 |
|---|---|---|---|---|---|
| DHT11 | ±2℃ | ±5%RH | 单总线 | 2-4元 | 便宜但湿度误差偏大 |
| DHT22 | ±0.5℃ | ±2%RH | 单总线 | 8-12元 | 性价比高,推荐首选 |
| SHT30 | ±0.3℃ | ±2%RH | I2C | 10-15元 | 精度高,但需要库支持 |
这里有个很多新手会忽视的点:DHT11的湿度精度是±5%RH,大棚里如果湿度已经达到80%RH,误差跨度可以到75%和85%的区别。这个误差在判断是否触发除湿逻辑时,会导致系统频繁误动作。所以我强烈建议至少用DHT22起步,不差钱直接上SHT30。
DHT22的接线非常简单,三根线:VCC接3.3V或5V,GND接地,DATA接单片机任意一个GPIO引脚,比如PB6。注意数据线需要接一个4.7kΩ到10kΩ的上拉电阻到VCC,因为它是单总线开漏输出,没有上拉电阻直接不干活。
土壤湿度传感器我用的是电容式,型号不敏感,市面上几十块钱的都行。它输出的是模拟电压信号,范围大概在0到3.3V之间,接STM32的ADC输入引脚,比如PA1。具体数值通过ADC转换后映射到0-100%的湿度百分比,数值越大说明越湿润。
2.3 执行设备控制电路:继电器隔离与驱动设计
执行设备是220V的风机和水泵,让单片机的3.3V GPIO直接去控制220V交流电是绝对不行的。铁一般的安全红线:数字地、模拟地、强电地必须隔离。
我采用的方案是低电平触发的继电器模块,控制信号经过光耦隔离,再驱动继电器线圈。STM32的引脚输出高电平时,光耦不导通,继电器不动作;输出低电平时,光耦导通,继电器吸合。这里有个容易理解错的地方:很多人看继电器模块的"低电平触发"字样就以为要一直给低电平才能保持吸合,实际上模块上的跳线帽选择HIGH模式还是LOW模式,决定的是信号极性而已。
继电器选型要看负载功率。一个1.5kW的轴流风机,额定电流接近7A,至少选10A规格的继电器模块才安全。水泵、风机、补光灯可以各用一个独立继电器,分别接在PC13、PC14、PC15这些引脚上,避免共用一个通道导致电流过载。
接线时还有一个小细节:继电器输出端的公共端COM接火线,常开端NO接负载。这样当继电器吸合时,负载得电工作。如果接成常闭端NC,上电瞬间设备就会误动作,这在现场很危险。
2.4 电源系统与OLED显示模块
整个系统的供电链路我设计成:12V电源适配器 -> 降压模块 -> 5V -> 板载稳压到3.3V给STM32。如果用的是普通USB供电,电流可能不够,因为继电器模块的线圈和OLED背光都要吃电流,再加上蓝牙模块,峰值电流可能到300mA左右。所以我建议用外部电源供电,而不是从开发板的USB口取电。
显示模块用的0.96寸OLED,SSD1306驱动芯片,I2C接口。OLED功耗低、响应快、在室外阳光下也看得清,比LCD1602少占4个GPIO。接线就是VCC接3.3V、GND接地、SCL接PB8、SDA接PB9。I2C地址默认0x78或者0x7A,如果屏幕不显示,第一件事就是检查I2C地址对不对。
3. 程序设计框架与核心代码实现
3.1 单片机程序的结构:用一个定时器把任务调度起来
这套程序的复杂度决定了不能把所有逻辑都塞在main函数里循环。我的设计思路是:系统上电后先初始化各模块,然后进入一个由定时器中断驱动的调度循环,每100ms处理一次事件队列。
具体拆分:系统心跳是100ms,每1秒采集一次DHT22数据,每200ms刷新一次OLED屏幕,每500ms检查一次蓝牙缓冲区是否有新数据需要解析。每个任务的执行周期不一样,这在嵌入式设计里叫“时间片轮询”。
在写代码时,我建议用STM32标准外设库或者HAL库,但不建议把整个工程搞成阻塞式。DHT22的读取时序刚好是个典型的时序敏感操作:单片机发完起始信号后,DHT22会拉低总线然后拉高,之后按高低电平宽度表示0和1。整个过程微秒级,如果用系统滴答去阻塞等待,就会拖累其他任务。所以DHT22的读取我放在了定时器回调之外,用一个专门的延时函数去处理,读取完后立刻恢复到轮询主循环。
核心代码逻辑大概这样:
void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); tick_100ms++; if (tick_100ms % 10 == 0) // 每秒 { sensor_read_flag = 1; } if (tick_100ms % 2 == 0) // 每200ms { oled_refresh_flag = 1; } if (tick_100ms % 5 == 0) // 每500ms { ble_poll_flag = 1; } } }标志位在main循环里对应的函数来处理,这样程序永远不会因为某个外设的等待时间而卡死。
3.2 DHT22传感器读取:时序要求与代码封装
DHT22的单总线协议是这套硬件里最"娇气"的部分。读取过程每一步都有严格的时间窗口:主机拉低18ms以上作为起始信号,然后释放总线;传感器回应一个80us的低电平信号,再拉高80us;之后就是40bit的数据,每一位由50us的低电平开始,后续高电平持续26-28us表示0,70us表示1。
我写了一个读取函数,核心在于微秒延时和读引脚电平:
uint8_t DHT22_ReadData(float *temp, float *humi) { uint8_t data[5] = {0, 0, 0, 0, 0}; GPIO_SetBits(GPIOB, GPIO_Pin_6); delay_ms(500); // 传感器上电稳定 GPIO_ResetBits(GPIOB, GPIO_Pin_6); delay_us(18000); // 起始信号大于18ms GPIO_SetBits(GPIOB, GPIO_Pin_6); delay_us(30); // 等待应答 if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == RESET) { while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == RESET); // 等待80us低电平结束 while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == SET); // 等待80us高电平结束 // 开始读40bit数据 for (int i = 0; i < 40; i++) { while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == RESET); delay_us(40); if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == SET) data[i / 8] = (data[i / 8] << 1) | 1; else data[i / 8] = (data[i / 8] << 1); while (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_6) == SET); } } else { return 1; // 传感器未应答 } uint8_t checksum = data[0] + data[1] + data[2] + data[3]; if (checksum != data[4]) return 2; // 校验失败 *humi = ((data[0] << 8) | data[1]) * 0.1; *temp = ((data[2] << 8) | data[3]) * 0.1; return 0; }读取时间窗口写不对,最容易出现的现象就是数据跳变或者偶发读不到数据。我的经验是:等待40us是在读到低电平结束后再等待,这个40us误差尽量控制在±5us,否则在临界点判断会把0判成1或者反过来。
3.3 蓝牙通信协议设计:串口如何传输结构化数据
蓝牙模块和单片机之间是串口通信,本质上就是一根TX和一根RX交叉接线。但通信内容要结构化,不能裸发包。我给这套系统设计了两个协议帧。
上行数据帧(单片机 -> 手机APP),采用JSON格式文本,方便APP直接解析:
{"t":25.6,"h":78.4,"s":45.2,"fan":0,"pump":1}字段含义是:温度25.6度,湿度78.4%RH,土壤湿度45.2%,风机状态关,水泵状态开。每个字段用逗号分隔,长度不超过200字节。单片机把数据拼成字符串后,通过串口发送到蓝牙模块,手机APP接收到字符串后JSON解析即可。
下行指令帧(手机APP -> 单片机),采用自定义的短指令格式:
@CMD:FAN,ON# @CMD:PUMP,OFF# @CMD:ALL,ON#帧头@,关键字CMD,命令类型是FAN或者PUMP,ON或者OFF是目标状态,帧尾#表示结束。单片机在串口接收中断里把字节拼进缓冲区,检测到#就认为一条指令完整了,然后交给解析函数处理。协议帧头和帧尾的作用就是分割指令,校验时再判断关键字段是否合理。
为什么不用JSON做下行?因为受控设备的指令格式越短,出错概率越低,解析开销也小。手机端接收JSON是为了可视化展示丰富,而单片机端解析短指令是为了减少代码复杂度。两端通信协议不一定非要统一,功能上能对上就行。
3.4 控制逻辑与阈值报警的实现
控制逻辑是这套系统的灵魂。我的设计方案里有两种模式:手动模式和自动模式,通过APP切换。
手动模式很简单,APP下发什么指令,单片机就执行什么。自动模式下,单片机会判断阈值:温度高于32℃自动开启风机,低于20℃自动停止;湿度高于85%RH自动开启除湿风机,低于70%关闭;土壤湿度低于30%时自动打开水泵滴灌,高于60%停止灌水。
这个自动逻辑不能用简单的if-else嵌套,因为大棚控制不是纯线性关系。比如夏天中午高温高湿同时存在,如果按优先级来处理,先降温还是先除湿?我的做法是设置一个状态机:每个设备有独立的使能标志位,只要温度超过高温阈值或湿度超过高湿阈值,风机就打开;水泵只由土壤湿度控制;补光灯由光照强度和时序决定,这部分可以做定时开关。
另外我加了一个"防抖"机制:设备状态切换之后,至少保持60秒不变。否则传感器的小幅波动会导致继电器频繁通断,一天下来继电器就废了,而且这种操作对电网冲击也大。实现方法就是在切换状态时记录时间戳,下次检查阈值时先看距离上次切换有没有超过60秒。
4. 手机APP端设计与无线控制
4.1 先跑通蓝牙串口调试通道再谈界面
很多新手一上来就写APP界面,结果手机连不上蓝牙模块,各种挫败。我的建议是:先下载一个蓝牙串口调试助手,比如"蓝牙调试器"这类的工具,手机和HC-05配对后,用它来发AT指令、收数据。等硬件链路完全稳定了,再动手写APP。
HC-05蓝牙模块的默认参数是:波特率9600,密码1234,模块名HC-05。第一次使用前,最好先通过AT指令配置一下。把蓝牙模块的KEY引脚拉高再接电源,然后通过USB转TTL接到电脑串口助手,发送AT+NAME=Greenhouse改名,AT+PSWD=8888改密码,AT+UART=115200,0,0把波特率改到115200。如果不改波特率,用9600和手机通信也没问题,但处理大量JSON数据时115200更快。
4.2 安卓上位机开发的框架选择
APP这部分我用的是Android Studio,原生Java写的,没有用繁琐的跨平台框架。开发蓝牙应用需要几个核心权限,Android 6.0以上需要动态申请定位权限(蓝牙经典扫描需要),Android 12以上蓝牙相关权限更细化。
蓝牙通信的核心流程分四步:蓝牙适配器初始化、搜索设备、建立socket连接、启动收发线程。搜索蓝牙设备用startDiscovery()方法,连接用BluetoothSocket的connect()方法,默认RFCOMM通道UUID是00001101-0000-1000-8000-00805F9B34FB。
我封装了一个蓝牙连接类,核心收发逻辑如下:
public class BluetoothService { private BluetoothAdapter mAdapter; private BluetoothSocket mSocket; private InputStream mInputStream; private OutputStream mOutputStream; private Handler mHandler; public void connect(BluetoothDevice device) throws IOException { mSocket = device.createRfcommSocketToServiceRecord(MY_UUID); mSocket.connect(); mInputStream = mSocket.getInputStream(); mOutputStream = mSocket.getOutputStream(); } public void sendCommand(String command) { try { mOutputStream.write(command.getBytes("UTF-8")); mOutputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } }收数据时需要注意:蓝牙串口是流式数据,数据到达是不定时的,而且可能一次达到一串。所以接收线程必须持续读InputStream,把字节累积到缓冲区,检测到换行符或者特定的EOF标记(我这里是})就截断出来,当成一帧完整数据交给JSON解析器。
4.3 APP界面布局与数据展示
APP界面我分三个区域:顶部是环境数据实时卡片,温度、湿度、土壤湿度三个数值配一个简明图表;中间是设备控制区,风机、水泵、补光灯各一个开关按钮;底部是通信状态指示灯,显示蓝牙连接状态。
界面用LinearLayout嵌套或ConstraintLayout都行,核心是数据刷新用Handler+Thread的模式,UI主线程不能直接做蓝牙读写。我开一条循环线程读串口,解析完数据后通过runOnUiThread把结果扔回UI线程更新界面。
控制按钮的事件绑定也很直接:点击按钮时把对应的指令字符串通过蓝牙发送,比如开风机就发@CMD:FAN,ON#,关水泵就发@CMD:PUMP,OFF#。发送后会收到单片机的回传确认,如果500ms内没收到,APP弹Toast提示通信失败。
注意:APP里所有通信操作必须在子线程中执行,绝对不能直接在UI线程里调
mOutputStream.write(),否则当蓝牙断开或数据量大会直接导致ANR崩溃。
4.4 蓝牙通信异常时的提示与重连策略
蓝牙设备的本质问题是连接状态不稳定,尤其大棚现场可能有金属棚架、电机干扰等,信号容易波动。我在APP里做了三层容错:第一层是通信超时重试,发指令后2秒内没收到回应,自动重发一次;第二层是断线自动重连,抛出IOException后把蓝牙状态标记为断开,弹窗提示用户检查设备;第三层是连接失败时给出诊断信息,区分是"设备未配对"还是"服务发现失败"还是"IO错误",这块调试起来很省心。
如果你用的是HC-05模块,还有一个冷知识:模块进入AT模式时是不能和手机连接通信的,所以接线和供电时一定要把KEY引脚的处理逻辑弄清楚。很多人的蓝牙模块"连不上",多半就是这个原因。
5. 原理图设计与PCB制作注意点
5.1 原理图模块划分与信号流向
原理图我画的时候分了两层结构。第一层是最小系统板,直接用现成的STM32F103C8T6核心板,省去画晶振电路、复位电路、Boot配置电路的麻烦。第二层是传感器接口、继电器驱动、蓝牙模块接口、电源接口的扩展底板。这样做的好处是核心板坏了可以单独换,扩展板可以复用。
原理图设计的重点在电源网络:12V输入经过LM2596降压模块变成5V,5V分两路,一路直接给继电器模块供电,另一路经过AMS1117-3.3变成3.3V给STM32、OLED、蓝牙模块供电。千万不要把5V直接灌进STM32的3.3V引脚,STM32不是所有引脚都容忍5V输入的,ADC引脚尤其脆弱。
5.2 PCB布局与走线的几条硬性约束
画PCB时我总结了几条必须遵守的规则。第一,电源线和地线加粗,至少1mm宽,继电器控制板的强电侧走线要保证足够电流承载。第二,蓝牙模块的天线区域下方不要铺铜,天线净空区要保持干净,否则蓝牙信号直接衰减到通信距离只有几米。第三,继电器模块和主控板之间用光耦隔离,如果光耦集成在继电器模块内部就无所谓,外接时一定加。
继电器切换感性负载时会产生反向电动势,必须在继电器线圈两端并联一个续流二极管(1N4007),否则关断瞬间的高压尖峰容易打坏光耦或者单片机引脚。这个细节在原理图上看不出来,但在实际运行中差异巨大。我有一个板子之前没加续流二极管,测试时偶尔死机,加了一个二极管后再也没有复现过。
5.3 结构设计与防潮处理
农业大棚环境最大的敌人就是潮气。裸PCB板在棚里放一个礼拜,插针和走线就开始氧化发绿。我建议把电路板装进防水接线盒,引线接口用电缆防水接头,板子和盒底之间垫一层绝缘珍珠棉。接线盒内部放一小包干燥剂,定期更换。
传感器的防水也要注意:DHT22本身不防水,裸奔测湿度时间久了会被水汽渗透,读数漂移。可以套一个透气防水膜,或者买那种带金属网罩的传感器保护壳,既能透气又不进液滴。
6. 常见问题与排查技巧实录
6.1 快速定位问题的检查顺序表
我做过的项目排障经验显示,这类系统八成的问题集中在硬件连线和通信配置上,真正程序逻辑出bug的概率反而没那么高。整理了一个排查顺序,按这个顺序查最快:
| 故障现象 | 优先检查项 | 具体检查方法 |
|---|---|---|
| OLED不显示 | I2C地址与接线 | 先用I2C扫描程序确认地址,检查SCL/SDA是否接错 |
| 传感器数据全为0 | 上拉电阻 | 用万用表量DATA线对VCC电压,应该约有3.3V |
| 手机搜不到蓝牙 | 模块电源与KEY脚 | HC-05的KEY脚不能悬空进入AT模式,电源电流需足够 |
| 蓝牙能连但收不到数据 | 波特率匹配 | 检查单片机串口波特率和蓝牙模块AT配置是否一致 |
| 继电器不动作 | 触发极性 | 确认模块跳线是LOW触发还是HIGH触发,和代码输出极性是否匹配 |
| 数据时断时续 | 电源稳定性 | 示波器看3.3V纹波,继电器吸合瞬间电压跌落是否超0.3V |
6.2 几个实践中才会明白的坑
第一个坑是HC-05蓝牙模块供电不足。它正常工作峰值电流能到50mA左右,如果直接从单片机3.3V引脚取电,在继电器吸合瞬间电流波动会导致蓝牙模块掉线。解决方式:蓝牙模块独立用AMS1117供电,甚至单独用一个小电容储能。
第二个坑是DHT22连续读取失败的问题。很多人的解决思路是重试三次再报故障,但实际原因往往是传感器上电后需要至少2秒稳定时间,而程序初始化后立即读取就会失败。我的做法是在上电后延时500ms再读第一次,之后每隔1秒读一次,连续5次失败才在屏幕上报"传感器异常"。
第三个坑是蓝牙模块和手机配对后,手机却显示"已配对但未连接"。这大概率是模块处于AT模式(KEY脚接高电平),或者串口波特率被改成115200但手机端软件还是9600。所以每次蓝牙模块出问题,先确认是不是进了AT模式,再确认波特率,基本能解决八成问题。
6.3 大数据量传输时的串口缓冲与丢帧处理
STC或者STM32的串口接收缓冲区默认不大,F103的USART1接收缓冲区是1字节硬件FIFO加上我们自己设置的软件缓冲区。如果蓝牙模块发送的JSON字符串超过缓冲区长度,数据会被截断,APP端解析自然报错。
我的做法是把接收缓冲区设成256字节,并采用环形队列。串口中断每收到一个字节就写入环形队列,主循环里定期从队列取数据拼接成完整帧。判断是否完整帧的依据就是开头是否有{结尾是否有}。如果缓冲区里出现了多个帧粘连的情况,用状态机切分而不是用简单的indexOf,这样可以避免半包错位的问题。
uint8_t ble_buffer[256]; uint16_t ble_index = 0; uint8_t frame_ready = 0; void USART1_IRQHandler(void) { if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uint8_t ch = USART_ReceiveData(USART1); if (ch == '@') ble_index = 0; // 新帧开始 if (ble_index < sizeof(ble_buffer)) ble_buffer[ble_index++] = ch; if (ch == '#') { ble_buffer[ble_index] = '\0'; frame_ready = 1; ble_index = 0; } } }用@作为帧头#作为帧尾,任何情况下接收状态都能快速复位,不会出现因为历史残留数据导致的解析错乱。
7. 现场部署与实测数据评估
7.1 传感器安装位置与数据可靠性
现场实测时,我特意对比了不同安装位置的温差:把传感器挂在薄膜下沿和挂在作物冠层上方,读出的温度差了接近6度。这是因为薄膜附近聚集了大量热气,传感器如果离膜太近,测出的温度对作物管理没有参考价值。正确的位置应该是在作物冠层或者吊挂在顶棚1/3处,同时避开风机直吹的区域。
土壤湿度传感器也有讲究,插太浅测的是表面干湿状态,插太深测的是底层水分。实践中最合理的是插在10-15cm深度,这也是多数作物根系密集区。另外要横插而不是竖插,因为横插可以获得整个土层横截面的平均湿润度。
7.2 连续运行7天的数据稳定性
我在某园区一个黄瓜大棚连续跑了7天测试,数据整体表现稳定。白天温度在28-35℃之间波动,凌晨湿度可以升到90%以上,这些极端值都能被系统准确捕捉并同步到APP。继电器自动控制风机水泵时,OLED屏幕和手机端显示状态完全同步,没有出现一次状态错乱。
蓝牙通信的稳定性也经受住了考验。测试的距离在10米左右,隔一层塑料膜,数据基本无损;隔一堵砖墙时信号骤降,但还在通信范围内。如果实际部署时管理房距离太远,可以把蓝牙模块换成带外置天线的版本,或者改用BLE低功耗蓝牙来延长通信距离。
7.3 APP远程控制延迟实测
实测了APP下发指令到继电器动作的端到端延迟,从手机点击按钮到听到继电器"哒"声吸合,大概在170毫秒左右。这个延迟对于手动控制完全可以接受,因为人在APP上操作本身就是有意识的慢动作。控制回传的确认信息也在300ms内就能显示到界面上。
不过还是要说清楚,蓝牙方案天然不适合超远距离控制,如果你需要人在几公里外远程监控,那正确的技术路线是用WiFi模块接路由器,或者直接用4G DTU。这套蓝牙系统适合的是"人在园区附近,但不想走路去大棚"的场景,定位要清晰。
最后再聊两句
我个人做这套系统最大的感受是:STM32单片机的资源对于这种程度的项目是绰绰有余的,真正决定项目成败的不是芯片性能,而是传感器数据可靠性和通信链路的稳定性。一个数据可信的监测系统,比一个界面花哨但数据漂移的系统有价值得多。如果你后续想扩展,可以在相同硬件基础上加CO2传感器、光照传感器,或者把蓝牙换成WiFi模块接入云端,底层的I2C和串口驱动代码基本可以复用。这套方案做成型的最终效果,现场用下来反馈不错,希望你也顺利调通。