Skip to content

Commit

Permalink
Fixes Ametek7270 (#897)
Browse files Browse the repository at this point in the history
* Reply from setting a property using the check_errors

* symetry in setting/getting properties

Some properties use integer to set their actual floating point values. The driver gives the possibility to get the integer or the float directly but you could only set it using the integer representation.

That creates an asymetry not working well with pymeasure Control object. I therefore changed the get command to the one using the integer (without the dot)

* Update ametek7270.py

* using dynamic setting of sensitivities values depending on the voltage/current mode

* adding the measurement theta for the phase of the signal (different from the phase Control representing the harmonic reference phase)

* use ask at the place of write to assert the return value of the null character as specified in the doc

* Update pymeasure/instruments/ametek/ametek7270.py

Co-authored-by: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com>

* Update pymeasure/instruments/ametek/ametek7270.py

Co-authored-by: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com>

* added docstring

* added author and test file

* added default termination and casting id to str

* Update test_ametek7270.py

* added tests generated using Generator #882

* reimplement ask

to make sure the new line character is removed when using ask

* linting

* Update ametek7270.py

* moved reimplemented methods into Ametek7270 class and added comma

* removing generate_tests.py file

* adding reference mode method and checking if related measurements are valid

* Update pymeasure/instruments/ametek/ametek7270.py

Co-authored-by: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com>

* Update pymeasure/instruments/ametek/ametek7270.py

Co-authored-by: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com>

* removed numpy import and usage

* Update ametek7270.py

* trying to parametrize the invalid properties tests

* linting

* tests invalid properties

* Update pymeasure/instruments/ametek/ametek7270.py

Co-authored-by: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com>

* applying requested changes on tests and modified the id property

* Update test_ametek7270.py

---------

Co-authored-by: Benedikt Moneke <67148916+bmoneke@users.noreply.github.com>
  • Loading branch information
seb5g and BenediktBurger committed Jun 2, 2023
1 parent fe171bb commit 463dc6b
Show file tree
Hide file tree
Showing 3 changed files with 624 additions and 42 deletions.
3 changes: 2 additions & 1 deletion AUTHORS.txt
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,5 @@ Max Herbold
Alexander Wichers
Ashok Bruno
Robert Roos
Sebastian Neusch
Sebastien Weber
Sebastian Neusch
183 changes: 142 additions & 41 deletions pymeasure/instruments/ametek/ametek7270.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,24 @@
log.addHandler(logging.NullHandler())


def check_read_not_empty(value):
"""Called by some properties to check if the reply is not an empty string
that would mean the properties is currently invalid (probably because the reference mode
is on single or dual)"""
if value == '':
raise ValueError('Invalid response from measurement call, '
'probably because the reference mode is set on single or dual')
else:
return value


class Ametek7270(Instrument):
"""This is the class for the Ametek DSP 7270 lockin amplifier"""
"""This is the class for the Ametek DSP 7270 lockin amplifier
In this instrument, some measurements are defined only for specific modes,
called Reference modes, see :meth`set_reference_mode` and will raise errors
if called incorrectly
"""

SENSITIVITIES = [
0.0, 2.0e-9, 5.0e-9, 10.0e-9, 20.0e-9, 50.0e-9, 100.0e-9,
Expand All @@ -41,6 +57,11 @@ class Ametek7270(Instrument):
200.0e-3, 500.0e-3, 1.0
]

SENSITIVITIES_IMODE = {0: SENSITIVITIES,
1: [sen * 1e-6 for sen in SENSITIVITIES],
2: [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2e-15, 5e-15, 10e-15,
20e-15, 50e-15, 100e-15, 200e-15, 500e-15, 1e-12, 2e-12]}

TIME_CONSTANTS = [
10.0e-6, 20.0e-6, 50.0e-6, 100.0e-6, 200.0e-6, 500.0e-6,
1.0e-3, 2.0e-3, 5.0e-3, 10.0e-3, 20.0e-3, 50.0e-3, 100.0e-3,
Expand All @@ -50,161 +71,241 @@ class Ametek7270(Instrument):
]

sensitivity = Instrument.control( # NOTE: only for IMODE = 1.
"SEN.", "SEN %d",
"SEN", "SEN %d",
""" A floating point property that controls the sensitivity
range in Volts, which can take discrete values from 2 nV to
1 V. This property can be set. """,
validator=truncated_discrete_set,
values=SENSITIVITIES,
map_values=True
map_values=True,
check_set_errors=True,
dynamic=True,
)

slope = Instrument.control(
"SLOPE", "SLOPE %d",
""" A integer property that controls the filter slope in
dB/octave, which can take the values 6, 12, 18, or 24 dB/octave.
This property can be set. """,
validator=truncated_discrete_set,
values=[6, 12, 18, 24],
map_values=True
map_values=True,
check_set_errors=True,
)

time_constant = Instrument.control( # NOTE: only for NOISEMODE = 0
"TC.", "TC %d",
"TC", "TC %d",
""" A floating point property that controls the time constant
in seconds, which takes values from 10 microseconds to 100,000
seconds. This property can be set. """,
validator=truncated_discrete_set,
values=TIME_CONSTANTS,
map_values=True
map_values=True,
check_set_errors=True,
)
# TODO: Figure out if this actually can send for X1. X2. Y1. Y2. or not.
# There's nothing in the manual about it but UtilMOKE sends these.

x = Instrument.measurement("X.",
""" Reads the X value in Volts """
""" Reads the X value in Volts """,
get_process=check_read_not_empty,
)
y = Instrument.measurement("Y.",
""" Reads the Y value in Volts """
""" Reads the Y value in Volts """,
get_process=check_read_not_empty,
)
x1 = Instrument.measurement("X1.",
""" Reads the first harmonic X value in Volts """
""" Reads the first harmonic X value in Volts """,
get_process=check_read_not_empty,
)
y1 = Instrument.measurement("Y1.",
""" Reads the first harmonic Y value in Volts """
""" Reads the first harmonic Y value in Volts """,
get_process=check_read_not_empty,
)
x2 = Instrument.measurement("X2.",
""" Reads the second harmonic X value in Volts """
""" Reads the second harmonic X value in Volts """,
get_process=check_read_not_empty,
)
y2 = Instrument.measurement("Y2.",
""" Reads the second harmonic Y value in Volts """
""" Reads the second harmonic Y value in Volts """,
get_process=check_read_not_empty,
)
xy = Instrument.measurement("XY.",
""" Reads both the X and Y values in Volts """
""" Reads both the X and Y values in Volts """,
get_process=check_read_not_empty,
)
mag = Instrument.measurement("MAG.",
""" Reads the magnitude in Volts """
""" Reads the magnitude in Volts """,
get_process=check_read_not_empty,
)

theta = Instrument.measurement("PHA.",
""" Reads the signal phase in degrees """,
get_process=check_read_not_empty,
)

harmonic = Instrument.control(
"REFN", "REFN %d",
""" An integer property that represents the reference
harmonic mode control, taking values from 1 to 127.
This property can be set. """,
validator=truncated_discrete_set,
values=list(range(1, 128))
values=list(range(1, 128)),
check_set_errors=True,
)
phase = Instrument.control(
"REFP.", "REFP. %g",
""" A floating point property that represents the reference
harmonic phase in degrees. This property can be set. """,
validator=modular_range,
values=[0, 360]
values=[0, 360],
check_set_errors=True,
)
voltage = Instrument.control(
"OA.", "OA. %g",
""" A floating point property that represents the voltage
in Volts. This property can be set. """,
validator=truncated_range,
values=[0, 5]
values=[0, 5],
check_set_errors=True,
)
frequency = Instrument.control(
"OF.", "OF. %g",
""" A floating point property that represents the lock-in
frequency in Hz. This property can be set. """,
validator=truncated_range,
values=[0, 2.5e5]
values=[0, 2.5e5],
check_set_errors=True,
)
dac1 = Instrument.control(
"DAC. 1", "DAC. 1 %g",
""" A floating point property that represents the output
value on DAC1 in Volts. This property can be set. """,
validator=truncated_range,
values=[-10, 10]
values=[-10, 10],
check_set_errors=True,
)
dac2 = Instrument.control(
"DAC. 2", "DAC. 2 %g",
""" A floating point property that represents the output
value on DAC2 in Volts. This property can be set. """,
validator=truncated_range,
values=[-10, 10]
values=[-10, 10],
check_set_errors=True,
)
dac3 = Instrument.control(
"DAC. 3", "DAC. 3 %g",
""" A floating point property that represents the output
value on DAC3 in Volts. This property can be set. """,
validator=truncated_range,
values=[-10, 10]
values=[-10, 10],
check_set_errors=True,
)
dac4 = Instrument.control(
"DAC. 4", "DAC. 4 %g",
""" A floating point property that represents the output
value on DAC4 in Volts. This property can be set. """,
validator=truncated_range,
values=[-10, 10]
values=[-10, 10],
check_set_errors=True,
)
adc1 = Instrument.measurement("ADC. 1",
""" Reads the input value of ADC1 in Volts """
""" Reads the input value of ADC1 in Volts """,
get_process=check_read_not_empty,
)
adc2 = Instrument.measurement("ADC. 2",
""" Reads the input value of ADC2 in Volts """
""" Reads the input value of ADC2 in Volts """,
get_process=check_read_not_empty,
)
adc3 = Instrument.measurement("ADC. 3",
""" Reads the input value of ADC3 in Volts """
""" Reads the input value of ADC3 in Volts """,
get_process=check_read_not_empty,
)
adc4 = Instrument.measurement("ADC. 4",
""" Reads the input value of ADC4 in Volts """
""" Reads the input value of ADC4 in Volts """,
get_process=check_read_not_empty,
)
id = Instrument.measurement("ID",
""" Reads the instrument identification """
)

def __init__(self, adapter, name="Ametek DSP 7270", **kwargs):
def __init__(self, adapter, name="Ametek DSP 7270",
read_termination='\x00',
write_termination='\x00',
**kwargs):

super().__init__(
adapter,
name,
**kwargs
)
read_termination=read_termination,
write_termination=write_termination,
**kwargs)

def check_set_errors(self):
"""mandatory to be used for property setter
The Ametek protocol expect the default null character to be read to check the property
has been correctly set. With default termination character set as Null character,
this turns out as an empty string to be read.
"""
if self.read() == '':
return []
else:
return ['Incorrect return from previously set property']

def ask(self, command, query_delay=0):
"""Send a command and read the response, stripping white spaces.
Usually the properties use the `values` method that adds a strip call,
however several methods use directly the result from ask to be casted into some other types.
It should therefore also add the strip here, as all responses end with a newline character.
"""
return super().ask(command, query_delay).strip()

def set_reference_mode(self, mode: int = 0):
"""Set the instrument in Single, Dual or harmonic mode.
:param mode: the integer specifying the mode: 0 for Single, 1 for Dual harmonic, and 2 for
Dual reference.
"""
if mode not in [0, 1, 2]:
raise ValueError('Invalid reference mode')
self.ask(f'REFMODE {mode}')

def set_voltage_mode(self):
""" Sets instrument to voltage control mode """
self.write("IMODE 0")
self.ask("IMODE 0")
self.sensitivity_values = self.SENSITIVITIES_IMODE[0]

def set_differential_mode(self, lineFiltering=True):
""" Sets instrument to differential mode -- assuming it is in voltage mode """
self.write("VMODE 3")
self.write("LF %d 0" % 3 if lineFiltering else 0)
self.ask("VMODE 3")
self.ask("LF %d 0" % 3 if lineFiltering else 0)

def set_current_mode(self, low_noise=False):
""" Sets instrument to current control mode with either low noise or high bandwidth"""
if low_noise:
self.ask("IMODE 2")
self.sensitivity_values = self.SENSITIVITIES_IMODE[2]
else:
self.ask("IMODE 1")
self.sensitivity_values = self.SENSITIVITIES_IMODE[1]

def set_channel_A_mode(self):
""" Sets instrument to channel A mode -- assuming it is in voltage mode """
self.write("VMODE 1")
self.ask("VMODE 1")

@property
def id(self):
"""Get the instrument ID and firmware version"""
return f"{self.ask('ID')}/{self.ask('VER')}"

@property
def auto_gain(self):
return (int(self.ask("AUTOMATIC")) == 1)
return int(self.ask("AUTOMATIC")) == 1

@auto_gain.setter
def auto_gain(self, setval):
if setval:
self.write("AUTOMATIC 1")
self.ask("AUTOMATIC 1")
else:
self.write("AUTOMATIC 0")
self.ask("AUTOMATIC 0")

def shutdown(self):
""" Ensures the instrument in a safe state """
Expand Down
Loading

0 comments on commit 463dc6b

Please sign in to comment.