QCoDeS example with Rigol DSA800

The Rigol DSA800 series are USB-controlled spectrum analysers. This notebook shows how to connect to the instrument, acquire and save a trace with QCoDeS, use a marker, and, on -TG models, enable the tracking generator.

Connect to the instrument

  1. Make sure you have QCoDeS set up (see the QCoDeS website or my notebook 14 minutes to QCoDeS).

  2. Connect the analyser over USB and find its VISA resource string.

  3. Replace the example address below with your instrument resource and check that you get a connect message.

  4. A good test configuration is to connect the input of your DSA800 to a signal generator at 400 MHz and -10 dBm.

[ ]:
import sys
from pathlib import Path
from qcodes.instrument import Instrument

from qcodes_contrib_drivers.drivers.Rigol.Rigol_DSA800 import RigolDSA800

Instrument.close_all()

address = "USB0::0x1AB1::0x0960::DSA8A191400227::INSTR"  # Replace with your VISA resource
sa = RigolDSA800("sa", address) # Should print something like "Connected to Rigol DSA800 series spectrum analyzer with ID: DSA8A191400227"
sa.get_idn() # Should print something like "{'vendor': 'Rigol Technologies', 'model': 'DSA815', 'serial': 'DSA8A191400227', 'firmware': '00.01.18.00.02'}"

Initialise QCoDeS control

[ ]:
from qcodes.dataset import (
    Measurement,
    do0d,
    do1d,
    initialise_or_create_database_at,
    load_or_create_experiment,
    plot_by_id,
)

initialise_or_create_database_at("./test_rigol_dsa800.db")
load_or_create_experiment(
    experiment_name="testing_rigol_dsa800",
    sample_name="signal_source",
)

Set up the analyser and save a trace

This example configures a simple sweep around a signal near 400 MHz, runs a single sweep, and stores the trace together with the matching frequency axis.

[ ]:
# Set up instrument parameters for a spectrum analyzer measurement:

# Frequency range and points:
sa.frequency_center(400e6)
sa.frequency_span(50e6)
sa.sweep_points(1001)
# Display settings:
sa.reference_level(0)
sa.video_bandwidth_auto("ON")
# Detector setup:
sa.detector("RMS")
sa.resolution_bandwidth(100e3)
# Other settings:
sa.trace1.mode("WRIT") # Clear Write mode: each new trace overwrites the previous one

# Acquire trace data and frequency axis:
trace_dbm = sa.acquire_trace(trace_index=1)
freq_hz = sa.trace_axis()

# Set the instrument back to continuous sweeping
sa.run_continuous()

# Save the data to the database and plot it:
meas = Measurement()
meas.register_parameter(sa.trace_axis)
meas.register_parameter(sa.trace1.data, setpoints=(sa.trace_axis,))

with meas.run() as datasaver:
    datasaver.add_result(
        (sa.trace_axis, freq_hz),
        (sa.trace1.data, trace_dbm),
    )
    run_id = datasaver.run_id

plot_by_id(run_id)

Make a measurement using do0d()

Use the workhorse do0d(...) command to read traces.

There are two ways to do this. The usual one is to grab whatever is on the screen:

[ ]:
do0d(sa.trace1.data, do_plot=True)  # This reads the trace exactly as it is on the screen.

If you want to guarantee that all the data is fresh, you can do this:

  • Put the analyser in HOLD mode

  • Estimate the scan time

  • Trigger one sweep

  • Call do0d(...) after that sweep has finished.

Like this:

[ ]:
sa.resolution_bandwidth(3e3)   # Deliberately slow down the scan for demonstration purposes.

sa.hold()

sweep_time_s = sa.sweep_time()
sweep_count = sa.sweep_count()
if sweep_time_s is None or sweep_count is None:
    raise RuntimeError("The instrument did not return sweep timing information.")

estimated_scan_time_s = float(sweep_time_s) * int(sweep_count)
previous_timeout_s = sa.timeout()
if previous_timeout_s is None:
    scan_timeout_s = estimated_scan_time_s + 1.0
else:
    scan_timeout_s = max(float(previous_timeout_s), estimated_scan_time_s + 1.0)

try:
    sa.timeout(scan_timeout_s)
    sa.trigger_sweep()
    do0d(sa.trace1.data, do_plot=True)
finally:
    sa.timeout(previous_timeout_s)      # Restore the original timeout value to avoid affecting other operations.

sa.run_continuous() # Set the instrument back to continuous sweeping when you have finished.
sa.resolution_bandwidth(100e3)  # Restore the resolution bandwidth to a more typical value.

Make a measurement using do1d()

To acquire a 2D scan, we use do1d(). (This is how QCoDeS works.)

In this example, we acquire a trace versus a dummy sweep parameter and frequency.

[ ]:
from qcodes.parameters import ManualParameter

dummy_step = ManualParameter("dummy_step", label="Dummy step", unit="arb", initial_value=0)

sa.run_continuous()

do1d_dataset, _, _ = do1d(
    dummy_step,
    0,
    9,
    10,
    0.2,
    sa.trace1.data,
    do_plot=True,
    show_progress=True,
)

Use a marker on the latest trace

[ ]:
sa.marker1.enabled("ON")
sa.marker1.peak_search()

peak_frequency_hz = sa.marker1.x()
peak_amplitude = sa.marker1.y()
if peak_frequency_hz is None or peak_amplitude is None:
    raise RuntimeError("The marker did not return a valid readout.")

print(f"Peak frequency: {peak_frequency_hz/1e6:.3f} MHz")
print(f"Peak amplitude: {peak_amplitude:.2f} {sa.power_unit()}")

Optional: use the tracking generator

The following cells are intended for models with a tracking generator.

Set the tracking generator up and turn it on:

[ ]:
sa.tracking_generator.enable()
sa.tracking_generator.fixed_power_amplitude(-20)
sa.tracking_generator.power_mode("FIX")
print("Tracking generator enabled.")

Turn the tracking generator off:

[ ]:
sa.tracking_generator.disable()
print("Tracking generator disabled.")

Close the connection

[ ]:
sa.close()