结论:从目标曲率到车轮,controlsd 要经过「来源仲裁 → 变化率/加速度/绝对曲率三道限幅 → 选定的横向控制器 → 车型编码 → panda 固件」这几层。两个容易被误读的边界是:来源选择只看 lateralManeuverPlan 的消息 valid 位,不看 updated、alive 或消息年龄,因此发布者停发而进程不重启时,最后一条有效计划可能被持续选为目标;另外变化率限幅本身不会置 curvature_limited 标志,不能用它判断「是否发生过裁剪」。以下为固定源码快照 c8fb906 的静态追踪。
两条目标来源与 valid / alive 的差别
正常目标来自 modelV2.action.desiredCurvature;若 controlsd 看到 lateralManeuverPlan 的消息 valid 位为真,则优先采用该计划;横向未激活时两条来源都改用实测曲率,以降低再使能时的阶跃。模型侧 get_action_from_model() 有两条版本兼容路径:没有 action 输出时从预测轨迹的 yaw/yaw rate 估算曲率,有 action 时把输出横向量除以 max(1,vEgo)^2;纵向加速度作 0.3 s 平滑,横向平滑常量为 0,但 vEgo<=0.3 m/s 时保持上一帧曲率。
实验来源 lateral_maneuversd 只有机动 active 且未完成才把计划标为有效,曲率以基线曲率加「目标横向加速度 / max(vEgo,1)^2」形成;进程表只在横向机动条件下管理它,生产运行不应把它当作常驻规划器。
失效语义的核心在消息健康位:SubMaster.update_msgs() 只在收到新消息时更新 valid;alive 按收包时间重新计算;all_checks() 才同时检查存活、频率与有效位。而 controlsd 的来源选择只检查 sm.valid['lateralManeuverPlan'],未检查该主题的 updated、alive 或消息年龄。因此曾收到有效实验计划后,若发布者停发而进程不重启,最后一条计划仍可能被持续选为目标——其后仍经过限幅、latActive、车型与 panda 门,不等于可以任意发送转向。
controlsd 的逐层处理
Controls.__init__() 读取 CarParams,按转向接口类型优先选 angle/curvature,否则按横向调参选 PID/torque;update() 以 selfdriveState 为轮询主题更新异步输入与姿态数据。state_control() 更新车辆模型、活动门、目标曲率与执行器,并对数值字段做有限性检查。VehicleModel.calc_curvature() 把扣除在线角偏置的方向盘角换成实测曲率,考虑转向比、速度与路面横滚;controlsd 在调用前取负号,方向约定需用实车校验。
| 函数/组件 | 源码作用 | 契约或边界 |
|---|---|---|
Controls.__init__() |
按转向接口类型优先选 angle/curvature,否则按横向调参选 PID/torque。 | 不同输出单位不能在插件间直接互换。 |
state_control() |
更新车辆模型、活动门、目标曲率与执行器,做有限性检查。 | 生成的是 CarControl,不是 CAN 或执行许可。 |
VehicleModel.calc_curvature() |
扣除在线角偏置的转角换算实测曲率,考虑转向比/速度/横滚。 | 调用前取负号,方向约定需实车校验。 |
clip_curvature() |
顺序限制变化率、考虑 roll 的横向加速度范围、绝对曲率。 | curvature_limited 只由后两道限幅标记,变化率限幅不置位。 |
Controls.publish() |
发布 controlsState 与 carControl,比较请求与 carOutput。 |
顶层 valid=CS.canValid,不是 sm.all_checks()。 |
Car.controls_update() |
carControl 存活且非被动/已初始化时调用车型 apply() 并发布 sendcan。 |
仍须通过车型编码与 panda 固件安全约束。 |
三道限幅与标志位陷阱
clip_curvature() 按顺序做三件事:用至少 1 m/s 的速度计算 5 m/s^3 的近似横向 jerk 限幅;再按 ±3 m/s^2 加 roll 补偿限加速度;最后按 ±0.2 1/m 限绝对曲率。源码明确标注 jerk 公式为近似,这些常量不是可以脱离车型和硬件直接复用的认证约束。
隔离探针给出一个典型陷阱:当变化率单独生效时,输出已从 0.1 改成 0.0005,但 curvature_limited=False。也就是说这个标志不能作为「无裁剪」的诊断依据。探针只验证纯数值函数,不代表 controlsd 消息时序、车型控制器或板端限幅已通过。
| 限幅 | 阈值 | 是否置 curvature_limited |
|---|---|---|
| 变化率(近似横向 jerk) | 5 m/s^3,速度下限 1 m/s |
否 |
| 横向加速度(含 roll 补偿) | ±3 m/s^2 |
是 |
| 绝对曲率 | ±0.2 1/m |
是 |
输出单位与有限性清理
控制器输出按 steerControlType 分流:通用第一返回值写 actuators.torque,angle/普通 PID 的第二返回值写 steeringAngleDeg,curvature 型写 actuators.curvature,扭矩控制的第二返回值为零。LatControlAngle 与 LatControlCurvature 都返回 steer=0,有效输出分别是角度与曲率字段;LatControlPID 在施力/低速/安全限幅时冻结积分;LatControlTorque 把目标曲率换横向加速度后返回时会翻转符号,不能把 pid_log.output 与 actuators.torque 直接同号比较。
state_control 对 Actuators 的数值字段逐个检查非有限值并清零,但这只是末端值清理,不是上游模型输入、车辆参数或中间 PID 状态的完整非有限传播防护。publish() 在 selfdriveState.active 时比较本次请求与已收到的 carOutput,把差异留给后续循环的控制器饱和判断,这不是同步的本帧硬件确认。
| 控制器 | 有效输出字段 | 边界 |
|---|---|---|
LatControlAngle |
steeringAngleDeg(steer=0) |
曲率反算目标方向盘角并加角偏置。 |
LatControlCurvature |
actuators.curvature(steer=0) |
曲率误差 PID 或前馈,驾驶员施力时用前馈。 |
LatControlPID |
steeringAngleDeg 或扭矩 |
施力/低速/安全限幅时冻结积分。 |
LatControlTorque |
actuators.torque |
返回时翻转符号;不能把 pid_log.output 与扭矩直接同号比较。 |
自制硬件与插件契约
源文档给出的约束是:候选规划插件应输出带来源、时间戳、年龄上限、曲率单位 1/m、车辆坐标/正方向、适用车速范围与显式失效状态的目标;仲裁层独占来源选择与 TTL,失效时回退模型或安全降级。车辆学习参数、转角编码与 carOutput 的实际执行反馈也应按车型/硬件版本记录。
不能让普通插件修改 latActive、车型极限或 panda 固件;CarControl 及其 valid 位也不能替代板级安全门。现有横向饱和测试覆盖部分车型与控制器,扭矩缓冲测试覆盖停用后缓冲清零,但未证明四类控制器全部车型、计划抢占超时、模型丢帧、车况异步或实车符号正确,仍需补齐来源有效→停发/无效切换的回放测试与 NaN/Inf 注入。
常见问题
实验计划停了不发,为什么车还在用它?
因为 controlsd 的来源选择只检查 sm.valid['lateralManeuverPlan'],未检查 updated、alive 或消息年龄。曾收到有效计划后,若发布者停发而进程不重启,最后一条计划仍可能被持续选为目标;但它仍经过限幅、latActive、车型与 panda 门。
curvature_limited=False 就代表没有裁剪吗?
不代表。这个标志只由横向加速度与绝对曲率两道限幅设置;变化率限幅本身不置位。探针显示变化率单独生效时输出已从 0.1 改成 0.0005,但标志仍是 False。
三道限幅的阈值分别是多少?
按顺序是:用至少 1 m/s 计算 5 m/s^3 的近似横向 jerk、±3 m/s^2 的横向加速度(含 roll 补偿)、±0.2 1/m 的绝对曲率。源码标注 jerk 公式为近似,不能直接当认证约束复用。
扭矩控制器的输出符号可以直接对比吗?
不能。LatControlTorque 返回时会翻转符号,不能把 pid_log.output 与 actuators.torque 直接同号比较;angle 与 curvature 两类控制器则返回 steer=0,有效输出在角度或曲率字段。
延伸阅读
- 17 个品牌参数怎么构建:CarParams 冻结只到顶层 —— 侧向加速度因子与摩擦从哪来。
- 从使能到车轮:执行链上的八道门,以及它们各自不保证什么 —— 限幅之后还有哪些门。
- modelV2 的三个 valid 位不能合并:modeld 三路输出各差一道门 —— 模型目标曲率的来源与有效位。
- 横向限制插件契约 —— 横向极限如何声明与验证。
参考资料
- 项目 02 ——
openpilot-research/LATERAL_TARGET_ACTUATION_DEEP_DIVE.md(两条来源仲裁、controlsd 逐层处理、三道限幅与四类控制器)。 - 项目 02 ——
openpilot-research/同目录探针记录:五项clip_curvature()NumPy 数值契约隔离探针结论。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复