[prev in list] [next in list] [prev in thread] [next in thread] 

List:       comedi
Subject:    Feedback stress-testing for comedi
From:       Tim Holy <holy () pcg ! wustl ! edu>
Date:       2002-09-20 19:46:46
[Download RAW message or body]

Hello,

I decided to write a "functional" test routine for comedi. I was inspired by a 
few recent bug reports (e.g., from Jack Culpepper and Markus Dostal) and a 
general, long-standing, and occasionally useful paranoia about making sure my 
data acquisition system is doing what I think it is. Now that I've run it on 
my systems, I can sleep easily...

To make this useful, you need to have both inputs and outputs available 
(either analog or digital), not necessarily on the same board (in theory, at 
least; I only have one board per machine so haven't tested this). The basic 
idea is to generate outputs and measure them with the inputs; since you 
generated them yourself, you know what the answer is supposed to be, and you 
can compare the two. While we all do that "by eye," an automated system can 
check several hours of data with a high degree of accuracy.

I've implemented a somewhat flexible function for doing this. It handles both 
digital and analog channels, single-sample and streaming types (sorry, 
digital streaming isn't yet implemented, since I don't use it and don't even 
have a board which can do it). You can do multiple channels simultaneously; 
the waveforms it generates are unique for each channel. Currently you need 
one output channel for each input; it might be worth generalizing this code 
to allow multiple inputs to "listen" to the same output, but because of other 
duties I am unlikely to implement this in the near future. Since this code is 
already useful in its current form, I thought I'd share it with the list. 
While it works well on my systems when I (the author) am running it, I expect 
other users will find bugs; please let me know when you find them.

It's a single file, which you can compile with
# gcc -o feedback feedback.c -lm -lcomedi

Execute
# feedback -h
to find out how to use it and to get a sense for the options available.

If it seems generally useful, feel free to include it in comedlib/testing. 
Since it makes fairly extensive use of comedilib, it's a handy complement to 
the routines in that directory.

Best,

-- 
Tim Holy
Asst. Prof. of Neurobiology
Washington University School of Medicine
Department of Anatomy and Neurobiology
4401 North Building
Campus Box 8108, 660 S. Euclid Avenue
St. Louis, MO 63110-1093
tel: 314-362-0086
fax: 314-362-3446
email: holy@pcg.wustl.edu
["feedback.c" (text/x-csrc)]

/*
 * Functional test of hardware and the facilities of comedi & comedilib
 *
 * Copyright (c) 2002 by Timothy E. Holy <holy@pcg.wustl.edu>
 *
 * Lifted heavily from comedi demos written by David A. Schleef and others
 * <ds@schleef.org>
 *
 * This file may be freely modified, distributed, and combined with
 * other software, as long as proper attribution is given in the
 * source code.
 * */

/*
 * This program is intended to give comedi and comedilib a workout; it
 * autonomously tests input and output, in both synchronous and
 * asynchronous modes.  By writing values to output and then measuring
 * them with input, it can help reveal bugs in the hardware, library
 * code, or driver code.
 *
 * See the "h" option in parse_options for usage info.
 */

/* A note on output to stdout: currently, grep ':' outfile prints out
   the bottom-line analysis. If this behavior seems useful, refrain
   from using ':' in any output line other than errors and
   mismatch/pass statements. */

#include <stdio.h>
#include <comedilib.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <ctype.h>
#include <math.h>
#include <termio.h>

/*#include <stdio.h>
#include <comedilib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <ctype.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#include "examples.h"
*/

char *ifilename="/dev/comedi0";
char *ofilename="/dev/comedi0";
comedi_t *idev,*odev;
int isubdev = -1;
int osubdev = -1;
int defaultchan[] = {0,1};
int *ichan = defaultchan;
int *ochan = defaultchan;
int n_chan = 2;
int digital = 0;
float irate = 10000.0;
float orate = 10000.0;
float duration = 1.0;
int aslop = 3;
float wskip = 1.0;

int verbose = 0;

int aref=AREF_GROUND;
int irange = 0;
int orange = 0;
int terminate = 1;

void print_mismatch_message(int);
void print_device_info(comedi_t *device);
void dump_cmd(FILE *out,comedi_cmd *cmd);
void dump_parameters(FILE *out);
char *cmd_src(int src,char *buf);
int listtoi(char *s,int **chanlist);
int parse_options(int argc, char *argv[]);
sampl_t waveform_sample(int channum,int scannum,int base_period,int offset,float \
ratio,sampl_t omin,sampl_t omax); void init_cmd(comedi_t *dev,int subdev,int \
*chan,int n_chan,float rate,float duration,int range,comedi_cmd *cmd); void \
find_or_check_subdevice(comedi_t *dev,int *subdev,int *dio,int atype,int dtype);

int main(int argc, char *argv[])
{
  int idio,odio,i,j,owritemask,mismatch,err,base_period;
  int owavelen,owavesize,iwavelen,iwavesize;
  int onb,inb,ons,ins,scansread,scanswritten,o_offset,o_delay;
  unsigned int bit,ibit;
  lsampl_t *imaxdata,*omaxdata,*omin,*omax;
  lsampl_t ovalue,ivalue,iexpected,etemp,asave[2];
  lsampl_t trigdata[1];
  sampl_t *obuf, *ibuf, *ebuf;
  comedi_range **irng,**orng;
  comedi_cmd inputcmd, *icmd = &inputcmd, outputcmd, *ocmd = &outputcmd;
  comedi_insnlist il;
  comedi_insn insn[2];
  float frac,base_period_f;
  char *mm_char = " X";

  parse_options(argc,argv);

  /*
  if (digital) {
    printf("Can't set digital in func_analog!\n");
    exit(1);
  }
  */

  /*
   * Device setup: here we open devices, find subdevices, and
   * configure channels
   */
  /* Open devices */
  idev = comedi_open(ifilename);
  if(idev == NULL){
    fprintf(stderr, "error opening input device %s\n", ifilename);
    return -1;
  }
  odev = comedi_open(ofilename);
  if(idev == NULL){
    fprintf(stderr, "error opening output device %s\n", ofilename);
    return -1;
  }

  /* Find/check the subdevices */
  find_or_check_subdevice(idev,&isubdev,&idio,COMEDI_SUBD_AI,COMEDI_SUBD_DI);
  find_or_check_subdevice(odev,&osubdev,&odio,COMEDI_SUBD_AO,COMEDI_SUBD_DO);

  if (digital) {
    dump_parameters(stdout);
    printf("Input is DIO %d, output is DIO %d\n",idio,odio);
    /* Check for a DIO channel conflict (through user error) */
    if (!strcmp(ifilename,ofilename) && isubdev == osubdev)
      for (i = 0; i < n_chan; i++)
	for (j = 0; j < n_chan; j++)
	  if (ichan[i] == ochan[j]) {
	    printf("Error: the same digital channel is being configured for both input and \
output.\nSpecify different channels in the channel lists (-c and -C options)\n");  \
exit(1);  }
    /* Configure channel directions, if necessary */
    if (idio)
      for (i = 0; i < n_chan; i++)
	if (comedi_dio_config(idev,isubdev,ichan[i],COMEDI_INPUT) < 0) {
	  printf("Error: configuring DIO direction input\n");
	  exit(1);
	}
    if (odio)
      for (i = 0; i < n_chan; i++)
	if (comedi_dio_config(odev,osubdev,ochan[i],COMEDI_OUTPUT) < 0) {
	  printf("Error: configuring DIO direction output\n");
	  exit(1);
	}
  }
  else { /* We're dealing with analog channels */
    /* Find maxdata for each of the selected channels */
    if ((imaxdata = (lsampl_t *) malloc(n_chan*sizeof(lsampl_t))) == NULL) {
      printf("Error: allocating space for imaxdata\n");
      exit(1);
    }
    if ((omaxdata = (lsampl_t *) malloc(n_chan*sizeof(lsampl_t))) == NULL) {
      printf("Error: allocating space for omaxdata\n");
      exit(1);
    }
    for (i = 0; i < n_chan; i++) {
      imaxdata[i] = comedi_get_maxdata(idev,isubdev,ichan[i]);
      if (!imaxdata[i]) {
	printf("Error: finding maxdata for input channel %d\n",ichan[i]);
	exit(1);
      }
      omaxdata[i] = comedi_get_maxdata(odev,osubdev,ochan[i]);
      if (!omaxdata[i]) {
	printf("Error: finding maxdata for output channel %d\n",ochan[i]);
	exit(1);
      }
    }
    /* Adjust the output range to insure it falls within the input range */
    if ((omax = (lsampl_t *) malloc(n_chan*sizeof(lsampl_t))) == NULL) {
      printf("Error: allocating space for omax\n");
      exit(1);
    }
    if ((omin = (lsampl_t *) malloc(n_chan*sizeof(lsampl_t))) == NULL) {
      printf("Error: allocating space for omin\n");
      exit(1);
    }
    if ((irng = (comedi_range **) malloc(n_chan*sizeof(comedi_range *))) == NULL) {
      printf("Error: allocating space for irng\n");
      exit(1);
    }
    if ((orng = (comedi_range **) malloc(n_chan*sizeof(comedi_range *))) == NULL) {
      printf("Error: allocating space for orng\n");
      exit(1);
    }
    for (i = 0; i < n_chan; i++) {
      omin[i] = 0;
      omax[i] = omaxdata[i];
      irng[i] = comedi_get_range(idev,isubdev,ichan[i],irange);
      if (irng[i] == NULL) {
	printf("Error: finding range for input channel %d\n",ichan[i]);
	exit(1);
      }
      orng[i] = comedi_get_range(odev,osubdev,ochan[i],orange);
      if (orng[i] == NULL) {
	printf("Error: finding range for output channel %d\n",ochan[i]);
	exit(1);
      }
      if (orng[i]->max > irng[i]->max)
	omax[i] = floor((irng[i]->max-orng[i]->min)/(orng[i]->max-orng[i]->min)*omaxdata[i]);
  if (orng[i]->min < irng[i]->min)
	omin[i] = floor((irng[i]->min-orng[i]->min)/(orng[i]->max-orng[i]->min)*omaxdata[i]);
  if (omin[i] >= omax[i]) {
	printf("Error: no valid range for output on channel %d\n",ochan[i]);
	exit(1);
      }
    }
    /* Dump info */
    dump_parameters(stdout);
    for (i = 0; i < n_chan; i++) {
      printf("For input channel %d, min %g, max \
                %g\n",ichan[i],irng[i]->min,irng[i]->max);
      printf("For output channel %d,  min %g (%d), max %g \
(%d)\n",ochan[i],orng[i]->min,omin[i],orng[i]->max,omax[i]);  }
  }

  /* 
   * Write single samples to output channels and read them
   */
  printf("Checking single samples...\n");
  mismatch = 0;
  if (digital) {
    for (i = 0; i < n_chan; i++)
      for (bit = 0; bit < 2; bit++) {
	if (comedi_dio_write(odev,osubdev,ochan[i],bit) < 0) {
	  printf("Error: writing single sample to channel %d\n",ochan[i]);
	  exit(1);
	}
	if (comedi_dio_read(idev,isubdev,ichan[i],&ibit) < 0) {
	  printf("Error: reading single sample from channel %d\n",ichan[i]);
	  exit(1);
	}
	if (bit != ibit) {
	  printf("Mismatch: output on channel %d and input on channel \
%d\n",ochan[i],ichan[i]);  mismatch = 1;
	}
      }
    if (mismatch)
      print_mismatch_message(1);
    else
      printf("Passed: single bit writing/reading\n");
    /* Test bitfield */
    printf("Testing bitfield operations...\n");
    owritemask = 0;
    for (i = 0; i < n_chan; i++)
      owritemask = owritemask | (1 << ochan[i]);
    bit = 0;
    if (comedi_dio_bitfield(odev,osubdev,owritemask,&bit) < 0) {
      printf("Error: writing bitfield\n");
      exit(1);
    }
    if (comedi_dio_bitfield(idev,isubdev,0,&ibit) < 0) {
      printf("Error: reading bitfield\n");
      exit(1);
    }
    mismatch = 0;
    for (i = 0; i < n_chan; i++)
      if (ibit & (1 << ichan[i]))
	mismatch = 1;
    if (mismatch) {
      printf("Mismatch: bitfield write/read\n");
      print_mismatch_message(1);
    }
    else
      printf("Passed: bitfield writing/reading.  But note the test is not as \
stringent as the\nsingle sample test as far as cross-connectivity goes.\n");  }
  else {   /* Analog channels, single samples */
    printf("Output\tExpect\tMeasured\n");
    for (i = 0; i < n_chan; i++) {
      printf("Channels %d (input)/%d (output)\n",ichan[i],ochan[i]);
      for (frac = 0; frac <= 1.01; frac += 0.1) {
	ovalue = (lsampl_t) ((1-frac)*(omin[i]+1) + frac*(omax[i]-1));
	if (comedi_data_write(odev,osubdev,ochan[i],orange,aref,ovalue) < 0) {
	  printf("Error: writing single sample to channel %d\n",ochan[i]);
	  exit(1);
	}
	if (comedi_data_read(idev,isubdev,ichan[i],irange,aref,&ivalue) < 0) {
	  printf("Error: reading single sample on channel %d\n",ichan[i]);
	  exit(1);
	}
	/* Save certain values for later use */
	if (i == 0 && (frac == 0 || frac > 0.9))
	  asave[(int) rint(frac)] = ivalue;
	/* Now compare the measured value to what we expect */
	iexpected = comedi_from_phys(comedi_to_phys(ovalue,orng[i],omaxdata[i]),irng[i],imaxdata[i]);
  printf("%d\t%d\t%c%d\n",ovalue,iexpected,mm_char[test_mismatch(iexpected,ivalue,aslop)],ivalue);
  if (test_mismatch(iexpected,ivalue,aslop))
	  mismatch = 1;
      }
    }
    if (mismatch) {
      printf("Mismatch: analog single samples\n");
      print_mismatch_message(1);
    }
    else
      printf("Passed: analog single samples\n");
  }

  /* 
   * Check streaming IO 
   */
  printf("Testing streaming capabilities...\n");
  /* Initialize the commands */
  init_cmd(idev,isubdev,ichan,n_chan,irate,duration,irange,icmd);
  init_cmd(odev,osubdev,ochan,n_chan,orate,-1,orange,ocmd);

  /* Convert actual rates to physical units */
  irate = 1e9/icmd->scan_begin_arg;
  orate = 1e9/ocmd->scan_begin_arg;
  printf("Modified rates (set to hardware capabilities) are input %g, output %g\n", \
irate, orate);

  /* Send the commands (won't actually get started until we send a trigger) */
  if ((err = comedi_command(idev, icmd)) < 0) {
    comedi_perror("comedi_command (input)");
    exit(1);
  }
  if ((err = comedi_command(odev, ocmd)) < 0) {
    comedi_perror("comedi_command (output)");
    exit(1);
  }
  
  if (digital) {
    printf("Streaming digital I/O is not yet supported by this test function, but may \
be supported within comedi.\n");  return 0;
  }

  /* Create output waveforms */
  /* Make the fastest-changing waveform changes by wskip between samples */
  /* Note: this code may need changing if you modify waveform_sample */
  base_period_f = 0;
  for (i = 0; i < n_chan; i++)
    base_period_f = ((omax[i]-omin[i])/(i+1) > base_period_f) ? \
(omax[i]-omin[i])/(i+1) : base_period_f;  base_period_f *= 2;   /* For up and down */
  base_period = (int) (rint(base_period_f/wskip));
  owavelen = base_period*n_chan;   /* The longest waveform has this period */
  owavesize = owavelen*n_chan;     /* We're making n_chan waveforms */
  if ((obuf = (sampl_t *) malloc(owavesize*sizeof(sampl_t))) == NULL) {
    printf("Error: allocating space for output buffer\n");
    exit(1);
  }
  for (i = 0; i < n_chan; i++)
    for (j = 0; j < owavelen; j++) {
      obuf[j*n_chan+i] = waveform_sample(i,j,base_period,0,1.0,omin[i]+1,omax[i]-1);
      /*printf("%d ",obuf[j*n_chan+i]);*/
    }

  /* Allocate input and expected-value buffers */
  iwavelen = ceil(owavelen*irate/orate*1.1);  /* 10% extra to make sure we keep up */
  iwavesize = iwavelen*n_chan;
  if ((ibuf = (sampl_t *) malloc(iwavesize*sizeof(sampl_t))) == NULL) {
    printf("Error: allocating space for input buffer\n");
    exit(1);
  }
  if ((ebuf = (sampl_t *) malloc(iwavesize*sizeof(sampl_t))) == NULL) {
    printf("Error: allocating space for expectation buffer\n");
    exit(1);
  }

  printf("owavelen %d, owavesize %d, iwavelen %d, iwavesize \
%d\n",owavelen,owavesize,iwavelen,iwavesize);

  /* Fill the output buffer */
  onb = write(comedi_fileno(odev),obuf,owavesize*sizeof(sampl_t));
  if(onb < 0){
    perror("write");
    exit(1);
  }
  scansread = 0;
  scanswritten = onb/(sizeof(sampl_t)*n_chan);

  /* Prepare the triggers to start processes on both subdevices  */
  il.n_insns = 2;
  il.insns = insn;
  memset(&insn,0,2*sizeof(comedi_insn));
  insn[0].insn = INSN_INTTRIG;
  insn[0].subdev = isubdev;
  insn[0].data = trigdata;
  insn[0].n = 1;
  trigdata[0] = 0;
  insn[1].insn = INSN_INTTRIG;
  insn[1].subdev = osubdev;
  insn[1].data = trigdata+1;
  insn[1].n = 1;
  trigdata[1] = 0;
  /* Send the trigger instructions */
  /* If the devices are different, we have to split this up */
  if (strcmp(ifilename,ofilename) == 0) {
    if (comedi_do_insnlist(idev,&il) < 2) {
      comedi_perror(ifilename);
      exit(1);
    }
  }
  else {
    if (comedi_do_insn(idev,&(insn[0])) < 0) {
      comedi_perror(ifilename);
      exit(1);
    }
    if (comedi_do_insn(odev,&(insn[1])) < 0) {
      comedi_perror(ofilename);
      exit(1);
    }
  }
  
  /* Write and read the data sequentially, and test to see if the read
     values match our expectations */
  while (scansread < icmd->stop_arg) {
    inb = read(comedi_fileno(idev),ibuf,iwavesize*sizeof(sampl_t));
    if (inb < 0) {
      perror("read");
      exit(1);
    } else if (inb == 0) {
      printf("Error: command terminated, but it shouldn't have\n");
      exit(1);
    }
    else {  
      ins = inb/(sizeof(sampl_t)*n_chan);
      /* If it's our first pass, examine the data to see when the
         streaming output got started */
      if (scansread == 0) {
	o_delay = 0;
	while (o_delay < ins && ibuf[o_delay*n_chan] > (asave[0]+asave[1])/2)
	  /*while (o_delay < ins && ibuf[o_delay*n_chan] > 100)*/
	  o_delay++;
	if (o_delay == ins) {
	  printf("Error: in computing output delay.\n");
	  exit(1);
	}
	printf("Analog output appears to have started with a delay of %d \
samples\n",o_delay);  }
      /* Compute expected values, and compare with measured ones */
      mismatch = 0;
      for (i = 0; i < n_chan; i++)
	for (j = 0; j < ins; j++) {
	  etemp = waveform_sample(i,j,base_period,scansread-o_delay,orate/irate,omin[i]+1,omax[i]-1);
  ebuf[j*n_chan+i] = \
comedi_from_phys(comedi_to_phys(etemp,orng[i],omaxdata[i]),irng[i],imaxdata[i]);  if \
(j-o_delay > 0)  /* Don't compare until after output is started */  if \
(test_mismatch(ebuf[j*n_chan+i],ibuf[j*n_chan+i],aslop))  mismatch = 1;
	}
      /* If there is a mismatch, output data and quit */
      if (mismatch) {
	printf("Mismatch: detected after %d scans (out of %d \
total):\n",scansread,icmd->stop_arg);  if (verbose) {
	  for (i = 0; i < n_chan; i++)
	    printf("Exp %d\tMeas %d\t",ichan[i],ichan[i]);
	  printf("\n");
	  for (j = 0; j < ins; j++) {
	    for (i = 0; i < n_chan; i++)
	      printf("%d\t%c%d\t",ebuf[j*n_chan+i],mm_char[test_mismatch(ebuf[j*n_chan+i],ibuf[j*n_chan+i],aslop)],ibuf[j*n_chan+i]);
  printf("\n");
	  }
	}
	print_mismatch_message(0);
	if (terminate)
	  exit(1);
      }
      /* Send more data to the output */
      o_offset = (int) fmod(scanswritten,owavelen);
      /* printf("%d scans written, out of a total %d\n", scanswritten, \
                ocmd->stop_arg); */
      onb = write(comedi_fileno(odev),obuf+o_offset*n_chan,(owavesize-o_offset*n_chan)*sizeof(sampl_t));
  if (onb < 0) {
	perror("write");
	exit(1);
      }
      /* Update counters */
      scansread += ins;
      scanswritten += onb/(sizeof(sampl_t)*n_chan);
      printf("%d scans read, out of a total %d\n", scansread, icmd->stop_arg); 
    }
  }
  printf("Passed: stream testing\n");
  return 0;
}

void init_cmd(comedi_t *dev,int subdev,int *chan,int n_chan,float rate,float \
duration,int range,comedi_cmd *cmd) {
  int i,ret;

  memset(cmd,0,sizeof(*cmd));
  if (comedi_get_cmd_generic_timed(dev,subdev,cmd,1e9/(rate)) < 0) {
    printf("Error: setting generic timed command.  Does the (sub)device support \
commands?\n");  exit(1);
  }
  cmd->start_src = TRIG_INT;
  cmd->chanlist = (unsigned int *) malloc(n_chan * sizeof(unsigned int));
  for (i = 0; i < n_chan; i++)
    cmd->chanlist[i] = CR_PACK(chan[i],range,aref);
  cmd->chanlist_len = n_chan;
  cmd->scan_end_src = TRIG_COUNT;
  cmd->scan_end_arg = n_chan;
  if (duration >= 0) {
    cmd->stop_src = TRIG_COUNT;
    cmd->stop_arg = (unsigned int) (1e9*duration/cmd->scan_begin_arg);
  } else {
    cmd->stop_src = TRIG_NONE;
    cmd->stop_arg = 0;
  }
  // Is this a comedilib bug, or is the following supposed to be necessary?
  // Or should I use rate*n_chan in the call to generic_timed?
  cmd->convert_arg /= n_chan;

  printf("The cmd before refinement is\n");
  dump_cmd(stdout,cmd);
  /* Refine command iteratively */
  i = 0;
  ret = 1;
  while (i < 5 && (ret = comedi_command_test(dev,cmd)))
    i++;
  if (ret)
    printf("Likely error preparing command\n");

  printf("The cmd after refinement is\n");
  dump_cmd(stdout,cmd);
}  

/* This creates single samples of triangular waveforms, different on each
   channel, with periods arranged in a harmonic series */
sampl_t waveform_sample(int channum,int scannum,int base_period,int offset,float \
ratio,sampl_t omin,sampl_t omax) {
  float frac,ret;

  /*printf("channum %d, scannum %d, base_period %d, offset %d, ratio %g, omin %d, \
omax %d\n",channum,scannum,base_period,offset,ratio,omin,omax);*/  frac = \
fmod((scannum+offset)*ratio,base_period*(channum+1))/(base_period*(channum+1));  /* \
The phase/2pi */  frac = 1-2*fabs(frac-0.5);
  ret = (frac*omax + (1-frac)*omin);
  /*printf("frac %g, retf %g, retd %d\n",frac,ret,(sampl_t)ret);*/
  return (sampl_t) ret;
}

int test_mismatch(lsampl_t expected,lsampl_t value,int aslop)
{
  return (expected > value + aslop || expected + aslop < value);
}

void find_or_check_subdevice(comedi_t *dev,int *subdev,int *dio,int atype,int dtype)
{
  int type;

  *dio = 0;
  if (*subdev < 0)
    if (digital) {
      *subdev = comedi_find_subdevice_by_type(dev,dtype,0);
      if (*subdev < 0) {
	*subdev = comedi_find_subdevice_by_type(dev,COMEDI_SUBD_DIO,0);
	*dio = 1;
      }
    }
    else
      *subdev = comedi_find_subdevice_by_type(dev,atype,0);
  else { /* User specified the type */
    type = comedi_get_subdevice_type(dev,*subdev);
    if (digital) {
      if (type != COMEDI_SUBD_DIO && type != dtype) {
	printf("Error: digital subdevice %d is not of the proper type.\n",*subdev);
	exit(1);
      }
      if (type == COMEDI_SUBD_DIO)
	*dio = 1;
    }
    else
      if (type != atype) {
	printf("Error: analog subdevice %d is not of the proper type.\n",*subdev);
	exit(1);
      }
  }
  if (*subdev < 0) {   /* The find-by-device-type must have failed */
    comedi_perror("find_or_check_subdevice:");
    exit(1);
  }
}

int parse_options(int argc, char *argv[])
{
  int c;
  int n_ichan = 2;
  int n_ochan = 2;
  
  while (-1 != (c = getopt(argc, argv, "I:i:O:o:c:C:f:F:t:r:R:s:w:dhvx"))) {
    switch (c) {
    case 'v':
      verbose = 1;
      break;
    case 'I':
      ifilename = optarg;
      break;
    case 'i':
      isubdev = strtoul(optarg,NULL,0);
      break;
    case 'O':
      ofilename = optarg;
      break;
    case 'o':
      osubdev = strtoul(optarg,NULL,0);
      break;
    case 'c':
      n_ichan = listtoi(optarg,&ichan);
      break;
    case 'C':
      n_ochan = listtoi(optarg,&ochan);
      break;
    case 'd':
      digital = 1;
      break;
    case 'f':
      irate = atof(optarg);
      break;
    case 'F':
      orate = atof(optarg);
      break;
    case 't':
      duration = atof(optarg);
      break;
    case 'r':
      irange = atoi(optarg);
      break;
    case 'R':
      orange = atoi(optarg);
      break;
    case 's':
      aslop = atoi(optarg);
      break;
    case 'w':
      wskip = atof(optarg);
      if (wskip <= 0) {
	printf("Error: waveform skip (option -w) must be positive\n");
	exit(1);
      }
      break;
    case 'x':
      terminate = 0;
      break;
    case 'h':
      printf("The standard usage is as follows:\n  %s options\nwhere options may take \
                the following form:\n",argv[0]);
      printf("  -v        Verbose output in case of mismatch (streaming data)\n");
      printf("  -I dev    Set the input device  (default /dev/comedi0)\n");
      printf("  -i n      Set the input subdevice number (default: finds input \
                type)\n");
      printf("  -O dev    Set the output device (default /dev/comedi0)\n");
      printf("  -o n      Set the output subdevice number (default: finds output \
                type)\n");
      printf("  -c nlist  Set the input channel numbers (default: 0,1; no spaces \
                allowed)\n");
      printf("  -C nlist  Set the output channel numbers (default: 0,1)\n");
      printf("and for devices which support streaming data,\n");
      printf("  -f n      Set the input scan frequency, in Hz (default: 10000)\n");
      printf("  -F n      Set the output scan frequency, in Hz (default: 10000)\n");
      printf("  -t n      Set the duration of the test, in seconds (default: 1)\n");
      printf("  -x        Keep going with stream test even with mismatch (use \
oscilloscope to see if you're generating output\n");  printf("Analog or \
                digital-specific options:\n");
      printf("  -r n      Analog input range number (valid values depend on \
                hardware)\n");
      printf("  -R n      Analog output range number (valid values depend on \
                hardware)\n");
      printf("  -s n      The permissible slop in AI values, in sample units \
                (default: 3)\n");
      printf("  -w n      The average change in waveform value between output scans, \
                in A/D units, for the fastest-changing channel (default: 1.0)\n");
      printf("  -d        Force digital test (default: use analog subdevices)\n");
    default:
      printf("Error: bad option\n");
      exit(1);
    }
  }
  /* Check arguments for consistency */
  if (n_ichan != n_ochan) {
    printf("Error: number of input channels is not equal to number of output \
channels\n");  exit(1);
  }
  n_chan = n_ichan;

  return argc;
}

/* This function takes a string of format 0,1,3 and converts it into
   an array of integers */
int listtoi(char *s,int **chanlist)
{
  int temp[256];
  int n_list,ret,i;
  char *snext;

  n_list = 0;
  snext = s;
  ret = sscanf(snext,"%d",temp+n_list);
  if (ret == 1)
      n_list++;
  while (ret > 0 && ret != EOF && snext != NULL) {
    snext = (char *) index(snext,',');
    if (snext != NULL) {
      snext++;
      ret = sscanf(snext,"%d,",temp+n_list);
      n_list++;
    }
  }
  if (ret == EOF) {
    printf("Error: parsing the final entry in channel list.\n");
    exit(1);
  }
  if ((*chanlist = (int *) malloc(n_list*sizeof(int))) == NULL) {
    printf("listtoi: Error allocating space for channel array\n");
    exit(1);
  }
  for (i = 0; i < n_list; i++) {
    (*chanlist)[i] = temp[i];
  }
  return n_list;
}
  
  

char *cmd_src(int src,char *buf)
{
	buf[0]=0;

	if(src&TRIG_NONE)strcat(buf,"none|");
	if(src&TRIG_NOW)strcat(buf,"now|");
	if(src&TRIG_FOLLOW)strcat(buf, "follow|");
	if(src&TRIG_TIME)strcat(buf, "time|");
	if(src&TRIG_TIMER)strcat(buf, "timer|");
	if(src&TRIG_COUNT)strcat(buf, "count|");
	if(src&TRIG_EXT)strcat(buf, "ext|");
	if(src&TRIG_INT)strcat(buf, "int|");
#ifdef TRIG_OTHER
	if(src&TRIG_OTHER)strcat(buf, "other|");
#endif

	if(strlen(buf)==0){
		sprintf(buf,"unknown(0x%08x)",src);
	}else{
		buf[strlen(buf)-1]=0;
	}

	return buf;
}

void dump_parameters(FILE *out)
{
  int i;

  fprintf(out,"Input device %s, subdevice %d\n",ifilename,isubdev);
  print_device_info(idev);
  fprintf(out,"Output device %s, subdevice %d\n",ofilename,osubdev);
  print_device_info(odev);
  fprintf(out,"Input channels ");
  for (i = 0; i < n_chan; i++)
    fprintf(out,"%d, ",ichan[i]);
  fprintf(out,"\n");
  fprintf(out,"Output channels ");
  for (i = 0; i < n_chan; i++)
    fprintf(out,"%d, ",ochan[i]);
  fprintf(out,"\n");
  if (digital)
    fprintf(out,"We're testing digital devices\n");
  fprintf(out,"Input rate %g Hz, output rate %g Hz, duration %g \
s\n",irate,orate,duration);  if (!digital)
    fprintf(out,"Analog slop %d, waveform skip %g (A/D units)\n",aslop,wskip);
}

void dump_cmd(FILE *out,comedi_cmd *cmd)
{
	char buf[100];

	fprintf(out,"subdevice %d, chanlist_len %d\n",
		cmd->subdev,cmd->chanlist_len);
	fprintf(out,"start      %-8s %d\n",
		cmd_src(cmd->start_src,buf),
		cmd->start_arg);

	fprintf(out,"scan_begin %-8s %d\n",
		cmd_src(cmd->scan_begin_src,buf),
		cmd->scan_begin_arg);

	fprintf(out,"convert    %-8s %d\n",
		cmd_src(cmd->convert_src,buf),
		cmd->convert_arg);

	fprintf(out,"scan_end   %-8s %d\n",
		cmd_src(cmd->scan_end_src,buf),
		cmd->scan_end_arg);

	fprintf(out,"stop       %-8s %d\n",
		cmd_src(cmd->stop_src,buf),
		cmd->stop_arg);
}

void print_device_info(comedi_t *device)
{
        int vers = comedi_get_version_code(device);
 
        printf("Comedi version %d.%d.%d\n",(vers>>16)&0xff,
                (vers>>8)&0xff,vers&0xff);
        printf("Comedilib version unknown =)\n");
        printf("driver name %s\n",comedi_get_driver_name(device));
        printf("device name %s\n",comedi_get_board_name(device));
}

void print_mismatch_message(int more)
{
  char c;

  printf("Mismatch detected!\n");
  fprintf(stderr,"A mismatch between output and input has been detected.  This can \
arise from\nseveral sources:\n  1. Hardware fault, or channels are not properly \
cross-connected.  Make sure\n     the output channels are connected to the \
corresponding input channels.\n  2. Analog channels are improperly calibrated.\n  3. \
With streaming data, there could be a timing mismatch between input and\n     output. \
This could arise from a failure of the two processes to start\n     simultaneously, \
or (if you are using two separate devices) from an\n     accumulating timing error.  \
The first problem should get worse for faster\n     scan rates; the latter should get \
worse for longer acquisitions.  Both\n     may be partly worked around by increasing \
the analog slop (option -a) or\n     slowing the speed of waveform change (option \
-w).\n  4. There's a bug in this code, comedi, or comedilib, at least for your\n     \
hardware.\n\nGood luck!\n");  if (more) {
    c = ' ';
    while (c != 'y' && c != 'n' && c != '\n') {
      fprintf(stderr,"Continue with further testing? (y/N) ");
      c = tolower(GetKey());
    }
    if (c == 'n' || c == '\n') {
      fprintf(stderr,"n\n");
      exit(0);
    }
    else
      fprintf(stderr,"%c\n",c);
  }
}

/* GetKey - Retrieves a single keystroke, un-buffered */
int GetKey( void )
{

        struct termio tio, tin;
        int Ch;
        /* Get Terminal characteresitics so we can restore them */
        ioctl( 0, TCGETA, &tio );

        /* Setup non-buffered access, with no time-out */
        tin = tio;
        tin.c_lflag &= ~(ICANON|ECHO);

        tin.c_cc[VMIN]=1;
        tin.c_cc[VTIME]=0;

        ioctl( 0, TCSETA, &tin );

        /* Now use standard character input */
        Ch = getchar();

        /* Restore terminal to previous, buffered mode */
        ioctl( 0, TCSETA, &tio );

        return( Ch );
}


_______________________________________________
comedi mailing list
comedi@comedi.org
https://cvs.comedi.org/cgi-bin/mailman/listinfo/comedi

[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic