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 del_timer(&devpriv
->timer
);
224 comedi_event(dev
, dev
->read_subdev
);
227 static int waveform_ai_cmdtest(struct comedi_device
*dev
,
228 struct comedi_subdevice
*s
,
229 struct comedi_cmd
*cmd
)
234 /* Step 1 : check if triggers are trivially valid */
236 err
|= cfc_check_trigger_src(&cmd
->start_src
, TRIG_NOW
);
237 err
|= cfc_check_trigger_src(&cmd
->scan_begin_src
, TRIG_TIMER
);
238 err
|= cfc_check_trigger_src(&cmd
->convert_src
, TRIG_NOW
| TRIG_TIMER
);
239 err
|= cfc_check_trigger_src(&cmd
->scan_end_src
, TRIG_COUNT
);
240 err
|= cfc_check_trigger_src(&cmd
->stop_src
, TRIG_COUNT
| TRIG_NONE
);
245 /* Step 2a : make sure trigger sources are unique */
247 err
|= cfc_check_trigger_is_unique(cmd
->convert_src
);
248 err
|= cfc_check_trigger_is_unique(cmd
->stop_src
);
250 /* Step 2b : and mutually compatible */
255 /* Step 3: check if arguments are trivially valid */
257 err
|= cfc_check_trigger_arg_is(&cmd
->start_arg
, 0);
259 if (cmd
->convert_src
== TRIG_NOW
)
260 err
|= cfc_check_trigger_arg_is(&cmd
->convert_arg
, 0);
262 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
263 err
|= cfc_check_trigger_arg_min(&cmd
->scan_begin_arg
,
265 if (cmd
->convert_src
== TRIG_TIMER
)
266 err
|= cfc_check_trigger_arg_min(&cmd
->scan_begin_arg
,
267 cmd
->convert_arg
* cmd
->chanlist_len
);
270 err
|= cfc_check_trigger_arg_min(&cmd
->chanlist_len
, 1);
271 err
|= cfc_check_trigger_arg_is(&cmd
->scan_end_arg
, cmd
->chanlist_len
);
273 if (cmd
->stop_src
== TRIG_COUNT
)
274 err
|= cfc_check_trigger_arg_min(&cmd
->stop_arg
, 1);
276 err
|= cfc_check_trigger_arg_is(&cmd
->stop_arg
, 0);
281 /* step 4: fix up any arguments */
283 if (cmd
->scan_begin_src
== TRIG_TIMER
) {
284 tmp
= cmd
->scan_begin_arg
;
285 /* round to nearest microsec */
286 cmd
->scan_begin_arg
=
287 nano_per_micro
* ((tmp
+
288 (nano_per_micro
/ 2)) / nano_per_micro
);
289 if (tmp
!= cmd
->scan_begin_arg
)
292 if (cmd
->convert_src
== TRIG_TIMER
) {
293 tmp
= cmd
->convert_arg
;
294 /* round to nearest microsec */
296 nano_per_micro
* ((tmp
+
297 (nano_per_micro
/ 2)) / nano_per_micro
);
298 if (tmp
!= cmd
->convert_arg
)
308 static int waveform_ai_cmd(struct comedi_device
*dev
,
309 struct comedi_subdevice
*s
)
311 struct waveform_private
*devpriv
= dev
->private;
312 struct comedi_cmd
*cmd
= &s
->async
->cmd
;
314 if (cmd
->flags
& TRIG_RT
) {
316 "commands at RT priority not supported in this driver");
320 devpriv
->timer_running
= 1;
321 devpriv
->ai_count
= 0;
322 devpriv
->scan_period
= cmd
->scan_begin_arg
/ nano_per_micro
;
324 if (cmd
->convert_src
== TRIG_NOW
)
325 devpriv
->convert_period
= 0;
326 else if (cmd
->convert_src
== TRIG_TIMER
)
327 devpriv
->convert_period
= cmd
->convert_arg
/ nano_per_micro
;
329 comedi_error(dev
, "bug setting conversion period");
333 do_gettimeofday(&devpriv
->last
);
334 devpriv
->usec_current
= devpriv
->last
.tv_usec
% devpriv
->usec_period
;
335 devpriv
->usec_remainder
= 0;
337 devpriv
->timer
.expires
= jiffies
+ 1;
338 add_timer(&devpriv
->timer
);
342 static int waveform_ai_cancel(struct comedi_device
*dev
,
343 struct comedi_subdevice
*s
)
345 struct waveform_private
*devpriv
= dev
->private;
347 devpriv
->timer_running
= 0;
348 del_timer(&devpriv
->timer
);
352 static int waveform_ai_insn_read(struct comedi_device
*dev
,
353 struct comedi_subdevice
*s
,
354 struct comedi_insn
*insn
, unsigned int *data
)
356 struct waveform_private
*devpriv
= dev
->private;
357 int i
, chan
= CR_CHAN(insn
->chanspec
);
359 for (i
= 0; i
< insn
->n
; i
++)
360 data
[i
] = devpriv
->ao_loopbacks
[chan
];
365 static int waveform_ao_insn_write(struct comedi_device
*dev
,
366 struct comedi_subdevice
*s
,
367 struct comedi_insn
*insn
, unsigned int *data
)
369 struct waveform_private
*devpriv
= dev
->private;
370 int i
, chan
= CR_CHAN(insn
->chanspec
);
372 for (i
= 0; i
< insn
->n
; i
++)
373 devpriv
->ao_loopbacks
[chan
] = data
[i
];
378 static int waveform_attach(struct comedi_device
*dev
,
379 struct comedi_devconfig
*it
)
381 struct waveform_private
*devpriv
;
382 struct comedi_subdevice
*s
;
383 int amplitude
= it
->options
[0];
384 int period
= it
->options
[1];
388 dev
->board_name
= dev
->driver
->driver_name
;
390 devpriv
= kzalloc(sizeof(*devpriv
), GFP_KERNEL
);
393 dev
->private = devpriv
;
395 /* set default amplitude and period */
397 amplitude
= 1000000; /* 1 volt */
399 period
= 100000; /* 0.1 sec */
401 devpriv
->uvolt_amplitude
= amplitude
;
402 devpriv
->usec_period
= period
;
404 ret
= comedi_alloc_subdevices(dev
, 2);
408 s
= &dev
->subdevices
[0];
409 dev
->read_subdev
= s
;
410 /* analog input subdevice */
411 s
->type
= COMEDI_SUBD_AI
;
412 s
->subdev_flags
= SDF_READABLE
| SDF_GROUND
| SDF_CMD_READ
;
415 s
->range_table
= &waveform_ai_ranges
;
416 s
->len_chanlist
= s
->n_chan
* 2;
417 s
->insn_read
= waveform_ai_insn_read
;
418 s
->do_cmd
= waveform_ai_cmd
;
419 s
->do_cmdtest
= waveform_ai_cmdtest
;
420 s
->cancel
= waveform_ai_cancel
;
422 s
= &dev
->subdevices
[1];
423 dev
->write_subdev
= s
;
424 /* analog output subdevice (loopback) */
425 s
->type
= COMEDI_SUBD_AO
;
426 s
->subdev_flags
= SDF_WRITEABLE
| SDF_GROUND
;
429 s
->range_table
= &waveform_ai_ranges
;
430 s
->len_chanlist
= s
->n_chan
* 2;
431 s
->insn_write
= waveform_ao_insn_write
;
433 s
->do_cmdtest
= NULL
;
436 /* Our default loopback value is just a 0V flatline */
437 for (i
= 0; i
< s
->n_chan
; i
++)
438 devpriv
->ao_loopbacks
[i
] = s
->maxdata
/ 2;
440 init_timer(&(devpriv
->timer
));
441 devpriv
->timer
.function
= waveform_ai_interrupt
;
442 devpriv
->timer
.data
= (unsigned long)dev
;
444 dev_info(dev
->class_dev
,
445 "%s: %i microvolt, %li microsecond waveform attached\n",
447 devpriv
->uvolt_amplitude
, devpriv
->usec_period
);
452 static void waveform_detach(struct comedi_device
*dev
)
454 struct waveform_private
*devpriv
= dev
->private;
457 waveform_ai_cancel(dev
, dev
->read_subdev
);
460 static struct comedi_driver waveform_driver
= {
461 .driver_name
= "comedi_test",
462 .module
= THIS_MODULE
,
463 .attach
= waveform_attach
,
464 .detach
= waveform_detach
,
466 module_comedi_driver(waveform_driver
);
468 MODULE_AUTHOR("Comedi http://www.comedi.org");
469 MODULE_DESCRIPTION("Comedi low-level driver");
470 MODULE_LICENSE("GPL");