diff --git a/documentation/changelog.md b/documentation/changelog.md index 6e940baa23..b043688540 100644 --- a/documentation/changelog.md +++ b/documentation/changelog.md @@ -90,6 +90,7 @@ please use _ni_x_series_in_streamer.py_ as hardware module. * New SwitchInterface and updated logic plus GUI * Added biexponential fit function, model and estimator * Added custom circular loading indicator widget `qtwidgets.loading_indicator.CircleLoadingIndicator` +* Added hardware module to interface NI analog output cards via ProcessControlInterface Config changes: diff --git a/hardware/ni_analog_output.py b/hardware/ni_analog_output.py new file mode 100644 index 0000000000..56a8890813 --- /dev/null +++ b/hardware/ni_analog_output.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- + +""" +This file contains the qudi hardware module to use a National Instruments 9263 Analog output module + +Qudi is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Qudi is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Qudi. If not, see . + +Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the +top-level directory of this distribution and at +""" + +import numpy as np +import nidaqmx as ni + +from core.module import Base +from core.configoption import ConfigOption + +from interface.process_control_interface import ProcessControlInterface + + +class NIAnalogOutput(Base, ProcessControlInterface): + """ A module to interface a National Instruments device that can apply an anlog output +/- 10 V on 4 channels + + Tested with : NI9263 connected via USB cDAQ-9171 + + Example config : + + ni_analog_output: + module.Class: 'ni_analog_output.NIAnalogOutput' + device_name: 'cDAQ1Mod1' # optional + voltage_limits: [[-10, 10], [-10, 10], [-10, 10], [-10, 10]] # optional + """ + + _device_name = ConfigOption(name='device_name', default='cDAQ1Mod1') + _voltage_limits_config = ConfigOption('voltage_limits', default=[[-10, 10], [-10, 10], [-10, 10], [-10, 10]]) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._system = None + self._device = None + self._output_number = None + self._tasks = None + self._voltage_limits = None + self._current_state = None + + def on_activate(self): + """ Starts up the NI-card and performs sanity checks. """ + # Device check + self._system = ni.system.System.local() + device_names = self._system.devices.device_names + if self._device_name not in device_names: + self.log.error('Device "{0}" not found in connected devices: {1}'.format(self._device_name, device_names)) + self._device = self._system.devices[self._device_name] + self._output_number = len(self._device.ao_physical_chans) + # Config check + self._voltage_limits = np.array(self._voltage_limits_config) + if self._voltage_limits.shape != (self._output_number, 2): + self.log.error('Hardware has {} analog output. Config limits specifies {}.'.format( + self._output_number, self._voltage_limits.shape[0])) + self._tasks = [] + for i, channel in enumerate(self._device.ao_physical_chans): + task = ni.Task() + task.ao_channels.add_ao_voltage_chan(channel.name, + min_val=self._voltage_limits[i, 0], max_val=self._voltage_limits[i, 1]) + self._tasks.append(task) + self._current_state = np.zeros(self._output_number) + + def on_deactivate(self): + """ Shut down the NI card. + """ + for task in self._tasks: + task.write(0) + task.close() + self._current_state[:] = 0 + + def set_control_value(self, value, channel=0): + """ Set the voltage on an analog output channel """ + self._tasks[channel].write(value) + self._current_state[channel] = value + + def get_control_value(self, channel=None): + """ Get the voltage on an analog output channel + + NI API does not give any getter for current state so value is stored by the hardware module. + """ + return self._current_state[channel] + + def get_control_unit(self, channel=None): + """ Return the unit that the value is set in as a tuple of ('abbreviation', 'full unit name')""" + return 'V', 'Volt' + + def get_control_limit(self, channel=0): + """ Return limits within which the controlled value can be set as a tuple of (low limit, high limit) """ + return self._voltage_limits[channel] + + def process_control_supports_multiple_channels(self): + """ Function to test if hardware support multiple channels """ + return True + + def process_control_get_number_channels(self): + """ Function to get the number of channels available for control """ + return self._output_number