ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Matlab控制系统建模:从传递函数到仿真实战

Matlab控制系统建模:从传递函数到仿真实战

1. 从一个实际需求说起:为什么要用Matlab建控制系统模型

很多刚接触控制理论的朋友,第一次看到传递函数、状态空间表达式这些概念时,脑子里全是问号。课本上给你一个弹簧阻尼系统,让你写出微分方程,再拉普拉斯变换,再整理成传递函数——一套流程走下来,手算倒是能算,但一旦系统稍微复杂一点,比如多输入多输出、有非线性环节、有延迟,手算基本就废了。更关键的是,你算完了想看看这个系统到底表现如何,阶跃响应长什么样,超调量多大,调节时间多久,靠手画根轨迹或者伯德图,精度和效率都堪忧。

这就是Matlab进入视野的原因。它不是一个“帮你算数”的计算器,而是一整套控制系统建模、分析、设计的工具链。你可以在里面用几行代码就把一个物理系统的数学模型搭出来,然后直接调step()看阶跃响应,调bode()看频域特性,调rlocus()画根轨迹,甚至直接上pidtune()帮你自动整定PID参数。整个过程从建模到验证到调参,闭环非常短。

这篇内容适合谁看?如果你正在学自动控制原理、现代控制理论,或者做课程设计、毕业设计,需要把课本上的理论落到实际仿真上,那这篇就是写给你的。如果你已经工作,偶尔需要快速验证一个控制方案的可行性,不想从零推导,那这篇也能帮你省不少时间。我默认你有最基本的Matlab操作基础——知道怎么新建脚本、怎么运行代码、怎么看工作区变量,这些就够了,不需要你精通Matlab编程。

接下来我会从建模思路、核心函数、实操步骤、常见坑四个维度,把“用Matlab建立控制系统模型”这件事讲透。不是照搬帮助文档,而是把我自己踩过的坑和总结的技巧都揉进去。

2. 控制系统建模的整体思路与方案选型

2.1 三种建模方式的适用场景对比

在Matlab里建立控制系统模型,本质上就是把物理系统的动态特性翻译成Matlab能理解的数学对象。翻译的方式有三种,分别对应不同的系统描述方法:

建模方式数学基础适用场景核心Matlab函数
传递函数模型拉普拉斯变换单输入单输出、线性时不变系统tf()
状态空间模型微分方程组多输入多输出、现代控制理论ss()
零极点增益模型因式分解频域设计、根轨迹分析zpk()

这三种模型在Matlab里是可以互相转换的。比如你用tf()建了一个传递函数,随时可以用ss()把它转成状态空间,反过来也行。所以选哪种方式入手,主要看你的系统描述习惯和分析需求。

传递函数最直观,适合经典控制理论那套东西——你手上有微分方程,拉氏变换一搞,整理成分子分母多项式的形式,直接往tf()里填系数就行。状态空间更适合多变量系统,或者你要用现代控制理论的方法(比如极点配置、LQR)来设计控制器。零极点增益模型则在频域设计里用得比较多,因为伯德图和根轨迹本质上就是在零极点平面上做文章。

2.2 为什么我建议从传递函数入手

虽然状态空间更“现代”,但我个人建议初学者先从传递函数建模开始。原因有三:

第一,传递函数的物理意义更直观。分子分母的系数直接对应微分方程的各项系数,你写出来的东西和课本上的公式是一一对应的,不容易搞错。第二,传递函数的阶次和零极点分布可以直接读出系统的动态特性——比如一阶系统没有超调,二阶系统看阻尼比就知道振荡程度,这些直觉在状态空间里需要额外计算才能获得。第三,经典控制理论的分析工具(根轨迹、伯德图、奈奎斯特图)都是基于传递函数的,你先用tf()建好模型,后面调rlocus()、bode()都是水到渠成的事。

当然,如果你的系统天生就是多输入多输出的,比如一个四旋翼飞行器的姿态控制,那直接上状态空间更合适,没必要硬拆成多个传递函数。选型的原则就一条:用最贴近系统物理本质的描述方式。

2.3 建模前必须想清楚的三个问题

在打开Matlab之前,先问自己三个问题:

  1. 系统的输入和输出分别是什么?这决定了你建模的边界。比如一个电机控制系统,输入是电压,输出是转速,那传递函数就是转速对电压的传递函数。如果你搞错了输入输出,后面所有分析都是白搭。

  2. 系统是线性的还是非线性的?Matlab的tf()和ss()只能建线性时不变模型。如果你的系统有非线性环节(比如饱和、死区、摩擦),要么先在工作点附近线性化,要么用Simulink搭非线性模型。这一点很多人容易忽略,拿一个非线性系统硬套传递函数,结果仿真和实际对不上。

  3. 系统的参数是确定的还是不确定的?如果你知道精确的参数值,直接建标称模型就行。如果参数有波动范围,可能需要建多个模型或者用鲁棒控制的方法。初学者一般先建标称模型,后面再考虑不确定性。

这三个问题想清楚了,建模就是几分钟的事。想不清楚,后面反复改模型,浪费的时间远不止几分钟。

3. 核心函数与实操要点详解

3.1 传递函数建模:tf函数的使用细节

tf()是Matlab里建传递函数最基础的函数。它的调用格式很简单:

sys = tf(num, den)

其中num是分子多项式的系数向量,den是分母多项式的系数向量,都按降幂排列。比如一个二阶系统:

% G(s) = (2s + 1) / (s^2 + 3s + 2) num = [2 1]; den = [1 3 2]; sys = tf(num, den);

运行之后,Matlab会输出:

sys = 2 s + 1 ------------- s^2 + 3 s + 2

这里有几个细节值得注意:

系数向量的长度决定了阶次。分子向量长度是2,对应一阶分子;分母向量长度是3,对应二阶分母。如果某一项系数是0,不能省略,必须补0。比如s^2 + 2的分母向量是[1 0 2],中间的0不能省。

可以指定采样时间建离散模型。如果你要建离散传递函数,加一个采样时间参数:

Ts = 0.01; % 采样周期10ms sys_d = tf(num, den, Ts);

这样建出来的就是离散模型,后面做数字控制器设计时直接用。

变量名不要用s。很多人习惯把传递函数变量命名为s,但Matlab里s不是保留变量,你赋值之后可能会覆盖掉。建议用sys、G、H这类名字。如果你确实想用s来写表达式,需要先定义:

s = tf('s'); G = (2*s + 1) / (s^2 + 3*s + 2);

这种方式更直观,适合写复杂的传递函数表达式,但要注意s变量一旦定义,后面就不能再当普通变量用了。

3.2 状态空间建模:ss函数的参数含义

状态空间模型的Matlab表示是:

sys = ss(A, B, C, D)

其中A是状态矩阵,B是输入矩阵,C是输出矩阵,D是直接传递矩阵。对于一个n阶系统、m个输入、p个输出,A是n×n,B是n×m,C是p×n,D是p×m。

举个例子,一个二阶系统:

A = [0 1; -2 -3]; B = [0; 1]; C = [1 0]; D = 0; sys = ss(A, B, C, D);

这个系统对应的传递函数是1/(s^2 + 3s + 2)。你可以用tf(sys)验证一下:

tf(sys)

输出应该是:

ans = 1 ------------- s^2 + 3 s + 2

D矩阵什么时候不为零?当系统有直接传递项时,D不为零。比如G(s) = (s+1)/(s+2),写成状态空间形式时D就是1。大部分物理系统的D矩阵是零,因为输入到输出之间没有瞬时通路。但如果你不确定,可以先设D=0,后面用tf()转换验证一下。

状态变量的选择不唯一。同一个传递函数可以对应无穷多组状态空间实现,A、B、C矩阵都不一样,但传递函数相同。Matlab的ss()函数接受你给的任意一组矩阵,不会帮你做规范化。如果你想让Matlab自动生成一组标准形式,可以先用tf()建传递函数,再用ss()转换:

sys_tf = tf([1], [1 3 2]); sys_ss = ss(sys_tf);

这样Matlab会自动选择一种可控标准型或可观标准型。

3.3 模型转换:tf、ss、zpk之间的互转

三种模型之间的转换是控制系统建模中最常用的操作之一。转换函数很直观:

sys_tf = tf(sys_ss); % 状态空间转传递函数 sys_ss = ss(sys_tf); % 传递函数转状态空间 sys_zpk = zpk(sys_tf); % 传递函数转零极点增益 sys_tf = tf(sys_zpk); % 零极点增益转传递函数

转换过程中Matlab会自动处理约分和规范化。但有一个坑要注意:数值精度问题。当系统阶次较高时,传递函数的分子分母系数可能非常大或非常小,转换过程中容易出现数值误差。比如一个六阶系统,转换后可能出现极点和零点几乎抵消但又不完全抵消的情况,导致传递函数看起来阶次很高但实际上可以降阶。这时候可以用minreal()函数做最小实现:

sys_min = minreal(sys);

minreal()会自动消去对消的零极点,给出最简模型。我建议每次做完模型转换后都跑一下minreal(),养成习惯。

3.4 模型连接:串联、并联、反馈

实际控制系统往往由多个环节组成,需要把多个模型连接起来。Matlab提供了几种连接方式:

串联:两个系统前后相连,总传递函数是乘积。

sys_series = series(sys1, sys2); % 或者直接用乘法 sys_series = sys1 * sys2;

并联:两个系统输入相同,输出相加。

sys_parallel = parallel(sys1, sys2); % 或者直接用加法 sys_parallel = sys1 + sys2;

反馈:前向通道和反馈通道构成闭环。

sys_cl = feedback(sys_forward, sys_feedback);

feedback()默认是负反馈。如果是正反馈,加第三个参数:

sys_cl = feedback(sys_forward, sys_feedback, +1);

这里有一个非常容易犯的错误:反馈通道的传递函数不要写成1。很多人以为单位负反馈就是feedback(G, 1),这没错,但如果你实际系统里传感器有动态特性(比如一阶滞后),反馈通道就不是1了,必须把传感器模型也建进去。我见过不少课程设计,传感器明明有延迟,学生直接按单位负反馈做,仿真结果和实际差了一大截。

4. 完整实操流程:从物理系统到仿真验证

4.1 案例背景:一个直流电机速度控制系统

为了把上面的知识点串起来,我用一个直流电机速度控制的例子走一遍完整流程。这个例子在课程设计和实际工程中都很典型。

假设我们有一个直流电机,输入是电枢电压,输出是电机转速。根据电机学原理,电机的微分方程可以写成:

J * dω/dt = Kt * i - B * ω L * di/dt = V - R * i - Ke * ω

其中J是转动惯量,B是阻尼系数,Kt是转矩常数,Ke是反电动势常数,L是电枢电感,R是电枢电阻,V是输入电压,ω是转速,i是电枢电流。

这是一个二阶系统,两个状态变量:转速ω和电流i。输入是电压V,输出是转速ω。

4.2 参数设定与状态空间建模

先设定一组典型的参数值:

J = 0.01; % 转动惯量 kg·m^2 B = 0.1; % 阻尼系数 N·m·s/rad Kt = 0.01; % 转矩常数 N·m/A Ke = 0.01; % 反电动势常数 V·s/rad L = 0.5; % 电枢电感 H R = 1; % 电枢电阻 Ω

把这些参数代入微分方程,整理成状态空间形式。选择状态变量x1 = ω,x2 = i,输入u = V,输出y = ω。

状态方程:

dx1/dt = (-B/J) * x1 + (Kt/J) * x2 dx2/dt = (-Ke/L) * x1 + (-R/L) * x2 + (1/L) * u

输出方程:

y = x1

写成矩阵形式:

A = [-B/J, Kt/J; -Ke/L, -R/L]; B = [0; 1/L]; C = [1, 0]; D = 0; motor_ss = ss(A, B, C, D);

运行之后,Matlab会输出状态空间模型。你可以用tf(motor_ss)看看对应的传递函数:

motor_tf = tf(motor_ss);

输出大概是:

motor_tf = 0.01 ---------------------- 0.005 s^2 + 0.06 s + 0.1

这个传递函数的分母是二阶的,对应电机的机械时间常数和电气时间常数。分子是常数,说明输入到输出没有微分项。

4.3 从状态空间到传递函数的转换与验证

得到传递函数后,我习惯做两件事:一是检查极点和零点,二是画阶跃响应看看基本特性。

% 查看零极点 zpk(motor_tf) % 画阶跃响应 figure; step(motor_tf); grid on; title('直流电机速度控制系统阶跃响应');

从零极点分布可以看到,系统有两个实极点,都在左半平面,说明系统是稳定的。阶跃响应应该是一条单调上升的曲线,没有超调,最终稳定在某个值。

这里有一个细节:稳态值是多少?根据终值定理,阶跃响应的稳态值等于传递函数在s=0处的值。对于上面的传递函数:

G(0) = 0.01 / 0.1 = 0.1

所以阶跃响应的稳态值是0.1。这意味着如果输入1V电压,电机最终转速是0.1 rad/s。这个值看起来很小,但这是因为我们设定的参数里阻尼系数相对较大。实际电机参数不同,稳态值也会不同。

4.4 加入PID控制器后的闭环仿真

开环系统虽然稳定,但稳态值只有0.1,而且响应速度可能不够快。实际应用中通常会加一个PID控制器来改善性能。

在Matlab里加PID控制器很简单:

% 设计PID控制器 Kp = 10; Ki = 5; Kd = 0.1; C = pid(Kp, Ki, Kd); % 构建闭环系统 sys_cl = feedback(C * motor_tf, 1); % 画阶跃响应 figure; step(sys_cl); grid on; title('加入PID控制器后的闭环阶跃响应');

运行之后你会看到,闭环系统的阶跃响应稳态值变成了1(因为积分环节消除了稳态误差),响应速度也快了很多。但超调量可能比较大,需要调整PID参数。

PID参数怎么调?如果你不想手动试凑,可以用Matlab的pidtune()函数:

[C_tuned, info] = pidtune(motor_tf, 'pid');

pidtune()会自动给你一组满足默认性能指标(比如相位裕度60度)的PID参数。你可以把C_tuned替换到闭环系统里再仿真,对比效果。

4.5 仿真结果的分析与模型修正

仿真跑完之后,不能只看曲线好看就完事了。我一般会做以下几项检查:

第一,检查稳态误差。如果闭环阶跃响应的稳态值不是1,说明系统有稳态误差。对于有积分环节的PID控制器,稳态误差应该为零。如果不是零,可能是积分增益太小或者仿真时间不够长。

第二,检查超调量和调节时间。用stepinfo()函数可以直接获取这些指标:

info = stepinfo(sys_cl); disp(info);

输出会包含上升时间、调节时间、超调量、峰值等。你可以根据这些指标判断是否需要继续调参。

第三,检查抗扰动能力。实际系统会有扰动,比如负载转矩变化。你可以在仿真里加一个扰动输入,看看闭环系统能不能抑制。这需要建一个扰动通道的模型,稍微复杂一点,但思路是一样的。

第四,检查模型是否过于简化。如果仿真结果和实际测试差距很大,可能是建模时忽略了一些因素,比如电机的非线性摩擦、电枢反应、PWM驱动器的延迟等。这时候需要回到建模阶段,把忽略的因素补进去,或者用Simulink搭更详细的模型。

5. 常见问题与排查技巧实录

5.1 模型转换后阶次异常怎么办

这是最常见的问题之一。你建了一个三阶传递函数,转成状态空间再转回来,发现变成四阶了,或者零极点对消不干净。原因通常是数值精度问题。

排查方法:用minreal()做最小实现,看看能不能降阶。如果minreal()之后阶次正常了,说明原来的模型里有对消的零极点,只是数值上没完全对消。

sys_min = minreal(sys); order(sys_min) % 查看最小实现后的阶次

如果minreal()也解决不了,可能是模型本身就有问题,比如你建传递函数时系数填错了,导致零极点位置不对。

5.2 阶跃响应不收敛或发散怎么查

阶跃响应发散,说明系统不稳定。排查步骤:

  1. 检查极点位置:pole(sys),如果有极点在右半平面(实部大于零),系统就是不稳定的。
  2. 检查反馈极性:如果你建的是闭环系统,检查feedback()函数用的是正反馈还是负反馈。正反馈容易导致不稳定。
  3. 检查控制器参数:如果加了PID控制器后发散,可能是增益太大。先把Kp调小,再逐步增加。
  4. 检查模型连接:串联、并联、反馈连接时,检查有没有把该连的通道连错。比如把反馈通道接到了前向通道上。

5.3 仿真速度慢的优化思路

当模型阶次很高或者仿真时间很长时,Matlab的仿真可能会很慢。几个优化思路:

  • 降阶:用balred()或minreal()降低模型阶次。很多高阶系统实际上可以用低阶模型近似。
  • 用离散模型:如果只是做数字控制仿真,用离散模型比连续模型快。
  • 减少输出点数:step()函数可以指定时间向量,减少仿真点数。
  • 用lsim()代替step():对于复杂输入,lsim()更灵活,也可以控制仿真步长。

5.4 常见问题速查表

问题现象可能原因排查方法解决方案
模型转换后阶次变高数值精度导致零极点未完全对消用minreal()检查做最小实现
阶跃响应发散系统不稳定或反馈极性错误检查极点位置和反馈符号调整控制器或修正反馈
稳态值不对传递函数增益错误用dcgain()检查修正分子分母系数
仿真报错“维度不一致”矩阵维度不匹配检查A、B、C、D的维度重新确认状态变量个数
闭环响应和开环一样反馈通道没接对检查feedback()参数确认前向和反馈通道
PID调参后超调过大增益过大或微分不足用stepinfo()看指标减小Kp或增大Kd

5.5 几个我踩过的坑和独家技巧

坑一:用s变量写传递函数时忘了定义。直接写G = 1/(s^2+1)会报错,因为s没有定义。必须先s = tf('s')。这个坑我踩过不止一次,尤其是从其他语言转过来的时候。

坑二:feedback()的第二个参数是反馈通道,不是反馈增益。很多人以为feedback(G, 1)里的1是增益,其实它是反馈通道的传递函数。单位负反馈就是1,但如果反馈通道有动态特性,必须把传递函数写进去。

坑三:离散模型的采样时间单位是秒,不是毫秒。如果你写Ts = 10,Matlab会认为是10秒,不是10毫秒。这个错误在数字控制器设计里很常见,会导致仿真结果完全不对。

技巧一:用ltiview交互式查看响应。ltiview可以同时显示多个系统的阶跃响应、伯德图、奈奎斯特图等,方便对比。在调参阶段特别有用。

技巧二:用stepinfo批量获取性能指标。如果你有多个候选控制器,可以写一个循环,对每个控制器计算stepinfo,然后对比超调量、调节时间等指标,快速筛选。

技巧三:保存模型用save,不要用saveas。save保存的是Matlab的工作区变量,下次可以直接load回来继续用。saveas保存的是图片,不能继续仿真。

技巧四:模型命名要有规律。比如motor_tf、motor_ss、motor_cl分别表示电机的传递函数、状态空间、闭环模型。这样后面代码多了不容易搞混。

6. 从建模到设计的延伸思路

模型建好之后,其实只是第一步。后面还有控制器设计、性能验证、鲁棒性分析等一系列工作。但建模是所有这些工作的基础,模型不对,后面全错。

如果你已经能熟练用tf()和ss()建模型,下一步可以尝试这几个方向:

一是用Simulink搭更复杂的系统。Matlab的命令行建模适合线性时不变系统,但如果你有非线性环节、逻辑判断、状态机,Simulink会更合适。而且Simulink可以直接把Matlab工作区的模型导入进去,两者配合使用效率很高。

二是尝试系统辨识。如果你没有系统的物理参数,但有输入输出数据,可以用Matlab的系统辨识工具箱(System Identification Toolbox)从数据里估计模型。这在实验建模中非常有用。

三是做鲁棒性分析。实际系统的参数不会一成不变,用robustcontrol工具箱可以分析参数波动对系统性能的影响,设计出对参数变化不敏感的控制器。

四是做降阶处理。高阶模型虽然精确,但计算量大、控制器设计复杂。用balred()或modred()可以把高阶模型降到低阶,同时保持主要动态特性。这在实时控制里很重要。

我个人在实际操作中的体会是,建模这件事,快就是慢,慢就是快。花十分钟想清楚系统的输入输出、线性与否、参数确定与否,比后面花两小时改模型要划算得多。另外,每次建完模型都跑一下minreal()和step(),确认模型没有异常,这个习惯能帮你省掉很多莫名其妙的调试时间。

最后再分享一个小技巧:如果你不确定自己的模型对不对,可以先用一个你熟悉的简单系统验证一下。比如一阶系统1/(s+1)的阶跃响应应该是1 - exp(-t),稳态值是1,时间常数是1秒。用Matlab跑一下,看看结果对不对,确认自己的建模流程没问题,再去建复杂系统。这个“用已知验证未知”的思路,在控制系统里永远好用。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进