Matlab定制伯德图坐标纸:从原理到工程实践

📅 2026/8/4 12:01:30
Matlab定制伯德图坐标纸:从原理到工程实践
1. 项目概述为什么需要定制伯德图坐标纸在自动控制、信号处理、电路设计这些领域伯德图Bode Plot是我们分析系统频率响应的“眼睛”。无论是评估一个滤波器的性能还是调试一个控制器的参数伯德图都能直观地展示系统的幅频特性和相频特性。标准的伯德图坐标纸其横轴是对数频率log10 ω纵轴是分贝dB和角度°这种特殊的坐标系是为了匹配系统传递函数的对数表达形式。然而在日常的科研、教学或者工程报告中我们常常遇到一个尴尬的情况用Matlab的bode函数生成的图虽然数据准确但直接粘贴到论文或报告里其默认的网格线、坐标轴标签、字体大小往往不符合出版或内部规范的要求。更常见的是我们需要将多组系统的伯德图绘制在同一张坐标纸上进行对比或者需要在图上手动标注关键频率点、增益裕度、相位裕度。这时Matlab默认的图形输出就显得力不从心了。市面上确实有现成的伯德图坐标纸可以下载打印但那是“死”的。你无法在上面动态地绘制和修改曲线。而完全依赖Matlab默认绘图又失去了坐标纸那种规整、专业的视觉参考线。所以一个理想的解决方案是用Matlab“画”出一张完全符合我们个性化需求的、高分辨率的、可重复使用的“活”坐标纸。这张坐标纸的网格、刻度、范围都可以自由定义生成后可以作为背景再将实际系统的伯德曲线叠加绘制上去最终得到一张既美观又专业的定制化图表。这不仅仅是美化图表那么简单。对于需要反复进行同类系统分析的同学或工程师来说拥有一套自己定制的绘图模板能极大提升工作效率和报告质量。接下来我就详细拆解如何从零开始在Matlab中实现这个功能。2. 核心思路与坐标系构建定制伯德图坐标纸的核心在于精确复现其特殊的双纵轴和对数横轴坐标系。我们不能简单地使用Matlab的bode函数然后去修改它的属性因为bode函数会自动计算并绘制系统响应其坐标轴属性是关联在特定的bode图形对象上的直接修改底层坐标轴来制作纯净的坐标纸反而更麻烦。我的思路是抛开bode函数直接使用底层图形函数从一张白纸开始手动构建伯德图坐标纸的所有元素。这包括对数刻度的横轴、分贝刻度的左纵轴和角度刻度的右纵轴以及与之对应的主网格线和辅助网格线。2.1 坐标系参数定义首先我们需要定义坐标纸的核心参数。这些参数决定了坐标纸的“规格”。% 1. 定义频率范围 (横轴对数刻度) f_start 0.1; % 起始频率单位 Hz 或 rad/s根据习惯 f_end 1000; % 终止频率 % 通常伯德图横轴按10的幂次划分我们生成对数等间隔的点 f logspace(log10(f_start), log10(f_end), 1000); % 1000个点用于绘制网格线 % 2. 定义幅值范围 (左纵轴dB) mag_min -40; % 最小幅值dB mag_max 40; % 最大幅值dB mag_ticks -40:20:40; % 主刻度位置每20dB一格 mag_minor_ticks setdiff(-40:10:40, mag_ticks); % 辅助刻度位置每10dB一格 % 3. 定义相位范围 (右纵轴度) phase_min -270; % 最小相位根据系统可能滞后超过-180° phase_max 90; % 最大相位 phase_ticks -270:90:90; % 主刻度每90°一格 phase_minor_ticks setdiff(-270:45:90, phase_ticks); % 辅助刻度每45°一格 % 4. 图形尺寸与分辨率用于导出高质量图片 fig_width 16; % 厘米适合A4纸半幅宽度 fig_height 12; % 厘米 dpi 600; % 导出分辨率注意频率范围f_start和f_end的选择非常关键。对于大多数动力系统如机械、电路0.1Hz到1000Hz是一个很常用的范围。但对于超低频如地质系统或超高频如射频电路应用你需要相应调整。logspace函数确保了我们在对数坐标上取点是均匀的这对于后续绘制垂直的网格线至关重要。2.2 创建图形与双纵轴在Matlab中我们要使用yyaxis函数来创建双纵轴但需要一些技巧来分别控制左右轴。figure(‘Position‘ [100, 100, fig_width/2.54*96, fig_height/2.54*96]); % 将厘米转换为像素 hold on; % 非常重要确保后续绘图都叠加在此图上 grid on; % 先打开网格但默认网格不符合要求后续会覆盖 % 激活左轴用于幅值 yyaxis left; ax_left gca; % 获取左轴句柄 set(ax_left, ‘XScale‘, ‘log‘); % 设置横轴为对数坐标 ylim([mag_min, mag_max]); ylabel(‘Magnitude (dB)‘, ‘FontSize‘, 11, ‘FontWeight‘, ‘bold‘); set(ax_left, ‘YColor‘, ‘k‘); % 设置左轴颜色为黑色 % 激活右轴用于相位 yyaxis right; ax_right gca; % 获取右轴句柄。注意gca始终返回当前活动轴此时是右轴。 set(ax_right, ‘XScale‘, ‘log‘); ylim([phase_min, phase_max]); ylabel(‘Phase (deg)‘, ‘FontSize‘, 11, ‘FontWeight‘, ‘bold‘); set(ax_right, ‘YColor‘, ‘k‘); % 设置右轴颜色为黑色 % 设置共同的横轴属性 xlim([f_start, f_end]); xlabel(‘Frequency (rad/s)‘, ‘FontSize‘, 11, ‘FontWeight‘, ‘bold‘); set([ax_left, ax_right], ‘XColor‘, ‘k‘, ‘Box‘, ‘off‘); % 统一横轴颜色关闭盒子实操心得这里最容易出错的地方是对坐标轴句柄ax_left和ax_right的管理。在调用yyaxis left和yyaxis right后gca获取当前坐标轴返回的是不同的对象。我们必须分别获取并存储它们的句柄因为后续我们需要对左右纵轴分别设置刻度、网格线等属性。‘Box‘, ‘off‘是为了让图形看起来更像传统的坐标纸而不是被一个矩形框死。3. 绘制自定义网格线与刻度Matlab自带的grid on在对数坐标下生成的是线性网格这完全不符合伯德图坐标纸的要求。伯德图坐标纸的网格线在横轴对数频率上应该是等间隔的垂直线对应10的幂次在纵轴上则是水平线。3.1 绘制垂直网格线频率格垂直网格线对应着频率轴上的主要刻度通常是10的整数次幂位置。% 生成频率主刻度位置例如 0.1, 1, 10, 100, 1000 f_major_ticks 10.^(floor(log10(f_start)):ceil(log10(f_end))); % 过滤出在显示范围内的刻度 f_major_ticks f_major_ticks(f_major_ticks f_start f_major_ticks f_end); % 绘制垂直主网格线实线较粗 for ftick f_major_ticks % 在左右两个轴上分别画线确保覆盖整个图形区域 yyaxis left; line([ftick, ftick], [mag_min, mag_max], ‘Color‘, [0.7, 0.7, 0.7], ‘LineWidth‘, 0.8, ‘LineStyle‘, ‘-‘); yyaxis right; line([ftick, ftick], [phase_min, phase_max], ‘Color‘, [0.7, 0.7, 0.7], ‘LineWidth‘, 0.8, ‘LineStyle‘, ‘-‘); end % 生成并绘制频率辅助刻度线例如 0.2, 0.3, ..., 2, 3, ..., 200, 300, ... % 这里我们绘制10的幂次之间的“次级”线例如在1和10之间画2,3,4,5,6,7,8,9 f_minor_ticks []; for i 1:(length(f_major_ticks)-1) start_decade f_major_ticks(i); end_decade f_major_ticks(i1); minor_set (2:9) * start_decade; % 生成2倍到9倍的刻度 minor_set minor_set(minor_set end_decade); % 确保不超过下一个10的幂次 f_minor_ticks [f_minor_ticks, minor_set]; end % 过滤显示范围 f_minor_ticks f_minor_ticks(f_minor_ticks f_start f_minor_ticks f_end); % 绘制垂直辅助网格线虚线较细 for ftick f_minor_ticks yyaxis left; line([ftick, ftick], [mag_min, mag_max], ‘Color‘, [0.85, 0.85, 0.85], ‘LineWidth‘, 0.4, ‘LineStyle‘, ‘:‘); yyaxis right; line([ftick, ftick], [phase_min, phase_max], ‘Color‘, [0.85, 0.85, 0.85], ‘LineWidth‘, 0.4, ‘LineStyle‘, ‘:‘); end3.2 绘制水平网格线幅值与相位格水平网格线的绘制相对直接分别在左右纵轴对应的位置画线即可。% 绘制幅值左轴水平主网格线 for mtick mag_ticks line([f_start, f_end], [mtick, mtick], ‘Color‘, [0.7, 0.7, 0.7], ‘LineWidth‘, 0.8, ‘LineStyle‘, ‘-‘); end % 绘制幅值水平辅助网格线 for mtick mag_minor_ticks line([f_start, f_end], [mtick, mtick], ‘Color‘, [0.85, 0.85, 0.85], ‘LineWidth‘, 0.4, ‘LineStyle‘, ‘:‘); end % 绘制相位右轴水平主网格线 % 注意画相位网格线时需要先将当前坐标轴切换到右轴或者直接用绝对坐标画。 % 我们采用在右轴活动状态下画线的方式。 yyaxis right; for ptick phase_ticks line([f_start, f_end], [ptick, ptick], ‘Color‘, [0.7, 0.7, 0.7], ‘LineWidth‘, 0.8, ‘LineStyle‘, ‘-‘); end % 绘制相位水平辅助网格线 for ptick phase_minor_ticks line([f_start, f_end], [ptick, ptick], ‘Color‘, [0.85, 0.85, 0.85], ‘LineWidth‘, 0.4, ‘LineStyle‘, ‘:‘); end注意事项绘制水平线时line函数使用的纵坐标值是基于“当前活动坐标轴”的。因此在画幅值网格线时确保当前是左轴yyaxis left画相位网格线时先切换到右轴yyaxis right。如果不小心弄混会导致线画在错误的位置上。颜色[0.7,0.7,0.7]是浅灰色[0.85,0.85,0.85]是更浅的灰色这样区分主次网格视觉上更清晰且打印出来不会太刺眼。3.3 精细设置坐标轴刻度与标签现在我们需要隐藏Matlab自动生成的刻度线因为我们自己画的网格线已经提供了参考同时保留清晰的刻度标签。% 设置左轴幅值轴刻度 yyaxis left; set(ax_left, ‘YTick‘, mag_ticks); % 只显示我们定义的主刻度 set(ax_left, ‘YTickLabel‘, arrayfun(num2str, mag_ticks, ‘UniformOutput‘, false)); % 设置刻度标签 set(ax_left, ‘YMinorTick‘, ‘off‘); % 关闭自动生成的次要刻度 set(ax_left, ‘YGrid‘, ‘off‘); % 关闭Matlab自带的网格非常重要 set(ax_left, ‘TickDir‘, ‘out‘); % 刻度朝外更美观 % 设置右轴相位轴刻度 yyaxis right; set(ax_right, ‘YTick‘, phase_ticks); set(ax_right, ‘YTickLabel‘, arrayfun(num2str, phase_ticks, ‘UniformOutput‘, false)); set(ax_right, ‘YMinorTick‘, ‘off‘); set(ax_right, ‘YGrid‘, ‘off‘); set(ax_right, ‘TickDir‘, ‘out‘); % 设置横轴刻度 set(ax_left, ‘XTick‘, f_major_ticks); % 设置主刻度位置 % 对数坐标的刻度标签我们希望显示为‘0.1‘, ‘1‘, ‘10‘, ‘100‘, ‘1000‘ xtick_labels cell(1, length(f_major_ticks)); for i 1:length(f_major_ticks) if f_major_ticks(i) 1 xtick_labels{i} sprintf(‘%.1f‘, f_major_ticks(i)); else xtick_labels{i} sprintf(‘%g‘, f_major_ticks(i)); end end set(ax_left, ‘XTickLabel‘, xtick_labels); set(ax_left, ‘XMinorTick‘, ‘off‘); set(ax_left, ‘XGrid‘, ‘off‘);至此一张干净的、只有自定义网格和刻度的伯德图坐标纸背景就绘制完成了。你可以运行一下代码应该能看到一张非常专业的坐标纸。接下来我们要解决如何将实际系统的伯德图“打印”到这张坐标纸上。4. 在定制坐标纸上叠加绘制系统曲线有了坐标纸背景绘制系统曲线就变成了一个“对齐”问题。我们需要确保系统频率响应的数据点被正确地绘制到我们自定义的对数频率轴和分贝/角度轴上。4.1 计算系统的频率响应假设我们有一个传递函数G(s) 100 / (s^2 5*s 100)。我们需要计算它在我们定义的频率向量f或角频率ω 2*pi*f上的响应。% 定义系统传递函数 (连续系统) s tf(‘s‘); G 100 / (s^2 5*s 100); % 示例二阶系统 % 计算频率响应。注意bode函数默认使用角频率 rad/s。 % 我们的 f 向量单位是 Hz需要转换为 rad/s: w 2 * pi * f w 2 * pi * f; % 角频率向量单位 rad/s [mag, phase, wout] bode(G, w); % 计算幅值和相位 % bode函数返回的mag和phase是3维数组需要压缩 mag squeeze(mag); % 幅值线性值 phase squeeze(phase); % 相位度 mag_db 20 * log10(mag); % 转换为分贝(dB)4.2 将曲线绘制到定制坐标纸上现在我们激活对应的坐标轴使用plot函数进行绘制。关键是要关闭并重新打开hold on以确保我们是在已有的坐标纸背景上添加曲线而不是新建一个图。% 确保我们还在之前的图形窗口中并且hold是on状态。 % 如果之前关闭了需要重新打开。 hold on; % 1. 绘制幅频特性曲线 (在左轴) yyaxis left; % 使用semilogx绘制因为横轴是对数的。但我们的坐标轴已设置为‘XScale‘, ‘log‘所以用plot也可以。 % 为了清晰我们使用semilogx。 h_mag semilogx(w/(2*pi), mag_db, ‘b-‘, ‘LineWidth‘, 2); % w/(2*pi)转换回Hz显示也可以直接用w % 设置线条属性例如蓝色实线线宽2 % 2. 绘制相频特性曲线 (在右轴) yyaxis right; h_phase semilogx(w/(2*pi), phase, ‘r-‘, ‘LineWidth‘, 2); % 红色实线线宽2 % 3. 添加图例 legend([h_mag, h_phase], {‘Magnitude‘, ‘Phase‘}, ‘Location‘, ‘best‘, ‘FontSize‘, 10); % 4. 调整图层顺序确保网格线在曲线下方这样曲线不会被网格线遮挡 % 获取图形中所有线条对象的句柄 all_lines findobj(gcf, ‘Type‘, ‘line‘); % 将我们绘制的系统曲线h_mag, h_phase提到最前面 uistack([h_mag, h_phase], ‘top‘);踩坑记录这里最大的一个坑是bode函数返回数据的维度。bode(G, w)返回的mag和phase是 1x1xN 的三维数组其中N是频率点数。直接用于绘图会出错。必须用squeeze()函数将其压缩为Nx1或1xN的向量。另一个常见问题是忘记单位转换bode函数内部使用角频率ω(rad/s)而我们定义频率范围时可能习惯用f(Hz)。在绘图时横坐标要统一要么全用ω要么全用f。我建议在计算响应时传入ω在设置坐标轴标签和绘图时根据领域习惯选择显示f(Hz)或ω(rad/s)。4.3 添加标注与关键信息一张专业的伯德图除了曲线还需要标注关键性能指标如增益穿越频率、相位裕度、截止频率等。% 计算增益裕度和相位裕度 [Gm, Pm, Wcg, Wcp] margin(G); % Gm是增益裕度线性值Pm是相位裕度度Wcg/Wcp是对应频率(rad/s) % 将增益裕度转换为分贝 Gm_db 20*log10(Gm); % 在图上标注相位裕度 yyaxis right; % 切换到相位轴 % 在增益穿越频率Wcp处找到对应的相位值 [~, idx_wcp] min(abs(wout - Wcp)); % 找到Wcp在响应数据中的索引 phase_at_wcp phase(idx_wcp); % 画一条从曲线到-180°或相位裕度参考线的指示线 line([Wcp/(2*pi), Wcp/(2*pi)], [phase_at_wcp, -180], ‘Color‘, ‘k‘, ‘LineStyle‘, ‘--‘, ‘LineWidth‘, 1); text(Wcp/(2*pi)*1.1, (phase_at_wcp-180)/2, sprintf(‘P.M. %.1f°‘, Pm), ... ‘FontSize‘, 9, ‘BackgroundColor‘, ‘w‘, ‘EdgeColor‘, ‘k‘); % 在图上标注增益裕度 yyaxis left; % 切换到幅值轴 % 在相位穿越频率Wcg处找到对应的幅值 [~, idx_wcg] min(abs(wout - Wcg)); mag_at_wcg_db mag_db(idx_wcg); % 画一条从曲线到0dB的指示线 line([Wcg/(2*pi), Wcg/(2*pi)], [mag_at_wcg_db, 0], ‘Color‘, ‘k‘, ‘LineStyle‘, ‘--‘, ‘LineWidth‘, 1); text(Wcg/(2*pi)*1.1, mag_at_wcg_db/2, sprintf(‘G.M. %.1f dB‘, Gm_db), ... ‘FontSize‘, 9, ‘BackgroundColor‘, ‘w‘, ‘EdgeColor‘, ‘k‘); % 可以添加标题 title(‘Custom Bode Plot: G(s) 100/(s^25s100)‘, ‘FontSize‘, 12, ‘FontWeight‘, ‘bold‘);现在运行全部代码你将得到一张包含自定义坐标纸网格、清晰刻度、系统伯德曲线以及关键稳定性指标标注的专业图表。5. 封装成函数与模板化应用每次都写这么长的脚本显然不高效。最佳实践是将创建坐标纸背景的功能封装成一个独立的函数例如createBodePaper.m。这个函数接受频率范围、幅值范围、相位范围等作为输入参数返回图形和坐标轴句柄。function [fig, ax_left, ax_right] createBodePaper(f_range, mag_range, phase_range) % f_range: [f_start, f_end] % mag_range: [mag_min, mag_max] % phase_range: [phase_min, phase_max] % 返回图形和坐标轴句柄方便后续绘图 ... % 此处是前面第2、3节的所有代码封装进来 end然后你的主脚本或另一个函数就可以这样调用% 主脚本 [f_range, mag_range, phase_range] deal([0.1, 1000], [-40, 40], [-270, 90]); [fig, ax_left, ax_right] createBodePaper(f_range, mag_range, phase_range); % 定义多个系统进行比较 sys1 tf(100, [1, 5, 100]); sys2 tf(50, [1, 2, 50]); % ... 计算频率响应 hold(ax_left, ‘on‘); % 注意对特定坐标轴使用hold hold(ax_right, ‘on‘); % 在ax_left和ax_right上分别绘制sys1和sys2的曲线 % ... legend(‘System 1‘, ‘System 2‘);更进一步你可以创建不同的“.mat”文件来保存多套坐标纸参数模板比如“标准模板.mat”、“宽幅值范围模板.mat”、“超低频模板.mat”。使用时直接加载参数调用createBodePaper函数极大提升效率。6. 导出与打印设置生成的图表最终需要插入文档或打印。Matlab的默认导出设置往往不理想我们需要精细控制。% 设置图形尺寸和分辨率应在创建图形时或导出前设置 fig gcf; fig.PaperUnits ‘centimeters‘; fig.PaperSize [fig_width, fig_height]; % 纸张大小 fig.PaperPosition [0, 0, fig_width, fig_height]; % 图像占满纸张 fig.PaperPositionMode ‘manual‘; fig.Renderer ‘painters‘; % 使用矢量渲染器导出eps/pdf时效果更好 % 导出为PDF矢量格式无限缩放 print(fig, ‘MyCustomBodePlot.pdf‘, ‘-dpdf‘, sprintf(‘-r%d‘, dpi)); % 导出为PNG位图格式用于网页或PPT print(fig, ‘MyCustomBodePlot.png‘, ‘-dpng‘, sprintf(‘-r%d‘, dpi)); % 导出为EPS用于LaTeX部分期刊要求 print(fig, ‘MyCustomBodePlot.eps‘, ‘-depsc‘, ‘-tiff‘); % ‘-tiff‘选项可嵌入预览图重要技巧如果你发现导出的PDF或EPS文件中虚线网格线变成了实线或者字体错乱这通常是渲染器的问题。尝试将Renderer设置为‘painters‘矢量渲染这能保证最好的打印质量。对于非常复杂的图形‘painters‘可能较慢或内存不足可以尝试‘opengl‘。另外在导出为位图如PNG时确保DPI设置足够高通常600 DPI用于出版150-300 DPI用于屏幕展示否则文字和线条会模糊。7. 常见问题与进阶技巧在实际操作中你可能会遇到一些棘手的情况。这里分享几个我踩过的坑和对应的解决方案。问题一双纵轴的刻度标签重叠或错位。现象左右纵轴的刻度数字挤在一起或者相位轴的0°线没有和幅值轴的0dB线对齐。解决这是yyaxis的一个已知痛点。我们可以通过手动调整两个Y轴的范围和刻度位置来对齐。一个取巧的方法是先绘制一个“虚拟”的、范围很大的曲线来固定坐标轴范围然后再绘制网格和实际曲线。更根本的解决方法是放弃yyaxis使用底层的axes和plotyy旧版本或手动创建两个重叠的坐标轴这样可以实现像素级的精确控制但代码更复杂。问题二自定义的网格线在缩放或平移图形时不会动态调整。现象用Matlab工具栏的放大镜缩放图形时我们手动绘制的line对象会停留在原地不会随坐标轴变化导致网格错位。解决我们绘制的网格线是静态的图形对象。对于需要交互式探索的图表这不是最佳选择。替代方案是使用Matlab的网格属性并精心设置YTick、YMinorTick、GridLineStyle等来模拟伯德图网格但这很难做到对数横轴上的等间隔垂直网格。因此本方案生成的坐标纸更适合用于生成最终报告图表而非交互分析。交互分析仍应使用标准的bode函数。问题三在同一个坐标纸上绘制多个系统时图例混乱或曲线覆盖顺序不对。现象后绘制的曲线图例覆盖了先绘制的或者曲线被网格线压在下面。解决图例在调用legend时明确传入需要列入图例的线条句柄如legend([h1, h2, h3], {‘sys1‘, ‘sys2‘, ‘sys3‘})避免自动生成时包含网格线。图层顺序使用uistack函数。在绘制完所有曲线和网格后执行all_lines findobj(gcf, ‘Type‘, ‘line‘);和curve_handles [...];你的曲线句柄然后uistack(curve_handles, ‘top‘);将曲线提到最上层。问题四如何绘制尼科尔斯图Nichols Chart或奈奎斯特图Nyquist Plot的定制坐标纸思路方法论完全一样尼科尔斯图是开环幅值dB对开环相位度的曲线坐标纸是幅相平面上的等M圆和等N圆网格。你可以先计算并绘制出这些圆形网格线作为背景。奈奎斯特图是实部对虚部坐标纸是复平面上的等阻尼比和等自然频率网格。核心步骤都是1) 创建空白图2) 根据目标坐标系的数学方程计算并绘制背景网格线3) 计算系统响应数据4) 将系统曲线叠加绘制上去。这实现了真正意义上的任意频率响应图坐标纸定制。通过以上步骤你不仅获得了一张定制的伯德图坐标纸更掌握了一套在Matlab中构建专业级、可出版质量技术图表的方法论。这套方法的核心思想——“将静态的参考坐标系与动态的系统数据分离绘制”——可以推广到许多其他需要特殊坐标纸的场合比如根轨迹图、仿真结果对比图等。花点时间打造属于自己的绘图模板库长远来看这比每次手动调整默认图表要节省大量时间并且能保证输出质量的一致性。