pyxdaq 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyxdaq/__init__.py ADDED
@@ -0,0 +1,9 @@
1
+ import logging
2
+
3
+ import colorlog
4
+
5
+ handler = colorlog.StreamHandler()
6
+ handler.setFormatter(
7
+ colorlog.ColoredFormatter('%(log_color)s%(asctime)s [%(levelname)s] %(name)s %(message)s')
8
+ )
9
+ logging.basicConfig(handlers=[handler], level=logging.INFO)
pyxdaq/board.py ADDED
@@ -0,0 +1,98 @@
1
+ import json
2
+ import logging
3
+ from copy import deepcopy
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Callable, List
7
+
8
+ from pylibxdaq import pyxdaq_device
9
+ from pylibxdaq.managers import manager_paths
10
+
11
+ from .constants import EndPoints
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ @dataclass
17
+ class DeviceSetup:
18
+ manager_path: Path
19
+ manager_info: dict
20
+ options: dict
21
+
22
+ def with_mode(self, mode: str):
23
+ info = deepcopy(self)
24
+ info.options['mode'] = mode
25
+ return info
26
+
27
+
28
+ class Board:
29
+
30
+ @classmethod
31
+ def list_devices(cls) -> List[DeviceSetup]:
32
+ devices = []
33
+ for manager_path in manager_paths:
34
+ manager = pyxdaq_device.get_device_manager(str(manager_path))
35
+ info = manager.info()
36
+ for device_options in json.loads(manager.list_devices()):
37
+ devices.append(DeviceSetup(manager_path, info, device_options))
38
+ return sorted(devices, key=lambda x: str(x.options))
39
+
40
+ def __init__(self, device_info: DeviceSetup):
41
+ manager: pyxdaq_device.DeviceManager = pyxdaq_device.get_device_manager(
42
+ str(device_info.manager_path)
43
+ )
44
+ self.dev = manager.create_device(json.dumps(device_info.options))
45
+ status = json.loads(self.dev.get_status())
46
+ if status['Mode'] == 'rhd':
47
+ self.rhs = False
48
+ elif status['Mode'] == 'rhs':
49
+ self.rhs = True
50
+
51
+ def __enter__(self):
52
+ return self
53
+
54
+ def __exit__(self, exc_type, exc_val, exc_tb):
55
+ del self.dev
56
+
57
+ def __getattr__(self, name: str):
58
+ return getattr(self.dev, name)
59
+
60
+ def GetWireOutValue(self, addr: EndPoints, update: bool = True) -> int:
61
+ if update:
62
+ return self.dev.get_register_sync(addr.value)
63
+ else:
64
+ return self.dev.get_register(addr.value)
65
+
66
+ def SetWireInValue(
67
+ self, addr: EndPoints, value: int, mask: int = 0xFFFFFFFF, update: bool = True
68
+ ):
69
+ if update:
70
+ self.dev.set_register_sync(addr.value, value, mask)
71
+ else:
72
+ self.dev.set_register(addr.value, value, mask)
73
+
74
+ def ActivateTriggerIn(self, addr: EndPoints, value: int):
75
+ self.dev.trigger(addr.value, value)
76
+
77
+ def WriteToBlockPipeIn(self, epAddr: EndPoints, data: bytearray):
78
+ return self.dev.write(epAddr.value, data)
79
+
80
+ def ReadFromBlockPipeOut(self, epAddr: EndPoints, data: bytearray):
81
+ return self.dev.read(epAddr.value, data)
82
+
83
+ def start_receiving_aligned_buffer(
84
+ self,
85
+ epAddr: EndPoints,
86
+ alignment: int,
87
+ callback: Callable[[pyxdaq_device.ManagedBuffer], None],
88
+ chunk_size: int = 0
89
+ ):
90
+ return self.dev.start_aligned_read_stream(
91
+ epAddr.value, alignment, callback, chunk_size=chunk_size
92
+ )
93
+
94
+ def SendTrig(
95
+ self, trig: EndPoints, bit: int, epAddr: EndPoints, value: int, mask: int = 0xFFFFFFFF
96
+ ):
97
+ self.dev.set_register_sync(epAddr.value, value, mask)
98
+ self.dev.trigger(trig.value, bit)
pyxdaq/constants.py ADDED
@@ -0,0 +1,309 @@
1
+ from enum import Enum
2
+
3
+
4
+ class EndPoints(Enum):
5
+ pass
6
+
7
+
8
+ class XDAQWireOut(EndPoints):
9
+ Serial = 0x32
10
+ Hdmi = 0x31
11
+ Fpga = 0x3f
12
+ Daio = 0x30
13
+ Oled = 0x33
14
+ Vido = 0x34
15
+ Expr = 0x35
16
+ Xprt = 0x36
17
+
18
+
19
+ class RHD(EndPoints):
20
+ TTL_override = 0x1400
21
+ WireInResetRun = 0x00
22
+ WireInMaxTimeStep = 0x01
23
+ WireInSerialDigitalInCntl = 0x02
24
+ WireInDataFreqPll = 0x03
25
+ WireInMisoDelay = 0x04
26
+ WireInCmdRamAddr = 0x05
27
+ WireInCmdRamBank = 0x06
28
+ WireInCmdRamData = 0x07
29
+ WireInAuxCmdBank1 = 0x08
30
+ WireInAuxCmdBank2 = 0x09
31
+ WireInAuxCmdBank3 = 0x0a
32
+ WireInAuxCmdLength = 0x0b
33
+ WireInAuxCmdLoop = 0x0c
34
+ WireInLedDisplay = 0x0d
35
+ WireInDacReref = 0x0e
36
+ WireInDataStreamEn = 0x14
37
+ WireInTtlOut = 0x15
38
+ WireInTtlOut32 = 0x10
39
+ WireInDacSource1 = 0x16
40
+ WireInDacSource2 = 0x17
41
+ WireInDacSource3 = 0x18
42
+ WireInDacSource4 = 0x19
43
+ WireInDacSource5 = 0x1a
44
+ WireInDacSource6 = 0x1b
45
+ WireInDacSource7 = 0x1c
46
+ WireInDacSource8 = 0x1d
47
+ WireInDacSource9 = 0x48
48
+ WireInDacSource10 = 0x49
49
+ WireInDacSource11 = 0x4A
50
+ WireInDacSource12 = 0x4B
51
+ WireInDacManual = 0x1e
52
+ WireInMultiUse = 0x1f
53
+ TrigInConfig = 0x40
54
+ TrigInSpiStart = 0x41
55
+ TrigInDacConfig = 0x42
56
+ WireOutNumWords = 0x20
57
+ WireOutSerialDigitalIn = 0x21
58
+ WireOutSpiRunning = 0x22
59
+ WireOutTtlIn = 0x23
60
+ WireOutDataClkLocked = 0x24
61
+ WireOutBoardMode = 0x25
62
+ WireOutBoardId = 0x3e
63
+ WireOutBoardVersion = 0x3f
64
+ PipeOutData = 0xa0
65
+ TrigVStim = 0x40
66
+ TrigMCU = 0x48
67
+ WireOutXDAQStatus = 0x22
68
+ WireInMCUControl = 0x02
69
+ PipeInFirmware = 0x88
70
+ PipeOutFirmware = 0xb0
71
+ WireInSetMode = 0x00
72
+ Enable32bitDIO = 0x12
73
+ PipeInDAC1 = 0x90
74
+ PipeInDAC2 = 0x91
75
+ PipeInDAC3 = 0x92
76
+ PipeInDAC4 = 0x93
77
+ PipeInDAC5 = 0x94
78
+ PipeInDAC6 = 0x95
79
+ PipeInDAC7 = 0x96
80
+ PipeInDAC8 = 0x97
81
+ PipeInDAC9 = 0x98
82
+ PipeInDAC10 = 0x99
83
+ PipeInDAC11 = 0x9A
84
+ PipeInDAC12 = 0x9B
85
+ ExpanderInfo = 0x35
86
+
87
+
88
+ class RHS(EndPoints):
89
+ TTL_override = 0x1400
90
+ WireInResetRun = 0x00
91
+ WireInMaxTimeStep = 0x01
92
+ WireInMaxTimeStepLsb = 0x01
93
+ WireInMaxTimeStepMsb = 0x02
94
+ WireInDataFreqPll = 0x03
95
+ WireInMisoDelay = 0x04
96
+ WireInStimCmdMode = 0x05
97
+ WireInStimRegAddr = 0x06
98
+ WireInStimRegWord = 0x07
99
+ WireInDcAmpConvert = 0x08
100
+ WireInExtraStates = 0x09
101
+ WireInDacReref = 0x0a
102
+ WireInAuxEnable = 0x0c
103
+ WireInGlobalSettleSelect = 0x0d
104
+ WireInAdcThreshold = 0x0f
105
+ WireInSerialDigitalInCntl = 0x10
106
+ WireInTtlOut32 = 0x10
107
+ WireInLedDisplay = 0x11
108
+ WireInManualTriggers = 0x12
109
+ WireInTtlOutMode = 0x13
110
+ WireInDataStreamEn = 0x14
111
+ WireInTtlOut = 0x15
112
+ WireInDacSource1 = 0x16
113
+ WireInDacSource2 = 0x17
114
+ WireInDacSource3 = 0x18
115
+ WireInDacSource4 = 0x19
116
+ WireInDacSource5 = 0x1a
117
+ WireInDacSource6 = 0x1b
118
+ WireInDacSource7 = 0x1c
119
+ WireInDacSource8 = 0x1d
120
+ WireInDacSource9 = 0x48
121
+ WireInDacSource10 = 0x49
122
+ WireInDacSource11 = 0x4A
123
+ WireInDacSource12 = 0x4B
124
+ WireInDacManual = 0x1e
125
+ WireInMultiUse = 0x1f
126
+ TrigInConfig = 0x40
127
+ TrigInDcmProg = 0x40
128
+ TrigInSpiStart = 0x41
129
+ TrigInRamAddrReset = 0x42
130
+ TrigInDacThresh = 0x43
131
+ TrigInDacHpf = 0x44
132
+ TrigInAuxCmdLength = 0x45
133
+ WireOutNumWords = 0x20
134
+ WireOutNumWordsLsb = 0x20
135
+ WireOutNumWordsMsb = 0x21
136
+ WireOutSpiRunning = 0x22
137
+ WireOutTtlIn = 0x23
138
+ WireOutDataClkLocked = 0x24
139
+ WireOutBoardMode = 0x25
140
+ WireOutSerialDigitalIn = 0x26
141
+ WireOutBoardId = 0x3e
142
+ WireOutBoardVersion = 0x3f
143
+ PipeInAuxCmd1Msw = 0x80
144
+ PipeInAuxCmd1 = 0x81
145
+ PipeInAuxCmd1Lsw = 0x81
146
+ PipeInAuxCmd2Msw = 0x82
147
+ PipeInAuxCmd2 = 0x83
148
+ PipeInAuxCmd2Lsw = 0x83
149
+ PipeInAuxCmd3Msw = 0x84
150
+ PipeInAuxCmd3 = 0x85
151
+ PipeInAuxCmd3Lsw = 0x85
152
+ PipeInAuxCmd4Msw = 0x86
153
+ PipeInAuxCmd4 = 0x87
154
+ PipeInAuxCmd4Lsw = 0x87
155
+ PipeOutData = 0xa0
156
+ TrigVStim = 0x40
157
+ TrigMCU = 0x48
158
+ WireOutXDAQStatus = 0x22
159
+ WireInMCUControl = 0x02
160
+ PipeInFirmware = 0x88
161
+ PipeOutFirmware = 0xb0
162
+ WireInSetMode = 0x00
163
+ Enable32bitDIO = 0x0B
164
+ PipeInDAC1 = 0x90
165
+ PipeInDAC2 = 0x91
166
+ PipeInDAC3 = 0x92
167
+ PipeInDAC4 = 0x93
168
+ PipeInDAC5 = 0x94
169
+ PipeInDAC6 = 0x95
170
+ PipeInDAC7 = 0x96
171
+ PipeInDAC8 = 0x97
172
+ PipeInDAC9 = 0x98
173
+ PipeInDAC10 = 0x99
174
+ PipeInDAC11 = 0x9A
175
+ PipeInDAC12 = 0x9B
176
+ ExpanderInfo = 0x35
177
+
178
+
179
+ class SampleRate(Enum):
180
+ SampleRate1000Hz = (7, 125, 1000)
181
+ SampleRate1250Hz = (7, 100, 1250)
182
+ SampleRate1500Hz = (21, 250, 1500)
183
+ SampleRate2000Hz = (14, 125, 2000)
184
+ SampleRate2500Hz = (35, 250, 2500)
185
+ SampleRate3000Hz = (21, 125, 3000)
186
+ SampleRate3333Hz = (14, 75, 3333)
187
+ SampleRate4000Hz = (28, 125, 4000)
188
+ SampleRate5000Hz = (7, 25, 5000)
189
+ SampleRate6250Hz = (7, 20, 6250)
190
+ SampleRate8000Hz = (112, 250, 8000)
191
+ SampleRate10000Hz = (14, 25, 10000)
192
+ SampleRate12500Hz = (7, 10, 12500)
193
+ SampleRate15000Hz = (21, 25, 15000)
194
+ SampleRate20000Hz = (28, 25, 20000)
195
+ SampleRate25000Hz = (35, 25, 25000)
196
+ SampleRate30000Hz = (42, 25, 30000)
197
+
198
+ @classmethod
199
+ def fromRate(cls, rate: int):
200
+ for sr in SampleRate:
201
+ if sr.value[2] == rate:
202
+ return sr
203
+ raise ValueError('Invalid sample rate')
204
+
205
+ @property
206
+ def rate(self):
207
+ return self.value[2]
208
+
209
+
210
+ class HeadstageChipID(Enum):
211
+ NA = 0
212
+ RHD2132 = 1
213
+ RHD2216 = 2
214
+ RHD2164 = 4
215
+ RHS2116 = 32
216
+
217
+ def num_channels(self):
218
+ if self == HeadstageChipID.RHD2164:
219
+ return 64
220
+ elif self == HeadstageChipID.RHD2132:
221
+ return 32
222
+ elif self == HeadstageChipID.RHD2216:
223
+ return 16
224
+ elif self == HeadstageChipID.RHS2116:
225
+ return 16
226
+ else:
227
+ return 0
228
+
229
+ def num_channels_per_stream(self):
230
+ if self == HeadstageChipID.RHD2164:
231
+ return self.num_channels() // 2
232
+ return self.num_channels()
233
+
234
+
235
+ class HeadstageChipMISOID(Enum):
236
+ NA = 0
237
+ MISO_A = 53
238
+ MISO_B = 58
239
+
240
+
241
+ class TriggerEvent(Enum):
242
+ Level = 0
243
+ Edge = 1
244
+
245
+
246
+ class TriggerPolarity(Enum):
247
+ Low = 0
248
+ High = 1
249
+
250
+
251
+ class StimShape(Enum):
252
+ Biphasic = 0
253
+ BiphasicWithInterphaseDelay = 1
254
+ Triphasic = 2
255
+ Monophasic = 3
256
+
257
+
258
+ class StartPolarity(Enum):
259
+ anodic = 0
260
+ cathodic = 1
261
+
262
+
263
+ class StimRegister(Enum):
264
+ Trigger = 0
265
+ Param = 1
266
+ EventAmpSettleOn = 2
267
+ EventAmpSettleOff = 3
268
+ EventStartStim = 4
269
+ EventStimPhase2 = 5
270
+ EventStimPhase3 = 6
271
+ EventEndStim = 7
272
+ EventRepeatStim = 8
273
+ EventChargeRecovOn = 9
274
+ EventChargeRecovOff = 10
275
+ EventAmpSettleOnRepeat = 11
276
+ EventAmpSettleOffRepeat = 12
277
+ EventEnd = 13
278
+
279
+
280
+ class StimStepSize(Enum):
281
+ StimStepSizeMin = 0
282
+ StimStepSize10nA = 1
283
+ StimStepSize20nA = 2
284
+ StimStepSize50nA = 3
285
+ StimStepSize100nA = 4
286
+ StimStepSize200nA = 5
287
+ StimStepSize500nA = 6
288
+ StimStepSize1uA = 7
289
+ StimStepSize2uA = 8
290
+ StimStepSize5uA = 9
291
+ StimStepSize10uA = 10
292
+ StimStepSizeMax = 11
293
+
294
+ @property
295
+ def nA(self):
296
+ return {
297
+ StimStepSize.StimStepSizeMin: float('nan'),
298
+ StimStepSize.StimStepSize10nA: 10,
299
+ StimStepSize.StimStepSize20nA: 20,
300
+ StimStepSize.StimStepSize50nA: 50,
301
+ StimStepSize.StimStepSize100nA: 100,
302
+ StimStepSize.StimStepSize200nA: 200,
303
+ StimStepSize.StimStepSize500nA: 500,
304
+ StimStepSize.StimStepSize1uA: 1000,
305
+ StimStepSize.StimStepSize2uA: 2000,
306
+ StimStepSize.StimStepSize5uA: 5000,
307
+ StimStepSize.StimStepSize10uA: 10000,
308
+ StimStepSize.StimStepSizeMax: float('nan')
309
+ }[self]
pyxdaq/datablock.py ADDED
@@ -0,0 +1,155 @@
1
+ import struct
2
+ from dataclasses import dataclass
3
+ from typing import List, Union
4
+
5
+ import numpy as np
6
+
7
+ _uint16le = np.dtype('u2').newbyteorder('<')
8
+ _uint32le = np.dtype('u4').newbyteorder('<')
9
+ _RHD_HEADER_MAGIC = 0xD7A22AAA38132A53
10
+ _RHS_HEADER_MAGIC = 0x8D542C8A49712F0B
11
+
12
+
13
+ @dataclass
14
+ class Sample:
15
+ """
16
+ Represents a single sample at time `ts` from the XDAQ data stream.
17
+ """
18
+ ts: int
19
+ aux: np.ndarray
20
+ amp: np.ndarray
21
+ adc: np.ndarray
22
+ ttlin: np.ndarray
23
+ ttlout: np.ndarray
24
+ dac: Union[None, np.ndarray]
25
+ stim: Union[None, np.ndarray]
26
+
27
+ @classmethod
28
+ def from_buffer(
29
+ cls, rhs: bool, buffer: Union[bytearray, memoryview], datastreams: int
30
+ ) -> 'Sample':
31
+ """
32
+ Deserialize a single sample from a buffer.
33
+ Keeps the same memory layout as the original data for further optimization.
34
+ """
35
+ idx = 0
36
+ magic, ts = struct.unpack("<QI", buffer[idx:12])
37
+ if magic != (_RHS_HEADER_MAGIC if rhs else _RHD_HEADER_MAGIC):
38
+ raise ValueError(f"Invalid magic: {magic:016X}")
39
+ idx += 12
40
+
41
+ aux = np.frombuffer(
42
+ buffer[idx:], dtype=_uint16le, count=3 * datastreams * (2 if rhs else 1)
43
+ ).reshape([3, datastreams] + ([2] if rhs else []))
44
+ idx += 3 * datastreams * 2 * (2 if rhs else 1)
45
+
46
+ amp = np.frombuffer(
47
+ buffer[idx:],
48
+ dtype=_uint16le,
49
+ count=(16 if rhs else 32) * datastreams * (2 if rhs else 1)
50
+ ).reshape([16 if rhs else 32, datastreams] + ([2] if rhs else []))
51
+ idx += (16 if rhs else 32) * datastreams * 2 * (2 if rhs else 1)
52
+
53
+ if rhs:
54
+ aux0 = np.frombuffer(
55
+ buffer[idx:], dtype=_uint16le, count=1 * datastreams * 2
56
+ ).reshape((1, datastreams, 2))
57
+ aux = np.concatenate((aux0, aux), axis=0)
58
+ idx += 1 * datastreams * 2 * 2
59
+
60
+ stim = np.frombuffer(
61
+ buffer[idx:], dtype=_uint16le, count=4 * datastreams
62
+ ).reshape(4, datastreams)
63
+ idx += 4 * datastreams * 2
64
+ idx += 4
65
+
66
+ dac = np.frombuffer(buffer[idx:], dtype=_uint16le, count=8)
67
+ idx += 16
68
+ else:
69
+ stim = None
70
+ dac = None
71
+ idx += 2 * ((datastreams + 2) % 4) # padding
72
+
73
+ adc = np.frombuffer(buffer[idx:], dtype=_uint16le, count=8)
74
+ idx += 16
75
+
76
+ ttlin = np.frombuffer(buffer[idx:], dtype=_uint32le, count=1)
77
+ idx += 4
78
+
79
+ ttlout = np.frombuffer(buffer[idx:], dtype=_uint32le, count=1)
80
+ idx += 4
81
+ return cls(ts, aux, amp, adc, ttlin, ttlout, dac, stim)
82
+
83
+
84
+ @dataclass
85
+ class Samples(Sample):
86
+ """
87
+ A collection of samples, the first dimension represents the sample index.
88
+ """
89
+ n: int
90
+
91
+ def device_name(self):
92
+ if self.n != 128:
93
+ raise ValueError("Unable to determine device name for non-128 sample data block")
94
+ if self.stim is None:
95
+ return self.aux[[32, 33, 34, 35, 36, 24, 25, 26], 2, :]
96
+ else:
97
+ rom = self.aux[:, 0, :, :][58:61, :, 0]
98
+ aux = np.array(rom).view(np.uint8).reshape(
99
+ (rom.shape[0], rom.shape[1], 2)
100
+ ).transpose(1, 0, 2).reshape((rom.shape[1], -1))
101
+ return aux[:, :0:-1].T
102
+
103
+ def device_id(self):
104
+ if self.n != 128:
105
+ raise ValueError("Unable to determine device ID for non-128 sample data block")
106
+ if self.stim is None:
107
+ return self.aux[19, 2, :], self.aux[23, 2, :]
108
+ else:
109
+ rom = self.aux[:, 0, :, :][56:58, :, 0]
110
+ aux = np.array(rom).view(np.uint8).reshape(
111
+ (rom.shape[0], rom.shape[1], 2)
112
+ ).transpose(1, 0, 2).reshape((rom.shape[1], -1))
113
+ return aux[:, 0].T, np.zeros_like(aux[:, 0].T)
114
+
115
+
116
+ @dataclass
117
+ class DataBlock:
118
+ """
119
+ Raw data block which keeps the original memory layout.
120
+ """
121
+ samples: List[Sample]
122
+ # Samples x C x Datastreams : [C x Datastreams] [C x Datastreams] ... [C x Datastreams]
123
+
124
+ @classmethod
125
+ def from_buffer(
126
+ cls, rhs, sample_size, buffer: Union[bytearray, memoryview], datastreams: int
127
+ ) -> 'DataBlock':
128
+ return cls(
129
+ [
130
+ Sample.from_buffer(rhs, buffer[i:i + sample_size], datastreams)
131
+ for i in range(0, len(buffer), sample_size)
132
+ ]
133
+ )
134
+
135
+ def to_samples(self) -> Samples:
136
+ """
137
+ Concatenate all samples into a single Samples object.
138
+ This method breaks the original memory layout.
139
+ """
140
+ return Samples(
141
+ np.array([s.ts for s in self.samples]), np.stack([s.aux for s in self.samples]),
142
+ np.stack([s.amp for s in self.samples]), np.stack([s.adc for s in self.samples]),
143
+ np.stack([s.ttlin for s in self.samples]), np.stack([s.ttlout for s in self.samples]),
144
+ None if self.samples[0].dac is None else np.stack([s.dac for s in self.samples]),
145
+ None if self.samples[0].dac is None else np.stack([s.stim for s in self.samples]),
146
+ len(self.samples)
147
+ )
148
+
149
+
150
+ def amplifier2mv(amp: np.array):
151
+ return (amp.astype(np.float32) - 32768) * 0.195
152
+
153
+
154
+ def adc2v(adc: np.array):
155
+ return (adc.astype(np.float32) - 32768) * 0.0003125
pyxdaq/gen2.py ADDED
@@ -0,0 +1,61 @@
1
+ import math
2
+ from typing import Union
3
+
4
+ import numpy as np
5
+
6
+ from .xdaq import XDAQ
7
+
8
+
9
+ def upload_waveform(xdaq: XDAQ, waveform: np.ndarray, idx: int, divisor: int):
10
+ if waveform.dtype != np.uint16:
11
+ raise TypeError(f'Only 16 bits resolution for waveform, get {waveform.dtype}')
12
+ for i, val in enumerate(waveform.astype(np.uint32)):
13
+ xdaq.dev.set_register(0x2008 + idx * 8, int(val) | (i << 16) | (1 << 31), 0xFFFFFFFF)
14
+ xdaq.dev.set_register(0x200C + idx * 8, divisor | (len(waveform) - 1) << 16, 0xFFFFFFFF)
15
+
16
+
17
+ def config_waveform(xdaq: XDAQ, enable: int):
18
+ xdaq.dev.set_register(0x2004, enable, 15) # enable
19
+ xdaq.dev.set_register(0x2000, enable, 15) # reset enabled
20
+
21
+
22
+ def config_channel(xdaq: XDAQ, channel: int, waveform: Union[int, None]):
23
+ if waveform is None:
24
+ xdaq.dev.set_register(0x2028 + 4 * channel, 0 | (0 << 31), 0xFFFFFFFF)
25
+ else:
26
+ xdaq.dev.set_register(0x2028 + 4 * channel, waveform | (1 << 31), 0xFFFFFFFF)
27
+
28
+
29
+ def get_sine(period, amp=32768):
30
+ return np.clip(
31
+ (32768 + amp * np.sin(np.linspace(0, np.pi * 2, period, endpoint=False))).astype(np.int32),
32
+ 0,
33
+ 65535,
34
+ ).astype(np.uint16)
35
+
36
+
37
+ def approximate_waveform_param(
38
+ target_freq: float,
39
+ tolerance: float = 0,
40
+ clock: int = int(125e6),
41
+ min_div: int = 1024,
42
+ max_div: int = 65535,
43
+ min_period: int = 32,
44
+ max_period: int = 1024,
45
+ ):
46
+ d = clock / target_freq
47
+ best = float('inf')
48
+ best_param = None
49
+ for period in range(max_period, min_period, -1):
50
+ a = max(int(math.floor(d / period)), min_div)
51
+ b = min(int(math.ceil(d / period)), max_div)
52
+ for div in range(a, b + 1):
53
+ if d / period == div:
54
+ return (period, div)
55
+ resudial = abs(clock / period / div - target_freq)
56
+ if resudial < tolerance:
57
+ return (period, div)
58
+ if resudial < best:
59
+ best = resudial
60
+ best_param = (period, div)
61
+ return best_param