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>
50 static caldac caldacs[N_CALDACS];
55 static int eeprom_subdev;
57 double read_chan(int adc,int range);
58 void write_caldac(comedi_t *dev,int subdev,int addr,int val);
59 void check_gain(int ad_chan,int range);
60 double check_gain_chan(int ad_chan,int range,int cdac);
65 void update_caldac(int i);
66 void reset_caldacs(void);
67 void setup_caldacs(void);
68 void cal_ni_mio_E(void);
69 void ni_mio_ai_postgain_cal(void);
70 void ni_mio_ai_postgain_cal_2(int chan,int dac,int range_lo,int range_hi,double gain);
71 void channel_dependence(int adc,int range);
72 void caldac_dependence(int caldac);
73 void dump_curve(int adc,int caldac);
74 void chan_cal(int adc,int caldac,int range,double target);
75 int read_eeprom(int addr);
89 double s1,sx,sy,sxy,sxx;
103 int linear_fit_monotonic(linear_fit_t *l);
104 double linear_fit_func_y(linear_fit_t *l,double x);
105 double check_gain_chan_x(linear_fit_t *l,int ad_chan,int range,int cdac);
125 int new_sv_measure(new_sv_t *sv);
126 int new_sv_init(new_sv_t *sv,comedi_t *dev,int subdev,int chan,int range,int aref);
130 void (*calibrate)(void);
133 void cal_ni_16e_1(void);
134 void cal_ni_16e_10(void);
135 void cal_ni_16xe_50(void);
136 void cal_ni_16xe_10(void);
137 void cal_ni_6023e(void);
138 void cal_ni_daqcard_ai_16xe_50(void);
139 void cal_ni_unknown(void);
141 struct board_struct boards[]={
142 { "at-mio-16e-1", cal_ni_16e_1 },
143 { "at-mio-16e-2", cal_ni_16e_1 },
144 { "at-mio-16e-10", cal_ni_16e_10 },
145 { "at-mio-16de-10", cal_ni_unknown },
146 { "at-mio-64e-3", cal_ni_16e_1 },
147 { "at-mio-16xe-50", cal_ni_unknown },
148 { "at-mio-16xe-10", cal_ni_unknown },
149 { "at-ai-16xe-10", cal_ni_unknown },
150 { "pci-mio-16xe-50", cal_ni_16xe_50 },
151 { "pci-mio-16xe-10", cal_ni_16xe_10 },
152 { "pxi-6030e", cal_ni_unknown },
153 { "pci-mio-16e-1", cal_ni_16e_1 },
154 { "pci-mio-16e-4", cal_ni_unknown },
155 { "pxi-6040e", cal_ni_unknown },
156 { "pci-6031e", cal_ni_unknown },
157 { "pci-6032e", cal_ni_unknown },
158 { "pci-6033e", cal_ni_unknown },
159 { "pci-6071e", cal_ni_unknown },
160 { "pci-6023e", cal_ni_6023e },
161 { "pci-6024e", cal_ni_unknown },
162 { "pci-6025e", cal_ni_unknown },
163 { "pxi-6025e", cal_ni_unknown },
164 { "pci-6034e", cal_ni_unknown },
165 { "pci-6035e", cal_ni_unknown },
166 { "pci-6052e", cal_ni_unknown },
167 { "pci-6110e", cal_ni_unknown },
168 { "pci-6111e", cal_ni_unknown },
169 // { "pci-6711", cal_ni_unknown },
170 // { "pci-6713", cal_ni_unknown },
171 { "pxi-6071e", cal_ni_unknown },
172 { "pxi-6070e", cal_ni_unknown },
173 { "pxi-6052e", cal_ni_unknown },
174 { "DAQCard-ai-16xe-50", cal_ni_daqcard_ai_16xe_50 },
175 { "DAQCard-ai-16e-4", cal_ni_unknown },
176 { "DAQCard-6062e", cal_ni_unknown },
177 { "DAQCard-6024e", cal_ni_unknown },
179 #define n_boards (sizeof(boards)/sizeof(boards[0]))
181 int main(int argc, char *argv[])
191 c = getopt(argc, argv, "f:vq");
205 printf("bad option\n");
210 dev = comedi_open(fn);
219 eeprom_subdev=comedi_find_subdevice_by_type(dev,COMEDI_SUBD_MEMORY,0);
221 drivername=comedi_get_driver_name(dev);
222 devicename=comedi_get_board_name(dev);
224 for(i=0;i<n_boards;i++){
225 if(!strcmp(boards[i].name,devicename)){
226 boards[i].calibrate();
231 printf("device %s unknown\n",devicename);
236 double ni_get_reference(int lsb_loc,int msb_loc)
242 lsb=read_eeprom(lsb_loc);
243 msb=read_eeprom(msb_loc);
244 printf("lsb=%d msb=%d\n",read_eeprom(425),read_eeprom(426));
247 if(uv>=0x8000)uv-=0x10000;
249 printf("ref=%g\n",ref);
254 void cal_ni_16e_1(void)
263 0 AI pre-gain offset 1.5e-6
264 1 AI post-gain offset 8.1e-4
265 2 AI unipolar offset 7.9e-4
267 4 AO 0 -1.2e-4 -1.2e-4
268 5 AO 0 -8.0e-4 -8.0e-4
269 6 AO 0 1.9e-4 -3.8e-7
270 7 AO 1 -8.0e-5 -1.2e-4
271 8 AO 1 -7.9e-4 -7.9e-4
276 printf("last factory calibration %02d/%02d/%02d\n",
277 read_eeprom(508),read_eeprom(507),read_eeprom(506));
279 ref=ni_get_reference(425,426);
281 printf("postgain offset\n");
282 ni_mio_ai_postgain_cal();
284 printf("pregain offset\n");
288 printf("unipolar offset\n");
292 printf("gain offset\n");
296 printf("ao 0 offset\n");
297 comedi_data_write(dev,1,0,0,0,2048);
301 printf("ao 0 gain\n");
302 comedi_data_write(dev,1,0,0,0,3072);
305 comedi_data_write(dev,1,0,0,0,2048);
309 void cal_ni_16e_10(void)
317 0 AI pre-gain offset 1.5e-6
318 1 AI post-gain offset 8.1e-4
319 2 AI unipolar offset 7.9e-4
327 10 AI pre-gain offset 6.4e-5
330 printf("last factory calibration %02d/%02d/%02d\n",
331 read_eeprom(508),read_eeprom(507),read_eeprom(506));
333 ref=ni_get_reference(423,424);
337 printf("postgain offset\n");
338 ni_mio_ai_postgain_cal();
340 printf("pregain offset\n");
341 chan_cal(0,10,7,0.0);
345 printf("unipolar offset\n");
349 printf("gain offset\n");
353 printf("results (offset)\n");
359 void cal_ni_16xe_10(void)
365 * results of channel dependency test:
367 * [0] [1] [2] [3] [8]
368 * offset, lo 1.9e-4* 2.2e-6 2.4e-7
369 * offset, hi 2.0e-6* 2.1e-8 2.7e-7
370 * offset, unip 1.9e-4 2.1e-6 3.9e-7
371 * ref -2.3e-5*-1.3e-6*1.9e-4* 2.1e-6* 3.2e-7
373 * thus, 2,3 are postgain offset, 8 is pregain, and
374 * 0,1 is gain. Note the suspicious lack of unipolar
381 * 2 AI postgain offset 1.9e-4
382 * 3 AI postgain offset 2.2e-6
387 * 8 AI pregain offset 2.4e-7
391 printf("last factory calibration %02d/%02d/%02d\n",
392 read_eeprom(508),read_eeprom(507),read_eeprom(506));
394 ref=ni_get_reference(430,431);
398 printf("postgain offset\n");
399 ni_mio_ai_postgain_cal_2(0,2,0,6,100.0);
400 ni_mio_ai_postgain_cal_2(0,3,0,6,100.0);
402 printf("pregain offset\n");
406 //printf("unipolar offset\n");
407 //chan_cal(0,2,8,0.0);
408 //chan_cal(0,2,8,0.0);
410 printf("gain offset\n");
415 printf("results (offset)\n");
421 void cal_ni_daqcard_ai_16xe_50(void)
427 * results of channel dependency test:
429 * [0] [1] [2] [3] [8]
430 * offset, lo -2.2e-6 1.5e-4* 2.5e-7
431 * offset, hi 7.8e-7* 1.3e-7
432 * offset, unip 7.4e-4 1.1e-5 1.5e-4 5.5e-7
433 * ref -3.7e-4 -5.4e-6 1.5e-4* 5.5e-7
435 * thus, 2 is postgain offset, 8 is pregain, 0 is
436 * unipolar offset, 1 is gain
440 * 0 AI unipolar offset 7.4e-4
442 * 2 AI postgain offset 1.5e-4
448 * 8 AI pregain offset 2.5e-7
452 printf("last factory calibration %02d/%02d/%02d\n",
453 read_eeprom(508),read_eeprom(507),read_eeprom(506));
455 ref=ni_get_reference(446,447);
459 printf("postgain offset\n");
460 ni_mio_ai_postgain_cal_2(0,2,0,3,100.0);
462 printf("pregain offset\n");
466 printf("unipolar offset\n");
470 printf("gain offset\n");
474 printf("results (offset)\n");
480 void cal_ni_16xe_50(void)
486 * results of channel dependency test:
488 * [0] [1] [2] [3] [8]
489 * offset, lo 1.6e-5 2.0e-7
490 * offset, hi 1.6e-7 1.8e-7
492 * ref -4.5e-5 -2.9e-6 1.6e-5* 5.5e-7
494 * thus, 2 is postgain offset, 8 is pregain, 0 is
495 * unipolar offset, 1 is gain
499 * 0 AI unipolar offset 7.4e-4
501 * 2 AI postgain offset 1.5e-4
507 * 8 AI pregain offset 2.5e-7
511 printf("last factory calibration %02d/%02d/%02d\n",
512 read_eeprom(508),read_eeprom(507),read_eeprom(506));
514 ref=ni_get_reference(437,438);
518 printf("postgain offset\n");
519 ni_mio_ai_postgain_cal_2(0,2,0,3,100.0);
521 printf("pregain offset\n");
526 printf("unipolar offset\n");
531 printf("gain offset\n");
536 printf("results (offset)\n");
542 void cal_ni_6023e(void)
548 * results of channel dependency test:
551 * offset, lo -2.8e-9 -7.6e-4
552 * offset, hi -2.0e-6 -3.8e-6 -1.4e-6
553 * offset, unip 1.0e-1*
554 * ref -7.6e-7 -7.6e-4 -5.6e-4 -6.2e-8
555 * ref2 -6.3e-8 -7.5e-4 -5.6e-4 -1.5e-8
557 * 0 is pregain offset
558 * 1 is postgain offset
563 * 0 AI pregain offset -2.0e-6
564 * 1 AI postgain offset -7.6e-4
577 //int unipolar_offset_ad = 1;
579 int pregain_offset_dac = 0;
580 int postgain_offset_dac = 1;
583 printf("last factory calibration %02d/%02d/%02d\n",
584 read_eeprom(508),read_eeprom(507),read_eeprom(506));
586 ref=ni_get_reference(444,443);
590 printf("postgain offset\n");
591 ni_mio_ai_postgain_cal_2(offset_ad,postgain_offset_dac,0,3,200.0);
593 printf("pregain offset\n");
594 chan_cal(offset_ad,pregain_offset_dac,3,0.0);
595 chan_cal(offset_ad,pregain_offset_dac,3,0.0);
597 printf("gain offset\n");
598 chan_cal(gain_ad,gain_dac,0,5.0);
599 chan_cal(gain_ad,gain_dac,0,5.0);
601 printf("results (offset)\n");
607 void cal_ni_unknown(void)
612 printf("Please send this output to <ds@schleef.org>\n");
614 printf("Device name: %s\n",comedi_get_board_name(dev));
615 printf("Comedi version: %d.%d.%d\n",
616 (comedi_get_version_code(dev)>>16)&0xff,
617 (comedi_get_version_code(dev)>>8)&0xff,
618 (comedi_get_version_code(dev))&0xff);
620 n_ranges=comedi_get_n_ranges(dev,ad_subdev,0);
622 /* 0 offset, low gain */
623 printf("channel dependence 0 range 0\n");
624 channel_dependence(0,0);
626 /* 0 offset, high gain */
627 printf("channel dependence 0 range %d\n",n_ranges/2-1);
628 channel_dependence(0,n_ranges/2-1);
630 /* unip/bip offset */
631 printf("channel dependence 0 range %d\n",n_ranges/2);
632 channel_dependence(0,n_ranges/2);
634 /* voltage reference */
635 printf("channel dependence 5 range 0\n");
636 channel_dependence(5,0);
641 void ni_mio_ai_postgain_cal(void)
649 check_gain_chan_x(&l,0,0,1);
650 offset_r0=linear_fit_func_y(&l,caldacs[1].current);
651 printf("offset r0 %g\n",offset_r0);
653 check_gain_chan_x(&l,0,7,1);
654 offset_r7=linear_fit_func_y(&l,caldacs[1].current);
655 printf("offset r7 %g\n",offset_r7);
659 a=(offset_r0-offset_r7)/(200.0-1.0);
660 a=caldacs[1].current-a/gain;
664 caldacs[1].current=rint(a);
668 void ni_mio_ai_postgain_cal_2(int chan,int dac,int range_lo,int range_hi,double gain)
670 double offset_lo,offset_hi;
675 check_gain_chan_x(&l,chan,range_lo,dac);
676 offset_lo=linear_fit_func_y(&l,caldacs[dac].current);
677 printf("offset lo %g\n",offset_lo);
679 check_gain_chan_x(&l,chan,range_hi,dac);
680 offset_hi=linear_fit_func_y(&l,caldacs[dac].current);
681 printf("offset hi %g\n",offset_hi);
685 a=(offset_lo-offset_hi)/(gain-1.0);
686 a=caldacs[dac].current-a/slope;
690 caldacs[dac].current=rint(a);
694 void chan_cal(int adc,int cdac,int range,double target)
701 check_gain_chan_x(&l,adc,range,cdac);
702 offset=linear_fit_func_y(&l,caldacs[cdac].current);
703 printf("offset %g\n",offset);
706 a=caldacs[cdac].current+(target-offset)/gain;
709 caldacs[cdac].current=rint(a);
715 void channel_dependence(int adc,int range)
720 for(i=0;i<n_caldacs;i++){
721 gain=check_gain_chan(adc,range,i);
725 void caldac_dependence(int caldac)
731 gain=check_gain_chan(i,0,caldac);
735 void dump_curve(int adc,int caldac)
739 check_gain_chan_x(&l,adc,0,caldac);
743 void setup_caldacs(void)
747 s=comedi_find_subdevice_by_type(dev,COMEDI_SUBD_CALIB,0);
749 printf("no calibration subdevice\n");
752 //printf("calibration subdevice is %d\n",s);
754 n_chan=comedi_get_n_channels(dev,s);
756 for(i=0;i<n_chan;i++){
759 caldacs[i].maxdata=comedi_get_maxdata(dev,s,i);
760 caldacs[i].current=0;
761 //printf("caldac %d, %d\n",i,caldacs[i].maxdata);
767 void reset_caldacs(void)
771 for(i=0;i<n_caldacs;i++){
772 caldacs[i].current=caldacs[i].maxdata/2;
777 void update_caldac(int i)
781 //printf("update %d %d %d\n",caldacs[i].subdev,caldacs[i].chan,caldacs[i].current);
782 //ret=comedi_data_write(dev,caldacs[i].subdev,caldacs[i].chan,0,0,caldacs[i].current);
783 write_caldac(dev,caldacs[i].subdev,caldacs[i].chan,caldacs[i].current);
786 printf("comedi_data_write() error\n");
790 void check_gain(int ad_chan,int range)
794 for(i=0;i<n_caldacs;i++){
795 check_gain_chan(ad_chan,range,i);
799 double check_gain_chan(int ad_chan,int range,int cdac)
803 return check_gain_chan_x(&l,ad_chan,range,cdac);
806 double check_gain_chan_x(linear_fit_t *l,int ad_chan,int range,int cdac)
814 n=caldacs[cdac].maxdata+1;
815 memset(l,0,sizeof(*l));
821 l->y_data=malloc(n*sizeof(double)/step);
822 if(l->y_data == NULL)
824 perror("comedi_calibrate");
828 orig=caldacs[cdac].current;
830 new_sv_init(&sv,dev,0,ad_chan,range,AREF_OTHER);
832 caldacs[cdac].current=0;
839 for(i=0;i*step<n;i++){
840 caldacs[cdac].current=i*step;
845 l->y_data[i]=sv.average;
846 if(!isnan(sv.average)){
853 caldacs[cdac].current=orig;
856 l->yerr=sum_err/sum_err_count;
860 linear_fit_monotonic(l);
862 printf("caldac[%d] gain=%g V/bit err=%g S_min=%g dof=%g min=%g max=%g\n",
863 cdac,l->slope,l->err_slope,l->S_min,l->dof,l->min,l->max);
864 //printf("--> %g\n",fabs(l.slope/l.err_slope));
867 static int dump_number=0;
870 printf("start dump %d\n",dump_number);
872 x=l->x0+i*l->dx-l->ave_x;
874 printf("D%d: %d %g %g %g\n",dump_number,i,y,
876 l->ave_y+l->slope*x-y);
878 printf("end dump\n");
894 int read_eeprom(int addr)
898 comedi_data_read(dev,eeprom_subdev,addr,0,0,&data);
903 double read_chan(int adc,int range)
908 new_sv_init(&sv,dev,0,adc,range,AREF_OTHER);
910 n=new_sv_measure(&sv);
912 printf("chan=%d ave=%g error=%g\n",adc,sv.average,sv.error);
917 void write_caldac(comedi_t *dev,int subdev,int addr,int val)
919 comedi_data_write(dev,subdev,addr,0,0,val);
924 int new_sv_init(new_sv_t *sv,comedi_t *dev,int subdev,int chan,int range,int aref)
926 memset(sv,0,sizeof(*sv));
929 //sv->t.flags=TRIG_DITHER;
934 //sv->chanlist[0]=CR_PACK(chan,range,aref);
936 sv->maxdata=comedi_get_maxdata(dev,subdev,chan);
937 sv->rng=comedi_get_range(dev,subdev,chan,range);
944 int comedi_data_read_n(comedi_t *it,unsigned int subdev,unsigned int chan,
945 unsigned int range,unsigned int aref,lsampl_t *data,unsigned int n)
952 insn.insn = INSN_READ;
955 insn.subdev = subdev;
956 insn.chanspec = CR_PACK(chan,range,aref);
957 /* enable dithering */
958 insn.chanspec |= (1<<26);
960 ret = comedi_do_insn(it,&insn);
964 printf("insn barfed: subdev=%d, chan=%d, range=%d, aref=%d, "
965 "n=%d, ret=%d, %s\n",subdev,chan,range,aref,n,ret,
971 int new_sv_measure(new_sv_t *sv)
980 data=malloc(sizeof(lsampl_t)*n);
983 perror("comedi_calibrate");
988 ret = comedi_data_read_n(dev,sv->subd,sv->chan,sv->range,
989 sv->aref,data+i,n-i);
999 a=comedi_to_phys(data[0],sv->rng,sv->maxdata);
1001 x=comedi_to_phys(data[i],sv->rng,sv->maxdata);
1006 sv->stddev=sqrt(n*s2-s*s)/n;
1007 sv->error=sv->stddev/sqrt(n);
1017 int new_sv_measure_order(new_sv_t *sv,int order)
1025 data=malloc(sizeof(lsampl_t)*n);
1028 perror("comedi_calibrate");
1033 ret = comedi_data_read_n(dev,sv->subd,sv->chan,sv->range,
1034 sv->aref,data+i,n-i);
1036 printf("barf order\n");
1044 a=comedi_to_phys(data[0],sv->rng,sv->maxdata);
1046 x=comedi_to_phys(data[i],sv->rng,sv->maxdata);
1051 sv->stddev=sqrt(n*s2-s*s)/n;
1052 sv->error=sv->stddev/sqrt(n);
1064 /* linear fitting */
1066 int calculate_residuals(linear_fit_t *l);
1068 int linear_fit_monotonic(linear_fit_t *l)
1081 for(i=0;i<l->n;i++){
1085 if(isnan(y))continue;
1087 if(l->y_data[i]<l->min)l->min=l->y_data[i];
1088 if(l->y_data[i]>l->max)l->max=l->y_data[i];
1095 sxp=l->sxx-l->sx*l->sx/l->s1;
1097 l->ave_x=l->sx/l->s1;
1098 l->ave_y=l->sy/l->s1;
1099 l->slope=(l->s1*l->sxy-l->sx*l->sy)/(l->s1*l->sxx-l->sx*l->sx);
1100 l->err_slope=l->yerr/sqrt(sxp);
1101 l->err_ave_y=l->yerr/sqrt(l->s1);
1103 calculate_residuals(l);
1108 int calculate_residuals(linear_fit_t *l)
1111 double res,sum_res2;
1115 for(i=0;i<l->n;i++){
1116 x=l->x0+i*l->dx-l->ave_x;
1119 if(isnan(y))continue;
1121 res=l->ave_y+l->slope*x-y;
1125 l->S_min=sum_res2/(l->yerr*l->yerr);
1131 double linear_fit_func_y(linear_fit_t *l,double x)
1133 return l->ave_y+l->slope*(x-l->ave_x);