🔬

Calculate Gain of OpAmp and Find -3dB point in Jupyter Notebooks

📘 Documentation and Examples for Jupyter Notebooks:

📁 Jupyter Notebooks file:

💡
When you have opened the Jupyter notebook app, drag the .ipynb file into the window. Alternatively open the file in a new tab and save it with the suffix .ipynb using CTRL+S.

🧠 Code Explanation

  • Importing libraries
    • time for delays. numpy for numerical work. matplotlib.pyplot for plotting. rp_overlay for FPGA overlay support. rp for Red Pitaya control.
import time import numpy as np from matplotlib import pyplot as plt from rp_overlay import overlay import rp
  • Initialization
    • Loads the FPGA overlay and initializes the Red Pitaya API so generation and acquisition calls are available.
# Initialize Red Pitaya overlay fpga = overlay() rp.rp_Init()
  • Constants for the experiment
    • Defines sweep start, step and max frequency, output amplitude, acquisition length, decimation and derived sampling rate. Stores the first measured gain as reference for −3 dB detection.
start_freq = 100 # Hz freq_step = 100 # Hz increment max_freq = 5000 # Hz ampl = 1 # V N = 16384 # samples initial_gain = None dec = rp.RP_DEC_128 sampling_rate = 125e6 / dec
  • Acquisition settings
    • Sets trigger level and delay, and selects AWG positive edge as the trigger source for synchronous capture.
trig_lvl = 0.5 trig_dly = 0 acq_trig_sour = rp.RP_TRIG_SRC_AWG_PE
  • Signal generation
    • Resets generator and acquisition, configures a sine on CH1 at the current sweep frequency and amplitude, enables output, and arms a software trigger.
rp.rp_GenReset() rp.rp_AcqReset() print(f"Generating {freq} Hz signal") rp.rp_GenWaveform(rp.RP_CH_1, rp.RP_WAVEFORM_SINE) rp.rp_GenFreqDirect(rp.RP_CH_1, freq) rp.rp_GenAmp(rp.RP_CH_1, ampl) rp.rp_GenOutEnable(rp.RP_CH_1) rp.rp_GenTriggerOnly(rp.RP_CH_1)
  • Signal acquisition
    • Applies decimation, trigger level and delay. Sets CH2 gain to HV range for headroom. Starts acquisition and sets the trigger source.
rp.rp_AcqSetDecimation(dec) rp.rp_AcqSetTriggerLevel(rp.RP_T_CH_1, trig_lvl) rp.rp_AcqSetTriggerDelay(trig_dly) rp.rp_AcqSetGain(rp.RP_CH_2, rp.RP_HIGH) rp.rp_AcqStart() rp.rp_AcqSetTriggerSrc(acq_trig_sour)
  • Wait and trigger
    • Gives the acquisition engine time to arm, then issues a generation trigger so the capture can start reliably.
time.sleep(1) rp.rp_GenTriggerOnly(rp.RP_CH_1)
  • Trigger and buffer fill wait
    • Spins until the trigger occurs and the buffer is fully populated, ensuring complete frames are retrieved.
# Wait for acquisition trigger while rp.rp_AcqGetTriggerState()[1] != rp.RP_TRIG_STATE_TRIGGERED: pass # Wait until buffer is full while not rp.rp_AcqGetBufferFillState()[1]: pass
  • Data retrieval
    • Reads oldest samples from both channels and converts them to numpy arrays for analysis and plotting.
fbuff_ch1 = rp.fBuffer(N) rp.rp_AcqGetOldestDataV(rp.RP_CH_1, N, fbuff_ch1) fbuff_ch2 = rp.fBuffer(N) rp.rp_AcqGetOldestDataV(rp.RP_CH_2, N, fbuff_ch2) # Convert the buffer data to numpy arrays data_V_ch1 = np.array([fbuff_ch1[i] for i in range(N)]) data_V_ch2 = np.array([fbuff_ch2[i] for i in range(N)])
  • Phase analysis at 100 Hz
    • Optional FFT-based phase measurement at a known good frequency to confirm circuit orientation and reference behavior.
if freq == 100: # Example: phase check at low frequency (FFT-based) # Compute phase difference to detect inverting vs non-inverting behavior pass
  • Gain calculation and reference
    • Computes Vin and Vout peak‑to‑peak amplitudes and gain in dB. Stores first gain as reference for −3 dB comparison.
input_amplitude = np.max(data_V_ch1) - np.min(data_V_ch1) output_amplitude = np.max(data_V_ch2) - np.min(data_V_ch2) gain = 20 * np.log10(output_amplitude / input_amplitude) if initial_gain is None: initial_gain = gain
  • −3 dB point detection
    • Checks for a 3 dB drop from the initial gain to find the bandwidth limit, then captures plots and exits the sweep.
if gain <= initial_gain - 3: # Reached −3 dB point: plot and break the sweep pass
  • Plotting
    • Visualizes Vin and Vout for the current frequency to validate measurement stability and clipping headroom.
plt.figure(figsize=(10, 5)) plt.plot(data_V_ch1, label='Input Signal (Vin)') plt.plot(data_V_ch2, label='Output Signal (Vout)') plt.title(f'Signals at {freq} Hz') plt.xlabel('Sample Number') plt.ylabel('Voltage (V)') plt.legend() plt.grid(True) plt.show()
  • Cleanup
    • Releases Red Pitaya resources to leave the device in a clean state.
rp.rp_Release()

Full Code

import time import numpy as np from matplotlib import pyplot as plt from rp_overlay import overlay import rp # Initialize Red Pitaya overlay fpga = overlay() rp.rp_Init() start_freq = 100 # Hz freq_step = 100 # Hz increment max_freq = 5000 # Hz ampl = 1 # V N = 16384 # samples initial_gain = None dec = rp.RP_DEC_128 sampling_rate = 125e6 / dec trig_lvl = 0.5 trig_dly = 0 acq_trig_sour = rp.RP_TRIG_SRC_AWG_PE rp.rp_GenReset() rp.rp_AcqReset() print(f"Generating {freq} Hz signal") rp.rp_GenWaveform(rp.RP_CH_1, rp.RP_WAVEFORM_SINE) rp.rp_GenFreqDirect(rp.RP_CH_1, freq) rp.rp_GenAmp(rp.RP_CH_1, ampl) rp.rp_GenOutEnable(rp.RP_CH_1) rp.rp_GenTriggerOnly(rp.RP_CH_1) rp.rp_AcqSetDecimation(dec) rp.rp_AcqSetTriggerLevel(rp.RP_T_CH_1, trig_lvl) rp.rp_AcqSetTriggerDelay(trig_dly) rp.rp_AcqSetGain(rp.RP_CH_2, rp.RP_HIGH) rp.rp_AcqStart() rp.rp_AcqSetTriggerSrc(acq_trig_sour) time.sleep(1) rp.rp_GenTriggerOnly(rp.RP_CH_1) # Wait for acquisition trigger while rp.rp_AcqGetTriggerState()[1] != rp.RP_TRIG_STATE_TRIGGERED: pass # Wait until buffer is full while not rp.rp_AcqGetBufferFillState()[1]: pass fbuff_ch1 = rp.fBuffer(N) rp.rp_AcqGetOldestDataV(rp.RP_CH_1, N, fbuff_ch1) fbuff_ch2 = rp.fBuffer(N) rp.rp_AcqGetOldestDataV(rp.RP_CH_2, N, fbuff_ch2) # Convert the buffer data to numpy arrays data_V_ch1 = np.array([fbuff_ch1[i] for i in range(N)]) data_V_ch2 = np.array([fbuff_ch2[i] for i in range(N)]) if freq == 100: # Example: phase check at low frequency (FFT-based) # Compute phase difference to detect inverting vs non-inverting behavior pass input_amplitude = np.max(data_V_ch1) - np.min(data_V_ch1) output_amplitude = np.max(data_V_ch2) - np.min(data_V_ch2) gain = 20 * np.log10(output_amplitude / input_amplitude) if initial_gain is None: initial_gain = gain if gain <= initial_gain - 3: # Reached −3 dB point: plot and break the sweep pass plt.figure(figsize=(10, 5)) plt.plot(data_V_ch1, label='Input Signal (Vin)') plt.plot(data_V_ch2, label='Output Signal (Vout)') plt.title(f'Signals at {freq} Hz') plt.xlabel('Sample Number') plt.ylabel('Voltage (V)') plt.legend() plt.grid(True) plt.show() rp.rp_Release()