车辆轨迹跟踪这个主题,做智能驾驶算法验证的人肯定不陌生。这两年我接触最多的就是标题里这套“Carsim + Simulink + 预瞄PID”的轨迹跟踪模型:主车动力学交给Carsim,Simulink负责控制算法,用预瞄PID实时计算方向盘转角,让车沿着参考路径走。相比直接跑去实车测试,这套联合仿真环境能把算法迭代周期从“周”压缩到“小时”,对刚接触横向控制的人来说,也是性价比很高的入门组合。这篇文章把整个模型的原理、环境搭建、代码实现和调参过程完整拆开讲,照着走一遍基本能跑通。
1. 项目概述与总体架构
1.1 轨迹跟踪到底在解决什么问题
轨迹跟踪,说简单点就是让车辆自动沿着一条预先定义好的路径行驶,比如车道中心线、换道路径、避障路径或者其他试验工况路径。这里面其实包含两层控制:纵向控制管车速,决定车跑多快;横向控制管方向盘,决定车往哪儿转。大部分轨迹跟踪算法研究的重点都在横向控制上,因为纵向作为独立模块相对容易处理,而横向控制的难点在于:车辆本身是一个复杂的非线性系统,轮胎侧偏、车速变化、道路曲率都会影响控制效果。
这套模型的应用场景很典型:算法开发初期没有条件做大规模实车测试,或者某个控制算法还处在验证阶段,就需要用高精度车辆模型代替真车,在仿真环境里反复跑工况、看曲线、调参数。Carsim正好提供高保真度的整车动力学模型,Simulink负责快速搭建控制逻辑,两者联合起来,可以在一个仿真周期内看到轨迹跟踪的实际效果。
我经常跟人打的比方是:Carsim相当于一个“虚拟试车场”,车辆的各种底盘、轮胎、悬架特性都给你算好了;Simulink相当于驾驶员的“大脑”,负责看路、算偏差、打方向。两者配合,就能在电脑上完成一轮又一轮的算法验证,既快又没有安全风险。
1.2 为什么是“Carsim + Simulink + 预瞄PID”的组合
先聊算法选型。轨迹跟踪领域常见方案无非这么几类:纯几何类的Pure Pursuit(纯跟踪)、基于运动学和动力学模型的最优控制(MPC、LQR)、以及最基础的PID类控制。纯跟踪实现简单,但高速大曲率工况下跟踪精度不够;MPC效果好,但对建模能力和计算资源要求高,Simulink里搭一个非线性MPC,光求解器配置就要折腾半天。
相比之下,预瞄PID这组合特别适合入门和快速验证:预瞄机制模拟了驾驶员“眼睛往前看”的驾驶习惯,PID控制器就干一件事——根据前方预瞄点与目标路径的横向偏差,把方向盘转角算出来。这种算法的好处是:物理意义清晰、代码量小、参数少、可解释性强。出了问题也容易定位,不会像MPC那样排查半天都不知道是模型失配还是求解器问题。
再说联合仿真环境。Carsim里整车参数都是现成的,换车型、改车速、改路面条件都在界面上点几下的功夫。Simulink里搭预瞄模块、PID模块、限幅模块,都是非常成熟的建模方式。这套组合让我最满意的地方是:它把“车辆系统”和“控制系统”的边界划得很清楚,车辆在Carsim里跑,算法在Simulink里算,中间的输入输出口就是方向盘转角、速度、位置这些物理量。
2. 预瞄PID算法的核心思想与数学模型
2.1 预瞄机制:模拟驾驶员的目光
预瞄PID里最关键的设计就是“预瞄”两个字。为什么不能直接拿当前时刻的横向偏差去控制?因为车辆有惯性,从打方向盘到车身真正产生横向位移,是有延迟的。如果只根据当前的偏差打方向,车在弯道里一定会“跟不上”,表现为转弯切内线、轨迹滞后甚至蛇形摆动。
预瞄的解决办法就是:向前看一段距离L,用前方远处的偏差来决定当前怎么打方向。相当于我们开车的时候,眼睛不会死盯着车头正下方的白线,而是会看前方几十米的车道线来调整方向盘,这就是预瞄的直观意义。预瞄机制的好处是,它在控制环节中引入了一个“提前量”,能让转向决策更平滑,也能补偿车辆转向响应的滞后。
预瞄距离L的计算,最常用的有两种方式。第一种是按预瞄时间T_preview乘以当前车速Vx: L = Vx * T_preview
T_preview一般取0.1到1.5秒。低速时预瞄时间可以取得小一些,高速时取大一些。第二种是加一个基础偏移量的经验公式: L = a * Vx + b
其中a、b是标定参数,比如a=0.8,b=3。本质上都是让车速越快,看得越远,给车辆足够的反应时间。我自己做模型时,习惯先把预瞄时间定在0.4秒左右,跑通整个闭环后再去扫参优化。
2.2 PID控制律与横向误差定义
预瞄PID的核心输入量是“横向偏差”,但偏差到底怎么算,这里面的细节决定了整个模型能不能跑起来。我采用的是车体坐标系下的投影式误差计算:
先拿到Carsim输出的车辆全局坐标位置(x_now, y_now)和航向角Yaw,把参考路径上的每个点都变换到车体坐标系下,纵向距离Lon和横向距离Lat分别按下面两式计算:
- Lon_i = (x_path_i - x_now) * cos(yaw) + (y_path_i - y_now) * sin(yaw)
- Lat_i = (y_path_i - y_now) * cos(yaw) - (x_path_i - x_now) * sin(yaw)
然后在路径点序列中寻找纵向距离最接近预瞄距离L的那个点,该点的Lat值就是当前控制周期要用的横向误差e_lat。这个误差的正负方向需要跟方向盘转角方向约定好,比如左偏为正、逆时针打轮为正,这套符号关系在模型调试时非常容易出错。
得到误差之后,PID控制律就按标准形式计算: delta = Kp * e_lat + Ki * integral(e_lat) + Kd * derivative(e_lat)
其中delta是方向盘转角需求值,单位建议用角度(度)。在Simulink里我一般不用PID Controller库自带模块,而是手写这个公式。原因很简单:手写方便加积分限幅和输出限幅,也方便把中间变量引出来看曲线,调参时能直观看到误差、积分项、微分项各自的变化。
2.3 参数整定的顺序和初值参考
PID参数整定看起来是老生常谈,但在轨迹跟踪场景里有自己的路数。我按下面的顺序走,踩坑最少:
第一步,先只调比例项Kp。把Ki和Kd设成0,从0.2开始逐步增大,观察车辆在直线路径上的响应。如果车速30km/h左右,横向误差在弯道区域出现高频震荡,说明Kp已经偏大,退回到震荡临界值的70%左右作为初始Kp。
第二步,加微分项Kd抑制超调。Kd的作用类似阻尼,能抑制蛇形摆动。初值可以取Kp的1/10到1/20,逐步试。
第三步,加积分项Ki消除稳态误差。直线、弯道和大曲率路径下如果横向误差迟迟收敛不到0附近,就需要Ki来纠正。注意积分项一定要做限幅,否则车辆在起步或路径突变时,积分项很容易堆积成饱和,引起大幅度超调。
按照这个流程,我在一套常见工况下给出的初值参考是:车速30km/h、预瞄时间0.4秒时,Kp取1.0至1.5,Ki取0.01至0.05,Kd取0.05至0.15。这个范围不保证所有车都适用,但作为起点足够。
3. 搭建Carsim与Simulink联合仿真环境
3.1 Carsim端配置:车型、工况与接口
Carsim端的配置是整个模型的基础。打开Carsim后,先选车型,我常用的是A级两厢车,整备质量在1200公斤左右,轴距和轮胎参数都有现成默认值,不需要额外标定。然后设置仿真工况,包括初始车速、路面附着系数、转向输入方式等等。
关键是接口配置。Carsim与Simulink联合仿真的本质是:Carsim把整车状态输出给Simulink,Simulink把控制量反馈给Carsim,两边按固定步长同步运行。在Carsim的主配置界面里,需要把“外部接口”设置为Simulink模式,然后分别定义输入通道和输出通道:
输入通道至少要包含方向盘转角,如果要做纵向控制,还要加节气门或制动压力。我早期做纯横向控制验证时,只保留方向盘转角作为输入,纵向速度由Carsim内置的定速控制器保持。这里有个小技巧:把Carsim的定速功能打开,设置目标车速为固定值,这样Simulink模型里就不用再管油门制动,可以把全部精力放在横向控制上。
输出通道建议选择车辆纵向速度Vx、全局横坐标X、纵坐标Y、航向角Yaw、横摆角速度YawRate。需要特别留意单位问题,Carsim里位置坐标单位是米,航向角单位是弧度,这些信息要提前确认好。
设置完成后,在Carsim里“发送”到Simulink,会生成一个Carsim S-Function封装模块,这个模块就是整个联合仿真的车辆动力学核心。
3.2 Simulink端模型架构
Simulink这一侧的模型结构其实不复杂,模块划分清晰就好。我一般把整个模型分成四块:参考路径输入模块、预瞄误差计算模块、PID控制器模块、Carsim主车模块。
参考路径输入模块用From Workspace或者Lookup Table,读入预先准备好的路径点数组。路径数据格式一般是时间或者里程索引对应的X、Y坐标数组。我这里更推荐用“路径累计里程s”作为索引基准,而不是单纯用点序号,因为在变速工况下,用点序号做预瞄索引容易错位。
预瞄误差计算模块用MATLAB Function实现,输入是车辆当前状态和参考路径点,输出是横向误差e_lat。这个模块是模型最核心的部分,直接决定了控制质量。PID控制器模块接收e_lat,计算方向盘转角,输出前加一个Saturation模块做限幅,比如±45度。Carsim主车模块就是上一步生成的S-Function,把方向盘转角接入它的输入端口,同时把它输出的车辆状态送给预瞄误差计算模块。
Simulink的求解器设置也很重要。我推荐使用定步长、固定步长0.01秒,求解器选ode4或ode5。定步长能保证Carsim与Simulink的同步稳定性,避免变步长时两个系统之间采样时刻对不上,引起莫名其妙的抖动。
4. 轨迹跟踪模型的核心实现
4.1 参考路径生成与导入
参考路径的来源有很多种,最常见的是双移线路径、圆弧路径、正弦路径,或者直接用实车采集的GPS轨迹。在仿真验证阶段,我喜欢先用Python脚本生成路径,再导出为CSV或MAT文件供Simulink读取。下面这段代码生成一个带平滑过渡的双移线路径,属于最常用的验证工况:
import numpy as np import pandas as pd def generate_double_lane_change(length=160, start=30, end=90, lane_offset=3.5): x = np.arange(0, length, 0.1) y = np.zeros_like(x) mask = (x >= start) & (x <= end) y[mask] = lane_offset * 0.5 * ( 1 - np.cos(2 * np.pi * (x[mask] - start) / (end - start)) ) y[x > end] = lane_offset # 路径点的累计里程,用于预瞄点搜索 dx = np.diff(x) dy = np.diff(y) s = np.concatenate(([0], np.cumsum(np.sqrt(dx**2 + dy**2)))) return np.column_stack([s, x, y]) path = generate_double_lane_change() df = pd.DataFrame(path, columns=["s", "x", "y"]) df.to_csv("double_lane_path.csv", index=False)路径生成时要注意一个细节:路径点的间距要尽量均匀,而且不能太稀。间距太大,预瞄误差计算时线性插值会带来额外偏差,导致控制结果出现肉眼可见的锯齿感。我通常把路径点间距控制在0.1米以内,文件不算大,仿真效率也能接受。
4.2 预瞄点计算模块实现
预瞄点计算模块是整个模型的核心,我用MATLAB Function实现,输入是当前车辆位置、航向角、速度和参考路径,输出是横向误差和预瞄点的目标路径索引。核心代码如下:
function [e_lat, e_lon, target_idx] = preview_error(... x_now, y_now, yaw, vx, path_s, path_x, path_y, T_preview) % 1. 根据车速和预瞄时间计算预瞄距离 L = vx * T_preview; % 2. 参考路径点变换到车体坐标系 dx = path_x - x_now; dy = path_y - y_now; lon = dx .* cos(yaw) + dy .* sin(yaw); lat = -dx .* sin(yaw) + dy .* cos(yaw); % 3. 找纵向距离最接近预瞄距离的点作为目标点 [~, target_idx] = min(abs(lon - L)); % 4. 输出该点的横向误差与实际纵向位置 e_lat = lat(target_idx); e_lon = lon(target_idx); end这段代码看起来简单,但实际用起来有几个容易踩的坑。第一个坑是航向角方向符号,Carsim输出的航向角是基于全局坐标系的,但不同版本、不同车型设置下,正方向定义可能不同。如果横向误差符号反了,车辆会朝着远离路径的方向跑,模型基本没法收敛。
第二个坑是预瞄匹配点的选择。当车速较高、预瞄距离远超路径长度时,min(abs(lon - L))可能会匹配到路径终点之后非常远处的点,导致误差异常。我的处理办法是对预瞄距离做上限约束,比如不超过路径总长度的80%,同时当路径剩余长度不足时直接用终点作为虚拟预瞄点。
第三个坑是车辆在路径上倒退或者静止起步时,前方预瞄点匹配逻辑会混乱。起步阶段我一般先让车速从0平滑上升到目标值,预瞄PID在速度接近0时输出0转角,避免车辆原地画圈。
4.3 方向盘转角约束与输出连接
PID控制器输出的delta是期望的方向盘转角,不能直接无限制地送给Carsim,原因有二:真实车辆方向盘转角有机械极限,超过极限值不符合物理规律;仿真中过大转角会导致车辆动力学模型数值发散。
所以输出端一定要加Saturation限幅。我常设的方向盘转角范围是正负45度,这个范围对应常规乘用车的方向盘极限。如果后面要接前轮转角,注意方向盘转角到前轮转角有个转向传动比,通常乘用车在12到18之间,Carsim内部会自己处理这个关系,只要输入给它方向盘转角即可。
还有一个细节是转向响应带宽问题。Carsim内部的转向系统模型是有惯性、有阻尼的,不是理想的“给多少转多少”。如果PID输出转角变化频率太高,实际车辆前轮响应跟不上,反而会激发振荡。遇到这种情况不要盲目加大Kd,而是应该在PID输出后面加一阶低通滤波器,把转角变化的速率限制住。我的习惯是在输出端加一个截止频率3到5Hz的低通滤波,效果比单纯调阻尼参数更直接。
控制器输出部分,如果用了Simulink的PID模块,需要勾选“限制输出”并配置积分抗饱和(anti-windup)方式,否则积分饱和很容易在路径突变工况下让方向盘卡在极限位置回不来。手写PID的集成限幅逻辑就更简单了:积分项单独做限幅,输出再做限幅,两段限幅分开都比只限输出更稳。
5. 仿真调试、问题排查与参数优化
5.1 由简到繁的调试路线
拿到一套新模型,第一件事不是跑双移线,而是跑直线。我的调试路线是典型的“由简到繁”:
第一步,直线偏移工况。给定一条直线参考路径,但初始时刻把车辆横向位置偏移0.5米。这时预瞄PID应该控制车辆快速回到直线上。跑这一步的目的有两个:核对误差符号是否正确、观察有无稳态误差。如果连直线都拉不回来,说明前面积分方向或者误差定义肯定有问题。
第二步,圆弧路径工况。让车辆沿半径50米的大圆弧行驶,观察横向误差的收敛值和车辆是否稳定。圆弧工况能暴露预瞄距离与车速匹配的问题,也能看到稳态误差是否明显。
第三步,双移线工况。这才是轨迹跟踪的“典型考题”,因为双移线包含连续两次反向变道,车速越高,对控制器的动态响应要求越高。先从20km/h跑起,逐步加到40km/h,记录横向误差峰值。
调试过程中,强烈建议把预瞄误差、PID输出、方向盘转角和横向位移这四个信号放到同一个Scope里对比观察,很多问题一眼就能看出来。比如误差还没有归零,方向盘就提前回正了,大概率是微分项作用过强;误差一直朝一个方向偏,大概率是积分项不够。
5.2 常见问题排查速查表
下面这些问题是这套联合仿真模型里出现频率最高的,我整理成了一张速查表,照着排查基本能覆盖绝大多数场景:
| 典型现象 | 可能原因 | 处理办法 |
|---|---|---|
| 模型运行后车辆直接跑出道路 | 误差符号相反 | 断开控制器,手动给正方向转角,确认Carsim输出航向角方向定义 |
| 横向误差高频震荡 | Kp过大或预瞄距离太短 | 降低Kp,或者增大预瞄时间到0.5秒以上 |
| 弯道处横向误差有稳定残余 | 积分项不足或预瞄距离过小 | 增加Ki,或者检查预瞄距离是否匹配当前车速 |
| 方向盘转角反复来回打 | Kd过大或输出未滤波 | 减小Kd,在输出端增加低通滤波 |
| 预瞄点索引跳动导致误差突变 | 路径点间距过大 | 加密路径点,确保间距小于0.1米 |
| 仿真速度明显变慢 | 变步长求解器或输出通道过多 | 改用定步长0.01秒,精简Carsim输出通道 |
| 积分饱和,车辆在直线后长时间偏离 | 积分项无限幅 | 对积分项单独限幅,或加PID抗饱和逻辑 |
| 高速工况控制性能明显恶化 | 预瞄距离偏短 | 将预瞄距离改成关于车速的线性函数 |
5.3 预瞄距离与PID参数的协同调节
预瞄距离和PID参数其实是耦合的,不能单独调。我在跑完一轮调试后有一个明显体会:先固定预瞄时间调PID,再回头调预瞄时间,这两个步骤要交替进行。
举个实际例子,当预瞄时间从0.4秒增加到0.7秒,横向误差的响应会变“迟钝”,路径尖角处的峰值误差会变大,但同时车辆姿态会更平稳,不会出现大幅度摆动。这时候如果发现弯道误差增大,就可以适当提高Kp,因为更远的预瞄距离让误差的“相位滞后”变小了,比例控制的稳定性余量反而更充足。
我最终采用的方式是把预瞄距离设计成速度的一次函数:低速时以固定偏置为主,高速时以速度项为主。比如: L = 3.0 + 0.6 * Vx
其中Vx的单位是km/h,这样30km/h时预瞄距离约21米,60km/h时约39米。高速时看得更远,车辆状态变化更平缓,效果比固定预瞄时间好很多。
调PID时还要注意车速变化。有些模型在Simulink里把车速设为变量,预瞄PID按当前车速实时计算预瞄距离,算法对加速和减速工况会自动适应。这种动态预瞄方式配合固定预瞄时间,等于把“经验”固化成代码,是我比较推荐的做法。
6. 个人实操心得与后续扩展方向
6.1 最容易被忽略的三个细节
这套模型我前后调了好几轮,每次回看都能发现一些细节问题。第一个最容易忽略的是Carsim坐标方向的一致性。全局坐标系下的Y轴正方向、航向角正方向,不同车型文件里定义不完全一样,误差计算前必须把符号关系理清楚,否则后面所有调试都是白费。
第二个细节是路径预处理。很多参考路径来自外部采集,可能存在毛刺和异常点。我通常在生成路径后先做一次滑动平均滤波,把突变点抹平。否则这些毛刺会被预瞄误差计算模块当成真实路径偏差,PID会跟着产生额外的转向波动。
第三个细节是模型里所有信号的数据类型和维度。Simulink的MATLAB Function模块对输入输出维度很敏感,路径数组是行向量还是列向量、Carsim输出是有时间戳的序列还是标量,这些不一致会引发各种报错。我的习惯是在模型里统一使用列向量,并在每个接口处加Selector模块做明确的数据抽取,不嫌麻烦。
6.2 在这套框架上还能怎么做扩展
这套联合仿真框架跑通之后,扩展性其实很强。最简单的扩展是把预瞄PID换成纯跟踪算法或Stanley算法,误差计算模块和路径模块都不用大改,只需要替换控制律那部分。我在同一套Carsim模型上对比过预瞄PID和纯跟踪,结论是低速工况两者差距不大,高速大曲率工况预瞄PID在参数整定后表现更稳。
再进一步,可以把横向控制器从PID换成LQR或者简单MPC。Carsim提供车辆状态输出,Simulink里可以搭一个线性二自由度车辆模型作为预测模型,计算量适中。这种做法的好处是:同一套仿真环境,不同控制算法的对比结果更公平,也更适合论文或项目汇报里做算法对比。
还有一个方向是跟纵向控制联立。目前我是靠Carsim定速功能保持车速,如果要模拟真实跟车工况,或者测试弯道自动减速逻辑,就需要在Simulink里增加一个纵向PID控制器来控制油门和制动。纵向控制器和横向预瞄PID共用一套Carsim车辆模型,不需要额外配置,只是输入通道多加两个而已。
最后说一下我的个人体会。这套模型跑到现在,最让我受益的不是某个具体的参数组合,而是整个“仿真闭环”思维:车辆模型用专业工具保真,控制算法用快速建模环境迭代,两边通过清晰的接口协同工作。这种思路放到实车测试阶段也一样适用,算法在仿真里调得越扎实,上车之后暴露的问题就越少。如果你也正在搭类似的轨迹跟踪模型,建议先把误差定义和坐标系逻辑理清楚,再追求复杂的控制算法,基础牢了,后面换什么控制器都有底气。