简介:围绕MATLAB环境下S平面传递函数的三维可视化展开,面向控制系统与信号处理方向的学习者,帮助理解复频率域中频率响应的直观呈现方式。资源核心为一个m脚本与一份txt说明,压缩包共2个文件、约2KB,体量轻便,拿到即可运行验证。脚本s_plane.m演示了从利用tf函数定义传递函数、设定频率范围,到计算响应并绘制三维曲面的完整流程,配合Simulink基础概念,能把抽象的极点零点分布与系统稳定性分析转化为可观察的图形。已有1822人学习,适合作为控制理论学习中的动手实验素材,也便于在课程设计或自学中快速复现并修改参数,观察不同系统频率响应的变化。
1. 一个三行脚本画出三张图:这份 MATLAB 传递函数图资源包的定位
接触过的控制系统项目里,最常见的交付物不是代码,而是三张图:Bode 图、Nyquist 图和阶跃响应曲线。评审扫一眼幅值裕度、相位裕度,再看一眼超调量和调节时间,基本就把一个 Simulink 基础模型的成色判断得七七八八。麻烦的地方在于,模型搭得出来,不代表图画得对——num 和 den 的系数顺序写反,Bode 图照样能出,只是相位从 +90° 起步;采样时间填错,阶跃响应会抖成一团毛刺;Transfer Fcn 模块里的系数和脚本里的不是同一套,两张图对不上还没人发现。这份 MATLAB 传递函数图资源包收的就是这条链路:用 tf、zpk、ss 建立对象,用 bode、nyquist、step、stepinfo 出图出指标,再把 Simulink 框图和传递函数对齐。适合刚上手 Simulink 基础操作、需要交一份别人能照着重跑一遍的分析结果的人。
2. 传递函数建模:系数顺序、零极点增益与离散化的三个决策点
动手画图之前,先把对象建对。这一步看着简单,实际上后面所有图的形状都由它决定,模型的分子分母一旦错位,调参数调到天亮也调不回来。常见做法是先把物理环节写成微分方程或已知的传递函数表达式,再逐项翻译成 MATLAB 的向量形式,翻译完不要急着画图,先让脚本把G打印出来,人眼对一遍。
2.1 从微分方程到 tf 对象:降幂排列是最容易翻车的地方
控制系统里最典型的二阶环节可以写成 $\frac{\omega_n^2}{s^2 + 2\zeta\omega_n s + \omega_n^2}$,翻译成代码时,分子向量是最高次项系数在前,分母同理。这条规则本身不复杂,但一旦中间缺项,比如 $s^2 + 1$ 没有 $s$ 的一次项,就必须补 0,漏掉这个 0 是最常见的错误来源。
% 二阶对象: wn = 1 rad/s, zeta = 0.3 wn = 1; zeta = 0.3; num = [wn^2]; % 分子: 只有一个常数项 den = [1, 2*zeta*wn, wn^2]; % 分母: s^2 系数、s 系数、常数项,从高次到低次 G = tf(num, den); % 建完立刻自检三件事 G % 打印,人眼核对系数 G.Variable % 确认变量名是 's' 而不是 'z' pole(G) % 极点实部应为负,系统稳定这几行的逻辑很直白:tf接受两个行向量,分别对应分子和分母多项式按降幂排列的系数,返回一个传递函数对象。参数上唯一需要注意的是G.Variable,默认是's',如果在同一个脚本里做过离散化,后面又复用变量名,很容易拿到一个'z'变量的对象去画连续域 Bode 图,得到的频率轴完全不是你要的东西。
建完之后建议再做一次零极点对消检查:
G_min = minreal(G, 1e-6); % 容差 1e-6,消掉数值上重合的零极点 size(G_min) % 返回 [输出数, 输入数],顺便确认没有变成 0 阶minreal处理的是分子分母存在近似公因子的情况,典型场景是串联了一个超前校正环节又串了一个滞后环节,理论上零极点抵消,但浮点运算留下了 1e-15 量级的残差。不消对消,Bode 图上会出现一段莫名其妙的相位回升,看着像系统要自激,实际上是数值噪声。容差参数不要给太大,1e-6 到 1e-8 之间比较稳妥,给到 1e-3 有可能把真正靠得很近的零极点也一并抹掉,模型就失真了。
2.2 zpk 与 ss 互转:增益的定义在不同模型里不一样
zpk形式的可读性比tf好得多,尤其在极点数量多、手动展开多项式容易算错的时候。零点、极点、增益三个参数一眼就能和设计指标挂钩:极点位置决定衰减快慢,零点位置影响相位超前滞后,增益决定稳态值。
% 零点在 -2,极点在 -1 和 -5,增益 10 Gz = zpk(-2, [-1, -5], 10); Gt = tf(Gz); % 转回多项式形式,便于和其他环节做 conv Gs = ss(Gz); % 转状态空间,便于求初始条件响应或做能控性分析 % 检查转换前后直流增益是否一致 dcgain(Gz) dcgain(Gt)逻辑说明:zpk(z, p, k)里k是零极点形式下的增益,它等于传递函数在 $s \to \infty$ 时的首项系数,和tf里num(1)并不是一回事。举个具体例子,zpk([], [-1], 2)展开后是 $\frac{2}{s+1}$,num是[2],两者恰好相同;但如果分子分母阶数相同,比如zpk(-3, -1, 2),展开是 $\frac{2(s+3)}{s+1} = \frac{2s+6}{s+1}$,num变成[2, 6],此时k=2对应的是num(1)=2,仍然一致;真正容易出错的是用zpk的k去反推dcgain,dcgain = k * prod(-z) / prod(-p),手算漏一个负号就全错。所以转换后务必用dcgain对一遍。
状态空间形式适合做更深一层的分析。ss(Gz)拿到的 A、B、C、D 矩阵可以直接喂给lyap求李雅普诺夫方程,也可以用来判断能控能观。但要注意,ss转tf时如果系统存在零极点对消,tf(ss(G))有时会还回一个阶数更高的模型,因为状态空间实现里保留了不可观或不可控的模态,这时候再补一次minreal才干净。
2.3 延迟与离散化:InputDelay 和 c2d 必须成对考虑
纯延迟在连续域里是 $e^{-\tau s}$,MATLAB 的tf对象用InputDelay属性承载它,不影响分子分母系数。离散化时延迟会折算成整数个采样周期的移位,如果延迟不是采样周期的整数倍,c2d会做近似处理,这一点在图上体现得很明显。
% 连续对象带 0.2 s 纯延迟 G = tf(1, [1, 1], 'InputDelay', 0.2); % 采样周期 0.05 s,零阶保持器离散化 Ts = 0.05; Gd = c2d(G, Ts, 'zoh'); Gd.InputDelay % 应显示为 4 个采样周期的整数延迟逻辑与参数:c2d的第三个参数决定离散化方法。'zoh'假设输入在采样间隔内保持不变,对应实际系统中的零阶保持器,是控制系统建模的默认选择;'tustin'用双线性变换,频域畸变小于'zoh',但会引入频率预畸变问题,画 Bode 图对比时高频段会出现明显偏差;'matched'只适用于单输入单输出且极点简单的场合。采样周期Ts的选取经验是取系统带宽对应周期的十分之一左右,或者取闭环上升时间的二十分之一,Ts给大了,离散模型的相位滞后会被严重低估,画出来的 Bode 图比实际乐观得多,这就是典型的“仿真通过、上机翻车”。
离散化之后画 Bode 图,频率轴的上限自动停在奈奎斯特频率 $\pi/T_s$,这一点不需要手动设,但要知道为什么图只画到那里——再往右没有物理意义,混叠已经把信息毁掉了。如果拿到的图在 $\pi/T_s$ 附近剧烈抖动,先查Ts是不是给得太大,而不是去怀疑模型本身。
3. 三类图的参数配法:Bode、Nyquist 与阶跃响应怎么调到能交付
模型建对之后,画图这一步的真正工作量在参数配置上。默认参数画出来的图能看,但拿去做报告或者做对比往往不够:频率范围可能只覆盖了无关的低频段,单位制可能和同行用的不一致,裕度标注可能挡住了曲线本身。这一章按三类图分别说清楚该调哪几个参数。
3.1 Bode 图:频率范围、单位制与裕度标注
bode(G)一行就能出图,但交付级别的图至少要把频率范围卡死。默认范围由系统极点自动推算,碰上刚性系统(极点跨越好几个数量级),图会拉得很长,关键的中频段被压成一条竖线。
opts = bodeoptions; opts.FreqUnits = 'Hz'; % 频率单位改为 Hz opts.MagUnits = 'dB'; % 幅值单位 dB,默认就是 dB opts.Grid = 'on'; % 打开网格,便于读裕度 opts.XLim = {[0.1, 1000]}; % 频率范围锁定在 0.1 到 1000 Hz opts.Title.String = '开环频率特性'; bode(G, opts); grid on;逻辑说明:bodeoptions返回一个配置对象,所有属性改完之后作为第二个参数传给bode,这样画出来的图直接可用于导出。XLim是个元胞数组,因为 Bode 图有多条子轴需要统一设置,写成{[0.1, 1000]}而不是[0.1, 1000],这一点新手经常写错然后报错说维度不匹配。
裕度读取用margin,它返回四个量,单位容易被误解:
[Gm, Pm, Wcg, Wcp] = margin(G); Gm_dB = 20*log10(Gm); % Gm 是绝对增益,转 dB 才能和图上读数对上 fprintf('幅值裕度 %.2f dB @ %.3f rad/s\n', Gm_dB, Wcg); fprintf('相位裕度 %.2f deg @ %.3f rad/s\n', Pm, Wcp);参数含义:Gm是幅值裕度的绝对值,物理意义是增益还能再放大多少倍系统才到临界稳定,工程上习惯用分贝表示,所以必须手动20*log10;Pm单位是度,直接可用;Wcg是相位穿越频率,即相位等于 -180° 处的频率;Wcp是幅值穿越频率,即幅值等于 0 dB 处的频率,闭环带宽通常就在这附近。四个量里Gm最容易看错,直接把绝对增益当 dB 写进报告,说“幅值裕度 5 dB”,实际是 5 倍即 14 dB,这种错误在评审现场很扎眼。
如果需要把裕度直接标在图上,margin(G)不带输出参数调用即可,它会自动在 Bode 图上打竖线和标注。但要注意,带输出参数和不带输出参数是两种行为,写了[Gm,Pm] = margin(G)之后图上什么都不会画,这是 MATLAB 一贯的“有输出就不画图”约定,不是 bug。
3.2 Nyquist 图:无穷远点、缩放与围线方向
Nyquist 图的默认自动缩放对初学者不友好:系统在原点有极点时,曲线会有一段半径极大的圆弧表示绕无穷远,自动缩放为了把这段弧塞进画布,会把真正有信息量的低频段压成一个点。
opts = nyquistoptions; opts.XLimMode = 'manual'; opts.YLimMode = 'manual'; opts.XLim = [-3, 3]; opts.YLim = [-3, 3]; opts.Grid = 'on'; nyquist(G, opts);逻辑说明:把XLimMode和YLimMode设为'manual'之后,XLim/YLim才生效,否则无论怎么设都会被自动缩放覆盖。范围的选择原则是让临界点 $(-1, 0)$ 落在画布中央偏左,同时留出足够的余量看清曲线相对临界点的位置关系。如果曲线在选定范围内看不到全貌,说明系统增益偏大或者存在积分环节,先确认这是真实特性再考虑换范围,不要为了图好看硬缩。
判断稳定性看的是曲线包围 $(-1,0)$ 点的圈数和方向,顺时针为负、逆时针为正。手动数圈数容易错,实测中更稳的做法是让 MATLAB 算:
% 闭环极点实部全为负即稳定,比数圈数可靠 T = feedback(G, 1); max(real(pole(T)))这一行的意思是:把开环G单位负反馈闭合,取闭环所有极点实部的最大值,小于 0 即稳定。对延迟系统,pole会给出无穷多个根,此时换用isstable(T)或者step观察是否收敛更合适。Nyquist 图更大的价值在于看相对稳定性,即曲线离 $(-1,0)$ 点最近的距离,这个距离和稳定裕度是同一件事的两种表达。
3.3 阶跃响应与 stepinfo:九个字段各自代表什么
时域指标是最好交付的一类结果,因为看的人不需要频域背景。step(G)画曲线,stepinfo(G)给指标,配合使用。
S = stepinfo(G); disp(S) % 非标准阈值需要显式指定 S2 = stepinfo(G, 'SettlingTimeThreshold', 0.02, ... 'RiseTimeLimits', [0.1, 0.9]);stepinfo返回结构体,字段含义分别是:RiseTime上升时间,默认从稳态值的 10% 到 90%;SettlingTime调节时间,默认进入并保持在稳态值 2% 误差带内的时间;SettlingMin和SettlingMax是调节时间之后的最小值和最大值;Overshoot超调量,单位是百分比;Undershoot反向超调,出现在稳态值为正但响应先往负方向走的系统里;Peak峰值;PeakTime峰值时间;SteadyStateValue稳态值。
两个参数需要按场景改。SettlingTimeThreshold默认 0.02,某些行业习惯用 5% 误差带,那就传 0.05;RiseTimeLimits默认[0.1, 0.9],有些教材用 0% 到 100%,对过阻尼系统差别不大,对欠阻尼系统差别能到一倍。交付时把用的阈值写在图注里,比什么解释都管用。
还有一类情况是stepinfo直接报警:“The step response has not settled within the simulation time”,意思是仿真时长内没进入误差带。原因多半是系统接近临界稳定或者有积分环节,step的默认时长不够。解决方式是显式指定时间向量:
t = 0:0.001:50; % 步长 1 ms,总时长 50 s [y, t] = step(G, t); S = stepinfo(y, t, 1); % 第三个参数是稳态值,积分环节必须手动给对含积分环节的对象,stepinfo无法自动推断稳态值,必须用三参数形式stepinfo(y, t, yfinal)手动指定,否则算出来的超调量是错的。
4. Simulink 基础对接:从 Transfer Fcn 模块到 linmod 拉回传递函数
脚本和框图是两条并行的建模路径,实际上大部分项目要求两边对得上:Simulink 里搭一个用于仿真的模型,同时要给出对应的传递函数图用于分析。这一章处理的就是这条对接链路,也是 Simulink 基础里最容易出问题的一段。
4.1 Transfer Fcn 模块的三个填参位
在 Simulink 基础库里搜索 Transfer Fcn,拿到的模块参数只有两个输入框:Numerator coefficients 和 Denominator coefficients。这里填的同样是降幂系数向量,写法和tf完全一致,可以填数值,也可以填工作区里的变量名。
% 在模型运行前,工作区里必须先有这些变量 num = [1]; den = [1, 0.6, 1]; Ts = 0.01;三个填参位分别对应:分子、分母、采样时间。前两个填向量,第三个填标量Ts表示离散,填0表示连续,填-1表示继承上游信号的采样时间。这个-1是个隐蔽的坑,模块处在多速率系统中间时,继承到的采样时间可能和你想的完全不同,波形对不上先查这里。
模块参数里填变量名而不是数值,好处是改参数只需要改脚本,模型不用动。代价是模型打开时工作区没有这些变量会直接报 undefined,交付给别人的时候必须连同.m初始化脚本一起给,单独发一个.slx文件,对方打开就是一片红色。这一点在协作场景里是硬约束,血泪经验。
模块自带的初始状态Initial states也要留意。默认是 0,做阶跃响应没问题;做的是抗扰或者初值响应,这里填 0 会让结果和理论解对不上,看起来像是模型有 bug。
4.2 linmod 把框图拉回 tf:A/B/C/D 到 ss 到 tf
框图上搭完,想把模型转成传递函数做频域分析,用linmod。它在工作点附近做数值线性化,返回状态空间矩阵。
% 模型名不带扩展名,模型必须在当前路径或已打开 [A, B, C, D] = linmod('my_control_model'); G_lin = ss(A, B, C, D); G_tf = tf(G_lin); bode(G_tf);逻辑说明:linmod在模型的默认工作点(通常是 t=0 时刻的状态)做扰动,用数值差分求雅可比矩阵,所以它要求模型能正常初始化并跑通至少一个时间步。拿到的 A/B/C/D 是状态空间形式,转ss再转tf,就回到了熟悉的传递函数。参数上,linmod支持额外的扰动幅度参数,默认值对线性模块完全够用,对含饱和、死区的非线性模块需要调小,否则差分近似误差会把高频特性带歪。
两个限制要清楚。第一,linmod对含代数环、触发子系统、Stateflow 的模型基本无能为力,会直接报错或者返回空矩阵;第二,它做的是单点线性化,大信号工况下模型偏离工作点很远时,线性化结果和实际响应差距会很大,这不是 bug 是方法本身的边界。新版本更推荐linearize配合 Simulink Control Design,但如果环境里没有这个工具箱,linmod依然是最省事的路径。
转出来的G_tf阶数往往比手写的高,因为状态空间实现里包含了所有状态。做频域分析前先minreal一次,把不可观不可控的模态剔掉:
G_tf = minreal(tf(ss(A, B, C, D))); order = size(G_tf); % 确认阶数符合物理预期4.3 求解器与步长:波形毛刺的来源
同样的模型,换求解器波形就不一样,这是新手最困惑的地方。Simulink 默认变步长ode45,对刚性系统会自动缩步长,仿真时间变长但结果可靠;切成定步长ode4且步长给到 0.1,高频模态直接被跨过去,画出来的阶跃响应会出现不该有的平顶和跳变。
% 在模型配置参数里设置,或用 set_param 脚本化 set_param('my_control_model', 'Solver', 'ode4'); set_param('my_control_model', 'FixedStep', '0.001'); set_param('my_control_model', 'StopTime', '10');逻辑说明:ode4是四阶龙格库塔定步长求解器,适合需要和实际控制器采样周期对齐的场合;FixedStep应该取系统最快时间常数的十分之一以下,工程上常用 1 ms 或更小。StopTime要覆盖到响应完全进入稳态,设短了stepinfo那边的“未进入误差带”警告就会一路跟过来。
定步长还有个副作用:步长越小,仿真越慢,一个含开关器件的模型把步长从 1e-3 降到 1e-6,跑一次可能要几分钟。我的习惯是先变步长跑通逻辑,确认波形形状对了,再切定步长做最终交付,两者结果对不上时优先信变步长,因为它的自适应策略不会漏掉快速变化段。
5. 避坑与排查:五个让曲线跑偏的实操记录
这一章记录的是实际调试中反复出现的五类问题,现象、原因、解决都写清楚,遇到类似的先照着对一遍,比从头查快得多。
5.1 建模阶段的三个坑
现象一:Bode 图相位从 +90° 起步,幅值斜率是 +20 dB/dec。原因:分子的阶数写成了和分母相同甚至更高,比如把 $\frac{1}{s+1}$ 写成num = [1, 0]、den = [1, 1],实际上分子多了一个 $s$,等效于一个微分环节。解决:打印G,确认分子向量的长度不大于分母,且最后一个非零元素位置符合预期的相对阶。用size(G)看输出输入数不够,要用tfdata(G, 'v')取系数逐个核对。
现象二:阶跃响应稳态值不等于 1,而是 10 或者 0.1。原因:zpk的增益k和tf的num(1)概念混用,或者从ss转tf时增益归一化被破坏。解决:用dcgain(G)手动算一遍理论值,和step的稳态值对比。dcgain对含积分环节的系统返回Inf,这是正常的,此时改用stepinfo(y, t, yfinal)手动指定稳态值。
现象三:离散化之后 Bode 图在低频段和连续模型完全重合,高频段突然断掉。原因:这不是 bug,是c2d之后频率轴自动截断在奈奎斯特频率。真正的问题是采样周期给太大,$\pi/T_s$ 已经落进了系统带宽以内。解决:把Ts减半再画一次,看截断点是否移到了带宽之外;判断标准是开环幅值穿越频率的 5 到 10 倍以上。
5.2 绘图与仿真阶段的三个坑
现象四:margin(G)有返回值但图上没有标注。原因:带输出参数调用时margin只算不画,这是 MATLAB 图形函数的通用约定。解决:改成不带输出的margin(G),或者手动算出Gm/Pm后用bode的opts加annotation自己标;需要两种都要时,调用两次,先画图再取数。
现象五:Simulink 模型跑出来的阶跃响应和脚本step(G)差一个数量级。原因:Transfer Fcn 模块里的系数向量和工作区变量不同步,模块参数被手动改过之后没有回写脚本,或者初始状态不是 0。解决:在模型里双击模块,把系数和脚本里的num/den逐项对照;再看Initial states。检查完之后用linmod拉回来的G_tf和脚本里的G做dcgain对比,两者一致才说明模型和脚本是同一套东西。
现象六:linmod报错说模型无法初始化,或者返回的 A 矩阵全是 NaN。原因:模型里有未定义的工作区变量、代数环、或者某个模块的输出是 Inf/NaN。解决:先不加任何线性化,直接sim跑一遍看能不能跑通,跑不通就是模型本身的问题;跑通了再查模型里有没有触发子系统、Stateflow 这类linmod不支持的结构,有的话改用linearize,或者在工作点附近手动搭建线性近似模型。
6. 进阶验证:用幅值裕度和相位裕度反推超调量
图都画出来之后,最后一个动作是做交叉验证:频域算出来的裕度和时域画出来的超调量,理论上应该能对上。对二阶系统,相位裕度和阻尼比之间有经典的经验关系 $\zeta \approx \frac{Pm}{100}$($Pm$ 单位为度,适用 20° 到 70° 区间),再代入二阶系统的超调量公式 $M_p = e^{-\pi\zeta/\sqrt{1-\zeta^2}}$,就可以在不开仿真、不画阶跃响应的前提下估出一个超调量。这个估算值拿去和stepinfo的结果对照,差得离谱就说明模型里藏着额外的高阶模态或者非线性环节。
[Gm, Pm, Wcg, Wcp] = margin(G); % 二阶近似:相位裕度反推阻尼比,再反推超调量 zeta_est = Pm / 100; Mp_est = exp(-pi * zeta_est / sqrt(1 - zeta_est^2)) * 100; % 时域实测 S = stepinfo(G); fprintf('相位裕度近似超调: %.1f %%\n', Mp_est); fprintf('仿真实际超调: %.1f %%\n', S.Overshoot);这段代码里,Pm直接来自margin,zeta_est用的是线性经验式而不是精确解,所以估算值本身就有几个百分点的误差,对照时不要把差距当成模型错误。真正需要警惕的是两种情况:一是Pm大于 70°,经验式完全失效,此时系统接近过阻尼,实际超调可能接近 0,而估算值还在两位数;二是实际超调远大于估算值,往往是模型里存在未建模的延迟或者高频谐振,这类问题在 Bode 图上表现为相位曲线比预期掉得更快,或者幅值曲线在某处有一个凸起。
验证做完之后,把三张图、两组裕度、一组时域指标写成一份固定的检查脚本,每次改完参数跑一遍,比人眼对图靠谱。我的习惯是把这个脚本存成check_response.m,放在项目根目录,任何一次提交之前强制走一遍:先dcgain对稳态,再margin对裕度,最后stepinfo对时域,三项全过才算这份传递函数图可以交付。有一次改了一个校正环节的参数,step看着挺正常,margin一跑发现幅值裕度从 12 dB 掉到了 3 dB,只是阶跃响应在有限时长内没暴露出来,从那以后这套检查流程就没再省过。希望帮到你。
本文还有配套的精品资源,点击获取