import numpy as np
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
# Circuit parameters
Av_intrinsic = -357
Av_with_RB = -4.0
fH = 188e3 # Hz (188 kHz)
wH = 2 * np.pi * fH # rad/s
# Generate frequency range (10 Hz to 100 MHz)
freq = np.logspace(1, 8, 1000)
w = 2 * np.pi * freq
# Calculate transfer function for both cases
def calculate_response(Av, w, wH):
s = w / wH
# Transfer function: H(jw) = Av / (1 + jw/wH)
H = Av / (1 + 1j * s)
magnitude_dB = 20 * np.log10(np.abs(H))
phase_deg = np.angle(H) * (180 / np.pi)
return magnitude_dB, phase_deg
mag_intrinsic, phase_intrinsic = calculate_response(Av_intrinsic, w, wH)
mag_with_RB, phase_with_RB = calculate_response(Av_with_RB, w, wH)
# Create figure with professional styling
plt.style.use('seaborn-v0_8-darkgrid')
fig = plt.figure(figsize=(14, 10))
gs = GridSpec(2, 1, height_ratios=[1, 1], hspace=0.3)
# Color scheme
color_intrinsic = '#2563eb' # Blue
color_rb = '#dc2626' # Red
color_cutoff = '#059669' # Green
# ==================== MAGNITUDE PLOT ====================
ax1 = fig.add_subplot(gs[0])
# Plot magnitude responses
ax1.semilogx(freq, mag_intrinsic, color=color_intrinsic, linewidth=2.5,
label=f'Intrinsic Gain (Av = {Av_intrinsic})', zorder=3)
ax1.semilogx(freq, mag_with_RB, color=color_rb, linewidth=2.5, linestyle='--',
label=f'Gain with RB (Av = {Av_with_RB})', zorder=3)
# Mark cutoff frequency
ax1.axvline(fH, color=color_cutoff, linestyle=':', linewidth=2,
label=f'fH = {fH/1e3:.0f} kHz', alpha=0.7, zorder=2)
# Mark -3dB points
dc_gain_intrinsic = 20 * np.log10(np.abs(Av_intrinsic))
dc_gain_rb = 20 * np.log10(np.abs(Av_with_RB))
ax1.plot(fH, dc_gain_intrinsic - 3, 'o', color=color_intrinsic,
markersize=8, markerfacecolor='white', markeredgewidth=2, zorder=4)
ax1.plot(fH, dc_gain_rb - 3, 's', color=color_rb,
markersize=8, markerfacecolor='white', markeredgewidth=2, zorder=4)
# Add annotation for -3dB point
ax1.annotate(f'-3 dB\n({fH/1e3:.0f} kHz)',
xy=(fH, dc_gain_intrinsic - 3),
xytext=(fH*5, dc_gain_intrinsic - 3),
fontsize=10, ha='left',
bbox=dict(boxstyle='round,pad=0.5', facecolor='yellow', alpha=0.7),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0', lw=1.5))
# Add asymptotic lines (Bode approximation)
freq_flat = freq[freq < fH]
freq_slope = freq[freq >= fH]
ax1.semilogx(freq_flat, np.ones_like(freq_flat) * dc_gain_intrinsic,
color=color_intrinsic, linestyle=':', linewidth=1.5, alpha=0.5)
slope_intrinsic = dc_gain_intrinsic - 20 * np.log10(freq_slope / fH)
ax1.semilogx(freq_slope, slope_intrinsic,
color=color_intrinsic, linestyle=':', linewidth=1.5, alpha=0.5)
# Formatting
ax1.set_xlabel('Frequency (Hz)', fontsize=13, fontweight='bold')
ax1.set_ylabel('Magnitude (dB)', fontsize=13, fontweight='bold')
ax1.set_title('BJT Common-Emitter Amplifier - Frequency Response',
fontsize=15, fontweight='bold', pad=20)
ax1.grid(True, which='both', alpha=0.3, linewidth=0.5)
ax1.legend(loc='upper right', fontsize=11, framealpha=0.95, edgecolor='black')
ax1.set_xlim([10, 1e8])
# Add text box with key parameters
textstr = '\n'.join((
'Key Parameters:',
f'DC Gain (intrinsic): {dc_gain_intrinsic:.1f} dB',
f'DC Gain (with RB): {dc_gain_rb:.1f} dB',
f'Cutoff Frequency: {fH/1e3:.0f} kHz',
f'Roll-off: -20 dB/decade'
))
props = dict(boxstyle='round', facecolor='wheat', alpha=0.9, edgecolor='black')
ax1.text(0.02, 0.05, textstr, transform=ax1.transAxes, fontsize=10,
verticalalignment='bottom', bbox=props, family='monospace')
# ==================== PHASE PLOT ====================
ax2 = fig.add_subplot(gs[1])
# Plot phase responses
ax2.semilogx(freq, phase_intrinsic, color=color_intrinsic, linewidth=2.5,
label=f'Intrinsic Gain (Av = {Av_intrinsic})', zorder=3)
ax2.semilogx(freq, phase_with_RB, color=color_rb, linewidth=2.5, linestyle='--',
label=f'Gain with RB (Av = {Av_with_RB})', zorder=3)
# Mark cutoff frequency
ax2.axvline(fH, color=color_cutoff, linestyle=':', linewidth=2,
label=f'fH = {fH/1e3:.0f} kHz', alpha=0.7, zorder=2)
# Mark -225° point at cutoff
phase_at_cutoff_intrinsic = np.interp(fH, freq, phase_intrinsic)
ax2.plot(fH, phase_at_cutoff_intrinsic, 'o', color=color_intrinsic,
markersize=8, markerfacecolor='white', markeredgewidth=2, zorder=4)
# Add phase annotations
ax2.annotate(f'{phase_at_cutoff_intrinsic:.0f}°',
xy=(fH, phase_at_cutoff_intrinsic),
xytext=(fH*5, phase_at_cutoff_intrinsic),
fontsize=10, ha='left',
bbox=dict(boxstyle='round,pad=0.5', facecolor='lightblue', alpha=0.7),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0', lw=1.5))
# Add horizontal reference lines
ax2.axhline(-180, color='gray', linestyle='--', linewidth=1, alpha=0.5)
ax2.axhline(-225, color='gray', linestyle='--', linewidth=1, alpha=0.5)
ax2.axhline(-270, color='gray', linestyle='--', linewidth=1, alpha=0.5)
# Add reference line labels
ax2.text(15, -180, '-180°', fontsize=10, va='bottom', color='gray', fontweight='bold')
ax2.text(15, -225, '-225°', fontsize=10, va='bottom', color='gray', fontweight='bold')
ax2.text(15, -270, '-270°', fontsize=10, va='top', color='gray', fontweight='bold')
# Formatting
ax2.set_xlabel('Frequency (Hz)', fontsize=13, fontweight='bold')
ax2.set_ylabel('Phase (degrees)', fontsize=13, fontweight='bold')
ax2.set_title('Phase Response', fontsize=15, fontweight='bold', pad=20)
ax2.grid(True, which='both', alpha=0.3, linewidth=0.5)
ax2.legend(loc='lower left', fontsize=11, framealpha=0.95, edgecolor='black')
ax2.set_xlim([10, 1e8])
ax2.set_ylim([-280, -170])
ax2.set_yticks(np.arange(-270, -170, 15))
# Add text box with phase info
textstr_phase = '\n'.join((
'Phase Characteristics:',
'• -180° due to inverting gain',
'• -45° shift at cutoff',
'• -90° total from pole',
'• Approaches -270° at HF'
))
props_phase = dict(boxstyle='round', facecolor='lightcyan', alpha=0.9, edgecolor='black')
ax2.text(0.02, 0.95, textstr_phase, transform=ax2.transAxes, fontsize=10,
verticalalignment='top', bbox=props_phase, family='monospace')
# Overall figure adjustments
fig.suptitle('BJT Common-Emitter Amplifier Bode Plot\nβ=100, RC=4kΩ, RB=100kΩ, Cπ=50pF, Cμ=2pF',
fontsize=17, fontweight='bold', y=0.995)
plt.tight_layout()
plt.savefig('bjt_bode_plot.png', dpi=300, bbox_inches='tight', facecolor='white')
plt.show()
print("Professional Bode plot saved as 'bjt_bode_plot.png'")