跳到主要内容

2. PID 工程

PID 公式很短,但工程里真正决定稳定性的,往往是采样周期、单位、限幅、滤波和异常处理。很多“控制器不好用”的问题,不是理论错了,而是这些细节没有处理。

2.1 离散实现

真实控制器按固定频率运行。例如:

  • 机械臂关节控制:100Hz 到 1kHz 常见。
  • 四足底层电机控制:500Hz 到数 kHz 常见。
  • 上层策略或 VLA 推理:几 Hz 到几十 Hz 常见。

连续 PID:

离散实现通常写成:

这里的 非常重要。控制频率变了,积分和微分的数值都会变。如果代码里没有显式使用 dt,换频率后参数很容易失效。

2.2 输出限幅

电机、舵机、液压执行器都有上限:

  • 最大力矩
  • 最大速度
  • 最大电流
  • 最大位置范围
  • 最大温度或功率

所以控制器输出必须限幅:

def clamp(value, lower, upper):
return max(lower, min(upper, value))

torque_cmd = clamp(torque_cmd, -max_torque, max_torque)

限幅不是“保守”,而是把控制器接入真实硬件的必要接口。没有限幅,仿真里也许只是动作夸张,真机上可能触发驱动器保护甚至损坏结构。

2.3 抗积分饱和

积分项最常见的问题是:输出已经到达上限,积分还在继续累积。

例如目标很远,控制器想输出 100 N·m,但电机最多只能输出 20 N·m。如果积分项继续累积,等关节接近目标时,积分项仍然很大,系统会继续往前冲,产生巨大超调。

常见抗积分饱和方法有三种。

第一种是积分限幅:

integral += error * dt
integral = clamp(integral, -integral_limit, integral_limit)

第二种是输出饱和时暂停积分:

raw_output = kp * error + ki * integral + kd * derivative
output = clamp(raw_output, lower, upper)

if raw_output == output:
integral += error * dt

第三种是反算抗饱和,把饱和前后的差值反馈给积分项。它更平滑,但实现也更复杂。

入门阶段优先掌握前两种就够了。

2.4 微分滤波

微分项对噪声敏感。传感器读数如果有抖动,差分后会被放大:

很小,哪怕误差只抖了一点,微分项也可能很大。

工程里常见做法:

  • 对测量速度做低通滤波。
  • 使用编码器 / 驱动器估计的速度,而不是自己从位置差分。
  • 做 derivative on measurement:对测量值求微分,而不是对误差求微分,避免目标突变导致 D 项尖峰。

关节定点控制常写成:

这等价于目标速度为 0 时的 PD 控制,也避免了目标位置阶跃变化时 D 项产生过大冲击。

2.5 单位检查

机器人控制里,单位错会直接毁掉调参结果。常见坑包括:

变量推荐单位常见错误
角度rad用 degree 调参
角速度rad/s把 rpm 当 rad/s
力矩N·m忽略减速比
位置mmm 和 m 混用
时间sms 没除以 1000

如果你发现参数看起来离谱,比如 Kp 要调到几十万才有反应,优先检查单位,而不是继续调参。

2.6 PID 代码

下面是一个可读性优先的 PID 骨架:

class PIDController:
def __init__(self, kp, ki, kd, output_limit, integral_limit):
self.kp = kp
self.ki = ki
self.kd = kd
self.output_limit = output_limit
self.integral_limit = integral_limit
self.integral = 0.0
self.prev_error = 0.0

def reset(self):
self.integral = 0.0
self.prev_error = 0.0

def step(self, target, measurement, dt):
error = target - measurement

self.integral += error * dt
self.integral = clamp(
self.integral,
-self.integral_limit,
self.integral_limit,
)

derivative = (error - self.prev_error) / dt
raw = self.kp * error + self.ki * self.integral + self.kd * derivative
output = clamp(raw, -self.output_limit, self.output_limit)

self.prev_error = error
return output

实际项目里还会加入:

  • dt 过小或异常时跳过更新。
  • 目标突变时重置积分项。
  • 根据模式切换清空历史状态。
  • 输出命令做斜率限制,避免瞬间跳变。

2.7 排错表

现象可能原因优先处理
响应很慢 太小,输出限幅太低增大 P,检查限幅
到目标附近来回振荡 太大, 太小降 P 或加 D
过冲很大阻尼不足,积分过强加 D,减 I
长期差一点到不了有负载或摩擦偏置少量加 I 或加前馈
输出忽大忽小微分放大噪声过滤速度,减 D
仿真好,真机抖摩擦、延迟、结构柔性、频率不同降增益,从低速开始

下一章进入状态空间和 LQR。它们不是替代 PID 的“高级玩具”,而是当系统状态耦合明显时,用模型系统化求反馈增益的方法。