From 75db2b20800ba3856b3f2a984533f329fd89cb7b Mon Sep 17 00:00:00 2001 From: bparks13 Date: Fri, 7 Mar 2025 12:18:39 -0500 Subject: [PATCH 01/25] Add MemoryMonitor device and interface --- Source/Devices/Bno055.cpp | 3 +- Source/Devices/DeviceList.h | 1 + Source/Devices/MemoryMonitor.cpp | 149 +++++++++++++++++++++++++++ Source/Devices/MemoryMonitor.h | 98 ++++++++++++++++++ Source/Devices/Neuropixels_1.cpp | 3 +- Source/OnixDevice.h | 5 +- Source/OnixSource.cpp | 33 +++++- Source/OnixSourceCanvas.cpp | 8 ++ Source/UI/InterfaceList.h | 3 +- Source/UI/MemoryMonitorInterface.cpp | 144 ++++++++++++++++++++++++++ Source/UI/MemoryMonitorInterface.h | 76 ++++++++++++++ Source/UI/SettingsInterface.h | 1 + 12 files changed, 516 insertions(+), 8 deletions(-) create mode 100644 Source/Devices/MemoryMonitor.cpp create mode 100644 Source/Devices/MemoryMonitor.h create mode 100644 Source/UI/MemoryMonitorInterface.cpp create mode 100644 Source/UI/MemoryMonitorInterface.h diff --git a/Source/Devices/Bno055.cpp b/Source/Devices/Bno055.cpp index 61d6cca..659212c 100644 --- a/Source/Devices/Bno055.cpp +++ b/Source/Devices/Bno055.cpp @@ -105,6 +105,8 @@ int Bno055::updateSettings() void Bno055::startAcquisition() { + currentFrame = 0; + sampleNumber = 0; } void Bno055::stopAcquisition() @@ -197,7 +199,6 @@ void Bno055::processFrames() { shouldAddToBuffer = true; currentFrame = 0; - sampleNumber = 0; } if (shouldAddToBuffer) diff --git a/Source/Devices/DeviceList.h b/Source/Devices/DeviceList.h index 228ff3c..5e5b61a 100644 --- a/Source/Devices/DeviceList.h +++ b/Source/Devices/DeviceList.h @@ -3,3 +3,4 @@ #include "HeadStageEEPROM.h" #include "Neuropixels_1.h" #include "Neuropixels2e.h" +#include "MemoryMonitor.h" diff --git a/Source/Devices/MemoryMonitor.cpp b/Source/Devices/MemoryMonitor.cpp new file mode 100644 index 0000000..b127955 --- /dev/null +++ b/Source/Devices/MemoryMonitor.cpp @@ -0,0 +1,149 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2020 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 "MemoryMonitor.h" + +MemoryMonitor::MemoryMonitor(String name, const oni_dev_idx_t deviceIdx_, const oni_ctx ctx_) + : OnixDevice(name, OnixDeviceType::MEMORYMONITOR, deviceIdx_, ctx_) +{ + StreamInfo percentUsedStream; + percentUsedStream.name = name + "-PercentUsed"; + percentUsedStream.description = "Percent of available memory that is currently used"; + percentUsedStream.identifier = "onix-memorymonitor.data.percentused"; + percentUsedStream.numChannels = 1; + percentUsedStream.sampleRate = samplesPerSecond; + percentUsedStream.channelPrefix = "Percent"; + //percentUsedStream.bitVolts = 0.195f; // Is this needed? + percentUsedStream.channelType = ContinuousChannel::Type::AUX; + streams.add(percentUsedStream); + + StreamInfo bytesUsedStream; + bytesUsedStream.name = name + "-BytesUsed"; + bytesUsedStream.description = "Number of bytes that are currently used"; + bytesUsedStream.identifier = "onix-memorymonitor.data.bytesused"; + bytesUsedStream.numChannels = 1; + bytesUsedStream.sampleRate = samplesPerSecond; + bytesUsedStream.channelPrefix = "Bytes"; + //bytesUsedStream.bitVolts = 0.195f; // Is this needed? + bytesUsedStream.channelType = ContinuousChannel::Type::AUX; + streams.add(bytesUsedStream); + + for (int i = 0; i < numFrames; i++) + eventCodes[i] = 0; +} + +MemoryMonitor::~MemoryMonitor() +{ +} + +int MemoryMonitor::enableDevice() +{ + ONI_OK_RETURN_INT(oni_write_reg(ctx, deviceIdx, (uint32_t)MemoryMonitorRegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0))); + + return 0; +} + +int MemoryMonitor::updateSettings() +{ + oni_reg_val_t clkHz; + + ONI_OK_RETURN_INT(oni_read_reg(ctx, deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::CLK_HZ, &clkHz)); + + ONI_OK_RETURN_INT(oni_write_reg(ctx, deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::CLK_DIV, clkHz / samplesPerSecond)); + + ONI_OK_RETURN_INT(oni_read_reg(ctx, deviceIdx, (oni_reg_addr_t)MemoryMonitorRegisters::TOTAL_MEM, &totalMemory)); + + return 0; +} + +void MemoryMonitor::startAcquisition() +{ + currentFrame = 0; + sampleNumber = 0; +} + +void MemoryMonitor::stopAcquisition() +{ + while (!frameArray.isEmpty()) + { + const GenericScopedLock frameLock(frameArray.getLock()); + oni_destroy_frame(frameArray.removeAndReturn(0)); + } + + currentFrame = 0; + sampleNumber = 0; +} + +void MemoryMonitor::addFrame(oni_frame_t* frame) +{ + const GenericScopedLock frameLock(frameArray.getLock()); + frameArray.add(frame); +} + +void MemoryMonitor::addSourceBuffers(OwnedArray& sourceBuffers) +{ + for (StreamInfo streamInfo : streams) + { + sourceBuffers.add(new DataBuffer(streamInfo.numChannels, (int)streamInfo.sampleRate * bufferSizeInSeconds)); + + if (streamInfo.channelPrefix.equalsIgnoreCase("Percent")) + percentUsedBuffer = sourceBuffers.getLast(); + else if (streamInfo.channelPrefix.equalsIgnoreCase("Bytes")) + bytesUsedBuffer = sourceBuffers.getLast(); + } +} + +void MemoryMonitor::processFrames() +{ + while (!frameArray.isEmpty()) + { + const GenericScopedLock frameLock(frameArray.getLock()); + oni_frame_t* frame = frameArray.removeAndReturn(0); + + int16_t* dataPtr = (int16_t*)frame->data; + + timestamps[currentFrame] = *(uint64_t*)frame->data; + + percentUsedSamples[currentFrame] = 100.0f * float(*(dataPtr + 4)) / totalMemory; + bytesUsedSamples[currentFrame] = float(*(dataPtr + 4)) * 4.0f; + + oni_destroy_frame(frame); + + sampleNumbers[currentFrame] = sampleNumber++; + + currentFrame++; + + if (currentFrame >= numFrames) + { + shouldAddToBuffer = true; + currentFrame = 0; + } + + if (shouldAddToBuffer) + { + shouldAddToBuffer = false; + percentUsedBuffer->addToBuffer(percentUsedSamples, sampleNumbers, timestamps, eventCodes, numFrames); + bytesUsedBuffer->addToBuffer(bytesUsedSamples, sampleNumbers, timestamps, eventCodes, numFrames); + } + } +} diff --git a/Source/Devices/MemoryMonitor.h b/Source/Devices/MemoryMonitor.h new file mode 100644 index 0000000..a262899 --- /dev/null +++ b/Source/Devices/MemoryMonitor.h @@ -0,0 +1,98 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2020 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 MEMORYMONITOR_H_DEFINED +#define MEMORYMONITOR_H_DEFINED + +#include "../OnixDevice.h" + +enum class MemoryMonitorRegisters : uint32_t +{ + ENABLE = 0, + CLK_DIV = 1, + CLK_HZ = 2, + TOTAL_MEM = 3 +}; + +/* + Streams data from a MemoryMonitor device on a Breakout Board +*/ +class MemoryMonitor : public OnixDevice +{ +public: + MemoryMonitor(String name, const oni_dev_idx_t, const oni_ctx); + + ~MemoryMonitor(); + + /** Enables the device so that it is ready to stream with default settings */ + int enableDevice() override; + + /** Update the settings of the device */ + int updateSettings() override; + + /** Starts probe data streaming */ + void startAcquisition() override; + + /** Stops probe data streaming*/ + void stopAcquisition() override; + + /** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */ + void addSourceBuffers(OwnedArray& sourceBuffers) override; + + void addFrame(oni_frame_t*) override; + + void processFrames() override; + + uint32_t getSamplesPerSecond() const { return samplesPerSecond; } + + void setSamplesPerSecond(uint32_t samplesPerSecond_) { samplesPerSecond = samplesPerSecond_; } + + DataBuffer* percentUsedBuffer = deviceBuffer; + DataBuffer* bytesUsedBuffer; + +private: + + static const int numFrames = 2; + + Array frameArray; + + unsigned short currentFrame = 0; + int sampleNumber = 0; + + /** The frequency at which memory use is recorded in Hz. */ + uint32_t samplesPerSecond = 10; + + /** The total amount of memory, in 32-bit words, on the hardware that is available for data buffering*/ + uint32_t totalMemory; + + bool shouldAddToBuffer = false; + + float percentUsedSamples[numFrames]; + float bytesUsedSamples[numFrames]; + + double timestamps[numFrames]; + int64 sampleNumbers[numFrames]; + uint64 eventCodes[numFrames]; +}; + +#endif \ No newline at end of file diff --git a/Source/Devices/Neuropixels_1.cpp b/Source/Devices/Neuropixels_1.cpp index 79a7cfb..146f644 100644 --- a/Source/Devices/Neuropixels_1.cpp +++ b/Source/Devices/Neuropixels_1.cpp @@ -424,8 +424,7 @@ void Neuropixels_1::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; auto dataclock = (unsigned char*)frame->data + 936; uint64 hubClock = ((uint64_t)(*(uint16_t*)dataclock) << 48) | diff --git a/Source/OnixDevice.h b/Source/OnixDevice.h index 5c372a6..48bfd35 100644 --- a/Source/OnixDevice.h +++ b/Source/OnixDevice.h @@ -39,7 +39,7 @@ #define ONI_OK(exp) {int res = exp; if (res != ONI_ESUCCESS){LOGD(oni_error_str(res));}} #define ONI_OK_RETURN_BOOL(exp) {int res = exp; if (res != ONI_ESUCCESS){LOGD(oni_error_str(res));return false;}} -#define ONI_OK_RETURN_INT(exp, val) {int res = exp; if (res != ONI_ESUCCESS){LOGD(oni_error_str(res));return val;}} +#define ONI_OK_RETURN_INT(exp) {int res = exp; if (res != ONI_ESUCCESS){LOGD(oni_error_str(res));return res;}} using namespace std::chrono; @@ -54,7 +54,8 @@ enum class OnixDeviceType { BNO, NEUROPIXELS_1, NEUROPIXELS_2, - ADC + ADC, + MEMORYMONITOR, }; struct StreamInfo { diff --git a/Source/OnixSource.cpp b/Source/OnixSource.cpp index 7d8b710..13fea05 100644 --- a/Source/OnixSource.cpp +++ b/Source/OnixSource.cpp @@ -30,10 +30,11 @@ OnixSource::OnixSource(SourceNode* sn) : editor(NULL), context() { + // TODO: Add these parameters in the registerParameters() override? 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); + addBooleanParameter(Parameter::PROCESSOR_SCOPE, "connected", "Connect", "Connect to Onix hardware", false, true); if (!context.isInitialized()) { LOGE("Failed to initialize context."); return; } } @@ -223,6 +224,22 @@ void OnixSource::initializeDevices(bool updateStreamInfo) sources.add(np2.release()); } } + else if (devices[dev_idx].id == ONIX_MEMUSAGE) + { + auto memoryMonitor = std::make_unique("MemoryMonitor", devices[dev_idx].idx, ctx); + + int result = memoryMonitor->enableDevice(); + + if (result != 0) + { + LOGE("Device Idx: ", devices[dev_idx].idx, " Error enabling device stream."); + continue; + } + + memoryMonitor->addSourceBuffers(sourceBuffers); + + sources.add(memoryMonitor.release()); + } } val = 1; @@ -375,13 +392,25 @@ void OnixSource::updateSettings(OwnedArray* continuousChannel DeviceInfo::Settings deviceSettings{ source->getName(), "Neuropixels 2.0 Probe", - "neuropixels1.probe", + "neuropixels2.probe", "0000000", "imec" }; deviceInfos->add(new DeviceInfo(deviceSettings)); } + else if (source->type == OnixDeviceType::MEMORYMONITOR) + { + DeviceInfo::Settings deviceSettings{ + source->getName(), + "Memory Monitor", + "memorymonitor", + "0000000", + "" + }; + + deviceInfos->add(new DeviceInfo(deviceSettings)); + } // add data streams and channels for (StreamInfo streamInfo : source->streams) diff --git a/Source/OnixSourceCanvas.cpp b/Source/OnixSourceCanvas.cpp index ee42914..a208ec6 100644 --- a/Source/OnixSourceCanvas.cpp +++ b/Source/OnixSourceCanvas.cpp @@ -97,6 +97,14 @@ void OnixSourceCanvas::populateSourceTabs(CustomTabComponent* portTab) settingsInterfaces.add((SettingsInterface*)bno055Interface); portTab->addTab(source->getName(), Colours::darkgrey, createCustomViewport(bno055Interface), true); + portTabIndex.add(portTabNumber++); + } + else if (source->type == OnixDeviceType::MEMORYMONITOR) + { + MemoryMonitorInterface* memoryMonitorInterface = new MemoryMonitorInterface(source, editor, this); + settingsInterfaces.add((SettingsInterface*)memoryMonitorInterface); + portTab->addTab(source->getName(), Colours::darkgrey, createCustomViewport(memoryMonitorInterface), true); + portTabIndex.add(portTabNumber++); } } diff --git a/Source/UI/InterfaceList.h b/Source/UI/InterfaceList.h index d5ac14d..1319798 100644 --- a/Source/UI/InterfaceList.h +++ b/Source/UI/InterfaceList.h @@ -1,3 +1,4 @@ #include "CustomViewport.h" #include "NeuropixV1Interface.h" -#include "Bno055Interface.h" \ No newline at end of file +#include "Bno055Interface.h" +#include "MemoryMonitorInterface.h" diff --git a/Source/UI/MemoryMonitorInterface.cpp b/Source/UI/MemoryMonitorInterface.cpp new file mode 100644 index 0000000..7688f7c --- /dev/null +++ b/Source/UI/MemoryMonitorInterface.cpp @@ -0,0 +1,144 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2020 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 "MemoryMonitorInterface.h" + +MemoryMonitorInterface::MemoryMonitorInterface(OnixDevice* d, OnixSourceEditor* e, OnixSourceCanvas* c) : + SettingsInterface(d, e, c), + device((MemoryMonitor*)d) +{ + if (device != nullptr) + { + nameLabel = std::make_unique