Got dummy waveform generation driver started by Wuttke Joachim working (sort of) so
authorFrank Mori Hess <fmhess@speakeasy.net>
Tue, 5 Mar 2002 00:23:02 +0000 (00:23 +0000)
committerFrank Mori Hess <fmhess@speakeasy.net>
Tue, 5 Mar 2002 00:23:02 +0000 (00:23 +0000)
I can bring comedi to a job interview :P

comedi/Config.in
comedi/drivers/Makefile.in
comedi/drivers/dummy_waveform.c [new file with mode: 0644]

index 9edc7ea01eedefe4093bb3900eee32cb6e856b57..f904ec3ef51511b13ab3b58cd72913663efa0916 100644 (file)
@@ -117,6 +117,7 @@ dep_tristate 'Winsystems PCM-A/D' CONFIG_COMEDI_PCMAD $CONFIG_COMEDI
 dep_tristate 'Amplicon PCI230' CONFIG_COMEDI_AMPLC_PCI230 $CONFIG_COMEDI
 dep_tristate 'SSV DIL/Net PC driver' CONFIG_COMEDI_SSV_DNP $CONFIG_COMEDI
 dep_tristate 'Inova ICP Multi' CONFIG_COMEDI_ICP_MULTI $CONFIG_COMEDI
+dep_tristate 'Dummy waveform driver' CONFIG_COMEDI_DUMMY_WAVEFORM $CONFIG_COMEDI
 dep_tristate 'Skeleton driver' CONFIG_COMEDI_SKEL $CONFIG_COMEDI
 
 if [ "$CONFIG_COMEDI_RT" = "y" ];then
index 5784d11c92d148c554bf4b03569b333260b126e3..d16cfd45b7ba94e086051e6e4d490b70c89c263c 100644 (file)
@@ -56,4 +56,5 @@ select(CONFIG_COMEDI_RTI800 rti800.o)
 select(CONFIG_COMEDI_RTI802 rti802.o)
 select(CONFIG_COMEDI_SKEL skel.o)
 select(CONFIG_COMEDI_SSV_DNP ssv_dnp.o)
+select(CONFIG_COMEDI_DUMMY_WAVEFORM dummy_waveform.o)
 
diff --git a/comedi/drivers/dummy_waveform.c b/comedi/drivers/dummy_waveform.c
new file mode 100644 (file)
index 0000000..000d8c6
--- /dev/null
@@ -0,0 +1,383 @@
+/*
+    dummy_waveform.c driver
+
+    Generates fake waveform signals that can be read through
+    the command interface.  It does _not_ read from any board;
+    it just generates deterministic waveforms.
+    Useful for various testing purposes.
+
+    Copyright (C) 2002 Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>
+    Copyright (C) 2002 Frank Mori Hess <fmhess@users.sourceforge.net>
+
+    COMEDI - Linux Control and Measurement Device Interface
+    Copyright (C) 2000 David A. Schleef <ds@schleef.org>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/sched.h>
+#include <linux/mm.h>
+#include <linux/malloc.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/timer.h>
+#include <linux/time.h>
+#include <linux/init.h>
+#include <linux/comedidev.h>
+
+/* Board descriptions */
+typedef struct waveform_board_struct{
+       char *name;
+       int ai_chans;
+       int ai_bits;
+       int have_dio;
+} waveform_board;
+static waveform_board waveform_boards[] = {
+       {
+       name:           "dummy_waveform",
+       ai_chans:       8,
+       ai_bits:        16,
+       have_dio:       0,
+       },
+};
+#define thisboard ((waveform_board *)dev->board_ptr)
+
+/* Data unique to this driver */
+typedef struct{
+       struct timer_list timer;
+       struct timeval last; // time at which last timer interrupt occured
+       unsigned int uvolt_amplitude;   // waveform amplitude in microvolts
+       unsigned long usec_period;      // waveform period in microseconds
+       unsigned long usec_current;     // current time (modulo waveform period)
+       volatile unsigned long ai_count;        // number of conversions remaining
+       volatile unsigned timer_running : 1;
+} waveform_private;
+#define devpriv ((waveform_private *)dev->private)
+
+static int waveform_attach(comedi_device *dev,comedi_devconfig *it);
+static int waveform_detach(comedi_device *dev);
+static comedi_driver driver_waveform={
+       driver_name:    "dummy_waveform",
+       module:         THIS_MODULE,
+       attach:         waveform_attach,
+       detach:         waveform_detach,
+       board_name:     waveform_boards,
+       offset:         sizeof(waveform_board),
+       num_names:      sizeof(waveform_boards) / sizeof(waveform_board),
+};
+
+static int waveform_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,
+       comedi_cmd *cmd);
+static int waveform_ai_cmd(comedi_device *dev, comedi_subdevice *s);
+static int waveform_ai_cancel(comedi_device *dev, comedi_subdevice *s);
+static int waveform_ai_insn_read(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data);
+static sampl_t fake_sawtooth(comedi_device *dev, unsigned long current_time);
+/*
+   This is the background routine used to generate arbitrary data.
+   It should run in the background; therefore it is scheduled by
+   a timer mechanism.
+*/
+void waveform_ai_interrupt(unsigned long arg)
+{
+       comedi_device *dev = (comedi_device*) arg;
+       comedi_async *async = dev->read_subdev->async;
+       comedi_cmd *cmd = &async->cmd;
+       unsigned long i;
+       // all times in microsec
+       unsigned long elapsed_time;
+       unsigned int num_scans;
+       unsigned int scan_period = async->cmd.scan_begin_arg / 1000;
+       unsigned int remainder = devpriv->usec_current % scan_period;
+       struct timeval now;
+
+       do_gettimeofday(&now);
+
+       elapsed_time = 1000000 * (now.tv_sec - devpriv->last.tv_sec) + now.tv_usec - devpriv->last.tv_usec;
+       devpriv->last = now;
+       num_scans = (remainder + elapsed_time) / scan_period;
+
+       async->events = 0;
+
+       for(i = 0; i < num_scans; i++)
+       {
+               comedi_buf_put(async,
+                       fake_sawtooth(dev, devpriv->usec_current + i * scan_period));
+               devpriv->ai_count++;
+               if(cmd->stop_src == TRIG_COUNT && devpriv->ai_count >= cmd->stop_arg)
+               {
+                       async->events |= COMEDI_CB_EOA;
+                       break;
+               }
+       }
+
+       devpriv->usec_current += elapsed_time;
+       devpriv->usec_current %= devpriv->usec_period;
+
+       async->events |= COMEDI_CB_BLOCK;
+
+       if((async->events & COMEDI_CB_EOA) == 0 && devpriv->timer_running)
+               mod_timer(&devpriv->timer, jiffies + 1);
+
+       comedi_event(dev, dev->read_subdev, async->events);
+}
+
+static int waveform_attach(comedi_device *dev,comedi_devconfig *it)
+{
+       comedi_subdevice *s;
+       int amplitude = it->options[0];
+       int period = it->options[1];
+
+       printk("comedi%d: dummy_waveform: ", dev->minor);
+
+       dev->board_name = thisboard->name;
+
+       if(alloc_private(dev, sizeof(waveform_private)) < 0)
+               return -ENOMEM;
+
+       // set default amplitude and period
+       if(amplitude <=0)
+               amplitude = 1000000;    // 1 volt
+       if(period <= 0)
+               period = 100000;        // 0.1 sec
+
+       devpriv->uvolt_amplitude = amplitude;
+       devpriv->usec_period = period;
+
+       printk("%i microvolt, %li microsecond waveform ", devpriv->uvolt_amplitude, devpriv->usec_period);
+       dev->n_subdevices = 1;
+       if(alloc_subdevices(dev) < 0) return -ENOMEM;
+
+       s = dev->subdevices + 0;
+       dev->read_subdev = s;
+       /* analog input subdevice */
+       s->type = COMEDI_SUBD_AI;
+       s->subdev_flags = SDF_READABLE | SDF_GROUND;
+       s->n_chan = thisboard->ai_chans;
+       s->maxdata = (1 << thisboard->ai_bits) - 1;
+       s->range_table = &range_bipolar10;
+       s->len_chanlist = 16;
+       s->insn_read = waveform_ai_insn_read; // apparently, we do not need waveform_ai_rinsn;
+       s->do_cmd = waveform_ai_cmd;
+       s->do_cmdtest = waveform_ai_cmdtest;
+       s->cancel = waveform_ai_cancel;
+
+       init_timer(&(devpriv->timer));
+       devpriv->timer.function = waveform_ai_interrupt;
+       devpriv->timer.data = (unsigned long) dev;
+
+       printk("attached\n");
+
+       return 1;
+}
+
+static int waveform_detach(comedi_device *dev)
+{
+       printk("comedi%d: dummy_waveform: remove\n",dev->minor);
+
+       if(dev->private)
+       {
+               waveform_ai_cancel(dev, dev->read_subdev);
+       }
+
+       return 0;
+}
+
+static int waveform_ai_cmdtest(comedi_device *dev,comedi_subdevice *s,
+       comedi_cmd *cmd)
+{
+       int err = 0;
+       int tmp;
+
+       /* step 1: make sure trigger sources are trivially valid */
+
+       tmp = cmd->start_src;
+       cmd->start_src &= TRIG_NOW;
+       if(!cmd->start_src || tmp != cmd->start_src) err++;
+
+       tmp = cmd->scan_begin_src;
+       cmd->scan_begin_src &= TRIG_TIMER;
+       if(!cmd->scan_begin_src || tmp != cmd->scan_begin_src) err++;
+
+       tmp = cmd->convert_src;
+       cmd->convert_src &= TRIG_NOW;
+       if(!cmd->convert_src || tmp != cmd->convert_src) err++;
+
+       tmp = cmd->scan_end_src;
+       cmd->scan_end_src &= TRIG_COUNT;
+       if(!cmd->scan_end_src || tmp != cmd->scan_end_src) err++;
+
+       tmp=cmd->stop_src;
+       cmd->stop_src &= TRIG_COUNT | TRIG_NONE;
+       if(!cmd->stop_src || tmp!=cmd->stop_src) err++;
+
+       if(err) return 1;
+
+       /* step 2: make sure trigger sources are unique and mutually compatible */
+
+       if(cmd->stop_src != TRIG_COUNT &&
+               cmd->stop_src != TRIG_NONE) err++;
+
+       if(err)return 2;
+
+       /* step 3: make sure arguments are trivially compatible */
+
+       if(cmd->start_arg != 0)
+       {
+               cmd->start_arg = 0;
+               err++;
+       }
+       if(cmd->convert_arg != 0)
+       {
+               cmd->convert_arg = 0;
+               err++;
+       }
+       if(cmd->scan_begin_src == TRIG_TIMER)
+       {
+               if(cmd->scan_begin_arg < 1000)
+                       cmd->scan_begin_arg = 1000;
+       }
+
+       // XXX these checks are generic and should go in core if not there already
+       if(!cmd->chanlist_len)
+       {
+               cmd->chanlist_len = 1;
+               err++;
+       }
+       if(cmd->scan_end_arg != cmd->chanlist_len)
+       {
+               cmd->scan_end_arg = cmd->chanlist_len;
+               err++;
+       }
+
+       if(cmd->stop_src == TRIG_COUNT)
+       {
+               if(!cmd->stop_arg)
+               {
+                       cmd->stop_arg = 1;
+                       err++;
+               }
+       } else
+       { /* TRIG_NONE */
+               if(cmd->stop_arg != 0)
+               {
+                       cmd->stop_arg = 0;
+                       err++;
+               }
+       }
+
+       if(err)return 3;
+
+       /* step 4: fix up any arguments */
+
+       if(cmd->scan_begin_src == TRIG_TIMER)
+       {
+               const int nano_per_micro = 1000;
+               tmp = cmd->scan_begin_arg;
+               // round to nearest microsec
+               cmd->scan_begin_arg = nano_per_micro * ((tmp + (nano_per_micro / 2)) / nano_per_micro);
+               if(tmp != cmd->scan_begin_arg) err++;
+       }
+
+       if(err) return 4;
+
+       return 0;
+}
+
+static int waveform_ai_cmd(comedi_device *dev, comedi_subdevice *s)
+{
+       comedi_cmd *cmd = &s->async->cmd;
+
+       devpriv->timer_running = 1;
+
+       if(cmd->stop_src == TRIG_COUNT)
+               devpriv->ai_count = 0;
+
+       do_gettimeofday(&devpriv->last);
+       devpriv->usec_current = 0;
+
+       devpriv->timer.expires = jiffies + 1;
+       add_timer(&devpriv->timer);
+       return 0;
+}
+
+static int waveform_ai_cancel(comedi_device *dev, comedi_subdevice *s)
+{
+       devpriv->timer_running = 0;
+       del_timer(&devpriv->timer);
+       return 0;
+}
+
+// divides an unsigned long long
+unsigned long long my_ull_div(unsigned long long numerator, unsigned long denominator)
+{
+       u32 value;
+       unsigned long long remainder;
+       unsigned int shift = 0;
+       const unsigned long max_u32 = 0xffffffff;
+
+       if(numerator <= max_u32)
+       {
+               // numerator is small enough that we can return correct result
+               value = numerator;
+               return value / denominator;
+       }
+
+       remainder = numerator;
+
+       // otherwise shift most significant bits into 32 bit variable
+       while(numerator > max_u32)
+       {
+               numerator >>= 1;
+               shift++;
+       }
+       value = numerator;
+       value /= denominator;
+       value <<= shift;
+
+       remainder -= ((unsigned long long) value) * denominator;
+
+       return value + my_ull_div(remainder, denominator);
+}
+
+static sampl_t fake_sawtooth(comedi_device *dev, unsigned long current_time)
+{
+       comedi_subdevice *s = dev->read_subdev;
+       unsigned int offset = s->maxdata / 2;
+       unsigned long long value = 0;
+
+       current_time %= devpriv->usec_period;
+       value = current_time;
+       value *= s->maxdata;
+       value = my_ull_div(value, devpriv->usec_period);
+       value *= devpriv->uvolt_amplitude;
+       value = my_ull_div(value, 20000000);    // XXX
+
+       return offset + value;
+}
+
+static int waveform_ai_insn_read(comedi_device *dev, comedi_subdevice *s, comedi_insn *insn, lsampl_t *data)
+{
+       int i;
+       for(i = 0; i < insn->n; i++)
+               data[i] = i;
+
+       return insn->n;
+}
+
+COMEDI_INITCLEANUP(driver_waveform);
+