hitl_tester.modules.bms.m300_dmm
Provides controls for the Rigol M300 DMM.
(c) 2020-2024 TurnAround Factor, Inc.
#
CUI DISTRIBUTION CONTROL
Controlled by: DLA J68 R&D SBIP
CUI Category: Small Business Research and Technology
Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS
POC: GOV SBIP Program Manager Denise Price, 571-767-0111
Distribution authorized to U.S. Government Agencies only, to protect information not owned by the
U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that
it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests
for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317,
Fort Belvoir, VA 22060-6221
#
SBIR DATA RIGHTS
Contract No.:SP4701-23-C-0083
Contractor Name: TurnAround Factor, Inc.
Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005
Expiration of SBIR Data Rights Period: September 24, 2029
The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer
software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights
in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause
contained in the above identified contract. No restrictions apply after the expiration date shown above. Any
reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce
the markings.
1""" 2Provides controls for the Rigol M300 DMM. 3 4# (c) 2020-2024 TurnAround Factor, Inc. 5# 6# CUI DISTRIBUTION CONTROL 7# Controlled by: DLA J68 R&D SBIP 8# CUI Category: Small Business Research and Technology 9# Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS 10# POC: GOV SBIP Program Manager Denise Price, 571-767-0111 11# Distribution authorized to U.S. Government Agencies only, to protect information not owned by the 12# U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that 13# it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests 14# for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317, 15# Fort Belvoir, VA 22060-6221 16# 17# SBIR DATA RIGHTS 18# Contract No.:SP4701-23-C-0083 19# Contractor Name: TurnAround Factor, Inc. 20# Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005 21# Expiration of SBIR Data Rights Period: September 24, 2029 22# The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer 23# software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights 24# in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause 25# contained in the above identified contract. No restrictions apply after the expiration date shown above. Any 26# reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce 27# the markings. 28""" 29 30from __future__ import annotations 31 32import time 33 34from hitl_tester.modules.bms_types import SafeResource, DMMImpedance 35from hitl_tester.modules.logger import logger 36 37 38class M300Dmm: 39 """Rigol M300 DMM command wrapper.""" 40 41 def __init__(self, resource: SafeResource, scan_list: list[dict[str, int]]): 42 """ 43 Initialize the M300 wrapper with a specific PyVISA resource. 44 This class does open the resource, don't open it for yourself! 45 """ 46 self.resource = resource 47 self.scan_list = scan_list 48 self.scan_index = 0 49 self.voltage_range = 20 # The maximum voltage that can be measured. Range(V) = 0.2, 2, 20, 200, 1000, AUTO 50 self.impedance_config = DMMImpedance(2, 7) 51 self.reset() 52 53 @property 54 def _scan_list_str(self): 55 """A string used by the VISA commands.""" 56 slot = self.scan_list[self.scan_index]["slot"] 57 channel = self.scan_list[self.scan_index]["channel"] 58 return f"(@{slot}{channel:02})" 59 60 def configure_voltage_trigger(self, timestamps: bool = False): 61 """Set up trigger parameters. Up to 50,000 readings.""" 62 self.resource.lock.acquire() 63 self.resource.write(f"CONF:VOLT:DC {self.voltage_range},MAX,{self._scan_list_str}") # Fastest integration 64 self.resource.write(f"ROUT:SCAN {self._scan_list_str}") # Select only this channel for the trigger 65 self.resource.write("ZERO:AUTO OFF") # No zero reading. Speeds up measurement 66 if timestamps: 67 self.resource.write("FORM:READ:TIME ON") # Include relative timestamp 68 # self.resource.write("TRIG:SOUR IMM") # Software trigger 69 self.resource.write("TRIG:SOUR TIM") # Software trigger 70 self.resource.write("TRIG:TIM 0.001") # 0.001 seconds between measurements 71 self.resource.write("TRIG:COUN 300") # 1 to 50000 scans (~250 Hz) 72 self.resource.write("*ESE 1") # Enable status bit (helps determine when operation completed) 73 74 def send_trigger(self): 75 """Send a software trigger.""" 76 77 # FIXME(JA): Speed up measurements 78 # self.resource.write(f"VOLT:DC:APER MIN,{self._scan_list_str}") 79 # self.resource.write(f"VOLT:DC:NPLC MIN,{self._scan_list_str}") 80 # self.resource.write(f"VOLT:DC:RES MAX,{self._scan_list_str}") 81 # self.resource.write(f"ROUT:CHAN:DEL 0,{self._scan_list_str}") 82 83 self.resource.write("INIT") # Run scans 84 self.resource.write("*OPC") # Set status bit to 1 when operation completes 85 time.sleep(0.25) 86 87 def read_internal_memory(self) -> list[float]: 88 """Return measured data from internal memory.""" 89 90 # Wait until operation completes 91 while int(self.resource.query("*ESR?")) & 1 != 1: 92 time.sleep(1) 93 94 # Fetch measurements 95 result = self.resource.query("FETC?") 96 97 # Decode measurements 98 last_valid_num = 0 99 100 def decode_num(string_num): 101 """Gracefully handle invalid numbers in returned values.""" 102 nonlocal last_valid_num 103 try: 104 last_valid_num = float(string_num) 105 except ValueError: 106 logger.write_debug_to_report(f"Could not convert: {string_num}, using {last_valid_num}") 107 return last_valid_num 108 109 return list(map(decode_num, result.split(","))) 110 111 def configure_voltage_normal(self): 112 """The voltage settings for normal measurements.""" 113 self.resource.write(f"ROUT:SCAN {self._scan_list_str}") # Select only this channel for the trigger 114 self.resource.write("ZERO:AUTO ON") # Activate zero reading to increase accuracy 115 self.resource.write("VOLT:DC:NPLC 10") # NPLC = 0.02, 0.2, 1, 10, 100, 200. 1PLC = 0.02s 116 self.resource.write("FORM:READ:TIME OFF") # Remove relative timestamp 117 self.resource.lock.release() 118 119 @property 120 def volts(self) -> float: 121 """Measures DC voltage.""" 122 return float(self.resource.query(f"MEAS:VOLT:DC? {self.voltage_range},DEF,{self._scan_list_str}")) 123 124 @property 125 def amps_ac(self) -> float: 126 """Measures AC current.""" 127 return float(self.resource.query(f"MEAS:CURR:AC? AUTO,DEF,{self._scan_list_str}")) 128 129 def reset(self): 130 """Resets the instrument.""" 131 self.resource.write("*CLS") # Reset any errors 132 self.resource.write("*RST") # Put the system in a known state 133 # resource.write("FORM:READ:TIME ON") # Include relative timestamp 134 for self.scan_index in range(len(self.scan_list)): 135 self.resource.lock.acquire() 136 self.configure_voltage_normal() 137 138 for i in range(100, 600, 100): 139 logger.write_debug_to_report(f"Slot {i}: {self.resource.query(f'SYST:CTYP? {i}').rstrip(chr(10))}")
class
M300Dmm:
39class M300Dmm: 40 """Rigol M300 DMM command wrapper.""" 41 42 def __init__(self, resource: SafeResource, scan_list: list[dict[str, int]]): 43 """ 44 Initialize the M300 wrapper with a specific PyVISA resource. 45 This class does open the resource, don't open it for yourself! 46 """ 47 self.resource = resource 48 self.scan_list = scan_list 49 self.scan_index = 0 50 self.voltage_range = 20 # The maximum voltage that can be measured. Range(V) = 0.2, 2, 20, 200, 1000, AUTO 51 self.impedance_config = DMMImpedance(2, 7) 52 self.reset() 53 54 @property 55 def _scan_list_str(self): 56 """A string used by the VISA commands.""" 57 slot = self.scan_list[self.scan_index]["slot"] 58 channel = self.scan_list[self.scan_index]["channel"] 59 return f"(@{slot}{channel:02})" 60 61 def configure_voltage_trigger(self, timestamps: bool = False): 62 """Set up trigger parameters. Up to 50,000 readings.""" 63 self.resource.lock.acquire() 64 self.resource.write(f"CONF:VOLT:DC {self.voltage_range},MAX,{self._scan_list_str}") # Fastest integration 65 self.resource.write(f"ROUT:SCAN {self._scan_list_str}") # Select only this channel for the trigger 66 self.resource.write("ZERO:AUTO OFF") # No zero reading. Speeds up measurement 67 if timestamps: 68 self.resource.write("FORM:READ:TIME ON") # Include relative timestamp 69 # self.resource.write("TRIG:SOUR IMM") # Software trigger 70 self.resource.write("TRIG:SOUR TIM") # Software trigger 71 self.resource.write("TRIG:TIM 0.001") # 0.001 seconds between measurements 72 self.resource.write("TRIG:COUN 300") # 1 to 50000 scans (~250 Hz) 73 self.resource.write("*ESE 1") # Enable status bit (helps determine when operation completed) 74 75 def send_trigger(self): 76 """Send a software trigger.""" 77 78 # FIXME(JA): Speed up measurements 79 # self.resource.write(f"VOLT:DC:APER MIN,{self._scan_list_str}") 80 # self.resource.write(f"VOLT:DC:NPLC MIN,{self._scan_list_str}") 81 # self.resource.write(f"VOLT:DC:RES MAX,{self._scan_list_str}") 82 # self.resource.write(f"ROUT:CHAN:DEL 0,{self._scan_list_str}") 83 84 self.resource.write("INIT") # Run scans 85 self.resource.write("*OPC") # Set status bit to 1 when operation completes 86 time.sleep(0.25) 87 88 def read_internal_memory(self) -> list[float]: 89 """Return measured data from internal memory.""" 90 91 # Wait until operation completes 92 while int(self.resource.query("*ESR?")) & 1 != 1: 93 time.sleep(1) 94 95 # Fetch measurements 96 result = self.resource.query("FETC?") 97 98 # Decode measurements 99 last_valid_num = 0 100 101 def decode_num(string_num): 102 """Gracefully handle invalid numbers in returned values.""" 103 nonlocal last_valid_num 104 try: 105 last_valid_num = float(string_num) 106 except ValueError: 107 logger.write_debug_to_report(f"Could not convert: {string_num}, using {last_valid_num}") 108 return last_valid_num 109 110 return list(map(decode_num, result.split(","))) 111 112 def configure_voltage_normal(self): 113 """The voltage settings for normal measurements.""" 114 self.resource.write(f"ROUT:SCAN {self._scan_list_str}") # Select only this channel for the trigger 115 self.resource.write("ZERO:AUTO ON") # Activate zero reading to increase accuracy 116 self.resource.write("VOLT:DC:NPLC 10") # NPLC = 0.02, 0.2, 1, 10, 100, 200. 1PLC = 0.02s 117 self.resource.write("FORM:READ:TIME OFF") # Remove relative timestamp 118 self.resource.lock.release() 119 120 @property 121 def volts(self) -> float: 122 """Measures DC voltage.""" 123 return float(self.resource.query(f"MEAS:VOLT:DC? {self.voltage_range},DEF,{self._scan_list_str}")) 124 125 @property 126 def amps_ac(self) -> float: 127 """Measures AC current.""" 128 return float(self.resource.query(f"MEAS:CURR:AC? AUTO,DEF,{self._scan_list_str}")) 129 130 def reset(self): 131 """Resets the instrument.""" 132 self.resource.write("*CLS") # Reset any errors 133 self.resource.write("*RST") # Put the system in a known state 134 # resource.write("FORM:READ:TIME ON") # Include relative timestamp 135 for self.scan_index in range(len(self.scan_list)): 136 self.resource.lock.acquire() 137 self.configure_voltage_normal() 138 139 for i in range(100, 600, 100): 140 logger.write_debug_to_report(f"Slot {i}: {self.resource.query(f'SYST:CTYP? {i}').rstrip(chr(10))}")
Rigol M300 DMM command wrapper.
M300Dmm( resource: hitl_tester.modules.bms_types.SafeResource, scan_list: list[dict[str, int]])
42 def __init__(self, resource: SafeResource, scan_list: list[dict[str, int]]): 43 """ 44 Initialize the M300 wrapper with a specific PyVISA resource. 45 This class does open the resource, don't open it for yourself! 46 """ 47 self.resource = resource 48 self.scan_list = scan_list 49 self.scan_index = 0 50 self.voltage_range = 20 # The maximum voltage that can be measured. Range(V) = 0.2, 2, 20, 200, 1000, AUTO 51 self.impedance_config = DMMImpedance(2, 7) 52 self.reset()
Initialize the M300 wrapper with a specific PyVISA resource. This class does open the resource, don't open it for yourself!
def
configure_voltage_trigger(self, timestamps: bool = False):
61 def configure_voltage_trigger(self, timestamps: bool = False): 62 """Set up trigger parameters. Up to 50,000 readings.""" 63 self.resource.lock.acquire() 64 self.resource.write(f"CONF:VOLT:DC {self.voltage_range},MAX,{self._scan_list_str}") # Fastest integration 65 self.resource.write(f"ROUT:SCAN {self._scan_list_str}") # Select only this channel for the trigger 66 self.resource.write("ZERO:AUTO OFF") # No zero reading. Speeds up measurement 67 if timestamps: 68 self.resource.write("FORM:READ:TIME ON") # Include relative timestamp 69 # self.resource.write("TRIG:SOUR IMM") # Software trigger 70 self.resource.write("TRIG:SOUR TIM") # Software trigger 71 self.resource.write("TRIG:TIM 0.001") # 0.001 seconds between measurements 72 self.resource.write("TRIG:COUN 300") # 1 to 50000 scans (~250 Hz) 73 self.resource.write("*ESE 1") # Enable status bit (helps determine when operation completed)
Set up trigger parameters. Up to 50,000 readings.
def
send_trigger(self):
75 def send_trigger(self): 76 """Send a software trigger.""" 77 78 # FIXME(JA): Speed up measurements 79 # self.resource.write(f"VOLT:DC:APER MIN,{self._scan_list_str}") 80 # self.resource.write(f"VOLT:DC:NPLC MIN,{self._scan_list_str}") 81 # self.resource.write(f"VOLT:DC:RES MAX,{self._scan_list_str}") 82 # self.resource.write(f"ROUT:CHAN:DEL 0,{self._scan_list_str}") 83 84 self.resource.write("INIT") # Run scans 85 self.resource.write("*OPC") # Set status bit to 1 when operation completes 86 time.sleep(0.25)
Send a software trigger.
def
read_internal_memory(self) -> list[float]:
88 def read_internal_memory(self) -> list[float]: 89 """Return measured data from internal memory.""" 90 91 # Wait until operation completes 92 while int(self.resource.query("*ESR?")) & 1 != 1: 93 time.sleep(1) 94 95 # Fetch measurements 96 result = self.resource.query("FETC?") 97 98 # Decode measurements 99 last_valid_num = 0 100 101 def decode_num(string_num): 102 """Gracefully handle invalid numbers in returned values.""" 103 nonlocal last_valid_num 104 try: 105 last_valid_num = float(string_num) 106 except ValueError: 107 logger.write_debug_to_report(f"Could not convert: {string_num}, using {last_valid_num}") 108 return last_valid_num 109 110 return list(map(decode_num, result.split(",")))
Return measured data from internal memory.
def
configure_voltage_normal(self):
112 def configure_voltage_normal(self): 113 """The voltage settings for normal measurements.""" 114 self.resource.write(f"ROUT:SCAN {self._scan_list_str}") # Select only this channel for the trigger 115 self.resource.write("ZERO:AUTO ON") # Activate zero reading to increase accuracy 116 self.resource.write("VOLT:DC:NPLC 10") # NPLC = 0.02, 0.2, 1, 10, 100, 200. 1PLC = 0.02s 117 self.resource.write("FORM:READ:TIME OFF") # Remove relative timestamp 118 self.resource.lock.release()
The voltage settings for normal measurements.
volts: float
120 @property 121 def volts(self) -> float: 122 """Measures DC voltage.""" 123 return float(self.resource.query(f"MEAS:VOLT:DC? {self.voltage_range},DEF,{self._scan_list_str}"))
Measures DC voltage.
amps_ac: float
125 @property 126 def amps_ac(self) -> float: 127 """Measures AC current.""" 128 return float(self.resource.query(f"MEAS:CURR:AC? AUTO,DEF,{self._scan_list_str}"))
Measures AC current.
def
reset(self):
130 def reset(self): 131 """Resets the instrument.""" 132 self.resource.write("*CLS") # Reset any errors 133 self.resource.write("*RST") # Put the system in a known state 134 # resource.write("FORM:READ:TIME ON") # Include relative timestamp 135 for self.scan_index in range(len(self.scan_list)): 136 self.resource.lock.acquire() 137 self.configure_voltage_normal() 138 139 for i in range(100, 600, 100): 140 logger.write_debug_to_report(f"Slot {i}: {self.resource.query(f'SYST:CTYP? {i}').rstrip(chr(10))}")
Resets the instrument.