PID 控制器:让系统学会纠偏
这是控制算法讲解的第一讲,本人水平有限,尽可能地讲述我对PID的理解,与现实有所偏差请见谅。正文将按照“被控对象建模 → 开环响应 → 闭环反馈 → P / I / D 各项影响→ 限幅与抗积分饱和等补偿 → 实战演示”的顺序展开。
本课目标
- 能写出误差 (e(t)=r(t)-y(t)) 并画出负反馈结构;
- 能解释比例、积分和微分分别影响什么;
- 能用阶跃响应判断超调、调节时间和稳态误差;
- 能说明执行器限幅为什么会导致积分饱和;
- 能把控制器放入 Simulink,并留下可复现的模型和指标。
先建立一个问题
控制系统真正要解决的问题,不是“让控制器输出一个很大的数”,而是让被控对象在目标变化、扰动和自身动态存在时,仍然按照预期运动。
例如,一个位置系统希望从 0 移动到 1。我们关心的不只是最后是否到达 1,还要问:
- 到达目标之前有没有冲过头?
- 需要多长时间才算稳定?
- 控制输入有没有碰到执行器上下限?
- 目标改变时,控制量是否出现尖峰?
- 参数变化以后,系统是否仍然有足够的稳定裕度?
本课用一个简单的二阶对象回答这些问题。它不是某一个特定电机或变换器的完整模型,但足够用来建立可迁移的控制直觉。
被控对象:先看系统本身
当前实验场使用的名义对象为:
\[ G(s)=\frac{18}{s^2+5.2s+20} \]
把它写成时域方程:
\[ \ddot y(t)+5.2\dot y(t)+20y(t)=18u(t) \]
也可以写成更直观的“质量—阻尼—弹簧”形式:
\[ m\ddot y+b\dot y+ky=K_u u \]
在本实验中:
m = 1
b = 5.2
k = 20
K_u = 18
其中 y(t) 是输出,u(t) 是控制输入。k y(t) 表示偏离零点后产生的恢复力,b y'(t) 表示与速度相关的阻尼力,18u(t) 表示控制器对对象施加的作用。
这个模型有两个重要提醒:
- 对象本身会运动,不会因为目标值改变就瞬间到达目标;
- 控制器的输出最终还要经过执行器,实际控制量不一定等于 PID 的未限幅计算值。
从开环到闭环
开环:不看结果的控制
开环控制只根据预先设定的输入驱动对象:
\[ u(t)\longrightarrow G(s)\longrightarrow y(t) \]
它没有比较目标值和实际输出,因此无法主动纠正模型误差、负载变化或外部扰动。如果对象参数发生变化,原来计算好的输入可能就不再合适。
下面的 Simulink 模型在开环前向通道中加入了一个增益 K=0.8,并额外计算误差 e(t)=r(t)-y(t) 供观察:
从图中可以同时看到目标输入 r(t)、对象输出 y(t) 和误差 e(t)。需要特别注意:这里的误差信号只被计算和显示,并没有回到前向通道,因此它不会参与控制,系统本质上仍然是开环。若被控对象为
\[ G(s)=\frac{18}{s^2+5.2s+20} \]
则其低频增益为 G(0)=0.9,加入 K=0.8 后,单位阶跃输入的理论稳态输出约为 0.72,稳态误差约为 0.28。这正是开环系统无法根据误差自动修正的表现。
闭环:用误差驱动修正
闭环系统把输出反馈回来,与目标值比较:
\[ e(t)=r(t)-y(t) \]
其中:
r(t):目标值或设定值;y(t):实际输出;e(t):误差。
闭环模型的阶跃响应如下:
与开环模型不同,闭环中的误差 e(t) 会进入 PID 控制器。控制器根据误差产生控制量,输出 y(t) 再经过负反馈返回比较器,形成“比较—调节—响应—再比较”的持续纠偏过程。图中 y(t) 虽然先出现短暂过冲和回落,但最终能够回到目标值 r(t) 附近,体现了反馈调节对稳态误差的修正作用。
控制器根据误差产生控制输入:
\[ r(t)\longrightarrow e(t)\longrightarrow C(s)\longrightarrow u(t)\longrightarrow G(s)\longrightarrow y(t) \]
负反馈的关键不是“把输出简单地减掉”,而是让系统拥有一个持续的纠偏通道。只要输出还没有达到目标,误差就会继续影响控制器。
对于单位负反馈,开环传递函数和闭环传递函数分别是:
\[ L(s)=C(s)G(s) \]
\[ T(s)=\frac{Y(s)}{R(s)}=\frac{L(s)}{1+L(s)} \]
后面的 Bode 图主要观察 L(s),时域响应则观察闭环 T(s) 的实际表现。两者是同一个闭环问题的两种观察角度。
PID 三项分别在做什么
连续时间 PID 控制器写成:
\[ u(t)=K_p e(t)+K_i\int_0^t e(\tau)\,d\tau+K_d\frac{de(t)}{dt} \]
为了理解每一项,不要先把它们记成三个旋钮,而要分别问:它在看什么信息?它改变了什么行为?它可能带来什么代价?
P:现在差多少
比例项为:
\[ u_P(t)=K_p e(t) \]
误差越大,控制作用越强。增大 Kp 通常会让系统更快地响应,但也可能带来更大的超调、振荡和执行器压力。
只使用 P 控制时,系统不一定能消除阶跃目标下的稳态误差。对当前对象,直流增益为:
\[ G(0)=\frac{18}{20}=0.9 \]
如果只有比例控制,单位阶跃的稳态误差近似为:
\[ e_{ss}=\frac{1}{1+K_pG(0)} \]
当 Kp = 3 时:
\[ e_{ss}\approx\frac{1}{1+3\times0.9}=0.270 \]
这说明“把 Kp 调大”可以减小误差,但不等于从结构上保证误差为零。
I:过去积累了多少
积分项为:
\[ u_I(t)=K_i\int_0^t e(\tau)\,d\tau \]
只要误差长期存在,积分状态就会持续变化。它可以提供一个长期保持的控制量,消除许多类型的稳态误差。
积分项的代价是它有记忆:
- 误差持续存在时,积分会不断累积;
- 执行器饱和时,增加积分不一定能增加实际输出;
- 目标值改变后,旧的积分状态可能仍然影响当前控制量。
因此,使用积分时必须同时考虑执行器限幅和抗积分饱和。
D:接下来会怎么变化
理想微分项为:
\[ u_D(t)=K_d\frac{de(t)}{dt} \]
它不主要关注误差有多大,而是关注误差正在变大还是变小、变化有多快。当系统快速接近目标时,误差正在减小,D 项可以提供反向作用,帮助系统提前减速,因此常表现为增加阻尼、减小超调。
微分项的代价是对快速变化和测量噪声敏感。若直接对误差微分,目标值突然变化也会进入 D 项:
\[ \frac{de}{dt}=\frac{dr}{dt}-\frac{dy}{dt} \]
这就是目标值阶跃时出现微分冲击的原因。当前交互演示保留了这个现象,方便观察;工程实现通常会让 D 项主要作用于测量值,或先对目标轨迹做平滑处理。
离散 PID:控制器每一拍做什么
数字控制器不是连续地计算,而是每隔采样周期 T_s 更新一次。令第 k 个采样点的目标、输出和误差分别为 r[k]、y[k] 和 e[k],则:
\[ e[k]=r[k]-y[k] \]
比例项直接计算:
\[ P[k]=K_p e[k] \]
积分项把当前误差累加到内部状态:
\[ I_{state}[k]=I_{state}[k-1]+T_s e[k] \]
输出形式的积分分量为:
\[ I[k]=K_i I_{state}[k] \]
最简单的后向差分微分为:
\[ D[k]=K_d\frac{e[k]-e[k-1]}{T_s} \]
因此,最基本的离散 PID 计算顺序是:
e[k] = r[k] - y[k]
P[k] = Kp * e[k]
Istate[k] = Istate[k-1] + Ts * e[k]
I[k] = Ki * Istate[k]
D[k] = Kd * (e[k] - e[k-1]) / Ts
u_raw[k] = P[k] + I[k] + D[k]
u[k] = limit(u_raw[k])
这里的 u_raw[k] 是控制器想要的控制量,u[k] 是经过执行器限幅后真正施加给对象的控制量。
从离散公式到 STM32 代码
前面的公式说明了 PID 每一拍要算什么,但数字控制器真正落地时,还要回答四个工程问题:反馈值从哪里来?什么时候计算?输出如何交给执行器?积分和历史量如何保存?STM32 代码正是把这四件事接起来。
先看参考工程的 PID 结构
你提供的 STM32F103C8 标准外设库工程把 PID 状态集中放在 PID_t 结构体中。核心字段可以这样对应:
| C 变量 | 离散控制含义 | 在循环中的作用 |
|---|---|---|
Target |
目标值 r[k] |
希望系统达到的速度、位置或角度 |
Actual |
反馈值 y[k] |
来自编码器或 ADC 的测量量 |
Err0 |
当前误差 e[k] |
Target - Actual |
Err1 |
上一次误差 e[k-1] |
为差分微分保留历史 |
ErrInt |
积分累加状态 | 累积长期误差 |
Out |
控制输出 u[k] |
送给电机驱动器或 PWM |
OutMax / OutMin |
输出限幅 | 模拟执行器的最大正、负能力 |
参考代码的核心计算是:
p->Err1 = p->Err0;
p->Err0 = p->Target - p->Actual;
p->ErrInt += p->Err0;
p->Out = p->Kp * p->Err0
+ p->Ki * p->ErrInt
+ p->Kd * (p->Err0 - p->Err1);
if (p->Out > p->OutMax) { p->Out = p->OutMax; }
if (p->Out < p->OutMin) { p->Out = p->OutMin; }这段代码与离散公式是一一对应的:先保存上一拍误差,再读取当前反馈,更新积分和微分,最后对总输出限幅。它短小、容易移植,很适合第一次理解“控制算法如何变成 C 语言”。
但要注意,它是简化实现:积分和微分都隐含了采样周期,微分直接作用于误差,ErrIntThreshold 超限时会把积分清零,而且没有把执行器饱和反馈到积分器。课程示例会保留这段代码作为参考,同时在下载版本中使用显式 T_s、测量值微分、积分限制和条件积分。
标准库工程的调用链
参考工程中,控制链可以画成:
TIM1 更新中断(1 ms)
│
├─ 每 40 次进入一次 Mode21_Tick / Mode22_Tick
│
├─ TIM3 编码器计数 → 位置反馈或位置差分 → Actual
│
├─ PID_Update() → Out
│
└─ Motor_SetPWM() → 方向 GPIO + TIM2 PWM → 电机
Timer.c 将 TIM1 配成:
TIM_Prescaler = 72 - 1;
TIM_Period = 1000 - 1;在定时器时钟为 72 MHz 时,更新频率为:
\[ f_{update}=\frac{72\ \mathrm{MHz}}{72\times1000}=1\ \mathrm{kHz} \]
所以中断间隔是 1 ms。Mode21_Tick() 和 Mode22_Tick() 每 40 次中断才计算一次 PID,因此控制器实际更新周期约为:
\[ T_s=40\times1\ \mathrm{ms}=40\ \mathrm{ms} \]
这就是为什么 T_s 不能只写在公式里:它必须和定时器调度真正一致。
速度环和位置环的反馈不同
参考工程的定速控制并没有直接测量速度,而是用两个采样时刻的编码器位置差近似速度:
PrevLocation = CurrLocation;
CurrLocation = Encoder_GetLocation(1);
SpeedPID.Actual = (int16_t)(CurrLocation - PrevLocation);
PID_Update(&SpeedPID);
Motor_SetPWM(1, SpeedPID.Out);可以把它理解为:
\[ v[k]\approx\frac{q[k]-q[k-1]}{T_s} \]
原工程把 T_s 和比例关系吸收到参数与单位定义中,因此 Actual 不是标准国际单位制的速度。教学和调参时必须说明反馈单位,否则同样的 K_p、K_i、K_d 不能直接搬到另一个编码器或减速比的系统。
定位置控制则直接使用编码器计数:
LocationPID.Actual = Encoder_GetLocation(1);
PID_Update(&LocationPID);
Motor_SetPWM(1, LocationPID.Out);两者共用 PID 结构,但控制对象、反馈单位、参数和采样解释并不相同。倒立摆模式进一步使用 ADC 角度反馈作为内环,并用位置环输出修改角度目标,这已经是串级控制的雏形。
PID 输出如何变成带方向的 PWM
Motor_SetPWM() 对 PID 输出的正负号做了方向分离:
Out > 0:方向 GPIO 设为正向,PWM 使用 Out
Out < 0:方向 GPIO 设为反向,PWM 使用 -Out
Out = 0:PWM 为零,电机停止
因此 PID 输出的范围必须和 PWM 比较寄存器的范围对应。例如参考工程的 PWM 周期是 100,PID 输出限幅是 -100 到 100,两者正好可以用百分比方式对应。换一个定时器周期,就必须重新设计输出映射,不能直接把 PID 数字写入比较寄存器。
标准库版本:推荐的教学模板
标准库完整示例可查看:STM32 标准库 PID 示例说明
下面是它的核心调用方式。与参考工程相比,这个版本把 T_s=0.04 s 明确写进 PID 初始化,并默认对测量值微分,以减小目标值阶跃造成的微分冲击:
static PID_t speed_pid;
static volatile uint8_t control_tick_pending;
static int16_t previous_location;
PID_Init(&speed_pid,
0.30f, 0.30f, 0.00f,
0.040f,
-100.0f, 100.0f,
100.0f);
/* 每 1 ms 调用;每 40 次产生一个控制更新请求。 */
void ControlLoop_On1msTick(void)
{
static uint8_t divider;
if (++divider >= 40U)
{
divider = 0U;
control_tick_pending = 1U;
}
}
/* 主循环中调用;这里完成一次采样、计算和 PWM 更新。 */
void ControlLoop_Run(void)
{
int16_t location;
int16_t speed_feedback;
if (control_tick_pending == 0U)
{
return;
}
control_tick_pending = 0U;
location = Encoder_GetLocation(1);
speed_feedback = location - previous_location;
previous_location = location;
Motor_SetPWM(1,
(int16_t)PID_Update(&speed_pid,
speed_pid.target,
(float)speed_feedback));
}这里需要区分“节拍”和“计算”:定时器中断只负责按固定时间产生请求,主循环完成反馈读取、PID 计算和输出更新。这样中断较短,也更容易加入串口记录、故障处理和其他任务。真正的高频电流环如果主循环无法保证时序,则应使用更严格的中断或 DMA 调度,但仍然要避免在中断里放阻塞操作。
HAL 版本:基于 CubeMX 的等价实现
HAL 完整示例可查看:STM32 HAL PID 示例说明
HAL 版本的初始化由 CubeMX 生成,应用代码只在 USER CODE 区域调用。示例的主要关系是:
static PID_Controller_t speed_pid;
static volatile uint8_t control_tick_pending;
void ControlLoop_HAL_Init(void)
{
PID_Controller_Init(&speed_pid,
0.30f, 0.30f, 0.00f,
0.040f,
-100.0f, 100.0f,
100.0f);
HAL_TIM_Base_Start_IT(&htim_control);
HAL_TIM_PWM_Start(&htim_pwm, TIM_CHANNEL_1);
HAL_TIM_Encoder_Start(&htim_encoder, TIM_CHANNEL_ALL);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint8_t divider;
if (htim == &htim_control)
{
if (++divider >= 40U)
{
divider = 0U;
control_tick_pending = 1U;
}
}
}
void ControlLoop_HAL_Run(void)
{
int16_t feedback;
float output;
if (control_tick_pending == 0U)
{
return;
}
control_tick_pending = 0U;
feedback = Encoder_ReadDelta();
output = PID_Controller_Update(&speed_pid,
speed_pid.target,
(float)feedback);
Motor_SetSignedPwm(output);
}在 HAL 工程中,htim_control、htim_pwm、htim_encoder 是 CubeMX 生成的定时器句柄;MOTOR_IN1_GPIO_Port、MOTOR_IN1_Pin 等方向引脚宏也需要换成你的工程名称。HAL_TIM_PeriodElapsedCallback() 只置位标志,主循环再执行 PID,可以避免在中断中调用 HAL_ADC_PollForConversion()、HAL_Delay()、printf() 或显示刷新。
参考代码与工程版本的差异
| 处理点 | 参考工程 | 教学模板 |
|---|---|---|
| 积分 | ErrInt += Err0,采样周期隐含在参数中 |
integral += Ki * error * Ts |
| 微分 | 对误差做差分 | 默认对测量值微分,减少目标值微分冲击 |
| 条件积分 | 超过阈值时直接清零积分 | 饱和且误差会继续加重饱和时暂停积分 |
| 积分保护 | 没有独立积分限幅 | 对积分项设置限幅 |
| 输出 | OutMin / OutMax 限幅 |
同样限幅,并保留未限幅计算逻辑供诊断 |
| 调度 | TIM1 中断内直接执行模式 Tick | 定时器产生请求,主循环完成控制计算 |
| 反馈单位 | 编码器计数或位置差 | 仍需由工程师根据编码器和采样周期明确标定 |
这不是说参考代码“不能用”,而是要知道它的适用边界。它适合帮助入门和快速验证;当系统加入噪声、执行器饱和、采样延迟、多个控制环或功率级保护后,就需要把采样周期、单位、限幅和调度写得更明确。
ADC、编码器和 PWM 的接入检查表
把 PID 接到真实硬件前,按下面顺序检查:
- 先单独读取 ADC 或编码器,确认静止时反馈稳定、方向正负正确、计数不会异常跳变。
- 再单独输出低占空比 PWM,确认正负输出对应的电机方向正确,且
0输出确实会停止。 - 固定控制周期,记录实际
T_s,不要用主循环“运行得差不多快”代替采样时基。 - 先令
Ki=0、Kd=0,只用 P 项确认误差方向和对象响应方向正确。 - 再加入 I 项,观察积分项、输出限幅和解除饱和后的恢复过程。
- 最后加入 D 项;优先对测量值微分,并对反馈噪声进行滤波。
- 用串口或示波器同时记录
target、actual、error、output和积分项,才能判断问题来自传感器、方向、参数还是饱和。
代码练习
建议把本节变成一次可验证的练习:
- 把参考工程的
ErrInt += Err0改写为显式ErrInt += Err0 * Ts,说明Ki的数值为什么也要重新整定; - 将直接对误差微分改为对测量值微分,对比目标阶跃时的输出尖峰;
- 将“误差超过阈值就清零积分”改为“饱和方向不利时暂停积分”,观察解除饱和后的恢复差异;
- 将
Ts从40 ms改成20 ms,重新检查速度反馈单位、积分作用和稳定性; - 在不改变 PID 核心函数的情况下,更换编码器和 ADC 反馈接口,体会算法层与硬件层的边界。
当前实验场使用一阶滤波微分,避免直接差分把高频噪声无限放大:
\[ x_D[k]=x_D[k-1]+N T_s\left(e[k]-x_D[k-1]\right) \]
\[ D[k]=K_dN\left(e[k]-x_D[k]\right) \]
其中 N = 30 rad/s 是微分滤波器参数,x_D[k] 是误差的平滑状态。误差变化很慢时,x_D[k] 会接近 e[k],D 项接近零;误差突然改变时,两者短时间内存在差异,D 项就会形成一个快速衰减的动作。
当前采样周期为:
Ts = 0.01 s
这意味着控制器每秒计算 100 次。采样周期越大,离散近似越粗;采样周期越小,计算量和噪声敏感性通常会提高。离散控制的参数不能脱离采样周期单独讨论。
为什么目标值变化会出现尖峰
假设实际输出暂时没有变化,目标值从 1.0 调到 1.1:
\[ e[k-1]=1.0-y[k-1] \]
\[ e[k]=1.1-y[k] \]
当 y[k] 与 y[k-1] 很接近时,误差会在一个采样点内增加约 0.1。对误差做差分后:
\[ \frac{e[k]-e[k-1]}{T_s}\approx\frac{0.1}{0.01}=10 \]
如果 Kd=0.5,理想微分项可能瞬间达到约 5。带滤波后尖峰会被压低并拉宽,但不会自动消失。
如果使用滑块连续拖动,浏览器会连续触发多个目标值变化:
1.0 -> 1.1 -> 1.2 -> 1.3 -> 1.4
每一档都会形成一次短暂的微分动作,所以图上会看到一串尖峰。观察 D 项时,应把“目标值变化造成的瞬态”和“系统输出变化造成的阻尼”分开理解。
执行器限幅与抗积分饱和
现实中的 PWM、驱动器、电机电流环或功率开关都存在输出范围。实验场用 outputLimit 表示执行器限幅:
\[ u[k]=\operatorname{sat}(u_{raw}[k]) \]
例如:
u_raw = 4
执行器上限 = 1
实际控制量 u = 1
这时控制器内部还可能继续计算更大的 u_raw。如果误差仍然朝同一方向,积分器会继续累积,形成积分饱和。
条件积分的条件
条件积分判断的是:当前已经饱和,并且继续积分会不会把饱和推得更深。
当前实时实现的判断可以写成:
\[ \begin{aligned} &u_{raw}>u_{max}\ \text{且}\ e>0 \quad\Rightarrow\quad \text{暂停积分}\\ &u_{raw}<u_{min}\ \text{且}\ e<0 \quad\Rightarrow\quad \text{暂停积分} \end{aligned} \]
其他情况下允许积分。对于对称限幅 u_min=-u_max,它的方向含义是:
| 状态 | 积分动作 |
|---|---|
| 正向饱和,误差为正 | 暂停,避免继续推向正方向 |
| 正向饱和,误差为负 | 允许,让积分释放并退出饱和 |
| 负向饱和,误差为负 | 暂停,避免继续推向负方向 |
| 负向饱和,误差为正 | 允许,让积分释放并退出饱和 |
| 未饱和 | 正常积分 |
条件积分的用途不是让 I 项永远停止,而是让积分只在“有帮助”的方向上工作。它通常可以减少超调、缩短解除饱和后的恢复时间。
交互控件中的四种模式可以这样区分:
- 无抗饱和:积分照常累加,用来观察 windup;
- 积分限幅:把积分状态限制在设定范围内;
- 条件积分:积分会加重饱和时暂停,能帮助退出饱和时继续;
- 反算回馈:根据
u - u_raw主动修正积分状态。
只有在控制量真正触碰执行器限幅,或者积分状态达到积分限幅时,这些模式才会出现明显差别。公平比较时,每切换一次模式都要点击“复位”,否则旧的输出、速度、积分状态和历史曲线会影响观察。
用时域指标读懂一条曲线
调节参数时,不要只看曲线“快不快”,至少记录下面四个指标:
稳态误差
系统稳定后,目标与输出之间的差:
\[ e_{ss}=|r-y(\infty)| \]
稳态误差小,说明系统长期跟踪目标的能力较好,但不代表瞬态一定稳定。
超调量
对正阶跃目标,超过最终目标的最大部分通常写成百分比:
\[ M_p=\frac{y_{max}-r}{|r|}\times100\% \]
超调过大可能意味着阻尼不足、比例增益过高、积分作用过强或执行器饱和后积分没有释放。
上升时间
输出从目标值的低百分比区间进入高百分比区间所需的时间。它反映系统启动响应的快慢,但不同教材对区间定义可能不同,记录结果时要写清楚定义。
调节时间
输出进入并保持在目标附近某个误差带内所需的时间。实验场使用约 2% 的误差带来给出教学估计。调节时间长,通常说明振荡衰减慢或系统阻尼不足。
用 Bode 图检查频域证据
在实验场切换到“Bode 频响”,观察的是名义开环:
\[ L(s)=C(s)G(s) \]
重点看四个量。
低频增益
低频增益反映系统对慢变化目标和慢变化扰动的作用能力。加入积分后,控制器在低频附近的增益会显著提高,因此更有利于减小稳态误差。
当前页面显示 0.1 rad/s 附近的开环增益,适合作为不同 Ki 设置之间的直观比较,但它不是所有工况下的完整性能结论。
增益穿越频率
增益穿越频率 ωgc 是满足下式的频率:
\[ |L(j\omega_{gc})|=1 \]
用 dB 表示就是幅值穿过 0 dB 的位置。它通常与闭环响应速度和带宽相关,但不是“越大越好”。穿越频率越高,对采样、延迟、噪声和未建模高频动态的要求通常越高。
相位裕度
在增益穿越频率处,相位距离 -180° 还剩多少:
\[ PM=180^\circ+\angle L(j\omega_{gc}) \]
相位裕度较大,通常表示系统对额外相位延迟更有余量,时域中往往更不容易振荡;相位裕度过小,则需要警惕超调、振荡和参数变化后的失稳。
增益裕度
在相位达到 -180° 的频率处,距离 0 dB 还剩多少增益余量。它反映系统还可以承受多大的开环增益变化。
需要注意,Bode 图给出的指标基于未限幅、未加噪声的名义线性模型。即使 Bode 图显示裕度充足,执行器饱和、采样延迟、死区、摩擦和持续扰动仍可能让实时系统表现变差。因此必须把频域指标和时域波形放在一起看。
参数调节顺序
建议使用“先 P、再 I、最后 D”的教学顺序,但每一步都要观察控制输入:
- 将
Ki=0、Kd=0,只调Kp,让系统先有明显且不过分的响应。 - 逐渐增加
Kd,观察超调、振荡和 D 分量如何变化;同时避免目标值快速拖动造成的微分冲击干扰判断。 - 增加
Ki,观察稳态误差下降以及积分分量逐步建立的过程。 - 降低执行器限幅,比较无抗饱和、积分限幅、条件积分和反算回馈。
- 切换到 Bode 图,记录低频增益、
ωgc、相位裕度和增益裕度。 - 回到时域,确认超调、调节时间、稳态误差和执行器状态是否仍然满足要求。
不要在一次实验中同时改变所有参数。否则即使曲线发生变化,也很难判断变化来自哪一个机制。
实验任务:用证据而不是感觉调参
实验 A:只用比例控制
设置:
Ki = 0
Kd = 0
目标量 = 1
记录:
- 最终输出是多少?
- 稳态误差是否为零?
- 提高
Kp后,响应速度和超调怎样变化?
实验 B:加入积分
保持 Kp 不变,逐步增加 Ki。观察:
- 稳态误差是否减小;
- I 分量是否逐步积累;
- 响应是否变得更容易振荡;
- 控制量是否更容易接近执行器限幅。
实验 C:加入微分
保持 Kp、Ki 不变,逐步增加 Kd。分别用“阶跃目标”和“手动目标”观察:
- 系统接近目标时,D 项是否产生制动作用;
- 快速拖动目标滑块时,控制量是否出现多个尖峰;
- 增加传感器噪声后,D 项是否更敏感。
实验 D:观察抗积分饱和
建议设置:
目标量 = 1.5
执行器限幅 = 1
Kd = 0
Ki = 3 或 5
分别选择四种抗饱和模式。每次切换后先点击“复位”,再点击“开始”,并勾选 I 和“限幅前”曲线。重点比较:
- 积分分量是否持续增长;
- 未限幅控制量与实际控制量差多少;
- 解除饱和后超调有多大;
- 系统恢复到目标附近需要多久。
实验 E:目标值为零的状态检查
将目标量设为 0,目标信号设为“手动目标”,点击实时仿真区域的“复位”后再运行。此时如果输出仍有明显运动,应依次检查:
- 是否存在持续扰动;
- 是否加入了传感器噪声;
- 是否保留了之前的输出、速度或积分状态;
- 是否使用了正弦或方波目标模式;
Kd是否因为目标突变带来微分瞬态。
目标值为零只表示最终参考值为零,不代表已有的输出和速度会瞬间消失。
前馈与复合控制:下一步的工程扩展
本课先把反馈 PID 讲清楚。后续如果已知对象模型,可以加入前馈:
\[ u(t)=u_{ff}(t)+u_{fb}(t) \]
其中 u_ff 根据目标或可测扰动提前计算,u_fb 由 PID 根据误差进行修正。对于当前的静态弹簧对象,恒定目标下可以用一个简单的静态前馈抵消弹簧力:
\[ u_{ff}=\frac{k r}{K_u} \]
反馈仍然不可省略,因为模型参数、摩擦、负载和扰动不可能完全准确。前馈负责“提前做”,反馈负责“纠偏”,这就是复合控制的基本思想。它属于 PID 基础完成后的进阶内容,不作为本课参数调节的前置条件。
本课验收标准
完成本课后,应该能够不看答案解释下面几件事:
- 为什么 P 项响应快,但单独使用 P 可能留下稳态误差;
- 为什么 I 项能消除稳态误差,却可能引起积分饱和;
- 为什么 D 项可以增加阻尼,却容易放大噪声并产生微分冲击;
- 离散积分为什么每次只增加
K_i T_s e[k]; - 条件积分在什么情况下暂停、什么情况下恢复;
- 为什么
ωgc、相位裕度和时域超调需要一起判断; - 为什么目标值为零时,系统仍可能因为已有状态、扰动或初始瞬态而运动。
视频讲解:用伯德图整定 PID 参数
下面的视频从频域角度介绍如何利用伯德图整定 PID 参数。观看时可以重点留意低频增益、增益穿越频率和相位裕度,它们与本课前面介绍的稳态误差、响应速度和稳定裕度相互对应。
进入课程实验
请先打开控制基础系列首页,完成参数实验场,然后再在本页补充推导、仿真模型和练习记录。
预备材料
- 胡寿松《自动控制原理》:控制系统基本概念、数学模型和时域分析;
- Åström and Murray 的 Feedback Systems;
- Brian Douglas 的 Control Systems Lectures;
- 本项目的 PID 阶跃响应实验。
- 《控制之美(卷1)》:补充传递函数、频率响应和状态空间之间的联系,在线阅读;
- 《控制之美(卷2)》:为后续学习最优控制、MPC 和卡尔曼滤波做准备,在线阅读;
- 《自动控制原理(第八版)》:作为经典控制、时域分析、频域分析和系统校正的主参考书,在线阅读。
第一版正文不追求一次写完。每次增加一个推导或仿真结果,都应同步补充参数、假设、指标和验证边界。