Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
75db2b2
Add MemoryMonitor device and interface
bparks13 Mar 7, 2025
08a8742
Merge branch 'main' into issue-9
bparks13 Mar 10, 2025
8629757
Fix merge error
bparks13 Mar 10, 2025
d54612c
Plot the memory usage as a LevelMonitor in the editor
bparks13 Mar 10, 2025
2f1a6cf
Code cleanup
bparks13 Mar 13, 2025
97abadf
Convert linear scale to logarithmic for memory usage
bparks13 Mar 14, 2025
8526fe3
Add OutputClock
bparks13 Mar 17, 2025
a66e01d
Only report read_frame error when acquisition is active
bparks13 Mar 17, 2025
c35b134
Add Heartbeat
bparks13 Mar 17, 2025
56c305c
Ensure that frames are deleted
bparks13 Mar 18, 2025
d3100f7
Add HarpSyncInput
bparks13 Mar 18, 2025
df3200a
Set toggle state after the listener is attached
bparks13 Mar 21, 2025
cb31424
Add AnalogIO
bparks13 Mar 21, 2025
4cccc33
Merge branch 'main' into issue-9
bparks13 Mar 21, 2025
692a09d
Fix merge conflicts
bparks13 Mar 21, 2025
c872a21
Add Breakout Board as a tab that automatically appears
bparks13 Mar 21, 2025
1236ed7
Change variable name to streamInfos
bparks13 Mar 24, 2025
30c2b64
Add DigitalIO
bparks13 Mar 24, 2025
d087f18
Save acquisition clock instead of hub clock for timestamps
bparks13 Mar 24, 2025
96203f7
Removed Heartbeat and Memory Monitor settings interfaces
bparks13 Mar 24, 2025
bcbb741
Remove unnecessary device variable from interface
bparks13 Mar 28, 2025
2d24c2d
Add DigitalIO events to MemoryMonitor data stream
bparks13 Mar 28, 2025
ec2675d
Merge branch 'main' into issue-9
bparks13 Mar 28, 2025
442a6d1
Update StreamInfo instantiation
bparks13 Mar 28, 2025
7f10f2f
Update Output Clock
bparks13 Mar 28, 2025
b7e8a11
Address review comments
bparks13 Apr 1, 2025
3dc3531
Update copyright
bparks13 Apr 3, 2025
3b595c7
Add const variables instead of hard-coded values
bparks13 Apr 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 190 additions & 0 deletions Source/Devices/AnalogIO.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/*
------------------------------------------------------------------

Copyright (C) Open Ephys

------------------------------------------------------------------

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

*/

#include "AnalogIO.h"

AnalogIO::AnalogIO(String name, const oni_dev_idx_t deviceIdx_, std::shared_ptr<Onix1> oni_ctx)
: OnixDevice(name, OnixDeviceType::ANALOGIO, deviceIdx_, oni_ctx)
{
StreamInfo analogInputStream = StreamInfo(
name + "-AnalogInput",
"Analog Input data",
"onix-analogio.data.input",
12,
std::floor(AnalogIOFrequencyHz / framesToAverage),
"AnalogInput",
ContinuousChannel::Type::ADC,
20.0f / numberOfDivisions, // NB: +/- 10 Volts
"V",
{});
streamInfos.add(analogInputStream);

for (int i = 0; i < numFrames; i++)
eventCodes[i] = 0;

for (int i = 0; i < numChannels; i += 1)
{
channelDirection[i] = AnalogIODirection::Input;
channelVoltageRange[i] = AnalogIOVoltageRange::TenVolts;
}

dataType = AnalogIODataType::Volts;
}

int AnalogIO::configureDevice()
{
if (deviceContext == nullptr || !deviceContext->isInitialized()) return -1;

deviceContext->writeRegister(deviceIdx, (uint32_t)AnalogIORegisters::ENABLE, (oni_reg_val_t)(isEnabled() ? 1 : 0));

return deviceContext->getLastResult();
}

bool AnalogIO::updateSettings()
{
for (int i = 0; i < numChannels; i += 1)
{
deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)AnalogIORegisters::CH00_IN_RANGE + i, (oni_reg_val_t)channelVoltageRange[i]);
if (deviceContext->getLastResult() != ONI_ESUCCESS) return false;
}

uint32_t ioReg = 0;

for (int i = 0; i < numChannels; i += 1)
{
ioReg = (ioReg & ~((uint32_t)1 << i)) | ((uint32_t)(channelDirection[i]) << i);
}

deviceContext->writeRegister(deviceIdx, (oni_reg_addr_t)AnalogIORegisters::CHDIR, ioReg);
if (deviceContext->getLastResult() != ONI_ESUCCESS) return false;

for (int i = 0; i < numChannels; i += 1)
{
voltsPerDivision[i] = getVoltsPerDivision(channelVoltageRange[i]);
}

return true;
}

float AnalogIO::getVoltsPerDivision(AnalogIOVoltageRange voltageRange)
{
switch (voltageRange)
{
case AnalogIOVoltageRange::TenVolts:
return 20.0f / numberOfDivisions;
case AnalogIOVoltageRange::TwoPointFiveVolts:
return 5.0f / numberOfDivisions;
case AnalogIOVoltageRange::FiveVolts:
return 10.0f / numberOfDivisions;
default:
return 0.0f;
}
}

void AnalogIO::startAcquisition()
{
currentFrame = 0;
currentAverageFrame = 0;
sampleNumber = 0;

analogInputSamples.fill(0);
}

void AnalogIO::stopAcquisition()
{
while (!frameArray.isEmpty())
{
const GenericScopedLock<CriticalSection> frameLock(frameArray.getLock());
oni_destroy_frame(frameArray.removeAndReturn(0));
}
}

void AnalogIO::addFrame(oni_frame_t* frame)
{
const GenericScopedLock<CriticalSection> frameLock(frameArray.getLock());
frameArray.add(frame);
}

void AnalogIO::addSourceBuffers(OwnedArray<DataBuffer>& sourceBuffers)
{
for (StreamInfo streamInfo : streamInfos)
{
sourceBuffers.add(new DataBuffer(streamInfo.getNumChannels(), (int)streamInfo.getSampleRate() * bufferSizeInSeconds));

if (streamInfo.getChannelPrefix().equalsIgnoreCase("AnalogInput"))
analogInputBuffer = sourceBuffers.getLast();
}
}

void AnalogIO::processFrames()
{
while (!frameArray.isEmpty())
{
const GenericScopedLock<CriticalSection> frameLock(frameArray.getLock());
oni_frame_t* frame = frameArray.removeAndReturn(0);

int16_t* dataPtr = (int16_t*)frame->data;

int dataOffset = 4;

for (int i = 0; i < numChannels; i += 1)
{
if (dataType == AnalogIODataType::S16)
analogInputSamples[currentFrame + i * numFrames] += *(dataPtr + dataOffset + i);
else
analogInputSamples[currentFrame + i * numFrames] += *(dataPtr + dataOffset + i) * voltsPerDivision[i];
}

currentAverageFrame += 1;

if (currentAverageFrame >= framesToAverage)
{
for (int i = 0; i < numChannels; i += 1)
{
analogInputSamples[currentFrame + i * numFrames] /= framesToAverage;
}

currentAverageFrame = 0;

timestamps[currentFrame] = frame->time;
sampleNumbers[currentFrame] = sampleNumber++;

currentFrame++;
}

oni_destroy_frame(frame);

if (currentFrame >= numFrames)
{
shouldAddToBuffer = true;
currentFrame = 0;
}

if (shouldAddToBuffer)
{
shouldAddToBuffer = false;
analogInputBuffer->addToBuffer(analogInputSamples.data(), sampleNumbers, timestamps, eventCodes, numFrames);

analogInputSamples.fill(0);
}
}
}
178 changes: 178 additions & 0 deletions Source/Devices/AnalogIO.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/*
------------------------------------------------------------------

Copyright (C) Open Ephys

------------------------------------------------------------------

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

*/

#pragma once

#include "../OnixDevice.h"

enum class AnalogIORegisters : uint32_t
{
ENABLE = 0,
CHDIR = 1,
CH00_IN_RANGE = 2,
CH01_IN_RANGE = 3,
CH02_IN_RANGE = 4,
CH03_IN_RANGE = 5,
CH04_IN_RANGE = 6,
CH05_IN_RANGE = 7,
CH06_IN_RANGE = 8,
CH07_IN_RANGE = 9,
CH08_IN_RANGE = 10,
CH09_IN_RANGE = 11,
CH10_IN_RANGE = 12,
CH11_IN_RANGE = 13,
};

enum class AnalogIOVoltageRange : uint32_t
{
TenVolts = 0,
TwoPointFiveVolts = 1,
FiveVolts = 2
};

enum class AnalogIODirection : uint32_t
{
Input = 0,
Output = 1
};

enum class AnalogIODataType : uint32_t
{
S16 = 0,
Volts = 1
};

/*
Configures and streams data from an AnalogIO device on a Breakout Board
*/
class AnalogIO : public OnixDevice
{
public:
AnalogIO(String name, const oni_dev_idx_t, std::shared_ptr<Onix1> oni_ctx);

/** Configures the device so that it is ready to stream with default settings */
int configureDevice() override;

/** Update the settings of the device */
bool updateSettings() override;

/** Starts probe data streaming */
void startAcquisition() override;

/** Stops probe data streaming*/
void stopAcquisition() override;

/** Given the sourceBuffers from OnixSource, add all streams for the current device to the array */
void addSourceBuffers(OwnedArray<DataBuffer>& sourceBuffers) override;

void addFrame(oni_frame_t*) override;

void processFrames() override;

AnalogIODirection getChannelDirection(int channelNumber)
{
if (channelNumber > numChannels || channelNumber < 0)
{
LOGE("Channel number must be between 0 and " + String(channelNumber));
return AnalogIODirection::Input;
}

return channelDirection[channelNumber];
}

void setChannelDirection(int channelNumber, AnalogIODirection direction)
{
if (channelNumber > numChannels || channelNumber < 0)
{
LOGE("Channel number must be between 0 and " + String(channelNumber));
return;
}

channelDirection[channelNumber] = direction;
}

AnalogIOVoltageRange getChannelVoltageRange(int channelNumber)
{
if (channelNumber > numChannels || channelNumber < 0)
{
LOGE("Channel number must be between 0 and " + String(channelNumber));
return AnalogIOVoltageRange::FiveVolts;
}

return channelVoltageRange[channelNumber];
}

void setChannelVoltageRange(int channelNumber, AnalogIOVoltageRange direction)
{
if (channelNumber > numChannels || channelNumber < 0)
{
LOGE("Channel number must be between 0 and " + String(channelNumber));
return;
}

channelVoltageRange[channelNumber] = direction;
}

AnalogIODataType getDataType() const { return dataType; }

void setDataType(AnalogIODataType type) { dataType = type; }

int getNumChannels() { return numChannels; }

private:

DataBuffer* analogInputBuffer = nullptr;

static const int AnalogIOFrequencyHz = 100000;

static const int numFrames = 25;
static const int framesToAverage = 4; // NB: Downsampling from 100 kHz to 25 kHz
static const int numChannels = 12;

static const int numberOfDivisions = 1 << 16;
const int dacMidScale = 1 << 15;

std::array<AnalogIODirection, numChannels> channelDirection;
std::array<AnalogIOVoltageRange, numChannels> channelVoltageRange;

AnalogIODataType dataType = AnalogIODataType::Volts;

Array<oni_frame_t*, CriticalSection, numFrames> frameArray;

unsigned short currentFrame = 0;
unsigned short currentAverageFrame = 0;
int sampleNumber = 0;

bool shouldAddToBuffer = false;

std::array<float, numFrames* numChannels> analogInputSamples;

double timestamps[numFrames];
int64 sampleNumbers[numFrames];
uint64 eventCodes[numFrames];

std::array<float, numChannels> voltsPerDivision;

static float getVoltsPerDivision(AnalogIOVoltageRange voltageRange);

JUCE_LEAK_DETECTOR(AnalogIO);
};
Loading