Skip to content

Commit

Permalink
Merge pull request #254 from open-ephys/issue-70-2
Browse files Browse the repository at this point in the history
Support for headstage-nric1384-203390
  • Loading branch information
jonnew authored Sep 17, 2024
2 parents b787415 + 4ab4c87 commit 7586b19
Show file tree
Hide file tree
Showing 10 changed files with 749 additions and 11 deletions.
3 changes: 2 additions & 1 deletion OpenEphys.Onix1.Design/NeuropixelsV1eDialog.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@ private void CheckStatus()
{
gainCorrection = NeuropixelsV1Helper.TryParseGainCalibrationFile(ConfigureNode.GainCalibrationFile,
ConfigureNode.ProbeConfiguration.SpikeAmplifierGain,
ConfigureNode.ProbeConfiguration.LfpAmplifierGain);
ConfigureNode.ProbeConfiguration.LfpAmplifierGain,
960);
}
catch (IOException ex)
{
Expand Down
151 changes: 151 additions & 0 deletions OpenEphys.Onix1/ConfigureHeadstageNric1384.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;

namespace OpenEphys.Onix1
{
/// <summary>
/// Configures a Nric1384 headstage on the specified port.
/// </summary>
/// <remarks>
/// The Nric1384 Headstage is a 2.5g serialized, multifunction headstage for small animals built around the
/// IMEC Nric1384 bioacquisition chip. This headstage is designed to function with passive probes of the
/// user's choosing (e.g. silicon probe arrays, high-density tetrode drives, etc). It provides the
/// following features:
/// <list type="bullet">
/// <item><description>384 analog ephys channels sampled at 30 kHz per channel and exposed via an array of
/// 12x ultra-high density Molex 203390-0323 quad-row connectors. </description></item>
/// <item><description>A BNO055 9-axis IMU for real-time, 3D orientation tracking at 100
/// Hz.</description></item>
/// <item><description>Two TS4231 light to digital converters for real-time, 3D position tracking with HTC
/// Vive base stations.</description></item>
/// <item><description>A single electrical stimulator (current controlled, +/-15V compliance, automatic
/// electrode discharge).</description></item>
/// </list>
/// </remarks>
[Description("Configures a Nric1384 Headstage headstage.")]
public class ConfigureHeadstageNric1384 : MultiDeviceFactory
{
PortName port;
readonly ConfigureHeadstageNric1384PortController PortControl = new();

/// <summary>
/// Initialize a new instance of a <see cref="ConfigureHeadstageNric1384"/> class.
/// </summary>
public ConfigureHeadstageNric1384()
{
Port = PortName.PortA;
PortControl.HubConfiguration = HubConfiguration.Standard;
}

/// <summary>
/// Gets or sets the Nric1384 bioacquisition chip configuration.
/// </summary>
[Category(DevicesCategory)]
[TypeConverter(typeof(SingleDeviceFactoryConverter))]
[Description("Specifies the configuration for the Nric1384 bioacquisition device.")]
public ConfigureNric1384 Nric1384 { get; set; } = new();

/// <summary>
/// Gets or sets the BNO055 9-axis inertial measurement unit configuration.
/// </summary>
[Category(DevicesCategory)]
[TypeConverter(typeof(SingleDeviceFactoryConverter))]
[Description("Specifies the configuration for the Bno055 device.")]
public ConfigureBno055 Bno055 { get; set; } = new();

/// <summary>
/// Gets or sets the port.
/// </summary>
/// <remarks>
/// The port is the physical connection to the ONIX breakout board and must be specified prior to operation.
/// </remarks>
[Description("Specifies the physical connection of the headstage to the ONIX breakout board.")]
[Category(ConfigurationCategory)]
public PortName Port
{
get { return port; }
set
{
port = value;
var offset = (uint)port << 8;
PortControl.DeviceAddress = (uint)port;
Nric1384.DeviceAddress = offset + 0;
Bno055.DeviceAddress = offset + 1;
}
}

/// <summary>
/// Gets or sets the port voltage.
/// </summary>
/// <remarks>
/// <para>
/// If defined, it will override automated voltage discovery and apply the specified voltage to the headstage.
/// If left blank, an automated headstage detection algorithm will attempt to communicate with the headstage and
/// apply an appropriate voltage for stable operation. Because ONIX allows any coaxial tether to be used, some of
/// which are thin enough to result in a significant voltage drop, its may be required to manually specify the
/// port voltage.
/// </para>
/// <para>
/// Warning: This device requires 3.8V to 5.5V for proper operation. Voltages higher than 5.5V can
/// damage the headstage.
/// </para>
/// </remarks>
[Description("If defined, overrides automated voltage discovery and applies " +
"the specified voltage to the headstage. Warning: this device requires 3.8V to 5.5V " +
"for proper operation. Higher voltages can damage the headstage.")]
[Category(ConfigurationCategory)]
public double? PortVoltage
{
get => PortControl.PortVoltage;
set => PortControl.PortVoltage = value;
}

internal override IEnumerable<IDeviceConfiguration> GetDevices()
{
yield return PortControl;
yield return Nric1384;
yield return Bno055;
}

class ConfigureHeadstageNric1384PortController : ConfigurePortController
{
protected override bool ConfigurePortVoltage(DeviceContext device)
{
const double MinVoltage = 3.8;
const double MaxVoltage = 5.5;
const double VoltageOffset = 0.7;
const double VoltageIncrement = 0.2;

for (var voltage = MinVoltage; voltage <= MaxVoltage; voltage += VoltageIncrement)
{
SetPortVoltage(device, voltage);
if (base.CheckLinkState(device))
{
SetPortVoltage(device, voltage + VoltageOffset);
return CheckLinkState(device);
}
}

return false;
}

private void SetPortVoltage(DeviceContext device, double voltage)
{
device.WriteRegister(PortController.PORTVOLTAGE, 0);
Thread.Sleep(500);
device.WriteRegister(PortController.PORTVOLTAGE, (uint)(10 * voltage));
Thread.Sleep(500);
}

protected override bool CheckLinkState(DeviceContext device)
{
// NB: needs an additional reset after power on to provide its device table.
device.Context.Reset();
var linkState = device.ReadRegister(PortController.LINKSTATE);
return (linkState & PortController.LINKSTATE_SL) != 0;
}

}
}
}
204 changes: 204 additions & 0 deletions OpenEphys.Onix1/ConfigureNric1384.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
using System;
using System.ComponentModel;
using Bonsai;

namespace OpenEphys.Onix1
{
/// <summary>
/// Configures a Nric184 bioacquisition chip.
/// </summary>
public class ConfigureNric1384 : SingleDeviceFactory
{
/// <summary>
/// Initialize a new instance of a <see cref="ConfigureNric1384"/> class.
/// </summary>
public ConfigureNric1384()
: base(typeof(Nric1384))
{
}

/// <summary>
/// Gets or sets the device enable state.
/// </summary>
/// <remarks>
/// If set to true, <see cref="Nric1384Data"/> will produce data. If set to false,
/// <see cref="Nric1384Data"/> will not produce data.
/// </remarks>
[Category(ConfigurationCategory)]
[Description("Specifies whether the Nric1384 data stream is enabled.")]
public bool Enable { get; set; } = true;

/// <summary>
/// Gets or sets the amplifier gain for the spike-band.
/// </summary>
/// <remarks>
/// The spike-band is from DC to 10 kHz if <see cref="SpikeFilter"/> is set to false, while the
/// spike-band is from 300 Hz to 10 kHz if <see cref="SpikeFilter"/> is set to true.
/// </remarks>
[Category(ConfigurationCategory)]
[Description("Amplifier gain for spike-band.")]
public NeuropixelsV1Gain SpikeAmplifierGain { get; set; } = NeuropixelsV1Gain.Gain1000;

/// <summary>
/// Gets or sets the amplifier gain for the LFP-band.
/// </summary>
/// <remarks>
/// The LFP band is from 0.5 to 500 Hz.
/// </remarks>
[Category(ConfigurationCategory)]
[Description("Amplifier gain for LFP-band.")]
public NeuropixelsV1Gain LfpAmplifierGain { get; set; } = NeuropixelsV1Gain.Gain50;

/// <summary>
/// Gets or sets the state of the spike-band filter.
/// </summary>
/// <remarks>
/// If set to true, the spike-band has a 300 Hz high-pass filter which will be activated. If set to
/// false, the high-pass filter will not to be activated.
/// </remarks>
[Category(ConfigurationCategory)]
[Description("If true, activates a 300 Hz high-pass in the spike-band data stream.")]
public bool SpikeFilter { get; set; } = true;

/// <summary>
/// Gets or sets the path to the gain calibration file.
/// </summary>
/// <remarks>
/// <para>
/// Each chip is linked to a gain calibration file that contains gain adjustments determined by IMEC during
/// factory testing. Electrode voltages are scaled using these values to ensure they can be accurately compared
/// across chips. Therefore, using the correct gain calibration file is mandatory to create standardized recordings.
/// </para>
/// <para>
/// Calibration files are chip-specific and not interchangeable across chips. Calibration files must contain the
/// serial number of the corresponding chip on their first line of text. If you have lost track of a calibration
/// file for your chip, email IMEC at neuropixels.info@imec.be with the chip serial number to retrieve a new copy.
/// </para>
/// </remarks>
[FileNameFilter("Gain calibration files (*_gainCalValues.csv)|*_gainCalValues.csv")]
[Description("Path to the Nric1384 gain calibraiton file.")]
[Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)]
public string GainCalibrationFile { get; set; }

/// <summary>
/// Gets or sets the path to the ADC calibration file.
/// </summary>
/// <remarks>
/// <para>
/// Each chip must be provided with an ADC calibration file that contains chip-specific hardware settings that is
/// created by IMEC during factory calibration. These files are used to set internal bias currents, correct for ADC
/// nonlinearities, correct ADC-zero crossing non-monotonicities, etc. Using the correct calibration file is mandatory
/// for the chip to operate correctly.
/// </para>
/// <para>
/// Calibration files are chip-specific and not interchangeable across chips. Calibration files must contain the
/// serial number of the corresponding chip on their first line of text. If you have lost track of a calibration
/// file for your chip, email IMEC at neuropixels.info@imec.be with the chip serial number to retrieve a new copy.
/// </para>
/// </remarks>
[FileNameFilter("ADC calibration files (*_ADCCalibration.csv)|*_ADCCalibration.csv")]
[Description("Path to the Nric1384 ADC calibraiton file.")]
[Editor("Bonsai.Design.OpenFileNameEditor, Bonsai.Design", DesignTypes.UITypeEditor)]
public string AdcCalibrationFile { get; set; }

/// <summary>
/// Configures a Nric1384 bioacquisition device.
/// </summary>
/// <remarks>
/// This will schedule configuration actions to be applied by a <see cref="StartAcquisition"/> node
/// prior to data acquisition.
/// </remarks>
/// <param name="source">A sequence of <see cref="ContextTask"/> that holds all configuration actions.</param>
/// <returns>
/// The original sequence with the side effect of an additional configuration action to configure
/// a Nric1384 device.
/// </returns>
public override IObservable<ContextTask> Process(IObservable<ContextTask> source)
{
var enable = Enable;
var deviceName = DeviceName;
var deviceAddress = DeviceAddress;
return source.ConfigureDevice(context =>
{
var device = context.GetDeviceContext(deviceAddress, typeof(Nric1384));
device.WriteRegister(Nric1384.ENABLE, enable ? 1u : 0);
if (enable)
{
var probeControl = new Nric1384RegisterContext(device, SpikeAmplifierGain, LfpAmplifierGain, SpikeFilter, GainCalibrationFile, AdcCalibrationFile);
probeControl.InitializeChip();
probeControl.WriteShiftRegisters();
}
return DeviceManager.RegisterDevice(deviceName, device, DeviceType);
});
}
}

static class Nric1384
{
public const int ID = 33;

public const int I2cAddress = 0x70;
public const int ChannelCount = 384;
public const int ElectrodeCount = 384;

// managed registers
public const uint ENABLE = 0x8000; // Enable or disable the data output stream
public const uint ADC00_OFF_THRESH = 0x8001; // ADC 0 offset and threshold parameters: [6-bit ADC 00 Offset, 10-bit ADC 00 Threshold]
public const uint ADC01_OFF_THRESH = 0x8002;
public const uint ADC02_OFF_THRESH = 0x8003;
public const uint ADC03_OFF_THRESH = 0x8004;
public const uint ADC04_OFF_THRESH = 0x8005;
public const uint ADC05_OFF_THRESH = 0x8006;
public const uint ADC06_OFF_THRESH = 0x8007;
public const uint ADC07_OFF_THRESH = 0x8008;
public const uint ADC08_OFF_THRESH = 0x8009;
public const uint ADC09_OFF_THRESH = 0x800a;
public const uint ADC10_OFF_THRESH = 0x800b;
public const uint ADC11_OFF_THRESH = 0x800c;
public const uint ADC12_OFF_THRESH = 0x800d;
public const uint ADC13_OFF_THRESH = 0x800e;
public const uint ADC14_OFF_THRESH = 0x800f;
public const uint ADC15_OFF_THRESH = 0x8010;
public const uint ADC16_OFF_THRESH = 0x8011;
public const uint ADC17_OFF_THRESH = 0x8012;
public const uint ADC18_OFF_THRESH = 0x8013;
public const uint ADC19_OFF_THRESH = 0x8014;
public const uint ADC20_OFF_THRESH = 0x8015;
public const uint ADC21_OFF_THRESH = 0x8016;
public const uint ADC22_OFF_THRESH = 0x8017;
public const uint ADC23_OFF_THRESH = 0x8018;
public const uint ADC24_OFF_THRESH = 0x8019;
public const uint ADC25_OFF_THRESH = 0x801a;
public const uint ADC26_OFF_THRESH = 0x801b;
public const uint ADC27_OFF_THRESH = 0x801c;
public const uint ADC28_OFF_THRESH = 0x801d;
public const uint ADC29_OFF_THRESH = 0x801e;
public const uint ADC30_OFF_THRESH = 0x801f;
public const uint ADC31_OFF_THRESH = 0x8020; // ADC 31 offset and threshold parameters: [6-bit ADC 31 Offset , 10-bit ADC 31 Threshold]
public const uint LFP_GAIN = 0x8021; // LFP gain correction parameter: [X Q1.14]
public const uint AP_GAIN = 0x8022; // AP gain correction parameter: [X Q1.14]

// unmanaged regiseters
public const uint OP_MODE = 0X00;
public const uint REC_MOD = 0X01;
public const uint CAL_MOD = 0X02;
public const uint STATUS = 0X08;
public const uint SYNC = 0X09;
public const uint SR_CHAIN3 = 0X0C; // Odd channels
public const uint SR_CHAIN2 = 0X0D; // Even channels
public const uint SR_LENGTH2 = 0X0F;
public const uint SR_LENGTH1 = 0X10;
public const uint SOFT_RESET = 0X11;

internal class NameConverter : DeviceNameConverter
{
public NameConverter()
: base(typeof(Nric1384))
{
}
}
}
}
2 changes: 1 addition & 1 deletion OpenEphys.Onix1/ContextTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private void Initialize()
DeviceTable = ctx.DeviceTable;
}

private void Reset()
internal void Reset()
{
lock (disposeLock)
lock (regLock)
Expand Down
Loading

0 comments on commit 7586b19

Please sign in to comment.