ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

一拍 10ms:openpilot 横向控制如何用曲率闭环 + 延迟补偿跟上转向滞后

一拍 10ms:openpilot 横向控制如何用曲率闭环 + 延迟补偿跟上转向滞后 一拍 10msopenpilot 横向控制如何用曲率闭环 延迟补偿跟上转向滞后【免费下载链接】openpilotopenpilot is an operating system for robotics. Currently, it upgrades the driver assistance system on 300 supported cars.项目地址: https://gitcode.com/GitHub_Trending/op/openpilotopenpilot 的横向控制把车道居中压成一个标量——曲率curvature单位 1/m再用延迟补偿和 jerk 限幅把方向盘 0.15~0.65 秒的执行滞后吸收掉。本文从 controlsd.py 每 10ms 一拍的方向盘指令切入讲这套闭环为什么这样设计以及二次开发该从哪几个文件动手。需要先说清一个前提很多资料里会提到 openpilot 用「模型预测控制MPC」求解横向轨迹但当前仓库里已经没有lateral_mpc这套求解器了。实际横向控制换成了「曲率闭环 前馈 延迟补偿」的轻量方案——计算量小一个量级靠工程上的限幅和延迟估计把稳态做出来。下面按数据流拆。为什么核心量是曲率而不是角度或扭矩曲率是 1/m 的物理量等于横向加速度除以车速平方κ a_lat / v²它只描述路往哪弯、弯多急与具体车型、转向比、方向盘扭矩完全无关。这让规划该往哪拐和怎么拧方向盘两件事解耦模型只吐一个曲率剩下的换算交给车型适配层。模型只输出一个期望曲率controlsd.py 的主循环每拍100Hz即 10ms做一次# 模型只吐一个期望曲率执行前统一限幅 new_desired_curvature model_v2.action.desiredCurvature if CC.latActive else self.curvature self.desired_curvature, curvature_limited clip_curvature(CS.vEgo, self.desired_curvature, new_desired_curvature, lp.roll) lat_delay self.sm[lateralDelay].lateralDelay LAT_SMOOTH_SECONDS为什么这么写desiredCurvature由视觉模型modelV2或变道/横向机动规划lateralManeuverPlan给出是全局唯一的横向指令源clip_curvature在指令下发前做硬限幅lat_delay单独算好、随指令一起传给控制器供下一节的延迟补偿用。把算路径收敛到一个标量后面四个不同控制器才能共享同一条指令。指令源在变道和巡航间切换controlsd里还有一处分支变道或做横向机动时new_desired_curvature取自lateralManeuverPlan.desiredCurvature否则取modelV2.action.desiredCurvature。横向未激活时它退回当前实际曲率self.curvature。这个未激活就退回当前值的写法是为了避免一激活就把期望曲率从一个远离当前值的位置硬拉过来、触发限幅抖动。朴素闭环为什么在 0.2 秒延迟下会滞后、过冲最直接的做法是期望曲率 − 实际曲率跑一个反馈环。问题在于方向盘的执行不是瞬时的从指令发出到车身真正产生对应的横摆角速度有 0.15~0.65 秒的时延lagd.py 里把搜索区间定死在这两者之间。反馈误差里混进了还没执行的那部分用当前期望做 setpoint 时误差 期望 − 实际。但实际反映的是方向盘上一拍甚至更早指令的结果。两者错开 0.2 秒反馈环看到的输入输出关系就偏了相位——环会朝过去的错误使劲结果就是转弯时先过冲再回摆高速下尤其明显。前馈能救一部分直接给未来期望不等反馈但救不了动态变化车速在变、坡度在变纯前馈的偏差没人管。所以真正的解法是在反馈侧把相位对齐回来——这正是下一节扭矩控制器的核心。执行前先限幅clip_curvature 的三道硬约束drive_helpers.py 的clip_curvature是整条链路的安全阀它把下一拍允许的曲率夹在三个边界里# 三道硬约束jerk 与 accel 上限取自 EU 指导值 max_curvature_rate MAX_LATERAL_JERK / (v_ego ** 2) # 5.0 m/s³ ÷ v² new_curvature np.clip(new_curvature, prev_curvature - max_curvature_rate * DT_CTRL, prev_curvature max_curvature_rate * DT_CTRL) max_lat_accel MAX_LATERAL_ACCEL_NO_ROLL roll * G # 3.0 坡道补偿 new_curvature, _ clamp(new_curvature, min_a / v_ego**2, max_a / v_ego**2) new_curvature, limited clamp(new_curvature, -MAX_CURVATURE, MAX_CURVATURE) # 0.2 1/m三个常量的来源和量级约束取值出处作用曲率变化率jerk5.0 m/s³代码注释标注 EU guidelines限制每拍曲率能跳多大横向加速度3.0 m/s²无坡度EU guidelines限制体感侧向力最大曲率0.2 1/mturn radius smaller than most cars can achieve兜底约 5m 转弯半径为什么把 jerk 写成5.0 / v²因为曲率 κ a_lat / v²所以 dκ/dt jerk / v²。这一项让每拍允许的曲率变化随速度自动缩放——车速 15 m/s 时每拍最多变约 0.00022 1/m降到 5 m/s 时同一步长允许变化约 9 倍。等于用一条公式同时照顾了低速的灵活和高速的平顺不需要单独写低/高速分支。限幅值为什么随坡度变roll * G这一项是横滚补偿坡道上车自身就有侧向加速度分量如果不把这部分从限幅里扣掉系统在坡上就会误判侧向力超限而提前收曲率表现为爬坡时转向发虚。限幅结果还会返回一个布尔limited是否被夹到边界它一路传到控制器的_check_saturation参与控制器是不是力不从心的判断。延迟补偿lagd 用掩膜互相关把执行时延测出来限幅管住了指令能跳多大但 0.15~0.65 秒的执行延迟还得有人量出来。这件事由 lagd.py 独立进程做它把期望横向加速度和实际横向加速度做掩膜归一化互相关masked NCC在时延区间里找峰值。60 秒窗口 FFT 互相关时延限定在 0.15~0.65s# 只在 [min_lag, max_lag] 区间内找相关峰值置信不足就不更新 ncc masked_normalized_cross_correlation(desired, actual, mask, padded_size) roi np.s_[ ... min_lag_samples : ... max_lag_samples ] # 限定搜索区间 max_corr_index np.argmax(roi_ncc) lag parabolic_peak_interp(roi_ncc, max_corr_index) * dt min_lag if corr self.min_ncc or confidence self.min_confidence: # NCC≥0.95 return关键阈值滑动窗口 60sMOVING_WINDOW_SEC其中有效样本至少 25s 才参与估计只认车速 50 mph、且期望与实际横向加速度差不超过 0.6 m/s² 的片段MIN_NCC 0.95是相关系数门槛confidence clip(1 − width·dt, 0, 1)衡量峰值有多尖尖才可信。时延用 50 块 × 100 点的块均值平滑至少 5 个有效块才对外发布estimated状态否则回落到CP.steerActuatorDelay 0.2的静态初值。为什么这么写直接对期望 vs 实际求相关峰值会被传感器噪声和多周期分量带偏。限定 ROI、抛物线峰值插值亚采样精度、NCC 与置信度双门槛是让0.25s 这种估计只在真的稳定时才更新——宁可发布unestimated也不发一个漂的值。扭矩控制器如何回溯缓冲去取该此刻执行的期望值latcontrol_torque.py 拿到lat_delay后不去用当前期望当 setpoint而是往回翻缓冲# 用一个 1.0s 的期望横向加速度缓冲回溯 lat_delay 取历史期望 delay_frames int(np.clip(lat_delay / self.dt 1, 1, self.lat_accel_request_buffer_len)) expected_lateral_accel self.lat_accel_request_buffer[-delay_frames] setpoint expected_lateral_accel error setpoint - measurement缓冲长度是int(1.0 / dt) 100 帧1 秒。当lat_delay ≈ 0.25s时delay_frames 25 1 26也就是回溯 26 帧、约 0.26 秒的期望值当 setpoint。从代码看其意图是让误差反映的是方向盘此刻真正在响应的滞后的那条指令的输入→输出关系从而把上一节说的相位错开对齐回来从根上削掉过冲。这是一个用历史指令换相位对齐的取舍代价是当延迟估计本身抖时setpoint 会跟着小幅回跳——所以lagd才要卡那么严的置信门槛。边界与取舍四套控制器的分流以及二次开发入口clip_curvature之后的期望曲率交给哪个控制器执行在 controlsd.py 构造时一次性定死按车型参数分流if self.CP.steerControlType car.CarParams.SteerControlType.angle: self.LaC LatControlAngle(...) # 直接控制方向盘角度 elif self.CP.steerControlType car.CarParams.SteerControlType.curvature: self.LaC LatControlCurvature(...) # 曲率空间闭环 elif self.CP.lateralTuning.which() pid: self.LaC LatControlPID(...) # 角度空间 PID 前馈 elif self.CP.lateralTuning.which() torque: self.LaC LatControlTorque(...) # 横向加速度空间闭环四套控制器在三个不同空间里算误差角度控制器直接出目标转角饱和阈值 2.5°曲率控制器在曲率空间饱和阈值 1e-3 1/m扭矩控制器在横向加速度空间——它先在加速度空间跑 PID最后才用torque_from_lateral_accel换算成扭矩注释明确写着在加速度空间做误差修正最后再换算以正确处理非线性的扭矩响应。饱和怎么上报、代价是什么三者共用基类LatControl._check_saturation输出顶到边界或指令被限幅时sat_time按dt累加反向则递减累计超过CP.steerLimitTimer且车速高于sat_check_min_speed角度/曲率环 5 m/s扭矩环 10 m/s才判定饱和。这里排除两种不是控制器的锅安全模块限扭steer_limited_by_safety由 controlsd.py 里指令 vs 车实际输出的偏差反推和驾驶员接管steeringPressed。代价也要说清扭矩环的 live 参数latAccelFactor、latAccelOffset、friction由 torqued.py 单独估计只对toyota/hyundai/rivian/honda/volkswagen且lateralTuning torque生效其余车只能吃静态标定值——跨品牌移植时这是最容易被忽略的一环。延迟补偿强依赖lagd的估计质量低速 50 mph不采样此时回落静态初值补偿精度下降。限幅用的是 EU/ISO 指导值是设计上限而非实车实测误差仓库里没有可直接引用的居中误差数字test_latcontrol.py、test_torqued_lat_accel_offset.py等测试是回归门槛不是精度标定。二次开发入口与读码顺序先读 controlsd.py 的state_control看清指令→限幅→控制器→下发这一拍的主干。再看 drive_helpers.py 的clip_curvature理解三道限幅如何随速度/坡度缩放。想改控制律按车型分流定位扭矩环看 latcontrol_torque.py通用 PID 行为看 pid.py 的抗积分饱和输出削顶时冻结 i 项。想理解精度从哪来读 lagd.py 的掩膜 NCC 与 torqued.py 的 SVD 参数估计——这两处是活的部分限幅和曲率闭环是死的骨架。扩展点新增横向机动类型时在lateralManeuverPlan指令源处接入调整延迟补偿灵敏度时动lat_delay的回溯逻辑与lagd的置信门槛两处必须一起动。【免费下载链接】openpilotopenpilot is an operating system for robotics. Currently, it upgrades the driver assistance system on 300 supported cars.项目地址: https://gitcode.com/GitHub_Trending/op/openpilot创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表