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