aboutsummaryrefslogtreecommitdiff
path: root/qolab/hardware/multimeter
diff options
context:
space:
mode:
authorMichael Vorobiov <mvorobiov@wm.edu>2024-11-05 14:19:36 -0500
committerMichael Vorobiov <mvorobiov@wm.edu>2024-11-05 14:19:36 -0500
commit3d320e680842fed9b45eb5d7353945755f8f50ee (patch)
tree3aaa72167ab51fe5c3f7fa278a8b0b0960eb4e3c /qolab/hardware/multimeter
parent4036ccefa36c14b68cc4605615550c549d6586c3 (diff)
downloadqolab-3d320e680842fed9b45eb5d7353945755f8f50ee.tar.gz
qolab-3d320e680842fed9b45eb5d7353945755f8f50ee.zip
Driver for HP3458A digital multimeter (partial implementation)
Diffstat (limited to 'qolab/hardware/multimeter')
-rw-r--r--qolab/hardware/multimeter/__init__.py3
-rw-r--r--qolab/hardware/multimeter/hp3457a.py244
2 files changed, 246 insertions, 1 deletions
diff --git a/qolab/hardware/multimeter/__init__.py b/qolab/hardware/multimeter/__init__.py
index bf8871d..d79b524 100644
--- a/qolab/hardware/multimeter/__init__.py
+++ b/qolab/hardware/multimeter/__init__.py
@@ -2,5 +2,6 @@
from .bk_5491 import BK_5491
from .hp_34401 import HP_34401
+from .hp3457a import HP3457A
-__all__ = ["BK_5491", "HP_34401"]
+__all__ = ["BK_5491", "HP_34401", 'HP_3457A']
diff --git a/qolab/hardware/multimeter/hp3457a.py b/qolab/hardware/multimeter/hp3457a.py
new file mode 100644
index 0000000..226d1e2
--- /dev/null
+++ b/qolab/hardware/multimeter/hp3457a.py
@@ -0,0 +1,244 @@
+"""
+Module: HP 3457A Digital Multimeter Control
+Description: This module provides a class to interface with a digital multimeter (DMM)
+ using pyvisa for performing measurements such as voltage, current,
+ and resistance.
+
+Author: Mykhailo Vorobiov
+Email: mvorobiov@wm.edu
+Date: 2024-10-02
+Updated: 2024-11-05
+"""
+
+from qolab.hardware.basic import BasicInstrument
+from ._basic import Multimeter
+
+import pyvisa
+
+class HP3457A(Multimeter):
+ """
+ A class to interface with HP 3457A Digital Multimeter (DMM) via pyvisa.
+
+ This class allows you to measure DC voltage, AC voltage, DC current, AC current,
+ and resistance from a DMM using pyvisa communication.
+ """
+
+ # List of data return formats
+ FORMATS = ['ASCII',
+ 'SINT',
+ 'DINT',
+ 'SREAL',
+ 'DREAL'
+ ]
+
+ BEEPER_STATUS = ['ON','OFF','ONCE']
+
+ # List of functions
+ FUNCTIONS = ['DCV',
+ 'ACV',
+ 'ACDCV',#(This is here for future work)
+ 'OHM',#(This is here for future work)
+ 'OHMF',#(This is here for future work)
+ 'DCI',
+ 'ACI',
+ 'ACDCI',#(This is here for future work)
+ 'FREQ',#(This is here for future work)
+ 'PER'#(This is here for future work)
+ ]
+
+ # DC Volts Spec (This is here for future work)
+ DCV_RES = {'30mv': {'6.5': 10e-9, '5.5': 100e-9, '4.5': 1e-6, '3.5': 10e-6},
+ '300mv': {'6.5': 100e-9, '5.5': 1e-6, '4.5': 10e-6, '3.5': 100e-6},
+ '3v': {'6.5': 1e-6, '5.5': 10e-6, '4.5': 100e-6, '3.5': 1e-3},
+ '30v': {'6.5': 10e-6, '5.5': 100e-6, '4.5': 1e-3, '3.5': 10e-3},
+ '300v': {'6.5': 100e-6, '5.5': 1e-3, '4.5': 10e-3, '3.5': 100e-3}
+ }
+
+ DCV_ACC = {'30mv': {'100': {'acc': .0045, 'counts': 365}, '10': {'acc': .0045, 'counts': 385},
+ '1': {'acc': .0045, 'counts': 500}, '.1': {'acc': .0045, 'counts': 70},
+ '.005': {'acc': .0045, 'counts': 19}, '.0005': {'acc': .0045, 'counts': 6}},
+ '300mv': {'100': {'acc': .0035, 'counts': 39}, '10': {'acc': .0035, 'counts': 40},
+ '1': {'acc': .0035, 'counts': 50}, '.1': {'acc': .0035, 'counts': 9},
+ '.005': {'acc': .0035, 'counts': 4}, '.0005': {'acc': .0035, 'counts': 4}},
+ '3v': {'100': {'acc': .0025, 'counts': 6}, '10': {'acc': .0025, 'counts': 7},
+ '1': {'acc': .0025, 'counts': 7}, '.1': {'acc': .0025, 'counts': 4},
+ '.005': {'acc': .0025, 'counts': 4}, '.0005': {'acc': .0025, 'counts': 4}},
+ '30v': {'100': {'acc': .0040, 'counts': 19}, '10': {'acc': .0040, 'counts': 20},
+ '1': {'acc': .0040, 'counts': 30}, '.1': {'acc': .0040, 'counts': 7},
+ '.005': {'acc': .0040, 'counts': 4}, '.0005': {'acc': .0040, 'counts': 4}},
+ '300v': {'100': {'acc': .0055, 'counts': 6}, '10': {'acc': .0055, 'counts': 7},
+ '1': {'acc': .0055, 'counts': 7}, '.1': {'acc': .0055, 'counts': 4},
+ '.005': {'acc': .0055, 'counts': 4}, '.0005': {'acc': .0055, 'counts': 4}}
+ }
+
+ # DC Current Spec (This is here for future work)
+ DCI_RES = {'300ua': {'6.5': 100e-12, '5.5': 1e-9, '4.5': 10e-9, '3.5': 100e-9},
+ '3ma': {'6.5': 1e-9, '5.5': 10e-9, '4.5': 100e-9, '3.5': 1e-6},
+ '30ma': {'6.5': 10e-9, '5.5': 100e-9, '4.5': 1e-6, '3.5': 10e-6},
+ '300ma': {'6.5': 100e-9, '5.5': 1e-6, '4.5': 10e-6, '3.5': 100e-6},
+ '1a': {'6.5': 1e-6, '5.5': 10e-6, '4.5': 100e-6, '3.5': 1e-3}
+ }
+ # (This is here for future work)
+ DCI_ACC = {'300ua': {'100': {'acc': .04, 'counts': 104}, '10': {'acc': .04, 'counts': 104},
+ '1': {'acc': .04, 'counts': 115}, '.1': {'acc': .04, 'counts': 14},
+ '.005': {'acc': .04, 'counts': 5}, '.0005': {'acc': .04, 'counts': 4}},
+ '3ma': {'100': {'acc': .04, 'counts': 104}, '10': {'acc': .04, 'counts': 104},
+ '1': {'acc': .04, 'counts': 115}, '.1': {'acc': .04, 'counts': 14},
+ '.005': {'acc': .04, 'counts': 5}, '.0005': {'acc': .04, 'counts': 4}},
+ '30ma': {'100': {'acc': .04, 'counts': 104}, '10': {'acc': .04, 'counts': 104},
+ '1': {'acc': .04, 'counts': 115}, '.1': {'acc': .04, 'counts': 14},
+ '.005': {'acc': .04, 'counts': 5}, '.0005': {'acc': .04, 'counts': 4}},
+ '300ma': {'100': {'acc': .08, 'counts': 204}, '10': {'acc': .08, 'counts': 204},
+ '1': {'acc': .08, 'counts': 215}, '.1': {'acc': .08, 'counts': 24},
+ '.005': {'acc': .08, 'counts': 6}, '.0005': {'acc': .08, 'counts': 4}},
+ '1a': {'100': {'acc': .08, 'counts': 604}, '10': {'acc': .08, 'counts': 604},
+ '1': {'acc': .08, 'counts': 615}, '.1': {'acc': .08, 'counts': 64},
+ '.005': {'acc': .08, 'counts': 10}, '.0005': {'acc': .08, 'counts': 5}}
+ }
+
+ def __init__(self, resource, *args, **kwds):
+ """
+ Initialize the Digital Multimeter class and establish a connection.
+
+ :param resource_name: VISA resource name for the multimeter.
+ :param alias: Alias for logging.
+ :param log_level: Logging level (default: INFO).
+ """
+ super().__init__(*args, **kwds)
+ self.resource = resource
+ self.config["Device model"] = "HP 3457A"
+ self.resource.timeout = 5000
+ self.resource.read_termination = '\r\n'
+ self.resource.write_termination = '\r\n'
+
+ self.read = self.resource.read
+ self.write = self.resource.write
+ self.read_bytes = self.resource.read_bytes
+ self.query = self.resource.query
+
+ self.switchTime = 0.5 # switch time in seconds for Function/Measurement change
+ self.deviceProperties.update({"Function"})
+
+
+ def get_idn(self):
+ """
+ Query the identification of the digital multimeter: manufacturer, model, serial number, and firmware version.
+
+ :return: Identifier string if query is successful, or None otherwise.
+ """
+ try:
+ self.write('ID?')
+ return self.read_bytes(9).decode().strip()
+ except pyvisa.VisaIOError as e:
+ print(f'[ERROR!] Error retrieving identification of the digital multimeter: {e}')
+ return None
+
+ def _check_format(self, format):
+ if format in self.FORMATS:
+ return True
+ else:
+ return False
+
+ def set_format(self, format='ASCII'):
+ if self._check_format(format):
+ try:
+ self.write(f'OFORMAT {format}')
+ print(f'[INFO] Format has been set to {format}')
+ except pyvisa.VisaIOError as e:
+ print(f'[ERROR!] Couldn\'t set reading format to {format}: {e}')
+ else:
+ print(f'[WARNING] Entered format is not allowed. Allowed formats: {self.FORMATS}')
+
+ def get_reading(self):
+ try:
+ self.write(f'TARM AUTO')
+ return float(self.read_bytes(16).decode().strip())
+ except pyvisa.VisaIOError as e:
+ print(f'[ERROR!] Error failed to get reading: {e}')
+
+ def set_beeper_status(self, status='OFF'):
+ if status in self.BEEPER_STATUS:
+ try:
+ self.write(f'BEEP {status}')
+ print(f'[INFO] Beeper is set to {status}')
+ except pyvisa.VisaIOError as e:
+ print(f'[ERROR!] Error setting beeper status: {e}')
+ else:
+ print(f'[WARNING!] The passed beeper status is not allowed.'
+ f'Choose from {self.BEEPER_STATUS}')
+
+ def set_function(self, function='DCV'):
+ """
+ Set the measurement function of the multimeter.
+
+ :param function: Measurement function .
+ """
+ if function in self.FUNCTIONS:
+ try:
+ self.write(f"FUNC {function}")
+ print(f"[INFO] Measurement function set to {function}")
+ except pyvisa.VisaIOError as e:
+ print(f"[ERROR!] Error setting measurement function: {e}")
+ else:
+ print(f"[WARNING!] Invalid measurement function '{function}'. "
+ f"Valid functions are: {', '.join(self.FUNCTIONS)}")
+
+ def get_temperature(self):
+ try:
+ self.write("TEMP?")
+ temp = float(self.read(16).decode().strip())
+ print(f"[INFO] DMM's internal temperature is: {temp}")
+ return temp
+ except pyvisa.VisaIOError as e:
+ print(f"[ERROR!] Error retrieving DMM's internal temperature: {e}")
+
+ def enable_keyboard(self, status=True):
+ if status:
+ try:
+ self.write("LOCK ON")
+ print(f"[INFO] DMM's keyboard is unlocked.")
+ except pyvisa.VisaIOError as e:
+ print(f"[ERROR!] Error unlocking the DMM's keyboard: {e}")
+ else:
+ try:
+ self.write("LOCK OFF")
+ print(f"[INFO] DMM's keyboard is locked.")
+ except pyvisa.VisaIOError as e:
+ print(f"[ERROR!] Error locking the DMM's keyboard: {e}")
+
+
+ #-----------------------------------------------------------
+ # Methods to comply with other DMM QOLab module drivers
+ #-----------------------------------------------------------
+ @BasicInstrument.tsdb_append
+ def getVdc(self):
+ self.set_function('DCV')
+ return self.get_reading()
+
+ @BasicInstrument.tsdb_append
+ def getVac(self):
+ self.set_function('ACV')
+ return self.get_reading()
+
+ @BasicInstrument.tsdb_append
+ def getAdc(self):
+ self.set_function('DCI')
+ return self.get_reading()
+
+ @BasicInstrument.tsdb_append
+ def getAac(self):
+ self.set_function('ACI')
+ return self.get_reading()
+
+# Example usage
+if __name__ == "__main__":
+ import time
+
+ rm = pyvisa.ResourceManager()
+ instrument = rm.open_resource("visa://192.168.194.15/GPIB1::22::INSTR")
+ dmm = HP3457A(instrument)
+ dmm.set_format('ASCII')
+ dmm.set_beeper_status('ONCE')
+ dmm.set_function('DCI')
+ print(dmm.get_reading())
+ dmm.close()