做车辆底盘控制或者自动驾驶路径跟踪的朋友,大概率绕不开轮胎模型。最近我把Dugoff轮胎模型在Simulink里搭好,再接上CarSim做联合仿真,前前后后折腾了接近一周,踩了不少坑。这篇就把整个搭建思路、信号流、公式细节和排查经验完整写出来。
这套联合仿真模型解决的是这样一个问题:CarSim自带的高精度轮胎模型(通常是Pacejka魔术公式)不能直接把轮胎力输出给你自己的控制器,而纯Simulink里自己写整车模型又往往太简化。Dugoff轮胎模型介于两者之间——公式简单、参数少、物理意义清楚,特别适合做基于模型的控制算法开发。把CarSim当成整车动力学提供者,把Simulink里的Dugoff模块当成轮胎力计算器,两头一接,控制算法需要的关键变量就全都有了。如果你在搞车辆稳定性控制、底盘控制或轨迹跟踪,这篇文章的模型和排坑经验可以直接抄。
我会把搭建步骤写得比较完整,新手可以按步骤复现,老手可以直接看第五章的常见问题,基本覆盖了联合仿真最容易翻车的几个点。
1. 为什么是Dugoff轮胎模型:先想清楚再动手
1.1 选型背后的三个理由
轮胎模型选型这件事,第一步不是看哪个公式精度最高,而是先想清楚你这套仿真到底要拿来干什么。如果目的是做整车操稳性分析,CarSim内置的Pacejka魔术公式肯定是对的,它基于大量试验数据拟合,横向纵向精度都够。但如果你的目的是开发控制算法,比如ESP、TCS、轨迹跟踪控制器,那Pacejka就有点“过于沉重”了。
Dugoff模型的核心优势是三个:
第一,计算简单。整个模型只有加减乘除、开方和一次条件判断,没有三角函数表,也没有大量拟合系数。在Simulink里跑,一个步长内四个轮的轮胎力计算开销几乎可以忽略。将来要生成C代码上快速原型,这种纯代数表达非常友好。
第二,参数少。整个模型需要标定的参数就四个:纵向刚度Cx、侧偏刚度Cα、路面附着系数μ和滚动半径R。相比Pacejka动辄几十个拟合系数,这四个参数都能从常规轮胎数据里查到或者估算。对做控制的人来说,参数越少,模型越可控。
第三,自带轮胎饱和特性。Dugoff模型的修正系数f(L)非常巧妙地描述了轮胎接近附着极限时的行为——纵向力和侧向力不再线性增长,而是互相制约,形成附着椭圆。这在极限工况控制策略验证里非常关键。如果你用纯线性轮胎模型,车辆快要失控时控制器看到的轮胎力还在线性增长,那基于这个模型设计的控制器在极限工况下就是个瞎子。
1.2 与魔术公式、线性模型的取舍
| 模型 | 参数数量 | 精度 | 实时性 | 控制器友好度 | 适用场景 |
|---|---|---|---|---|---|
| Pacejka魔术公式 | 大量拟合系数 | 高 | 中 | 低,表达式复杂难求导 | 高精度整车动力学分析 |
| Dugoff模型 | Cx、Cα、μ、R共4个 | 中高 | 高 | 高,解析表达式带饱和 | 控制算法开发与实时仿真 |
| 线性模型 | 2-4个 | 低 | 高 | 高,但无饱和 | 常规工况,非极限工况 |
在CarSim联合仿真的场景里,我自己更倾向于把Dugoff当成“控制算法内部模型”,把CarSim当成“被控对象真值”。也就是说,CarSim内部用它的高精度轮胎算车怎么跑,你的控制器里用Dugoff模型预测轮胎力,两者对比,控制效果好,说明你的算法对模型误差有鲁棒性。反过来如果你直接用CarSim输出轮胎力来喂控制器,那你验证的控制器等于“作弊”了,因为它拿到了真实对象的信息。
所以这篇联合仿真的正确打开方式是:CarSim里关闭内部轮胎力,改用外部Dugoff模型算出的力来驱动整车。这样你的控制算法在Simulink里看到的所有力和运动状态,都来自同一个模型,构建了一个完全一致的控制闭环。
1.3 数学表达与符号定义
Dugoff模型的完整计算公式如下:
sigma_x = (omega * R - Vx_wheel) / Vx_wheel % 纵向滑移率,制动时为负 sigma_y = tan(alpha) % 侧向滑移量 S = sqrt((Cx * sigma_x)^2 + (Calpha * sigma_y)^2) % 综合滑移强度 L = mu * Fz * (1 + sigma_x) / (2 * S) % 无量纲饱和因子 f_L = L * (2 - L), L < 1 f_L = 1, L >= 1 Fx = Cx * sigma_x / (1 + sigma_x) * f_L Fy = Calpha * sigma_y / (1 + sigma_x) * f_L式子里涉及的物理量再展开解释一下:
omega是车轮旋转角速度,单位rad/s,乘以滚动半径R变成等效纵向速度。制动时omega*R < Vx,所以sigma_x为负,对应的Fx也是负值,表示制动力,这符合直觉。alpha是轮胎侧偏角,单位rad。侧偏角由轮心处速度方向与轮胎指向的夹角决定,tan(alpha)直接作为侧向滑移量进入模型。Fz是车轮垂直载荷,单位N。就是轮胎压在地面上的力,这个量很重要,因为它直接出现在饱和因子L的分母里。Fz越大,轮胎能提供的最大附着力越大,越不容易饱和。mu是路面附着系数。干沥青大约0.8-0.9,湿滑路面0.3-0.5,冰雪路面可能低到0.1-0.2。
有一个细节特别容易踩坑:公式中S表达式的分母,当sigma_x和alpha同时为0时,S为0,计算L会除零。车辆静止或匀速直线行驶时正好是这个状态。解决方法是加上一个极小量保护,比如S = max(S, 1e-3),或者干脆设一个速度门限:当轮心速度小于0.5m/s时,直接把轮胎力置零。
2. 联合仿真总体架构:轮胎力从哪来到哪去
2.1 CarSim和Simulink各干各的活
做联合仿真之前,先要把“分工”理清楚。CarSim负责的是整车的运动学与动力学解算:车身六个自由度、悬架运动学、转向系统、驾驶员模型、路面和障碍物定义,以及最后的三维动画。Simulink这边负责的是控制算法和自定义模型——这次的核心是Dugoff轮胎力计算模块。
在这套架构里,CarSim的轮胎模型被设置成外部模型。也就是说,CarSim不再自己算轮胎与地面的作用力,而是等着Simulink把四个轮的Fx、Fy送进来,再把这些力施加到车体上,算出车辆接下来往哪儿跑。这样做的最大好处是:控制算法和轮胎模型在同一个Simulink环境里,状态变量没有跨工具的数据壁垒,调试起来非常舒服。
2.2 信号流设计
完整的信号流可以这样概括:
CarSim输出给Simulink的信号:
- 整车纵向速度Vx和侧向速度Vy
- 每个车轮的轮心纵向速度Vx_wheel和侧向速度Vy_wheel
- 每个车轮的角速度omega
- 每个车轮的垂直载荷Fz
- 路面附着系数mu(如果道路有变化)
Simulink根据这些输入,利用Dugoff公式算出每个车轮的纵向力Fx和侧向力Fy,再送回给CarSim作为外部轮胎力的输入。
这里有一个很容易忽视的问题:滑移率的计算到底用轮心速度还是整车速度?我强烈建议用CarSim输出的轮心速度。因为转向和横摆时,四个轮的轮心速度差别很大——外侧轮快、内侧轮慢,转弯时外轮滑移率天然和内轮不一样。如果统一用整车Vx计算,侧偏角和滑移率都会失真,尤其在双移线这种大幅度转向工况下,误差会直接反映到轮胎力上。
2.3 坐标系与符号约定
坐标系问题是我这次联调当中花时间最多的一个点。CarSim的车辆坐标系是ISO标准的右手系:X轴向前,Y轴向左,Z轴向上。在这个坐标系下,纵向力Fx向前为正,制动力为负;侧向力Fy向左为正。
Dugoff模型算出来的轮胎力尽管数学上是沿着滑移方向的,实际输出到CarSim之前还是要仔细对一次符号。一个最直接的验证方法:直线制动工况,四个轮的Fx应该都是负值,车速应该下降。如果仿真结果显示出速度不降反升或者车辆自动加速,不用怀疑,符号反了,去模型里把Fx输出加个负号。
侧偏力的符号也一样。让车辆有一个向左的侧偏角(比如方向盘轻微左打),左前轮产生的侧向力应该向右(即Fy为负),因为轮胎要抵抗侧偏。如果你发现Fy方向和侧偏角方向一致了,那说明公式里sigma_y的符号或者CarSim输出信道的正负定义有冲突,需要统一。
2.4 步长与解算器配置
联合仿真跑起来之后,步长选多少直接影响稳定性和运行速度。我实测的经验值如下:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| Simulink求解器 | ode4固定步长 | 不建议用ode45变步长,CarSim的S-Function是混合系统,变步长容易出问题 |
| 仿真步长 | 1ms | 常规工况足够;低附着路面建议降到0.5ms |
| CarSim通信步长 | 与Simulink一致 | 在CarSim的Run Control里设置 |
| 低速门限 | Vx_wheel < 0.5m/s | 轮胎力强制置零,防止除零 |
步长这个参数看似是小事,但实际影响非常大。1ms步长跑双移线工况,整车轨迹和0.5ms步长的差别很小;但一旦路面附着系数降到0.3以下,车辆进入极限工况,轮胎力变化剧烈,1ms就开始出现数值抖动。如果你在低附着仿真中看到车辆的横摆角速度高频振荡,先别急着怀疑模型,把步长改成0.5ms试试。
3. Simulink端搭建Dugoff轮胎模型的实操过程
3.1 顶层模型组织
我习惯把顶层模型拆成几个清晰的功能块:CarSim S-Function模块、Bus Selector信号提取、四个独立的Dugoff轮胎子系统、以及输出信号汇总。这样做的目的是将来好维护——如果你想对比Pacejka和Dugoff,只需把Dugoff子系统换成另一个轮胎模型,其余不动。
四个轮胎子系统必须独立,不能一个模块算四次,因为每个轮的垂直载荷、滑移率、侧偏角都不一样。我建议把Dugoff模块封装成Masked Subsystem,参数直接填在Mask里,比如:
前轮Cx = 60000 N 前轮Calpha = 85000 N/rad 滚动半径R = 0.325 m每个轮实例化一次,填对应参数。
3.2 车速与滑移率计算细节
滑移率的计算在Bus Selector提取信号之后做一个子模块来完成。这里推荐自己写公式计算滑移率和侧偏角,不要直接用CarSim内部输出的轮胎滑移率通道。原因是CarSim输出的滑移率符号定义和Dugoff公式要求的不一定完全一致,不同版本之间也有差异,自己算一遍最保险。
omega_eff = omega * R; kappa = (omega_eff - Vx_wheel) / Vx_wheel; alpha = atan2(Vy_wheel, Vx_wheel);轮心速度和车轮角速度都从CarSim模块的输出总线里取。注意atan2而不是atan,这样可以保留侧偏角的正负号信息。如果只用atan(Vy/Vx),在侧向速度方向变化时角度符号会丢失,低速时尤其明显。
这里还有一条重要经验:低速保护一定要放在滑移率计算之前。我第一版模型没做这个保护,车辆原地静止起步那一刻,Vx_wheel是0,算出来的kappa是无穷大NaN,整个仿真直接崩了。后来加了门限判断,Vx_wheel小于0.5m/s时把kappa和alpha都强制设成0,轮胎力输出0,问题才解决。
3.3 垂直载荷的处理方式
垂直载荷这个量,在CarSim联合仿真里最好直接用CarSim输出的动态Fz。CarSim已经把静态载荷、俯仰转移、侧倾转移、悬架几何等全部考虑进去了,精度比你自己搭公式高得多。
如果你的CarSim版本找不到动态Fz输出,我给出一个常用的估算公式做后备方案:
Fz_fl = m * g * Lr / (2*L) - m * ax * h / (2*L) - m * ay * h / (2*tf) Fz_fr = m * g * Lr / (2*L) - m * ax * h / (2*L) + m * ay * h / (2*tf)其中m是整车质量,g是重力加速度,L是轴距,Lr是质心到后轴距离,h是质心高度,tf是前轮距,ax和ay是纵向和侧向加速度。注意最后一项的正负号要跟转向方向匹配。左侧轮在右转时载荷减小,右侧轮增大,这个方向别搞反。
不过再次强调,能直接从CarSim拿到动态Fz就用CarSim的,自己做公式只是为了应对没有信号的情况。
3.4 修正系数与输出限幅
Dugoff模型里最容易引起数值问题的地方就是修正系数f_L的切换。L=1这个地方是一个折角,模型从线性区平滑过渡到饱和区,理论上没问题,但数值积分时,如果步长偏大或者周围模块对导数比较敏感,折角处可能引起高频抖动。
我实际处理办法是给f_L增加一点平滑处理。不一定要用复杂的平滑函数,简单的饱和限幅加一阶低通滤波就够了:
L_limited = max(L, 0); % 防止负值 f_L = L_limited * (2 - L_limited); f_L = min(f_L, 1); % 饱和限幅 f_L = lowpass(f_L, 30Hz); % 一阶低通,滤掉高频抖动这个低通滤波的截止频率要选好,太低了会拖慢轮胎力响应,太高了起不到滤波作用。我一般从20Hz开始试,根据仿真结果调整。对于普通的底盘控制仿真,30Hz左右是个不错的起点。
3.5 前后轮参数差异
前后轮因为载荷和规格不同,Cx和Cα不能取同一个值。我参考的是一辆1.5吨轿车的典型数据:
| 参数 | 前轮 | 后轮 |
|---|---|---|
| Cx(纵向刚度) | 55000N | 48000N |
| Cα(侧偏刚度) | 82000N/rad | 70000N/rad |
| 滚动半径R | 0.32m | 0.32m |
| 单轮静态Fz | 约4300N | 约3800N |
这个数据不是绝对的,不同车型、不同轮胎差异很大。你可以先用一个合理的初值让模型跑起来,再根据仿真曲线微调。判断的标准很直观:同样的方向盘转角输入下,车辆横摆角速度和侧向加速度是否符合直觉。
4. CarSim端配置与接口对接
4.1 车辆模型选择与外部轮胎开关
CarSim端配置是整个联合仿真的另一个大头。以我常用的CarSim 2019版为例,进入车辆动力学模型后,在轮胎设置页面需要把轮胎模型切换成外部模型。这一步非常关键,如果没切换成功,CarSim内部默认的轮胎模型也在算力,外部Simulink的力又往车上叠一遍,轮胎力就重复了,仿真结果必然离谱。
切换成外部模型后,CarSim的Import通道会自动出现四个轮的外部力输入变量,通常是这样命名的:
IMP_FX_L1 / IMP_FY_L1 / IMP_MZ_L1 % 左前轮 IMP_FX_R1 / IMP_FY_R1 / IMP_MZ_R1 % 右前轮 IMP_FX_L2 / IMP_FY_L2 / IMP_MZ_L2 % 左后轮 IMP_FX_R2 / IMP_FY_R2 / IMP_MZ_R2 % 右后轮不同版本命名可能略有差异,但规律都是把L1/R1/L2/R2当作四个车轮的代号。搜索“External Tire”或“IMP_FX”应该能定位到相应配置页。
4.2 导入导出信道设置
在CarSim的I/O Channels页面,需要把下面这些信号加到Export(输出)列表里:
| 输出信号 | 用途 |
|---|---|
| Vx / Vy | 整车速度 |
| Vx_wheel_L1/R1/L2/R2 | 各轮轮心纵向速度,算滑移率 |
| Vy_wheel_L1/R1/L2/R2 | 各轮轮心侧向速度,算侧偏角 |
| omega_L1/R1/L2/R2 | 各轮角速度 |
| Fz_L1/R1/L2/R2 | 各轮垂直载荷 |
| mu_L1/R1/L2/R2 | 路面附着系数 |
这些量在CarSim里的具体通道名不同版本不太一样,最稳妥的办法是在Export列表里搜索关键词,比如搜“wheel speed”“omega”“Fz”等。找到对应通道拖到列表里即可。
4.3 生成Simulink模型的标准流程
配置完成后,开始生成联合仿真模型。标准流程是:
- 在CarSim的Run Control页面,设置仿真时长,和Simulink里的仿真时长一致。
- 确认求解器配置为固定步长,步长选1ms。
- 点击“Send to Simulink”按钮,CarSim会生成对应的Simulink模型并自动打开。
- 生成的模型里有一个CarSim的S-Function模块,双击可以加载parfile。
- 把S-Function的输出总线用Bus Selector拆开,提取需要的信号。
- 将处理后的信号接入你的Dugoff轮胎模型。
- 把Dugoff模型的四个轮Fx/Fy输出接回到CarSim S-Function模块的输入端口。
这个流程每一步都有坑,但最关键的还是第5步和第7步。信号接错顺序或者接错端口,仿真能跑但结果完全不对。
4.4 启动顺序与参数初始化
联合仿真的启动顺序也有讲究。我推荐的操作是:先打开CarSim GUI,确认车辆参数和I/O配置都没问题,点击“Send to Simulink”生成模型,再在Simulink里运行仿真。如果你直接打开一个之前生成过的Simulink模型而不经过CarSim的Send步骤,S-Function里加载的parfile可能和当前CarSim工程的参数不一致,仿真结果不是你想要的。
还有一个容易忽略的问题:工作目录不能有中文或者特殊字符。CarSim生成临时文件和编译S-Function时,路径带中文会直接报错。尽量把整个工程放到纯英文路径下,比如D:/Simulation/Dugoff_CarSim/。
5. 联调常见问题与排查经验
5.1 Bus Selector没有可选信号
热词“simulink bus selector 没有可选信号”被问得非常多,我自己也撞到过。症状是双击Bus Selector模块,想选需要的信号,结果输入总线的信号列表是空的。
最可能的原因有三个:第一,CarSim生成的S-Function模块没有被正确初始化,总线对象没有加载到MATLAB工作区。解决方法是重新运行CarSim的Send步骤,或者手动执行CarSim自带的脚本load_ctrl_parfile.m之类的初始化脚本。第二,换了MATLAB版本后总线对象丢失。这个只能重新生成模型,没有别的办法。第三,CarSim模块输出端口的信号类型不是总线,而是普通向量。检查一下S-Function模块的输出端口参数,确保它使用的是Bus Object。
5.2 一运行就NaN
这个问题的典型表现是仿真刚启动,还没跑到0.1秒,车速直接变成NaN,Simulink报错。原因十有八九是低速除零。车辆静止时,Vx_wheel=0,滑移率公式里分母为0,kappa变成无穷大NaN,然后一路传给Dugoff模型,整个仿真链全崩。
解决思路很明确:在滑移率计算模块前面加一个低车速保护逻辑。Vx_wheel的绝对值小于0.5m/s时,直接把kappa和alpha置零,对应的轮胎力输出也为零。等车速超过0.5m/s再正常计算。这里有一点要注意:不要把门限设得太高,否则车辆在低速蠕行时轮胎力一直是0,起车过程的仿真结果就不对了。
5.3 轮胎力方向反了或者左右轮搞混了
轮胎力方向反了的排查方法,前面已经提过直线制动验证。这里再说一个检查左右轮的技巧。在CarSim的3D动画里,做一次方向盘阶跃输入(比如0.5秒内方向盘打到90度),然后分别查看四个轮的Fx和Fy随时间的变化曲线。右侧车轮和左侧车轮在转向时侧偏角方向是相反的,所以Fy的符号也应该相反。如果发现四个轮的Fy符号完全一致,那就是车轮编号搞混了。
5.4 动画里车轮箭头显示不对
CarSim动画里的车轮箭头其实是力的可视化。如果你用了外部轮胎模型,箭头默认显示的可能还是内部的计算量,看起来就会和Simulink里算出的力对不上。解决办法是在3D动画的显示设置里,把力箭头的显示源切换到外部力通道。不同版本的菜单名称有差异,一般搜“Force Arrow”或“External Tire Force”就能找到。
还有一个细节:箭头显示的力大小和方向受缩放比例影响,经常出现“箭头巨大”或者“几乎看不见”的情况。在动画设置里调整Force Scale参数,让箭头长度适中。
5.5 高附着转低附着瞬间发散
把路面附着系数从0.85突然切到0.35时,仿真很容易发散。原因是Dugoff模型的饱和因子L对mu的变化非常敏感,mu跳变瞬间轮胎力突变,车辆动力学状态出现脉冲激励。
我惯用的处理方式是对mu做渐变过渡,而不是阶跃切换。比如在Simulink里用一个斜坡信号模块,让mu在0.3秒内从0.85线性降到0.35。这样更接近真实路面变化,也能有效避免数值发散。如果你的道路场景必须在某个位置突然变得很滑,那就把仿真步长进一步缩小到0.5ms。
5.6 问题排查速查表
| 现象 | 可能原因 | 排查手段 |
|---|---|---|
| Bus Selector空列表 | 总线对象未加载 | 重新Send to Simulink |
| 启动即NaN | 低速除零 | 加低速门限保护 |
| 车速曲线异常 | 轮胎力符号反 | 直线制动验符号 |
| 动画箭头不对 | 显示源设错 | 切换力箭头显示源 |
| 低附着发散 | mu跳变导致 | mu渐变替代阶跃 |
| 编译报错 | 路径含中文 | 全英文工作目录 |
6. 结果验证与工况设计
6.1 先做单元验证再联调
我强烈建议在把Dugoff模型接入CarSim之前,先在Simulink里单独做一次开环测试。方法很简单:给Dugoff模型固定一组Fz和mu,然后让滑移率从-0.3扫描到0.3,侧偏角分别设0度、5度、10度,把所有输出曲线画出来。
你会发现典型的Dugoff特性:滑移率绝对值较小时,纵向力近似线性增长;超过一定范围后,Fx进入饱和平台,不再增加。侧偏角和纵向力同时存在时,总力会被“附着椭圆”限制。这些曲线就是你联调之后判断仿真结果是否合理的参考基准。
6.2 双移线工况验证
联调完成后,第一个验证场景我建议做ISO双移线或者简单的单移线工况。双移线的好处是既有纵向加减速,又有大幅横向变道,轮胎力变化剧烈,能充分检验模型是否正确。
观察指标主要是三个:车辆横向位移轨迹、横摆角速度、侧向加速度。对比CarSim内置轮胎模型和Dugoff外部轮胎模型的仿真结果,两者整体趋势要一致,峰值横向加速度差异在10%-20%以内都算正常。如果差异非常大,优先检查Cx和Cα的取值是否合理,其次检查垂直载荷的符号。
6.3 不同附着路面下的表现对比
模型调通之后,一定要做一组不同附着系数的对比。干沥青mu=0.85和湿滑路面mu=0.35,分别跑同一个双移线工况。干沥青路面上车辆能够顺利完成双移线,横摆角速度跟得上方向盘输入;湿滑路面上车辆侧偏角明显加大,横摆响应滞后,如果车速足够快,车辆会在第二个弯道出现过度转向的趋势。
这一组对比结果非常直观地说明Dugoff模型的饱和特性是否起作用了。如果两个附着系数下的结果几乎一样,多半是mu没有真正接到模型里,检查一下mu信号是否从CarSim输出正确传到了Simulink。
6.4 后续可扩展方向
这套模型跑通之后,扩展空间非常大。一个很自然的方向是路面附着系数在线估计。Dugoff模型的mu作为独立输入,完全可以把它当成待估计的状态量,用扩展卡尔曼滤波或者递推最小二乘来辨识。还有一个方向是直接和底盘控制算法闭环——把Dugoff模型算出的轮胎力作为前馈项,叠加到ESP或者MPC控制器里,能明显改善低附着路面的控制效果。
如果你有快速原型的需求,Dugoff模块因为只用到代数运算,用Simulink Coder生成C代码非常简单,可以直接部署到实时硬件上做HIL测试。但要注意,CarSim的S-Function模块本身不建议生成C代码进实时机,联调模型和实时模型最好分开维护。
这套模型我在项目里反复用了很多次,最直观的感受是,Dugoff模型让你终于知道你Simulink里那套控制算法看到的轮胎力是怎么来的,而不是一个黑盒。以后如果做紧急避障或者低附着路面稳定控制,在这个基础上改起来会顺手很多。最后分享一个小经验:把这个Dugoff模块做成子系统并留好mu输入口,之后你无论是做附着估计还是变路面仿真,都不用回来动核心算法。