From 8455f49e5fa0dde3018a8a822cfd60cbc00b1656 Mon Sep 17 00:00:00 2001 From: Frank Mori Hess Date: Tue, 5 Mar 2002 00:23:02 +0000 Subject: [PATCH] Got dummy waveform generation driver started by Wuttke Joachim working (sort of) so I can bring comedi to a job interview :P --- comedi/Config.in | 1 + comedi/drivers/Makefile.in | 1 + comedi/drivers/dummy_waveform.c | 383 ++++++++++++++++++++++++++++++++ 3 files changed, 385 insertions(+) create mode 100644 comedi/drivers/dummy_waveform.c diff --git a/comedi/Config.in b/comedi/Config.in index 9edc7ea0..f904ec3e 100644 --- a/comedi/Config.in +++ b/comedi/Config.in @@ -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 diff --git a/comedi/drivers/Makefile.in b/comedi/drivers/Makefile.in index 5784d11c..d16cfd45 100644 --- a/comedi/drivers/Makefile.in +++ b/comedi/drivers/Makefile.in @@ -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 index 00000000..000d8c66 --- /dev/null +++ b/comedi/drivers/dummy_waveform.c @@ -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 + Copyright (C) 2002 Frank Mori Hess + + COMEDI - Linux Control and Measurement Device Interface + Copyright (C) 2000 David A. Schleef + + 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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); + -- 2.26.2