PyVISA
PyVISADocs

Keysight Instruments

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

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: