PyVISA
PyVISADocs

Keysight Instruments

Control Keysight instruments with PyVISA - oscilloscopes, multimeters, signal generators, network analyzers, and power supplies with examples and optimization tips.

Programming Keysight instruments with PyVISA. Covers major instrument families with examples, performance tips, and troubleshooting.

Supported Keysight Instrument Families

Digital Multimeters

  • 34461A/34470A (6½ and 7½ digit Truevolt DMMs)
  • 34401A (6½ digit legacy)
  • U3606A (Handheld multimeters)

Oscilloscopes

  • InfiniiVision Series: 1000X, 2000X, 3000X, 4000X
  • Infiniium Series: 90000A, 90000X, Z-Series
  • Portable: U1600A handheld series

Signal Generators

  • 33500B Series (Function/Arbitrary Waveform Generators)
  • E8257D/E8267D (PSG Vector Signal Generators)
  • N5182A/N5172B (MXG Signal Generators)

Network Analyzers

  • E5071C/E5063A (ENA Series)
  • N9918A (FieldFox Handheld)
  • P9372A (USB Vector Network Analyzers)

Power Supplies

  • E36300 Series (Triple output)
  • N6700/N6900 Series (Modular power systems)
  • U8000/U3600 Series (DC power supplies)

Quick Connection Guide

USB Connection (Most Common)

import pyvisa

# Connect to Keysight instrument via USB
rm = pyvisa.ResourceManager()

# Find Keysight instruments (Vendor ID: 0x2A8D)
resources = rm.list_resources()
keysight_instruments = [r for r in resources if "0x2A8D" in r]

print("Found Keysight instruments:")
for instrument in keysight_instruments:
    try:
        inst = rm.open_resource(instrument)
        idn = inst.query("*IDN?")
        print(f"  {instrument}: {idn.strip()}")
        inst.close()
    except:
        print(f"  {instrument}: Connection failed")

Ethernet/LAN Connection

# Direct IP connection (fastest)
scope_ip = "192.168.1.100"
scope = rm.open_resource(f"TCPIP0::{scope_ip}::5025::SOCKET")

# VXI-11 connection (more compatible)
scope_vxi11 = rm.open_resource(f"TCPIP0::{scope_ip}::inst0::INSTR")

# Auto-discovery using Keysight Connection Expert
# (Install Keysight IO Libraries Suite first)

1. Digital Multimeters (34461A/34470A)

Basic Setup and Measurement

class Keysight34461A:
    """Keysight 34461A/34470A Truevolt DMM Controller"""
    
    def __init__(self, resource_string):
        self.rm = pyvisa.ResourceManager()
        self.dmm = self.rm.open_resource(resource_string)
        
        # Configure for optimal performance
        self.dmm.timeout = 10000
        self.dmm.write_termination = '\n'
        self.dmm.read_termination = '\n'
        
        # Verify connection
        idn = self.dmm.query("*IDN?")
        if "34461A" not in idn and "34470A" not in idn:
            raise Exception(f"Expected Keysight 34461A/34470A, got: {idn}")
        
        print(f"Connected to: {idn.strip()}")
        
        # Initialize to known state
        self.dmm.write("*RST")
        self.dmm.write("*CLS")
    
    def configure_high_accuracy_dc_voltage(self, range_val=10):
        """Configure for highest accuracy DC voltage measurements"""
        
        self.dmm.write("CONF:VOLT:DC")
        self.dmm.write(f"VOLT:DC:RANGE {range_val}")
        self.dmm.write("VOLT:DC:NPLC 100")          # Maximum integration time
        self.dmm.write("VOLT:DC:ZERO:AUTO ON")      # Enable autozero
        self.dmm.write("VOLT:DC:IMP:AUTO OFF")      # Fixed high impedance
        self.dmm.write("VOLT:DC:IMP 10e9")          # 10 GOhm
        
        print(f"Configured for high-accuracy DC voltage, {range_val}V range")
    
    def fast_dc_voltage_measurement(self, samples=100):
        """Fast DC voltage measurements for monitoring applications"""
        
        self.dmm.write("CONF:VOLT:DC")
        self.dmm.write("VOLT:DC:NPLC 0.02")         # Minimum integration time
        self.dmm.write("VOLT:DC:ZERO:AUTO OFF")     # Disable autozero for speed
        self.dmm.write(f"SAMP:COUN {samples}")
        
        # Trigger and read all samples
        start_time = time.time()
        self.dmm.write("READ?")
        response = self.dmm.read()
        measurement_time = time.time() - start_time
        
        # Parse results
        values = [float(x) for x in response.split(',')]
        rate = len(values) / measurement_time
        
        print(f"Fast measurement: {rate:.0f} samples/second")
        return values
    
    def measure_with_statistics(self, measurement_type="VOLT:DC", samples=20):
        """Perform measurement with comprehensive statistics"""
        
        import statistics
        
        # Configure measurement
        self.dmm.write(f"CONF:{measurement_type}")
        if measurement_type == "VOLT:DC":
            self.dmm.write("VOLT:DC:NPLC 10")  # Good balance of speed/accuracy
        
        measurements = []
        print(f"Taking {samples} {measurement_type} measurements...")
        
        for i in range(samples):
            self.dmm.write("READ?")
            value = float(self.dmm.read())
            measurements.append(value)
            print(f"  #{i+1}: {value:.8f}")
            time.sleep(0.1)
        
        # Calculate statistics
        stats = {
            'mean': statistics.mean(measurements),
            'stdev': statistics.stdev(measurements) if len(measurements) > 1 else 0,
            'min': min(measurements),
            'max': max(measurements),
            'range': max(measurements) - min(measurements),
            'samples': measurements
        }
        
        return stats
    
    def close(self):
        """Clean up resources"""
        self.dmm.close()
        self.rm.close()

# Example usage
dmm = Keysight34461A("USB0::0x2A8D::0x0101::MY53220001::INSTR")

# High accuracy measurement
dmm.configure_high_accuracy_dc_voltage(10)
voltage_stats = dmm.measure_with_statistics("VOLT:DC", 10)
print(f"Voltage: {voltage_stats['mean']:.6f} ± {voltage_stats['stdev']:.6f} V")

# Fast monitoring
fast_data = dmm.fast_dc_voltage_measurement(100)
print(f"Fast measurements: {len(fast_data)} samples")

dmm.close()

Advanced DMM Features

def keysight_dmm_advanced_features(dmm):
    """Demonstrate advanced Keysight DMM features"""
    
    # Math functions
    dmm.write("CALC:FUNC NULL")  # Enable null function
    dmm.write("CALC:NULL:OFFS 5.000")  # Set null offset to 5V
    dmm.write("CALC:STAT ON")    # Enable math
    
    # Limits testing
    dmm.write("CALC2:FUNC LIM")  # Limit testing
    dmm.write("CALC2:LIM:LOW 4.5")   # Lower limit
    dmm.write("CALC2:LIM:UPP 5.5")   # Upper limit
    dmm.write("CALC2:STAT ON")
    
    # Perform measurement with math
    dmm.write("MEAS:VOLT:DC?")
    raw_value = float(dmm.read())
    
    # Get calculated (null) value
    dmm.write("CALC:DATA?")
    null_value = float(dmm.read())
    
    # Get limit test result
    dmm.write("CALC2:DATA?")
    limit_result = dmm.read().strip()  # "PASS" or "FAIL"
    
    print(f"Raw measurement: {raw_value:.6f} V")
    print(f"Null value: {null_value:.6f} V")
    print(f"Limit test: {limit_result}")
    
    return {
        'raw': raw_value,
        'null': null_value,
        'limit_test': limit_result
    }

2. Oscilloscopes (InfiniiVision Series)

Waveform Acquisition and Analysis

class KeysightInfiniiVision:
    """Keysight InfiniiVision Oscilloscope Controller"""
    
    def __init__(self, resource_string):
        self.rm = pyvisa.ResourceManager()
        self.scope = self.rm.open_resource(resource_string)
        
        # Configure for large data transfers
        self.scope.timeout = 30000
        self.scope.chunk_size = 1024 * 1024  # 1MB chunks
        
        # Verify connection
        idn = self.scope.query("*IDN?")
        print(f"Connected to: {idn.strip()}")
        
        # Initialize
        self.scope.write("*RST")
        self.scope.write(":WAV:FORM BYTE")  # 8-bit data format
        self.scope.write(":WAV:MODE NORM")  # Normal mode
    
    def setup_acquisition(self, channel=1, scale=1.0, time_scale=1e-3):
        """Setup basic acquisition parameters"""
        
        # Channel setup
        self.scope.write(f":CHAN{channel}:DISP ON")
        self.scope.write(f":CHAN{channel}:SCAL {scale}")
        self.scope.write(f":CHAN{channel}:OFFS 0")
        
        # Time base setup
        self.scope.write(f":TIM:SCAL {time_scale}")
        self.scope.write(":TIM:POS 0")
        
        # Trigger setup
        self.scope.write(f":TRIG:EDGE:SOUR CHAN{channel}")
        self.scope.write(":TRIG:EDGE:LEV 0")
        self.scope.write(":TRIG:EDGE:SLOP POS")
        
        print(f"Setup complete: CH{channel}, {scale}V/div, {time_scale*1000}ms/div")
    
    def acquire_waveform(self, channel=1, points=1000):
        """Acquire waveform data efficiently"""
        
        # Set data source
        self.scope.write(f":WAV:SOUR CHAN{channel}")
        self.scope.write(f":WAV:POIN {points}")
        
        # Single acquisition
        self.scope.write(":SING")
        
        # Wait for trigger
        self.scope.query("*OPC?")
        
        # Get waveform preamble (scaling info)
        preamble = self.scope.query(":WAV:PRE?").split(',')
        y_increment = float(preamble[7])
        y_origin = float(preamble[8])
        y_reference = float(preamble[9])
        x_increment = float(preamble[4])
        x_origin = float(preamble[5])
        
        # Get waveform data
        self.scope.write(":WAV:DATA?")
        raw_data = self.scope.read_raw()
        
        # Remove IEEE header (first few bytes)
        header_len = 2 + int(chr(raw_data[1]))
        waveform_data = raw_data[header_len:-1]  # Remove header and terminator
        
        # Convert to numpy array
        import numpy as np
        data_array = np.frombuffer(waveform_data, dtype=np.uint8)
        
        # Scale to actual voltage values
        voltage_data = (data_array - y_reference) * y_increment + y_origin
        
        # Create time array
        time_data = np.arange(len(voltage_data)) * x_increment + x_origin
        
        return time_data, voltage_data, {
            'points': len(voltage_data),
            'time_scale': x_increment,
            'voltage_scale': y_increment,
            'sample_rate': 1/x_increment
        }
    
    def measure_parameters(self, channel=1):
        """Get automated measurements"""
        
        measurements = {}
        
        # Voltage measurements
        measurements['vpp'] = float(self.scope.query(f":MEAS:VPP? CHAN{channel}"))
        measurements['vmax'] = float(self.scope.query(f":MEAS:VMAX? CHAN{channel}"))
        measurements['vmin'] = float(self.scope.query(f":MEAS:VMIN? CHAN{channel}"))
        measurements['vavg'] = float(self.scope.query(f":MEAS:VAV? CHAN{channel}"))
        measurements['vrms'] = float(self.scope.query(f":MEAS:VRMS? CHAN{channel}"))
        
        # Time measurements
        try:
            measurements['frequency'] = float(self.scope.query(f":MEAS:FREQ? CHAN{channel}"))
            measurements['period'] = float(self.scope.query(f":MEAS:PER? CHAN{channel}"))
            measurements['rise_time'] = float(self.scope.query(f":MEAS:RIS? CHAN{channel}"))
            measurements['fall_time'] = float(self.scope.query(f":MEAS:FALL? CHAN{channel}"))
        except:
            # Measurements might fail if signal is not periodic
            pass
        
        return measurements
    
    def screenshot(self, filename="screenshot.png"):
        """Capture oscilloscope screenshot"""
        
        # Set image format
        self.scope.write(":DISP:DATA? PNG, COL")
        image_data = self.scope.read_raw()
        
        # Remove SCPI header
        header_len = 2 + int(chr(image_data[1]))
        png_data = image_data[header_len:]
        
        # Save to file
        with open(filename, 'wb') as f:
            f.write(png_data)
        
        print(f"Screenshot saved as {filename}")
        return filename
    
    def close(self):
        """Clean up resources"""
        self.scope.close()
        self.rm.close()

# Example usage
scope = KeysightInfiniiVision("USB0::0x2A8D::0x0001::MY52345678::INSTR")

# Setup and acquire
scope.setup_acquisition(channel=1, scale=2.0, time_scale=1e-3)  # 2V/div, 1ms/div
time_data, voltage_data, info = scope.acquire_waveform(channel=1, points=2000)

print(f"Acquired {info['points']} points at {info['sample_rate']/1e6:.1f} MSa/s")

# Get measurements
measurements = scope.measure_parameters(channel=1)
print("Automated measurements:")
for param, value in measurements.items():
    print(f"  {param}: {value}")

# Take screenshot
scope.screenshot("waveform_capture.png")

scope.close()

3. Signal Generators (33500B Series)

Waveform Generation and Modulation

class Keysight33500B:
    """Keysight 33500B Series Function/Arbitrary Waveform Generator"""
    
    def __init__(self, resource_string):
        self.rm = pyvisa.ResourceManager()
        self.gen = self.rm.open_resource(resource_string)
        
        # Configure connection
        self.gen.timeout = 10000
        
        # Verify and initialize
        idn = self.gen.query("*IDN?")
        print(f"Connected to: {idn.strip()}")
        
        self.gen.write("*RST")
        self.gen.write("*CLS")
    
    def generate_sine_wave(self, channel=1, frequency=1000, amplitude=1.0, offset=0):
        """Generate basic sine wave"""
        
        # Configure waveform
        self.gen.write(f"SOUR{channel}:FUNC SIN")
        self.gen.write(f"SOUR{channel}:FREQ {frequency}")
        self.gen.write(f"SOUR{channel}:VOLT {amplitude}")
        self.gen.write(f"SOUR{channel}:VOLT:OFFS {offset}")
        
        # Enable output
        self.gen.write(f"OUTP{channel} ON")
        
        print(f"CH{channel}: {frequency}Hz sine wave, {amplitude}Vpp, {offset}V offset")
    
    def generate_square_wave(self, channel=1, frequency=1000, amplitude=5.0, duty_cycle=50):
        """Generate square wave with adjustable duty cycle"""
        
        self.gen.write(f"SOUR{channel}:FUNC SQU")
        self.gen.write(f"SOUR{channel}:FREQ {frequency}")
        self.gen.write(f"SOUR{channel}:VOLT {amplitude}")
        self.gen.write(f"SOUR{channel}:FUNC:SQU:DCYC {duty_cycle}")
        
        self.gen.write(f"OUTP{channel} ON")
        
        print(f"CH{channel}: {frequency}Hz square wave, {duty_cycle}% duty cycle")
    
    def sweep_frequency(self, channel=1, start_freq=100, stop_freq=10000, 
                       sweep_time=10, amplitude=1.0):
        """Generate frequency sweep"""
        
        # Configure basic waveform
        self.gen.write(f"SOUR{channel}:FUNC SIN")
        self.gen.write(f"SOUR{channel}:VOLT {amplitude}")
        
        # Configure sweep
        self.gen.write(f"SOUR{channel}:FREQ:START {start_freq}")
        self.gen.write(f"SOUR{channel}:FREQ:STOP {stop_freq}")
        self.gen.write(f"SOUR{channel}:SWE:TIME {sweep_time}")
        self.gen.write(f"SOUR{channel}:SWE:STAT ON")
        
        self.gen.write(f"OUTP{channel} ON")
        
        print(f"CH{channel}: Frequency sweep {start_freq}-{stop_freq}Hz in {sweep_time}s")
    
    def arbitrary_waveform(self, channel=1, waveform_data, sample_rate=1e6):
        """Upload and generate arbitrary waveform"""
        
        import numpy as np
        
        # Normalize waveform data to ±1
        if isinstance(waveform_data, list):
            waveform_data = np.array(waveform_data)
        
        waveform_normalized = waveform_data / np.max(np.abs(waveform_data))
        
        # Convert to comma-separated string
        waveform_str = ','.join([f"{val:.6f}" for val in waveform_normalized])
        
        # Upload waveform
        self.gen.write(f"SOUR{channel}:DATA:VOL:CLE")  # Clear memory
        self.gen.write(f"SOUR{channel}:DATA VOLATILE,{waveform_str}")
        
        # Configure arbitrary function
        self.gen.write(f"SOUR{channel}:FUNC ARB")
        self.gen.write(f"SOUR{channel}:FUNC:ARB VOLATILE")
        
        # Set sample rate (determines frequency)
        points = len(waveform_data)
        frequency = sample_rate / points
        self.gen.write(f"SOUR{channel}:FREQ {frequency}")
        
        self.gen.write(f"OUTP{channel} ON")
        
        print(f"CH{channel}: Arbitrary waveform, {points} points, {frequency:.0f}Hz")
    
    def burst_mode(self, channel=1, burst_count=10, frequency=1000):
        """Configure burst mode operation"""
        
        # Configure basic waveform
        self.gen.write(f"SOUR{channel}:FUNC SIN")
        self.gen.write(f"SOUR{channel}:FREQ {frequency}")
        
        # Configure burst
        self.gen.write(f"SOUR{channel}:BURS:STAT ON")
        self.gen.write(f"SOUR{channel}:BURS:MODE TRIG")
        self.gen.write(f"SOUR{channel}:BURS:NCYC {burst_count}")
        
        # Configure trigger
        self.gen.write("TRIG:SOUR BUS")
        
        self.gen.write(f"OUTP{channel} ON")
        
        print(f"CH{channel}: Burst mode, {burst_count} cycles per trigger")
    
    def trigger_burst(self):
        """Trigger a burst"""
        self.gen.write("*TRG")
        print("Burst triggered")
    
    def close(self):
        """Clean up resources"""
        # Turn off all outputs
        self.gen.write("OUTP1 OFF")
        self.gen.write("OUTP2 OFF")
        self.gen.close()
        self.rm.close()

# Example usage
gen = Keysight33500B("USB0::0x2A8D::0x0001::MY52345678::INSTR")

# Basic sine wave
gen.generate_sine_wave(channel=1, frequency=1000, amplitude=2.0)

# Square wave on channel 2
gen.generate_square_wave(channel=2, frequency=500, amplitude=3.3, duty_cycle=25)

# Create and upload arbitrary waveform
import numpy as np
t = np.linspace(0, 2*np.pi, 1000)
custom_wave = np.sin(t) + 0.3*np.sin(3*t)  # Fundamental + 3rd harmonic
gen.arbitrary_waveform(channel=1, waveform_data=custom_wave, sample_rate=1e6)

gen.close()

4. Keysight-Specific Optimization Tips

Performance Optimization

def optimize_keysight_connection(instrument):
    """Apply Keysight-specific optimizations"""
    
    # Use largest possible buffer sizes
    instrument.chunk_size = 2 * 1024 * 1024  # 2MB
    instrument.timeout = 60000  # 60 seconds
    
    # Keysight instruments support fast binary transfers
    try:
        # Set binary data format when available
        instrument.write("FORM:DATA REAL,64")  # Double precision
        # or
        instrument.write("FORM:DATA INT,32")   # 32-bit integers
    except:
        pass
    
    # Disable unnecessary features for speed
    try:
        instrument.write("SYST:DISP:UPD OFF")  # Disable display updates
        instrument.write("SYST:BEEP:STAT OFF") # Disable beep
    except:
        pass

def keysight_error_checking(instrument):
    """Comprehensive error checking for Keysight instruments"""
    
    # Check system errors
    error_count = 0
    while True:
        error = instrument.query("SYST:ERR?")
        if error.startswith("+0,"):  # No error
            break
        print(f"System error: {error}")
        error_count += 1
        if error_count > 10:  # Prevent infinite loop
            break
    
    # Check specific instrument status
    try:
        # For oscilloscopes
        acq_status = instrument.query(":OPER:COND?")
        print(f"Acquisition status: {acq_status}")
        
        # For signal generators
        output_status = instrument.query("OUTP:PROT:TRIP?")
        if output_status.strip() == "1":
            print("Warning: Output protection tripped!")
            
    except:
        pass
    
    return error_count == 0

Advanced Features

def keysight_advanced_features():
    """Demonstrate advanced Keysight-specific features"""
    
    # Segmented memory (oscilloscopes)
    def setup_segmented_memory(scope, segments=100):
        scope.write(f":ACQ:SEGM:COUN {segments}")
        scope.write(":ACQ:SEGM:STAT ON")
        print(f"Segmented memory: {segments} segments")
    
    # High-speed digitizing mode
    def setup_high_speed_digitizing(scope):
        scope.write(":ACQ:MODE HRES")  # High resolution mode
        scope.write(":ACQ:SRAT:AUTO OFF")
        scope.write(":ACQ:SRAT MAX")  # Maximum sample rate
        print("High-speed digitizing mode enabled")
    
    # Waveform math (oscilloscopes)
    def setup_waveform_math(scope):
        scope.write(":FUNC:DISP ON")
        scope.write(":FUNC:OPER ADD")  # Add channels
        scope.write(":FUNC:SOUR1 CHAN1")
        scope.write(":FUNC:SOUR2 CHAN2")
        print("Math function: CH1 + CH2")
    
    # Sequence mode (signal generators)
    def setup_sequence_mode(generator):
        # Create sequence with multiple waveforms
        generator.write("SOUR:FUNC:ARB:SEQ:LENG 3")
        generator.write("SOUR:FUNC:ARB:SEQ:TRAC:WFOR SINE")
        generator.write("SOUR:FUNC:ARB:SEQ:TRAC:FREQ 1000")
        # Add more sequence steps...
        print("Sequence mode configured")

5. Troubleshooting Keysight Instruments

Common Issues and Solutions

def troubleshoot_keysight_connection():
    """Troubleshoot common Keysight connection issues"""
    
    print("=== Keysight Instrument Troubleshooting ===")
    
    # Check for Keysight IO Libraries
    try:
        import win32api
        io_libs_path = r"C:\Program Files (x86)\Keysight\IO Libraries Suite"
        if os.path.exists(io_libs_path):
            print("Keysight IO Libraries Suite found")
        else:
            print("Keysight IO Libraries Suite not found")
            print("   Download from: https://www.keysight.com/find/iosuite")
    except:
        pass
    
    # Test USB connection
    rm = pyvisa.ResourceManager()
    keysight_usb = [r for r in rm.list_resources() if "0x2A8D" in r]
    
    if keysight_usb:
        print(f"Found {len(keysight_usb)} Keysight USB instruments")
        for resource in keysight_usb:
            try:
                inst = rm.open_resource(resource)
                idn = inst.query("*IDN?")
                print(f"  {resource}: {idn.strip()}")
                inst.close()
            except Exception as e:
                print(f"  {resource}: Error - {e}")
    else:
        print("No Keysight USB instruments found")
        print("   Check USB connection and drivers")
    
    # Test common network addresses
    common_ips = ["192.168.1.100", "10.0.0.100", "169.254.1.1"]
    for ip in common_ips:
        try:
            inst = rm.open_resource(f"TCPIP0::{ip}::5025::SOCKET", timeout=2000)
            idn = inst.query("*IDN?")
            if "Keysight" in idn or "Agilent" in idn:
                print(f"Found Keysight instrument at {ip}: {idn.strip()}")
            inst.close()
        except:
            pass
    
    rm.close()

Best Practices for Keysight Instruments

Do This:

  • Install Keysight IO Libraries Suite for best compatibility
  • Use binary data formats for large transfers
  • Enable segmented memory for repetitive measurements
  • Check system errors regularly
  • Use appropriate timeout values (Keysight instruments can be slow for complex operations)

Avoid This:

  • Mixing Keysight and NI VISA drivers (can cause conflicts)
  • Using ASCII format for waveform data
  • Forgetting to turn off outputs when done
  • Rapid command sequences without checking completion

Performance Tips:

  • Use USB 3.0 ports when available
  • For LAN, use raw socket connection (port 5025) when possible
  • Disable display updates during high-speed operations
  • Use burst/block measurement modes for multiple readings

Next Steps

  • Try specific examples: Test with your Keysight instruments
  • Explore advanced features: Segmented memory, math functions
  • Integration: Combine multiple Keysight instruments in test systems
  • Performance: Optimize for your specific measurement requirements

For more instrument-specific guides:

How is this guide?