Repository navigation
Completing RedPitaya Move Plugins with additional functionalities of the Fast and Slow Analog outputs. #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0858818
ff9b365
f2a184a
34f6aaf
99c5ddb
6737059
b359bb6
5840cfc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
|
|
||
| from typing import Union, List, Dict | ||
| from pymodaq.control_modules.move_utility_classes import (DAQ_Move_base, comon_parameters_fun, | ||
| main, DataActuatorType, DataActuator) | ||
|
|
||
| from pymodaq_utils.utils import ThreadCommand # object used to send info back to the main thread | ||
|
|
||
| from pymodaq_gui.parameter import Parameter | ||
|
|
||
| from pymeasure.instruments.redpitaya.redpitaya_scpi import RedPitayaScpi, AnalogOutputFastChannel | ||
|
|
||
| from pymodaq_data import Q_ | ||
|
|
||
| from pymodaq_plugins_redpitaya.utils import Config | ||
|
|
||
| plugin_config = Config() | ||
|
|
||
| class DAQ_Move_RedpitayaAnalogSlow(DAQ_Move_base): | ||
| """ Instrument plugin class for Red Pitaya | ||
|
|
||
| This object inherits all functionalities to communicate with PyMoDAQ’s DAQ_Move module through | ||
| inheritance via DAQ_Move_base. It makes a bridge between the DAQ_Move module and the | ||
| Python wrapper of a particular instrument. | ||
|
|
||
| * Should be compatible with all redpitaya flavours using the SCPI communication protocol | ||
| * Tested with Red Pitaya 2.00-37 OS | STEMlab 125-14 Pro (Gen 2) version | ||
| * PyMoDAQ >= 5.0.5 | ||
| * Tested with Linux Ubuntu 24.04.1 LTS | ||
| * Installation instruction: | ||
| These instructions are valid when using an Ethernet cable to communicate with the Red Pitaya. | ||
| The instrument is accessed using a TCP/IP Socket communication adapter, in the form: “TCPIP::x.y.z.k::port::SOCKET” | ||
| - x.y.z.k is the IP address of the SCPI server (that should be activated on the board) | ||
| - port is the TCP/IP port number, usually 5000 | ||
| To activate the use of the Red Pitaya: | ||
| 1. Connect the redpitaya to your computer/network with an ethernet cable. | ||
| 2. Enter the url address written on the network plug (on the Red Pitaya) | ||
| It should be something like “RP-F0B462.LOCAL/” | ||
| 3. Browse the menu, open the System application then the Network manager application | ||
| Look at the ip_address, and modify the config_template | ||
| 4. Return to the menu, open the Development application and activate the SCPI server. | ||
| 5. You can now run daq_move_RedpiatayaSCPI.py | ||
|
|
||
| Attributes: | ||
| ----------- | ||
| controller: object | ||
| The particular object that allow the communication with the hardware, in general a python wrapper around the | ||
| hardware library. | ||
| """ | ||
|
|
||
| is_multiaxes = True | ||
| _axis_names: Union[List[str], Dict[str, int]] = ['voltage'] | ||
| _controller_units: Union[str, List[str]] = ['V'] | ||
| _epsilon: Union[float, List[float]] = [0.128] # Detailing 5mV and 1Hz. Resolution of reading is 5mV | ||
| # _epsilon: Union[float, List[float]] = 0.1 # Detailing 1mV and 1Hz #TODO replace this by a value that is correct depending on your controller | ||
| # TODO it could be a single float of a list of float (as much as the number of axes) | ||
| data_actuator_type = DataActuatorType.DataActuator | ||
|
|
||
| params = [ {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', | ||
| 'value': plugin_config('ip_address')}, | ||
| {'title': 'Port:', 'name': 'port', 'type': 'int', 'value': plugin_config('port')}, | ||
| {'title': 'Board name:', 'name': 'bname', 'type': 'str', 'readonly': True}, | ||
|
|
||
| {'title': 'Channel', 'name': 'channel', 'type': 'list', 'limits':{'0': 0, '1': 1, '2': 2, '3': 3}, | ||
| 'value': plugin_config('generator_slow', 'channel')}, | ||
|
|
||
| ] + comon_parameters_fun(is_multiaxes, axis_names=_axis_names, epsilon=_epsilon) | ||
| # _epsilon is the initial default value for the epsilon parameter allowing pymodaq to know if the controller reached | ||
| # the target value. It is the developer responsibility to put here a meaningful value | ||
|
|
||
| def ini_attributes(self): | ||
| self.controller: RedPitayaScpi = None | ||
| self.target_value = 0 | ||
| # self.conv_factor = 50e-6 | ||
| # self.scale = True | ||
| pass | ||
|
|
||
| def get_actuator_value(self): | ||
| """Get the current value from the hardware with scaling conversion. | ||
|
|
||
| Returns | ||
| ------- | ||
| float: The position obtained after scaling conversion. | ||
| """ | ||
|
|
||
| pos = DataActuator(data=getattr(self.aout_slow, self.axis_name), | ||
| units=self.axis_unit) | ||
| pos = self.get_position_with_scaling(pos) | ||
| return pos | ||
|
|
||
|
|
||
| def close(self): | ||
| """Terminate the communication protocol""" | ||
| self.aout_slow.reset() | ||
|
|
||
|
|
||
| def commit_settings(self, param: Parameter): | ||
| """Apply the consequences of a change of value in the detector settings | ||
|
|
||
| Parameters | ||
| ---------- | ||
| param: Parameter | ||
| A given parameter (within detector_settings) whose value has been changed by the user | ||
| """ | ||
| if param.name() == 'axis': | ||
| if param.value == 'voltage': | ||
| # self.settings.child('bounds', 'is_bounds').value() | ||
| self.settings.child('bounds', 'min_bound').setValue(0) | ||
| self.settings.child('bounds', 'max_bound').setValue(1.8) | ||
| self.settings.child('bounds', 'is_bounds').setValue(True) | ||
|
|
||
|
|
||
| # def is_enabled(self) -> bool: | ||
| # "It defines if the supply voltage is enabled on the output channel chosen" | ||
| # return self.settings['enable'] == True | ||
|
|
||
| @property | ||
| def aout_slow(self): | ||
| """ It defines what output channel the user chose""" | ||
| return self.controller.analog_out_slow[self.settings['channel']] | ||
|
|
||
| def ini_stage(self, controller=None): | ||
| """Actuator communication initialization | ||
|
|
||
| Parameters | ||
| ---------- | ||
| controller: (object) | ||
| custom object of a PyMoDAQ plugin (Slave case). None if only one actuator by controller (Master case) | ||
|
|
||
| Returns | ||
| ------- | ||
| info: str | ||
| initialized: bool | ||
| False if initialization failed otherwise True | ||
| """ | ||
|
|
||
| if self.is_master: # is needed when controller is master | ||
| self.controller = RedPitayaScpi(ip_address=self.settings['ip_address']) # arguments for instantiation!) | ||
| # self.controller = RedPitayaScpi(ip_address=plugin_config('ip_address')) # arguments for instantiation!) | ||
| else: | ||
| self.controller = controller | ||
|
|
||
| self.settings.child('bounds', 'min_bound').setValue(0) | ||
| self.settings.child('bounds', 'max_bound').setValue(1.8) | ||
| self.settings.child('bounds', 'is_bounds').value() | ||
| self.settings.child('bounds', 'is_bounds').setValue(True) | ||
| # self.settings.child('bounds', 'is_bounds').setOpts(readonly=True) | ||
|
|
||
| info = "Whatever info you want to log" | ||
| initialized = True | ||
| return info, initialized | ||
|
|
||
| def move_abs(self, value: DataActuator): | ||
| """ Move the actuator to the absolute target defined by value | ||
|
|
||
| Parameters | ||
| ---------- | ||
| value: (float) value of the absolute target positioning | ||
| """ | ||
| value = self.check_bound(value) #if user checked bounds, the defined bounds are applied here | ||
| self.target_value = value | ||
| print(value) | ||
| value = self.set_position_with_scaling(value) # TODO apply scaling if the user specified one | ||
| print(self.axis_unit) | ||
| setattr(self.aout_slow, self.axis_name, value.value(self.axis_unit)) | ||
| # TODO change this run command to work with other trigger sources than INTernal trigger | ||
|
|
||
| def move_rel(self, value: DataActuator): | ||
| """ Move the actuator to the relative target actuator value defined by value | ||
|
|
||
| Parameters | ||
| ---------- | ||
| value: (float) value of the relative target positioning | ||
| """ | ||
| value = self.check_bound(self.current_position + value) - self.current_position | ||
| self.target_value = value + self.current_position | ||
| self.move_abs(self.target_value) | ||
|
|
||
| def move_home(self): | ||
| """Call the reference method of the controller""" | ||
| pass | ||
|
|
||
| def stop_motion(self): | ||
| """Stop the actuator and emits move_done signal""" | ||
| pass | ||
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| main(__file__, init=False) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ class DAQ_Move_RedpitayaSCPI(DAQ_Move_base): | |
| Python wrapper of a particular instrument. | ||
|
|
||
| * Should be compatible with all redpitaya flavours using the SCPI communication protocol | ||
| * Tested with Red Pitaya 2.00-37 OS | STEMlab 125-14 | ||
| * Tested with Red Pitaya 2.00-37 OS | STEMlab 125-14 Pro (Gen 2) version | ||
| * PyMoDAQ >= 5.0.5 | ||
| * Tested with Linux Ubuntu 24.04.1 LTS | ||
| * Installation instruction: | ||
|
|
@@ -50,14 +50,19 @@ class DAQ_Move_RedpitayaSCPI(DAQ_Move_base): | |
| is_multiaxes = True | ||
| _axis_names: Union[List[str], Dict[str, int]] = ['amplitude', 'frequency'] | ||
| _controller_units: Union[str, List[str]] = ['V','Hz'] | ||
| _epsilon: Union[float, List[float]] = 0.1 # Detailing 1mV and 1Hz #TODO replace this by a value that is correct depending on your controller | ||
| _epsilon: Union[float, List[float]] = [0.005, 1] # Detailing 5mV and 1Hz. Resolution of reading is 5mV | ||
| # _epsilon: Union[float, List[float]] = 0.1 # Detailing 1mV and 1Hz #TODO replace this by a value that is correct depending on your controller | ||
| # TODO it could be a single float of a list of float (as much as the number of axes) | ||
| data_actuator_type = DataActuatorType.DataActuator | ||
|
|
||
| params = [ {'title': 'IP Address:', 'name': 'ip_address', 'type': 'str', | ||
| 'value': plugin_config('ip_address')}, | ||
| {'title': 'Port:', 'name': 'port', 'type': 'int', 'value': plugin_config('port')}, | ||
| {'title': 'Board name:', 'name': 'bname', 'type': 'str', 'readonly': True}, | ||
|
|
||
| {'title': 'Board Generation', 'name': 'gen', 'type': 'list', 'limits': {'Original': 1, 'Gen 2': 2}, | ||
| 'value': plugin_config('generation')}, | ||
|
|
||
| {'title': 'Channel', 'name': 'channel', 'type': 'list', 'limits':{'1': 1, '2': 2}, | ||
| 'value': plugin_config('generator', 'channel')}, | ||
|
|
||
|
|
@@ -129,10 +134,14 @@ def commit_settings(self, param: Parameter): | |
| self.settings.child('bounds', 'max_bound').setValue(50e6) | ||
| elif param.value == 'amplitude': | ||
| self.settings.child('bounds', 'min_bound').setValue(0) | ||
| self.settings.child('bounds', 'max_bound').setValue(1) | ||
|
|
||
| self.settings.child('bounds', 'max_bound').setValue(self.settings['gen']) | ||
| self.settings.child('bounds', 'is_bounds').value() | ||
| self.settings.child('bounds', 'is_bounds').setValue(True) | ||
|
|
||
| elif param.name() == 'gen': | ||
| if self.axis_name == 'amplitude': | ||
| self.settings.child('bounds', 'min_bound').setValue(0) | ||
| self.settings.child('bounds', 'max_bound').setValue(param.value()) | ||
| elif param.name() == 'enable': | ||
| self.aout.enable = param.value() | ||
| elif param.name() == 'shape': | ||
|
|
@@ -155,6 +164,9 @@ def commit_settings(self, param: Parameter): | |
| self.aout.sweep_state = param.value() | ||
| elif param.name() == 'sweep_direction': | ||
| self.aout.sweep_direction = param.value() | ||
| elif param.name() == 'gen_trigger': | ||
| self.aout.gen_trigger_source = param.value() | ||
| # TODO implement trigger source setting correctly | ||
|
|
||
| def is_enabled(self) -> bool: | ||
| "It defines if the supply voltage is enabled on the output channel chosen" | ||
|
|
@@ -181,16 +193,24 @@ def ini_stage(self, controller=None): | |
| """ | ||
|
|
||
| if self.is_master: # is needed when controller is master | ||
| self.controller = RedPitayaScpi(ip_address=plugin_config('ip_address')) # arguments for instantiation!) | ||
| self.controller = RedPitayaScpi(ip_address=self.settings['ip_address']) # arguments for instantiation!) | ||
| else: | ||
| self.controller = controller | ||
|
|
||
| self.aout.shape = self.settings['shape'] #plugin_config('generator', 'shape') | ||
|
|
||
| if self.axis_name =='frequency': | ||
| self.settings.child('bounds', 'min_bound').setValue(1e-6) | ||
| self.settings.child('bounds', 'max_bound').setValue(50e6) | ||
| elif self.axis_name == 'amplitude': | ||
| self.settings.child('bounds', 'min_bound').setValue(0) | ||
| self.settings.child('bounds', 'max_bound').setValue(self.settings['gen']) | ||
| self.settings.child('bounds', 'is_bounds').value() | ||
| self.settings.child('bounds', 'is_bounds').setValue(True) | ||
| self.settings.child('bounds', 'is_bounds').setOpts(readonly=True) | ||
|
|
||
| self.aout.enable = True | ||
| self.aout.run() | ||
| self.aout.run() # TODO change this function to work with other trigger sources than INTernal trigger | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we had quite a lot of issues with triggering with gen1 redpitaya, wonder if they worked on that |
||
|
|
||
| info = "Whatever info you want to log" | ||
| initialized = True | ||
|
|
@@ -208,8 +228,9 @@ def move_abs(self, value: DataActuator): | |
| value = self.check_bound(value) #if user checked bounds, the defined bounds are applied here | ||
| self.target_value = value | ||
| value = self.set_position_with_scaling(value) # apply scaling if the user specified one | ||
|
|
||
| setattr(self.controller.analog_out[self.settings['channel']], self.axis_name, value.value(self.axis_unit)) | ||
| setattr(self.aout, self.axis_name, value.value(self.axis_unit)) | ||
| self.aout.run() # regenerates trigger, otherwise it will continue to generate on the passed settings | ||
| # TODO change this function to work with other trigger sources than INTernal trigger | ||
|
|
||
| def move_rel(self, value: DataActuator): | ||
| """ Move the actuator to the relative target actuator value defined by value | ||
|
|
@@ -232,4 +253,4 @@ def stop_motion(self): | |
|
|
||
|
|
||
| if __name__ == '__main__': | ||
| main(__file__) | ||
| main(__file__, init=False) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,8 +8,7 @@ | |
| from pymodaq.utils.parameter import Parameter | ||
| from pymodaq_plugins_redpitaya.utils import Config | ||
|
|
||
| from pymeasure.instruments.redpitaya.redpitaya_scpi import RedPitayaScpi | ||
|
|
||
| from pymeasure.instruments.redpitaya.redpitaya_scpi import RedPitayaScpi, AnalogInputFastChannel | ||
|
|
||
| class DAQ_1DViewer_RedPitayaSCPI(DAQ_Viewer_base): | ||
| """ Instrument plugin class for a 1D viewer. | ||
|
|
@@ -19,7 +18,7 @@ class DAQ_1DViewer_RedPitayaSCPI(DAQ_Viewer_base): | |
| Python wrapper of a particular instrument. | ||
|
|
||
| * Should be compatible with all redpitaya flavour using the SCPI communication protocol | ||
| * Tested with the STEMlab 125-14 version | ||
| * Tested with the STEMlab 125-14 Pro (Gen 2) version | ||
| * PyMoDAQ >= 4.1.0 | ||
|
|
||
| Attributes: | ||
|
|
@@ -47,8 +46,12 @@ class DAQ_1DViewer_RedPitayaSCPI(DAQ_Viewer_base): | |
| {'title': 'Buffer Length:', 'name': 'buffer_length', 'type': 'int', 'readonly': True}, | ||
| {'title': 'Window Length:', 'name': 'window_length', 'type': 'float', | ||
| 'value': 0, 'siPrefix': True, 'suffix': 's', 'readonly': True}, | ||
| {'title': 'Gain1 (jumper CH1):', 'name': 'gain1', 'type': 'list', | ||
| 'limits': RedPitayaScpi.GAINS, 'value': plugin_config('sampling', 'gain1')}, | ||
| {'title': 'Gain2 (jumper CH2):', 'name': 'gain2', 'type': 'list', | ||
| 'limits': RedPitayaScpi.GAINS, 'value': plugin_config('sampling', 'gain2')}, | ||
|
|
||
| ]}, | ||
| ]}, | ||
| {'title': 'Triggering:', 'name': 'triggering', 'type': 'group', 'children': [ | ||
| {'title': 'Source:', 'name': 'trigger_source', 'type': 'list', | ||
| 'limits': RedPitayaScpi.TRIGGER_SOURCES, 'value': plugin_config('trigger', 'source')}, | ||
|
|
@@ -95,6 +98,11 @@ def commit_settings(self, param: Parameter): | |
| elif param.name() == 'trigger_source': | ||
| self.controller.acq_trigger_source = param.value() | ||
|
|
||
| elif param.name() == 'gain1': | ||
| self.controller.acq_gain1 = param.value() | ||
| elif param.name() == 'gain2': | ||
| self.controller.acq_gain2 = param.value() | ||
|
|
||
| def _center_trigger(self): | ||
| if self.settings['triggering', 'center_trigger']: | ||
| self.controller.acq_trigger_delay_samples = \ | ||
|
|
@@ -134,8 +142,12 @@ def ini_detector(self, controller=None): | |
| self.controller.acq_format = 'ASCII' | ||
| self.controller.acq_units = 'VOLTS' | ||
|
|
||
| self.controller.acq_gain1 = self.settings['sampling', 'gain1'] | ||
| self.controller.acq_gain2 = self.settings['sampling', 'gain2'] | ||
|
|
||
| self.controller.acq_trigger_level = self.settings['triggering', 'level'] | ||
|
|
||
| self.controller.decimation = self.settings['sampling', 'decimation'] | ||
| self.controller.decimation = self.settings['sampling', 'decimation'] | ||
| self.controller.average_skipped_samples = self.settings['sampling', 'average'] | ||
| self.settings.child('sampling', 'buffer_length').setValue(self.controller.buffer_length) | ||
|
|
@@ -160,7 +172,7 @@ def grab_data(self, Naverage=1, **kwargs): | |
| ---------- | ||
| Naverage: int | ||
| Number of hardware averaging (if hardware averaging is possible, self.hardware_averaging should be set to | ||
| True in class preamble and you should code this implementation) | ||
| True in class preamble, and you should code this implementation) | ||
| kwargs: dict | ||
| others optionals arguments | ||
| """ | ||
|
|
@@ -196,7 +208,7 @@ def grab_data(self, Naverage=1, **kwargs): | |
| size=nsamples) | ||
| self.dte_signal.emit(DataToExport('Redpitaya_dte', | ||
| data=[DataFromPlugins(name='RedPitaya', data=data_list, | ||
| dim='Data1D', labels=['AI0', 'AI1'], | ||
| dim='Data1D', labels=['IN1', 'IN2'], | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why changing the labels?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To agree with what is physically written on the Redpitaya's ports. |
||
| axes=[axis])])) | ||
|
|
||
| def stop(self): | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this has changed wrt the redpitaya version, one should add_them in the plugin preferences adding also the selection of the type of board gen1 or 2