diff --git a/Ph2C_notes.txt b/Ph2C_notes.txt new file mode 100644 index 0000000..257fb94 --- /dev/null +++ b/Ph2C_notes.txt @@ -0,0 +1,49 @@ +3-28-2024 +--- + +Upgraded QBSC firmware from .189 to .190 +Compiled plugin w/ API 3.67 by commenting out all code related to setting SYNC param. +Initially plugin wouldn't load. Dependency walker showed it was linking to NeuropixAPI_x64_3_67_dbg.dll. +Renaming NeuropixAPI_x64_3_67.dll to NeuropixAPI_x64_3_67_dbg.dll enabled the plugin to load properly. + +I was able to connect to one headstage and one probe: + +[open-ephys] Scanning for devices... +[open-ephys] Found 1 device. +[open-ephys] Opening device on slot 4 +[open-ephys] Opened BS on slot 4 +[open-ephys] BS firmware: 2.0169 +[open-ephys] Searching for probes... +[open-ephys] Got HS part #: NPM_HSTC_ext <-- Do we ignore this part #? +[open-ephys] Got HS part #: NPM_HS_32 +[open-ephys] Found 2.0 Phase 2C dual-dock headstage on port: 1 +[open-ephys] No headstage detected on port: 3 +[open-ephys] No headstage detected on port: 4 +[open-ephys] Found probe part number: NP2020 +[open-ephys] Found probe serial number: 22053112201 + +On subsequent connections randomly getting either error code 8 OR 2.0 probe headstage number: + +[open-ephys] ***detectHeadstage failed w/ error code: 8 +[open-ephys] Got HS part #: NPM_HS_01 + + +Downgrading QBSC firmware back to .189 no longer gives wrong HS PN but rarely still getting error 8. +Created Ph2C Geometry based off NP2 multi shank class and Ph2C User Manual to validate probe. +Added inner loop to read packets by base / source type 384 channels at a time to fill a 384 * 4 total buffer. +Currently assuming packet count for each base is the same and not resetting between bases. Need to validate this. + +Changes required for compatibility with current plugin paradigm +--- + +1. Upgrade parallelized PortChecker b/c ports are no longer independent and connecting to multiple Ph2C probes will require extra checks. +We either have to scan ports serially and check that NPM_HS_32 is on port 1 or 3, if so, ensure that NPM_HSTC_ext is found on 3 or 4 respectively. +Otherwise, keep the parallel jobs but then validate port indeces before initiating connection to probes. + +2. Modify basestation view in plugin editor. +If NP2020 detected on port 1/3, show as dual dock and remove port circle on 2/4 respectively. + +Questions for Bill/Jan: + +1. What to do about port 2/4 status LED, it currently remains red even if port 1/3 is connected/acquiring + TODO: Try to open port 2/4 anyway and see if it is able to connect/change the status LED. \ No newline at end of file diff --git a/Resources/imec-firmware-for-plugin-0.6.x/imec-firmware-for-plugin-0.6.x/BS_FPGA_B169.bin b/Resources/imec-firmware-for-plugin-0.6.x/imec-firmware-for-plugin-0.6.x/BS_FPGA_B169.bin new file mode 100644 index 0000000..b0e6e20 Binary files /dev/null and b/Resources/imec-firmware-for-plugin-0.6.x/imec-firmware-for-plugin-0.6.x/BS_FPGA_B169.bin differ diff --git a/Resources/imec-firmware-for-plugin-0.6.x/imec-firmware-for-plugin-0.6.x/QBSC_FPGA_B189.bin b/Resources/imec-firmware-for-plugin-0.6.x/imec-firmware-for-plugin-0.6.x/QBSC_FPGA_B189.bin new file mode 100644 index 0000000..a6739c3 Binary files /dev/null and b/Resources/imec-firmware-for-plugin-0.6.x/imec-firmware-for-plugin-0.6.x/QBSC_FPGA_B189.bin differ diff --git a/Source/Basestations/Basestation_v3.cpp b/Source/Basestations/Basestation_v3.cpp index 60b4078..fecbe61 100644 --- a/Source/Basestations/Basestation_v3.cpp +++ b/Source/Basestations/Basestation_v3.cpp @@ -33,6 +33,7 @@ along with this program. If not, see . #include "../Headstages/Headstage2.h" #include "../Headstages/Headstage_Analog128.h" #include "../Headstages/Headstage_Custom384.h" +#include "../Headstages/Headstage_Ph2C.h" #define MAXLEN 50 @@ -129,6 +130,11 @@ ThreadPoolJob::JobStatus PortChecker::runJob() LOGC(" Found 2.0 dual-dock headstage on port: ", port); headstage = new Headstage2(basestation, port); } + else if (hsPartNumber == "NPM_HS_32") //Ph2C headstage + { + LOGC(" Found 2.0 Phase 2C dual-dock headstage on port: ", port); + headstage = new Headstage_Ph2C(basestation, port); + } else { headstage = nullptr; diff --git a/Source/Headstages/Headstage_Ph2C.cpp b/Source/Headstages/Headstage_Ph2C.cpp new file mode 100644 index 0000000..791eb68 --- /dev/null +++ b/Source/Headstages/Headstage_Ph2C.cpp @@ -0,0 +1,119 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2024 Allen Institute for Brain Science and 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 "Headstage_Ph2C.h" +#include "../Probes/Neuropixels_Ph2C.h" + +#define MAXLEN 50 + +void Headstage_Ph2C::getInfo() +{ + + int version_major; + int version_minor; + + errorCode = Neuropixels::getHSVersion(basestation->slot, port, &version_major, &version_minor); + + info.version = String(version_major) + "." + String(version_minor); + + errorCode = Neuropixels::readHSSN(basestation->slot, port, &info.serial_number); + + char pn[MAXLEN]; + errorCode = Neuropixels::readHSPN(basestation->slot, port, pn, MAXLEN); + + info.part_number = String(pn); + +} + + +void Flex_Ph2C::getInfo() +{ + + int version_major; + int version_minor; + + errorCode = Neuropixels::getFlexVersion(headstage->basestation->slot, + headstage->port, + dock, + &version_major, + &version_minor); + + info.version = String(version_major) + "." + String(version_minor); + + char pn[MAXLEN]; + errorCode = Neuropixels::readFlexPN(headstage->basestation->slot, + headstage->port, + dock, + pn, + MAXLEN); + + info.part_number = String(pn); + +} + + +Headstage_Ph2C::Headstage_Ph2C(Basestation* bs_, int port) : Headstage(bs_, port) +{ + getInfo(); + + int count; + + Neuropixels::getHSSupportedProbeCount(basestation->slot, port, &count); + + for (int dock = 1; dock <= count; dock++) + { + bool flexDetected; + + Neuropixels::detectFlex(basestation->slot, port, dock, &flexDetected); + + if (flexDetected) + { + flexCables.add(new Flex_Ph2C(this, dock)); + Neuropixels_Ph2C* probe = new Neuropixels_Ph2C(basestation, this, flexCables.getLast(), dock); + + if (probe->isValid) + { + probe->setStatus(SourceStatus::CONNECTING); + probes.add(probe); + } + else + { + delete probe; + probes.add(nullptr); + } + + } + else { + probes.add(nullptr); + } + + } + +} + +Flex_Ph2C::Flex_Ph2C(Headstage* hs_, int dock) : Flex(hs_, dock) +{ + getInfo(); + + errorCode = Neuropixels::SUCCESS; +} \ No newline at end of file diff --git a/Source/Headstages/Headstage_Ph2C.h b/Source/Headstages/Headstage_Ph2C.h new file mode 100644 index 0000000..35169cc --- /dev/null +++ b/Source/Headstages/Headstage_Ph2C.h @@ -0,0 +1,52 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2024 Allen Institute for Brain Science and 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 . + +*/ + +#ifndef __NEUROPIXHSP2C_H_2C4C2D67__ +#define __NEUROPIXHSP2C_H_2C4C2D67__ + + +#include "../API/v3/NeuropixAPI.h" +#include "../NeuropixComponents.h" + + +class Headstage_Ph2C : public Headstage +{ +public: + Headstage_Ph2C::Headstage_Ph2C(Basestation*, int port); + void getInfo() override; + bool hasTestModule() override { return false; } + void runTestModule() override {} + + Neuropixels::NP_ErrorCode errorCode; +}; + +class Flex_Ph2C : public Flex +{ +public: + Flex_Ph2C::Flex_Ph2C(Headstage*, int dock); + void getInfo() override; + + Neuropixels::NP_ErrorCode errorCode; +}; + +#endif // __NEUROPIXHSP2C_H_2C4C2D67__ \ No newline at end of file diff --git a/Source/NeuropixComponents.h b/Source/NeuropixComponents.h index a155087..80169c6 100644 --- a/Source/NeuropixComponents.h +++ b/Source/NeuropixComponents.h @@ -89,7 +89,8 @@ enum class ProbeType { UHD2, NP2_1, NP2_4, - OPTO + OPTO, + PH2C }; enum class SourceStatus { diff --git a/Source/Probes/Geometry.cpp b/Source/Probes/Geometry.cpp index 48af838..3828ea7 100644 --- a/Source/Probes/Geometry.cpp +++ b/Source/Probes/Geometry.cpp @@ -28,6 +28,8 @@ bool Geometry::forPartNumber(String PN, ProbeMetadata& pm) { + LOGC("Validating part number: ", PN); + bool found_valid_part_number = true; if (PN.equalsIgnoreCase("NP1010") @@ -92,6 +94,9 @@ bool Geometry::forPartNumber(String PN, else if (PN.equalsIgnoreCase("NP1110")) UHD(true, 8, 6, em, pm); // UHD2 - switchable, 8 cols, 6 um spacing + else if (PN.equalsIgnoreCase("NP2020")) + PH2C(em, pm); + else found_valid_part_number = false; @@ -846,4 +851,209 @@ void Geometry::OPTO(Array& electrodeMetadata, } } +} + +void Geometry::PH2C(Array& electrodeMetadata, + ProbeMetadata& probeMetadata) +{ + + int shank_count = 4; + + probeMetadata.type = ProbeType::PH2C; + probeMetadata.name = " Neuropixels 2.0 Ph2C - Passive"; + + + 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(); + + probeMetadata.shank_count = shank_count; + probeMetadata.electrodes_per_shank = 1280; + probeMetadata.rows_per_shank = 1280 / 2; + probeMetadata.columns_per_shank = 2; + probeMetadata.shankOutline = path; + probeMetadata.num_adcs = 96; + + probeMetadata.availableBanks = + { Bank::A, + Bank::B, + Bank::C, + Bank::D, + Bank::OFF //disconnected + }; + + for (int i = 0; i < probeMetadata.electrodes_per_shank * probeMetadata.shank_count; i++) + { + ElectrodeMetadata metadata; + + metadata.global_index = i; + + metadata.shank = i / probeMetadata.electrodes_per_shank; + metadata.shank_local_index = i % 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; + + if (i < 384 * 4) + { + 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; // 48-96 (Bank 0-3) + break; + case 2: + metadata.channel = block_index + 48 * 3; // 144-192 (Bank 0-3) + break; + case 3: + metadata.channel = block_index + 48 * 5; // 240-27 + 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; + } + } 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; + } + } 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; // disable internal reference + + electrodeMetadata.add(metadata); + } } \ No newline at end of file diff --git a/Source/Probes/Geometry.h b/Source/Probes/Geometry.h index d4af457..7c28a67 100644 --- a/Source/Probes/Geometry.h +++ b/Source/Probes/Geometry.h @@ -88,6 +88,11 @@ class Geometry Array& esm, ProbeMetadata& pm); + /** Neuropixels PH2C + */ + static void PH2C(Array& em, + ProbeMetadata& pm); + }; #endif //__GEOMETRY_H_2C4C2D67__ \ No newline at end of file diff --git a/Source/Probes/Neuropixels_Ph2C.cpp b/Source/Probes/Neuropixels_Ph2C.cpp new file mode 100644 index 0000000..fcd807f --- /dev/null +++ b/Source/Probes/Neuropixels_Ph2C.cpp @@ -0,0 +1,833 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2023 Allen Institute for Brain Science and 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 "Neuropixels_Ph2C.h" +#include "Geometry.h" + +#include "../NeuropixThread.h" + +#define MAXLEN 50 + +void Neuropixels_Ph2C::getInfo() +{ + errorCode = Neuropixels::readProbeSN(basestation->slot, headstage->port, dock, &info.serial_number); + + char pn[MAXLEN]; + errorCode = Neuropixels::readProbePN(basestation->slot_c, headstage->port_c, dock, pn, MAXLEN); + + LOGC(" Found probe part number: ", pn); + LOGC(" Found probe serial number: ", info.serial_number); + + info.part_number = String(pn); +} + +Neuropixels_Ph2C::Neuropixels_Ph2C(Basestation* bs, Headstage* hs, Flex* fl, int dock) : Probe(bs, hs, fl, dock) +{ + getInfo(); + + setStatus(SourceStatus::DISCONNECTED); + + customName.probeSpecific = String(info.serial_number); + + LOGC("Trying to open probe, slot: ", basestation->slot, " port: ", headstage->port, " dock: ", dock); + + if (Geometry::forPartNumber(info.part_number, electrodeMetadata, probeMetadata)) + { + name = probeMetadata.name; + type = probeMetadata.type; + + settings.probe = this; + + settings.availableBanks = probeMetadata.availableBanks; + + settings.apGainIndex = -1; + settings.lfpGainIndex = -1; + settings.referenceIndex = 0; + settings.apFilterState = false; + + channel_count = 384 * 4; + lfp_sample_rate = 2500.0f; // not used + ap_sample_rate = 30000.0f; + + for (int i = 0; i < channel_count; i++) + { + settings.selectedBank.add(Bank::A); + settings.selectedChannel.add(electrodeMetadata[i].channel); + settings.selectedShank.add(0); + settings.selectedElectrode.add(electrodeMetadata[i].global_index); + + } + + if (probeMetadata.shank_count == 1) + { + settings.availableReferences.add("Ext"); + settings.availableReferences.add("Tip"); + + settings.availableElectrodeConfigurations.add("Bank A"); + settings.availableElectrodeConfigurations.add("Bank B"); + settings.availableElectrodeConfigurations.add("Bank C"); + settings.availableElectrodeConfigurations.add("Bank D"); + } + else { + settings.availableReferences.add("Ext"); + settings.availableReferences.add("1: Tip"); + settings.availableReferences.add("2: Tip"); + settings.availableReferences.add("3: Tip"); + settings.availableReferences.add("4: Tip"); + + 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"); + } + + if (info.part_number.equalsIgnoreCase("NP2013")) + { + settings.availableReferences.add("Ground"); + } + + open(); + } + else { + LOGC("Unable to open probe!"); + isValid = false; + } + + + +} + +bool Neuropixels_Ph2C::open() +{ + errorCode = Neuropixels::openProbe(basestation->slot, headstage->port, dock); + LOGC("openProbe: slot: ", basestation->slot, " port: ", headstage->port, " dock: ", dock, " errorCode: ", errorCode); + + ap_timestamp = 0; + lfp_timestamp = 0; + eventCode = 0; + + apView = new ActivityView(channel_count, 3000); + + return errorCode == Neuropixels::SUCCESS; + +} + +bool Neuropixels_Ph2C::close() +{ + errorCode = Neuropixels::closeProbe(basestation->slot, headstage->port, dock); + LOGD("closeProbe: slot: ", basestation->slot, " port: ", headstage->port, " dock: ", dock, " errorCode: ", errorCode); + + return errorCode == Neuropixels::SUCCESS; +} + +void Neuropixels_Ph2C::initialize(bool signalChainIsLoading) +{ + errorCode = Neuropixels::init(basestation->slot, headstage->port, dock); + LOGD("init: slot: ", basestation->slot, " port: ", headstage->port, " dock: ", dock, " errorCode: ", errorCode); + +} + + +void Neuropixels_Ph2C::calibrate() +{ + File baseDirectory = File::getSpecialLocation(File::currentExecutableFile).getParentDirectory(); + File calibrationDirectory = baseDirectory.getChildFile("CalibrationInfo"); + File probeDirectory = calibrationDirectory.getChildFile(String(info.serial_number)); + + if (!probeDirectory.exists()) + { + // check alternate location + baseDirectory = CoreServices::getSavedStateDirectory(); + calibrationDirectory = baseDirectory.getChildFile("CalibrationInfo"); + probeDirectory = calibrationDirectory.getChildFile(String(info.serial_number)); + } + + if (!probeDirectory.exists()) + { + + if (!calibrationWarningShown) + { + // show popup notification window + String message = "Missing calibration files for probe serial number " + String(info.serial_number); + message += ". ADC and Gain calibration files must be located in 'CalibrationInfo\\' folder in the directory where the Open Ephys GUI was launched."; + message += "The GUI will proceed without calibration."; + message += "The plugin must be deleted and re-inserted once calibration files have been added"; + + AlertWindow::showMessageBox(AlertWindow::AlertIconType::WarningIcon, "Calibration files missing", message, "OK"); + + calibrationWarningShown = true; + } + + return; + } + + String gainFile = probeDirectory.getChildFile(String(info.serial_number) + "_gainCalValues.csv").getFullPathName(); + + LOGD("Gain file: ", gainFile); + + errorCode = Neuropixels::setGainCalibration(basestation->slot, headstage->port, dock, gainFile.toRawUTF8()); + + if (errorCode == 0) { LOGD("Successful gain calibration."); } + else { LOGD("Unsuccessful gain calibration, failed with error code: ", errorCode); } + + errorCode = Neuropixels::writeProbeConfiguration(basestation->slot, headstage->port, dock, false); + + if (!errorCode == Neuropixels::SUCCESS) { LOGD("Failed to write probe config w/ error code: ", errorCode); } + else { LOGD("Successfully wrote probe config "); } + + errorCode = Neuropixels::np_setHSLed(basestation->slot, headstage->port, false); + + +} + +void Neuropixels_Ph2C::selectElectrodes() +{ + + Neuropixels::NP_ErrorCode ec; + + if (settings.selectedBank.size() == 0) + return; + + for (int ch = 0; ch < settings.selectedChannel.size(); ch++) + { + + ec = Neuropixels::selectElectrode(basestation->slot, + headstage->port, + dock, + settings.selectedChannel[ch], + settings.selectedShank[ch], + settings.availableBanks.indexOf(settings.selectedBank[ch])); + + } + + LOGD("Updated electrode settings for slot: ", basestation->slot, " port: ", headstage->port, " dock: ", dock); + +} + +Array Neuropixels_Ph2C::selectElectrodeConfiguration(String config) +{ + Array selection; + + if (config.equalsIgnoreCase("Bank A")) + { + for (int i = 0; i < 384; i++) + selection.add(i); + } + else if (config.equalsIgnoreCase("Bank B")) + { + for (int i = 384; i < 768; i++) + selection.add(i); + } + else if (config.equalsIgnoreCase("Bank C")) + { + + for (int i = 768; i < 1152; i++) + selection.add(i); + + } + else if (config.equalsIgnoreCase("Bank D")) + { + + for (int i = 896; i < 1280; i++) + selection.add(i); + + } + else if (config.equalsIgnoreCase("Shank 1 Bank A")) + { + for (int i = 0; i < 384; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 1 Bank B")) + { + for (int i = 384; i < 768; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 1 Bank C")) + { + for (int i = 768; i < 1152; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 2 Bank A")) + { + int startElectrode = 1280; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 2 Bank B")) + { + int startElectrode = 1280 + 384; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 2 Bank C")) + { + int startElectrode = 1280 + 384 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 3 Bank A")) + { + int startElectrode = 1280 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 3 Bank B")) + { + int startElectrode = 1280 * 2 + 384; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 3 Bank C")) + { + int startElectrode = 1280 * 2 + 384 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 4 Bank A")) + { + int startElectrode = 1280 * 3; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 4 Bank B")) + { + int startElectrode = 1280 * 3 + 384; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.add(i); + } + } + else if (config.equalsIgnoreCase("Shank 4 Bank C")) + { + int startElectrode = 1280 * 3 + 384 * 2; + + for (int i = startElectrode; i < startElectrode + 384; i++) + { + selection.add(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.add(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.add(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.add(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.add(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.add(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.add(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.add(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.add(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.add(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.add(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.add(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.add(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.add(i); + } + } + } + + return selection; +} + + +void Neuropixels_Ph2C::setApFilterState() +{ + // no filter cut available +} + +void Neuropixels_Ph2C::setAllGains() +{ + // no gain available +} + + +void Neuropixels_Ph2C::setAllReferences() +{ + + Neuropixels::channelreference_t refId; + int refElectrodeBank = 0; + int shank = 0; + + switch (settings.referenceIndex) + { + case 0: + refId = Neuropixels::EXT_REF; + break; + case 1: + refId = Neuropixels::TIP_REF; + shank = 0; + break; + case 2: + refId = Neuropixels::TIP_REF; + shank = 1; + break; + case 3: + refId = Neuropixels::TIP_REF; + shank = 2; + break; + case 4: + refId = Neuropixels::TIP_REF; + shank = 3; + break; + case 5: + refId = Neuropixels::INT_REF; + break; + + default: + refId = Neuropixels::EXT_REF; + } + + for (int channel = 0; channel < channel_count; channel++) + Neuropixels::setReference(basestation->slot, + headstage->port, + dock, + channel, + shank, + refId, + refElectrodeBank); + + LOGD("Updated reference for slot: ", basestation->slot, " port: ", headstage->port, " dock: ", dock, " to ", refId); + +} + +void Neuropixels_Ph2C::writeConfiguration() +{ + + errorCode = Neuropixels::writeProbeConfiguration(basestation->slot, headstage->port, dock, false); +} + +void Neuropixels_Ph2C::startAcquisition() +{ + ap_timestamp = 0; + apBuffer->clear(); + + apView->reset(); + + last_npx_timestamp = 0; + passedOneSecond = false; + + SKIP = sendSync ? 384 * 4 + 1 : 384 * 4; + + LOGD(" Starting thread."); + startThread(); +} + +void Neuropixels_Ph2C::stopAcquisition() +{ + LOGC("Probe stopping thread."); + signalThreadShouldExit(); +} + +void Neuropixels_Ph2C::run() +{ + + while (!threadShouldExit()) + { + int channel_count = 384; + + int packet_count = MAXPACKETS; + + for (int base = 0; base < 4; base++) { + + int count = MAXPACKETS; + + errorCode = Neuropixels::readPackets( + basestation->slot, + headstage->port, + dock, + static_cast(base), + &packetInfo[0], + &data[0], + channel_count, + packet_count, + &packet_count); + + if (errorCode == Neuropixels::SUCCESS && packet_count > 0) + { + for (int packetNum = 0; packetNum < packet_count; packetNum++) + { + if (base == 0) + { + eventCode = packetInfo[packetNum].Status >> 6; + + if (invertSyncLine) + eventCode = ~eventCode; + + uint32_t npx_timestamp = packetInfo[packetNum].Timestamp; + + uint32_t timestamp_jump = npx_timestamp - last_npx_timestamp; + + if (timestamp_jump > MAX_ALLOWABLE_TIMESTAMP_JUMP) + { + if (passedOneSecond && timestamp_jump < MAX_HEADSTAGE_CLK_SAMPLE) + { + String msg = "NPX TIMESTAMP JUMP: " + String(timestamp_jump) + + ", expected 3 or 4...Possible data loss on slot " + + String(basestation->slot_c) + ", probe " + String(headstage->port_c) + + " at sample number " + String(ap_timestamp); + + LOGC(msg); + + basestation->neuropixThread->sendBroadcastMessage(msg); + } + } + + last_npx_timestamp = npx_timestamp; + + if (sendSync) + apSamples[4 * channel_count + SKIP * packetNum] = (float)eventCode; + + } + + for (int j = base * 384; j < (base + 1) * channel_count; j++) + { + apSamples[j + packetNum * SKIP] = + float(data[packetNum * channel_count + j]) * 1.0f / 16384.0f * 1000000.0f / 80.0f; // convert to microvolts + + apView->addSample(apSamples[j + packetNum * SKIP], j); + + } + + ap_timestamps[packetNum] = ap_timestamp++; + event_codes[packetNum] = eventCode; + + } + + } + else if (errorCode != Neuropixels::SUCCESS) + { + LOGD("readPackets error code: ", errorCode, " for Basestation ", int(basestation->slot), ", probe ", int(headstage->port)); + } + + } + + apBuffer->addToBuffer(apSamples, ap_timestamps, timestamp_s, event_codes, packet_count); + + if (!passedOneSecond) + { + if (ap_timestamp > 30000) + passedOneSecond = true; + } + + int packetsAvailable; + int headroom; + + Neuropixels::getPacketFifoStatus( + basestation->slot, + headstage->port, + dock, + static_cast(0), + &packetsAvailable, + &headroom); + + fifoFillPercentage = float(packetsAvailable) / float(packetsAvailable + headroom); + + if (packetsAvailable < MAXPACKETS) + { + int uSecToWait = (MAXPACKETS - packetsAvailable) * 30; + + std::this_thread::sleep_for(std::chrono::microseconds(uSecToWait)); + } + } + +} + +bool Neuropixels_Ph2C::runBist(BIST bistType) +{ + + close(); + open(); + + int slot = basestation->slot; + int port = headstage->port; + + bool returnValue = false; + + switch (bistType) + { + case BIST::SIGNAL: + { + if (Neuropixels::bistSignal(slot, port, dock) == Neuropixels::SUCCESS) + returnValue = true; + break; + } + case BIST::NOISE: + { + if (Neuropixels::bistNoise(slot, port, dock) == Neuropixels::SUCCESS) + returnValue = true; + break; + } + case BIST::PSB: + { + if (Neuropixels::bistPSB(slot, port, dock) == Neuropixels::SUCCESS) + returnValue = true; + break; + } + case BIST::SR: + { + if (Neuropixels::bistSR(slot, port, dock) == Neuropixels::SUCCESS) + returnValue = true; + break; + } + case BIST::EEPROM: + { + if (Neuropixels::bistEEPROM(slot, port) == Neuropixels::SUCCESS) + returnValue = true; + break; + } + case BIST::I2C: + { + if (Neuropixels::bistI2CMM(slot, port, dock) == Neuropixels::SUCCESS) + returnValue = true; + break; + } + case BIST::SERDES: + { + int errors; + Neuropixels::bistStartPRBS(slot, port); + Sleep(200); + Neuropixels::bistStopPRBS(slot, port, &errors); + + if (errors == 0) + returnValue = true; + break; + } + case BIST::HB: + { + if (Neuropixels::bistHB(slot, port, dock) == Neuropixels::SUCCESS) + returnValue = true; + break; + } case BIST::BS: + { + if (Neuropixels::bistBS(slot) == Neuropixels::SUCCESS) + returnValue = true; + break; + } default: + CoreServices::sendStatusMessage("Test not found."); + } + + close(); + open(); + initialize(false); + + return returnValue; +} diff --git a/Source/Probes/Neuropixels_Ph2C.h b/Source/Probes/Neuropixels_Ph2C.h new file mode 100644 index 0000000..00f1c4a --- /dev/null +++ b/Source/Probes/Neuropixels_Ph2C.h @@ -0,0 +1,119 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2024 Allen Institute for Brain Science and 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 . + +*/ + +#ifndef __NEUROPIXP2C_H_2C4C2D67__ +#define __NEUROPIXP2C_H_2C4C2D67__ + +#include "../NeuropixComponents.h" + +#include "../API/v3/NeuropixAPI.h" + +# define MAXPACKETS 64 * 12 * 4 + +/** + + Acquires data from a Neuropixels Ph2C probe, + using IMEC's v3 API. + +*/ +class Neuropixels_Ph2C : public Probe +{ +public: + + /** Constructor */ + Neuropixels_Ph2C(Basestation*, + Headstage*, + Flex*, + int dock); + + /** Reads probe part number and serial number */ + void getInfo() override; + + /** Opens the connection to the probe (fast) */ + bool open() override; + + /** Closes the connection to the probe (fast) */ + bool close() override; + + /** Call init, setOPMODE, and setHSLED (slow) */ + void initialize(bool signalChainIsLoading) override; + + /** Selects active electrodes based on settings.selectedChannel */ + void selectElectrodes() override; + + /** Select a preset electrode configuration */ + Array selectElectrodeConfiguration(String config); + + /** Sets reference for all channels based on settings.referenceIndex */ + void setAllReferences() override; + + /** Sets gains for all channels based on settings.apGainIndex and settings.lfpGainIndex */ + void setAllGains() override; + + /** Sets AP filter cut based on settings.apFilterState */ + void setApFilterState() override; + + /** Writes latest settings to the probe (slow) */ + void writeConfiguration() override; + + /** Resets timestamps, clears buffers, and starts the thread*/ + void startAcquisition() override; + + /** Stops the thread */ + void stopAcquisition() override; + + /** Runs a built-in self test. */ + bool runBist(BIST bistType) override; + + /** Uploads ADC and gain calibration files */ + void calibrate() override; + + /** Signals that this probe DOES NOT have an LFP data stream*/ + bool generatesLfpData() { return false; } + + /** Signals that this probe DOES NOT have AP filter switch*/ + bool hasApFilterSwitch() { return false; } + + /** Acquires data from the probe */ + void run() override; // acquire data + +private: + + Neuropixels::NP_ErrorCode errorCode; + + int SKIP; + + float apSamples[(384 * 4 + 1) * MAXPACKETS]; + int64 ap_timestamps[MAXPACKETS]; + uint64 event_codes[MAXPACKETS]; + + int16_t data[MAXPACKETS * 384]; + + Neuropixels::streamsource_t source = Neuropixels::SourceAP; + + Neuropixels::PacketInfo packetInfo[MAXPACKETS]; + +}; + + +#endif // __NEUROPIXP2C_H_2C4C2D67__ \ No newline at end of file