做自动驾驶横向控制的人,基本都绕不开 Stanley 这个名字。它是最经典的几何类轨迹跟踪控制器之一,代码量不大,但调好之后能把横向误差压得很低。最近我把 Stanley 轨迹跟踪和 Carsim、Simulink 做了联仿,路径做成了可改的外部输入,直线、双移线、带曲率变化的弯道都跑了一遍,整体效果很稳。这篇文章把我的联仿思路、配置步骤、参数标定数据和踩过的坑全整理出来,给正在做横向控制或者想快速验证控制算法的人一个可以直接参考的完整版本。
如果你刚接触车辆横向控制,或者是做规划算法想验证底层跟踪能力,这篇都适用。它不涉及特别高深的数学,核心是把你从"看公式"带到"能复现"这一步。我先从整体设计讲起,再逐步拆控制器实现、动态路径配置、联调标定和故障排查,最后放一些我的个人经验。
1. 项目概述与整体设计思路
1.1 为什么轨迹跟踪总要提 Stanley,它到底解决什么问题
轨迹跟踪说白了就是一件事:给你一条参考路径(可能是一串坐标点,也可能是连续曲线),让车辆的实际位置尽量贴合这条路径,同时保证前轮转角输出合理、不发散。Stanley 控制器就是干这个的,它根据车辆当前位置和参考路径的几何关系,直接算出前轮转角,属于几何类控制方法里最实用的一种。
我之前也试过 PID 和 LQR,但 Stanley 在工程验证阶段更舒服。PID 调三个系数,往往调完 P 又发现 D 太敏感,整圈下来累得够呛。LQR 需要比较准确的车辆模型,轮胎、轴距、转动惯量都得心里有数,否则算出来的增益拿过来也不一定好用。Stanley 的核心表达式只有一个,需要调的增益参数也少,物理意义非常直白:横向偏差越大,转角修正越大;航向偏差越大,转角修正也越大。这种直观性在联仿阶段特别宝贵,因为它出问题时好定位,你一眼能看出是反馈量算错了,还是增益给大了。
1.2 为什么选 Carsim 加 Simulink 这对组合来联仿
做横向控制验证,有纯 Python/C++ 仿真,也有直接上实车的。纯运动学仿真快,但车辆模型太理想,轮胎侧偏、转向系统延迟一概没有,算出来的效果参考价值有限。直接上实车又太贵、太危险,尤其你还在调参数阶段,稍不注意就可能冲出场地。Carsim 加 Simulink 是中间那条路:Carsim 提供高保真的车辆动力学模型,轮胎、悬架、转向、制动这些子系统都在里面,Simulink 负责跑控制器逻辑,两边通过接口互相传数据。
这种组合的好处是分层清晰。Carsim 是"被控对象",Simulink 是"控制器",两者独立修改互不影响。你想换一种控制算法,直接在 Simulink 里替换掉控制器模块就行;你想换一辆车或者换一种路面,在 Carsim 里重选车型和工况就行。我这次的项目就是把 Stanley 控制器放在 Simulink 里,Carsim 实时把车辆位置、航向角、速度传出来,Simulink 算好前轮转角再传回去,整个闭环联调一次就能跑通。
1.3 这个项目适合谁,做完到底能学到什么
如果你正在做自动泊车、车道保持、L2/L3 横向控制方案,或者刚入门控制算法想找一个能落地验证的方向,这个项目很适合。它不要求你懂特别深的现代控制理论,只需要基础的运动学概念就能上手。做完之后你能掌握三件事:第一,Stanley 公式在实际车辆模型上怎么实现,以及增益 k 对跟踪效果的影响规律;第二,Carsim 和 Simulink 联仿的完整链路,包括接口配置、步长设置、信号传递;第三,怎么把路径做成外部可修改的输入,而不是写死在模型里,这点对后面接规划模块特别有用。
2. 联仿环境搭建与配置要点
2.1 软件版本匹配与 S-Function 编译环境配置
联仿第一步不是写控制器,而是把环境搞通,这一步坑最多。Carsim 与 Simulink 的通信依赖一个 S-Function 动态库,这个库需要和你本机安装的编译器匹配。我一开始装好软件直接点运行,结果 Simulink 报无法加载动态库,折腾了半天才发现是编译器没配好。在 MATLAB 命令行里运行mex -setup,把默认编译器指定好,比如 VS 对应的 C/C++ 编译器,然后再重新启动 Simulink 模型,问题就消失了。
版本匹配也要注意。Carsim 的老版本和较新的 MATLAB 之间可能存在接口不兼容的情况,报错往往五花八门,有的是找不到模块,有的是数据长度对不上。我的经验是先用官网或软件文档里标注过匹配组合的版本,别最新版和最新版强行配对。另外还有一个特别容易踩的坑:整个工程文件的路径不要带中文,不要带空格,S-Function 对路径里的特殊字符很敏感,放到纯英文目录下最保险。
2.2 Carsim 输入输出端口映射与信号方向核对
联仿模型里,Carsim 提供给 Simulink 的 S-Function 模块端口顺序需要严格匹配。Carsim 那边配置的输出变量,比如车辆纵向位置 x、侧向位置 y、横摆角 yaw、纵向速度 vx、侧向速度 vy,它们的顺序会和 S-Function 的模块端口一一对应。如果顺序定义错了,控制器里把侧向速度当成纵向速度使用,结果会非常离谱。
我建议把 S-Function 输出的原始信号先接进一个 Bus Creator 或直接接若干 Demux,把每个信号单独引出来,用 Scope 或者显示模块先看一遍数值是否合理。空挡状态下车辆应该静止在起点,x、y 几乎不变;给一个固定前轮转角,车辆应该按预期画圆弧。这样在接入控制器之前就把信号方向核对清楚。转向输入那边同理,Carsim 的输入端口接收的是前轮转角还是方向盘转角,必须在 Carsim 的接口配置里确认,我这次用的是前轮转角作为 Simulink 输出,省去转向比的换算,如果你的是方向盘转角,需要额外乘一个传动比。
2.3 仿真步长与求解器怎么设才稳
联仿的步长设置直接影响控制效果。Carsim 模型内部有自己的积分步长,Simulink 仿真步长要和它匹配好。我用的是定步长求解器,步长设成 0.01 秒,控制频率 100Hz,Carsim 那边同样设置,两边保持一致。如果步长差太多,比如 Simulink 是 0.1 秒而 Carsim 是 0.001 秒,控制器的输出就会明显滞后,车辆在路径上走出蛇形。
步长太小也不好,不是精度不够,而是仿真速度慢,调试效率低。0.001 秒对大部分车型和场景都没必要,0.01 秒在 36km/h 的车速下已经能覆盖到 10cm 级的位移分辨率,Stanley 反应不过来的是算法问题,不是步长问题。把求解器设置成定步长的另一个好处是结果可复现,每次跑出来的数据一致,比变步长更适合做参数对比。
3. Stanley 控制器算法拆解与参数化实现
3.1 Stanley 公式拆解,两个误差到底怎么算
Stanley 控制器最核心的控制量表达式是:前轮转角 delta = theta_e + arctan(k * e / v),其中 theta_e 是航向误差,e 是横向误差,v 是纵向车速,k 是增益系数。
这里的航向误差 theta_e 怎么理解?它等于车辆当前航向角减去参考路径上最近点处的切线方向角。也就是说,如果车头指向和路径方向一致,这项就是零,不需要额外修正。横向误差 e 是车辆后轴中心到参考路径上最近点的距离,方向判断很重要,通常定义车辆在路径左侧为正还是右侧为正,要保持一致。之所以选用后轴中心而不是前轴中心,是因为车辆运动学模型里后轴中心点不产生侧偏约束,位置变化规律更干净,Stanley 本身也是基于后轴之间的几何关系推导的。控制量里那个 arctan,作用是把横向误差造成的修正量压缩到有限角度范围内,这样即使误差瞬间很大,转角输出也不会爆表,这是几何控制器天生的安全性优势。
3.2 增益 k 和车速 v 的相互作用,调参手感讲清楚
k 的物理意义相当于是横向误差的反馈强度,k 越大,同样的横向误差产生的转角修正就越大,收敛越快。但k不是越大越好。我在双移线路径上做过对比,车速 36km/h 时,k 取 0.5 收敛偏慢,弯道里最大横向误差接近半米;k 取 1.0 效果最好,误差能压到 0.3 米以内,车辆姿态也平稳;k 取 2.0 收敛确实更快,但车辆会出现小幅蛇形摆动,乘坐体验很差;k 再往上到 4.0 左右就开始发散。
v 在分母上这个细节很多人会忽略。它意味着同样大小的横向误差,车速越快得到的转角修正越小。这不是控制器变迟钝了,而是逻辑上合理的:高速下车辆转弯半径大,同一转角产生的横向位移变化更快,如果还按低速的大转角输出,车辆会猛烈摆头。所以车速提高后,如果感觉跟踪变钝,可以把 k 适当调大,或者让 k 随速度变化。我用过一个简单方案:k = 基础增益 + 速度比例项,低速保持基础值,高速自动增加反馈强度,整体效果比固定 k 平滑很多。
3.3 前轮转角限幅与方向盘转角转换,控制器落地的关键细节
Stanley 公式算出来的只是理想前轮转角,实际车辆未必执行得了。乘用车前轮转角通常不会超过正负 30 到 40 度,所以在控制器输出末端一定要加饱和限幅模块。我之前没限幅直接跑,路径曲率大时转角输出到 60 多度,Carsim 里的车辆直接原地打转,数据基本没法看。限幅到正负 30 度后,双移线工况下几乎没有触发饱和,说明限幅没有影响正常工作范围。
还有一个接口细节:如果 Carsim 输入的转向量是方向盘转角而不是前轮转角,你需要把 Stanley 的输出乘上传动比。以前轮 1 度对应方向盘 17 度计算,控制器输出 1 度,实际发给 Carsim 的就是 17 度。最稳妥的方式是把传动比做成一个可以在模型外修改的参数,而不是写死在模块内部,方便以后换车型时直接改数。符号方向也要测一下:给一个正转角,看车辆是左转还是右转,再和控制器的符号定义对齐,否则一上车就是反向修正,必然发散。
4. 路径定义与可改路径的实现方案
4.1 路径数据的三种来源,按需选择就行
这次项目里我重点关注"路径可改"这件事,因为控制器几十行就写完了,真正让仿真贴近工程实际的是路径模块。路径数据有三种常见来源。第一是文件读取,把预先设计好的路径点存成 CSV 或 MAT 文件,Simulink 启动时读进工作区,适合路径固定不变的场景。第二是 MATLAB 脚本生成,用程序直接生成直线、圆弧、双移线、正弦曲线等测试路径,适合做算法验证,因为你可以快速换不同的工况。第三是规划模块在线输出,这也是后续最可能用的方式,控制器实时接收规划层的路径点,这次项目我用数组模拟这个接口。
我觉得做联仿验证阶段,脚本生成加工作区变量是最舒服的。你不需要为了改一条路径重新打开模型,只需要在 MATLAB 命令行里重新生成变量,再跑一次仿真就行。这样批量化做参数扫描也比手动修改模型高效得多。
4.2 最近点查找与路径插值,切线方向算错就全乱套
Stanley 控制器需要一个"最近路径点"作为计算基准。最简单的实现方法是遍历所有路径点,找到离车辆后轴中心欧氏距离最小的那个点。路径点数量少时这个方案完全够用,几百个点遍历一遍消耗很小,但如果路径很长、点多到几千上万个,每步仿真都全量遍历就太浪费了。我采用了一个小优化:保留上一时刻的最近点索引,下一时刻只在该索引附近的一段窗口内搜索,路径点连续的情况下几乎总能命中,计算量大大下降。
最近点找到之后,还得求这个点处的切线方向角。如果路径点足够密,直接用当前点和下一个点的差分角就行;如果路径点很稀疏,差分结果会很跳,转角就会一顿一顿。这种情况需要对路径做三次样条插值,把点加密,再算切线方向。插值前还要注意去重排序,不能有重复点或者顺序混乱的路径段,否则最近点搜索可能跳来跳去,横向误差变化剧烈。
4.3 运行时可改路径的具体做法,不用改模型也能换线
这次我把路径数组定义成 Simulink 工作区里的变量,格式是一个三行的矩阵:第一行是路径点的 x 坐标,第二行是 y 坐标,第三行是累计弧长或点序号。控制器模块通过 From Workspace 模块读取这个变量。需要改路径时,我直接在 MATLAB 脚本里重新给变量赋值,然后重新运行仿真,控制器代码一行都不用动。
这里要注意 From Workspace 的数据格式。它默认要求数据按时间维度组织,第一列必须是时间戳,后面才是数据列,而且时间要递增。如果你只是想给控制器提供一组静态路径,不希望它随时间切换,可以把时间列设成全零,或者改用直接读取基础工作区变量的方式。我在快速加速模式下遇到过读不到工作区变量的情况,最终退回使用 From Workspace 模块才稳定。路径更新后最好在 Simulink 里加一个显示模块实时画出参考路径点和车辆实际位置,这样改完路径一眼就能看出对应关系对不对。
5. 联调实操全过程与标定记录
5.1 从开环到闭环的调试顺序,先直线后弯道
联调切忌一上来就接好闭环跑双移线。我这次按四步走,每一步都确认没问题再进下一步。第一步,不开控制器,Simulink 模型直接连着一个常值前轮转角,让 Carsim 跑 2 秒,确认车辆能正常启动,输出的 x、y、yaw、vx 都有合理数值。第二步,给前轮转角一个阶跃信号,比如 2 度,看车辆是不是画了一个平滑的圆弧,yaw 变化是否连续,这一步确认转向通道完全正确。
第三步才开始接入 Stanley,但路径先用最简单的直线,车速控制在 10km/h。直线路径下只有横向误差的收敛问题,没有曲率干扰,如果控制器工作正常,横向误差应该快速收敛到零附近。直线跑稳之后,第四步换成双移线路径,车速逐步从 10km/h 提到 20、36、60km/h,每档速度都记录一组误差数据。这样由简到繁搞下来,出问题时很容易定位是控制器问题还是路径问题。
5.2 参数标定记录,k 从 0.5 到 2.0 的实跑数据
以轴距 2.91m 的某车型在双移线路径上的测试为例,车速 36km/h,控制器输出限幅正负 30 度,我做了四组对比测试,数据如下:
| 增益 k | 最大横向误差 | 稳态横向误差 | 车辆姿态表现 |
|---|---|---|---|
| 0.5 | 0.48m | 0.12m | 平缓,过弯略慢 |
| 1.0 | 0.31m | 0.05m | 平稳,无明显摆动 |
| 2.0 | 0.22m | 0.03m | 收敛较快,轻微蛇形 |
| 4.0 | 无法收敛 | 明显发散 | 横向振荡加剧 |
这是同一组路径、同一车速下的数据。横向误差最大点基本出现在双移线换道的中点附近,因为那段曲率变化最快。k 取 2.0 时最大误差确实更小,但车辆偶尔出现左右摆动,如果以乘坐舒适性为标准,我宁可选择 1.0 而不是极限压误差。高速 60km/h 测试时,固定 k 为 1.0 的跟踪误差明显变大,改为随车速增加的比例增益后误差回升到可接受范围。
5.3 跟踪效果怎么评估,别只盯最大误差一个数
很多初学者只看最大横向误差,认为越小越好,实际上需要看三个维度:最大误差、稳态误差和误差的振荡情况。最大误差反映控制器在最难路段的极限能力,稳态误差反映收敛后的贴合精度,振荡情况反映车辆姿态是否平顺。我用 MATLAB 在仿真结束后画出误差曲线,先看整体趋势是否收敛,再看是否有高频抖动,然后计算均方根误差作为综合指标。
直线路径上,稳态误差能压到 0.03 到 0.05 米甚至更小,主要是数值噪声和控制周期造成的。双移线路径上,最大误差 0.3 米左右已经比较理想,追求更小的极限值意味着更高的反馈增益,往往得不偿失。航向误差同样值得看,它反映车辆姿态和路径方向的贴合程度,Stanley 的航向误差本身会随着横向误差收敛而收敛,如果航向误差曲线出现持续偏置,说明横向误差符号定义有问题或者路径切线方向算得不准。
6. 常见问题与排查技巧实录
6.1 Carsim 联仿断连与 DLL 加载失败的快速排查
联仿最常见的故障就是 Simulink 报 S-Function 无法加载或者 Carsim 一直连不上。这类问题大多出在三个地方:编译器没配好、路径里有中文或空格、MATLAB 版本和 Carsim 版本不匹配。排查顺序我建议先从编译器入手,运行mex -setup确认编译器;再把整个工程移到纯英文目录;最后确认版本匹配关系。还有一个容易忽略的点是 Carsim 每次改动车辆模型后,必须重新生成一次动态库或重新发送到 Simulink,否则模型跑的还是上一次的内容,界面提示容易误导人。
如果 C 编译报错出在某个头文件找不到,基本可以锁定是 Carsim 的安装目录没有被 MATLAB 识别。把 Carsim 安装目录下的相关文件夹加入 MATLAB 路径,再重新运行初始化脚本,通常就能解决。
6.2 车辆蛇形摆动或发散,先查符号再降增益
闭合回路后车辆来回摆动,这是 Stanley 联调里最让人头疼的问题。我排查的顺序是:第一步看横向偏差的符号定义,车辆在路径右侧时偏差应该是负还是正,这直接决定转角修正方向,一旦符号反了就是正反馈,车会越修越偏;第二步把 theta_e 角度归一化到负 180 度到正 180 度范围,否则角度跨过正负 180 度边界时数值跳变,转角跟着剧烈突变;第三步再考虑降低 k 值或者增大限幅判断是不是增益过大。
用 Scope 把 e、theta_e、delta 三个中间变量同时画出来,能很快看出是哪个环节先异常的。一般来说,符号问题会导致误差一路增大最终发散,角度未归一化会导致转角的尖刺脉冲,增益过大则表现为持续但幅度有限的摆动,三者特征不一样,看曲线基本能区分。
6.3 路径跳变与延时错位,坐标和时间轴都要对齐
路径跳变的现场是车辆突然向某个方向猛打一把方向,然后很快回正。排查下来大多不是控制器问题,而是参考路径本身不连续。一种是路径点顺序错乱,某个点的索引跳跃导致最近点一下子跳到远处;另一种是 Carsim 输出的车辆坐标和路径坐标不在同一个坐标系。Carsim 默认输出的是车辆起始位置的绝对坐标,而有些脚本生成的路径是从原点开始的局部坐标,两边不统一就会出现起始阶段偏差特别大的现象。
延时错位则是另一个问题,常见于 From Workspace 读取路径时时间列没设置好,路径数据峰值和车辆位置在时间上错开,导致控制器一直在跟踪一个滞后或者超前的路径。我后来把路径数据格式统一为静态数组,时间列固定为递增序号,彻底避开了时间轴匹配问题。
6.4 低速饱和与高速迟钝,让增益随车速变化
联调跑不同车速时我发现一个规律:低速下 Stanley 的转角输出很容易触发限幅,高速下又觉得修正力度不够。原因是 arctan(k * e / v) 这个项里 v 在分母上,低速时同样的横向误差会产生更大的转角,如果路径曲率再大一点就饱和了;高速时转角修正变小,跟踪误差自然变大。
解决方式是让 k 随车速变化。我测试的方案是 k = 基础值 + 速度系数乘以 vx,基础值负责低速段的稳定性,速度系数负责高速段的修正强度。低速段 k 保持在 1.0 附近,车速到 60km/h 时 k 提升到 1.8 左右,双移线路径的高速跟踪误差比固定 k 下降了大概三分之一。如果你不想用速变增益,也可以给 v 加一个小量避免低速除零,然后在低速工况下单独限幅,这样做系统更稳但误差表现会逊色一些。
7. 实操心得与后续扩展方向
最后分享一点我的个人体会。做完这轮 Stanley 联仿,我最大的感受是这类几何控制器看起来简单,真正花时间的地方全在细节:误差符号、坐标对齐、限幅、步长、路径插值,任何一个没弄好,整体效果都会大打折扣。联仿最怕的不是算法不行,而是接口和数据定义不一致,你在控制器里看到的是某个信号,实际 Carsim 传过来的可能是另一个含义,这种错位很难一眼看出来。
建议第一次做的人严格按照先直线后弯道、先低速后高速的顺序来,把每个环节的数据都留档。路径可改这个能力值得做成标准模块,后面接真实规划模块的时候,控制器部分几乎不用动,只需要把路径数据源从工作区变量换成规划输出就行。如果你想继续深入,可以尝试把 Stanley 和预瞄思想结合起来,或者在前馈项里加入路径曲率补偿,让高速大弯道的跟踪误差进一步下降。我后续打算把路径模块升级成实时接收规划层的离散路径点,再做一轮曲率补偿实验,到时候继续把这些实测数据整理出来。