做Carsim与MATLAB联合仿真的朋友应该都有体会:双车跟驰、单车轨迹跟踪这类场景,跑通并不难;但一旦场景里面出现三辆车——主车、前车、对向车——问题的性质就彻底变了。它不再是一个单纯的“跟踪问题”,而是要求主车在一个不断缩小的时空窗口内,决定到底是减速制动,还是借道变道避障。我最近正好做了一版三车避障轨迹规划,判定策略采用安全距离模型加碰撞时间(TTC)双重触发,车辆动力学部分交给Carsim,规划与控制算法放在MATLAB/Simulink里。整套链路做下来,最大的感受是:算法本身并不复杂,复杂的是两个软件之间的接口耦合,以及触发参数的标定。这篇文章打算把三车避障场景的触发逻辑设计、联合仿真环境搭建、轨迹生成方法和调试中踩过的坑完整梳理一遍,给正在做多车避障仿真或者准备接触Carsim联合仿真的朋友一条可以复现的路径。
1. 三车避障的本质:从“离得够不够远”到“能不能变道过去”
1.1 安全距离回答了“要不要干预”,TTC回答了“敢不敢变道”
在只考虑单车跟驰时,核心判断就是“和前车的距离够不够远”,不够远就减速,够远就维持当前车速,这是ACC(自适应巡航)和AEB(自动紧急制动)最常见的逻辑。但三车避障场景里,主车不仅要处理前车,还要处理对向车道的来车,这时候“距离够不够”只是第一层判断,更关键的是第二层判断:如果变道到对向车道,在完成避障之前,会不会和对面来车撞上。
这里就能看出安全距离模型和TTC的分工。安全距离模型负责判断“纵向迫近程度”,它告诉我主车和前车之间还有多少安全冗余;TTC负责判断“对向时间窗口”,它告诉我以当前相对接近速度,还有多少秒会和来车相遇。举个例子:前车突然减速,安全距离模型很快报警,但如果这时候只触发制动,主车可能被后车追尾,或者错失唯一能绕过去的机会。反过来,如果只看对向车道很安全就立刻变道,但前车距离已经贴到几米内,那还没等轨迹规划完成,可能已经先撞上前车了。
所以三车场景的正确逻辑是:安全距离模型决定“是否需要躲避”,TTC决定“是否具备躲避条件”,两者缺一不可。
1.2 三车冲突的时空联合约束
三车避障的难点在于,纵向和前车的约束、横向和对向车的约束,是互相耦合的。纵向约束要求主车与前车保持安全距离,这决定了主车什么时候必须开始动作;横向约束要求主车在完成换道的时间内,不能与对向车相遇,这决定了换道动作能不能执行。
两者联合起来,可以写成两个必要的可行性条件:
- 纵向空间条件:
v_e * t_f + d_margin < d_lead。意思是整个换道过程主车纵向行驶过的距离,加上一个安全余量,必须小于主车当前到前车的距离。 - 对向时间窗条件:
TTC_on > t_f + t_margin。意思是换道完成之前,主车与对向车之间保留足够的时间余量。
这两个条件在工程上非常直观。如果只满足纵向空间,但不满足对向时间窗,那说明变道过程中会和对向车迎面相遇,只能放弃变道,选择制动;如果只满足对向时间窗,但纵向空间不够,说明前车已经太近,来不及完成变道动作,同样只能制动。真正能执行换道避障的工况,必须两个条件同时成立,这也是我在实际仿真里反复验证过的结论。
2. Carsim与MATLAB/Simulink联合仿真环境搭建
2.1 版本匹配与编译器问题
Carsim和MATLAB/Simulink联合仿真第一步,也是最容易卡住的一步,就是版本兼容。Carsim生成Simulink模块时,本质上是在调用MATLAB的S-Function机制,生成一个可以被Simulink调用的动态链接库,这个过程对编译器的版本非常敏感。
我用的是Carsim 2019搭配MATLAB R2020b的组合,整体比较稳定。如果你用的版本组合不同,最常遇到的问题是点击生成Simulink模型时报错,提示找不到编译器或者无法创建MEX文件。出现这类问题,先不要急着怀疑模型,优先检查MATLAB的mex -setup是否指向了正确的编译器。Carsim在生成模型时会调用MATLAB的MEX编译配置,如果配置里选的编译器和Carsim内部的编译器不一致,就会在这里断掉。
这个坑很隐蔽,因为Carsim自身的界面不会报编译错误细节,它只会告诉你“无法生成Simulink模块”,导致很多人以为是软件安装问题,折腾半天重装,最后发现是编译器配置的问题。
2.2 车辆模型输入输出通道配置
联合仿真的核心是数据交换。Carsim负责车辆动力学,外部控制信号要从Simulink进来;车辆状态要从Carsim输出给Simulink。I/O通道的配置必须提前确认,不然后续所有算法都是无源之水。
以我常用的配置为例:
| 方向 | 通道名 | 物理含义 | 单位 |
|---|---|---|---|
| 输入 | Steering_L1 | 前轮转向角或方向盘转角 | deg |
| 输入 | Throttle | 油门开度 | 0~1 |
| 输入 | Brake_Master_Cylinder | 制动主缸压力 | MPa |
| 输出 | Vx | 纵向车速 | m/s |
| 输出 | Vy | 横向车速 | m/s |
| 输出 | X, Y | 车辆全局坐标 | m |
| 输出 | Yaw | 横摆角 | rad |
| 输出 | Yaw_rate | 横摆角速度 | rad/s |
| 输出 | Ax, Ay | 纵向和横向加速度 | m/s² |
配置步骤上,先在Carsim图形界面里选择Simulink接口,然后在输入输出页面里添加上述通道,最后生成对应的S-Function模型文件。需要注意的一点是:联合仿真时一定要把Carsim内部自带的驾驶员模型断开,否则外部控制信号和内部驾驶员模型会互相竞争,车辆会出现严重的抖动或方向来回摆动。
2.3 步长匹配与代数环处理
联合仿真的步长设置,属于那种“看似小事、出事就是大事”的环节。Carsim内部求解器一般按1毫秒积分,Simulink控制层如果也按1毫秒跑,计算量会很大;如果控制步长设太大,比如50毫秒,车辆状态更新太慢,控制器反应不过来,容易造成轨迹跟踪震荡。
我通常的做法是:Simulink整体固定步长设为1毫秒或2毫秒,规划和控制模块的更新周期单独用采样时间模块设成10毫秒或20毫秒。这样既能保证车辆动力学仿真的精度,又不让规划算法每一个仿真步都重复计算。
另外一定要留意代数环。Carsim的S-Function输出车辆状态,状态再进入控制器计算控制量,控制量又立即返回S-Function,这一圈如果没有任何延迟环节,Simulink会形成代数环,直接导致仿真速度极慢或者报错。解决办法很简单,在状态信号进入控制器之前,加一个Unit Delay模块,打破这个瞬时反馈环路,仿真就能正常跑起来。
3. 碰撞风险判定:安全距离与TTC的协同触发逻辑
3.1 安全距离模型的数学形式与参数
我采用的是基于临界制动距离的安全距离模型,也就是业内常说的Mazda模型。它的核心思想是:主车当前速度下全力制动所需距离,减去前车全力制动所需距离,再叠加算法反应时间内驶过的路程和最后保留的冗余间距,得到最小安全跟车间距。
表达式为:
s_safe = 0.5 * (v_e² / a_e - v_f² / a_f) + v_e * t_resp + d0
其中:
v_e是主车速度,a_e是主车最大制动减速度v_f是前车速度,a_f是前车最大制动减速度t_resp是反应时间,包含传感器延迟和算法处理延迟d0是停车后保留的最小冗余间距
如果前车是静止障碍物,v_f = 0,模型退化为0.5 * v_e² / a_e + v_e * t_resp + d0。请注意,这个公式在低速场景下要加一个下限保护,否则前车在红绿灯路口减速时,模型会频繁触发不必要的干预。我在代码里对s_safe做了一个最小限制,不能小于5m,实测下来误报少了很多。
参数标定上,t_resp我取了1.2秒,这个值对算法级控制来说留得比较足;d0取了3米;最大制动减速度按良好路面取6m/s²,湿滑路面工况会降到4m/s²以下。这些参数没有绝对标准,要根据你的场景和触发灵敏度调整。
3.2 TTC计算与阈值分层
TTC(Time to Collision,碰撞时间)的定义非常简单:当前相对距离除以相对速度。主车与前车之间的TTC:
TTC_lead = d_lead / (v_e - v_f)
注意这个公式只在v_e > v_f时有效,也就是主车正在迫近前车;如果主车速度比前车慢,相对速度是负数,TTC没有实际意义,应该视为无效状态,不参与触发判断。
三车场景中,更要关注的是主车与对向车之间的TTC。对向车是相向运动,相对接近速度等于两者速度之和:
TTC_on = d_on / (v_e + v_on)
这个值直接决定了主车还有多少秒进入对向车的迎面冲突区域。
实际工程里,TTC不能只设一个阈值,我用了三层阈值叠加:
| 层级 | 触发条件 | 动作策略 |
|---|---|---|
| 预警层 | TTC_on < 5s或d_lead < 1.5 * s_safe | 记录风险,进入预减速状态 |
| 执行层 | TTC_on > 3s且纵向空间条件成立 | 启动换道轨迹规划 |
| 放弃层 | TTC_on < 1.2s | 取消换道,转入全力制动 |
这个分层设计的逻辑是:换道动作本身需要时间,从轨迹生成到转向执行再到车辆完全变道,大概需要4秒左右,所以执行层阈值必须留出足够余量;而一旦TTC逼近1.2秒,说明时间窗口已经很小,强行变道极有可能在路中间和对向车相撞,这时候全力制动反而更安全。
3.3 触发仲裁逻辑的实现
把上面的数学模型组合起来,仲裁逻辑并不复杂,关键是状态机的顺序要理清楚。我实现的逻辑可以用一个伪代码表达:
if d_lead > s_safe: 状态 = 正常跟驰 else: if ttc_on > TTC_ALLOW and v_e * t_f + d_margin < d_lead: 状态 = 换道避障 else: 状态 = 紧急制动但有一个容易被忽略的细节:换道动作一旦开始,不能原地不动等待结果,必须持续监测对向车的TTC。真实仿真中,对向车速度不是恒定的,可能突然加速,这时换道窗口会快速关闭。我加入了一个“换道中止”机制:每当TTC_on跌破放弃层阈值,规划器立即生成一条返程轨迹,让主车从当前对向车道位置回到原车道中心,同时切换为制动状态。这个逻辑在纯双车场景里不存在,但三车场景必须写上,否则会出现“变到一半撞上对向车”的严重事故。
4. 避障轨迹生成:五次多项式换道与速度协同规划
4.1 为什么选五次多项式
避障轨迹生成,我最终选了五次多项式。原因很直接:它可以同时约束位置、速度和加速度六个边界条件,保证换道轨迹的两端横向加速度连续变化,没有突变;三次多项式只能约束位置和速度,无法控制起终点加速度,导致车辆在轨迹起点和终点产生横向加速度跳变,反映到Carsim车辆模型里,就是明显的横向冲击。
五次多项式的表达式是:
y(t) = a0 + a1*t + a2*t² + a3*t³ + a4*t⁴ + a5*t⁵
其中y(t)是横向位移,t是换道时间。六个系数由六个边界条件唯一确定。
4.2 横向轨迹边界条件与系数求解
换道起点和终点的边界条件如下:
- 起点
t=0:横向位置为0、横向速度为0、横向加速度为0 - 终点
t=t_f:横向位置为车道变换距离d_lane、横向速度为0、横向加速度为0
写成矩阵形式,在MATLAB里可以直接用左除求解:
t_f = 4.5; % 换道时间,单位s d_lane = 3.5; % 横向位移,单位m,近似一个车道宽度 A = [1, 0, 0, 0, 0, 0; 0, 1, 0, 0, 0, 0; 0, 0, 2, 0, 0, 0; 1, t_f, t_f^2, t_f^3, t_f^4, t_f^5; 0, 1, 2*t_f, 3*t_f^2, 4*t_f^3, 5*t_f^4; 0, 0, 2, 6*t_f, 12*t_f^2, 20*t_f^3]; b = [0; 0; 0; d_lane; 0; 0]; coeff = A \ b; t = 0:0.01:t_f; y = coeff(1) + coeff(2)*t + coeff(3)*t.^2 + coeff(4)*t.^3 + coeff(5)*t.^4 + coeff(6)*t.^5;这样生成的轨迹,横向位移从0平滑过渡到3.5米,起终点速度和加速度都为零,曲线形状是一个典型的S形。Carsim车辆模型跟踪这种轨迹,转向角输入会非常平滑,不会出现剧烈的左右摆动。
4.3 换道时间与可行域验证
换道时间t_f的选择是规划层的核心参数。t_f太短,横向加速度过大,乘员舒适性下降,而且轮胎侧向力容易饱和;t_f太长,纵向行驶距离过大,可能在前车位置附近还没完成足够的横向偏移,直接撞上去。
工程上可以按峰值横向加速度来预估换道时间。车道宽度3.5米,五次多项式换道轨迹的峰值横向加速度大约出现在轨迹中点附近,量级大致为2 * d_lane / t_f²。如果限制峰值横向加速度不超过3m/s²,那么t_f至少要4秒以上。我常用4.5到5秒,兼顾安全与舒适。
选完t_f之后,还要回到第1节说的联合可行性条件做一次验证。算一个我实际跑过的工况:主车车速20m/s,前方60米处有一辆静止车辆,对向车道300米外有一辆以30m/s行驶的来车。
- 换道纵向距离:
20 * 4.5 = 90m,已经大于前车距离60m,按保守判据,换道中途无法完全避开静止前车,所以这个工况只能制动,不能换道。 - 对向车TTC:
300 / (20+30) = 6s,时间窗足够,但纵向空间不够,联合判断依然指向“不可换道”。
这就是联合判据的价值:只算时间窗没问题,会诱导你执行一个根本完不成的动作。
4.4 纵向速度规划
横向轨迹确定之后,纵向速度也需要同步规划。换道过程中不应该急加速,否则横向轨迹带来的额外轮胎负荷和纵向加速度叠加,容易突破附着极限;也不应该大幅减速,那样对向车时间窗会快速缩短。
我采用梯形速度规划:换道前半段保持当前速度,中间根据前车距离做一次小幅降速,后半段恢复原速。纵向加速度限制在2m/s²以内,冲击度(加速度变化率)限制在3m/s³以内。最终的期望纵向速度作为PID控制器的目标值,和横向轨迹一起下发到控制层。速度曲线和横向轨迹分开规划,再通过时间序列对齐,是工程上非常实用的做法。
5. 轨迹跟踪控制与Simulink闭环实现
5.1 控制架构:规划层与控制层分离
整个Simulink程序我分成两个层级:规划层负责根据仲裁逻辑生成参考轨迹,控制层负责让Carsim车辆模型跟踪参考轨迹。这两层必须分离,否则每次控制周期都重新做轨迹规划,计算量太大,而且轨迹连续性没法保证。
规划层以事件驱动的方式运行:仲裁逻辑一旦判定需要换道,就生成一次完整的换道轨迹,后续控制周期只是沿着这条轨迹取点,不重复规划。只有当“换道中止”条件触发时,规划层才重新生成返程轨迹。
控制层包括两个方向:
- 横向控制采用纯跟踪模型或Stanley控制器,输入是参考轨迹点和车辆当前位姿,输出是转向角。
- 纵向控制采用增量式PID,输入是期望速度和实际速度的误差,输出是油门或制动命令。
5.2 油门与制动的切换处理
纵向控制里一个常见的坑是油门和制动的切换抖动。PID输出正值时给油门,负值时给制动,看起来逻辑很顺,但实际仿真中,PID输出在零附近反复波动,会导致油门和制动频繁交替,车辆一顿一顿地往前蹿。
我的处理方式是在油门和制动之间加一个死区。PID输出大于0.02时,油门值等于PID输出;PID输出小于-0.02时,制动压力等于PID输出的绝对值;中间区间一律输出零。另外,制动命令不能直接给到车辆,我会把制动压力做一次限幅,最大不超过6MPa,避免制动瞬间抱死导致车辆失稳。
纯跟踪控制器的前视距离也需要根据速度自适应。低速时前视距离取3到5米,高速时取10到15米,否则同一个固定前视距离要么在低速时太短导致转向振荡,要么在高速时太长导致弯道追踪滞后。这个参数非常敏感,建议做成速度查表。
5.3 仿真数据记录与分析
联合仿真跑完,数据记录如果没做好,结果分析会非常痛苦。我在Simulink里用To Workspace模块把车辆状态、参考轨迹、TTC、安全距离、控制量全部保存下来,仿真结束后统一用脚本画图分析。
一组典型的成功换道避障结果如下:
| 指标 | 数值 | 评估 |
|---|---|---|
| 最小对向距离 | 1.8m | 满足安全余量 |
| 全程最小TTC | 2.1s | 未进入放弃层 |
| 换道完成时间 | 4.6s | 与规划一致 |
| 峰值横向加速度 | 2.6m/s² | 未超过3m/s²限制 |
| 最大轨迹跟踪误差 | 0.18m | 控制精度良好 |
看到这些指标符合预期时,整个链路才算真正跑通。后续就可以在这个基础上做参数扫描,比如改变对向车速度、前车初始距离,观察仲裁逻辑是否按照预期切换制动和换道状态。
6. 联合仿真调试中踩过的坑与参数整定经验
6.1 Carsim与MATLAB编译器不匹配
前面提过版本匹配问题,这里再具体一点。我遇到过一次报错信息只有“Unable to create Mex file”,当时第一反应是模型配置出了问题,排查了很久才发现是MATLAB的默认编译器从之前的高版本被改到了低版本,Carsim生成的代码需要更高版本的C++标准支持,导致编译直接失败。解决方法是打开MATLAB命令行,执行mex -setup,手动选中与Carsim配套的编译器,重新生成一次Simulink模块,问题就消失了。
如果你更换过MATLAB版本或者重装过编译器,一定要记得回来检查这一步。
6.2 仿真发散与步长失配
联合仿真跑到1到2秒后,横摆角速度和横向加速度突然开始剧烈发散,是另一个高频问题。我排查过的原因是:Simulink控制步长设成了20毫秒,但Carsim内部状态更新频率更高,两个数据源采样的时间基准不一致,导致控制器看到的车辆状态跳变严重,在一些边界工况下直接激励出高频振荡。
解决办法有两步。第一步,把Simulink的固定步长改小到1毫秒,控制器的采样保持设为20毫秒,这样控制器仍然以20毫秒周期性动作,但Carsim的状态输入是平滑的。第二步,在状态信号进入控制器前加Unit Delay打破代数环。这两个操作叠加后,仿真曲线明显平滑,发散问题不再出现。
6.3 TTC奇异值与误触发
TTC的分母是相对速度,当主车速度和对向车速度接近时,相对速度趋近于零,TTC会计算出一个巨大的值或者跳变成负值。如果这个奇异值进入仲裁逻辑,可能让系统误判为“对向车很安全”,从而允许换道,实际却是灾难性的。
我的处理是:对TTC计算增加一个死区判断。相对速度绝对值小于0.1m/s时,TTC方向判为无效,不参与仲裁;同时从算法层面做了低通滤波,避免传感器噪声导致分母过零抖动。这类边缘情况在做参数矩阵扫描时会特别频繁地出现,一定要提前兜住。
6.4 坐标变换与对向车距离计算
另一个隐蔽的坑在坐标统一。Carsim输出的全局坐标是大地坐标系下的X和Y,而仲裁逻辑需要的是主车与对向车之间的相对距离。如果直接用两点坐标做欧氏距离,偶尔方向搞反,就会计算出“对向车越跑越远”的错误结果。
我自己是先在全局坐标系下统一计算所有车辆的位置,然后通过对向车的全局坐标减去主车全局坐标,得到相对距离向量,再投影到主车航向方向,区分出纵向距离和横向距离。千万不要在东一个坐标系、西一个坐标系里混着算,这是三车场景最容易出低级错误的地方。
6.5 可复用的参数整定参考表
最后整理一份我调参过程中相对稳定的初始值,方便快速起步:
| 参数 | 推荐初始值 | 调参说明 |
|---|---|---|
反应时间t_resp | 1.2s | 算法可靠可降到1.0s,保守场景取1.5s |
冗余间距d0 | 3m | 低速场景可调小,高速场景适当增大 |
| 最大制动减速度 | 6m/s² | 湿滑路面按4m/s²以下设置 |
| 换道允许TTC阈值 | 5s | 车速越高,阈值越大 |
| 换道放弃TTC阈值 | 1.2s | 小于该值必须放弃换道 |
换道时间t_f | 4.5s | 按峰值横向加速度反推 |
| 峰值横向加速度限值 | 3m/s² | 舒适性要求高时降到2.5m/s²以内 |
| 车道横向位移 | 3.5m | 按实际道路宽度调整 |
整套流程跑通之后,我最深的体会是:联合仿真的瓶颈往往不在算法本身的复杂度,而在物理模型与逻辑模型之间的接口精度和参数匹配。轨迹规划要做得好,前提是你对Carsim输出的每一个状态变量都有足够的信任;而这种信任,是在一次次排查步长、坐标、编译器这些看起来不起眼的问题之后慢慢建立起来的。建议你先用一个最简单的直线加速工况,把Carsim和Simulink的数据流验证干净,再上三车避障这种复杂场景。否则真到了对向车呼啸而来的那一步,你都分不清是控制算法的问题还是接口传输的问题。另外一个小技巧:把Carsim车辆参数和仲裁阈值全部提到MATLAB工作区,用脚本统一管理,这样跑参数扫描时就不用反复打开界面手动改参数,效率能提升不少。