Skip to content

Commit bd066e1

Browse files
johnjim0816johnjim0816
authored andcommitted
📝 docs: 新增控制器教程章节
1 parent 730b24c commit bd066e1

14 files changed

Lines changed: 1297 additions & 1 deletion

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Datawhale 的具身智能开源教程,目标人群是**求职/转行**:应
3939
按机器人系统能力拆分,4 列。
4040

4141
- **大脑:智能决策** — 强化学习决策、VLA、World-Model
42-
- **小脑:运动控制** — 强化学习控制、运动规划
42+
- **小脑:运动控制** — 强化学习控制、控制器、运动规划
4343
- **感官:感知系统** — VLM、定位与触觉感知
4444
- **工程底座** — 仿真工具、ROS2、CAN 与 MCU 通信、机械结构、模仿学习 + LeRobot
4545

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
| 章节 | 简介 | 状态 |
7474
| :--- | :--- | :--- |
7575
| [强化学习控制](docs/foundations/rl-for-robotics/10.ppo.md) | 把策略学习接到连续控制和机器人任务上 | ✅ 可用 |
76+
| [控制器](docs/foundations/controllers/intro.md) | PID、LQR、MPC、阻抗控制与系统集成教程 | ✅ 可用 |
7677
| [运动规划](docs/foundations/robotics-and-ros2/10.moveit2_basics.md) | Motion Planning 与 MoveIt 2 规划闭环 | ✅ 可用 |
7778

7879
### 感官:感知系统
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
---
2+
title: "1. 闭环与 PID"
3+
sidebar_position: 2
4+
displayed_sidebar: foundationsControllersSidebar
5+
---
6+
7+
# 1. 闭环与 PID
8+
9+
控制器最基本的问题是:目标已经给定,机器人却因为惯性、摩擦、负载、外力和模型误差不会自动到达目标。闭环反馈的作用,就是不断比较“想要的状态”和“真实状态”,再用误差修正控制输入。
10+
11+
## 1.1 开环局限
12+
13+
开环控制只按预先设定的命令执行:
14+
15+
```text
16+
目标命令 -> 执行器 -> 机器人运动
17+
```
18+
19+
它不关心机器人到底有没有到达目标。比如你给一个关节固定电压,让它转 0.5 秒后停止。如果关节负载变重、摩擦变大、供电变低,它实际转过的角度都会变。
20+
21+
闭环控制多了传感器反馈:
22+
23+
```text
24+
目标状态 -> 比较误差 -> 控制器 -> 执行器 -> 机器人
25+
^ |
26+
| v
27+
+-------- 传感器反馈 -------
28+
```
29+
30+
它每个控制周期都问一次:
31+
32+
- 当前状态是多少?
33+
- 目标状态是多少?
34+
- 差了多少?
35+
- 下一步应该输出多大控制量?
36+
37+
机器人关节、移动底盘、机械臂末端、四足足端接触,几乎都离不开这条闭环。
38+
39+
## 1.2 P 控制
40+
41+
比例控制是最简单的反馈:
42+
43+
$$
44+
u(t) = K_p e(t)
45+
$$
46+
47+
其中 $e(t) = x^\*(t) - x(t)$ 是目标和当前状态的误差。$K_p$ 越大,误差带来的修正越强。
48+
49+
直觉上,P 控制像一根弹簧:
50+
51+
- 离目标越远,弹簧拉力越大。
52+
- 离目标越近,拉力越小。
53+
- $K_p$ 太小,系统软绵绵,回目标很慢。
54+
- $K_p$ 太大,系统容易冲过目标,来回振荡。
55+
56+
对一个关节位置控制,可以写成:
57+
58+
$$
59+
\tau = K_p(q^\* - q)
60+
$$
61+
62+
这里 $q^\*$ 是目标关节角,$q$ 是当前关节角,$\tau$ 是输出力矩。
63+
64+
## 1.3 D 控制
65+
66+
只用 P 控制时,关节会因为惯性冲过目标。D 项根据误差变化速度输出控制量:
67+
68+
$$
69+
u_D(t) = K_d \frac{de(t)}{dt}
70+
$$
71+
72+
在关节控制里,常写成:
73+
74+
$$
75+
\tau = K_p(q^\* - q) + K_d(\dot{q}^\* - \dot{q})
76+
$$
77+
78+
如果目标速度 $\dot{q}^\*$ 为 0,那么 D 项会在关节运动过快时产生反向阻尼。它像汽车避震器,主要作用是抑制振荡。
79+
80+
PD 控制是机器人里非常常见的底层控制器。很多强化学习 locomotion 策略并不直接输出电机电流,而是输出目标关节角,再由 PD 控制器在高频闭环里跟踪。
81+
82+
## 1.4 I 控制
83+
84+
积分项累计过去的误差:
85+
86+
$$
87+
u_I(t) = K_i \int_0^t e(\tau)d\tau
88+
$$
89+
90+
它适合处理长期存在的小偏差。例如电机负载导致 P 控制总是差一点到不了目标,积分项会不断累积这个小误差,直到控制输入足够抵消负载。
91+
92+
但积分项也有副作用:
93+
94+
- 大误差持续时间太长时,积分会越积越大。
95+
- 控制量已经饱和时,积分还在继续累积,会导致严重超调。
96+
- 传感器有偏置或低频噪声时,积分会把偏差放大。
97+
98+
这就是工程里常说的**积分饱和**。它不是理论上的小问题,而是真机上很常见的失稳来源。
99+
100+
## 1.5 PID 组合
101+
102+
完整 PID 写成:
103+
104+
$$
105+
u(t) = K_p e(t) + K_i \int_0^t e(\tau)d\tau + K_d \frac{de(t)}{dt}
106+
$$
107+
108+
三项可以这样理解:
109+
110+
| 控制项 | 直觉 | 主要解决 | 常见风险 |
111+
|---|---|---|---|
112+
| P | 误差越大,修正越强 | 响应速度 | 过大振荡,过小迟钝 |
113+
| I | 长期误差慢慢补回来 | 稳态误差 | 积分饱和,超调 |
114+
| D | 看误差变化趋势 | 阻尼和稳定性 | 放大噪声 |
115+
116+
机器人关节控制里,最常见的是 PD 或带很小积分项的 PID。原因是关节编码器速度反馈通常足够好,而积分项在高动态任务里容易带来额外风险。
117+
118+
## 1.6 离散 PD
119+
120+
真实控制器在固定周期内运行。假设控制周期是 `dt`,关节目标是 `q_des`,当前关节角是 `q`,当前关节速度是 `dq`
121+
122+
```python
123+
def pd_control(q_des, dq_des, q, dq, kp, kd):
124+
position_error = q_des - q
125+
velocity_error = dq_des - dq
126+
torque = kp * position_error + kd * velocity_error
127+
return torque
128+
```
129+
130+
如果目标是定点控制,`dq_des` 通常设为 0。如果目标是一条轨迹,`dq_des` 应该来自轨迹本身,而不是简单设 0。
131+
132+
## 1.7 调参流程
133+
134+
PID 调参不要三个参数一起动。推荐顺序:
135+
136+
1. 先只开 P,让系统能朝目标运动。
137+
2. 慢慢增大 P,直到响应够快但还没有严重振荡。
138+
3. 加 D,把过冲和振荡压下来。
139+
4. 如果存在稳定后的长期偏差,再少量加 I。
140+
5. 一旦加 I,必须考虑积分限幅和输出饱和。
141+
142+
下一章会把这些工程细节拆开:离散化、限幅、抗积分饱和、微分滤波和真机调参顺序。
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
---
2+
title: "2. PID 工程"
3+
sidebar_position: 3
4+
displayed_sidebar: foundationsControllersSidebar
5+
---
6+
7+
# 2. PID 工程
8+
9+
PID 公式很短,但工程里真正决定稳定性的,往往是采样周期、单位、限幅、滤波和异常处理。很多“控制器不好用”的问题,不是理论错了,而是这些细节没有处理。
10+
11+
## 2.1 离散实现
12+
13+
真实控制器按固定频率运行。例如:
14+
15+
- 机械臂关节控制:100Hz 到 1kHz 常见。
16+
- 四足底层电机控制:500Hz 到数 kHz 常见。
17+
- 上层策略或 VLA 推理:几 Hz 到几十 Hz 常见。
18+
19+
连续 PID:
20+
21+
$$
22+
u(t) = K_p e(t) + K_i \int e(t)dt + K_d \frac{de(t)}{dt}
23+
$$
24+
25+
离散实现通常写成:
26+
27+
$$
28+
I_k = I_{k-1} + e_k \Delta t
29+
$$
30+
31+
$$
32+
D_k = \frac{e_k - e_{k-1}}{\Delta t}
33+
$$
34+
35+
$$
36+
u_k = K_p e_k + K_i I_k + K_d D_k
37+
$$
38+
39+
这里的 $\Delta t$ 非常重要。控制频率变了,积分和微分的数值都会变。如果代码里没有显式使用 `dt`,换频率后参数很容易失效。
40+
41+
## 2.2 输出限幅
42+
43+
电机、舵机、液压执行器都有上限:
44+
45+
- 最大力矩
46+
- 最大速度
47+
- 最大电流
48+
- 最大位置范围
49+
- 最大温度或功率
50+
51+
所以控制器输出必须限幅:
52+
53+
```python
54+
def clamp(value, lower, upper):
55+
return max(lower, min(upper, value))
56+
57+
torque_cmd = clamp(torque_cmd, -max_torque, max_torque)
58+
```
59+
60+
限幅不是“保守”,而是把控制器接入真实硬件的必要接口。没有限幅,仿真里也许只是动作夸张,真机上可能触发驱动器保护甚至损坏结构。
61+
62+
## 2.3 抗积分饱和
63+
64+
积分项最常见的问题是:输出已经到达上限,积分还在继续累积。
65+
66+
例如目标很远,控制器想输出 `100 N·m`,但电机最多只能输出 `20 N·m`。如果积分项继续累积,等关节接近目标时,积分项仍然很大,系统会继续往前冲,产生巨大超调。
67+
68+
常见抗积分饱和方法有三种。
69+
70+
第一种是积分限幅:
71+
72+
```python
73+
integral += error * dt
74+
integral = clamp(integral, -integral_limit, integral_limit)
75+
```
76+
77+
第二种是输出饱和时暂停积分:
78+
79+
```python
80+
raw_output = kp * error + ki * integral + kd * derivative
81+
output = clamp(raw_output, lower, upper)
82+
83+
if raw_output == output:
84+
integral += error * dt
85+
```
86+
87+
第三种是反算抗饱和,把饱和前后的差值反馈给积分项。它更平滑,但实现也更复杂。
88+
89+
入门阶段优先掌握前两种就够了。
90+
91+
## 2.4 微分滤波
92+
93+
微分项对噪声敏感。传感器读数如果有抖动,差分后会被放大:
94+
95+
$$
96+
D_k = \frac{e_k - e_{k-1}}{\Delta t}
97+
$$
98+
99+
当 $\Delta t$ 很小,哪怕误差只抖了一点,微分项也可能很大。
100+
101+
工程里常见做法:
102+
103+
- 对测量速度做低通滤波。
104+
- 使用编码器 / 驱动器估计的速度,而不是自己从位置差分。
105+
- 做 derivative on measurement:对测量值求微分,而不是对误差求微分,避免目标突变导致 D 项尖峰。
106+
107+
关节定点控制常写成:
108+
109+
$$
110+
\tau = K_p(q^\* - q) - K_d \dot{q}
111+
$$
112+
113+
这等价于目标速度为 0 时的 PD 控制,也避免了目标位置阶跃变化时 D 项产生过大冲击。
114+
115+
## 2.5 单位检查
116+
117+
机器人控制里,单位错会直接毁掉调参结果。常见坑包括:
118+
119+
| 变量 | 推荐单位 | 常见错误 |
120+
|---|---|---|
121+
| 角度 | rad | 用 degree 调参 |
122+
| 角速度 | rad/s | 把 rpm 当 rad/s |
123+
| 力矩 | N·m | 忽略减速比 |
124+
| 位置 | m | mm 和 m 混用 |
125+
| 时间 | s | ms 没除以 1000 |
126+
127+
如果你发现参数看起来离谱,比如 `Kp` 要调到几十万才有反应,优先检查单位,而不是继续调参。
128+
129+
## 2.6 PID 代码
130+
131+
下面是一个可读性优先的 PID 骨架:
132+
133+
```python
134+
class PIDController:
135+
def __init__(self, kp, ki, kd, output_limit, integral_limit):
136+
self.kp = kp
137+
self.ki = ki
138+
self.kd = kd
139+
self.output_limit = output_limit
140+
self.integral_limit = integral_limit
141+
self.integral = 0.0
142+
self.prev_error = 0.0
143+
144+
def reset(self):
145+
self.integral = 0.0
146+
self.prev_error = 0.0
147+
148+
def step(self, target, measurement, dt):
149+
error = target - measurement
150+
151+
self.integral += error * dt
152+
self.integral = clamp(
153+
self.integral,
154+
-self.integral_limit,
155+
self.integral_limit,
156+
)
157+
158+
derivative = (error - self.prev_error) / dt
159+
raw = self.kp * error + self.ki * self.integral + self.kd * derivative
160+
output = clamp(raw, -self.output_limit, self.output_limit)
161+
162+
self.prev_error = error
163+
return output
164+
```
165+
166+
实际项目里还会加入:
167+
168+
- `dt` 过小或异常时跳过更新。
169+
- 目标突变时重置积分项。
170+
- 根据模式切换清空历史状态。
171+
- 输出命令做斜率限制,避免瞬间跳变。
172+
173+
## 2.7 排错表
174+
175+
| 现象 | 可能原因 | 优先处理 |
176+
|---|---|---|
177+
| 响应很慢 | $K_p$ 太小,输出限幅太低 | 增大 P,检查限幅 |
178+
| 到目标附近来回振荡 | $K_p$ 太大,$K_d$ 太小 | 降 P 或加 D |
179+
| 过冲很大 | 阻尼不足,积分过强 | 加 D,减 I |
180+
| 长期差一点到不了 | 有负载或摩擦偏置 | 少量加 I 或加前馈 |
181+
| 输出忽大忽小 | 微分放大噪声 | 过滤速度,减 D |
182+
| 仿真好,真机抖 | 摩擦、延迟、结构柔性、频率不同 | 降增益,从低速开始 |
183+
184+
下一章进入状态空间和 LQR。它们不是替代 PID 的“高级玩具”,而是当系统状态耦合明显时,用模型系统化求反馈增益的方法。

0 commit comments

Comments
 (0)