<!DOCTYPE html>
<html>
<head>
<title>伯德图(Bode Plot)原理示意图</title>
<style>
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; margin: 0; padding: 20px; background: #f5f7fa; }
.container { max-width: 1000px; margin: 0 auto; }
h1 { text-align: center; color: #2c3e50; margin-bottom: 10px; }
.subtitle { text-align: center; color: #7f8c8d; margin-bottom: 30px; }
.plot-container { display: flex; flex-direction: column; gap: 30px; }
.plot { background: white; border-radius: 10px; box-shadow: 0 4px 20px rgba(0,0,0,0.08); padding: 20px; position: relative; }
.plot-title { font-size: 18px; font-weight: bold; margin-bottom: 15px; color: #34495e; }
.canvas-container { position: relative; }
canvas { border: 1px solid #ecf0f1; border-radius: 5px; }
.legend { display: flex; gap: 20px; margin-top: 15px; justify-content: center; }
.legend-item { display: flex; align-items: center; }
.color-box { width: 20px; height: 20px; margin-right: 8px; border-radius: 3px; }
.controls { display: flex; gap: 15px; margin-top: 20px; justify-content: center; }
.control-group { display: flex; flex-direction: column; }
label { margin-bottom: 5px; font-size: 14px; color: #34495e; }
input[type="range"] { width: 200px; }
.explanation { margin-top: 30px; background: #e8f4f8; padding: 20px; border-radius: 10px; border-left: 4px solid #3498db; }
.explanation h3 { color: #2c3e50; margin-top: 0; }
.explanation ul { padding-left: 20px; }
.explanation li { margin-bottom: 10px; line-height: 1.6; }
.key-point { color: #e74c3c; font-weight: bold; }
</style>
</head>
<body>
<div class="container">
<h1>伯德图(Bode Plot)原理分析</h1>
<div class="subtitle">幅度响应和相位响应的频率特性图示</div>
<div class="plot-container">
<div class="plot">
<div class="plot-title">幅度响应图 (Magnitude Plot)</div>
<div class="canvas-container">
<canvas id="magnitudePlot" width="900" height="300"></canvas>
</div>
</div>
<div class="plot">
<div class="plot-title">相位响应图 (Phase Plot)</div>
<div class="canvas-container">
<canvas id="phasePlot" width="900" height="300"></canvas>
</div>
</div>
<div class="legend">
<div class="legend-item">
<div class="color-box" style="background-color: #3498db;"></div>
<span>系统实际响应</span>
</div>
<div class="legend-item">
<div class="color-box" style="background-color: #e74c3c;"></div>
<span>渐近线近似</span>
</div>
<div class="legend-item">
<div class="color-box" style="background-color: #27ae60;"></div>
<span>截止频率点</span>
</div>
</div>
<div class="controls">
<div class="control-group">
<label for="poleFreq">极点频率 (Hz): <span id="poleValue">1000</span></label>
<input type="range" id="poleFreq" min="100" max="5000" value="1000" step="100">
</div>
<div class="control-group">
<label for="zeroFreq">零点频率 (Hz): <span id="zeroValue">2000</span></label>
<input type="range" id="zeroFreq" min="500" max="5000" value="2000" step="100">
</div>
</div>
</div>
<div class="explanation">
<h3>伯德图核心概念解析:</h3>
<ul>
<li><span class="key-point">伯德图组成</span>:由幅度图(dB-频率)和相位图(度-频率)组成,均采用半对数坐标系</li>
<li><span class="key-point">渐近线近似</span>:用直线段近似表示系统频率响应,在极点处-20dB/decade下降,零点处+20dB/decade上升</li>
<li><span class="key-point">截止频率</span>(图中绿点):幅度下降3dB对应的频率点,相位变化45°的位置</li>
<li><span class="key-point">稳定性分析</span>:通过相位裕度(PM)和增益裕度(GM)判断系统稳定性</li>
<li><span class="key-point">工程应用</span>:用于滤波器设计、控制系统稳定性分析、放大器频率补偿等</li>
</ul>
</div>
</div>
<script>
// 获取Canvas元素和上下文
const magCanvas = document.getElementById('magnitudePlot');
const phaseCanvas = document.getElementById('phasePlot');
const magCtx = magCanvas.getContext('2d');
const phaseCtx = phaseCanvas.getContext('2d');
// 获取控制滑块
const poleSlider = document.getElementById('poleFreq');
const zeroSlider = document.getElementById('zeroFreq');
const poleValue = document.getElementById('poleValue');
const zeroValue = document.getElementById('zeroValue');
// 初始参数
let poleFrequency = 1000;
let zeroFrequency = 2000;
// 更新显示值
poleSlider.addEventListener('input', function() {
poleFrequency = parseInt(this.value);
poleValue.textContent = poleFrequency;
drawBodePlots();
});
zeroSlider.addEventListener('input', function() {
zeroFrequency = parseInt(this.value);
zeroValue.textContent = zeroFrequency;
drawBodePlots();
});
// 计算传递函数响应
function calculateResponse(freq) {
// 一阶系统传递函数: H(s) = (s/zero + 1) / (s/pole + 1)
const s = 2 * Math.PI * freq * 1j;
// 计算幅度响应 (dB)
const magnitude = 20 * Math.log10(
Math.sqrt(1 + (freq/zeroFrequency)**2) /
Math.sqrt(1 + (freq/poleFrequency)**2)
);
// 计算相位响应 (度)
const phase = (
Math.atan(freq/zeroFrequency) -
Math.atan(freq/poleFrequency)
) * 180 / Math.PI;
return { magnitude, phase };
}
// 计算渐近线
function calculateAsymptotes(freq) {
let magAsymptote = 0;
let phaseAsymptote = 0;
if (freq < Math.min(poleFrequency, zeroFrequency)) {
magAsymptote = 0;
phaseAsymptote = 0;
} else if (freq >= Math.min(poleFrequency, zeroFrequency) && freq < Math.max(poleFrequency, zeroFrequency)) {
// 在极点和零点之间
if (poleFrequency < zeroFrequency) {
magAsymptote = -20 * Math.log10(freq/poleFrequency);
phaseAsymptote = -45;
} else {
magAsymptote = 20 * Math.log10(freq/zeroFrequency);
phaseAsymptote = 45;
}
} else {
// 超过极点和零点
magAsymptote = 20 * Math.log10(zeroFrequency/poleFrequency);
phaseAsymptote = 0;
}
return { magAsymptote, phaseAsymptote };
}
// 绘制伯德图
function drawBodePlots() {
// 清除画布
magCtx.clearRect(0, 0, magCanvas.width, magCanvas.height);
phaseCtx.clearRect(0, 0, phaseCanvas.width, phaseCanvas.height);
// 设置绘图参数
const margin = { top: 30, right: 30, bottom: 50, left: 60 };
const magWidth = magCanvas.width - margin.left - margin.right;
const magHeight = magCanvas.height - margin.top - margin.bottom;
const phaseWidth = phaseCanvas.width - margin.left - margin.right;
const phaseHeight = phaseCanvas.height - margin.top - margin.bottom;
// 频率范围 (Hz) - 对数坐标
const minFreq = 10;
const maxFreq = 100000;
// 绘制幅度图
drawPlot(magCtx, magWidth, magHeight, margin, true);
// 绘制相位图
drawPlot(phaseCtx, phaseWidth, phaseHeight, margin, false);
}
// 绘制单个图表
function drawPlot(ctx, width, height, margin, isMagnitude) {
// 设置坐标轴范围
const minFreq = 10;
const maxFreq = 100000;
const minMag = -40;
const maxMag = 20;
const minPhase = -90;
const maxPhase = 90;
// 绘制坐标轴
ctx.strokeStyle = '#34495e';
ctx.lineWidth = 1;
// 绘制X轴
ctx.beginPath();
ctx.moveTo(margin.left, margin.top + height);
ctx.lineTo(margin.left + width, margin.top + height);
ctx.stroke();
// 绘制Y轴
ctx.beginPath();
ctx.moveTo(margin.left, margin.top);
ctx.lineTo(margin.left, margin.top + height);
ctx.stroke();
// 绘制网格和刻度
drawGrid(ctx, width, height, margin, minFreq, maxFreq,
isMagnitude ? minMag : minPhase,
isMagnitude ? maxMag : maxPhase,
isMagnitude);
// 绘制标题
ctx.fillStyle = '#2c3e50';
ctx.font = '14px Arial';
ctx.textAlign = 'center';
ctx.fillText('频率 (Hz)', margin.left + width/2, margin.top + height + 35);
ctx.save();
ctx.translate(15, margin.top + height/2);
ctx.rotate(-Math.PI/2);
ctx.textAlign = 'center';
ctx.fillText(isMagnitude ? '幅度 (dB)' : '相位 (度)', 0, 0);
ctx.restore();
// 绘制曲线
const step = 0.1; // 对数坐标中的步长
let lastX, lastY;
// 绘制实际响应曲线
ctx.beginPath();
for (let logFreq = Math.log10(minFreq); logFreq <= Math.log10(maxFreq); logFreq += step) {
const freq = Math.pow(10, logFreq);
const response = calculateResponse(freq);
const value = isMagnitude ? response.magnitude : response.phase;
const x = margin.left + (logFreq - Math.log10(minFreq)) / (Math.log10(maxFreq) - Math.log10(minFreq)) * width;
const y = margin.top + height - (value - (isMagnitude ? minMag : minPhase)) /
((isMagnitude ? maxMag : maxPhase) - (isMagnitude ? minMag : minPhase)) * height;
if (lastX) {
ctx.lineTo(x, y);
} else {
ctx.moveTo(x, y);
}
lastX = x;
lastY = y;
}
ctx.strokeStyle = '#3498db';
ctx.lineWidth = 2;
ctx.stroke();
// 绘制渐近线
ctx.beginPath();
lastX = null;
lastY = null;
for (let logFreq = Math.log10(minFreq); logFreq <= Math.log10(maxFreq); logFreq += step) {
const freq = Math.pow(10, logFreq);
const asymptotes = calculateAsymptotes(freq);
const value = isMagnitude ? asymptotes.magAsymptote : asymptotes.phaseAsymptote;
const x = margin.left + (logFreq - Math.log10(minFreq)) / (Math.log10(maxFreq) - Math.log10(minFreq)) * width;
const y = margin.top + height - (value - (isMagnitude ? minMag : minPhase)) /
((isMagnitude ? maxMag : maxPhase) - (isMagnitude ? minMag : minPhase)) * height;
if (lastX) {
ctx.lineTo(x, y);
} else {
ctx.moveTo(x, y);
}
lastX = x;
lastY = y;
}
ctx.strokeStyle = '#e74c3c';
ctx.lineWidth = 1.5;
ctx.setLineDash([5, 3]);
ctx.stroke();
ctx.setLineDash([]);
// 标记截止频率点
const poleX = margin.left + (Math.log10(poleFrequency) - Math.log10(minFreq)) /
(Math.log10(maxFreq) - Math.log10(minFreq)) * width;
const poleY = margin.top + height - (isMagnitude ? -3 - minMag : -45 - minPhase) /
((isMagnitude ? maxMag : maxPhase) - (isMagnitude ? minMag : minPhase)) * height;
ctx.fillStyle = '#27ae60';
ctx.beginPath();
ctx.arc(poleX, poleY, 6, 0, Math.PI * 2);
ctx.fill();
// 标记零点
if (zeroFrequency > poleFrequency) {
const zeroX = margin.left + (Math.log10(zeroFrequency) - Math.log10(minFreq)) /
(Math.log10(maxFreq) - Math.log10(minFreq)) * width;
const zeroY = margin.top + height - (isMagnitude ? 0 - minMag : 0 - minPhase) /
((isMagnitude ? maxMag : maxPhase) - (isMagnitude ? minMag : minPhase)) * height;
ctx.fillStyle = '#9b59b6';
ctx.beginPath();
ctx.arc(zeroX, zeroY, 6, 0, Math.PI * 2);
ctx.fill();
}
// 添加标注
ctx.fillStyle = '#2c3e50';
ctx.font = '12px Arial';
ctx.fillText(`极点: ${poleFrequency}Hz`, poleX + 10, poleY - 10);
if (zeroFrequency > poleFrequency) {
ctx.fillText(`零点: ${zeroFrequency}Hz`, poleX + 10, poleY + 20);
}
}
// 绘制网格和刻度
function drawGrid(ctx, width, height, margin, minFreq, maxFreq, minValue, maxValue, isMagnitude) {
ctx.strokeStyle = '#ecf0f1';
ctx.lineWidth = 1;
ctx.font = '12px Arial';
ctx.fillStyle = '#7f8c8d';
ctx.textAlign = 'center';
// 水平网格线 (Y轴)
for (let value = minValue; value <= maxValue; value += isMagnitude ? 10 : 30) {
const y = margin.top + height - (value - minValue) / (maxValue - minValue) * height;
ctx.beginPath();
ctx.moveTo(margin.left, y);
ctx.lineTo(margin.left + width, y);
ctx.stroke();
ctx.fillText(value.toString(), margin.left - 20, y + 4);
}
// 垂直网格线 (X轴) - 对数刻度
const logMin = Math.log10(minFreq);
const logMax = Math.log10(maxFreq);
const decades = Math.floor(logMax) - Math.floor(logMin);
for (let decade = Math.floor(logMin); decade <= Math.ceil(logMax); decade++) {
for (let multiplier = 1; multiplier <= 9; multiplier++) {
const freq = multiplier * Math.pow(10, decade);
if (freq < minFreq || freq > maxFreq) continue;
const logFreq = Math.log10(freq);
const x = margin.left + (logFreq - logMin) / (logMax - logMin) * width;
if (multiplier === 1) {
// 主刻度 (10^n)
ctx.beginPath();
ctx.moveTo(x, margin.top);
ctx.lineTo(x, margin.top + height);
ctx.stroke();
ctx.fillText(formatFrequency(freq), x, margin.top + height + 20);
} else {
// 次刻度
ctx.beginPath();
ctx.moveTo(x, margin.top + height - 5);
ctx.lineTo(x, margin.top + height);
ctx.stroke();
}
}
}
}
// 格式化频率显示
function formatFrequency(freq) {
if (freq >= 1000) {
return (freq/1000).toFixed(freq % 1000 === 0 ? 0 : 1) + 'k';
}
return freq.toString();
}
// 复数表示辅助函数
function 1j() { return { real: 0, imag: 1 }; }
// 初始绘制
drawBodePlots();
</script>
</body>
</html>
上述代码没有任何图像显示
最新发布