Multimeter Example
PyVISA guide for controlling digital multimeters - Keysight 34461A, automated measurements, data logging, and measurement techniques for voltage, current, and resistance.
Digital multimeter automation with PyVISA. From basic measurements to data logging and automated test sequences.
Quick Start Example
import pyvisa
import time
# Connect to Keysight 34461A DMM
rm = pyvisa.ResourceManager()
dmm = rm.open_resource("USB0::0x2A8D::0x0101::MY53220001::INSTR")
try:
# Basic voltage measurement
dmm.write("MEAS:VOLT:DC?")
voltage = float(dmm.read())
print(f"DC Voltage: {voltage:.6f} V")
finally:
dmm.close()
rm.close()
Supported Multimeters
This guide covers programming patterns that work with most SCPI-compatible DMMs:
Keysight (Agilent) Series
- 34461A/34470A (6½ and 7½ digit) - Featured in examples
- 34401A (6½ digit legacy)
- U3606A (Handheld series)
Tektronix Series
- DMM4050/4040 (6½ digit)
- DMM4020 (5½ digit)
Fluke Series
- 8845A/8846A (6½ digit)
- 8808A (5½ digit)
Rohde & Schwarz
- HMC8012 (5½ digit)
Complete Programming Guide
1. Connection Setup
import pyvisa
import time
import csv
from datetime import datetime
def connect_dmm(resource_string):
"""Connect to DMM with proper configuration"""
rm = pyvisa.ResourceManager()
try:
dmm = rm.open_resource(resource_string)
dmm.timeout = 10000 # 10 second timeout
dmm.write_termination = '\n'
dmm.read_termination = '\n'
# Identify the instrument
idn = dmm.query("*IDN?")
print(f"Connected to: {idn}")
# Reset to known state
dmm.write("*RST")
dmm.write("*CLS") # Clear error queue
return dmm, rm
except pyvisa.VisaIOError as e:
print(f"Connection failed: {e}")
rm.close()
raise
# Example usage
dmm, rm = connect_dmm("USB0::0x2A8D::0x0101::MY53220001::INSTR")
2. Basic Measurements
def basic_measurements(dmm):
"""Perform all basic measurement types"""
measurements = {}
# DC Voltage (default range auto)
dmm.write("CONF:VOLT:DC")
time.sleep(0.1)
dmm.write("READ?")
measurements['dc_voltage'] = float(dmm.read())
# AC Voltage
dmm.write("CONF:VOLT:AC")
time.sleep(0.1)
dmm.write("READ?")
measurements['ac_voltage'] = float(dmm.read())
# DC Current (be careful with current measurements!)
dmm.write("CONF:CURR:DC")
time.sleep(0.1)
dmm.write("READ?")
measurements['dc_current'] = float(dmm.read())
# Resistance (2-wire)
dmm.write("CONF:RES")
time.sleep(0.1)
dmm.write("READ?")
measurements['resistance'] = float(dmm.read())
# 4-wire resistance (more accurate for low resistance)
dmm.write("CONF:FRES")
time.sleep(0.1)
dmm.write("READ?")
measurements['resistance_4wire'] = float(dmm.read())
return measurements
# Example usage
results = basic_measurements(dmm)
for measurement, value in results.items():
print(f"{measurement}: {value}")
3. Advanced Measurement Configuration
Parameter | Description | Type | Default | Values |
---|---|---|---|---|
VOLT:DC:NPLC | Integration time in Power Line Cycles | number | 1 | 0.02, 0.2, 1, 10, 100 |
VOLT:DC:RANGE | Measurement range in volts | number | AUTO | AUTO | 0.1, 1, 10, 100, 1000 |
VOLT:DC:ZERO:AUTO | Enable/disable autozero for drift compensation | ON | OFF | ON | - |
VOLT:DC:RES | Measurement resolution | number | MAX | MIN | Medium | - |
VOLT:DC:IMP | Input impedance in ohms | number | 10e9 | 10e6 (10 MOhm), 10e9 (10 GOhm) |
def configure_advanced_measurement(dmm):
"""Configure DMM for high-precision measurements"""
# Set integration time for better accuracy vs speed
# Integration times: 0.02, 0.2, 1, 10, 100 PLCs (Power Line Cycles)
dmm.write("VOLT:DC:NPLC 10") # 10 PLC = best accuracy, slower
# Set specific range (avoids range switching noise)
dmm.write("VOLT:DC:RANGE 10") # 10V range
# Enable autozero for drift compensation
dmm.write("VOLT:DC:ZERO:AUTO ON")
# Set resolution (number of digits)
dmm.write("VOLT:DC:RES MAX") # Maximum resolution
# Configure input impedance (for sensitive circuits)
dmm.write("VOLT:DC:IMP:AUTO OFF") # Disable auto impedance
dmm.write("VOLT:DC:IMP 10e9") # 10 GOhm fixed impedance
print("Advanced measurement configuration complete")
def high_precision_voltage_measurement(dmm, samples=10):
"""Perform high-precision voltage measurement with statistics"""
configure_advanced_measurement(dmm)
measurements = []
print(f"Taking {samples} high-precision measurements...")
for i in range(samples):
dmm.write("READ?")
value = float(dmm.read())
measurements.append(value)
print(f" Sample {i+1}: {value:.8f} V")
time.sleep(0.5) # Allow settling time
# Calculate statistics
import statistics
mean_value = statistics.mean(measurements)
std_dev = statistics.stdev(measurements) if len(measurements) > 1 else 0
min_value = min(measurements)
max_value = max(measurements)
results = {
'mean': mean_value,
'std_dev': std_dev,
'min': min_value,
'max': max_value,
'samples': measurements
}
print(f"\nMeasurement Statistics:")
print(f" Mean: {mean_value:.8f} V")
print(f" Std Dev: {std_dev:.8f} V")
print(f" Range: {min_value:.8f} to {max_value:.8f} V")
return results
# Example usage
precision_results = high_precision_voltage_measurement(dmm, samples=20)
4. Data Logging and Continuous Monitoring
Data Logger Configuration
The DMM_DataLogger class provides professional data logging capabilities with automatic CSV export and statistics calculation.
class DMM_DataLogger:
"""Professional data logging class for DMM measurements"""
def __init__(self, dmm, filename=None):
self.dmm = dmm
self.filename = filename or f"dmm_log_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
self.measurements = []
def configure_measurement(self, measurement_type='VOLT:DC', range_val='AUTO',
nplc=1, samples=1):
"""Configure measurement parameters"""
# Set measurement function
self.dmm.write(f"CONF:{measurement_type}")
# Set range
if range_val != 'AUTO':
self.dmm.write(f"{measurement_type}:RANGE {range_val}")
# Set integration time
self.dmm.write(f"{measurement_type}:NPLC {nplc}")
# Configure for multiple samples
self.dmm.write(f"SAMP:COUN {samples}")
print(f"Configured for {measurement_type}, Range: {range_val}, NPLC: {nplc}")
def log_single_measurement(self):
"""Log a single measurement with timestamp"""
timestamp = datetime.now()
self.dmm.write("READ?")
value = float(self.dmm.read())
measurement = {
'timestamp': timestamp,
'value': value,
'iso_time': timestamp.isoformat()
}
self.measurements.append(measurement)
return measurement
def continuous_logging(self, duration_minutes=10, interval_seconds=1):
"""Continuously log measurements for specified duration"""
end_time = datetime.now().timestamp() + (duration_minutes * 60)
measurement_count = 0
print(f"Starting continuous logging for {duration_minutes} minutes...")
print("Press Ctrl+C to stop early")
try:
while datetime.now().timestamp() < end_time:
measurement = self.log_single_measurement()
measurement_count += 1
print(f"#{measurement_count}: {measurement['value']:.6f} at {measurement['timestamp'].strftime('%H:%M:%S')}")
time.sleep(interval_seconds)
except KeyboardInterrupt:
print(f"\nLogging stopped by user after {measurement_count} measurements")
self.save_to_csv()
return self.measurements
def save_to_csv(self):
"""Save measurements to CSV file"""
if not self.measurements:
print("No measurements to save")
return
with open(self.filename, 'w', newline='') as csvfile:
fieldnames = ['timestamp', 'iso_time', 'value']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
for measurement in self.measurements:
writer.writerow(measurement)
print(f"Saved {len(self.measurements)} measurements to {self.filename}")
def get_statistics(self):
"""Calculate statistics from logged data"""
if not self.measurements:
return None
values = [m['value'] for m in self.measurements]
import statistics
stats = {
'count': len(values),
'mean': statistics.mean(values),
'median': statistics.median(values),
'std_dev': statistics.stdev(values) if len(values) > 1 else 0,
'min': min(values),
'max': max(values),
'range': max(values) - min(values)
}
return stats
# Example usage
logger = DMM_DataLogger(dmm, "battery_monitoring.csv")
logger.configure_measurement('VOLT:DC', range_val=10, nplc=1)
# Log for 5 minutes, one measurement per second
measurements = logger.continuous_logging(duration_minutes=5, interval_seconds=1)
# Get statistics
stats = logger.get_statistics()
print(f"\nLogging Statistics:")
for key, value in stats.items():
if isinstance(value, float):
print(f" {key}: {value:.6f}")
else:
print(f" {key}: {value}")
5. Battery Testing and Characterization
def battery_discharge_test(dmm, initial_voltage=12.0, cutoff_voltage=10.5):
"""Automated battery discharge test"""
print("=== Battery Discharge Test ===")
print(f"Monitoring voltage from {initial_voltage}V down to {cutoff_voltage}V")
# Configure for battery voltage monitoring
dmm.write("CONF:VOLT:DC")
dmm.write("VOLT:DC:RANGE 100") # Use 100V range for stability
dmm.write("VOLT:DC:NPLC 1") # 1 PLC for good balance of speed/accuracy
start_time = datetime.now()
measurements = []
try:
while True:
# Take measurement
dmm.write("READ?")
voltage = float(dmm.read())
current_time = datetime.now()
elapsed_minutes = (current_time - start_time).total_seconds() / 60
measurement = {
'time_minutes': elapsed_minutes,
'voltage': voltage,
'timestamp': current_time
}
measurements.append(measurement)
print(f"Time: {elapsed_minutes:6.1f} min, Voltage: {voltage:.3f} V")
# Check cutoff condition
if voltage <= cutoff_voltage:
print(f"\nCutoff voltage {cutoff_voltage}V reached!")
break
# Check for unrealistic voltage (connection issues)
if voltage < 0 or voltage > 50:
print(f"Warning: Unusual voltage reading {voltage}V")
time.sleep(60) # Measure every minute
except KeyboardInterrupt:
print("\nTest stopped by user")
# Save results
filename = f"battery_test_{start_time.strftime('%Y%m%d_%H%M%S')}.csv"
with open(filename, 'w', newline='') as csvfile:
fieldnames = ['time_minutes', 'voltage', 'timestamp']
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
writer.writeheader()
writer.writerows(measurements)
total_time = measurements[-1]['time_minutes'] if measurements else 0
print(f"Test completed in {total_time:.1f} minutes")
print(f"Results saved to {filename}")
return measurements
# Example usage
# battery_test_results = battery_discharge_test(dmm)
6. Automated Component Testing
def resistor_tolerance_test(dmm, nominal_values, tolerance_percent=5):
"""Test multiple resistors for tolerance compliance"""
print("=== Resistor Tolerance Test ===")
# Configure for resistance measurement
dmm.write("CONF:RES")
dmm.write("RES:NPLC 10") # High accuracy for component testing
results = []
for i, nominal in enumerate(nominal_values):
input(f"\nConnect resistor #{i+1} (nominal {nominal} Ω) and press Enter...")
# Take multiple measurements for accuracy
measurements = []
for j in range(5):
dmm.write("READ?")
value = float(dmm.read())
measurements.append(value)
time.sleep(0.2)
# Calculate average
avg_resistance = sum(measurements) / len(measurements)
deviation_percent = ((avg_resistance - nominal) / nominal) * 100
within_tolerance = abs(deviation_percent) <= tolerance_percent
result = {
'resistor_number': i + 1,
'nominal_ohms': nominal,
'measured_ohms': avg_resistance,
'deviation_percent': deviation_percent,
'within_tolerance': within_tolerance,
'tolerance_limit': tolerance_percent
}
results.append(result)
# Print result
status = "PASS" if within_tolerance else "FAIL"
print(f"Resistor #{i+1}: {avg_resistance:.2f} Ω ({deviation_percent:+.2f}%) - {status}")
# Summary
passed = sum(1 for r in results if r['within_tolerance'])
print(f"\nTest Summary: {passed}/{len(results)} resistors passed tolerance test")
return results
# Example usage
# Test 1kΩ, 10kΩ, 100kΩ resistors with 5% tolerance
# test_results = resistor_tolerance_test(dmm, [1000, 10000, 100000], tolerance_percent=5)
7. Error Handling and Robustness
def robust_dmm_operation(dmm, operation_func, max_retries=3):
"""Wrapper for robust DMM operations with error handling"""
for attempt in range(max_retries):
try:
# Clear any previous errors
dmm.write("*CLS")
# Perform operation
result = operation_func(dmm)
# Check for instrument errors
error = dmm.query("SYST:ERR?")
if not error.startswith("+0,"):
print(f"Instrument error: {error}")
if attempt < max_retries - 1:
time.sleep(1)
continue
else:
raise Exception(f"Instrument error after {max_retries} attempts: {error}")
return result
except pyvisa.VisaIOError as e:
print(f"VISA error (attempt {attempt + 1}): {e}")
if attempt < max_retries - 1:
time.sleep(2) # Wait before retry
continue
else:
raise
except Exception as e:
print(f"Unexpected error (attempt {attempt + 1}): {e}")
if attempt < max_retries - 1:
time.sleep(1)
continue
else:
raise
return None
def safe_voltage_measurement(dmm):
"""Safe voltage measurement function"""
dmm.write("CONF:VOLT:DC")
dmm.write("READ?")
return float(dmm.read())
# Example usage with error handling
try:
voltage = robust_dmm_operation(dmm, safe_voltage_measurement)
print(f"Measured voltage: {voltage:.6f} V")
except Exception as e:
print(f"Measurement failed: {e}")
Performance Optimization
Fast Measurement Techniques
def fast_measurement_burst(dmm, count=100):
"""Optimized for maximum measurement speed"""
# Configure for speed
dmm.write("CONF:VOLT:DC")
dmm.write("VOLT:DC:NPLC 0.02") # Minimum integration time
dmm.write("VOLT:DC:ZERO:AUTO OFF") # Disable autozero for speed
dmm.write(f"SAMP:COUN {count}")
# Trigger burst measurement
start_time = time.time()
dmm.write("READ?")
# Read all measurements at once
response = dmm.read()
end_time = time.time()
# Parse comma-separated values
values = [float(x) for x in response.split(',')]
measurement_rate = len(values) / (end_time - start_time)
print(f"Completed {len(values)} measurements in {end_time - start_time:.3f}s")
print(f"Measurement rate: {measurement_rate:.1f} measurements/second")
return values
# Example usage
# fast_data = fast_measurement_burst(dmm, count=50)
Best Practices Summary
Do This:
- Always use try/finally for resource cleanup
- Set appropriate timeout values for your application
- Use context managers when possible
- Check instrument errors regularly with
SYST:ERR?
- Configure measurement parameters explicitly
- Use appropriate integration times (NPLC) for your accuracy needs
Avoid This:
- Leaving connections open indefinitely
- Using default timeouts for critical measurements
- Ignoring instrument error messages
- Rapidly switching measurement functions without delays
- Using auto-ranging when you know the expected range
Troubleshooting Tips:
- If measurements seem unstable, increase NPLC (integration time)
- For battery-powered devices, consider input impedance settings
- Use 4-wire resistance for low resistance measurements
- Check cable connections for intermittent readings
- Monitor instrument temperature for precision work
Next Steps
- Learn advanced SCPI: Explore SCPI Commands Reference
- Try other instruments: Check out Power Supply Example
- Performance optimization: Visit Performance Guide
- Troubleshooting: See Connection Issues
How is this guide?