Skip to content

WSU-Carbon-Lab/physics-labs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

inst-ctrl

Python interfaces for controlling Fluke digital multimeters, Siglent arbitrary waveform generators, and Rigol power supplies via PyVISA.

Overview

This package provides high-level Python interfaces for instrument control:

  • Fluke 45 and Fluke 8845A/8846A digital multimeters
  • Siglent SDG2042X arbitrary waveform generator
  • Rigol DP800 series programmable power supplies

All instruments support multiple connection methods (GPIB, USB, Ethernet, RS-232) and provide type-safe, well-documented APIs with comprehensive error handling.

Installation

Requirements

  • Python >= 3.13
  • NI-VISA or PyVISA-py backend
  • uv package manager (recommended)

Install with uv

uv pip install -e .

Install with pip

pip install -e .

Optional Dependencies

For pint unit support with Siglent instruments:

uv pip install -e ".[units]"

Quick Start

Fluke 45 Digital Multimeter

from inst_ctrl import Fluke45, Func, Rate

# Connect via GPIB
with Fluke45(gpib_address=3) as dmm:
    dmm.primary_function = Func.VDC
    dmm.rate = Rate.FAST
    voltage = dmm.primary()
    print(f"Voltage: {voltage} V")

Fluke 8845A/8846A Digital Multimeter

from inst_ctrl import Fluke88, Func

# Connect via Ethernet
with Fluke88(ip_address='192.168.1.100') as dmm:
    dmm.primary_function = Func.OHMS
    resistance = dmm.primary()
    print(f"Resistance: {resistance} Ohms")

Siglent SDG2042X Waveform Generator

from inst_ctrl import SiglentSDG2042X

# Auto-discover and connect
with SiglentSDG2042X() as sig_gen:
    sig_gen.channel = 1
    sig_gen.waveform_type = 'SINE'
    sig_gen.frequency = 1000
    sig_gen.amplitude = 1.0
    sig_gen.output_state = True

Rigol DP800 Power Supply

from inst_ctrl import RigolDP800, Channel

# Connect via Ethernet
with RigolDP800(ip_address='192.168.1.100') as psu:
    psu.channel = Channel.CH1
    psu.voltage = 5.0
    psu.current = 1.0
    psu.output = True

Connection Methods

Fluke Instruments

GPIB Connection:

dmm = Fluke45(gpib_address=3)
dmm.connect()

Ethernet Connection (Fluke 88xx only):

dmm = Fluke88(ip_address='192.168.1.100')
dmm.connect()

Serial Connection:

# Windows
dmm = Fluke45(serial_port='COM1')

# Linux/Mac
dmm = Fluke45(serial_port='/dev/ttyUSB0')
dmm.connect()

Direct Resource Name:

dmm = Fluke45(resource_name='GPIB0::3::INSTR')
dmm.connect()

Auto-Discovery:

# Automatically finds first Fluke instrument on GPIB bus
dmm = Fluke45()
dmm.connect()

Siglent Instruments

Auto-Discovery:

# Automatically finds first Siglent SDG instrument
sig_gen = SiglentSDG2042X()
sig_gen.connect()

Direct Resource Name:

sig_gen = SiglentSDG2042X(resource_name='USB0::0x0483::0x7540::SDG2042X12345678::INSTR')
sig_gen.connect()

Rigol Instruments

Ethernet Connection:

psu = RigolDP800(ip_address='192.168.1.100')
psu.connect()

USB Connection:

psu = RigolDP800(usb_serial='DP8C12345678')
psu.connect()

GPIB Connection:

psu = RigolDP800(gpib_address=5)
psu.connect()

Direct Resource Name:

psu = RigolDP800(resource_name='TCPIP0::192.168.1.100::INSTR')
psu.connect()

Auto-Discovery:

# Automatically finds first Rigol DP800 instrument
psu = RigolDP800()
psu.connect()

Fluke 45 Examples

Basic Voltage Measurement

from inst_ctrl import Fluke45, Func

with Fluke45(gpib_address=3) as dmm:
    dmm.primary_function = Func.VDC
    dmm.auto_range = True
    voltage = dmm.primary()
    print(f"DC Voltage: {voltage} V")

Dual Display Measurement

from inst_ctrl import Fluke45, Func, Func2

with Fluke45() as dmm:
    dmm.primary_function = Func.VDC
    dmm.secondary_function = Func2.FREQ
    voltage, frequency = dmm.both()
    print(f"Voltage: {voltage} V, Frequency: {frequency} Hz")

Manual Range and Rate Control

from inst_ctrl import Fluke45, Func, Rate

with Fluke45() as dmm:
    dmm.primary_function = Func.OHMS
    dmm.auto_range = False
    dmm.range = 3
    dmm.rate = Rate.SLOW
    resistance = dmm.primary()
    print(f"Resistance: {resistance} Ohms")

Relative Measurement Mode

with Fluke45() as dmm:
    dmm.primary_function = Func.VDC
    dmm.relative_mode = True
    dmm.set_relative_offset(1.0)
    relative_voltage = dmm.primary()
    print(f"Relative to 1.0V: {relative_voltage} V")

Compare Mode

with Fluke45() as dmm:
    dmm.primary_function = Func.VDC
    dmm.compare_mode = True
    dmm.compare_hi = 10.0
    dmm.compare_lo = 5.0
    result = dmm.compare_result
    print(f"Compare result: {result}")  # 'HI', 'LO', or 'PASS'

Trigger Control

from inst_ctrl import Fluke45, TriggerMode

with Fluke45() as dmm:
    dmm.trigger_mode = TriggerMode.EXTERNAL
    dmm.trigger()
    measurement = dmm.primary()

Fluke 8845A/8846A Examples

Basic Measurement

from inst_ctrl import Fluke88, Func

with Fluke88(ip_address='192.168.1.100') as dmm:
    dmm.primary_function = Func.VDC
    dmm.auto_range = True
    voltage = dmm.primary()
    print(f"Voltage: {voltage} V")

Measurement Rate Control

from inst_ctrl import Fluke88, Func, Rate

with Fluke88(gpib_address=1) as dmm:
    dmm.primary_function = Func.OHMS
    dmm.rate = Rate.FAST
    resistance = dmm.primary()

Siglent SDG2042X Examples

Basic Sine Wave Generation

from inst_ctrl import SiglentSDG2042X

with SiglentSDG2042X() as sig_gen:
    sig_gen.channel = 1
    sig_gen.waveform_type = 'SINE'
    sig_gen.frequency = 1000
    sig_gen.amplitude = 2.0
    sig_gen.offset = 0.5
    sig_gen.output_state = True

Configure Waveform with All Parameters

with SiglentSDG2042X() as sig_gen:
    sig_gen.channel = 1
    sig_gen.configure_waveform(
        waveform_type='SINE',
        frequency=1000,
        amplitude=2.0,
        offset=0.5,
        phase=45
    )
    sig_gen.output_state = True

Square Wave with Duty Cycle

with SiglentSDG2042X() as sig_gen:
    sig_gen.channel = 1
    sig_gen.waveform_type = 'SQUARE'
    sig_gen.frequency = 10000
    sig_gen.amplitude = 3.3
    sig_gen.set_duty_cycle(25.0)
    sig_gen.output_state = True

Pulse Waveform

with SiglentSDG2042X() as sig_gen:
    sig_gen.channel = 1
    sig_gen.waveform_type = 'PULSE'
    sig_gen.frequency = 1000
    sig_gen.amplitude = 5.0
    sig_gen.set_pulse_width(1e-3)
    sig_gen.set_rise_time(1e-6)
    sig_gen.set_fall_time(1e-6)
    sig_gen.output_state = True

Ramp Waveform with Symmetry

with SiglentSDG2042X() as sig_gen:
    sig_gen.channel = 1
    sig_gen.waveform_type = 'RAMP'
    sig_gen.frequency = 500
    sig_gen.amplitude = 2.0
    sig_gen.set_symmetry(75.0)
    sig_gen.output_state = True

Using Pint Units

from inst_ctrl import SiglentSDG2042X

sig_gen = SiglentSDG2042X(unit_mode='pint')
sig_gen.connect()
sig_gen.channel = 1
sig_gen.frequency = 1000

freq = sig_gen.frequency
print(f"Frequency: {freq}")  # Prints as pint Quantity

Load Impedance Configuration

with SiglentSDG2042X() as sig_gen:
    sig_gen.channel = 1
    sig_gen.load_impedance = 'HiZ'
    sig_gen.load_impedance = 50

Arbitrary Waveform Selection

with SiglentSDG2042X() as sig_gen:
    waveforms = sig_gen.list_waveforms()
    for wv in waveforms:
        print(f"Index {wv['index']}: {wv['name']}")

    sig_gen.channel = 1
    sig_gen.waveform_type = 'ARB'
    sig_gen.select_arbitrary_waveform(index=1)

Rigol DP800 Examples

Basic Power Supply Control

from inst_ctrl import RigolDP800, Channel

with RigolDP800(ip_address='192.168.1.100') as psu:
    psu.channel = Channel.CH1
    psu.voltage = 5.0
    psu.current = 1.0
    psu.output = True

Set Voltage and Current Together

with RigolDP800() as psu:
    psu.apply(voltage=12.0, current=2.0, channel=Channel.CH1)
    psu.output_on(Channel.CH1)

Measure Output Parameters

with RigolDP800() as psu:
    psu.channel = Channel.CH1
    voltage = psu.measured_voltage
    current = psu.measured_current
    power = psu.measured_power
    print(f"V: {voltage}V, I: {current}A, P: {power}W")

Measure All Parameters at Once

with RigolDP800() as psu:
    voltage, current, power = psu.measure_all(Channel.CH1)
    print(f"V: {voltage}V, I: {current}A, P: {power}W")

Multi-Channel Control

with RigolDP800() as psu:
    # Configure and enable CH1
    psu.apply(voltage=5.0, current=1.0, channel=Channel.CH1)
    psu.output_on(Channel.CH1)

    # Configure and enable CH2
    psu.apply(voltage=12.0, current=2.0, channel=Channel.CH2)
    psu.output_on(Channel.CH2)

Over-Voltage and Over-Current Protection

with RigolDP800() as psu:
    psu.channel = Channel.CH1
    psu.set_ovp(15.0)  # Set OVP to 15V
    psu.enable_ovp(True)  # Enable OVP

    psu.set_ocp(2.5)  # Set OCP to 2.5A
    psu.enable_ocp(True)  # Enable OCP

Query Settings

with RigolDP800() as psu:
    voltage, current = psu.get_settings(Channel.CH1)
    print(f"CH1 settings: {voltage}V, {current}A")

API Reference

Fluke 45

Connection

  • Fluke45(gpib_address=None, serial_port=None, resource_name=None, timeout=5000)
  • connect() - Establish connection
  • disconnect() - Close connection
  • check_connection() - Verify communication

Measurement Functions

  • primary_function - Set/get primary measurement function (Func enum)
  • secondary_function - Set/get secondary measurement function (Func2 enum, Fluke 45 only)
  • primary() - Trigger and read primary measurement
  • secondary() - Trigger and read secondary measurement (Fluke 45 only)
  • both() - Trigger and read both displays (Fluke 45 only)
  • primary_value - Read current primary value without triggering
  • secondary_value - Read current secondary value without triggering

Configuration

  • auto_range - Enable/disable auto range
  • range - Set manual range (1-7)
  • rate - Set measurement rate (Rate enum: SLOW, MEDIUM, FAST)
  • trigger_mode - Set trigger mode (TriggerMode enum)
  • trigger() - Send trigger command

Special Modes

  • relative_mode - Enable/disable relative measurement
  • set_relative_offset(offset) - Set relative offset value
  • db_mode - Enable/disable dB measurement
  • set_db_reference(impedance) - Set dB reference impedance
  • hold_mode - Enable/disable hold mode
  • min_max_mode(mode) - Set min/max tracking mode
  • compare_mode - Enable/disable compare mode
  • compare_hi - Set compare high limit
  • compare_lo - Set compare low limit
  • compare_result - Get compare result

System Commands

  • reset() - Reset instrument to defaults
  • clear_status() - Clear status registers
  • self_test() - Execute self-test
  • read_status_byte() - Read status byte
  • read_event_status() - Read event status register

Fluke 8845A/8846A

Fluke88 inherits from Fluke45 and provides the same interface. Key differences:

  • Supports Ethernet connections via ip_address parameter
  • Uses SCPI commands internally
  • Does not support secondary display (Func2)
  • secondary() and both() methods raise exceptions

Siglent SDG2042X

Connection

  • SiglentSDG2042X(resource_name=None, timeout=5000, unit_mode='tuple')
  • connect() - Establish connection
  • disconnect() - Close connection
  • check_connection() - Verify communication

Channel Control

  • channel - Set active channel (1 or 2)

Waveform Parameters

  • waveform_type - Set/get waveform type ('SINE', 'SQUARE', 'RAMP', 'PULSE', 'NOISE', 'ARB', 'DC', 'PRBS', 'IQ')
  • frequency - Set/get frequency
  • amplitude - Set/get amplitude
  • offset - Set/get DC offset
  • phase - Set/get phase

Waveform-Specific Parameters

  • get_duty_cycle() / set_duty_cycle(duty) - For SQUARE/PULSE waveforms
  • get_symmetry() / set_symmetry(symmetry) - For RAMP waveforms
  • get_pulse_width() / set_pulse_width(width) - For PULSE waveforms
  • get_rise_time() / set_rise_time(rise_time) - For PULSE waveforms
  • get_fall_time() / set_fall_time(fall_time) - For PULSE waveforms

Output Control

  • output_state - Enable/disable output
  • load_impedance - Set/get load impedance ('HiZ' or numeric value)

Convenience Methods

  • configure_waveform(waveform_type, frequency, amplitude, offset=0, phase=0) - Configure all parameters at once
  • list_waveforms() - List available arbitrary waveforms
  • select_arbitrary_waveform(index=None, name=None) - Select arbitrary waveform
  • get_all_parameters() - Get raw parameter string

Parameter Limits

  • limits - ParameterLimits object for validation
    • limits.freq_min, limits.freq_max
    • limits.amp_min, limits.amp_max
    • limits.offset_min, limits.offset_max
    • limits.phase_min, limits.phase_max
    • limits.reset_to_defaults()

Rigol DP800

Connection

  • RigolDP800(resource_name=None, ip_address=None, usb_serial=None, gpib_address=None, timeout=5000)
  • connect() - Establish connection
  • disconnect() - Close connection
  • check_connection() - Verify communication

Channel Control

  • channel - Set/get active channel (Channel enum, string, or int)

Voltage and Current Control

  • voltage - Set/get voltage setting for active channel (volts)
  • current - Set/get current limit for active channel (amperes)
  • apply(voltage, current, channel=None) - Set both voltage and current for specified channel
  • get_settings(channel=None) - Query voltage and current settings (returns tuple)

Measurement

  • measured_voltage - Measure actual output voltage for active channel
  • measured_current - Measure actual output voltage
  • measured_power - Measure actual output power (watts)
  • power - Convenience alias for measured_power
  • measure_all(channel=None) - Measure voltage, current, and power (returns tuple)

Output Control

  • output - Set/get output state (True/False or 'ON'/'OFF')
  • output_on(channel=None) - Enable output for specified channel
  • output_off(channel=None) - Disable output for specified channel

Protection

  • set_ovp(value, channel=None) - Set over-voltage protection level
  • set_ocp(value, channel=None) - Set over-current protection level
  • enable_ovp(state=True, channel=None) - Enable/disable over-voltage protection
  • enable_ocp(state=True, channel=None) - Enable/disable over-current protection

System Commands

  • reset() - Reset instrument to factory defaults
  • clear_status() - Clear status registers
  • self_test() - Execute self-test (returns True if passed)

Enums and Constants

Fluke Enums

  • Func - Primary measurement functions: VDC, VAC, VACDC, ADC, AAC, OHMS, FREQ, DIODE
  • Func2 - Secondary measurement functions (Fluke 45 only): VDC, VAC, ADC, AAC, OHMS, FREQ, DIODE, CLEAR
  • Rate - Measurement rates: SLOW, MEDIUM, FAST
  • TriggerMode - Trigger modes: INTERNAL, EXTERNAL, EXTERNAL_NO_DELAY, EXTERNAL_DELAY, EXTERNAL_REAR_NO_DELAY, EXTERNAL_REAR_DELAY

Siglent Waveform Types

  • 'SINE', 'SQUARE', 'RAMP', 'PULSE', 'NOISE', 'ARB', 'DC', 'PRBS', 'IQ'

Rigol Enums

  • Channel - Power supply channels: CH1, CH2, CH3

Error Handling

All instruments raise specific exception types:

Fluke Exceptions

  • FlukeError - Base exception
  • FlukeConnectionError - Connection failures
  • FlukeValidationError - Invalid parameter values
  • FlukeCommandError - Command execution failures

Siglent Exceptions

  • SiglentError - Base exception
  • SiglentConnectionError - Connection failures
  • SiglentValidationError - Invalid parameter values
  • SiglentCommandError - Command execution failures

Rigol Exceptions

  • RigolError - Base exception
  • RigolConnectionError - Connection failures
  • RigolValidationError - Invalid parameter values
  • RigolCommandError - Command execution failures

Example Error Handling

from inst_ctrl import Fluke45, FlukeConnectionError, FlukeValidationError

try:
    dmm = Fluke45(gpib_address=3)
    dmm.connect()
    dmm.primary_function = 'INVALID'
except FlukeConnectionError as e:
    print(f"Connection failed: {e}")
except FlukeValidationError as e:
    print(f"Invalid parameter: {e}")

Context Manager Usage

All instruments support context manager syntax for automatic connection management:

from inst_ctrl import Fluke45, SiglentSDG2042X

# Automatically connects on entry, disconnects on exit
with Fluke45(gpib_address=3) as dmm:
    voltage = dmm.primary()

with SiglentSDG2042X() as sig_gen:
    sig_gen.frequency = 1000
    sig_gen.output_state = True

with RigolDP800(ip_address='192.168.1.100') as psu:
    psu.voltage = 5.0
    psu.output = True

Type Safety

All functions are fully typed with type hints. The package uses strict typing practices:

  • Functions must have complete type annotations
  • Variables are typed only when ambiguous
  • Type narrowing ensures safe instrument access after connection verification

Requirements

  • Python >= 3.13
  • pyvisa >= 1.16.1
  • NI-VISA or PyVISA-py backend
  • pint >= 0.25.2 (optional, for unit support)

Development

Project Structure

inst_ctrl/
├── src/
│   └── inst_ctrl/
│       ├── __init__.py
│       ├── fluke.py      # Fluke 45 and 8845A/8846A interfaces
│       ├── siglent.py    # Siglent SDG2042X interface
│       └── rigol.py      # Rigol DP800 series power supply interface
├── pyproject.toml
└── README.md

Building

uv build

License

[Add your license information here]

Contributing

[Add contribution guidelines here]

About

Instrument control scripts for the physics 410 labs

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages