☰
MATLAB伯德图绘制与打印优化:从建模到导出的完整指南
2026/10/13 14:55:58 网站建设 项目流程

简介:面向自动控制原理学习与工程应用,这份工具包提供基于Matlab的伯德图坐标纸定制脚本,适合控制类专业学生、科研人员及工程师快速生成规范化幅频和相频特性图纸。压缩包共4个文件,含三个Matlab脚本文件与一份说明文档,包体仅5KB,轻量易用。目前已有1034人浏览学习。脚本围绕伯德图绘制函数封装,支持自定义频率范围、轴刻度、子图布局与导出样式,可生成1比1、2比1、2比2等比例伯德图坐标纸,省去手动调整坐标轴的繁琐步骤。配合说明文档,读者可快速掌握传递函数频率响应绘制方法,并将成果保存为PNG或PDF,用于论文插图、实验报告与工程分析,有效提升控制系统设计的效率与规范性。

1. 伯德图不是「画出来」就完事:bode() 默认图离打印要求差几步

自动控制原理这门课,伯德图是绕不开的硬通货。很多人在 MATLAB 里敲一句 bode(G) 就以为完事了,结果截图交上去,线宽细得像头发丝,相位曲线还在 -180° 附近反复横跳,根本没法看。

这份 Bode_PrintPaper 资源包解决的就是这个 gap:把「能运行的 Bode 图」变成「能打印的 Bode 图」。包里是成套的 MATLAB 绘图脚本,从传递函数建模、频率响应取数,到手写绘图模板、批量导出高分辨率图片,每个环节都给了可直接抄的参数。

适合做自控课程设计的学生,也适合要验证系统稳定裕度的工程师——只敲 bode(G) 然后截图的,这份资源是后悔药。

2. 建模与取数:bode() 背后的频率响应数据怎么拿

2.1 三种建模方式:tf、zpk、ss 的选型逻辑

要画伯德图,先得有系统模型。资源包里的示例系统同时给了三种建模方式,目的不是炫技,而是让你在不同的场景入口都能接上这套绘图流程。先看第一种。

% 方式一:有理分式传递函数,分子分母按 s 降幂排列 G1 = tf(10, [1 2 10]);

tf 的第一个参数是分子系数 [10],代表常数增益 10;第二个参数是分母系数 [1 2 10],对应 s^2 + 2s + 10。系数顺序必须是 s 次数从高到低,写反了系统就变成另一个东西,画出来的伯德图会和手算完全对不上。这个坑我在课程设计时踩过一次,当时怎么都对不上渐近线,最后发现是分母系数写反了。

% 方式二:零极点增益形式,直观体现系统的极点分布 G2 = zpk([], [-1+3j, -1-3j], 10);

zpk 的三个参数分别是零点、极点、增益。这个例子没有零点,极点在 -1±3j,是一对共轭复极点,自然频率约 3.16 rad/s,阻尼比约 0.316。用 zpk 建模的好处是能从极点位置直接预判谐振峰出现的频段,画完图可以拿这个预期去验证曲线形状。

% 方式三:状态空间模型,适合 MIMO 或从 Simulink 导出的系统 A = [0 1; -10 -2]; B = [0; 1]; C = [10 0]; D = 0; G3 = ss(A, B, C, D);

ss 建模适合状态方程已经摆在面前的情况,比如从 Simulink 线性化分析导出的模型,或者需要同时看多个输入输出通道的 MIMO 系统。三种方式最终画出来的伯德图完全一致,因为 MATLAB 内部都会先算频率响应再绘图。我刚拿到资源包时不太信,把 G1、G2、G3 用三种不同线型画在同一个 figure 里,三条曲线严丝合缝,这才确认 tf、zpk、ss 三种入口在频率响应计算上是完全等价的。选型一句话:手推的传递函数用 tf,零极点明确的用 zpk,状态方程现成的用 ss。

2.2 取数:mag、phase、w 的数据结构

资源包的主脚本几乎不用 bode(G) 直接画图,而是用带返回值的调用方式,把数据取出来自己排布。原因很简单:默认图的排版离打印要求太远,自绘才有控制权。

% 取出频率响应数据,w 是频率向量,mag 是线性幅值,phase 是相角 [mag, phase, w] = bode(G1); % 幅值转 dB,去掉单例维度 mag_db = 20 * log10(squeeze(mag)); phase_deg = squeeze(phase);

这里有个新手必踩的坑:SISO 系统下 mag 和 phase 返回的是 1×1×N 的三维数组。直接 plot(w, mag) 会得到一堆空线,因为 MATLAB 不知道你想画哪个维度。squeeze 把长度为 1 的维度压缩掉,得到 N×1 向量,之后才能和 w 对齐。另一个细节是 phase 返回的单位是度(deg),不是弧度,后面要做解包裹时必须先用 pi/180 换算,否则结果完全不对。

幅值为什么要转 dB?因为幅频特性跨越好几个数量级,线性坐标下曲线全挤在一起,看不出渐近线斜率。20*log10 是幅值转分贝的标准公式,也是手画伯德图纵轴的单位。

2.3 频率范围与点数:logspace 的参数怎么定

bode() 不指定频率时,会自动扫描一个足够宽的频带。但资源包里的脚本都显式指定频率范围,原因有两个:让曲线覆盖你关心的频段,让数据点分布可控。

% 频率范围 0.01 到 1000 rad/s,对数等间距 500 个点 w = logspace(-2, 3, 500); [mag, phase, ~] = bode(G1, w);

logspace 的 -2 和 3 分别代表 10^-2 到 10^3,即 0.01 到 1000 rad/s。低频从 0.01 起步,是为了让积分环节的低频渐近线有至少两个十倍频程的展示空间;高频到 1000,覆盖穿越频率附近的关键区域。500 个点对打印分辨率没有影响,但对相位曲线在谐振点附近的平滑度很关键——点太少,转折频率附近会出现折线感。

注意:logspace 的第三个参数是总点数,不是密度。相同频段跨度下,点数翻倍,相邻点的频率间隔才会减半。

如果发现相位曲线在谐振峰附近有明显尖角,先把点数加到 800 试试,这个玄学问题多半是点数不够引起的,而不是绘图模板的问题。

2.4 用三类典型环节验证曲线形状

画完图先别急着导出,用典型环节的预期形状自检一遍,能拦住大部分数据错误。任何传递函数都可以拆成增益、积分/微分环节、一阶惯性环节和二阶振荡环节的组合。

  • 纯增益 K:整条幅频曲线抬高 20*log10(K) dB,相位不变。
  • 积分环节 1/s:幅频斜率 -20 dB/dec,在 ω = 1 处过 0 dB。
  • 一阶惯性 1/(Ts+1):转折频率 ω = 1/T,之后斜率变为 -20 dB/dec,转折点处幅值比渐近线低 3 dB。
% 用一阶惯性环节验证取数流程是否正确 G_check = tf(1, [1 1]); % 转折频率 1 rad/s [mag_check, ~, ~] = bode(G_check, 1); mag_check_db = 20 * log10(mag_check); % 理论上应为 -3.01 dB,误差在数值计算精度内

这一段的自检逻辑:如果 mag_check_db 明显偏离 -3.01 dB,说明 squeeze 或 log10 的处理有问题,先修数据管线,别急着调图。

3. 手绘模板:线宽、字体、网格这些打印细节为什么不能省

3.1 默认 bode() 图的三个硬伤

拿 bode(G) 直接出的图去打印,通常有三个问题。第一,线宽默认只有 0.5 pt,喷墨打印出来像头发丝,论文里缩印之后直接看不清。第二,没有网格线,也没有单位标注,幅频纵轴只写 Magnitude,读者不知道是 dB 还是线性值。第三,标题、图例的字体和排版都是默认值,中文字体还经常乱码。

如果你用 bodeplot 而不是 bode,能直接设置 LineStyle、LineWidth 这些属性,免去手绘的麻烦。但 bodeplot 的输出仍是固定布局,相位解包裹、中文字体、批量导出这些需求它管不了,所以我更倾向于 semilogx 全手绘。这三个问题都不影响 bode() 的计算结果,但直接影响图能不能用,资源包的做法就是彻底绕开自动绘图。

3.2 幅频与相频分栏:绘图模板代码

两行子图是最常用的布局,上半幅频、下半相频,共用横轴。以下模板可以直接抄。

% 画布尺寸设为厘米单位,宽 16 cm、高 12 cm,适合双栏论文排版 figure('Units', 'centimeters', 'Position', [2 2 16 12]); % 上半:幅频特性 subplot(2, 1, 1); semilogx(w, mag_db, 'LineWidth', 1.2, 'Color', [0.10 0.40 0.80]); hold on; grid on; grid minor; ylabel('幅值 (dB)'); title('开环系统的伯德图'); set(gca, 'FontName', 'Times New Roman', 'FontSize', 10.5); % 下半:相频特性 subplot(2, 1, 2); semilogx(w, phase_deg, 'LineWidth', 1.2, 'Color', [0.80 0.20 0.20]); hold on; yline(-180, 'k--'); % -180° 参考线,判断稳定性用 grid on; grid minor; ylabel('相角 (deg)'); xlabel('频率 (rad/s)'); set(gca, 'FontName', 'Times New Roman', 'FontSize', 10.5);

参数说明逐个说。LineWidth 1.2 是打印的推荐值,低于 1.0 导出后容易发虚,超过 1.5 又显得粗糙。Color 用 RGB 三元组而不是简写 'b' 和 'r',因为简写颜色打印出来偏艳,三元组可以调成接近印刷色。grid minor 开启次要网格,在对数坐标下每个十倍频程内再细分,方便读渐近线斜率。yline(-180, 'k--') 是相频图上的稳定性参考线——判断系统是否稳定的关键就是从这条线读相位裕度。

FontName 设成 Times New Roman 是英文排版的安全选择,但注意它不支持中文。如果你的标签里有中文,要么改成英文,要么单独设中文字体,后面避坑章会细说。figure 的 Position 四项分别是屏幕起点 x、y 和宽、高,单位跟随 Units,这里设成厘米是为了和后续打印纸张尺寸对齐。

3.3 多曲线对比:线型与图例的约定

控制课设里最常见的需求是校正前后的对比。两条线叠在一张图上时,线型和颜色都要有区分度,还得考虑打印出来是黑白的。

% 叠加显示校正前后的幅频特性 h1 = semilogx(w, mag_db_before, 'b-', 'LineWidth', 1.2); hold on; h2 = semilogx(w, mag_db_after, 'r--', 'LineWidth', 1.2); legend([h1 h2], {'校正前', '校正后'}, 'Location', 'southwest');

实线和虚线搭配,比两条同线型不同颜色的方案更稳妥,黑白打印也能区分。legend 的 Location 选 southwest 或 northeast,避免盖住曲线穿越 0 dB 的位置。这里用句柄数组 [h1 h2] 而不是让 legend 自动抓取,是因为自动抓取经常把网格或参考线也算进去,导致图例顺序错乱,这在叠加了 yline 之后尤其明显。

3.4 坐标轴细节:对数刻度与刻度标签

semilogx 已经处理了对数横轴,但有两个细节容易翻车。第一,如果误用 plot(w, mag_db),横轴变成线性刻度,低频段全部挤在左边,整个图看起来像一条竖线。第二,X 轴刻度标签默认科学计数法,打印出来是 10^-1、10^0 这种形式,可读性还行;但如果想要 0.1、1、10 这种普通数字,需要手动指定。

% 手动指定横轴刻度:0.01, 0.1, 1, 10, 100, 1000 set(gca, 'XTick', [0.01 0.1 1 10 100 1000]); set(gca, 'XTickLabel', {'0.01', '0.1', '1', '10', '100', '1000'});

XTick 和 XTickLabel 必须一一对应,个数不一致会直接报错。这个设置通常在数据量大的图上用,数据点少时默认的刻度已经够清楚,设了反而显得啰嗦。

4. 批量出图与打印尺寸:exportgraphics 的高分辨率导出路径

4.1 三种导出方式怎么选

MATLAB 出图有三个主流选择,我按自己的使用经验排一下优先级。

方式语法分辨率适用场景
exportgraphicsexportgraphics(gcf, 'a.png', 'Resolution', 300)可控,最高 600R2020a 之后的推荐方式,自动裁剪白边
printprint(gcf, '-dpng', '-r300', 'a.png')可控老版本兼容性好,paper 参数相关的坑多
saveassaveas(gcf, 'a.png')固定 96 dpi只适合截图留念,不适合打印

saveas 固定低分辨率,拿去打印会糊。print 是老牌方案,但在输出尺寸上有很多 PaperPosition 相关参数要调,漏一个就翻车。exportgraphics 在 R2020a 之后可用,自动裁剪画布周围的白边,省去手动设置 PaperPosition 的麻烦,是我现在的主力导出方式。

% 推荐:exportgraphics,300 dpi 满足打印最低要求 exportgraphics(gcf, 'bode_G1.png', 'Resolution', 300);

Resolution 参数直接控制输出像素密度。300 dpi 是打印的最低标准,投期刊图建议 600。注意 exportgraphics 裁剪的是图形对象本身的外接区域,如果你用默认的 subplot 布局,它会把整个 figure 包括标题栏一起裁进去,所以 figure 的 Position 要先设好。

4.2 打印纸张尺寸与图面比例

如果你要把图排进 Word 或 LaTeX,图的长宽比和绝对尺寸就得提前想好。双栏论文的单栏图片宽度一般 8-9 cm,通栏图 16-17 cm。资源包里的模板默认 16×12 cm 的 figure,对应通栏宽度,两行子图各占一半高度。

% 控制打印纸张和版心,单位统一为厘米 set(gcf, 'PaperUnits', 'centimeters'); set(gcf, 'PaperSize', [16 12]); set(gcf, 'PaperPosition', [0 0 16 12]);

PaperUnits 设成 centimeters 是为了让 PaperSize 和 PaperPosition 的单位一致。这三个 set 连用是 print 和 PDF 导出的标准前置操作,能避免「屏幕上正常、存成 PDF 后图被裁掉一半」的经典翻车。如果你只用 exportgraphics,前两个 set 可以省略,但 PaperPosition 设一下没坏处。

导出矢量图也是论文场景的常见需求。矢量图在 PDF 里任意缩放都不失真,适合 LaTeX 用户。

% 矢量格式导出:PDF 或 SVG exportgraphics(gcf, 'bode_G1.pdf', 'ContentType', 'vector'); exportgraphics(gcf, 'bode_G1.svg', 'ContentType', 'vector');

ContentType 设为 vector 时,导出的是矢量图形而不是位图。注意矢量图对字体的要求更高,如果系统缺了模板里指定的字体,PDF 里会变成乱码或替换字体。

4.3 批量处理多个系统的循环脚本

资源包里最实用的部分我认为是批量导出脚本。控制课设往往要画七八个系统的伯德图,手动一个个导又慢又容易漏。

% 批量绘制并导出多个系统的伯德图 systems = {G1, G2, G3}; sys_names = {'G1_simple', 'G2_zpk', 'G3_ss'}; for i = 1:length(systems) % 统一使用同一频率向量,保证图与图可比 [mag, phase, ~] = bode(systems{i}, w); mag_db = 20 * log10(squeeze(mag)); phase_deg = squeeze(phase); figure('Units', 'centimeters', 'Position', [2 2 16 12]); subplot(2, 1, 1); semilogx(w, mag_db, 'LineWidth', 1.2); grid on; ylabel('幅值 (dB)'); title(['开环伯德图 - ' sys_names{i}]); subplot(2, 1, 2); semilogx(w, phase_deg, 'LineWidth', 1.2); grid on; ylabel('相角 (deg)'); xlabel('频率 (rad/s)'); % 统一 300 dpi 导出,文件名带系统标识 exportgraphics(gcf, ['bode_' sys_names{i} '.png'], 'Resolution', 300); close(gcf); % 关掉当前 figure,防止循环里内存堆积 end

close(gcf) 是循环里最容易被漏掉的一行。如果循环体最后不关 figure,每一轮都会开一个新窗口,跑完七八个系统后内存占用会涨得很快,老机器甚至会卡死。sys_names 里我加了后缀区分建模方式,导出文件名一目了然,后续整理报告时不会搞混。

这段脚本的另一个细节是统一 w 向量。所有系统用同一组频率点,导出的图才有可比性,画在同一张图里叠加时完全对齐。如果每个系统用默认频率范围,频率点分布不一致,叠加时曲线在细节上会错位。

提示:如果系统里含 time delay 或复杂的非最小相位环节,直接用 bode 自动选频可能漏掉关键频段,建议在循环里显式传入 w 并扩大范围。

5. 避坑:相位跳变、NaN 与图例错位的五个排查记录

5.1 相位曲线在 ±180° 附近跳变

现象:相位曲线在 -180° 附近突然从 -179° 跳到 +179°,或反过来,图上出现一个突兀的尖刺,相频曲线看起来像断裂了。

原因:MATLAB 默认把相角包裹在 (-180°, 180°] 区间内。系统相位越过 -180° 时会直接从 -180° 跳到 +180°,实际上相位是连续的,但显示出来就是跳变。对于含积分环节的系统,相频曲线经常跨过 ±180°,所以这个现象非常常见。

解决:对相位做 unwrap 解包裹。MATLAB 的 unwrap 函数会把相邻点的跳变补齐,还原连续相位曲线。

% 相位解包裹:输入弧度,输出也是弧度 phase_unwrapped = unwrap(phase_deg * pi / 180) * 180 / pi;

注意 unwrap 要求输入是弧度,所以要先把 deg 转成 rad,处理完再转回 deg。unwrap 默认的跳变阈值是 π,如果你的相位数据恰好只有很小的跳变(比如噪声引起的),此时不会触发修正,要回头检查原始数据而不是怪 unwrap。

5.2 低频段幅值为 NaN 或 -Inf

现象:画幅频图时,曲线低频端突然断掉,或者出现一个指向 -∞ 的尖峰,整条曲线的形状完全不符合渐近线预期。

原因:系统含积分环节 1/s 时,s = 0 处增益无穷大。如果 w 向量里包含 0,或者频率下限取得太低,20*log10 计算时会得到 Inf 或 NaN。低频渐近线是 -20 dB/dec 的斜线,频率越低幅值越大,这是正常的,但数据点算不出来就是程序问题。

解决:频率下限从 1e-2 起步,别把 w 写成从 0 开始的等差序列。用 linspace(0, 100, 1000) 这种写法画伯德图是必然翻车的,横轴必须用 logspace 或对数刻度。

% 错误示范:w 包含 0,低频端出 NaN w_bad = linspace(0, 100, 1000); % 正确做法:w 从 1e-2 开始,对数等间距 w_good = logspace(-2, 2, 500);

5.3 叠加两条曲线后图例对不上

现象:用了 hold on 在同一个坐标区画两条曲线,legend 里传了两个字符串,但显示出来的线型和图上对不上,或者图例多出一些莫名其妙的项。

原因:legend 在没指定句柄时,按坐标区里图形对象的创建顺序自动抓取。如果之间插入了 yline、grid minor 生成的线对象,或者先画了别的辅助线,自动抓取就会错位,图例项数量和线型全乱。

解决:返回 semilogx 的句柄,用句柄数组显式指定图例项。

h1 = semilogx(w, mag_db_before, 'b-', 'LineWidth', 1.2); hold on; h2 = semilogx(w, mag_db_after, 'r--', 'LineWidth', 1.2); legend([h1 h2], {'校正前', '校正后'}, 'Location', 'southwest');

这个习惯我后来一直保留:只要一个坐标区里的线超过一条,就一律用句柄指定图例,省得后面加辅助线时图例变乱。

5.4 中文标签导出后变方块

现象:标题用中文,屏幕上显示正常,用 exportgraphics 导出 PNG 后,中文全变成方框或乱码,打印出来更是没法看。

原因:模板里 FontName 设的是 Times New Roman,这个字体不含中文字形,MATLAB 在导出时会用系统默认中文字体替换,替换失败就渲染成方块。另一个相关问题是字体嵌入,导出 PDF 时如果字体没嵌入,换台电脑打开就缺字。

解决:两条路。要么图内文字全部用英文,这是论文投稿最稳的做法;要么在 Windows 下把字体设成 SimHei 或 Microsoft YaHei。

% 中文字体设置(Windows),SimHei 打印黑体效果 set(gca, 'FontName', 'SimHei'); % 标题单独设字体,正文和刻度保持 Times New Roman title('开环系统的伯德图', 'FontName', 'SimHei');

提示:投稿论文时,图内文字建议全部使用英文,中文字体在不同平台和不同 PDF 阅读器下的渲染差异太大,省事也省心。

5.5 subplot 标题与刻度标签重叠

现象:两行子图之间,上面的标题和下面子图的刻度标签贴在一起,导出的图里文字互相压着,分辨率越高越明显。

原因:figure 高度不够,或者字号调大后 subplot 默认间距不足。默认的 subplot 间距是按默认字号算的,字号调到 10.5 甚至 12 后,间距就不够用了。

解决:把 figure 高度从 12 加到 14 厘米,或者用 subplot 的 Position 属性手动布局。

% 手动布局:两行子图,分别指定位置,单位是归一化坐标 subplot('Position', [0.12 0.55 0.82 0.38]); subplot('Position', [0.12 0.12 0.82 0.38]);

用归一化坐标的好处是精确控制间距和留白,缺点是换字体或字号时还要回来重新调。偷懒方案是把 figure 高度加大到 14,大多数情况下够用。

6. 进阶:用 margin() 数据交叉验证手绘图上的裕度标注

手绘图再漂亮,数据错了也是白搭。最后这步是把 margin() 的系统计算结果和你手绘的曲线做交叉验证,确保图上的每个关键点都能对上数字。

6.1 margin() 的四个返回值

[Gm, Pm, Wcg, Wcp] = margin(G1);

Gm 是幅值裕度,返回值是线性值,转 dB 要 20*log10(Gm);Pm 是相角裕度,单位 deg;Wcg 是相位穿越频率,即相位等于 -180° 的频率;Wcp 是增益穿越频率,即幅值等于 0 dB 的频率。这四个值是自控课设里稳定分析的输出结果,现在反过来用它们验证绘图。

6.2 在自绘图上标注穿越频率

% 幅频图上加 0 dB 参考线和增益穿越频率标记 figure('Units', 'centimeters', 'Position', [2 2 16 12]); subplot(2, 1, 1); semilogx(w, mag_db, 'LineWidth', 1.2); hold on; yline(0, 'k-'); semilogx([Wcp Wcp], ylim, 'k--'); text(Wcp, -20, sprintf('ω_c = %.2f rad/s', Wcp), 'FontSize', 9);

ylim 取当前坐标区纵轴范围,semilogx 画竖线时要用它,不然线段长度不对。text 的位置要避开曲线本身,幅频图上 Wcp 处曲线必定穿过 0 dB,所以把文字放在 -20 dB 附近不会挡线。

6.3 三个验证步骤

第一步,看幅频曲线穿过 0 dB 的横坐标,和 Wcp 对比,误差应在几个点以内。第二步,在 Wcp 处的相频曲线上读相角值,Pm = 相角值 + 180°,和 margin 返回的 Pm 对比。第三步,在 Wcg 处读幅值,20*log10(Gm) 应该等于该处幅值的绝对值,因为幅值裕度本质是 0 dB 减去 Wcg 处的幅值。

三个验证做完,这张图就不是「看着像那么回事」,而是每个关键点都有据可查。从那以后,我每次画完伯德图都强制自己走一遍这步,线宽、字体、导出参数全部按资源包里的模板来,不自己临时乱调。磨刀不误砍柴工,这套流程跑顺了,画一张图从建模到导出用不了两分钟,希望帮到你。

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

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

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

立即咨询