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