Module: xenomai-3 Branch: next Commit: ad1472d5370f47a50a4cf9a52b6854c06ea11931 URL: http://git.xenomai.org/?p=xenomai-3.git;a=commit;h=ad1472d5370f47a50a4cf9a52b6854c06ea11931
Author: Jorge Ramirez-Ortiz <j...@xenomai.org> Date: Wed Aug 13 12:24:16 2014 -0400 utils/analogy: NI-M software calibration [Part I] Generates a calibration file as per the comedi implementation. --- utils/analogy/Makefile.am | 24 +- utils/analogy/analogy_calibrate.c | 157 +++++ utils/analogy/analogy_calibrate.h | 182 +++++ utils/analogy/calibration_ni_m.c | 1406 +++++++++++++++++++++++++++++++++++++ utils/analogy/calibration_ni_m.h | 280 ++++++++ 5 files changed, 2048 insertions(+), 1 deletion(-) diff --git a/utils/analogy/Makefile.am b/utils/analogy/Makefile.am index c731b7a..fe2f317 100644 --- a/utils/analogy/Makefile.am +++ b/utils/analogy/Makefile.am @@ -1,4 +1,4 @@ -sbin_PROGRAMS = analogy_config +sbin_PROGRAMS = analogy_config analogy_calibrate bin_PROGRAMS = \ cmd_read \ @@ -29,6 +29,24 @@ analogy_config_LDADD = \ @XENO_USER_LDADD@ \ -lpthread -lrt +analogy_calibrate_SOURCES = analogy_calibrate.c calibration_ni_m.c +analogy_calibrate.c: git-stamp.h calibration_ni_m.h +git-stamp.h: git-stamp + @set -x; if test -r $(top_srcdir)/.git; then \ + stamp=`git --git-dir=$(top_srcdir)/.git rev-list --abbrev-commit -1 HEAD`; \ + if test \! -s $@ || grep -wvq $$stamp $@; then \ + date=`git --git-dir=$(top_srcdir)/.git log -1 $$stamp --pretty=format:%ci`; \ + echo "#define GIT_STAMP \"#$$stamp ($$date)\"" > $@; \ + fi; \ + elif test \! -r $@ -o -s $@; then \ + rm -f $@ && touch $@; \ + fi; true +analogy_calibrate_LDADD = \ + ../../lib/analogy/libanalogy.la \ + ../../lib/cobalt/libcobalt.la \ + @XENO_USER_LDADD@ \ + -lpthread -lrt -lgsl -lgslcblas -lm + cmd_read_SOURCES = cmd_read.c cmd_read_LDADD = \ ../../lib/analogy/libanalogy.la \ @@ -77,3 +95,7 @@ insn_bits_LDADD = \ wf_generate_SOURCES = wf_generate.c wf_generate_LDADD = ./libwaveform.la -lm + + +.PHONY: git-stamp + diff --git a/utils/analogy/analogy_calibrate.c b/utils/analogy/analogy_calibrate.c new file mode 100644 index 0000000..586a38a --- /dev/null +++ b/utils/analogy/analogy_calibrate.c @@ -0,0 +1,157 @@ +/** + * @file + * Analogy for Linux, calibration program + * + * @note Copyright (C) 2014 Jorge A. Ramirez-Ortiz <j...@xenomai.org> + * + * from original code from the Comedi project + * + * Xenomai is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Xenomai is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xenomai; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <sys/time.h> +#include <sys/resource.h> +#include <getopt.h> +#include <pthread.h> +#include <sys/mman.h> +#include <xeno_config.h> +#include <rtdm/analogy.h> +#include "analogy_calibrate.h" +#include "calibration_ni_m.h" + + +struct timespec calibration_start_time; +static const char *revision = "0.0.1"; +a4l_desc_t descriptor; +FILE *cal = NULL; + +static const struct option options[] = { + { +#define help_opt 0 + .name = "help", + .has_arg = 0, + .flag = NULL, + }, + { +#define device_opt 1 + .name = "device", + .has_arg = 1, + .flag = NULL, + }, + { +#define output_opt 2 + .name = "output", + .has_arg = 1, + .flag = NULL, + }, + { + .name = NULL, + } +}; + +static void print_usage(void) +{ + fprintf(stderr, "Usage: analogy_calibrate \n" + " --help : this menu \n" + " --device /dev/analogyX : analogy device to calibrate \n" + " --output filename : calibration results \n" + ); +} + +static void __attribute__ ((constructor)) __analogy_calibrate_init(void) +{ + clock_gettime(CLOCK_MONOTONIC, &calibration_start_time); +} + +int main(int argc, char *argv[]) +{ + struct sched_param param = {.sched_priority = 99}; + char *device = NULL, *file = NULL; + int v, i, fd, err = 0; + struct rlimit rl; + + __debug("version: git commit %s, revision %s \n", GIT_STAMP, revision); + + for (;;) { + i = -1; + v = getopt_long_only(argc, argv, "", options, &i); + if (v == EOF) + break; + switch (i) { + case help_opt: + print_usage(); + exit(0); + case device_opt: + device = optarg; + break; + case output_opt: + file = optarg; + cal = fopen(file, "w+"); + if (!cal) + error(EXIT, errno, "calibration file"); + __debug("calibration output: %s \n", file); + break; + default: + print_usage(); + exit(EXIT_FAILURE); + } + } + + err = getrlimit(RLIMIT_STACK, &rl); + if (!err) { + if (rl.rlim_cur < rl.rlim_max) { + rl.rlim_cur = rl.rlim_max; + err = setrlimit(RLIMIT_STACK, &rl); + if (err) + __debug("setrlimit errno (%d) \n", errno); + else + __debug("Program Stack Size: %ld MB \n\n", rl.rlim_cur/(1024*1024)); + } + } + + err = mlockall(MCL_CURRENT | MCL_FUTURE); + if (err < 0) + error(EXIT, errno, "mlockall error"); + + err = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); + if (err) + error(EXIT, 0, "pthread_setschedparam failed (0x%x)", err); + + fd = a4l_open(&descriptor, device); + if (fd < 0) + error(EXIT, 0, "open %s failed (%d)", device, fd); + + err = ni_m_board_supported(descriptor.driver_name); + if (err) + error(EXIT, 0, "board %s: driver %s not supported", + descriptor.board_name, descriptor.driver_name); + + /* + * TODO: modify the meaning of board/driver in the proc + */ + push_to_cal_file("[platform] \n"); + push_to_cal_file("driver_name: %s \n", descriptor.board_name); + push_to_cal_file("board_name: %s \n", descriptor.driver_name); + + err = ni_m_software_calibrate(); + if (err) + error(CONT, 0, "software calibration failed (%d)", err); + + a4l_close(&descriptor); + if (cal) + fclose(cal); + + return err; +} diff --git a/utils/analogy/analogy_calibrate.h b/utils/analogy/analogy_calibrate.h new file mode 100644 index 0000000..1fb548e --- /dev/null +++ b/utils/analogy/analogy_calibrate.h @@ -0,0 +1,182 @@ +/** + * @file + * Analogy for Linux, calibration program + * + * @note Copyright (C) 2014 Jorge A. Ramirez-Ortiz <j...@xenomai.org> + * + * from original code from the Comedi project + * + * Xenomai is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Xenomai is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xenomai; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + + +#ifndef __ANALOGY_CALIBRATE_H__ +#define __ANALOGY_CALIBRATE_H__ + +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> + +#include "git-stamp.h" +#include "error.h" + +extern struct timespec calibration_start_time; +extern a4l_desc_t descriptor; +extern FILE *cal; + +#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0])) + + +#define RETURN 1 +#define CONT 0 +#define EXIT -1 + +#define error(action, code, fmt, ...) \ +do { \ + error_at_line(action, code, __FILE__, __LINE__, fmt, ##__VA_ARGS__ ); \ + if (action == RETURN) \ + return -1; \ +}while(0) + +struct breakdown_time { + int ms; + int us; + int ns; +}; + +static inline void do_time_breakdown(struct breakdown_time *p, + const struct timespec *t) +{ + unsigned long long ms, us, ns; + + ns = t->tv_sec * 1000000000ULL; + ns += t->tv_nsec; + ms = ns / 1000000ULL; + us = (ns % 1000000ULL) / 1000ULL; + + p->ms = (int)ms; + p->us = (int)us; + p->ns = (int)ns; +} + +static inline void timespec_sub(struct timespec *r, + const struct timespec *__restrict t1, + const struct timespec *__restrict t2) +{ + r->tv_sec = t1->tv_sec - t2->tv_sec; + r->tv_nsec = t1->tv_nsec - t2->tv_nsec; + if (r->tv_nsec < 0) { + r->tv_sec--; + r->tv_nsec += 1000000000; + } +} + +static inline void __debug(char *fmt, ...) +{ + struct timespec now, delta; + struct breakdown_time tm; + char *header, *msg; + int hlen, mlen; + va_list ap; + __attribute__((unused)) int err; + + va_start(ap, fmt); + + clock_gettime(CLOCK_MONOTONIC, &now); + timespec_sub(&delta, &now, &calibration_start_time); + do_time_breakdown(&tm, &delta); + + hlen = asprintf(&header, "%4d\"%.3d.%.3d| ", + tm.ms / 1000, tm.ms % 1000, tm.us); + + mlen = vasprintf(&msg, fmt, ap); + + err = write(fileno(stdout), header, hlen); + err = write(fileno(stdout), msg, mlen); + + free(header); + free(msg); + + va_end(ap); +} + +static inline void __push_to_file(FILE *file, char *fmt, ...) +{ + va_list ap; + + if (!file) + return; + + va_start(ap, fmt); + vfprintf(file, fmt, ap); + va_end(ap); +} + +static inline int __array_search(char *elem, const char *array[], int len) +{ + int i; + + for (i = 0; i < len; i++) + if (strncmp(elem, array[i], strlen(array[i])) == 0) + return 0; + + return -1; +} + +#define push_to_cal_file(fmt, ...) \ + do { \ + if (cal) \ + __push_to_file(cal, fmt, ##__VA_ARGS__); \ + } while(0) + + +static inline void debug_cmd(a4l_cmd_t *cmd) +{ + __debug(" cmd.start_src :0x%x \n",cmd->start_src); + __debug(" cmd.scan_begin_src :0x%x \n",cmd->scan_begin_src); + __debug(" cmd.scan_begin_arg :0x%x \n",cmd->scan_begin_arg); + __debug(" cmd.convert_src :0x%x \n",cmd->convert_src); + __debug(" cmd.convert_arg :0x%x \n",cmd->convert_arg); + __debug(" cmd.scan_end_src :0x%x \n",cmd->scan_end_src); + __debug(" cmd.scan_end_arg :0x%x \n",cmd->scan_end_arg); + __debug(" cmd.stop_src :0x%x \n",cmd->stop_src); + __debug(" cmd.stop_arg :0x%x \n",cmd->stop_arg); + __debug(" cmd.chan_descs :0x%x \n",cmd->chan_descs); + __debug(" cmd.nb_chan :0x%x \n",cmd->nb_chan); +} + +static inline double rng_max(a4l_rnginfo_t *range) +{ + double a, b; + b = A4L_RNG_FACTOR * 1.0; + + a = (double) range->max; + a = a / b; + return a; +} + +static inline double rng_min(a4l_rnginfo_t *range) +{ + double a, b; + + b = A4L_RNG_FACTOR * 1.0; + a = (double) range->min; + a = a / b; + return a; +} + +#endif diff --git a/utils/analogy/calibration_ni_m.c b/utils/analogy/calibration_ni_m.c new file mode 100644 index 0000000..05e5f24 --- /dev/null +++ b/utils/analogy/calibration_ni_m.c @@ -0,0 +1,1406 @@ +/** + * @file + * Analogy for Linux, NI - M calibration program + * + * @note Copyright (C) 2014 Jorge A. Ramirez-Ortiz <j...@xenomai.org> + * + * from original code from the Comedi project + * + * Xenomai is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Xenomai is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xenomai; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include <gsl/gsl_statistics_double.h> +#include <gsl/gsl_multifit.h> +#include <gsl/gsl_matrix.h> +#include <gsl/gsl_vector.h> +#include <rtdm/analogy.h> +#include <math.h> + +#include "calibration_ni_m.h" + +struct list ai_calibration_list; +struct list ao_calibration_list; + +static struct references references; +static struct subdevice mem_subd; +static struct subdevice cal_subd; +static struct subdevice ao_subd; +static struct subdevice ai_subd; +static struct subdev_ops ops; +static struct eeprom eeprom; +static struct gnumath math; + +/* + * generate the calibration file + */ +static void +write_calibration(struct list *calibration_list, struct subdevice *subd) +{ + struct subdevice_calibration_node *e, *t; + int i, j = 0; + + if (list_empty(calibration_list)) + return; + + push_to_cal_file("\n[%s] \n", subd->name); + push_to_cal_file("index: %d \n", subd->idx); + list_for_each_entry_safe(e, t, calibration_list, node) + j++; + push_to_cal_file("elements: %d \n", j); + + j = 0; + list_for_each_entry_safe(e, t, calibration_list, node) { + push_to_cal_file("[%s_%d] \n", subd->name, j); + push_to_cal_file("channel: %d \n", e->channel); + push_to_cal_file("range: %d \n", e->range); + push_to_cal_file("expansion_origin: %g \n", + e->polynomial->expansion_origin); + push_to_cal_file("nbcoeff: %d \n", + e->polynomial->nb_coefficients); + push_to_cal_file("coefficients: "); + + for (i = 0;;) { + push_to_cal_file("%g", e->polynomial->coefficients[i]); + i++; + if (i == e->polynomial->nb_coefficients) { + push_to_cal_file(" \n"); + break; + } + push_to_cal_file(", "); + } + j++; + } + + return; +} + + +/* + * eeprom + */ +static int +eeprom_read_byte(unsigned address, unsigned *val) +{ + ops.data.read(val, &mem_subd, address, 0, 0); + if (*val > 0xff) + error(EXIT, 0, "failed to read byte from EEPROM %d > 0xff", *val); + + return 0; +} + +static int +eeprom_read_uint16(unsigned address, unsigned *val) +{ + unsigned a = 0, b = 0; + int err; + + err = eeprom_read_byte(address, &a); + if (err) + error(EXIT, 0, "failed to read byte from EEPROM"); + a = a << 8; + + err = eeprom_read_byte(address + 1, &b); + if (err) + error(EXIT, 0, "failed to read byte from EEPROM"); + + *val = a | b; + + return 0; +} + +static int +eeprom_get_calibration_base_address(unsigned *address) +{ + eeprom_read_uint16(24, address); + + return 0; +} + +static int +eeprom_read_float(unsigned address, float *val) +{ + union float_converter { + unsigned u; + float f; + } converter; + + unsigned a = 0, b = 0, c = 0, d = 0; + + if (sizeof(float) != sizeof(uint32_t)) + error(EXIT, 0, "eeprom_read_float"); + + eeprom_read_byte(address++, &a); + a = a << 24; + eeprom_read_byte(address++, &b); + b = b << 16; + eeprom_read_byte(address++, &c); + c = c << 8; + eeprom_read_byte(address++, &d); + + converter.u = a | b | c | d; + *val = converter.f; + + return 0; +} + +static int +eeprom_read_reference_voltage(float *val) +{ + unsigned address; + + eeprom_get_calibration_base_address(&address); + eeprom_read_float(address + eeprom.voltage_ref_offset, val); + + return 0; +} + +/* + * subdevice operations + */ +static int +data_read_hint(struct subdevice *s, int channel, int range, int aref, + unsigned int delay) +{ + sampl_t dummy_data; + a4l_insn_t insn; + int err; + + memset(&insn, 0, sizeof(insn)); + insn.chan_desc = PACK(channel, range, aref); + insn.idx_subd = s->idx; + insn.type = A4L_INSN_READ; + insn.data = &dummy_data; + insn.data_size = 0; + + err = a4l_snd_insn(&descriptor, &insn); + if (err < 0) + error(EXIT, 0, "a4l_snd_insn (%d)", err); + + return 0; +} + +static int +data_read(unsigned *data, struct subdevice *s, int channel, int range, int aref) +{ + a4l_insn_t insn; + int err; + + memset(&insn, 0, sizeof(insn)); + insn.chan_desc = PACK(channel, range, aref); + insn.idx_subd = s->idx; + insn.type = A4L_INSN_READ; + insn.data = data; + insn.data_size = 1; + + err = a4l_snd_insn(&descriptor, &insn); + if (err < 0) + error(EXIT, 0, "a4l_snd_insn (%d)", err); + + return 0; +} + +static int +data_write(long int *data, struct subdevice *s, int channel, int range, int aref) +{ + a4l_insn_t insn; + int err; + + memset(&insn, 0, sizeof(insn)); + insn.chan_desc = PACK(channel, range, aref); + insn.idx_subd = s->idx; + insn.type = A4L_INSN_WRITE; + insn.data = data; + insn.data_size = sizeof(*data); + + err = a4l_snd_insn(&descriptor, &insn); + if (err < 0) + error(EXIT, 0, "a4l_snd_insn (%d)", err); + + return 0; +} + +static int +data_read_async(void *dst, struct subdevice *s, unsigned int nb_samples, + int speriod, int irange) +{ + int i, len, err; + a4l_cmd_t cmd; + unsigned int chan_descs[] = { + PACK(CR_ALT_SOURCE|CR_ALT_FILTER, irange, AREF_DIFF) + }; + + memset(&cmd, 0, sizeof(cmd)); + cmd.scan_begin_src = TRIG_TIMER; + cmd.scan_end_src = TRIG_COUNT; + cmd.convert_src = TRIG_TIMER; + cmd.stop_src = TRIG_COUNT; + cmd.start_src = TRIG_NOW; + cmd.scan_end_arg = 1; + cmd.convert_arg = 0; + cmd.nb_chan = 1; + cmd.scan_begin_arg = speriod; + cmd.chan_descs = chan_descs; + cmd.idx_subd = s->idx; + cmd.stop_arg = nb_samples; + cmd.flags = A4L_CMD_SIMUL; + SET_BIT(3, &cmd.valid_simul_stages); + + /* get driver specific info into the command structure */ + for (i = 0; i< 4; i++) + a4l_snd_command(&descriptor, &cmd); + + /* send the real command */ + cmd.flags = 0; + err = a4l_snd_command(&descriptor, &cmd); + if (err) + error(EXIT, 0, "a4l_snd_command (%d)", err); + + len = nb_samples * ai_subd.slen; + for (;;) { + err = a4l_async_read(&descriptor, dst, len, A4L_INFINITE); + if (err <0) + error(EXIT, 0, "a4l_async_read (%d)", err); + if (err < len) { + dst = dst + err; + len = len - err; + } else + break; + } + a4l_snd_cancel(&descriptor, ai_subd.idx); + + return 0; +} + +/* + * + * math: uses the gnu statistic library and the math library + * + */ +static int +statistics_standard_deviation_of_mean(double *dst, double src[], int len, + double mean) +{ + double a; + + a = gsl_stats_variance_m(src, 1, len, mean); + a = sqrt(a/len); + *dst = a; + + return 0; +} + +static int +statistics_standard_deviation(double *dst, double src[], int len, double mean) +{ + double a; + + a = gsl_stats_variance_m(src, 1, len, mean); + a = sqrt(a); + *dst = a; + + return 0; +} + +static int +statistics_mean(double *dst, double src[], int len) +{ + *dst = gsl_stats_mean(src, 1, len); + + return 0; +} + +static int +polynomial_fit(struct polynomial *dst, struct codes_info *src) +{ + gsl_multifit_linear_workspace *work; + const int nb_coeff = dst->order + 1; + gsl_matrix *covariance, *m; + gsl_vector_view b, result; + double a, *tmp, chisq; + int i, j, len; + + work = gsl_multifit_linear_alloc(src->nb_codes, nb_coeff); + covariance = gsl_matrix_alloc(nb_coeff, nb_coeff); + m = gsl_matrix_alloc(src->nb_codes, nb_coeff); + + for (i = 0; i < src->nb_codes; i++) { + gsl_matrix_set(m, i, 0, 1.0); + for (j = 1; j < nb_coeff; j++) { + a = gsl_matrix_get(m, i, j - 1); + a = a * (src->codes[i].nominal - dst->expansion_origin); + gsl_matrix_set(m, i, j, a); + } + } + + len = src->nb_codes * sizeof(double); + tmp = malloc(len); + if (!tmp) + error(EXIT, 0, "malloc (%d)", len); + + for (i = 0; i < src->nb_codes; i++) + tmp[i] = src->codes[i].measured; + + b = gsl_vector_view_array(tmp, src->nb_codes); + + dst->nb_coefficients = nb_coeff; + len = dst->nb_coefficients * sizeof(double); + dst->coefficients = malloc(len); + if (!dst->coefficients) + error(EXIT, 0, "malloc (%d)", len); + + result = gsl_vector_view_array(dst->coefficients, nb_coeff); + gsl_multifit_linear(m, &b.vector, &result.vector, covariance, &chisq, + work); + gsl_matrix_free(m); + gsl_matrix_free(covariance); + gsl_multifit_linear_free(work); + + free(tmp); + + return 0; +} + +static int +polynomial_linearize(double *dst, struct polynomial *p, double val) +{ + double a = 0.0, b = 1.0; + int i; + + for (i = 0; i < p->nb_coefficients; i++) { + a = a + p->coefficients[i] * b; + b = b * (val - p->expansion_origin); + } + + *dst = a; + + return 0; +} + +/* + * + * reference + * + */ +static int +reference_get_min_sampling_period(int *val) +{ + unsigned int chan_descs[] = { 0 }; + a4l_cmd_t cmd; + int err; + + memset(&cmd, 0, sizeof(cmd)); + cmd.scan_begin_src = TRIG_TIMER; + cmd.scan_end_src = TRIG_COUNT; + cmd.convert_src = TRIG_TIMER; + cmd.stop_src = TRIG_COUNT; + cmd.start_src = TRIG_NOW; + cmd.scan_begin_arg = 0; + cmd.convert_arg = 0; + cmd.stop_arg = 1; + cmd.nb_chan = 1; + cmd.scan_end_arg = ai_subd.info->nb_chan; + cmd.chan_descs = chan_descs; + cmd.idx_subd = ai_subd.idx; + cmd.flags = A4L_CMD_SIMUL; + SET_BIT(3, &cmd.valid_simul_stages); + + err = a4l_snd_command(&descriptor, &cmd); + if (err) + error(EXIT, 0, "a4l_snd_command (%d)", err); + + *val = cmd.scan_begin_arg; + + return 0; +} + +static int +reference_set_bits(unsigned int bits) +{ + unsigned int data[2] = { A4L_INSN_CONFIG_ALT_SOURCE, bits }; + a4l_insn_t insn; + int err; + + insn.data_size = sizeof(data); + insn.type = A4L_INSN_CONFIG; + insn.idx_subd = ai_subd.idx; + insn.chan_desc = 0; + insn.data = data; + + err = a4l_snd_insn(&descriptor, &insn); + if (err) + error(EXIT, 0, "a4l_snd_insn (%d)", err); + + return 0; +} + +static int +reference_set_pwm(struct subdevice *s, unsigned int h, unsigned int d, + unsigned int *rh, unsigned int *rd) +{ + unsigned int data[5] = { + [0] = A4L_INSN_CONFIG_PWM_OUTPUT, + [1] = TRIG_ROUND_NEAREST, + [2] = h, + [3] = TRIG_ROUND_NEAREST, + [4] = d + }; + a4l_insn_t insn; + int err; + + insn.data_size = sizeof(data); + insn.idx_subd = s->idx; + insn.type = A4L_INSN_CONFIG; + insn.chan_desc = 0; + insn.data = data; + + err = a4l_snd_insn(&descriptor, &insn); + if (err) + error(EXIT, 0, "a4l_snd_insn (%d)", err); + + *rh = data[2]; + *rd = data[4]; + + return 0; +} + +static int +reference_read_doubles(double dst[], unsigned int nb_samples, + int speriod, int irange, unsigned int settle_time) +{ + int i, err = 0; + sampl_t *p; + + p = malloc(nb_samples * ai_subd.slen); + if (!p) + error(EXIT, 0, "malloc"); + + err = references.read_samples(p, nb_samples, speriod, irange, + settle_time); + if (err) { + free(p); + error(EXIT, 0, "read_samples"); + } + + for (i = 0; i < nb_samples; i++) + dst[i] = p[i]; + + free(p); + + return 0; +} + +static int +reference_read_samples(void *dst, unsigned int nb_samples, int speriod, + int irange, unsigned int settle_time) +{ + int err; + + if (settle_time > 1000000000) + error(EXIT, 0, "invalid argument (%d)", settle_time); + + if (!nb_samples) + error(EXIT, 0, "invalid nb samples (%d)", nb_samples); + + err = ops.data.read_hint(&ai_subd, CR_ALT_SOURCE|CR_ALT_FILTER, + irange, AREF_DIFF, settle_time); + if (err) + error(EXIT, 0, "read_hint (%d)", err); + + err = ops.data.read_async(dst, &ai_subd, nb_samples, speriod, irange); + if (err) + error(EXIT, 0, "read_async (%d)", err); + + return 0; +} + +/* + * + * calibrator + * + * + */ +const char *ni_m_boards[] = { + "pci-6220", "pci-6221", "pci-6221_37pin", "pci-6224", "pci-6225", + "pci-6229", "pci-6250", "pci-6251", "pci-6254", "pci-6259", "pcie-6259", + "pci-6280", "pci-6281", "pxi-6281", "pci-6284", "pci-6289"}; + +const int nr_ni_m_boards = ARRAY_LEN(ni_m_boards); + +static inline +int pwm_period_ticks(void) +{ + int min_speriod, speriod_ticks, ticks; + int err; + + err = references.get_min_speriod(&min_speriod); + if (err || !min_speriod) + error(EXIT, 0, "couldn't retrieve the sampling period"); + + speriod_ticks = min_speriod / NI_M_MASTER_CLOCK_PERIOD; + ticks = (NI_M_TARGET_PWM_PERIOD_TICKS + speriod_ticks - 1) / + speriod_ticks; + ticks = ticks * speriod_ticks; + + return ++ticks; +} + +static inline +int pwm_rounded_nsamples(void) +{ + int pwm_period, total_speriod, min_speriod; + int err; + + err = references.get_min_speriod(&min_speriod); + if (err || !min_speriod) + error(EXIT, 0, "couldn't retrieve the sampling period"); + + pwm_period = pwm_period_ticks() * NI_M_MASTER_CLOCK_PERIOD; + total_speriod = (NI_M_NR_SAMPLES * min_speriod + pwm_period / 2) / + pwm_period; + total_speriod = total_speriod * pwm_period; + + return total_speriod / min_speriod; +} + +static int +check_buf_size(int slen) +{ + unsigned long blen, req_blen; + int err; + + err = a4l_get_bufsize(&descriptor, ai_subd.idx, &blen); + if (err) + error(EXIT, 0, "a4l_get_bufsize (%d)", err); + + req_blen = slen * pwm_rounded_nsamples(); + if (blen < req_blen) + error(EXIT, 0, "blen (%ld) < req_blen (%ld)", blen, req_blen); + + return 0; +} + +static int +set_pwm_up_ticks(int t) +{ + unsigned int up_p, down_p, real_up_p, real_down_p; + int err; + + up_p = t * NI_M_MASTER_CLOCK_PERIOD; + down_p = (pwm_period_ticks() - t) * NI_M_MASTER_CLOCK_PERIOD; + err = references.set_pwm(&cal_subd, up_p, down_p, &real_up_p, + &real_down_p); + if (err) + error(EXIT, 0, "reference_set_pwm"); + + return 0; +} + +static int +characterize_pwm(struct pwm_info *dst, int pref, unsigned range) +{ + int i, up_ticks, err, speriod, len; + double mean, stddev, stddev_of_mean; + double *p; + + err = references.set_bits(pref | REF_NEG_CAL_GROUND); + if (err) + error(EXIT, EINVAL, "reference_set_bits"); + + len = pwm_rounded_nsamples() * sizeof(*p); + p = malloc(len); + if (!p) + error(EXIT, 0, "malloc (%d)", len); + + for (i = 0; i < dst->nb_nodes; i++) { + + up_ticks = NI_M_MIN_PWM_PULSE_TICKS * (i + 1); + err = set_pwm_up_ticks(up_ticks); + if (err) + error(EXIT, 0, "set_pwm_up_ticks"); + + err = references.get_min_speriod(&speriod); + if (err) + error(EXIT, 0, "get_min_speriod"); + + err = references.read_doubles(p, len/sizeof(*p), speriod, range, + NI_M_SETTLE_TIME); + if (err) + error(EXIT, 0, "read_doubles"); + + err = math.stats.mean(&mean, p, len/sizeof(*p)); + if (err) + error(EXIT, 0, "estimate_mean"); + + err = math.stats.stddev(&stddev, p, len/sizeof(*p), mean); + if (err) + error(EXIT, 0, "estimate_stddev"); + + err = math.stats.stddev_of_mean(&stddev_of_mean, p, + len/sizeof(*p), mean); + if (err) + error(EXIT, 0, "estimate_stddev_of_mean"); + + dst->node[i].up_tick = up_ticks; + dst->node[i].mean = mean; + } + free(p); + + return 0; +} + +static void +print_polynomial(struct polynomial *p) +{ + int i; + + __debug("Polynomial :\n"); + __debug("\torder = %d \n", p->order); + __debug("\texpansion origin = %f \n", p->expansion_origin); + + for (i = 0; i < p->nb_coefficients; i++) + __debug("\torder %d coefficient = %g \n", + i, p->coefficients[i]); +} + +static int +calibrate_non_linearity(struct polynomial *poly, struct pwm_info *src) +{ + unsigned int max_data = (1 << ai_subd.slen * 8) - 2; + unsigned up_ticks, down_ticks, i; + struct codes_info data; + int len; + + data.nb_codes = src->nb_nodes; + len = data.nb_codes * sizeof(*data.codes); + data.codes = malloc(len); + if (!data.codes) + error (EXIT, 0, "malloc (%d)", len); + + for (i = 0; i < data.nb_codes; i++) { + up_ticks = src->node[i].up_tick; + down_ticks = pwm_period_ticks() - up_ticks; + data.codes[i].nominal = max_data * down_ticks / + pwm_period_ticks(); + data.codes[i].measured = src->node[i].mean; + } + + poly->order = 3; + poly->expansion_origin = max_data / 2; + math.polynomial.fit(poly, &data); + + print_polynomial(poly); + free(data.codes); + + return 0; +} + +static int +calibrate_ai_gain_and_offset(struct polynomial *dst, struct polynomial *src, + unsigned pos_ref, float volt_ref, unsigned range) +{ + double *p; + double measured_ground_code, linearized_ground_code; + double measured_reference_code, linearized_reference_code; + double gain, offset; + int i, len, err, speriod; + double a, b; + + len = pwm_rounded_nsamples() * sizeof(*p); + p = malloc(len); + if (!p) + error(EXIT, 0, "malloc (%d)", len); + + /* ground */ + references.set_bits(REF_POS_CAL_GROUND | REF_NEG_CAL_GROUND); + err = references.get_min_speriod(&speriod); + if (err) + error(EXIT, 0, "get_min_speriod"); + err = references.read_doubles(p, len/sizeof(*p), speriod, range, + NI_M_SETTLE_TIME); + if (err) + error(EXIT, 0, "read_doubles"); + math.stats.mean(&measured_ground_code, p, len/sizeof(*p)); + math.polynomial.linearize(&linearized_ground_code, src, + measured_ground_code); + + /* reference */ + references.set_bits(pos_ref | REF_NEG_CAL_GROUND); + err = references.get_min_speriod(&speriod); + if (err) + error(EXIT, 0, "get_min_speriod"); + err = references.read_doubles(p, len/sizeof(*p), speriod, range, + NI_M_SETTLE_TIME); + if (err) + error(EXIT, 0, "read_doubles"); + math.stats.mean(&measured_reference_code, p, len/sizeof(*p)); + math.polynomial.linearize(&linearized_reference_code, src, + measured_reference_code); + + gain = volt_ref / (linearized_reference_code - linearized_ground_code); + + /* + * update output + */ + + dst->coefficients = malloc(src->nb_coefficients * sizeof(double)); + if (!dst->coefficients) + error(EXIT, 0, "malloc"); + + dst->expansion_origin = src->expansion_origin; + dst->nb_coefficients = src->nb_coefficients; + dst->order = src->order; + for (i = 0; i < dst->nb_coefficients; i++) + dst->coefficients[i] = src->coefficients[i] * gain; + + math.polynomial.linearize(&offset, dst, measured_ground_code); + dst->coefficients[0] = dst->coefficients[0] - offset; + + __debug("volt_ref = %g \n", volt_ref); + __debug("measured_ground_code = %g, linearized_ground_code = %g \n", + measured_ground_code, linearized_ground_code); + __debug("measured_reference_code = %g, linearized_reference_code = %g \n", + measured_reference_code, linearized_reference_code); + + math.polynomial.linearize(&a, dst, measured_ground_code); + __debug("full_correction(measured_ground_code) = %g \n", a); + math.polynomial.linearize(&b, dst, measured_reference_code); + __debug("full_correction(measured_reference_code) = %g \n", b); + + print_polynomial(dst); + + free(p); + + return 0; +} + +static int +calibrate_base_range(struct polynomial *dst, struct polynomial *src) +{ + float volt_ref; + int err; + + eeprom.ops.read_reference_voltage(&volt_ref); + err = calibrate_ai_gain_and_offset(dst, src, REF_POS_CAL, volt_ref, + NI_M_BASE_RANGE); + if (err) + error(EXIT, 0, "calibrate_ai_gain_and_offset"); + + return err; +} + + +static struct subdevice_calibration_node * +get_calibration_node(struct list *l, unsigned channel, unsigned range) { + struct subdevice_calibration_node *e, *t; + + if (list_empty(l)) + return NULL; + + list_for_each_entry_safe(e, t, l, node) { + if (e->channel == channel || + e->channel == ALL_CHANNELS || + channel == ALL_CHANNELS) { + if (e->range == range || + e->range == ALL_RANGES || + range == ALL_RANGES) { + return e; + } + } + } + + return NULL; +} + +static int +calibrate_pwm(struct polynomial *dst, struct pwm_info *pwm_info, + struct subdevice_calibration_node *range_calibration) +{ + double pwm_cal, adrange_cal, lsb_error; + double aprox_volts_per_bit, a, b; + double measured_voltages; + struct codes_info info; + int i; + + if (!pwm_info->nb_nodes) + error(EXIT, 0, "no pwm nodes \n"); + + info.nb_codes = pwm_info->nb_nodes; + info.codes = malloc (info.nb_codes * sizeof(*info.codes)); + + for (i = 0; i < pwm_info->nb_nodes; i++) { + info.codes[i].nominal = pwm_info->node[i].up_tick; + math.polynomial.linearize(&measured_voltages, + range_calibration->polynomial, + pwm_info->node[i].mean); + info.codes[i].measured = measured_voltages; + } + + dst->order = 1; + dst->expansion_origin = pwm_period_ticks() / 2; + math.polynomial.fit(dst, &info); + + math.polynomial.linearize(&a, range_calibration->polynomial, 1); + math.polynomial.linearize(&b, range_calibration->polynomial, 0); + aprox_volts_per_bit = a - b; + + for (i = 0; i < pwm_info->nb_nodes; i++) { + math.polynomial.linearize(&pwm_cal, dst, + pwm_info->node[i].up_tick); + math.polynomial.linearize(&adrange_cal, + range_calibration->polynomial, + pwm_info->node[i].mean); + lsb_error = (adrange_cal - pwm_cal) / aprox_volts_per_bit; + __debug("upTicks=%d code=%g " + "pwm_cal=%g adrange_cal=%g lsb_error=%g \n", + pwm_info->node[i].up_tick, pwm_info->node[i].mean, + pwm_cal, adrange_cal, lsb_error); + } + + return 0; +} + +static int +append_calibration_node(struct list *l, struct polynomial *polynomial, + unsigned channel, unsigned range) +{ + struct subdevice_calibration_node *q; + + q = malloc(sizeof(struct subdevice_calibration_node)); + if (!q) + error(EXIT, 0, "malloc"); + + q->polynomial = polynomial; + q->channel = channel; + q->range = range; + list_append(&q->node ,l); + + return 0; +} + +static int +calibrate_ai_range(struct polynomial *dst, struct polynomial *pwm_calibration, + struct polynomial *non_linearity_correction, unsigned pos_ref, + unsigned range) +{ + struct polynomial inverse_pwm_calibration; + double reference_voltage; + a4l_rnginfo_t *rng; + unsigned up_ticks; + double *p, val; + int err; + + if (pwm_calibration->order != 1) + error(EXIT, -1, "pwm_calibration order \n"); + + inverse_pwm_calibration.expansion_origin = pwm_calibration->coefficients[0]; + p = malloc((pwm_calibration->order + 1) * sizeof(double)); + if (!p) + error(EXIT,0,"malloc\n"); + + inverse_pwm_calibration.order = pwm_calibration->order; + inverse_pwm_calibration.nb_coefficients = pwm_calibration->order + 1; + inverse_pwm_calibration.coefficients = p; + inverse_pwm_calibration.coefficients[0] = pwm_calibration->expansion_origin; + inverse_pwm_calibration.coefficients[1] = 1.0 / pwm_calibration->coefficients[1]; + + err = a4l_get_rnginfo(&descriptor, ai_subd.idx, 0, range, &rng); + if (err < 0) + error(EXIT,0,"a4l_get_rnginfo (%d)\n", err); + + __debug("adjusted rng_max: %g \n", rng_max(rng) * 0.9); + + math.polynomial.linearize(&val, &inverse_pwm_calibration, + rng_max(rng) * 0.9); + up_ticks = lrint(val); + free(p); + + if (up_ticks > pwm_period_ticks() - NI_M_MIN_PWM_PULSE_TICKS) + up_ticks = pwm_period_ticks() - NI_M_MIN_PWM_PULSE_TICKS; + + set_pwm_up_ticks(up_ticks); + math.polynomial.linearize(&val, pwm_calibration, up_ticks); + reference_voltage = val; + calibrate_ai_gain_and_offset(dst, non_linearity_correction, pos_ref, + reference_voltage, range); + + return 0; +} + +static int +calibrate_ranges_above_threshold(struct polynomial *pwm_calibration, + struct polynomial *non_linearity_correction, + unsigned pos_ref, + struct list *calibration_list, + struct calibrated_ranges *calibrated, + double max_range_threshold ) +{ + struct polynomial *dst; + a4l_rnginfo_t *rnginfo; + int err, i; + + for (i = 0; i < calibrated->nb_ranges; i++) { + if (calibrated->ranges[i] == 1) + continue; + + err = a4l_get_rnginfo(&descriptor, ai_subd.idx, 0, i, &rnginfo); + if (err < 0) + error(EXIT,0,"a4l_get_rnginfo (%d)\n", err); + + if (rng_max(rnginfo) < max_range_threshold) + continue; + + dst = malloc(sizeof(*dst)); + if (!dst) + error(EXIT, 0, "malloc"); + + __debug("calibrating range %d \n", i); + calibrate_ai_range(dst, pwm_calibration, non_linearity_correction, + pos_ref, i); + append_calibration_node(calibration_list, dst, ALL_CHANNELS, i); + calibrated->ranges[i] = 1; + __debug("done \n"); + } + + return 0; +} + +static int +get_min_range_containing(struct calibrated_ranges *calibrated, double value) +{ + a4l_rnginfo_t *rnginfo, *smallest = NULL; + unsigned smallest_range = 0; + int err, i; + + for (i = 0; i < calibrated->nb_ranges; i++) { + if (!calibrated->ranges[i]) + continue; + + err = a4l_get_rnginfo(&descriptor, ai_subd.idx, 0, i, &rnginfo); + if (err < 0) + error(EXIT,0,"a4l_get_rnginfo (%d)\n", err); + + if (rng_max(rnginfo) > value && + (smallest_range == 0 || rng_max(rnginfo) < rng_max(smallest))) { + smallest_range = i; + smallest = rnginfo; + } + } + if (!smallest) + error(EXIT,0,"no cal range with max volt above %g V found \n", value); + + return smallest_range; +} + +static int +ni_m_calibrate_ai(void) +{ + const unsigned PWM_CAL_POINTS = (NI_M_TARGET_PWM_PERIOD_TICKS / NI_M_MIN_PWM_PULSE_TICKS); + const double MEDIUM_RANGE = 0.499; + const double LARGE_RANGE = 1.99; + const double SMALL_RANGE = 0.0; + + struct polynomial non_linearity_correction, full_correction; + struct subdevice_calibration_node *node; + struct calibrated_ranges calibrated; + struct polynomial pwm_calibration; + struct pwm_info pwm_info; + a4l_chinfo_t *chan_info; + int i, err; + + struct calibration_loop { + const char *message; + unsigned ref_pos; + double threshold; + double item; + int range; + } calibration_info [] = { + [0] = { + .message = "low gain range ", + .ref_pos = REF_POS_CAL_PWM_10V, + .threshold = LARGE_RANGE, + .range = NI_M_BASE_RANGE, + .item = - 1, + }, + [1] = { + .message = "medium gain range ", + .ref_pos = REF_POS_CAL_PWM_2V, + .threshold = MEDIUM_RANGE, + .item = LARGE_RANGE, + .range = -1, + }, + [2] = { + .message = "high gain range ", + .ref_pos = REF_POS_CAL_PWM_500mV, + .threshold = SMALL_RANGE, + .item = MEDIUM_RANGE, + .range = -1, + }, + + }; + + list_init(&ai_calibration_list); + + /* + * check if the buffer is big enough + */ + err = a4l_get_chinfo(&descriptor, ai_subd.idx, 0, &chan_info); + if (err) + error(EXIT, 0,"a4l_get_chinfo (%d)", err); + + calibrated.nb_ranges = chan_info->nb_rng; + calibrated.ranges = malloc(chan_info->nb_rng * sizeof(unsigned)); + if (!calibrated.ranges) + error(EXIT, 0,"malloc"); + + memset(calibrated.ranges, 0, calibrated.nb_ranges * sizeof(unsigned)); + + ai_subd.slen = a4l_sizeof_chan(chan_info); + if (ai_subd.slen < 0) + error (RETURN, 0, "a4l_sizeof_chan (%d)", err); + + err = check_buf_size(ai_subd.slen); + if (err) + error(EXIT, -1, "ni_m_check_buf_size: device buffer too small, " + "please re-attach a bigger buffer"); + + pwm_info.nb_nodes = PWM_CAL_POINTS; + pwm_info.node = malloc(PWM_CAL_POINTS * sizeof(*pwm_info.node)); + if (err) + error(EXIT, -ENOMEM, "malloc error"); + + /* + * calibrate base range + */ + err = characterize_pwm(&pwm_info, REF_POS_CAL_PWM_10V, NI_M_BASE_RANGE); + if (err) + error(EXIT, 0, "characterize_pwm"); + + err = calibrate_non_linearity(&non_linearity_correction, &pwm_info); + if (err) + error(EXIT, 0, "calibrate_non_linearity"); + + err = calibrate_base_range(&full_correction, &non_linearity_correction); + if (err) + error(EXIT, 0, "calibrate_ai_base_range"); + + append_calibration_node(&ai_calibration_list, &full_correction, + ALL_CHANNELS, NI_M_BASE_RANGE); + calibrated.ranges[NI_M_BASE_RANGE] = 1; + + + /* + * calibrate low, medium and high gain ranges + */ + for (i = 0; i < ARRAY_LEN(calibration_info); i++) { + + __debug("Calibrating AI: %s \n", calibration_info[i]); + + if (calibration_info[i].range >= 0) + goto calibrate; + + calibration_info[i].range = get_min_range_containing(&calibrated, + calibration_info[i].item); + if (!calibrated.ranges[calibration_info[i].range]) + error(EXIT, 0, "not calibrated yet \n" ); + + err = characterize_pwm(&pwm_info, calibration_info[i].ref_pos, + calibration_info[i].range); + if (err) + error(EXIT, 0, "characterize_pwm \n"); + +calibrate: + node = get_calibration_node(&ai_calibration_list, 0, + calibration_info[i].range); + if (!node) + error(EXIT, 0, "couldnt find node \n"); + + err = calibrate_pwm(&pwm_calibration, &pwm_info, node); + if (err) + error(EXIT, 0, "calibrate_pwm \n"); + + err = calibrate_ranges_above_threshold(&pwm_calibration, + &non_linearity_correction, + calibration_info[i].ref_pos, + &ai_calibration_list, + &calibrated, + calibration_info[i].threshold); + if (err) + error(EXIT, 0, "calibrate_ranges_above_threshold \n"); + + } + + return 0; +} + +static unsigned +find_ai_range_for_ao(unsigned ao_range) +{ + a4l_rnginfo_t *ao_rng_info, *ai_rng_info, *rng_info = NULL; + a4l_chinfo_t *ai_chan_info; + unsigned range = 0xFFFF; + double max_ao_voltage; + int num_ai_ranges; + int i, err; + + err = a4l_get_chinfo(&descriptor, ai_subd.idx, 0, &ai_chan_info); + if (err) + error(EXIT, 0,"a4l_get_chinfo (%d)", err); + + num_ai_ranges = ai_chan_info->nb_rng; + + err = a4l_get_rnginfo(&descriptor, ao_subd.idx, 0, ao_range, &ao_rng_info); + if (err) + error(EXIT, 0, "a4l_get_rng_info (%d)", err); + + max_ao_voltage = rng_max(ao_rng_info); + + for (i = 0; i < num_ai_ranges; i++) { + err = a4l_get_rnginfo(&descriptor, ai_subd.idx, 0, i, &ai_rng_info); + if (err) + error(EXIT, 0, "a4l_get_rng_info (%d)", err); + + if (rng_info == NULL || + (rng_max(ai_rng_info) > max_ao_voltage && + rng_max(ai_rng_info) < rng_max(rng_info)) || + (rng_max(rng_info) < max_ao_voltage && + rng_max(ai_rng_info) > rng_max(rng_info))) { + + range = i; + rng_info = ai_rng_info; + } + } + + if (rng_info == NULL) + error(EXIT, 0, "cant find range"); + + return range; +} + +static long int +get_high_code(unsigned ai_rng, unsigned ao_rng) +{ + unsigned int ao_max_data = (1 << ao_subd.slen * 8) - 2; + a4l_rnginfo_t *ai, *ao; + double fractional_code; + + a4l_get_rnginfo(&descriptor, ai_subd.idx, 0, ai_rng, &ai); + a4l_get_rnginfo(&descriptor, ao_subd.idx, 0, ai_rng, &ao); + + if (rng_max(ai) > rng_max(ao)) + return lrint(ao_max_data * 0.9); + + fractional_code = (0.9 * rng_max(ai) - rng_min(ao)) / (rng_max(ao) - rng_min(ao)); + if (fractional_code < 0.0 || fractional_code > 1.0) + error(EXIT, 0, "error looking for high code"); + + return lrint(ao_max_data * fractional_code); +} + +static int +calibrate_ao_channel_and_range(unsigned ai_rng, unsigned ao_channel, unsigned ao_rng) +{ + unsigned int ao_max_data = (1 << ao_subd.slen * 8) - 2; + double measured_low_code, measured_high_code, tmp; + long int low_code = lrint(ao_max_data * 0.1); + struct subdevice_calibration_node *node; + struct codes_info data; + struct polynomial poly; + long int high_code; + double *readings; + int speriod; + int i; + + node = get_calibration_node(&ai_calibration_list, 0, ai_rng); + if (!node) + error(EXIT, 0, "couldnt find node \n"); + + data.nb_codes = 2; + data.codes = malloc (data.nb_codes * sizeof(*data.codes)); + readings = malloc(NI_M_NR_SAMPLES * sizeof(*readings)); + if (data.codes == NULL || readings == NULL) + error(EXIT,0, "malloc"); + + if ((ao_channel & 0xf) != ao_channel) + error(EXIT,0, "wrong ao channel (%d)", ao_channel); + + references.set_bits(REF_POS_CAL_AO | REF_NEG_CAL_GROUND | ao_channel << 15); + + /* low nominals */ + data.codes[0].nominal = low_code; + ops.data.write(&low_code, &ao_subd, ao_channel, ao_rng, AREF_GROUND); + references.get_min_speriod(&speriod); + references.read_doubles(readings, NI_M_NR_SAMPLES, speriod, ai_rng, + NI_M_SETTLE_TIME); + math.stats.mean(&measured_low_code, readings, NI_M_NR_SAMPLES); + math.polynomial.linearize(&data.codes[0].measured, node->polynomial, + measured_low_code); + + /* high nominals */ + high_code = get_high_code(ai_rng, ao_rng); + data.codes[1].nominal = (1.0) * (double) high_code; + ops.data.write(&high_code, &ao_subd, ao_channel, ao_rng, AREF_GROUND); + references.get_min_speriod(&speriod); + references.read_doubles(readings, NI_M_NR_SAMPLES, speriod, ai_rng, + NI_M_SETTLE_TIME); + math.stats.mean(&measured_high_code, readings, NI_M_NR_SAMPLES); + math.polynomial.linearize(&data.codes[1].measured, node->polynomial, + measured_high_code); + + poly.expansion_origin = 0.0; + poly.order = data.nb_codes - 1; + __debug("AO calibration for channel %d, range %d \n", ao_channel, ao_rng); + + for (i = 0; i < data.nb_codes ; i++) + __debug("set ao to %g, measured %g \n", data.codes[i].nominal, + data.codes[i].measured); + + /*---------------------------------------------------------------------- + * the comedi calibration seems to invert the nominal and measured + * values (I suppose they know about this) so I will have to hack it + */ + for (i = 0; i < data.nb_codes; i++) { + tmp = data.codes[i].measured ; + data.codes[i].measured = data.codes[i].nominal; + data.codes[i].nominal = tmp; + } + /*--------------------------------------------------------------------*/ + math.polynomial.fit(&poly, &data); + + append_calibration_node(&ao_calibration_list, &poly, ao_channel, ao_rng); + + print_polynomial(&poly); + free(data.codes); + + return 0; +} + +static int +ni_m_calibrate_ao(void) +{ + a4l_rnginfo_t *range_info; + a4l_chinfo_t *chan_info; + unsigned channel, range; + unsigned ai_range; + int err; + + list_init(&ao_calibration_list); + + err = a4l_get_chinfo(&descriptor, ao_subd.idx, 0, &chan_info); + if (err) + error(EXIT, 0,"a4l_get_chinfo (%d)", err); + + ao_subd.slen = a4l_sizeof_chan(chan_info); + if (ao_subd.slen < 0) + error (RETURN, 0, "a4l_sizeof_chan (%d)", err); + + for (channel = 0; channel < ao_subd.info->nb_chan; channel++) { + for (range = 0 ; range < chan_info->nb_rng; range++) { + + err = a4l_get_rnginfo(&descriptor, ao_subd.idx, 0, range, + &range_info); + if (err) + error(EXIT, 0, "a4l_get_rng_info (%d)", err); + + if (A4L_RNG_UNIT(range_info->flags) != A4L_RNG_VOLT_UNIT) + continue; + + ai_range = find_ai_range_for_ao(range); + + err = calibrate_ao_channel_and_range(ai_range, channel, + range); + if (err) + error(EXIT, 0, "calibrate_ao"); + } + } + + return 0; +} + +/* + * main entry + */ +int ni_m_software_calibrate(void) +{ + a4l_sbinfo_t *sbinfo; + int i, err; + + __debug("calibrating device: %s \n", descriptor.board_name); + + descriptor.sbdata = malloc(descriptor.sbsize); + if (descriptor.sbdata == NULL) + error(EXIT, 0, "malloc ENOMEM (requested %d)", descriptor.sbsize); + + err = a4l_fill_desc(&descriptor); + if (err) + error(EXIT, 0, "a4l_fill_desc (%d)", err); + + for (i = 0; i < descriptor.nb_subd; i++) { + + err = a4l_get_subdinfo(&descriptor, i, &sbinfo); + if (err < 0) + error(EXIT, 0, "a4l_get_subdinfo (%d)", err); + + switch (sbinfo->flags & A4L_SUBD_TYPES) { + case A4L_SUBD_CALIB: + SET_SUBD(cal, i, sbinfo, "calibration"); + break; + case A4L_SUBD_AI: + SET_SUBD(ai, i, sbinfo, "analog_input"); + break; + case A4L_SUBD_AO: + SET_SUBD(ao, i, sbinfo, "analog_output"); + break; + case A4L_SUBD_MEMORY: + SET_SUBD(mem, i, sbinfo, "memory"); + break; + } + } + + if (cal_subd.idx < 0 || ai_subd.idx < 0 || mem_subd.idx < 0) + error(EXIT, 0, "can't find subdevice"); + + err = ni_m_calibrate_ai(); + if (err) + error(EXIT, 0, "ai calibration error (%d)", err); + + write_calibration(&ai_calibration_list, &ai_subd); + + /* only calibrate the analog output subdevice if present */ + if (ao_subd.idx < 0) { + __debug("analog output not present \n"); + return 0; + } + + err = ni_m_calibrate_ao(); + if (err) + error(EXIT, 0, "ao calibration error (%d)", err); + + write_calibration(&ao_calibration_list, &ao_subd); + + return 0; +} + +static void __attribute__ ((constructor)) __ni_m_calibrate_init(void) +{ + init_interface(references, REFERENCES); + init_interface(eeprom, EEPROM); + init_interface(ops,SUBDEV_OPS); + init_interface(mem_subd, SUBD); + init_interface(cal_subd, SUBD); + init_interface(math, GNU_MATH); + init_interface(ao_subd, SUBD); + init_interface(ai_subd, SUBD); +} + diff --git a/utils/analogy/calibration_ni_m.h b/utils/analogy/calibration_ni_m.h new file mode 100644 index 0000000..c9805bb --- /dev/null +++ b/utils/analogy/calibration_ni_m.h @@ -0,0 +1,280 @@ +/** + * @file + * Analogy for Linux, NI - M calibration program + * + * @note Copyright (C) 2014 Jorge A. Ramirez-Ortiz <j...@xenomai.org> + * + * from original code from the Comedi project + * + * Xenomai is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Xenomai is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Xenomai; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef __NI_M_SOFTWARE_CALIBRATE_H__ +#define __NI_M_SOFTWARE_CALIBRATE_H__ + +#include "analogy_calibrate.h" +#include "boilerplate/list.h" + +extern const char *ni_m_boards[]; +extern const int nr_ni_m_boards; + +#define ni_m_board_supported(id) __array_search(id, ni_m_boards, nr_ni_m_boards) +int ni_m_software_calibrate(void); + +#define init_interface(a, b) a = ((typeof(a)) INIT_##b); + +#define SET_BIT(n,set) do { *(set) |= (1 << n); } while(0) + +/* + * subdevice + */ +#define SET_SUBD(type, a, b, c) \ + do { type##_subd.idx = a; \ + type##_subd.info = b; \ + type##_subd.name = c; \ + } while (0) + +#define INIT_SUBD \ +{ \ + .slen = 0, \ + .idx = -1, \ + .info = NULL, \ + .name = NULL, \ +} + +struct subdevice { + a4l_sbinfo_t *info; + int slen; + int idx; + char *name; +}; + +/* + * eeprom + */ +#define INIT_EEPROM_OPS \ +{ \ + .get_calibration_base_address = eeprom_get_calibration_base_address, \ + .read_reference_voltage = eeprom_read_reference_voltage, \ + .read_uint16 = eeprom_read_uint16, \ + .read_float = eeprom_read_float, \ + .read_byte = eeprom_read_byte, \ +} + +#define INIT_EEPROM \ +{ \ + .ops = INIT_EEPROM_OPS, \ + .voltage_ref_offset = 12, \ +} + +typedef int (*eeprom_get_calibration_base_address_function)(unsigned *); +typedef int (*eeprom_read_uint16_function)(unsigned, unsigned *); +typedef int (*eeprom_read_byte_function)(unsigned, unsigned *); +typedef int (*eeprom_read_reference_voltage_function)(float *); +typedef int (*eeprom_read_float_function)(unsigned, float *); + +struct eeprom_ops { + eeprom_get_calibration_base_address_function get_calibration_base_address; + eeprom_read_reference_voltage_function read_reference_voltage; + eeprom_read_uint16_function read_uint16; + eeprom_read_float_function read_float; + eeprom_read_byte_function read_byte; +}; + +struct eeprom { + struct eeprom_ops ops; + int voltage_ref_offset; +}; + +/* + * subdevice operations + */ +#define INIT_SUBDEV_DATA_OPS \ +{ \ + .read_async = data_read_async, \ + .read_hint = data_read_hint, \ + .read = data_read, \ + .write = data_write, \ +} + +#define INIT_SUBDEV_OPS \ +{ \ + .data = INIT_SUBDEV_DATA_OPS \ +} + +typedef int (*data_read_async_function)(void *, struct subdevice *, unsigned , int , int); +typedef int (*data_read_hint_function)(struct subdevice *s, int, int, int, unsigned); +typedef int (*data_read_function)(unsigned *, struct subdevice *, int, int, int); +typedef int (*data_write_function)(long int *, struct subdevice *s, int, int, int); + +struct subdev_ops { + struct data_ops { + data_read_async_function read_async; + data_read_hint_function read_hint; + data_write_function write; + data_read_function read; + } data; +}; + + +/* + * gnumath + */ +#define INIT_GNU_MATH_STATS \ +{ \ + .stddev_of_mean = statistics_standard_deviation_of_mean, \ + .stddev = statistics_standard_deviation, \ + .mean = statistics_mean, \ +} + +#define INIT_GNU_MATH_POLYNOMIAL \ +{ \ + .fit = polynomial_fit, \ + .linearize = polynomial_linearize, \ +} + +#define INIT_GNU_MATH \ +{ \ + .stats = INIT_GNU_MATH_STATS, \ + .polynomial = INIT_GNU_MATH_POLYNOMIAL, \ +} + +struct polynomial { + double expansion_origin; + double *coefficients; + int nb_coefficients; + int order; +}; + +struct codes { + double measured; + double nominal; +}; + +struct codes_info { + struct codes *codes; + int nb_codes; +}; + +typedef int (*statistics_standard_deviation_of_mean_function)(double *, double [], int, double ); +typedef int (*statistics_standard_deviation_function)(double *, double [], int, double); +typedef int (*statistics_mean_function)(double *, double [], int); + +struct statistics_ops { + statistics_standard_deviation_of_mean_function stddev_of_mean; + statistics_standard_deviation_function stddev; + statistics_mean_function mean; +}; + + +typedef int (*polynomial_linearize_function) (double *, struct polynomial *, double); +typedef int (*polynomial_fit_function)(struct polynomial *, struct codes_info *); +struct polynomial_ops { + polynomial_fit_function fit; + polynomial_linearize_function linearize; +}; + +struct gnumath { + struct statistics_ops stats; + struct polynomial_ops polynomial; +}; + +/* + * reference + */ +#define positive_cal_shift 7 +#define negative_cal_shift 10 +#define REF_POS_CAL (2 << positive_cal_shift) +#define REF_POS_CAL_PWM_500mV (3 << positive_cal_shift) +#define REF_POS_CAL_PWM_2V (4 << positive_cal_shift) +#define REF_POS_CAL_PWM_10V (5 << positive_cal_shift) +#define REF_POS_CAL_GROUND (6 << positive_cal_shift) +#define REF_POS_CAL_AO (7 << positive_cal_shift) + +#define REF_NEG_CAL_1V (2 << negative_cal_shift) +#define REF_NEG_CAL_1mV (3 << negative_cal_shift) +#define REF_NEG_CAL_GROUND (5 << negative_cal_shift) +#define REF_NEG_CAL_GROUND2 (6 << negative_cal_shift) +#define REF_NEG_CAL_PWM_10V (7 << negative_cal_shift) + +#define INIT_REFERENCES \ +{ \ + .get_min_speriod = reference_get_min_sampling_period, \ + .set_bits = reference_set_bits, \ + .set_pwm = reference_set_pwm, \ + .read_samples = reference_read_samples, \ + .read_doubles = reference_read_doubles, \ +} + +typedef int (*reference_set_pwm_function)(struct subdevice *s, unsigned, unsigned, unsigned *, unsigned *); +typedef int (*reference_read_reference_doubles_function)(double [], unsigned, int, int, unsigned); +typedef int (*reference_read_reference_samples_function)(void *, unsigned, int, int, unsigned); +typedef int (*reference_get_min_sampling_period_function)(int *); +typedef int (*reference_set_reference_channel_function)(void); +typedef int (*reference_set_reference_src_function)(void); +typedef int (*reference_set_bits_function)(unsigned); + +struct references { + reference_set_reference_src_function set_src; + reference_set_reference_channel_function set_chan; + reference_set_pwm_function set_pwm; + reference_read_reference_samples_function read_samples; + reference_read_reference_doubles_function read_doubles; + reference_get_min_sampling_period_function get_min_speriod; + /* private */ + reference_set_bits_function set_bits; + +}; + +struct characterization_node { + double mean; + int up_tick; +}; + +struct pwm_info { + struct characterization_node *node; + unsigned nb_nodes; +}; + +/* + * NI M calibrator data + */ + +#define NI_M_MIN_PWM_PULSE_TICKS ( 0x20 ) +#define NI_M_MASTER_CLOCK_PERIOD ( 50 ) +#define NI_M_TARGET_PWM_PERIOD_TICKS ( 20 * NI_M_MIN_PWM_PULSE_TICKS ) +#define NI_M_NR_SAMPLES ( 15000 ) +#define NI_M_BASE_RANGE ( 0 ) +#define NI_M_SETTLE_TIME ( 1000000 ) + + +struct subdevice_calibration_node { + struct holder node; + struct polynomial *polynomial; + unsigned channel; + unsigned range; +}; + +struct calibrated_ranges { + unsigned *ranges; + unsigned nb_ranges; +}; + +#define ALL_CHANNELS 0xFFFFFFFF +#define ALL_RANGES 0xFFFFFFFF + + + +#endif _______________________________________________ Xenomai-git mailing list Xenomai-git@xenomai.org http://www.xenomai.org/mailman/listinfo/xenomai-git