一、PID 核心理论与作用

PID (比例-积分-微分) 控制器是一种应用最广泛的反馈控制方法,用于计算所需的控制量 (如电机力矩、电压) 来消除系统的误差。

1. PID 控制器的总公式

  • 误差,定义为 目标值 - 当前测量值
  • 分别是比例、积分、微分系数(增益),是需要调整的参数。

2. 三个分量的作用

分量 名称 公式项 作用描述 (基于当前/历史/未来) 效果总结
比例项 (Proportional) 当前 误差有多大,就产生多大的修正力。 提高系统响应速度。
积分项 (Integral) 历史 误差的累积。即使当前误差为零,也能提供持续的修正力。 消除稳态误差 (Steady-State Error)。
微分项 (Derivative) 误差变化的速度 (趋势)。误差变化越快,修正力越大。 抑制振荡和过冲 (Overshoot),增加阻尼。

二、具身应用与调优挑战

在具身智能中,PID 控制器的调优是核心挑战。每个分量在解决一个问题的同时,也会引入新的副作用。

1. 具身智能中的三大挑战

性能目标 挑战描述 主要由哪个 影响
快速性 系统启动慢,到达目标时间长。 (应提高)
准确性 稳定后停留在目标附近(稳态误差)。 (应提高)
稳定性 冲过目标,并来回振荡。 (应提高)

2. 分量副作用与振荡类型

分量 解决的挑战 (优点) 引入的副作用 (缺点) 振荡类型
使系统快速响应 稳态误差 (需要持续非零误差才能抵抗负载) 轻微/快速振荡
消除稳态误差 (抵抗重力/摩擦) 低频振荡积分饱和 (I项记忆太强) 慢速、大幅度 摆动
抑制振荡和过冲 (预判刹车) 对噪声敏感 (放大传感器噪声) 高速、小幅度 抖动 (Chattering)

3. 核心调优原则 (Z-N 法简述)

调优顺序应为:,每次只调整一个参数。

    • 设为
    • 逐渐增大 直到系统开始出现持续的等幅振荡。记录此值 (临界增益)。
    • 设置
    • 保持 不变。
    • 从小值开始逐渐增大 ,直到稳态误差被消除
    • 如果出现低频、大幅度振荡,则减小
    • 保持 不变。
    • 从小值开始逐渐增大 ,直到过冲和振荡被有效抑制
    • 如果系统在目标点附近出现高频、小幅度抖动,则减小

三、两种振荡的区别

特征 项过大引起的低频振荡 项过大引起的高频抖动
频率/速度 慢速、长周期 (例如 1-2 秒一次摆动) 高速、短周期 (电机快速抽搐)
幅度 大幅度冲过目标 小幅度颤振、发抖
原因 项记忆累积过多,导致修正过大。 项放大了传感器噪声
解决 太大 减小 太大 减小 或加滤波器。

四、PID实践

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
!pip install numpy matplotlib
import numpy as np
import matplotlib.pyplot as plt # <--- 确保你导入了 pyplot 并将其命名为 plt

# =================================================================
# 1. PID 控制器类
# =================================================================
class PID:
def __init__(self,Kp,Ki,Kd,setpoint=0,sample_time=0.01,output_limit=100.0):
self.Kp = Kp
self.Ki = Ki
self.Kd = Kd
self.setpoint = setpoint
self.sample_time = sample_time
self.output_limit = output_limit #控制输出的绝对最大值

self.pre_error = 0
self.integral = 0
#增加积分项的限制,防止积分饱和
self.integral_limit = output_limit / Ki if Ki > 0 else 0

def update(self,measured_value):
#1.计算误差
error = self.setpoint - measured_value

#2.计算积分项
self.integral += error *self.sample_time

# 积分项核心改进,积分限幅
# 当积分超过限幅之后,不再累加
if self.integral_limit > 0:
self.integral = max(-self.integral_limit,min(self.integral_limit,self.integral))

#3.计算微分项
derivative = (error - self.pre_error) / self.sample_time

#4.计算总控制输出
output = self.Kp * error + self.Ki * self.integral + self.Kd * derivative

#5.存储上一次误差
self.pre_error = error

#6.控制输出限幅(模拟电机功率限制)
output = max(-self.output_limit,min(self.output_limit,output))

return output

# 2. 虚拟系统模型(模拟机器人关节的物理特性)
# =================================================================

class MotorSystem:
def __init__(self,initial_angle=0.0,inertia=1.0,damping=0.5,load_torque=0.0):
self.angle = initial_angle #当前角度
self.velocity = 0.0 #当前角速度
self.inertia = inertia #惯性
self.damping = damping #阻尼
self.load_torque = load_torque #恒定负载(模拟重力)

def step(self,control_torque,dt):
# 净力矩 = 控制器输出 - 摩擦阻尼 - 外部负载
net_torque = control_torque - self.damping * self.velocity - self.load_torque

# 加速度 = 净力矩 / 惯性 (牛顿第二定律 f=ma)
acceleration = net_torque / self.inertia

# 速度积分 (欧拉积分)
self.velocity += acceleration * dt

# 位置积分 (欧拉积分)
self.angle += self.velocity * dt

return self.angle

# =================================================================
# 3. 仿真主程序
# =================================================================
def run_simulation(Kp,Ki,Kd,setpoint,simulation_time=5.0,load=0.0):
#初始化参数
dt = 0.01 #采样时间
# 💥 优化:延长仿真时间到 10 秒,以便看到完整的响应过程
simulation_time = 25.0
num_steps = int(simulation_time / dt)

#实例化系统和控制器
motor = MotorSystem(initial_angle = 0.0,load_torque=load)
pid = PID(Kp,Ki,Kd,setpoint = setpoint,sample_time=dt,output_limit=100.0)#限制最大力矩为10

#记录即使数据
time_history = []
angle_history = []

for i in range(num_steps):
t = i*dt

#1.PID计算控制力矩
control_torque = pid.update(motor.angle)

#2.系统步进
current_angle = motor.step(control_torque,dt)

#3.记录数据
time_history.append(t)
angle_history.append(current_angle)

return time_history,angle_history

# =================================================================
# 4. 实践和可视化 (关键调试部分)
# =================================================================
# 目标位置
SETPOINT = 90.0
# 模拟重力/负载,如果 load=0,则没有稳态误差。设置为 1.0 可以看到 P 项的局限性。
LOAD_TORQUE = 1.0

# --- 实验 1: 纯 P 控制 (查看快速性和稳态误差) ---
# Kp 设得较高,让它快点响应,但 Ki 和 Kd 保持 0
Kp1, Ki1, Kd1 = 1.0, 0.0, 0.0
time1, angle1 = run_simulation(Kp1, Ki1, Kd1, SETPOINT, load=LOAD_TORQUE)

# --- 实验 2: 加入 I 控制 (消除稳态误差) ---
# 保持 Kp,增加 Ki 消除稳态误差
Kp2, Ki2, Kd2 = 1.0, 0.1, 0.0
time2, angle2 = run_simulation(Kp2, Ki2, Kd2, SETPOINT, load=LOAD_TORQUE)

# --- 实验 3: 加入 D 控制 (抑制过冲) ---
# 保持 Kp, Ki,增加 Kd 来平抑振荡
Kp3, Ki3, Kd3 = 1.0, 0.1, 0.5
time3, angle3 = run_simulation(Kp3, Ki3, Kd3, SETPOINT, load=LOAD_TORQUE)


# 绘制结果
plt.figure(figsize=(12, 6))

plt.plot(time1, angle1, label=f'P Only (Kp={Kp1})', linestyle='--')
plt.plot(time2, angle2, label=f'PI Control (Kp={Kp2}, Ki={Ki2})', linestyle='-.')
plt.plot(time3, angle3, label=f'PID Control (Kp={Kp3}, Ki={Ki3}, Kd={Kd3})', linestyle='-')

# 绘制目标值
plt.axhline(SETPOINT, color='r', linestyle=':', label='Set Point') # 目标角度 (Set Point) -> Set Point

plt.title(f'PID Control System Response (Load: {LOAD_TORQUE} N·m)') # PID 控制系统响应对比 (负载: {LOAD_TORQUE} N·m) -> PID Control System Response
plt.xlabel('Time (s)') # 时间 (秒) -> Time (s)
plt.ylabel('Angle (degrees)') # 角度 (度) -> Angle (degrees)
plt.legend()
plt.grid(True)
plt.show()

最终绘制结果如图:
final result figure