news 2026/9/20 10:30:51

温室温湿度控制:ESP32+SHT30增量式PID整定实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
温室温湿度控制:ESP32+SHT30增量式PID整定实践

简介:这份PDF文献围绕基于PID算法的温室内温湿度智能控制系统展开,面向农业物联网、智能控制与自动化方向的本科生、研究生及工程技术人员,也可作为课程设计与论文写作的参考文献。文件仅1个PDF,约1.44MB,为期刊论文全文,完整呈现系统的硬件与软件设计思路:硬件以SLZ2016-558单片机为核心控制板,配置TL-W温度传感器与TL-N湿度传感器,并给出测量范围与误差指标;软件将温度、湿度设为PID目标函数,通过编码、参数解码与误差调节完成算法实现。文中还给出温室温湿度采集对比实验,验证该系统8小时内控制精准度稳定在80%以上,最优可达90%,明显优于传统方案。目前已有331人学习,可为读者提供算法落地路径、传感器选型依据与实验对比思路,专业指导价值较强。

1. 温室温湿度控制为什么很少只用阈值开关

去年帮朋友看一个 200 平米的连栋温室,日志里那条曲线很典型:夜里温度掉到 12℃,加热管道全开,凌晨两点冲到 26℃,湿度从 90% 掉到 55%,天亮前又爬回 92%。温度在设定点上下画正弦波,执行器一天启停四百多次,电磁阀烧了一个。这不是传感器不准,是控制律的问题。阈值开关(bang-bang)只有开和关两个状态,被控量一旦进入设定点附近就反复横跳,而温室恰恰是个大惯性、纯滞后对象,等你看到温度到了再关,热量已经在管道里了。

PID 做的事就是把「开/关」换成连续的占空比或阀门开度:比例项负责响应速度,积分项负责把稳态偏差磨掉,微分项负责在看到趋势时提前收手。它适合谁?做农业物联网终端、环境监测终端、嵌入式控制板的工程师,主控常见是 STM32 或 ESP32,目标是把棚内温湿度稳在 ±1℃、±5%RH 以内。要注意的是,温室不是一个标准伺服系统,把电机调速那套 PID 参数直接搬过来,结果通常只有两种:慢得像没接上,或者震荡到执行器报警。

2. 温室被控对象的特性与 PID 形式选型

2.1 大惯性、纯滞后、增益漂移:先把对象摸清楚

温室里的温度回路可以近似成一阶惯性加纯滞后,传递函数写成 G(s) = K·e^(−τs)/(Ts+1)。这里的 K 不是常数,晴天中午开一度阀门,棚温半小时能升 4℃,阴天同样开度可能只升 1.5℃;τ(纯滞后)来自热量从管道到空气、再到作物冠层的传递时间;T(时间常数)来自空气和基质的热容。湿度回路更麻烦,喷雾或湿帘增湿是秒级响应,但相对湿度本身跟温度强相关——温度升 1℃,相对湿度大概掉 4~6 个百分点,所以温度一动,湿度设定点就被动摇了。

工程上第一件事不是写 PID,而是把执行器列清楚,明确每个执行器动一下会同时影响哪些量。下面这张表是我在项目启动阶段一定会填的:

执行器主作用量典型滞后 τ副作用建议驱动方式
加热管道 / 热风机升温60~180 s相对湿度下降时间比例继电器
湿帘 / 超声波喷雾增湿20~60 s温度下降 2~5℃继电器 + 延时保护
顶窗 / 环流风机降温降湿10~30 s温度骤降、能耗高开度 0~100%
补光灯升温即时夜间加湿分组 PWM

看清楚副作用,才能理解为什么后面要加互锁:加热和湿帘同时满负荷,等于一边烧钱一边降温。

2.2 位置式与增量式 PID 的选型依据

两种形式的离散表达式差别不大,落地表现差别很大:

位置式:u(k) = Kp·e(k) + Ki·Σe(j) + Kd·[e(k) − e(k−1)]

增量式:Δu(k) = Kp·[e(k) − e(k−1)] + Ki·e(k) + Kd·[e(k) − 2e(k−1) + e(k−2)]

位置式每次输出的是绝对量,积分项在 MCU 里累加。温室控制器常年无人值守,一旦掉电重启,累加器清零,输出直接从 80% 掉到 0%,加热阀会「啪」地全关,温度立刻掉;手动切自动时也会因为累加器与当前实际输出不一致而跳变。增量式输出的是本次要增加多少,天然带限幅、天然无扰切换,抗积分饱和也更省事。所以在这个场景里我一般直接用增量式,只在需要严格跟踪绝对开度的阀门控制里才回到位置式,并且额外做积分跟踪。

注意:增量式并非没有积分,Ki 项就是积分的增量形式;区别在于它不保存一个巨大的累加值,而是保存最近三次误差。

2.3 采样周期和控制周期怎么定

经验规则是采样周期取纯滞后的 1/10 到 1/5。温室 τ 在 30~180 s,采集可以 1 s 一次,但控制周期设 10~30 s 更合适,同时保证控制周期与采样周期是整数倍关系,否则微分项会吃到采样抖动。如果控制周期设成 0.5 s,SHT30 那点 ±0.2℃ 的噪声会被微分项放大成几十个百分点的输出抖动,继电器就开始打点了。

3. 用 ESP32 + SHT30 搭最小可跑链路

3.1 采集与执行器接线

主控用 ESP32,传感器用 SHT30(I2C 接口),执行器走继电器或固态继电器。接线如下,SHT30 的 SDA/SCL 必须加上拉,模块自带的话就不用外接:

功能ESP32 引脚说明
SHT30 SDAGPIO21I2C 数据,4.7k 上拉到 3.3V
SHT30 SCLGPIO22I2C 时钟,4.7k 上拉到 3.3V
加热继电器GPIO25高电平导通,串联 1k 基极限流
湿帘继电器GPIO26与加热互锁
顶窗开度GPIO27PWM,1 kHz
环流风机GPIO14PWM 调速

SHT30 的地址是 0x44(ADDR 接地)或 0x45(ADDR 接 VDD),扫描 I2C 时如果只看到 0x44 就说明 ADDR 悬空或接地。

3.2 SHT30 单次测量与 CRC 校验

from machine import I2C, Pin import time I2C_ADDR = 0x44 CMD_MEASURE_HIGH = b'\x2C\x06' # 高重复性,无时钟拉伸 i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=100000) def crc8(data): crc = 0xFF for byte in data: crc ^= byte for _ in range(8): crc = ((crc << 1) ^ 0x31) & 0xFF if crc & 0x80 else (crc << 1) & 0xFF return crc def read_sht30(): i2c.writeto(I2C_ADDR, CMD_MEASURE_HIGH) time.sleep_ms(20) # 高重复性转换时间约 15ms raw = i2c.readfrom(I2C_ADDR, 6) if crc8(raw[0:2]) != raw[2] or crc8(raw[3:5]) != raw[5]: return None # 校验失败丢弃本次 t_raw = (raw[0] << 8) | raw[1] h_raw = (raw[3] << 8) | raw[4] temp = -45 + 175 * t_raw / 65535.0 humi = 100 * h_raw / 65535.0 return temp, humi

命令 0x2C06 的含义是「高重复性测量 + 不启用时钟拉伸」,转换时间 15 ms,这里等 20 ms 留余量。温度换算公式来自传感器的线性化输出,原始值 0~65535 映射到 −45~125℃;湿度映射到 0~100%RH。CRC8 的多项式是 0x31、初值 0xFF,如果不做校验,I2C 受干扰时会读出 65535 这种离谱值,直接进 PID 就是一次满功率输出。

3.3 时间比例输出:让继电器也能吃连续量

继电器不能真的 PWM,但慢过程可以用时间比例:把 PID 输出的 0~100% 映射到一个 30 s 窗口内的导通秒数。

WINDOW_MS = 30000 def apply_heater(percent, last_toggle): percent = max(0.0, min(100.0, percent)) now = time.ticks_ms() on_time = int(WINDOW_MS * percent / 100) elapsed = time.ticks_diff(now, last_toggle) if elapsed >= WINDOW_MS: last_toggle = now elapsed = 0 state = 1 if elapsed < on_time else 0 Pin(25, Pin.OUT).value(state) # 加热继电器 return last_toggle

窗口越短,控制越平滑,但继电器动作越频繁。机械继电器 30 s 窗口比较折中,一天最多动作 2880 次;换成固态继电器可以压到 5 s。要注意加热和湿帘必须互锁,同一窗口内两者导通时间之和不能超过窗口长度,否则就是在对着干。

3.4 滤波:别把开窗当成噪声滤掉

一阶低通 y(k) = α·x(k) + (1−α)·y(k−1),α 取 0.2 左右,能把 SHT30 的 ±0.2℃ 抖动压到 ±0.05℃。但这里有个坑:开窗导致的真实温度骤降也会被低通拖慢,PID 会以为扰动很小而反应迟钝。我的做法是双判据——先用 5 点中值滤波剔除野值,再看变化率,如果 10 s 内温度变化超过 0.8℃ 就判定为真实工况变化,α 临时提到 0.8 快速跟随,之后回落到 0.2。

4. Kp、Ki、Kd 三个必调参数怎么整定

4.1 整定顺序:先比例,后积分,最后微分

第一步把 Ki 和 Kd 都置 0,只留 Kp。从 0.5 开始往上加,每加一档观察 3 个控制周期,直到温度出现等幅振荡,记下此时的临界增益 Ku 和振荡周期 Tu。温室加热回路 Ku 通常在 8~20 之间(输出按 0~100 计),Tu 在 300~900 s。第二步按经验公式算出初值,第三步手动微调,第四步才是小幅加 Kd。

一定要在「白天开窗、夜间闭棚」两种工况下各跑一遍,因为增益 K 差异太大,同一组参数白天合适,晚上可能就震荡。

4.2 临界比例度法初值与温室修正系数

控制器类型KpTiTd
P0.5·Ku
PI0.45·KuTu/1.2
PID0.6·KuTu/2Tu/8

由 Ti、Td 反推离散参数:Ki = Kp·T/Ti,Kd = Kp·Td/T,T 是控制周期。以 Ku=12、Tu=600 s、T=15 s 为例,PID 组算下来 Kp=7.2,Ki=7.2×15/300=0.36,Kd=7.2×75/15=36。

这套公式对纯滞后对象是偏激进的,温室 τ/T 比值大,我一般把 Ki 再乘 0.5~0.7,Kd 乘 0.4~0.6,宁慢勿震。微分项对传感器噪声极其敏感,如果 SHT30 没做滤波,Kd 直接给 36 会让输出在每个周期跳 20 个百分点以上。

4.3 增量式 PID 的 C 实现与抗积分饱和

typedef struct { float Kp, Ki, Kd; float e0, e1, e2; // 当前、上次、上上次误差 float out; // 累计输出 0~100 float out_min, out_max; float deadband; // 死区,单位与误差一致 float delta_max; // 单周期最大增量 } inc_pid_t; float inc_pid_update(inc_pid_t *p, float sp, float pv) { float e = sp - pv; if (e > -p->deadband && e < p->deadband) e = 0.0f; // 死区内不动 float delta = p->Kp * (e - p->e1) + p->Ki * e + p->Kd * (e - 2 * p->e1 + p->e2); if (delta > p->delta_max) delta = p->delta_max; // 变化率限幅 if (delta < -p->delta_max) delta = -p->delta_max; float next = p->out + delta; if (next > p->out_max) next = p->out_max; // 输出限幅 if (next < p->out_min) next = p->out_min; p->e2 = p->e1; p->e1 = e; p->out = next; return p->out; }

几处关键处理:死区设 ±0.3℃,避免温度在设定点附近来回触发继电器;delta_max 限住单周期增量,防止一次扰动让输出从 0 跳到 100;输出限幅同时隐含了抗积分饱和——增量式不会累积超出物理范围的积分量,因为输出本身被钳在 0~100。如果换成位置式,就还得额外加积分分离:当 |e| 超过 2℃ 时停止积分累加,只用比例项快速拉回。

4.4 温湿度双回路的耦合处理

温度和湿度不能各跑各的 PID。常见做法是分层:温度回路作为主回路,湿度回路作为从回路。湿度偏差先不直接驱动湿帘,而是修正温度设定点——湿度低时把温度设定点下调 0.5~1℃,减少加热带来的相对湿度下降,再决定要不要开湿帘。另一种是前馈补偿:顶窗开度超过 30% 时,按开度线性前馈给加热回路一个增量,抵消通风带来的降温,不用等 PID 反馈。

两路输出还要做资源仲裁:加热与湿帘互斥,同一时刻只有一路能拿到超过 50% 的输出;如果温度和湿度同时大幅偏离,优先保温度,因为温度直接决定作物是否受冻害。

提示:湿度传感器不要装在加热管正上方或喷雾口附近,否则测到的是局部值,PID 会一直误判。安装位置离地面 1.2~1.5 m,加装防辐射罩。

5. 上线后的验证方法与三类典型异常

调完参数不等于完工,得用可量化的指标验证。我的做法是做一次设定点阶跃:温度设定点从 18℃ 跳到 22℃,采样周期 10 s,记录 PV、SP、OUT 三条曲线,重点看四个数:

指标含义合理范围(温室)
上升时间从 10% 到 90% 设定点耗时8~20 min
超调量峰值超出设定点的比例< 15%
稳态误差稳定后 PV 与 SP 之差±0.5℃ 内
恢复时间开窗扰动后回到 ±1℃ 的耗时< 10 min

日志建议每 10 s 存一条 CSV,字段至少包含时间戳、温度、湿度、设定点、两路输出、执行器状态。没有日志的调参就是盲调,出了震荡根本分不清是 Kp 大了还是控制周期和采样周期打架。

最常见的三类异常:

输出长期贴在 0 或 100 不动。八成是执行器功率不够或者被限幅,积分项在位置式里已经饱和;增量式则表现为 delta 一直为正向最大值。先算热负荷,再改参数。

周期性等幅振荡且周期接近控制周期。这是微分项在放大传感器噪声,先把 Kd 砍到原来的三分之一,再检查滤波是否开启。

重启后输出跳变、温度掉坑。说明用了位置式 PID 且没做积分跟踪,或者增量式里的 out 初值没有从继电器当前状态恢复。解决方式是把 out 持久化到 NVS/Flash,上电时先读回上一次的输出值再使能控制。

湿度控不住但温度正常,先别改 PID 参数,去看传感器位置和湿帘水温——湿帘水温超过 25℃ 时增湿效率会掉一半,这时候调什么参数都是白费。把执行器能力边界确认清楚,再回到 Kp、Ki、Kd 上做微调。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 10:30:41

RIOT 外设 PIO 测试应用深入解析:指令内存分配与状态机管理

RIOT 外设 PIO 测试应用深入解析&#xff1a;指令内存分配与状态机管理 【免费下载链接】RIOT RIOT - The friendly OS for IoT 项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT 导读 PIO&#xff08;Programmable IO&#xff0c;可编程 IO&#xff09;是 RP…

作者头像 李华
网站建设 2026/9/20 10:28:20

Atlas 300V 24G实战:从昇腾推理卡到YOLO部署全流程

最近有个朋友抛了个问题给我&#xff1a;Atlas 300V 24G到底算不算运算加速卡&#xff1f;他要拿它跑YOLO&#xff0c;但是看了一圈资料还是没搞清楚这东西和平时用的游戏显卡、工作站显卡有什么区别。这个问题放在半年前&#xff0c;我大概也就回一句"算&#xff0c;它就…

作者头像 李华
网站建设 2026/9/20 10:27:38

Easy-Vibe 项目全览:从零开始用 AI 编程,把想法做成真实产品

Easy-Vibe 项目全览&#xff1a;从零开始用 AI 编程&#xff0c;把想法做成真实产品 【免费下载链接】easy-vibe 从 0 到 1 学会 vibe coding&#xff0c;项目制学习 项目地址: https://gitcode.com/datawhalechina/easy-vibe 本文基于 Datawhale 开源课程 easy-vibe 的法…

作者头像 李华