Hi Axel.
We tested the current HP 4141B version yesterday and at first, it looked fine.
Then, we took a curve of a simple diode within the range of -0.4V to +1.0V in 0.01V steps and noticed that the measurement points where not equally spaced. We repeated this with a stepsize of 0.1V and it worsened even more.
Uppon further investigation, it turned out that the SMU was not always proceeding with its voltage steps and sticking to previous values sometimes, putting out multiple measurements for these.
I digged into the GPIB protocol and saw something curious: SweepMe did not requested Voltage values in the form of [-0.4, -0.3, -0.2,…,+0.9, +1.0] but created rounding errors, requesting values like -0.299999999999999 etc.
In turn, these values with a lot of trailing numbers behind the decimal point where given over to the GPIB interface without shortening, effectivly overflowing the maximum number of ciffers that are allowed as a parameter for a voltage output on the HP 4141B. The SMU then just skipped the request for a new voltage step and repeated the measurement on the old voltage value, until a “proper” voltage would be requested again without rounding errors and within the ciffer limit.
To prevent this behaviour and to take care of maximum resolution, I implemented a conversion to scientific notation prior to giving the value over to the GPIB bus. The HP4141B GPIB/HPIB programming manual contains just examples with no more than 4 ciffers behind the decimal point, which is why I chose this limitation in the conversion to be on the safe side (I hope).
As a collegue of mine told me that we had observed a slight deficit of these units in accuracy on very low currents compared to modern units when choosing a fast integration speed, I took the time to implement the missing option of choosing the integration speed on the HP 4141B. Note that the manual states that just one integration speed can be set for all channels. Therefore, one should keep this setting identical on all channels used in a SweepMe sequence.
One last comment: there is an additional possibility to take measurements with the HP 4141B that allows for multiple channels to be measured at the same time. This methord works completely different by defining “measurement channels” first with the MC command and then executing an XE command to perform the simultaneous measurement on all defined channels at once. However, this procedure bears two main problems:
- The result is written in the buffer in just one line and has to be seperated dynamically depending on the amount of channels and their electrical quantity (Current or Voltage), which would mean programming a failure-proof processing on the output
- Once a channel has been designated as a “MeasurementChannel”, a simple single channel reading cannot be taken anymore. In essence, this conflicts with the current (standard) procedure to perform seperate measuerments on each channel as they appear in the SweepMe sequence. So extreme care would have to be taken that both measurement procedures do not interfere with each other.
For these reasons, I would propose to stick to the current version of performing the measurements.
# This Device Class is published under the terms of the MIT License.
# Required Third Party Libraries, which are included in the Device Class
# package for convenience purposes, may have a different license. You can
# find those in the corresponding folders or contact the maintainer.
#
# MIT License
#
# Copyright (c) 2024 SweepMe! GmbH (sweep-me.net)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# SweepMe! driver
# * Module: SMU
# * Instrument: HP 4141B
from pysweepme.EmptyDeviceClass import EmptyDevice
class Device(EmptyDevice):
def __init__(self):
EmptyDevice.__init__(self)
self.variables = ["Voltage", "Current"]
self.units = ["V", "A"]
self.plottype = [True, True] # True to plot data
self.savetype = [True, True] # True to save data
self.port_manager = True
self.port_types = ["GPIB"]
self.port_properties = {
"timeout": 3,
}
self.current_ranges = {
"Auto": "0",
"1 nA limited auto": "1",
"10 nA limited auto": "2",
"100 nA limited auto": "3",
"1 μA limited auto": "4",
"10 μA limited auto": "5",
"100 μA limited auto": "6",
"1 mA limited auto": "7",
"10 mA limited auto": "8",
"100 mA limited auto": "9",
}
def set_GUIparameter(self):
GUIparameter = {
"SweepMode": ["Voltage in V", "Current in A"],
"Channel": ["CH1", "CH2", "CH3", "CH4"],
"RouteOut": ["Rear"],
"Speed": ["Fast", "Medium", "Slow"],
"Range": list(self.current_ranges.keys()),
"Compliance": 100e-6,
"Average": 1,
}
return GUIparameter
def get_GUIparameter(self, parameter={}):
self.route_out = parameter['RouteOut']
self.source = parameter['SweepMode']
self.port_string = parameter['Port']
self.irange = self.current_ranges[parameter['Range']]
self.protection = parameter['Compliance']
self.speed = parameter['Speed']
self.average = int(parameter['Average'])
self.channel = parameter['Channel'][-1]
self.shortname = "HP4141B CH%s" % self.channel
# voltage autoranging
self.vrange = "0"
def initialize(self):
unique_DC_port_string = "HP4141B_" + self.port_string
# initialize commands only need to be sent once, so we check here whether another instance of the same
# driver AND same port did it already. If not, this instance is the first and has to do it.
if unique_DC_port_string not in self.device_communication:
self.port.write("ID") # identification
identifier = self.port.read()
# print("Identifier:", identifier)
self.port.write("*RST?") # reset to initial settings
self.port.write("BC") # buffer clear
self.port.write("FMT 2") # use to change the output format
# if initialize commands have been sent, we can add the unique_DC_port_string to the dictionary that
# is seen by all drivers
self.device_communication[unique_DC_port_string] = True
def configure(self):
# The command CN has to be sent at the beginning as it resets certain parameters
# If the command CN would be used later, e.g. during 'poweron', it would overwrite parameters that are
# defined during 'configure'
self.port.write("CN " + self.channel) # switches the channel on
if self.speed == "Fast": # 1 Short (4 measurements with 4 per PLC) preconfigured selection Fast
self.port.write("IT 1")
#self.port.write("AAD %s,0" % self.channel)
elif self.speed == "Medium": # 2 Medium (16 measurements with 16 per PLC) preconfigured selection Normal
self.port.write("IT 2")
#self.port.write("AAD %s,1" % self.channel)
elif self.speed == "Slow": # 3 Long (256 measurements with 16 per PLC) preconfigured selection Quiet
self.port.write("IT 3")
#self.port.write("AAD %s,1" % self.channel)
def unconfigure(self):
self.port.write("IN" + self.channel)
# resets to zero volt
# self.port.write("DZ")
def poweron(self):
pass
# In a previous version, the CN command was sent here. However, this leads to a reset of all parameters
# previously changed during 'configure'
# Therefore, the CN command should not be used here, but has been moved to the beginning of 'configure'
def poweroff(self):
# do not supply channel numbers as arguments, will not work on HP4141B otherwise
self.port.write("CL") # switches all channels off
# TODO: "CL" is called for each channel so more often than needed. In future, we can add a mechanism to do it
# only once by using self.device_communication dictionary.
# Please note that because CL takes no channel number and switches off all channel, also channels are powered
# off that should stay if they are even present in the next active branch of SweepMe! sequencer.
def apply(self):
value = str("{:.4E}".format(self.value))
if self.source.startswith("Voltage"):
self.port.write("DV " + self.channel + "," + self.vrange + "," + value + "," + self.protection)
if self.source.startswith("Current"):
self.port.write("DI " + self.channel + "," + self.irange + "," + value + "," + self.protection)
def measure(self):
# Current
self.port.write("TI " + self.channel)
answer = self.port.read()
# removes first three characters (can be "NAI", "NAV", "XAI", "XAV") and converts value to float
self.i = float(answer[3:])
# Voltage
self.port.write("TV " + self.channel)
answer = self.port.read()
# removes first three characters (can be "NAI", "NAV", "XAI", "XAV") and converts value to float
self.v = float(answer[3:])
def call(self):
return [self.v, self.i]
"""
Enables channels CN [chnum ... [,chnum] ... ]
Disables channels CL [chnum ... [,chnum] ... ]
Sets filter ON/OFF [FL] mode[,chnum ... [,chnum] ... ]
Sets series resistor ON/OFF [SSR] chnum,mode
Sets integration time
(Agilent B1500 can use
AAD/AIT instead of AV.)
[AV] number[,mode]
[AAD] chnum[,type]
[AIT] type,mode[,N]
Forces constant voltage DV chnum,range,output
[,comp[,polarity[,crange]]]
Forces constant current DI
Sets voltage measurement
range
[RV] chnum,range
Sets current measurement
range
[RI] chnum,range
[RM] chnum,mode[,rate]
Sets measurement mode MM 1,chnum[,chnum ... [,chnum] ... ]
Sets SMU operation mode [CMM] chnum,mode
Executes measurement XE
"""
Best wishes,
Christian
PS: the HP 4141B demands the “E” in the scientif notation to be a capital letter. These 80s machines were a bit “strict”