news 2026/9/16 4:42:20

车辆二自由度模型传递函数推导与Python实现:横摆角速度与侧偏角分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
车辆二自由度模型传递函数推导与Python实现:横摆角速度与侧偏角分析

简介:资源包聚焦于车辆二自由度动态模型的搭建与分析,面向汽车工程及自动驾驶方向的开发者、研究人员。模型从状态空间方程出发,转换为传递函数形式,重点考察横摆角速度与车辆侧偏角对操控稳定性的影响。包体共2个文件,分别为MATLAB脚本与FIG图形文件,脚本负责状态空间至传递函数的转换与仿真计算,图形文件展示波特图、阶跃响应等可视化结果;压缩包仅74KB,结构精简便于快速部署。当前已有642人学习下载,使用者可借助脚本自定义输入条件,观察不同前轮转角或车速下的车辆动态响应,评估急转向、紧急避障等工况的稳定性,也可为横摆力矩控制器的参数整定提供参考。该文件包虽小,但完整覆盖从建模、转换到响应分析的核心流程,适合车辆控制入门者与工程师快速验证。

1. 车辆二自由度模型的传递函数,到底在解决什么问题?

做底盘电控或辅助驾驶横向控制时,最容易碰到的场景是:前轮转角给下去,车身先建立侧偏角,再慢慢形成横摆角速度,两个响应耦合在一起,而且随车速变化非常大。如果直接用整车的非线性动力学模型去看,很难分清哪个环节在限制带宽,哪个环节在贡献相位延迟。dof2_tf这类二自由度传递函数工具的用处,就是先把车辆真正最核心的横向与横摆运动提炼成两个可解析的传递函数:前轮转角到横摆角速度、前轮转角到车辆侧偏角。

这篇文章不打算讲泛泛的“车辆动力学”,而是直接把自行车模型的状态方程写出来,推导出传递函数,然后再用 Python 把增益、阻尼、零点和首一/尾一归一化这些工程上最容易踩坑的点全部算一遍。适合正在做车辆稳定性分析、EPS/ESP 控制标定或者模拟器整车模型简化的工程师阅读。

2. 车辆二自由度模型的状态方程与横摆传递函数推导

2.1 用自行车模型写线性动力学方程

二自由度模型通常指“自行车模型”:忽略左右轮差异,把前后轴各自合并成一个等效轮胎,假设纵向车速Vx恒定,只保留侧向速度vy和横摆角速度r两个自由度。为了直接观察质心侧偏角,这里用状态变量x = [β, r]^T,其中β = vy / Vx。前轮转角δ_f是输入。

在这个假设下,侧向力平衡方程和横摆力矩方程可以写成:

$$ m V_x (\dot{\beta} + r) = C_f(\delta_f - \beta - \frac{l_f r}{V_x}) + C_r(- \beta + \frac{l_r r}{V_x}) $$

$$ I_z \dot{r} = l_f C_f(\delta_f - \beta - \frac{l_f r}{V_x}) - l_r C_r(- \beta + \frac{l_r r}{V_x}) $$

其中m是整车质量,Iz是绕 Z 轴的横摆转动惯量,lflr分别是质心到前轴和后轴的距离,C_fC_r是前、后轴的等效侧偏刚度,都取正值。

把两个方程整理成状态空间形式,得到:

$$ \begin{bmatrix} \dot{\beta}\ \dot{r} \end{bmatrix}

\begin{bmatrix} -\frac{C_f+C_r}{mV_x} & -1 - \frac{C_f l_f - C_r l_r}{mV_x^2}\ -\frac{C_f l_f - C_r l_r}{I_z} & -\frac{C_f l_f^2 + C_r l_r^2}{I_z V_x} \end{bmatrix} \begin{bmatrix} \beta\ r \end{bmatrix} + \begin{bmatrix} \frac{C_f}{mV_x}\ \frac{l_f C_f}{I_z} \end{bmatrix} \delta_f $$

注意矩阵第一行第二列里的-1来自侧向力方程中的离心项m Vx r,这一项是最容易被忽略的。如果把它丢掉,高速时横摆角速度直流增益会明显偏大。

输出选择y = [r, β]^T,也就是第二行横摆角速度、第一行侧偏角,所以输出矩阵为:

$$ C_{out} = \begin{bmatrix} 0 & 1\ 1 & 0 \end{bmatrix}, \quad D = 0 $$

这里用C_out这个符号,是为了和侧偏刚度C_f区分。实际代码中矩阵名不要直接用C,否则很容易在调试时把两个含义弄混。

2.2 传递函数分母与分子:二阶系统加一个零点

G(s) = C_out (sI-A)^{-1} B求传递函数。因为系统只有两个状态,所以特征多项式一定是二阶:

$$ \Delta(s) = s^2 + 2\zeta \omega_n s + \omega_n^2 $$

其中ωn是固有圆频率,ζ是阻尼比。由于状态矩阵A中含有1/Vx项,这两个指标都会随车速变化。

横摆角速度对前轮转角的传递函数分子通常不是常数,而是一个带正实零点的多项式。也就是说,r/δ_f是“一个零点 + 两个极点”的系统,零点会让高频相位提前,这也是为什么实际车辆在快速转向时横摆响应比简单的二阶滞后模型更快一些。车辆侧偏角对前轮转角的传递函数分子同样含有一个零点,但其常数项往往是负值,这代表稳态时质心侧偏角与转向方向相反。

直接用sympy做一次符号推导,可以很清楚地看到这种结构:

import sympy as sp s = sp.symbols('s') m, Iz, lf, lr, Cf, Cr, Vx = sp.symbols('m Iz lf lr Cf Cr Vx') A = sp.Matrix([ [-(Cf + Cr) / (m * Vx), -1 - (Cf * lf - Cr * lr) / (m * Vx**2)], [-(Cf * lf - Cr * lr) / Iz, -(Cf * lf**2 + Cr * lr**2) / (Iz * Vx)] ]) B = sp.Matrix([[Cf / (m * Vx)], [lf * Cf / Iz]]) C_out = sp.Matrix([[0, 1], [1, 0]]) G = sp.simplify(C_out * (s * sp.eye(2) - A).inv() * B) print(sp.factor(G[0, 0])) # r/delta_f print(sp.factor(G[1, 0])) # beta/delta_f

这段代码会输出两个传递函数的符号表达式。可以看到分子中确实都存在s的一次项,而不是纯比例或纯积分形式。符号表达式的意义在于:当你改变质心位置或前后侧偏刚度比例时,零点会如何移动,一眼就能看出来,这比纯数值调参有用得多。

2.3 从直流增益看不足转向与过度转向

稳态横摆角速度增益指的就是s→0时的传递函数值,工程上通常叫“横摆增益”或“转向灵敏度”。它可以从状态空间直接求直流增益:

$$ G_r(0) = - C_{out, r} A^{-1} B $$

也可以解稳态代数方程得到。对二自由度模型来说,横摆增益随车速的变化会呈现出“先线性增大,后增速放缓,最终下降”的形状。峰值出现在什么车速,取决于不足转向系数;如果峰值一直不出现,直到临界车速前都在上升,那就是中性转向或过度转向性质。

先给出一组真实的整车参数,后面所有代码和表格都基于这组参数计算。这是一台典型 B 级轿车的等效参数:

参数符号含义数值单位
m整车质量1500kg
Iz横摆转动惯量2500kg·m²
lf质心到前轴距离1.2m
lr质心到后轴距离1.4m
Cf前轴等效侧偏刚度45000N/rad
Cr后轴等效侧偏刚度45000N/rad
Vx纵向车速20m/s

注意这里的C_fC_r是整轴等效侧偏刚度,不是单胎值,通常来自轮胎试验数据换算后的结果。如果直接把单胎刚度乘 2 放进去,往往会让车辆看起来比实际偏过度。

3. 用 Python 把 dof2_tf 传递函数算出来

3.1 最小状态空间实现与 ss2tf 转换

现在用control库把上一章的状态空间矩阵变成传递函数。Python 环境需要安装numpycontrol,推荐再装一个matplotlib,方便后面画阶跃响应。

先实现最小可用版本:

import numpy as np import control as ct # 参数来自表 2-1 m = 1500.0 Iz = 2500.0 lf = 1.2 lr = 1.4 Cf = 45000.0 Cr = 45000.0 Vx = 20.0 # 状态向量 [beta, r],输入 delta_f A = np.array([ [-(Cf + Cr) / (m * Vx), -1.0 - (Cf * lf - Cr * lr) / (m * Vx * Vx)], [-(Cf * lf - Cr * lr) / Iz, -(Cf * lf * lf + Cr * lr * lr) / (Iz * Vx)] ]) B = np.array([[Cf / (m * Vx)], [lf * Cf / Iz]]) # 输出顺序:第一行 r,第二行 beta C_out = np.array([[0.0, 1.0], [1.0, 0.0]]) D_out = np.zeros((2, 1)) sys_ss = ct.ss(A, B, C_out, D_out) sys_tf = ct.ss2tf(sys_ss) for idx, name in [(0, 'r/delta_f'), (1, 'beta/delta_f')]: print(name, sys_tf[idx, 0])

在 Vx = 20 m/s 时,输出大约为:

r/delta_f = (21.6 s + 70.2) / (s^2 + 6.06 s + 12.7) beta/delta_f = (1.5 s - 16.7) / (s^2 + 6.06 s + 12.7)

说明这段代码里的几个关键点:

矩阵A第一行第二列的-1后面还有一项-(Cf*lf - Cr*lr)/(mVx^2),因为这里前、后轴侧偏刚度相同但轴距载荷分配不同,所以综合结果是+0.015,最终让A[0,1]变成约-0.985。如果你把这一项漏掉,横摆角速度的直流增益会上偏约 3% 左右。

C_out的行顺序直接决定ss2tf输出哪一行是r,哪一行是β。很多人调试时发现两个传递函数对调,就是因为这里的行顺序写反了。建议在注释里写清楚输出顺序。

整理成尾一形式后,r/δ_f的直流增益为:

$$ \frac{70.2}{12.7} = 5.52 \ \text{rad/s per rad} $$

这个数值意味着前轮转角给 0.1 rad(约 5.7°)时,稳态横摆角速度大约为 0.55 rad/s。

3.2 首一还是尾一:开环传递函数增益藏在常数项比值里

control库返回的传递函数默认是“首一”形式,也就是分子分母都除以最高次项系数,使分母最高次项系数为 1。比如上面的传递函数中,分母已经写成s^2 + 6.06s + 12.7,最高次项系数就是 1。

但工程上做增益调度时,更习惯用“尾一”形式,也就是分母常数项为 1。因为直流增益直接等于分子常数项与分母常数项的比值。两种归一化方式本身没有对错,但换算错了会导致开环增益差好几个数量级。

把首一形式转成尾一形式的代码是:

num = np.asarray(sys_tf[0, 0].num[0][0], float) # 首一分子系数 den = np.asarray(sys_tf[0, 0].den[0][0], float) # 首一分母系数 # 直流增益 = 分子常数项 / 分母常数项 dc_gain = num[-1] / den[-1] print("直流增益 =", dc_gain) # 约 5.516 # 转尾一:分子分母同除以分母常数项 num_tail = num / den[-1] den_tail = den / den[-1] print("尾一 num:", num_tail) # 约 [1.697, 5.516] print("尾一 den:", den_tail) # 约 [0.0786, 0.476, 1.0]

转换之后,横摆角速度传递函数变成:

$$ \frac{r}{\delta_f}(s) = \frac{1.697s + 5.516}{0.0786s^2 + 0.476s + 1} $$

这时分子常数项就是直流增益5.516,而分母常数项是 1。两种形式描述同一个系统,但如果你拿着首一形式去找“开环传递函数增益”,很可能会把70.2当成表调增益,或者在 matlab 里用dcgain时忘记除以分母常数项。建议把这段转换逻辑封装成一个公共函数,所有增益表都从尾一形式读取。

3.3 批量计算不同速度下的传函参数

实际工程中不会只算一个速度。把上面的逻辑包进函数,循环计算 10、20、30、40 m/s 下的直流增益、固有频率和阻尼比:

def vehicle_tf(Vx, m=1500.0, Iz=2500.0, lf=1.2, lr=1.4, Cf=45000.0, Cr=45000.0): A = np.array([ [-(Cf + Cr) / (m * Vx), -1.0 - (Cf * lf - Cr * lr) / (m * Vx * Vx)], [-(Cf * lf - Cr * lr) / Iz, -(Cf * lf * lf + Cr * lr * lr) / (Iz * Vx)] ]) B = np.array([[Cf / (m * Vx)], [lf * Cf / Iz]]) sys = ct.ss(A, B, np.array([[0.0, 1.0]]), np.zeros((1, 1))) return ct.ss2tf(sys) for V in [10, 20, 30, 40]: G = vehicle_tf(V) num = np.asarray(G[0, 0].num[0][0], float) den = np.asarray(G[0, 0].den[0][0], float) dc = num[-1] / den[-1] wn = np.sqrt(den[-1]) zeta = -den[1] / (2.0 * wn) print(f"{V:2.0f} m/s: dc={dc:.3f}, wn={wn:.2f}, zeta={zeta:.2f}")

注意这里求阻尼比使用的是den[1],因为首一形式下numpy数组的索引 0 是s^2系数,索引 1 是s系数,索引 2 是常数项。这是信号处理库的常见排列顺序,和手写多项式时的习惯不一样,容易看错。

4. 阶跃响应与转向特性分析:横摆角速度增益怎么变

4.1 速度对横摆增益与阻尼比的影响

用上一节代码跑出来的结果,整理成表格:

Vx (m/s)直流增益 r/δ (rad/s per rad)ωn (rad/s)ζ
103.506.330.96
205.523.570.85
306.112.770.73
405.972.430.62

横向对比这组数据可以得出几个工程判断:

低速时,横摆角速度直流增益随车速近似线性增大,驾驶员会觉得转向“灵敏”。到了 30 m/s 以后,增益不再继续上升,这说明车辆已经有明显的不足转向趋势。如果增大后轴侧偏刚度Cr,峰值速度会降低,不足转向量变大;减小Cr则相反,车辆会变得更灵活,但阻尼比下降速度会加快。

阻尼比从 0.96 降到 0.62,意味着高速情况下横摆振荡衰减变慢。你可以在阶跃响应里看到第一个超调量增大。对于线性模型而言,只要阻尼比大于 1 就没有超调;在 40 m/s 时虽然还有 0.62,但实际车辆因为轮胎非线性衰减会更快,所以不能只看这个线性阻尼比来标定转向手感。

4.2 车辆侧偏角响应:低速小、高速大的物理原因

车辆侧偏角 β 的稳态值也是从传递函数直流增益里直接读。使用上一节的vehicle_tf函数,把输出矩阵改为只输出 β 即可。

从传递函数本身可以看出,β 的分子常数项是负值,所以稳态 β 与转向方向符号相反。前轮转向时车头往内转,质心速度方向却对外偏,β 为负说明质心“跟不上”车头方向。高速时这个负值会明显增大,因为侧向力建立得更慢,需要更大的侧偏角来产生足够的横摆力矩。

如果根据参数表手动计算,Vx = 20 m/s 时 β/δ_f 的直流增益约为-1.31 rad/rad,即前轮转角给 0.1 rad 时稳态侧偏角约为 -0.13 rad,约 7.5°。这在真实车辆上已经算很大的侧偏角了,说明该参数下车辆在高速紧急变道时稳定性余量较小。

在 ESP/CMS 标定中,常用质心侧偏角速度dβ/dt作为触发信号,因为 β 本身变化较慢,dβ/dt能更早暴露失稳趋势。用传递函数可以很容易求出β的导数的频域响应,也就是在原传递函数前面乘一个s,这比做差分滤波更干净。

4.3 零点、极点与转向灵敏度的统一理解

横摆角速度传递函数的零点随速度变化,但它始终是一个负实数零点,所以系统是“最小相位”的,不会有初期反向响应。零点位置越接近虚轴,相位提前作用越明显。以 Vx = 20 m/s 为例,零点在约-3.25 rad/s,和固有频率3.57 rad/s很接近,所以相位提前在中频段已经很显著。

用以下代码可以同时打印零点和极点:

num = np.asarray(sys_tf[0, 0].num[0][0], float) den = np.asarray(sys_tf[0, 0].den[0][0], float) zeros = np.roots(num) poles = np.roots(den) print("零点:", zeros) print("极点:", poles) print("阻尼比:", -poles.real / np.abs(poles))

如果某个参数组合下极点实部出现在右半平面,直接说明该速度下车队是开环不稳定的。这个分析完全基于传递函数,不需要做非线性仿真,适合在概念设计阶段快速筛选参数范围。

5. 用状态空间数值积分反查传递函数,守住模型边界

5.1 阶跃响应对照:tf 和 lsim 必须一致

ss2tf转换过程中涉及矩阵求逆和多项式化简,代码里最容易出错的是输出矩阵行顺序。一个很有效的验证方法是:用同一个状态空间模型分别做step_responselsim,再把结果和传递函数的step_response对比,最大误差应接近机器精度。

import numpy as np import control as ct # 复用前面的 sys_ss,取第一输出 r sys_r_ss = ct.ss(A, B, np.array([[0.0, 1.0]]), np.zeros((1, 1))) T = np.linspace(0, 2.0, 1000) t_ss, y_ss = ct.step_response(sys_r_ss, T=T) t_tf, y_tf = ct.step_response(sys_tf[0, 0], T=T) err = np.max(np.abs(y_ss[0, :] - y_tf[0, :])) print("最大误差:", err)

这个误差如果超过 1e-8,基本可以确定状态矩阵某个元素或输出矩阵写错了。也可以换成长度足够长的正弦输入,用lsim对比频域响应,能覆盖更多频率点。

5.2 低速与高频边界

二自由度传递函数在低速段和高速段都有自己的使用边界。车速低于 5 m/s 时,侧偏角本身很小,轮胎基本没有多少侧向力,模型中的1/Vx项会迅速放大,导致传递函数数值对参数极度敏感。此时更适合用运动学自行车模型。

高频段方面,线性轮胎模型没有考虑轮胎松弛长度,也没有考虑悬架侧倾转向,所以当输入频率超过悬架共振频率后,真实车辆的横摆响应会出现额外相位滞后。用二自由度传递函数做控制设计时,建议把有效带宽限制在 3 Hz 以内,如果整车频响在这一段出现明显的 -60 dB/dec 斜率,说明垂向和悬架自由度已经参与进来,不能再简化成二自由度系统。

把这段对照验证代码放进你的回归脚本里,每次改一个参数就跑一遍,确认直流增益和零极点都和表 4-1 对得上,再谈下游的增益调度。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 4:41:27

GPU UMD Stage3Part4:多上下文调度与命令流水线实战

1. UMD驱动开发不是“装个驱动就完事”:Stage3Part4到底在解决什么真问题?很多人看到“GPU UMD 学习指南 stage3part4”这个标题,第一反应是:“又一个教你怎么装NVIDIA/AMD驱动的教程?”——错了。UMD(User…

作者头像 李华
网站建设 2026/9/16 4:40:34

Matlab实现IEEE 14节点碳排放流计算:从原理到代码

做电力系统低碳化研究的朋友,十有八九都绕不开碳排放流这个工具。我在读文献时第一次看到“碳排放流”四个字,以为又是某篇论文里玄乎的新概念,直到自己动手在IEEE 14节点系统上把计算流程完整走了一遍,才发现它其实就是一套把“发…

作者头像 李华
网站建设 2026/9/16 4:40:29

WLS与PMU融合的电力系统状态估计Matlab实现与精度对比

1. 项目概述1.1 核心需求解析做电力系统状态估计的同行应该都有切身体会:调度中心里那些实时数据,看着是一大屏,实际上每一路遥测都带着或多或少的误差。有的来自CT/PT变比误差,有的是模数转换的量化误差,还有的干脆就…

作者头像 李华
网站建设 2026/9/16 4:39:49

C# WPF半导体上位机开发实战:晶圆搬移系统设计与避坑指南

1. 项目概述:这不是一个普通上位机,而是一套晶圆级精密搬移的“神经中枢”你手上正要做的,不是那种点个按钮读个串口、画几条曲线就完事的Demo级上位机。它要直接对接半导体Fab厂里真实运行的晶圆搬运机械臂——准确说是石墨岛(Gr…

作者头像 李华
网站建设 2026/9/16 4:39:15

滤镜不是美颜贴纸:2026年光学滤镜原理与实战指南

1. 摄影滤镜不是“美颜贴纸”,而是光学镜头的延伸手你有没有过这样的经历:拍完一张风光照,回看时发现天空惨白一片,云层毫无层次;或者在强光下拍人像,反光刺眼、细节全失;又或者想拍出车流光轨&…

作者头像 李华
网站建设 2026/9/16 4:38:35

C#实现图片和扫描PDF文字识别:OCR引擎选型与实战

做C#开发的朋友,十有八九会遇到这类需求:从一张JPG里把订单号扣出来,从一个扫描合同PDF里全文检索关键词,或者给内部OA加一个凭证自动录入功能。我去年接过一个项目,对方发来80多个扫描版PDF,全是票据&…

作者头像 李华