// SPDX-License-Identifier: GPL-2.0+ /* * comedi/drivers/comedi_test.c * * 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>
*/
/* * Driver: comedi_test * Description: generates fake waveforms * Author: Joachim Wuttke <Joachim.Wuttke@icn.siemens.de>, Frank Mori Hess * <fmhess@users.sourceforge.net>, ds * Devices: * Status: works * Updated: Sat, 16 Mar 2002 17:34:48 -0800 * * This driver is mainly for testing purposes, but can also be used to * generate sample waveforms on systems that don't have data acquisition * hardware. * * Auto-configuration is the default mode if no parameter is supplied during * module loading. Manual configuration requires COMEDI userspace tool. * To disable auto-configuration mode, pass "noauto=1" parameter for module * loading. Refer modinfo or MODULE_PARM_DESC description below for details. * * Auto-configuration options: * Refer modinfo or MODULE_PARM_DESC description below for details. * * Manual configuration options: * [0] - Amplitude in microvolts for fake waveforms (default 1 volt) * [1] - Period in microseconds for fake waveforms (default 0.1 sec) * * Generates a sawtooth wave on channel 0, square wave on channel 1, additional * waveforms could be added to other channels (currently they return flatline * zero volts).
*/
module_param_named(amplitude, set_amplitude, uint, 0444);
MODULE_PARM_DESC(amplitude, "Set auto mode wave amplitude in microvolts: (defaults to 1 volt)");
module_param_named(period, set_period, uint, 0444);
MODULE_PARM_DESC(period, "Set auto mode wave period in microseconds: (defaults to 0.1 sec)");
/* Data unique to this driver */ struct waveform_private { struct timer_list ai_timer; /* timer for AI commands */
u64 ai_convert_time; /* time of next AI conversion in usec */ unsignedint wf_amplitude; /* waveform amplitude in microvolts */ unsignedint wf_period; /* waveform period in microseconds */ unsignedint wf_current; /* current time in waveform period */ unsignedint ai_scan_period; /* AI scan period in usec */ unsignedint ai_convert_period; /* AI conversion period in usec */ struct timer_list ao_timer; /* timer for AO commands */ struct comedi_device *dev; /* parent comedi device */
u64 ao_last_scan_time; /* time of previous AO scan in usec */ unsignedint ao_scan_period; /* AO scan period in usec */ bool ai_timer_enable:1; /* should AI timer be running? */ bool ao_timer_enable:1; /* should AO timer be running? */ unsignedshort ao_loopbacks[N_CHANS];
};
value = current_time;
value *= binary_amplitude * 2;
do_div(value, devpriv->wf_period);
value += offset; /* get rid of sawtooth's dc offset and clamp value */ if (value < binary_amplitude) {
value = 0; /* negative saturation */
} else {
value -= binary_amplitude; if (value > s->maxdata)
value = s->maxdata; /* positive saturation */
}
value = s->maxdata;
value *= devpriv->wf_amplitude;
do_div(value, krange->max - krange->min);
/* get one of two values for square-wave and clamp */ if (current_time < devpriv->wf_period / 2) { if (offset < value)
value = 0; /* negative saturation */ else
value = offset - value;
} else {
value += offset; if (value > s->maxdata)
value = s->maxdata; /* positive saturation */
}
/* generates a different waveform depending on what channel is read */ staticunsignedshort fake_waveform(struct comedi_device *dev, unsignedint channel, unsignedint range, unsignedint current_time)
{ enum {
SAWTOOTH_CHAN,
SQUARE_CHAN,
}; switch (channel) { case SAWTOOTH_CHAN: return fake_sawtooth(dev, range, current_time); case SQUARE_CHAN: return fake_squarewave(dev, range, current_time); default: break;
}
return fake_flatline(dev, range, 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.
*/ staticvoid waveform_ai_timer(struct timer_list *t)
{ struct waveform_private *devpriv = timer_container_of(devpriv, t,
ai_timer); struct comedi_device *dev = devpriv->dev; struct comedi_subdevice *s = dev->read_subdev; struct comedi_async *async = s->async; struct comedi_cmd *cmd = &async->cmd;
u64 now; unsignedint nsamples; unsignedint time_increment;
now = ktime_to_us(ktime_get());
nsamples = comedi_nsamples_left(s, UINT_MAX);
/* * Simulate first conversion to occur at convert period after * conversion timer starts. If scan_begin_src is TRIG_FOLLOW, assume * the conversion timer starts immediately. If scan_begin_src is * TRIG_TIMER, assume the conversion timer starts after the scan * period.
*/
first_convert_time = devpriv->ai_convert_period; if (cmd->scan_begin_src == TRIG_TIMER)
first_convert_time += devpriv->ai_scan_period;
devpriv->ai_convert_time = ktime_to_us(ktime_get()) +
first_convert_time;
/* Determine time within waveform period at time of conversion. */
wf_current = devpriv->ai_convert_time;
devpriv->wf_current = do_div(wf_current, devpriv->wf_period);
/* * Schedule timer to expire just after first conversion time. * Seem to need an extra jiffy here, otherwise timer expires slightly * early!
*/
spin_lock_bh(&dev->spinlock);
devpriv->ai_timer_enable = true;
devpriv->ai_timer.expires =
jiffies + usecs_to_jiffies(devpriv->ai_convert_period) + 1;
add_timer(&devpriv->ai_timer);
spin_unlock_bh(&dev->spinlock); return 0;
}
spin_lock_bh(&dev->spinlock);
devpriv->ai_timer_enable = false;
spin_unlock_bh(&dev->spinlock); if (in_softirq()) { /* Assume we were called from the timer routine itself. */
timer_delete(&devpriv->ai_timer);
} else {
timer_delete_sync(&devpriv->ai_timer);
} return 0;
}
for (i = 0; i < insn->n; i++)
data[i] = devpriv->ao_loopbacks[chan];
return insn->n;
}
/* * This is the background routine to handle AO commands, scheduled by * a timer mechanism.
*/ staticvoid waveform_ao_timer(struct timer_list *t)
{ struct waveform_private *devpriv = timer_container_of(devpriv, t,
ao_timer); struct comedi_device *dev = devpriv->dev; struct comedi_subdevice *s = dev->write_subdev; struct comedi_async *async = s->async; struct comedi_cmd *cmd = &async->cmd;
u64 now;
u64 scans_since; unsignedint scans_avail = 0;
/* determine number of scan periods since last time */
now = ktime_to_us(ktime_get());
scans_since = now - devpriv->ao_last_scan_time;
do_div(scans_since, devpriv->ao_scan_period); if (scans_since) { unsignedint i;
/* determine scans in buffer, limit to scans to do this time */
scans_avail = comedi_nscans_left(s, 0); if (scans_avail > scans_since)
scans_avail = scans_since; if (scans_avail) { /* skip all but the last scan to save processing time */ if (scans_avail > 1) { unsignedint skip_bytes, nbytes;
skip_bytes =
comedi_samples_to_bytes(s, cmd->scan_end_arg *
(scans_avail - 1));
nbytes = comedi_buf_read_alloc(s, skip_bytes);
comedi_buf_read_free(s, nbytes);
comedi_inc_scan_progress(s, nbytes); if (nbytes < skip_bytes) { /* unexpected underrun! (cancelled?) */
async->events |= COMEDI_CB_OVERFLOW; goto underrun;
}
} /* output the last scan */ for (i = 0; i < cmd->scan_end_arg; i++) { unsignedint chan = CR_CHAN(cmd->chanlist[i]); unsignedshort *pd;
staticint waveform_ao_insn_config(struct comedi_device *dev, struct comedi_subdevice *s, struct comedi_insn *insn, unsignedint *data)
{ if (data[0] == INSN_CONFIG_GET_CMD_TIMING_CONSTRAINTS) { /* we don't care about actual channels */
data[1] = NSEC_PER_USEC; /* scan_begin_min */
data[2] = 0; /* convert_min */ return 0;
}
return -EINVAL;
}
staticint waveform_common_attach(struct comedi_device *dev, int amplitude, int period)
{ struct waveform_private *devpriv; struct comedi_subdevice *s; int i; int ret;
devpriv = comedi_alloc_devpriv(dev, sizeof(*devpriv)); if (!devpriv) return -ENOMEM;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.