RTXI 1.3
comedi/comedi/drivers/comedi_test.c
Go to the documentation of this file.
00001 /*
00002     comedi/drivers/comedi_test.c
00003 
00004     Generates fake waveform signals that can be read through
00005     the command interface.  It does _not_ read from any board;
00006     it just generates deterministic waveforms.
00007     Useful for various testing purposes.
00008 
00009     Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>
00010     Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
00011 
00012     COMEDI - Linux Control and Measurement Device Interface
00013     Copyright (C) 2000 David A. Schleef <ds@schleef.org>
00014 
00015     This program is free software; you can redistribute it and/or modify
00016     it under the terms of the GNU General Public License as published by
00017     the Free Software Foundation; either version 2 of the License, or
00018     (at your option) any later version.
00019 
00020     This program is distributed in the hope that it will be useful,
00021     but WITHOUT ANY WARRANTY; without even the implied warranty of
00022     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00023     GNU General Public License for more details.
00024 
00025     You should have received a copy of the GNU General Public License
00026     along with this program; if not, write to the Free Software
00027     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00028 
00029 ************************************************************************/
00030 /*
00031 Driver: comedi_test
00032 Description: generates fake waveforms
00033 Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess
00034   <fmhess@users.sourceforge.net>, ds
00035 Devices:
00036 Status: works
00037 Updated: Sat, 16 Mar 2002 17:34:48 -0800
00038 
00039 This driver is mainly for testing purposes, but can also be used to
00040 generate sample waveforms on systems that don't have data acquisition
00041 hardware.
00042 
00043 Configuration options:
00044   [0] - Amplitude in microvolts for fake waveforms (default 1 volt)
00045   [1] - Period in microseconds for fake waveforms (default 0.1 sec)
00046 
00047 Generates a sawtooth wave on channel 0, square wave on channel 1, additional
00048 waveforms could be added to other channels (currently they return flatline
00049 zero volts).
00050 
00051 */
00052 
00053 #include <linux/comedidev.h>
00054 
00055 #include <asm/div64.h>
00056 
00057 #include "comedi_fc.h"
00058 
00059 /* Board descriptions */
00060 typedef struct waveform_board_struct {
00061         const char *name;
00062         int ai_chans;
00063         int ai_bits;
00064         int have_dio;
00065 } waveform_board;
00066 
00067 #define N_CHANS 8
00068 
00069 static const waveform_board waveform_boards[] = {
00070         {
00071               name:     "comedi_test",
00072               ai_chans:N_CHANS,
00073               ai_bits:  16,
00074               have_dio:0,
00075                 },
00076 };
00077 
00078 #define thisboard ((const waveform_board *)dev->board_ptr)
00079 
00080 /* Data unique to this driver */
00081 typedef struct {
00082         struct timer_list timer;
00083         struct timeval last;    // time at which last timer interrupt occured
00084         unsigned int uvolt_amplitude;   // waveform amplitude in microvolts
00085         unsigned long usec_period;      // waveform period in microseconds
00086         volatile unsigned long usec_current;    // current time (modulo waveform period)
00087         volatile unsigned long usec_remainder;  // usec since last scan;
00088         volatile unsigned long ai_count;        // number of conversions remaining
00089         unsigned int scan_period;       // scan period in usec
00090         unsigned int convert_period;    // conversion period in usec
00091         volatile unsigned timer_running:1;
00092         volatile lsampl_t ao_loopbacks[N_CHANS];
00093 } waveform_private;
00094 #define devpriv ((waveform_private *)dev->private)
00095 
00096 static int waveform_attach(comedi_device * dev, comedi_devconfig * it);
00097 static int waveform_detach(comedi_device * dev);
00098 static comedi_driver driver_waveform = {
00099       driver_name:"comedi_test",
00100       module:THIS_MODULE,
00101       attach:waveform_attach,
00102       detach:waveform_detach,
00103       board_name:&waveform_boards[0].name,
00104       offset:sizeof(waveform_board),
00105       num_names:sizeof(waveform_boards) / sizeof(waveform_board),
00106 };
00107 
00108 COMEDI_INITCLEANUP(driver_waveform);
00109 
00110 static int waveform_ai_cmdtest(comedi_device * dev, comedi_subdevice * s,
00111         comedi_cmd * cmd);
00112 static int waveform_ai_cmd(comedi_device * dev, comedi_subdevice * s);
00113 static int waveform_ai_cancel(comedi_device * dev, comedi_subdevice * s);
00114 static int waveform_ai_insn_read(comedi_device * dev, comedi_subdevice * s,
00115         comedi_insn * insn, lsampl_t * data);
00116 static int waveform_ao_insn_write(comedi_device * dev, comedi_subdevice * s,
00117         comedi_insn * insn, lsampl_t * data);
00118 static sampl_t fake_sawtooth(comedi_device * dev, unsigned int range,
00119         unsigned long current_time);
00120 static sampl_t fake_squarewave(comedi_device * dev, unsigned int range,
00121         unsigned long current_time);
00122 static sampl_t fake_flatline(comedi_device * dev, unsigned int range,
00123         unsigned long current_time);
00124 static sampl_t fake_waveform(comedi_device * dev, unsigned int channel,
00125         unsigned int range, unsigned long current_time);
00126 
00127 static const int nano_per_micro = 1000; // 1000 nanosec in a microsec
00128 
00129 // fake analog input ranges
00130 static const comedi_lrange waveform_ai_ranges = {
00131         2,
00132         {
00133                         BIP_RANGE(10),
00134                         BIP_RANGE(5),
00135                 }
00136 };
00137 
00138 /*
00139    This is the background routine used to generate arbitrary data.
00140    It should run in the background; therefore it is scheduled by
00141    a timer mechanism.
00142 */
00143 static void waveform_ai_interrupt(unsigned long arg)
00144 {
00145         comedi_device *dev = (comedi_device *) arg;
00146         comedi_async *async = dev->read_subdev->async;
00147         comedi_cmd *cmd = &async->cmd;
00148         unsigned int i, j;
00149         // all times in microsec
00150         unsigned long elapsed_time;
00151         unsigned int num_scans;
00152         struct timeval now;
00153 
00154         do_gettimeofday(&now);
00155 
00156         elapsed_time =
00157                 1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec -
00158                 devpriv->last.tv_usec;
00159         devpriv->last = now;
00160         num_scans =
00161                 (devpriv->usec_remainder + elapsed_time) / devpriv->scan_period;
00162         devpriv->usec_remainder =
00163                 (devpriv->usec_remainder + elapsed_time) % devpriv->scan_period;
00164         async->events = 0;
00165 
00166         for (i = 0; i < num_scans; i++) {
00167                 for (j = 0; j < cmd->chanlist_len; j++) {
00168                         cfc_write_to_buffer(dev->read_subdev,
00169                                 fake_waveform(dev, CR_CHAN(cmd->chanlist[j]),
00170                                         CR_RANGE(cmd->chanlist[j]),
00171                                         devpriv->usec_current +
00172                                         i * devpriv->scan_period +
00173                                         j * devpriv->convert_period));
00174                 }
00175                 devpriv->ai_count++;
00176                 if (cmd->stop_src == TRIG_COUNT
00177                         && devpriv->ai_count >= cmd->stop_arg) {
00178                         async->events |= COMEDI_CB_EOA;
00179                         break;
00180                 }
00181         }
00182 
00183         devpriv->usec_current += elapsed_time;
00184         devpriv->usec_current %= devpriv->usec_period;
00185 
00186         if ((async->events & COMEDI_CB_EOA) == 0 && devpriv->timer_running)
00187                 mod_timer(&devpriv->timer, jiffies + 1);
00188         else
00189                 del_timer(&devpriv->timer);
00190 
00191         comedi_event(dev, dev->read_subdev);
00192 }
00193 
00194 static int waveform_attach(comedi_device * dev, comedi_devconfig * it)
00195 {
00196         comedi_subdevice *s;
00197         int amplitude = it->options[0];
00198         int period = it->options[1];
00199 
00200         printk("comedi%d: comedi_test: ", dev->minor);
00201 
00202         dev->board_name = thisboard->name;
00203 
00204         if (alloc_private(dev, sizeof(waveform_private)) < 0)
00205                 return -ENOMEM;
00206 
00207         // set default amplitude and period
00208         if (amplitude <= 0)
00209                 amplitude = 1000000;    // 1 volt
00210         if (period <= 0)
00211                 period = 100000;        // 0.1 sec
00212 
00213         devpriv->uvolt_amplitude = amplitude;
00214         devpriv->usec_period = period;
00215 
00216         printk("%i microvolt, %li microsecond waveform ",
00217                 devpriv->uvolt_amplitude, devpriv->usec_period);
00218         dev->n_subdevices = 2;
00219         if (alloc_subdevices(dev, dev->n_subdevices) < 0)
00220                 return -ENOMEM;
00221 
00222         s = dev->subdevices + 0;
00223         dev->read_subdev = s;
00224         /* analog input subdevice */
00225         s->type = COMEDI_SUBD_AI;
00226         s->subdev_flags = SDF_READABLE | SDF_GROUND | SDF_CMD_READ;
00227         s->n_chan = thisboard->ai_chans;
00228         s->maxdata = (1 << thisboard->ai_bits) - 1;
00229         s->range_table = &waveform_ai_ranges;
00230         s->len_chanlist = s->n_chan * 2;
00231         s->insn_read = waveform_ai_insn_read;
00232         s->do_cmd = waveform_ai_cmd;
00233         s->do_cmdtest = waveform_ai_cmdtest;
00234         s->cancel = waveform_ai_cancel;
00235 
00236         s = dev->subdevices + 1;
00237         dev->write_subdev = s;
00238         /* analog output subdevice (loopback) */
00239         s->type = COMEDI_SUBD_AO;
00240         s->subdev_flags = SDF_WRITEABLE | SDF_GROUND;
00241         s->n_chan = thisboard->ai_chans;
00242         s->maxdata = (1 << thisboard->ai_bits) - 1;
00243         s->range_table = &waveform_ai_ranges;
00244         s->len_chanlist = s->n_chan * 2;
00245         s->insn_write = waveform_ao_insn_write;
00246         s->do_cmd = 0;
00247         s->do_cmdtest = 0;
00248         s->cancel = 0;
00249         {
00250                 /* Our default loopback value is just a 0V flatline */
00251                 int i;
00252                 for (i = 0; i < s->n_chan; i++)
00253                         devpriv->ao_loopbacks[i] = s->maxdata / 2;
00254         }
00255 
00256         init_timer(&(devpriv->timer));
00257         devpriv->timer.function = waveform_ai_interrupt;
00258         devpriv->timer.data = (unsigned long)dev;
00259 
00260         printk("attached\n");
00261 
00262         return 1;
00263 }
00264 
00265 static int waveform_detach(comedi_device * dev)
00266 {
00267         printk("comedi%d: comedi_test: remove\n", dev->minor);
00268 
00269         if (dev->private) {
00270                 waveform_ai_cancel(dev, dev->read_subdev);
00271         }
00272 
00273         return 0;
00274 }
00275 
00276 static int waveform_ai_cmdtest(comedi_device * dev, comedi_subdevice * s,
00277         comedi_cmd * cmd)
00278 {
00279         int err = 0;
00280         int tmp;
00281 
00282         /* step 1: make sure trigger sources are trivially valid */
00283 
00284         tmp = cmd->start_src;
00285         cmd->start_src &= TRIG_NOW;
00286         if (!cmd->start_src || tmp != cmd->start_src)
00287                 err++;
00288 
00289         tmp = cmd->scan_begin_src;
00290         cmd->scan_begin_src &= TRIG_TIMER;
00291         if (!cmd->scan_begin_src || tmp != cmd->scan_begin_src)
00292                 err++;
00293 
00294         tmp = cmd->convert_src;
00295         cmd->convert_src &= TRIG_NOW | TRIG_TIMER;
00296         if (!cmd->convert_src || tmp != cmd->convert_src)
00297                 err++;
00298 
00299         tmp = cmd->scan_end_src;
00300         cmd->scan_end_src &= TRIG_COUNT;
00301         if (!cmd->scan_end_src || tmp != cmd->scan_end_src)
00302                 err++;
00303 
00304         tmp = cmd->stop_src;
00305         cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
00306         if (!cmd->stop_src || tmp != cmd->stop_src)
00307                 err++;
00308 
00309         if (err)
00310                 return 1;
00311 
00312         /* step 2: make sure trigger sources are unique and mutually compatible */
00313 
00314         if (cmd->convert_src != TRIG_NOW && cmd->convert_src != TRIG_TIMER)
00315                 err++;
00316         if (cmd->stop_src != TRIG_COUNT && cmd->stop_src != TRIG_NONE)
00317                 err++;
00318 
00319         if (err)
00320                 return 2;
00321 
00322         /* step 3: make sure arguments are trivially compatible */
00323 
00324         if (cmd->start_arg != 0) {
00325                 cmd->start_arg = 0;
00326                 err++;
00327         }
00328         if (cmd->convert_src == TRIG_NOW) {
00329                 if (cmd->convert_arg != 0) {
00330                         cmd->convert_arg = 0;
00331                         err++;
00332                 }
00333         }
00334         if (cmd->scan_begin_src == TRIG_TIMER) {
00335                 if (cmd->scan_begin_arg < nano_per_micro) {
00336                         cmd->scan_begin_arg = nano_per_micro;
00337                         err++;
00338                 }
00339                 if (cmd->convert_src == TRIG_TIMER &&
00340                         cmd->scan_begin_arg <
00341                         cmd->convert_arg * cmd->chanlist_len) {
00342                         cmd->scan_begin_arg =
00343                                 cmd->convert_arg * cmd->chanlist_len;
00344                         err++;
00345                 }
00346         }
00347         // XXX these checks are generic and should go in core if not there already
00348         if (!cmd->chanlist_len) {
00349                 cmd->chanlist_len = 1;
00350                 err++;
00351         }
00352         if (cmd->scan_end_arg != cmd->chanlist_len) {
00353                 cmd->scan_end_arg = cmd->chanlist_len;
00354                 err++;
00355         }
00356 
00357         if (cmd->stop_src == TRIG_COUNT) {
00358                 if (!cmd->stop_arg) {
00359                         cmd->stop_arg = 1;
00360                         err++;
00361                 }
00362         } else {                /* TRIG_NONE */
00363                 if (cmd->stop_arg != 0) {
00364                         cmd->stop_arg = 0;
00365                         err++;
00366                 }
00367         }
00368 
00369         if (err)
00370                 return 3;
00371 
00372         /* step 4: fix up any arguments */
00373 
00374         if (cmd->scan_begin_src == TRIG_TIMER) {
00375                 tmp = cmd->scan_begin_arg;
00376                 // round to nearest microsec
00377                 cmd->scan_begin_arg =
00378                         nano_per_micro * ((tmp +
00379                                 (nano_per_micro / 2)) / nano_per_micro);
00380                 if (tmp != cmd->scan_begin_arg)
00381                         err++;
00382         }
00383         if (cmd->convert_src == TRIG_TIMER) {
00384                 tmp = cmd->convert_arg;
00385                 // round to nearest microsec
00386                 cmd->convert_arg =
00387                         nano_per_micro * ((tmp +
00388                                 (nano_per_micro / 2)) / nano_per_micro);
00389                 if (tmp != cmd->convert_arg)
00390                         err++;
00391         }
00392 
00393         if (err)
00394                 return 4;
00395 
00396         return 0;
00397 }
00398 
00399 static int waveform_ai_cmd(comedi_device * dev, comedi_subdevice * s)
00400 {
00401         comedi_cmd *cmd = &s->async->cmd;
00402 
00403         if (cmd->flags & TRIG_RT) {
00404                 comedi_error(dev,
00405                         "commands at RT priority not supported in this driver");
00406                 return -1;
00407         }
00408 
00409         devpriv->timer_running = 1;
00410         devpriv->ai_count = 0;
00411         devpriv->scan_period = cmd->scan_begin_arg / nano_per_micro;
00412 
00413         if (cmd->convert_src == TRIG_NOW)
00414                 devpriv->convert_period = 0;
00415         else if (cmd->convert_src == TRIG_TIMER)
00416                 devpriv->convert_period = cmd->convert_arg / nano_per_micro;
00417         else {
00418                 comedi_error(dev, "bug setting conversion period");
00419                 return -1;
00420         }
00421 
00422         do_gettimeofday(&devpriv->last);
00423         devpriv->usec_current = devpriv->last.tv_usec % devpriv->usec_period;
00424         devpriv->usec_remainder = 0;
00425 
00426         devpriv->timer.expires = jiffies + 1;
00427         add_timer(&devpriv->timer);
00428         return 0;
00429 }
00430 
00431 static int waveform_ai_cancel(comedi_device * dev, comedi_subdevice * s)
00432 {
00433         devpriv->timer_running = 0;
00434         del_timer(&devpriv->timer);
00435         return 0;
00436 }
00437 
00438 static sampl_t fake_sawtooth(comedi_device * dev, unsigned int range_index,
00439         unsigned long current_time)
00440 {
00441         comedi_subdevice *s = dev->read_subdev;
00442         unsigned int offset = s->maxdata / 2;
00443         u64 value;
00444         const comedi_krange *krange = &s->range_table->range[range_index];
00445         u64 binary_amplitude;
00446 
00447         binary_amplitude = s->maxdata;
00448         binary_amplitude *= devpriv->uvolt_amplitude;
00449         do_div(binary_amplitude, krange->max - krange->min);
00450 
00451         current_time %= devpriv->usec_period;
00452         value = current_time;
00453         value *= binary_amplitude * 2;
00454         do_div(value, devpriv->usec_period);
00455         value -= binary_amplitude;      // get rid of sawtooth's dc offset
00456 
00457         return offset + value;
00458 }
00459 static sampl_t fake_squarewave(comedi_device * dev, unsigned int range_index,
00460         unsigned long current_time)
00461 {
00462         comedi_subdevice *s = dev->read_subdev;
00463         unsigned int offset = s->maxdata / 2;
00464         u64 value;
00465         const comedi_krange *krange = &s->range_table->range[range_index];
00466         current_time %= devpriv->usec_period;
00467 
00468         value = s->maxdata;
00469         value *= devpriv->uvolt_amplitude;
00470         do_div(value, krange->max - krange->min);
00471 
00472         if (current_time < devpriv->usec_period / 2)
00473                 value *= -1;
00474 
00475         return offset + value;
00476 }
00477 
00478 static sampl_t fake_flatline(comedi_device * dev, unsigned int range_index,
00479         unsigned long current_time)
00480 {
00481         return dev->read_subdev->maxdata / 2;
00482 }
00483 
00484 // generates a different waveform depending on what channel is read
00485 static sampl_t fake_waveform(comedi_device * dev, unsigned int channel,
00486         unsigned int range, unsigned long current_time)
00487 {
00488         enum {
00489                 SAWTOOTH_CHAN,
00490                 SQUARE_CHAN,
00491         };
00492         switch (channel) {
00493         case SAWTOOTH_CHAN:
00494                 return fake_sawtooth(dev, range, current_time);
00495                 break;
00496         case SQUARE_CHAN:
00497                 return fake_squarewave(dev, range, current_time);
00498                 break;
00499         default:
00500                 break;
00501         }
00502 
00503         return fake_flatline(dev, range, current_time);
00504 }
00505 
00506 static int waveform_ai_insn_read(comedi_device * dev, comedi_subdevice * s,
00507         comedi_insn * insn, lsampl_t * data)
00508 {
00509         int i, chan = CR_CHAN(insn->chanspec);
00510 
00511         for (i = 0; i < insn->n; i++)
00512                 data[i] = devpriv->ao_loopbacks[chan];
00513 
00514         return insn->n;
00515 }
00516 
00517 static int waveform_ao_insn_write(comedi_device * dev, comedi_subdevice * s,
00518         comedi_insn * insn, lsampl_t * data)
00519 {
00520         int i, chan = CR_CHAN(insn->chanspec);
00521 
00522         for (i = 0; i < insn->n; i++)
00523                 devpriv->ao_loopbacks[chan] = data[i];
00524 
00525         return insn->n;
00526 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines