☰
Matlab控制系统建模:传递函数、状态空间与零极点模型实战指南
2026/10/17 1:36:06 网站建设 项目流程

1. 从一个"公式满天飞"的困境说起

如果你正在学自动控制原理,或者刚进实验室被导师安排做控制系统仿真,大概率会遇到这样一个场景:纸上推导了一堆传递函数,拉普拉斯变换、梅森增益公式、状态空间表达式轮番上阵,感觉自己已经"懂了",但一打开Matlab,面对空白的命令行窗口,却不知道从哪一行代码开始敲。更尴尬的是,好不容易照着教材敲了几行,出来的阶跃响应曲线跟老师PPT上的完全对不上,反复检查公式也没发现错,最后发现是feedback函数的正负号搞反了。

这不是个别现象。我见过太多人在"手推公式"和"上机仿真"之间卡住,核心原因不是数学不好,而是没有建立起一套从物理系统到Matlab模型的标准化映射思维。控制系统的建模,本质上就是把一个真实世界的物理对象,翻译成Matlab能听懂的语言——传递函数、状态空间、零极点增益,这三种语言各有各的适用场景,选错了表达方式,后面的分析和设计就会事倍功半。

这篇内容就是围绕"Matlab建立控制系统模型"这件事,把我在实际项目和教学中反复验证过的一套流程拆开来讲。不管你是刚接触控制的大二学生,还是需要快速搭建仿真验证算法的研究生,或者是工作中要用Simulink做系统级仿真的工程师,这里面的思路和踩坑经验都能直接用上。我会重点讲清楚三件事:为什么要选某种模型表示形式、每种建模方式的具体操作和参数含义、以及那些教材上不会写但实际一用就出问题的地方。

关键词就三个:Matlab、控制系统建模、传递函数。围绕这三个词,我们把整个建模链路走通。

2. 传递函数、状态空间、零极点:三种模型到底该用哪个

2.1 传递函数模型:最直观但也最容易踩坑的入口

传递函数是绝大多数人接触控制建模的第一个工具。它的核心思想很简单:在零初始条件下,输出量的拉普拉斯变换与输入量的拉普拉斯变换之比。用Matlab表示就是tf函数。

% 一个典型的二阶系统传递函数 num = [1]; % 分子系数 den = [1, 2, 1]; % 分母系数:s^2 + 2s + 1 sys_tf = tf(num, den)

运行后会得到:

sys_tf = 1 ------------- s^2 + 2s + 1

看起来很简单对吧?但这里有几个新手必踩的坑。

第一个坑是系数向量的顺序。Matlab的tf函数要求分子分母系数按降幂排列,缺项必须补零。比如传递函数是 $G(s) = \frac{10}{s^2 + 3s}$,分母是 $s^2 + 3s + 0$,你必须写成den = [1, 3, 0],不能写成[1, 3]。我见过有人因为漏写这个零,导致系统阶次判断错误,后面的稳定性分析全盘皆输。

第二个坑是连续系统和离散系统的混淆。tf默认创建连续时间模型,如果你要建离散模型,必须指定采样时间:

% 离散传递函数,采样时间0.01秒 sys_d = tf([1], [1, -0.5], 0.01)

不指定采样时间,Matlab会默认采样时间为0,表示连续系统。这个细节在做数字控制器设计时特别关键,采样时间设错,整个频域特性都会偏移。

第三个坑是MIMO系统的传递函数矩阵。当系统有多个输入多个输出时,传递函数不再是一个标量,而是一个矩阵。Matlab支持用元胞数组来构建:

% 2输入2输出系统的传递函数矩阵 s = tf('s'); G11 = 1/(s+1); G12 = 2/(s+2); G21 = 3/(s+3); G22 = 4/(s+4); G = [G11, G12; G21, G22];

这里用了一个很实用的技巧:先定义s = tf('s'),然后就可以像写数学公式一样直接写传递函数表达式,不用再手动提取分子分母系数。这个写法在构建复杂系统时能省下大量时间,而且不容易出错。

2.2 状态空间模型:处理多变量和初值问题的利器

传递函数只能描述零初始条件下的输入输出关系,一旦系统有非零初始状态,或者你需要观察内部状态变量的变化,传递函数就无能为力了。这时候状态空间模型就派上用场。

状态空间的标准形式是:

$$\dot{x} = Ax + Bu$$ $$y = Cx + Du$$

Matlab用ss函数创建:

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

状态空间模型最大的优势在于能直接处理多输入多输出系统,而且初值响应、能控性、能观性分析都天然支持。比如你要检查系统的能控性:

Co = ctrb(A, B); rank(Co) % 如果秩等于状态维数,则系统完全能控

这个操作在传递函数模型下是做不了的,因为传递函数已经"抹掉"了系统的内部结构信息。

但状态空间也有它的麻烦之处。状态变量的选取不唯一,同一个系统可以有无穷多种状态空间实现。这就导致一个很实际的问题:你从物理方程推导出来的状态空间模型,和Matlab通过ss(sys_tf)转换得到的模型,虽然输入输出特性完全一致,但A、B、C矩阵可能完全不同。这不是错误,只是状态变量的定义不同而已。

2.3 零极点增益模型:频域分析和控制器设计的桥梁

零极点增益模型把传递函数写成:

$$G(s) = K \frac{(s-z_1)(s-z_2)\cdots}{(s-p_1)(s-p_2)\cdots}$$

Matlab用zpk函数:

z = [-1]; % 零点 p = [-2, -3]; % 极点 k = 5; % 增益 sys_zpk = zpk(z, p, k)

这种表示形式在根轨迹分析和频域设计中特别有用。因为根轨迹就是研究极点随增益变化的轨迹,零极点形式让这个过程一目了然。而且zpk模型在Matlab内部计算时数值稳定性通常比tf更好,尤其是高阶系统。

三种模型之间可以自由转换:

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

但这里有一个非常重要的注意事项:转换过程中可能会发生零极点对消。比如一个系统在传递函数形式下看起来是三阶的,但转换到零极点形式后,发现有一对零极点几乎重合,实际上系统可以降阶为二阶。这种数值上的"近似对消"在理论推导中不会出现,但在Matlab的浮点运算中很常见。如果你不做检查就直接用降阶后的模型做控制器设计,可能会忽略掉一些高频动态,导致实际系统出现振荡。

提示:转换后一定要用zpk查看零极点分布,确认没有意外的零极点对消。可以用minreal函数进行最小实现化简。

3. 从物理方程到Matlab代码:一条完整的建模链路

3.1 以弹簧-质量-阻尼系统为例的推导过程

光讲函数用法太干,我们拿一个经典系统走一遍完整流程。考虑一个弹簧-质量-阻尼系统,质量块$m$在弹簧$k$和阻尼器$c$的作用下沿水平方向运动,外力$F$为输入,质量块位移$x$为输出。

根据牛顿第二定律:

$$m\ddot{x} = F - c\dot{x} - kx$$

整理成标准形式:

$$m\ddot{x} + c\dot{x} + kx = F$$

取拉普拉斯变换,零初始条件下:

$$(ms^2 + cs + k)X(s) = F(s)$$

传递函数为:

$$G(s) = \frac{X(s)}{F(s)} = \frac{1}{ms^2 + cs + k}$$

假设$m=1$,$c=2$,$k=1$,Matlab代码就是:

m = 1; c = 2; k = 1; num = [1]; den = [m, c, k]; sys = tf(num, den); step(sys); grid on;

这条阶跃响应曲线应该是单调上升的,因为系统阻尼比$\zeta = \frac{c}{2\sqrt{mk}} = 1$,正好处于临界阻尼状态。

3.2 状态空间形式的推导与代码实现

同一个系统,如果选状态变量$x_1 = x$(位移),$x_2 = \dot{x}$(速度),则:

$$\dot{x}_1 = x_2$$ $$\dot{x}_2 = -\frac{k}{m}x_1 - \frac{c}{m}x_2 + \frac{1}{m}F$$

写成矩阵形式:

$$\begin{bmatrix} \dot{x}_1 \ \dot{x}_2 \end{bmatrix} = \begin{bmatrix} 0 & 1 \ -\frac{k}{m} & -\frac{c}{m} \end{bmatrix} \begin{bmatrix} x_1 \ x_2 \end{bmatrix} + \begin{bmatrix} 0 \ \frac{1}{m} \end{bmatrix} F$$

$$y = \begin{bmatrix} 1 & 0 \end{bmatrix} \begin{bmatrix} x_1 \ x_2 \end{bmatrix}$$

Matlab代码:

A = [0, 1; -k/m, -c/m]; B = [0; 1/m]; C = [1, 0]; D = 0; sys_ss = ss(A, B, C, D);

你可以验证一下,tf(sys_ss)得到的结果和上面直接写的传递函数完全一致。这个验证步骤在实际项目中非常有用,相当于一个交叉检验,能帮你发现推导过程中的符号错误或系数遗漏。

3.3 建模过程中最容易出错的三个环节

第一个环节是单位的一致性。物理系统的参数往往带有单位,比如质量用kg,阻尼用N·s/m,弹簧刚度用N/m。如果你在推导时混用了不同量纲的单位,Matlab不会报错,但仿真结果会完全离谱。我的习惯是在代码开头用注释标明每个参数的单位,并且在推导纸上也标注清楚。

第二个环节是符号的正负。弹簧力和阻尼力的方向总是与位移和速度方向相反,所以方程中它们前面是负号。但在状态空间矩阵中,负号的位置很容易写错。一个实用的检查方法是:看A矩阵的特征值实部是否都为负(对于稳定系统),如果出现正实部,大概率是符号搞反了。

第三个环节是输入输出的对应关系。在多变量系统中,B矩阵的列对应输入,C矩阵的行对应输出。如果B和C的维度搞混,Matlab会直接报维度不匹配的错误,这个还好发现。但如果维度恰好相同但物理意义对应错了,Matlab不会报错,仿真结果却完全错误。这种"沉默的错误"最危险。

注意:每次建完模型后,至少做一次step或impulse响应,用物理直觉判断结果是否合理。比如弹簧-质量-阻尼系统的阶跃响应最终应该稳定在$F/k$的位置,如果稳态值不对,说明增益有问题。

4. 模型连接与化简:串联、并联、反馈的Matlab实现

4.1 串联、并联与反馈的标准操作

实际控制系统很少是单个环节,而是多个子系统的互联。Matlab提供了非常直观的运算符号来处理这些连接。

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

sys1 = tf([1], [1, 1]); sys2 = tf([2], [1, 2]); sys_series = sys1 * sys2; % 或者 series(sys1, sys2)

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

sys_parallel = sys1 + sys2; % 或者 parallel(sys1, sys2)

反馈:这是控制系统中最常见的连接方式。

sys_forward = tf([1], [1, 1]); sys_feedback = tf([1], [1, 2]); sys_closed = feedback(sys_forward, sys_feedback); % 默认负反馈

feedback函数的默认是负反馈,也就是$G/(1+GH)$。如果要正反馈,需要加第三个参数:

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

这里有一个非常容易搞错的地方:feedback(sys1, sys2)计算的是$\frac{sys1}{1 + sys1 \cdot sys2}$,也就是说第二个参数是反馈路径的传递函数,不是反馈信号本身。很多人会误以为第二个参数是"反馈减去参考"的那个环节,结果接反了。

4.2 用代数运算替代feedback函数的场景

有时候反馈路径不是简单的传递函数,而是带有前置滤波器或者扰动输入的结构。这时候用代数运算更灵活:

s = tf('s'); G = 1/(s^2 + 2*s + 1); % 被控对象 C = 10*(s+1)/s; % PI控制器 H = 1; % 传感器增益 % 闭环传递函数(参考输入到输出) T = (C*G)/(1 + C*G*H); % 扰动传递函数(扰动到输出) S = G/(1 + C*G*H);

这种写法虽然多敲几个字符,但结构清晰,而且方便同时计算多个传递函数。在实际项目中,我通常会把所有闭环传递函数都算出来,包括参考传递函数、扰动传递函数、控制量传递函数,这样才能全面评估系统性能。

4.3 模型降阶与最小实现的实际操作

高阶系统在Matlab中做控制器设计时,经常会遇到数值问题。比如一个20阶的系统,step响应可能因为数值误差出现高频振荡。这时候需要做模型降阶。

Matlab提供了balred(平衡截断)和minreal(最小实现)两个工具:

% 最小实现,消除零极点对消 sys_min = minreal(sys); % 平衡截断降阶到5阶 sys_reduced = balred(sys, 5);

minreal适合处理零极点几乎对消的情况,balred适合处理需要主动降阶的高阶系统。但降阶之前一定要做频域对比,确认降阶后的模型在关键频段内与原模型一致:

bode(sys, sys_reduced); legend('原始模型', '降阶模型');

如果两条曲线在穿越频率附近偏差超过3dB,降阶就要慎重了,因为穿越频率直接决定闭环系统的稳定裕度。

5. 仿真验证:从阶跃响应到频域分析的完整检查清单

5.1 时域响应检查的四个关键指标

建完模型后,第一件事就是看阶跃响应。但很多人只看一眼曲线形状就过了,其实至少应该检查四个指标:

指标含义检查方法
稳态值系统最终稳定在哪里dcgain(sys)
上升时间从10%到90%的时间stepinfo(sys).RiseTime
超调量峰值超过稳态值的百分比stepinfo(sys).Overshoot
调节时间进入并保持在2%误差带的时间stepinfo(sys).SettlingTime
info = stepinfo(sys); disp(info);

stepinfo返回的结构体包含了所有这些信息,比手动读图准确得多。我见过有人用鼠标在图上点来点去读数据,效率低还容易出错。

5.2 频域分析的三个必看曲线

时域响应直观,但频域分析能揭示更多关于稳定性和鲁棒性的信息。bode图是最常用的:

bode(sys); margin(sys); % 直接标注幅值裕度和相位裕度

margin函数会直接在图上标出增益裕度(Gain Margin)和相位裕度(Phase Margin)。对于大多数控制系统,相位裕度在45°到60°之间是比较理想的,太小会导致超调大,太大则响应慢。

nyquist图用于判断闭环稳定性:

nyquist(sys);

看Nyquist图的关键是看曲线是否包围$(-1, j0)$点。不包围则闭环稳定,包围则不稳定。但要注意,如果开环系统本身有右半平面极点,判断规则要修正,这时候用nyquist的返回参数更可靠。

nichols图在工程中用得少一些,但在需要同时看幅值和相位关系时很方便:

nichols(sys); ngrid; % 添加网格线

5.3 初值响应与Simulink联合仿真的衔接

状态空间模型可以做初值响应,这是传递函数做不到的:

x0 = [1; 0]; % 初始位移为1,初始速度为0 initial(sys_ss, x0);

这个功能在验证系统能控性和观测器设计时特别有用。比如你设计了一个状态反馈控制器,想看看从非零初始状态出发系统能否回到原点,initial函数就能直接给出答案。

如果模型比较复杂,或者需要模拟实际系统中的非线性环节(如饱和、死区),就需要转到Simulink。从Matlab工作区把模型导入Simulink很简单,在Simulink中直接用LTI System模块,变量名填工作区中的模型名称即可。但要注意Simulink的求解器步长设置,对于刚性系统(Stiff System),用ode23t或ode15s比默认的ode45更稳定。

6. 那些教材不会告诉你的实操经验

6.1 关于数值精度的两个隐蔽问题

Matlab默认使用双精度浮点数,对于大多数控制系统建模足够了。但有两种情况需要特别注意。

第一种是极点非常接近原点时。比如一个积分环节$1/s$,在tf中表示为tf([1], [1, 0])。这个模型在bode图中低频段幅值会趋向无穷大,数值计算时可能出现警告。处理方法是改用zpk模型,或者在做频域分析时手动设置频率范围。

第二种是零极点几乎对消但又不完全对消时。比如一个系统有一个极点-0.0001和一个零点-0.0002,理论上没有对消,但实际上对系统动态的影响微乎其微。这种"近似对消"会让模型阶次虚高,做控制器设计时增加不必要的复杂度。我的做法是设置一个容差阈值,用minreal(sys, tol)指定对消容差,通常取1e-3到1e-4之间。

6.2 模型命名与版本管理的实用技巧

做实际项目时,一个系统往往会迭代很多版模型。如果所有模型都叫sys,工作区很快就会乱成一锅粥。我的命名习惯是:sys_对象名_模型类型_版本号,比如sys_motor_tf_v2、sys_motor_ss_v2。这样一眼就能看出模型对应哪个物理对象、用的什么表示形式、是第几版。

另外,Matlab的save命令可以保存工作区变量,但更推荐用save('model_data.mat', 'sys_*')只保存模型相关的变量,避免把中间计算过程的临时变量也存进去。

6.3 从建模到控制器设计的过渡建议

模型建好只是第一步,接下来要做控制器设计。这里有一个很实用的经验:在开始设计控制器之前,先把被控对象的模型特性摸清楚。具体来说,至少要知道:

  • 系统的型别(Type Number):0型、I型还是II型?这决定了能否跟踪阶跃、斜坡信号。
  • 开环极点和零点分布:有没有右半平面零点?右半平面零点会限制闭环带宽,是控制器设计中的"硬约束"。
  • 相对阶次:极点数减零点数,决定了Bode图高频段的衰减斜率。

这些信息用zpk模型一看便知,但很多人急着上控制器,忽略了这一步,结果设计出来的控制器要么性能不达标,要么根本不稳定。

提示:可以用pole(sys)和zero(sys)分别获取极点和零点,用damp(sys)查看阻尼比和自然频率。

6.4 常见报错与快速排查

报错信息可能原因解决方法
"The numerator and denominator must have the same length"分子分母向量长度不一致检查是否漏写零系数
"Cannot convert to state-space"传递函数分子阶次高于分母检查传递函数是否真实现
"Model is singular"系统有积分环节或零极点对消用minreal化简或改用zpk
"Sample time mismatch"连续和离散模型混用统一采样时间或做离散化

这些报错信息看起来吓人,但原因往往很简单。关键是要养成"报错先看维度"的习惯,Matlab的维度错误占了所有报错的一半以上。

7. 一个完整的建模脚本模板

把上面的内容串起来,我整理了一个可以直接复用的建模脚本模板。这个模板适用于大多数单输入单输出系统的建模和初步验证:

%% 控制系统建模模板 clear; clc; close all; %% 1. 定义物理参数 m = 1; % 质量 (kg) c = 2; % 阻尼系数 (N·s/m) k = 1; % 弹簧刚度 (N/m) %% 2. 建立传递函数模型 num = [1]; den = [m, c, k]; sys_tf = tf(num, den); %% 3. 建立状态空间模型 A = [0, 1; -k/m, -c/m]; B = [0; 1/m]; C = [1, 0]; D = 0; sys_ss = ss(A, B, C, D); %% 4. 模型验证:传递函数与状态空间一致性检查 sys_check = tf(sys_ss); err = norm(sys_tf - sys_check); fprintf('模型一致性误差: %e\n', err); %% 5. 时域分析 figure; step(sys_tf); grid on; title('阶跃响应'); info = stepinfo(sys_tf); fprintf('上升时间: %.3f s\n', info.RiseTime); fprintf('超调量: %.2f%%\n', info.Overshoot); fprintf('调节时间: %.3f s\n', info.SettlingTime); %% 6. 频域分析 figure; bode(sys_tf); grid on; title('Bode图'); figure; margin(sys_tf); title('稳定裕度'); %% 7. 零极点分析 figure; pzmap(sys_tf); grid on; title('零极点分布'); fprintf('极点: \n'); disp(pole(sys_tf)); fprintf('零点: \n'); disp(zero(sys_tf));

这个模板的好处是每一步都有明确的输出和检查,不会出现"跑完了但不知道对不对"的情况。特别是第4步的一致性检查,用norm计算两个模型的差异,如果误差在1e-10量级以下,说明转换正确;如果误差很大,说明推导或代码有问题。

实际项目中,我会把这个模板保存为model_template.m,每次新建模型时复制一份,改改参数就能用。省去了反复敲重复代码的时间,也避免了遗漏检查步骤。

建模这件事,说到底就是"翻译"——把物理世界的规律翻译成数学语言,再翻译成Matlab能执行的代码。翻译过程中难免有信息损失或偏差,所以每一步都要做验证。我个人的体会是,花在验证上的时间永远不会白费,因为一个错误的模型会导致后面所有的分析和设计都建立在错误的基础上,越往后代价越大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询