Skip to content

Commit

Permalink
Merge pull request #3 from Montpellier-S2QT/test_counter
Browse files Browse the repository at this point in the history
New counter functionalities
  • Loading branch information
seb5g authored Mar 6, 2023
2 parents f7b4754 + 0561099 commit 1745165
Show file tree
Hide file tree
Showing 3 changed files with 204 additions and 15 deletions.
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

0 comments on commit 1745165

Please sign in to comment.