简介:这份资源是面向嵌入式初学者与课程设计、毕业设计参考者的 STM32 温度控制完整设计文档,围绕 STM32F103 展开,用 DS18B20 采集温度、电阻加热丝升温、OLED 显示,并以 PID 位置式算法输出 PWM 调节加热功率,把温度稳定在预设值,同时支持按键修改目标温度。压缩包内共 1 个 PDF 文件,约 658KB,包含设计报告正文及配套电路,正文覆盖方案设计、系统框图、电源与最小系统、温度采集、显示、控温、按键电路等硬件章节,以及主程序、温度采集、控温、显示、按键调控等软件流程说明与总结、附录,可当作一份从选型到电路、再到程序结构的完整项目蓝本。已有 6719 人学习下载,适合需要理解 PID 闭环温控、PWM 输出与传感器接口配合的读者,对照报告快速梳理硬件模块划分与软件分层思路,也能直接参考其调试要点与精度、可靠性、性价比方面的结论。
1. 温控系统的分水岭不在 PID 公式,而在采样那一刻
STM32F103 加 DS18B20 做温度控制,硬件清单不过十来种元件,位置式 PID 的公式在任何一本控制教材里都印在同一页;可有人做出来能把温度稳在 ±0.5℃,有人做出来温度像心电图一样上下摆三五度。差别几乎从不在公式本身,而在“温度什么时候被读进来、PWM 什么时候被送出去”这条时序链上。这套以 STM32F103 为核心、DS18B20 测温、电阻加热丝升温、OLED 显示、四键改目标值的设计,把单总线时序、位置式 PID 和 PWM 占空比串成一个闭环——把每一环的约束摸清楚,比背会那三个字母有用得多。
2. 温度采集:DS18B20 一根线凭什么能双向通信
测温是整个闭环的输入口,也是最容易被当成“接上就能读数”的一环。DS18B20 只有一根 DQ 线接到 STM32F103 的 PA1,既要发命令又要收数据,还要给器件供电,这在一根线上同时成立,靠的是电气结构加时序约定两件事。搞不清这两件事,读出来的温度就只是“看起来像温度”的数字。
2.1 开漏加 上拉:单总线的电气前提
单总线上的每个器件都只能把线往低电平拉,不能主动推高电平,高电平完全由上拉电阻(典型 4.7kΩ)提供。这种“线与”结构带来两个直接后果:任意一个器件拉低,整条线就是低;所有器件都释放,线才回到高。主机和从机共用同一根线却不会互相短路,双向通信在电气层面才成立。
理解了这一点,几个常见现象就顺了。挂在同一根线上的多个 DS18B20 可以组网多点测温,因为它们不会互相顶电平;DQ 上不加 4.7kΩ 上拉,读到的永远是 0 或者浮空的随机值;把 PA1 配置成推挽输出并且忘了切回输入,从机拉低时会出现两个输出对顶,电流直接顶到限流值。所以代码里必须成对出现“切成推挽输出拉低”和“切成上拉输入释放”两个动作,切换时机就是时序本身。
寄生供电模式下还有个附加约束:温度转换期间器件电流会从微安级跳到毫安级,单靠上拉电阻供电会把电压拖垮,此时主机必须在转换命令后把 DQ 强制拉高(强上拉)并保持到转换结束。本设计用 3.3V 常规供电时不必强上拉,但如果板上是从 5V 取电、又省掉了 VDD 引脚,这一条就必须补上,否则温度会随转换周期跳动。
2.2 微秒级时隙:复位、存在脉冲与读写窗口
单总线的所有语义都由“低电平持续多久”编码。复位是主机拉低超过 480μs;从机收到后等待 15~60μs,主动拉低 60~240μs 作为存在脉冲应答;写 1 是拉低 1~15μs 后马上释放,写 0 是拉低 60μs 以上;读时隙是主机拉低至少 1μs 后释放,然后在下降沿之后 15μs 内采样。这些窗口都在微秒量级,一旦被打断,读到的就是错位的比特。
/* 单总线复位:拉低 >480us 后释放,等待从机的存在脉冲应答 */ uint8_t ow_reset(void) { uint8_t presence; OW_OUT_PP(); /* PA1 切成推挽输出 */ OW_LOW(); delay_us(520); /* 复位脉冲下限 480us,留 40us 余量 */ OW_IN_PULLUP(); /* 释放总线:切成上拉输入 */ delay_us(70); /* 从机在 15~60us 内拉低,70us 时采样最稳 */ presence = OW_READ(); /* 0 表示器件在线,1 表示无应答 */ delay_us(430); /* 补齐本时隙,避免下一时隙踩到存在脉冲尾巴 */ return presence; }这段里三个延时都不是随便取的。520μs 对应复位脉冲下限加余量;70μs 是存在脉冲窗口的中点——太早采样可能还没拉低,太晚采样从机可能已经释放;430μs 的补齐是为了让整个复位时隙超过 960μs,从机复位状态机才彻底走完。返回值 presence 一定要判,读不到应答就继续往下跑,后面读到的全是没有意义的原始码,这也是“温度显示 85℃”这类现象最常见的来源之一。
/* 读一个 bit:拉低 >1us 发起时隙,释放后在 15us 窗口内采样 */ uint8_t ow_read_bit(void) { uint8_t b; OW_OUT_PP(); OW_LOW(); delay_us(2); /* 拉低 >1us 即发起读时隙 */ OW_IN_PULLUP(); /* 立刻释放,电平由从机决定 */ delay_us(10); /* 在 15us 采样窗口内取 10us 处 */ b = OW_READ(); delay_us(50); /* 时隙总长 >=60us,再留 1us 恢复时间 */ return b; }写时隙与之对称,只是主机始终掌控电平:写 1 拉低 2μs 后释放,写 0 拉低 60μs 再释放。读写的关键区别在于——读时隙里主机必须“放手”,让从机决定这一位是 0 还是 1;写时隙里主机必须“按住”足够久,从机才认得出这是 0。这两段函数内部不允许被长时间中断打断,实践中的做法是在时隙前后开一个几十微秒的临界区,硬件仿真或者高优先级中断恰好在窗口内触发时,这一位就会翻。
2.3 16 位原始码到 0.0625℃:符号位的坑
DS18B20 的暂存器里温度占两个字节,低字节在前、高字节在后,12 位分辨率时低 4 位是小数部分,1 个 LSB 等于 0.0625℃。高字节的高 5 位是符号扩展位,负温度时全是 1。用无符号整型去接,零下温度会变成一个接近 65535 的巨大正数,OLED 上就会显示出离谱的值。
/* 读暂存器前两个字节并换算成 0.001℃ 为单位的整数,全程不碰浮点 */ int32_t ds18b20_read_milli(void) { uint8_t buf[2]; int16_t raw; ow_reset(); ow_write_byte(0xCC); /* 跳过 ROM:总线上只有一个器件时用 */ ow_write_byte(0x44); /* 启动一次温度转换 */ delay_ms(750); /* 12 位分辨率最大转换时间 */ ow_reset(); ow_write_byte(0xCC); ow_write_byte(0xBE); /* 读暂存器,从字节 0 开始 */ buf[0] = ow_read_byte(); /* LSB,先出来 */ buf[1] = ow_read_byte(); /* MSB */ raw = (int16_t)((buf[1] << 8) | buf[0]); /* 必须带符号,否则负温全错 */ return (int32_t)raw * 625 / 10; /* 0.0625℃ = 62.5 毫度 */ }raw * 625 / 10是把 0.0625℃ 换算成以 0.001℃ 为单位的整数:raw 每加 1 代表 62.5 毫度,先乘 625 再除 10 保住精度,最后那次整除会截掉 0.5 毫度,对 0.0625℃ 的分辨率没有影响。用整数而不是浮点,是因为 Cortex-M3 没有硬件 FPU,浮点运算走的是软件库,一次换算几十微秒起步,放在 1 秒级的控制环里虽不致命,但在中断里就很危险。同理,OLED 显示时把毫度拆成整数部分和小数部分,比printf("%f")稳得多。
2.4 750ms 转换期:主循环不能在这儿阻塞
12 位分辨率下一次转换最长 750ms。如果按“启动转换→死等 750ms→读结果→算 PID→输出 PWM”的顺序写,主循环的周期就被钉死在 1 秒以上,按键按下去要等半秒才有反应,OLED 刷新也是一卡一卡。这套设计里按键走的是外部中断,看起来不受影响,但中断里如果还做长延时消抖,照样翻车。
合理的组织方式是把采集拆成状态机:主循环发 0x44 后立刻返回,用定时器节拍记录时间,够 750ms 再来取数;这段时间里刷 OLED、扫按键、跑 PID 输出当前占空比都不耽误。DS18B20 本身也支持“读两次取后一次”的做法——上电默认值是 85℃,第一次读到的往往就是它,跳过第一次能省掉一类假数据。这两点没有写进流程图,但决定了这套系统的手感是“跟手”还是“迟钝”。
3. PID 与 PWM:占空比怎么变成“稳”
从测到温度到让加热丝出力,中间隔着一个控制算法和一个功率开关。位置式 PID 算出的是一个绝对出力值,PWM 恰好也是绝对量——占空比 30% 就是 30% 的功率,这种对应关系让位置式在这个场景里比增量式更自然。但“算得出”和“稳得住”之间隔着限幅、饱和、死区和采样周期四道坎。
3.1 位置式 PID 在温控里到底累加了什么
离散化的位置式 PID 写成 u(k) = Kp·e(k) + Ki·T·Σe(j) + Kd·(e(k) − e(k−1))/T,三项的物理意义在温控里非常具体。比例项看当前差多少,差 5℃ 就出 5 份力;积分项看历史上一直差多少,是消除稳态误差的唯一手段——加热丝功率有限、环境一直在散热,纯比例控制永远会停在一个比目标低一点的位置上;微分项看差距变化的速度,起阻尼作用,防止冲过头。
问题是微分项在温度信号上特别容易变成噪声放大器。DS18B20 末位 0.0625℃ 的量化抖动,除以 1 秒的采样周期再乘 Kd,能放大成肉眼可见的输出跳变。常见的处理是给微分项串一个一阶低通(不完全微分),或者干脆用“微分先行”——只对测量值求微分,不对目标值求微分,这样按键改目标值的瞬间不会因为 e(k) 突变而产生一个巨大的微分冲击。
typedef struct { float kp, ki, kd; float target; /* 目标温度,单位 ℃ */ float integral; /* 已乘过 dt 的积分累加项 */ float last_err; float out_max; /* 对应 PWM 满占空比,例如 1000 */ float dead_zone; /* 死区,抑制末位抖动导致的频繁动作 */ } pid_t; float pid_calc(pid_t *p, float meas, float dt) { float err = p->target - meas; float deriv, out; if (fabsf(err) < p->dead_zone) err = 0.0f; /* 死区内不动作 */ /* 积分分离:误差大时先不积分,避免上电阶段积分暴涨 */ if (fabsf(err) < 5.0f) p->integral += err * dt; deriv = (err - p->last_err) / dt; p->last_err = err; out = p->kp * err + p->ki * p->integral + p->kd * deriv; if (out > p->out_max) { /* 上限幅 + 积分回退 */ out = p->out_max; p->integral -= err * dt; } else if (out < 0.0f) { /* 下限幅 + 积分回退 */ out = 0.0f; p->integral -= err * dt; } return out; }dt必须和实际调用周期一致,这里取 1.0 秒,对应上面 750ms 转换加处理开销。dead_zone取 0.2℃ 左右:比 0.0625℃ 的量化步长大三倍以上,能盖住末位抖动,又不至于把稳态误差固化得太明显。两处integral -= err * dt是简易的抗饱和回退,虽然不如标准的反算抗饱和精确,但在只有一个执行器、单向加热的场景里够用——加热丝只能加热不能制冷,输出下限天然是 0,饱和几乎是必然会发生的。
3.2 输出限幅与积分饱和:温控翻车第一现场
上电那一刻,实测温度可能是 20℃,目标是 60℃,误差 40℃。比例项乘上 Kp 立刻打满,积分项也开始以 40·T 的速率疯狂累加。等温度真的爬到 60℃ 时,积分项已经攒到一个巨大的值,输出仍然维持在满占空比,温度一路冲过目标,然后要花很长时间才能把积分“吐”回去。整个过程表现为:升温很慢、冲过头很多、回调极慢,看起来像 PID 参数完全没调对。
这就是积分饱和。上面代码里两个动作合起来解决它:积分分离让误差大于 5℃ 时根本不累加,上电阶段积分项始终是零;限幅时回退积分,让累加器不会跑到输出能力之外。实践中的经验值是积分分离阈值取量程的 10%~20%,回退系数不用太精确,只要能保证退出饱和时积分项在合理范围内就行。
另一类不那么明显的问题是死区设太宽。把 dead_zone 设成 1℃,稳态时温度会在目标值下方 0.9℃ 附近停住不动,因为误差进了死区、积分不再增长,输出刚好维持住散热。这个偏差靠调 Kp 是补不回来的——Kp 再大,误差也已经被死区清零了。死区宽度必须小于你能接受的稳态误差,这是设计时先定的指标,不是调参调出来的。
3.3 PWM 频率与分辨率:占空比不是越小越精细
STM32F103 的通用定时器在 72MHz 主频下,预分频 71 得到 1MHz 计数时钟,自动重装值 999 得到 1kHz 的 PWM 频率,比较寄存器 0~999 对应 0~99.9% 的占空比。频率和分辨率是一对矛盾:同样的计数时钟下,ARR 越大分辨率越高、频率越低,两者乘积被时钟锁死。
/* TIM3_CH3 -> PA2,1MHz 计数,ARR=999:1kHz PWM,0.1% 分辨率 */ void pwm_init(void) { TIM_TimeBaseInitTypeDef tb; TIM_OCInitTypeDef oc; /* GPIOA_Pin2 配成复用推挽输出,略 */ tb.TIM_Prescaler = 72 - 1; /* 72MHz / 72 = 1MHz */ tb.TIM_Period = 1000 - 1; /* 1MHz / 1000 = 1kHz */ tb.TIM_CounterMode = TIM_CounterMode_Up; tb.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, &tb); TIM_OCStructInit(&oc); oc.TIM_OCMode = TIM_OCMode_PWM1; /* 计数小于 CCR 时输出有效电平 */ oc.TIM_OutputState = TIM_OutputState_Enable; oc.TIM_Pulse = 0; /* 上电不加热 */ TIM_OC3Init(TIM3, &oc); TIM_Cmd(TIM3, ENABLE); } void pwm_set(float out) /* out 已限幅在 0~1000 */ { TIM_SetCompare3(TIM3, (uint16_t)out); }为什么是 1kHz?往下限看,加热丝本身热惯性大,几十赫兹的通断在温度上根本体现不出来,但频率太低会让 12V 电源上的电流脉动变大,走线辐射也难处理;往上看,MOSFET 的开关损耗与频率成正比,栅极电荷每次都要重新充放,几十千赫兹以上驱动电路就得认真设计。1kHz 到 20kHz 是常见区间,取 1kHz 还有个附带好处:低于人耳可听范围的下沿,不会因为电阻丝的机械振动发出啸叫。
分辨率 0.1% 够不够,要看执行器的绝对功率。12V、2A 的加热丝满功率 24W,0.1% 就是 24mW 的调节步进,对一个大惯性的温室对象绰绰有余。但如果换成小功率加热片,或者目标温度对环境温度只差 1~2℃,就可能需要更高的分辨率,这时要么降低 PWM 频率,要么改用定时器 + 软件累加的方式做等效分辨率的做法。
3.4 采样周期被传感器锁死之后
PID 的采样周期不是自由参数,它被 DS18B20 的 750ms 转换时间卡死。采样周期取 1 秒,意味着控制环的带宽上限被压得很低,对象的时间常数必须远大于 1 秒才谈得上“稳定”。温室、恒温箱这类对象的热时间常数通常在几分钟到几十分钟,1 秒采样完全够;但如果探头贴在一个小金属块上,热时间常数只有几秒,这个采样率根本来不及反应,微分项也会完全失效。
采样周期固定带来的另一个结果是参数不可移植。同一套 Kp/Ki/Kd 在 1 秒周期下调好了,换成 2 秒周期,积分项的效果直接翻倍,系统就会振荡。所以代码里 dt 必须是常量而不是“上次循环到现在的时间”,否则一旦主循环里插入了 OLED 全屏刷新或者长时间阻塞的操作,控制行为就会莫名变化。这条经验在很多温控项目里都翻过车:调好的参数过两天就不对了,最后发现是显示刷新逻辑改了。
4. 功率级与电源:从 3.3V 的 GPIO 到 12V 的电热丝
控制算法算出的是 0~1000 的数字,落到物理世界要靠功率级把 12V 电源斩成占空比可调的平均功率。从 PA2 的一个引脚到电阻丝之间,隔着一到两级开关器件、一个稳压模块和一套接地策略,任何一处设计得随意,前面的时序和 PID 都白搭。
4.1 两级驱动:三极管加 MOSFET 各自解决什么问题
STM32F103 的 GPIO 输出高电平是 3.3V,最大输出电流十几毫安。直接用这个电平去推 MOSFET 栅极,会遇到两个问题:一是常见功率 MOSFET 的开启阈值在 2~4V 之间,3.3V 恰好落在线性区边缘,导通电阻远大于手册标称值,管子会烫得厉害;二是栅极电容每次开关都要瞬间充放电流,GPIO 的驱动能力不足会让开关沿变缓,进一步增加开关损耗。
设计里“三极管加场效应管”这个结构就是解决这两件事的。前级 NPN 三极管做电平转换和电流放大:GPIO 通过基极电阻(典型 1kΩ)驱动三极管导通,三极管把 MOSFET 的栅极拉到地,栅极关断;GPIO 为低时三极管截止,栅极被上拉到 12V 而导通。这样栅极得到的是接近 12V 的驱动电压,MOSFET 进入深线性区,导通电阻降到标称值附近。
后级 MOSFET 负责扛电流。选型时三个参数最关键:栅极阈值电压要在驱动电平之下留足余量,导通电阻要小到额定电流下的压降和发热可接受,封装的热阻要能散掉这部分热量。加热丝是纯阻性负载,理论上不需要续流,但引线电感在关断瞬间会产生电压尖峰,di/dt 越大尖峰越高,超过管子耐压就会雪崩击穿。工程上的做法是在加热丝两端反向并一只快恢复二极管,栅极串 100Ω 电阻限制充电峰值电流,栅源之间再并一只 10kΩ 下拉电阻防止悬空误导通。
4.2 压差账:AMS1117 与 78 系列不能随便接
这套设计的供电分两路:5V 输入经 AMS1117-3.3 得到 3.3V,供 STM32F103 最小系统和 DS18B20 使用;12V 供给加热丝。前者是线性稳压器的典型用法,5V 到 3.3V 的压差 1.7V,在 50mA 左右的负载下耗散不到 0.1W,不需要散热片。这里唯一要注意的是压差本身——AMS1117 这类低压差器件在 1A 输出时需要 1V 以上的压差才能维持稳压,如果输入掉到 4.2V,输出就不再是干净的 3.3V,MCU 会随机复位。
后面这一路就值得多说一句。原始资料里写的是“5V 电源经过 7812 三端稳压器可输出 12V 电压”,这句话在真实电路里不成立。78 系列是降压型线性稳压器,输入端必须比输出高 2V 以上才能正常稳压,7812 的输入至少要 14V 以上。拿 5V 灌进 7812,输出只会是 3~4V 甚至更低,而且芯片会因为无法建立稳压而发热。正确的接法是反过来:用 12V 或 15V 的适配器做输入,7812 输出稳定的 12V 给加热丝,同时从 12V 再经 AMS1117-3.3 得到 3.3V。如果加热丝电流在安培级,7812 上的耗散会达到几瓦,必须配散热片,或者干脆让 12V 开关电源直接带电热丝、7812 只负责给控制部分供电。
注意:凡是看到“X 伏经 78XX 得到更高电压”的描述,先按压差账算一遍。线性稳压器只能降压,这一条没有例外。
4.3 功率地与信号地:读数跳变的隐形来源
加热丝工作时电流在安培级,回流路径上的走线电阻哪怕只有几十毫欧,也会产生几十毫伏的压降。如果 DS18B20 的地和 MCU 的地接在这段回流路径的中间,测温参考点就随着 PWM 通断在跳动,表现出来就是温度读数周期性抖动、OLED 偶尔花屏、严重时 MCU 直接复位。
标准做法是星型接地:电源入口处设一个单点,功率回路的地和信号回路的地各自走线到这里汇合,中间不共用任何一段。DS18B20 的地线尤其要单独走,不能图省事就近接到加热丝的负极焊盘上。另外,电源入口处并一只几百微法的大电容做能量缓冲,加热丝两端再并一只小电容吸收高频尖峰,能让 PWM 开关瞬间的电源波动小一个量级。
5. 排查与避坑:几条反复踩过的记录
原理讲清楚之后,真正耗时间的往往是那些“按图接完却不工作”的现象。下面几条都是这套结构里高频出现的,每一条都按现象、原因、解决三段记录,遇到时可以直接对照。
5.1 采集与显示:85℃、跳变与花屏
现象一:上电后温度恒定显示 85℃,怎么等都不变。原因是 DS18B20 的上电复位默认值就是 85℃,如果启动转换命令没发出去,或者发完之后没等够 750ms 就读暂存器,读到的就是这个默认值。另一个可能是复位时序里没检测存在脉冲,器件根本不在线,读回的全是 0xFF,按 12 位换算也接近 85℃。解决办法是先把ow_reset()的返回值判一遍,再确认 0x44 命令发出后等待时间足够,最后可以在初始化阶段读一次配置寄存器,确认分辨率和供电模式符合预期。
现象二:温度大部分时间正常,偶尔跳到 0 或者 -0.0625℃。原因是读时隙被中断打断了。STM32F103 的 NVIC 支持中断嵌套,如果读位的过程中来了一个高优先级中断,主机在 15μs 采样窗口之外才去读 DQ,从机早已释放总线,采到的就是高电平,整字节错位后换算结果就离谱。解决办法是在读写字节函数的前后包一个几十微秒的临界区,用__disable_irq()和__enable_irq()成对包裹,或者把单总线读写放到定时器中断里的固定时隙执行。临界区长度不能超过几十微秒,否则会影响按键响应。
现象三:OLED 花屏、首行出现随机亮点。原因通常是两个:一是上电后没等够时间就初始化驱动芯片,内部电荷泵还没建立稳定电压;二是用 GPIO 模拟 IIC 时半周期延时给得太短,从机采样不到正确的电平。解决办法是上电延时 200ms 再做初始化序列,软件 IIC 的半周期延时不少于 5μs(对应约 100kHz),并且每写一页显存后检查一次应答位。另外整屏刷新不要放在 while(1) 的每一轮里,限速到 10Hz 左右,既省时间也避免 IIC 总线长期被占用。
5.2 控制环:稳态偏差为什么调 Kp 补不上
现象四:温度爬到设定值下方 1~2℃ 就停住,加大 Kp 只会让超调变大,稳态偏差一点没变。先看死区,误差进了死区就被清零,积分不再增长,输出刚好维持住散热,这个偏差是被死区锁死的。再看积分项是否真的在累加:如果积分分离的阈值设得太小(比如 1℃),而稳态误差恰好是 1.5℃,积分永远进不去,系统退化成纯比例控制。解决办法是把死区压到 0.2℃ 以内、积分分离阈值放到量程的 10% 以上,然后观察积分项的实际数值——它应该是一个缓慢变化的非零值,如果一直是 0,说明分离条件压根没满足。
5.3 电源与功率:照图接线就翻车的那几处
现象五:加热一开始,温度读数就跳、屏幕闪烁,严重时 MCU 复位。三个方向依次排查:功率地和信号地是否共用了回流通路,按上面的星型接地改;加热丝两端有没有续流二极管,关断尖峰可能从 MOSFET 的漏极耦合到电源;栅极驱动电阻是否省掉了,栅极电容充电时的瞬时电流会顺着 GPIO 灌进 MCU 的电源。改完之后用示波器看 3.3V 上的纹波,正常应该在几十毫伏以内;如果还有几百毫伏的毛刺,就在电源入口加大电容并在加热丝回路串一个磁珠。
现象六:7812 输出只有 3V 多,芯片摸上去发烫。这就是压差账没算。78 系列要求输入比输出高 2V 以上,5V 输入接 7812 属于反向操作。解决办法是换成 12V 以上的输入,或者改用低压差的开关电源模块。顺带一句,如果加热丝电流在 1A 以上,7812 本身的耗散会很大,散热片是必须的,或者把加热丝直接挂在 12V 输入上,让 7812 只带控制部分。
6. 设计取舍:这套方案停在“能用”和“好用”之间
位置式和增量式的选择,其实是这套设计里最值得多看一眼的地方。PWM 的占空比是绝对量,位置式 PID 算出来的也是绝对出力,两者直接对应,代码里不需要保存“上次输出”,逻辑最短。增量式的优势在于天然抗饱和——它只输出增量,累加由执行器自己完成,但 PWM 没有“记忆”,增量必须靠软件累加回来,等于把积分项换个地方存。所以在这个场景下选位置式是对的。代价是必须手工处理饱和与分离,这两段代码写不好,系统就会表现出“升温慢、冲过头、回不来”的典型症状。
| 维度 | 位置式 | 增量式 |
|---|---|---|
| 与 PWM 的对应 | 直接对应占空比 | 需软件累加 |
| 积分饱和 | 需显式限幅回退 | 天然抑制 |
| 参数突变影响 | 输出连续,冲击小 | 可能跳变 |
| 代码复杂度 | 低 | 中 |
精度天花板的账也要算清楚。DS18B20 在 -10~85℃ 区间是 ±0.5℃,这个数字是器件本身的指标,PID 调得再好也突破不了。想让稳态误差小于 0.5℃,要么换铂电阻加高分辨率 ADC,要么多点测温取平均,前者成本翻几倍、后者要处理多点布线。对温室这类应用,±0.5℃ 已经完全够用,用数字传感器省掉运放、基准源和 ADC 标定这一整套,是这套方案性价比高的真正原因。
我最早做这类项目时,习惯先把 PID 参数凑出一组能跑的,再回头补时序和接地。结果就是每次现象都指向算法,改半天参数毫无起色,最后发现问题出在单总线被中断打断、或者加热丝回流路径压着测温地。从那以后我每次上电的第一件事,都是先只跑采集、把温度连续打印十分钟看有没有跳变,再接功率级、最后才开 PID。这个顺序看起来慢,其实省掉了大把在算法里空转的时间。希望帮到你。
本文还有配套的精品资源,点击获取