ddsctrl 0.5.1__tar.gz → 0.5.4__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.
@@ -0,0 +1,17 @@
1
+ Benoit Dubois
2
+ Copyright (C) 2019 FEMTO Engineering
3
+ benoit.dubois@femto-st.fr
4
+
5
+ This program is free software; you can redistribute it and/or
6
+ modify it under the terms of the GNU Lesser General Public
7
+ License as published by the Free Software Foundation; either
8
+ version 3 of the License, or (at your option) any later version.
9
+
10
+ This program is distributed in the hope that it will be useful,
11
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
+ Lesser General Public License for more details.
14
+
15
+ You should have received a copy of the GNU Lesser General Public License
16
+ along with this program; if not, write to the Free Software Foundation,
17
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
ddsctrl-0.5.4/PKG-INFO ADDED
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: ddsctrl
3
+ Version: 0.5.4
4
+ Summary: ddscontroller allow basic handling of AD9912 and AD9915 DDS development board.
5
+ Home-page: https://gitlab.com/bendub/ddsctrl
6
+ Author: Benoit Dubois
7
+ Author-email: benoit.dubois@femto-engineering.fr
8
+ License: LGPL-3.0-or-later
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: Natural Language :: English
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Topic :: Scientific/Engineering
15
+ License-File: LICENSE.txt
16
+ Requires-Dist: PyQt5
17
+ Requires-Dist: ad9xdds
18
+ Requires-Dist: signalslot
19
+ Dynamic: author
20
+ Dynamic: author-email
21
+ Dynamic: classifier
22
+ Dynamic: home-page
23
+ Dynamic: license
24
+ Dynamic: license-file
25
+ Dynamic: requires-dist
26
+ Dynamic: summary
@@ -0,0 +1,614 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """package ddsctrl
5
+ author Benoit Dubois
6
+ copyright FEMTO ENGINEERING, 2016, 2023
7
+ license GPL v3.0+
8
+ brief Main script of the DDS controller project.
9
+ """
10
+
11
+ # Ctrl-c closes the application
12
+ import signal
13
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
14
+
15
+ import sys
16
+ import argparse
17
+ import os.path as path
18
+ import logging
19
+ import itertools
20
+ import usb.core
21
+ import usb.util
22
+ from PyQt5.QtCore import QSettings
23
+ from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox
24
+
25
+ import ad9xdds.sad9912dev as sad9912dev
26
+ import ad9xdds.ad9912dev as ad9912dev
27
+ import ad9xdds.sad9915dev_umr232hm as sad9915dev
28
+ import ad9xdds.ad9915dev_umr232hm as ad9915dev
29
+
30
+ from ddsctrl.ddsctrlui import DdsCtrlUi, DdsBoardSelection
31
+ from ddsctrl.constants import ORGANIZATION, APP_NAME, DEFAULT_AUTO_UPDATE, \
32
+ DEFAULT_AD9912_OFREQ, DEFAULT_AD9912_AMP, DEFAULT_AD9912_PHASE, \
33
+ DEFAULT_AD9912_PLL_EN, DEFAULT_AD9912_PLL_FACTOR, \
34
+ DEFAULT_AD9912_CP_CURRENT, DEFAULT_AD9912_VCO_RANGE, \
35
+ DEFAULT_AD9912_PLL_DOUBLER, DEFAULT_AD9912_HSTL_EN, \
36
+ DEFAULT_AD9912_CMOS_EN, DEFAULT_AD9912_IFREQ, \
37
+ DEFAULT_AD9912_HSTL_DOUBLER
38
+
39
+ CONSOLE_LOG_LEVEL = logging.DEBUG
40
+ FILE_LOG_LEVEL = logging.DEBUG
41
+
42
+
43
+ # =============================================================================
44
+ def configure_logging():
45
+ """Configures logs.
46
+ """
47
+ home = path.expanduser("~")
48
+ log_file = "." + APP_NAME + ".log"
49
+ abs_log_file = path.join(home, log_file)
50
+ date_fmt = "%d/%m/%Y %H:%M:%S"
51
+ log_format = "%(asctime)s %(levelname) -8s %(filename)s " + \
52
+ " %(funcName)s (%(lineno)d): %(message)s"
53
+ logging.basicConfig(level=FILE_LOG_LEVEL,
54
+ datefmt=date_fmt,
55
+ format=log_format,
56
+ filename=abs_log_file,
57
+ filemode='w')
58
+ console = logging.StreamHandler()
59
+ # define a Handler which writes messages to the sys.stderr
60
+ console.setLevel(CONSOLE_LOG_LEVEL)
61
+ # set a format which is simpler for console use
62
+ console_format = '%(levelname) -8s %(filename)s (%(lineno)d): %(message)s'
63
+ formatter = logging.Formatter(console_format)
64
+ # tell the handler to use this format
65
+ console.setFormatter(formatter)
66
+ # add the handler to the root logger
67
+ logging.getLogger('').addHandler(console)
68
+
69
+
70
+ # =============================================================================
71
+ def parse_cmd_line():
72
+ """Parses command line: allow user to choose a different ini file.
73
+ :returns: parser object (object)
74
+ """
75
+ parser = argparse.ArgumentParser(description='DdsController')
76
+ parser.add_argument('-ini', '--ini_file', dest='ini_file',
77
+ type=str, default='', help='-ini=<str>')
78
+ parser.add_argument('-wt', '--window_title', dest='wtitle',
79
+ type=str, default="DDS controller")
80
+ parser.add_argument('-d', '--dds_model', choices=["AD9912", "AD9915"],
81
+ dest='model')
82
+ parser.add_argument('-b', '--dds_bus', type=int, dest='bus')
83
+ parser.add_argument('-a', '--dds_address', type=int, dest='address')
84
+ args = parser.parse_args()
85
+ if args.model and (args.bus is None or args.address is None):
86
+ parser.error("--dds_model requires --dds_bus and --dds_adress.")
87
+ if not args.model and (args.bus is not None or args.address is not None):
88
+ parser.error("--dds_bus and --dds_adress requires --dds_model.")
89
+ return args
90
+
91
+
92
+ # =============================================================================
93
+ def reset_ini(settings):
94
+ """Resets the "settings" file with default values.
95
+ We use AD9912 default values because these values are "conservative" values
96
+ which could be used with AD9915 device (ofreq, ifreq, amp).
97
+ Note: due to conversion problem from QVariant (type returned by QSettings)
98
+ to boolean type in Python, boolean value are stored in integer:
99
+ 0 <-> False, 1 <-> True.
100
+ :param ui: ddsctrlui class instance (object)
101
+ :param settings: a valid QSettings object (QSettings)
102
+ :returns: None
103
+ """
104
+ settings.clear()
105
+ settings.setValue('dds_ctrl/auto_update', int(DEFAULT_AUTO_UPDATE))
106
+ settings.setValue('dds_ctrl/ifreq', DEFAULT_AD9912_IFREQ)
107
+ settings.setValue('dds_ctrl/ofreq', DEFAULT_AD9912_OFREQ)
108
+ settings.setValue('dds_ctrl/amp', DEFAULT_AD9912_AMP)
109
+ settings.setValue('dds_ctrl/phase', DEFAULT_AD9912_PHASE)
110
+ settings.setValue('dds_ctrl/hstl_en', int(DEFAULT_AD9912_HSTL_EN))
111
+ settings.setValue('dds_ctrl/cmos_en', int(DEFAULT_AD9912_CMOS_EN))
112
+ settings.setValue('dds_ctrl/hstl_doubler', int(DEFAULT_AD9912_HSTL_DOUBLER))
113
+ settings.setValue('dds_ctrl/pll_en', int(DEFAULT_AD9912_PLL_EN))
114
+ settings.setValue('dds_ctrl/pll_doubler', int(DEFAULT_AD9912_PLL_DOUBLER))
115
+ settings.setValue('dds_ctrl/pll_factor', DEFAULT_AD9912_PLL_FACTOR)
116
+ settings.setValue('dds_ctrl/vco_range', DEFAULT_AD9912_VCO_RANGE)
117
+ settings.setValue('dds_ctrl/cp_current', DEFAULT_AD9912_CP_CURRENT)
118
+
119
+
120
+ # =============================================================================
121
+ def save_all_settings(ui):
122
+ """Save current setting to ini file.
123
+ :returns: None
124
+ """
125
+ settings = QSettings()
126
+ settings.setValue('dds_ctrl/auto_update',
127
+ ui.ocontroller_ui.auto_update_ckbox.isChecked())
128
+ settings.setValue('dds_ctrl/ofreq', ui.ocontroller_ui.ofreq_tuning.value())
129
+ settings.setValue('dds_ctrl/phase', ui.ocontroller_ui.phy_tuning.value())
130
+ settings.setValue('dds_ctrl/amp', ui.ocontroller_ui.amp_tuning.value())
131
+ settings.setValue('dds_ctrl/ifreq', ui.icontroller_ui.ifreq_dsb.value())
132
+ if model == "AD9912":
133
+ settings.setValue('dds_ctrl/hstl_en',
134
+ int(ui.ocontroller_ui.out_hstl_ckbox.isChecked()))
135
+ settings.setValue('dds_ctrl/cmos_en',
136
+ int(ui.ocontroller_ui.out_cmos_ckbox.isChecked()))
137
+ settings.setValue(
138
+ 'dds_ctrl/hstl_doubler',
139
+ int(ui.ocontroller_ui.hstl_doubler_ckbox.isChecked()))
140
+ settings.setValue(
141
+ 'dds_ctrl/pll_en',
142
+ int(ui.icontroller_ui.advanced_config_gbox.isChecked()))
143
+ settings.setValue(
144
+ 'dds_ctrl/pll_doubler',
145
+ int(ui.icontroller_ui.pll_doubler_ckbox.checkState()))
146
+ settings.setValue('dds_ctrl/pll_factor',
147
+ ui.icontroller_ui.pll_factor_sbox.value())
148
+ settings.setValue('dds_ctrl/vco_range',
149
+ ui.icontroller_ui.vco_range_cbox.currentIndex())
150
+ settings.setValue('dds_ctrl/cp_current',
151
+ ui.icontroller_ui.cp_current_cbox.currentIndex())
152
+
153
+
154
+ # =============================================================================
155
+ def check_ini_file(user_ini_file=None):
156
+ """Check validity of ini file. Ini file can be given by user or can be the
157
+ system default ini file. If the system ini file is broken or missing,
158
+ create an ini file with default values.
159
+ :param user_ini_file: Ini file given by user (str)
160
+ :returns: None
161
+ """
162
+ if user_ini_file is not None:
163
+ settings = QSettings(user_ini_file)
164
+ else:
165
+ settings = QSettings()
166
+ retval = list()
167
+ try:
168
+ retval.append(settings.value('dds_ctrl/auto_update'))
169
+ retval.append(settings.value('dds_ctrl/ofreq'))
170
+ retval.append(settings.value('dds_ctrl/phase'))
171
+ retval.append(settings.value('dds_ctrl/amp'))
172
+ retval.append(settings.value('dds_ctrl/hstl_en'))
173
+ retval.append(settings.value('dds_ctrl/cmos_en'))
174
+ retval.append(settings.value('dds_ctrl/hstl_doubler'))
175
+ retval.append(settings.value('dds_ctrl/ifreq'))
176
+ retval.append(settings.value('dds_ctrl/pll_en'))
177
+ retval.append(settings.value('dds_ctrl/pll_doubler'))
178
+ retval.append(settings.value('dds_ctrl/pll_factor'))
179
+ retval.append(settings.value('dds_ctrl/vco_range'))
180
+ retval.append(settings.value('dds_ctrl/cp_current'))
181
+ if None in retval:
182
+ # Test needded because QSettings does not catch exception when
183
+ # a parameter is missing in ini file, instead QSettings return None.
184
+ if user_ini_file is None:
185
+ logging.warning("Missing or broken ini file")
186
+ reset_ini(settings)
187
+ logging.info("Create ini file with default values")
188
+ else:
189
+ logging.critical("Ini file given by user is not valid")
190
+ QMessageBox().critical(None, "Ini file error", \
191
+ "User ini file not valid")
192
+ sys.exit()
193
+ except Exception as ex:
194
+ if user_ini_file is None:
195
+ logging.warning("Missing or broken ini file")
196
+ reset_ini(settings)
197
+ logging.info("Create ini file with default values")
198
+ else:
199
+ logging.critical("User ini file not valid %r", ex)
200
+ QMessageBox().critical(None, "Ini file error",
201
+ "User ini file not valid: {}".format(ex))
202
+ sys.exit()
203
+
204
+
205
+ # =============================================================================
206
+ def init_ui(ui, model):
207
+ """Init UI.
208
+ :param ui: ddsctrlui class instance (object)
209
+ :param model: dds model i.e. "AD9912" or "AD9915" (str)
210
+ :returns: None
211
+ """
212
+ if model == "AD9912":
213
+ amax = ad9912dev.AMAX
214
+ ifmax = ad9912dev.IFMAX
215
+ else:
216
+ amax = ad9915dev.AMAX
217
+ ifmax = ad9915dev.IFMAX
218
+ ui.icontroller_ui.set_ifmax(ifmax)
219
+ ui.ocontroller_ui.set_ofmax(ifmax*0.4)
220
+ ui.ocontroller_ui.set_amax(amax)
221
+ # Init UI with respect to (ini file) actual dds configuration
222
+ settings = QSettings()
223
+ auto_update = bool(settings.value('dds_ctrl/auto_update'))
224
+ ofreq = float(settings.value('dds_ctrl/ofreq'))
225
+ ifreq = float(settings.value('dds_ctrl/ifreq'))
226
+ phase = float(settings.value('dds_ctrl/phase'))
227
+ amp = int(settings.value('dds_ctrl/amp'))
228
+ ##ui.ocontroller_ui.auto_update_ckbox.setChecked(False)
229
+ ui.ocontroller_ui.auto_update_ckbox.setChecked(auto_update)
230
+ ui.ocontroller_ui.ofreq_tuning.setValue(ofreq)
231
+ ui.ocontroller_ui.phy_tuning.setValue(phase)
232
+ ui.ocontroller_ui.amp_tuning.setValue(amp)
233
+ ui.icontroller_ui.ifreq_dsb.setValue(ifreq)
234
+ if model == "AD9912":
235
+ hstl_en = bool(int(settings.value('dds_ctrl/hstl_en')))
236
+ cmos_en = bool(int(settings.value('dds_ctrl/cmos_en')))
237
+ hstl_doubler = bool(int(settings.value('dds_ctrl/hstl_doubler')))
238
+ pll_en = bool(int(settings.value('dds_ctrl/pll_en')))
239
+ pll_doubler = bool(int(settings.value('dds_ctrl/pll_doubler')))
240
+ pll_factor = int(settings.value('dds_ctrl/pll_factor'))
241
+ vco_range = int(settings.value('dds_ctrl/vco_range'))
242
+ cp_current = int(settings.value('dds_ctrl/cp_current'))
243
+ ui.ocontroller_ui.out_hstl_ckbox.setChecked(hstl_en)
244
+ ui.ocontroller_ui.hstl_doubler_ckbox.setChecked(hstl_doubler)
245
+ ui.ocontroller_ui.out_cmos_ckbox.setChecked(cmos_en)
246
+ ui.icontroller_ui.advanced_config_gbox.setChecked(pll_en)
247
+ ui.icontroller_ui.pll_doubler_ckbox.setChecked(pll_doubler)
248
+ ui.icontroller_ui.pll_factor_sbox.setValue(pll_factor)
249
+ ui.icontroller_ui.vco_range_cbox.setCurrentIndex(vco_range)
250
+ ui.icontroller_ui.cp_current_cbox.setCurrentIndex(cp_current)
251
+ ui.icontroller_ui.update_sys_clock_label()
252
+
253
+
254
+ # =============================================================================
255
+ def init_dev(dds, model):
256
+ """Init device.
257
+ :param dds: dds class instance (object)
258
+ :param model: dds model i.e. "AD9912" or "AD9915" (str)
259
+ :returns: None
260
+ """
261
+ settings = QSettings()
262
+ ofreq = float(settings.value('dds_ctrl/ofreq'))
263
+ ifreq = float(settings.value('dds_ctrl/ifreq'))
264
+ phase = float(settings.value('dds_ctrl/phase'))
265
+ amp = int(settings.value('dds_ctrl/amp'))
266
+ dds.set_ofreq(ofreq)
267
+ dds.set_ifreq(ifreq)
268
+ dds.set_phy(phase)
269
+ dds.set_amp(amp)
270
+ if model == "AD9912":
271
+ hstl_en = bool(int(settings.value('dds_ctrl/hstl_en')))
272
+ cmos_en = bool(int(settings.value('dds_ctrl/cmos_en')))
273
+ hstl_doubler = bool(int(settings.value('dds_ctrl/hstl_doubler')))
274
+ pll_en = bool(int(settings.value('dds_ctrl/pll_en')))
275
+ pll_doubler = bool(int(settings.value('dds_ctrl/pll_doubler')))
276
+ pll_factor = int(settings.value('dds_ctrl/pll_factor'))
277
+ vco_range = int(settings.value('dds_ctrl/vco_range'))
278
+ cp_current = int(settings.value('dds_ctrl/cp_current'))
279
+ dds.set_hstl_output_state(hstl_en)
280
+ dds.set_cmos_output_state(cmos_en)
281
+ dds.set_hstl_doubler_state(hstl_doubler)
282
+ dds.set_pll_state(pll_en)
283
+ dds.set_pll_doubler_state(pll_doubler)
284
+ dds.set_pll_multiplier_factor(pll_factor)
285
+ dds.set_vco_range(vco_range)
286
+ dds.set_cp_current(cp_current)
287
+
288
+
289
+ # =============================================================================
290
+ def handle_ocontroller_ui_behavior(ui, dds, model):
291
+ """Handle behavior of output widget tab with respect to automatic update
292
+ checkbox: if auto is true, modification are directly take into account,
293
+ else modification are take into account after apply button is pressed.
294
+ :param ui: ddsctrlui class instance (object)
295
+ :param dds: dds class instance (object)
296
+ :param model: dds model i.e. "AD9912" or "AD9915" (str)
297
+ :returns: None
298
+ """
299
+ if ui.ocontroller_ui.auto_update_ckbox.isChecked() is True:
300
+ ui.ocontroller_ui.ofreq_tuning.valueChanged[str].connect(
301
+ lambda: ofreq_changed(ui, dds))
302
+ ui.ocontroller_ui.amp_tuning.valueChanged[str].connect(
303
+ lambda: amplitude_changed(ui, dds))
304
+ ui.ocontroller_ui.phy_tuning.valueChanged[str].connect(
305
+ lambda: phase_changed(ui, dds))
306
+ if model == "AD9912":
307
+ ui.ocontroller_ui.out_hstl_ckbox.stateChanged.connect(
308
+ lambda: hstl_output_changed(ui, dds))
309
+ ui.ocontroller_ui.hstl_doubler_ckbox.stateChanged.connect(
310
+ lambda: hstl_doubler_changed(ui, dds))
311
+ ui.ocontroller_ui.out_cmos_ckbox.stateChanged.connect(
312
+ lambda: cmos_output_changed(ui, dds))
313
+ try:
314
+ ui.ocontroller_ui.apply_btn.clicked.disconnect()
315
+ except Exception as ex:
316
+ logging.warning(ex)
317
+ else:
318
+ ui.ocontroller_ui.apply_btn.clicked.connect(
319
+ lambda: apply_btn_octrl_clicked(ui, dds, model))
320
+ try:
321
+ ui.ocontroller_ui.ofreq_tuning.valueChanged[str].disconnect()
322
+ ui.ocontroller_ui.amp_tuning.valueChanged[str].disconnect()
323
+ ui.ocontroller_ui.phy_tuning.valueChanged[str].disconnect()
324
+ if model == "AD9912":
325
+ ui.ocontroller_ui.out_hstl_ckbox.stateChanged.disconnect()
326
+ ui.ocontroller_ui.hstl_doubler_ckbox.stateChanged.disconnect()
327
+ ui.ocontroller_ui.out_cmos_ckbox.stateChanged.disconnect()
328
+ except Exception as ex:
329
+ logging.warning(ex)
330
+
331
+
332
+ # =============================================================================
333
+ def get_reg_btn_clicked(ui, dds):
334
+ """Get register value.
335
+ :param ui: ddsctrlui class instance (object)
336
+ :param dds: dds class instance (object)
337
+ :returns: None
338
+ """
339
+ try:
340
+ reg_val = dds.get_reg(
341
+ int(str(ui.debug_ui.reg_add_led.text()), 16))
342
+ except ValueError as ex:
343
+ pass # Error handled in dds.get_reg
344
+ except Exception as ex:
345
+ logging.warning("Problem when getting register value: %s", str(ex))
346
+ else:
347
+ ui.debug_ui.reg_val_led.setText(format(reg_val, '#02x'))
348
+
349
+
350
+ # =============================================================================
351
+ def set_reg_btn_clicked(ui, dds):
352
+ """Set register value.
353
+ :param ui: ddsctrlui class instance (object)
354
+ :param dds: dds class instance (object)
355
+ :returns: None
356
+ """
357
+ dds.set_reg(int(str(ui.debug_ui.reg_add_led.text()), 16),
358
+ int(str(ui.debug_ui.reg_val_led.text()), 16))
359
+
360
+
361
+ # =============================================================================
362
+ def ofreq_changed(ui, dds, **kwargs):
363
+ """Update output frequency
364
+ :param ui: ddsctrlui class instance (object)
365
+ :param dds: dds class instance (object)
366
+ :returns: None
367
+ """
368
+ act_ofreq = dds.set_ofreq(ui.ocontroller_ui.ofreq_tuning.value())
369
+ ui.ocontroller_ui.ofreq_tuning.setValue(float(act_ofreq))
370
+ QSettings().setValue('dds_ctrl/ofreq', act_ofreq)
371
+
372
+
373
+ # =============================================================================
374
+ def amplitude_changed(ui, dds):
375
+ """Update output amplitude
376
+ :param ui: ddsctrlui class instance (object)
377
+ :param dds: dds class instance (object)
378
+ :returns: None
379
+ """
380
+ act_amp = dds.set_amp(int(ui.ocontroller_ui.amp_tuning.value()))
381
+ ui.ocontroller_ui.amp_tuning.setValue(float(act_amp))
382
+ QSettings().setValue('dds_ctrl/amp', act_amp)
383
+
384
+
385
+ # =============================================================================
386
+ def phase_changed(ui, dds):
387
+ """Update output phase
388
+ :param ui: ddsctrlui class instance (object)
389
+ :param dds: dds class instance (object)
390
+ :returns: None
391
+ """
392
+ act_phy = dds.set_phy(ui.ocontroller_ui.phy_tuning.value())
393
+ ui.ocontroller_ui.phy_tuning.setValue(float(act_phy))
394
+ QSettings().setValue('dds_ctrl/phase', act_phy)
395
+
396
+
397
+ # =============================================================================
398
+ def hstl_output_changed(ui, dds):
399
+ """Update HSTL outputs state
400
+ :param ui: ddsctrlui class instance (object)
401
+ :param dds: dds class instance (object)
402
+ :returns: None
403
+ """
404
+ dds.set_hstl_output_state(ui.ocontroller_ui.out_hstl_ckbox.isChecked())
405
+ QSettings().setValue('dds_ctrl/hstl_en',
406
+ int(ui.ocontroller_ui.out_hstl_ckbox.isChecked()))
407
+
408
+
409
+ # =============================================================================
410
+ def hstl_doubler_changed(ui, dds):
411
+ """Update HSTL doubler state
412
+ :param ui: ddsctrlui class instance (object)
413
+ :param dds: dds class instance (object)
414
+ :returns: None
415
+ """
416
+ dds.set_hstl_doubler_state(ui.ocontroller_ui.hstl_doubler_ckbox.isChecked())
417
+ QSettings().setValue('dds_ctrl/hstl_doubler',
418
+ int(ui.ocontroller_ui.hstl_doubler_ckbox.isChecked()))
419
+
420
+
421
+ # =============================================================================
422
+ def cmos_output_changed(ui, dds):
423
+ """Update CMOS outputs state
424
+ :param ui: ddsctrlui class instance (object)
425
+ :param dds: dds class instance (object)
426
+ :returns: None
427
+ """
428
+ dds.set_cmos_output_state(ui.ocontroller_ui.out_cmos_ckbox.isChecked())
429
+ QSettings().setValue('dds_ctrl/cmos_en',
430
+ int(ui.ocontroller_ui.out_cmos_ckbox.isChecked()))
431
+
432
+
433
+ # =============================================================================
434
+ def apply_btn_ictrl_clicked(ui, dds, model):
435
+ """Define action when apply button of input control form is clicked.
436
+ :param ui: ddsctrlui class instance (object)
437
+ :param dds: dds class instance (object)
438
+ :returns: None
439
+ """
440
+ # Update input frequency
441
+ dds.set_ifreq(ui.icontroller_ui.ifreq_dsb.value())
442
+ if model == "AD9912":
443
+ # Update PLL attribute
444
+ dds.set_pll_state(ui.icontroller_ui.advanced_config_gbox.isChecked())
445
+ dds.set_pll_doubler_state(
446
+ ui.icontroller_ui.pll_doubler_ckbox.isChecked())
447
+ dds.set_pll_multiplier_factor(
448
+ ui.icontroller_ui.pll_factor_sbox.value())
449
+ dds.set_cp_current(ui.icontroller_ui.cp_current_cbox.currentIndex())
450
+ dds.set_vco_range(ui.icontroller_ui.vco_range_cbox.currentIndex())
451
+ # Force update of output frequency in case of system clock is updated
452
+ # because output is a function of system clock frequency value.
453
+ ####ofreq_changed(ui, dds)
454
+ # Save new settings in ini file.
455
+ settings = QSettings()
456
+ settings.setValue('dds_ctrl/ifreq', ui.icontroller_ui.ifreq_dsb.value())
457
+ if model == "AD9912":
458
+ settings.setValue(
459
+ 'dds_ctrl/pll_en',
460
+ int(ui.icontroller_ui.advanced_config_gbox.isChecked()))
461
+ settings.setValue('dds_ctrl/pll_doubler',
462
+ int(ui.icontroller_ui.pll_doubler_ckbox.isChecked()))
463
+ settings.setValue('dds_ctrl/pll_factor',
464
+ ui.icontroller_ui.pll_factor_sbox.value())
465
+ settings.setValue('dds_ctrl/vco_range',
466
+ ui.icontroller_ui.vco_range_cbox.currentIndex())
467
+ settings.setValue('dds_ctrl/cp_current',
468
+ ui.icontroller_ui.cp_current_cbox.currentIndex())
469
+
470
+
471
+ # =============================================================================
472
+ def apply_btn_octrl_clicked(ui, dds, model):
473
+ """Define action when apply button of output control form is clicked.
474
+ :param ui: ddsctrlui class instance (object)
475
+ :param dds: dds class instance (object)
476
+ :param model: dds model i.e. "AD9912" or "AD9915" (str)
477
+ :returns: None
478
+ """
479
+ ofreq_changed(ui, dds)
480
+ amplitude_changed(ui, dds)
481
+ phase_changed(ui, dds)
482
+ if model == "AD9912":
483
+ hstl_output_changed(ui, dds)
484
+ hstl_doubler_changed(ui, dds)
485
+ cmos_output_changed(ui, dds)
486
+
487
+
488
+ # =============================================================================
489
+ def find_dds_board():
490
+ """Return list of AD9912 and AD9915 dds development board connected to
491
+ computer.
492
+ :returns: list of AD9912 and AD9915 dds (list of usb.core.Device)
493
+ """
494
+ # if firmware of AD9912Dev board is not loaded, display msgbox.
495
+ if usb.core.find(idVendor=0x0456, idProduct=0xee08) is not None or \
496
+ usb.core.find(idVendor=0x0456, idProduct=0xee24) is not None:
497
+ QMessageBox().warning(None, title="AD9912 detected",
498
+ text="AD9912 detected but firmware not loaded")
499
+ # AD9912 dev board
500
+ ad9912dev = usb.core.find(idVendor=0x0456, idProduct=0xee09, find_all=True)
501
+ # AD9915 dev board (through FTDI UM232H)
502
+ ad9915dev = usb.core.find(idVendor=0x0403, idProduct=0x6014, find_all=True)
503
+ return {'ad9912dev': ad9912dev, 'ad9915dev': ad9915dev}
504
+
505
+
506
+ # =============================================================================
507
+ def ddscontroller(ini_file, window_title,
508
+ dds_model=None, dds_bus=None, dds_address=None):
509
+ """Main program of DDS controller project.
510
+ :returns: None
511
+ """
512
+ app = QApplication(sys.argv)
513
+ app.setOrganizationName(ORGANIZATION)
514
+ app.setApplicationName(APP_NAME)
515
+
516
+ if dds_model is None or dds_bus is None or dds_address is None:
517
+ # Select board to handle
518
+ dds_boards = find_dds_board()
519
+ dds_boards_for_human = \
520
+ ["AD9912 on bus {} address {}".format(dds.bus, dds.address)
521
+ for dds in dds_boards['ad9912dev']] + \
522
+ ["AD9915 on bus {} address {}".format(dds.bus, dds.address)
523
+ for dds in dds_boards['ad9915dev']]
524
+ msg = DdsBoardSelection(dds_boards_for_human)
525
+ retval = msg.exec_()
526
+ if retval == 1:
527
+ dds_item = msg.dds_selected()
528
+ dds_model = dds_item.split()[0]
529
+ dds_bus = int(dds_item.split()[3])
530
+ dds_address = int(dds_item.split()[5])
531
+ else:
532
+ sys.exit()
533
+ if dds_model == "AD9912":
534
+ dds = sad9912dev.SAd9912Dev()
535
+ retval = dds.connect(bus=dds_bus, address=dds_address)
536
+ if retval is False:
537
+ logging.critical('DDS connection problem')
538
+ QMessageBox.critical(None, 'DDS connection error',
539
+ 'DDS connection problem\nCheck connection')
540
+ sys.exit()
541
+ ui = DdsCtrlUi("ad9912")
542
+ elif dds_model == "AD9915":
543
+ dds = sad9915dev.SAd9915Dev()
544
+ url = "ftdi://ftdi:232h:{:x}:{:x}/1".format(dds_bus, dds_address)
545
+ retval = dds.connect(url)
546
+ if retval is False:
547
+ logging.critical('DDS connection problem')
548
+ QMessageBox.critical(None, 'DDS connection error',
549
+ 'DDS connection problem\nCheck connection')
550
+ sys.exit()
551
+ dds.set_operation_mode(ad9915dev.OPMODE['modulus'])
552
+ ui = DdsCtrlUi("ad9915")
553
+ else:
554
+ logging.critical('DDS selection problem')
555
+ QMessageBox.critical(None, 'DDS selection error',
556
+ 'Problem with the DDS selection')
557
+ sys.exit()
558
+
559
+ if path.isfile(ini_file) is True:
560
+ check_ini_file(ini_file)
561
+ else:
562
+ check_ini_file()
563
+
564
+ init_dev(dds, dds_model)
565
+ init_ui(ui, dds_model)
566
+ handle_ocontroller_ui_behavior(ui, dds, dds_model)
567
+
568
+ # Logic
569
+ # # Following parameters, input freq, pll state, pll doubler state and
570
+ # # pll factor, modify system clock frequency which in turn modify
571
+ # # output frequency. Behavior of UI can be:
572
+ # # - update display of output frequency to new dds frequency
573
+ # # - update dds frequency to current display of output frequency
574
+ # # Choice was taken to used the last behavior.
575
+ dds.ifreq_updated.connect(lambda **kwargs: ofreq_changed(ui, dds))
576
+ if dds_model == "AD9912":
577
+ dds.pll_state_updated.connect(
578
+ lambda **kwargs: ofreq_changed(ui, dds))
579
+ dds.pll_doubler_updated.connect(
580
+ lambda **kwargs: ofreq_changed(ui, dds))
581
+ dds.pll_factor_updated.connect(
582
+ lambda **kwargs: ofreq_changed(ui, dds))
583
+
584
+ ui.ocontroller_ui.auto_update_ckbox.stateChanged.connect(
585
+ lambda: handle_ocontroller_ui_behavior(ui, dds, dds_model))
586
+ ui.ocontroller_ui.auto_update_ckbox.stateChanged.connect(
587
+ lambda: QSettings().setValue('dds_ctrl/auto_update',
588
+ ui.ocontroller_ui.auto_update_ckbox.isChecked()))
589
+
590
+ ui.icontroller_ui.apply_btn.clicked.connect(
591
+ lambda: apply_btn_ictrl_clicked(ui, dds, dds_model))
592
+
593
+ ui.debug_ui.get_reg_btn.clicked.connect(
594
+ lambda: get_reg_btn_clicked(ui, dds))
595
+ ui.debug_ui.set_reg_btn.clicked.connect(
596
+ lambda: set_reg_btn_clicked(ui, dds))
597
+
598
+ form = QMainWindow()
599
+ form.setCentralWidget(ui)
600
+ form.setWindowTitle(window_title)
601
+ form.show()
602
+
603
+ sys.exit(app.exec_())
604
+
605
+
606
+ #==============================================================================
607
+ if __name__ == '__main__':
608
+ # Setup logger
609
+ configure_logging()
610
+ # Parse command line
611
+ args = parse_cmd_line()
612
+ # Setup application
613
+ ddscontroller(args.ini_file, args.wtitle,
614
+ args.model, args.bus, args.address)