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"
58 #include <linux/timer.h>
62 /* Data unique to this driver */
63 struct waveform_private
{
64 struct timer_list timer
;
65 struct timeval last
; /* time at which last timer interrupt occurred */
66 unsigned int uvolt_amplitude
; /* waveform amplitude in microvolts */
67 unsigned long usec_period
; /* waveform period in microseconds */
68 unsigned long usec_current
; /* current time (modulo waveform period) */
69 unsigned long usec_remainder
; /* usec since last scan; */
70 unsigned long ai_count
; /* number of conversions remaining */
71 unsigned int scan_period
; /* scan period in usec */
72 unsigned int convert_period
; /* conversion period in usec */
73 unsigned timer_running
:1;
74 unsigned int ao_loopbacks
[N_CHANS
];
77 /* 1000 nanosec in a microsec */
78 static const int nano_per_micro
= 1000;
80 /* fake analog input ranges */
81 static const struct comedi_lrange waveform_ai_ranges
= {
89 static short fake_sawtooth(struct comedi_device
*dev
, unsigned int range_index
,
90 unsigned long current_time
)
92 struct waveform_private
*devpriv
= dev
->private;
93 struct comedi_subdevice
*s
= dev
->read_subdev
;
94 unsigned int offset
= s
->maxdata
/ 2;
96 const struct comedi_krange
*krange
=
97 &s
->range_table
->range
[range_index
];
100 binary_amplitude
= s
->maxdata
;
101 binary_amplitude
*= devpriv
->uvolt_amplitude
;
102 do_div(binary_amplitude
, krange
->max
- krange
->min
);
104 current_time
%= devpriv
->usec_period
;
105 value
= current_time
;
106 value
*= binary_amplitude
* 2;
107 do_div(value
, devpriv
->usec_period
);
108 value
-= binary_amplitude
; /* get rid of sawtooth's dc offset */
110 return offset
+ value
;
113 static short fake_squarewave(struct comedi_device
*dev
,
114 unsigned int range_index
,
115 unsigned long current_time
)
117 struct waveform_private
*devpriv
= dev
->private;
118 struct comedi_subdevice
*s
= dev
->read_subdev
;
119 unsigned int offset
= s
->maxdata
/ 2;
121 const struct comedi_krange
*krange
=
122 &s
->range_table
->range
[range_index
];
123 current_time
%= devpriv
->usec_period
;
126 value
*= devpriv
->uvolt_amplitude
;
127 do_div(value
, krange
->max
- krange
->min
);
129 if (current_time
< devpriv
->usec_period
/ 2)
132 return offset
+ value
;
135 static short fake_flatline(struct comedi_device
*dev
, unsigned int range_index
,
136 unsigned long current_time
)
138 return dev
->read_subdev
->maxdata
/ 2;
141 /* generates a different waveform depending on what channel is read */
142 static short fake_waveform(struct comedi_device
*dev
, unsigned int channel
,
143 unsigned int range
, unsigned long current_time
)
151 return fake_sawtooth(dev
, range
, current_time
);
154 return fake_squarewave(dev
, range
, current_time
);
160 return fake_flatline(dev
, range
, current_time
);
164 This is the background routine used to generate arbitrary data.
165 It should run in the background; therefore it is scheduled by
168 static void waveform_ai_interrupt(unsigned long arg
)
170 struct comedi_device
*dev
= (struct comedi_device
*)arg
;
171 struct waveform_private
*devpriv
= dev
->private;
172 struct comedi_async
*async
= dev
->read_subdev
->async
;
173 struct comedi_cmd
*cmd
= &async
->cmd
;
175 /* all times in microsec */
176 unsigned long elapsed_time
;
177 unsigned int num_scans
;
180 do_gettimeofday(&now
);
183 1000000 * (now
.tv_sec
- devpriv
->last
.tv_sec
) + now
.tv_usec
-
184 devpriv
->last
.tv_usec
;
187 (devpriv
->usec_remainder
+ elapsed_time
) / devpriv
->scan_period
;
188 devpriv
->usec_remainder
=
189 (devpriv
->usec_remainder
+ elapsed_time
) % devpriv
->scan_period
;
192 for (i
= 0; i
< num_scans
; i
++) {
193 for (j
= 0; j
< cmd
->chanlist_len
; j
++) {
194 cfc_write_to_buffer(dev
->read_subdev
,
203 devpriv
->scan_period
+
209 if (cmd
->stop_src
== TRIG_COUNT
210 && devpriv
->ai_count
>= cmd
->stop_arg
) {
211 async
->events
|= COMEDI_CB_EOA
;
216 devpriv
->usec_current
+= elapsed_time
;
217 devpriv
->usec_current
%= devpriv
->usec_period
;
219 if ((async
->events
& COMEDI_CB_EOA
) == 0 && devpriv
->timer_running
)
220 mod_timer(&devpriv
->timer
, jiffies
+ 1);
222 comedi_event(dev
, dev
->read_subdev
);
225 static int waveform_ai_cmdtest(struct comedi_device
*dev
,
226 struct comedi_subdevice
*s
,
227 struct comedi_cmd
*cmd
)
232 /* Step 1 : check if triggers are trivially valid */
234 err
|= cfc_check_trigger_src(&cmd
->start_src
, TRIG_NOW
);
235 err
|= cfc_check_trigger_src(&cmd
->scan_begin_src
, TRIG_TIMER
);
236 err
|= cfc_check_trigger_src(&cmd
->convert_src
, TRIG_NOW
| TRIG_TIMER
);
237 err
|= cfc_check_trigger_src(&cmd
->scan_end_src
, TRIG_COUNT
);
238 err
|= cfc_check_trigger_src(&cmd
->stop_src
, TRIG_COUNT
| TRIG_NONE
);
243 /* Step 2a : make sure trigger sources are unique */
245 err
|= cfc_check_trigger_is_unique(cmd
->convert_src
);
246 err
|= cfc_check_trigger_is_unique(cmd
->stop_src
);
248 /* Step 2b : and mutually compatible */
253 /* Step 3: check if arguments are trivially valid */
255 err
|= cfc_check_trigger_arg_is(&cmd
->start_arg
, 0);
257 if (cmd
->convert_src
== TRIG_NOW
)
258 err
|= cfc_check_trigger_arg_is(&cmd
->convert_arg
, 0);
260 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
261 err
|= cfc_check_trigger_arg_min(&cmd
->scan_begin_arg
,
263 if (cmd
->convert_src
== TRIG_TIMER
)
264 err
|= cfc_check_trigger_arg_min(&cmd
->scan_begin_arg
,
265 cmd
->convert_arg
* cmd
->chanlist_len
);
268 err
|= cfc_check_trigger_arg_min(&cmd
->chanlist_len
, 1);
269 err
|= cfc_check_trigger_arg_is(&cmd
->scan_end_arg
, cmd
->chanlist_len
);
271 if (cmd
->stop_src
== TRIG_COUNT
)
272 err
|= cfc_check_trigger_arg_min(&cmd
->stop_arg
, 1);
274 err
|= cfc_check_trigger_arg_is(&cmd
->stop_arg
, 0);
279 /* step 4: fix up any arguments */
281 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
282 tmp
= cmd
->scan_begin_arg
;
283 /* round to nearest microsec */
284 cmd
->scan_begin_arg
=
285 nano_per_micro
* ((tmp
+
286 (nano_per_micro
/ 2)) / nano_per_micro
);
287 if (tmp
!= cmd
->scan_begin_arg
)
290 if (cmd
->convert_src
== TRIG_TIMER
) {
291 tmp
= cmd
->convert_arg
;
292 /* round to nearest microsec */
294 nano_per_micro
* ((tmp
+
295 (nano_per_micro
/ 2)) / nano_per_micro
);
296 if (tmp
!= cmd
->convert_arg
)
306 static int waveform_ai_cmd(struct comedi_device
*dev
,
307 struct comedi_subdevice
*s
)
309 struct waveform_private
*devpriv
= dev
->private;
310 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
312 if (cmd
->flags
& TRIG_RT
) {
314 "commands at RT priority not supported in this driver");
318 devpriv
->timer_running
= 1;
319 devpriv
->ai_count
= 0;
320 devpriv
->scan_period
= cmd
->scan_begin_arg
/ nano_per_micro
;
322 if (cmd
->convert_src
== TRIG_NOW
)
323 devpriv
->convert_period
= 0;
324 else if (cmd
->convert_src
== TRIG_TIMER
)
325 devpriv
->convert_period
= cmd
->convert_arg
/ nano_per_micro
;
327 comedi_error(dev
, "bug setting conversion period");
331 do_gettimeofday(&devpriv
->last
);
332 devpriv
->usec_current
= devpriv
->last
.tv_usec
% devpriv
->usec_period
;
333 devpriv
->usec_remainder
= 0;
335 devpriv
->timer
.expires
= jiffies
+ 1;
336 add_timer(&devpriv
->timer
);
340 static int waveform_ai_cancel(struct comedi_device
*dev
,
341 struct comedi_subdevice
*s
)
343 struct waveform_private
*devpriv
= dev
->private;
345 devpriv
->timer_running
= 0;
346 del_timer_sync(&devpriv
->timer
);
350 static int waveform_ai_insn_read(struct comedi_device
*dev
,
351 struct comedi_subdevice
*s
,
352 struct comedi_insn
*insn
, unsigned int *data
)
354 struct waveform_private
*devpriv
= dev
->private;
355 int i
, chan
= CR_CHAN(insn
->chanspec
);
357 for (i
= 0; i
< insn
->n
; i
++)
358 data
[i
] = devpriv
->ao_loopbacks
[chan
];
363 static int waveform_ao_insn_write(struct comedi_device
*dev
,
364 struct comedi_subdevice
*s
,
365 struct comedi_insn
*insn
, unsigned int *data
)
367 struct waveform_private
*devpriv
= dev
->private;
368 int i
, chan
= CR_CHAN(insn
->chanspec
);
370 for (i
= 0; i
< insn
->n
; i
++)
371 devpriv
->ao_loopbacks
[chan
] = data
[i
];
376 static int waveform_attach(struct comedi_device
*dev
,
377 struct comedi_devconfig
*it
)
379 struct waveform_private
*devpriv
;
380 struct comedi_subdevice
*s
;
381 int amplitude
= it
->options
[0];
382 int period
= it
->options
[1];
386 dev
->board_name
= dev
->driver
->driver_name
;
388 devpriv
= kzalloc(sizeof(*devpriv
), GFP_KERNEL
);
391 dev
->private = devpriv
;
393 /* set default amplitude and period */
395 amplitude
= 1000000; /* 1 volt */
397 period
= 100000; /* 0.1 sec */
399 devpriv
->uvolt_amplitude
= amplitude
;
400 devpriv
->usec_period
= period
;
402 ret
= comedi_alloc_subdevices(dev
, 2);
406 s
= &dev
->subdevices
[0];
407 dev
->read_subdev
= s
;
408 /* analog input subdevice */
409 s
->type
= COMEDI_SUBD_AI
;
410 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
| SDF_CMD_READ
;
413 s
->range_table
= &waveform_ai_ranges
;
414 s
->len_chanlist
= s
->n_chan
* 2;
415 s
->insn_read
= waveform_ai_insn_read
;
416 s
->do_cmd
= waveform_ai_cmd
;
417 s
->do_cmdtest
= waveform_ai_cmdtest
;
418 s
->cancel
= waveform_ai_cancel
;
420 s
= &dev
->subdevices
[1];
421 dev
->write_subdev
= s
;
422 /* analog output subdevice (loopback) */
423 s
->type
= COMEDI_SUBD_AO
;
424 s
->subdev_flags
= SDF_WRITEABLE
| SDF_GROUND
;
427 s
->range_table
= &waveform_ai_ranges
;
428 s
->len_chanlist
= s
->n_chan
* 2;
429 s
->insn_write
= waveform_ao_insn_write
;
431 s
->do_cmdtest
= NULL
;
434 /* Our default loopback value is just a 0V flatline */
435 for (i
= 0; i
< s
->n_chan
; i
++)
436 devpriv
->ao_loopbacks
[i
] = s
->maxdata
/ 2;
438 init_timer(&(devpriv
->timer
));
439 devpriv
->timer
.function
= waveform_ai_interrupt
;
440 devpriv
->timer
.data
= (unsigned long)dev
;
442 dev_info(dev
->class_dev
,
443 "%s: %i microvolt, %li microsecond waveform attached\n",
445 devpriv
->uvolt_amplitude
, devpriv
->usec_period
);
450 static void waveform_detach(struct comedi_device
*dev
)
452 struct waveform_private
*devpriv
= dev
->private;
455 waveform_ai_cancel(dev
, dev
->read_subdev
);
458 static struct comedi_driver waveform_driver
= {
459 .driver_name
= "comedi_test",
460 .module
= THIS_MODULE
,
461 .attach
= waveform_attach
,
462 .detach
= waveform_detach
,
464 module_comedi_driver(waveform_driver
);
466 MODULE_AUTHOR("Comedi http://www.comedi.org");
467 MODULE_DESCRIPTION("Comedi low-level driver");
468 MODULE_LICENSE("GPL");