summaryrefslogtreecommitdiff
path: root/spectro/spyd2.c
diff options
context:
space:
mode:
Diffstat (limited to 'spectro/spyd2.c')
-rw-r--r--spectro/spyd2.c3808
1 files changed, 3808 insertions, 0 deletions
diff --git a/spectro/spyd2.c b/spectro/spyd2.c
new file mode 100644
index 0000000..f8f9bac
--- /dev/null
+++ b/spectro/spyd2.c
@@ -0,0 +1,3808 @@
+
+/*
+ * Argyll Color Correction System
+ *
+ * Datacolor/ColorVision Spyder 2/3/4 related software.
+ *
+ * Author: Graeme W. Gill
+ * Date: 17/9/2007
+ *
+ * Copyright 2006 - 2013, Graeme W. Gill
+ * All rights reserved.
+ *
+ * (Based initially on i1disp.c)
+ *
+ * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+ * see the License2.txt file for licencing details.
+ */
+
+/*
+ IMPORTANT NOTES:
+
+ The Spyder 2 instrument cannot function without the driver software
+ having access to the vendor supplied PLD firmware pattern for it.
+ This firmware is not provided with Argyll, since it is not available
+ under a compatible license.
+
+ The purchaser of a Spyder 2 instrument should have received a copy
+ of this firmware along with their instrument, and should therefore be able to
+ enable the Argyll driver for this instrument by using the spyd2en utility
+ to create a spyd2PLD.bin file.
+
+ [ The Spyder 3 & 4 don't need a PLD firmware file. ]
+
+
+ The Spyder 4 instrument will not have the full range of manufacturer
+ calibration settings available without the vendor calibration data.
+ This calibration day is not provided with Argyll, since it is not
+ available under a compatible license.
+
+ The purchaser of a Spyder 4 instrument should have received a copy
+ of this calibration data along with their instrument, and should therefore
+ be able to enable the use of the full range of calibration settings
+ by using the spyd4en utility to create a spyd4cal.bin file.
+
+ Alternatively, you can use Argyll .ccss files to set a Spyder 4
+ calibration.
+
+ [ The Spyder 2 & 3 don't need a calibration data file. ]
+
+ The hwver == 5 code is not fully implemented.
+ It's not possible to get it going without an instrument to verify on.
+ (Perhaps it has only 4 sensors ?)
+
+ The frequency measurement is not very accurate, particularly for
+ the Spyder 3 & 4, being too low by about 3.5%.
+
+ */
+
+/*
+ If you make use of the instrument driver code here, please note
+ that it is the author(s) of the code who take responsibility
+ for its operation. Any problems or queries regarding driving
+ instruments with the Argyll drivers, should be directed to
+ the Argyll's author(s), and not to any other party.
+
+ If there is some instrument feature or function that you
+ would like supported here, it is recommended that you
+ contact Argyll's author(s) first, rather than attempt to
+ modify the software yourself, if you don't have firm knowledge
+ of the instrument communicate protocols. There is a chance
+ that an instrument could be damaged by an incautious command
+ sequence, and the instrument companies generally cannot and
+ will not support developers that they have not qualified
+ and agreed to support.
+ */
+
+/* TTBD:
+
+ Would be good to add read/write data values if debug >= 3
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <time.h>
+#include <stdarg.h>
+#include <math.h>
+#include <fcntl.h>
+#ifndef SALONEINSTLIB
+#include "copyright.h"
+#include "aconfig.h"
+#include "numlib.h"
+#else /* SALONEINSTLIB */
+#include "sa_config.h"
+#include "numsup.h"
+#endif /* SALONEINSTLIB */
+#include "xspect.h"
+#include "insttypes.h"
+#include "conv.h"
+#include "icoms.h"
+#include "spyd2.h"
+
+#undef PLOT_SPECTRA /* Plot the sensor senitivity spectra */
+#undef PLOT_SPECTRA_EXTRA /* Plot the sensor senitivity spectra extra values */
+#undef SAVE_SPECTRA /* Save the sensor senitivity spectra to "sensors.sp" */
+#undef SAVE_XYZSPECTRA /* Save the XYZ senitivity spectra to "sensorsxyz.sp" (scale 1.4) */
+#undef SAVE_STDXYZ /* save 1931 2 degree to stdobsxyz.sp */
+
+
+#define DO_RESETEP /* Do the miscelanous resetep()'s */
+#define CLKRATE 1000000 /* Clockrate the Spyder 2 hardware runs at */
+#define MSECDIV (CLKRATE/1000) /* Divider to turn clocks into msec */
+#define DEFRRATE 50 /* Default display refresh rate */
+#define DO_ADAPTIVE /* Adapt the integration time to the light level */
+ /* This helps repeatability at low levels A LOT */
+
+#define LEVEL2 /* Second level (nonliniarity) calibration */
+#define RETRIES 4 /* usb_reads are unreliable - bug in spyder H/W ?*/
+
+#ifdef DO_ADAPTIVE
+# define RINTTIME 2.0 /* Base time to integrate reading over - refresh display */
+# define NINTTIME 2.0 /* Base time to integrate reading over - non-refresh display */
+#else /* !DO_ADAPTIVE */
+# define RINTTIME 5.0 /* Integrate over fixed longer time (manufacturers default) */
+# define NINTTIME 5.0 /* Integrate over fixed longer time (manufacturers default) */
+#endif /* !DO_ADAPTIVE */
+
+static inst_code spyd2_interp_code(inst *pp, int ec);
+
+/* ------------------------------------------------------------------------ */
+/* Implementation */
+
+/* Interpret an icoms error into a SPYD2 error */
+static int icoms2spyd2_err(int se) {
+ if (se != ICOM_OK)
+ return SPYD2_COMS_FAIL;
+ return SPYD2_OK;
+}
+
+/* ----------------------------------------------------- */
+/* Big endian wire format conversion routines */
+
+/* Take an int, and convert it into a byte buffer big endian */
+static void int2buf(unsigned char *buf, int inv) {
+ buf[0] = (unsigned char)(inv >> 24) & 0xff;
+ buf[1] = (unsigned char)(inv >> 16) & 0xff;
+ buf[2] = (unsigned char)(inv >> 8) & 0xff;
+ buf[3] = (unsigned char)(inv >> 0) & 0xff;
+}
+
+/* Take a short, and convert it into a byte buffer big endian */
+static void short2buf(unsigned char *buf, int inv) {
+ buf[0] = (unsigned char)(inv >> 8) & 0xff;
+ buf[1] = (unsigned char)(inv >> 0) & 0xff;
+}
+
+/* Take a short sized buffer, and convert it to an int big endian */
+static int buf2short(unsigned char *buf) {
+ int val;
+ val = buf[0];
+ val = ((val << 8) + (0xff & buf[1]));
+ return val;
+}
+
+/* Take a unsigned short sized buffer, and convert it to an int big endian */
+static int buf2ushort(unsigned char *buf) {
+ int val;
+ val = (0xff & buf[0]);
+ val = ((val << 8) + (0xff & buf[1]));
+ return val;
+}
+
+/* Take a word sized buffer, and convert it to an int big endian. */
+static int buf2int(unsigned char *buf) {
+ int val;
+ val = buf[0];
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[3]));
+ return val;
+}
+
+/* Take a word sized buffer, and convert it to an unsigned int big endian. */
+static unsigned int buf2uint(unsigned char *buf) {
+ unsigned int val;
+ val = buf[0];
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[3]));
+ return val;
+}
+
+
+/* Take a 3 byte buffer, and convert it to an unsigned int big endian. */
+static unsigned int buf2uint24(unsigned char *buf) {
+ unsigned int val;
+ val = buf[0];
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[2]));
+ return val;
+}
+
+/* Take a 24 bit unsigned sized buffer in little endian */
+/* format, and return an int */
+static unsigned int buf2uint24le(unsigned char *buf) {
+ unsigned int val;
+ val = (0xff & buf[2]);
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a 24 bit unsigned sized buffer in little endian */
+/* nibble swapped format, and return an int */
+static unsigned int buf2uint24lens(unsigned char *buf) {
+ unsigned int val;
+ val = (0xf & buf[2]);
+ val = (val << 4) + (0xf & (buf[2] >> 4));
+ val = (val << 4) + (0xf & buf[1]);
+ val = (val << 4) + (0xf & (buf[1] >> 4));
+ val = (val << 4) + (0xf & buf[0]);
+ val = (val << 4) + (0xf & (buf[0] >> 4));
+ return val;
+}
+
+/* Take a 64 sized return buffer, and convert it to a ORD64 */
+static ORD64 buf2ord64(unsigned char *buf) {
+ ORD64 val;
+ val = buf[7];
+ val = ((val << 8) + (0xff & buf[6]));
+ val = ((val << 8) + (0xff & buf[5]));
+ val = ((val << 8) + (0xff & buf[4]));
+ val = ((val << 8) + (0xff & buf[3]));
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* ============================================================ */
+/* Low level commands */
+
+
+/* USB Instrument commands */
+
+/* Spyder 2: Reset the instrument */
+static inst_code
+spyd2_reset(
+ spyd2 *p
+) {
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 3, "spyd2_reset: called\n");
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC7, 0, 0, NULL, 0, 5.0);
+
+ if (se == ICOM_OK) {
+ a1logd(p->log, 6, "spyd2_reset: complete, ICOM code 0x%x\n",se);
+ break;
+ }
+ if (retr >= RETRIES ) {
+ a1logd(p->log, 1, "spyd2_reset: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_reset: reset retry with ICOM err 0x%x\n",se);
+ }
+
+ return rv;
+}
+
+/* Spyder 2: Get status */
+/* return pointer may be NULL if not needed. */
+static inst_code
+spyd2_getstatus(
+ spyd2 *p,
+ int *stat /* Return the 1 byte status code */
+) {
+ unsigned char pbuf[8]; /* status bytes read */
+ int _stat;
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 3, "spyd2_getstatus: called\n");
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC6, 0, 0, pbuf, 8, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES ) {
+ a1logd(p->log, 1, "spyd2_getstatus: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_getstatus: retry with ICOM err 0x%x\n",se);
+ }
+ msec_sleep(100); /* Limit rate status commands can be given */
+
+ _stat = pbuf[0]; /* Only the first byte is examined. */
+ /* Other bytes have information, but SW ignores them */
+
+ a1logd(p->log, 3, "spyd2_getstatus: returns %d ICOM err 0x%x\n", _stat, se);
+
+ if (stat != NULL) *stat = _stat;
+
+ return rv;
+}
+
+/* Read Serial EEProm bytes (implementation) */
+/* Can't read more than 256 in one go */
+static inst_code
+spyd2_readEEProm_imp(
+ spyd2 *p,
+ unsigned char *buf, /* Buffer to return bytes in */
+ int addr, /* Serial EEprom address, 0 - 1023 */
+ int size /* Number of bytes to read, 0 - 128 (ie. max of bank) */
+) {
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 3, "spyd2_readEEProm_imp: addr %d, bytes %d\n",addr,size);
+
+ if (addr < 0
+ || (p->hwver < 7 && (addr + size) > 512)
+ || (p->hwver == 7 && (addr + size) > 1024))
+ return spyd2_interp_code((inst *)p, SPYD2_BAD_EE_ADDRESS);
+
+ if (size >= 256)
+ return spyd2_interp_code((inst *)p, SPYD2_BAD_EE_SIZE);
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC4, addr, size, buf, size, 5.0);
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_readEEProm_imp: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_readEEProm_imp: retry with ICOM err 0x%x\n",se);
+ }
+
+
+ a1logd(p->log, 3, "spyd2_readEEProm_imp: returning ICOM err 0x%x\n", se);
+
+ return rv;
+}
+
+/* Read Serial EEProm bytes */
+/* (Handles reads > 256 bytes) */
+static inst_code
+spyd2_readEEProm(
+ spyd2 *p,
+ unsigned char *buf, /* Buffer to return bytes in */
+ int addr, /* Serial EEprom address, 0 - 511 */
+ int size /* Number of bytes to read, 0 - 511 */
+) {
+
+ if (addr < 0
+ || (p->hwver < 7 && (addr + size) > 512)
+ || (p->hwver == 7 && (addr + size) > 1024))
+ return spyd2_interp_code((inst *)p, SPYD2_BAD_EE_ADDRESS);
+
+ while (size > 255) { /* Single read is too big */
+ inst_code rv;
+ if ((rv = spyd2_readEEProm_imp(p, buf, addr, 255)) != inst_ok)
+ return rv;
+ size -= 255;
+ buf += 255;
+ addr += 255;
+ }
+ return spyd2_readEEProm_imp(p, buf, addr, size);
+}
+
+/* Spyder 2: Download PLD pattern */
+static inst_code
+spyd2_loadPLD(
+ spyd2 *p,
+ unsigned char *buf, /* Bytes to download */
+ int size /* Number of bytes */
+) {
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 6, "spyd2_loadPLD: Load PLD %d bytes\n",size);
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC0, 0, 0, buf, size, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES ) {
+ a1logd(p->log, 1, "spyd2_loadPLD: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_loadPLD: retry with ICOM err 0x%x\n",se);
+ }
+
+ a1logd(p->log, 6, "spyd2_loadPLD: returns ICOM err 0x%x\n", se);
+
+ return rv;
+}
+
+/* Get minmax command. */
+/* Figures out the current minimum and maximum frequency periods */
+/* so as to be able to set a frame detect threshold. */
+/* Note it returns 0,0 if there is not enough light. */
+/* (The light to frequency output period size is inversly */
+/* related to the lightness level) */
+/* (This isn't used by the manufacturers Spyder3/4 driver, */
+/* but the instrument seems to impliment it.) */
+static inst_code
+spyd2_GetMinMax(
+ spyd2 *p,
+ int *clocks, /* Number of clocks to use (may get limited) */
+ int *min, /* Return min and max light->frequency periods */
+ int *max
+) {
+ int rwbytes; /* Data bytes read or written */
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int index;
+ int retr;
+ unsigned char buf[8]; /* return bytes read */
+
+ a1logd(p->log, 2, "spyd2_GetMinMax: %d clocks\n",*clocks);
+
+ /* Issue the triggering command */
+ if (*clocks > 0xffffff)
+ *clocks = 0xffffff; /* Maximum count hardware will take ? */
+ value = *clocks >> 8;
+ value = (value >> 8) | ((value << 8) & 0xff00); /* Convert to big endian */
+ index = (*clocks << 8) & 0xffff;
+ index = (index >> 8) | ((index << 8) & 0xff00); /* Convert to big endian */
+
+ for (retr = 0; ; retr++) {
+ /* Issue the trigger command */
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC2, value, index, NULL, 0, 5.0);
+
+ if ((se != ICOM_OK && retr >= RETRIES)) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rwbytes, 1.0);
+
+ a1logd(p->log, 1, "spyd2_GetMinMax: trig failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ if (se != ICOM_OK) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rwbytes, 1.0);
+
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetMinMax: trig retry with ICOM err 0x%x\n",se);
+ continue;
+ }
+
+ a1logd(p->log, 3, "spyd2_GetMinMax: trig returns ICOM err 0x%x\n", se);
+
+ /* Allow some time for the instrument to respond */
+ msec_sleep(*clocks/MSECDIV);
+
+ /* Now read the bytes */
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rwbytes, 5.0);
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_GetMinMax: get failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetMinMax: get retry with ICOM err 0x%x\n",se);
+ }
+
+ if (rwbytes != 8) {
+ a1logd(p->log, 1, "spyd2_GetMinMax: got short data read %d",rwbytes);
+ return spyd2_interp_code((inst *)p, SPYD2_BADREADSIZE);
+ }
+
+ *min = buf2ushort(&buf[0]);
+ *max = buf2ushort(&buf[2]);
+
+ a1logd(p->log, 3, "spyd2_GetMinMax: got %d/%d returns ICOM err 0x%x\n", *min, *max, se);
+
+ return rv;
+}
+
+/* Get refresh rate (low level) command */
+/* (This isn't used by the manufacturers Spyder3 driver, */
+/* but the instrument seems to implement it.) */
+static inst_code
+spyd2_GetRefRate_ll(
+ spyd2 *p,
+ int *clocks, /* Maximum number of clocks to use */
+ int nframes, /* Number of frames to count */
+ int thresh, /* Frame detection threshold */
+ int *minfclks, /* Minimum number of clocks per frame */
+ int *maxfclks, /* Maximum number of clocks per frame */
+ int *clkcnt /* Return number of clocks for nframes frames */
+) {
+ int rwbytes; /* Data bytes read or written */
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int index;
+ int flag;
+ int retr;
+ unsigned char buf1[8]; /* send bytes */
+ unsigned char buf2[8]; /* return bytes read */
+
+ a1logd(p->log, 3, "spyd2_GetRefRate_ll: %d clocks\n",*clocks);
+
+ /* Setup the triggering parameters */
+ if (*clocks > 0xffffff) /* Enforce hardware limits */
+ *clocks = 0xffffff;
+ if (*minfclks > 0x7fff)
+ *minfclks = 0x7fff;
+ if (*maxfclks > 0x7fff)
+ *maxfclks = 0x7fff;
+ value = *clocks >> 8;
+ value = (value >> 8) | ((value << 8) & 0xff00); /* Convert to big endian */
+ index = (*clocks << 8) & 0xffff;
+ index = (index >> 8) | ((index << 8) & 0xff00); /* Convert to big endian */
+
+ /* Setup parameters in send buffer */
+ short2buf(&buf1[0], thresh);
+ short2buf(&buf1[2], nframes);
+ short2buf(&buf1[4], *minfclks);
+ short2buf(&buf1[6], *maxfclks);
+
+ /* Issue the triggering command */
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC3, value, index, buf1, 8, 5.0);
+
+ if (se != ICOM_OK && retr >= RETRIES) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 1.0);
+
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: trig failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ if (se != ICOM_OK) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 1.0);
+
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: trig retry with ICOM err 0x%x\n",se);
+ continue;
+ }
+
+ a1logd(p->log, 3, "spyd2_GetRefRate_ll: trig returns ICOM err 0x%x\n", se);
+
+ /* Allow some time for the instrument to respond */
+ msec_sleep(*clocks/MSECDIV);
+
+ /* Now read the bytes */
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 5.0);
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 3, "spyd2_GetRefRate_ll: get failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: get retry with ICOM err 0x%x\n",se);
+ }
+
+ if (rwbytes != 8) {
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: got short data read %d",rwbytes);
+ return spyd2_interp_code((inst *)p, SPYD2_BADREADSIZE);
+ }
+
+ flag = buf2[0];
+ *clkcnt = buf2uint24(&buf2[1]);
+
+ /* Spyder2 */
+ if (p->hwver < 4 && flag == 1) {
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: got trigger timeout");
+ return spyd2_interp_code((inst *)p, SPYD2_TRIGTIMEOUT);
+ }
+
+ /* Spyder2 */
+ if (p->hwver < 4 && flag == 2) {
+ a1logd(p->log, 1, "spyd2_GetRefRate_ll: got overall timeout");
+ return spyd2_interp_code((inst *)p, SPYD2_OVERALLTIMEOUT);
+ }
+
+ a1logd(p->log, 3, "spyd2_GetRefRate_ll: result %d, returns ICOM err 0x%x\n", *clkcnt, se);
+
+ return rv;
+}
+
+/* Get a reading (low level) command */
+static inst_code
+spyd2_GetReading_ll(
+ spyd2 *p,
+ int *clocks, /* Nominal/Maximum number of integration clocks to use */
+ int nframes, /* Number of refresh frames being measured (not used ?) */
+ int thresh, /* Frame detection threshold */
+ int *minfclks, /* Minimum number of clocks per frame */
+ int *maxfclks, /* Maximum number of clocks per frame */
+ double *sensv, /* Return the 8 sensor readings (may be NULL) */
+ int *maxtcnt, /* Return the maximum transition count (may be NULL) */
+ int *mintcnt /* Return the minimum transition count (may be NULL) */
+) {
+ int rwbytes; /* Data bytes read or written */
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int index;
+ int flag;
+ int nords, retr;
+ unsigned char buf1[8]; /* send bytes */
+ unsigned char buf2[9 * 8]; /* return bytes read */
+ int rvals[3][8]; /* Raw values */
+ int _maxtcnt = 0; /* Maximum transition count */
+ int _mintcnt = 0x7fffffff; /* Minumum transition count */
+ int i, j, k;
+
+ a1logd(p->log, 3, "spyd2_GetReading_ll: clocks = %d, minfc = %d, maxfc = %d\n",*clocks,*minfclks,*maxfclks);
+
+ /* Setup the triggering parameters */
+ if (*clocks > 0xffffff)
+ *clocks = 0xffffff;
+ if (*minfclks > 0x7fff)
+ *minfclks = 0x7fff;
+ if (*maxfclks > 0x7fff)
+ *maxfclks = 0x7fff;
+ value = *clocks >> 8;
+ if (p->hwver == 5) { /* Hmm. not sure if this is right */
+ value /= 1000;
+ }
+ value = (value >> 8) | ((value << 8) & 0xff00); /* Convert to big endian */
+ index = (*clocks << 8) & 0xffff;
+ index = (index >> 8) | ((index << 8) & 0xff00); /* Convert to big endian */
+
+ /* Setup parameters in send buffer */
+ /* (Spyder3 doesn't seem to use these. Perhaps it does its */
+ /* own internal refresh detection and syncronization ?) */
+ thresh *= 256;
+ int2buf(&buf1[0], thresh);
+ short2buf(&buf1[4], *minfclks);
+ short2buf(&buf1[6], *maxfclks);
+
+ /* If we aborted a read, the prevraw values are now invalid. */
+ /* We fix it by doing a dumy reading. */
+ if (p->hwver < 4 && p->prevrawinv) {
+ int clocks = 500;
+ int minfclks = 0;
+ int maxfclks = 0;
+ p->prevrawinv = 0;
+ a1logd(p->log, 3, "spyd2_GetReading_ll: doing dummy read to get prevraw\n");
+ if ((rv = spyd2_GetReading_ll(p, &clocks, 10, 0, &minfclks, &maxfclks, NULL, NULL, NULL)) != inst_ok) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: dummy read failed\n");
+ p->prevrawinv = 1;
+ return rv;
+ }
+ }
+
+ /* The Spyder comms seems especially flakey... */
+ for (retr = 0, nords = 1; ; retr++, nords++) {
+
+//int start = msec_time();
+
+ /* Issue the triggering command */
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xC1, value, index, buf1, 8, 5.0);
+
+ if (se != ICOM_OK && retr >= RETRIES) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ for (i = 0; i < (1+9); i++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 1.0);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+
+ a1logd(p->log, 1, "spyd2_GetReading_ll: trig failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ if (se != ICOM_OK) {
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ msec_sleep(*clocks/MSECDIV);
+ for (i = 0; i < (1+9); i++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 1.0);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetReading_ll: trig retry with ICOM err 0x%x\n",se);
+ continue;
+ }
+
+ a1logd(p->log, 3, "spyd2_GetReading_ll: reading returns ICOM code 0x%x\n", se);
+
+ /* Allow some time for the instrument to respond */
+ msec_sleep(*clocks/MSECDIV);
+
+ /* Now read the first 8 bytes (status etc.) */
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 5.0);
+ if (se != ICOM_OK && retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: read stat failed with ICOM err 0x%x\n",se);
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (i = 0; i < (1+9); i++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+ msec_sleep(500);
+
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+//printf("~1 trig -> read = %d msec\n",msec_time() - start);
+
+ if (se != ICOM_OK) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: read stat retry with ICOM err 0x%x\n", se);
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (i = 0; i < (1+9); i++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+ msec_sleep(500);
+ continue; /* Retry the whole command */
+ }
+
+ if (rwbytes != 8) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: read stat got short data read %d",rwbytes);
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (i = 0; i < (1+9); i++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+ msec_sleep(500);
+
+ return spyd2_interp_code((inst *)p, SPYD2_BADREADSIZE);
+ }
+
+ flag = buf2[0];
+
+ /* Spyder2 */
+ if (p->hwver < 4 && flag == 1) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: read stat is trigger timeout");
+ return spyd2_interp_code((inst *)p, SPYD2_TRIGTIMEOUT);
+ }
+
+ /* Spyder2 */
+ if (p->hwver < 4 && flag == 2) {
+ a1logd(p->log, 1, "spyd2_GetReading_ll: read stat is overall timeout");
+ return spyd2_interp_code((inst *)p, SPYD2_OVERALLTIMEOUT);
+ }
+
+ /* Now read the following 9 x 8 bytes of sensor data */
+ for (i = 0; i < 9; i++) {
+
+ se = p->icom->usb_read(p->icom, NULL, 0x81, buf2 + i * 8, 8, &rwbytes, 5.0);
+ if (se != ICOM_OK && retr >= RETRIES) {
+ int ii = i;
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (; ii < (1+9); ii++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2 + i * 8, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+
+ a1logd(p->log, 1, "spyd2_GetReading_ll: get reading failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ if (se != ICOM_OK) {
+ int ii = i;
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (; ii < (1+9); ii++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2 + i * 8, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+
+ break;
+ }
+ if (rwbytes != 8) {
+ int ii = i;
+ a1logd(p->log, 1, "spyd2_GetReading_ll: got short data read %d",rwbytes);
+
+ /* Complete the operation so as not to leave the instrument in a hung state */
+ for (; ii < (1+9); ii++)
+ p->icom->usb_read(p->icom, NULL, 0x81, buf2 + i * 8, 8, &rwbytes, 0.5);
+ p->prevrawinv = 1; /* prevraw are now invalid */
+
+ return spyd2_interp_code((inst *)p, SPYD2_BADREADSIZE);
+ }
+ }
+
+ if (i >= 9)
+ break; /* We're done */
+
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_GetReading_ll: reading retry with ICOM err 0x%x\n",se);
+
+#ifdef DO_RESETEP /* Do the miscelanous resetep()'s */
+ a1logd(p->log, 1, "spyd2_GetReading_ll: resetting end point\n");
+ p->icom->usb_resetep(p->icom, 0x81);
+ msec_sleep(1); /* Let device recover ? */
+#endif /* DO_RESETEP */
+ } /* End of whole command retries */
+
+ if (p->log->debug >= 5) {
+ a1logd(p->log, 5, "spyd2_GetReading_ll: got bytes:\n");
+ for (i = 0; i < 9; i++) {
+ a1logd(p->log, 5, " %d: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x\n",i * 8,
+ buf2[i * 8 + 0], buf2[i * 8 + 1], buf2[i * 8 + 2], buf2[i * 8 + 3],
+ buf2[i * 8 + 4], buf2[i * 8 + 5], buf2[i * 8 + 6], buf2[i * 8 + 7]);
+ }
+ }
+
+ /* Spyder 2 decoding */
+ /* Spyder2 */
+ if (p->hwver < 4) {
+ /* Convert the raw buffer readings into 3 groups of 8 integers. */
+ /* At the start of each reading, the HW starts counting master */
+ /* (1MHz) clocks. When the first transition after the start of */
+ /* the reading is received from a light->frequency sensor (TAOS TSL237), */
+ /* the clock count is recorded, and returned as the second of the three */
+ /* numbers returned for each sensor. */
+ /* When the last transition before the end of the reading period is */
+ /* received, the clock count is recorded, and returned as the first */
+ /* of the three numbers. The integration period is therefore */
+ /* the first number minus the second. */
+ /* The third number is the number of transitions from the sensor */
+ /* counted during the integration period. Since this 24 bit counter is */
+ /* not reset between readings, the previous count is recorded (prevraw[]), */
+ /* and subtracted (modulo 24 bits) from the current value. */
+ /* The light level is directly proportional to the frequency, */
+ /* hence the transitions-1 counted. */
+ /* In the case of a CRT, the total number of clocks is assumed to be */
+ /* set to an integer number of refresh cycles, and the total transitions */
+ /* over that period are counted. */
+ for (i = j = 0; j < 3; j++) {
+ for (k = 0; k < 8; k++, i += 3) {
+ rvals[j][k] = buf2uint24lens(buf2 + i);
+// a1logd(p->log, 1, "got rvals[%d][%d] = 0x%x\n",j,k,rvals[j][k]);
+ }
+ }
+
+ /* And convert 3 values per sensor into sensor values */
+ for (k = 0; k < 8; k++) {
+ int transcnt, intclks;
+
+ /* Compute difference of L2F count to previous value */
+ /* read, modulo 24 bits */
+ a1logd(p->log, 5, "%d: transcnt %d, previous %d\n", k,rvals[2][k],p->prevraw[k]);
+ if (p->prevraw[k] <= rvals[2][k]) {
+ transcnt = rvals[2][k] - p->prevraw[k];
+ } else {
+ transcnt = rvals[2][k] + 0x1000000 - p->prevraw[k];
+ }
+
+ p->prevraw[k] = rvals[2][k]; /* New previuos value */
+
+ /* Compute difference of 1MHz clock count of first to second value */
+ intclks = rvals[0][k] - rvals[1][k];
+
+ if (transcnt == 0 || intclks <= 0) { /* It's too dark ... */
+ if (sensv != NULL)
+ sensv[k] = 0.0;
+ _mintcnt = 0;
+ } else { /* We discard 0'th L2F transion to give transitions */
+ /* over the time period. */
+ if (sensv != NULL)
+ sensv[k] = ((double)transcnt - 1.0) * (double)CLKRATE
+ / ((double)nords * (double)intclks);
+ if (transcnt > _maxtcnt)
+ _maxtcnt = transcnt;
+ if (transcnt < _mintcnt)
+ _mintcnt = transcnt;
+ }
+ if (p->log->debug >= 4 && sensv != NULL)
+ a1logd(p->log, 4, "%d: initial senv %f from transcnt %d and intclls %d\n",
+ k,sensv[k],transcnt,intclks);
+
+#ifdef NEVER /* This seems to make repeatability worse ??? */
+ /* If CRT and bright enough */
+ if (sensv != NULL && sensv[k] > 1.5 && p->refrmode != 0) {
+ sensv[k] = ((double)transcnt) * (double)p->refrate/(double)nframes;
+ }
+#endif
+ }
+
+ /* Spyder 3/4 decoding */
+ } else {
+ /* Convert the raw buffer readings into 3 groups of 8 integers. */
+ /* At the start of each reading, the HW starts counting master */
+ /* (1MHz) clocks. When the first transition after the start of */
+ /* the reading is received from a light->frequency sensor (TAOS TSL238T), */
+ /* the clock count is recorded, and returned as the second of the three */
+ /* numbers returned for each sensor. */
+ /* When the last transition before the end of the reading period is */
+ /* received, the clock count is recorded, and returned as the first */
+ /* of the three numbers. The integration period is therefore */
+ /* the first number minus the second. */
+ /* The third number is the number of transitions from the sensor */
+ /* counted during the integration period. */
+ /* The light level is directly proportional to the frequency, */
+ /* hence the transitions-1 counted. */
+
+ int map[8] = { 0,0,1,2,5,6,7,4 }; /* Map sensors into Spyder 2 order */
+
+ for (j = 0; j < 3; j++) {
+ for (k = 0; k < 8; k++) {
+ rvals[j][k] = buf2uint24le(buf2 + (j * 24 + map[k] * 3));
+// a1logd(p->log, 1, "got rvals[%d][%d] = 0x%x\n",j,k,rvals[j][k]);
+ }
+ }
+
+ /* And convert 3 integers per sensor into sensor values */
+ for (k = 0; k < 8; k++) {
+ int transcnt, intclks;
+
+ /* Number of sensor transitions */
+ transcnt = rvals[2][k];
+
+ /* Compute difference of first integer to second */
+ intclks = rvals[0][k] - rvals[1][k];
+
+ if (transcnt == 0 || intclks <= 0) { /* It's too dark ... */
+ if (sensv != NULL)
+ sensv[k] = 0.0;
+ _mintcnt = 0;
+ } else { /* Transitions within integration period */
+ /* hence one is discarded ? */
+ if (sensv != NULL)
+ sensv[k] = ((double)transcnt - 1.0) * (double)CLKRATE
+ / ((double)intclks * 8.125);
+ if (transcnt > _maxtcnt)
+ _maxtcnt = transcnt;
+ if (transcnt < _mintcnt)
+ _mintcnt = transcnt;
+ }
+ if (p->log->debug >= 4 && sensv != NULL)
+ a1logd(p->log, 4, "%d: initial senv %f from transcnt %d and intclls %d\n",
+ k,sensv[k],transcnt,intclks);
+ }
+ }
+
+ if (maxtcnt != NULL)
+ *maxtcnt = _maxtcnt;
+ if (mintcnt != NULL)
+ *mintcnt = _mintcnt;
+
+ return rv;
+}
+
+/* Spyder 3: Set the LED */
+static inst_code
+spyd2_setLED(
+ spyd2 *p,
+ int mode, /* LED mode: 0 = off, 1 = pulse, 2 = on */
+ double period /* Pulse period in seconds */
+) {
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int index;
+ int retr;
+ int ptime; /* Pulse time, 1 - 255 x 20 msec */
+
+ if (mode < 0)
+ mode = 0;
+ else if (mode > 2)
+ mode = 2;
+ ptime = (int)(period/0.02 + 0.5);
+ if (ptime < 0)
+ ptime = 0;
+ else if (ptime > 255)
+ ptime = 255;
+
+ if (p->log->debug >= 2) {
+ if (mode == 1)
+ a1logd(p->log, 3, "spyd2_setLED: set to pulse, %f secs\n",ptime * 0.02);
+ else
+ a1logd(p->log, 3, "spyd2_setLED: set to %s\n",mode == 0 ? "off" : "on");
+ }
+
+ value = mode; /* Leave little endian */
+ index = ptime;
+
+ for (retr = 0; ; retr++) {
+ /* Issue the trigger command */
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xF6, value, index, NULL, 0, 5.0);
+
+ if (se == ICOM_OK) {
+ a1logd(p->log, 5, "spyd2_setLED: OK, ICOM code 0x%x\n",se);
+ break;
+ }
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_setLED: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_setLED: retry with ICOM err 0x%x\n",se);
+ }
+
+ return rv;
+}
+
+
+/* Spyder 3: Set the ambient control register */
+static inst_code
+spyd2_SetAmbReg(
+ spyd2 *p,
+ int val /* 8 bit ambient config register value */
+) {
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int retr;
+
+ a1logd(p->log, 3, "spyd2_SetAmbReg: control register to %d\n",val);
+
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+ value = val; /* Leave little endian */
+
+ for (retr = 0; ; retr++) {
+ /* Issue the trigger command */
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xF3, value, 0, NULL, 0, 5.0);
+
+ if (se == ICOM_OK) {
+ a1logd(p->log, 5, "spyd2_SetAmbReg: OK, ICOM code 0x%x\n",se);
+ break;
+ }
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_SetAmbReg: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_SetAmbReg: retry with ICOM err 0x%x\n",se);
+ }
+
+ return rv;
+}
+
+/* Spyder3/4: Read ambient light timing */
+/* The byte value seems to be composed of:
+ bits 0,1
+ bits 4
+*/
+static inst_code
+spyd2_ReadAmbTiming(
+ spyd2 *p,
+ int *val /* Return the 8 bit timing value */
+) {
+ unsigned char pbuf[1]; /* Timing value read */
+ int _val;
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 3, "spyd2_ReadAmbTiming: called\n");
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xF4, 0, 0, pbuf, 1, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_ReadAmbTiming: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_ReadAmbTiming: retry with ICOM err 0x%x\n",se);
+ }
+
+ _val = pbuf[0];
+
+ a1logd(p->log, 5, "spyd2_ReadAmbTiming: returning val %d ICOM err 0x%x\n",_val, se);
+
+ if (val != NULL) *val = _val;
+
+ return rv;
+}
+
+
+/* Spyder3/4: Read ambient light channel 0 or 1 */
+static inst_code
+spyd2_ReadAmbChan(
+ spyd2 *p,
+ int chan, /* Ambient channel, 0 or 1 */
+ int *val /* Return the 16 bit channel value */
+) {
+ unsigned char pbuf[2]; /* Channel value read */
+ int _val;
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ chan &= 1;
+
+ a1logd(p->log, 3, "spyd2_ReadAmbChan: channel %d\n",chan);
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xF0 + chan, 0, 0, pbuf, 2, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 2, "spyd2_ReadAmbChan: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 2, "spyd2_ReadAmbChan: retry with ICOM err 0x%x\n",se);
+ }
+
+ _val = buf2ushort(pbuf);
+
+ a1logd(p->log, 3, "spyd2_ReadAmbChan: chan %d returning %d ICOM err 0x%x\n", chan, _val, se);
+
+ if (val != NULL) *val = _val;
+
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Spyder3/4: Read temperature config */
+static inst_code
+spyd2_ReadTempConfig(
+ spyd2 *p,
+ int *val /* Return the 8 bit config value */
+) {
+ unsigned char pbuf[1]; /* Config value read */
+ int _val;
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ a1logd(p->log, 3, "spyd2_ReadTempConfig: called\n");
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xE1, 0, 0, pbuf, 1, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_ReadTempConfig: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_ReadTempConfig: retry with ICOM err 0x%x\n",se);
+ }
+
+ _val = pbuf[0];
+
+ a1logd(p->log, 3, "spyd2_ReadTempConfig: returning %d ICOM err 0x%x\n", _val, se);
+
+ if (val != NULL) *val = _val;
+
+ return rv;
+}
+
+/* Spyder 3/4: Write Register */
+static inst_code
+spyd2_WriteReg(
+ spyd2 *p,
+ int reg,
+ int val /* 8 bit temp config register value */
+) {
+ int se;
+ inst_code rv = inst_ok;
+ int value;
+ int retr;
+
+ a1logd(p->log, 3, "spyd2_WriteReg: val %d to register %d\n",reg, val);
+
+ if (val < 0)
+ val = 0;
+ else if (val > 255)
+ val = 255;
+ value = val; /* Leave little endian */
+
+ reg &= 0xff;
+ value |= (reg << 8);
+
+ for (retr = 0; ; retr++) {
+ /* Issue the trigger command */
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_OUT | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xE2, value, 0, NULL, 0, 5.0);
+
+ if (se == ICOM_OK) {
+ a1logd(p->log, 5, "spyd2_WriteReg: OK, ICOM code 0x%x\n",se);
+ break;
+ }
+ if (retr >= RETRIES) {
+ a1logd(p->log, 5, "spyd2_WriteReg: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 5, "spyd2_WriteReg: retry with ICOM err 0x%x\n",se);
+ }
+
+ return rv;
+}
+
+
+/* Spyder3/4: Read Register */
+static inst_code
+spyd2_ReadRegister(
+ spyd2 *p,
+ int reg,
+ int *pval /* Return the register value */
+) {
+ unsigned char pbuf[2]; /* Temp value read */
+ int ival;
+// double _val;
+ int se;
+ int retr;
+ inst_code rv = inst_ok;
+
+ reg &= 0xff;
+
+ a1logd(p->log, 3, "spyd2_ReadRegister: register %d\n",reg);
+
+ for (retr = 0; ; retr++) {
+ se = p->icom->usb_control(p->icom,
+ IUSB_ENDPOINT_IN | IUSB_REQ_TYPE_VENDOR | IUSB_REQ_RECIP_DEVICE,
+ 0xE0, reg, 0, pbuf, 2, 5.0);
+
+ if (se == ICOM_OK)
+ break;
+ if (retr >= RETRIES) {
+ a1logd(p->log, 1, "spyd2_ReadRegister: failed with ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+ msec_sleep(500);
+ a1logd(p->log, 1, "spyd2_ReadRegister: retry with ICOM err 0x%x\n",se);
+ }
+
+ ival = buf2ushort(&pbuf[0]);
+// _val = (double)ival * 12.5; /* Read temperature */
+
+ a1logd(p->log, 1, "spyd2_ReadRegister: reg %d returning %d ICOM err 0x%x\n", ival, rv, se);
+
+ if (pval != NULL) *pval = ival;
+
+ return rv;
+}
+
+
+/* hwver == 5, set gain */
+/* Valid gain values 1, 4, 16, 64 */
+static inst_code
+spyd2_SetGain(
+ spyd2 *p,
+ int gain
+) {
+ int gv = 0;
+
+ p->gain = (double)gain;
+
+ switch (gain) {
+ case 1:
+ gv = 0;
+ break;
+ case 4:
+ gv = 16;
+ break;
+ case 16:
+ gv = 32;
+ break;
+ case 64:
+ gv = 48;
+ break;
+ }
+ return spyd2_WriteReg(p, 7, gv);
+}
+
+/* ============================================================ */
+/* Medium level commands */
+
+/* Read a 8 bits from the EEProm */
+static inst_code
+spyd2_rd_ee_uchar(
+ spyd2 *p, /* Object */
+ unsigned int *outp, /* Where to write value */
+ int addr /* EEprom Address, 0 - 510 */
+) {
+ inst_code ev;
+ unsigned char buf[1];
+
+ if ((ev = spyd2_readEEProm(p, buf, addr, 1)) != inst_ok)
+ return ev;
+
+ *outp = buf[0];
+
+ return inst_ok;
+}
+
+/* Read a 16 bit word from the EEProm */
+static inst_code
+spyd2_rd_ee_ushort(
+ spyd2 *p, /* Object */
+ unsigned int *outp, /* Where to write value */
+ int addr /* EEprom Address, 0 - 510 */
+) {
+ inst_code ev;
+ unsigned char buf[2];
+
+ if ((ev = spyd2_readEEProm(p, buf, addr, 2)) != inst_ok)
+ return ev;
+
+ *outp = buf2ushort(buf);
+
+ return inst_ok;
+}
+
+/* Read a 32 bit word from the EEProm */
+static inst_code
+spyd2_rd_ee_int(
+ spyd2 *p, /* Object */
+ int *outp, /* Where to write value */
+ int addr /* EEprom Address, 0 - 508 */
+) {
+ inst_code ev;
+ unsigned char buf[4];
+
+ if ((ev = spyd2_readEEProm(p, buf, addr, 4)) != inst_ok)
+ return ev;
+
+ *outp = buf2int(buf);
+
+ return inst_ok;
+}
+
+/* Read a float from the EEProm */
+static inst_code
+spyd2_rdreg_float(
+ spyd2 *p, /* Object */
+ double *outp, /* Where to write value */
+ int addr /* Register Address, 0 - 508 */
+) {
+ inst_code ev;
+ int val;
+
+ if ((ev = spyd2_rd_ee_int(p, &val, addr)) != inst_ok)
+ return ev;
+
+ *outp = IEEE754todouble((unsigned int)val);
+ return inst_ok;
+}
+
+unsigned int spyd4_crctab[256];
+
+static void spyd4_crc32_init(void) {
+ int i, j;
+
+ unsigned int crc;
+
+ for (i = 0; i < 256; i++) {
+ crc = i;
+ for (j = 0; j < 8; j++) {
+ if (crc & 1)
+ crc = (crc >> 1) ^ 0xedb88320;
+ else
+ crc = crc >> 1;
+ }
+ spyd4_crctab[i] = crc;
+// a1logd(p->log, 1, "spyd4_crc32_init: crctab[%d] = 0x%08x\n",i,crctab[i]);
+ }
+}
+
+static unsigned int spyd4_crc32(unsigned char *data, int len) {
+ unsigned int crc;
+ int i;
+
+ crc = ~0;
+ for (i = 0; i < len; i++)
+ crc = spyd4_crctab[(crc ^ *data++) & 0xff] ^ (crc >> 8);
+ return ~crc;
+}
+
+/* For HWV 7, check the EEPRom CRC */
+static inst_code
+spyd2_checkEECRC(
+ spyd2 *p /* Object */
+) {
+ inst_code ev;
+ unsigned char buf[1024], *bp;
+ unsigned int crct, crc; /* Target value, computed value */
+ int i;
+
+ spyd4_crc32_init();
+
+ if ((ev = spyd2_readEEProm(p, buf, 0, 1024)) != inst_ok)
+ return ev;
+
+ /* Target value */
+ crct = buf2uint(buf + 1024 - 4);
+
+ bp = buf;
+ crc = ~0;
+ for (i = 0; i < (1024 - 4); i++, bp++)
+ crc = spyd4_crctab[(crc ^ *bp) & 0xff] ^ (crc >> 8);
+ crc = ~crc;
+
+ a1logd(p->log, 4, "spyd2_checkEECRC: EEProm CRC is 0x%x, should be 0x%x\n",crc,crct);
+
+ if (crc != crct)
+ return spyd2_interp_code((inst *)p, SPYD2_BAD_EE_CRC);
+
+ return inst_ok;
+}
+
+/* Special purpose float read, */
+/* Read three 9 vectors of floats from the EEprom */
+static inst_code
+spyd2_rdreg_3x9xfloat(
+ spyd2 *p, /* Object */
+ double *out0, /* Where to write first 9 doubles */
+ double *out1, /* Where to write second 9 doubles */
+ double *out2, /* Where to write third 9 doubles */
+ int addr /* Register Address, 0 - 1023 */
+) {
+ inst_code ev;
+ unsigned char buf[3 * 9 * 4], *bp;
+ int i;
+
+ if ((ev = spyd2_readEEProm(p, buf, addr, 3 * 9 * 4)) != inst_ok)
+ return ev;
+
+ bp = buf;
+ for (i = 0; i < 9; i++, bp +=4, out0++) {
+ int val;
+ val = buf2int(bp);
+ *out0 = IEEE754todouble((unsigned int)val);
+ }
+
+ for (i = 0; i < 9; i++, bp +=4, out1++) {
+ int val;
+ val = buf2int(bp);
+ *out1 = IEEE754todouble((unsigned int)val);
+ }
+
+ for (i = 0; i < 9; i++, bp +=4, out2++) {
+ int val;
+ val = buf2int(bp);
+ *out2 = IEEE754todouble((unsigned int)val);
+ }
+
+ return inst_ok;
+}
+
+/* Special purpose short read, */
+/* Read 7 x 41 vectors of ints from the EEprom */
+static inst_code
+spyd2_rdreg_7x41xshort(
+ spyd2 *p, /* Object */
+ double sens[7][41], /* Destination */
+ int addr /* Register Address, 0 - 1023 */
+) {
+ inst_code ev;
+ unsigned char buf[7 * 41 * 2], *bp;
+ int i, j;
+
+ if ((ev = spyd2_readEEProm(p, buf, addr, 7 * 41 * 2)) != inst_ok)
+ return ev;
+
+ bp = buf;
+ for (i = 0; i < 7; i++) {
+ for (j = 0; j < 41; j++, bp += 2) {
+ int val;
+ val = buf2ushort(bp);
+ sens[i][j] = val / 100.0;
+ }
+ }
+
+ return inst_ok;
+}
+
+/* Get refresh rate command. Set it to DEFRRATE if not detectable */
+/* if no refresh rate can be established */
+/* (This isn't used by the manufacturers Spyder3/4 driver, */
+/* but the instrument seems to impliment it.) */
+static inst_code
+spyd2_read_refrate(
+ inst *pp,
+ double *ref_rate
+) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev;
+ int clocks; /* Clocks to run commands */
+ int min, max; /* min and max light intensity frequency periods */
+
+ a1logd(p->log, 3, "spyd2_read_refrate: called\n");
+
+ /* Establish the frame rate detect threshold level */
+ clocks = (10 * CLKRATE)/DEFRRATE;
+
+ if ((ev = spyd2_GetMinMax(p, &clocks, &min, &max)) != inst_ok)
+ return ev;
+
+ if (min == 0 || max < (5 * min)) {
+ a1logd(p->log, 3, "spyd2_read_refrate: no refresh rate detectable\n");
+ if (ref_rate != NULL)
+ *ref_rate = 0.0;
+ return inst_ok;
+ } else {
+ int frclocks; /* notional clocks per frame */
+ int nframes; /* Number of frames to count */
+ int thresh; /* Frame detection threshold */
+ int minfclks; /* Minimum number of clocks per frame */
+ int maxfclks; /* Maximum number of clocks per frame */
+ int clkcnt; /* Return number of clocks for nframes frames */
+
+ frclocks = CLKRATE/DEFRRATE;
+ nframes = 50;
+ thresh = (max - min)/5 + min; /* Threshold is at 80% of max brightness */
+ minfclks = frclocks/3; /* Allow for 180 Hz */
+ maxfclks = (frclocks * 5)/2; /* Allow for 24 Hz */
+ clocks = nframes * frclocks * 2; /* Allow for 120 Hz */
+
+ if ((ev = spyd2_GetRefRate_ll(p, &clocks, nframes, thresh, &minfclks, &maxfclks,
+ &clkcnt)) != inst_ok)
+ return ev;
+
+ /* Compute the refresh rate */
+ if (ref_rate != NULL)
+ *ref_rate = ((double)nframes * (double)CLKRATE)/(double)clkcnt;
+ return inst_ok;
+ }
+}
+
+/* Get refresh rate command. Set it to DEFRRATE if not detectable */
+/* if no refresh rate can be established */
+/* (This isn't used by the manufacturers Spyder3/4 driver, */
+/* but the instrument seems to impliment it.) */
+static inst_code
+spyd2_GetRefRate(
+ spyd2 *p
+) {
+ int i;
+ inst_code ev;
+
+ a1logd(p->log, 3, "Frequency calibration called\n");
+
+ if ((ev = spyd2_read_refrate((inst *)p, &p->refrate)) != inst_ok) {
+ return ev;
+ }
+ if (p->refrate != 0.0) {
+ a1logd(p->log, 3, "spyd2_GetRefRate: refresh rate is %f Hz\n",p->refrate);
+ p->refrvalid = 1;
+ } else {
+ a1logd(p->log, 3, "spyd2_GetRefRate: no refresh rate detectable\n");
+ p->refrate = DEFRRATE;
+ p->refrvalid = 0;
+ }
+ p->rrset = 1;
+
+ return inst_ok;
+}
+
+/* Do a reading. */
+/* Note that the Spyder 3 seems to give USB errors on the data */
+/* read if the measurement time is too small (ie. 0.2 seconds) */
+/* when reading dark values. */
+static inst_code
+spyd2_GetReading(
+ spyd2 *p,
+ double *XYZ /* return the XYZ values */
+) {
+ inst_code ev;
+ int clocks1, clocks2; /* Clocks to run commands */
+ int min, max; /* min and max light intensity frequency periods */
+ int frclocks; /* notional clocks per frame */
+ int nframes; /* Number of frames to measure over */
+ int thresh; /* Frame detection threshold */
+ int minfclks; /* Minimum number of clocks per frame */
+ int maxfclks; /* Maximum number of clocks per frame */
+ double sensv[8]; /* The 8 final sensor value readings */
+ int maxtcnt; /* The maximum transition count measured */
+ int mintcnt; /* The minumum transition count measured */
+ double a_sensv[8]; /* Accumulated sensor value readings */
+ double a_w[8]; /* Accumulated sensor value weight */
+ double pows[9]; /* Power combinations of initial XYZ */
+ int i, j, k;
+ double inttime = 0.0;
+
+ a1logd(p->log, 3, "spyd2_GetReading: called\n");
+
+ if (p->refrmode)
+ inttime = RINTTIME; /* ie. 1 second */
+ else
+ inttime = NINTTIME; /* ie. 1 second */
+
+ /* Compute number of frames for desired base read time */
+ nframes = (int)(inttime * p->refrate + 0.5);
+
+ /* Establish the frame rate detect threshold level */
+ /* (The Spyder 3 doesn't use this ?) */
+ clocks1 = (int)((nframes * CLKRATE)/(10 * p->refrate) + 0.5); /* Use 10% of measurement clocks */
+
+ if ((ev = spyd2_GetMinMax(p, &clocks1, &min, &max)) != inst_ok)
+ return ev;
+
+ /* Setup for measurement */
+ thresh = (max - min)/5 + min; /* Threshold is at 80% of max brightness */
+ if (thresh == 0)
+ thresh = 65535; /* Set to max, otherwise reading will be 0 */
+ frclocks = (int)(CLKRATE/p->refrate + 0.5); /* Nominal clocks per frame */
+ minfclks = frclocks/3; /* Allow for 180 Hz */
+ maxfclks = (frclocks * 5)/2; /* Allow for 24 Hz */
+
+ if (p->hwver < 7) {
+ /* Check calibration is valid */
+ if ((p->icx & 1) == 0 && (p->fbits & 1) == 0) {
+// return spyd2_interp_code((inst *)p, SPYD2_NOCRTCAL);
+ a1logd(p->log, 1, "spyd2_GetReading: instrument appears to have no CRT calibration "
+ "table! Proceeding anyway..\n");
+ }
+
+ if ((p->icx & 1) == 1 && (p->fbits & 2) == 0) {
+// return spyd2_interp_code((inst *)p, SPYD2_NOLCDCAL);
+ a1logd(p->log, 1, "spyd2_GetReading: instrument appears to have no LCD calibration "
+ "table! Proceeding anyway..\n");
+ }
+ }
+
+ a1logd(p->log, 3, "spyd2_GetReading: Using cal table %d\n",(p->icx & 1));
+ if (p->hwver)
+ a1logd(p->log, 3, "spyd2_GetReading: using spectral cal table %d\n",p->icx >> 1);
+
+ for (k = 0; k < 8; k++) /* Zero weighted average */
+ a_sensv[k] = a_w[k] = 0.0;
+
+ /* For initial and possible adaptive readings */
+ for (i = 0;; i++) {
+ double itime; /* Integration time */
+
+ clocks2 = (int)((double)nframes/p->refrate * (double)CLKRATE + 0.5);
+
+ if ((ev = spyd2_GetReading_ll(p, &clocks2, nframes, thresh, &minfclks, &maxfclks,
+ sensv, &maxtcnt, &mintcnt)) != inst_ok)
+ return ev;
+// a1logd(p->log, 3, "spyd2_GetReading: returned number of clocks = %d\n",clocks2);
+
+ if (p->log->debug >= 3) {
+ for (k = 0; k < 8; k++)
+ a1logd(p->log, 3, "Sensor %d value = %f\n",k,sensv[k]);
+ }
+
+ itime = (double)clocks2 / (double)CLKRATE;
+// a1logd(p->log, 3, "spyd2_GetReading: reading %d was %f secs\n",i,itime);
+
+ /* Accumulate it for weighted average */
+ for (k = 0; k < 8; k++) {
+ if (sensv[k] != 0.0) { /* Skip value where we didn't get any transitions */
+ a_sensv[k] += sensv[k] * itime;
+ a_w[k] += itime;
+ }
+ }
+
+#ifdef DO_ADAPTIVE
+ a1logd(p->log, 3, "spyd2_GetReading: Maxtcnt = %d, Mintcnt = %d\n",maxtcnt,mintcnt);
+ if (i > 0)
+ break; /* Done adaptive */
+
+ /* Decide whether to go around again */
+
+ if (maxtcnt <= (100/16)) {
+ nframes *= 16; /* Typically 16 seconds */
+ a1logd(p->log, 3, "spyd2_GetReading: using maximum integration time\n");
+ } else if (maxtcnt < 100) {
+ double mulf;
+ mulf = 100.0/maxtcnt;
+ mulf -= 0.8; /* Just want to accumulate up to target, not re-do it */
+ nframes = (int)(nframes * mulf + 0.5);
+ a1logd(p->log, 3, "spyd2_GetReading: increasing total integration time "
+ "by %.1f times\n",1+mulf);
+ } else {
+ break; /* No need for another reading */
+ }
+#else /* !DO_ADAPTIVE */
+ a1logw(p->log, "!!!! Spyder 2 DO_ADAPTIVE is off !!!!\n");
+ break;
+#endif /* !DO_ADAPTIVE */
+ }
+
+ /* Compute weighted average and guard against silliness */
+ for (k = 0; k < 8; k++) {
+ if (a_w[k] > 0.0) {
+ a_sensv[k] /= a_w[k];
+ }
+ }
+
+ if (p->hwver == 5) {
+ double gainscale = 1.0;
+ unsigned int v381;
+
+ if ((ev = spyd2_rd_ee_uchar(p, &v381, 381)) != inst_ok)
+ return ev;
+
+ gainscale = (double)v381/p->gain;
+ a1logd(p->log, 3, "spyd2_GetReading: hwver5 v381 = %d, gain = %f, gainscale = %f\n",
+ v381,p->gain,gainscale);
+ /* Convert sensor readings to XYZ value */
+ for (j = 0; j < 3; j++) {
+ XYZ[j] = p->cal_A[p->icx & 1][j][0]; /* First entry is a constant */
+ for (k = 1; k < 8; k++)
+ XYZ[j] += a_sensv[k] * p->cal_A[p->icx & 1][j][k+2] * gainscale;
+ }
+
+ } else {
+ /* Convert sensor readings to XYZ value */
+ for (j = 0; j < 3; j++) {
+ XYZ[j] = p->cal_A[p->icx & 1][j][0]; /* First entry is a constant */
+ for (k = 1; k < 8; k++)
+ XYZ[j] += a_sensv[k] * p->cal_A[p->icx & 1][j][k+1];
+ }
+ }
+
+// a1logd(p->log, 3, "spyd2_GetReading: real Y = %f\n",XYZ[1]);
+ a1logd(p->log, 3, "spyd2_GetReading: initial XYZ reading %f %f %f\n",XYZ[0], XYZ[1], XYZ[2]);
+
+#ifdef LEVEL2
+ /* Add "level 2" correction factors */
+ pows[0] = XYZ[0];
+ pows[1] = XYZ[1];
+ pows[2] = XYZ[2];
+ pows[3] = XYZ[0] * XYZ[1];
+ pows[4] = XYZ[0] * XYZ[2];
+ pows[5] = XYZ[1] * XYZ[2];
+ pows[6] = XYZ[0] * XYZ[0];
+ pows[7] = XYZ[1] * XYZ[1];
+ pows[8] = XYZ[2] * XYZ[2];
+
+
+ for (j = 0; j < 3; j++) {
+ XYZ[j] = 0.0;
+
+ for (k = 0; k < 9; k++) {
+ XYZ[j] += pows[k] * p->cal_B[p->icx & 1][j][k];
+ }
+ }
+ a1logd(p->log, 3, "spyd2_GetReading: 2nd level XYZ reading %f %f %f\n",XYZ[0], XYZ[1], XYZ[2]);
+#endif
+
+ /* Protect against silliness (This may stuff up averages though!) */
+ for (j = 0; j < 3; j++) {
+ if (XYZ[j] < 0.0)
+ XYZ[j] = 0.0;
+ }
+ a1logd(p->log, 3, "spyd2_GetReading: final XYZ reading %f %f %f\n",XYZ[0], XYZ[1], XYZ[2]);
+
+ return ev;
+}
+
+/* Spyder3/4: Do an ambient reading */
+
+/* NOTE :- the ambient sensor is something like a TAOS TLS 2562CS. */
+/* It has two sensors, one wide band and the other infra-red, */
+/* the idea being to subtract them to get a rough human response. */
+/* The reading is 16 bits, and the 8 bit conrol register */
+/* controls gain and integration time: */
+
+/* Bits 0,1 inttime, 0 = scale 0.034, 1 = scale 0.252. 2 = scale 1, 3 = manual */
+/* Bit 2, manual, 0 = stop int, 1 = start int */
+/* Bit 4, gain, 0 = gain 1, 1 = gain 16 */
+
+
+static inst_code
+spyd2_GetAmbientReading(
+ spyd2 *p,
+ double *XYZ /* return the ambient XYZ values */
+) {
+ inst_code ev = inst_ok;
+ int tconf; /* Ambient timing config */
+ int iamb0, iamb1;
+ double amb0, amb1; /* The two ambient values */
+ double trv; /* Trial value */
+ double thr[8] = { 64/512.0, 128/512.0, 192/512.0, 256/512.0, /* Magic tables */
+ 312/512.0, 410/512.0, 666/512.0, 0 };
+ double s0[8] = { 498/128.0, 532/128.0, 575/128.0, 624/128.0,
+ 367/128.0, 210/128.0, 24/128.0, 0 };
+ double s1[8] = { 446/128.0, 721/128.0, 891/128.0, 1022/128.0,
+ 508/128.0, 251/128.0, 18/128.0, 0 };
+ double amb; /* Combined ambient value */
+ double sfact;
+ int i;
+
+ a1logd(p->log, 3, "spyd2_GetAmbientReading: called\n");
+
+ /* Set the ambient control register to 3 */
+ if ((ev = spyd2_SetAmbReg(p, 3)) != inst_ok)
+ return ev;
+
+ /* Wait one second */
+ msec_sleep(1000);
+
+ /* Read the ambient timing config value */
+ if ((ev = spyd2_ReadAmbTiming(p, &tconf)) != inst_ok)
+ return ev;
+// a1logd(p->log, 4, "spyd2_GetAmbientReading: timing = %d\n",tconf);
+
+ /* Read the ambient values */
+ if ((ev = spyd2_ReadAmbChan(p, 0, &iamb0)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_ReadAmbChan(p, 1, &iamb1)) != inst_ok)
+ return ev;
+
+// a1logd(p->log, 4, "spyd2_GetAmbientReading: values = %d, %d\n",iamb0,iamb1);
+ amb0 = iamb0/128.0;
+ amb1 = iamb1/128.0;
+
+ /* Set the ambient control register to 0 */
+ if ((ev = spyd2_SetAmbReg(p, 0)) != inst_ok)
+ return ev;
+
+ /* Compute the scale factor from the timing config value */
+ if ((tconf & 3) == 0)
+ sfact = 1.0/0.034;
+ else if ((tconf & 3) == 1)
+ sfact = 1.0/0.252;
+ else
+ sfact = 1.0;
+
+ if ((tconf & 0x10) == 0)
+ sfact *= 16.0;
+
+ amb0 *= sfact;
+ amb1 *= sfact;
+
+ if (amb0 > 0.0)
+ trv = amb1/amb0;
+ else
+ trv = 0.0;
+
+ for (i = 0; i < 7; i++) {
+ if (trv <= thr[i])
+ break;
+ }
+// a1logd(p->log, 4, "spyd2_GetAmbientReading: trv = %f, s0 = %f, s1 = %f\n",trv, s0[i],s1[i]);
+ /* Compute ambient in Lux */
+ amb = s0[i] * amb0 - s1[i] * amb1;
+
+// a1logd(p->log, 4, "spyd2_GetAmbientReading: combined ambient = %f Lux\n",amb);
+ /* Compute the Y value */
+
+ XYZ[1] = amb; /* cd/m^2 ??? - not very accurate, due to */
+ /* spectral response and/or integration angle. */
+ XYZ[0] = icmD50.X * XYZ[1]; /* Convert to D50 neutral */
+ XYZ[2] = icmD50.Z * XYZ[1];
+
+ a1logd(p->log, 3, "spyd2_GetAmbientReading: returning %f %f %f\n",XYZ[0],XYZ[1],XYZ[2]);
+
+ return ev;
+}
+
+/* ------------------------------------------------------------ */
+/* Spyder 4 manufacturer calibration data */
+int spyd4_nocals = 0; /* Number of calibrations */
+xspect *spyd4_cals = NULL; /* [nocals] Device spectrum */
+
+/* ------------------------------------------------------------ */
+/* Spyder4: Create a calibration matrix using the manufacturers */
+/* calibration data. */
+
+static inst_code
+spyd4_set_cal(
+ spyd2 *p, /* Object */
+ int ix /* Selection, 0 .. spyd4_nocals-1 */
+) {
+ int i, j, k;
+ xspect *oc[3]; /* The XYZ observer curves */
+
+ if (ix < 0 || ix >= spyd4_nocals) {
+ return spyd2_interp_code((inst *)p, SPYD2_DISP_SEL_RANGE) ;
+ }
+
+ /* The Manufacturers calibration routine computes a least squares */
+ /* spectral fit of the sensor spectral sensitivities to the */
+ /* standard observer curves, weighted by the white illuminant */
+ /* of the display technology. We use the same approach for the */
+ /* default calibration selections, to be faithful to the Manufacturers */
+ /* intentions. */
+
+ if (standardObserver(oc, icxOT_CIE_1931_2)) {
+ return spyd2_interp_code((inst *)p, SPYD2_DISP_SEL_RANGE) ;
+ }
+
+ /* We compute X,Y & Z independently. */
+ for (k = 0; k < 3; k++) {
+ double target[81]; /* Spectral target @ 5nm spacing */
+ double **wsens; /* Weighted sensor sensitivities */
+ double **psisens; /* Pseudo inverse of sensitivies */
+
+ /* Load up the observer curve and weight it by the display spectrum */
+ /* and mW Lumoinance efficiency factor. */
+ for (i = 0; i < 81; i++) {
+ double nm = 380.0 + i * 5.0;
+ target[i] = value_xspect(&spyd4_cals[ix], nm) * value_xspect(oc[k], nm) * 0.683002;
+ }
+
+ /* Load up the sensor curves and weight by the display spectrum */
+ wsens = dmatrix(0, 6, 0, 80);
+ psisens = dmatrix(0, 80, 0, 6);
+ for (j = 0; j < 7; j++) {
+ for (i = 0; i < 81; i++) {
+ double nm = 380.0 + i * 5.0;
+ wsens[j][i] = value_xspect(&spyd4_cals[ix], nm) * value_xspect(&p->sens[j], nm);
+ }
+ }
+
+ /* Compute the pseudo-inverse matrix */
+ if (lu_psinvert(psisens, wsens, 7, 81) != 0) {
+ free_dmatrix(wsens, 0, 6, 0, 80);
+ free_dmatrix(psisens, 0, 80, 0, 6);
+ return spyd2_interp_code((inst *)p, SPYD2_CAL_FAIL) ;
+ }
+
+ {
+ double *cc, *tt;
+ p->cal_A[1][k][0] = 0.0; /* Offset is zero */
+ p->cal_A[1][k][1] = 0.0; /* Unused is zero */
+ cc = &p->cal_A[1][k][2]; /* 7 cal values go here */
+ tt = target;
+
+ /* Multiply inverse by target to get calibration matrix */
+ if (matrix_mult(&cc, 1, 7, &tt, 1, 81, psisens, 81, 7))
+ return spyd2_interp_code((inst *)p, SPYD2_CAL_FAIL) ;
+ }
+// a1logd(p->log, 3, "Cal %d = %f %f %f %f %f %f %f\n", k, p->cal_A[1][k][2], p->cal_A[1][k][3], p->cal_A[1][k][4], p->cal_A[1][k][5], p->cal_A[1][k][6], p->cal_A[1][k][7], p->cal_A[1][k][8]);
+ }
+
+#ifdef PLOT_SPECTRA
+ /* Plot the calibrated sensor spectra */
+ {
+ int i, j, k;
+ double xx[81];
+ double yy[10][81], *yp[10];
+
+ for (i = 0; i < 81; i++)
+ xx[i] = 380.0 + i * 5.0;
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 81; i++) {
+ yy[j][i] = 0.0;
+ for (k = 0; k < 7; k++) {
+ yy[j][i] += p->cal_A[1][j][k+2] * value_xspect(&p->sens[k], xx[i]);
+ }
+ }
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The calibrated sensor sensitivities\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+ }
+#endif /* PLOT_SPECTRA */
+
+#ifdef SAVE_XYZSPECTRA /* Save the default XYZ senitivity spectra to "sensorsxyz.sp" */
+ {
+ int i, j, k;
+ xspect xyz[3];
+ double wl;
+
+ for (j = 0; j < 3; j++) {
+ xyz[j].spec_n = 81;
+ xyz[j].spec_wl_short = 380;
+ xyz[j].spec_wl_long = 780;
+ xyz[j].norm = 1.0;
+ for (i = 0; i < 81; i++) {
+ wl = 380.0 + i * 5.0;
+ xyz[j].spec[i] = 0.0;
+ for (k = 0; k < 7; k++)
+ xyz[j].spec[i] += p->cal_A[1][j][k+2] * value_xspect(&p->sens[k], wl);
+ xyz[j].spec[i] *= 1.4; /* Align with std XYZ */
+ }
+ }
+ write_nxspect("sensorsxyz.sp", xyz, 3, 0);
+ }
+#endif
+#ifdef SAVE_STDXYZ
+ {
+ xspect xyz[3];
+ standardObserver(&xyz[0], &xyz[1], &xyz[2],icxOT_CIE_1931_2);
+ write_nxspect("stdobsxyz.sp", xyz, 3, 0);
+ }
+#endif /* SAVE_STDXYZ */
+
+ return inst_ok;
+}
+
+
+/* The CCSS calibration uses a set of spectral samples, */
+/* and a least squares matrix is computed to map the sensor RGB */
+/* to the computed XYZ values. This allows better accuracy for */
+/* a typical display that has only 3 degrees of freedom, and */
+/* allows weigting towards a distribution of actual spectral samples. */
+/* Because the typical display has only three degrees of freedom, */
+/* while the instrument has 7 sensors, some extra dummy spectral */
+/* samples are added to the list to provide some slight extra goal. */
+
+/* [ Given the poor curve shapes that can come out of this, it's not */
+/* clear that it wouldn't be better using the default flat-spetrum */
+/* calibration and computing a 3x3 calibration matrix over the top of it. ] */
+static inst_code
+spyd4_comp_calmat(
+ spyd2 *p,
+ icxObserverType obType, /* XYZ Observer type */
+ xspect custObserver[3], /* Optional custom observer */ \
+ xspect *samples, /* Array of nsamp spectral samples */
+ int nsamp /* Number of real samples */
+) {
+ int i, j;
+ int nasamp = nsamp + 81; /* Number of real + augmented samples */
+ double exwt = 1.0; /* Extra spectral point weight */
+ double **sampXYZ; /* Sample XYZ values */
+ double **sampSENS; /* Sample Sensor values */
+ double **isampSENS; /* Pseudo-inverse of sensor values */
+ double **calm; /* Calibration matrix */
+ xsp2cie *conv;
+ double wl;
+ xspect white;
+
+ if (nsamp < 3)
+ return spyd2_interp_code((inst *)p, SPYD2_TOO_FEW_CALIBSAMP);
+
+ /* Create white spectrum samples */
+ XSPECT_COPY_INFO(&white, &samples[0]);
+ for (j = 0; j < white.spec_n; j++)
+ white.spec[j] = 0.0;
+ for (i = 0; i < nsamp; i++) {
+ for (j = 0; j < white.spec_n; j++)
+ if (samples[i].spec[j] > white.spec[j])
+ white.spec[j] = samples[i].spec[j];
+ }
+
+ /* Compute XYZ of the real sample array. */
+ if ((conv = new_xsp2cie(icxIT_none, NULL, obType, custObserver, icSigXYZData, icxClamp)) == NULL)
+ return spyd2_interp_code((inst *)p, SPYD2_INT_CIECONVFAIL);
+ sampXYZ = dmatrix(0, nasamp-1, 0, 3-1);
+ for (i = 0; i < nsamp; i++) {
+ conv->convert(conv, sampXYZ[i], &samples[i]);
+// a1logd(p->log, 3, "asamp[%d] XYZ = %f %f %f\n", i,sampXYZ[nsamp+i][0],sampXYZ[nsamp+i][1], sampXYZ[nsamp+i][2]);
+ }
+
+ /* Create extra spectral samples */
+ for (i = 0; i < 81; i++) {
+ for (j = 0; j < 3; j++) {
+ wl = 380.0 + i * 5;
+ sampXYZ[nsamp+i][j] = exwt * value_xspect(&white, wl)
+ * value_xspect(&conv->observer[j], wl) * 0.683002;
+ }
+// a1logd(p->log, 3, "asamp[%d] XYZ = %f %f %f\n", i,sampXYZ[nsamp+i][0],sampXYZ[nsamp+i][1], sampXYZ[nsamp+i][2]);
+ }
+ conv->del(conv);
+
+ sampSENS = dmatrix(0, nasamp-1, 0, 7-1);
+
+ /* Compute sensor values of the sample array */
+ for (i = 0; i < nsamp; i++) {
+ for (j = 0; j < 7; j++) {
+ sampSENS[i][j] = 0.0;
+ for (wl = p->sens[0].spec_wl_short; wl <= p->sens[0].spec_wl_long; wl += 1.0) {
+ sampSENS[i][j] += value_xspect(&samples[i], wl) * value_xspect(&p->sens[j], wl);
+ }
+ }
+ }
+ /* Create sensor values of the extra sample array */
+ for (i = 0; i < 81; i++) {
+ for (j = 0; j < 7; j++) {
+ wl = 380.0 + i * 5;
+ sampSENS[nsamp+i][j] = exwt * value_xspect(&white, wl) * value_xspect(&p->sens[j], wl);
+ }
+// a1logd(p->log, 3, "asamp[%d] Sens = %f %f %f %f %f %f %f\n", i,
+// sampSENS[nsamp+i][0],sampSENS[nsamp+i][1], sampSENS[nsamp+i][2],
+// sampSENS[nsamp+i][3],sampSENS[nsamp+i][4], sampSENS[nsamp+i][5],
+// sampSENS[nsamp+i][6]);
+ }
+#if defined(PLOT_SPECTRA_EXTRA)
+ /* Plot the target extra values */
+ {
+ int i, j, k;
+ double xx[81];
+ double yy[10][81], *yp[10];
+
+ for (i = 0; i < 81; i++)
+ xx[i] = 380.0 + i * 5;
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 81; i++) {
+ yy[j][i] = sampXYZ[nsamp+i][j];
+ }
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The target extra XYZ values\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+
+ for (j = 0; j < 7; j++) {
+ for (i = 0; i < 81; i++) {
+ yy[j][i] = sampSENS[nsamp+i][j];
+ }
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The given extra sensor values\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+ }
+#endif /* PLOT_SPECTRA_EXTRA */
+
+
+ isampSENS = dmatrix(0, 7-1, 0, nasamp-1);
+
+ /* Compute the pseudo inverse of sampSENS */
+ if (lu_psinvert(isampSENS, sampSENS, nasamp, 7) != 0) {
+ free_dmatrix(sampXYZ, 0, nasamp-1, 0, 3-1);
+ free_dmatrix(sampSENS, 0, nasamp-1, 0, 7-1);
+ free_dmatrix(isampSENS, 0, 7-1, 0, nasamp-1);
+ return spyd2_interp_code((inst *)p, SPYD2_CAL_FAIL) ;
+ }
+
+ calm = dmatrix(0, 7-1, 0, 3-1);
+
+ /* Multiply inverse by target to get calibration matrix */
+ if (matrix_mult(calm, 7, 3, isampSENS, 7, nasamp, sampXYZ, nasamp, 3)) {
+ free_dmatrix(sampXYZ, 0, nasamp-1, 0, 3-1);
+ free_dmatrix(sampSENS, 0, nasamp-1, 0, 7-1);
+ free_dmatrix(isampSENS, 0, 7-1, 0, nasamp-1);
+ free_dmatrix(calm, 0, 7-1, 0, 3-1);
+ return spyd2_interp_code((inst *)p, SPYD2_CAL_FAIL);
+ }
+
+ /* Copy the matrix into place */
+ for (i = 0; i < 7; i++) {
+ for (j = 0; j < 3; j++) {
+//calm[i][j] = 0.5;
+ p->cal_A[1][j][2+i] = calm[i][j];
+ }
+ }
+
+ free_dmatrix(calm, 0, 7-1, 0, 3-1);
+
+#ifdef NEVER
+
+ /* Compute the residuals */
+ {
+ double **refXYZ;
+ double t1, t2;
+
+ refXYZ = dmatrix(0, nasamp-1, 0, 3-1);
+
+ if (matrix_mult(refXYZ, nasamp, 3, sampSENS, nasamp, 7, calm, 7, 3)) {
+ printf("Residual matrix mult failed\n");
+ } else {
+ t1 = 0.0;
+ for (i = 0; i < nsamp; i++) {
+ t1 += icmLabDE(refXYZ[i],sampXYZ[i]);
+ }
+ t1 /= nsamp;
+ printf("Average error for sample points = %f\n",t1);
+ t2 = 0.0;
+ for (i = nsamp; i < (nsamp + 81); i++) {
+ t2 += icmLabDE(refXYZ[i],sampXYZ[i]);
+// printf("Resid %d error = %f, %f %f %f, %f %f %f\n",
+// i, icmLabDE(refXYZ[i],sampXYZ[i]), sampXYZ[i][0], sampXYZ[i][1],
+// sampXYZ[i][2], refXYZ[i][0], refXYZ[i][1], refXYZ[i][2]);
+ }
+ t2 /= 81;
+ printf("Average error for extra points = %f\n",t2);
+ }
+ }
+#endif
+
+#ifdef PLOT_SPECTRA
+ /* Plot the calibrated sensor spectra */
+ {
+ int i, j, k;
+ double xx[81];
+ double yy[10][81], *yp[10];
+
+ for (i = 0; i < 81; i++)
+ xx[i] = 380.0 + i * 5.0;
+
+ for (j = 0; j < 3; j++) {
+ for (i = 0; i < 81; i++) {
+ yy[j][i] = 0.0;
+ for (k = 0; k < 7; k++) {
+ yy[j][i] += p->cal_A[1][j][k+2] * value_xspect(&p->sens[k], xx[i]);
+ }
+ }
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The calibrated sensor sensitivities\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+ }
+#endif /* PLOT_SPECTRA */
+
+ free_dmatrix(sampXYZ, 0, nasamp-1, 0, 3-1);
+ free_dmatrix(sampSENS, 0, nasamp-1, 0, 7-1);
+ free_dmatrix(isampSENS, 0, 7-1, 0, nasamp-1);
+
+ return inst_ok;
+}
+
+
+/* ------------------------------------------------------------ */
+
+/* Read all the relevant register values */
+static inst_code
+spyd2_read_all_regs(
+ spyd2 *p /* Object */
+) {
+ inst_code ev;
+
+ a1logd(p->log, 3, "spyd2_read_all_regs: about to read all the EEProm values\n");
+
+ /* HW version */
+ if ((ev = spyd2_rd_ee_uchar(p, &p->hwver, 5)) != inst_ok)
+ return ev;
+
+ /* Feature bits */
+ if ((ev = spyd2_rd_ee_uchar(p, &p->fbits, 6)) != inst_ok)
+ return ev;
+
+ a1logd(p->log, 3, "spyd2_read_all_regs: hwver = 0x%02x%02x\n",p->hwver,p->fbits);
+
+ /* Check the EEProm checksum */
+ if (p->hwver == 7) {
+ if ((ev = spyd2_checkEECRC(p)) != inst_ok)
+ return ev;
+ }
+
+ /* Serial number */
+ if ((ev = spyd2_readEEProm(p, (unsigned char *)p->serno, 8, 8)) != inst_ok)
+ return ev;
+ p->serno[8] = '\000';
+ a1logd(p->log, 3, "spyd2_read_all_regs: serno = '%s'\n",p->serno);
+
+ if (p->hwver < 7) {
+
+ /* Hmm. We deliberately ignore the fbits 0, 1 & 2 here, in case they are faulty */
+ /* (Not sure if we should look at fbits 1 or not) */
+
+ /* Spyde2: CRT calibration values */
+ /* Spyde3: Unknown calibration values */
+ if ((ev = spyd2_rdreg_3x9xfloat(p, p->cal_A[0][0], p->cal_A[0][1], p->cal_A[0][2], 16))
+ != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_3x9xfloat(p, p->cal_B[0][0], p->cal_B[0][1], p->cal_B[0][2], 128))
+ != inst_ok)
+ return ev;
+
+
+ /* Hmm. The 0 table seems to sometimes be scaled. Is this a bug ? */
+ /* (might be gain factor ?) */
+ /* The spyder 3/4 doesn't use this anyway. */
+ if (p->hwver >= 4) {
+ int j, k, i;
+ double avgmag = 0.0;
+
+ for (i = j = 0; j < 3; j++) {
+ for (k = 0; k < 9; k++) {
+ if (p->cal_A[0][j][k] != 0.0) {
+ avgmag += fabs(p->cal_A[0][j][k]);
+ i++;
+ }
+ }
+ }
+ avgmag /= (double)(i);
+ a1logd(p->log, 4, "spyd2_read_all_regs: Cal_A avgmag = %f\n",avgmag);
+
+ if (avgmag < 0.05) {
+ a1logd(p->log, 5, "spyd2_read_all_regs: Scaling Cal_A by 16\n");
+ for (j = 0; j < 3; j++) {
+ for (k = 0; k < 9; k++) {
+ p->cal_A[0][j][k] *= 16.0;
+ }
+ }
+ }
+ }
+
+ /* Spyder2: LCD calibration values */
+ /* Spyder3: Normal CRT/LCD calibration values */
+ if ((ev = spyd2_rdreg_3x9xfloat(p, p->cal_A[1][0], p->cal_A[1][1], p->cal_A[1][2], 256))
+ != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_3x9xfloat(p, p->cal_B[1][0], p->cal_B[1][1], p->cal_B[1][2], 384))
+ != inst_ok)
+ return ev;
+
+ /* The monochrome "TOKIOBLUE" calibration */
+ /* (Not sure if this is fbits 2 and 4 or not) */
+
+ /* Luminence only calibration values ??? */
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[0], 240)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[1], 244)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[2], 248)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[3], 252)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[4], 364)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[5], 368)) != inst_ok)
+ return ev;
+ if ((ev = spyd2_rdreg_float(p, &p->cal_F[6], 372)) != inst_ok)
+ return ev;
+
+ if (p->log->debug >= 4) {
+ int i, j, k;
+
+ a1logd(p->log, 4, "Cal_A:\n");
+ for (i = 0; i < 2;i++) {
+ for (j = 0; j < 3; j++) {
+ for (k = 0; k < 9; k++) {
+ a1logd(p->log, 4, "Cal_A [%d][%d][%d] = %f\n",i,j,k,p->cal_A[i][j][k]);
+ }
+ }
+ }
+ a1logd(p->log, 4, "\nCal_B:\n");
+ for (i = 0; i < 2;i++) {
+ for (j = 0; j < 3; j++) {
+ for (k = 0; k < 9; k++) {
+ a1logd(p->log, 4, "Cal_B [%d][%d][%d] = %f\n",i,j,k,p->cal_B[i][j][k]);
+ }
+ }
+ }
+ a1logd(p->log, 4, "\nCal_F:\n");
+ for (i = 0; i < 7;i++) {
+ a1logd(p->log, 4, "Cal_F [%d] = %f\n",i,p->cal_F[i]);
+ }
+ a1logd(p->log, 4, "\n");
+ }
+
+ } else if (p->hwver == 7) {
+ int i, j;
+ unsigned int sscal;
+ double tsens[7][41];
+
+ /* Read sensor sensitivity spectral data */
+ if ((ev = spyd2_rdreg_7x41xshort(p, tsens, 170)) != inst_ok)
+ return ev;
+
+ /* Sensor scale factor */
+ if ((ev = spyd2_rd_ee_ushort(p, &sscal, 21)) != inst_ok)
+ return ev;
+
+ /* And apply it to the sensor data */
+ for (j = 0; j < 7; j++) {
+ for (i = 0; i < 41; i++) {
+ tsens[j][i] /= 1000; /* Convert to Hz per mW/nm/m^2 */
+ tsens[j][i] /= sscal/1e5; /* Sensitivity scale value */
+ }
+ }
+
+ /* Convert sensor values to xspect's */
+ for (i = 0; i < 7; i++) {
+ p->sens[i].spec_n = 41;
+ p->sens[i].spec_wl_short = 380;
+ p->sens[i].spec_wl_long = 780;
+ p->sens[i].norm = 1.0;
+ for (j = 0; j < 41; j++) {
+ p->sens[i].spec[j] = tsens[i][j];
+ }
+ }
+#ifdef SAVE_SPECTRA
+ write_nxspect("sensors.sp", p->sens, 7, 0);
+#endif
+
+ /* Linearization */
+ if ((ev = spyd2_rdreg_3x9xfloat(p, p->cal_B[1][0], p->cal_B[1][1], p->cal_B[1][2], 60))
+ != inst_ok)
+ return ev;
+
+#ifdef PLOT_SPECTRA
+ /* Plot the sensor spectra */
+ {
+ int i, j;
+ double xx[81];
+ double yy[10][81], *yp[10];
+
+ for (i = 0; i < 81; i++)
+ xx[i] = 380.0 + i * 5.0;
+
+ for (j = 0; j < 7; j++) {
+ for (i = 0; i < 81; i++)
+ yy[j][i] = value_xspect(&p->sens[j], xx[i]);
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The sensor and ambient sensor sensitivy curves\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+
+
+ for (j = 0; j < spyd4_nocals; j++) {
+ double max = 0;
+ for (i = 0; i < 81; i++) {
+ if (yy[j][i] = value_xspect(&spyd4_cals[j], xx[i]) > max)
+ max = value_xspect(&spyd4_cals[j], xx[i]);
+ }
+ for (i = 0; i < 81; i++)
+ yy[j][i] = value_xspect(&spyd4_cals[j], xx[i])/max;
+ yp[j] = yy[j];
+ }
+ for (; j < 10; j++)
+ yp[j] = NULL;
+
+ printf("The display spectra\n");
+ do_plot10(xx, yp[0], yp[1], yp[2], yp[3], yp[4], yp[5], yp[6], yp[7], yp[8], yp[9], 81, 0);
+ }
+#endif /* PLOT_SPECTRA */
+
+ }
+
+ a1logd(p->log, 3, "spyd2_read_all_regs: all EEProm read OK\n");
+
+ return inst_ok;
+}
+
+/* ------------------------------------------------------------ */
+
+/* Table to hold Spyder 2 Firmware, if it's installed */
+unsigned int _spyder2_pld_size = 0; /* Number of bytes to download */
+unsigned int *spyder2_pld_size = &_spyder2_pld_size;
+unsigned char *spyder2_pld_bytes = NULL;
+
+/* Spyder 2: Download the PLD if it is available, and check status */
+static inst_code
+spyd2_download_pld(
+ spyd2 *p /* Object */
+) {
+ inst_code ev;
+ int stat;
+ int i;
+
+ a1logd(p->log, 2, "spyd2_download_pld: called\n");
+
+ if (*spyder2_pld_size == 0 || *spyder2_pld_size == 0x11223344) {
+ a1logd(p->log, 1, "spyd2_download_pld: No PLD pattern available! (have you run spyd2en ?)\n");
+ return spyd2_interp_code((inst *)p, SPYD2_NO_PLD_PATTERN) ;
+ }
+
+ for (i = 0; i < *spyder2_pld_size; i += 8) {
+ if ((ev = spyd2_loadPLD(p, spyder2_pld_bytes + i, 8)) != inst_ok)
+ return ev;
+ }
+
+ /* Let the PLD initialize */
+ msec_sleep(500);
+
+#ifdef DO_RESETEP /* Do the miscelanous resetep()'s */
+ /* Reset the coms */
+ p->icom->usb_resetep(p->icom, 0x81);
+ msec_sleep(1); /* Let device recover ? */
+#endif /* DO_RESETEP */
+
+ /* Check the status */
+ if ((ev = spyd2_getstatus(p, &stat)) != inst_ok)
+ return ev;
+
+ if (stat != 0) {
+ a1logd(p->log, 1, "spyd2_download_pld: PLD download failed!\n");
+ return spyd2_interp_code((inst *)p, SPYD2_PLDLOAD_FAILED);
+ }
+
+ a1logd(p->log, 2, "spyd2_download_pld: PLD download OK\n");
+
+ msec_sleep(500);
+#ifdef DO_RESETEP /* Do the miscelanous resetep()'s */
+ p->icom->usb_resetep(p->icom, 0x81);
+ msec_sleep(1); /* Let device recover ? */
+#endif /* DO_RESETEP */
+
+ return inst_ok;
+}
+
+
+/* ------------------------------------------------------------ */
+/* Setup Spyder4 native calibrations */
+
+/* Load the manufacturers Spyder4 calibration data */
+/* Return a SPYD2_ error value */
+static int
+spyd4_load_cal(spyd2 *p) {
+ char **bin_paths = NULL;
+ int no_paths = 0;
+ unsigned int size;
+ unsigned char *buf = NULL;
+ FILE *fp = NULL;
+ int nocals = 0;
+ int i, j;
+
+ /* If already loaded */
+ if (spyd4_nocals != 0)
+ return SPYD2_OK;
+
+
+ for (;;) { /* So we can break */
+ if ((no_paths = xdg_bds(NULL, &bin_paths, xdg_data, xdg_read, xdg_user,
+ "ArgyllCMS/spyd4cal.bin" XDG_FUDGE "color/spyd4cal.bin"
+ )) < 1)
+ break;
+
+ /* open binary file */
+#if !defined(O_CREAT) && !defined(_O_CREAT)
+# error "Need to #include fcntl.h!"
+#endif
+#if defined(O_BINARY) || defined(_O_BINARY)
+ if ((fp = fopen(bin_paths[0],"rb")) == NULL)
+#else
+ if ((fp = fopen(bin_paths[0],"r")) == NULL)
+#endif
+ break;
+ xdg_free(bin_paths, no_paths);
+
+ /* Figure out how big file it is */
+ if (fseek(fp, 0, SEEK_END)) {
+ fclose(fp);
+ break;
+ }
+ size = (unsigned long)ftell(fp);
+
+ if ((size % (41 * 8)) != 0) {
+ fclose(fp);
+ a1logd(p->log, 1, "spyd4_load_cal: calibration file '%s' is unexpected size\n",bin_paths[0]);
+ break;
+ }
+
+ nocals = size/(41 * 8);
+ if (nocals != 6) {
+ fclose(fp);
+ a1logd(p->log, 1, "spyd4_load_cal: calibration file '%s' is unexpected number of calibrations (%d)\n",bin_paths[0],nocals);
+ break;
+ }
+
+ if (fseek(fp, 0, SEEK_SET)) {
+ fclose(fp);
+ break;
+ }
+
+ if ((buf = (unsigned char *)calloc(nocals * 41, 8)) == NULL) {
+ fclose(fp);
+ return SPYD2_MALLOC;
+ }
+
+ if (fread(buf, 1, size, fp) != size) {
+ free(buf);
+ fclose(fp);
+ break;
+ }
+ fclose(fp);
+ break;
+ }
+
+ if (buf == NULL)
+ nocals = 1;
+
+ if ((spyd4_cals = (xspect *)calloc(nocals, sizeof(xspect))) == NULL) {
+ if (buf != NULL)
+ free(buf);
+ return SPYD2_MALLOC;
+ }
+
+ /* If we have calibrations */
+ if (buf != NULL) {
+ unsigned char *bp;
+
+ for (i = 0; i < nocals; i++) {
+ bp = buf + 41 * 8 * i;
+
+ spyd4_cals[i].spec_n = 41;
+ spyd4_cals[i].spec_wl_short = 380;
+ spyd4_cals[i].spec_wl_long = 780;
+ spyd4_cals[i].norm = 1.0;
+
+ for (j = 0; j < 41; j++, bp += 8) {
+ ORD64 val;
+
+ val = buf2ord64(bp);
+ spyd4_cals[i].spec[j] = IEEE754_64todouble(val);
+// a1logd(p->log, 3, "cal[%d][%d] = %f\n",i,j,spyd4_cals[i].spec[j]);
+ }
+ }
+
+ } else {
+
+ /* Create a default calibration */
+ for (j = 0; j < 41; j++)
+ spyd4_cals[0].spec_n = 41;
+ spyd4_cals[0].spec_wl_short = 380;
+ spyd4_cals[0].spec_wl_long = 780;
+ spyd4_cals[0].norm = 1.0;
+
+ for (j = 0; j < 41; j++) {
+ spyd4_cals[0].spec[j] = 1.0;
+ }
+ }
+
+ spyd4_nocals = nocals;
+
+ return SPYD2_OK;
+}
+
+/* ============================================================ */
+
+
+/* Establish communications with a SPYD2 */
+/* If it's a serial port, use the baud rate given, and timeout in to secs */
+/* Return DTP_COMS_FAIL on failure to establish communications */
+static inst_code
+spyd2_init_coms(inst *pp, baud_rate br, flow_control fc, double tout) {
+ spyd2 *p = (spyd2 *) pp;
+ int se;
+ icomuflags usbflags = icomuf_none;
+
+ a1logd(p->log, 2, "spyd2_init_coms: about to init coms\n");
+
+ if (p->icom->port_type(p->icom) != icomt_usb) {
+ a1logd(p->log, 1, "spyd2_init_coms: coms is not the right type!\n");
+ return spyd2_interp_code((inst *)p, SPYD2_UNKNOWN_MODEL);
+ }
+
+ a1logd(p->log, 2, "spyd2_init_coms: about to init USB\n");
+
+ /* On MSWindows the Spyder 3 doesn't work reliably unless each */
+ /* read is preceeded by a reset endpoint. */
+ /* (!!! This needs checking to see if it's still true. */
+ /* Should switch back to libusb0.sys and re-test.) */
+ /* (and Spyder 2 hangs if a reset ep is done on MSWin.) */
+ /* The spyder 2 doesn't work well with the winusb driver either, */
+ /* it needs icomuf_resetep_before_read to work at all, and */
+ /* gets retries anyway. So we use the libusb0 driver for it. */
+#if defined(NT)
+ if (p->itype == instSpyder3) {
+ usbflags |= icomuf_resetep_before_read; /* The spyder USB is buggy ? */
+ }
+#endif
+
+ /* On OS X the Spyder 2 can't close properly */
+#if defined(__APPLE__) /* OS X*/
+ if (p->itype == instSpyder2) {
+ usbflags |= icomuf_reset_before_close; /* The spyder 2 USB is buggy ? */
+ }
+#endif
+
+#ifdef NEVER /* Don't want this now that we avoid 2nd set_config on Linux */
+#if defined(UNIX_X11) /* Linux*/
+ /* On Linux the Spyder 2 doesn't work reliably unless each */
+ /* read is preceeded by a reset endpoint. */
+ if (p->itype == instSpyder2) {
+ usbflags |= icomuf_resetep_before_read; /* The spyder USB is buggy ? */
+ }
+#endif
+#endif
+
+ /* Set config, interface, write end point, read end point */
+ /* ("serial" end points aren't used - the spyd2lay uses USB control messages) */
+ if ((se = p->icom->set_usb_port(p->icom, 1, 0x00, 0x00, usbflags, 0, NULL)) != ICOM_OK) {
+ a1logd(p->log, 1, "spyd2_init_coms: failed ICOM err 0x%x\n",se);
+ return spyd2_interp_code((inst *)p, icoms2spyd2_err(se));
+ }
+
+ a1logd(p->log, 2, "spyd2_init_coms: suceeded\n");
+
+ p->gotcoms = 1;
+ return inst_ok;
+}
+
+static inst_code set_default_disp_type(spyd2 *p);
+
+/* Initialise the SPYD2 */
+/* return non-zero on an error, with an inst_code */
+static inst_code
+spyd2_init_inst(inst *pp) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev = inst_ok;
+ int stat;
+ int i;
+
+ a1logd(p->log, 2, "spyd2_init_inst: called\n");
+
+ if (p->gotcoms == 0) /* Must establish coms before calling init */
+ return spyd2_interp_code((inst *)p, SPYD2_NO_COMS);
+
+ if (p->itype != instSpyder2
+ && p->itype != instSpyder3
+ && p->itype != instSpyder4)
+ return spyd2_interp_code((inst *)p, SPYD2_UNKNOWN_MODEL);
+
+ p->refrate = DEFRRATE;
+ for (i = 0; i < 8; i++)
+ p->prevraw[i] = 0; /* Internal counters will be reset */
+ p->prevrawinv = 0; /* prevraw is valid */
+
+ /* For Spyder 1 & 2, reset the hardware and wait for it to become ready. */
+ if (p->itype != instSpyder3
+ && p->itype != instSpyder4) {
+
+ /* Reset the instrument */
+ if ((ev = spyd2_reset(p)) != inst_ok)
+ return ev;
+
+ /* Fetch status until we get a status = 1 */
+ for (i = 0; i < 50; i++) {
+ if ((ev = spyd2_getstatus(p, &stat)) != inst_ok)
+ return ev;
+
+ if (stat == 1)
+ break;
+ }
+ if (i >= 50)
+ return spyd2_interp_code((inst *)p, SPYD2_BADSTATUS);
+
+ } else {
+ /* Because the Spyder 3/4 doesn't have a reset command, */
+ /* it may be left in a borked state if the driver is aborted. */
+ /* Make sure there's no old read data hanging around. */
+ /* Sometimes it takes a little while for the old data to */
+ /* turn up, so try at least for 1 second. */
+ /* This won't always work if the driver is re-started */
+ /* quickly after aborting a long integration read. */
+
+ unsigned char buf[8]; /* return bytes read */
+ int rwbytes; /* Data bytes read or written */
+
+
+ for (i = 0; i < 10; i++) {
+ if ((p->icom->usb_read(p->icom, NULL, 0x81, buf, 8, &rwbytes, 0.1) & ICOM_TO)
+ && i > 9)
+ break; /* Done when read times out */
+ }
+ }
+
+ /* Read the Serial EEProm contents */
+ if ((ev = spyd2_read_all_regs(p)) != inst_ok)
+ return ev;
+
+ /* Spyder 2 */
+ if (p->hwver < 4) {
+ /* Download the PLD pattern and check the status */
+ if ((ev = spyd2_download_pld(p)) != inst_ok)
+ return ev;
+ }
+
+ p->gain = 1.0;
+ if (p->hwver == 5) {
+ if ((ev = spyd2_SetGain(p, 4)) != inst_ok)
+ return ev;
+ }
+
+ /* Set a default calibration */
+ if ((ev = set_default_disp_type(p)) != inst_ok) {
+ return ev;
+ }
+
+ /* Do a dumy sensor read. This will set prevraw[] values. */
+ {
+ int clocks = 500;
+ int minfclks = 0;
+ int maxfclks = 0;
+ msec_sleep(100);
+ if ((ev = spyd2_GetReading_ll(p, &clocks, 10, 0, &minfclks, &maxfclks, NULL, NULL, NULL)) != inst_ok)
+ return ev;
+ }
+
+ p->trig = inst_opt_trig_user; /* default trigger mode */
+
+ p->inited = 1;
+ a1logd(p->log, 2, "spyd2_init_inst: inited OK\n");
+
+ if (p->hwver >= 4) {
+ /* Flash the LED, just cos we can! */
+ if ((ev = spyd2_setLED(p, 2, 0.0)) != inst_ok)
+ return ev;
+ msec_sleep(200);
+ if ((ev = spyd2_setLED(p, 0, 0.0)) != inst_ok)
+ return ev;
+ }
+
+ a1logv(p->log, 1, "Instrument Type: %s\n"
+ "Serial Number: %s\n"
+ "Hardware version: 0x%02x%02x\n"
+ ,inst_name(p->itype) ,p->serno ,p->hwver,p->fbits);
+
+ return inst_ok;
+}
+
+/* Read a single sample */
+/* Return the dtp error code */
+static inst_code
+spyd2_read_sample(
+inst *pp,
+char *name, /* Strip name (7 chars) */
+ipatch *val, /* Pointer to instrument patch value */
+instClamping clamp) { /* NZ if clamp XYZ/Lab to be +ve */
+ spyd2 *p = (spyd2 *)pp;
+ int user_trig = 0;
+ inst_code ev = inst_protocol_error;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->trig == inst_opt_trig_user) {
+
+ if (p->uicallback == NULL) {
+ a1logd(p->log, 1, "sptyd2: inst_opt_trig_user but no uicallback function set!\n");
+ return inst_unsupported;
+ }
+
+ for (;;) {
+ if ((ev = p->uicallback(p->uic_cntx, inst_armed)) != inst_ok) {
+ if (ev == inst_user_abort)
+ return ev; /* Abort */
+ if (ev == inst_user_trig) {
+ user_trig = 1;
+ break; /* Trigger */
+ }
+ }
+ msec_sleep(200);
+ }
+ /* Notify of trigger */
+ if (p->uicallback)
+ p->uicallback(p->uic_cntx, inst_triggered);
+
+ /* Progromatic Trigger */
+ } else {
+ /* Check for abort */
+ if (p->uicallback != NULL
+ && (ev = p->uicallback(p->uic_cntx, inst_armed)) == inst_user_trig)
+ return ev; /* Abort */
+ }
+
+ if (IMODETST(p->mode, inst_mode_emis_ambient)) {
+ if ((ev = spyd2_GetAmbientReading(p, val->XYZ)) != inst_ok)
+ return ev;
+
+ } else {
+
+ /* Attempt a CRT frame rate calibration if needed */
+ if (p->refrmode != 0 && p->rrset == 0) {
+ if ((ev = spyd2_GetRefRate(p)) != inst_ok)
+ return ev;
+ }
+
+ /* Read the XYZ value */
+ if ((ev = spyd2_GetReading(p, val->XYZ)) != inst_ok)
+ return ev;
+
+ /* Apply the colorimeter correction matrix */
+ icmMulBy3x3(val->XYZ, p->ccmat, val->XYZ);
+ }
+ /* This may not change anything since instrument may clamp */
+ if (clamp)
+ icmClamp3(val->XYZ, val->XYZ);
+
+ val->loc[0] = '\000';
+ if (IMODETST(p->mode, inst_mode_emis_ambient))
+ val->mtype = inst_mrt_ambient;
+ else
+ val->mtype = inst_mrt_emission;
+ val->XYZ_v = 1; /* These are absolute XYZ readings ? */
+ val->sp.spec_n = 0;
+ val->duration = 0.0;
+
+ if (user_trig)
+ return inst_user_trig;
+ return ev;
+}
+
+/* Insert a colorimetric correction matrix in the instrument XYZ readings */
+/* This is only valid for colorimetric instruments. */
+/* To remove the matrix, pass NULL for the filter filename */
+inst_code spyd2_col_cor_mat(
+inst *pp,
+double mtx[3][3]
+) {
+ spyd2 *p = (spyd2 *)pp;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (mtx == NULL) {
+ icmSetUnity3x3(p->ccmat);
+ } else {
+ if (p->cbid == 0) {
+ a1loge(p->log, 1, "spyd2: can't set col_cor_mat over non base display type\n");
+ inst_wrong_setup;
+ }
+ icmCpy3x3(p->ccmat, mtx);
+ }
+
+ return inst_ok;
+}
+
+/* Use a Colorimeter Calibration Spectral Set to set the */
+/* instrumen calibration. */
+/* This is only valid for colorimetric instruments. */
+/* To set calibration back to default, pass NULL for sets. */
+inst_code spyd2_col_cal_spec_set(
+inst *pp,
+xspect *sets,
+int no_sets
+) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev = inst_ok;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+ if (p->hwver < 7)
+ return inst_unsupported;
+
+ if (sets == NULL || no_sets <= 0) {
+ if ((ev = set_default_disp_type(p)) != inst_ok)
+ return ev;
+ } else {
+ /* Use given spectral samples */
+ if ((ev = spyd4_comp_calmat(p, p->obType, p->custObserver, sets, no_sets)) != inst_ok)
+ return ev;
+ p->icx = (99 << 1) | 1; /* Out of range index */
+ }
+ return ev;
+}
+
+/* Return needed and available inst_cal_type's */
+static inst_code spyd2_get_n_a_cals(inst *pp, inst_cal_type *pn_cals, inst_cal_type *pa_cals) {
+ spyd2 *p = (spyd2 *)pp;
+
+ inst_cal_type n_cals = inst_calt_none;
+ inst_cal_type a_cals = inst_calt_none;
+
+ if (p->refrmode != 0) {
+ if (p->rrset == 0)
+ n_cals |= inst_calt_ref_freq;
+ a_cals |= inst_calt_ref_freq;
+ }
+
+ if (pn_cals != NULL)
+ *pn_cals = n_cals;
+
+ if (pa_cals != NULL)
+ *pa_cals = a_cals;
+
+ return inst_ok;
+}
+
+/* Request an instrument calibration. */
+/* This is use if the user decides they want to do a calibration, */
+/* in anticipation of a calibration (needs_calibration()) to avoid */
+/* requiring one during measurement, or in response to measuring */
+/* returning inst_needs_cal. Initially us an inst_cal_cond of inst_calc_none, */
+/* and then be prepared to setup the right conditions, or ask the */
+/* user to do so, each time the error inst_cal_setup is returned. */
+inst_code spyd2_calibrate(
+inst *pp,
+inst_cal_type *calt, /* Calibration type to do/remaining */
+inst_cal_cond *calc, /* Current condition/desired condition */
+char id[CALIDLEN] /* Condition identifier (ie. white reference ID) */
+) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev = inst_ok;
+ inst_cal_type needed, available;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ id[0] = '\000';
+
+ if ((ev = spyd2_get_n_a_cals((inst *)p, &needed, &available)) != inst_ok)
+ return ev;
+
+ /* Translate inst_calt_all/needed into something specific */
+ if (*calt == inst_calt_all
+ || *calt == inst_calt_needed
+ || *calt == inst_calt_available) {
+ if (*calt == inst_calt_all)
+ *calt = (needed & inst_calt_n_dfrble_mask) | inst_calt_ap_flag;
+ else if (*calt == inst_calt_needed)
+ *calt = needed & inst_calt_n_dfrble_mask;
+ else if (*calt == inst_calt_available)
+ *calt = available & inst_calt_n_dfrble_mask;
+
+ a1logd(p->log,4,"spyd2_calibrate: doing calt 0x%x\n",calt);
+
+ if ((*calt & inst_calt_n_dfrble_mask) == 0) /* Nothing todo */
+ return inst_ok;
+ }
+
+ if ((*calt & inst_calt_ref_freq) && p->refrmode != 0) {
+
+ if (*calc != inst_calc_emis_white) {
+ *calc = inst_calc_emis_white;
+ return inst_cal_setup;
+ }
+
+ /* Do CRT frame rate calibration */
+ if ((ev = spyd2_GetRefRate(p)) != inst_ok)
+ return ev;
+
+ *calt &= ~inst_calt_ref_freq;
+ }
+
+ return inst_ok;
+}
+
+/* Return the last calibrated refresh rate in Hz. Returns: */
+static inst_code spyd2_get_refr_rate(inst *pp,
+double *ref_rate
+) {
+ spyd2 *p = (spyd2 *)pp;
+ if (p->refrvalid) {
+ *ref_rate = p->refrate;
+ return inst_ok;
+ } else if (p->rrset) {
+ *ref_rate = 0.0;
+ return inst_misread;
+ }
+ return inst_needs_cal;
+}
+
+/* Set the calibrated refresh rate in Hz. */
+/* Set refresh rate to 0.0 to mark it as invalid */
+/* Rates outside the range 5.0 to 150.0 Hz will return an error */
+static inst_code spyd2_set_refr_rate(inst *pp,
+double ref_rate
+) {
+ spyd2 *p = (spyd2 *)pp;
+
+ if (ref_rate != 0.0 && (ref_rate < 5.0 || ref_rate > 150.0))
+ return inst_bad_parameter;
+
+ p->refrate = ref_rate;
+ if (ref_rate == 0.0)
+ p->refrate = DEFRRATE;
+ else
+ p->refrvalid = 1;
+ p->rrset = 1;
+
+ return inst_ok;
+}
+
+/* Error codes interpretation */
+static char *
+spyd2_interp_error(inst *pp, int ec) {
+// spyd2 *p = (spyd2 *)pp;
+ ec &= inst_imask;
+ switch (ec) {
+ case SPYD2_INTERNAL_ERROR:
+ return "Non-specific software internal software error";
+ case SPYD2_COMS_FAIL:
+ return "Communications failure";
+ case SPYD2_UNKNOWN_MODEL:
+ return "Not a Spyder 2 or 3";
+ case SPYD2_DATA_PARSE_ERROR:
+ return "Data from i1 Display didn't parse as expected";
+
+ case SPYD2_OK:
+ return "No device error";
+
+ /* device specific errors */
+ case SPYD2_BADSTATUS:
+ return "Too many retries waiting for status to come good";
+ case SPYD2_PLDLOAD_FAILED:
+ return "Wrong status after download of PLD";
+ case SPYD2_BADREADSIZE:
+ return "Didn't read expected amount of data";
+ case SPYD2_TRIGTIMEOUT:
+ return "Trigger timout";
+ case SPYD2_OVERALLTIMEOUT:
+ return "Overall timout";
+ case SPYD2_BAD_EE_CRC:
+ return "Serial EEProm CRC failed";
+
+ /* Internal errors */
+ case SPYD2_BAD_EE_ADDRESS:
+ return "Serial EEProm read is out of range";
+ case SPYD2_BAD_EE_SIZE:
+ return "Serial EEProm read size > 256";
+ case SPYD2_NO_PLD_PATTERN:
+ return "No PLD firmware pattern is available (have you run spyd2en ?)";
+ case SPYD2_NO_COMS:
+ return "Communications hasn't been established";
+ case SPYD2_NOT_INITED:
+ return "Insrument hasn't been initialised";
+ case SPYD2_NOCRTCAL:
+ return "Insrument is missing the CRT calibration table";
+ case SPYD2_NOLCDCAL:
+ return "Insrument is missing the Normal or LCD calibration table";
+ case SPYD2_MALLOC:
+ return "Memory allocation failure";
+ case SPYD2_OBS_SELECT:
+ return "Failed to set observer type";
+ case SPYD2_CAL_FAIL:
+ return "Calibration calculation failed";
+ case SPYD2_INT_CIECONVFAIL:
+ return "Creating spectral to CIE converted failed";
+ case SPYD2_TOO_FEW_CALIBSAMP:
+ return "There are too few spectral calibration samples - need at least 3";
+
+ /* Configuration */
+ case SPYD2_DISP_SEL_RANGE:
+ return "Display device selection out of range";
+
+ default:
+ return "Unknown error code";
+ }
+}
+
+
+/* Convert a machine specific error code into an abstract dtp code */
+static inst_code
+spyd2_interp_code(inst *pp, int ec) {
+// spyd2 *p = (spyd2 *)pp;
+
+ ec &= inst_imask;
+ switch (ec) {
+
+ case SPYD2_OK:
+ return inst_ok;
+
+ case SPYD2_INTERNAL_ERROR:
+ case SPYD2_NO_COMS:
+ case SPYD2_NOT_INITED:
+ case SPYD2_BAD_EE_ADDRESS:
+ case SPYD2_BAD_EE_SIZE:
+ case SPYD2_NO_PLD_PATTERN:
+ case SPYD2_MALLOC:
+ case SPYD2_OBS_SELECT:
+ case SPYD2_CAL_FAIL:
+ case SPYD2_INT_CIECONVFAIL:
+ case SPYD2_TOO_FEW_CALIBSAMP:
+ return inst_internal_error | ec;
+
+ case SPYD2_COMS_FAIL:
+ case SPYD2_BADREADSIZE:
+ case SPYD2_TRIGTIMEOUT:
+ case SPYD2_BADSTATUS:
+ case SPYD2_OVERALLTIMEOUT:
+ return inst_coms_fail | ec;
+
+ case SPYD2_UNKNOWN_MODEL:
+ return inst_unknown_model | ec;
+
+// return inst_protocol_error | ec;
+
+ case SPYD2_NOCRTCAL:
+ case SPYD2_NOLCDCAL:
+ case SPYD2_PLDLOAD_FAILED:
+ case SPYD2_BAD_EE_CRC:
+ return inst_hardware_fail | ec;
+
+ case SPYD2_DISP_SEL_RANGE:
+ return inst_wrong_setup | ec;
+
+ }
+ return inst_other_error | ec;
+}
+
+/* Destroy ourselves */
+static void
+spyd2_del(inst *pp) {
+ spyd2 *p = (spyd2 *)pp;
+ if (p->icom != NULL)
+ p->icom->del(p->icom);
+ inst_del_disptype_list(p->dtlist, p->ndtlist);
+ free(p);
+}
+
+/* Return the instrument mode capabilities */
+void spyd2_capabilities(inst *pp,
+inst_mode *pcap1,
+inst2_capability *pcap2,
+inst3_capability *pcap3) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_mode cap1= 0;
+ inst2_capability cap2 = 0;
+
+ cap1 |= inst_mode_emis_spot
+ | inst_mode_emis_refresh_ovd
+ | inst_mode_emis_norefresh_ovd
+ | inst_mode_colorimeter
+ ;
+
+ /* We don't seem to have a way of detecting the lack */
+ /* of ambinent capability, short of doing a read */
+ /* and noticing the result is zero. */
+ if (p->itype == instSpyder3
+ || p->itype == instSpyder4) {
+ cap1 |= inst_mode_emis_ambient;
+ }
+
+ cap2 |= inst2_prog_trig
+ | inst2_user_trig
+ | inst2_ccmx
+ | inst2_refresh_rate
+ | inst2_emis_refr_meas
+ ;
+
+ if (p->itype == instSpyder3
+ || p->itype == instSpyder4) {
+ cap2 |= inst2_disptype;
+ cap2 |= inst2_has_leds;
+ cap2 |= inst2_ambient_mono;
+ } else {
+ cap2 |= inst2_disptype;
+ }
+
+ if (p->itype == instSpyder4)
+ cap2 |= inst2_ccss; /* Spyder4 has spectral sensiivities */
+
+ if (pcap1 != NULL)
+ *pcap1 = cap1;
+ if (pcap2 != NULL)
+ *pcap2 = cap2;
+ if (pcap3 != NULL)
+ *pcap3 = inst3_none;
+}
+
+/* Check device measurement mode */
+inst_code spyd2_check_mode(inst *pp, inst_mode m) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_mode cap;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ pp->capabilities(pp, &cap, NULL, NULL);
+
+ /* Simple test */
+ if (m & ~cap)
+ return inst_unsupported;
+
+ if (!IMODETST(m, inst_mode_emis_spot)
+ && !IMODETST(m, inst_mode_emis_ambient)) {
+ return inst_unsupported;
+ }
+
+ return inst_ok;
+}
+
+/* Set device measurement mode */
+inst_code spyd2_set_mode(inst *pp, inst_mode m) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev;
+
+ if ((ev = spyd2_check_mode(pp, m)) != inst_ok)
+ return ev;
+
+ p->mode = m;
+
+ if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) /* Must test this first! */
+ p->refrmode = 0;
+ else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd))
+ p->refrmode = 1;
+
+ return inst_ok;
+}
+
+inst_disptypesel spyd2_disptypesel[3] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "l",
+ "LCD display",
+ 0,
+ 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "c", /* sel */
+ "CRT display", /* desc */
+ 1, /* refr */
+ 0 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+inst_disptypesel spyd3_disptypesel[3] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "nl",
+ "Non-Refresh display",
+ 0,
+ 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "rc", /* sel */
+ "Refresh display", /* desc */
+ 1, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+inst_disptypesel spyd4_disptypesel_1[8] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "nl",
+ "Generic Non-Refresh Display",
+ 0,
+ 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "rc", /* sel */
+ "Generic Refresh Display", /* desc */
+ 1, /* refr */
+ 1 /* ix */
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+inst_disptypesel spyd4_disptypesel[8] = {
+ {
+ inst_dtflags_default,
+ 1,
+ "n",
+ "Generic Non-Refresh Display",
+ 0,
+ 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 2, /* cbid */
+ "r", /* sel */
+ "Generic Refresh Display", /* desc */
+ 1, /* refr */
+ 1 /* ix = hw bit + spec table << 1 */
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0,
+ "f",
+ "LCD, CCFL Backlight",
+ 0,
+ (1 << 1) | 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0,
+ "L",
+ "Wide Gamut LCD, CCFL Backlight",
+ 0,
+ (2 << 1) | 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0,
+ "e",
+ "LCD, White LED Backlight",
+ 0,
+ (3 << 1) | 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0,
+ "B",
+ "Wide Gamut LCD, RGB LED Backlight",
+ 0,
+ (4 << 1) | 1
+ },
+ {
+ inst_dtflags_none, /* flags */
+ 0,
+ "x",
+ "LCD, CCFL Backlight (Laptop ?)",
+ 0,
+ (5 << 1) | 1
+ },
+ {
+ inst_dtflags_end,
+ 0,
+ "",
+ "",
+ 0,
+ 0
+ }
+};
+
+static void set_base_disptype_list(spyd2 *p) {
+ /* set the base display type list */
+ if (p->itype == instSpyder4) {
+ if (spyd4_nocals <= 1) {
+ p->_dtlist = spyd4_disptypesel_1;
+ } else {
+ p->_dtlist = spyd4_disptypesel;
+ }
+ } else if (p->itype == instSpyder3) {
+ p->_dtlist = spyd3_disptypesel;
+ } else {
+ p->_dtlist = spyd2_disptypesel;
+ }
+}
+
+/* Get mode and option details */
+static inst_code spyd2_get_disptypesel(
+inst *pp,
+int *pnsels, /* Return number of display types */
+inst_disptypesel **psels, /* Return the array of display types */
+int allconfig, /* nz to return list for all configs, not just current. */
+int recreate /* nz to re-check for new ccmx & ccss files */
+) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code rv = inst_ok;
+
+ /* Create/Re-create a current list of abailable display types */
+ if (p->dtlist == NULL || recreate) {
+ if ((rv = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ p->_dtlist, p->hwver >= 7 ? 1 : 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return rv;
+ }
+
+ if (pnsels != NULL)
+ *pnsels = p->ndtlist;
+
+ if (psels != NULL)
+ *psels = p->dtlist;
+
+ return inst_ok;
+}
+
+/* Given a display type entry, setup for that type */
+static inst_code set_disp_type(spyd2 *p, inst_disptypesel *dentry) {
+ inst_code ev;
+ int refrmode;
+
+ p->icx = dentry->ix;
+ p->cbid = dentry->cbid;
+ refrmode = dentry->refr;
+
+ if ( IMODETST(p->mode, inst_mode_emis_norefresh_ovd)) { /* Must test this first! */
+ refrmode = 0;
+ } else if (IMODETST(p->mode, inst_mode_emis_refresh_ovd)) {
+ refrmode = 1;
+ }
+
+ if (p->refrmode != refrmode) {
+ p->rrset = 0; /* This is a hint we may have swapped displays */
+ p->refrvalid = 0;
+ }
+ p->refrmode = refrmode;
+
+ if (dentry->flags & inst_dtflags_ccss) {
+
+ if ((ev = spyd4_comp_calmat(p, p->obType, p->custObserver, dentry->sets, dentry->no_sets))
+ != inst_ok) {
+ a1logd(p->log, 1, "spyd4_set_disp_type: comp_calmat ccss failed with rv = 0x%x\n",ev);
+ return ev;
+ }
+ p->icx = (99 << 1) | 1; /* Out of range index */
+ icmSetUnity3x3(p->ccmat);
+
+ } else {
+
+ if (p->hwver >= 7) {
+ if ((p->icx >> 1) > spyd4_nocals)
+ return inst_unsupported;
+
+ /* Create the calibration matrix */
+ if ((ev = spyd4_set_cal(p, p->icx >> 1)) != inst_ok)
+ return ev;
+ }
+
+ if (dentry->flags & inst_dtflags_ccmx) {
+ icmCpy3x3(p->ccmat, dentry->mat);
+ } else {
+ icmSetUnity3x3(p->ccmat);
+ }
+ }
+
+ return inst_ok;
+}
+
+
+/* Setup the default display type */
+static inst_code set_default_disp_type(spyd2 *p) {
+ inst_code ev;
+ int i;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list((inst *)p, &p->ndtlist, &p->dtlist,
+ p->_dtlist, p->hwver >= 7 ? 1 : 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ for (i = 0; !(p->dtlist[i].flags & inst_dtflags_end); i++) {
+ if (p->dtlist[i].flags & inst_dtflags_default)
+ break;
+ }
+ if (p->dtlist[i].flags & inst_dtflags_end) {
+ a1loge(p->log, 1, "set_default_disp_type: failed to find type!\n");
+ return inst_internal_error;
+ }
+ if ((ev = set_disp_type(p, &p->dtlist[i])) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/* Set the display type */
+static inst_code spyd2_set_disptype(inst *pp, int ix) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev;
+ inst_disptypesel *dentry;
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ if (p->dtlist == NULL) {
+ if ((ev = inst_creat_disptype_list(pp, &p->ndtlist, &p->dtlist,
+ p->_dtlist, p->hwver >= 7 ? 1 : 0 /* doccss*/, 1 /* doccmx */)) != inst_ok)
+ return ev;
+ }
+
+ if (ix < 0 || ix >= p->ndtlist)
+ return inst_unsupported;
+
+ dentry = &p->dtlist[ix];
+
+ if ((ev = set_disp_type(p, dentry)) != inst_ok) {
+ return ev;
+ }
+
+ return inst_ok;
+}
+
+/*
+ * set or reset an optional mode
+ *
+ * Some options talk to the instrument, and these will
+ * error if it hasn't been initialised.
+ * [We could fix this by setting a flag and adding
+ * some extra logic in init()]
+ */
+static inst_code
+spyd2_get_set_opt(inst *pp, inst_opt_type m, ...) {
+ spyd2 *p = (spyd2 *)pp;
+ inst_code ev = inst_ok;
+
+ /* Record the trigger mode */
+ if (m == inst_opt_trig_prog
+ || m == inst_opt_trig_user) {
+ p->trig = m;
+ return inst_ok;
+ }
+
+ if (!p->gotcoms)
+ return inst_no_coms;
+ if (!p->inited)
+ return inst_no_init;
+
+ /* Get the display type information */
+ if (m == inst_opt_get_dtinfo) {
+ va_list args;
+ int *refrmode, *cbid;
+
+ va_start(args, m);
+ refrmode = va_arg(args, int *);
+ cbid = va_arg(args, int *);
+ va_end(args);
+
+ if (refrmode != NULL)
+ *refrmode = p->refrmode;
+ if (cbid != NULL)
+ *cbid = p->cbid;
+
+ return inst_ok;
+ }
+
+ /* Set the ccss observer type */
+ if (m == inst_opt_set_ccss_obs) {
+ va_list args;
+ icxObserverType obType;
+ xspect *custObserver;
+
+ va_start(args, m);
+ obType = va_arg(args, icxObserverType);
+ custObserver = va_arg(args, xspect *);
+ va_end(args);
+
+ if (obType == icxOT_default)
+ obType = icxOT_CIE_1931_2;
+ p->obType = obType;
+ if (obType == icxOT_custom) {
+ p->custObserver[0] = custObserver[0];
+ p->custObserver[1] = custObserver[1];
+ p->custObserver[2] = custObserver[2];
+ }
+
+ return inst_ok;
+ }
+
+ /* Operate the LED */
+ if (p->hwver >= 4) {
+ if (m == inst_opt_get_gen_ledmask) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = 0x1; /* One general LED */
+ return inst_ok;
+ } else if (m == inst_opt_get_led_state) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = p->led_state;
+ return inst_ok;
+ } else if (m == inst_opt_set_led_state) {
+ va_list args;
+ int mask = 0;
+
+ va_start(args, m);
+ mask = 1 & va_arg(args, int);
+ va_end(args);
+ if ((ev = spyd2_setLED(p, mask & 1 ? 2 : 0, 0.0)) == inst_ok) {
+ p->led_state = mask;
+ }
+ return ev;
+ }
+ }
+
+ if (m == inst_opt_get_pulse_ledmask) {
+ va_list args;
+ int *mask = NULL;
+
+ va_start(args, m);
+ mask = va_arg(args, int *);
+ va_end(args);
+ *mask = 0x1; /* General LED is pulsable */
+ return inst_ok;
+ } else if (m == inst_opt_set_led_pulse_state) {
+ va_list args;
+ double period, on_time_prop, trans_time_prop;
+ int mode;
+
+ va_start(args, m);
+ period = va_arg(args, double);
+ on_time_prop = va_arg(args, double);
+ trans_time_prop = va_arg(args, double);
+ va_end(args);
+ if (period < 0.0
+ || on_time_prop < 0.0 || on_time_prop > 1.0
+ || trans_time_prop < 0.0 || trans_time_prop > 1.0
+ || trans_time_prop > on_time_prop || trans_time_prop > (1.0 - on_time_prop))
+ return inst_bad_parameter;
+ if (period == 0.0 || on_time_prop == 0.0) {
+ period = 0.0;
+ mode = 0;
+ p->led_state = 0;
+ } else {
+ mode = 1;
+ p->led_state = 1;
+ }
+ p->led_period = period;
+ p->led_on_time_prop = on_time_prop;
+ p->led_trans_time_prop = trans_time_prop;
+ return spyd2_setLED(p, mode, period);
+ } else if (m == inst_opt_get_led_state) {
+ va_list args;
+ double *period, *on_time_prop, *trans_time_prop;
+
+ va_start(args, m);
+ period = va_arg(args, double *);
+ on_time_prop = va_arg(args, double *);
+ trans_time_prop = va_arg(args, double *);
+ va_end(args);
+ if (period != NULL) *period = p->led_period;
+ if (on_time_prop != NULL) *on_time_prop = p->led_on_time_prop;
+ if (trans_time_prop != NULL) *trans_time_prop = p->led_trans_time_prop;
+ return inst_ok;
+ }
+ return inst_unsupported;
+}
+
+/* Constructor */
+extern spyd2 *new_spyd2(icoms *icom, instType itype) {
+ spyd2 *p;
+ if ((p = (spyd2 *)calloc(sizeof(spyd2),1)) == NULL) {
+ a1loge(icom->log, 1, "new_spyd2: malloc failed!\n");
+ return NULL;
+ }
+
+ p->log = new_a1log_d(icom->log);
+
+ p->init_coms = spyd2_init_coms;
+ p->init_inst = spyd2_init_inst;
+ p->capabilities = spyd2_capabilities;
+ p->check_mode = spyd2_check_mode;
+ p->set_mode = spyd2_set_mode;
+ p->get_disptypesel = spyd2_get_disptypesel;
+ p->set_disptype = spyd2_set_disptype;
+ p->get_set_opt = spyd2_get_set_opt;
+ p->read_sample = spyd2_read_sample;
+ p->read_refrate = spyd2_read_refrate;
+ p->get_n_a_cals = spyd2_get_n_a_cals;
+ p->calibrate = spyd2_calibrate;
+ p->col_cor_mat = spyd2_col_cor_mat;
+ p->col_cal_spec_set = spyd2_col_cal_spec_set;
+ p->get_refr_rate = spyd2_get_refr_rate;
+ p->set_refr_rate = spyd2_set_refr_rate;
+ p->interp_error = spyd2_interp_error;
+ p->del = spyd2_del;
+
+ p->icom = icom;
+ p->itype = icom->itype;
+
+ /* Load manufacturers Spyder4 calibrations */
+ if (itype == instSpyder4) {
+ int rv;
+ p->hwver = 7; /* Set preliminary version */
+ if ((rv = spyd4_load_cal(p)) != SPYD2_OK)
+ a1logd(p->log, 1, "Loading Spyder4 calibrations failed with '%s'\n",p->interp_error((inst *)p, rv));
+ if (spyd4_nocals < 1)
+ a1logd(p->log, 1, "Spyder4 choice of calibrations not available\n");
+ }
+ if (itype == instSpyder3) {
+ p->hwver = 4; /* Set preliminary version */
+ }
+ if (itype == instSpyder2) {
+ p->hwver = 3; /* Set preliminary version */
+ }
+
+ icmSetUnity3x3(p->ccmat); /* Set the colorimeter correction matrix to do nothing */
+ set_base_disptype_list(p);
+
+ return p;
+}
+