ESP32+STM32双MCU水培营养液自动控制系统设计

发布时间:2026/9/1 17:55:03
ESP32+STM32双MCU水培营养液自动控制系统设计 简介本资源是一套基于水培场景的智能农业系统完整嵌入式开发项目面向物联网、嵌入式系统与智慧农业方向的初学者及进阶开发者解决水培环境多参数协同监控与闭环控制的技术落地问题。项目采用ESP32与STM32双MCU协同架构ESP32负责Wi-Fi联网、远程指令接收与数据上报STM32F1系列承担高实时性传感器采集温湿度、光照、水位、EC、pH及执行器驱动水泵、补光灯、风扇等软硬件协同实现营养液状态精准调控。压缩包共265个文件含8.68MB核心代码资产涵盖68个.h头文件定义传感器接口与通信协议、28个.c源文件HAL驱动、主控逻辑、PID调节模块、30个.o编译中间文件及多个.mk、json、readme等构建与配置文件预览可见stm32f1xx_hal_adc/tim/uart等底层驱动及MainApp.c主应用逻辑体现典型嵌入式分层设计思想。已有71人学习下载提供可直接编译运行的工程框架、多传感器融合采集范例、双MCU串口通信协议模板及完整目录结构是理解智能农业系统软硬协同开发路径的优质实践样本。 当你把第一茬生菜苗从育苗海绵挪进水培定植篮大概不会想到真正难的不是让根泡上营养液而是让营养液里的电导率、pH值、水位和温度一直待在植物舒服的区间。我做的这套系统核心就是基于ESP32和STM32两颗微控制器的双架构分工把温湿度传感器、光照传感器、水位传感器、电导率传感器、pH值传感器的数据全部采集上来再驱动水泵、补光灯、加液泵去闭环调控。这套东西解决的不是“能不能远程看一眼数据”的演示问题而是“离开人工干预之后系统能不能自己在营养液状态跑偏时把它拉回来”的落地问题。它比较适合两类人一类是已经玩过单片机想把水培自动化从实验台搬到阳台或小温室的人另一类是嵌入式方向想练双MCU协作、UART协议、传感器信号调理这条完整链路的人。下面我把整套系统的架构决策、传感器选型、控制策略和排查记录都摊开讲尽量说清楚每个设计背后的原因而不是只丢一个能跑的表面方案。1. 项目全景这套水培系统的控制目标与整体架构1.1 水培环境里要盯住的五个变量先说养殖对象。水培和土培最大的区别是植物根系直接泡在营养液里根区的肥力、酸碱度、溶氧和温度变化非常快而且没有土壤这个缓冲器。所以这套系统最核心的任务就是让营养液始终维持在植物能接受的窄区间内。我给自己定的默认参数是这样一组经验值适合生菜、油麦菜这类叶菜参数目标区间常见问题叶菜根区温度20~28℃过低停止吸收过高烂根空气湿度60%~80%RH影响蒸腾和钙吸收光照强度8000~12000lux弱光徒长强光烧叶营养液EC值1.2~2.0 mS/cm过高烧根过低缺素营养液pH值5.5~6.5偏离导致元素沉淀营养液水位根系浸入3~5cm过低根干过高根缺氧这里最容易被新手忽略的是EC和pH不是孤立调参它们会互相影响。比如pH偏高时铁、锰等微量元素会以不可溶形态沉淀出来你测EC可能还是正常的但植物已经缺铁黄叶了。所以系统不能只做“超限报警”要把几路数据做成联动。温度和湿度虽然看着基础但对植物蒸腾拉力影响很大蒸腾又影响根系对钙、硼等中微量元素的吸收。如果只关注EC和pH往往会出现莫名其妙的生理病害。1.2 ESP32和STM32双微控制器架构的分工逻辑这套系统最关键的决策是没用一块高性能板子把所有活干完而是让ESP32和STM32各管一摊。原因很直接ESP32强在WiFi、蓝牙网络能力但它的ADC线性度和实时性并不理想STM32强在定时器、ADC、PWM这些实时控制外设但网络接入要外挂模块麻烦且稳定性不如ESP32原生WiFi。所以我的分工是STM32负责传感器采集、滤波、执行机构驱动、控制算法、看门狗保护纯粹干实时的脏活累活。ESP32负责WiFi连接、MQTT上报、本地配置页面、OTA升级、异常告警处理的是非实时网络业务。两颗MCU之间通过UART串口通信STM32以固定频率发送采集数据ESP32只做解析和转发不参与闭环控制判断。这样即使ESP32死机重启、WiFi断线STM32依然能独立维持营养液环境不会出现“网络一断水泵就停”的翻车现场。这是智能农业这类系统最重要的可靠性兜底。1.3 硬件拓扑和供电设计硬件上我用的是一块STM32F103C8T6核心板加一块ESP32-WROOM-32开发板。常见配置如下模块型号/方式接口温湿度DHT22/AM2301单总线GPIO光照BH1750I2C水位双浮球开关GPIO数字输入EC传感器自制石墨电极AC激励板模拟电压pH传感器pH电极变送板模拟电压营养液温度NTC 10K热敏电阻模拟电压执行机构水泵、补光灯、气泵、加液泵继电器/MOS通信UART2互连115200bps供电是24V开关电源进系统经LM2596降压到12V给水泵、气泵、LED补光灯再经AMS1117降到5V给传感器最后降到3.3V给两颗MCU。这里要注意的是水培环境湿度大电源模块和接线端子要做三防漆或者隔离罩不然腐蚀是迟早的事。2. 传感器链路从模拟信号到营养液状态2.1 六路传感器选型与接线传感器是这套系统的眼睛选型上不能只看单颗价格要考虑长期在潮湿环境下的稳定性。温湿度传感器用DHT22而不是DHT11因为DHT11的湿度精度是±5%对判断空气湿度变化不够用DHT22能到±2%成本只贵几块钱。单总线协议简单STM32任何GPIO都能读缺点是读一次至少等2毫秒而且对时序要求严格建议用定时器辅助延时不要用普通的delay循环。光照传感器用BH1750I2C接口直接输出lux值精度和线性度都不错。这个传感器的坑在于地址它ADDR引脚拉低时是0x23拉高是0x5C如果你板上同时挂了OLED等I2C设备要注意地址冲突。实测中I2C线长超过20cm时速率要从400k降到100k否则容易丢数据。水位传感器最开始我想用超声波模块测距离但在潮湿营养液上方超声波探头的镜面会凝结水雾读数经常莫名跳变。后来改成了两个浮球开关一个装在正常水位下限一个装在危险低水位简单可靠成本也低。EC传感器是整套系统里最不好搞的部分。市面成品EC模块要么贵要么需要定期换探头。我自制方案是用两片石墨电极接到一块交流激励板上通过1kHz方波激励产生交流电压再经过整流放大输出0~3V模拟量。后面会详细讲为什么要用交流激励这里先记住结论直流激励会使电极极化导致数据越来越漂。pH传感器直接买成品电极和变送板输出模拟电压范围大致是0~3V对应0~14pH。电极有个特点长期不用或反复干燥后液接界会失效所以不用的时候要泡在3M KCl保存液里不能泡在去离子水里。2.2 模拟量采集STM32 ADC多通道循环采样DMASTM32F103内部的ADC是12位单次转换不难难的是多路模拟量要采集得快、采得稳不能让主循环阻塞在等待转换上。我采用的是ADC1多通道循环采样加DMA搬运一轮扫描把pH、EC、营养液温度三个通道都采完DMA自动把数据存到数组里全程不占CPU。以HAL库为例初始化大概是这样ADC_ChannelConfTypeDef sConfig {0}; // 假设已通过 MX_ADC1_Init 配置好校准和扫描模式 sConfig.Channel ADC_CHANNEL_0; // PA0: pH传感器 sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_55CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); sConfig.Channel ADC_CHANNEL_1; // PA1: EC传感器 sConfig.Rank ADC_REGULAR_RANK_2; sConfig.SamplingTime ADC_SAMPLETIME_55CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); sConfig.Channel ADC_CHANNEL_2; // PA2: NTC温度 sConfig.Rank ADC_REGULAR_RANK_3; sConfig.SamplingTime ADC_SAMPLETIME_55CYCLES_5; HAL_ADC_ConfigChannel(hadc1, sConfig); HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_buf, 3 * ADC_SAMPLE_COUNT);这里有个关键参数采样时间。营养液里传感器信号变化很慢但如果采样时间太短会使ADC输入电容充电不足读数会明显偏低。我跑下来用55.5周期的采样时间比较稳转换结果跳动幅度控制在±2个LSB以内。DMA每次传完一轮会触发转换完成中断在中断里把数据存到全局结构体。要注意的是DMA缓冲数组的位数必须是32位对齐因为STM32F103的ADC是12位但DMA搬运时会按半字或字节处理。2.3 数据清洗与滑动滤波采集到的ADC原始值不能直接用传感器信号在潮湿环境下多少有噪声。我的处理分两步。第一步是剔除粗大误差。DMA缓冲区里每个通道采集了比如64个样本我先去掉最大最小值避免继电器动作、水泵启动瞬间造成的偶发尖刺。第二步是滑动平均滤波。连续采集5轮有效数据每轮间隔500ms对这5个值做指数加权平均。这样既不会把信号变得太迟钝也能压掉周期性干扰。滤波完还需要做传感器标定。ADC原始值到实际工程量的换算不能靠固定公式因为pH电极的零点和斜率会漂移EC电极的极片常数也不是完全一致。pH校准我采用标准的两点标定法把电极泡在pH 6.86标准液里读取ADC值记为p1。把电极泡在pH 4.01标准液里读取ADC值记为p2。斜率slope (4.01 - 6.86) / (p2 - p1)零点offset 6.86 - slope * p1。之后任意时刻pH slope * adc_value offset再把标定结果存进STM32的Flash掉电不丢。EC的标定也一样用已知电导率的标准液比如1413uS/cm反推极片常数。温度补偿则是把当前水温带到公式里把实际EC折算到25℃下的等效值[ EC_{25} EC_t \times (1 0.02 \times (t - 25)) ]营养液温度每变化1℃EC读值大约变化2%如果不做补偿春天晚上和中午的EC能差0.3足够让控制逻辑误动作。3. 控制执行营养液维护的闭环逻辑3.1 执行机构与驱动电路系统里执行机构主要是泵和灯营养液循环泵、增氧气泵、浓缩营养液补充泵、酸泵、碱泵、LED补光灯。它们大部分是12V或者220V负载MCU引脚不能直接驱动中间必须有继电器或MOS。我用了两种驱动方式水泵和气泵这种不需要频繁通断的用5V继电器模块而补光灯需要无级调光用PWM控制MOS管驱动。STM32的定时器输出PWM给MOS栅极通过调整占空比控制光照强度实现日出日落的平滑渐变比直接用继电器开关对植物刺激小得多。加液泵是小型蠕动泵流量很小每秒只有几毫升适合缓慢调节EC。蠕动泵的好处是只接触泵管不会污染浓缩液而且可以较精确计量流量。坏处是连续运行会磨蚀泵管所以要限制单次最长运行时间防止卡死烧毁。3.2 EC和pH的联动控制策略控制逻辑不是简单的“低于下限就开泵到了就停”那样执行机构会频繁启停而且EC、pH系统有滞后性加进去的营养液要循环一会儿才能被传感器感知到。我采用的是带死区的分区控制。EC控制当EC 下限 - 死区时开启浓缩营养液泵每次运行3秒钟然后停止循环泵等待3分钟再测量。当EC 上限 死区时打开纯净水补液阀同样每次补充少量。当EC落在死区内不做任何动作。这里死区取0.1 mS/cm。如果死区太小传感器噪声容易导致频繁启停太大EC会明显偏离目标。pH控制更要注意因为酸碱的加入对pH影响非线性而且pH电极响应本来就有延迟当pH 6.5时开启酸泵磷酸每次1秒然后停止加液循环5分钟后重新测量。当pH 5.5时开启碱泵氢氧化钾溶液每次1秒。酸碱泵严禁同时打开这是控制逻辑里的互斥条件。为什么每次只加1秒因为蠕动泵虽然流量小但1秒也能加入约3~5毫升浓缩酸液。营养液箱总共才20升你看着pH只高了0.1实际上加酸后可能需要几分钟才完全混合。一次性加太多pH会直接冲过目标区间从偏碱变成偏酸植物根系更受伤。EC和pH还会互相影响。加入磷酸调节pH时磷酸本身含磷会让EC升高一点加入浓缩营养液时浓缩液通常是酸性的也会让pH下降。所以我把两个控制循环设计成有主次顺序如果EC和pH都偏离先处理pH等pH稳定半小时后再看EC。这样能避免两个泵同时动作互相干扰测量数据。3.3 安全保护机制农业系统没有人在旁边盯着最怕两件事泵空转和继电器触点粘连。营养液箱的水位如果降到危险值循环泵继续运转只会抽空气对泵本身损害很大而浓缩营养液泵如果是靠液位差自然流入一旦营养液箱水位过高还可能发生倒流。我加了两个浮球开关低水位浮球触发后无论控制逻辑怎么要求所有泵都被锁定不能打开只能发出报警。等水位恢复正常再自动解除锁定。还有一个参数是“单次最大运行时间”。比如浓缩液泵如果连续运行超过15秒说明要么电磁阀卡住要么浓缩液箱空了控制系统应当强制停止并报警。这个时间限制要写到控制循环里去不能依赖网络端因为WiFi断线时ESP32可能不在线。STM32侧我开启了独立看门狗IWDG主循环每200ms喂狗。如果程序卡死在某个死循环或者传感器读取阻塞看门狗超时后会自动复位MCU。掉电后重新上电状态机会恢复到一个安全姿态所有泵关闭补光灯关闭只保留传感器采集和通信。4. 双MCU协作STM32做实时ESP32做网络4.1 为什么不让ESP32直接干实时控制的活市面上的物联网方案很多人喜欢用ESP32一块板子把传感器、继电器、WiFi全包了因为简单代码也少。我也试过初期确实跑得起来但问题出在ESP32的固有问题第一ESP32虽然双核但WiFi协议栈会占用大量CPU和中断当网络重连、TCP重传时主任务会被明显拖慢。我曾经遇到过WiFi频谱拥堵时继电器控制任务偶尔延迟几百毫秒对水培系统来说几百毫秒延迟不算致命但对执行机构来说任何不可控抖动都是隐患。第二ESP32的ADC在电池供电场景下会遇到非线性问题而且引脚输入范围有限不能直接测0~5V模拟信号。虽然可以外搭电路但没必要STM32的12位ADC虽然也不是顶配但配合DMA更稳定。所以最终方案是STM32只管传感器和控制ESP32只管网络和展示。两者之间用UART串口通信通信数据量很小一帧几十个字节115200波特率绰绰有余。4.2 UART帧协议设计既然有两个MCU帧协议就要定得清晰。我定义了一个简单的二进制帧字段字节数说明帧头20xAA 0x55长度1数据域长度设备ID10x01 STM320x02 ESP32命令10x01上报0x02控制0x03校准数据域N具体参数CRC162CRC16-Modbus校验例如STM32上报的状态数据域可能长这样温度int16单位0.1℃湿度uint16单位0.1%RH光照uint32单位lux水位状态uint8EC值uint16单位0.001mS/cmpH值uint16单位0.01pH泵状态uint8位图CRC16校验非常有必要。我在调试时遇到过UART传输错一位导致ESP32把水位从“正常”解析成“低水位”差点把所有泵都锁死。加了CRC之后任何一帧不合法都会被丢弃并且ESP32会回传一个NACK让STM32下次重发。这里的一个经验是不要用帧尾标识比如“0x0D 0x0A”因为数据域里完全可能出现0x0D容易误判。用“帧头长度CRC”的方式解析起来更可靠。UART接收建议用中断加空闲中断接收而不是在while循环里阻塞等待。STM32HAL库里有HAL_UARTEx_ReceiveToIdle_IT可以在一帧数据接收完毕且总线空闲时触发回调。如果不用空闲中断你很难判断一帧数据什么时候结束。4.3 ESP32的WiFi上报与本地缓存ESP32端我刷的是Arduino框架主要做三件事接收UART数据、连接MQTT、把数据写到Web界面。ESP32的WiFi稳定性是个大坑。水培系统放在阳台或大棚里WiFi信号可能不稳定。ESP32一旦断开WiFi如果代码处理不当它会反复重连导致UART接收任务被卡死。我的做法是把WiFi重连逻辑放到一个独立任务里UART接收放在另一个核心上用FreeRTOS队列把数据传给网络任务。// ESP32 Arduino 伪代码双任务模型 void TaskUart(void *pvParameters) { for (;;) { if (Serial2.available()) { // 解析帧放入队列 xQueueSend(dataQueue, frame, 0); } vTaskDelay(10 / portTICK_PERIOD_MS); } } void TaskNetwork(void *pvParameters) { for (;;) { // 从队列取数据即使WiFi断开也缓存 if (xQueueReceive(dataQueue, frame, portMAX_DELAY)) { appendToLocalBuffer(frame); if (WiFi.status() WL_CONNECTED) { mqttClient.publish(hydroponic/status, payload); } } } }本地缓存很重要。我让ESP32在内存里缓存最近10分钟的数据WiFi恢复连接后一次性补报。虽然MQTT的持久会话也能做到一部分但本地缓存更可控而且Web页面可以直接从内存读最近数据不依赖网络。ESP32上SPIFFS文件系统用来存WiFi配置、校准参数和MQTT服务器地址。这样如果要改WiFi你不用重新刷固件在Web配置页面里填一下就行。SPIFFS在Arduino环境下用起来很简单我建议配置分区大小至少256KB不然固件大一点就存不下。5. 实测数据与避坑记录5.1 一周连续运行的实测数据整套系统在阳台跑了大概一周我记录了每天的EC、pH、温湿度数据。这里分享一组比较有代表性的数据时间空气温度(℃)湿度(%RH)光照(lux)EC(mS/cm)pH水位周一 08:0022.56545001.486.2正常周一 14:0028.158120001.526.3正常周二 08:0021.27038001.386.0正常周三 16:0030.55298001.616.5低液位报警周四 08:0020.87234001.556.1正常周五 12:0027.06087001.496.3正常可以看出EC并不是完全稳定在1.3~1.6之间缓慢波动这是植物在吸收水分和养分、夜间蒸发量变化共同造成的。pH则稳定在6.0~6.5之间这和我在控制逻辑里设置的死区以及酸碱泵的次第动作有关。水位低液位报警发生在周三下午是植物蒸腾量大的时候营养液消耗快。报警触发后系统自动锁泵并推送消息到手机我补了水才恢复。这种场景正是水箱液位监控的意义所在。5.2 踩坑记录电极极化、校准漂移、水位卡滞这套系统我从原型到稳定踩了不少坑挑几个影响最大的说。第一个坑是EC电极极化。最开始我采用的是直流电压激励电极简单只用给电极加一个恒定的直流电压然后测量电流再换算成电导率。结果发现一开始测量还准大约半小时后读数慢慢漂最后直接偏到离谱。原因是直流电场会让电极附近的离子重新分布形成极化等效电阻不断变化测量自然不准。后来改成1kHz交流方波激励正负极交替切换极化效应就基本消除了。如果你的EC电路感量不高可以像我一样用三极管和电容搭一个简单的方波发生器不需要用正弦波发生器只要频率稳定有效值误差可控就行。第二个坑是pH探头测量结果漂移。营养液里有大量离子、有机物长时间浸泡后pH电极的玻璃球泡会被有机物覆盖导致响应变慢、零点漂移。我一开始设定一个月校准一次实践证明太久了两周之后pH读数就明显钝化响应时间从几秒变成几十秒。后来我改成每周校准一次并且把电极放在pH 6.86标准液里存储使用前用去离子水冲洗。这个操作在系统里留了个“校准模式”通过ESP32页面上发命令进入让STM32停止执行控制只显示当前读数方便现场校准。第三个坑是浮球开关卡滞。阳台环境灰尘不大但营养液箱内壁会滋生藻类浮球和导向柱之间偶尔会卡住导致水位状态一直停在“正常”直到水泵空转才被发现。我做了两个改进一是把浮球开关装在一个独立的液位计筒里只留上下小孔让液体进入减少藻类接触二是把一个浮球换成双浮球冗余如果两个浮球状态不一致超过30秒就判断为异常并报警而不是直接信任某一个状态。第四个坑是ST-Link烧录时遇到的问题。我用的STM32F103C8T6烧录时需要把BOOT0拉高再上电才能进入ISP模式用ST-Link Utility烧录时偶尔遇到连接失败。后来发现是ST-Link的SWD接线过长影响了信号波形尤其是SWCLK线把线缩短到10cm以内并在SWDIO/SWCLK各加一个10k上拉电阻后问题就消失了。如果你也遇到类似烧录失败可以优先排查这几根线。5.3 可以继续扩展的方向这套系统目前是单箱体的水培数据也没有上真正的云虽然ESP32有MQTT但还只是本地组网。后续可以做几个方向第一多箱体组网。一个STM32节点管一个水培箱所有节点通过ESP32网关汇聚到一个MQTT Broker这样就能在一个控制面板上管理多个种植区域而且每个节点还是可以独立运行不受网关影响。第二加入营养液配方管理。不同生长阶段叶菜需要的EC和pH不同。苗期EC低一点成长期提高一点。可以在ESP32的Web页面上做配方表按生长天数自动下发新的EC/pH目标区间到STM32不用每次手动改代码。第三把植物识别和图像生长记录做进来。比如用ESP32-CAM定时拍摄植物冠层照片通过简单的颜色统计判断叶片是否发黄把图像和传感器数据一起上报这对大规模生产很有价值。不过要提醒的是项目做稳定比加功能重要。我这套系统当时同步研发的还有CO2浓度检测和营养液自动配比但后来发现越堆越复杂出问题的点也越多。最后砍掉一堆花哨功能只保留最核心的闭环控制反而运行得更顺。如果你也想复刻这套系统我的建议是先别急着把功能堆满把STM32侧的传感器采集和控制逻辑跑稳让ESP32只做转发和展示等运行一两周再逐步加自动化策略。毕竟水培这东西植物不会等你的Bug修完再长。我是从把第一版连根拔起的教训里才真正明白这个道理的。本文还有配套的精品资源点击获取