先说点实在的。ACC和CACC这套东西,名字看着高大上,说白了就是让车自己跟着前车走、跟着车队走的控制问题。ACC叫自适应巡航控制,现在十几万的家用车已经标配了;CACC叫协同自适应巡航控制,多了一个车与车之间通信的能力,属于自动驾驶和车路协同领域的核心研究方向。用MATLAB和Simulink来建这套系统,是我认为目前最适合入门的路径,没有之一。原因很简单:你要验证控制算法好坏,最怕的就是在实车上反复试错,而Simulink可以在几分钟内搭出一条完整车队,让算法在仿真里先跑几百上千公里,把逻辑漏洞、参数毛病全暴露出来,再上实车就从容得多。
这篇内容写给三类人:正在做车辆工程、控制工程相关课题的学生;做ADAS功能开发的工程师;以及想搞懂ACC/CACC原理、打算自己动手复现一个仿真模型的爱好者。看完你能得到一套可以直接搭建的模型框架,知道每一步为什么这么做,也会清楚我自己实测时踩过的坑。
1. 项目框架与核心思路
1.1 ACC和CACC的本质区别
ACC的核心逻辑,是“感知-决策-执行”这条闭环:毫米波雷达或摄像头测出前车的距离和相对速度,控制器算出一个合理的期望加速度,然后发动机或制动系统去执行这个加速度。整个过程只有本车与前车的互动,车上不依赖任何外部信息。
CACC则在这个基础上引入了V2V通信,也就是车与车之间的数据交换。前车不仅把自己当前的加速度、速度告诉后车,有的拓扑结构里还会把前前车的信息也一并传过来。后车不再是被动地“看着前车动我才动”,而是提前知道前车要刹车了、要加速了,提前做出反应。
这个区别带来的核心指标是弦稳定性,英文叫String Stability。什么意思?想象一条车队,头车轻轻点了下刹车,如果控制不好,越往后的车反应越大,像波浪一样被放大,最后面的车可能直接急刹甚至追尾。ACC因为信息滞后一个车距,理论上很难保证整个车队的弦稳定性;CACC因为有了通信,前车的加速度信息可以直接传给后车,车队就能像火车车厢一样整体联动,波动在传递中逐渐衰减而不是放大。
所以我做这个项目的核心思路,就是先搭一个完整的ACC单车主模型,验证它自己能稳定跟车;然后在这个基础上加入通信链路,改造成CACC多车队列,做对比分析。这个从简到繁、从单车到车队的路线,也是业内最标准的做法。
1.2 分层控制架构:为什么Simulink适合做这件事
ACC/CACC控制器的工程实现,几乎都采用分层架构,这个分层方式本身就是在Simulink里建模的地图。
上层叫策略层,也叫运动控制层,负责根据相对距离、相对速度、本车速度计算出期望加速度。这里做的事是“决定车应该以多大的加速度往前走”。
下层叫执行层,负责把期望加速度转换成具体的油门开度或制动压力。这里涉及车辆逆动力学模型,要回答的问题是“给出这个加速度,发动机和刹车该怎么配合”。
通信层是CACC特有的,负责接收前车广播的加速度、速度、位置信息,并把这些信息融合进控制算法。
在Simulink里,这三层天然对应三个子系统。你可以把上层控制器封装成一个模块,把下层车辆模型又封装成一个模块,然后用信号线把它们连起来。改控制算法时,只需要动上层那个子系统内部,车辆模型不用碰。这种模块化解耦的思维方式,对我的调试效率提升不是一点半点。
另外,Simulink有自带的一体化环境,状态流模块用来描述逻辑切换,比如ACC和定速巡航之间的模式切换;Simscape或者Vehicle Dynamics Blockset可以直接提供车辆动力学模型;Simulink Real-Time和Embedded Coder支持生成C代码。这意味着你在仿真里验证过的模型,可以经过代码生成直接部署到快速控制原型设备上,仿真和实车的差距被压缩到最小。
2. 模型搭建前的准备:车辆模型与参数设定
2.1 车辆纵向动力学模型
别一上来就用Carsim那种高精度车辆模型,除非你做的就是底盘动力学研究。做ACC/CACC算法验证,重点在控制策略本身,车辆模型精度做到“合理可信”就够了。
我常用的车辆纵向动力学模型是基于牛顿第二定律的简化形式:
[ a = \frac{F_{traction} - F_{resistance}}{m} ]
其中,驱动力 (F_{traction}) 与油门/制动指令相关,阻力项 (F_{resistance}) 包括滚动阻力、空气阻力和坡道阻力。为了更贴近实际,可以加一阶惯性环节模拟发动机和制动系统的响应滞后:
[ \dot{a}{actual} = -\frac{1}{\tau} a{actual} + \frac{1}{\tau} a_{desired} ]
其中 (\tau) 是动力总成时间常数,一般汽油车取0.3到0.5秒,电动车更快一些,可以取0.2秒左右。这个时间常数直接影响控制器的稳定性边界,我调试时发现,如果 (\tau) 设得过大上限,PID参数无论怎么调都会出现振荡,因为被控对象的相位裕度已经不够了。
这里我一般直接使用Simulink自带的车辆动力学模块,或者自己用传递函数搭建。自己搭的话,至少要包含:
- 车体纵向运动方程,输出速度和位置
- 一阶惯性环节代表执行器响应
- 油门/制动切换逻辑,避免同时踩油门和刹车
2.2 传感器与通信模型
传感器这块,ACC通常用毫米波雷达,模型里我用的是“距离 + 相对速度”输出。雷达模型不需要太复杂,加上一个高斯白噪声和一个量测延迟就够了。噪声方差和延迟时间这两个参数非常重要,直接影响上层控制器的带宽上限。
CACC通信模型则是另一套逻辑。V2V通信不是零延迟的理想信道,实际工程中V2X通信延迟在10到100毫秒之间,还会出现丢包。我在模型里用Simulink的Transport Delay模块模拟通信延迟,又用伯努利随机数生成器模拟丢包事件,把通信的不完美性直接暴露给控制器。这个做法比较贴近真实工程场景,后面做鲁棒性分析时用得上。
关键参数我列个表,方便对照设置:
| 参数 | 符号 | 取值 | 说明 |
|---|---|---|---|
| 整车质量 | m | 1500 kg | 中型轿车典型值 |
| 滚动阻力系数 | f | 0.015 | 常规沥青路面 |
| 空气阻力系数×迎风面积 | Cd×A | 0.6 m² | 轿车典型值 |
| 动力总成时间常数 | τ | 0.4 s | 汽油车典型值 |
| 雷达量测噪声标准差 | σ_d | 0.1 m | 相对距离噪声 |
| 相对速度噪声标准差 | σ_v | 0.05 m/s | 相对速度噪声 |
| 通信延迟 | τ_comm | 20 ms | 典型V2V延迟 |
| 期望车头时距 | h | 1.2 s | 常用设定值 |
2.3 工具箱准备
如果要用MATLAB/Simulink完整跑通这套系统,需要的工具箱包括:Simulink、Stateflow、Simulink Control Design、Simulink 3D Animation可以用来做可视化,Vehicle Dynamics Blockset是锦上添花。搞CACC的话可能还要用到Instrument Control Toolbox用来对接硬件通信。版本方面,我现在用的是2024a版本,太老的版本缺少一些V2X相关的示例模型,但核心功能差异不大,不用迷信新版。
3. ACC控制器建模:从间距策略到油门刹车
3.1 间距策略:恒定车头时距与恒定间距
ACC控制器的第一步是确定期望间距。间距定得太近,不安全,乘客心理压力也大;间距定得太远,容易被加塞,道路通行效率低。现在主流方案是恒定车头时距(CTH):
[ d_{des} = d_0 + h \cdot v_{ego} ]
其中 (d_0) 是最小安全间距,一般取2到5米;(h) 是车头时距,一般取0.8到2秒;(v_{ego}) 是本车速度。这个策略的思路是,车速越快,间距线性拉大,保证有足够的反应时间。
另外一种策略是恒定间距(CSC),也就是不管车速多少,间距都固定不变。这种策略在理论分析中很常用,因为传递函数简单,便于做稳定性分析,但在现实中很难落地,速度一快就变成危险驾驶了。
我把两种策略都做进了模型里,通过一个常数模块切换。实测下来,CSC在低速园区工况下表现还算稳定,但在高速场景下,突遇前车急刹时,CSC的响应明显更激进,舒适性很差。所以主推还是CTH。
3.2 上层控制器设计:PID与MPC对比
期望间距算出来了,下一步就是把这个间距误差变成期望加速度。最常见的做法是PID控制器:
[ a_{des} = k_p (d_{rel} - d_{des}) + k_d (v_{rel} - h \cdot a_{est}) + k_{acc} \cdot a_{lead} ]
注意第三项 (a_{lead}) 是前车加速度,在ACC中不知道,所以这一项为零;在CACC中通过通信获得,就是后面要讲的重点。
PID实现简单、参数含义直观,适合快速验证整个模型链路是否通畅。但PID有三个毛病:第一,参数整定依赖经验,不同工况可能要换不同套参数;第二,无法显式处理加速度和加加速度约束;第三,对前车加速度信息利用不充分,导致跟车响应偏慢。
所以我后来又把上层控制器升级成了MPC,也就是模型预测控制。MPC的核心思想是,在当前时刻预测未来N步的系统状态,然后求解一个带约束的优化问题,得到最优控制序列,只执行第一步,下一时刻重新滚动优化。
这个思路很直观,像你开车时不是只看眼前一米的距离,而是会预判未来几秒的距离变化趋势。MPC在Simulink里的实现可以用MPC Toolbox,也可以手写YALMIP产线或直接用quadprog求解器。MPC对间距误差的控制效果与PID相比,超调量可以减小一半以上,加速度变化也更加平滑。
我在实际项目中做了一次对比实验,工况是前车从30 m/s急减速到10 m/s。PID控制器的最大间距误差约4.2米,加速度最大变化率约6 m/s³;MPC的最大间距误差约2.5米,加速度变化率也控制在2 m/s³以内。差距很直观。
3.3 下层逆动力学模型与油门刹车切换
上层算出的期望加速度是正负任意值,但车辆执行器并没有“负油门”这种东西,需要把加速度映射到油门/刹车上。这部分就是下层控制的功能。
我的做法是把期望加速度分三种情况:
- 期望加速度大于某个阈值(比如0.2 m/s²),控制节气门开度,制动压力为零
- 期望加速度小于某个阈值(比如-0.2 m/s²),控制制动压力,节气门关闭
- 介于两者之间,保持当前状态,避免频繁切换
这里有个关键细节:油门和制动的切换要加滞回区间。如果阈值设置没有滞回,车辆会在这个区间内来回抖动,油门刹车快速交替,不仅舒适性极差,还会让Simulink的步长求解器因为频繁的事件触发而变慢。我用Stateflow实现了这个切换逻辑,状态图里三个状态之间用滞回条件迁移,实际效果非常平稳。
油门开度与加速度之间的关系并不线性,一般用查表方式建模。转速为2500 rpm(每分钟两千五百转)、挡位处于中低挡时,油门每增加一个单位,加速度增量可能很大;但在高挡高速工况,油门响应明显变钝。所以查表数据一定要覆盖你仿真所涉及的速度区间,否则高速跟车场景会出现控制器输出饱和的问题。
4. CACC协同控制:通信拓扑与控制律
4.1 V2V通信拓扑与车队稳定性
CACC相比ACC,多出来的核心就是通信拓扑。业内常见的几种拓扑包括:
- 前车跟随型,英文缩写PF,只接收紧邻前车信息
- 领航车-前车跟随型,缩写PLF,同时接收头车和紧邻前车信息
- 多前车跟随型,缩写MPF,接收前几辆车的信息
不同拓扑对车队稳定性的影响差异显著。理论分析通常通过传递函数来做:把间距误差定义为本车实际间距与期望间距之差,然后推导相邻两车误差之间的传递函数关系,再分析这个传递函数的H∞范数是否小于等于1,代表扰动在队列中不会被放大。
在PF拓扑下,由于本车只能感知前车运动状态,整个车队相当于一串首尾相扣的弹簧,间距误差容易向队尾传播放大。PLF拓扑多了一个领航车信息的提前作用,相当于让每个后车都知道队列的目标运动意图,稳定性条件明显放宽。MPF则更进一层,但代价是通信带宽和拓扑复杂度上升。
所以我在建模时选了PLF拓扑作为主要研究方案,既有足够的协同性,又不至于把通信模型搞得太复杂。每个后车接收的数据是:前车的加速度、速度、实际位置,以及领航车的加速度。数据通过Simulink的总线信号统一封装,便于扩展。
4.2 CACC控制律推导与实现
CACC的控制律可以看作在ACC基础上,用通信获得的前车加速度做前馈补偿。经典的控制律形式为:
[ a_{des} = k_p (d_{rel} - d_{des}) + k_d (v_{rel} - h \cdot a_{prev}) + k_f \cdot a_{prev} ]
其中 (a_{prev}) 是紧邻前车的加速度。通过通信获得后,把这一项用前馈方式叠加到控制输出中。为什么前馈有效?因为前车尚未产生明显相对位移变化时,后车已经从通信中感知到前车的加速意图,可以在物理相对距离改变之前就调整自己的加速度。这相当于你在排队时,身后的人通过无线电告诉你前面的人突然停了,你可以提前刹车,而不是等看到前面的人停下才知道。
在Simulink中实现这个控制律非常直观:前车加速度信号通过通信延迟模块进入控制器的前馈通道,乘以一个前馈增益之后,与PID反馈项叠加。需要注意的是,前馈通道必须加一个低通滤波器,因为通信信号中混杂着高频噪声,原样导入会让执行器高频抖动。
我实际测试的PLF拓扑CACC控制律代码,核心逻辑在MATLAB Function模块中实现,伪代码如下:
function a_des = CACC_controller(d_rel, v_rel, v_ego, a_prev, a_lead) d0 = 2.0; h = 1.2; d_des = d0 + h * v_ego; kp = 0.8; kd = 0.6; kf = 0.9; a_feedback = kp * (d_rel - d_des) + kd * (v_rel - h * a_prev); a_feedforward = kf * a_prev; a_des = a_feedback + a_feedforward; end4.3 通信时延与丢包的应对
通信链路不是理想的。我前面提到用Transport Delay模块模拟通信延迟,用伯努利随机数模拟丢包,这两个模块加进去之后,原来的CACC性能明显恶化:车队尾部开始出现振荡,甚至在某些丢包率下,后车的加速度出现了类似刹车点头的现象。
应对策略有几个层面。
第一,控制器参数要针对通信延迟做鲁棒性设计。延迟变大时,前馈增益 (k_f) 必须适当降低。我的经验是把 (k_f) 设置成通信延迟的函数,延迟越大,前馈作用越弱,让系统回归到以反馈为主的模式。
第二,丢包处理常用直接丢弃旧数据,本周期若没收到新数据,就用上一周期的数据,相当于零阶保持。这种方式最简单,但在连续丢包时效果不好。我加了一个模型预测模块,当丢包发生时,用车辆运动学模型对前车未来位置做预测填充,直至通信恢复。实测下来,这个改进让车队在5%丢包率下依然保持稳定的差距水平,零阶保持方案在3%丢包时就开始出现明显超调。
第三,在CACC设计中加入控制器的增益调度也很重要。不同车速、不同通信质量下,使用不同组别的控制参数,是工程化落地的基础。这个增益调度表我就直接放在Simulink的Lookup Table模块里,简单直观。
5. 场景仿真与调参实战
5.1 多车队列场景搭建
我搭建的仿真场景是一个五辆车组成的车队,头车编号0,后面跟着编号1到4。车辆的初始位置按间距设定均匀分布,初始速度都设为20 m/s。
整个模型的顶层布局分三大块:左侧是领航车的驾驶行为信号源,我叫它Scenario Generator,负责切换不同驾驶工况;中间是五辆车的子系统,每辆车内都包含感知、上层控制、下层控制、车辆动力学四个子模块;右侧是监视显示系统,包括实时曲线显示和车队行驶动画空间。
领航车工况我用一个信号发生器搭建,默认设计了几段典型工况:匀速巡航、正弦加减速、紧急制动、阶梯提速,方便把整个控制器的响应特性都压测一遍。
五辆车之间通过信号线连接时,我踩过一个坑:如果直接用Goto、From模块跨层引用信号,模型可以跑,但逻辑清晰度很差,信号源判断容易混乱。建议把所有车辆间的通信信号统一整理成总线对象,在基础工作区定义Simulink.Bus,然后在模型里用总线端口连线。这样查线、查信号、做代码生成都方便很多。
5.2 典型工况测试与结果分析
我重点测试了三个工况,结果都很有代表性。
第一个是头车紧急制动:头车从20 m/s以最大制动减速度,大概7 m/s²,刹到停车。纯ACC车队中,由于每辆车都是看到前车减速才减速,间距误差在车队中逐渐累积,第4辆车的最小间距缩小到不足2.5米,濒临碰撞风险。CACC车队中,头车的制动意图通过V2V通信几乎同步传递给后方车辆,第4辆车的最小间距仍保持在5米以上,刹车强度曲线也远没有ACC车队那么陡,舒适性改善明显。
第二个是头车正弦加减速:这个工况用来观察车队在持续扰动下的弦稳定性。ACC车队中,正弦扰动向后传递时,间距误差逐渐放大,从第1辆到第4辆,误差振幅几乎翻倍;CACC车队中,误差振幅是递减的,正体现了弦稳定性的效果。
第三个是头车阶梯提速:头车从20 m/s提速到30 m/s,保持一段时间,再回到20 m/s。这个工况下,两套系统的稳态跟车误差相差不大,但在提速初期,CACC的响应速度明显快于ACC,车队的车速同步性更好。
5.3 参数整定心得
参数整定是整个项目中最花时间的一步。我先说最核心的原则:先内部,后外部;先单车,后车队。
具体来说,先把车辆动力学模型单独验证,看看开环响应是否合理,再闭合ACC反馈环,只调单车PID参数,让它在阶跃工况下能够无静差跟车。单车存在问题,调好的参数不要动,再引入通信链路,调CACC的前馈增益和滤波器参数。
PID参数怎么快速定一个初始值?我的经验是用临界比例度法。先把积分项和微分项设为0,只保留比例项,从小到大增加比例增益,直到系统输出出现等幅振荡,记下此时的临界增益 (K_c) 和振荡周期 (T_c)。然后按照经验公式计算初始PID参数:
[ K_p = 0.6 K_c, \quad K_i = \frac{K_p}{0.5 T_c}, \quad K_d = K_p \cdot 0.125 T_c ]
算出来的参数通常就能跑了,再在此基础上细调。注意这个初始参数可能会偏激进,因为车辆模型含有时间常数,实车场景还要再降一些增益,我习惯把仿真调好参数乘以0.7到0.8再试验。
前馈增益 (k_f) 的设定相对固定,理论上接近1就能实现完美的前馈补偿,但考虑到通信延迟和执行器滞后,我一般设置在0.8到0.95之间,配合低通滤波器使用。滤波截止频率设在2 Hz左右比较合适,既能滤掉通信噪声,又不会明显削弱控制信号的上升沿。
6. 常见问题与排查技巧实录
6.1 常见仿真问题与排查方法
我自己在搭建和调试过程中遇到过不少问题,挑几个最典型的列在下面,你可以直接对照排查。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 仿真起动很慢,计算步长很小 | 模型中存在高频振荡或代数环 | 检查油门/制动切换是否频繁,加入滞回;用低通滤波器消除高频分量 |
| 车辆间距出现负值 | 初始间距设置过小或控制器发散 | 增大初始间距到100米以上,先手动降PID增益让系统稳定 |
| 车队尾部出现“蛇形摆动” | 通信延迟过大且前馈增益偏高 | 降低前馈增益到0.7,或增大低通滤波器的滤波强度 |
| 突然出现NAN(无效数值) | 查表模块的输入超出了表头取值范围 | 检查油门/制动查表模块的边界值,对控制器输出做Saturation限幅 |
| 仿真结果与理论分析有出入 | 离散控制器的采样时间不一致 | 让所有控制器的采样时间统一,避免多速率采样导致的混叠效应 |
| 代码生成失败 | 模型层级不规范或Bus对象未定义 | 统一使用Bus对象传递总线信号,确保Simulink代码生成模块没有歧义 |
6.2 从仿真到落地部署的一条路
最后说一点扩展思路。这套模型的价值远不止停留在仿真层面,我后续做的是把控制器部分抽离出来,通过Simulink的Embedded Coder生成C代码,部署到NI控制器或快速控制原型设备上做硬件在环测试。
注意,在模型准备做代码生成之前,提前养成几个习惯能省很多功夫:第一,所有控制器模块用离散时间更新,别用连续时间,因为目标硬件是周期性任务执行;第二,饱和模块、限幅模块显式添加到每个输出端口,防止溢出;第三,不要在控制器通路里放Simulink示波器模块,它对代码生成不友好,用信号记录模块代替。
还有一个小技巧:在Simulink中做ACC/CACC时,给每辆车内部的控制模块加上后缀编号,比如Ctrl_Car1、Ctrl_Car2,这样生成代码后函数名清晰可辨,排查问题会非常方便。
这套项目做完之后,我的整体感受是:ACC是基础功,CACC才是真正体现协同价值的进阶玩法。如果你正在做相关方向,建议先花一周时间把单车的ACC模型搭熟,然后再去碰通信和控制律的协同设计,进度反而会比直接从CACC入手更快。有条件的话,把仿真参数和算法固化下来,再做一版硬件在环的测试,你会对整个V2X控制链路有更深的理解。