![]() |
RTXI 1.3
|
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 }