
package nmc.sidekick;
import javax.comm.SerialPort;
import com.ajile.drivers.spi.*;
import java.lang.Thread;

// The DPA board looks like the following

/*
 * Major things not yet tested:
 *   - taking a reading from the ADC
 *   - taking all readings from the ADC
 *   - turning each relay on, power cycle, first write is turn it off
 *   - turning each relay off, power cycle, first write is turn it on
 *   - turning each relay of, power cycle, first write is turn it off
 *   - turning each relay on, power cycle, first write is turn it on
 *   - I presume that interactions between relays are not significant
 *   - getting an enabled interrupt
 *   - getting an enabled interrupt and disabling it
 *   - getting a disabled interrupt
 *   - tripping the digital circuit breaker
 *   - tripping the digital circuit breaker and resetting it
 *   - repeatedly trip/reset the digital circuit breaker, deal with short
 *   - various combinations of drive modes (unipolar/bipolar, RS-485, etc.)
 *
 */



/*
- DPA
   - Notes:  In the listing below, single-bit register fields are only
      listed by their values for a '1' in that position.  If a '0' in
      that position means something other than the simple negation of
      a '1', then the meaning of '0' is listed in the same line.
      There are only four items that are not single-bit fields:  (1) the
      command (in all cases), (2) the power-down (or simply 'power') mode for
      an ADC Register Write, (3) the channel value for an ADC Register
      Write, and (4) the 'count', or number of clicks to move the
      potentiometer for a Dpot
      (Current Limit) Register Write.  There are individual names for
      each possible value for the cmnd field and for the ADC power
      mode.  The ADC Channel and the Dpot Count field are simply
      unsigned numbers, so only a mask value is specified for those.
   - Defaults:  Most of the values do not have meaningful defaults.
        Those that do have a '*' or a '!' before the value.  An '*'
        means that the asterisked value is the default.  A '!' means
        that a 0 in that bit position is the default.
   - Summary of registers
      - ADC
      - Interrupt
      - Relays
      - Dpot
      - Channel Control
   - DPA board-level commands (cmnd mask 0xf000, values:)
      - ADC
         - Notes: Programmer writes a command to the ADC and waits for
              the DPA's status to become not busy, which indicates
              that the command has been accepted and the conversion
              has been completed.  After the DPA status becomes
              nonbusy, the programmer can read the ADC.  If the power
              mode is power on, the command is also a conversion
              request; all bits are always meaningful, so every
              conversion request must include power mode, acquisition
              mode and drive types.
         - 0x4000 ADC Write (aka take reading)
            - Power Mode (mask 0x00c0, values:)
               -  0x0000: full power down [default when not sampling]
               - *0x0040: standby
               -  0x0080: power on / use internal clock [default when sampling]
               -  0x00c0: power on / use external clock
            - Acquisition Mode
               - !0x0020: External (0 means Internal)
            - SGL/DIF (drive type)
               - *0x0010: Single-ended (0 means Differential)
            - Uni/Bi (drive polarity)
               - *0x0008: Unipolar (0 means Bipolar)
            - Channels (mask 0x0007, values:)
               - 0x0007: Battery Supply Voltage (Vbat = 5.8*Vin)
               - 0x0006: Right Channel voltage sense (Vinstr=6*Vin)
               - 0x0005: Right Channel trip level sense
               - 0x0004: Right Channel current sense
               - 0x0003: Heat sink temperature
               - 0x0002: Left Channel voltage sense (Vinstr=6*Vin)
               - 0x0001: Left Channel trip level sense
               - 0x0000: Left Channel current sense
         - 0xb000 ADC Read (mask for values: 0x0fff)
      - Interrupt Register
         - Notes: The DPA is capable of generating an interrupt to the
              SideKick processor ONLY on overcurrent conditions.  If a
              channel has an overcurrent condition (is drawing more
              current than allowed by the SetCurrentLimit cmnd), the
              electronic circuit breaker will trip.  An interrupt will
              be asserted iff the electronic circuit breaker for that
              channel has tripped (and has not been reset), AND the
              interrupt enable for that channel is set, AND the global
              interrupt enable has been set.  The assertion of an
              interrupt will cause three things to be true: the
              interrupt flag for that channel will be set, the global
              interrupt flag will be set, and the DPA board will
              assert an interrupt to the SideKick processor.  The
              overcurrent condition itself is asserted by the digital
              circuit breaker, and that breaker is cleared by turning
              it off.  (Reading the interrupt register has no effect
              on the interrupt flags.)
         - Commands
            - 0x5000 Write
            - 0xb000 Read
         - Register bits (mask 0x003f, RO=WriteOnly, RW=Read/Write)
            - 0x0020: (RW)Left Channel overcurrent intr en
            - 0x0010: (RW)Right Channel overcurrent intr en
            - 0x0008: (RW)Global intr en
            - 0x0004: (RO)Left Channel overcurrent intr flag
            - 0x0002: (RO)Right Channel overcurrent intr flag
            - 0x0001: (RO)Global intr flag
   - DPA Channel-level commands (cmnd mask 0xf000, values:)
      - Relays
         - Commands
            - 0x2000: Write left channel register
            - 0x3000: Write right channel register
            - 0xa000: Read left channel register
            - 0xb000: Read right channel register
         - Register bits
            - 0x0004: Connect 485 Terminators
            - 0x0002: Isolate communications ground
            - 0x0001: Isolate instrument power
      - Dpot (Digital Circuit Breaker Current Limit)
         - Commands
            - 0x0000: Write left channel
            - 0x1000: Write right channel
         - Register bits and fields
            - 0x0100: Save position
            - 0x0080: Up (move potentiometer UP)
            - 0x007f: mask for counter value (0 through 127)
      - Channel Control
         - Commands
            - 0x6000: Write left channel
            - 0x7000: Write right channel
            - 0xd000: Read left channel
            - 0xe000: Read right channel
         - Register bits
            - 0x0020: Serial tx power on
            - 0x0010: Comm fast (0 means Slew Rate Controlled)
           - 0x0008: Comm Mode RS-485 (0 means RS-232)
           - 0x0004: Comm Half Duplex (0 means full duplex)
            - 0x0002: Comm Power On
            - 0x0001: Instrument Power On (this is the Digital Circuit Breaker)


*/


// Implementation notes:  when a board is created (ctor) it should
// instantiate all the left and right components, because it should
// initialize them.  From there on out, when a user of a channel
// manipulates any of its components, including components that are
// specialized to the left or right channel, they will do the right
// thing, since (a) they have access to the DPA board resources (shared
// between the channels) and (b) have their own control when that's
// appropriate.  Since the operation will be specified in the (abstract)
// superclass while the implementation lives in the subclass, the
// user can safely call superclass operations and they will either
// do the right thing because they are identical for both channels, or
// they will do the right thing because the implementation that gets
// invoked has been correctly specialized to the correct channel.
// Probably the only thing that really needs to handle the left and
// right channels as such is the board itself, and it can do so.
// E.g., if it needs to initialize the current limit by setting both
// channels to zero, it can do { leftChannel.adc.setCurrentLimit(0);
// rightChannel.adc.setCurrentLimit(0); }.
//


public class DpaBoard {

    private DpaChannel dpaLeftChannel; // auto-initialized to null
    private boolean dpaAvailChecked;    // have we looked for a DPA?
    private boolean dpaHardwareIsPresent; // is it there?

    private DpaChannel dpaRightChannel; // auto-initialized to null
    private int[] adcReg = new int[1];
    private int[] leftRelayReg = new int[1];
    private int[] rightRelayReg = new int[1];
    private int[] leftChannelCtrlReg = new int[1];
    private int[] rightChannelCtrlReg = new int[1];
    private int[] interruptReg = new int[1];
    {
        adcReg[0] = 0;
        leftRelayReg[0] = 0;
        rightRelayReg[0] = 0;
        leftChannelCtrlReg[0] = 0;
        rightChannelCtrlReg[0] = 0;
        interruptReg[0] = 0;
    }

    /*
    public void printShadowRegisters() {
        System.out.println("adcReg[0] = " + adcReg[0]
                           + "; leftRelayReg[0] = " + leftRelayReg[0]
                           + "; rightRelayReg[0] = " + rightRelayReg[0]
c                          + "; leftChannelCtrlReg[0] = " + leftChannelCtrlReg[0]
                           + "; rightChannelCtrlReg[0] = " + rightChannelCtrlReg[0]
                           );
    }
    */

    abstract public class HdwrReg {
        protected int[] regVal;
        protected boolean changed = true; // power-up value uncertain

        HdwrReg() {
            System.out.println("HdwrReg(void) ctor invoked");
        }
        HdwrReg(int[] regVal) {
            System.out.println("HdwrReg(int[]) ctor invoked");
            this.regVal = regVal;
        }
        protected void set(int mask, int val) {
            if ((regVal[0] & mask) == val)
                return;
            regVal[0] &= ~mask;
            regVal[0] |= val;
            changed = true;
        }
        protected void set(int bitmask) {
            set (bitmask, bitmask);
        }
        protected void clear(int mask) {
            set(mask, 0);
        }
    };

    class Adc extends HdwrReg {

        public final static int Wr = 0x4000;
        public final static int Rd = 0xb000;

        private final static int PowerMask  = 0x00c0;
        private final static int PowerOff  = 0x0000;
        private final static int PowerStby = 0x0040;
        private final static int PowerOnIntClk = 0x0080;
        //private final static int PowerOnExtClk = 0x00c0;

        private final static int AcqExt = 0x0020;
        private final static int SglDrive = 0x0010;
        private final static int Unipolar = 0x0008;
        private final static int   BatterySupplyVoltage = 0x0007;
        protected final static int RightVoltage         = 0x0006;
        protected final static int RightTripLevel       = 0x0005;
        protected final static int RightCurrent         = 0x0004;
        private final static int   HeatSinkTemp         = 0x0003;
        protected final static int LeftVoltage          = 0x0002;
        protected final static int LeftTripLevel        = 0x0001;
        protected final static int LeftCurrent          = 0x0000;



        private Adc(int[] regVal) {
            super(regVal);
            System.out.println("Adc ctor invoked");
            // These values will ALWAYS be this way.
            setSglDrive();
            setUnipolar();
        }
        public void setPowerOff() {
            set(PowerMask, PowerOff);
        }
        public void setPowerStby() {
            set(PowerMask, PowerStby);
        }
        public void setPowerOnIntClk() {
            set(PowerMask, PowerOnIntClk);
        }
        // PowerOnExternalClock will NEVER be used.  Here only for reference.
        //public void setPowerOnExtClk() {
        //    set(PowerMask, PowerOnExtClk);
        //}
        public void setAcqExternal() {
            set (AcqExt, AcqExt);
        }
        public void setAcqInternal() {
            set (AcqExt, 0);
        }
        public void setSglDrive() {
            set (SglDrive, SglDrive);
        }
        public void setDifferentialDrive() {
            set (SglDrive, 0);
        }
        public void setUnipolar() {
            set (Unipolar, Unipolar);
        }
        public void setBipolar() {
            set (Unipolar, 0);
        }
        // To take a reading, put the ADC into the on/ext state.
        // Wait 100msec.
        // Put it into the stby state.
        // Wait for the board to become nonbusy.
        // Read the value.
        void initialize() {
            setAcqInternal();
            setPowerStby();
            writeReadSpi(Wr | regVal[0] | 0); // channel is don't-care here
            stallOnBusyBit();
        }

        protected int getReading(int adcChannel) {
            setAcqExternal();
            setPowerOnIntClk();
            writeReadSpi(Wr | regVal[0] | adcChannel);
            try {
                Thread.sleep(100);
            } catch (java.lang.InterruptedException e) {
                System.out.println("ADC sleep got interrupted: " + e);
            }
            stallOnBusyBit();

            setAcqInternal();
            setPowerStby();
            writeReadSpi(Wr | regVal[0] | adcChannel);
            stallOnBusyBit();

            int retval = writeReadSpi(Rd); // other 12 bits are don't-cares
            stallOnBusyBit(); // read cannot possibly make it busy, though
            return retval;
        }

    };

    private Adc adc = new Adc(adcReg);

    public class BoardAdc {
        public int getBatterySupplyVoltage() {
            return adc.getReading(Adc.BatterySupplyVoltage);
        }
        public int getHeatSinkTemp() {
            return adc.getReading(Adc.HeatSinkTemp);
        }
    };

    public BoardAdc boardAdc = new BoardAdc();

    public abstract class ChannelAdc {
        abstract public int getVoltage();
        abstract public int getTripLevel();
        abstract public int getCurrent();
    };

    public class LeftChannelAdc extends ChannelAdc {
        public int getVoltage() {
            return adc.getReading(Adc.LeftVoltage);
        }
        public int getTripLevel() {
            return adc.getReading(Adc.LeftTripLevel);
        }
        public int getCurrent() {
            return adc.getReading(Adc.LeftCurrent);
        }
    }

    public class RightChannelAdc extends ChannelAdc {
        public int getVoltage() {
            return adc.getReading(Adc.RightVoltage);
        }
        public int getTripLevel() {
            return adc.getReading(Adc.RightTripLevel);
        }
        public int getCurrent() {
            return adc.getReading(Adc.RightCurrent);
        }
    }

    // To read the intr register, first do a read (otherwise you may get
    // a stale value)
    //

    abstract public class RelayReg extends HdwrReg {
        private final static int Connect485Terminators = 0x0004;
        private final static int IsolateCommunicationsGround = 0x0002;
        private final static int IsolateInstrumentPower = 0x0001;
        RelayReg(int[] regVal) {
            super(regVal);
            System.out.println("RelayReg ctor invoked");
        }
        public void connect485Terminators() {
            set(Connect485Terminators);
        }
        public void disconnect485Terminators() {
            clear(Connect485Terminators);
        }
        public void isolateCommunicationsGround() {
            set(IsolateCommunicationsGround);
        }
        public void connectCommunicationsGround() {
            clear(IsolateCommunicationsGround);
        }
        public void isolateInstrumentPower() {
            set(IsolateInstrumentPower);
        }
        public void connectInstrumentPower() {
            clear(IsolateInstrumentPower);
        }
        abstract public void write();
    };

    private class LeftRelayReg extends RelayReg {
        LeftRelayReg(int[] regVal) {
            super(regVal);
            System.out.println("LeftRelayReg ctor invoked");
        }
        private final static int Wr = 0x2000;
        public void write() {
            writeReadSpi(Wr | regVal[0]);
            stallOnBusyBit();
        }
    };
    private class RightRelayReg extends RelayReg {
        RightRelayReg(int[] regVal) {
            super(regVal);
            System.out.println("RightRelayReg ctor invoked");
        }
        private final static int Wr = 0x3000;
        public void write() {
            writeReadSpi(Wr | regVal[0]);
            stallOnBusyBit();
        }
    };

    abstract public class ChannelCtrlReg extends HdwrReg {
        private final static int TxHiPowerOn        = (1<<5);
        private final static int SlewRateNotLimited = (1<<4);
        private final static int CommMode485        = (1<<3);
        private final static int CommHalfDuplex     = (1<<2);
        private final static int CommPowerOn        = (1<<1);
        private final static int InstrumentPowerOn  = (1<<0);


        ChannelCtrlReg(int[] regVal) {
            super(regVal);
        }
        public void setTxHiPower() {
            set(TxHiPowerOn);
        }
        public void setTxLoPower() {
            clear(TxHiPowerOn);
        }
        public void setSlewRateNotLimited() {
            set(SlewRateNotLimited);
        }
        public void setSlewRateLimited() {
            clear(SlewRateNotLimited);
        }
        public void setCommModeRs485() {
            set(CommMode485);
        }
        public void setCommModeRs232() {
            clear(CommMode485);
        }
        public void setCommHalfDuplex() {
            set(CommHalfDuplex);
        }
        public void setCommFullDuplex() {
            clear(CommHalfDuplex);
        }
        public void setCommPowerOn() {
            set(CommPowerOn);
        }
        public void setCommPowerOff() {
            clear(CommPowerOn);
        }
        public void setInstrumentPowerOn() {
            set(InstrumentPowerOn);
        }
        public void setInstrumentPowerOff() {
            clear(InstrumentPowerOn);
        }
        abstract public void write();
    };
    private class LeftChannelCtrlReg extends ChannelCtrlReg {
        private LeftChannelCtrlReg(int[] regVal) {
            super(regVal);
            System.out.println("LeftChannelCtrlReg ctor invoked");
        }
        private final static int Wr = 0x6000;
        public void write() {
            System.out.println("doing a LeftChannelCtrlReg write()");
            writeReadSpi(Wr | regVal[0]);
            stallOnBusyBit();
        }
    };

    public class RightChannelCtrlReg extends ChannelCtrlReg {
        private RightChannelCtrlReg(int[] regVal) {
            super(regVal);
            System.out.println("RightChannelCtrlReg ctor invoked");
        }
        private final static int Wr = 0x7000;
        public void write() {
            System.out.println("doing a RightChannelCtrlReg write()");
            writeReadSpi(Wr | regVal[0]);
            stallOnBusyBit();
        }
    };

    abstract private class InterruptReg extends HdwrReg {
        public static final int Wr = 0x5000;
        public static final int EnableMask                = 0x0038;
        public static final int LeftOvercurrentEnable     = 0x0020;
        public static final int RightOvercurrentEnable    = 0x0010;
        public static final int GlobalOvercurrentEnable   = 0x0008;
        public static final int FlagMask                  = 0x0007;
        public static final int LeftOvercurrentIntrFlag   = 0x0004;
        public static final int RightOvercurrentIntrFlag  = 0x0002;
        public static final int GlobalOvercurrentIntrFlag = 0x0001;

        public InterruptReg(int[] regVal) {
            super(regVal);
        }

        public void setGlobalOvercurrentEn() {
            set(GlobalOvercurrentEnable);
        }
        public void clearGlobalOvercurrentEn() {
            clear(GlobalOvercurrentEnable);
        }
        public boolean isGlobalOvercurrentIntrFlagSet() {
            return ((regVal[0] & GlobalOvercurrentIntrFlag) != 0);
        }
        abstract public void setOvercurrentIntrEn();
        abstract public void clearOvercurrentIntrEn();
        abstract public boolean isOvercurrentFlagSet();
        public void write() {
            int retval = writeReadSpi(Wr | regVal[0]);
            regVal[0] = ((regVal[0] & EnableMask) | (retval & FlagMask));
            stallOnBusyBit();
        }
        //abstract public void read(); // actually, we can implement this here
    };
    private class LeftInterruptReg extends InterruptReg {
        private LeftInterruptReg(int[] regVal) {
            super(regVal);
        }
        public void setOvercurrentIntrEn() {
            set(LeftOvercurrentEnable);
        }
        public void clearOvercurrentIntrEn() {
            clear(LeftOvercurrentEnable);
        }
        public boolean isOvercurrentFlagSet() {
            return ((regVal[0] & LeftOvercurrentIntrFlag) != 0);
        }
    };
    private class RightInterruptReg extends InterruptReg {
        private RightInterruptReg(int[] regVal) {
            super(regVal);
        }
        public void setOvercurrentIntrEn() {
            set(RightOvercurrentEnable);
        }
        public void clearOvercurrentIntrEn() {
            clear(RightOvercurrentEnable);
        }
        public boolean isOvercurrentFlagSet() {
            return ((regVal[0] & RightOvercurrentIntrFlag) != 0);
        }
    };

    protected boolean doDebug = false;

    protected int spiSlaveSelectValue;
    protected static SpiMaster spi = SpiMaster.getInstance();
    protected boolean boardInitialized = false;

    public DpaBoard (int spiSlaveSelectValue) {
        System.out.println("DpaBoard ctor invoked");
        this.spiSlaveSelectValue = spiSlaveSelectValue;
    }

    // NOTE:  The create-if-null strategy will NOT work in the presence
    // of contention by multiple threads
    //
    public DpaChannel getLeftChannel() {
        if (dpaLeftChannel == null)
            dpaLeftChannel = this.new DpaLeftChannel();

        return dpaLeftChannel;
    }

    public DpaChannel getRightChannel() {
        if (dpaRightChannel == null)
            dpaRightChannel = this.new DpaRightChannel();

        return dpaRightChannel;
    }

    static int writeReadSpi(int slaveSelect, int dataOut) {
        int dataIn;
        synchronized (spi) {
            spi.setSlaveSelect(slaveSelect);
            dataIn = spi.writeReadData(0xff & (dataOut >> 8));
            dataIn <<= 8;
            dataIn |= (0xff & spi.writeReadData(0xff & dataOut));
            spi.setSlaveSelect(spi.SPI_SLAVE_SELECT_NONE);
        }
        /**/
        System.out.println("   writeReadSpi(0x"
                           + Integer.toHexString(dataOut)
                           + ") returns 0x"
                           + Integer.toHexString(dataIn));
        /**/

        // if (dataIn == 0xffff) or
        //  (dataIn & 0x7fff) == 0x7fff) throw exception
        return dataIn;
    }


    // returns true if the hardware is there
    static boolean checkForDpaHardware(int slaveSelectVal) {
        int dpaRetVal = writeReadSpi(slaveSelectVal, 0xf << 12);

        // 0x7fff is the busy indication.  If we get that on
        // the first read, we know that the real hardware,
        // which would definitely not be busy, is not there
        // and the circuitry that is there will assert 'busy'
        // forever.  Note that sometimes no hardware will
        // return 0xffff, hence the mask.

        return ((dpaRetVal & 0x7fff) != 0x7fff);
    }

    protected int writeReadSpi(int dataOut) {
        return writeReadSpi(spiSlaveSelectValue, dataOut);
    }

    public synchronized void stallOnBusyBit() {
        /**/
        int regVal;
        if (dpaHardwareIsPresent) {
            do {
                regVal = writeReadSpi(0xf << 12);
            } while ((regVal & 0x7fff) == 0x7fff);
        }
        /**/
        /**
        System.out.println("Spi bus cycle complete");
        /**/
    }

    /*
     *  The DpaChannel contains everything associated with the
     *  channel, and also with its DPA board.
     */
    public abstract class DpaChannel implements IoResource {

        protected boolean channelInitialized = false;
        protected boolean currentCtrlInitialized = false;
        protected Instrument instrument;
        protected javax.comm.SerialPort instrumentPort;
        protected int presentPosition;
        protected static final int MOVE_UP = 1;
        protected static final int MOVE_DOWN = 0;
        protected ChannelAdc channelAdc;
        protected RelayReg relayReg;
        protected ChannelCtrlReg channelCtrlReg;
        protected InterruptReg interruptCtrlReg;

        public DpaChannel() {
            System.out.println("DpaChannel ctor invoked");
        }

        void initializeChannel() {
            if (! channelInitialized) {
                initializeBoard(); // no-op if board already initialized

                //     1)  Initialize the ADC.  Done in initializeBoard();

                //     2)  Initialze the relays
                relayReg.disconnect485Terminators();
                relayReg.isolateCommunicationsGround();
                relayReg.isolateInstrumentPower();
                relayReg.write();

                //     3)  Set Current Limit -- this is done by the
                //         SideArm (or some other configurator) later.

                setCurrentLimit(1400); // sets trip value for
                                       // digital circuit breaker
                                       // 1400 is 11 steps (rounded down)

                //     4)  Configure the comm channel -- this is also done
                //         by the configurator.
                relayReg.connectInstrumentPower();
                relayReg.write();
                System.out.println("initializing channelCtrlReg");
                channelCtrlReg.setTxHiPower();
                channelCtrlReg.setSlewRateNotLimited();
                channelCtrlReg.setCommModeRs232();
                channelCtrlReg.setCommFullDuplex();
                channelCtrlReg.setCommPowerOn();
                channelCtrlReg.setInstrumentPowerOn();
                channelCtrlReg.write();

                // should remember global setting and other channel's setting
                interruptCtrlReg.setOvercurrentIntrEn();
                interruptCtrlReg.write();

                channelInitialized = true;
            }
        }


        protected void checkOnceForDpaHardware() {
            if (! dpaAvailChecked) {
                // this is just like stallOnBusyBit()
                int spiVal;
                spiVal = writeReadSpi(0xf << 12);
                dpaAvailChecked = true;

                // 0x7fff is the busy indication.  If we get that on
                // the first read, we know that the real hardware,
                // which would definitely not be busy, is not there
                // and the circuitry that is there will assert 'busy'
                // forever.  Note that sometimes no hardware will
                // return 0xffff, hence the mask.

                dpaHardwareIsPresent = ((spiVal & 0x7fff) != 0x7fff);
                System.out.print("checkOnceForDpaHardware() called...");
                if (dpaHardwareIsPresent) {
                    System.out.println("DPA is available");
                } else {
                    System.out.println("found no DPA hardware.");
                }
            }
        }

        protected void initializeBoard() {
            System.out.println("first line of initializeBoard()");
            if (! boardInitialized) {
                System.out.println("Doing DpaChannel.initializeBoard()");
                spi.setClkDivider(spi.SPI_CLOCK_DIVIDER_64);

                spi.setSlaveSelect(spi.SPI_SLAVE_SELECT_NONE);
                spi.writeReadData(0); // Do a write with no chip select.
                                      // This forces the DPA into a known
                                      // state (just in case it had seen
                                      // spurious spi clock edges with a
                                      // spurious CS).


                // Check for DPA hardware before any normal SPI cycles.
                //
             checkOnceForDpaHardware();

//              adc.initialize();
                interruptCtrlReg.setGlobalOvercurrentEn();
                interruptCtrlReg.write();
                boardInitialized = true;
            }
        }

        public void setSerialPort(SerialPort aPort) {
            // if aPort is null or instrumentPort isn't, throw exception...
            this.instrumentPort = aPort;
        }

        public Object getResourceType(IoResourceType aType) {
            if (aType == IoResourceType.DPA_BOARD)
                return this;
            else if (aType == IoResourceType.INSTRUMENT)
                return instrument;
            else
                return null;
        }

        private void initializeCurrentCtrl() {
            System.out.println("in InitializeCurrentCtrl()");

            movePot(MOVE_DOWN, 99);
            stallOnBusyBit();
            presentPosition = 0;
            currentCtrlInitialized = true;
        }

        public void setCurrentLimit(int milliamps) {
            System.out.println("In setCurrentLimit(int milliamps)");
            if (! currentCtrlInitialized) {
                System.out.println("  about to initializeCurrentCtrl()...");
                initializeCurrentCtrl();
                System.out.println("  initializeCurrentCtrl() done.");
            }

            // if milliamps < 0 or > ?? throw exception

            // For safety, we always round DOWN (i.e., std integer division)
            // The device can be set in increments of 120 milliamps,
            // from 0*120 to 99*120 = 11880 (i.e., from 0 to 11.88 Amps)
            int newPosition = milliamps / 120;
            int diff = newPosition - presentPosition;
            if (diff > 0)
                movePot(MOVE_UP, diff );
            else if (diff < 0)
                movePot(MOVE_DOWN, -diff );

            stallOnBusyBit();

            this.presentPosition = newPosition;
            /*END*/
        }

        int getCurrentLimitCtrlValue() {
            return presentPosition * 120;
        }
        protected abstract void movePot(int direction, int nStepsUp );

        public void setInstrument( Instrument anInstrument ) {
            instrument = anInstrument;
        }
    };

    public class DpaLeftChannel extends DpaChannel {
        DpaLeftChannel() {
            System.out.println("DpaLeftChannel ctor invoked");
            channelAdc = new LeftChannelAdc();
            relayReg = new LeftRelayReg(leftRelayReg); // but not relay reg
            channelCtrlReg = new LeftChannelCtrlReg(leftChannelCtrlReg);
            interruptCtrlReg = new LeftInterruptReg(interruptReg);
        }

        protected void movePot(int direction, int nStepsUp ) {
            System.out.println("DpaLeftChannel movePot(dir="
                               + direction
                               + "; steps=" + nStepsUp );
            int regValue =
                (0 << 12)            // CMND = 0 is left channel Dpot cmnd
                | (direction << 8)   // up is 1 in bit 8, down is 0
                | (0 << 7)           // do not save position to flash
                | (nStepsUp & 0x7f); // the count is in the low order 7 bits


            writeReadSpi(regValue);
        }

    };

    public class DpaRightChannel extends DpaChannel {
        DpaRightChannel() {
            System.out.println("DpaRightChannel ctor invoked");
            channelAdc = new RightChannelAdc();
            relayReg = new RightRelayReg(rightRelayReg); // but not relay reg
            channelCtrlReg = new RightChannelCtrlReg(rightChannelCtrlReg);
            interruptCtrlReg = new RightInterruptReg(interruptReg);
        }

        protected void movePot(int direction, int nStepsUp ) {
            int regValue =
                (1 << 12)            // CMND = 1 is right channel Dpot cmnd
                | (direction << 8)   // up is 1 in bit 8, down is 0
                | (0 << 7)           // do not save position to flash
                | (nStepsUp & 0x7f); // the count is in the low order 7 bits

            writeReadSpi(regValue);
        }
    };

}
