Skip to content
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

New counter functionalities #3

Merged
merged 18 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import numpy as np
from pymodaq.utils.daq_utils import ThreadCommand
from pymodaq.utils.data import DataFromPlugins
from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
from pymodaq.utils.parameter import Parameter

from pymodaq_plugins_daqmx.hardware.national_instruments.daqmx import DAQmx, \
Edge, ClockSettings, Counter, ClockCounter, TriggerSettings

from PyDAQmx import DAQmx_Val_DoNotInvertPolarity, DAQmxConnectTerms, DAQmx_Val_ContSamps
# , DAQmx_Val_FiniteSamps, DAQmx_Val_CurrReadPos, \
# DAQmx_Val_DoNotOverwriteUnreadSamps

class DAQ_0DViewer_DAQmx_PLcounter(DAQ_Viewer_base):
"""
Plugin for a 0D PL counter, based on a NI card.
"""
params = comon_parameters+[
{"title": "Counting channel:", "name": "counter_channel",
"type": "list", "limits": DAQmx.get_NIDAQ_channels(source_type="Counter")},
{"title": "Photon source:", "name": "photon_channel",
"type": "list", "limits": DAQmx.getTriggeringSources()},
{"title": "Clock frequency (Hz):", "name": "clock_freq",
"type": "float", "value": 100., "default": 100., "min": 1},
{'title': 'Clock channel:', 'name': 'clock_channel', 'type': 'list',
'limits': DAQmx.get_NIDAQ_channels(source_type='Counter')}
]

def ini_attributes(self):
self.controller = None
self.clock_channel = None
self.counter_channel = None
self.live = False # True during a continuous grab
self.counting_time = 0.1

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() == "clock_freq":
self.counting_time = 1/param.value()
else:
self.stop()
self.update_tasks()


def ini_detector(self, controller=None):
"""Detector communication initialization

Parameters
----------
controller: (object)
custom object of a PyMoDAQ plugin (Slave case). None if only one actuator/detector by controller
(Master case)

Returns
-------
info: str
initialized: bool
False if initialization failed otherwise True
"""
self.controller = {"clock": DAQmx(), "counter": DAQmx()}
try:
self.update_tasks()
initialized = True
info = "NI card based PL counter"
except Exception as e:
print(e)
initialized = False
info = "Error"

self.data_grabed_signal_temp.emit([DataFromPlugins(name='PL',data=[np.array([0])],
dim='Data0D', labels=['PL (kcts/s)'])])

return info, initialized

def close(self):
"""Terminate the communication protocol"""
self.controller["clock"].close()
self.controller["counter"].close()

def grab_data(self, Naverage=1, **kwargs):
"""Start a grab from the detector

Parameters
----------
Naverage: int
Number of hardware averaging not relevant here.
kwargs: dict
others optionals arguments
"""
update = True # to decide if we do the initial set up or not

if 'live' in kwargs:
if kwargs['live'] == self.live and self.live:
update = False # we are already live
self.live = kwargs['live']

if update:
self.update_tasks()
self.controller["clock"].start()


read_data = self.controller["counter"].readCounter(1, counting_time=self.counting_time)
data_pl = read_data*self.counting_time
self.data_grabed_signal.emit([DataFromPlugins(name='PL', data=[data_pl],
dim='Data0D', labels=['PL (kcts/s)'])])

def stop(self):
"""Stop the current grab hardware wise if necessary"""
self.close()
self.emit_status(ThreadCommand('Update_Status', ['Acquisition stopped.']))
return ''

def update_tasks(self):
"""Set up the counting tasks in the NI card."""
# Create channels
self.clock_channel = ClockCounter(self.settings.child("clock_freq").value(),
name=self.settings.child("clock_channel").value(),
source="Counter")
self.counter_channel = Counter(name=self.settings.child("counter_channel").value(),
source="Counter", edge=Edge.names()[0])

self.controller["clock"].update_task(channels=[self.clock_channel],
clock_settings=ClockSettings(),
trigger_settings=TriggerSettings())
self.controller["clock"].task.CfgImplicitTiming(DAQmx_Val_ContSamps, 1)

self.controller["counter"].update_task(channels=[self.counter_channel],
clock_settings=ClockSettings(),
trigger_settings=TriggerSettings())

self.controller["counter"].task.SetSampClkSrc("/" + self.clock_channel.name + "InternalOutput")


if __name__ == '__main__':
main(__file__)
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ def __init__(self):
self.channels = None
self.clock_settings = None
self.trigger_settings = None
self.live = False
self.refresh_hardware()


Expand Down
77 changes: 62 additions & 15 deletions src/pymodaq_plugins_daqmx/hardware/national_instruments/daqmx.py
Original file line number Diff line number Diff line change
Expand Up @@ -193,11 +193,26 @@ def __init__(self, **kwargs):

class Counter(Channel):
def __init__(self, edge=Edge.names()[0], **kwargs):
assert edge in Edge.names()
assert edge in Edge.names()
super().__init__(**kwargs)
self.edge = edge
self.counter_type = "Edge Counter"


class ClockCounter(Counter):
def __init__(self, clock_frequency, **kwargs):
super().__init__(**kwargs)
self.clock_frequency = clock_frequency
self.counter_type = "Clock Output"


class SemiPeriodCounter(Counter):
def __init__(self, value_max, **kwargs):
super().__init__(**kwargs)
self.value_max = value_max
self.counter_type = "SemiPeriod Input"


class DigitalChannel(Channel):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Expand Down Expand Up @@ -409,7 +424,7 @@ def update_task(self, channels=[], clock_settings=ClockSettings(), trigger_setti
for channel in channels:
if channel.source == 'Analog_Input': #analog input
if channel.analog_type == "Voltage":
err_code = self._task.CreateAIVoltageChan(channel.name, "",
err_code = self._task.CreateAIVoltageChan(channel.name, "analog voltage task",
DAQ_termination[channel.termination].value,
channel.value_min,
channel.value_max,
Expand All @@ -432,9 +447,31 @@ def update_task(self, channels=[], clock_settings=ClockSettings(), trigger_setti
PyDAQmx.DAQmx_Val_BuiltIn, 0., "")

elif channel.source == 'Counter': #counter
err_code = self._task.CreateCICountEdgesChan(channel.name, "",
Edge[channel.edge].value, 0,
PyDAQmx.DAQmx_Val_CountUp)
if channel.counter_type == "Edge Counter":
err_code = self._task.CreateCICountEdgesChan(channel.name, "",
Edge[channel.edge].value, 0,
PyDAQmx.DAQmx_Val_CountUp)
elif channel.counter_type == "Clock Output":
err_code = self._task.CreateCOPulseChanFreq(channel.name, "clock task",
# units, Hertz in our case
PyDAQmx.DAQmx_Val_Hz,
# idle state
PyDAQmx.DAQmx_Val_Low,
# initial delay
0,
# pulse frequency
channel.clock_frequency,
# duty cycle of pulses, 0.5 such that
# high and low duration are both
# equal to count_interval
0.5)
elif channel.counter_type == "SemiPeriod Input":
err_code = self._task.CreateCISemiPeriodChan(channel.name, "counter task",
0, # expected min
channel.value_max, # expected max
PyDAQmx.DAQmx_Val_Ticks, "")


if not not err_code:
status = self.DAQmxGetErrorString(err_code)
raise IOError(status)
Expand Down Expand Up @@ -533,14 +570,18 @@ def update_task(self, channels=[], clock_settings=ClockSettings(), trigger_setti
# else:
# pass

##configure the triggering
##configure the triggering, except for counters
if not trigger_settings.enable:
err = self._task.DisableStartTrig()
if err != 0:
raise IOError(self.DAQmxGetErrorString(err))
if channel.source == 'Counter':
pass
else:
err = self._task.DisableStartTrig()
if err != 0:
raise IOError(self.DAQmxGetErrorString(err))
else:
if 'PF' in trigger_settings.trig_source:
self._task.CfgDigEdgeStartTrig(trigger_settings.trig_source, Edge[trigger_settings.edge].value)
self._task.CfgDigEdgeStartTrig(trigger_settings.trig_source,
Edge[trigger_settings.edge].value)
elif 'ai' in trigger_settings.trig_source:
self._task.CfgAnlgEdgeStartTrig(trigger_settings.trig_source,
Edge[trigger_settings.edge].value,
Expand Down Expand Up @@ -634,13 +675,19 @@ def readAnalog(self, Nchannels, clock_settings):
else:
raise IOError(f'Insufficient number of samples have been read:{read.value}/{N}')

def readCounter(self, Nchannels, counting_time=10.):
def readCounter(self, Nchannels, counting_time=10., read_function="Ex"):

data_counter = np.zeros(Nchannels, dtype='uint32')
read = PyDAQmx.int32()
self._task.ReadCounterU32Ex(PyDAQmx.DAQmx_Val_Auto, 2*counting_time, PyDAQmx.DAQmx_Val_GroupByChannel,
data_counter,
Nchannels, PyDAQmx.byref(read), None)
if read_function == "Ex":
self._task.ReadCounterU32Ex(PyDAQmx.DAQmx_Val_Auto, 2*counting_time,
PyDAQmx.DAQmx_Val_GroupByChannel,
data_counter,
Nchannels, PyDAQmx.byref(read), None)
else:
self._task.ReadCounterU32(PyDAQmx.DAQmx_Val_Auto, 2*counting_time,
data_counter, Nchannels, PyDAQmx.byref(read), None)

self._task.StopTask()

if read.value == Nchannels:
Expand Down Expand Up @@ -739,4 +786,4 @@ def refresh_hardware(self):

if __name__ == '__main__':
print(DAQmx.get_NIDAQ_channels())
pass
pass