Skip to content

Commit cb71e07

Browse files
authored
Merge pull request #3 from bkav456/main
daqhats128
2 parents 1768445 + 4253045 commit cb71e07

2 files changed

Lines changed: 335 additions & 1 deletion

File tree

‎README.rst‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ Instruments
3232

3333
Below is the list of instruments included in this plugin
3434

35+
Viewer1D
36+
++++++++
37+
38+
* **daqhats**: control of the MCC128 DAQ hat for analog input/output
39+
3540

3641
Viewer2D
3742
++++++++
@@ -51,6 +56,6 @@ Viewer2D
5156
Installation instructions
5257
=========================
5358

54-
* PyMoDAQ’s version >= 4
59+
* PyMoDAQ’s version >= 5
5560
* Tested on/with a raspberry pi 4
5661

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
import numpy as np
2+
from pymodaq.utils.daq_utils import ThreadCommand
3+
from pymodaq.utils.data import DataFromPlugins, Axis, DataToExport
4+
from pymodaq.control_modules.viewer_utility_classes import DAQ_Viewer_base, comon_parameters, main
5+
from pymodaq.utils.parameter import Parameter
6+
from daqhats import mcc128, OptionFlags, AnalogInputMode, TriggerModes, AnalogInputRange
7+
from pymodaq.utils.parameter.utils import iter_children
8+
9+
10+
class DAQ_1DViewer_daqhats(DAQ_Viewer_base):
11+
""" Instrument plugin class for a 1D viewer.
12+
13+
This object inherits all functionalities to communicate with PyMoDAQ’s DAQ_Viewer module through inheritance via
14+
DAQ_Viewer_base. It makes a bridge between the DAQ_Viewer module and the Python wrapper of a particular instrument.
15+
16+
Attributes:
17+
-----------
18+
controller: object
19+
The particular object that allow the communication with the hardware, in general a python wrapper around the
20+
hardware library.
21+
22+
"""
23+
params = comon_parameters + [
24+
{'title': 'Trigger Active', 'name': 'trigger_active', 'type': 'bool', 'value': False},
25+
{'title': 'Trigger Mode', 'name': 'trigger_mode', 'type': 'list', 'value': 'RISING_EDGE',
26+
'limits': ['RISING_EDGE', 'FALLING_EDGE', 'ACTIVE_HIGH',
27+
'ACTIVE_LOW']},
28+
{'title': 'External_clock', 'name': 'extclock_mode', 'type': 'bool', 'value': False},
29+
{'title': 'External sampling rate(Hz)', 'name': 'extclock_rate', 'type': 'int', 'value': 0, 'visible': False},
30+
{'title': 'Number of samples:', 'name': 'num_sample', 'type': 'int', 'value': 1000, 'min': 0},
31+
{'title': 'Sampling rate(Hz) :', 'name': 'sampling_rate', 'type': 'int', 'value': 10000, 'min': 0, 'max': 100000},
32+
{'title': 'Range(V)', 'name': 'range', 'type': 'list', 'value': 10, 'limits': [10, 5, 2, 1]},
33+
{'title': 'Mode', 'name': 'mode', 'type': 'list', 'value': 'SINGLE_END',
34+
'limits': ['SINGLE_ENDED', 'DIFFERENTIAL']},
35+
{'title': "Channel", 'name': 'channel_on', 'type': 'group', 'children': [
36+
{'title': 'CH0H', 'name': 'CH0H', 'type': 'bool', 'value': False},
37+
{'title': 'CH1H', 'name': 'CH1H', 'type': 'bool', 'value': False},
38+
{'title': 'CH2H', 'name': 'CH2H', 'type': 'bool', 'value': False},
39+
{'title': 'CH3H', 'name': 'CH3H', 'type': 'bool', 'value': False},
40+
{'title': 'CH0L', 'name': 'CH0L', 'type': 'bool', 'value': False},
41+
{'title': 'CH1L', 'name': 'CH1L', 'type': 'bool', 'value': False},
42+
{'title': 'CH2L', 'name': 'CH2L', 'type': 'bool', 'value': False},
43+
{'title': 'CH3L', 'name': 'CH3L', 'type': 'bool', 'value': False}
44+
]}
45+
46+
]
47+
48+
def __init__(self, parent=None, params_state=None):
49+
super().__init__(parent, params_state)
50+
self.num_channels = None
51+
52+
def ini_attributes(self):
53+
self.controller: mcc128 = None
54+
self.option = 0
55+
self.mode = 0
56+
self.trigger_mode = 0
57+
self.range = 0
58+
self.channel_mask = 0
59+
self.num_channels = 0
60+
self.data_signal = []
61+
self.list_channel_names = []
62+
63+
def commit_settings(self, param: Parameter):
64+
"""Apply the consequences of a change of value in the detector settings
65+
Parameters
66+
----------
67+
param: Parameter
68+
A given parameter (within detector_settings) whose value has been changed by the user
69+
"""
70+
71+
if param.name() == 'mode':
72+
self.set_mode()
73+
elif param.name() == 'range':
74+
self.set_range()
75+
elif param.name() == 'trigger_mode':
76+
self.set_trigger()
77+
elif param.name() in iter_children(self.settings.child('channel_on'), []):
78+
self.active_channel()
79+
elif param.name() == 'sampling_rate':
80+
sample_rate_real = self.controller.a_in_scan_actual_rate(self.num_channels, param.value())
81+
param.setValue(sample_rate_real)
82+
elif param.name() == 'extclock_mode':
83+
self.settings.child('extclock_rate').setOpts(visible = param.value())
84+
85+
def scan_data(self, totalsamples: int, scan_rate: int, name_channel: int):
86+
"""This starts a hardware-paced analog input channel scan . Then this function reads and returns the data from the
87+
buffer.
88+
89+
The aggregate sampling rate of all activated channels can't exceed 100kS/s
90+
================= ================
91+
Channels activate Scan rate (kS/s)
92+
================= ================
93+
1 100
94+
2 50
95+
3 33,33
96+
4 25
97+
5 20
98+
6 16,67
99+
7 14,29
100+
8 12,50
101+
============== ================
102+
103+
Parameters
104+
-----------
105+
totalsamples: int
106+
The numbers of samples which we want to aquire
107+
scan_rate: int
108+
The sampling rate of each channel
109+
name_channel: int
110+
The bit mask of the channels activated
111+
112+
Returns
113+
-------
114+
voltage.data : list
115+
The list of data scanned
116+
"""
117+
118+
self.controller.a_in_scan_start(name_channel, totalsamples, scan_rate, self.option)
119+
voltage = self.controller.a_in_scan_read_numpy(totalsamples, 0.5)
120+
self.controller.a_in_scan_stop()
121+
self.controller.a_in_scan_cleanup()
122+
return voltage.data
123+
124+
def set_range(self):
125+
""" This sets the analog input range to one of these values:
126+
+/- 10V
127+
+/- 5V
128+
+/- 2V
129+
+/- 1V
130+
"""
131+
132+
if self.settings['range'] == 10:
133+
self.range = AnalogInputRange['BIP_10V'].value
134+
elif self.settings['range'] == 5:
135+
self.range = AnalogInputRange['BIP_5V'].value
136+
elif self.settings['range'] == 2:
137+
self.range = AnalogInputRange['BIP_2V'].value
138+
elif self.settings['range'] == 1:
139+
self.range = AnalogInputRange['BIP_1V'].value
140+
self.controller.a_in_range_write(self.range)
141+
142+
def set_mode(self):
143+
""" This sets the analog input mode to one of two values
144+
* Single ended
145+
* Differential
146+
"""
147+
148+
if self.settings['mode'] == 'SINGLE_ENDED':
149+
self.mode = AnalogInputMode.SE.value
150+
elif self.settings['mode'] == 'DIFFERENTIAL':
151+
self.mode = AnalogInputMode.DIFF.value
152+
self.controller.a_in_mode_write(self.mode)
153+
154+
def set_trigger(self):
155+
"""Read and Set the external trigger mode input of the card
156+
157+
There are 4 type available TRIGGER mode:
158+
*RISING_EDGE: Start the scan when the trigger signal transition from LOW to HIGH
159+
*FALLING_EDGE: Start the scan when the trigger signal transition from HIGH to LOW
160+
*ACTIVE_HIGH: Start the scan when the trigger signal is HIGH
161+
*ACTIVE_LOW: Start the scan when the trigger signal is LOW
162+
163+
"""
164+
165+
mode_trigger = self.settings['trigger_mode']
166+
if mode_trigger == 'RISING_EDGE':
167+
self.trigger_mode = TriggerModes.RISING_EDGE
168+
elif mode_trigger == 'FALLING_EDGE':
169+
self.trigger_mode = TriggerModes.FALLING_EDGE
170+
elif mode_trigger == 'ACTIVE_HIGH':
171+
self.trigger_mode = TriggerModes.ACTIVE_HIGH
172+
elif mode_trigger == 'ACTIVE_LOW':
173+
self.trigger_mode = TriggerModes.ACTIVE_LOW
174+
self.controller.trigger_mode(self.trigger_mode)
175+
176+
177+
def active_channel(self):
178+
"""This function reads the states of channels on the interface and sets the bit mask of the active channels,
179+
the number of activated channels, and a list of the names of these channels.
180+
181+
"""
182+
183+
bit_mask = 0 # the bit mask of the active channels
184+
number_channels = 0 # the number of activated channels
185+
name_channels = [] # the list of the names of these channels
186+
if self.settings['channel_on', 'CH0H']:
187+
bit_mask = bit_mask + 1
188+
number_channels += 1
189+
name_channels.append('CH0H')
190+
if self.settings['channel_on', 'CH1H']:
191+
bit_mask = bit_mask + 2
192+
number_channels += 1
193+
name_channels.append('CH1H')
194+
if self.settings['channel_on', 'CH2H']:
195+
bit_mask = bit_mask + 4
196+
number_channels += 1
197+
name_channels.append('CH2H')
198+
if self.settings['channel_on', 'CH3H']:
199+
bit_mask = bit_mask + 8
200+
number_channels += 1
201+
name_channels.append('CH3H')
202+
if self.settings['channel_on', 'CH0L']:
203+
bit_mask = bit_mask + 16
204+
number_channels += 1
205+
name_channels.append('CH0L')
206+
if self.settings['channel_on', 'CH1L']:
207+
bit_mask = bit_mask + 32
208+
number_channels += 1
209+
name_channels.append('CH1L')
210+
if self.settings['channel_on', 'CH2L']:
211+
bit_mask = bit_mask + 64
212+
number_channels += 1
213+
name_channels.append('CH2L')
214+
if self.settings['channel_on', 'CH3L']:
215+
bit_mask = bit_mask + 128
216+
number_channels += 1
217+
name_channels.append('CH3L')
218+
self.channel_mask, self.num_channels, self.label = bit_mask, number_channels, name_channels
219+
220+
def get_data(self, list_signal_non_arranged, nb_channel_activated):
221+
""" Arrange the list of data received from activated channels
222+
223+
This methode arranges the list of data received from activated channels. It converts this list of data into a list
224+
of numpy arrays. The order of each array corresponds to the order of each activated channel on the interface (from CH0H to CH3L)
225+
Example: When you activate 2 channels CHOH and CH2L, this function will return a list of 2 arrays: the first one is
226+
data of channel CH0H and the second one is data of channel CH2L
227+
228+
Parameters
229+
----------
230+
list_signal_non_arranged : list
231+
List of data obtained by function scan_data()
232+
nb_channel_activated : int
233+
The quantity of activated channels
234+
235+
Returns
236+
-------
237+
list_signal_arranged : list of numpy array
238+
list of data of each channel arranged
239+
"""
240+
list_signal_arranged = []
241+
if len(list_signal_non_arranged) != 0:
242+
for k in range(nb_channel_activated):
243+
list_signal_arranged.append(np.array(list_signal_non_arranged[k]))
244+
i = nb_channel_activated
245+
while i <= len(list_signal_non_arranged) - nb_channel_activated:
246+
for j in range(nb_channel_activated):
247+
list_signal_arranged[j] = np.append(list_signal_arranged[j], list_signal_non_arranged[i + j])
248+
i = i + nb_channel_activated
249+
return list_signal_arranged
250+
251+
def ini_detector(self, controller=None):
252+
"""Detector communication initialization
253+
254+
Parameters
255+
----------
256+
controller: (object)
257+
custom object of a PyMoDAQ plugin (Slave case). None if only one actuator/detector by controller
258+
(Master case)
259+
260+
Returns
261+
-------
262+
info: str
263+
initialized: bool
264+
False if initialization failed otherwise True
265+
"""
266+
267+
self.ini_detector_init(old_controller=controller,
268+
new_controller=mcc128(0))
269+
270+
info = ""
271+
initialized = True
272+
return info, initialized
273+
274+
def close(self):
275+
pass
276+
"""Terminate the communication protocol"""
277+
278+
def grab_data(self, Naverage=1, **kwargs):
279+
"""Start a grab from the detector
280+
281+
Parameters
282+
----------
283+
Naverage: int
284+
Number of hardware averaging (if hardware averaging is possible, self.hardware_averaging should be set to
285+
True in class preamble and you should code this implementation)
286+
kwargs: dict
287+
others optionals arguments
288+
"""
289+
290+
num_sample = self.settings['num_sample']
291+
freq = self.settings['sampling_rate']
292+
293+
self.option = 0
294+
295+
# Set up mode Trigger
296+
if self.settings['trigger_active'] == True:
297+
self.option += OptionFlags['EXTTRIGGER'].value
298+
299+
# Set up mode External clock
300+
if self.settings['extclock_mode'] == True:
301+
self.option += OptionFlags['EXTCLOCK'].value
302+
external_spl_rate = self.settings['extclock_rate']
303+
if external_spl_rate == 0:
304+
raise ValueError("Sampling rate cannot be zero")
305+
else:
306+
xaxis = Axis('time', 'seconds', np.arange(0, num_sample * 1 / external_spl_rate, 1 / external_spl_rate),
307+
0) # Redefine the x-axis when the external clock is used
308+
else:
309+
xaxis = Axis('time', 'seconds', np.arange(0, num_sample * 1 / freq, 1 / freq), 0)
310+
311+
self.channel_mask, self.num_channels, self.list_channel_names = self.active_channel()
312+
313+
signal = self.scan_data(num_sample, freq, self.channel_mask)
314+
315+
if len(signal) != 0: # Condition to avoid empty data error
316+
self.data_signal = self.get_data(signal, self.num_channels)
317+
318+
self.dte_signal.emit(DataToExport('mcc128_plugin', data=[DataFromPlugins(name='mcc128', data=self.data_signal,
319+
dim='Data1D',
320+
labels=self.list_channel_names,
321+
axes=[xaxis])]))
322+
323+
def stop(self):
324+
"""Stop the current grab hardware wise if necessary"""
325+
return ''
326+
327+
328+
if __name__ == '__main__':
329+
main(__file__, init=False)

0 commit comments

Comments
 (0)