comedilib/comedi_calibrate/comedi_calibrate.c
2004-03-29 01:34:15 +00:00

1642 lines
38 KiB
C

/*
A little auto-calibration utility, for boards
that support it.
copyright (C) 1999,2000,2001,2002 by David Schleef
copyright (C) 2003 by Frank Mori Hess
*/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
* published by *
* the Free Software Foundation; either version 2.1 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#define _GNU_SOURCE
#include <stdio.h>
#include "comedilib.h"
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <getopt.h>
#include <ctype.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "calib.h"
/* global variables */
int verbose = 0;
struct board_struct{
char *name;
char *id;
int (*init_setup)( calibration_setup_t *setup, const char *device_name );
};
struct board_struct drivers[] = {
{ "ni_pcimio", ni_id, ni_setup },
{ "ni_atmio", ni_id, ni_setup },
{ "ni_mio_cs", ni_id, ni_setup },
{ "cb_pcidas", cb_id, cb_setup },
{ "cb_pcidas64", cb64_id, cb64_setup },
{ "ni_labpc", ni_labpc_id, ni_labpc_setup },
};
#define n_drivers (sizeof(drivers)/sizeof(drivers[0]))
void help(void)
{
printf("comedi_calibrate [options] - autocalibrates a Comedi device\n");
printf(" --verbose, -v \n");
printf(" --quiet, -q \n");
printf(" --help, -h \n");
printf(" --file, -f [/dev/comediN] \n");
printf(" --save-file, -S [filepath] \n");
printf(" --driver-name [driver] \n");
printf(" --device-name [device] \n");
printf(" --[no-]reset \n");
printf(" --[no-]calibrate \n");
printf(" --[no-]dump \n");
printf(" --[no-]results \n");
printf(" --[no-]output \n");
}
typedef struct
{
int verbose;
char *file_path;
char *save_file_path;
char *driver_name;
char *device_name;
int do_reset;
int do_dump;
int do_calibrate;
int do_results;
int do_output;
unsigned int subdevice;
unsigned int channel;
unsigned int range;
unsigned int aref;
} parsed_options_t;
static void parse_options( int argc, char *argv[], parsed_options_t *settings )
{
int c, index;
struct option options[] = {
{ "verbose", 0, 0, 'v' },
{ "quiet", 0, 0, 'q' },
{ "file", 1, 0, 'f' },
{ "save-file", 1, 0, 'S' },
{ "help", 0, 0, 'h' },
{ "driver-name", 1, 0, 0x1000 },
{ "device-name", 1, 0, 0x1001 },
{ "reset", 0, &settings->do_reset, 1 },
{ "no-reset", 0, &settings->do_reset, 0 },
{ "calibrate", 0, &settings->do_calibrate, 1 },
{ "no-calibrate", 0, &settings->do_calibrate, 0 },
{ "dump", 0, &settings->do_dump, 1 },
{ "no-dump", 0, &settings->do_dump, 0 },
{ "results", 0, &settings->do_results, 1 },
{ "no-results", 0, &settings->do_results, 0 },
{ "output", 0, &settings->do_output, 1 },
{ "no-output", 0, &settings->do_output, 0 },
{ "subdevice", 1, 0, 's' },
{ "channel", 1, 0, 'c' },
{ "range", 1, 0, 'r' },
{ "aref", 1, 0, 'a' },
{ 0 },
};
while (1) {
c = getopt_long(argc, argv, "f:S:vqs:c:r:a:", options, &index);
if (c == -1)break;
switch (c) {
case 0:
continue;
case 'h':
help();
exit(0);
break;
case 'f':
settings->file_path = optarg;
break;
case 'S':
settings->save_file_path = optarg;
break;
case 'v':
settings->verbose++;
break;
case 'q':
settings->verbose--;
break;
case 0x1000:
settings->driver_name = optarg;
break;
case 0x1001:
settings->device_name = optarg;
break;
case 's':
settings->subdevice = strtoul( optarg, NULL, 0 );
break;
case 'c':
settings->channel = strtoul( optarg, NULL, 0 );
break;
case 'r':
settings->range = strtoul( optarg, NULL, 0 );
break;
case 'a':
settings->aref = strtoul( optarg, NULL, 0 );
break;
default:
help();
exit(1);
}
}
}
int main(int argc, char *argv[])
{
int i;
struct board_struct *this_board;
int device_status = STATUS_UNKNOWN;
calibration_setup_t setup;
int retval;
parsed_options_t options;
memset( &setup, 0, sizeof( setup ) );
setup.sv_settling_time_ns = 99999;
setup.sv_order = 10;
memset( &options, 0, sizeof( options ) );
options.do_dump = 0;
options.do_reset = 0;
options.do_calibrate = -1;
options.do_results = 0;
options.do_output = 1;
options.file_path = "/dev/comedi0";
parse_options( argc, argv, &options );
verbose = options.verbose;
setup.dev = comedi_open( options.file_path );
if( setup.dev == NULL ) {
fprintf( stderr, "comedi_open() failed, with device file name: %s\n",
options.file_path );
comedi_perror("comedi_open");
exit(0);
}
if( options.save_file_path == NULL )
options.save_file_path = comedi_get_default_calibration_path( setup.dev );
if(!options.driver_name)
options.driver_name=comedi_get_driver_name( setup.dev );
if(!options.device_name)
options.device_name=comedi_get_board_name( setup.dev );
setup.ad_subdev=comedi_find_subdevice_by_type( setup.dev,COMEDI_SUBD_AI,0);
setup.da_subdev=comedi_find_subdevice_by_type( setup.dev,COMEDI_SUBD_AO,0);
setup.caldac_subdev=comedi_find_subdevice_by_type( setup.dev,COMEDI_SUBD_CALIB,0);
setup.eeprom_subdev=comedi_find_subdevice_by_type( setup.dev,COMEDI_SUBD_MEMORY,0);
for(i=0;i<n_drivers;i++){
if(!strcmp(drivers[i].name,options.driver_name)){
this_board = drivers+i;
goto ok;
}
}
fprintf(stderr, "Driver %s unknown\n", options.driver_name);
return 1;
ok:
retval = this_board->init_setup( &setup, options.device_name );
if( retval < 0 ){
fprintf(stderr, "init_setup() failed for %s\n", options.device_name );
return 1;
}
device_status = setup.status;
if(device_status<STATUS_DONE){
printf("Warning: device may not be not fully calibrated due to "
"insufficient information.\n"
"Please file a bug report at https://bugs.comedi.org/ and attach this output.\n"
"This output will also allow comedi_calibrate to execute more\n"
"quickly in the future.\n");
if(verbose<1){
verbose=1;
printf("Forcing option: --verbose\n");
}
if(device_status==STATUS_UNKNOWN){
options.do_reset=1;
options.do_dump=1;
options.do_calibrate=0;
options.do_results=0;
printf("Forcing options: --reset --dump --no-calibrate --no-results\n");
}
if(device_status==STATUS_SOME){
options.do_reset=1;
options.do_dump=1;
options.do_calibrate=1;
options.do_results=1;
printf("Forcing options: --reset --dump --calibrate --results\n");
}
if(device_status==STATUS_GUESS){
options.do_reset=1;
options.do_dump=1;
options.do_calibrate=1;
options.do_results=1;
printf("Forcing options: --reset --dump --calibrate --results\n");
}
}
if(verbose>=0){
char *s = "$Id$";
printf("%.*s\n",(int)strlen(s)-2,s+1);
printf("Driver name: %s\n", options.driver_name);
printf("Device name: %s\n", options.device_name);
printf("%.*s\n",(int)strlen(this_board->id)-2,this_board->id+1);
printf("Comedi version: %d.%d.%d\n",
(comedi_get_version_code(setup.dev)>>16)&0xff,
(comedi_get_version_code(setup.dev)>>8)&0xff,
(comedi_get_version_code(setup.dev))&0xff);
}
if( options.do_reset == 0 )
setup.old_calibration = comedi_parse_calibration_file( options.save_file_path );
else
setup.old_calibration = NULL;
if( options.do_calibrate < 0 )
{
if( setup.old_calibration ) options.do_calibrate = 0;
else options.do_calibrate = 1;
}
setup.do_output = options.do_output;
setup.cal_save_file_path = options.save_file_path;
if(options.do_dump) observe( &setup );
if(options.do_calibrate && setup.do_cal)
{
setup.new_calibration = malloc( sizeof( comedi_calibration_t ) );
assert( setup.new_calibration );
memset( setup.new_calibration, 0, sizeof( comedi_calibration_t ) );
setup.new_calibration->driver_name = strdup( comedi_get_driver_name( setup.dev ) );
assert( setup.new_calibration->driver_name != NULL );
setup.new_calibration->board_name = strdup( comedi_get_board_name( setup.dev ) );
assert( setup.new_calibration->board_name != NULL );
retval = setup.do_cal( &setup );
if( retval < 0 )
{
fprintf( stderr, "calibration function returned error\n" );
return -1;
}
}
if(options.do_results) observe( &setup );
if( setup.old_calibration ) comedi_cleanup_calibration( setup.old_calibration );
if( setup.new_calibration ) comedi_cleanup_calibration( setup.new_calibration );
retval = comedi_apply_calibration( setup.dev, options.subdevice,
options.channel, options.range, options.aref, setup.cal_save_file_path );
if( retval < 0 )
{
DPRINT( 0, "Failed to apply " );
}else
{
DPRINT( 0, "Applied " );
}
DPRINT( 0, "calibration for subdevice %i, channel %i, range %i, aref %i\n",
options.subdevice, options.channel, options.range, options.aref );
comedi_close(setup.dev);
return retval;
}
void set_target( calibration_setup_t *setup, int obs, double target)
{
comedi_range *range;
lsampl_t maxdata, data;
comedi_set_global_oor_behavior( COMEDI_OOR_NUMBER );
range = comedi_get_range(setup->dev,
setup->observables[obs].preobserve_insn.subdev,
CR_CHAN( setup->observables[obs].preobserve_insn.chanspec ),
CR_RANGE( setup->observables[obs].preobserve_insn.chanspec ));
assert( range );
maxdata = comedi_get_maxdata( setup->dev,
setup->observables[obs].preobserve_insn.subdev,
CR_CHAN(setup->observables[obs].preobserve_insn.chanspec));
assert( maxdata > 0 );
data = comedi_from_phys(target,range,maxdata);
setup->observables[obs].preobserve_data[0] = data;
setup->observables[obs].target = comedi_to_phys(data,range,maxdata);
}
static void apply_appropriate_cal( calibration_setup_t *setup, comedi_insn insn )
{
int retval = 0;
if( setup->new_calibration )
{
retval = comedi_apply_parsed_calibration( setup->dev, insn.subdev,
CR_CHAN( insn.chanspec ), CR_RANGE( insn.chanspec ),
CR_AREF( insn.chanspec ), setup->new_calibration );
}else if( setup->old_calibration )
{
retval = comedi_apply_parsed_calibration( setup->dev, insn.subdev,
CR_CHAN( insn.chanspec ), CR_RANGE( insn.chanspec ),
CR_AREF( insn.chanspec ), setup->old_calibration );
}else
{
reset_caldacs( setup );
return;
}
if( retval < 0 )
DPRINT( 1, "failed to apply ");
else
DPRINT( 1, "applied ");
DPRINT( 1, "calibration for subdev %i, channel %i, range %i, aref %i\n", insn.subdev,
CR_CHAN( insn.chanspec ), CR_RANGE( insn.chanspec ),
CR_AREF( insn.chanspec ) );
}
void observe( calibration_setup_t *setup )
{
int i;
observable *obs;
for( i = 0; i < setup->n_observables; i++){
obs = &setup->observables[ i ];
if( obs->observe_insn.n == 0 ) continue;
preobserve( setup, i);
DPRINT(0,"%s\n", setup->observables[i].name);
if( obs->preobserve_insn.n != 0){
apply_appropriate_cal( setup, obs->preobserve_insn );
}
apply_appropriate_cal( setup, obs->observe_insn );
measure_observable( setup, i);
if(verbose>=1){
observable_dependence( setup, i);
}
}
}
int preobserve( calibration_setup_t *setup, int obs)
{
int retval;
comedi_insn reference_source_config;
lsampl_t ref_data[ 2 ];
// setup reference source
if(setup->observables[obs].reference_source >= 0)
{
memset( &reference_source_config, 0, sizeof(reference_source_config) );
reference_source_config.insn = INSN_CONFIG;
reference_source_config.n = 2;
reference_source_config.subdev = setup->ad_subdev;
reference_source_config.data = ref_data;
reference_source_config.data[ 0 ] = INSN_CONFIG_ALT_SOURCE;
reference_source_config.data[ 1 ] = setup->observables[obs].reference_source;
retval = comedi_do_insn( setup->dev, &reference_source_config );
/* ignore errors for now since older ni driver doesn't
* support reference config insn */
if( retval < 0 )
perror("preobserve() ignoring reference config error" );
}
retval = 0;
if( setup->observables[obs].preobserve_insn.n != 0){
retval = comedi_do_insn( setup->dev, &setup->observables[obs].preobserve_insn);
}
if( retval < 0 )
perror("preobserve()");
return retval;
}
void measure_observable( calibration_setup_t *setup, int obs)
{
char s[32];
int n;
new_sv_t sv;
my_sv_init(&sv, setup,
setup->observables[obs].observe_insn.subdev,
setup->observables[obs].observe_insn.chanspec);
n = new_sv_measure(setup->dev, &sv);
sci_sprint_alt(s,sv.average,sv.error);
DPRINT(0,"reading %s, target %g\n",s, setup->observables[obs].target);
assert( isnan( setup->observables[obs].target) == 0 );
}
void observable_dependence(calibration_setup_t *setup, int obs)
{
int i;
linear_fit_t l;
for( i = 0; i < setup->n_caldacs; i++){
check_gain_chan_x( setup, &l,
setup->observables[obs].observe_insn.chanspec, i);
}
}
void postgain_cal( calibration_setup_t *setup, int obs1, int obs2, int dac)
{
double offset1,offset2;
linear_fit_t l;
double slope1,slope2;
double a;
double gain;
comedi_range *range1,*range2;
DPRINT(0,"postgain: %s; %s\n", setup->observables[obs1].name,
setup->observables[obs2].name);
preobserve(setup, obs1);
check_gain_chan_x( setup, &l, setup->observables[obs1].observe_insn.chanspec, dac);
offset1=linear_fit_func_y(&l, setup->caldacs[dac].current);
DPRINT(2,"obs1: [%d] offset %g\n",obs1,offset1);
range1 = comedi_get_range(setup->dev, setup->observables[obs1].observe_insn.subdev,
CR_CHAN( setup->observables[obs1].observe_insn.chanspec),
CR_RANGE( setup->observables[obs1].observe_insn.chanspec));
slope1=l.slope;
preobserve( setup, obs2);
check_gain_chan_x( setup, &l, setup->observables[obs2].observe_insn.chanspec,dac);
offset2=linear_fit_func_y(&l, setup->caldacs[dac].current);
DPRINT(2,"obs2: [%d] offset %g\n",obs2,offset2);
range2 = comedi_get_range(setup->dev, setup->observables[obs2].observe_insn.subdev,
CR_CHAN( setup->observables[obs2].observe_insn.chanspec),
CR_RANGE( setup->observables[obs2].observe_insn.chanspec));
slope2=l.slope;
gain = (range1->max-range1->min)/(range2->max-range2->min);
DPRINT(4,"range1 %g range2 %g\n", range1->max-range1->min,
range2->max-range2->min);
DPRINT(3,"gain: %g\n",gain);
DPRINT(3,"difference: %g\n",offset2-offset1);
a = (offset1-offset2)/(slope1-slope2);
a=setup->caldacs[dac].current-a;
update_caldac( setup, dac, rint(a) );
usleep(caldac_settle_usec);
DPRINT(0,"caldac[%d] set to %g (%g)\n",dac,rint(a),a);
if(verbose>=2){
preobserve( setup, obs1);
measure_observable( setup, obs1);
preobserve( setup, obs2);
measure_observable( setup, obs2);
}
}
void cal1( calibration_setup_t *setup, int obs, int dac)
{
linear_fit_t l;
double a;
DPRINT(0,"linear: %s\n", setup->observables[obs].name);
preobserve( setup, obs);
check_gain_chan_x( setup, &l, setup->observables[obs].observe_insn.chanspec,dac);
a=linear_fit_func_x(&l, setup->observables[obs].target);
update_caldac( setup, dac, rint(a) );
usleep(caldac_settle_usec);
DPRINT(0,"caldac[%d] set to %g (%g)\n",dac,rint(a),a);
if(verbose>=3){
measure_observable( setup, obs);
}
}
void cal1_fine( calibration_setup_t *setup, int obs, int dac )
{
linear_fit_t l;
double a;
DPRINT(0,"linear fine: %s\n", setup->observables[obs].name);
preobserve( setup, obs);
check_gain_chan_fine( setup, &l, setup->observables[obs].observe_insn.chanspec,dac);
a=linear_fit_func_x(&l,setup->observables[obs].target);
update_caldac( setup, dac, rint(a) );
usleep(caldac_settle_usec);
DPRINT(0,"caldac[%d] set to %g (%g)\n",dac,rint(a),a);
if(verbose>=3){
measure_observable( setup, obs);
}
}
void peg_binary( calibration_setup_t *setup, int obs, int dac, int maximize )
{
int x0, x1, x;
double y0, y1;
new_sv_t sv;
unsigned int chanspec = setup->observables[obs].observe_insn.chanspec;
int polarity;
DPRINT(0,"binary peg: %s\n", setup->observables[obs].name);
preobserve( setup, obs);
comedi_set_global_oor_behavior( COMEDI_OOR_NUMBER );
my_sv_init(&sv, setup, setup->ad_subdev, chanspec);
x0 = setup->caldacs[dac].maxdata;
update_caldac( setup, dac, x0 );
usleep(caldac_settle_usec);
new_sv_measure( setup->dev, &sv);
y0 = sv.average;
x1 = 0;
update_caldac( setup, dac, x1 );
usleep(caldac_settle_usec);
new_sv_measure( setup->dev, &sv);
y1 = sv.average;
if( (y0 - y1) > 0.0 ) polarity = 1;
else polarity = -1;
if( maximize )
{
if( polarity > 0 ) x = x0;
else x = x1;
}else
{
if( polarity > 0 ) x = x1;
else x = x0;
}
update_caldac( setup, dac, x );
DPRINT(0,"caldac[%d] set to %d\n",dac,x);
if(verbose>=3){
measure_observable( setup, obs);
}
}
void cal_binary( calibration_setup_t *setup, int obs, int dac)
{
int x0, x1, x2, x;
unsigned int bit;
double y0, y1, y2;
new_sv_t sv;
double target = setup->observables[obs].target;
unsigned int chanspec = setup->observables[obs].observe_insn.chanspec;
int polarity;
DPRINT(0,"binary: %s\n", setup->observables[obs].name);
preobserve( setup, obs);
comedi_set_global_oor_behavior( COMEDI_OOR_NUMBER );
my_sv_init(&sv, setup, setup->ad_subdev, chanspec);
x0 = setup->caldacs[dac].maxdata;
update_caldac( setup, dac, x0 );
usleep(caldac_settle_usec);
new_sv_measure( setup->dev, &sv);
y0 = sv.average;
x1 = x2 = 0;
update_caldac( setup, dac, x1 );
usleep(caldac_settle_usec);
new_sv_measure( setup->dev, &sv);
y1 = y2 = sv.average;
if( (y0 - y1) > 0.0 ) polarity = 1;
else polarity = -1;
bit = 1;
while( ( bit << 1 ) < setup->caldacs[dac].maxdata )
bit <<= 1;
for( ; bit; bit >>= 1 ){
x2 = x1 | bit;
update_caldac( setup, dac, x2 );
usleep(caldac_settle_usec);
new_sv_measure( setup->dev, &sv);
y2 = sv.average;
DPRINT(3,"trying %d, result %g, target %g\n",x2,y2,target);
if( (y2 - target) * polarity < 0.0 ){
x1 = x2;
y1 = y2;
}
if(verbose>=3){
measure_observable( setup, obs);
}
}
// get that least signficant bit right
if( fabs( y1 - target ) < fabs( y2 - target ) )
x = x1;
else x = x2;
update_caldac( setup, dac, x );
DPRINT(0,"caldac[%d] set to %d\n",dac,x);
if( x >= setup->caldacs[dac].maxdata || x <= 0 )
DPRINT(0,"WARNING: caldac[%d] pegged!\n", dac );
if(verbose>=3){
measure_observable( setup, obs);
}
}
void cal_postgain_binary( calibration_setup_t *setup, int obs1, int obs2, int dac)
{
cal_relative_binary( setup, obs1, obs2, dac );
}
void cal_relative_binary( calibration_setup_t *setup, int obs1, int obs2, int dac)
{
int x0, x1, x2, x, polarity;
double y0, y1, y2;
new_sv_t sv1, sv2;
double target = setup->observables[obs1].target - setup->observables[obs2].target;
unsigned int chanspec1 = setup->observables[obs1].observe_insn.chanspec;
unsigned int chanspec2 = setup->observables[obs2].observe_insn.chanspec;
unsigned int bit;
DPRINT(0,"relative binary: %s, %s\n", setup->observables[obs1].name,
setup->observables[obs2].name);
comedi_set_global_oor_behavior( COMEDI_OOR_NUMBER );
x0 = setup->caldacs[dac].maxdata;
update_caldac( setup, dac, x0 );
usleep(caldac_settle_usec);
preobserve( setup, obs1);
my_sv_init(&sv1, setup, setup->ad_subdev,chanspec1);
new_sv_measure( setup->dev, &sv1);
preobserve( setup, obs2);
my_sv_init(&sv2, setup, setup->ad_subdev,chanspec2);
new_sv_measure( setup->dev, &sv2);
y0 = sv1.average - sv2.average;
x1 = x2 = 0;
update_caldac( setup, dac, x1 );
usleep(caldac_settle_usec);
preobserve( setup, obs1);
new_sv_measure( setup->dev, &sv1);
preobserve( setup, obs2);
new_sv_measure( setup->dev, &sv2);
y1 = y2 = sv1.average - sv2.average;
if( (y0 - y1) > 0.0 ) polarity = 1;
else polarity = -1;
bit = 1;
while( ( bit << 1 ) < setup->caldacs[dac].maxdata )
bit <<= 1;
for( ; bit; bit >>= 1 )
{
x2 = x1 | bit;
update_caldac( setup, dac, x2 );
usleep(caldac_settle_usec);
preobserve( setup, obs1);
new_sv_measure( setup->dev, &sv1);
preobserve( setup, obs2);
new_sv_measure( setup->dev, &sv2);
y2 = sv1.average - sv2.average;
DPRINT(3,"trying %d, result %g, target %g\n",x2,y2,target);
if( (y2 - target) * polarity < 0.0 ){
x1 = x2;
y1 = y2;
}
if(verbose>=3){
preobserve( setup, obs1);
measure_observable( setup, obs1);
preobserve( setup, obs2);
measure_observable( setup, obs2);
}
}
if( fabs( y1 - target ) < fabs( y2 - target ) )
x = x1;
else
x = x2;
update_caldac( setup, dac, x );
DPRINT(0,"caldac[%d] set to %d\n",dac,x);
if( x >= setup->caldacs[dac].maxdata || x <= 0 )
DPRINT(0,"WARNING: caldac[%d] pegged!\n", dac );
if(verbose>=3){
preobserve( setup, obs1);
measure_observable( setup, obs1);
preobserve( setup, obs2);
measure_observable( setup, obs2);
}
}
void cal_linearity_binary( calibration_setup_t *setup, int obs1, int obs2, int obs3, int dac)
{
int x0, x1, x2, x, polarity;
double y0, y1, y2;
new_sv_t sv1, sv2, sv3;
double target = ( setup->observables[obs3].target - setup->observables[obs2].target ) /
( setup->observables[obs2].target - setup->observables[obs1].target );
unsigned int chanspec1 = setup->observables[obs1].observe_insn.chanspec;
unsigned int chanspec2 = setup->observables[obs2].observe_insn.chanspec;
unsigned int chanspec3 = setup->observables[obs3].observe_insn.chanspec;
unsigned int bit;
DPRINT(0,"linearity binary: %s,\n%s,\n%s\n", setup->observables[obs1].name,
setup->observables[obs2].name,setup->observables[obs3].name);
comedi_set_global_oor_behavior( COMEDI_OOR_NUMBER );
x0 = setup->caldacs[dac].maxdata;
update_caldac( setup, dac, x0 );
usleep(caldac_settle_usec);
preobserve( setup, obs1);
my_sv_init(&sv1, setup, setup->ad_subdev,chanspec1);
new_sv_measure( setup->dev, &sv1);
preobserve( setup, obs2);
my_sv_init(&sv2, setup, setup->ad_subdev,chanspec2);
new_sv_measure( setup->dev, &sv2);
preobserve( setup, obs3);
my_sv_init(&sv3, setup, setup->ad_subdev,chanspec3);
new_sv_measure( setup->dev, &sv3);
y0 = ( sv3.average - sv2.average ) / ( sv2.average - sv1.average );
x1 = x2 = 0;
update_caldac( setup, dac, x1 );
usleep(caldac_settle_usec);
preobserve( setup, obs1);
new_sv_measure( setup->dev, &sv1);
preobserve( setup, obs2);
new_sv_measure( setup->dev, &sv2);
preobserve( setup, obs3);
new_sv_measure( setup->dev, &sv3);
y1 = y2 = ( sv3.average - sv2.average ) / ( sv2.average - sv1.average );
if( (y0 - y1) > 0.0 ) polarity = 1;
else polarity = -1;
bit = 1;
while( ( bit << 1 ) < setup->caldacs[dac].maxdata )
bit <<= 1;
for( ; bit; bit >>= 1 )
{
x2 = x1 | bit;
update_caldac( setup, dac, x2 );
usleep(caldac_settle_usec);
preobserve( setup, obs1);
new_sv_measure( setup->dev, &sv1);
preobserve( setup, obs2);
new_sv_measure( setup->dev, &sv2);
preobserve( setup, obs3);
new_sv_measure( setup->dev, &sv3);
y2 = ( sv3.average - sv2.average ) / ( sv2.average - sv1.average );
DPRINT(3,"trying %d, result %g, target %g\n",x2,y2,target);
if( (y2 - target) * polarity < 0.0 ){
x1 = x2;
y1 = y2;
}
if(verbose>=3){
preobserve( setup, obs1);
measure_observable( setup, obs1);
preobserve( setup, obs2);
measure_observable( setup, obs2);
preobserve( setup, obs3);
measure_observable( setup, obs3);
}
}
if( fabs( y1 - target ) < fabs( y2 - target ) )
x = x1;
else
x = x2;
update_caldac( setup, dac, x );
DPRINT(0,"caldac[%d] set to %d\n",dac,x);
if( x >= setup->caldacs[dac].maxdata || x <= 0 )
DPRINT(0,"WARNING: caldac[%d] pegged!\n", dac );
if(verbose>=3){
preobserve( setup, obs1);
measure_observable( setup, obs1);
preobserve( setup, obs2);
measure_observable( setup, obs2);
preobserve( setup, obs3);
measure_observable( setup, obs3);
}
}
#if 0
void chan_cal(int adc,int cdac,int range,double target)
{
linear_fit_t l;
double offset;
double gain;
double a;
char s[32];
check_gain_chan_x(&l,CR_PACK(adc,range,AREF_OTHER),cdac);
offset=linear_fit_func_y(&l,caldacs[cdac].current);
gain=l.slope;
a=caldacs[cdac].current+(target-offset)/gain;
update_caldac( setup, cdac, rint(a));
read_chan2(s,adc,range);
DPRINT(1,"caldac[%d] set to %g, offset=%s\n",cdac,a,s);
}
#endif
#if 0
void channel_dependence(int adc,int range)
{
int i;
double gain;
for(i=0;i<n_caldacs;i++){
gain=check_gain_chan(adc,range,i);
}
}
#endif
#if 0
void caldac_dependence(int caldac)
{
int i;
double gain;
for(i=0;i<8;i++){
gain=check_gain_chan(i,0,caldac);
}
}
#endif
void setup_caldacs( calibration_setup_t *setup, int caldac_subdev )
{
int n_chan, i;
if( caldac_subdev < 0 ){
printf("no calibration subdevice\n");
return;
}
// XXX check subdevice type is really calibration
n_chan = comedi_get_n_channels( setup->dev, caldac_subdev );
assert(n_chan >= 0);
assert(setup->n_caldacs + n_chan < N_CALDACS);
for(i = 0; i < n_chan; i++){
setup->caldacs[ setup->n_caldacs + i ].subdev = caldac_subdev;
setup->caldacs[ setup->n_caldacs + i ].chan = i;
setup->caldacs[ setup->n_caldacs + i ].maxdata = comedi_get_maxdata(setup->dev, caldac_subdev, i);
assert(setup->caldacs[ setup->n_caldacs + i ].maxdata > 0);
setup->caldacs[ setup->n_caldacs + i ].current=0;
}
setup->n_caldacs += n_chan;
}
void reset_caldac( calibration_setup_t *setup, int caldac_index )
{
if( caldac_index < 0 ) return;
assert( caldac_index < setup->n_caldacs );
update_caldac( setup, caldac_index, setup->caldacs[ caldac_index ].maxdata / 2 );
}
void reset_caldacs( calibration_setup_t *setup )
{
int i;
for( i = 0; i < setup->n_caldacs; i++){
reset_caldac( setup, i );
}
}
void update_caldac( calibration_setup_t *setup, int caldac_index,
int value )
{
int ret;
caldac_t *dac;
if( caldac_index < 0 ) return;
if( caldac_index > setup->n_caldacs )
{
fprintf( stderr, "invalid caldac index\n" );
return;
}
dac = &setup->caldacs[ caldac_index ];
dac->current = value;
DPRINT(4,"update %d %d %d\n", dac->subdev, dac->chan, dac->current);
if( dac->current < 0 ){
DPRINT(1,"caldac set out of range (%d<0)\n", dac->current);
dac->current = 0;
}
if( dac->current > dac->maxdata ){
DPRINT(1,"caldac set out of range (%d>%d)\n",
dac->current, dac->maxdata);
dac->current = dac->maxdata;
}
ret = comedi_data_write( setup->dev, dac->subdev, dac->chan, 0, 0,
dac->current);
if(ret < 0) perror("update_caldac()");
}
#if 0
void check_gain(int ad_chan,int range)
{
int i;
for(i=0;i<n_caldacs;i++){
check_gain_chan(ad_chan,range,i);
}
}
#endif
#if 0
double check_gain_chan(int ad_chan,int range,int cdac)
{
linear_fit_t l;
return check_gain_chan_x(&l,CR_PACK(ad_chan,range,AREF_OTHER),cdac);
}
#endif
double check_gain_chan_x( calibration_setup_t *setup, linear_fit_t *l,unsigned int ad_chanspec,int cdac)
{
int orig,i,n;
int step;
new_sv_t sv;
double sum_err;
int sum_err_count=0;
char str[20];
n = setup->caldacs[cdac].maxdata+1;
memset(l,0,sizeof(*l));
step=n/16;
if(step<1)step=1;
l->n=0;
l->y_data=malloc(n*sizeof(double)/step);
if(l->y_data == NULL)
{
perror( __FUNCTION__ );
exit(1);
}
orig = setup->caldacs[cdac].current;
my_sv_init(&sv, setup, setup->ad_subdev,ad_chanspec);
update_caldac( setup, cdac, 0 );
usleep(caldac_settle_usec);
new_sv_measure( setup->dev, &sv);
sum_err=0;
for(i=0;i*step<n;i++){
update_caldac( setup, cdac, i*step );
usleep(caldac_settle_usec);
new_sv_measure( setup->dev, &sv);
l->y_data[i]=sv.average;
if(!isnan(sv.average)){
sum_err+=sv.error;
sum_err_count++;
}
l->n++;
}
update_caldac( setup, cdac, orig );
l->yerr=sum_err/sqrt(sum_err_count);
l->dx=step;
l->x0=0;
linear_fit_monotonic(l);
if(verbose>=2 || (verbose>=1 && fabs(l->slope/l->err_slope)>4.0)){
sci_sprint_alt(str,l->slope,l->err_slope);
printf("caldac[%d] gain=%s V/bit S_min=%g dof=%g\n",
cdac,str,l->S_min,l->dof);
//printf("--> %g\n",fabs(l.slope/l.err_slope));
}
if(verbose>=3)dump_curve(l);
free(l->y_data);
return l->slope;
}
double check_gain_chan_fine( calibration_setup_t *setup, linear_fit_t *l,unsigned int ad_chanspec,int cdac)
{
int orig,i,n;
int step;
new_sv_t sv;
double sum_err;
int sum_err_count=0;
char str[20];
int fine_size = 10;
n=2*fine_size+1;
memset(l,0,sizeof(*l));
step=1;
l->n=0;
l->y_data=malloc(n*sizeof(double)/step);
if(l->y_data == NULL)
{
perror( __FUNCTION__ );
exit(1);
}
orig = setup->caldacs[cdac].current;
my_sv_init(&sv, setup, setup->ad_subdev,ad_chanspec);
update_caldac( setup, cdac, 0 );
usleep(caldac_settle_usec);
new_sv_measure( setup->dev, &sv);
sum_err=0;
for(i=0;i<n;i++){
update_caldac( setup, cdac, i+orig-fine_size );
usleep(caldac_settle_usec);
new_sv_measure( setup->dev, &sv);
l->y_data[i]=sv.average;
if(!isnan(sv.average)){
sum_err+=sv.error;
sum_err_count++;
}
l->n++;
}
update_caldac( setup, cdac, orig );
l->yerr=sum_err/sqrt(sum_err_count);
l->dx=1;
l->x0=orig-fine_size;
linear_fit_monotonic(l);
if(verbose>=2 || (verbose>=1 && fabs(l->slope/l->err_slope)>4.0)){
sci_sprint_alt(str,l->slope,l->err_slope);
printf("caldac[%d] gain=%s V/bit S_min=%g dof=%g\n",
cdac,str,l->S_min,l->dof);
//printf("--> %g\n",fabs(l.slope/l.err_slope));
}
if(verbose>=3)dump_curve(l);
free(l->y_data);
return l->slope;
}
/* helpers */
int is_unipolar( comedi_t *dev, unsigned int subdevice,
unsigned int channel, unsigned int range )
{
comedi_range *range_ptr;
range_ptr = comedi_get_range( dev, subdevice, channel, range );
assert( range_ptr != NULL );
/* This method is better than a direct test, which might fail */
if( fabs( range_ptr->min ) < fabs( range_ptr->max * 0.001 ) )
return 1;
else
return 0;
}
int is_bipolar( comedi_t *dev, unsigned int subdevice,
unsigned int channel, unsigned int range )
{
comedi_range *range_ptr;
range_ptr = comedi_get_range( dev, subdevice, channel, range );
assert( range_ptr != NULL );
/* This method is better than a direct test, which might fail */
if( fabs( range_ptr->max + range_ptr->min ) < fabs( range_ptr->max * 0.001 ) )
return 1;
else
return 0;
}
int get_bipolar_lowgain(comedi_t *dev,int subdev)
{
int ret = -1;
int i;
int n_ranges = comedi_get_n_ranges(dev,subdev,0);
double max = 0;
comedi_range *range;
for(i=0;i<n_ranges;i++){
range = comedi_get_range(dev,subdev,0,i);
if( is_bipolar( dev, subdev, 0, i ) == 0 ) continue;
if(range->unit == UNIT_none ) continue;
if(range->max>max){
ret = i;
max=range->max;
}
}
return ret;
}
int get_bipolar_highgain(comedi_t *dev,int subdev)
{
int ret = -1;
int i;
int n_ranges = comedi_get_n_ranges(dev,subdev,0);
double min = HUGE_VAL;
comedi_range *range;
for(i=0;i<n_ranges;i++){
range = comedi_get_range(dev,subdev,0,i);
if( is_bipolar( dev, subdev, 0, i ) == 0 ) continue;
if(range->unit == UNIT_none ) continue;
if(range->max<min){
ret = i;
min=range->max;
}
}
return ret;
}
int get_unipolar_lowgain(comedi_t *dev,int subdev)
{
int ret = -1;
int i;
int n_ranges = comedi_get_n_ranges(dev,subdev,0);
double max = 0.0;
comedi_range *range;
for(i=0;i<n_ranges;i++){
range = comedi_get_range(dev,subdev,0,i);
if( is_unipolar( dev, subdev, 0, i ) == 0 ) continue;
if(range->unit == UNIT_none ) continue;
if(range->max>max){
ret = i;
max=range->max;
}
}
return ret;
}
int get_unipolar_highgain(comedi_t *dev,int subdev)
{
int ret = -1;
int i;
int n_ranges = comedi_get_n_ranges(dev,subdev,0);
double max = HUGE_VAL;
comedi_range *range;
for(i=0;i<n_ranges;i++){
range = comedi_get_range(dev,subdev,0,i);
if( is_unipolar( dev, subdev, 0, i ) == 0 ) continue;
if(range->unit == UNIT_none ) continue;
if(range->max < max){
ret = i;
max=range->max;
}
}
return ret;
}
int read_eeprom( calibration_setup_t *setup, int addr)
{
lsampl_t data = 0;
int retval;
retval = comedi_data_read( setup->dev, setup->eeprom_subdev, addr,0,0,&data);
if( retval < 0 )
{
perror( "read_eeprom()" );
return retval;
}
return data;
}
double read_chan( calibration_setup_t *setup, int adc,int range)
{
int n;
new_sv_t sv;
char str[20];
my_sv_init(&sv, setup, setup->ad_subdev,CR_PACK(adc,range,AREF_OTHER));
n=new_sv_measure( setup->dev, &sv);
sci_sprint_alt(str,sv.average,sv.error);
printf("chan=%d ave=%s\n",adc,str);
return sv.average;
}
int read_chan2( calibration_setup_t *setup, char *s,int adc,int range)
{
int n;
new_sv_t sv;
my_sv_init(&sv, setup, setup->ad_subdev,CR_PACK(adc,range,AREF_OTHER));
n=new_sv_measure( setup->dev, &sv);
return sci_sprint_alt(s,sv.average,sv.error);
}
#if 0
void set_ao(comedi_t *dev,int subdev,int chan,int range,double value)
{
comedi_range *r = comedi_get_range(dev,subdev,chan,range);
lsampl_t maxdata = comedi_get_maxdata(dev,subdev,chan);
lsampl_t data;
data = comedi_from_phys(value,r,maxdata);
comedi_data_write(dev,subdev,chan,range,AREF_GROUND,data);
}
#endif
int my_sv_init( new_sv_t *sv, const calibration_setup_t *setup, int subdev,
unsigned int chanspec )
{
int retval;
retval = new_sv_init( sv, setup->dev, subdev, chanspec );
sv->settling_time_ns = setup->sv_settling_time_ns;
sv->order = setup->sv_order;
return retval;
}
int new_sv_init(new_sv_t *sv,comedi_t *dev,int subdev,unsigned int chanspec)
{
memset(sv,0,sizeof(*sv));
sv->subd=subdev;
//sv->t.flags=TRIG_DITHER;
sv->chanspec = chanspec;
//sv->chanlist[0]=CR_PACK(chan,range,aref);
sv->maxdata=comedi_get_maxdata(dev,subdev,CR_CHAN(chanspec));
sv->rng=comedi_get_range(dev,subdev,
CR_CHAN(chanspec), CR_RANGE(chanspec));
sv->order=10;
return 0;
}
int new_sv_measure( comedi_t *dev, new_sv_t *sv)
{
lsampl_t *data;
int n,i,ret;
double x,s,s2;
n=1<<sv->order;
data=malloc(sizeof(lsampl_t)*n);
if(data == NULL)
{
perror( __FUNCTION__ );
exit(1);
}
ret = comedi_data_read_hint(dev, sv->subd, sv->chanspec, 0, 0);
if(ret<0){
printf("hint barf\n");
goto out;
}
comedi_nanodelay(dev, sv->settling_time_ns);
ret = comedi_data_read_n(dev, sv->subd, sv->chanspec, 0, 0, data, n);
if(ret<0){
printf("barf\n");
goto out;
}
s=0.0;
s2=0.0;
for(i = 0; i < n; i++){
x = comedi_to_phys(data[i], sv->rng, sv->maxdata);
s += x;
s2 += x * x;
}
s /= n;
s2 /= n;
sv->average=s;
sv->stddev=sqrt( ( ( n + 1 ) / n ) * ( s2 - s * s ) );
sv->error=sv->stddev / sqrt( n );
ret=n;
out:
free(data);
return ret;
}
int new_sv_measure_order( comedi_t *dev, new_sv_t *sv,int order)
{
lsampl_t *data;
int n,i,ret;
double x,s,s2;
n=1<<order;
data=malloc(sizeof(lsampl_t)*n);
if(data == NULL)
{
perror( __FUNCTION__ );
exit(1);
}
ret = comedi_data_read_n(dev, sv->subd, sv->chanspec, 0, 0, data, n);
if(ret<0){
printf("barf order\n");
goto out;
}
s=0;
s2=0;
for(i = 0; i < n; i++){
x = comedi_to_phys(data[i], sv->rng, sv->maxdata);
s += x;
s2 += x * x;
}
s /= n;
s2 /= n;
sv->average = s;
sv->stddev=sqrt( ( ( n + 1 ) / n ) * ( s2 - s * s ) );
sv->error=sv->stddev / sqrt( n );
ret=n;
out:
free(data);
return ret;
}
/* linear fitting */
int calculate_residuals(linear_fit_t *l);
int linear_fit_monotonic(linear_fit_t *l)
{
double x,y;
double sxp;
int i;
l->min=HUGE_VAL;
l->max=-HUGE_VAL;
l->s1=0;
l->sx=0;
l->sy=0;
l->sxy=0;
l->sxx=0;
for(i=0;i<l->n;i++){
x=l->x0+i*l->dx;
y=l->y_data[i];
if(isnan(y))continue;
if(l->y_data[i]<l->min)l->min=l->y_data[i];
if(l->y_data[i]>l->max)l->max=l->y_data[i];
l->s1+=1;
l->sx+=x;
l->sy+=y;
l->sxy+=x*y;
l->sxx+=x*x;
}
sxp=l->sxx-l->sx*l->sx/l->s1;
l->ave_x=l->sx/l->s1;
l->ave_y=l->sy/l->s1;
l->slope=(l->s1*l->sxy-l->sx*l->sy)/(l->s1*l->sxx-l->sx*l->sx);
l->err_slope=l->yerr/sqrt(sxp);
l->err_ave_y=l->yerr/sqrt(l->s1);
calculate_residuals(l);
return 0;
}
int calculate_residuals(linear_fit_t *l)
{
double x,y;
double res,sum_res2;
int i;
sum_res2=0;
for(i=0;i<l->n;i++){
x=l->x0+i*l->dx-l->ave_x;
y=l->y_data[i];
if(isnan(y))continue;
res=l->ave_y+l->slope*x-y;
sum_res2+=res*res;
}
l->S_min=sum_res2/(l->yerr*l->yerr);
l->dof=l->s1-2;
return 0;
}
double linear_fit_func_y(linear_fit_t *l,double x)
{
return l->ave_y+l->slope*(x-l->ave_x);
}
double linear_fit_func_x(linear_fit_t *l,double y)
{
return l->ave_x+(y-l->ave_y)/l->slope;
}
void dump_curve(linear_fit_t *l)
{
static int dump_number=0;
double x,y;
int i;
printf("start dump %d\n",dump_number);
for(i=0;i<l->n;i++){
x=l->x0+i*l->dx-l->ave_x;
y=l->y_data[i];
printf("D%d: %d %g %g %g\n",dump_number,i,y,
l->ave_y+l->slope*x,
l->ave_y+l->slope*x-y);
}
printf("end dump\n");
dump_number++;
}
/* printing of scientific numbers (with errors) */
int sci_sprint(char *s,double x,double y)
{
int errsig;
int maxsig;
int sigfigs;
double mantissa;
double error;
double mindigit;
errsig = floor(log10(y));
maxsig = floor(log10(x));
mindigit = pow(10,errsig);
if(maxsig<errsig)maxsig=errsig;
sigfigs = maxsig-errsig+2;
mantissa = x*pow(10,-maxsig);
error = y*pow(10,-errsig+1);
return sprintf(s,"%0.*f(%2.0f)e%d",sigfigs-1,mantissa,error,maxsig);
}
int sci_sprint_alt(char *s,double x,double y)
{
int errsig;
int maxsig;
int sigfigs;
double mantissa;
double error;
double mindigit;
errsig = floor(log10(fabs(y)));
maxsig = floor(log10(fabs(x)));
mindigit = pow(10,errsig);
if(maxsig<errsig)maxsig=errsig;
sigfigs = maxsig-errsig+2;
mantissa = x*pow(10,-maxsig);
error = y*pow(10,-errsig+1);
if(isnan(x)){
return sprintf(s,"%g",x);
}
if(errsig==1 && maxsig<4 && maxsig>1){
return sprintf(s,"%0.0f(%2.0f)",x,error);
}
if(maxsig<=0 && maxsig>=-2){
return sprintf(s,"%0.*f(%2.0f)",sigfigs-1-maxsig,
mantissa*pow(10,maxsig),error);
}
return sprintf(s,"%0.*f(%2.0f)e%d",sigfigs-1,mantissa,error,maxsig);
}
double very_low_target( comedi_t *dev, unsigned int subdevice,
unsigned int channel, unsigned int range )
{
comedi_range *range_ptr;
int max_data;
range_ptr = comedi_get_range( dev, subdevice, channel, range );
assert( range_ptr != NULL );
max_data = comedi_get_maxdata( dev, subdevice, 0 );
assert( max_data > 0 );
return comedi_to_phys( 1, range_ptr, max_data ) / 2.0;
}
double fractional_offset( calibration_setup_t *setup, int subdevice,
unsigned int channel, unsigned int range, int obs )
{
comedi_range *range_ptr;
double target;
double reading;
unsigned int chanspec;
new_sv_t sv;
if( subdevice < 0 || obs < 0 ) return 0.0;
chanspec = setup->observables[obs].observe_insn.chanspec;
target = setup->observables[obs].target;
range_ptr = comedi_get_range( setup->dev, subdevice, channel, range );
assert( range_ptr != NULL );
comedi_set_global_oor_behavior( COMEDI_OOR_NUMBER );
preobserve( setup, obs);
my_sv_init( &sv, setup, setup->ad_subdev, chanspec );
new_sv_measure( setup->dev, &sv );
reading = sv.average;
return ( reading - target ) / ( range_ptr->max - range_ptr->min );
}
double get_tolerance( calibration_setup_t *setup, int subdevice,
double num_bits )
{
int maxdata;
if( subdevice < 0 ) return INFINITY;
maxdata = comedi_get_maxdata( setup->dev, subdevice, 0 );
assert( maxdata > 0 );
return num_bits / maxdata;
}