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);
129 printf("bad option\n");
134 dev = comedi_open(fn);
141 drivername=comedi_get_driver_name(dev);
143 devicename=comedi_get_board_name(dev);
145 ad_subdev=comedi_find_subdevice_by_type(dev,COMEDI_SUBD_AI,0);
146 da_subdev=comedi_find_subdevice_by_type(dev,COMEDI_SUBD_AO,0);
147 caldac_subdev=comedi_find_subdevice_by_type(dev,COMEDI_SUBD_CALIB,0);
148 eeprom_subdev=comedi_find_subdevice_by_type(dev,COMEDI_SUBD_MEMORY,0);
150 for(i=0;i<n_drivers;i++){
151 if(!strcmp(drivers[i].name,drivername)){
152 this_board = drivers+i;
156 printf("Driver %s unknown\n",drivername);
162 if(device_status<STATUS_DONE){
163 printf("Warning: device not fully calibrated due to insufficient information\n");
164 printf("Please send this output to <ds@schleef.org>\n");
165 if(verbose<0)verbose=0;
166 if(device_status==STATUS_UNKNOWN){
172 if(device_status==STATUS_SOME){
181 printf("Driver name: %s\n",drivername);
182 printf("Device name: %s\n",devicename);
183 printf("Comedi version: %d.%d.%d\n",
184 (comedi_get_version_code(dev)>>16)&0xff,
185 (comedi_get_version_code(dev)>>8)&0xff,
186 (comedi_get_version_code(dev))&0xff);
189 if(do_reset)reset_caldacs();
190 if(do_dump)observe();
191 if(do_calibrate && do_cal)do_cal();
192 if(do_results)observe();
197 void set_target(int obs,double target)
200 lsampl_t maxdata, data;
202 range = comedi_get_range(dev,observables[obs].preobserve_insn.subdev,
203 CR_CHAN(observables[obs].preobserve_insn.chanspec),
204 CR_RANGE(observables[obs].preobserve_insn.chanspec));
205 maxdata = comedi_get_maxdata(dev,observables[obs].preobserve_insn.subdev,
206 CR_CHAN(observables[obs].preobserve_insn.chanspec));
208 data = comedi_from_phys(target,range,maxdata);
210 observables[obs].preobserve_data = data;
211 observables[obs].target = comedi_to_phys(data,range,maxdata);
218 for(i=0;i<n_observables;i++){
220 DPRINT(0,"%s\n",observables[i].name);
221 measure_observable(i);
222 observable_dependence(i);
227 void preobserve(int obs)
229 if(observables[obs].preobserve_insn.n!=0){
230 comedi_do_insn(dev,&observables[obs].preobserve_insn);
234 void measure_observable(int obs)
241 observables[obs].observe_insn.subdev,
242 CR_CHAN(observables[obs].observe_insn.chanspec),
243 CR_RANGE(observables[obs].observe_insn.chanspec),
244 CR_AREF(observables[obs].observe_insn.chanspec));
246 n=new_sv_measure(&sv);
248 sci_sprint_alt(s,sv.average,sv.error);
249 printf("offset %s, target %g\n",s,observables[obs].target);
252 void observable_dependence(int obs)
257 for(i=0;i<n_caldacs;i++){
258 check_gain_chan_x(&l,
259 observables[obs].observe_insn.chanspec,i);
265 void postgain_cal(int obs1, int obs2, int dac)
267 double offset1,offset2;
269 double slope1,slope2;
272 comedi_range *range1,*range2;
274 DPRINT(0,"postgain calibration\n");
276 check_gain_chan_x(&l,observables[obs1].observe_insn.chanspec,dac);
277 offset1=linear_fit_func_y(&l,caldacs[dac].current);
278 DPRINT(1,"obs1: [%d] offset %g\n",obs1,offset1);
279 range1 = comedi_get_range(dev,observables[obs1].observe_insn.subdev,
280 CR_CHAN(observables[obs1].observe_insn.chanspec),
281 CR_RANGE(observables[obs1].observe_insn.chanspec));
285 check_gain_chan_x(&l,observables[obs2].observe_insn.chanspec,dac);
286 offset2=linear_fit_func_y(&l,caldacs[dac].current);
287 DPRINT(1,"obs2: [%d] offset %g\n",obs2,offset2);
288 range2 = comedi_get_range(dev,observables[obs2].observe_insn.subdev,
289 CR_CHAN(observables[obs2].observe_insn.chanspec),
290 CR_RANGE(observables[obs2].observe_insn.chanspec));
293 gain = (range1->max-range1->min)/(range2->max-range2->min);
294 DPRINT(3,"range1 %g range2 %g\n", range1->max-range1->min,
295 range2->max-range2->min);
296 DPRINT(2,"gain: %g\n",gain);
298 DPRINT(2,"difference: %g\n",offset2-offset1);
300 a = (offset1-offset2)/(slope1-slope2);
301 a=caldacs[dac].current-a;
303 DPRINT(0,"caldac[%d] set to %g\n",dac,a);
305 caldacs[dac].current=rint(a);
311 measure_observable(obs1);
313 measure_observable(obs2);
317 void cal1(int obs, int dac)
327 check_gain_chan_x(&l,observables[obs].observe_insn.chanspec,dac);
328 offset=linear_fit_func_y(&l,caldacs[dac].current);
331 target = observables[obs].target;
332 a=caldacs[dac].current+(target-offset)/gain;
334 caldacs[dac].current=rint(a);
338 DPRINT(0,"caldac[%d] set to %g\n",dac,a);
340 measure_observable(obs);
344 void chan_cal(int adc,int cdac,int range,double target)
352 check_gain_chan_x(&l,CR_PACK(adc,range,AREF_OTHER),cdac);
353 offset=linear_fit_func_y(&l,caldacs[cdac].current);
356 a=caldacs[cdac].current+(target-offset)/gain;
358 caldacs[cdac].current=rint(a);
361 read_chan2(s,adc,range);
362 DPRINT(0,"caldac[%d] set to %g, offset=%s\n",cdac,a,s);
367 void channel_dependence(int adc,int range)
372 for(i=0;i<n_caldacs;i++){
373 gain=check_gain_chan(adc,range,i);
377 void caldac_dependence(int caldac)
383 gain=check_gain_chan(i,0,caldac);
387 void dump_curve(int adc,int caldac)
391 check_gain_chan_x(&l,CR_PACK(adc,0,AREF_OTHER),caldac);
395 void setup_caldacs(void)
400 printf("no calibration subdevice\n");
404 n_chan=comedi_get_n_channels(dev,caldac_subdev);
406 for(i=0;i<n_chan;i++){
407 caldacs[i].subdev=caldac_subdev;
409 caldacs[i].maxdata=comedi_get_maxdata(dev,caldac_subdev,i);
410 caldacs[i].current=0;
416 void reset_caldacs(void)
420 for(i=0;i<n_caldacs;i++){
421 caldacs[i].current=caldacs[i].maxdata/2;
426 void update_caldac(int i)
430 DPRINT(3,"update %d %d %d\n",caldacs[i].subdev,caldacs[i].chan,caldacs[i].current);
431 if(caldacs[i].current<0){
432 DPRINT(0,"caldac set out of range (%d<0)\n",caldacs[i].current);
433 caldacs[i].current=0;
435 if(caldacs[i].current>caldacs[i].maxdata){
436 DPRINT(0,"caldac set out of range (%d>%d)\n",
437 caldacs[i].current,caldacs[i].maxdata);
438 caldacs[i].current=caldacs[i].maxdata;
441 ret = comedi_data_write(dev,caldacs[i].subdev,caldacs[i].chan,0,0,
443 if(ret<0)perror("update_caldac()");
447 void check_gain(int ad_chan,int range)
451 for(i=0;i<n_caldacs;i++){
452 check_gain_chan(ad_chan,range,i);
456 double check_gain_chan(int ad_chan,int range,int cdac)
460 return check_gain_chan_x(&l,CR_PACK(ad_chan,range,AREF_OTHER),cdac);
463 double check_gain_chan_x(linear_fit_t *l,unsigned int ad_chanspec,int cdac)
472 n=caldacs[cdac].maxdata+1;
473 memset(l,0,sizeof(*l));
479 l->y_data=malloc(n*sizeof(double)/step);
480 if(l->y_data == NULL)
482 perror("comedi_calibrate");
486 orig=caldacs[cdac].current;
488 new_sv_init(&sv,dev,0,
489 CR_CHAN(ad_chanspec),
490 CR_RANGE(ad_chanspec),
491 CR_AREF(ad_chanspec));
493 caldacs[cdac].current=0;
500 for(i=0;i*step<n;i++){
501 caldacs[cdac].current=i*step;
506 l->y_data[i]=sv.average;
507 if(!isnan(sv.average)){
514 caldacs[cdac].current=orig;
517 l->yerr=sum_err/sum_err_count;
521 linear_fit_monotonic(l);
523 if(verbose>=1 || (verbose>=0 && fabs(l->slope/l->err_slope)>4.0)){
524 sci_sprint_alt(str,l->slope,l->err_slope);
525 printf("caldac[%d] gain=%s V/bit S_min=%g dof=%g\n",
526 cdac,str,l->S_min,l->dof);
527 //printf("--> %g\n",fabs(l.slope/l.err_slope));
531 static int dump_number=0;
534 printf("start dump %d\n",dump_number);
536 x=l->x0+i*l->dx-l->ave_x;
538 printf("D%d: %d %g %g %g\n",dump_number,i,y,
540 l->ave_y+l->slope*x-y);
542 printf("end dump\n");
557 int get_bipolar_lowgain(comedi_t *dev,int subdev)
561 int n_ranges = comedi_get_n_ranges(dev,subdev,0);
565 for(i=0;i<n_ranges;i++){
566 range = comedi_get_range(dev,subdev,0,i);
567 if(range->min != -range->max)continue;
577 int get_bipolar_highgain(comedi_t *dev,int subdev)
581 int n_ranges = comedi_get_n_ranges(dev,subdev,0);
582 double min = HUGE_VAL;
585 for(i=0;i<n_ranges;i++){
586 range = comedi_get_range(dev,subdev,0,i);
587 if(range->min != -range->max)continue;
597 int get_unipolar_lowgain(comedi_t *dev,int subdev)
601 int n_ranges = comedi_get_n_ranges(dev,subdev,0);
605 for(i=0;i<n_ranges;i++){
606 range = comedi_get_range(dev,subdev,0,i);
607 if(range->min != 0)continue;
618 int read_eeprom(int addr)
622 comedi_data_read(dev,eeprom_subdev,addr,0,0,&data);
627 double read_chan(int adc,int range)
633 new_sv_init(&sv,dev,0,adc,range,AREF_OTHER);
635 n=new_sv_measure(&sv);
637 sci_sprint_alt(str,sv.average,sv.error);
638 printf("chan=%d ave=%s\n",adc,str);
643 int read_chan2(char *s,int adc,int range)
648 new_sv_init(&sv,dev,0,adc,range,AREF_OTHER);
650 n=new_sv_measure(&sv);
652 return sci_sprint_alt(s,sv.average,sv.error);
655 void set_ao(comedi_t *dev,int subdev,int chan,int range,double value)
657 comedi_range *r = comedi_get_range(dev,subdev,chan,range);
658 lsampl_t maxdata = comedi_get_maxdata(dev,subdev,chan);
661 data = comedi_from_phys(value,r,maxdata);
663 comedi_data_write(dev,subdev,chan,range,AREF_GROUND,data);
667 int new_sv_init(new_sv_t *sv,comedi_t *dev,int subdev,int chan,int range,int aref)
669 memset(sv,0,sizeof(*sv));
672 //sv->t.flags=TRIG_DITHER;
677 //sv->chanlist[0]=CR_PACK(chan,range,aref);
679 sv->maxdata=comedi_get_maxdata(dev,subdev,chan);
680 sv->rng=comedi_get_range(dev,subdev,chan,range);
687 int comedi_data_read_n(comedi_t *it,unsigned int subdev,unsigned int chan,
688 unsigned int range,unsigned int aref,lsampl_t *data,unsigned int n)
695 insn.insn = INSN_READ;
698 insn.subdev = subdev;
699 insn.chanspec = CR_PACK(chan,range,aref);
700 /* enable dithering */
701 insn.chanspec |= (1<<26);
703 ret = comedi_do_insn(it,&insn);
707 printf("insn barfed: subdev=%d, chan=%d, range=%d, aref=%d, "
708 "n=%d, ret=%d, %s\n",subdev,chan,range,aref,n,ret,
714 int new_sv_measure(new_sv_t *sv)
723 data=malloc(sizeof(lsampl_t)*n);
726 perror("comedi_calibrate");
731 ret = comedi_data_read_n(dev,sv->subd,sv->chan,sv->range,
732 sv->aref,data+i,n-i);
742 a=comedi_to_phys(data[0],sv->rng,sv->maxdata);
744 x=comedi_to_phys(data[i],sv->rng,sv->maxdata);
749 sv->stddev=sqrt(n*s2-s*s)/n;
750 sv->error=sv->stddev/sqrt(n);
760 int new_sv_measure_order(new_sv_t *sv,int order)
768 data=malloc(sizeof(lsampl_t)*n);
771 perror("comedi_calibrate");
776 ret = comedi_data_read_n(dev,sv->subd,sv->chan,sv->range,
777 sv->aref,data+i,n-i);
779 printf("barf order\n");
787 a=comedi_to_phys(data[0],sv->rng,sv->maxdata);
789 x=comedi_to_phys(data[i],sv->rng,sv->maxdata);
794 sv->stddev=sqrt(n*s2-s*s)/n;
795 sv->error=sv->stddev/sqrt(n);
809 int calculate_residuals(linear_fit_t *l);
811 int linear_fit_monotonic(linear_fit_t *l)
828 if(isnan(y))continue;
830 if(l->y_data[i]<l->min)l->min=l->y_data[i];
831 if(l->y_data[i]>l->max)l->max=l->y_data[i];
838 sxp=l->sxx-l->sx*l->sx/l->s1;
840 l->ave_x=l->sx/l->s1;
841 l->ave_y=l->sy/l->s1;
842 l->slope=(l->s1*l->sxy-l->sx*l->sy)/(l->s1*l->sxx-l->sx*l->sx);
843 l->err_slope=l->yerr/sqrt(sxp);
844 l->err_ave_y=l->yerr/sqrt(l->s1);
846 calculate_residuals(l);
851 int calculate_residuals(linear_fit_t *l)
859 x=l->x0+i*l->dx-l->ave_x;
862 if(isnan(y))continue;
864 res=l->ave_y+l->slope*x-y;
868 l->S_min=sum_res2/(l->yerr*l->yerr);
874 double linear_fit_func_y(linear_fit_t *l,double x)
876 return l->ave_y+l->slope*(x-l->ave_x);
880 /* printing of scientific numbers (with errors) */
882 int sci_sprint(char *s,double x,double y)
891 errsig = floor(log10(y));
892 maxsig = floor(log10(x));
893 mindigit = pow(10,errsig);
895 if(maxsig<errsig)maxsig=errsig;
897 sigfigs = maxsig-errsig+2;
899 mantissa = x*pow(10,-maxsig);
900 error = y*pow(10,-errsig+1);
902 return sprintf(s,"%0.*f(%2.0f)e%d",sigfigs-1,mantissa,error,maxsig);
905 int sci_sprint_alt(char *s,double x,double y)
914 errsig = floor(log10(fabs(y)));
915 maxsig = floor(log10(fabs(x)));
916 mindigit = pow(10,errsig);
918 if(maxsig<errsig)maxsig=errsig;
920 sigfigs = maxsig-errsig+2;
922 mantissa = x*pow(10,-maxsig);
923 error = y*pow(10,-errsig+1);
926 return sprintf(s,"%g",x);
928 if(errsig==1 && maxsig<4 && maxsig>1){
929 return sprintf(s,"%0.0f(%2.0f)",x,error);
931 if(maxsig<=0 && maxsig>=-2){
932 return sprintf(s,"%0.*f(%2.0f)",sigfigs-1-maxsig,
933 mantissa*pow(10,maxsig),error);
935 return sprintf(s,"%0.*f(%2.0f)e%d",sigfigs-1,mantissa,error,maxsig);