|
@@ -0,0 +1,606 @@
|
|
1
|
+@** Support for Devices Using the DATAQ SDK.
|
|
2
|
+
|
|
3
|
+\noindent Support for hardware from DATAQ Instruments is currently provided
|
|
4
|
+through the DATAQ SDK. This means that this support is currently only available
|
|
5
|
+on Microsoft Windows. The first planned supported device is the DI-145 with
|
|
6
|
+support for the DI-148U planned later. The devices are sufficiently similar
|
|
7
|
+that adding support for other devices from this manufacturer should be easy,
|
|
8
|
+but I do not have hardware samples to use for testing with other devices. The
|
|
9
|
+DI-145 additionally has a documented serial protocol which should make the
|
|
10
|
+hardware usable without the DATAQ SDK both on Microsoft Windows and on other
|
|
11
|
+platforms for which there is a suitable serial driver.
|
|
12
|
+
|
|
13
|
+@<Class declarations@>=
|
|
14
|
+#ifdef Q_OS_WIN32
|
|
15
|
+class DataqSdkDeviceImplementation;
|
|
16
|
+class DataqSdkDevice : public QObject
|
|
17
|
+{
|
|
18
|
+ Q_OBJECT
|
|
19
|
+ DataqSdkDeviceImplementation *imp;
|
|
20
|
+ private slots:
|
|
21
|
+ void threadFinished();
|
|
22
|
+ public:
|
|
23
|
+ DataqSdkDevice(QString device);
|
|
24
|
+ ~DataqSdkDevice();
|
|
25
|
+ Channel* newChannel(Units::Unit scale);
|
|
26
|
+ Q_INVOKABLE void setClockRate(double Hz);
|
|
27
|
+ Q_INVOKABLE void start();
|
|
28
|
+};
|
|
29
|
+#endif
|
|
30
|
+
|
|
31
|
+@ The |DataqSdkDevice| class has as a private member an instance of a class
|
|
32
|
+called |DataqSdkDeviceImplementation|. The two classes together create and run
|
|
33
|
+a new thread of execution. This thread spends most of its time blocking while
|
|
34
|
+waiting for a new measurement to become available. When a new measurement is
|
|
35
|
+available, that measurement is passed to the appropriate channel which in turn
|
|
36
|
+passes it to any interested object.
|
|
37
|
+
|
|
38
|
+Note that subclassing |QThread| in this way is no longer considered best
|
|
39
|
+practice. This particular code architecture is based on code written when this
|
|
40
|
+was considered the right thing to do, but it would be good to rewrite this to
|
|
41
|
+not subclass |QThread| now that this is no longer required.
|
|
42
|
+
|
|
43
|
+@<Class declarations@>=
|
|
44
|
+#ifdef Q_OS_WIN32
|
|
45
|
+class DataqSdkDeviceImplementation : public QThread
|
|
46
|
+{
|
|
47
|
+ Q_OBJECT
|
|
48
|
+ public:
|
|
49
|
+ DataqSdkDeviceImplementation();
|
|
50
|
+ ~DataqSdkDeviceImplementation();
|
|
51
|
+ void run();
|
|
52
|
+ @<DATAQ SDK library function pointers@>@;
|
|
53
|
+ @<DataqSdkDeviceImplementation member data@>@;
|
|
54
|
+ public slots:
|
|
55
|
+ void measure();
|
|
56
|
+ private:
|
|
57
|
+ qint16 *buffer;
|
|
58
|
+};
|
|
59
|
+#endif
|
|
60
|
+
|
|
61
|
+@ While the |DAQ| class for communicating with National Instruments devices
|
|
62
|
+uses a single function pointer type, increased variety of function signatures
|
|
63
|
+in the DATAQ SDK makes using several types a better option. This also
|
|
64
|
+eliminates the need for explicit casts on the arguments.
|
|
65
|
+
|
|
66
|
+@<DATAQ SDK library function pointers@>=
|
|
67
|
+typedef struct di_inlist_struct {
|
|
68
|
+ unsigned short chan;
|
|
69
|
+ unsigned short diff;
|
|
70
|
+ unsigned short gain;
|
|
71
|
+ unsigned short unipolar;
|
|
72
|
+ unsigned short dig_out_enable;
|
|
73
|
+ unsigned short dig_out;
|
|
74
|
+ unsigned short ave;
|
|
75
|
+ unsigned short counter;
|
|
76
|
+} DI_INLIST_STRUCT;
|
|
77
|
+
|
|
78
|
+typedef int (PASCAL *FPDIOPEN)(unsigned);
|
|
79
|
+typedef int (PASCAL *FPDICLOSE)(void);
|
|
80
|
+typedef double (PASCAL *FPDISAMPLERATE)(double, long*, long*);
|
|
81
|
+typedef double (PASCAL *FPDIMAXIMUMRATE)(double);
|
|
82
|
+typedef int (PASCAL *FPDILISTLENGTH)(unsigned, unsigned);
|
|
83
|
+typedef int (PASCAL *FPDIINLIST)(di_inlist_struct*);
|
|
84
|
+typedef int* (PASCAL *FPDIBUFFERALLOC)(unsigned, unsigned);
|
|
85
|
+typedef int (PASCAL *FPDISTARTSCAN)(void);
|
|
86
|
+typedef unsigned (PASCAL *FPDISTATUSREAD)(short*, unsigned);
|
|
87
|
+typedef unsigned (PASCAL *FPDIBUFFERSTATUS)(unsigned);
|
|
88
|
+typedef int (PASCAL *FPDIBURSTRATE)(unsigned);
|
|
89
|
+typedef int (PASCAL *FPDISTOPSCAN)(void);
|
|
90
|
+
|
|
91
|
+FPDIOPEN di_open;
|
|
92
|
+FPDICLOSE di_close;
|
|
93
|
+FPDISAMPLERATE di_sample_rate;
|
|
94
|
+FPDIMAXIMUMRATE di_maximum_rate;
|
|
95
|
+FPDILISTLENGTH di_list_length;
|
|
96
|
+FPDIINLIST di_inlist;
|
|
97
|
+FPDIBUFFERALLOC di_buffer_alloc;
|
|
98
|
+FPDISTARTSCAN di_start_scan;
|
|
99
|
+FPDISTATUSREAD di_status_read;
|
|
100
|
+FPDIBUFFERSTATUS di_buffer_status;
|
|
101
|
+FPDIBURSTRATE di_burst_rate;
|
|
102
|
+FPDISTOPSCAN di_stop_scan;
|
|
103
|
+
|
|
104
|
+@ The |PASCAL| macro is defined in the {\tt windef.h} header file which will
|
|
105
|
+need to be included. This modifies the mechanics of the function call. A
|
|
106
|
+feature of the C language which C++ inherits is the ability to create variadic
|
|
107
|
+functions. To facilitate this, when one function calls another, the function
|
|
108
|
+making that call is responsible for cleaning up the stack. The function being
|
|
109
|
+called has no reliable way of knowing how many and what type of arguments have
|
|
110
|
+been passed if it is a variadic function, but this can be determined in the
|
|
111
|
+calling function at compile time. This is effectively a compiler implementation
|
|
112
|
+detail which is unimportant to the vast majority of application code. Use of
|
|
113
|
+the |PASCAL| macro informs the compiler that the function being called will
|
|
114
|
+clean up the stack itself. This precludes the use of variadic functions, but
|
|
115
|
+results in a smaller executable. The choice of name for that macro is
|
|
116
|
+unfortunate as arguments are placed on the stack in the order opposite of
|
|
117
|
+calling conventions of the Pascal programming language, but these are
|
|
118
|
+unimportant details so long as the resulting program works.
|
|
119
|
+
|
|
120
|
+@<Header files to include@>=
|
|
121
|
+#ifdef Q_OS_WIN32
|
|
122
|
+#include <windef.h>
|
|
123
|
+#endif
|
|
124
|
+
|
|
125
|
+@ |DataqSdkDeviceImplementation| maintains information about the device and the
|
|
126
|
+channels the measurements are sent to.
|
|
127
|
+
|
|
128
|
+@<DataqSdkDeviceImplementation member data@>=
|
|
129
|
+bool isOpen;
|
|
130
|
+double sampleRate;
|
|
131
|
+long oversample;
|
|
132
|
+long burstDivisor;
|
|
133
|
+QString device;
|
|
134
|
+unsigned deviceNumber;
|
|
135
|
+QVector<Channel*> channelMap;
|
|
136
|
+
|
|
137
|
+int error;
|
|
138
|
+int channels;
|
|
139
|
+bool ready;
|
|
140
|
+QLibrary *driver;
|
|
141
|
+QVector<Units::Unit> unitMap;
|
|
142
|
+int *input_buffer;
|
|
143
|
+QTimer *eventClock;
|
|
144
|
+QMultiMap<int, double> > smoother;
|
|
145
|
+
|
|
146
|
+@ Most of the interesting work associated with the |DataqSdkDevice| class is
|
|
147
|
+handled in the |measure()| method of |DataqSdkDeviceImplementation|. This
|
|
148
|
+method will block until a measurement is available. Once |buffer| is filled by
|
|
149
|
+|di_status_read()| that function returns and new |Measurement| objects are
|
|
150
|
+created based on the information in the buffer. These measurements are sent to
|
|
151
|
+|Channel| objects tracked by |channelMap|.
|
|
152
|
+
|
|
153
|
+The buffered values are presented in terms of ADC counts. Before using these
|
|
154
|
+values to convert to a voltage measurement, the two least significant binary
|
|
155
|
+digits of the count are set to 0 to improve measurement accuracy as recommended
|
|
156
|
+in the DATAQ SDK reference documentation.
|
|
157
|
+
|
|
158
|
+One of the use cases for this class is using the data port provided on some
|
|
159
|
+roasters from Diedrich Manufacturing. In this case there are three channels
|
|
160
|
+that are used: one provides a 0-10V signal that maps to temperature
|
|
161
|
+measurements of 32 to 1832 degrees F, one provides a signal in the same range
|
|
162
|
+requiring distinguishing among three values for air flow settings, and one is
|
|
163
|
+intended to show a percentage for the fuel setting. After experimenting with
|
|
164
|
+the most direct approach, there are limitations of the hardware that complicate
|
|
165
|
+matters for the channel representing bean temperature. The hardware is
|
|
166
|
+providing a 14 bit value representing a signal in the range of +/-10V so as a
|
|
167
|
+practical matter we only have 13 bits for temperature values. There is a desire
|
|
168
|
+to present measurements with at least one digit after the decimal point,
|
|
169
|
+meaning that we require 18,000 distinct values despite likely only ever seeing
|
|
170
|
+values in the lower third of that range. A 13 bit value only allows 8,192
|
|
171
|
+distinct values to be represented. The result of this is that stable signals
|
|
172
|
+between representable values are coded in an inconsistent fashion which can be
|
|
173
|
+seen as displayed measurements varying erratically. The usual solution to this
|
|
174
|
+problem is to collect many measurements quickly and average them, which is a
|
|
175
|
+reasonable thing to do with the sample rates available on DATAQ hardware.
|
|
176
|
+Examining measurements at a higher sample rate unfortunately reveals a periodic
|
|
177
|
+structure to the measurement error which averaging alone is not adequate to
|
|
178
|
+solve. The quality of the measurements can be improved somewhat by removing the
|
|
179
|
+extreme values from each set of measurements prior to averaging, however this
|
|
180
|
+does not fully address the lower frequency error sources. Further improvements
|
|
181
|
+can be made by maintaining a multimap of recent ADC count values to averaged
|
|
182
|
+voltage values and producing results that take this slightly longer term data
|
|
183
|
+into account. This is essential for obtaining a sufficiently stable low
|
|
184
|
+temperature calibration value and introduces minimal additional measurement
|
|
185
|
+latency during a roast.
|
|
186
|
+
|
|
187
|
+At present smoothing is applied to the first data channel and no others. It
|
|
188
|
+should be possible to enable or disable adaptive smoothing for all channels
|
|
189
|
+independently to better handle different hardware configurations.
|
|
190
|
+
|
|
191
|
+@<DataqSdkDevice implementation@>=
|
|
192
|
+void DataqSdkDeviceImplementation::measure()
|
|
193
|
+{
|
|
194
|
+ unsigned count = channels * 40;
|
|
195
|
+ di_status_read(buffer, count);
|
|
196
|
+ QTime time = QTime::currentTime();
|
|
197
|
+ for(unsigned int i = 0; i < count; i++)
|
|
198
|
+ {
|
|
199
|
+ buffer[i] = buffer[i] & 0xFFFC;
|
|
200
|
+ }
|
|
201
|
+ QList<int> countList;
|
|
202
|
+ for(unsigned int i = 0; i < (unsigned)channels; i++)
|
|
203
|
+ {
|
|
204
|
+ QList<double> channelBuffer;
|
|
205
|
+ for(unsigned int j = 0; j < 40; j++)
|
|
206
|
+ {
|
|
207
|
+ channelbuffer << ((double)buffer[i+(channels*j)] * 10.0) / 32768.0;
|
|
208
|
+ if(i == 0)
|
|
209
|
+ {
|
|
210
|
+ countList << buffer[i+(channels*j)];
|
|
211
|
+ }
|
|
212
|
+ }
|
|
213
|
+ double value = 0.0;
|
|
214
|
+ for(unsigned int j = 0; j < 40; j++)
|
|
215
|
+ {
|
|
216
|
+ value += channelbuffer[j];
|
|
217
|
+ }
|
|
218
|
+ value /= 40.0;
|
|
219
|
+ if(i == 0)
|
|
220
|
+ {
|
|
221
|
+ QList<double> smoothingList;
|
|
222
|
+ smoothingList << value;
|
|
223
|
+ QList<int> smoothingKeys = smoother.uniqueKeys();
|
|
224
|
+ for(int j = 0; j < smoothingKeys.size(); j++)
|
|
225
|
+ {
|
|
226
|
+ if(countList.contains(smoothingKeys[j]))
|
|
227
|
+ {
|
|
228
|
+ QList<double> keyValues = smoother.values(smoothingKeys[j]);
|
|
229
|
+ for(int k = 0; k < keyValues.size(); k++)
|
|
230
|
+ {
|
|
231
|
+ smoothingList << keyValues[k];
|
|
232
|
+ }
|
|
233
|
+ }
|
|
234
|
+ else
|
|
235
|
+ {
|
|
236
|
+ smoother.remove(smoothingKeys[j]);
|
|
237
|
+ }
|
|
238
|
+ }
|
|
239
|
+ qSort(countList);
|
|
240
|
+ int lastCount = 0;
|
|
241
|
+ for(int j = 0; j < countList.size(); j++)
|
|
242
|
+ {
|
|
243
|
+ if(j == 0 || countList[j] != lastCount)
|
|
244
|
+ {
|
|
245
|
+ smoother.insert(countList[j], value);
|
|
246
|
+ lastCount = countList[j];
|
|
247
|
+ }
|
|
248
|
+ }
|
|
249
|
+ value = 0.0;
|
|
250
|
+ for(int j = 0; j < smoothingList.size(); j++)
|
|
251
|
+ {
|
|
252
|
+ value += smoothingList[j];
|
|
253
|
+ }
|
|
254
|
+ value /= smoothingList.size();
|
|
255
|
+ }
|
|
256
|
+ Measurement measure(value, time, unitMap[i]);
|
|
257
|
+ channelMap[i]->input(measure);
|
|
258
|
+ }
|
|
259
|
+}
|
|
260
|
+
|
|
261
|
+@ It was noted that |di_status_read()| blocks until it is able to fill the
|
|
262
|
+|buffer| passed to it. To prevent this behavior from having adverse effects on
|
|
263
|
+the rest of the program, |measure()| is called from a loop running in its own
|
|
264
|
+thread of execution. When the thread is started, it begins its execution from
|
|
265
|
+the |run()| method of |DataqSdkDeviceImplementation| which overrides the
|
|
266
|
+|run()| method of |QThread|.
|
|
267
|
+
|
|
268
|
+The while loop is controlled by |ready| which is set to |false| when there is
|
|
269
|
+an error in collecting a measurement or when there is a desire to stop logging.
|
|
270
|
+It could also be set to |false| for reconfiguration events.
|
|
271
|
+
|
|
272
|
+All device initialization happens in this method.
|
|
273
|
+
|
|
274
|
+Note that while the equivalent method when communicating with National
|
|
275
|
+Instruments hardware sets a time critical thread priority in an attempt to cut
|
|
276
|
+down on the variation in time between recorded measurements, that is a really
|
|
277
|
+bad idea when using the DATAQ SDK. The result was that the main thread never
|
|
278
|
+got enough time to report measurements and responsiveness throughout the entire
|
|
279
|
+system became barely usable to the point that it was difficult to kill the
|
|
280
|
+process. If anybody reading this can provide some insight into why setting the
|
|
281
|
+thread priority is fine with interacting with either DAQmx or DAQmxBase but not
|
|
282
|
+when interacting with the DATAQ SDK, I would like to read such an explanation.
|
|
283
|
+
|
|
284
|
+@<DataqSdkDevice implementation@>=
|
|
285
|
+void DataqSdkDeviceImplementation::run()
|
|
286
|
+{
|
|
287
|
+ if(!ready)
|
|
288
|
+ {
|
|
289
|
+ error = 9; // Device data not available
|
|
290
|
+ return;
|
|
291
|
+ }
|
|
292
|
+ driver = new QLibrary(device);
|
|
293
|
+ if(!driver->load())
|
|
294
|
+ {
|
|
295
|
+ error = 1; // Failed to load driver.
|
|
296
|
+ return;
|
|
297
|
+ }
|
|
298
|
+ di_open = (FPDIOPEN)driver->resolve("di_open");
|
|
299
|
+ di_close = (FPDICLOSE)driver->resolve("di_close");
|
|
300
|
+ di_sample_rate = (FPDISAMPLERATE)driver->resolve("di_sample_rate");
|
|
301
|
+ di_maximum_rate = (FPDIMAXIMUMRATE)driver->resolve("di_maximum_rate");
|
|
302
|
+ di_list_length = (FPDILISTLENGTH)driver->resolve("di_list_length");
|
|
303
|
+ di_inlist = (FPDIINLIST)driver->resolve("di_inlist");
|
|
304
|
+ di_buffer_alloc = (FPDIBUFFERALLOC)driver->resolve("di_buffer_alloc");
|
|
305
|
+ di_start_scan = (FPDISTARTSCAN)driver->resolve("di_start_scan");
|
|
306
|
+ di_status_read = (FPDISTATUSREAD)driver->resolve("di_status_read");
|
|
307
|
+ di_buffer_status = (FPDIBUFFERSTATUS)driver->resolve("di_buffer_status");
|
|
308
|
+ di_burst_rate = (FPDIBURSTRATE)driver->resolve("di_burst_rate");
|
|
309
|
+ di_stop_scan = (FPDISTOPSCAN)driver->resolve("di_stop_scan");
|
|
310
|
+ if((!di_open) || (!di_close) || (!di_sample_rate) || (!di_maximum_rate) ||
|
|
311
|
+ (!di_list_length) || (!di_inlist) || (!di_buffer_alloc) ||
|
|
312
|
+ (!di_start_scan) || (!di_status_read) || (!di_buffer_status) ||
|
|
313
|
+ (!di_burst_rate) || (!di_stop_scan))
|
|
314
|
+ {
|
|
315
|
+ error = 2; // Failed to link required symbol
|
|
316
|
+ return;
|
|
317
|
+ }
|
|
318
|
+ error = di_open(deviceNumber);
|
|
319
|
+ if(error)
|
|
320
|
+ {
|
|
321
|
+ di_close();
|
|
322
|
+ error = di_open(deviceNumber);
|
|
323
|
+ if(error)
|
|
324
|
+ {
|
|
325
|
+ error = 3; // Failed to open device
|
|
326
|
+ di_close();
|
|
327
|
+ return;
|
|
328
|
+ }
|
|
329
|
+ }
|
|
330
|
+ isOpen = true;
|
|
331
|
+ di_maximum_rate(240.0);
|
|
332
|
+ sampleRate = di_sample_rate(sampleRate * channels * 40, &oversample,
|
|
333
|
+ &burstDivisor);
|
|
334
|
+ buffer = new qint16[(int)sampleRate];
|
|
335
|
+ di_inlist_struct inlist[16] = {{0}};
|
|
336
|
+ for(unsigned short i = 0; i < channels; i++)
|
|
337
|
+ {
|
|
338
|
+ inlist[i].chan = i;
|
|
339
|
+ inlist[i].gain = 0;
|
|
340
|
+ inlist[i].ave = 1;
|
|
341
|
+ inlist[i].counter = (oversample - 1);
|
|
342
|
+ }
|
|
343
|
+ error = di_list_length(channels, 0)
|
|
344
|
+ if(error)
|
|
345
|
+ {
|
|
346
|
+ error = 4; // List length error
|
|
347
|
+ return;
|
|
348
|
+ }
|
|
349
|
+ error = di_inlist(inlist);
|
|
350
|
+ if(error)
|
|
351
|
+ {
|
|
352
|
+ error = 5; // Inlist error
|
|
353
|
+ return;
|
|
354
|
+ }
|
|
355
|
+ input_buffer = di_buffer_alloc(0, 4096);
|
|
356
|
+ if(input_buffer == NULL)
|
|
357
|
+ {
|
|
358
|
+ error = 6; // Failed to allocate buffer
|
|
359
|
+ return;
|
|
360
|
+ }
|
|
361
|
+ error = di_start_scan();
|
|
362
|
+ if(error)
|
|
363
|
+ {
|
|
364
|
+ error = 7; // Failed to start scanning
|
|
365
|
+ return;
|
|
366
|
+ }
|
|
367
|
+ while(ready)
|
|
368
|
+ {
|
|
369
|
+ measure();
|
|
370
|
+ }
|
|
371
|
+}
|
|
372
|
+
|
|
373
|
+@ When the loop exits, |DataqSdkDeviceImplementation| emits a finished signal
|
|
374
|
+to indicate that the thread is no longer running. This could be due to normal
|
|
375
|
+conditions or there could be a problem that should be reported. That signal is
|
|
376
|
+connected to a function that checks for error conditions and reports them if
|
|
377
|
+needed.
|
|
378
|
+
|
|
379
|
+@<DataqSdkDevice implementation@>=
|
|
380
|
+void DataqSdkDevice::threadFinished()
|
|
381
|
+{
|
|
382
|
+ if(imp->error)
|
|
383
|
+ {
|
|
384
|
+ @<Display DATAQ SDK Error@>@;
|
|
385
|
+ }
|
|
386
|
+}
|
|
387
|
+
|
|
388
|
+@ The DATAQ SDK does not have a single method for reporting errors. Instead,
|
|
389
|
+any method that can return an error code has its return value checked and
|
|
390
|
+|error| is set to a value that allows the source of the problem to be
|
|
391
|
+determined. At present, error handling is very poor.
|
|
392
|
+
|
|
393
|
+@<Display DATAQ SDK Error@>=
|
|
394
|
+imp->ready = false;
|
|
395
|
+QMessageBox warning;
|
|
396
|
+warning.setStandardButtons(QMessageBox::Cancel);
|
|
397
|
+warning.setIcon(QMessageBox::Warning);
|
|
398
|
+warning.setText(QString(tr("Error: %1")).arg(imp->error));
|
|
399
|
+warning.setInformativeText(tr("An error occurred"));
|
|
400
|
+warning.setWindowTitle(QString(PROGRAM_NAME));
|
|
401
|
+warning.exec();
|
|
402
|
+
|
|
403
|
+@ Starting the thread is very simple. Device initialization happens in the new
|
|
404
|
+thread which then begins taking measurements. The call to |imp->start()| starts
|
|
405
|
+the new thread and passes control of that thread to |imp->run()|. The main
|
|
406
|
+thread of execution returns without waiting for the new thread to do anything.
|
|
407
|
+When the thread is finished, the |finished()| signal is emitted which we have
|
|
408
|
+connected to |threadFinished()|.
|
|
409
|
+
|
|
410
|
+@<DataqSdkDevice implementation@>=
|
|
411
|
+void DataqSdkDevice::start()
|
|
412
|
+{
|
|
413
|
+ connect(imp, SIGNAL(finished()), this, SLOT(threadFinished()));
|
|
414
|
+ imp->start();
|
|
415
|
+}
|
|
416
|
+
|
|
417
|
+@ Setting up the device begins by constructing a new |DataqSdkDevice| object.
|
|
418
|
+The constructor takes as its argument a string which identifies the device. For
|
|
419
|
+legacy reasons this currently accepts device names such as |"Dev1"| and looks
|
|
420
|
+up currently connected devices to determine which serial port should be used.
|
|
421
|
+Now that it is preferred to configure devices graphically this is not a good
|
|
422
|
+way to do this. This should be changed before release.
|
|
423
|
+
|
|
424
|
+@<DataqSdkDevice implementation@>=
|
|
425
|
+DataqSdkDevice::DataqSdkDevice(QString device) : imp(new DataqSdkDeviceImplementation)
|
|
426
|
+{
|
|
427
|
+ QSettings deviceLookup("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\usbser\\Enum",
|
|
428
|
+ QSettings::NativeFormat);
|
|
429
|
+ QStringList keys = deviceLookup.childKeys();
|
|
430
|
+ QStringList devices;
|
|
431
|
+ for(int i = 0; i < keys.size(); i++)
|
|
432
|
+ {
|
|
433
|
+ QString value = deviceLookup.value(keys.at(i)).toString();
|
|
434
|
+ if(value.startsWith("USB\\VID_0683&PID_1450\\"))
|
|
435
|
+ {
|
|
436
|
+ devices.append(value.split("\\").at(2));
|
|
437
|
+ }
|
|
438
|
+ }
|
|
439
|
+ device = device.remove(0, 3);
|
|
440
|
+ int index = device.toInt() - 1;
|
|
441
|
+ if(index >= 0 && index < devices.size())
|
|
442
|
+ {
|
|
443
|
+ QString deviceKey = QString(
|
|
444
|
+ "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\USB\\VID_0683&PID_1450\\%1").
|
|
445
|
+ arg(devices.at(index));
|
|
446
|
+ QSettings deviceEntry(deviceKey, QSettings::NativeFormat);
|
|
447
|
+ QString portString = deviceEntry.value("FriendlyName").toString();
|
|
448
|
+ int rstart = portString.indexOf("COM");
|
|
449
|
+ portString.remove(0, rstart + 3);
|
|
450
|
+ portString.chop(1);
|
|
451
|
+ if(portString.toInt() < 10)
|
|
452
|
+ {
|
|
453
|
+ imp->device = QString("DI10%1INT.DLL").arg(portString);
|
|
454
|
+ }
|
|
455
|
+ else
|
|
456
|
+ {
|
|
457
|
+ imp->device = QString("DI1%1INT.DLL").arg(portString);
|
|
458
|
+ }
|
|
459
|
+ imp->deviceNumber = 0x12C02D00;
|
|
460
|
+ imp->deviceNumber += portString.toInt();
|
|
461
|
+ imp->ready = true;
|
|
462
|
+ }
|
|
463
|
+ else
|
|
464
|
+ {
|
|
465
|
+ imp->error = 8; // Failed to find device.
|
|
466
|
+ }
|
|
467
|
+}
|
|
468
|
+
|
|
469
|
+@ Once the |DataqSdkDevice| is created, one or more channels can be added.
|
|
470
|
+
|
|
471
|
+@<DataqSdkDevice implementation@>=
|
|
472
|
+Channel* DataqSdkDevice::newChannel(Units::Unit scale)
|
|
473
|
+{
|
|
474
|
+ Channel *retval = NULL;
|
|
475
|
+ if(imp->ready)
|
|
476
|
+ {
|
|
477
|
+ retval = new Channel();
|
|
478
|
+ imp->channelMap[imp->channels] = retval;
|
|
479
|
+ imp->unitMap[imp->channels] = scale;
|
|
480
|
+ imp->channels++;
|
|
481
|
+ }
|
|
482
|
+ return retval;
|
|
483
|
+}
|
|
484
|
+
|
|
485
|
+@ Once the channels are created, it is necessary to set the clock rate of the
|
|
486
|
+device. The DATAQ SDK will set the clock rate to be whichever value is closest
|
|
487
|
+to the specified value that is supported by the hardware. Note that when
|
|
488
|
+measuring multiple channels the device clock rate should be the desired sample
|
|
489
|
+rate per channel multiplied by the number of channels.
|
|
490
|
+
|
|
491
|
+The amount of time between measurements may vary slightly. Tests have shown
|
|
492
|
+that while most measurements come within 1ms of the expected time, some
|
|
493
|
+measurements do not come in within 100ms of the expected time.
|
|
494
|
+
|
|
495
|
+@<DataqSdkDevice implementation@>=
|
|
496
|
+void DataqSdkDevice::setClockRate(double Hz)
|
|
497
|
+{
|
|
498
|
+ imp->sampleRate = Hz;
|
|
499
|
+}
|
|
500
|
+
|
|
501
|
+@ The destructor instructs the measurement thread to stop, waits for it to
|
|
502
|
+finish, and resets the device. If this is not done, an error would be issued
|
|
503
|
+the next time a program attempted to use the device.
|
|
504
|
+
|
|
505
|
+@<DataqSdkDevice implementation@>=
|
|
506
|
+DataqSdkDevice::~DataqSdkDevice()
|
|
507
|
+{
|
|
508
|
+ if(imp->ready)
|
|
509
|
+ {
|
|
510
|
+ imp->ready = false;
|
|
511
|
+ }
|
|
512
|
+ imp->wait(ULONG_MAX);
|
|
513
|
+ delete imp;
|
|
514
|
+}
|
|
515
|
+
|
|
516
|
+@ The constructor and destructor in |DataqSdkDeviceImplementation| currently
|
|
517
|
+limit the number of channels to 4. As additional devices are supported this
|
|
518
|
+restriction should be lifted.
|
|
519
|
+
|
|
520
|
+Very little is needed from the constructor. The destructor is responsible for
|
|
521
|
+closing the device and unloading the device driver.
|
|
522
|
+
|
|
523
|
+@<DataqSdkDevice implementation@>=
|
|
524
|
+DataqSdkDeviceImplementation::DataqSdkDeviceImplementation() : QThread(NULL),
|
|
525
|
+ channelMap(4), error(0), channels(0), ready(false), unitMap(4)
|
|
526
|
+{
|
|
527
|
+ /* Nothing needs to be done here. */
|
|
528
|
+}
|
|
529
|
+
|
|
530
|
+DataqSdkDeviceImplementation::~DataqSdkDeviceImplementation()
|
|
531
|
+{
|
|
532
|
+ if(isOpen)
|
|
533
|
+ {
|
|
534
|
+ di_stop_scan();
|
|
535
|
+ di_close();
|
|
536
|
+ }
|
|
537
|
+ if(driver->isLoaded())
|
|
538
|
+ {
|
|
539
|
+ driver->unload();
|
|
540
|
+ }
|
|
541
|
+}
|
|
542
|
+
|
|
543
|
+@ This is exposed to the scripting engine in the usual way.
|
|
544
|
+
|
|
545
|
+@<Function prototypes for scripting@>=
|
|
546
|
+QScriptValue constructDataqSdkDevice(QScriptContext *context, QScriptEngine *engine);
|
|
547
|
+QScriptValue DataqSdkDevice_newChannel(QScriptContext *context, QScriptEngine *engine);
|
|
548
|
+void setDataqSdkDeviceProperties(QScriptValue value, QScriptEngine *engine);
|
|
549
|
+
|
|
550
|
+@ These functions are made known to the scripting engine.
|
|
551
|
+
|
|
552
|
+@<Set up the scripting engine@>=
|
|
553
|
+constructor = engine->newFunction(constructDataqSdkDevice);
|
|
554
|
+value = engine->newQMetaObject(&DataqSdkDevice::staticMetaObject, constructor);
|
|
555
|
+engine->globalObject().setProperty("DataqSdkDevice", value);
|
|
556
|
+
|
|
557
|
+@ When creating a new device we make sure that it is owned by the script
|
|
558
|
+engine. This is necessary to ensure that the destructor is called before \pn{}
|
|
559
|
+exits. Just as the constructor requires an argument that specifies the device
|
|
560
|
+name, the constructor available from a script also requires this argument.
|
|
561
|
+
|
|
562
|
+@<Functions for scripting@>=
|
|
563
|
+QScriptValue constructDataqSdkDevice(QScriptContext *context, QScriptEngine *engine)
|
|
564
|
+{
|
|
565
|
+ QScriptValue object;
|
|
566
|
+ if(context->argumentCount() == 1)
|
|
567
|
+ {
|
|
568
|
+ object = engine->newQObject(new DataqSdkDevice(argument<QString>(0, context)),
|
|
569
|
+ QScriptEngine::ScriptOwnership);
|
|
570
|
+ setDataqSdkDeviceProperties(object, engine);
|
|
571
|
+ }
|
|
572
|
+ else
|
|
573
|
+ {
|
|
574
|
+ context->throwError("Incorrect number of arguments passed to "
|
|
575
|
+ "DataqSdkDevice. The constructor takes one string "
|
|
576
|
+ "as an argument specifying a device name. "
|
|
577
|
+ "Example: Dev1");
|
|
578
|
+ }
|
|
579
|
+ return object;
|
|
580
|
+}
|
|
581
|
+
|
|
582
|
+@ As |DataqSdkDevice| inherits |QObject| we add the |newChannel()| property
|
|
583
|
+after adding any |QObject| properties.
|
|
584
|
+
|
|
585
|
+@<Functions for scripting@>=
|
|
586
|
+void setDataqSdkDeviceProperties(QScriptValue value, QScriptEngine *engine)
|
|
587
|
+{
|
|
588
|
+ setQObjectProperties(value, engine);
|
|
589
|
+ value.setProperty("newChannel", engine->newFunction(DataqSdkDevice_newChannel));
|
|
590
|
+}
|
|
591
|
+
|
|
592
|
+@ The |newChannel()| wrapper requires two arguments.
|
|
593
|
+
|
|
594
|
+@<Functions for scripting@>=
|
|
595
|
+QScriptValue DataqSdkDevice_newChannel(QScriptContext *context, QScriptEngine *engine)
|
|
596
|
+{
|
|
597
|
+ DataqSdkDevice *self = getself<DataqSdkDevice *>(context);
|
|
598
|
+ QScriptValue object;
|
|
599
|
+ if(self)
|
|
600
|
+ {
|
|
601
|
+ object = engine->newQObject(self->newChannel(argument<int>(0, context),
|
|
602
|
+ argument<int>(1, context)));
|
|
603
|
+ setChannelProperties(object, engine);
|
|
604
|
+ }
|
|
605
|
+ return object;
|
|
606
|
+}
|