简介:这是一份面向自动化、控制工程与嵌入式方向学习者的增量式PID控制算法MATLAB仿真文档,聚焦被控对象传递函数的离散化建模、控制器实现与参数整定,适合课程设计、毕业设计和仿真实验入门。资源包内仅含一个docx文件,大小约162KB,正文以原理推导、程序清单、波形结果和调试记录组织,便于直接阅读与复现。文档从比例、积分、微分控制原理切入,说明增量式算法输出控制量增量而非绝对量,并演示Z变换、差分方程、状态变量计算及MATLAB代码实现,涵盖单位阶跃与正弦输入、毫秒级采样、控制器输出限幅、系统输出和误差曲线绘制。文中还记录比例、积分、微分三个增益由临界稳定逐步调节的过程,可帮助读者理解参数变化对超调、振荡和稳态误差的影响,并对照仿真波形排查效果不理想的原因。已有2088人学习下载,适合需要快速掌握增量式PID仿真流程与调参思路的读者参考。
1. 从执行器抖动说起:增量式PID到底解决什么问题
做过温度控制的人多半遇到过这种现场:加热丝在稳态附近来回抽动,电机调速时转速在一两百转内高频晃。把位置式 PID 换成增量式 PID,同一组参数下曲线反而干净很多。原因不玄学——位置式 PID 每拍算的是绝对输出量 u(k),积分项对历史误差累加,累加器一旦被扰动带偏,或者手动/自动切换瞬间误差突变,u(k) 会直接跟着跳,执行器只能照单全收。
增量式 PID 算的是增量 Δu(k),执行环节自己保留上一次的位置,控制器只告诉它「再加一点」。输出天然具备保持特性,切换或复位时没有冲击,单拍算错也只影响一步。代价是必须有一个能累加的执行环节,阀门、变频器、PWM 占空比寄存器都满足。
后面的内容围绕 MATLAB 展开:先把差分方程推清楚,再搭一个最小仿真闭环,然后把参数整定、仿真发散、积分饱和这些坑逐个拆开。适合做嵌入式温控、电机调速、PLC 模拟量输出的工程师,也适合想把算法先在 MATLAB 上验证再往 C 里搬的人。
2. 增量式PID的差分方程推导与位置式PID的取舍
2.1 位置式PID的离散化差分方程长什么样
连续形式的位置式 PID 是大家熟的那一条:
u(t) = Kp * [ e(t) + (1/Ti) * ∫e(t)dt + Td * de(t)/dt ]做数字控制必须离散化。常见做法是积分用矩形法累加、微分用后向差分,采样周期记作 T:
u(k) = Kp*e(k) + Kp*(T/Ti)*Σe(j) + Kp*(Td/T)*[e(k)-e(k-1)]把两个组合系数拎出来,记 Ki = KpT/Ti、Kd = KpTd/T,就是写代码时最熟的那版:
u(k) = Kp*e(k) + Ki*Σ(j=0..k)e(j) + Kd*[e(k)-e(k-1)]这条式子有三个后遗症。第一,Σe(j) 是一个只增不减的累加器,必须用一个变量常驻保存,溢出和饱和都出在这里。第二,输出是绝对量,执行器拿到的就是 u(k) 本身,限幅做在输出口上。第三,Ki 和 Kd 都含 T,采样周期一改,之前调好的参数全部失效。这三条正是后面所有坑的源头。
2.2 两步相减推出增量式PID的三系数形式
把 2.1 的式子写成 k-1 拍:
u(k-1) = Kp*e(k-1) + Ki*Σ(j=0..k-1)e(j) + Kd*[e(k-1)-e(k-2)]两式相减,求和项被消掉,只剩 e(k) 这一项:
Δu(k) = u(k) - u(k-1) = Kp*[e(k)-e(k-1)] + Ki*e(k) + Kd*[e(k)-2e(k-1)+e(k-2)]把 e(k)、e(k-1)、e(k-2) 的系数合并:
Δu(k) = A*e(k) - B*e(k-1) + C*e(k-2) A = Kp + Ki + Kd B = Kp + 2*Kd C = Kd这就是工程上常说的「三系数增量式 PID」。它的状态只有两个:e(k-1) 和 e(k-2)。没有积分累加器,所以不存在累加器溢出;系数 A、B、C 一旦算好,每拍只做三次乘法和两次加减,Cortex-M0 级别也扛得住。
注意:没有累加器不等于天然抗积分饱和。u(k) = u(k-1) + Δu(k) 本身就是一次累加,u 顶到限幅之后 Δu 还在算,恢复时照样会冲过头。抗饱和得单独做,见 4.4。
位置式和增量式的差别可以整理成一张表,选型时对着看:
| 对比项 | 位置式 PID | 增量式 PID |
|---|---|---|
| 输出含义 | 绝对量 u(k) | 增量 Δu(k) |
| 需要保存的状态 | e(k-1)、积分累加器 | e(k-1)、e(k-2) |
| 手动/自动切换 | 需做无扰切换处理 | 天然平滑 |
| 积分饱和风险 | 高,必须有抗饱和 | 累加仍会饱和,需限幅 |
| 适合的执行器 | 伺服位置给定、绝对量输出 | 阀门、变频器、PWM |
| 采样周期改动 | Ki、Kd 全部要重算 | 同样要重算,但系数可整体缩放 |
2.3 三种等价写法的MATLAB实现与取舍
第一种是直接用 Kp、Ki、Kd 写,好处是整定结果能和位置式共用:
function [du, st] = pid_inc_gain(Kp, Ki, Kd, ek, st) % 增量式PID:由 Kp/Ki/Kd 直接计算本拍增量 % st.e1 存 e(k-1),st.e2 存 e(k-2) du = Kp*(ek - st.e1) + Ki*ek + Kd*(ek - 2*st.e1 + st.e2); st.e2 = st.e1; % 历史误差整体后移一拍 st.e1 = ek; end第二种是三系数形式,适合参数定死、要往单片机上搬的场景:
function [du, st] = pid_inc_abc(A, B, C, ek, st) % 增量式PID:三系数形式,每拍三次乘法 du = A*ek - B*st.e1 + C*st.e2; st.e2 = st.e1; st.e1 = ek; end两种写法数学上完全等价,区别在于:pid_inc_gain 改一个 Kp 不需要重算别的,调试期用它;pid_inc_abc 把系数在初始化时算好,运行期不再碰乘法以外的逻辑,量产代码用它。实测中还有一个细节,A、B、C 用定点数存的时候要留意 A 的绝对值最大,通常取 Q12 或 Q15 格式,先把 Kp、Ki、Kd 放大再截断,别到运行期才转。
第三种写法是把 Ki 并进 Kp,也就是所谓的 PI 增量式,只保留 A、B 两个系数。微分项在温度这种大惯性对象上贡献有限,噪声又大,很多现场干脆不用 D。要不要砍掉 D,看 4.3 里微分项的排查结果。
3. 在MATLAB里跑通增量式PID仿真的最小闭环
3.1 被控对象建模与采样周期T的选取
仿真发散有一多半不是 PID 的问题,是对象模型和采样周期没配对。温度、加热这类过程常用一阶惯性加纯滞后:
G(s) = K0 / (T0*s + 1) * exp(-tau*s)K0 是对象增益,T0 是时间常数,tau 是纯滞后。用零阶保持离散化,得到可以直接迭代的差分方程:
a = exp(-T/T0) b = K0*(1 - a) d = round(tau/T) y(k) = a*y(k-1) + b*u(k-1-d)d 是滞后拍数,取整之后会引入误差,所以 T 不能取得太大。采样周期的经验是 T 取 T0 的 1/10 到 1/20,同时保证纯滞后至少覆盖 3 到 5 个采样拍。算下来 d 小于 2 的时候,仿真里的振荡往往是离散化误差,不是控制器真的不稳。
一组可以直接抄的参数:
| 参数 | 取值 | 说明 |
|---|---|---|
| K0 | 2 | 对象静态增益 |
| T0 | 20 s | 时间常数 |
| tau | 5 s | 纯滞后 |
| T | 0.5 s | 采样周期,T0/40 |
| d | 10 | tau/T,滞后拍数 |
| t_end | 100 s | 仿真时长 |
3.2 增量式PID控制器的MATLAB函数
控制器就写成 2.3 里的 pid_inc_gain,单独一个文件,方便后面做位置式对比时两边共用。状态用结构体传递,避免用 global:
function [du, st] = pid_inc_gain(Kp, Ki, Kd, ek, st) % 增量式PID,状态结构体 st 含 e1、e2 du = Kp*(ek - st.e1) + Ki*ek + Kd*(ek - 2*st.e1 + st.e2); st.e2 = st.e1; st.e1 = ek; end参数顺序不要随意调。ek 是当前误差,必须是本拍采样得到的值;st 是上一次调用留下的状态,第一次调用前要显式初始化为 struct('e1',0,'e2',0),否则 MATLAB 会报字段不存在。
3.3 主仿真脚本:阶跃响应从0到稳态
下面是完整脚本,把对象、控制器、限幅串成一个闭环。注意误差取的是 r - y(k-1),因为数字控制器先采样上一拍输出、再计算、再输出,这个顺序和实际程序一致。
%% 增量式PID MATLAB仿真最小闭环 clear; clc; close all; T = 0.5; t_end = 100; N = round(t_end/T); K0 = 2; T0 = 20; tau = 5; d = round(tau/T); a = exp(-T/T0); b = K0*(1-a); Kp = 0.8; Ki = 0.05; Kd = 0.5; % 待整定参数 r = 1; % 阶跃给定 u_max = 5; u_min = 0; % 执行器上下限 y = zeros(1,N); u = zeros(1,N); e = zeros(1,N); st = struct('e1',0,'e2',0); u_prev = 0; for k = 2:N e(k) = r - y(k-1); % 当前误差 [dk, st] = pid_inc_gain(Kp, Ki, Kd, e(k), st); u_old = u_prev; u_prev = u_prev + dk; % 增量累加成绝对输出 u_prev = min(max(u_prev, u_min), u_max);% 限幅 dk = u_prev - u_old; % 实际生效的增量 u(k) = u_prev; idx = k - 1 - d; % 纯滞后回看 if idx >= 1 ul = u(idx); else ul = 0; end y(k) = a*y(k-1) + b*ul; % 对象差分方程 end figure; subplot(2,1,1); plot((0:N-1)*T, y, 'LineWidth',1.2); grid on; ylabel('y'); subplot(2,1,2); plot((0:N-1)*T, u, 'LineWidth',1.2); grid on; ylabel('u'); xlabel('t/s');逻辑上有三处容易写错:滞后索引 idx 是从 k-1-d 往前回看,写反会变成超前;限幅要放在累加之后、赋值给 u(k) 之前;dk 被限幅改写之后,下一拍用作判断的 u_prev 已经是最新的饱和值,反算才成立。
3.4 仿真曲线怎么读:超调、上升时间、稳态误差
跑完之后不要只看形状,把三个指标算出来。手写循环没有现成的 stepinfo 可用,可以自己提:
ymax = max(y); overshoot = (ymax - r)/r*100; % 超调量 % i10 = find(y >= 0.1*r, 1); i90 = find(y >= 0.9*r, 1); tr = (i90 - i10)*T; % 上升时间 s ess = r - mean(y(end-100:end)); % 稳态误差 fprintf('超调 %.1f%%, 上升时间 %.2f s, 稳态误差 %.4f\n', overshoot, tr, ess);目标形态是超调 10% 到 20%、上升时间在 1 到 2 倍 T0 之间、稳态误差趋近 0。三样里只要有一项不对劲,就去 4.3 的排查表里对号入座,不要先去动参数。
4. 增量式PID参数整定与仿真发散排查
4.1 Kp、Ki、Kd与PB、TI、TD的换算关系
西门子 S7-200 SMART、三菱 FX 系列以及不少温控仪表用的是另一套参数:PB 是比例带(%),TI 是积分时间(s),TD 是微分时间(s)。从现场抄下来的参数要进 MATLAB,必须先换算:
Kp = 100 / PB Ki = Kp * T / TI Kd = Kp * TD / T反过去也一样:
PB = 100 / Kp TI = Kp * T / Ki TD = Kd * T / Kp举个实际的:PB=20%、TI=40 s、TD=5 s,采样周期 T=0.5 s,算出来 Kp=5、Ki=50.5/40=0.0625、Kd=55/0.5=50。注意 TD 只有 5 秒,换算完 Kd 却有 50,这就是为什么增量式 PID 里 Kd 经常比 Kp 大一个量级——它乘的是二阶差分,不放大根本不起作用。
| 现场参数 | 数值 | 换算结果 | 数值 |
|---|---|---|---|
| PB | 20% | Kp | 5 |
| TI | 40 s | Ki | 0.0625 |
| TD | 5 s | Kd | 50 |
| T | 0.5 s | — | — |
提示:不同品牌的 TI、TD 单位可能是秒也可能是分钟,某些仪表还有 TI=0 表示取消积分的约定。抄参数前先翻一眼手册,单位错了整定方向会完全反掉。
4.2 用pidTuner先调位置式再换算增量式增益
MATLAB 的 pidTuner(新版叫 PID Tuner App)只能对线性模型调参,拿到的是位置式增益,但 Kp、Ki、Kd 的定义和增量式完全一致,可以直接代入:
G = tf(K0, [T0 1], 'InputDelay', tau); Gd = c2d(G, T, 'zoh'); % 采样周期必须和仿真脚本一致 pidTuner(Gd, 'PID'); % 交互式拖动,满意后导出 Kp/Ki/Kd导出数值之后,把 3.3 脚本里的 Kp、Ki、Kd 替换掉即可。这里有两个硬约束:c2d 用的 T 必须等于仿真脚本的 T,否则 Ki、Kd 全错;pidTuner 默认按「平衡响应」给参数,实际工程里如果超调偏大,可以手动把 Kd 减一半再跑,很多时候 pid 最优曲线并不等于现场最舒服的曲线,执行器动作幅度也是约束。
4.3 仿真发散的六类原因与逐条定位
曲线发散的原因和现象基本是一对一的,对表排查比盲调参数快得多:
| 现象 | 最可能原因 | 排查动作 |
|---|---|---|
| 指数上扬,不振荡 | Kp 过大,超过稳定边界 | 把 Ki、Kd 置 0,只留 P 单独试 |
| 一拍正一拍负的高频振荡 | 采样周期 T 相对 T0 太大,或滞后拍数 d 太小 | T 减半重跑,d 至少 3 |
| 周期几秒到几十秒的缓慢振荡 | Ki 偏大 | Ki 降到原来的 1/2 或 1/3 |
| 曲线上有密集毛刺并逐渐发散 | Kd 偏大,微分在放大测量噪声 | 微分改为对 y 求差分,或串一阶低通 |
| u 顶到限幅、y 不动,之后大幅超调 | 积分饱和 | 加限幅并做反算抗饱和 |
| 曲线朝反方向跑到底 | 反馈极性接反 | 检查 e = r - y 的符号 |
第一类最容易被误判成对象不稳。判断方法很直接:Kp 单独作用时如果输出单调发散,那确实是 Kp 过大;如果输出是振荡发散,通常是 T 和 d 的组合问题。
4.4 积分饱和、输出限幅与抗噪声的微分改法
第三条排查里提到反算抗饱和,具体写法是在累加限幅之后,把被削掉的部分从增量里扣回去:
u_old = u_prev; u_prev = u_prev + dk; if u_prev > u_max u_prev = u_max; elseif u_prev < u_min u_prev = u_min; end dk = u_prev - u_old; % 实际生效的增量,回写给状态判断这一行 dk = u_prev - u_old 是关键。不加它,控制器内部以为输出到了 8,实际执行器停在 5,等误差反号之后要先把内部这笔「虚账」还完才肯掉头,超调就是从这里来的。
微分冲击是另一个常见问题。给定值阶跃跳变时,de/dt 理论上是冲激,实际代码里就是采样点上的一个尖峰。把微分作用改到 y 上可以绕开:
function [du, st] = pid_inc_dy(Kp, Ki, Kd, ek, yk, st) % 微分作用在输出 y 上,避免给定跳变时产生微分冲击 dy = yk - st.y1; du = Kp*(ek - st.e1) + Ki*ek - Kd*dy; st.e1 = ek; st.y1 = yk; end这样改的代价是微分不再响应给定的前馈变化,跟踪型应用里上升时间会略变长。跟踪精度要求高的场合,可以保留 e 的微分,但给 Kd 串一个一阶低通:Kd_lp = Kd * alpha,alpha 取 0.1 到 0.3,实测能把毛刺压掉又不太伤相位。
5. 增量式PID的进阶玩法与结果验证
5.1 增量式与位置式在同一对象上的对比实验
同一对象、同一组 Kp/Ki/Kd,两个控制器并排跑,差异一眼能看出来。把两种实现都包成函数,签名保持一致:
[u_inc, y_inc] = sim_inc(Kp, Ki, Kd, T, N, a, b, d, u_max, u_min, r); [u_pos, y_pos] = sim_pos(Kp, Ki, Kd, T, N, a, b, d, u_max, u_min, r); plot((0:N-1)*T, y_pos, '--', (0:N-1)*T, y_inc, '-', 'LineWidth', 1.2); legend('位置式','增量式'); grid on; xlabel('t/s'); ylabel('y');位置式那版要显式维护积分累加器,并在每次更新后判断限幅状态。跑出来通常能看到:给定阶跃阶段两条曲线几乎重合,差异出现在给定突变、手动切换、输出饱和恢复这三个时刻。增量式在这三处的输出更连贯,位置式则容易出现一次明显的跳变。
5.2 级联PID:外环位置式、内环增量式
温度-流量、位置-速度这类双环结构里,常见做法是外环用位置式、内环用增量式。外环的输出含义是「给内环的设定值」,本身就是绝对量,用位置式更直观;内环直接驱动变频器或 PWM 占空比,用增量式省状态、切换平滑。
% 外环:位置式,输出作为内环给定 [r_inner, st_out] = pid_pos(Kp1, Ki1, Kd1, r - y_out, st_out); % 内环:增量式,输出到执行器 [du_in, st_in] = pid_inc_gain(Kp2, Ki2, Kd2, r_inner - y_in, st_in);两环的采样周期一般差 5 到 10 倍,内环快、外环慢。写仿真时要按各自周期分别积分,不能共用一个循环节拍,否则内环的积分项会算错。
5.3 从仿真到C代码的验证清单
仿真跑通之后往单片机或 PLC 搬之前,逐条对一遍:
| 检查项 | 仿真里怎么验 | 移植后怎么验 |
|---|---|---|
| 系数溢出 | A/B/C 用定点数截断重跑 | 打印中间值,看是否回绕 |
| 采样周期一致 | T 与定时器中断周期对齐 | 用示波器量中断间隔 |
| 状态初始化 | struct 显式赋 0 | 上电首拍手动清 e1、e2 |
| 限幅位置 | 累加后、赋值前 | 限幅分支加断点 |
| 手动/自动切换 | 手动段强制 u_prev 跟随 | 切换瞬间看 u 有无跳变 |
| 抗饱和反算 | dk 扣回后重跑饱和场景 | 长时间顶限幅后撤给定 |
验证最有效的一招是把同一组输入数据分别喂给 MATLAB 和 C 代码,逐拍比对 du 的数值。定点实现下允许最后一两位差异,如果从某一拍开始整体偏移,多半是状态更新顺序写反了——MATLAB 里先算 du 再挪历史,C 代码里写成先挪历史再算 du,误差就会一路累积下去。
本文还有配套的精品资源,点击获取