Skip to content

Input-Scaling Verification

Verify the whole input chain — physical gain knobs included — by measuring a source of known level and comparing against the volts pydvma predicts. Unlike a relative level meter or coherence check, this validates absolute scaling.

Known-Source Verification

verify_input_scaling

verify_input_scaling(settings, source='loopback', freq=997.0, level_vrms=0.1, duration=2.0, tol=0.05, channels=None)

Verify the WHOLE input chain -- physical gain knobs included -- by measuring a source of KNOWN level and comparing against the volts pydvma predicts.

This is the check a coherence/level meter cannot do: those confirm a signal is present and roughly the right shape, but nothing upstream knows the true input level, so a wrong preamp gain, a wrong VmaxNI/VmaxSC, or a stale input_gain_db all pass silently. Comparing against a KNOWN source closes that gap.

Two source options, with different validity:

  • 'loopback' (default): pydvma generates a plain sine and plays it out the configured AO device. On an NI device (device_driver='nidaq'/output_device_driver='nidaq') the AO voltage is hardware-calibrated, so a physical AO->AI BNC cable exercises and verifies the WHOLE analogue input path with no extra equipment. On a sound card the analogue output level depends on an uncalibrated volume control, so this only proves chain CONSISTENCY (repeatable, internally coherent), not absolute scaling; a warning is printed (not a refusal -- consistency checks are still useful) when settings.output_device_driver == 'soundcard'.
  • An object with set_sine(freq_hz, vrms) and output(on) methods, e.g. RigolDG1022Z: the object is commanded to the requested tone, switched on, given ~0.5 s to settle, then settings captures it with no output argument (nothing is played by pydvma). The source is switched off again afterwards regardless of outcome. This is the only path that verifies absolute scaling on a sound card whose loopback is DIGITAL (e.g. a Scarlett 2i2's inputs 3/4 copy the output stream pre-preamp, so a digital loopback cannot see the analogue input gain).

Per-channel attenuators, transducer sensitivities and channel_sensitivities calibration are OUT OF SCOPE: this function checks the digitiser chain in volts (the same domain MySettings.channel_sensitivities is applied on top of), not any downstream engineering-unit calibration.

Parameters:

  • settings (MySettings) –

    Acquisition configuration; captured via a shallow copy internally, never mutated in place. Its fs/channels/device_driver govern the capture.

  • source (str or object, default: 'loopback' ) –

    'loopback' (default) or an instrument object exposing set_sine(freq_hz, vrms) and output(on) (e.g. RigolDG1022Z).

  • freq (float, default: 997.0 ) –

    Tone frequency in Hz (default 997.0 -- avoids mains harmonics (50/60 Hz and multiples) and round-number divisor artefacts that 1000 Hz risks). Must satisfy 0 < freq < 0.8 * (settings.fs / 2).

  • level_vrms (float, default: 0.1 ) –

    Known tone RMS amplitude in volts (default 0.1) -- this is also the "expected" value each measured channel is compared against.

  • duration (float, default: 2.0 ) –

    Capture duration in seconds (default 2.0).

  • tol (float, default: 0.05 ) –

    Fractional tolerance on the measured/expected ratio (default 0.05, i.e. +-5%, about +-0.42 dB). A channel passes when abs(ratio - 1) <= tol.

  • channels (list of int or None, default: None ) –

    Channel indices to check (default None = every channel, range(settings.channels)).

Returns a list of one dict per checked channel: {'channel': int, 'measured_vrms': float, 'expected_vrms': float, 'ratio': float, 'error_db': float, 'ok': bool}, where expected_vrms is level_vrms for every channel (per-channel sensitivity is out of scope, see above) and ok is abs(ratio - 1) <= tol. As a side effect, prints a small aligned per-channel table with a PASS/FAIL verdict, followed by either "input scaling verified" or, per failing channel, a line like "channel 1 reads +2.3 dB high -- check the gain setting".

Rigol DG1022Z SCPI Wrapper

RigolDG1022Z is a minimal SCPI wrapper (via pyvisa) used as the known-level source for verify_input_scaling on hardware whose loopback cannot verify absolute scaling (e.g. a sound card's digital loopback, which copies the output stream pre-preamp).

RigolDG1022Z

Minimal SCPI wrapper for a Rigol DG1022Z (or compatible DG1000Z) function generator, used as a known-level source for verify_input_scaling.

Talks VISA via pyvisa (pyvisa-py backend, no NI-VISA dependency). Every method issues plain SCPI writes and is safe to call with nothing connected except the instrument itself — there is no polling loop or blocking wait beyond the VISA I/O timeout.

Amplitude is always commanded in Vrms, not Vpp: the DG1022Z defaults to Vpp, but its :SOURn:VOLT:UNIT command accepts VRMS directly (confirmed in the DG1022Z working notes, section 3), so set_sine switches the unit mode explicitly rather than converting via the x2*sqrt(2) Vpp<->Vrms factor. That keeps the amplitude math (and the number visible on the instrument's own display) in the same units end to end, with no extra place for an RMS/peak mixup to creep in.

Use as a context manager to guarantee the output relay is switched OFF when done, even if the code in between raises:

>>> with RigolDG1022Z() as gen:              # doctest: +SKIP
...     gen.set_sine(997, 0.1)
...     gen.output(True)
...     ...
# output is OFF here, on ANY exit path

Attributes

_rm instance-attribute
_rm = pyvisa.ResourceManager('@py')
resource instance-attribute
resource = resource
_inst instance-attribute
_inst = self._rm.open_resource(resource)

Methods:

__init__
__init__(resource=None)

Open a VISA session to a Rigol DG1022Z.

Parameters:

  • resource (str or None, default: None ) –

    Explicit VISA resource string (e.g. 'USB0::0x1AB1::0x0642::DG1ZA000000::INSTR'). None (default) auto-discovers via pyvisa's list_resources() and picks the first resource string containing 'DG1'. Raises ValueError naming every resource that WAS found (so a wrong-but-present instrument is easy to spot) if none match.

Raises ImportError with pip-install guidance if pyvisa is not installed, and ValueError if auto-discovery finds no DG1* resource.

set_sine
set_sine(freq_hz, vrms, channel=1)

Command a sine wave at freq_hz Hz, vrms V RMS, on channel.

Sends, in order (order matters: the DG1022Z notes warn that changing waveform shape while in Vrms mode recalculates the amplitude, so the shape is set first, then the level) with n = channel:

  • :SOURn:FUNC SIN
  • :OUTPn:LOAD INF — assume a high-impedance load. A DAQ or scope input IS high-Z; leaving the DG1022Z's default 50 ohm load assumption would make it report (and the RMS-mode math compute) exactly double the voltage that actually appears across a high-Z load, per the notes' load-mismatch gotcha.
  • :SOURn:VOLT:UNIT VRMS
  • :SOURn:VOLT <vrms>
  • :SOURn:VOLT:OFFS 0
  • :SOURn:FREQ <freq_hz>

Parameters:

  • freq_hz (float) –

    Sine frequency in Hz.

  • vrms (float) –

    Sine RMS amplitude in volts.

  • channel (int, default: 1 ) –

    DG1022Z output channel, 1 or 2 (default 1).

output
output(on, channel=1)

Switch channel's output relay ON or OFF.

Sends :OUTPn:STAT ON or :OUTPn:STAT OFF (n = channel).

Parameters:

  • on (bool) –

    True enables the output, False disables it.

  • channel (int, default: 1 ) –

    DG1022Z output channel, 1 or 2 (default 1).

close
close()

Return the front panel to local control and close the VISA session.

Sends :SYST:LOCal — SCPI control locks the front panel out until this is sent (or the instrument's own Local key is pressed) — before closing the underlying VISA resource. Safe to call more than once.

__enter__
__enter__()
__exit__
__exit__(exc_type, exc_val, exc_tb)

Switch the output OFF and close, on every exit path including an exception — a bench source left running unattended is exactly the failure mode this class exists to avoid.