dt5810 2.0.1__tar.gz

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.
dt5810-2.0.1/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nuclear Instruments SRL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ The CyAPI library (sdk/cpp/third_party/cyapi) is Copyright Cypress Semiconductor
24
+ Corporation (Infineon Technologies) and is distributed under its own license terms.
@@ -0,0 +1,6 @@
1
+ include README.md
2
+ include build_wheel.py
3
+ graft tests
4
+ graft examples
5
+ prune src/dt5810/_native
6
+ global-exclude *.py[cod]
dt5810-2.0.1/PKG-INFO ADDED
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: dt5810
3
+ Version: 2.0.1
4
+ Summary: Python binding of the DT5810 SDK: control of the CAEN DT5810 / DT5810B Digital Detector Emulator
5
+ Author-email: Nuclear Instruments <support@nuclearinstruments.eu>
6
+ License: MIT
7
+ Project-URL: Homepage, https://gitlab.nuclearinstruments.eu/public-repo/dt5810/dt5810-sdk
8
+ Project-URL: Documentation, https://public-repo.pages.nuclearinstruments.eu/dt5810/dt5810-sdk/
9
+ Project-URL: Source, https://gitlab.nuclearinstruments.eu/public-repo/dt5810/dt5810-sdk
10
+ Project-URL: Issues, https://gitlab.nuclearinstruments.eu/public-repo/dt5810/dt5810-sdk/-/issues
11
+ Keywords: DT5810,DT5810B,CAEN,Nuclear Instruments,detector emulator,pulser,nuclear physics
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Topic :: Scientific/Engineering :: Physics
27
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.8
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ Provides-Extra: numpy
33
+ Requires-Dist: numpy; extra == "numpy"
34
+ Provides-Extra: test
35
+ Requires-Dist: pytest; extra == "test"
36
+ Dynamic: license-file
37
+
38
+ # dt5810 — Python binding of the DT5810 SDK
39
+
40
+ Control the **CAEN DT5810 / DT5810B Digital Detector Emulator** (Nuclear Instruments) from
41
+ Python: energy (fixed or spectrum), pulse shape, time base, noise, baseline drift,
42
+ correlation, digital I/O, run control, monitoring, AWG / sequences and the analog input
43
+ MCA. The package calls the native DT5810 library (`dt5810.dll` / `libdt5810.so`) through
44
+ `ctypes`; a built-in simulator (`sim://`) lets you develop and test without hardware.
45
+
46
+ ## Installation
47
+
48
+ ```
49
+ pip install dt5810
50
+ ```
51
+
52
+ The wheels for Windows x64 and Linux x86_64 (glibc 2.34+) include the native library.
53
+ Python 3.8 or later; no mandatory dependency (`pip install dt5810[numpy]` to get numpy
54
+ arrays from the read functions with `as_numpy=True`).
55
+
56
+ With the source distribution, or to use another build of the library, point
57
+ `DT5810_LIBRARY` at it (full path of `dt5810.dll` / `libdt5810.so`); otherwise the library
58
+ is looked up in the package, then by the system loader.
59
+
60
+ ## Quick start
61
+
62
+ ```python
63
+ import dt5810
64
+
65
+ print(dt5810.version())
66
+ for d in dt5810.enumerate(dt5810.Bus.USB):
67
+ print(d.serial, d.address)
68
+
69
+ with dt5810.open("sim://") as dev: # or "usb://", "usb://NI050123"
70
+ print(dev.info.model_name, dev.info.serial)
71
+ ch = dev.channel(0)
72
+ ch.set_fixed_energy(20000) # LSB, 0..32767
73
+ ch.set_drc(rise_ns=20, decay_ns=50000)
74
+ ch.set_timebase(dt5810.TimebaseMode.POISSON, rate_hz=10000)
75
+ ch.enable()
76
+ print(ch.stats())
77
+ ```
78
+
79
+ Errors raise `dt5810.Dt5810Error` subclasses (`OutOfRangeError`, `InvalidParameterError`,
80
+ `NoDataError`, `NotFoundError`, `DeviceTimeoutError`, ...) carrying `.status` and
81
+ `.message`. The `examples/` folder has runnable scripts for every feature
82
+ (`python examples/01_info.py usb://`).
83
+
84
+ ## Documentation
85
+
86
+ User guide and API reference: <https://public-repo.pages.nuclearinstruments.eu/dt5810/dt5810-sdk/>
87
+
88
+ Source: <https://gitlab.nuclearinstruments.eu/public-repo/dt5810/dt5810-sdk> — MIT License.
dt5810-2.0.1/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # dt5810 — Python binding of the DT5810 SDK
2
+
3
+ Control the **CAEN DT5810 / DT5810B Digital Detector Emulator** (Nuclear Instruments) from
4
+ Python: energy (fixed or spectrum), pulse shape, time base, noise, baseline drift,
5
+ correlation, digital I/O, run control, monitoring, AWG / sequences and the analog input
6
+ MCA. The package calls the native DT5810 library (`dt5810.dll` / `libdt5810.so`) through
7
+ `ctypes`; a built-in simulator (`sim://`) lets you develop and test without hardware.
8
+
9
+ ## Installation
10
+
11
+ ```
12
+ pip install dt5810
13
+ ```
14
+
15
+ The wheels for Windows x64 and Linux x86_64 (glibc 2.34+) include the native library.
16
+ Python 3.8 or later; no mandatory dependency (`pip install dt5810[numpy]` to get numpy
17
+ arrays from the read functions with `as_numpy=True`).
18
+
19
+ With the source distribution, or to use another build of the library, point
20
+ `DT5810_LIBRARY` at it (full path of `dt5810.dll` / `libdt5810.so`); otherwise the library
21
+ is looked up in the package, then by the system loader.
22
+
23
+ ## Quick start
24
+
25
+ ```python
26
+ import dt5810
27
+
28
+ print(dt5810.version())
29
+ for d in dt5810.enumerate(dt5810.Bus.USB):
30
+ print(d.serial, d.address)
31
+
32
+ with dt5810.open("sim://") as dev: # or "usb://", "usb://NI050123"
33
+ print(dev.info.model_name, dev.info.serial)
34
+ ch = dev.channel(0)
35
+ ch.set_fixed_energy(20000) # LSB, 0..32767
36
+ ch.set_drc(rise_ns=20, decay_ns=50000)
37
+ ch.set_timebase(dt5810.TimebaseMode.POISSON, rate_hz=10000)
38
+ ch.enable()
39
+ print(ch.stats())
40
+ ```
41
+
42
+ Errors raise `dt5810.Dt5810Error` subclasses (`OutOfRangeError`, `InvalidParameterError`,
43
+ `NoDataError`, `NotFoundError`, `DeviceTimeoutError`, ...) carrying `.status` and
44
+ `.message`. The `examples/` folder has runnable scripts for every feature
45
+ (`python examples/01_info.py usb://`).
46
+
47
+ ## Documentation
48
+
49
+ User guide and API reference: <https://public-repo.pages.nuclearinstruments.eu/dt5810/dt5810-sdk/>
50
+
51
+ Source: <https://gitlab.nuclearinstruments.eu/public-repo/dt5810/dt5810-sdk> — MIT License.
@@ -0,0 +1,195 @@
1
+ #!/usr/bin/env python3
2
+ """Builds a platform wheel of the dt5810 package with the native library inside.
3
+
4
+ python build_wheel.py --lib <dt5810.dll | libdt5810.so> --plat-name <tag> [--outdir dist]
5
+
6
+ Examples of `--plat-name`: ``win_amd64``, ``manylinux_2_34_x86_64``.
7
+
8
+ Steps: copy the library into ``src/dt5810/_native/``, build the (pure) wheel with
9
+ ``python -m build --wheel`` (or ``pip wheel --no-deps`` when ``build`` is not installed),
10
+ retag it ``py3-none-<plat>`` (WHEEL file, RECORD and file name rewritten here: no extra
11
+ tool needed), check that it carries the library, and empty ``_native/`` again.
12
+
13
+ The build does not use an isolated environment when setuptools is already installed
14
+ (no network needed); pass ``--isolation`` to force one.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import argparse
20
+ import base64
21
+ import glob
22
+ import hashlib
23
+ import importlib.util
24
+ import os
25
+ import shutil
26
+ import subprocess
27
+ import sys
28
+ import tempfile
29
+ import zipfile
30
+
31
+ HERE = os.path.dirname(os.path.abspath(__file__))
32
+ NATIVE = os.path.join(HERE, "src", "dt5810", "_native")
33
+ NATIVE_NAMES = ("dt5810.dll", "libdt5810.so", "libdt5810.dylib")
34
+
35
+
36
+ def log(msg: str) -> None:
37
+ print("[build_wheel] " + msg, flush=True)
38
+
39
+
40
+ def native_name(lib: str, plat: str) -> str:
41
+ if plat.startswith("win"):
42
+ return "dt5810.dll"
43
+ if plat.startswith("macosx"):
44
+ return "libdt5810.dylib"
45
+ if lib.lower().endswith(".dll"):
46
+ raise SystemExit("a .dll cannot go in a %s wheel" % plat)
47
+ return "libdt5810.so"
48
+
49
+
50
+ def clean_native() -> None:
51
+ for name in NATIVE_NAMES:
52
+ p = os.path.join(NATIVE, name)
53
+ if os.path.exists(p):
54
+ os.remove(p)
55
+
56
+
57
+ def have(module: str) -> bool:
58
+ return importlib.util.find_spec(module) is not None
59
+
60
+
61
+ def setuptools_can_build_wheels() -> bool:
62
+ if not have("setuptools"):
63
+ return False
64
+ if have("wheel"):
65
+ return True
66
+ try: # setuptools >= 70.1 has its own bdist_wheel
67
+ import setuptools
68
+
69
+ major, minor = (int(x) for x in setuptools.__version__.split(".")[:2])
70
+ return (major, minor) >= (70, 1)
71
+ except Exception:
72
+ return False
73
+
74
+
75
+ def build_pure_wheel(tmpdir: str, isolation: bool) -> str:
76
+ for d in ("build", os.path.join("src", "dt5810.egg-info")):
77
+ shutil.rmtree(os.path.join(HERE, d), ignore_errors=True)
78
+ no_isolation = not isolation and setuptools_can_build_wheels()
79
+ if have("build"):
80
+ cmd = [sys.executable, "-m", "build", "--wheel", "--outdir", tmpdir]
81
+ if no_isolation:
82
+ cmd.append("--no-isolation")
83
+ else:
84
+ cmd = [sys.executable, "-m", "pip", "wheel", "--no-deps", "-w", tmpdir]
85
+ if no_isolation:
86
+ cmd.append("--no-build-isolation")
87
+ cmd.append(HERE)
88
+ log(" ".join(cmd))
89
+ subprocess.check_call(cmd, cwd=HERE)
90
+ wheels = glob.glob(os.path.join(tmpdir, "dt5810-*.whl"))
91
+ if len(wheels) != 1:
92
+ raise SystemExit("expected one wheel in %s, found %r" % (tmpdir, wheels))
93
+ for d in ("build", os.path.join("src", "dt5810.egg-info")):
94
+ shutil.rmtree(os.path.join(HERE, d), ignore_errors=True)
95
+ return wheels[0]
96
+
97
+
98
+ def record_hash(data: bytes) -> str:
99
+ digest = hashlib.sha256(data).digest()
100
+ return "sha256=" + base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
101
+
102
+
103
+ def retag(wheel: str, plat: str, outdir: str) -> str:
104
+ """Rewrites `wheel` as py3-none-<plat> into `outdir`; returns the new path."""
105
+ base = os.path.basename(wheel)
106
+ parts = base[:-4].split("-")
107
+ if len(parts) < 5:
108
+ raise SystemExit("unexpected wheel name " + base)
109
+ name, ver = parts[0], parts[1]
110
+ tag = "py3-none-" + plat
111
+ target = os.path.join(outdir, "%s-%s-%s.whl" % (name, ver, tag))
112
+ dist_info = "%s-%s.dist-info" % (name, ver)
113
+ with zipfile.ZipFile(wheel) as zin:
114
+ entries = [(i, zin.read(i.filename)) for i in zin.infolist()]
115
+ files = {}
116
+ order = []
117
+ for info, data in entries:
118
+ order.append(info)
119
+ files[info.filename] = data
120
+ wheel_meta = files[dist_info + "/WHEEL"].decode("utf-8").splitlines()
121
+ out_lines = []
122
+ for line in wheel_meta:
123
+ if line.startswith("Tag:"):
124
+ continue
125
+ if line.startswith("Root-Is-Purelib:"):
126
+ line = "Root-Is-Purelib: false"
127
+ out_lines.append(line)
128
+ while out_lines and not out_lines[-1].strip():
129
+ out_lines.pop()
130
+ out_lines.append("Tag: " + tag)
131
+ files[dist_info + "/WHEEL"] = ("\n".join(out_lines) + "\n").encode("utf-8")
132
+ record_name = dist_info + "/RECORD"
133
+ record = []
134
+ for info in order:
135
+ if info.filename == record_name or info.filename.endswith("/"):
136
+ continue
137
+ data = files[info.filename]
138
+ record.append("%s,%s,%d" % (info.filename, record_hash(data), len(data)))
139
+ record.append(record_name + ",,")
140
+ files[record_name] = ("\n".join(record) + "\n").encode("utf-8")
141
+ if os.path.exists(target):
142
+ os.remove(target)
143
+ with zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED) as zout:
144
+ for info in order:
145
+ zi = zipfile.ZipInfo(info.filename, date_time=info.date_time)
146
+ zi.external_attr = info.external_attr
147
+ zi.compress_type = zipfile.ZIP_DEFLATED
148
+ zout.writestr(zi, files[info.filename])
149
+ return target
150
+
151
+
152
+ def main() -> int:
153
+ ap = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
154
+ ap.add_argument("--lib", required=True, help="path of dt5810.dll or libdt5810.so")
155
+ ap.add_argument("--plat-name", required=True,
156
+ help="platform tag, e.g. win_amd64 or manylinux_2_34_x86_64")
157
+ ap.add_argument("--outdir", default=os.path.join(HERE, "dist"))
158
+ ap.add_argument("--isolation", action="store_true",
159
+ help="build in an isolated environment (downloads setuptools)")
160
+ ap.add_argument("--keep-native", action="store_true",
161
+ help="leave the library in src/dt5810/_native/ afterwards")
162
+ args = ap.parse_args()
163
+
164
+ lib = os.path.abspath(args.lib)
165
+ if not os.path.isfile(lib):
166
+ raise SystemExit("library not found: " + lib)
167
+ plat = args.plat_name.replace("-", "_").replace(".", "_")
168
+ outdir = os.path.abspath(args.outdir)
169
+ os.makedirs(outdir, exist_ok=True)
170
+
171
+ os.makedirs(NATIVE, exist_ok=True)
172
+ clean_native()
173
+ dest = os.path.join(NATIVE, native_name(lib, plat))
174
+ shutil.copy2(lib, dest)
175
+ log("copied %s -> %s" % (lib, dest))
176
+ try:
177
+ with tempfile.TemporaryDirectory() as tmp:
178
+ pure = build_pure_wheel(tmp, args.isolation)
179
+ target = retag(pure, plat, outdir)
180
+ finally:
181
+ if not args.keep_native:
182
+ clean_native()
183
+
184
+ with zipfile.ZipFile(target) as z:
185
+ names = z.namelist()
186
+ inside = "dt5810/_native/" + os.path.basename(dest)
187
+ if inside not in names:
188
+ raise SystemExit("the wheel does not contain " + inside)
189
+ log("wheel: " + target)
190
+ print(target)
191
+ return 0
192
+
193
+
194
+ if __name__ == "__main__":
195
+ sys.exit(main())
@@ -0,0 +1,38 @@
1
+ """Lists the instruments and prints the identity of one of them.
2
+
3
+ python 01_info.py [address] (default "sim://", e.g. "usb://" or "usb://NI050123")
4
+ """
5
+
6
+ import sys
7
+
8
+ import dt5810
9
+
10
+
11
+ def main(address: str) -> None:
12
+ print("dt5810 package %s, native library %s" % (dt5810.__version__, dt5810.version()))
13
+ print("buses compiled in: %s" % ", ".join(b.name for b in dt5810.supported_buses()))
14
+
15
+ buses = dt5810.Bus.SIMULATOR if address.startswith("sim://") else dt5810.Bus.USB
16
+ for d in dt5810.enumerate(buses):
17
+ print("found %-10s %-20s %s" % (d.serial or "?", d.address, d.description))
18
+
19
+ with dt5810.open(address) as dev:
20
+ info = dev.info
21
+ print("model %s (code 0x%08X)" % (info.model_name, info.model_code))
22
+ print("serial %s" % info.serial)
23
+ print("firmware 0x%08X" % info.firmware_release)
24
+ print("outputs/inputs %d / %d" % (info.output_channels, info.input_channels))
25
+ print("clock %.0f MHz, DAC %.0f MS/s"
26
+ % (info.clock_hz / 1e6, info.sample_rate_hz / 1e6))
27
+ print("running %s" % dt5810.RunningMode(dev.running_mode()).name)
28
+ act = dev.get_activation()
29
+ print("activation active=%s trial_counter=%d expired=%s" % tuple(act))
30
+ clk = dev.get_clock_config()
31
+ print("clock config external_in=%s clock_out=%s" % tuple(clk))
32
+ for i in range(4):
33
+ cal = dev.read_calibration(i)
34
+ print("calibration %d offset=%g gain=%g lsb_to_volt=%g" % ((i,) + tuple(cal)))
35
+
36
+
37
+ if __name__ == "__main__":
38
+ main(sys.argv[1] if len(sys.argv) > 1 else "sim://")
@@ -0,0 +1,31 @@
1
+ """A periodic pulser: fixed amplitude, exponential shape, constant rate.
2
+
3
+ python 02_fixed_energy_pulser.py [address] (default "sim://")
4
+ """
5
+
6
+ import sys
7
+ import time
8
+
9
+ import dt5810
10
+
11
+
12
+ def main(address: str) -> None:
13
+ with dt5810.open(address) as dev:
14
+ ch = dev.channel(0)
15
+ ch.set_output(gain=1.0, offset=0, invert=False, filter=True,
16
+ range=dt5810.OutputRange.HIGH_PERFORMANCE)
17
+ ch.set_fixed_energy(16000) # LSB (0..32767)
18
+ ch.set_drc(rise_ns=20, decay_ns=50000) # exponential pulse
19
+ ch.set_timebase(dt5810.TimebaseMode.CONSTANT, rate_hz=1000)
20
+ ch.set_noise() # no noise
21
+ ch.enable()
22
+ print("amplitude: 16000 LSB = %.3f V" % ch.lsb_to_volt(16000))
23
+
24
+ time.sleep(1.0)
25
+ st = ch.stats()
26
+ print("rate %d cps, %d events" % (st.count_rate, st.event_counter))
27
+ ch.disable()
28
+
29
+
30
+ if __name__ == "__main__":
31
+ main(sys.argv[1] if len(sys.argv) > 1 else "sim://")
@@ -0,0 +1,51 @@
1
+ """Emulates a gamma spectrum: three Gaussian peaks on an exponential background,
2
+ Poisson arrivals and some Gaussian noise.
3
+
4
+ python 03_spectrum_emulation.py [address] (default "sim://")
5
+ """
6
+
7
+ import math
8
+ import sys
9
+ import time
10
+
11
+ import dt5810
12
+
13
+ PEAKS = [ # (bin, sigma in bins, relative intensity)
14
+ (2000, 25, 1.0),
15
+ (6000, 40, 0.6),
16
+ (11000, 60, 0.3),
17
+ ]
18
+
19
+
20
+ def make_spectrum():
21
+ s = []
22
+ for i in range(dt5810.SPECTRUM_BINS):
23
+ v = 0.02 * math.exp(-i / 3000.0) # background
24
+ for centre, sigma, height in PEAKS:
25
+ v += height * math.exp(-0.5 * ((i - centre) / sigma) ** 2)
26
+ s.append(v)
27
+ return s
28
+
29
+
30
+ def main(address: str) -> None:
31
+ with dt5810.open(address) as dev:
32
+ ch = dev.channel(0)
33
+ ch.set_spectrum(make_spectrum()) # program + select the spectrum
34
+ ch.set_drc(rise_ns=30, decay_ns=40000)
35
+ ch.set_timebase(dt5810.TimebaseMode.POISSON, rate_hz=20000, dead_time_ns=200)
36
+ ch.set_noise(gauss=100)
37
+ ch.reset_spectrum_loopback()
38
+ ch.enable()
39
+
40
+ time.sleep(2.0)
41
+ spectrum = ch.spectrum_loopback() # 4096 bins
42
+ total = sum(spectrum)
43
+ print("emitted spectrum: %d counts in %d bins" % (total, len(spectrum)))
44
+ if total:
45
+ top = max(range(len(spectrum)), key=spectrum.__getitem__)
46
+ print("highest bin: %d (%d counts)" % (top, spectrum[top]))
47
+ ch.disable()
48
+
49
+
50
+ if __name__ == "__main__":
51
+ main(sys.argv[1] if len(sys.argv) > 1 else "sim://")
@@ -0,0 +1,36 @@
1
+ """Custom pulse shapes: one shape from samples, then two shapes with probabilities.
2
+
3
+ python 04_custom_shape.py [address] (default "sim://")
4
+ """
5
+
6
+ import math
7
+ import sys
8
+
9
+ import dt5810
10
+
11
+
12
+ def main(address: str) -> None:
13
+ with dt5810.open(address) as dev:
14
+ ch = dev.channel(0)
15
+ ts = 1.0 / dev.info.sample_rate_hz # sample period in s
16
+
17
+ # the "fast" exponential of the Control Software: 50 ns rise, 5 us decay
18
+ fast = dt5810.make_fast_exponential(rise_us=0.05, decay_us=5.0, sample_period_s=ts,
19
+ length=dt5810.CUSTOM_SHAPE_MAX_POINTS)
20
+ ch.set_custom_shape(fast, pileup_limit=16)
21
+ print("custom shape: %d samples, peak %.0f" % (len(fast), max(fast)))
22
+
23
+ # a slower, Gaussian-like shape mixed with the first one: 70 % / 30 %
24
+ slow = [32575 * math.exp(-0.5 * ((i - 300) / 80.0) ** 2) for i in range(1200)]
25
+ ch.set_multi_shapes([fast, slow], [70, 30], pileup_limit=8)
26
+ print("multishape: 70 % fast exponential, 30 % gaussian")
27
+
28
+ ch.set_fixed_energy(20000)
29
+ ch.set_timebase(dt5810.TimebaseMode.POISSON, rate_hz=5000)
30
+ ch.enable()
31
+ ch.disable()
32
+ ch.disable_custom_shape() # back to the digital RC shaper
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main(sys.argv[1] if len(sys.argv) > 1 else "sim://")
@@ -0,0 +1,42 @@
1
+ """Monitoring: statistics, run-control live data and the signal / spectrum loopback.
2
+
3
+ python 05_monitor.py [address] [seconds] (default "sim://", 3 s)
4
+ """
5
+
6
+ import sys
7
+ import time
8
+
9
+ import dt5810
10
+
11
+
12
+ def main(address: str, seconds: float) -> None:
13
+ with dt5810.open(address) as dev:
14
+ ch = dev.channel(0)
15
+ ch.set_fixed_energy(12000)
16
+ ch.set_drc(rise_ns=20, decay_ns=20000)
17
+ ch.set_timebase(dt5810.TimebaseMode.POISSON, rate_hz=50000)
18
+ ch.set_run_control(dt5810.RunMode.FREE_RUNNING)
19
+ ch.run_control_reset_stats()
20
+ ch.reset_counter()
21
+ ch.enable()
22
+
23
+ end = time.time() + seconds
24
+ while time.time() < end:
25
+ time.sleep(0.5)
26
+ st = ch.stats()
27
+ ld = ch.live_data()
28
+ print("rate %6d cps events %9d real %.2f s run %.2f s busy=%s sat=%s"
29
+ % (st.count_rate, st.event_counter, ld.real_time_s, ld.run_time_s,
30
+ ld.busy, ld.saturated))
31
+
32
+ signal = ch.signal_loopback() # 8192 samples
33
+ spectrum = ch.spectrum_loopback() # 4096 bins
34
+ print("signal loopback: %d samples, min %d, max %d"
35
+ % (len(signal), min(signal), max(signal)))
36
+ print("spectrum loopback: %d bins, %d counts" % (len(spectrum), sum(spectrum)))
37
+ ch.disable()
38
+
39
+
40
+ if __name__ == "__main__":
41
+ main(sys.argv[1] if len(sys.argv) > 1 else "sim://",
42
+ float(sys.argv[2]) if len(sys.argv) > 2 else 3.0)
@@ -0,0 +1,51 @@
1
+ """The analog input MCA: trapezoidal filter, spectrum acquisition and preview.
2
+
3
+ python 06_mca_input.py [address] [seconds] (default "sim://", 2 s)
4
+ """
5
+
6
+ import sys
7
+ import time
8
+
9
+ import dt5810
10
+
11
+
12
+ def main(address: str, seconds: float) -> None:
13
+ with dt5810.open(address) as dev:
14
+ dev.mca_set_input(impedance=0, divider=0, mode=0, scale=0, gain=0, offset=0)
15
+
16
+ cfg = dt5810.mca_default_config()
17
+ cfg.peaking_us = 2.0
18
+ cfg.flat_top_us = 0.5
19
+ cfg.decay_tau_us = 50.0
20
+ dev.mca_configure(cfg)
21
+
22
+ dev.mca_reset()
23
+ dev.mca_clear_spectrum()
24
+ dev.mca_run(True)
25
+ time.sleep(seconds)
26
+ spectrum = dev.mca_get_spectrum() # 16384 bins
27
+ dev.mca_stop()
28
+ total = sum(spectrum)
29
+ print("MCA spectrum: %d counts in %d bins" % (total, len(spectrum)))
30
+ if total:
31
+ peak = max(range(len(spectrum)), key=spectrum.__getitem__)
32
+ print("peak at bin %d" % peak)
33
+
34
+ dev.mca_preview_configure(analog1=0, analog2=1, digital1=0, digital2=1,
35
+ trigger_source=0, trigger_level=500, post_trigger=1024)
36
+ for _ in range(20):
37
+ try:
38
+ p = dev.mca_preview_read()
39
+ break
40
+ except dt5810.NoDataError: # not triggered yet
41
+ time.sleep(0.1)
42
+ else:
43
+ print("the preview did not trigger")
44
+ return
45
+ print("preview: %d samples, analog1 max %d, analog2 max %d"
46
+ % (len(p.analog1), max(p.analog1), max(p.analog2)))
47
+
48
+
49
+ if __name__ == "__main__":
50
+ main(sys.argv[1] if len(sys.argv) > 1 else "sim://",
51
+ float(sys.argv[2]) if len(sys.argv) > 2 else 2.0)
@@ -0,0 +1,37 @@
1
+ """Arbitrary waveform generator: a sine wave on channel 0, a pulse sequence on channel 1.
2
+
3
+ python 07_awg.py [address] (default "sim://")
4
+ """
5
+
6
+ import math
7
+ import sys
8
+
9
+ import dt5810
10
+
11
+
12
+ def main(address: str) -> None:
13
+ with dt5810.open(address) as dev:
14
+ awg = dev.channel(0)
15
+ samples = [int(15000 * math.sin(2 * math.pi * i / 256)) for i in range(4096)]
16
+ awg.set_awg_waveform(samples) # padded to a multiple of 16
17
+ awg.set_awg_scaler(0) # one sample per clock period
18
+ awg.set_mode(dt5810.ChannelMode.AWG)
19
+ awg.enable()
20
+ print("channel 0: AWG, %d samples" % len(samples))
21
+
22
+ seq = dev.channel(1)
23
+ energies = [4000, 8000, 12000, 16000]
24
+ times = [2500, 2500, 2500, 25000] # clock periods after the previous pulse
25
+ seq.set_sequence(energy=energies, time=times)
26
+ seq.set_drc(rise_ns=20, decay_ns=10000)
27
+ seq.enable()
28
+ print("channel 1: sequence of %d pulses" % len(energies))
29
+
30
+ seq.disable()
31
+ seq.clear_sequence()
32
+ awg.disable()
33
+ awg.set_mode(dt5810.ChannelMode.PULSER)
34
+
35
+
36
+ if __name__ == "__main__":
37
+ main(sys.argv[1] if len(sys.argv) > 1 else "sim://")