From 1a839c0b4026ed933b3232b13502fce907e0a8c1 Mon Sep 17 00:00:00 2001
From: jsiegle <jsiegle@mit.edu>
Date: Sun, 7 Apr 2013 17:19:54 -0400
Subject: [PATCH] Add code for automatically identifying the presence of
 headstages

---
 .../Processors/DataThreads/RHD2000Thread.cpp  | 328 +++++++++++++++---
 Source/Processors/DataThreads/RHD2000Thread.h |   8 +
 .../DataThreads/rhythm-api/rhd2000datablock.h |   2 +-
 3 files changed, 280 insertions(+), 58 deletions(-)

diff --git a/Source/Processors/DataThreads/RHD2000Thread.cpp b/Source/Processors/DataThreads/RHD2000Thread.cpp
index 59172015c..282812fd7 100644
--- a/Source/Processors/DataThreads/RHD2000Thread.cpp
+++ b/Source/Processors/DataThreads/RHD2000Thread.cpp
@@ -24,7 +24,8 @@
 #include "RHD2000Thread.h"
 #include "../SourceNode.h"
 
-RHD2000Thread::RHD2000Thread(SourceNode* sn) : DataThread(sn), isTransmitting(false)
+RHD2000Thread::RHD2000Thread(SourceNode* sn) : DataThread(sn), isTransmitting(false),
+    fastSettleEnabled(false)
 {
     evalBoard = new Rhd2000EvalBoard;
     dataBlock = new Rhd2000DataBlock(1);
@@ -43,66 +44,18 @@ RHD2000Thread::RHD2000Thread(SourceNode* sn) : DataThread(sn), isTransmitting(fa
 
     if (deviceFound)
     {
-        string bitfilename;
-        bitfilename = "rhd2000.bit";
-        evalBoard->uploadFpgaBitfile(bitfilename);
-
-        // Initialize board.
-        evalBoard->initialize();
-        evalBoard->setContinuousRunMode(false);
-        evalBoard->flush(); // flush in case it crashed with data remaining
-
-        // set defaults
-        //  4 data sources : 0 -> PortA1
-        //                   1 -> PortB1
-        //                   2 -> PortC1
-        //                   3 -> PortD1
-        //  
-        //  source 0 is enabled with 32 channels; the rest are disabled
-        //
-        //  sample rate is 10 kHz
-
-        evalBoard->setDataSource(0, Rhd2000EvalBoard::PortA1);
-        evalBoard->setDataSource(1, Rhd2000EvalBoard::PortB1);
-        evalBoard->setDataSource(2, Rhd2000EvalBoard::PortC1);
-        evalBoard->setDataSource(3, Rhd2000EvalBoard::PortD1);
-
-        for (int i = 0; i < 4; i++)
-        {
-            numChannelsPerDataStream.add(0);
-        }
-
-        dataBuffer = new DataBuffer(32, 10000);
-
-        enableHeadstage(0, true);
-        enableHeadstage(1, true);
-       // enableHeadstage(2, false);
-      //  enableHeadstage(3, false);
 
-        setCableLength(0, 3.0f);
-        setCableLength(1, 3.0f);
-        setCableLength(2, 3.0f);
-        setCableLength(3, 3.0f);
+        numChannelsPerDataStream.insertMultiple(0,0,4);
 
-        // Select per-channel amplifier sampling rate.
-        evalBoard->setSampleRate(Rhd2000EvalBoard::SampleRate10000Hz);
+        // initialize data buffer for 32 channels
+        dataBuffer = new DataBuffer(32, 10000); 
 
-        // Let's turn one LED on to indicate that the program is running.
-        int ledArray[8] = {1, 0, 0, 0, 0, 0, 0, 0};
-        evalBoard->setLedDisplay(ledArray);
-
-        // Set up an RHD2000 register object using this sample rate to optimize MUX-related
-        // register settings.
-        chipRegisters = new Rhd2000Registers(evalBoard->getSampleRate());
+        initializeBoard();
 
-        // Before generating register configuration command sequences, set amplifier
-        // bandwidth paramters.
-        double dspCutoffFreq;
-        dspCutoffFreq = chipRegisters->setDspCutoffFreq(10.0);
-        cout << "Actual DSP cutoff frequency: " << dspCutoffFreq << " Hz" << endl;
+        enableHeadstage(0,true); // start off with one headstage
 
-        chipRegisters->setLowerBandwidth(1.0);
-        chipRegisters->setUpperBandwidth(7500.0);
+        // automatically find connected headstages -- needs debugging
+        //scanPorts();
 
     }
 
@@ -123,6 +76,264 @@ RHD2000Thread::~RHD2000Thread()
 
 }
 
+void RHD2000Thread::initializeBoard()
+{
+    string bitfilename;
+    bitfilename = "rhd2000.bit";
+    evalBoard->uploadFpgaBitfile(bitfilename);
+
+    // Initialize board.
+    evalBoard->initialize();
+
+    // Select per-channel amplifier sampling rate.
+    evalBoard->setSampleRate(Rhd2000EvalBoard::SampleRate10000Hz);
+
+    // Select RAM Bank 0 for AuxCmd3 initially, so the ADC is calibrated.
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA, Rhd2000EvalBoard::AuxCmd3, 0);
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortB, Rhd2000EvalBoard::AuxCmd3, 0);
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortC, Rhd2000EvalBoard::AuxCmd3, 0);
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortD, Rhd2000EvalBoard::AuxCmd3, 0);evalBoard->flush(); // flush in case it crashed with data remaining
+
+    // Since our longest command sequence is 60 commands, run the SPI interface for
+    // 60 samples
+    evalBoard->setMaxTimeStep(60);
+    evalBoard->setContinuousRunMode(false);
+
+    // Start SPI interface
+    evalBoard->run();
+
+    // Wait for the 60-sample run to complete
+    while (evalBoard->isRunning())
+    {
+        ;
+    }
+
+    // Read the resulting single data block from the USB interface. We don't
+    // need to do anything with this, since it was only used for ADC calibration
+    Rhd2000DataBlock *dataBlock = new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams());
+    evalBoard->readDataBlock(dataBlock);
+
+    // Now that ADC calibration has been performed, we switch to the command sequence
+    // that does not execute ADC calibration.
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA, Rhd2000EvalBoard::AuxCmd3,
+                                fastSettleEnabled ? 2 : 1);
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortB, Rhd2000EvalBoard::AuxCmd3,
+                                fastSettleEnabled ? 2 : 1);
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortC, Rhd2000EvalBoard::AuxCmd3,
+                                fastSettleEnabled ? 2 : 1);
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortD, Rhd2000EvalBoard::AuxCmd3,
+                                fastSettleEnabled ? 2 : 1);
+
+
+    // Set default configuration for all eight DACs on interface board.
+    evalBoard->enableDac(0, false);
+    evalBoard->enableDac(1, false);
+    evalBoard->enableDac(2, false);
+    evalBoard->enableDac(3, false);
+    evalBoard->enableDac(4, false);
+    evalBoard->enableDac(5, false);
+    evalBoard->enableDac(6, false);
+    evalBoard->enableDac(7, false);
+    evalBoard->selectDacDataStream(0, 0);
+    evalBoard->selectDacDataStream(1, 0);
+    evalBoard->selectDacDataStream(2, 0);
+    evalBoard->selectDacDataStream(3, 0);
+    evalBoard->selectDacDataStream(4, 0);
+    evalBoard->selectDacDataStream(5, 0);
+    evalBoard->selectDacDataStream(6, 0);
+    evalBoard->selectDacDataStream(7, 0);
+    evalBoard->selectDacDataChannel(0, 0);
+    evalBoard->selectDacDataChannel(1, 1);
+    evalBoard->selectDacDataChannel(2, 0);
+    evalBoard->selectDacDataChannel(3, 0);
+    evalBoard->selectDacDataChannel(4, 0);
+    evalBoard->selectDacDataChannel(5, 0);
+    evalBoard->selectDacDataChannel(6, 0);
+    evalBoard->selectDacDataChannel(7, 0);
+    evalBoard->setDacManual(Rhd2000EvalBoard::DacManual1, 32768);
+    evalBoard->setDacManual(Rhd2000EvalBoard::DacManual2, 32768);
+    evalBoard->setDacGain(0);
+    evalBoard->setAudioNoiseSuppress(0);
+
+
+    // Set up an RHD2000 register object using this sample rate to optimize MUX-related
+    // register settings.
+    chipRegisters = new Rhd2000Registers(evalBoard->getSampleRate());
+
+    // Before generating register configuration command sequences, set amplifier
+    // bandwidth paramters.
+    double dspCutoffFreq;
+    dspCutoffFreq = chipRegisters->setDspCutoffFreq(10.0);
+    cout << "Actual DSP cutoff frequency: " << dspCutoffFreq << " Hz" << endl;
+
+    chipRegisters->setLowerBandwidth(1.0);
+    chipRegisters->setUpperBandwidth(7500.0);
+
+
+    // Let's turn one LED on to indicate that the program is running.
+    int ledArray[8] = {1, 0, 0, 0, 0, 0, 0, 0};
+    evalBoard->setLedDisplay(ledArray);
+
+       
+}
+
+void RHD2000Thread::scanPorts()
+{
+    // Scan SPI ports
+
+    int delay, stream, id, i, channel, port;
+    int stream1, stream2;
+    int numChannelsOnPort[4] = {0, 0, 0, 0};
+
+    // assume we only have 32-channel headstages, for the sake of
+    // simplicity; this will have to be changed once 64-channel
+    // headstages are an option
+    evalBoard->setDataSource(0, Rhd2000EvalBoard::PortA1);
+    evalBoard->setDataSource(1, Rhd2000EvalBoard::PortB1);
+    evalBoard->setDataSource(2, Rhd2000EvalBoard::PortC1);
+    evalBoard->setDataSource(3, Rhd2000EvalBoard::PortD1);
+
+    evalBoard->enableDataStream(0, true);
+    evalBoard->enableDataStream(1, true);
+    evalBoard->enableDataStream(2, true);
+    evalBoard->enableDataStream(3, true);
+
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortA,
+                                        Rhd2000EvalBoard::AuxCmd3, 0);
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortB,
+                                        Rhd2000EvalBoard::AuxCmd3, 0);
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortC,
+                                        Rhd2000EvalBoard::AuxCmd3, 0);
+    evalBoard->selectAuxCommandBank(Rhd2000EvalBoard::PortD,
+                                       Rhd2000EvalBoard::AuxCmd3, 0);
+
+    // Since our longest command sequence is 60 commands, we run the SPI
+    // interface for 60 samples.
+    evalBoard->setMaxTimeStep(60);
+    evalBoard->setContinuousRunMode(false);
+
+    Rhd2000DataBlock *dataBlock =
+                new Rhd2000DataBlock(evalBoard->getNumEnabledDataStreams());
+
+    Array<int> sumGoodDelays;
+    sumGoodDelays.insertMultiple(0,0,4);
+
+    Array<int> indexFirstGoodDelay;
+    indexFirstGoodDelay.insertMultiple(0,-1,4);
+
+    Array<int> indexSecondGoodDelay;
+    indexSecondGoodDelay.insertMultiple(0,-1,4);
+
+    Array<int> chipId;
+    chipId.insertMultiple(0,0,4);
+
+    Array<int> optimumDelay;
+    optimumDelay.insertMultiple(0,0,4);
+
+     // Run SPI command sequence at all 16 possible FPGA MISO delay settings
+     // to find optimum delay for each SPI interface cable.
+     for (delay = 0; delay < 16; ++delay) 
+     {
+        evalBoard->setCableDelay(Rhd2000EvalBoard::PortA, delay);
+        evalBoard->setCableDelay(Rhd2000EvalBoard::PortB, delay);
+        evalBoard->setCableDelay(Rhd2000EvalBoard::PortC, delay);
+        evalBoard->setCableDelay(Rhd2000EvalBoard::PortD, delay);
+
+        // Start SPI interface.
+        evalBoard->run();
+
+        // Wait for the 60-sample run to complete.
+        while (evalBoard->isRunning()) {
+            ;
+        }
+
+        // Read the resulting single data block from the USB interface.
+        evalBoard->readDataBlock(dataBlock);
+
+        // Read the Intan chip ID number from each RHD2000 chip found.
+        // Record delay settings that yield good communication with the chip.
+        for (stream = 0; stream < 4; ++stream) {
+            id = deviceId(dataBlock, stream);
+            std::cout << "Device ID found: " << id << std::endl;
+            if (id > 0) {
+                sumGoodDelays.set(stream,sumGoodDelays[stream] + 1);
+                if (indexFirstGoodDelay[stream] == -1) {
+                    indexFirstGoodDelay.set(stream, delay);
+                    chipId.set(stream,id);
+                } else if (indexSecondGoodDelay[stream] == -1) {
+                    indexSecondGoodDelay.set(stream,delay);
+                    chipId.set(stream,id);
+                }
+            }
+        }
+    }
+
+    // Now, disable data streams where we did not find chips present.
+    for (stream = 0; stream < 4; ++stream) 
+    {
+        if (chipId[stream] > 0) 
+        {
+            enableHeadstage(stream, true);
+        } else 
+        {
+            enableHeadstage(stream, false);
+        }
+    }
+
+    // Set cable delay settings that yield good communication with each
+    // RHD2000 chip.
+    for (stream = 0; stream < 4; ++stream) {
+        if (sumGoodDelays[stream] == 1 || sumGoodDelays[stream] == 2) {
+            optimumDelay.set(stream,indexFirstGoodDelay[stream]);
+        } else if (sumGoodDelays[stream] > 2) {
+            optimumDelay.set(stream,indexSecondGoodDelay[stream]);
+        }
+    }
+
+    evalBoard->setCableDelay(Rhd2000EvalBoard::PortA,
+                                 optimumDelay[0]);
+    evalBoard->setCableDelay(Rhd2000EvalBoard::PortB,
+                                 optimumDelay[1]);
+    evalBoard->setCableDelay(Rhd2000EvalBoard::PortC,
+                                 optimumDelay[2]);
+    evalBoard->setCableDelay(Rhd2000EvalBoard::PortD,
+                                 optimumDelay[3]);
+
+    cableLengthPortA =
+            evalBoard->estimateCableLengthMeters(optimumDelay[0]);
+    cableLengthPortB =
+            evalBoard->estimateCableLengthMeters(optimumDelay[1]);
+    cableLengthPortC =
+            evalBoard->estimateCableLengthMeters(optimumDelay[2]);
+    cableLengthPortD =
+            evalBoard->estimateCableLengthMeters(optimumDelay[3]);
+
+
+}
+
+int RHD2000Thread::deviceId(Rhd2000DataBlock* dataBlock, int stream)
+{
+     bool intanChipPresent;
+
+    // First, check ROM registers 32-36 to verify that they hold 'INTAN'.
+    // This is just used to verify that we are getting good data over the SPI
+    // communication channel.
+    intanChipPresent = ((char) dataBlock->auxiliaryData[stream][2][32] == 'I' &&
+                        (char) dataBlock->auxiliaryData[stream][2][33] == 'N' &&
+                        (char) dataBlock->auxiliaryData[stream][2][34] == 'T' &&
+                        (char) dataBlock->auxiliaryData[stream][2][35] == 'A' &&
+                        (char) dataBlock->auxiliaryData[stream][2][36] == 'N');
+
+    // If the SPI communication is bad, return -1.  Otherwise, return the Intan
+    // chip ID number stored in ROM regstier 63.
+    if (!intanChipPresent) {
+        return -1;
+    } else {
+        return dataBlock->auxiliaryData[stream][2][19]; // chip ID (Register 63)
+    }
+}
+
+
 bool RHD2000Thread::isAcquisitionActive()
 {
     return isTransmitting;
@@ -138,7 +349,10 @@ int RHD2000Thread::getNumChannels()
         numChannels += numChannelsPerDataStream[i];
     }
 
-    return numChannels;
+    if (numChannels > 0)
+        return numChannels;
+    else
+        return 1; // to prevent crashing with 0 channels
 }
 
 int RHD2000Thread::getNumEventChannels()
diff --git a/Source/Processors/DataThreads/RHD2000Thread.h b/Source/Processors/DataThreads/RHD2000Thread.h
index e1542d32e..894384754 100644
--- a/Source/Processors/DataThreads/RHD2000Thread.h
+++ b/Source/Processors/DataThreads/RHD2000Thread.h
@@ -88,11 +88,19 @@ private:
 
     bool isTransmitting;
 
+    bool fastSettleEnabled;
+
     bool startAcquisition();
     bool stopAcquisition();
 
+    void initializeBoard();
+    void scanPorts();
+    int deviceId(Rhd2000DataBlock* dataBlock, int stream);
+
     bool updateBuffer();
 
+    double cableLengthPortA, cableLengthPortB, cableLengthPortC, cableLengthPortD;
+
     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RHD2000Thread);
 };
 
diff --git a/Source/Processors/DataThreads/rhythm-api/rhd2000datablock.h b/Source/Processors/DataThreads/rhythm-api/rhd2000datablock.h
index d27a2c3ee..3a48746f7 100755
--- a/Source/Processors/DataThreads/rhythm-api/rhd2000datablock.h
+++ b/Source/Processors/DataThreads/rhythm-api/rhd2000datablock.h
@@ -20,7 +20,7 @@
 #ifndef RHD2000DATABLOCK_H
 #define RHD2000DATABLOCK_H
 
-#define SAMPLES_PER_DATA_BLOCK 300
+#define SAMPLES_PER_DATA_BLOCK 60
 #define RHD2000_HEADER_MAGIC_NUMBER 0xc691199927021942
 
 using namespace std;
-- 
GitLab