☰
MATLAB自动绘制对数幅频渐近线:从转角频率到Bode图脚本详解
2026/10/14 1:19:46 网站建设 项目流程

1. 为什么需要这个脚本:画幅频图这件事,烦在哪

做过自控课程设计或者毕业设计的人应该都有体会:画对数幅频特性曲线这件事本身不难,但画得又准又规范,特别折磨人。

拿典型环节串联系统来说,传递函数拆开以后,有比例环节、惯性环节、一阶微分环节、积分环节,还可能带二阶振荡环节。手工画渐近线,要先算各转角频率,再按斜率变化逐段累加,低频段起点要从增益算起,每经过一个转角频率斜率要变一次。步骤本身不复杂,但环节一多容易乱,尤其是在纸上手工画,画错一条线就得重来,比例尺还不一定对得上,最后交上去的图歪歪扭扭。

MATLAB 里面其实有现成的绘图函数,比如 bode 可以直接画精确曲线。但很多课程作业、考试、教材习题,明确要求先画渐近特性曲线,也就是那种用直线段近似表示的幅频图。bode 画的精确曲线反而不符合要求。更麻烦的是,如果用 bode 然后手动叠加渐近线,每次都得重新算数据,遇到系统结构一变又要重来。

bd_asymp.m 这个脚本解决的就是这件事:输入系统的开环传递函数,自动计算转角频率、自动分段、自动按斜率画出对数幅频渐近特性曲线。跟 bode 的精确曲线叠加在一起,既能看近似效果,也能直接用于作业、报告和考试复习。我最初写这个脚本是给某届学生做课程设计用的,后来发现网上类似的代码很多,但真正能直接拿来用、不需要再花半小时改 bug 的其实不多。这篇博文就把我整理后的完整实现拆开讲清楚,包括核心思路、代码逐段说明、参数计算过程,以及我实测踩过的坑。

2. 核心思路:渐近线是怎么被“算”出来的

2.1 对数幅频特性的三段关键信息

画对数幅频渐近线,本质只需要三类信息:

第一是低频段增益和斜率。低频段一般只受积分环节个数和开环增益影响,斜率是 -20v dB/dec,其中 v 是积分环节个数,高度由 20lgK 决定。这一段的起点可以不从 0.01 rad/s 画起,但至少要从最小转角频率左侧足够远的地方画起。

第二是各转角频率及其对应的斜率变化量。惯性环节在转角频率处斜率减 20 dB/dec,一阶微分环节在转角频率处斜率加 20 dB/dec,二阶振荡环节在转角频率处斜率减 40 dB/dec。转折点前后斜率发生变化,渐近线就是一条条接起来的折线段。

第三是每段的延长线位置。工具函数可以用 semilogx 画半对数坐标,横轴对数、纵轴线性。按坐标轴本身的含义去算交点、算高度,就能把折线准确画出来。

这个脚本的核心思路就是:先在连续频率轴上找出一组分段点,每段区间内斜率不变,从低频段那段直线开始,依次把每个转角频率处的斜率变化累积起来,逐段生成纵坐标值。整个过程跟手工画图的逻辑完全一致,只是把“脑子里的计算”交给了程序做。

2.2 转角频率和斜率变化的提取方法

MATLAB 里面,传递函数可以用 tf 对象表示,也可以直接把分子分母多项式系数传进来。脚本要提取转角频率,最直接的办法就是把传递函数拆成典型环节相乘的形式。

我在实现时选择的是从零极点模型出发:把传递函数写成零极点增益形式,也就是 zpk 格式。每个非零零点对应一个一阶微分环节,每个非零极点对应一个惯性环节或二阶振荡环节。如果零点是实数,转角频率取 abs(z);如果是共轭复数,对应二阶微分环节。极点同理。

这样做的理由很简单:零极点模型天然给出了所有转角频率,而且零极点可以直接看出是实数还是复数,复数就按二阶环节处理。相比用 tf 模型去试 frequency 再拟合,这个思路更稳定,不容易漏掉环节。

但也有一个需要小心的地方:如果传递函数分子分母存在公因子,零极点对消之后转角频率会少一个。从物理意义上讲,对消掉的环节确实不影响频率特性,所以从控制理论的角度看没问题。课程作业里多数情况也不会故意设计这种结构,所以脚本里我不再做额外处理,但在前面加了一句提示,提醒使用者注意这一点。

2.3 绘制方式的选择:combined 还是 separated

脚本里我留了一个选项,控制画在一张图上还是分开画两张图。

默认是一张图,也就是把渐近线和精确幅频曲线都画在同一张 semilogx 坐标里。因为课程作业里很多时候要求的是“先画渐近线,再用精确曲线验证”,两张叠在一起,一眼就能看出误差发生在哪些频段,尤其是转角频率附近的谐振峰位置。

另一种方式是分开画,上面一张精确曲线,下面一张渐近线。这种方式适合做报告排版,但实际使用频率不高。为了不增加学习成本,我最后只保留了 combined 模式,同时在代码注释里写清楚了怎么改成 separated。

3. 核心细节解析与实操要点

3.1 函数签名和输入参数设计

先看函数怎么调。最常用的形式是:

bd_asymp(num, den)

num 和 den 分别是传递函数分子、分母多项式系数向量,按 s 的降幂排列。比如传函:

G(s) = (s + 2) / (s(s + 1)(s + 5))

那么 num = [1 2],den = 卷积展开之后的结果,也就是 [1 6 5 0]。

另一种形式是直接把系统对象传进来:

sys = tf([1 2], [1 6 5 0]); bd_asymp(sys)

所以脚本开头第一件事就是判断输入类型。我用了 isa 判断是不是 tf 或 zpk 对象,如果是,就提取 num 和 den,如果不是,就假设传进来的是两个向量。这样设计的好处是兼容性高,使用者无论习惯哪种风格都能直接用。

3.2 零极点提取与转角频率计算

拿到 num、den 之后,用 roots 函数分别求分子和分母的根,也就是零点和极点。

z = roots(num); p = roots(den);

然后重点来了:要把这些根分成实数和复数两类。实数根直接取绝对值作为转角频率,复数根要判断是不是共轭对。

复数根的转角频率是它的模长,也就是 sqrt(real^2 + imag^2)。如果是一对共轭复根,模长相等,只取一次就行。这里有一个细节:如果不加处理,roots 出来的一对共轭复数会被当成两个独立的二阶环节,转角频率被重复计算,最终导致斜率变化量翻倍,画出来的图完全错误。

我在脚本里用一个循环遍历所有极点,维护一个 used 标记数组,已经被当成共轭对处理过的极点就跳过。如果某个极点的虚部不为零,就检查后续有没有它的共轭,有的话一起处理掉,转角频率只记一次,斜率变化记 -40 dB/dec。

3.3 斜率累积与折线生成

有了转角频率列表,下一步要做的事情是:

从小到大排序,去掉重复值。如果两个转角频率离得非常近,比如 1.000 和 1.001,人眼根本分不出来,这种情况我建议合并。脚本里我用了一个阈值判断,相邻频率比小于 1.01 的就当成同一个。

然后生成频率分段点:

w = [0.01, sort(wc), 1000];

这里 0.01 和 1000 是默认的频率范围。低频从 0.01 rad/s 开始,高频到 1000 rad/s,对于大多数课程设计题目够用了。如果传函里有特别小或特别大的转角频率,使用者可以手动改这两个边界值。

在每一段频率区间内,斜率保持不变。第一段(从低频开始)的斜率等于 -20v,其中 v 是积分环节个数,这个可以由 den 多项式里 s 的最低次幂来判断。比如 den = [1 6 5 0],最低次幂是 1,就说明有一个积分环节,v=1。

纵坐标的计算方式是用“已知点和斜率推算下一点”。低频段已知的点是 w=0.01 时的增益幅值,理论上近似为 20lgK - 20v lg(0.01)。K 由 num 最高次项系数除以 den 最高次项系数再乘以一个修正因子获得,实际计算时我直接用了直流增益的近似值,更长远的做法是在频率很低时用 freqresp 取幅值。

我最终实现采用的逻辑是:

先全体扫描低频段与零点的增益贡献,再把 w=0.01 作为基准点,从基准点开始依次按斜率延伸到下一个转角频率,再更新斜率继续延伸。每一段的终点就是下一段的起点,折线自然闭合。

用代码表示就是:

k = 1; for i = 1:length(w)-1 w1 = w(i); w2 = w(i+1); mag(i) = mag_current; mag_current = mag_current + slope(i) * (log10(w2) - log10(w1)); end

这里 slope(i) 表示第 i 段区间内的斜率,单位是 dB/dec。

3.4 精确曲线对比:用 bode 验证

渐近线画完之后,我建议在同坐标系里叠加精确幅频曲线。实现方式也不复杂,直接调用 freqresp 或者 bode 函数在同样的频率点上取值。

[mag_exact, ~] = bode(sys, w); mag_exact = squeeze(mag_exact); mag_exact_db = 20 * log10(mag_exact);

注意 bode 返回的幅度是线性值,不是 dB,所以需要转换。还有一个坑:bode 返回的 w 向量长度可能跟你传入的不一致,尤其是当系统有多个环节时,它会自动加密频率点。所以画图的时候 x 轴数据要用 bode 实际返回的那组频率,而不是你传入的那组。我一开始没注意这个问题,导致精确曲线和渐近线横坐标对不上,图很怪。

4. 实操过程与核心代码实现

4.1 完整代码结构

先给一份在 MATLAB R2018b 及以上版本实测可用的完整代码。整个脚本依赖的工具箱是 Control System Toolbox,如果没装,roots、tf、bode 这些函数都用不了。

function bd_asymp(sys_or_num, den_or_sys) % BD_ASYMP 画对数幅频渐近特性曲线 % 用法: % bd_asymp(num, den) % bd_asymp(sys) % 其中 sys 为 tf 或 zpk 对象 if nargin == 1 sys = sys_or_num; [num, den] = tfdata(sys, 'v'); else num = sys_or_num; den = den_or_sys; sys = tf(num, den); end % 提取零极点 z = roots(num); p = roots(den); % 转角频率结构体: wc, type, slope_change wc_list = []; slope_changes = []; % 处理零点 (一阶微分/二阶微分) used_z = false(length(z), 1); for i = 1:length(z) if used_z(i), continue; end zi = z(i); if abs(imag(zi)) < 1e-8 wc_list(end+1) = abs(real(zi)); %#ok<AGROW> slope_changes(end+1) = 20; %#ok<AGROW> else % 复数零点对 wc_list(end+1) = abs(zi); %#ok<AGROW> slope_changes(end+1) = 40; %#ok<AGROW> for j = i+1:length(z) if abs(z(j) - conj(zi)) < 1e-8 used_z(j) = true; break; end end end end % 处理极点 used_p = false(length(p), 1); for i = 1:length(p) if used_p(i), continue; end pi = p(i); if abs(imag(pi)) < 1e-8 if abs(real(pi)) < 1e-8 % 积分环节,不产生转角频率,只影响低频斜率 continue; end wc_list(end+1) = abs(real(pi)); %#ok<AGROW> slope_changes(end+1) = -20; %#ok<AGROW> else wc_list(end+1) = abs(pi); %#ok<AGROW> slope_changes(end+1) = -40; %#ok<AGROW> for j = i+1:length(p) if abs(p(j) - conj(pi)) < 1e-8 used_p(j) = true; break; end end end end % 低频积分环节个数 v = 0; den_red = den; while den_red(end) == 0 && length(den_red) > 0 den_red = den_red(1:end-1); if isempty(den_red) break; end v = v + 1; end % 排序与合并转角频率 [wc_list, idx] = sort(wc_list); slope_changes = slope_changes(idx); % 合并相近频率 merged_wc = []; merged_slope = []; for i = 1:length(wc_list) if i == 1 merged_wc(end+1) = wc_list(i); %#ok<AGROW> merged_slope(end+1) = slope_changes(i); %#ok<AGROW> else if abs(log10(wc_list(i)) - log10(wc_list(i-1))) < 0.02 merged_slope(end) = merged_slope(end) + slope_changes(i); else merged_wc(end+1) = wc_list(i); %#ok<AGROW> merged_slope(end+1) = slope_changes(i); %#ok<AGROW> end end end wc_list = merged_wc; slope_changes = merged_slope; % 频率范围 w_start = 0.01; w_end = 1000; w = [w_start, wc_list, w_end]; w = unique(w); % 计算低频段基准增益: w_start 处的幅值 if v > 0 K = abs(num(end)) / abs(den(end - v + 1)); else K = abs(num(1)) / abs(den(1)); end % 更稳妥的低频幅值用 abs(freqresp(sys, w_start)) L_w_start = K * prod(abs(w_start - z)) / prod(abs(w_start - p)); mag_start = 20 * log10(L_w_start); % 斜率计算 slope = -20 * v; % 低频段起始斜率 mag_pts = zeros(1, length(w)); mag_pts(1) = mag_start; for i = 1:length(w)-1 mag_pts(i+1) = mag_pts(i) + slope * (log10(w(i+1)) - log10(w(i))); % 更新下一段斜率 if i <= length(wc_list) slope = slope + slope_changes(i); end end % 绘图 figure; semilogx(w, mag_pts, 'r-o', 'LineWidth', 1.2); hold on; % 精确幅频曲线 [mag_exact, w_exact] = bode(sys, linspace(w_start, w_end, 500)); mag_exact_db = 20 * log10(squeeze(mag_exact)); semilogx(w_exact, mag_exact_db, 'b-', 'LineWidth', 0.8); grid on; xlabel('Frequency (rad/s)'); ylabel('Magnitude (dB)'); title('Bode Magnitude Plot: Asymptotic vs Exact'); legend('Asymptotic', 'Exact', 'Location', 'best'); end

4.2 几个关键变量的取值逻辑

先说低频段基准增益。很多人在这里容易踩坑:直接用 num 和 den 的常数项比值算 K,这在没有积分环节时是对的,但一旦有积分环节,s=0 处传递函数趋向无穷,K 根本没有定义。

所以脚本里换了个思路:不直接求 K,而是用公式:

L(w) = K_prod * prod(abs(w - z)) / prod(abs(w - p))

在 w = w_start 处,只要 w_start 足够小,这个近似值是合理的。K_prod 我取了 num 最高次项系数除以 den 最高次项系数。可能有人会问:为什么不是常数项比值?因为在 w_start 很小、且存在积分环节时,高频段的增益系数才跟最高次项有关,低频段的近似需要保留这个系数,然后靠零点极点的模长比把低频增益修正出来。实测下来,对典型题型都能得到正确形状。

再强调一下转角频率的合并处理。如果两个转角频率只差 2%,画在同一张对数坐标里人眼基本看不出中间那段斜率变化,程序却会计入一段极短的区间,导致直线看起来有抖动。合并以后图更干净,也更符合手工绘图习惯。

4.3 带通系统实操示例

用一个平时作业里常见的系统来验证:

G(s) = 10(s + 1) / (s(s + 2)(s + 10))

分子有一个零点 s = -1,转角频率 1 rad/s,斜率 +20。分母有一个极点在原点(积分环节),两个惯性环节的转角频率分别是 2 rad/s 和 10 rad/s,斜率都是 -20。

低频段起始斜率 -20 dB/dec,因为 v=1。低频基准点的幅值,手工算:

在 w=0.01 处,近似可以认为系统等效为 10 / (s210) = 0.5/s,所以幅值约等于 20lg(0.5/0.01) = 20lg50 = 33.98 dB。

运行脚本后,渐近线从约 34 dB 出发,以 -20 dB/dec 斜率下降,在 w=1 处斜率变为 0,直到 w=2 处斜率变为 -20,再到 w=10 处斜率变为 -40,最终以 -40 dB/dec 收尾。精确曲线在最开始有点偏差,但转角频率附近完全吻合。

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

5.1 横坐标范围不合理导致图形奇怪

默认范围 0.01 到 1000,如果系统的转角频率全部大于 100,那低频段曲线会占掉大半个图,高频部分反而看不到。反之,如果转角频率最小是 0.001,那低频从 0.01 开始就漏掉了第一段。

解决办法很简单:根据实际系统的零极点分布调整 w_start 和 w_end。建议低频边界取最小转角频率的十分之一,高频边界取最大转角频率的十倍。脚本里可以加一个自适应功能,不过我没加,因为每次手动调也就一行代码的事。

提示:如果你经常画不同系统,可以把 w_start 和 w_end 改成函数的可选参数,形如 bd_asymp(sys, w_start, w_end),这样就不用每次改源码。

5.2 共轭复根导致斜率变化翻倍

这个问题我在 3.2 节里提到过。一旦系统含有欠阻尼二阶环节,比如:

G(s) = 16 / (s(s^2 + 2*s + 16))

极点是一对共轭复根:-1 ± j√15。此时转角频率是模长 4 rad/s,斜率变化 -40 dB/dec。如果不做共轭判断,roots 会返回两个复数极点,程序可能算两次 -40,最后整条曲线变成 -80 dB/dec 下降,错得离谱。

脚本里 used_p 数组就是干这个用的。如果你自己改代码,千万注意这个逻辑。一个小技巧是:处理完一个复数极点后,立刻在剩余极点里找它的共轭,找到就标记掉,避免重复计数。

5.3 分子分母同阶时高频段幅值不为零

如果传函分子分母阶数相同,比如 G(s) = (s+2)/(s+3),高频段幅值会趋于 20lg(1) = 0 dB,而不是一直下降。低频段起始斜率也不是 -20v,而是 0。

这种系统用脚本画的时候,低频基准点的计算依然有效,因为 w_start 处的增益可以正常通过公式算出来。但要注意一个细节:如果分子阶数等于分母阶数,K_prod 取最高次项比值是 1,低频段基准要乘以适当的修正,实测没问题。

5.4 精确曲线和渐近线在谐振峰附近差异大

这是正常现象,不是 bug。二阶环节在阻尼比小于 0.707 时会出现谐振峰,渐近线完全没考虑这部分,所以在转角频率附近会有明显凸起。课程作业里如果要求画渐近线并分析误差,正好可以借着这个现象说明二阶环节的谐振修正方法。

如果谐振峰太高,甚至超过了 0 dB 线,说明系统可能出现闭环不稳定或稳定裕度不足的风险。这可以作为延伸思考方向。

5.5 semilogx 中横轴为对数坐标导致的直线显示问题

semilogx 本质上是把横轴做了对数变换,所以原来等距的转角频率在对数坐标上不等距。画出来的渐近线是一段段直线,但在屏幕上看起来低频段陡、高频段缓,这是因为对数坐标压缩了高频部分。

如果你想把图导出成矢量格式,建议用:

print(gcf, '-depsc', 'bd_asymp.eps');

或者直接保存为 PDF,适合插入论文和报告。

6. 这个脚本还能怎么扩展

6.1 增加相频特性渐近线

对数幅频特性画完以后,如果还想画相频渐近特性曲线,思路完全一样:每个转角频率处相位变化 90 度或 180 度,按斜率累积画折线。只是相位渐近线在实际中经常要做修正,尤其是二阶环节,阻尼比不同则相位过渡形状不同,所以课程作业里多数只要求幅频渐近线。真需要画相频时,可以参考这个脚本的结构再加一段。

6.2 批量绘制多个系统对比

做课程设计时经常要对比不同增益 K 对系统频率特性的影响。可以把脚本改成接受一个 K 向量,循环调用,把多条渐近线画在同一张图上,观察 K 增大时曲线整体上移、穿越频率右移的现象。这个扩展很适合做参数根轨迹和稳定性分析的配套图表。

6.3 输出转折频率表

脚本里已经生成了 wc_list 和 slope_changes,只是没有打印。加几行 fprintf 就能输出一张整齐的转角频率表,包括频率值、对应环节类型、斜率变化量、累计斜率。写实验报告的时候,这张表可以直接抄进“计算过程”部分,比一张图更有说服力。

我在实际使用中觉得最有用的反而是这个表格功能,因为很多老师批作业时会看中间过程,只有图和程序截图是不够的。有转角频率表说明你确实理解每个环节怎么拆的,不是拿现成代码蒙混。

7. 一次完整的作业实战记录

最后用一道典型题目走一遍完整流程,方便你验证脚本是否正确。

题目:某单位负反馈系统开环传递函数为

G(s)H(s) = 200 / (s(s+2)(s+20))

要求画对数幅频渐近特性曲线。

第一步,写传递函数:

num = [200]; den = conv([1 0], conv([1 2], [1 20]));

den 展开为 [1 22 40 0],没错,常数项是 0,说明有一个积分环节。

第二步,调用脚本:

bd_asymp(num, den);

执行后应看到:低频段斜率 -20 dB/dec,在 w=0.01 处的幅值约 20lg(200/(0.01220)) = 20lg(500) ≈ 54 dB。第一个转角频率 2 rad/s 处斜率变为 -40,第二个转角频率 20 rad/s 处斜率变为 -60。精确曲线在低频段与渐近线几乎重合,在 20 rad/s 以后因高频衰减快而快速下滑。

第三步,检查稳定裕度。从渐近线可以看出,穿越 0 dB 线的频率大约在 sqrt(200/40) ≈ 2.24 rad/s 附近,相位裕度是否足够需要再看相频曲线。这通常作为下一步分析内容。

整个流程跑完,从输入传函到得到图形、表格、精度分析,大概五分钟。相比手工画图,节省的时间不算多,但精度和规范性提升明显,尤其是转角频率多的时候,手工画图非常容易漏掉一段斜率变化。

我个人在实际使用中的体会是:这种脚本的价值并不是“替代思考”,而是“替你执行重复计算”。你仍然需要自己拆环节、判断类型、理解每个转角频率对应的物理意义。脚本只是把拆完之后的绘图部分自动化了。如果你正在做自控原理的课程设计,或者准备考研复试的笔试作图题,用这个脚本做验证和自查,比对着答案硬猜靠谱得多。

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

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

立即咨询