HF2LI#
The Zurich Instruments HF2LI is a digital lock-in amplifier covering the frequency range between DC and 50 MHz. It is one of the first instrument developed by Zurich Instruments. Since the initial release of the HF2 a lot more Instruments have joined the family of Zurich Instrument and the features of LabOne where constantly upgraded. Never the less the HF2LI offers exceptional features and measurement performance and is of course still fully supported by LabOne and zhinst-toolkit. However it has a few specialities like a seperate Data Sever or the maximal support of API level 1.
Never the less qcodes offers a similiar handling of the HF2 compared to other devices. Since the HF2 uses a different data server one can not share a session with other devices.
[ ]:
from zhinst.qcodes import HF2
device = HF2("DEVXXXX", "localhost")
Polling DEMOD Samples#
To get samples for the device subscribe and poll is the prefered way. After the subscribe command the data server will the node changes in an internaly. In case of a sample node the values will be updated by the device in the respective sampling frequency. With the poll command the buffered data can be fetched.
Note:
The poll command is implemented at the session level since it returns all subscribed data within a session. It is not possible to poll only a single node if multiple nodes are subscribed.
[ ]:
import time
device.demods[0].sample.subscribe()
time.sleep(1)
device.demods[0].sample.unsubscribe()
poll_result = device.session.poll()
demod_sample = poll_result[device.demods[0].sample]
[ ]:
import matplotlib.pyplot as plt
_, axis = plt.subplots(1, 1)
axis.plot(demod_sample["x"])
axis.grid(True)
axis.set_title("X Data from the polled demod sample")
axis.set_xlabel("Sample")
axis.set_ylabel("Value")
plt.show()
PID Advisor#
Demonstrate how to connect to a Zurich Instruments HF2 Lock-in Amplifier and use the PID Advisor to set up an internal PLL control loop using the pid_advisor module.
Device Settings#
[ ]:
OUT_MIXER_CHANNEL = 6
with device.set_transaction():
device.oscs[0].freq(1e6)
device.sigouts[0].on(True)
device.sigouts[0].enables[OUT_MIXER_CHANNEL].value(True)
device.sigouts[0].range(1.0)
device.sigouts[0].amplitudes[OUT_MIXER_CHANNEL].value(1.0)
PID Advisor Module Settings#
[ ]:
pid_advisor = device.session.modules.pid_advisor
pid_advisor.device(device)
# Turn off auto-calc on param change. Enabled auto calculation can be used to
# automatically update response data based on user input.
pid_advisor.auto(False)
# Target bandwidth (Hz).
pid_advisor.pid.targetbw(10e3)
# PID advising mode (bit coded)
# bit 0: optimize/tune P
# bit 1: optimize/tune I
# bit 2: optimize/tune D
# Example: mode = 7: Optimize/tune PID
pid_advisor.pid.mode(7)
# PID index to use (first PLL of device: 0)
pid_advisor.index(0)
DUT Settings#
[ ]:
# DUT model
# source = 1: Lowpass first order
# source = 2: Lowpass second order
# source = 3: Resonator frequency
# source = 4: Internal PLL
# source = 5: VCO
# source = 6: Resonator amplitude
dut_source = 4
pid_advisor.dut.source(dut_source)
if dut_source == 4:
# HF2: Since the PLL and PID are 2 separate hardware units on the device, we need to
# additionally specify that the PID Advisor should model the HF2's PLL.
pid_advisor.pid.type("pll")
# Note: The PID Advisor is appropriate for optimizing the HF2's PLL parameters (pid/type set
# to 'pll') or the HF2's PID parameters (pid/type set to 'pid').
else:
# Other DUT parameters (not required for the internal PLL model)
pid_advisor.dut.gain(1.0)
pid_advisor.dut.bw(1000)
pid_advisor.dut.fcenter(15e6)
pid_advisor.dut.damping(0.1)
pid_advisor.dut.q(10e3)
# IO Delay of the feedback system describing the earliest response
# for a step change. This parameter does not affect the shape of
# the DUT transfer function
pid_advisor.dut.delay(0.0)
Run#
[ ]:
# Start values for the PID optimization. Zero
# values will imitate a guess. Other values can be
# used as hints for the optimization process.
pid_advisor.pid.p(0)
pid_advisor.pid.i(0)
pid_advisor.pid.d(0)
# Start the module thread
pid_advisor.raw_module.execute()
pid_advisor.calculate(1)
pid_advisor.wait_done(timeout=1000)
# print results
p_advised = pid_advisor.pid.p()
i_advised = pid_advisor.pid.i()
d_advised = pid_advisor.pid.d()
print(
f"The pidAdvisor calculated the following gains, P: {p_advised}, I: {i_advised}, "
f"D: {d_advised}."
)
Plot#
Bode Diagramm#
[ ]:
import matplotlib.pyplot as plt
import numpy as np
bode_result = pid_advisor.bode()
bode_complex_data = bode_result["x"] + 1j * bode_result["y"]
bw_advisor = pid_advisor.bw()
_, axes = plt.subplots(2, 1)
axes[0].plot(bode_result["grid"], 20 * np.log10(np.abs(bode_complex_data)))
axes[0].set_xscale("log")
axes[0].grid(True)
axes[0].set_title(
"Model response for internal PLL with "
f"P = {p_advised:.1f}, I = {i_advised:.1f},\n"
f"D = {d_advised:.5f} and bandwidth {(bw_advisor * 1e-3):.1f} kHz"
)
axes[0].set_ylabel("Bode Gain (dB)")
axes[0].autoscale(enable=True, axis="x", tight=True)
axes[1].plot(bode_result["grid"], np.angle(bode_complex_data) / np.pi * 180)
axes[1].set_xscale("log")
axes[1].grid(True)
axes[1].set_xlabel("Frequency (Hz)")
axes[1].set_ylabel("Bode Phase (deg)")
axes[1].autoscale(enable=True, axis="x", tight=True)
plt.show()
Step Response#
[ ]:
import matplotlib.pyplot as plt
step_result = pid_advisor.step()
_, axis = plt.subplots(1, 1)
axis.plot(step_result["grid"] * 1e6, step_result["x"])
axis.grid(True)
axis.set_title(
"Step response for internal PLL with "
f"P = {p_advised:0.1f}, I = {i_advised:0.1f},\n"
f"D = {d_advised:0.5f} and bandwidth {(bw_advisor * 1e-3):.1f} kHz"
)
axis.set_xlabel(r"Time ($\mu$s)")
axis.set_ylabel(r"Step Response")
axis.autoscale(enable=True, axis="x", tight=True)
axis.set_ylim([0.0, 1.05])
plt.show()
Upload values to devices#
[ ]:
pid_advisor.todevice(1)