diff --git a/Source/Devices/AnalogIO.cpp b/Source/Devices/AnalogIO.cpp index 86c4bf6..0e47eee 100644 --- a/Source/Devices/AnalogIO.cpp +++ b/Source/Devices/AnalogIO.cpp @@ -23,10 +23,10 @@ #include "AnalogIO.h" AnalogIO::AnalogIO(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, OnixDeviceType::ANALOGIO, deviceIdx_, oni_ctx) + : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::ANALOGIO, deviceIdx_, oni_ctx) { StreamInfo analogInputStream = StreamInfo( - name + "-AnalogInput", + OnixDevice::createStreamName({ getHeadstageName(), name, "AnalogInput" }), "Analog Input data", "onix-analogio.data.input", 12, @@ -54,17 +54,17 @@ int AnalogIO::configureDevice() { if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; - deviceContext->writeRegister(deviceIdx, (uint32_t)AnalogIORegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); - - return deviceContext->getLastResult(); + return deviceContext->writeRegister(deviceIdx, (uint32_t)AnalogIORegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); } bool AnalogIO::updateSettings() { + int rc = 0; + for (int i = 0; i < numChannels; i += 1) { - deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)AnalogIORegisters::CH00_IN_RANGE + i, (oni_reg_val_t)channelVoltageRange[i]); - if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + rc = deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)AnalogIORegisters::CH00_IN_RANGE + i, (oni_reg_val_t)channelVoltageRange[i]); + if (rc != ONI_ESUCCESS) return false; } uint32_t ioReg = 0; @@ -74,8 +74,8 @@ bool AnalogIO::updateSettings() ioReg = (ioReg & ~((uint32_t)1 << i)) | ((uint32_t)(channelDirection[i]) << i); } - deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)AnalogIORegisters::CHDIR, ioReg); - if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + rc = deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)AnalogIORegisters::CHDIR, ioReg); + if (rc != ONI_ESUCCESS) return false; for (int i = 0; i < numChannels; i += 1) { diff --git a/Source/Devices/Bno055.cpp b/Source/Devices/Bno055.cpp index 49cc3e3..636ee56 100644 --- a/Source/Devices/Bno055.cpp +++ b/Source/Devices/Bno055.cpp @@ -22,13 +22,14 @@ #include "Bno055.h" -Bno055::Bno055(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx) - : OnixDevice(name, OnixDeviceType::BNO, deviceIdx_, ctx) +Bno055::Bno055(String name, String headstageName, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx) + : OnixDevice(name, headstageName, OnixDeviceType::BNO, deviceIdx_, ctx) { const float bitVolts = 1.0; + String port = PortController::getPortName(PortController::getPortFromIndex(deviceIdx)); StreamInfo eulerAngleStream = StreamInfo( - name + "-Euler", + OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Euler" }), "Bosch Bno055 9-axis inertial measurement unit (IMU) Euler angle", "onix-bno055.data.euler", 3, @@ -41,7 +42,7 @@ Bno055::Bno055(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptrisInitialized()) return -1; - deviceContext->writeRegister(deviceIdx, (uint32_t)Bno055Registers::ENABLE, isEnabled() ? 1 : 0); - - return deviceContext->getLastResult(); + return deviceContext->writeRegister(deviceIdx, (uint32_t)Bno055Registers::ENABLE, isEnabled() ? 1 : 0); } bool Bno055::updateSettings() @@ -154,10 +155,6 @@ void Bno055::processFrames() int dataOffset = 4; - const float eulerAngleScale = 1.0f / 16; // 1 degree = 16 LSB - const float quaternionScale = 1.0f / (1 << 14); // 1 = 2^14 LSB - const float accelerationScale = 1.0f / 100; // 1m / s^2 = 100 LSB - int channelOffset = 0; // Euler diff --git a/Source/Devices/Bno055.h b/Source/Devices/Bno055.h index 9f4c6cf..7948475 100644 --- a/Source/Devices/Bno055.h +++ b/Source/Devices/Bno055.h @@ -23,6 +23,7 @@ #pragma once #include "../OnixDevice.h" +#include "PortController.h" enum class Bno055Registers { @@ -37,7 +38,7 @@ class Bno055 : public OnixDevice public: /** Constructor */ - Bno055(String name, const oni_dev_idx_t, std::shared_ptr ctx); + Bno055(String name, String headstageName, const oni_dev_idx_t, std::shared_ptr ctx); int configureDevice() override; @@ -60,6 +61,10 @@ class Bno055 : public OnixDevice DataBuffer* bnoBuffer; + const float eulerAngleScale = 1.0f / 16; // 1 degree = 16 LSB + const float quaternionScale = 1.0f / (1 << 14); // 1 = 2^14 LSB + const float accelerationScale = 1.0f / 100; // 1m / s^2 = 100 LSB + static const int numFrames = 2; Array frameArray; diff --git a/Source/Devices/DS90UB9x.h b/Source/Devices/DS90UB9x.h index 93463b9..7e44719 100644 --- a/Source/Devices/DS90UB9x.h +++ b/Source/Devices/DS90UB9x.h @@ -27,8 +27,6 @@ static class DS90UB9x { public: - const int ID = 24; - // managed registers static const uint32_t ENABLE = 0x8000; static const uint32_t READSZ = 0x8001; @@ -89,6 +87,12 @@ static class DS90UB9x enum class DS90UB9xDeserializerI2CRegister : uint32_t { PortMode = 0x6D, + PortSel = 0x4C, + I2CConfig = 0x58, + GpioCtrl0 = 0x6E, + GpioCtrl1 = 0x6F, + + SerAlias = 0x5C, SlaveID1 = 0x5E, SlaveID2 = 0x5F, diff --git a/Source/Devices/DeviceList.h b/Source/Devices/DeviceList.h index 30ebb78..7c7dfcb 100644 --- a/Source/Devices/DeviceList.h +++ b/Source/Devices/DeviceList.h @@ -32,3 +32,4 @@ #include "AnalogIO.h" #include "DigitalIO.h" #include "PortController.h" +#include "PolledBno055.h" diff --git a/Source/Devices/DigitalIO.cpp b/Source/Devices/DigitalIO.cpp index 32dff0f..8685ba2 100644 --- a/Source/Devices/DigitalIO.cpp +++ b/Source/Devices/DigitalIO.cpp @@ -23,7 +23,7 @@ #include "DigitalIO.h" DigitalIO::DigitalIO(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, OnixDeviceType::DIGITALIO, deviceIdx_, oni_ctx) + : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::DIGITALIO, deviceIdx_, oni_ctx) { } @@ -31,9 +31,7 @@ int DigitalIO::configureDevice() { if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; - deviceContext->writeRegister(deviceIdx, (uint32_t)DigitalIORegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); - - return deviceContext->getLastResult(); + return deviceContext->writeRegister(deviceIdx, (uint32_t)DigitalIORegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); } bool DigitalIO::updateSettings() @@ -59,7 +57,7 @@ EventChannel::Settings DigitalIO::getEventChannelSettings() // NB: The stream must be assigned before adding the channel EventChannel::Settings settings{ EventChannel::Type::TTL, - "DigitalIO Event Channel", + OnixDevice::createStreamName({getHeadstageName(), getName(), "Events"}), "Digital inputs and breakout button states coming from a DigitalIO device", "onix-digitalio.events", nullptr, diff --git a/Source/Devices/HarpSyncInput.cpp b/Source/Devices/HarpSyncInput.cpp index 1af5d66..e7ec571 100644 --- a/Source/Devices/HarpSyncInput.cpp +++ b/Source/Devices/HarpSyncInput.cpp @@ -23,12 +23,12 @@ #include "HarpSyncInput.h" HarpSyncInput::HarpSyncInput(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, OnixDeviceType::HARPSYNCINPUT, deviceIdx_, oni_ctx) + : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::HARPSYNCINPUT, deviceIdx_, oni_ctx) { setEnabled(false); StreamInfo harpTimeStream = StreamInfo( - name + "-HarpTime", + OnixDevice::createStreamName({ getHeadstageName(), getName(), "HarpTime" }), "Harp clock time corresponding to the local acquisition ONIX clock count", "onix-harpsyncinput.data.harptime", 1, @@ -48,16 +48,12 @@ int HarpSyncInput::configureDevice() { if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; - deviceContext->writeRegister(deviceIdx, (uint32_t)HarpSyncInputRegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); - - return deviceContext->getLastResult(); + return deviceContext->writeRegister(deviceIdx, (uint32_t)HarpSyncInputRegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0)); } bool HarpSyncInput::updateSettings() { - deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)HarpSyncInputRegisters::SOURCE, (oni_reg_val_t)HarpSyncSource::Breakout); - - return deviceContext->getLastResult() == ONI_ESUCCESS; + return deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)HarpSyncInputRegisters::SOURCE, (oni_reg_val_t)HarpSyncSource::Breakout); } void HarpSyncInput::startAcquisition() diff --git a/Source/Devices/HeadStageEEPROM.cpp b/Source/Devices/HeadStageEEPROM.cpp index 7f2577c..7d28807 100644 --- a/Source/Devices/HeadStageEEPROM.cpp +++ b/Source/Devices/HeadStageEEPROM.cpp @@ -29,18 +29,21 @@ HeadStageEEPROM::HeadStageEEPROM(const oni_dev_idx_t dev_id, std::shared_ptr(DS90UB9x::DES_ADDR, dev_id, ctx); uint32_t alias = HeadStageEEPROM::EEPROM_ADDRESS << 1; - deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::SlaveID7, alias); + int rc = deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::SlaveID7, alias); + if (rc != ONI_ESUCCESS) return; deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::SlaveAlias7, alias); + if (rc != ONI_ESUCCESS) return; } uint32_t HeadStageEEPROM::GetHeadStageID() { uint32_t data = 0; + int rc = 0; for (unsigned int i = 0; i < sizeof(uint32_t); i++) { oni_reg_val_t val; - ReadByte(DEVID_START_ADDR + i, &val, true); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return data; + rc = ReadByte(DEVID_START_ADDR + i, &val, true); + if (rc != ONI_ESUCCESS) return data; data += (val & 0xFF) << (8 * i); } return data; diff --git a/Source/Devices/Heartbeat.cpp b/Source/Devices/Heartbeat.cpp index 6ddd99f..664ab26 100644 --- a/Source/Devices/Heartbeat.cpp +++ b/Source/Devices/Heartbeat.cpp @@ -23,7 +23,7 @@ #include "Heartbeat.h" Heartbeat::Heartbeat(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, OnixDeviceType::HEARTBEAT, deviceIdx_, oni_ctx) + : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::HEARTBEAT, deviceIdx_, oni_ctx) { } @@ -33,17 +33,16 @@ int Heartbeat::configureDevice() if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; - deviceContext->writeRegister(deviceIdx, (uint32_t)HeartbeatRegisters::ENABLE, 1); - - return deviceContext->getLastResult(); + return deviceContext->writeRegister(deviceIdx, (uint32_t)HeartbeatRegisters::ENABLE, 1); } bool Heartbeat::updateSettings() { - oni_reg_val_t clkHz = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)HeartbeatRegisters::CLK_HZ); - deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)HeartbeatRegisters::CLK_DIV, clkHz / beatsPerSecond); + oni_reg_val_t clkHz; + int rc = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)HeartbeatRegisters::CLK_HZ, &clkHz); + rc = deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)HeartbeatRegisters::CLK_DIV, clkHz / beatsPerSecond); - if (deviceContext->getLastResult() == ONI_EREADONLY) return true; // NB: Ignore read-only errors + if (rc == ONI_EREADONLY) return true; // NB: Ignore read-only errors - return deviceContext->getLastResult() == ONI_ESUCCESS; + return rc == ONI_ESUCCESS; } diff --git a/Source/Devices/Heartbeat.h b/Source/Devices/Heartbeat.h index e23bcfc..7819de3 100644 --- a/Source/Devices/Heartbeat.h +++ b/Source/Devices/Heartbeat.h @@ -32,7 +32,7 @@ enum class HeartbeatRegisters : uint32_t }; /* - Configures and streams data from a MemoryMonitor device on a Breakout Board + Configures and streams data from a Heartbeat device on a Breakout Board */ class Heartbeat : public OnixDevice { diff --git a/Source/Devices/MemoryMonitor.cpp b/Source/Devices/MemoryMonitor.cpp index b631305..6fc5da7 100644 --- a/Source/Devices/MemoryMonitor.cpp +++ b/Source/Devices/MemoryMonitor.cpp @@ -56,10 +56,10 @@ void MemoryMonitorUsage::stopAcquisition() } MemoryMonitor::MemoryMonitor(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, OnixDeviceType::MEMORYMONITOR, deviceIdx_, oni_ctx) + : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::MEMORYMONITOR, deviceIdx_, oni_ctx) { StreamInfo percentUsedStream = StreamInfo( - name + "-PercentUsed", + OnixDevice::createStreamName({ getHeadstageName(), getName(), "PercentUsed" }), "Percent of available memory that is currently used", "onix - memorymonitor.data.percentused", 1, @@ -68,7 +68,7 @@ MemoryMonitor::MemoryMonitor(String name, const oni_dev_idx_t deviceIdx_, std::s ContinuousChannel::Type::AUX, 1.0f, "%", - {""}); + { "" }); streamInfos.add(percentUsedStream); for (int i = 0; i < numFrames; i++) @@ -81,21 +81,23 @@ int MemoryMonitor::configureDevice() if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; - deviceContext->writeRegister(deviceIdx, (uint32_t)MemoryMonitorRegisters::ENABLE, 1); - if (deviceContext->getLastResult() != ONI_ESUCCESS) return deviceContext->getLastResult(); + int rc = deviceContext->writeRegister(deviceIdx, (uint32_t)MemoryMonitorRegisters::ENABLE, 1); + if (rc != ONI_ESUCCESS) return rc; - totalMemory = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::TOTAL_MEM); + rc = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::TOTAL_MEM, &totalMemory); - return deviceContext->getLastResult(); + return rc; } bool MemoryMonitor::updateSettings() { - oni_reg_val_t clkHz = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::CLK_HZ); + oni_reg_val_t clkHz; + int rc = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::CLK_HZ, &clkHz); + if (rc != ONI_ESUCCESS) return rc; - deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::CLK_DIV, clkHz / samplesPerSecond); + rc = deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::CLK_DIV, clkHz / samplesPerSecond); - return deviceContext->getLastResult() == ONI_ESUCCESS; + return rc == ONI_ESUCCESS; } void MemoryMonitor::startAcquisition() diff --git a/Source/Devices/Neuropixels2e.cpp b/Source/Devices/Neuropixels2e.cpp index 8ccb09d..e8b10d7 100644 --- a/Source/Devices/Neuropixels2e.cpp +++ b/Source/Devices/Neuropixels2e.cpp @@ -21,20 +21,32 @@ */ #include "Neuropixels2e.h" -#include "DS90UB9x.h" -Neuropixels2e::Neuropixels2e(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx_) - : OnixDevice(name, OnixDeviceType::NEUROPIXELS_2, deviceIdx_, ctx_) -{} +Neuropixels2e::Neuropixels2e(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx_) : + OnixDevice(name, NEUROPIXELSV2E_HEADSTAGE_NAME, OnixDeviceType::NEUROPIXELSV2E, deviceIdx_, ctx_), + I2CRegisterContext(ProbeI2CAddress, deviceIdx_, ctx_), + INeuropixel(NeuropixelsV2eValues::numberOfSettings, NeuropixelsV2eValues::numberOfShanks) +{ + probeSN[0] = 0; + probeSN[1] = 0; + + for (int i = 0; i < NeuropixelsV2eValues::numberOfSettings; i++) + { + defineMetadata(settings[i].get(), NeuropixelsV2eValues::numberOfShanks); + } + + for (int i = 0; i < numFrames; i++) + eventCodes[i] = 0; +} void Neuropixels2e::createDataStream(int n) { StreamInfo apStream = StreamInfo( - getName() + "-" + String(n), + OnixDevice::createStreamName({ PortController::getPortName(PortController::getPortFromIndex(deviceIdx)), getHeadstageName(), "Probe" + String(n) }), "Neuropixels 2.0 data stream", "onix-neuropixels2.data", - 384, - 30000.0f, + numberOfChannels, + sampleRate, "CH", ContinuousChannel::Type::ELECTRODE, 0.195f, @@ -48,24 +60,307 @@ int Neuropixels2e::getNumProbes() const return m_numProbes; } +std::vector Neuropixels2e::selectElectrodeConfiguration(String config) +{ + std::vector selection; + + if (config.equalsIgnoreCase("Bank A") || config.equalsIgnoreCase("Shank 1 Bank A")) + { + for (int i = 0; i < 384; i++) + selection.emplace_back(i); + } + else if (config.equalsIgnoreCase("Bank B") || config.equalsIgnoreCase("Shank 1 Bank B")) + { + for (int i = 384; i < 768; i++) + selection.emplace_back(i); + } + else if (config.equalsIgnoreCase("Bank C") || config.equalsIgnoreCase("Shank 1 Bank C")) + { + for (int i = 768; i < 1152; i++) + selection.emplace_back(i); + } + else if (config.equalsIgnoreCase("Bank D")) + { + for (int i = 896; i < 1280; i++) + selection.emplace_back(i); + } + else if (config.equalsIgnoreCase("Shank 2 Bank A")) + { + int startElectrode = 1280; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 2 Bank B")) + { + int startElectrode = 1280 + 384; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 2 Bank C")) + { + int startElectrode = 1280 + 384 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 3 Bank A")) + { + int startElectrode = 1280 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 3 Bank B")) + { + int startElectrode = 1280 * 2 + 384; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 3 Bank C")) + { + int startElectrode = 1280 * 2 + 384 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 4 Bank A")) + { + int startElectrode = 1280 * 3; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 4 Bank B")) + { + int startElectrode = 1280 * 3 + 384; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("Shank 4 Bank C")) + { + int startElectrode = 1280 * 3 + 384 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.emplace_back(i); + } + } + else if (config.equalsIgnoreCase("All Shanks 1-96")) + { + int startElectrode = 0; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 97-192")) + { + int startElectrode = 96; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 193-288")) + { + int startElectrode = 192; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 289-384")) + { + int startElectrode = 288; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 385-480")) + { + int startElectrode = 384; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 481-576")) + { + int startElectrode = 480; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 577-672")) + { + int startElectrode = 576; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 673-768")) + { + int startElectrode = 672; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 769-864")) + { + int startElectrode = 768; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 865-960")) + { + int startElectrode = 864; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 961-1056")) + { + int startElectrode = 960; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 1057-1152")) + { + int startElectrode = 1056; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + else if (config.equalsIgnoreCase("All Shanks 1153-1248")) + { + int startElectrode = 1152; + + for (int shank = 0; shank < 4; shank++) + { + for (int i = startElectrode + 1280 * shank; i < startElectrode + 96 + 1280 * shank; i++) + { + selection.emplace_back(i); + } + } + } + + return selection; +} + +uint64_t Neuropixels2e::getProbeSerialNumber(int index) +{ + switch (index) + { + case 0: + return probeSN[0]; + case 1: + return probeSN[1]; + default: + return 0; + } +} + int Neuropixels2e::configureDevice() { if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + int rc = deviceContext->writeRegister(deviceIdx, DS90UB9x::ENABLE, isEnabled() ? 1 : 0); + if (rc != ONI_ESUCCESS) return rc; + configureSerDes(); setProbeSupply(true); - probeSNA = getProbeSN(ProbeASelected); - probeSNB = getProbeSN(ProbeBSelected); + rc = serializer->set933I2cRate(400e3); + if (rc != ONI_ESUCCESS) return rc; + probeSN[0] = getProbeSN(ProbeASelected); + probeSN[1] = getProbeSN(ProbeBSelected); setProbeSupply(false); - LOGD("Probe A SN: ", probeSNA); - LOGD("Probe B SN: ", probeSNB); + LOGD("Probe A SN: ", probeSN[0]); + LOGD("Probe B SN: ", probeSN[1]); - if (probeSNA == -1 && probeSNB == -1) + if (probeSN[0] == 0 && probeSN[1] == 0) { m_numProbes = 0; return -2; } - else if (probeSNA != -1 && probeSNB != -1) + else if (probeSN[0] != 0 && probeSN[1] != 0) { m_numProbes = 2; } @@ -74,16 +369,93 @@ int Neuropixels2e::configureDevice() m_numProbes = 1; } + streamInfos.clear(); + for (int i = 0; i < m_numProbes; i++) + { createDataStream(i); - - //TODO: Gain correction loading would be here + } return 0; } bool Neuropixels2e::updateSettings() { + for (int i = 0; i < 2; i++) + { + if (probeSN[i] != 0) + { + if (gainCorrectionFilePath[i] == "None" || gainCorrectionFilePath[i] == "") + { + LOGE("Missing gain correction file for probe " + String(probeSN[i])); + return false; + } + + File gainCorrectionFile = File(gainCorrectionFilePath[i]); + + if (!gainCorrectionFile.existsAsFile()) + { + LOGE("The gain correction file \"", gainCorrectionFilePath[i], "\" for probe ", String(probeSN[i]), " does not exist."); + return false; + } + + StringArray fileLines; + gainCorrectionFile.readLines(fileLines); + + fileLines.removeEmptyStrings(true); + + auto gainSN = std::stoull(fileLines[0].toStdString()); + + if (gainSN != probeSN[i]) + { + LOGE("Invalid serial number found in the calibration file. Should match the probe serial number (", String(probeSN[i]), ")"); + return false; + } + + if (fileLines.size() != numberOfChannels + 1) + { + LOGE("Found the wrong number of lines in the calibration file. Expected ", numberOfChannels + 1, ", found ", fileLines.size()); + return false; + } + + StringRef breakCharacters = ","; + StringRef noQuote = ""; + StringArray firstLine = StringArray::fromTokens(fileLines[1], breakCharacters, noQuote); + auto correctionValue = std::stod(firstLine[1].toStdString()); + + for (int j = 0; j < numberOfChannels; j++) + { + StringArray calibrationValues = StringArray::fromTokens(fileLines[j + 1], breakCharacters, noQuote); + + if (std::stoi(calibrationValues[0].toStdString()) != j || std::stod(calibrationValues[1].toStdString()) != correctionValue) + { + LOGE("Calibration file is incorrectly formatted for probe ", String(probeSN[i])); + return false; + } + } + + gainCorrection[i] = correctionValue * -1.0f; + } + else + gainCorrection[i] = 0; + } + + setProbeSupply(true); + resetProbes(); + + for (int i = 0; i < 2; i++) + { + if (probeSN[i] != 0) + { + selectProbe(i == 0 ? ProbeASelected : ProbeBSelected); + writeConfiguration(settings[i].get()); + configureProbeStreaming(); + } + } + + selectProbe(NoProbeSelected); + //IMPORTANT! BNO polling thread must be started after this + return true; } @@ -116,24 +488,30 @@ void Neuropixels2e::configureSerDes() deviceContext->writeRegister(deviceIdx, DS90UB9x::ENABLE, 1); // configure deserializer trigger mode - deviceContext->writeRegister(deviceIdx,DS90UB9x::TRIGGEROFF, 0); - deviceContext->writeRegister(deviceIdx,DS90UB9x::TRIGGER, (uint32_t)(DS90UB9x::DS90UB9xTriggerMode::Continuous)); - deviceContext->writeRegister(deviceIdx,DS90UB9x::SYNCBITS, 0); - deviceContext->writeRegister(deviceIdx,DS90UB9x::DATAGATE, (uint32_t)(DS90UB9x::DS90UB9xDataGate::Disabled)); - deviceContext->writeRegister(deviceIdx,DS90UB9x::MARK, (uint32_t)(DS90UB9x::DS90UB9xMarkMode::Disabled)); + deviceContext->writeRegister(deviceIdx, DS90UB9x::TRIGGEROFF, 0); + deviceContext->writeRegister(deviceIdx, DS90UB9x::TRIGGER, (uint32_t)(DS90UB9x::DS90UB9xTriggerMode::Continuous)); + deviceContext->writeRegister(deviceIdx, DS90UB9x::SYNCBITS, 0); + deviceContext->writeRegister(deviceIdx, DS90UB9x::DATAGATE, (uint32_t)(DS90UB9x::DS90UB9xDataGate::Disabled)); + deviceContext->writeRegister(deviceIdx, DS90UB9x::MARK, (uint32_t)(DS90UB9x::DS90UB9xMarkMode::Disabled)); // configure two 4-bit magic word-triggered streams, one for each probe - deviceContext->writeRegister(deviceIdx,DS90UB9x::READSZ, 0x00100009); // 16 frames/superframe, 8x 12-bit words + magic bits - deviceContext->writeRegister(deviceIdx,DS90UB9x::MAGIC_MASK, 0xC000003F); // Enable inverse, wait for non-inverse, 14-bit magic word - deviceContext->writeRegister(deviceIdx,DS90UB9x::MAGIC, 0b0000000000101110); // Super-frame sync word - deviceContext->writeRegister(deviceIdx,DS90UB9x::MAGIC_WAIT, 0); - deviceContext->writeRegister(deviceIdx,DS90UB9x::DATAMODE, 0b00100000000000000000001010110101); - deviceContext->writeRegister(deviceIdx,DS90UB9x::DATALINES0, 0xFFFFF8A6); // NP A - deviceContext->writeRegister(deviceIdx,DS90UB9x::DATALINES1, 0xFFFFF97B); // NP B + deviceContext->writeRegister(deviceIdx, DS90UB9x::READSZ, 0x00100009); // 16 frames/superframe, 8x 12-bit words + magic bits + deviceContext->writeRegister(deviceIdx, DS90UB9x::MAGIC_MASK, 0xC000003F); // Enable inverse, wait for non-inverse, 14-bit magic word + deviceContext->writeRegister(deviceIdx, DS90UB9x::MAGIC, 0b0000000000101110); // Super-frame sync word + deviceContext->writeRegister(deviceIdx, DS90UB9x::MAGIC_WAIT, 0); + deviceContext->writeRegister(deviceIdx, DS90UB9x::DATAMODE, 0b00100000000000000000001010110101); + deviceContext->writeRegister(deviceIdx, DS90UB9x::DATALINES0, 0xFFFFF8A6); // NP A + deviceContext->writeRegister(deviceIdx, DS90UB9x::DATALINES1, 0xFFFFF97B); // NP B deserializer = std::make_unique(DS90UB9x::DES_ADDR, deviceIdx, deviceContext); + deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::PortSel, 0x01); int coaxMode = 0x4 + (uint32_t)(DS90UB9x::DS90UB9xMode::Raw12BitHighFrequency); // 0x4 maintains coax mode deserializer->WriteByte((uint32_t)(DS90UB9x::DS90UB9xDeserializerI2CRegister::PortMode), coaxMode); + deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::I2CConfig, 0b01011000); + deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::SerAlias, DS90UB9x::SER_ADDR << 1); + + deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::GpioCtrl0, 0x10); + deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::GpioCtrl1, 0x32); int alias = ProbeI2CAddress << 1; deserializer->WriteByte((uint32_t)(DS90UB9x::DS90UB9xDeserializerI2CRegister::SlaveID1), alias); @@ -145,7 +523,7 @@ void Neuropixels2e::configureSerDes() serializer = std::make_unique(DS90UB9x::SER_ADDR, deviceIdx, deviceContext); flex = std::make_unique(FlexAddress, deviceIdx, deviceContext); - probeControl = std::make_unique(ProbeI2CAddress, deviceIdx, deviceContext); //TODO: this probably be a derived class that includes all the connfiguration methods + probeControl = std::make_unique(ProbeI2CAddress, deviceIdx, deviceContext); } void Neuropixels2e::setProbeSupply(bool en) @@ -154,7 +532,7 @@ void Neuropixels2e::setProbeSupply(bool en) selectProbe(NoProbeSelected); serializer->WriteByte((uint32_t)(DS90UB9x::DS90UB9xSerializerI2CRegister::GPIO10), gpo10Config); - Thread::sleep(10); + Thread::sleep(20); } void Neuropixels2e::selectProbe(uint8_t probeSelect) @@ -176,15 +554,15 @@ uint64_t Neuropixels2e::getProbeSN(uint8_t probeSelect) { selectProbe(probeSelect); uint64_t probeSN = 0; - int errorCode = 0; + int errorCode = 0, rc; for (unsigned int i = 0; i < sizeof(probeSN); i++) { oni_reg_addr_t reg_addr = OFFSET_PROBE_SN + i; oni_reg_val_t val; - flex->ReadByte(reg_addr, &val); + rc = flex->ReadByte(reg_addr, &val); - if (flex->getLastResult() != ONI_ESUCCESS) return -1; + if (rc != ONI_ESUCCESS) return 0; if (val <= 0xFF) { @@ -196,23 +574,11 @@ uint64_t Neuropixels2e::getProbeSN(uint8_t probeSelect) void Neuropixels2e::startAcquisition() { - setProbeSupply(true); - resetProbes(); - if (probeSNA != -1) - { - selectProbe(ProbeASelected); - //TODO: Here would be proper probe configuration theough a proper probe control class - configureProbeStreaming(); - } - if (probeSNB != -1) - { - selectProbe(ProbeBSelected); - //TODO: Here would be proper probe configuration theough a proper probe control class - configureProbeStreaming(); - } + shouldAddToBuffer = false; + sampleNumber = 0; + frameCount = 0; - // disconnect i2c bus from both probes to prevent digital interference during acquisition - selectProbe(NoProbeSelected); //IMPORTANT! BNO polling thread must be started after this + singleProbe = m_numProbes == 1; } void Neuropixels2e::stopAcquisition() @@ -238,7 +604,7 @@ void Neuropixels2e::addSourceBuffers(OwnedArray& sourceBuffers) for (const auto& streamInfo : streamInfos) { sourceBuffers.add(new DataBuffer(streamInfo.getNumChannels(), (int)streamInfo.getSampleRate() * bufferSizeInSeconds)); - apBuffer[bufferIdx++] = sourceBuffers.getLast(); + amplifierBuffer[bufferIdx++] = sourceBuffers.getLast(); } } @@ -249,21 +615,565 @@ void Neuropixels2e::processFrames() const GenericScopedLock frameLock(frameArray.getLock()); oni_frame_t* frame = frameArray.removeAndReturn(0); - uint16_t* dataPtr; - dataPtr = (uint16_t*)frame->data; + uint16_t* dataPtr = (uint16_t*)frame->data; + uint16_t probeIndex = *(dataPtr + 4); - uint16_t* amplifierData = dataPtr + 6; - uint64_t timestamp = frame->time; + uint16_t* amplifierData = dataPtr + 9; + + sampleNumbers[frameCount] = sampleNumber; + timestamps[frameCount] = frame->time; - if (m_numProbes == 1) + for (int i = 0; i < FramesPerSuperFrame; i++) { - //Fill always in buffer[0], we can ignore the probeIndex + auto adcDataOffset = i * FrameWords; + + for (int j = 0; j < AdcsPerProbe; j++) + { + const int channelIndex = rawToChannel[j][i]; + + samples[channelIndex * numFrames + frameCount] = + (float)(*(amplifierData + adcIndices[j] + adcDataOffset)) * gainCorrection[probeIndex]; + } } - else + + frameCount++; + sampleNumber++; + + if (frameCount >= numFrames) { - //Fill buffer[probeindex] + frameCount = 0; + shouldAddToBuffer = true; + } + + if (shouldAddToBuffer) + { + shouldAddToBuffer = false; + + amplifierBuffer[probeIndex]->addToBuffer(samples.data(), sampleNumbers, timestamps, eventCodes, numFrames); } oni_destroy_frame(frame); } } + +void Neuropixels2e::writeConfiguration(ProbeSettings* settings) +{ + auto baseBits = makeBaseBits(getReference(settings->referenceIndex)); + writeShiftRegister(SR_CHAIN5, baseBits[0]); + writeShiftRegister(SR_CHAIN6, baseBits[1]); + + auto shankBits = makeShankBits(getReference(settings->referenceIndex), settings->electrodeMetadata); + writeShiftRegister(SR_CHAIN1, shankBits[0]); + writeShiftRegister(SR_CHAIN2, shankBits[1]); + writeShiftRegister(SR_CHAIN3, shankBits[2]); + writeShiftRegister(SR_CHAIN4, shankBits[3]); +} + +template +void Neuropixels2e::writeShiftRegister(uint32_t srAddress, std::bitset bits) +{ + std::vector bytes = toBitReversedBytes(bits); + + for (int i = 2; i > 0; i -= 1) + { + WriteByte(SOFT_RESET, 0xFF); + WriteByte(SOFT_RESET, 0x00); + + WriteByte(SR_LENGTH1, (uint32_t)(bytes.size() % 0x100)); + WriteByte(SR_LENGTH2, (uint32_t)(bytes.size() / 0x100)); + + for (auto b : bytes) + { + WriteByte(srAddress, b); + } + } + + uint32_t status; + ReadByte(STATUS, &status); + if (status != (uint32_t)NeuropixelsV2Status::SR_OK) + { + LOGE("Warning: Shift register ", srAddress, " status check failed. ", getShankName(srAddress), " may be damaged."); + } +} + +String Neuropixels2e::getShankName(uint32_t shiftRegisterAddress) +{ + switch (shiftRegisterAddress) + { + case SR_CHAIN1: + return "Shank 1"; + case SR_CHAIN2: + return "Shank 2"; + case SR_CHAIN3: + return "Shank 3"; + case SR_CHAIN4: + return "Shank 4"; + default: + return ""; + } +} + +void Neuropixels2e::setGainCorrectionFile(int index, String filename) +{ + if (index < gainCorrectionFilePath.size()) + { + gainCorrectionFilePath[index] = filename; + } +} + +String Neuropixels2e::getGainCorrectionFile(int index) +{ + if (index < gainCorrectionFilePath.size()) + { + return gainCorrectionFilePath[index]; + } + else + return ""; +} + +NeuropixelsV2Reference Neuropixels2e::getReference(int index) +{ + switch (index) + { + case 0: + return NeuropixelsV2Reference::External; + case 1: + return NeuropixelsV2Reference::Tip1; + case 2: + return NeuropixelsV2Reference::Tip2; + case 3: + return NeuropixelsV2Reference::Tip3; + case 4: + return NeuropixelsV2Reference::Tip4; + default: + break; + } + + return NeuropixelsV2Reference::External; +} + +Neuropixels2e::BaseBitsArray Neuropixels2e::makeBaseBits(NeuropixelsV2Reference reference) +{ + BaseBitsArray baseBits; + + int referenceBit; + + if (reference == NeuropixelsV2Reference::External) + referenceBit = 1; + else + referenceBit = 2; + + for (int i = 0; i < numberOfChannels; i++) + { + auto configIndex = i % 2; + auto bitOffset = (382 - i + configIndex) / 2 * baseBitsPerChannel; + baseBits[configIndex][bitOffset + 0] = false; + baseBits[configIndex][bitOffset + referenceBit] = true; + } + + return baseBits; +} + +Neuropixels2e::ShankBitsArray Neuropixels2e::makeShankBits(NeuropixelsV2Reference reference, std::array channelMap) +{ + ShankBitsArray shankBits; + + if (reference != NeuropixelsV2Reference::External) + { + shankBits[(int)reference - 1][643] = true; + shankBits[(int)reference - 1][644] = true; + } + else + { + shankBits[0][2] = true; + shankBits[0][1285] = true; + shankBits[1][2] = true; + shankBits[1][1285] = true; + shankBits[2][2] = true; + shankBits[2][1285] = true; + shankBits[3][2] = true; + shankBits[3][1285] = true; + } + + const int pixelOffset = (NeuropixelsV2eValues::electrodesPerShank - 1) / 2; + const int referencePixelOffset = 3; + + int count = 0; + + for (const auto& e : channelMap) + { + if (e.status == ElectrodeStatus::CONNECTED) + { + auto baseIndex = e.shank_local_index % 2; + auto pixelIndex = e.shank_local_index / 2; + pixelIndex = baseIndex == 0 + ? pixelIndex + pixelOffset + 2 * referencePixelOffset + : pixelOffset - pixelIndex + referencePixelOffset; + + shankBits[e.shank][pixelIndex] = true; + + count++; + } + } + + if (count != numberOfChannels) + { + LOGE("Invalid number of channels connected for Neuropixels 2.0e, configuration might be invalid."); + } + + return shankBits; +} + +void Neuropixels2e::setSettings(ProbeSettings* settings_, int index) +{ + if (index >= settings.size()) + { + LOGE("Invalid index given when trying to update settings."); + return; + } + + settings[index]->updateProbeSettings(settings_); +} + +void Neuropixels2e::defineMetadata(ProbeSettings* settings, int shankCount) +{ + settings->probeType = ProbeType::NPX_V2E; + settings->probeMetadata.name = "Neuropixels 2.0e" + String(shankCount == 1 ? " - Single Shank" : " - Quad Shank"); + + Path path; + path.startNewSubPath(27, 31); + path.lineTo(27, 514); + path.lineTo(27 + 5, 522); + path.lineTo(27 + 10, 514); + path.lineTo(27 + 10, 31); + path.closeSubPath(); + + settings->probeMetadata.shank_count = shankCount; + settings->probeMetadata.electrodes_per_shank = NeuropixelsV2eValues::electrodesPerShank; + settings->probeMetadata.rows_per_shank = NeuropixelsV2eValues::electrodesPerShank / 2; + settings->probeMetadata.columns_per_shank = 2; + settings->probeMetadata.shankOutline = path; + settings->probeMetadata.num_adcs = 24; + settings->probeMetadata.adc_bits = 12; + + settings->availableBanks = { + Bank::A, + Bank::B, + Bank::C, + Bank::D, + Bank::NONE //disconnected + }; + + for (int i = 0; i < settings->probeMetadata.electrodes_per_shank * settings->probeMetadata.shank_count; i++) + { + ElectrodeMetadata metadata; + + metadata.global_index = i; + + metadata.shank = i / settings->probeMetadata.electrodes_per_shank; + metadata.shank_local_index = i % settings->probeMetadata.electrodes_per_shank; + + metadata.xpos = i % 2 * 32.0f + 8.0f; + metadata.ypos = (metadata.shank_local_index - (metadata.shank_local_index % 2)) * 7.5f; + metadata.site_width = 12; + + metadata.column_index = i % 2; + metadata.row_index = metadata.shank_local_index / 2; + + metadata.isSelected = false; + + metadata.colour = Colours::lightgrey; + + if (shankCount == 1) + { + if (i < 384) + { + metadata.bank = Bank::A; + + int bank_index = metadata.shank_local_index % 384; + int block = bank_index / 32; + int row = (bank_index % 32) / 2; + + if (i % 2 == 0) + { + metadata.channel = row * 2 + block * 32; + } + else + { + metadata.channel = row * 2 + block * 32 + 1; + } + + metadata.status = ElectrodeStatus::CONNECTED; + } + else if (i >= 384 && i < 768) + { + metadata.bank = Bank::B; + + int bank_index = metadata.shank_local_index % 384; + int block = bank_index / 32; + int row = (bank_index % 32) / 2; + + if (i % 2 == 0) + { + metadata.channel = ((row * 7) % 16) * 2 + block * 32; + } + else + { + metadata.channel = ((row * 7 + 4) % 16) * 2 + block * 32 + 1; + } + + metadata.status = ElectrodeStatus::DISCONNECTED; + } + else if (i >= 768 && i < 1152) + { + metadata.bank = Bank::C; + + int bank_index = metadata.shank_local_index % 384; + int block = bank_index / 32; + int row = (bank_index % 32) / 2; + + if (i % 2 == 0) + { + metadata.channel = ((row * 5) % 16) * 2 + block * 32; + } + else + { + metadata.channel = ((row * 5 + 8) % 16) * 2 + block * 32 + 1; + } + + metadata.status = ElectrodeStatus::DISCONNECTED; + } + else + { + metadata.bank = Bank::D; + + int bank_index = metadata.shank_local_index % 384; + int block = bank_index / 32; + int row = (bank_index % 32) / 2; + + if (i % 2 == 0) + { + metadata.channel = ((row * 3) % 16) * 2 + block * 32; + } + else + { + metadata.channel = ((row * 3 + 12) % 16) * 2 + block * 32 + 1; + } + + metadata.status = ElectrodeStatus::DISCONNECTED; + } + } + else if (shankCount == 4) + { + if (i < 384) + { + metadata.status = ElectrodeStatus::CONNECTED; + } + else + { + metadata.status = ElectrodeStatus::DISCONNECTED; + } + + if (metadata.shank_local_index < 384) + metadata.bank = Bank::A; + else if (metadata.shank_local_index >= 384 && metadata.shank_local_index < 768) + metadata.bank = Bank::B; + else if (metadata.shank_local_index >= 768 && metadata.shank_local_index < 1152) + metadata.bank = Bank::C; + else + metadata.bank = Bank::D; + + int block = metadata.shank_local_index % 384 / 48 + 1; + int block_index = metadata.shank_local_index % 48; + + if (metadata.shank == 0) + { + switch (block) + { + case 1: + metadata.channel = block_index + 48 * 0; // 1-48 (Bank 0-3) + break; + case 2: + metadata.channel = block_index + 48 * 2; // 96-144 (Bank 0-3) + break; + case 3: + metadata.channel = block_index + 48 * 4; // 192-223 (Bank 0-3) + break; + case 4: + metadata.channel = block_index + 48 * 6; // 288-336 (Bank 0-2) + break; + case 5: + metadata.channel = block_index + 48 * 5; // 240-288 (Bank 0-2) + break; + case 6: + metadata.channel = block_index + 48 * 7; // 336-384 (Bank 0-2) + break; + case 7: + metadata.channel = block_index + 48 * 1; // 48-96 (Bank 0-2) + break; + case 8: + metadata.channel = block_index + 48 * 3; // 144-192 (Bank 0-2) + break; + default: + metadata.channel = -1; + } + } + else if (metadata.shank == 1) + { + switch (block) + { + case 1: + metadata.channel = block_index + 48 * 1; + break; + case 2: + metadata.channel = block_index + 48 * 3; + break; + case 3: + metadata.channel = block_index + 48 * 5; + break; + case 4: + metadata.channel = block_index + 48 * 7; + break; + case 5: + metadata.channel = block_index + 48 * 4; + break; + case 6: + metadata.channel = block_index + 48 * 6; + break; + case 7: + metadata.channel = block_index + 48 * 0; + break; + case 8: + metadata.channel = block_index + 48 * 2; + break; + default: + metadata.channel = -1; + } + } + else if (metadata.shank == 2) + { + switch (block) + { + case 1: + metadata.channel = block_index + 48 * 4; + break; + case 2: + metadata.channel = block_index + 48 * 6; + break; + case 3: + metadata.channel = block_index + 48 * 0; + break; + case 4: + metadata.channel = block_index + 48 * 2; + break; + case 5: + metadata.channel = block_index + 48 * 1; + break; + case 6: + metadata.channel = block_index + 48 * 3; + break; + case 7: + metadata.channel = block_index + 48 * 5; + break; + case 8: + metadata.channel = block_index + 48 * 7; + break; + default: + metadata.channel = -1; + } + } + else if (metadata.shank == 3) + { + switch (block) + { + case 1: + metadata.channel = block_index + 48 * 5; + break; + case 2: + metadata.channel = block_index + 48 * 7; + break; + case 3: + metadata.channel = block_index + 48 * 1; + break; + case 4: + metadata.channel = block_index + 48 * 3; + break; + case 5: + metadata.channel = block_index + 48 * 0; + break; + case 6: + metadata.channel = block_index + 48 * 2; + break; + case 7: + metadata.channel = block_index + 48 * 4; + break; + case 8: + metadata.channel = block_index + 48 * 6; + break; + default: + metadata.channel = -1; + } + } + + metadata.type = ElectrodeType::ELECTRODE; + } + + settings->electrodeMetadata[i] = metadata; + } + + settings->apGainIndex = -1; + settings->lfpGainIndex = -1; + settings->referenceIndex = 0; + settings->apFilterState = false; + + auto selection = selectElectrodeConfiguration("Bank A"); + settings->selectElectrodes(selection); + + settings->availableReferences.add("Ext"); + settings->availableReferences.add("Tip1"); + + if (shankCount == 4) + { + settings->availableReferences.add("Tip2"); + settings->availableReferences.add("Tip3"); + settings->availableReferences.add("Tip4"); + } + + if (shankCount == 1) + { + settings->availableElectrodeConfigurations.add("Bank A"); + settings->availableElectrodeConfigurations.add("Bank B"); + settings->availableElectrodeConfigurations.add("Bank C"); + settings->availableElectrodeConfigurations.add("Bank D"); + } + else if (shankCount == 4) + { + settings->availableElectrodeConfigurations.add("Shank 1 Bank A"); + settings->availableElectrodeConfigurations.add("Shank 1 Bank B"); + settings->availableElectrodeConfigurations.add("Shank 1 Bank C"); + settings->availableElectrodeConfigurations.add("Shank 2 Bank A"); + settings->availableElectrodeConfigurations.add("Shank 2 Bank B"); + settings->availableElectrodeConfigurations.add("Shank 2 Bank C"); + settings->availableElectrodeConfigurations.add("Shank 3 Bank A"); + settings->availableElectrodeConfigurations.add("Shank 3 Bank B"); + settings->availableElectrodeConfigurations.add("Shank 3 Bank C"); + settings->availableElectrodeConfigurations.add("Shank 4 Bank A"); + settings->availableElectrodeConfigurations.add("Shank 4 Bank B"); + settings->availableElectrodeConfigurations.add("Shank 4 Bank C"); + settings->availableElectrodeConfigurations.add("All Shanks 1-96"); + settings->availableElectrodeConfigurations.add("All Shanks 97-192"); + settings->availableElectrodeConfigurations.add("All Shanks 193-288"); + settings->availableElectrodeConfigurations.add("All Shanks 289-384"); + settings->availableElectrodeConfigurations.add("All Shanks 385-480"); + settings->availableElectrodeConfigurations.add("All Shanks 481-576"); + settings->availableElectrodeConfigurations.add("All Shanks 577-672"); + settings->availableElectrodeConfigurations.add("All Shanks 673-768"); + settings->availableElectrodeConfigurations.add("All Shanks 769-864"); + settings->availableElectrodeConfigurations.add("All Shanks 865-960"); + settings->availableElectrodeConfigurations.add("All Shanks 961-1056"); + settings->availableElectrodeConfigurations.add("All Shanks 1057-1152"); + settings->availableElectrodeConfigurations.add("All Shanks 1153-1248"); + } + + settings->electrodeConfigurationIndex = 0; +} diff --git a/Source/Devices/Neuropixels2e.h b/Source/Devices/Neuropixels2e.h index 2c7f692..563e1ca 100644 --- a/Source/Devices/Neuropixels2e.h +++ b/Source/Devices/Neuropixels2e.h @@ -24,15 +24,46 @@ #include "../OnixDevice.h" #include "../I2CRegisterContext.h" +#include "../NeuropixComponents.h" +#include "DS90UB9x.h" +#include "PortController.h" + +enum class NeuropixelsV2Reference : uint32_t +{ + External, + Tip1, + Tip2, + Tip3, + Tip4 +}; + +enum class NeuropixelsV2Status : uint32_t +{ + SR_OK = 1 << 7 +}; /* Configures and streams data from a Neuropixels 2.0e device */ -class Neuropixels2e : public OnixDevice +class Neuropixels2e : public INeuropixel, + public OnixDevice, + public I2CRegisterContext { public: Neuropixels2e(String name, const oni_dev_idx_t, std::shared_ptr); + ~Neuropixels2e() + { + if (serializer != nullptr) + serializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xSerializerI2CRegister::GPIO10, DefaultGPO10Config); + + if (i2cContext != nullptr) + selectProbe(NoProbeSelected); + + if (deviceContext != nullptr && deviceContext->isInitialized()) + deviceContext->setOption(ONIX_OPT_PASSTHROUGH, 0); + } + int configureDevice() override; /** Update the settings of the device */ @@ -52,8 +83,42 @@ class Neuropixels2e : public OnixDevice int getNumProbes() const; + static const int baseBitsPerChannel = 4; + static const int configurationBitCount = NeuropixelsV2eValues::numberOfChannels * baseBitsPerChannel / 2; + + static const int referencePixelCount = 4; + static const int dummyPixelCount = 4; + static const int registersPerShank = NeuropixelsV2eValues::electrodesPerShank + referencePixelCount + dummyPixelCount; + + using BaseBitsArray = std::array, 2>; + using ShankBitsArray = std::array, 4>; + + BaseBitsArray static makeBaseBits(NeuropixelsV2Reference reference); + ShankBitsArray static makeShankBits(NeuropixelsV2Reference reference, std::array channelMap); + + template + void writeShiftRegister(uint32_t srAddress, std::bitset bits); + + void setGainCorrectionFile(int index, String filename); + + String getGainCorrectionFile(int index); + + // INeuropixel Methods + + std::vector selectElectrodeConfiguration(String config) override; + + uint64_t getProbeSerialNumber(int index) override; + + void defineMetadata(ProbeSettings*, int); + + void setSettings(ProbeSettings* settings_, int index) override; + private: - DataBuffer* apBuffer[2]; + DataBuffer* amplifierBuffer[2]; + + std::array probeSN; + std::array gainCorrection; + std::array gainCorrectionFilePath; void createDataStream(int n); @@ -64,13 +129,33 @@ class Neuropixels2e : public OnixDevice void selectProbe(uint8_t probeSelect); void configureProbeStreaming(); + void writeConfiguration(ProbeSettings*); + + NeuropixelsV2Reference getReference(int); + static String getShankName(uint32_t shiftRegisterAddress); int m_numProbes = 0; + const float sampleRate = 30000.0f; + static const int numFrames = 10; + static const int numSamples = numberOfChannels * numFrames; + + std::array samples; + + int64 sampleNumbers[numFrames]; + double timestamps[numFrames]; + uint64 eventCodes[numFrames]; + + bool shouldAddToBuffer = false; + int frameCount = 0; + int sampleNumber = 0; + + bool singleProbe = false; + std::unique_ptr serializer; std::unique_ptr deserializer; std::unique_ptr flex; - std::unique_ptr probeControl; //TODO: this probably be a derived class that includes all the connfiguration methods + std::unique_ptr probeControl; static const int ProbeI2CAddress = 0x10; static const int FlexAddress = 0x50; @@ -130,10 +215,52 @@ class Neuropixels2e : public OnixDevice const uint32_t OFFSET_FLEX_PN = 0x20; const uint32_t OFFSET_PROBE_PN = 0x40; - uint64_t probeSNA; - uint64_t probeSNB; - Array frameArray; + static inline const std::array adcIndices = { + 0, 1, 2, + 4, 5, 6, + 8, 9, 10, + 12, 13, 14, + 16, 17, 18, + 20, 21, 22, + 24, 25, 26, + 28, 29, 30 + }; + + static inline const std::array, AdcsPerProbe> rawToChannel = { { + { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }, // Data Index 9, ADC 0 + { 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, 158 }, // Data Index 10, ADC 8 + { 256, 258, 260, 262, 264, 266, 268, 270, 272, 274, 276, 278, 280, 282, 284, 286 }, // Data Index 11, ADC 16 + + { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 }, // Data Index 13, ADC 1 + { 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149, 151, 153, 155, 157, 159 }, // Data Index 14, ADC 9 + { 257, 259, 261, 263, 265, 267, 269, 271, 273, 275, 277, 279, 281, 283, 285, 287 }, // Data Index 15, ADC 17 + + { 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62 }, // Data Index 17, ADC 2 + { 160, 162, 164, 166, 168, 170, 172, 174, 176, 178, 180, 182, 184, 186, 188, 190 }, // Data Index 18, ADC 10 + { 288, 290, 292, 294, 296, 298, 300, 302, 304, 306, 308, 310, 312, 314, 316, 318 }, // Data Index 19, ADC 18 + + { 33, 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63 }, // Data Index 21, ADC 3 + { 161, 163, 165, 167, 169, 171, 173, 175, 177, 179, 181, 183, 185, 187, 189, 191 }, // Data Index 22, ADC 11 + { 289, 291, 293, 295, 297, 299, 301, 303, 305, 307, 309, 311, 313, 315, 317, 319 }, // Data Index 23, ADC 19 + + { 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94 }, // Data Index 25, ADC 4 + { 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222 }, // Data Index 26, ADC 12 + { 320, 322, 324, 326, 328, 330, 332, 334, 336, 338, 340, 342, 344, 346, 348, 350 }, // Data Index 27, ADC 20 + + { 65, 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95 }, // Data Index 29, ADC 5 + { 193, 195, 197, 199, 201, 203, 205, 207, 209, 211, 213, 215, 217, 219, 221, 223 }, // Data Index 30, ADC 13 + { 321, 323, 325, 327, 329, 331, 333, 335, 337, 339, 341, 343, 345, 347, 349, 351 }, // Data Index 31, ADC 21 + + { 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126 }, // Data Index 33, ADC 6 + { 224, 226, 228, 230, 232, 234, 236, 238, 240, 242, 244, 246, 248, 250, 252, 254 }, // Data Index 34, ADC 14 + { 352, 354, 356, 358, 360, 362, 364, 366, 368, 370, 372, 374, 376, 378, 380, 382 }, // Data Index 35, ADC 22 + + { 97, 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123, 125, 127 }, // Data Index 37, ADC 7 + { 225, 227, 229, 231, 233, 235, 237, 239, 241, 243, 245, 247, 249, 251, 253, 255 }, // Data Index 38, ADC 15 + { 353, 355, 357, 359, 361, 363, 365, 367, 369, 371, 373, 375, 377, 379, 381, 383 }, // Data Index 39, ADC 23 + } }; + JUCE_LEAK_DETECTOR(Neuropixels2e); }; diff --git a/Source/Devices/Neuropixels_1.cpp b/Source/Devices/Neuropixels_1.cpp index 3854746..91f31ef 100644 --- a/Source/Devices/Neuropixels_1.cpp +++ b/Source/Devices/Neuropixels_1.cpp @@ -94,13 +94,13 @@ void BackgroundUpdaterWithProgressWindow::run() LOGD("Gain calibration file SN = ", gainSN); - if (gainSN != device->getProbeNumber()) + if (gainSN != device->getProbeSerialNumber()) { result = false; - LOGE("Gain calibration serial number (", gainSN, ") does not match probe serial number (", device->getProbeNumber(), ")."); + LOGE("Gain calibration serial number (", gainSN, ") does not match probe serial number (", device->getProbeSerialNumber(), ")."); - CoreServices::sendStatusMessage("Serial Number Mismatch: Gain calibration (" + String(gainSN) + ") does not match " + String(device->getProbeNumber())); + CoreServices::sendStatusMessage("Serial Number Mismatch: Gain calibration (" + String(gainSN) + ") does not match " + String(device->getProbeSerialNumber())); return; } @@ -111,8 +111,8 @@ void BackgroundUpdaterWithProgressWindow::run() StringArray calibrationValues = StringArray::fromTokens(gainCalLine, breakCharacters, noQuote); - double apGainCorrection = std::stod(calibrationValues[device->settings->apGainIndex + 1].toStdString()); - double lfpGainCorrection = std::stod(calibrationValues[device->settings->lfpGainIndex + 8].toStdString()); + double apGainCorrection = std::stod(calibrationValues[device->settings[0]->apGainIndex + 1].toStdString()); + double lfpGainCorrection = std::stod(calibrationValues[device->settings[0]->lfpGainIndex + 8].toStdString()); LOGD("AP gain correction = ", apGainCorrection, ", LFP gain correction = ", lfpGainCorrection); @@ -125,13 +125,13 @@ void BackgroundUpdaterWithProgressWindow::run() LOGD("ADC calibration file SN = ", adcSN); - if (adcSN != device->getProbeNumber()) + if (adcSN != device->getProbeSerialNumber()) { result = false; - LOGE("ADC calibration serial number (", adcSN, ") does not match probe serial number (", device->getProbeNumber(), ")."); + LOGE("ADC calibration serial number (", adcSN, ") does not match probe serial number (", device->getProbeSerialNumber(), ")."); - CoreServices::sendStatusMessage("Serial Number Mismatch: ADC calibration (" + String(adcSN) + ") does not match " + String(device->getProbeNumber())); + CoreServices::sendStatusMessage("Serial Number Mismatch: ADC calibration (" + String(adcSN) + ") does not match " + String(device->getProbeSerialNumber())); return; } @@ -158,8 +158,8 @@ void BackgroundUpdaterWithProgressWindow::run() setProgress(0.8); // Write shift registers - auto shankBits = device->makeShankBits(device->getReference(device->settings->referenceIndex), device->settings->selectedChannel); - auto configBits = device->makeConfigBits(device->getReference(device->settings->referenceIndex), device->getGainEnum(device->settings->apGainIndex), device->getGainEnum(device->settings->lfpGainIndex), true, adcs); + auto shankBits = device->makeShankBits(device->getReference(device->settings[0]->referenceIndex), device->settings[0]->selectedElectrode); + auto configBits = device->makeConfigBits(device->getReference(device->settings[0]->referenceIndex), device->getGainEnum(device->settings[0]->apGainIndex), device->getGainEnum(device->settings[0]->lfpGainIndex), true, adcs); device->writeShiftRegisters(shankBits, configBits, adcs, lfpGainCorrection, apGainCorrection); @@ -169,15 +169,17 @@ void BackgroundUpdaterWithProgressWindow::run() } Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx_) : - OnixDevice("Neuropixels 1.0 " + name, OnixDeviceType::NEUROPIXELS_1, deviceIdx_, ctx_), - I2CRegisterContext(ProbeI2CAddress, deviceIdx_, ctx_) + OnixDevice(name, NEUROPIXELSV1F_HEADSTAGE_NAME, OnixDeviceType::NEUROPIXELS_1, deviceIdx_, ctx_), + I2CRegisterContext(ProbeI2CAddress, deviceIdx_, ctx_), + INeuropixel(NeuropixelsV1fValues::numberOfSettings, NeuropixelsV1fValues::numberOfShanks) { + String port = PortController::getPortName(PortController::getPortFromIndex(deviceIdx)); StreamInfo apStream = StreamInfo( - name + "-AP", + OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "AP" }), "Neuropixels 1.0 AP band data stream", "onix-neuropixels1.data.ap", - 384, - 30000.0f, + numberOfChannels, + apSampleRate, "AP", ContinuousChannel::Type::ELECTRODE, 0.195f, @@ -186,11 +188,11 @@ Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::s streamInfos.add(apStream); StreamInfo lfpStream = StreamInfo( - name + "-LFP", + OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "LFP" }), "Neuropixels 1.0 LFP band data stream", "onix-neuropixels1.data.lfp", - 384, - 2500.0f, + numberOfChannels, + lfpSampleRate, "LFP", ContinuousChannel::Type::ELECTRODE, 0.195f, @@ -198,11 +200,16 @@ Neuropixels_1::Neuropixels_1(String name, const oni_dev_idx_t deviceIdx_, std::s {}); streamInfos.add(lfpStream); - settings = std::make_unique(); - defineMetadata(settings.get()); + defineMetadata(settings[0].get()); adcCalibrationFilePath = "None"; gainCalibrationFilePath = "None"; + + for (int i = 0; i < numUltraFrames; i++) + { + apEventCodes[i] = 0; + lfpEventCodes[i] = 0; + } } NeuropixelsGain Neuropixels_1::getGainEnum(int index) @@ -259,37 +266,41 @@ int Neuropixels_1::getGainValue(NeuropixelsGain gain) } } -NeuropixelsReference Neuropixels_1::getReference(int index) +NeuropixelsV1Reference Neuropixels_1::getReference(int index) { switch (index) { case 0: - return NeuropixelsReference::External; + return NeuropixelsV1Reference::External; case 1: - return NeuropixelsReference::Tip; + return NeuropixelsV1Reference::Tip; default: break; } - return NeuropixelsReference::External; + return NeuropixelsV1Reference::External; } int Neuropixels_1::configureDevice() { if (deviceContext == nullptr || !deviceContext->isInitialized()) return -5; + deviceContext->writeRegister(deviceIdx, ENABLE, isEnabled() ? 1 : 0); + // Get Probe SN uint32_t eepromOffset = 0; uint32_t i2cAddr = 0x50; int errorCode = 0; + int rc; for (int i = 0; i < 8; i++) { oni_reg_addr_t reg_addr = ((eepromOffset + i) << 7) | i2cAddr; - oni_reg_val_t reg_val = deviceContext->readRegister(deviceIdx, reg_addr); + oni_reg_val_t reg_val; + rc = deviceContext->readRegister(deviceIdx, reg_addr, ®_val); - if (deviceContext->getLastResult() != ONI_ESUCCESS) return -1; + if (rc != ONI_ESUCCESS) return -1; if (reg_val <= 0xFF) { @@ -299,23 +310,28 @@ int Neuropixels_1::configureDevice() LOGD("Probe SN: ", probeNumber); + if (!isEnabled()) + { + return 0; + } + // Enable device streaming - deviceContext->writeRegister(deviceIdx, 0x8000, 1); - if (deviceContext->getLastResult() != ONI_ESUCCESS) return -2; + rc = deviceContext->writeRegister(deviceIdx, 0x8000, 1); + if (rc != ONI_ESUCCESS) return -2; - WriteByte((uint32_t)NeuropixelsRegisters::CAL_MOD, (uint32_t)CalMode::CAL_OFF); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return -3; + rc = WriteByte((uint32_t)NeuropixelsRegisters::CAL_MOD, (uint32_t)CalMode::CAL_OFF); + if (rc != ONI_ESUCCESS) return -3; - WriteByte((uint32_t)NeuropixelsRegisters::SYNC, (uint32_t)0); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return -3; + rc = WriteByte((uint32_t)NeuropixelsRegisters::SYNC, (uint32_t)0); + if (rc != ONI_ESUCCESS) return -3; - WriteByte((uint32_t)NeuropixelsRegisters::REC_MOD, (uint32_t)RecMod::DIG_AND_CH_RESET); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return -3; + rc = WriteByte((uint32_t)NeuropixelsRegisters::REC_MOD, (uint32_t)RecMod::DIG_AND_CH_RESET); + if (rc != ONI_ESUCCESS) return -3; - WriteByte((uint32_t)NeuropixelsRegisters::OP_MODE, (uint32_t)OpMode::RECORD); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return -3; + rc = WriteByte((uint32_t)NeuropixelsRegisters::OP_MODE, (uint32_t)OpMode::RECORD); + if (rc != ONI_ESUCCESS) return -3; - return deviceContext->getLastResult(); + return rc; } bool Neuropixels_1::updateSettings() @@ -325,60 +341,68 @@ bool Neuropixels_1::updateSettings() return updater.updateSettings(); } -void Neuropixels_1::setSettings(ProbeSettings* settings_) const +void Neuropixels_1::setSettings(ProbeSettings* settings_, int index) { - settings->updateProbeSettings(settings_); + if (index >= settings.size()) + { + LOGE("Invalid index given when trying to update settings."); + return; + } + + settings[index]->updateProbeSettings(settings_); } -Array Neuropixels_1::selectElectrodeConfiguration(String config) +std::vector Neuropixels_1::selectElectrodeConfiguration(String config) { - Array selection; + std::vector selection; if (config.equalsIgnoreCase("Bank A")) { for (int i = 0; i < 384; i++) - selection.add(i); + selection.emplace_back(i); } else if (config.equalsIgnoreCase("Bank B")) { for (int i = 384; i < 768; i++) - selection.add(i); + selection.emplace_back(i); } else if (config.equalsIgnoreCase("Bank C")) { for (int i = 576; i < 960; i++) - selection.add(i); + selection.emplace_back(i); } else if (config.equalsIgnoreCase("Single Column")) { for (int i = 0; i < 384; i += 2) - selection.add(i); + selection.emplace_back(i); for (int i = 385; i < 768; i += 2) - selection.add(i); + selection.emplace_back(i); } else if (config.equalsIgnoreCase("Tetrodes")) { for (int i = 0; i < 384; i += 8) { for (int j = 0; j < 4; j++) - selection.add(i + j); + selection.emplace_back(i + j); } for (int i = 388; i < 768; i += 8) { for (int j = 0; j < 4; j++) - selection.add(i + j); + selection.emplace_back(i + j); } } + assert(selection.size() == numberOfChannels && "Invalid number of selected channels."); + return selection; } void Neuropixels_1::startAcquisition() { - apGain = getGainValue(getGainEnum(settings->apGainIndex)); - lfpGain = getGainValue(getGainEnum(settings->lfpGainIndex)); + apGain = getGainValue(getGainEnum(settings[0]->apGainIndex)); + lfpGain = getGainValue(getGainEnum(settings[0]->lfpGainIndex)); apOffsetValues.clear(); apOffsetValues.reserve(numberOfChannels); @@ -438,8 +462,8 @@ void Neuropixels_1::addFrame(oni_frame_t* frame) void Neuropixels_1::processFrames() { - float apConversion = 1171.875 / apGain; - float lfpConversion = 1171.875 / lfpGain; + const float apConversion = (1171.875 / apGain) * -1.0f; + const float lfpConversion = (1171.875 / lfpGain) * -1.0f; while (!frameArray.isEmpty()) { @@ -465,9 +489,8 @@ void Neuropixels_1::processFrames() for (int adc = 0; adc < 32; adc++) { int chanIndex = adcToChannel[adc] + superCountOffset * 2; // map the ADC to muxed channel - float offset = shouldCorrectOffset ? lfpOffsets.at(chanIndex) : 0.0f; lfpSamples[(chanIndex * numUltraFrames) + ultraFrameCount] = - lfpConversion * float((*(dataPtr + adcToFrameIndex[adc] + dataOffset) >> 5) - 512) - offset; + lfpConversion * float((*(dataPtr + adcToFrameIndex[adc] + dataOffset) >> 5) - 512) - lfpOffsets.at(chanIndex); } } else // AP data @@ -476,9 +499,8 @@ void Neuropixels_1::processFrames() for (int adc = 0; adc < 32; adc++) { int chanIndex = adcToChannel[adc] + chanOffset; // map the ADC to muxed channel. - float offset = shouldCorrectOffset ? apOffsets.at(chanIndex) : 0.0f; apSamples[(chanIndex * superFramesPerUltraFrame * numUltraFrames) + superFrameCount] = - apConversion * float((*(dataPtr + adcToFrameIndex[adc] + i * 36 + dataOffset) >> 5) - 512) - offset; + apConversion * float((*(dataPtr + adcToFrameIndex[adc] + i * 36 + dataOffset) >> 5) - 512) - apOffsets.at(chanIndex); } } } @@ -573,7 +595,7 @@ void Neuropixels_1::updateLfpOffsets(std::array& samples, } } -std::bitset Neuropixels_1::makeShankBits(NeuropixelsReference reference, Array channelMap) +Neuropixels_1::ShankBitset Neuropixels_1::makeShankBits(NeuropixelsV1Reference reference, std::array channelMap) { const int shankBitExt1 = 965; const int shankBitExt2 = 2; @@ -596,11 +618,11 @@ std::bitset Neuropixels_1::makeShankB switch (reference) { - case NeuropixelsReference::External: + case NeuropixelsV1Reference::External: shankBits[shankBitExt1] = true; shankBits[shankBitExt2] = true; break; - case NeuropixelsReference::Tip: + case NeuropixelsV1Reference::Tip: shankBits[shankBitTip1] = true; shankBits[shankBitTip2] = true; break; @@ -608,14 +630,19 @@ std::bitset Neuropixels_1::makeShankB break; } + if (channelMap.size() != numberOfChannels) + { + LOGE("Invalid number of channels connected for Neuropixels 1.0f, configuration might be invalid."); + } + return shankBits; } -std::vector> Neuropixels_1::makeConfigBits(NeuropixelsReference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs) +Neuropixels_1::CongigBitsArray Neuropixels_1::makeConfigBits(NeuropixelsV1Reference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs) { const int BaseConfigurationConfigOffset = 576; - std::vector> baseConfigs(2); + CongigBitsArray baseConfigs; for (int i = 0; i < numberOfChannels; i++) { @@ -691,38 +718,20 @@ std::vector> Neuropixels_1 return baseConfigs; } -template std::vector Neuropixels_1::toBitReversedBytes(std::bitset bits) -{ - std::vector bytes((bits.size() - 1) / 8 + 1); - - for (int i = 0; i < bytes.size(); i++) - { - for (int j = 0; j < 8; j++) - { - bytes[i] |= bits[i * 8 + j] << (8 - j - 1); - } - - // NB: Reverse bytes (http://graphics.stanford.edu/~seander/bithacks.html) - bytes[i] = (unsigned char)((bytes[i] * 0x0202020202ul & 0x010884422010ul) % 1023); - } - - return bytes; -} - -void Neuropixels_1::writeShiftRegisters(std::bitset shankBits, std::vector> configBits, Array adcs, double lfpGainCorrection, double apGainCorrection) +void Neuropixels_1::writeShiftRegisters(ShankBitset shankBits, CongigBitsArray configBits, Array adcs, double lfpGainCorrection, double apGainCorrection) { auto shankBytes = toBitReversedBytes(shankBits); - WriteByte((uint32_t)ShiftRegisters::SR_LENGTH1, (uint32_t)shankBytes.size() % 0x100); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return; + int rc = WriteByte((uint32_t)ShiftRegisters::SR_LENGTH1, (uint32_t)shankBytes.size() % 0x100); + if (rc != ONI_ESUCCESS) return; - WriteByte((uint32_t)ShiftRegisters::SR_LENGTH2, (uint32_t)shankBytes.size() / 0x100); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return; + rc = WriteByte((uint32_t)ShiftRegisters::SR_LENGTH2, (uint32_t)shankBytes.size() / 0x100); + if (rc != ONI_ESUCCESS) return; for (auto b : shankBytes) { - WriteByte((uint32_t)ShiftRegisters::SR_CHAIN1, b); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return; + rc = WriteByte((uint32_t)ShiftRegisters::SR_CHAIN1, b); + if (rc != ONI_ESUCCESS) return; } const uint32_t shiftRegisterSuccess = 1 << 7; @@ -735,23 +744,23 @@ void Neuropixels_1::writeShiftRegisters(std::bitset { auto baseBytes = toBitReversedBytes(configBits[i]); - WriteByte((uint32_t)ShiftRegisters::SR_LENGTH1, (uint32_t)baseBytes.size() % 0x100); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return; + rc = WriteByte((uint32_t)ShiftRegisters::SR_LENGTH1, (uint32_t)baseBytes.size() % 0x100); + if (rc != ONI_ESUCCESS) return; - WriteByte((uint32_t)ShiftRegisters::SR_LENGTH2, (uint32_t)baseBytes.size() / 0x100); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return; + rc = WriteByte((uint32_t)ShiftRegisters::SR_LENGTH2, (uint32_t)baseBytes.size() / 0x100); + if (rc != ONI_ESUCCESS) return; for (auto b : baseBytes) { - WriteByte(srAddress, b); - if (i2cContext->getLastResult() != ONI_ESUCCESS) return; + rc = WriteByte(srAddress, b); + if (rc != ONI_ESUCCESS) return; } } oni_reg_val_t value; - ReadByte((uint32_t)NeuropixelsRegisters::STATUS, &value); + rc = ReadByte((uint32_t)NeuropixelsRegisters::STATUS, &value); - if (i2cContext->getLastResult() != ONI_ESUCCESS || value != shiftRegisterSuccess) + if (rc != ONI_ESUCCESS || value != shiftRegisterSuccess) { LOGE("Shift register ", srAddress, " status check failed."); return; @@ -763,9 +772,9 @@ void Neuropixels_1::writeShiftRegisters(std::bitset for (uint32_t i = 0; i < adcs.size(); i += 2) { auto value = (uint32_t)(adcs[i + 1].offset << 26 | adcs[i + 1].threshold << 16 | adcs[i].offset << 10 | adcs[i].threshold); - deviceContext->writeRegister(deviceIdx, ADC01_00_OFF_THRESH + i, value); + rc = deviceContext->writeRegister(deviceIdx, ADC01_00_OFF_THRESH + i, value); - if (deviceContext->getLastResult() != ONI_ESUCCESS) + if (rc != ONI_ESUCCESS) { LOGE("Error writing to register ", ADC01_00_OFF_THRESH + i, "."); return; @@ -780,15 +789,15 @@ void Neuropixels_1::writeShiftRegisters(std::bitset for (uint32_t i = 0; i < numberOfChannels / 2; i++) { - deviceContext->writeRegister(deviceIdx, CHAN001_000_LFPGAIN + i, fixedPointLfPGain << 16 | fixedPointLfPGain); - if (deviceContext->getLastResult() != ONI_ESUCCESS) + rc = deviceContext->writeRegister(deviceIdx, CHAN001_000_LFPGAIN + i, fixedPointLfPGain << 16 | fixedPointLfPGain); + if (rc != ONI_ESUCCESS) { LOGE("Error writing to register ", CHAN001_000_LFPGAIN + i, "."); return; } - deviceContext->writeRegister(deviceIdx, CHAN001_000_APGAIN + i, fixedPointApGain << 16 | fixedPointApGain); - if (deviceContext->getLastResult() != ONI_ESUCCESS) + rc = deviceContext->writeRegister(deviceIdx, CHAN001_000_APGAIN + i, fixedPointApGain << 16 | fixedPointApGain); + if (rc != ONI_ESUCCESS) { LOGE("Error writing to register ", CHAN001_000_APGAIN + i, "."); return; @@ -796,7 +805,7 @@ void Neuropixels_1::writeShiftRegisters(std::bitset } } -void Neuropixels_1::defineMetadata(ProbeSettings* settings) +void Neuropixels_1::defineMetadata(ProbeSettings* settings) { settings->probeType = ProbeType::NPX_V1E; settings->probeMetadata.name = "Neuropixels 1.0f"; @@ -810,8 +819,8 @@ void Neuropixels_1::defineMetadata(ProbeSettings* settings) path.closeSubPath(); settings->probeMetadata.shank_count = 1; - settings->probeMetadata.electrodes_per_shank = 960; - settings->probeMetadata.rows_per_shank = 960 / 2; + settings->probeMetadata.electrodes_per_shank = numberOfElectrodes; + settings->probeMetadata.rows_per_shank = numberOfElectrodes / 2; settings->probeMetadata.columns_per_shank = 2; settings->probeMetadata.shankOutline = path; settings->probeMetadata.num_adcs = 32; // NB: Is this right for 1.0e? @@ -826,7 +835,7 @@ void Neuropixels_1::defineMetadata(ProbeSettings* settings) Array xpositions = { 27.0f, 59.0f, 11.0f, 43.0f }; - for (int i = 0; i < settings->probeMetadata.electrodes_per_shank * settings->probeMetadata.shank_count; i++) + for (int i = 0; i < numberOfElectrodes; i++) { ElectrodeMetadata metadata; @@ -869,7 +878,7 @@ void Neuropixels_1::defineMetadata(ProbeSettings* settings) metadata.type = ElectrodeType::ELECTRODE; } - settings->electrodeMetadata.add(metadata); + settings->electrodeMetadata[i] = metadata; } settings->apGainIndex = 4; // NB: AP Gain Index of 4 = Gain1000 @@ -879,10 +888,9 @@ void Neuropixels_1::defineMetadata(ProbeSettings* settings) for (int i = 0; i < numberOfChannels; i++) { - settings->selectedBank.add(Bank::A); - settings->selectedChannel.add(i); - settings->selectedShank.add(0); - settings->selectedElectrode.add(i); + settings->selectedBank[i] = Bank::A; + settings->selectedShank[i] = 0; + settings->selectedElectrode[i] = i; } settings->availableApGains.add(50.0f); @@ -913,4 +921,6 @@ void Neuropixels_1::defineMetadata(ProbeSettings* settings) settings->availableElectrodeConfigurations.add("Tetrodes"); settings->electrodeConfigurationIndex = 0; + + settings->isValid = true; } diff --git a/Source/Devices/Neuropixels_1.h b/Source/Devices/Neuropixels_1.h index 7da604c..e543926 100644 --- a/Source/Devices/Neuropixels_1.h +++ b/Source/Devices/Neuropixels_1.h @@ -24,13 +24,7 @@ #include "../OnixDevice.h" #include "../NeuropixComponents.h" - -#include -#include -#include -#include -#include -#include +#include "PortController.h" enum class NeuropixelsRegisters : uint32_t { @@ -78,7 +72,7 @@ enum class RecMod : uint32_t ACTIVE = DIG_NRESET | CH_NRESET }; -enum class NeuropixelsReference : unsigned char +enum class NeuropixelsV1Reference : unsigned char { External = 0b001, Tip = 0b010 @@ -148,7 +142,8 @@ struct NeuropixelsV1Adc Configures and streams data from a Neuropixels 1.0f device */ -class Neuropixels_1 : public OnixDevice, +class Neuropixels_1 : public INeuropixel, + public OnixDevice, public I2CRegisterContext { public: @@ -161,8 +156,6 @@ class Neuropixels_1 : public OnixDevice, /** Update the settings of the device by writing to hardware */ bool updateSettings() override; - void setSettings(ProbeSettings* settings_) const; - /** Starts probe data streaming */ void startAcquisition() override; @@ -176,42 +169,48 @@ class Neuropixels_1 : public OnixDevice, void processFrames() override; - int64 getProbeNumber() const { return probeNumber; } - NeuropixelsGain getGainEnum(int index); int getGainValue(NeuropixelsGain); - NeuropixelsReference getReference(int index); - - /** Select a preset electrode configuration */ - Array selectElectrodeConfiguration(String config); + NeuropixelsV1Reference getReference(int index); static const int shankConfigurationBitCount = 968; static const int BaseConfigurationBitCount = 2448; - std::bitset static makeShankBits(NeuropixelsReference reference, Array channelMap); + using ShankBitset = std::bitset; + using CongigBitsArray = std::array, 2>; - std::vector> static makeConfigBits(NeuropixelsReference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs); + String adcCalibrationFilePath; + String gainCalibrationFilePath; - void writeShiftRegisters(std::bitset shankBits, std::vector> configBits, Array adcs, double lfpGainCorrection, double apGainCorrection); + bool getCorrectOffset() const { return correctOffset; } - std::unique_ptr settings; + void setCorrectOffset(bool value) { correctOffset = value; } - static const int numberOfChannels = 384; + ShankBitset static makeShankBits(NeuropixelsV1Reference reference, std::array channelMap); - String adcCalibrationFilePath; - String gainCalibrationFilePath; + CongigBitsArray static makeConfigBits(NeuropixelsV1Reference reference, NeuropixelsGain spikeAmplifierGain, NeuropixelsGain lfpAmplifierGain, bool spikeFilterEnabled, Array adcs); - bool getShouldCorrectOffset() const { return shouldCorrectOffset; } + void writeShiftRegisters(ShankBitset shankBits, CongigBitsArray configBits, Array adcs, double lfpGainCorrection, double apGainCorrection); - void setShouldCorrectOffset(bool value) { shouldCorrectOffset = value; } + // INeuropixels methods + void setSettings(ProbeSettings* settings_, int index = 0) override; + + void defineMetadata(ProbeSettings* settings) override; + + uint64_t getProbeSerialNumber(int index = 0) override { return probeNumber; } + + /** Select a preset electrode configuration */ + std::vector selectElectrodeConfiguration(String config) override; private: DataBuffer* apBuffer; DataBuffer* lfpBuffer; + const uint32_t ENABLE = 0x8000; + static const int superFramesPerUltraFrame = 12; static const int framesPerSuperFrame = 13; static const int framesPerUltraFrame = superFramesPerUltraFrame * framesPerSuperFrame; @@ -224,12 +223,14 @@ class Neuropixels_1 : public OnixDevice, static const uint32_t numLfpSamples = 384 * numUltraFrames; static const uint32_t numApSamples = 384 * numUltraFrames * superFramesPerUltraFrame; - const float lfpSampleRate = 2500.0f; - const float apSampleRate = 30000.0f; + static constexpr float lfpSampleRate = 2500.0f; + static constexpr float apSampleRate = 30000.0f; bool lfpOffsetCalculated = false; bool apOffsetCalculated = false; + bool correctOffset = true; + std::array apOffsets; std::array lfpOffsets; @@ -241,13 +242,9 @@ class Neuropixels_1 : public OnixDevice, static const int ProbeI2CAddress = 0x70; - template std::vector static toBitReversedBytes(std::bitset shankBits); - - void defineMetadata(ProbeSettings* settings); - Array frameArray; - int64 probeNumber = 0; + uint64_t probeNumber = 0; std::array lfpSamples; std::array apSamples; @@ -270,8 +267,6 @@ class Neuropixels_1 : public OnixDevice, int apGain = 1000; int lfpGain = 50; - bool shouldCorrectOffset = true; - JUCE_LEAK_DETECTOR(Neuropixels_1); }; diff --git a/Source/Devices/OutputClock.cpp b/Source/Devices/OutputClock.cpp index c3f1ca4..de56efa 100644 --- a/Source/Devices/OutputClock.cpp +++ b/Source/Devices/OutputClock.cpp @@ -23,41 +23,27 @@ #include "OutputClock.h" OutputClock::OutputClock(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr oni_ctx) - : OnixDevice(name, OnixDeviceType::OUTPUTCLOCK, deviceIdx_, oni_ctx) + : OnixDevice(name, BREAKOUT_BOARD_NAME, OnixDeviceType::OUTPUTCLOCK, deviceIdx_, oni_ctx) { } -OutputClock::~OutputClock() -{ - if (deviceContext != nullptr && deviceContext->isInitialized()) setGateRun(false, true); -} - bool OutputClock::updateSettings() { - oni_reg_val_t baseFreqHz = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::BASE_FREQ_HZ); + oni_reg_val_t baseFreqHz; + int rc = deviceContext->readRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::BASE_FREQ_HZ, &baseFreqHz); auto periodCycles = (uint32_t)(baseFreqHz / frequencyHz); auto h = (uint32_t)(periodCycles * ((double)dutyCycle / 100.0)); auto l = periodCycles - h; auto delayCycles = (uint32_t)(delay * baseFreqHz); - deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::CLOCK_GATE, 1); if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + rc = deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::CLOCK_GATE, 1); if (rc != ONI_ESUCCESS) return false; - deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::HIGH_CYCLES, h); if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; - deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::LOW_CYCLES, l); if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; - deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::DELAY_CYCLES, delayCycles); if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + rc = deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::HIGH_CYCLES, h); if (rc != ONI_ESUCCESS) return false; + rc = deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::LOW_CYCLES, l); if (rc != ONI_ESUCCESS) return false; + rc = deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::DELAY_CYCLES, delayCycles); if (rc != ONI_ESUCCESS) return false; - deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::GATE_RUN, gateRun ? 1 : 0); if (deviceContext->getLastResult() != ONI_ESUCCESS) return false; + rc = deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)OutputClockRegisters::GATE_RUN, gateRun ? 1 : 0); if (rc != ONI_ESUCCESS) return false; return true; } - -void OutputClock::startAcquisition() -{ - writeGateRunRegister(); -} - -void OutputClock::stopAcquisition() -{ - setGateRun(false, true); -} \ No newline at end of file diff --git a/Source/Devices/OutputClock.h b/Source/Devices/OutputClock.h index d93f859..9c3ab5b 100644 --- a/Source/Devices/OutputClock.h +++ b/Source/Devices/OutputClock.h @@ -43,8 +43,6 @@ class OutputClock : public OnixDevice public: OutputClock(String name, const oni_dev_idx_t, std::shared_ptr oni_ctx); - ~OutputClock(); - /** Device is always enabled */ int configureDevice() override { setEnabled(true); return 0; }; @@ -52,10 +50,10 @@ class OutputClock : public OnixDevice bool updateSettings() override; /** Starts probe data streaming */ - void startAcquisition() override; + void startAcquisition() override {}; /** Stops probe data streaming*/ - void stopAcquisition() override; + void stopAcquisition() override {}; /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ void addSourceBuffers(OwnedArray& sourceBuffers) override {}; diff --git a/Source/Devices/PolledBno055.cpp b/Source/Devices/PolledBno055.cpp new file mode 100644 index 0000000..4936f02 --- /dev/null +++ b/Source/Devices/PolledBno055.cpp @@ -0,0 +1,276 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#include "PolledBno055.h" + +PolledBno055::PolledBno055(String name, String headstageName, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx) + : OnixDevice(name, headstageName, OnixDeviceType::POLLEDBNO, deviceIdx_, ctx), + I2CRegisterContext(Bno055Address, deviceIdx_, ctx) +{ + const float bitVolts = 1.0; + + String port = PortController::getPortName(PortController::getPortFromIndex(deviceIdx)); + StreamInfo eulerAngleStream = StreamInfo( + OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Euler" }), + "Bosch Bno055 9-axis inertial measurement unit (IMU) Euler angle", + "onix-bno055.data.euler", + 3, + sampleRate, + "Euler", + ContinuousChannel::Type::AUX, + bitVolts, + "Degrees", + { "Yaw", "Roll", "Pitch" }); + streamInfos.add(eulerAngleStream); + + StreamInfo quaternionStream = StreamInfo( + OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Quaternion" }), + "Bosch Bno055 9-axis inertial measurement unit (IMU) Quaternion", + "onix-bno055.data.quat", + 4, + sampleRate, + "Quaternion", + ContinuousChannel::Type::AUX, + bitVolts, + "", + { "W", "X", "Y", "Z" }); + streamInfos.add(quaternionStream); + + StreamInfo accelerationStream = StreamInfo( + OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Acceleration" }), + "Bosch Bno055 9-axis inertial measurement unit (IMU) Acceleration", + "onix-bno055.data.acc", + 3, + sampleRate, + "Acceleration", + ContinuousChannel::Type::AUX, + bitVolts, + "m / s ^ 2", + { "X", "Y", "Z" }); + streamInfos.add(accelerationStream); + + StreamInfo gravityStream = StreamInfo( + OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Gravity" }), + "Bosch Bno055 9-axis inertial measurement unit (IMU) Gravity", + "onix-bno055.data.grav", + 3, + sampleRate, + "Gravity", + ContinuousChannel::Type::AUX, + bitVolts, + "m/s^2", + { "X", "Y", "Z" }); + streamInfos.add(gravityStream); + + StreamInfo temperatureStream = StreamInfo( + OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Temperature" }), + "Bosch Bno055 9-axis inertial measurement unit (IMU) Temperature", + "onix-bno055.data.temp", + 1, + sampleRate, + "Temperature", + ContinuousChannel::Type::AUX, + bitVolts, + "Celsius", + { "" }); + streamInfos.add(temperatureStream); + + StreamInfo calibrationStatusStream = StreamInfo( + OnixDevice::createStreamName({ port, getHeadstageName(), getName(), "Calibration" }), + "Bosch Bno055 9-axis inertial measurement unit (IMU) Calibration", + "onix-bno055.data.cal", + 1, + sampleRate, + "Calibration", + ContinuousChannel::Type::AUX, + bitVolts, + "", + { "" }); + streamInfos.add(calibrationStatusStream); + + for (int i = 0; i < numFrames; i++) + eventCodes[i] = 0; +} + +int PolledBno055::configureDevice() +{ + if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1; + + deserializer = std::make_unique(DS90UB9x::DES_ADDR, deviceIdx, deviceContext); + uint32_t alias = Bno055Address << 1; + int rc = deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::SlaveID4, alias); + if (rc != ONI_ESUCCESS) return -2; + rc = deserializer->WriteByte((uint32_t)DS90UB9x::DS90UB9xDeserializerI2CRegister::SlaveAlias4, alias); + if (rc != ONI_ESUCCESS) return -2; + + return ONI_ESUCCESS; +} + +bool PolledBno055::updateSettings() +{ + if (!isEnabled()) return true; + + int rc = WriteByte(0x3E, 0x00); // Power mode normal + if (rc != ONI_ESUCCESS) return false; + + rc = WriteByte(0x07, 0x00); // Page ID address 0 + if (rc != ONI_ESUCCESS) return false; + + rc = WriteByte(0x3F, 0x00); // Internal oscillator + if (rc != ONI_ESUCCESS) return false; + + rc = WriteByte(0x41, (uint32_t)axisMap); // Axis map config + if (rc != ONI_ESUCCESS) return false; + + rc = WriteByte(0x42, (uint32_t)axisSign); // Axis sign + if (rc != ONI_ESUCCESS) return false; + + rc = WriteByte(0x3D, 0x0C); // Operation mode is NDOF + if (rc != ONI_ESUCCESS) return false; + + rc = set933I2cRate(i2cRate); + + return rc == ONI_ESUCCESS; +} + +void PolledBno055::startAcquisition() +{ + sampleNumber = 0; + currentFrame = 0; + + if (isEnabled()) + startTimer(timerIntervalInMilliseconds); +} + +void PolledBno055::stopAcquisition() +{ + stopTimer(); + + while (isTimerRunning()) + std::this_thread::sleep_for(std::chrono::milliseconds(10)); +} + +void PolledBno055::addFrame(oni_frame_t* frame) +{ + oni_destroy_frame(frame); +} + +void PolledBno055::addSourceBuffers(OwnedArray& sourceBuffers) +{ + sourceBuffers.add(new DataBuffer(numberOfChannels, (int)sampleRate * bufferSizeInSeconds)); + + bnoBuffer = sourceBuffers.getLast(); +} + +int16_t PolledBno055::readInt16(uint32_t startAddress) +{ + oni_reg_val_t byte1 = 0, byte2 = 0; + + int rc = ReadByte(startAddress, &byte1); + if (rc != ONI_ESUCCESS) return 0; + rc = ReadByte(startAddress + 1, &byte2); + if (rc != ONI_ESUCCESS) return 0; + + return (static_cast(byte2) << 8) | byte1; +} + +void PolledBno055::hiResTimerCallback() +{ + int offset = 0; + + // Euler + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress) * eulerAngleScale; + offset++; + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 2) * eulerAngleScale; + offset++; + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 4) * eulerAngleScale; + offset++; + + // Quaternion + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 6) * quaternionScale; + offset++; + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 8) * quaternionScale; + offset++; + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 10) * quaternionScale; + offset++; + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 12) * quaternionScale; + offset++; + + // Acceleration + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 14) * accelerationScale; + offset++; + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 16) * accelerationScale; + offset++; + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 18) * accelerationScale; + offset++; + + // Gravity + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 20) * accelerationScale; + offset++; + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 22) * accelerationScale; + offset++; + + bnoSamples[offset * numFrames + currentFrame] = readInt16(EulerHeadingLsbAddress + 24) * accelerationScale; + offset++; + + // Temperature + + oni_reg_val_t byte; + ReadByte(EulerHeadingLsbAddress + 26, &byte); + bnoSamples[offset * numFrames + currentFrame] = static_cast(byte); + offset++; + + // Calibration Status + + ReadByte(EulerHeadingLsbAddress + 27, &byte); + bnoSamples[offset * numFrames + currentFrame] = byte; + offset++; + + oni_reg_val_t timestampL = 0, timestampH = 0; + int rc = deviceContext->readRegister(deviceIdx, DS90UB9x::LASTI2CL, ×tampL); + if (rc != ONI_ESUCCESS) return; + rc = (uint64_t)deviceContext->readRegister(deviceIdx, DS90UB9x::LASTI2CH, ×tampH); + if (rc != ONI_ESUCCESS) return; + + bnoTimestamps[currentFrame] = (uint64_t(timestampH) << 32) | uint64_t(timestampL); + + sampleNumbers[currentFrame] = sampleNumber++; + + currentFrame++; + + if (currentFrame >= numFrames) + { + bnoBuffer->addToBuffer(bnoSamples.data(), sampleNumbers, bnoTimestamps, eventCodes, numFrames); + currentFrame = 0; + } + +} diff --git a/Source/Devices/PolledBno055.h b/Source/Devices/PolledBno055.h new file mode 100644 index 0000000..9492191 --- /dev/null +++ b/Source/Devices/PolledBno055.h @@ -0,0 +1,135 @@ +/* + ------------------------------------------------------------------ + + Copyright (C) Open Ephys + + ------------------------------------------------------------------ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#pragma once + +#include "../OnixDevice.h" +#include "../I2CRegisterContext.h" +#include "DS90UB9x.h" +#include "PortController.h" + +#include + +class PolledBno055 : public OnixDevice, + public I2CRegisterContext, + public HighResolutionTimer +{ +public: + + /** Constructor */ + PolledBno055(String name, String headstageName, const oni_dev_idx_t, std::shared_ptr ctx); + + ~PolledBno055() + { + stopTimer(); + } + + int configureDevice() override; + + /** Update the settings of the device */ + bool updateSettings() override; + + /** Starts probe data streaming */ + void startAcquisition() override; + + /** Stops probe data streaming*/ + void stopAcquisition() override; + + void addFrame(oni_frame_t*) override; + + void processFrames() override {}; + + void addSourceBuffers(OwnedArray& sourceBuffers) override; + + void hiResTimerCallback() override; + +private: + + DataBuffer* bnoBuffer; + + static const int Bno055Address = 0x28; + static const int EulerHeadingLsbAddress = 0x1A; + + const double i2cRate = 400e3; + + static const int numberOfBytes = 28; + + const float eulerAngleScale = 1.0f / 16; // 1 degree = 16 LSB + const float quaternionScale = 1.0f / (1 << 14); // 1 = 2^14 LSB + const float accelerationScale = 1.0f / 100; // 1m / s^2 = 100 LSB + + std::unique_ptr deserializer; + + enum class Bno055AxisMap : uint32_t + { + XYZ = 0b00100100, // Specifies that X->X', Y->Y', Z->Z' (chip default). + XZY = 0b00011000, // Specifies that X->X', Z->Y', Y->Z' + YXZ = 0b00100001, // Specifies that Y->X', X->Y', Z->Z' + YZX = 0b00001001, // Specifies that Y->X', Z->Y', X->Z' + ZXY = 0b00010010, // Specifies that Z->X', X->Y', Y->Z' + ZYX = 0b00000110, // Specifies that Z->X', Y->Y', X->Z' + }; + + enum class Bno055AxisSign : uint32_t + { + Default = 0b00000000, // Specifies that all axes are positive (chip default). + MirrorZ = 0b00000001, // Specifies that Z' axis should be mirrored. + MirrorY = 0b00000010, // Specifies that Y' axis should be mirrored. + MirrorX = 0b00000100, // Specifies that X' axis should be mirrored. + }; + + enum class PolledBno055Registers : int32_t + { + EulerAngle = 0x1, // Specifies that the Euler angles will be polled. + Quaternion = 0x2, // Specifies that the quaternion will be polled. + Acceleration = 0x4, // Specifies that the linear acceleration will be polled. + Gravity = 0x8, // Specifies that the gravity vector will be polled. + Temperature = 0x10, // Specifies that the temperature measurement will be polled. + Calibration = 0x20, // Specifies that the sensor calibration status will be polled. + All = EulerAngle | Quaternion | Acceleration | Gravity | Temperature | Calibration, // Specifies that all sensor measurements and calibration status will be polled. + }; + + const Bno055AxisMap axisMap = Bno055AxisMap::XYZ; + const Bno055AxisSign axisSign = Bno055AxisSign::Default; + + static const int numberOfChannels = 3 + 3 + 4 + 3 + 1 + 1; + static constexpr double sampleRate = 30.0; + + static const int timerIntervalInMilliseconds = (int)(1e3 * (1 / sampleRate)); + + static const int numFrames = 2; + + std::array bnoSamples; + + double bnoTimestamps[numFrames]; + int64 sampleNumbers[numFrames]; + uint64 eventCodes[numFrames]; + + unsigned short currentFrame = 0; + int sampleNumber = 0; + + // Given the starting address (i.e., the LSB), read two bytes and convert to an int16_t + int16_t readInt16(uint32_t); + + JUCE_LEAK_DETECTOR(PolledBno055); +}; + diff --git a/Source/Devices/PortController.cpp b/Source/Devices/PortController.cpp index 1d0eddb..1bbdea4 100644 --- a/Source/Devices/PortController.cpp +++ b/Source/Devices/PortController.cpp @@ -23,7 +23,7 @@ #include "PortController.h" PortController::PortController(PortName port_, std::shared_ptr ctx_) : - OnixDevice(getPortName(port_), OnixDeviceType::PORT_CONTROL, (oni_dev_idx_t)port_, ctx_), + OnixDevice(getPortName(port_), BREAKOUT_BOARD_NAME, OnixDeviceType::PORT_CONTROL, (oni_dev_idx_t)port_, ctx_), port(port_) { } @@ -32,9 +32,7 @@ int PortController::configureDevice() { if (deviceContext == nullptr || !deviceContext->isInitialized()) return -5; - deviceContext->writeRegister(deviceIdx, (uint32_t)PortControllerRegister::ENABLE, 1); - - return deviceContext->getLastResult(); + return deviceContext->writeRegister(deviceIdx, (uint32_t)PortControllerRegister::ENABLE, 1); } void PortController::startAcquisition() @@ -90,6 +88,10 @@ DiscoveryParameters PortController::getHeadstageDiscoveryParameters(String heads { return DiscoveryParameters(5.0, 7.0, 1.0, 0.2); } + else if (headstage == NEUROPIXELSV2E_HEADSTAGE_NAME) + { + return DiscoveryParameters(3.3f, 5.5f, 1.0f, 0.2f); + } return DiscoveryParameters(); } @@ -135,7 +137,8 @@ void PortController::setVoltageOverride(double voltage, bool waitToSettle) { if (voltage < 0.0 && voltage > 7.0) { LOGE("Invalid voltage value. Tried to set the port to " + String(voltage) + " V."); return; } - deviceContext->writeRegister((oni_dev_idx_t)port, (oni_reg_addr_t)PortControllerRegister::PORTVOLTAGE, voltage * 10); + int rc = deviceContext->writeRegister((oni_dev_idx_t)port, (oni_reg_addr_t)PortControllerRegister::PORTVOLTAGE, voltage * 10); + if (rc != ONI_ESUCCESS) return; lastVoltageSet = voltage; @@ -146,11 +149,12 @@ void PortController::setVoltage(double voltage) { if (voltage < 0.0 && voltage > 7.0) { LOGE("Invalid voltage value. Tried to set the port to " + String(voltage) + " V."); return; } - deviceContext->writeRegister((oni_dev_idx_t)port, (oni_reg_addr_t)PortControllerRegister::PORTVOLTAGE, 0); + int rc = deviceContext->writeRegister((oni_dev_idx_t)port, (oni_reg_addr_t)PortControllerRegister::PORTVOLTAGE, 0); sleep_for(std::chrono::milliseconds(300)); - if (deviceContext->getLastResult() != ONI_ESUCCESS) return; + if (rc != ONI_ESUCCESS) return; - deviceContext->writeRegister((oni_dev_idx_t)port, (oni_reg_addr_t)PortControllerRegister::PORTVOLTAGE, voltage * 10); + rc = deviceContext->writeRegister((oni_dev_idx_t)port, (oni_reg_addr_t)PortControllerRegister::PORTVOLTAGE, voltage * 10); + if (rc != ONI_ESUCCESS) return; lastVoltageSet = voltage; @@ -159,10 +163,11 @@ void PortController::setVoltage(double voltage) bool PortController::checkLinkState() const { - oni_reg_val_t linkState = deviceContext->readRegister((oni_dev_idx_t)port, (oni_reg_addr_t)PortControllerRegister::LINKSTATE); + oni_reg_val_t linkState; + int rc = deviceContext->readRegister((oni_dev_idx_t)port, (oni_reg_addr_t)PortControllerRegister::LINKSTATE, &linkState); - if (deviceContext->getLastResult() != ONI_ESUCCESS) { return false; } - else if ((linkState & LINKSTATE_SL) == 0) { LOGE("Unable to acquire communication lock."); return false; } + if (rc != ONI_ESUCCESS) { return false; } + else if ((linkState & LINKSTATE_SL) == 0) { LOGD("Unable to acquire communication lock."); return false; } else return true; } diff --git a/Source/Formats/ProbeInterface.h b/Source/Formats/ProbeInterface.h index eca22e1..635b34b 100644 --- a/Source/Formats/ProbeInterface.h +++ b/Source/Formats/ProbeInterface.h @@ -27,7 +27,7 @@ class ProbeInterfaceJson { public: - static bool writeProbeSettingsToJson(File& file, ProbeSettings* settings) + static bool writeProbeSettingsToJson(File& file, ProbeSettings<0, 0>* settings) { DynamicObject output; @@ -49,22 +49,23 @@ class ProbeInterfaceJson for (int elec = 0; elec < settings->electrodeMetadata.size(); elec++) { - ElectrodeMetadata& em = settings->electrodeMetadata.getReference(elec); - int channelIndex = settings->selectedElectrode.indexOf(elec); - int channel = -1; - if (channelIndex > -1) - channel = settings->selectedChannel[channelIndex]; + ElectrodeMetadata& em = settings->electrodeMetadata[elec]; + int channelIndex = settings->selectedElectrode[elec]; + // TODO: Fix this section + //int channel = -1; + //if (channelIndex > -1) + // channel = settings->selectedChannel[channelIndex]; Array contact_position; contact_position.add(em.xpos + 250 * em.shank); contact_position.add(em.ypos); DynamicObject::Ptr contact_shape_param = new DynamicObject; - contact_shape_param->setProperty(Identifier("width"), settings->electrodeMetadata.getReference(elec).site_width); + contact_shape_param->setProperty(Identifier("width"), settings->electrodeMetadata[elec].site_width); contact_positions.add(contact_position); shank_ids.add(String(em.shank)); - device_channel_indices.add(channel); + device_channel_indices.add(0); contact_plane_axes.add(contact_plane_axis); contact_shapes.add("square"); contact_shape_params.add(contact_shape_param.get()); @@ -105,7 +106,7 @@ class ProbeInterfaceJson return true; } - static bool readProbeSettingsFromJson(File& file, ProbeSettings* settings) + static bool readProbeSettingsFromJson(File& file, ProbeSettings<0,0>* settings) { var result; diff --git a/Source/FrameReader.cpp b/Source/FrameReader.cpp index ae15ed5..52d324f 100644 --- a/Source/FrameReader.cpp +++ b/Source/FrameReader.cpp @@ -35,7 +35,7 @@ void FrameReader::run() { oni_frame_t* frame = context->readFrame(); - if (context->getLastResult() < ONI_ESUCCESS) + if (frame == nullptr) { if (threadShouldExit()) return; @@ -48,7 +48,7 @@ void FrameReader::run() for (const auto& source : sources) { - if (frame->dev_idx == source->getDeviceIdx()) + if (frame->dev_idx == source->getDeviceIdx(true)) { source->addFrame(frame); destroyFrame = false; diff --git a/Source/I2CRegisterContext.cpp b/Source/I2CRegisterContext.cpp index 46405e6..3036d9e 100644 --- a/Source/I2CRegisterContext.cpp +++ b/Source/I2CRegisterContext.cpp @@ -23,23 +23,32 @@ #include "I2CRegisterContext.h" I2CRegisterContext::I2CRegisterContext(uint32_t address_, const oni_dev_idx_t devIdx_, std::shared_ptr ctx_) - : deviceIndex(devIdx_), i2caddress(address_) + : deviceIndex(devIdx_), i2cAddress(address_) { i2cContext = ctx_; } -void I2CRegisterContext::WriteByte(uint32_t address, uint32_t value, bool sixteenBitAddress) +int I2CRegisterContext::WriteByte(uint32_t address, uint32_t value, bool sixteenBitAddress) { - uint32_t registerAddress = (address << 7) | (i2caddress & 0x7F); + uint32_t registerAddress = (address << 7) | (i2cAddress & 0x7F); registerAddress |= sixteenBitAddress ? 0x80000000 : 0; - i2cContext->writeRegister(deviceIndex, registerAddress, value); + return i2cContext->writeRegister(deviceIndex, registerAddress, value); } -void I2CRegisterContext::ReadByte(uint32_t address, oni_reg_val_t* value, bool sixteenBitAddress) +int I2CRegisterContext::ReadByte(uint32_t address, oni_reg_val_t* value, bool sixteenBitAddress) { - uint32_t registerAddress = (address << 7) | (i2caddress & 0x7F); + uint32_t registerAddress = (address << 7) | (i2cAddress & 0x7F); registerAddress |= sixteenBitAddress ? 0x80000000 : 0; - *value = i2cContext->readRegister(deviceIndex, registerAddress); + return i2cContext->readRegister(deviceIndex, registerAddress, value); +} + +int I2CRegisterContext::set933I2cRate(double rate) +{ + auto sclTimes = (uint32_t)(std::round(1.0 / (100e-9 * rate))); + int rc = WriteByte((uint32_t)DS90UB9x::DS90UB9xSerializerI2CRegister::SCLHIGH, sclTimes); + if (rc != ONI_ESUCCESS) return rc; + rc = WriteByte((uint32_t)DS90UB9x::DS90UB9xSerializerI2CRegister::SCLLOW, sclTimes); + if (rc != ONI_ESUCCESS) return rc; } \ No newline at end of file diff --git a/Source/I2CRegisterContext.h b/Source/I2CRegisterContext.h index 1820d1b..ec4604b 100644 --- a/Source/I2CRegisterContext.h +++ b/Source/I2CRegisterContext.h @@ -25,6 +25,7 @@ #include "ProcessorHeaders.h" #include "Onix1.h" +#include "Devices/DS90UB9x.h" #include @@ -34,11 +35,11 @@ class I2CRegisterContext I2CRegisterContext(uint32_t address, const oni_dev_idx_t, std::shared_ptr); - void WriteByte(uint32_t address, uint32_t value, bool sixteenBitAddress = false); + int WriteByte(uint32_t address, uint32_t value, bool sixteenBitAddress = false); - void ReadByte(uint32_t address, oni_reg_val_t* value, bool sixteenBitAddress = false); + int ReadByte(uint32_t address, oni_reg_val_t* value, bool sixteenBitAddress = false); - int getLastResult() { return i2cContext->getLastResult(); } + int set933I2cRate(double); protected: std::shared_ptr i2cContext; @@ -47,7 +48,7 @@ class I2CRegisterContext const oni_dev_idx_t deviceIndex; - const uint32_t i2caddress; + const uint32_t i2cAddress; JUCE_LEAK_DETECTOR(I2CRegisterContext); }; diff --git a/Source/NeuropixComponents.h b/Source/NeuropixComponents.h index e29363c..40cef71 100644 --- a/Source/NeuropixComponents.h +++ b/Source/NeuropixComponents.h @@ -25,14 +25,18 @@ #include #include #include +#include #include "UI/ActivityView.h" -# define SAMPLECOUNT 64 -# define MAX_HEADSTAGE_CLK_SAMPLE 3221225475 -# define MAX_ALLOWABLE_TIMESTAMP_JUMP 4 - -# define MAXPACKETS 64 +enum class VisualizationMode +{ + ENABLE_VIEW, + AP_GAIN_VIEW, + LFP_GAIN_VIEW, + REFERENCE_VIEW, + ACTIVITY_VIEW +}; enum class ProbeType { @@ -103,6 +107,24 @@ struct ProbeMetadata bool switchable; }; +struct NeuropixelsV1fValues +{ + static const int numberOfChannels = 384; + static const int numberOfElectrodes = 960; + static const int numberOfShanks = 1; + static const int numberOfSettings = 1; +}; + +struct NeuropixelsV2eValues +{ + static const int numberOfChannels = 384; + static const int electrodesPerShank = 1280; + static const int numberOfShanks = 4; + static const int numberOfElectrodes = numberOfShanks * electrodesPerShank; + static const int numberOfSettings = 2; +}; + +template struct ProbeSettings { void updateProbeSettings(ProbeSettings* newSettings) @@ -121,7 +143,6 @@ struct ProbeSettings selectedBank = newSettings->selectedBank; selectedShank = newSettings->selectedShank; - selectedChannel = newSettings->selectedChannel; selectedElectrode = newSettings->selectedElectrode; electrodeMetadata = newSettings->electrodeMetadata; @@ -134,10 +155,45 @@ struct ProbeSettings { selectedBank.clear(); selectedShank.clear(); - selectedChannel.clear(); selectedElectrode.clear(); } + void selectElectrodes(std::vector electrodes) + { + for (int i = 0; i < electrodes.size(); i++) + { + selectElectrode(electrodes[i]); + } + } + + void selectElectrode(int electrode) + { + Bank bank = electrodeMetadata[electrode].bank; + int channel = electrodeMetadata[electrode].channel; + int shank = electrodeMetadata[electrode].shank; + int global_index = electrodeMetadata[electrode].global_index; + + for (int j = 0; j < electrodeMetadata.size(); j++) + { + if (electrodeMetadata[j].channel == channel) + { + if (electrodeMetadata[j].bank == bank && electrodeMetadata[j].shank == shank) + { + electrodeMetadata[j].status = ElectrodeStatus::CONNECTED; + } + + else + { + electrodeMetadata[j].status = ElectrodeStatus::DISCONNECTED; + } + } + } + + selectedBank[channel] = bank; + selectedShank[channel] = shank; + selectedElectrode[channel] = global_index; + } + Array availableElectrodeConfigurations; Array availableApGains; // Available AP gain values for each channel (if any) Array availableLfpGains; // Available LFP gain values for each channel (if any) @@ -150,13 +206,62 @@ struct ProbeSettings int referenceIndex; bool apFilterState; - Array selectedBank; - Array selectedShank; - Array selectedChannel; - Array selectedElectrode; - Array electrodeMetadata; + std::array selectedBank; + std::array selectedShank; + std::array selectedElectrode; + std::array electrodeMetadata; ProbeType probeType; ProbeMetadata probeMetadata; + + bool isValid = false; +}; + +template +std::vector toBitReversedBytes(std::bitset bits) +{ + std::vector bytes((bits.size() - 1) / 8 + 1); + + for (int i = 0; i < bytes.size(); i++) + { + for (int j = 0; j < 8; j++) + { + bytes[i] |= bits[i * 8 + j] << (8 - j - 1); + } + + // NB: Reverse bytes (http://graphics.stanford.edu/~seander/bithacks.html) + bytes[i] = (unsigned char)((bytes[i] * 0x0202020202ul & 0x010884422010ul) % 1023); + } + + return bytes; +} + +template +class INeuropixel +{ +public: + INeuropixel(int numSettings, int numShanks) : numberOfShanks(numShanks) + { + if (numSettings > 2) return; + + for (int i = 0; i < numSettings; i++) + { + settings.emplace_back(std::make_unique>()); + } + } + + static const int numberOfChannels = ch; + static const int numberOfElectrodes = e; + const int numberOfShanks; + + std::vector>> settings; + + virtual void setSettings(ProbeSettings* settings_, int index) { return; } + + virtual void defineMetadata(ProbeSettings* settings) { return; } + + virtual uint64_t getProbeSerialNumber(int index) { return 0; } + + virtual std::vector selectElectrodeConfiguration(String config) { return {}; } }; diff --git a/Source/Onix1.cpp b/Source/Onix1.cpp index 7dfdcb0..a63ac47 100644 --- a/Source/Onix1.cpp +++ b/Source/Onix1.cpp @@ -29,10 +29,12 @@ Onix1::Onix1(int hostIndex) if (ctx_ == nullptr) throw std::system_error(errno, std::system_category()); - result = oni_init_ctx(ctx_, hostIndex); + int rc = oni_init_ctx(ctx_, hostIndex); - if (result != ONI_ESUCCESS) - throw error_t(result); + if (rc != ONI_ESUCCESS) + throw error_t(rc); + + oni_version(&major, &minor, &patch); } Onix1::~Onix1() @@ -40,14 +42,15 @@ Onix1::~Onix1() oni_destroy_ctx(ctx_); } -void Onix1::updateDeviceTable() +int Onix1::updateDeviceTable() { if (deviceTable.size() > 0) deviceTable.clear(); - auto numDevices = getOption(ONI_OPT_NUMDEVICES); + oni_size_t numDevices; + int rc = getOption(ONI_OPT_NUMDEVICES, &numDevices); - if (numDevices == 0) return; + if (numDevices == 0 || rc != ONI_ESUCCESS) return rc; size_t devicesSize = sizeof(oni_device_t) * numDevices; deviceTable.reserve(devicesSize); @@ -55,39 +58,48 @@ void Onix1::updateDeviceTable() std::vector devices; devices.resize(numDevices); - get_opt_(ONI_OPT_DEVICETABLE, devices.data(), &devicesSize); + rc = get_opt_(ONI_OPT_DEVICETABLE, devices.data(), &devicesSize); + + if (rc != ONI_ESUCCESS) return rc; for (const auto& device : devices) { deviceTable.insert({ device.idx, device }); } + + return rc; } -void Onix1::get_opt_(int option, void* value, size_t* size) +int Onix1::get_opt_(int option, void* value, size_t* size) const { - result = oni_get_opt(ctx_, option, value, size); - if (result != ONI_ESUCCESS) LOGE(oni_error_str(result)); + int rc = oni_get_opt(ctx_, option, value, size); + if (rc != ONI_ESUCCESS) LOGE(oni_error_str(rc)); + return rc; } -oni_reg_val_t Onix1::readRegister(oni_dev_idx_t devIndex, oni_reg_addr_t registerAddress) +int Onix1::readRegister(oni_dev_idx_t devIndex, oni_reg_addr_t registerAddress, oni_reg_val_t* value) const { - oni_reg_val_t value = 0; - result = oni_read_reg(ctx_, devIndex, registerAddress, &value); - if (result != ONI_ESUCCESS) LOGE(oni_error_str(result)); - return value; + int rc = oni_read_reg(ctx_, devIndex, registerAddress, value); + if (rc != ONI_ESUCCESS) LOGE(oni_error_str(rc)); + return rc; } -void Onix1::writeRegister(oni_dev_idx_t devIndex, oni_reg_addr_t registerAddress, oni_reg_val_t value) +int Onix1::writeRegister(oni_dev_idx_t devIndex, oni_reg_addr_t registerAddress, oni_reg_val_t value) const { - result = oni_write_reg(ctx_, devIndex, registerAddress, value); - if (result != ONI_ESUCCESS) LOGE(oni_error_str(result)); + int rc = oni_write_reg(ctx_, devIndex, registerAddress, value); + if (rc != ONI_ESUCCESS) LOGE(oni_error_str(rc)); + return rc; } -oni_frame_t* Onix1::readFrame() +oni_frame_t* Onix1::readFrame() const { - oni_frame_t* frame; - result = oni_read_frame(ctx_, &frame); - if (result < ONI_ESUCCESS) LOGE(oni_error_str(result)); + oni_frame_t* frame = nullptr; + int rc = oni_read_frame(ctx_, &frame); + if (rc < ONI_ESUCCESS) + { + LOGE(oni_error_str(rc)); + return nullptr; + } return frame; } diff --git a/Source/Onix1.h b/Source/Onix1.h index 397b8a0..dc7e9cd 100644 --- a/Source/Onix1.h +++ b/Source/Onix1.h @@ -29,7 +29,8 @@ #include "../../plugin-GUI/Source/Utils/Utils.h" -constexpr char* NEUROPIXELSV1F_HEADSTAGE_NAME = "Neuropixels 1.0f"; +constexpr char* NEUROPIXELSV1F_HEADSTAGE_NAME = "Neuropixels 1.0 Headstage"; +constexpr char* NEUROPIXELSV2E_HEADSTAGE_NAME = "Neuropixels 2.0 Headstage"; constexpr char* BREAKOUT_BOARD_NAME = "Breakout Board"; class error_t : public std::exception @@ -60,43 +61,45 @@ class Onix1 inline bool isInitialized() const { return ctx_ != nullptr; } template - opt_t getOption(int option) + int getOption(int option, opt_t* value) { - opt_t value; size_t len = sizeof(opt_t); - get_opt_(option, &value, &len); - return value; + int rc = get_opt_(option, value, &len); + return rc; } template - void setOption(int option, const opt_t value) + int setOption(int option, const opt_t value) { - result = oni_set_opt(ctx_, option, &value, opt_size_(value)); - if (result != ONI_ESUCCESS) LOGE(oni_error_str(result)); + int rc = oni_set_opt(ctx_, option, &value, opt_size_(value)); + if (rc != ONI_ESUCCESS) LOGE(oni_error_str(rc)); + return rc; } + + int readRegister(oni_dev_idx_t devIndex, oni_reg_addr_t registerAddress, oni_reg_val_t* value) const; - oni_reg_val_t readRegister(oni_dev_idx_t, oni_reg_addr_t); - - void writeRegister(oni_dev_idx_t, oni_reg_addr_t, oni_reg_val_t); + int writeRegister(oni_dev_idx_t, oni_reg_addr_t, oni_reg_val_t) const; device_map_t getDeviceTable() const noexcept { return deviceTable; } - void updateDeviceTable(); + int updateDeviceTable(); - oni_frame_t* readFrame(); + oni_frame_t* readFrame() const; - int getLastResult() const { return result; } + int issueReset() { int val = 1; int rc = setOption(ONI_OPT_RESET, val); return rc; } - void issueReset() { int val = 1; setOption(ONI_OPT_RESET, val); } + String getVersion() { return String(major) + "." + String(minor) + "." + String(patch); } private: /** The ONI ctx object */ oni_ctx ctx_; - device_map_t deviceTable; + int major; + int minor; + int patch; - int result; + device_map_t deviceTable; template size_t opt_size_(opt_t opt) @@ -112,5 +115,5 @@ class Onix1 return len; } - void get_opt_(int option, void* value, size_t* size); + int get_opt_(int option, void* value, size_t* size) const; }; diff --git a/Source/OnixDevice.cpp b/Source/OnixDevice.cpp index 1ae5b3b..9d84f71 100644 --- a/Source/OnixDevice.cpp +++ b/Source/OnixDevice.cpp @@ -22,9 +22,31 @@ #include "OnixDevice.h" -OnixDevice::OnixDevice(String name_, OnixDeviceType type_, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx) +OnixDevice::OnixDevice(String name_, String headstageName, OnixDeviceType type_, const oni_dev_idx_t deviceIdx_, std::shared_ptr ctx) : type(type_), deviceIdx(deviceIdx_) { deviceContext = ctx; name = name_; + m_headstageName = headstageName; + + if (type == OnixDeviceType::NEUROPIXELSV2E || type == OnixDeviceType::POLLEDBNO) + isPassthrough = true; +} + +oni_dev_idx_t OnixDevice::getDeviceIdx(bool getPassthroughIndex) +{ + if (isPassthrough && !getPassthroughIndex) + return getDeviceIndexFromPassthroughIndex(deviceIdx); + else + return deviceIdx; +} + +oni_dev_idx_t OnixDevice::getDeviceIndexFromPassthroughIndex(oni_dev_idx_t passthroughIndex) +{ + oni_dev_idx_t idx = (passthroughIndex - 7) << 8; + + if (type == OnixDeviceType::POLLEDBNO) + idx++; + + return idx; } diff --git a/Source/OnixDevice.h b/Source/OnixDevice.h index b3a7a6d..b7e02a1 100644 --- a/Source/OnixDevice.h +++ b/Source/OnixDevice.h @@ -44,8 +44,9 @@ enum class PortName enum class OnixDeviceType { HS64, BNO, + POLLEDBNO, NEUROPIXELS_1, - NEUROPIXELS_2, + NEUROPIXELSV2E, ADC, PORT_CONTROL, MEMORYMONITOR, @@ -135,7 +136,7 @@ class OnixDevice public: /** Constructor */ - OnixDevice(String name_, OnixDeviceType type_, const oni_dev_idx_t, std::shared_ptr oni_ctx); + OnixDevice(String name_, String headstageName, OnixDeviceType type_, const oni_dev_idx_t, std::shared_ptr oni_ctx); /** Destructor */ ~OnixDevice() { } @@ -161,16 +162,36 @@ class OnixDevice /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ virtual void addSourceBuffers(OwnedArray& sourceBuffers) {}; - const oni_dev_idx_t getDeviceIdx() const { return deviceIdx; } + oni_dev_idx_t getDeviceIdx(bool getPassthroughIndex = false); - OnixDeviceType type; + /** Creates a stream name using the provided inputs, returning a String following the pattern: name[0]-name[1]-name[2]-etc., with all spaces removed */ + static String createStreamName(std::vector names) + { + String streamName; + + for (int i = 0; i < names.size(); i++) + { + streamName += names[i].removeCharacters(" "); + + if (i != names.size() - 1) streamName += "-"; + } + + return streamName; + } + + const OnixDeviceType type; Array streamInfos; const int bufferSizeInSeconds = 10; + String getHeadstageName() { return m_headstageName; } + void setHeadstageName(String headstage) { m_headstageName = headstage; } + protected: + oni_dev_idx_t getDeviceIndexFromPassthroughIndex(oni_dev_idx_t hubIndex); + const oni_dev_idx_t deviceIdx; std::shared_ptr deviceContext; @@ -179,6 +200,9 @@ class OnixDevice String name; bool enabled = true; + bool isPassthrough = false; + + String m_headstageName; JUCE_LEAK_DETECTOR(OnixDevice); }; diff --git a/Source/OnixSource.cpp b/Source/OnixSource.cpp index 8dd355f..13315c2 100644 --- a/Source/OnixSource.cpp +++ b/Source/OnixSource.cpp @@ -59,8 +59,6 @@ OnixSource::OnixSource(SourceNode* sn) : addBooleanParameter(Parameter::PROCESSOR_SCOPE, "passthroughA", "Passthrough", "Enables passthrough mode for e-variant headstages on Port A", false, true); addBooleanParameter(Parameter::PROCESSOR_SCOPE, "passthroughB", "Passthrough", "Enables passthrough mode for e-variant headstages on Port B", false, true); - addBooleanParameter(Parameter::PROCESSOR_SCOPE, "connected", "Connect", "Connect to Onix hardware", false, true); - portA = std::make_shared(PortName::PortA, context); portB = std::make_shared(PortName::PortB, context); @@ -89,6 +87,9 @@ void OnixSource::disconnectDevices(bool updateStreamInfo) devicesFound = false; + if (context != nullptr && context->isInitialized()) + context->setOption(ONIX_OPT_PASSTHROUGH, 0); + if (updateStreamInfo) CoreServices::updateSignalChain(editor); } @@ -105,23 +106,29 @@ void OnixSource::initializeDevices(bool updateStreamInfo) disconnectDevices(false); } - // TODO: How to set passthrough for Port A vs. Port B? - if (getParameter("passthroughA")->getValue() || getParameter("passthroughB")->getValue()) + int val = 0; + + if (getParameter("passthroughA")->getValue()) { - LOGD("Passthrough mode enabled"); - int val = 1; - context->setOption(ONIX_OPT_PASSTHROUGH, val); + LOGD("Passthrough mode enabled for Port A"); + val |= 1 << 0; } - else + + if (getParameter("passthroughB")->getValue()) { - int val = 0; - context->setOption(ONIX_OPT_PASSTHROUGH, val); + LOGD("Passthrough mode enabled for Port B"); + val |= 1 << 2; } + context->setOption(ONIX_OPT_PASSTHROUGH, val); + context->issueReset(); - context->updateDeviceTable(); + int rc = context->updateDeviceTable(); + + if (rc != ONI_ESUCCESS) return; - if (context->getLastResult() != ONI_ESUCCESS) return; + if (portA->configureDevice() != ONI_ESUCCESS) LOGE("Unable to configure Port A."); + if (portB->configureDevice() != ONI_ESUCCESS) LOGE("Unable to configure Port B."); device_map_t deviceMap = context->getDeviceTable(); @@ -134,7 +141,6 @@ void OnixSource::initializeDevices(bool updateStreamInfo) devicesFound = true; - static const String probeLetters = "ABCDEFGHI"; const int bufferSizeInSeconds = 10; int npxProbeIdx = 0; @@ -142,7 +148,7 @@ void OnixSource::initializeDevices(bool updateStreamInfo) { if (device.id == ONIX_NEUROPIX1R0) { - auto np1 = std::make_shared("Probe-" + String::charToString(probeLetters[npxProbeIdx]), index, context); + auto np1 = std::make_shared("Probe" + String(npxProbeIdx++), index, context); int res = np1->configureDevice(); @@ -170,12 +176,12 @@ void OnixSource::initializeDevices(bool updateStreamInfo) sources.emplace_back(np1); headstages.insert({ PortController::getOffsetFromIndex(index), NEUROPIXELSV1F_HEADSTAGE_NAME }); - - npxProbeIdx++; } else if (device.id == ONIX_BNO055) { - auto bno = std::make_shared("BNO055", index, context); + String headstage = headstages.find(PortController::getOffsetFromIndex(index))->second; + + auto bno = std::make_shared("BNO055", headstage, index, context); int result = bno->configureDevice(); @@ -198,11 +204,11 @@ void OnixSource::initializeDevices(bool updateStreamInfo) auto EEPROM = std::make_unique(index, context); uint32_t hsid = EEPROM->GetHeadStageID(); LOGD("Detected headstage ", hsid); - if (hsid == 8) //Npix2.0e headstage, constant needs to be added to onix.h + if (hsid == ONIX_HUB_HSNP2E) { - auto np2 = std::make_shared("Probe-" + String::charToString(probeLetters[npxProbeIdx]), index, context); + auto np2 = std::make_shared("", index, context); int res = np2->configureDevice(); - if (res != 0) + if (res != ONI_ESUCCESS) { if (res == -1) { @@ -211,9 +217,27 @@ void OnixSource::initializeDevices(bool updateStreamInfo) //TODO add other errors if needed continue; } - npxProbeIdx += np2->getNumProbes(); - + sources.emplace_back(np2); + + auto polledBno = std::make_shared("BNO055", NEUROPIXELSV2E_HEADSTAGE_NAME, index, context); + + if (polledBno->configureDevice() != ONI_ESUCCESS) + { + if (res == -1) + { + LOGE("Context is not initialized properly for PolledBno055"); + } + else if (res == -2) + { + LOGE("Error writing bytes for PolledBno055"); + } + continue; + } + + sources.emplace_back(polledBno); + + headstages.insert({ PortController::getOffsetFromIndex(index), NEUROPIXELSV2E_HEADSTAGE_NAME }); } } else if (device.id == ONIX_MEMUSAGE) @@ -308,15 +332,13 @@ void OnixSource::initializeDevices(bool updateStreamInfo) } } - if (portA->configureDevice() != ONI_ESUCCESS) LOGE("Unable to configure Port A."); - if (portB->configureDevice() != ONI_ESUCCESS) LOGE("Unable to configure Port B."); - context->issueReset(); - oni_size_t frameSize = context->getOption(ONI_OPT_MAXREADFRAMESIZE); + oni_size_t frameSize; + rc = context->getOption(ONI_OPT_MAXREADFRAMESIZE, &frameSize); printf("Max. read frame size: %u bytes\n", frameSize); - frameSize = context->getOption(ONI_OPT_MAXWRITEFRAMESIZE); + rc = context->getOption(ONI_OPT_MAXWRITEFRAMESIZE, &frameSize); printf("Max. write frame size: %u bytes\n", frameSize); context->setOption(ONI_OPT_BLOCKREADSIZE, block_read_size); @@ -338,6 +360,19 @@ OnixDeviceVector OnixSource::getDataSources() return devices; } +OnixDeviceVector OnixSource::getEnabledDataSources() +{ + OnixDeviceVector devices{}; + + for (const auto& source : sources) + { + if (source->isEnabled()) + devices.emplace_back(source); + } + + return devices; +} + OnixDeviceVector OnixSource::getDataSourcesFromPort(PortName port) { OnixDeviceVector devices{}; @@ -522,7 +557,7 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel addIndividualStreams(source->streamInfos, dataStreams, deviceInfos, continuousChannels); } - else if (source->type == OnixDeviceType::BNO) + else if (source->type == OnixDeviceType::BNO || source->type == OnixDeviceType::POLLEDBNO) { DeviceInfo::Settings deviceSettings{ source->getName(), @@ -535,7 +570,7 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel deviceInfos->add(new DeviceInfo(deviceSettings)); DataStream::Settings dataStreamSettings{ - "Bno055", + OnixDevice::createStreamName({PortController::getPortName(PortController::getPortFromIndex(source->getDeviceIdx())), source->getHeadstageName(), source->getName()}), "Continuous data from a Bno055 9-axis IMU", "onix-bno055.data", source->streamInfos[0].getSampleRate() @@ -543,7 +578,7 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel addCombinedStreams(dataStreamSettings, source->streamInfos, dataStreams, deviceInfos, continuousChannels); } - else if (source->type == OnixDeviceType::NEUROPIXELS_2) + else if (source->type == OnixDeviceType::NEUROPIXELSV2E) { DeviceInfo::Settings deviceSettings{ source->getName(), @@ -712,8 +747,8 @@ bool OnixSource::isReady() } uint32_t val = 2; - context->setOption(ONI_OPT_RESETACQCOUNTER, val); - if (context->getLastResult() != ONI_ESUCCESS) return false; + int rc = context->setOption(ONI_OPT_RESETACQCOUNTER, val); + if (rc != ONI_ESUCCESS) return false; return true; } @@ -722,24 +757,17 @@ bool OnixSource::startAcquisition() { frameReader.reset(); - OnixDeviceVector devices; - - for (const auto& source : sources) - { - if (!source->isEnabled()) continue; - - devices.emplace_back(source); - } + enabledSources = getEnabledDataSources(); - devices.emplace_back(portA); - devices.emplace_back(portB); + enabledSources.emplace_back(portA); + enabledSources.emplace_back(portB); - for (const auto& source : devices) + for (const auto& source : enabledSources) { source->startAcquisition(); } - frameReader = std::make_unique(devices, context); + frameReader = std::make_unique(enabledSources, context); frameReader->startThread(); startThread(); @@ -758,21 +786,18 @@ bool OnixSource::stopAcquisition() if (!portA->getErrorFlag() && !portB->getErrorFlag()) waitForThreadToExit(2000); - if (devicesFound) - { - oni_size_t reg = 0; - context->setOption(ONI_OPT_RUNNING, reg); - } + auto polledBno055 = getDevice(OnixDeviceType::POLLEDBNO); - for (const auto& source : sources) - { - if (!source->isEnabled()) continue; + if (polledBno055 != nullptr && polledBno055->isEnabled()) + polledBno055->stopAcquisition(); // NB: Polled BNO must be stopped before other devices to ensure there are no stream clashes + for (const auto& source : enabledSources) + { source->stopAcquisition(); } - portA->stopAcquisition(); - portB->stopAcquisition(); + oni_size_t reg = 0; + context->setOption(ONI_OPT_RUNNING, reg); for (auto buffers : sourceBuffers) buffers->clear(); @@ -803,11 +828,11 @@ bool OnixSource::stopAcquisition() bool OnixSource::updateBuffer() { - for (const auto& source : sources) + for (const auto& source : enabledSources) { - if (!source->isEnabled()) continue; - source->processFrames(); + + if (threadShouldExit()) return true; } portA->processFrames(); diff --git a/Source/OnixSource.h b/Source/OnixSource.h index 59a56f4..99ffbf1 100644 --- a/Source/OnixSource.h +++ b/Source/OnixSource.h @@ -27,8 +27,8 @@ #include "Onix1.h" #include "OnixDevice.h" #include "OnixSourceEditor.h" -#include "Devices/DeviceList.h" #include "FrameReader.h" +#include "Devices/PortController.h" /** @@ -96,6 +96,8 @@ class OnixSource : public DataThread OnixDeviceVector getDataSources(); + OnixDeviceVector getEnabledDataSources(); + OnixDeviceVector getDataSourcesFromPort(PortName port); OnixDeviceVector getDataSourcesFromOffset(int offset); @@ -108,6 +110,8 @@ class OnixSource : public DataThread std::map getHeadstageMap(); + String getLiboniVersion() { if (context != nullptr && context->isInitialized()) return context->getVersion(); else return ""; } + void updateSourceBuffers(); // DataThread Methods @@ -122,6 +126,7 @@ class OnixSource : public DataThread /** Available data sources */ OnixDeviceVector sources; + OnixDeviceVector enabledSources; /** Available headstages, indexed by their offset value */ std::map headstages; diff --git a/Source/OnixSourceCanvas.cpp b/Source/OnixSourceCanvas.cpp index 2db1685..072f4d2 100644 --- a/Source/OnixSourceCanvas.cpp +++ b/Source/OnixSourceCanvas.cpp @@ -21,38 +21,14 @@ */ #include "OnixSourceCanvas.h" - -CustomTabButton::CustomTabButton(const String& name, TabbedComponent* parent, bool isTopLevel_) : - TabBarButton(name, parent->getTabbedButtonBar()), - isTopLevel(isTopLevel_) -{ -} - -void CustomTabButton::paintButton(Graphics& g, - bool isMouseOver, - bool isMouseDown) -{ - getTabbedButtonBar().setTabBackgroundColour(getIndex(), Colours::grey); - - getLookAndFeel().drawTabButton(*this, g, isMouseOver, isMouseDown); -} - -CustomTabComponent::CustomTabComponent(OnixSourceEditor* editor_, bool isTopLevel_) : - TabbedComponent(TabbedButtonBar::TabsAtTop), - editor(editor_), - isTopLevel(isTopLevel_) -{ - setTabBarDepth(26); - setOutline(0); - setIndent(0); -} +#include "OnixSource.h" OnixSourceCanvas::OnixSourceCanvas(GenericProcessor* processor_, OnixSourceEditor* editor_, OnixSource* onixSource_) : Visualizer(processor_), editor(editor_), source(onixSource_) { - topLevelTabComponent = std::make_unique(editor, true); + topLevelTabComponent = std::make_unique(true); addAndMakeVisible(topLevelTabComponent.get()); addHub(BREAKOUT_BOARD_NAME, 0); @@ -60,7 +36,7 @@ OnixSourceCanvas::OnixSourceCanvas(GenericProcessor* processor_, OnixSourceEdito CustomTabComponent* OnixSourceCanvas::addTopLevelTab(String tabName, int index) { - CustomTabComponent* tab = new CustomTabComponent(editor, false); + CustomTabComponent* tab = new CustomTabComponent(false); topLevelTabComponent->addTab(tabName, Colours::grey, tab, true, index); tab->setName(tabName); @@ -70,11 +46,6 @@ CustomTabComponent* OnixSourceCanvas::addTopLevelTab(String tabName, int index) return tab; } -Parameter* OnixSourceCanvas::getSourceParameter(String name) -{ - return source->getParameter(name); -} - void OnixSourceCanvas::addHub(String hubName, int offset) { CustomTabComponent* tab = nullptr; @@ -85,9 +56,9 @@ void OnixSourceCanvas::addHub(String hubName, int offset) { tab = addTopLevelTab(getTopLevelTabName(port, hubName), (int)port); - devices.emplace_back(std::make_shared("Probe-A", offset, nullptr)); - devices.emplace_back(std::make_shared("Probe-B", offset + 1, nullptr)); - devices.emplace_back(std::make_shared("BNO055", offset + 2, nullptr)); + devices.emplace_back(std::make_shared("Probe0", offset, nullptr)); + devices.emplace_back(std::make_shared("Probe1", offset + 1, nullptr)); + devices.emplace_back(std::make_shared("BNO055", hubName, offset + 2, nullptr)); } else if (hubName == BREAKOUT_BOARD_NAME) { @@ -98,6 +69,15 @@ void OnixSourceCanvas::addHub(String hubName, int offset) devices.emplace_back(std::make_shared("Digital IO", 7, nullptr)); devices.emplace_back(std::make_shared("Harp Sync Input", 12, nullptr)); } + else if (hubName == NEUROPIXELSV2E_HEADSTAGE_NAME) + { + const int passthroughIndex = (offset >> 8) + 7; + + tab = addTopLevelTab(getTopLevelTabName(port, hubName), (int)port); + + devices.emplace_back(std::make_shared("", passthroughIndex, nullptr)); + devices.emplace_back(std::make_shared("BNO055", hubName, passthroughIndex, nullptr)); + } if (tab != nullptr && devices.size() > 0) { @@ -114,32 +94,42 @@ void OnixSourceCanvas::populateSourceTabs(CustomTabComponent* tab, OnixDeviceVec if (device->type == OnixDeviceType::NEUROPIXELS_1) { auto neuropixInterface = std::make_shared(std::static_pointer_cast(device), editor, this); - addInterfaceToTab(getDeviceTabName(device), tab, neuropixInterface); + addInterfaceToTab(device->getName(), tab, neuropixInterface); } else if (device->type == OnixDeviceType::BNO) { auto bno055Interface = std::make_shared(std::static_pointer_cast(device), editor, this); - addInterfaceToTab(getDeviceTabName(device), tab, bno055Interface); + addInterfaceToTab(device->getName(), tab, bno055Interface); } else if (device->type == OnixDeviceType::OUTPUTCLOCK) { auto outputClockInterface = std::make_shared(std::static_pointer_cast(device), editor, this); - addInterfaceToTab(getDeviceTabName(device), tab, outputClockInterface); + addInterfaceToTab(device->getName(), tab, outputClockInterface); } else if (device->type == OnixDeviceType::HARPSYNCINPUT) { auto harpSyncInputInterface = std::make_shared(std::static_pointer_cast(device), editor, this); - addInterfaceToTab(getDeviceTabName(device), tab, harpSyncInputInterface); + addInterfaceToTab(device->getName(), tab, harpSyncInputInterface); } else if (device->type == OnixDeviceType::ANALOGIO) { auto analogIOInterface = std::make_shared(std::static_pointer_cast(device), editor, this); - addInterfaceToTab(getDeviceTabName(device), tab, analogIOInterface); + addInterfaceToTab(device->getName(), tab, analogIOInterface); } else if (device->type == OnixDeviceType::DIGITALIO) { auto digitalIOInterface = std::make_shared(std::static_pointer_cast(device), editor, this); - addInterfaceToTab(getDeviceTabName(device), tab, digitalIOInterface); + addInterfaceToTab(device->getName(), tab, digitalIOInterface); + } + else if (device->type == OnixDeviceType::NEUROPIXELSV2E) + { + auto npxv2eInterface = std::make_shared(std::static_pointer_cast(device), editor, this); + addInterfaceToTab(device->getHeadstageName(), tab, npxv2eInterface); + } + else if (device->type == OnixDeviceType::POLLEDBNO) + { + auto polledBnoInterface = std::make_shared(std::static_pointer_cast(device), editor, this); + addInterfaceToTab(device->getName(), tab, polledBnoInterface); } } } @@ -147,7 +137,19 @@ void OnixSourceCanvas::populateSourceTabs(CustomTabComponent* tab, OnixDeviceVec void OnixSourceCanvas::addInterfaceToTab(String tabName, CustomTabComponent* tab, std::shared_ptr interface_) { settingsInterfaces.emplace_back(interface_); - tab->addTab(tabName, Colours::darkgrey, createCustomViewport(interface_.get()), true); + tab->addTab(tabName, Colours::darkgrey, CustomViewport::createCustomViewport(interface_.get()), true); +} + +bool OnixSourceCanvas::compareDeviceNames(String dev1, String dev2) +{ + StringRef charsToTrim = "-ABCD"; + + if (dev1 == dev2) + return true; + else if (dev1.trimCharactersAtEnd(charsToTrim) == dev2.trimCharactersAtEnd(charsToTrim)) + return true; + else + return false; } void OnixSourceCanvas::updateSettingsInterfaceDataSource(std::shared_ptr device) @@ -157,7 +159,7 @@ void OnixSourceCanvas::updateSettingsInterfaceDataSource(std::shared_ptrgetDeviceIdx() == settingsInterfaces[j]->device->getDeviceIdx() && - device->getName() == settingsInterfaces[j]->device->getName()) + compareDeviceNames(device->getName(), settingsInterfaces[j]->device->getName())) { ind = j; break; @@ -176,9 +178,10 @@ void OnixSourceCanvas::updateSettingsInterfaceDataSource(std::shared_ptr(device); auto npx1Selected = std::static_pointer_cast(settingsInterfaces[ind]->device); - npx1Found->setSettings(npx1Selected->settings.get()); + npx1Found->setSettings(npx1Selected->settings[0].get()); npx1Found->adcCalibrationFilePath = npx1Selected->adcCalibrationFilePath; npx1Found->gainCalibrationFilePath = npx1Selected->gainCalibrationFilePath; + npx1Found->setCorrectOffset(npx1Selected->getCorrectOffset()); } else if (device->type == OnixDeviceType::OUTPUTCLOCK) { @@ -198,6 +201,17 @@ void OnixSourceCanvas::updateSettingsInterfaceDataSource(std::shared_ptrsetChannelDirection(i, analogIOSelected->getChannelDirection(i)); } } + else if (device->type == OnixDeviceType::NEUROPIXELSV2E) + { + auto npx2Found = std::static_pointer_cast(device); + auto npx2Selected = std::static_pointer_cast(settingsInterfaces[ind]->device); + npx2Found->setSettings(npx2Selected->settings[0].get(), 0); + npx2Found->setSettings(npx2Selected->settings[1].get(), 1); + npx2Found->setGainCorrectionFile(0, npx2Selected->getGainCorrectionFile(0)); + npx2Found->setGainCorrectionFile(1, npx2Selected->getGainCorrectionFile(1)); + + std::static_pointer_cast(settingsInterfaces[ind])->updateDevice(npx2Found); + } device->setEnabled(settingsInterfaces[ind]->device->isEnabled()); settingsInterfaces[ind]->device.reset(); @@ -209,11 +223,6 @@ String OnixSourceCanvas::getTopLevelTabName(PortName port, String headstage) return PortController::getPortName(port) + ": " + headstage; } -String OnixSourceCanvas::getDeviceTabName(std::shared_ptr device) -{ - return String(device->getDeviceIdx()) + ": " + device->getName(); -} - Array OnixSourceCanvas::getHeadstageTabs() { Array tabs; @@ -226,13 +235,6 @@ Array OnixSourceCanvas::getHeadstageTabs() return tabs; } -CustomViewport* OnixSourceCanvas::createCustomViewport(SettingsInterface* settingsInterface) -{ - Rectangle bounds = settingsInterface->getBounds(); - - return new CustomViewport(settingsInterface, bounds.getWidth(), bounds.getHeight()); -} - void OnixSourceCanvas::refresh() { repaint(); @@ -331,6 +333,8 @@ void OnixSourceCanvas::askKeepUpdate(int offset, String foundHeadstage, OnixDevi { String selectedHeadstage = editor->getHeadstageSelected(offset); + if (selectedHeadstage == foundHeadstage) return; + String msg = "Headstage " + selectedHeadstage + " is selected on " + PortController::getPortName(offset) + ". "; msg += "However, headstage " + foundHeadstage + " was found on " + PortController::getPortName(offset) + ". \n\n"; msg += "Select one of the options below to continue:\n"; diff --git a/Source/OnixSourceCanvas.h b/Source/OnixSourceCanvas.h index c7bbced..54b22db 100644 --- a/Source/OnixSourceCanvas.h +++ b/Source/OnixSourceCanvas.h @@ -25,57 +25,14 @@ #include #include "UI/InterfaceList.h" +#include "UI/CustomTabComponent.h" #include "OnixSourceEditor.h" -#include "OnixSource.h" class OnixSource; /** - - TabBarButton with custom appearance - -*/ -class CustomTabButton : public TabBarButton -{ -public: - /** Constructor */ - CustomTabButton(const String& name, TabbedComponent* parent, bool isTopLevel_); - - /** Paints the button */ - void paintButton(Graphics& g, bool isMouseOver, bool isMouseDown) override; - -private: - bool isTopLevel; -}; - -/** - - Adds a callback when tab is changed - -*/ - -class CustomTabComponent : public TabbedComponent -{ -public: - /** Constructor */ - CustomTabComponent(OnixSourceEditor* editor_, bool isTopLevel_); - - /** Create tab buttons*/ - TabBarButton* createTabButton(const juce::String& name, int index) override - { - return new CustomTabButton(name, this, isTopLevel); - } - -private: - OnixSourceEditor* editor; - bool isTopLevel; -}; - -/** - Holds the visualizer for additional probe settings - */ class OnixSourceCanvas : public Visualizer { @@ -129,12 +86,6 @@ class OnixSourceCanvas : public Visualizer /** Sets bounds of sub-components*/ void resized(); - /** Get the given parameter from the source node */ - Parameter* getSourceParameter(String name); - - /** Creates a custom viewport for the given interface */ - CustomViewport* createCustomViewport(SettingsInterface* settingsInterface); - Array getHeadstageTabs(); std::map createSelectedMap(std::vector>); @@ -160,9 +111,10 @@ class OnixSourceCanvas : public Visualizer void updateSettingsInterfaceDataSource(std::shared_ptr); - String getTopLevelTabName(PortName port, String headstage); + // Compare two device names, ignoring the "-X" if it exists + bool compareDeviceNames(String dev1, String dev2); - String getDeviceTabName(std::shared_ptr device); + String getTopLevelTabName(PortName port, String headstage); /** Create an alert window that asks whether to keep the selected headstage on the given port, diff --git a/Source/OnixSourceEditor.cpp b/Source/OnixSourceEditor.cpp index 51777a3..963c7a0 100644 --- a/Source/OnixSourceEditor.cpp +++ b/Source/OnixSourceEditor.cpp @@ -24,7 +24,7 @@ #include "OnixSource.h" OnixSourceEditor::OnixSourceEditor(GenericProcessor* parentNode, OnixSource* source_) - : VisualizerEditor(parentNode, "Onix Source", 220), source(source_) + : VisualizerEditor(parentNode, "Onix Source", 240), source(source_) { canvas = nullptr; @@ -44,7 +44,7 @@ OnixSourceEditor::OnixSourceEditor(GenericProcessor* parentNode, OnixSource* sou addAndMakeVisible(portLabelA.get()); headstageComboBoxA = std::make_unique("headstageComboBoxA"); - headstageComboBoxA->setBounds(portLabelA->getRight() + 2, portLabelA->getY(), 120, portLabelA->getHeight()); + headstageComboBoxA->setBounds(portLabelA->getRight() + 2, portLabelA->getY(), 140, portLabelA->getHeight()); headstageComboBoxA->addListener(this); headstageComboBoxA->setTooltip("Select the headstage connected to port A."); addHeadstageComboBoxOptions(headstageComboBoxA.get()); @@ -83,7 +83,6 @@ OnixSourceEditor::OnixSourceEditor(GenericProcessor* parentNode, OnixSource* sou headstageComboBoxB->addListener(this); headstageComboBoxB->setTooltip("Select the headstage connected to port B."); addHeadstageComboBoxOptions(headstageComboBoxB.get()); - headstageComboBoxB->setSelectedId(1, dontSendNotification); addAndMakeVisible(headstageComboBoxB.get()); @@ -118,6 +117,13 @@ OnixSourceEditor::OnixSourceEditor(GenericProcessor* parentNode, OnixSource* sou connectButton->setTooltip("Press to connect or disconnect from Onix hardware"); connectButton->addListener(this); addAndMakeVisible(connectButton.get()); + + liboniVersionLabel = std::make_unique