2 A little auto-calibration utility, for boards
5 Right now, it only supports NI E series boards,
6 but it should be easily portable.
8 A few things need improvement here:
9 - current system gets "close", but doesn't
11 - no pre/post gain discrimination for the
13 - should read (and use) the actual reference
14 voltage value from eeprom
15 - statistics would be nice, to show how good
17 - doesn't check unipolar ranges
18 - "alternate calibrations" would be cool--to
19 accurately measure 0 in a unipolar range
26 #include <comedilib.h>
38 /* global variables */
40 caldac caldacs[N_CALDACS];
43 observable observables[N_OBSERVABLES];
53 char *drivername = NULL;
54 char *devicename = NULL;
58 int device_status = STATUS_UNKNOWN;
68 struct board_struct drivers[] = {
69 { "ni_pcimio", ni_setup },
70 { "ni_atmio", ni_setup },
71 { "ni_mio_cs", ni_setup },
73 #define n_drivers (sizeof(drivers)/sizeof(drivers[0]))
81 struct option options[] = {
82 { "verbose", 0, 0, 'v' },
83 { "quiet", 0, 0, 'q' },
84 { "file", 1, 0, 'f' },
85 { "driver-name", 1, 0, 0x1000 },
86 { "device-name", 1, 0, 0x1001 },
87 { "reset", 0, &do_reset, 1 },
88 { "no-reset", 0, &do_reset, 0 },
89 { "calibrate", 0, &do_calibrate, 1 },
90 { "no-calibrate", 0, &do_calibrate, 0 },
91 { "dump", 0, &do_dump, 1 },
92 { "no-dump", 0, &do_dump, 0 },
93 { "results", 0, &do_results, 1 },
94 { "no-results", 0, &do_results, 0 },
95 { "output", 0, &do_output, 1 },
96 { "no-output", 0, &do_output, 0 },
100 int main(int argc, char *argv[])
105 struct board_struct *this_board;
110 c = getopt_long(argc, argv, "f:vq", options, &index);
131 printf("bad option %d\n",c);
136 dev = comedi_open(fn);
143 drivername=comedi_get_driver_name(dev);
145 devicename=comedi_get_board_name(dev);
147 ad_subdev=comedi_find_subdevice_by_type(dev,COMEDI_SUBD_AI,0);
148 da_subdev=comedi_find_subdevice_by_type(dev,COMEDI_SUBD_AO,0);
149 caldac_subdev=comedi_find_subdevice_by_type(dev,COMEDI_SUBD_CALIB,0);
150 eeprom_subdev=comedi_find_subdevice_by_type(dev,COMEDI_SUBD_MEMORY,0);
152 for(i=0;i<n_drivers;i++){
153 if(!strcmp(drivers[i].name,drivername)){
154 this_board = drivers+i;
158 printf("Driver %s unknown\n",drivername);
164 if(device_status<STATUS_DONE){
165 printf("Warning: device not fully calibrated due to insufficient information\n");
166 printf("Please send this output to <ds@schleef.org>\n");
167 if(verbose<1)verbose=1;
168 if(device_status==STATUS_UNKNOWN){
174 if(device_status==STATUS_SOME){
180 if(device_status==STATUS_GUESS){
189 printf("Driver name: %s\n",drivername);
190 printf("Device name: %s\n",devicename);
191 printf("Comedi version: %d.%d.%d\n",
192 (comedi_get_version_code(dev)>>16)&0xff,
193 (comedi_get_version_code(dev)>>8)&0xff,
194 (comedi_get_version_code(dev))&0xff);
197 if(do_reset)reset_caldacs();
198 if(do_dump)observe();
199 if(do_calibrate && do_cal)do_cal();
200 if(do_results)observe();
205 void set_target(int obs,double target)
208 lsampl_t maxdata, data;
210 range = comedi_get_range(dev,observables[obs].preobserve_insn.subdev,
211 CR_CHAN(observables[obs].preobserve_insn.chanspec),
212 CR_RANGE(observables[obs].preobserve_insn.chanspec));
213 maxdata = comedi_get_maxdata(dev,observables[obs].preobserve_insn.subdev,
214 CR_CHAN(observables[obs].preobserve_insn.chanspec));
216 data = comedi_from_phys(target,range,maxdata);
218 observables[obs].preobserve_data = data;
219 observables[obs].target = comedi_to_phys(data,range,maxdata);
226 for(i=0;i<n_observables;i++){
228 DPRINT(0,"%s\n",observables[i].name);
229 measure_observable(i);
231 observable_dependence(i);
237 void preobserve(int obs)
239 if(observables[obs].preobserve_insn.n!=0){
240 comedi_do_insn(dev,&observables[obs].preobserve_insn);
244 void measure_observable(int obs)
251 observables[obs].observe_insn.subdev,
252 CR_CHAN(observables[obs].observe_insn.chanspec),
253 CR_RANGE(observables[obs].observe_insn.chanspec),
254 CR_AREF(observables[obs].observe_insn.chanspec));
256 n=new_sv_measure(&sv);
258 sci_sprint_alt(s,sv.average,sv.error);
259 DPRINT(0,"offset %s, target %g\n",s,observables[obs].target);
262 void observable_dependence(int obs)
267 for(i=0;i<n_caldacs;i++){
268 check_gain_chan_x(&l,
269 observables[obs].observe_insn.chanspec,i);
275 void postgain_cal(int obs1, int obs2, int dac)
277 double offset1,offset2;
279 double slope1,slope2;
282 comedi_range *range1,*range2;
284 DPRINT(0,"postgain: %s; %s\n",observables[obs1].name,
285 observables[obs2].name);
287 check_gain_chan_x(&l,observables[obs1].observe_insn.chanspec,dac);
288 offset1=linear_fit_func_y(&l,caldacs[dac].current);
289 DPRINT(2,"obs1: [%d] offset %g\n",obs1,offset1);
290 range1 = comedi_get_range(dev,observables[obs1].observe_insn.subdev,
291 CR_CHAN(observables[obs1].observe_insn.chanspec),
292 CR_RANGE(observables[obs1].observe_insn.chanspec));
296 check_gain_chan_x(&l,observables[obs2].observe_insn.chanspec,dac);
297 offset2=linear_fit_func_y(&l,caldacs[dac].current);
298 DPRINT(2,"obs2: [%d] offset %g\n",obs2,offset2);
299 range2 = comedi_get_range(dev,observables[obs2].observe_insn.subdev,
300 CR_CHAN(observables[obs2].observe_insn.chanspec),
301 CR_RANGE(observables[obs2].observe_insn.chanspec));
304 gain = (range1->max-range1->min)/(range2->max-range2->min);
305 DPRINT(4,"range1 %g range2 %g\n", range1->max-range1->min,
306 range2->max-range2->min);
307 DPRINT(3,"gain: %g\n",gain);
309 DPRINT(3,"difference: %g\n",offset2-offset1);
311 a = (offset1-offset2)/(slope1-slope2);
312 a=caldacs[dac].current-a;
314 caldacs[dac].current=rint(a);
318 DPRINT(0,"caldac[%d] set to %g (%g)\n",dac,rint(a),a);
322 measure_observable(obs1);
324 measure_observable(obs2);
328 void cal1(int obs, int dac)
333 DPRINT(0,"linear: %s\n",observables[obs].name);
335 check_gain_chan_x(&l,observables[obs].observe_insn.chanspec,dac);
336 a=linear_fit_func_x(&l,observables[obs].target);
338 caldacs[dac].current=rint(a);
342 DPRINT(0,"caldac[%d] set to %g (%g)\n",dac,rint(a),a);
344 measure_observable(obs);
348 void cal1_fine(int obs, int dac)
353 DPRINT(0,"linear fine: %s\n",observables[obs].name);
355 check_gain_chan_fine(&l,observables[obs].observe_insn.chanspec,dac);
356 a=linear_fit_func_x(&l,observables[obs].target);
358 caldacs[dac].current=rint(a);
362 DPRINT(0,"caldac[%d] set to %g (%g)\n",dac,rint(a),a);
364 measure_observable(obs);
369 void chan_cal(int adc,int cdac,int range,double target)
377 check_gain_chan_x(&l,CR_PACK(adc,range,AREF_OTHER),cdac);
378 offset=linear_fit_func_y(&l,caldacs[cdac].current);
381 a=caldacs[cdac].current+(target-offset)/gain;
383 caldacs[cdac].current=rint(a);
386 read_chan2(s,adc,range);
387 DPRINT(1,"caldac[%d] set to %g, offset=%s\n",cdac,a,s);
393 void channel_dependence(int adc,int range)
398 for(i=0;i<n_caldacs;i++){
399 gain=check_gain_chan(adc,range,i);
405 void caldac_dependence(int caldac)
411 gain=check_gain_chan(i,0,caldac);
417 void setup_caldacs(void)
422 printf("no calibration subdevice\n");
426 n_chan=comedi_get_n_channels(dev,caldac_subdev);
428 for(i=0;i<n_chan;i++){
429 caldacs[i].subdev=caldac_subdev;
431 caldacs[i].maxdata=comedi_get_maxdata(dev,caldac_subdev,i);
432 caldacs[i].current=0;
438 void reset_caldacs(void)
442 for(i=0;i<n_caldacs;i++){
443 caldacs[i].current=caldacs[i].maxdata/2;
448 void update_caldac(int i)
452 DPRINT(4,"update %d %d %d\n",caldacs[i].subdev,caldacs[i].chan,caldacs[i].current);
453 if(caldacs[i].current<0){
454 DPRINT(1,"caldac set out of range (%d<0)\n",caldacs[i].current);
455 caldacs[i].current=0;
457 if(caldacs[i].current>caldacs[i].maxdata){
458 DPRINT(1,"caldac set out of range (%d>%d)\n",
459 caldacs[i].current,caldacs[i].maxdata);
460 caldacs[i].current=caldacs[i].maxdata;
463 ret = comedi_data_write(dev,caldacs[i].subdev,caldacs[i].chan,0,0,
465 if(ret<0)perror("update_caldac()");
469 void check_gain(int ad_chan,int range)
473 for(i=0;i<n_caldacs;i++){
474 check_gain_chan(ad_chan,range,i);
480 double check_gain_chan(int ad_chan,int range,int cdac)
484 return check_gain_chan_x(&l,CR_PACK(ad_chan,range,AREF_OTHER),cdac);
489 double check_gain_chan_x(linear_fit_t *l,unsigned int ad_chanspec,int cdac)
498 n=caldacs[cdac].maxdata+1;
499 memset(l,0,sizeof(*l));
505 l->y_data=malloc(n*sizeof(double)/step);
506 if(l->y_data == NULL)
508 perror("comedi_calibrate");
512 orig=caldacs[cdac].current;
514 new_sv_init(&sv,dev,0,
515 CR_CHAN(ad_chanspec),
516 CR_RANGE(ad_chanspec),
517 CR_AREF(ad_chanspec));
519 caldacs[cdac].current=0;
526 for(i=0;i*step<n;i++){
527 caldacs[cdac].current=i*step;
533 l->y_data[i]=sv.average;
534 if(!isnan(sv.average)){
541 caldacs[cdac].current=orig;
544 l->yerr=sum_err/sum_err_count;
548 linear_fit_monotonic(l);
550 if(verbose>=2 || (verbose>=1 && fabs(l->slope/l->err_slope)>4.0)){
551 sci_sprint_alt(str,l->slope,l->err_slope);
552 printf("caldac[%d] gain=%s V/bit S_min=%g dof=%g\n",
553 cdac,str,l->S_min,l->dof);
554 //printf("--> %g\n",fabs(l.slope/l.err_slope));
557 if(verbose>=3)dump_curve(l);
565 double check_gain_chan_fine(linear_fit_t *l,unsigned int ad_chanspec,int cdac)
576 memset(l,0,sizeof(*l));
581 l->y_data=malloc(n*sizeof(double)/step);
582 if(l->y_data == NULL)
584 perror("comedi_calibrate");
588 orig=caldacs[cdac].current;
590 new_sv_init(&sv,dev,0,
591 CR_CHAN(ad_chanspec),
592 CR_RANGE(ad_chanspec),
593 CR_AREF(ad_chanspec));
595 caldacs[cdac].current=0;
603 caldacs[cdac].current=i+orig-fine_size;
609 l->y_data[i]=sv.average;
610 if(!isnan(sv.average)){
617 caldacs[cdac].current=orig;
620 l->yerr=sum_err/sum_err_count;
622 l->x0=orig-fine_size;
624 linear_fit_monotonic(l);
626 if(verbose>=2 || (verbose>=1 && fabs(l->slope/l->err_slope)>4.0)){
627 sci_sprint_alt(str,l->slope,l->err_slope);
628 printf("caldac[%d] gain=%s V/bit S_min=%g dof=%g\n",
629 cdac,str,l->S_min,l->dof);
630 //printf("--> %g\n",fabs(l.slope/l.err_slope));
633 if(verbose>=3)dump_curve(l);
644 int get_bipolar_lowgain(comedi_t *dev,int subdev)
648 int n_ranges = comedi_get_n_ranges(dev,subdev,0);
652 for(i=0;i<n_ranges;i++){
653 range = comedi_get_range(dev,subdev,0,i);
654 if(range->min != -range->max)continue;
664 int get_bipolar_highgain(comedi_t *dev,int subdev)
668 int n_ranges = comedi_get_n_ranges(dev,subdev,0);
669 double min = HUGE_VAL;
672 for(i=0;i<n_ranges;i++){
673 range = comedi_get_range(dev,subdev,0,i);
674 if(range->min != -range->max)continue;
684 int get_unipolar_lowgain(comedi_t *dev,int subdev)
688 int n_ranges = comedi_get_n_ranges(dev,subdev,0);
692 for(i=0;i<n_ranges;i++){
693 range = comedi_get_range(dev,subdev,0,i);
694 if(range->min != 0)continue;
705 int read_eeprom(int addr)
709 comedi_data_read(dev,eeprom_subdev,addr,0,0,&data);
714 double read_chan(int adc,int range)
720 new_sv_init(&sv,dev,0,adc,range,AREF_OTHER);
722 n=new_sv_measure(&sv);
724 sci_sprint_alt(str,sv.average,sv.error);
725 printf("chan=%d ave=%s\n",adc,str);
730 int read_chan2(char *s,int adc,int range)
735 new_sv_init(&sv,dev,0,adc,range,AREF_OTHER);
737 n=new_sv_measure(&sv);
739 return sci_sprint_alt(s,sv.average,sv.error);
743 void set_ao(comedi_t *dev,int subdev,int chan,int range,double value)
745 comedi_range *r = comedi_get_range(dev,subdev,chan,range);
746 lsampl_t maxdata = comedi_get_maxdata(dev,subdev,chan);
749 data = comedi_from_phys(value,r,maxdata);
751 comedi_data_write(dev,subdev,chan,range,AREF_GROUND,data);
756 int new_sv_init(new_sv_t *sv,comedi_t *dev,int subdev,int chan,int range,int aref)
758 memset(sv,0,sizeof(*sv));
761 //sv->t.flags=TRIG_DITHER;
766 //sv->chanlist[0]=CR_PACK(chan,range,aref);
768 sv->maxdata=comedi_get_maxdata(dev,subdev,chan);
769 sv->rng=comedi_get_range(dev,subdev,chan,range);
776 int comedi_data_read_n(comedi_t *it,unsigned int subdev,unsigned int chan,
777 unsigned int range,unsigned int aref,lsampl_t *data,unsigned int n)
784 insn.insn = INSN_READ;
787 insn.subdev = subdev;
788 insn.chanspec = CR_PACK(chan,range,aref);
789 /* enable dithering */
790 insn.chanspec |= (1<<26);
792 ret = comedi_do_insn(it,&insn);
796 printf("insn barfed: subdev=%d, chan=%d, range=%d, aref=%d, "
797 "n=%d, ret=%d, %s\n",subdev,chan,range,aref,n,ret,
803 int new_sv_measure(new_sv_t *sv)
812 data=malloc(sizeof(lsampl_t)*n);
815 perror("comedi_calibrate");
820 ret = comedi_data_read_n(dev,sv->subd,sv->chan,sv->range,
821 sv->aref,data+i,n-i);
831 a=comedi_to_phys(data[0],sv->rng,sv->maxdata);
833 x=comedi_to_phys(data[i],sv->rng,sv->maxdata);
838 sv->stddev=sqrt(n*s2-s*s)/n;
839 sv->error=sv->stddev/sqrt(n);
849 int new_sv_measure_order(new_sv_t *sv,int order)
857 data=malloc(sizeof(lsampl_t)*n);
860 perror("comedi_calibrate");
865 ret = comedi_data_read_n(dev,sv->subd,sv->chan,sv->range,
866 sv->aref,data+i,n-i);
868 printf("barf order\n");
876 a=comedi_to_phys(data[0],sv->rng,sv->maxdata);
878 x=comedi_to_phys(data[i],sv->rng,sv->maxdata);
883 sv->stddev=sqrt(n*s2-s*s)/n;
884 sv->error=sv->stddev/sqrt(n);
898 int calculate_residuals(linear_fit_t *l);
900 int linear_fit_monotonic(linear_fit_t *l)
917 if(isnan(y))continue;
919 if(l->y_data[i]<l->min)l->min=l->y_data[i];
920 if(l->y_data[i]>l->max)l->max=l->y_data[i];
927 sxp=l->sxx-l->sx*l->sx/l->s1;
929 l->ave_x=l->sx/l->s1;
930 l->ave_y=l->sy/l->s1;
931 l->slope=(l->s1*l->sxy-l->sx*l->sy)/(l->s1*l->sxx-l->sx*l->sx);
932 l->err_slope=l->yerr/sqrt(sxp);
933 l->err_ave_y=l->yerr/sqrt(l->s1);
935 calculate_residuals(l);
940 int calculate_residuals(linear_fit_t *l)
948 x=l->x0+i*l->dx-l->ave_x;
951 if(isnan(y))continue;
953 res=l->ave_y+l->slope*x-y;
957 l->S_min=sum_res2/(l->yerr*l->yerr);
963 double linear_fit_func_y(linear_fit_t *l,double x)
965 return l->ave_y+l->slope*(x-l->ave_x);
968 double linear_fit_func_x(linear_fit_t *l,double y)
970 return l->ave_x+(y-l->ave_y)/l->slope;
973 void dump_curve(linear_fit_t *l)
975 static int dump_number=0;
979 printf("start dump %d\n",dump_number);
981 x=l->x0+i*l->dx-l->ave_x;
983 printf("D%d: %d %g %g %g\n",dump_number,i,y,
985 l->ave_y+l->slope*x-y);
987 printf("end dump\n");
992 /* printing of scientific numbers (with errors) */
994 int sci_sprint(char *s,double x,double y)
1003 errsig = floor(log10(y));
1004 maxsig = floor(log10(x));
1005 mindigit = pow(10,errsig);
1007 if(maxsig<errsig)maxsig=errsig;
1009 sigfigs = maxsig-errsig+2;
1011 mantissa = x*pow(10,-maxsig);
1012 error = y*pow(10,-errsig+1);
1014 return sprintf(s,"%0.*f(%2.0f)e%d",sigfigs-1,mantissa,error,maxsig);
1017 int sci_sprint_alt(char *s,double x,double y)
1026 errsig = floor(log10(fabs(y)));
1027 maxsig = floor(log10(fabs(x)));
1028 mindigit = pow(10,errsig);
1030 if(maxsig<errsig)maxsig=errsig;
1032 sigfigs = maxsig-errsig+2;
1034 mantissa = x*pow(10,-maxsig);
1035 error = y*pow(10,-errsig+1);
1038 return sprintf(s,"%g",x);
1040 if(errsig==1 && maxsig<4 && maxsig>1){
1041 return sprintf(s,"%0.0f(%2.0f)",x,error);
1043 if(maxsig<=0 && maxsig>=-2){
1044 return sprintf(s,"%0.*f(%2.0f)",sigfigs-1-maxsig,
1045 mantissa*pow(10,maxsig),error);
1047 return sprintf(s,"%0.*f(%2.0f)e%d",sigfigs-1,mantissa,error,maxsig);