DEEP 199 · 第十六辑 · 小白基础课 · LEARN NOTES

收进学习手册 · 2026-09-11 · duck.whatled.com

解读 199 · PID 控制入门:舵机内部的黑盒

解读对象:duck-control/src/bus.rs 的 set_gain(506–527 行)+ duck-control/src/hl2915.rs(P_GAIN_ADDR=21 / D_GAIN_ADDR=22)
需要的前置:解读 151(指令怎么送到舵机);解读 53(寄存器是什么)。

PID 三兄弟:误差的过去、现在与积累

PID 是最经典的控制回路:P(比例)看现在差多少,误差大就用力猛;I(积分)记过去欠了多少账,纠正长期偏差;D(微分)预判趋势,误差收窄时先松劲防过冲。你的鸭子每 20ms 给舵机一个目标角度,而"转到那个角度"这件事,是舵机内部的一个 PID 闭环在干:它读自己的编码器、算误差、驱动电机——对外只表现出"到位了"。策略网络再聪明,也站在这个黑盒之上:它出目标,黑盒出力矩。

set_gain:对黑盒做的三笔写入

我们没法改舵机里的 PID 算法,只能通过寄存器调它的三个系数。bus.rs 的 set_gain(kp: u16) 每次都把三个一起写:

const KI: u16 = 0;
const KD: u16 = 0;
for &id in &JOINT_IDS { /* write_position_p_gain / i_gain / d_gain */ }

为什么 kd 要写成定值 0?注释讲了一个真实故事:这些是 RAM 寄存器,断电即失,每次上电恢复出厂值——而出厂的 D 增益不是 0:"left in place it damps the servo's internal PID, and the robot runs measurably softer than the prototype at the same kP"(留着它,同一只鸭子在同一个 kP 下明显偏软)。这不是谁调出来的手感,是没清零的出厂默认,所以代码把它钉死,不留给用户当旋钮。

同一件事,两代舵机两种写法

换成 HL-2915 后,连"写增益"的量纲都变了。hl2915.rs 里地址是明码:

const P_GAIN_ADDR: u8 = 21;
const D_GAIN_ADDR: u8 = 22;
const I_GAIN_ADDR: u8 = 23;

写入时 kp.min(255) as u8——一个字节,最多 255;XL330 的 set_gain 则是 u16,16 位。同一个"200":在 XL330 上是一个 16 位寄存器里的 200,在 HL-2915 上是一字节寄存器 21 里的 200,含义和分辨率都不一样。换舵机为什么必须重新辨识、重训策略(第七辑),这就是微观例证之一。仿真侧同样记得这件事:infer_policy.py 里 BAM_KP_FW = 200.0 注释写着 "microduck's preserved firmware stiffness"——把固件 P 增益 200 建模进仿真,黑盒才不会在两个世界里表现不一。

增益不是越大越好:safety.rs 的用法

robotd 启动时 io.set_gain(params.policy.gain)(参数注册表:"Servo position P gain while running"),safety.rs 给出两组真实数值:gain_running: 200(正常跑)、gain_limp: 50(摔倒时)。检测到摔倒就把增益从 200 降到 50——P 小了,舵机不再硬顶,机器人"软下去"安全躺平。同一个黑盒,kP 的大小直接是"手劲"的大小,安全层把它当刹车用。

你带走的收获

延伸

什么鸭 WHATDUCK · 复刻学习笔记 ← 全部手册 · 首页