2 comedi/drivers/comedi_test.c
4 Generates fake waveform signals that can be read through
5 the command interface. It does _not_ read from any board;
6 it just generates deterministic waveforms.
7 Useful for various testing purposes.
9 Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>
10 Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
12 COMEDI - Linux Control and Measurement Device Interface
13 Copyright (C) 2000 David A. Schleef <ds@schleef.org>
15 This program is free software; you can redistribute it and/or modify
16 it under the terms of the GNU General Public License as published by
17 the Free Software Foundation; either version 2 of the License, or
18 (at your option) any later version.
20 This program is distributed in the hope that it will be useful,
21 but WITHOUT ANY WARRANTY; without even the implied warranty of
22 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 GNU General Public License for more details.
25 You should have received a copy of the GNU General Public License
26 along with this program; if not, write to the Free Software
27 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
29 ************************************************************************/
32 Description: generates fake waveforms
33 Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
34 <fmhess@users.sourceforge.net>, ds
37 Updated: Sat, 16 Mar 2002 17:34:48 -0800
39 This driver is mainly for testing purposes, but can also be used to
40 generate sample waveforms on systems that don't have data acquisition
43 Configuration options:
44 [0] - Amplitude in microvolts for fake waveforms (default 1 volt)
45 [1] - Period in microseconds for fake waveforms (default 0.1 sec)
47 Generates a sawtooth wave on channel 0, square wave on channel 1, additional
48 waveforms could be added to other channels (currently they return flatline
53 #include "../comedidev.h"
55 #include <asm/div64.h>
57 #include "comedi_fc.h"
59 /* Board descriptions */
60 struct waveform_board
{
69 static const struct waveform_board waveform_boards
[] = {
71 .name
= "comedi_test",
78 #define thisboard ((const struct waveform_board *)dev->board_ptr)
80 /* Data unique to this driver */
81 struct waveform_private
{
82 struct timer_list timer
;
83 struct timeval last
; /* time at which last timer interrupt occured */
84 unsigned int uvolt_amplitude
; /* waveform amplitude in microvolts */
85 unsigned long usec_period
; /* waveform period in microseconds */
86 unsigned long usec_current
; /* current time (modulo waveform period) */
87 unsigned long usec_remainder
; /* usec since last scan; */
88 unsigned long ai_count
; /* number of conversions remaining */
89 unsigned int scan_period
; /* scan period in usec */
90 unsigned int convert_period
; /* conversion period in usec */
91 unsigned timer_running
:1;
92 lsampl_t ao_loopbacks
[N_CHANS
];
94 #define devpriv ((struct waveform_private *)dev->private)
96 static int waveform_attach(comedi_device
*dev
, comedi_devconfig
*it
);
97 static int waveform_detach(comedi_device
*dev
);
98 static comedi_driver driver_waveform
= {
99 .driver_name
= "comedi_test",
100 .module
= THIS_MODULE
,
101 .attach
= waveform_attach
,
102 .detach
= waveform_detach
,
103 .board_name
= &waveform_boards
[0].name
,
104 .offset
= sizeof(struct waveform_board
),
105 .num_names
= sizeof(waveform_boards
) / sizeof(struct waveform_board
),
108 COMEDI_INITCLEANUP(driver_waveform
);
110 static int waveform_ai_cmdtest(comedi_device
*dev
, comedi_subdevice
*s
,
112 static int waveform_ai_cmd(comedi_device
*dev
, comedi_subdevice
*s
);
113 static int waveform_ai_cancel(comedi_device
*dev
, comedi_subdevice
*s
);
114 static int waveform_ai_insn_read(comedi_device
*dev
, comedi_subdevice
*s
,
115 comedi_insn
*insn
, lsampl_t
*data
);
116 static int waveform_ao_insn_write(comedi_device
*dev
, comedi_subdevice
*s
,
117 comedi_insn
*insn
, lsampl_t
*data
);
118 static sampl_t
fake_sawtooth(comedi_device
*dev
, unsigned int range
,
119 unsigned long current_time
);
120 static sampl_t
fake_squarewave(comedi_device
*dev
, unsigned int range
,
121 unsigned long current_time
);
122 static sampl_t
fake_flatline(comedi_device
*dev
, unsigned int range
,
123 unsigned long current_time
);
124 static sampl_t
fake_waveform(comedi_device
*dev
, unsigned int channel
,
125 unsigned int range
, unsigned long current_time
);
127 /* 1000 nanosec in a microsec */
128 static const int nano_per_micro
= 1000;
130 /* fake analog input ranges */
131 static const comedi_lrange waveform_ai_ranges
= {
140 This is the background routine used to generate arbitrary data.
141 It should run in the background; therefore it is scheduled by
144 static void waveform_ai_interrupt(unsigned long arg
)
146 comedi_device
*dev
= (comedi_device
*) arg
;
147 comedi_async
*async
= dev
->read_subdev
->async
;
148 comedi_cmd
*cmd
= &async
->cmd
;
150 /* all times in microsec */
151 unsigned long elapsed_time
;
152 unsigned int num_scans
;
155 do_gettimeofday(&now
);
158 1000000 * (now
.tv_sec
- devpriv
->last
.tv_sec
) + now
.tv_usec
-
159 devpriv
->last
.tv_usec
;
162 (devpriv
->usec_remainder
+ elapsed_time
) / devpriv
->scan_period
;
163 devpriv
->usec_remainder
=
164 (devpriv
->usec_remainder
+ elapsed_time
) % devpriv
->scan_period
;
167 for (i
= 0; i
< num_scans
; i
++) {
168 for (j
= 0; j
< cmd
->chanlist_len
; j
++) {
169 cfc_write_to_buffer(dev
->read_subdev
,
170 fake_waveform(dev
, CR_CHAN(cmd
->chanlist
[j
]),
171 CR_RANGE(cmd
->chanlist
[j
]),
172 devpriv
->usec_current
+
173 i
* devpriv
->scan_period
+
174 j
* devpriv
->convert_period
));
177 if (cmd
->stop_src
== TRIG_COUNT
178 && devpriv
->ai_count
>= cmd
->stop_arg
) {
179 async
->events
|= COMEDI_CB_EOA
;
184 devpriv
->usec_current
+= elapsed_time
;
185 devpriv
->usec_current
%= devpriv
->usec_period
;
187 if ((async
->events
& COMEDI_CB_EOA
) == 0 && devpriv
->timer_running
)
188 mod_timer(&devpriv
->timer
, jiffies
+ 1);
190 del_timer(&devpriv
->timer
);
192 comedi_event(dev
, dev
->read_subdev
);
195 static int waveform_attach(comedi_device
*dev
, comedi_devconfig
*it
)
198 int amplitude
= it
->options
[0];
199 int period
= it
->options
[1];
202 dev
->board_name
= thisboard
->name
;
204 if (alloc_private(dev
, sizeof(struct waveform_private
)) < 0)
207 /* set default amplitude and period */
209 amplitude
= 1000000; /* 1 volt */
211 period
= 100000; /* 0.1 sec */
213 devpriv
->uvolt_amplitude
= amplitude
;
214 devpriv
->usec_period
= period
;
216 dev
->n_subdevices
= 2;
217 if (alloc_subdevices(dev
, dev
->n_subdevices
) < 0)
220 s
= dev
->subdevices
+ 0;
221 dev
->read_subdev
= s
;
222 /* analog input subdevice */
223 s
->type
= COMEDI_SUBD_AI
;
224 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
| SDF_CMD_READ
;
225 s
->n_chan
= thisboard
->ai_chans
;
226 s
->maxdata
= (1 << thisboard
->ai_bits
) - 1;
227 s
->range_table
= &waveform_ai_ranges
;
228 s
->len_chanlist
= s
->n_chan
* 2;
229 s
->insn_read
= waveform_ai_insn_read
;
230 s
->do_cmd
= waveform_ai_cmd
;
231 s
->do_cmdtest
= waveform_ai_cmdtest
;
232 s
->cancel
= waveform_ai_cancel
;
234 s
= dev
->subdevices
+ 1;
235 dev
->write_subdev
= s
;
236 /* analog output subdevice (loopback) */
237 s
->type
= COMEDI_SUBD_AO
;
238 s
->subdev_flags
= SDF_WRITEABLE
| SDF_GROUND
;
239 s
->n_chan
= thisboard
->ai_chans
;
240 s
->maxdata
= (1 << thisboard
->ai_bits
) - 1;
241 s
->range_table
= &waveform_ai_ranges
;
242 s
->len_chanlist
= s
->n_chan
* 2;
243 s
->insn_write
= waveform_ao_insn_write
;
245 s
->do_cmdtest
= NULL
;
248 /* Our default loopback value is just a 0V flatline */
249 for (i
= 0; i
< s
->n_chan
; i
++)
250 devpriv
->ao_loopbacks
[i
] = s
->maxdata
/ 2;
252 init_timer(&(devpriv
->timer
));
253 devpriv
->timer
.function
= waveform_ai_interrupt
;
254 devpriv
->timer
.data
= (unsigned long)dev
;
256 printk(KERN_INFO
"comedi%d: comedi_test: "
257 "%i microvolt, %li microsecond waveform attached\n", dev
->minor
,
258 devpriv
->uvolt_amplitude
, devpriv
->usec_period
);
262 static int waveform_detach(comedi_device
*dev
)
264 printk("comedi%d: comedi_test: remove\n", dev
->minor
);
267 waveform_ai_cancel(dev
, dev
->read_subdev
);
272 static int waveform_ai_cmdtest(comedi_device
*dev
, comedi_subdevice
*s
,
278 /* step 1: make sure trigger sources are trivially valid */
280 tmp
= cmd
->start_src
;
281 cmd
->start_src
&= TRIG_NOW
;
282 if (!cmd
->start_src
|| tmp
!= cmd
->start_src
)
285 tmp
= cmd
->scan_begin_src
;
286 cmd
->scan_begin_src
&= TRIG_TIMER
;
287 if (!cmd
->scan_begin_src
|| tmp
!= cmd
->scan_begin_src
)
290 tmp
= cmd
->convert_src
;
291 cmd
->convert_src
&= TRIG_NOW
| TRIG_TIMER
;
292 if (!cmd
->convert_src
|| tmp
!= cmd
->convert_src
)
295 tmp
= cmd
->scan_end_src
;
296 cmd
->scan_end_src
&= TRIG_COUNT
;
297 if (!cmd
->scan_end_src
|| tmp
!= cmd
->scan_end_src
)
301 cmd
->stop_src
&= TRIG_COUNT
| TRIG_NONE
;
302 if (!cmd
->stop_src
|| tmp
!= cmd
->stop_src
)
309 * step 2: make sure trigger sources are unique and mutually compatible
312 if (cmd
->convert_src
!= TRIG_NOW
&& cmd
->convert_src
!= TRIG_TIMER
)
314 if (cmd
->stop_src
!= TRIG_COUNT
&& cmd
->stop_src
!= TRIG_NONE
)
320 /* step 3: make sure arguments are trivially compatible */
322 if (cmd
->start_arg
!= 0) {
326 if (cmd
->convert_src
== TRIG_NOW
) {
327 if (cmd
->convert_arg
!= 0) {
328 cmd
->convert_arg
= 0;
332 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
333 if (cmd
->scan_begin_arg
< nano_per_micro
) {
334 cmd
->scan_begin_arg
= nano_per_micro
;
337 if (cmd
->convert_src
== TRIG_TIMER
&&
338 cmd
->scan_begin_arg
<
339 cmd
->convert_arg
* cmd
->chanlist_len
) {
340 cmd
->scan_begin_arg
=
341 cmd
->convert_arg
* cmd
->chanlist_len
;
346 * XXX these checks are generic and should go in core if not there
349 if (!cmd
->chanlist_len
) {
350 cmd
->chanlist_len
= 1;
353 if (cmd
->scan_end_arg
!= cmd
->chanlist_len
) {
354 cmd
->scan_end_arg
= cmd
->chanlist_len
;
358 if (cmd
->stop_src
== TRIG_COUNT
) {
359 if (!cmd
->stop_arg
) {
363 } else { /* TRIG_NONE */
364 if (cmd
->stop_arg
!= 0) {
373 /* step 4: fix up any arguments */
375 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
376 tmp
= cmd
->scan_begin_arg
;
377 /* round to nearest microsec */
378 cmd
->scan_begin_arg
=
379 nano_per_micro
* ((tmp
+
380 (nano_per_micro
/ 2)) / nano_per_micro
);
381 if (tmp
!= cmd
->scan_begin_arg
)
384 if (cmd
->convert_src
== TRIG_TIMER
) {
385 tmp
= cmd
->convert_arg
;
386 /* round to nearest microsec */
388 nano_per_micro
* ((tmp
+
389 (nano_per_micro
/ 2)) / nano_per_micro
);
390 if (tmp
!= cmd
->convert_arg
)
400 static int waveform_ai_cmd(comedi_device
*dev
, comedi_subdevice
*s
)
402 comedi_cmd
*cmd
= &s
->async
->cmd
;
404 if (cmd
->flags
& TRIG_RT
) {
406 "commands at RT priority not supported in this driver");
410 devpriv
->timer_running
= 1;
411 devpriv
->ai_count
= 0;
412 devpriv
->scan_period
= cmd
->scan_begin_arg
/ nano_per_micro
;
414 if (cmd
->convert_src
== TRIG_NOW
)
415 devpriv
->convert_period
= 0;
416 else if (cmd
->convert_src
== TRIG_TIMER
)
417 devpriv
->convert_period
= cmd
->convert_arg
/ nano_per_micro
;
419 comedi_error(dev
, "bug setting conversion period");
423 do_gettimeofday(&devpriv
->last
);
424 devpriv
->usec_current
= devpriv
->last
.tv_usec
% devpriv
->usec_period
;
425 devpriv
->usec_remainder
= 0;
427 devpriv
->timer
.expires
= jiffies
+ 1;
428 add_timer(&devpriv
->timer
);
432 static int waveform_ai_cancel(comedi_device
*dev
, comedi_subdevice
*s
)
434 devpriv
->timer_running
= 0;
435 del_timer(&devpriv
->timer
);
439 static sampl_t
fake_sawtooth(comedi_device
*dev
, unsigned int range_index
,
440 unsigned long current_time
)
442 comedi_subdevice
*s
= dev
->read_subdev
;
443 unsigned int offset
= s
->maxdata
/ 2;
445 const comedi_krange
*krange
= &s
->range_table
->range
[range_index
];
446 u64 binary_amplitude
;
448 binary_amplitude
= s
->maxdata
;
449 binary_amplitude
*= devpriv
->uvolt_amplitude
;
450 do_div(binary_amplitude
, krange
->max
- krange
->min
);
452 current_time
%= devpriv
->usec_period
;
453 value
= current_time
;
454 value
*= binary_amplitude
* 2;
455 do_div(value
, devpriv
->usec_period
);
456 value
-= binary_amplitude
; /* get rid of sawtooth's dc offset */
458 return offset
+ value
;
460 static sampl_t
fake_squarewave(comedi_device
*dev
, unsigned int range_index
,
461 unsigned long current_time
)
463 comedi_subdevice
*s
= dev
->read_subdev
;
464 unsigned int offset
= s
->maxdata
/ 2;
466 const comedi_krange
*krange
= &s
->range_table
->range
[range_index
];
467 current_time
%= devpriv
->usec_period
;
470 value
*= devpriv
->uvolt_amplitude
;
471 do_div(value
, krange
->max
- krange
->min
);
473 if (current_time
< devpriv
->usec_period
/ 2)
476 return offset
+ value
;
479 static sampl_t
fake_flatline(comedi_device
*dev
, unsigned int range_index
,
480 unsigned long current_time
)
482 return dev
->read_subdev
->maxdata
/ 2;
485 /* generates a different waveform depending on what channel is read */
486 static sampl_t
fake_waveform(comedi_device
*dev
, unsigned int channel
,
487 unsigned int range
, unsigned long current_time
)
495 return fake_sawtooth(dev
, range
, current_time
);
498 return fake_squarewave(dev
, range
, current_time
);
504 return fake_flatline(dev
, range
, current_time
);
507 static int waveform_ai_insn_read(comedi_device
*dev
, comedi_subdevice
*s
,
508 comedi_insn
*insn
, lsampl_t
*data
)
510 int i
, chan
= CR_CHAN(insn
->chanspec
);
512 for (i
= 0; i
< insn
->n
; i
++)
513 data
[i
] = devpriv
->ao_loopbacks
[chan
];
518 static int waveform_ao_insn_write(comedi_device
*dev
, comedi_subdevice
*s
,
519 comedi_insn
*insn
, lsampl_t
*data
)
521 int i
, chan
= CR_CHAN(insn
->chanspec
);
523 for (i
= 0; i
< insn
->n
; i
++)
524 devpriv
->ao_loopbacks
[chan
] = data
[i
];