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?