Skip to content

Commit

Permalink
Add headstage-64 electricals stimulator configur and trigger nodes
Browse files Browse the repository at this point in the history
- Right now, the trigger node is non-functional because the ENABLE
  register state is not maintained at 1 after its configured. I dont
  know why
  • Loading branch information
jonnew committed Feb 20, 2024
1 parent 6cc67ed commit 3efaf1b
Show file tree
Hide file tree
Showing 3 changed files with 250 additions and 0 deletions.
6 changes: 6 additions & 0 deletions OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstage64.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ public ConfigureHeadstage64()
[TypeConverter(typeof(HubDeviceConverter))]
public ConfigureTS4231 TS4231 { get; set; } = new() { Enable = false };

[Category(ConfigurationCategory)]
[TypeConverter(typeof(HubDeviceConverter))]
public ConfigureHeadstage64ElectricalStimulator ElectricalStimulator { get; set; } = new() { Enable = false };

public PortName Port
{
get { return port; }
Expand All @@ -46,6 +50,7 @@ public PortName Port
Rhd2164.DeviceAddress = offset + 0;
Bno055.DeviceAddress = offset + 1;
TS4231.DeviceAddress = offset + 2;
ElectricalStimulator.DeviceAddress = offset + 3;
}
}

Expand All @@ -55,6 +60,7 @@ internal override IEnumerable<IDeviceConfiguration> GetDevices()
yield return Rhd2164;
yield return Bno055;
yield return TS4231;
yield return ElectricalStimulator;
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Reactive.Disposables;
using System.Reactive.Subjects;
using Bonsai;

namespace OpenEphys.Onix
{
public class ConfigureHeadstage64ElectricalStimulator : SingleDeviceFactory
{

readonly BehaviorSubject<double> phaseOneCurrent = new(0);
readonly BehaviorSubject<double> interPhaseCurrent = new(0);
readonly BehaviorSubject<double> phaseTwoCurrent = new(0);
readonly BehaviorSubject<uint> phaseOneDuration = new(0);
readonly BehaviorSubject<uint> interPhaseInterval = new(0);
readonly BehaviorSubject<uint> phaseTwoDuration = new(0);
readonly BehaviorSubject<uint> interPulseInterval = new(0);
readonly BehaviorSubject<uint> burstPulseCount = new(0);
readonly BehaviorSubject<uint> interBurstInterval = new(0);
readonly BehaviorSubject<uint> trainBurstCount = new(0);
readonly BehaviorSubject<uint> trainDelay = new(0);
readonly BehaviorSubject<bool> powerEnable = new(false);

const double DacBitDepth = 16;
const double AbsMaxMicroAmps = 2500;

public ConfigureHeadstage64ElectricalStimulator()
: base(typeof(Headstage64ElectricalStimulator))
{
}

[Category(ConfigurationCategory)]
[Description("Specifies whether the electrical stimulation subcircuit will respect triggers.")]
public bool Enable { get; set; } = true;

[Category(AcquisitionCategory)]
[Description("Phase 1 pulse current (uA).")]
[Range(-AbsMaxMicroAmps, AbsMaxMicroAmps)]
[Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))]
[Precision(3, 1)]
public double PhaseOneCurrent
{
get => phaseOneCurrent.Value;
set => phaseOneCurrent.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Interphase rest current (uA).")]
[Range(-AbsMaxMicroAmps, AbsMaxMicroAmps)]
[Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))]
[Precision(3, 1)]
public double InterPhaseCurrent
{
get => interPhaseCurrent.Value;
set => interPhaseCurrent.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Phase 2 pulse current (uA).")]
[Range(-AbsMaxMicroAmps, AbsMaxMicroAmps)]
[Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))]
[Precision(3, 1)]
public double PhaseTwoCurrent
{
get => phaseTwoCurrent.Value;
set => phaseTwoCurrent.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Pulse train start delay (uSec).")]
[Range(0, uint.MaxValue)]
public uint TrainDelay
{
get => trainDelay.Value;
set => trainDelay.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Phase 1 pulse duration (uSec).")]
[Range(0, uint.MaxValue)]
public uint PhaseOneDuration
{
get => phaseOneDuration.Value;
set => phaseOneDuration.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Inter-phase interval (uSec).")]
[Range(0, uint.MaxValue)]
public uint InterPhaseInterval
{
get => interPhaseInterval.Value;
set => interPhaseInterval.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Phase 2 pulse duration (uSec).")]
[Range(0, uint.MaxValue)]
public uint PhaseTwoDuration
{
get => phaseTwoDuration.Value;
set => phaseTwoDuration.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Inter-pulse interval (uSec).")]
[Range(0, uint.MaxValue)]
public uint InterPulseInterval
{
get => interPulseInterval.Value;
set => interPulseInterval.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Inter-burst interval (uSec).")]
[Range(0, uint.MaxValue)]
public uint InterBurstInterval
{
get => interBurstInterval.Value;
set => interBurstInterval.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Number of pulses in each burst.")]
[Range(0, uint.MaxValue)]
public uint BurstPulseCount
{
get => burstPulseCount.Value;
set => burstPulseCount.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Number of bursts in each train.")]
[Range(0, uint.MaxValue)]
public uint TrainBurstCount
{
get => trainBurstCount.Value;
set => trainBurstCount.OnNext(value);
}

[Category(AcquisitionCategory)]
[Description("Stimulator power on/off.")]
[Range(0, uint.MaxValue)]
public bool PowerEnable
{
get => powerEnable.Value;
set => powerEnable.OnNext(value);
}

public override IObservable<ContextTask> Process(IObservable<ContextTask> source)
{
var deviceName = DeviceName;
var deviceAddress = DeviceAddress;
return source.ConfigureDevice(context =>
{
var device = context.GetDeviceContext(deviceAddress, Headstage64ElectricalStimulator.ID);
device.WriteRegister(Headstage64ElectricalStimulator.ENABLE, Enable ? 1u : 0u);

static uint uAToCode(double currentuA)
{
var k = 1 / (2 * AbsMaxMicroAmps / (Math.Pow(2, DacBitDepth) - 1)); // static
return (uint)(k * (currentuA + AbsMaxMicroAmps));
}

return new CompositeDisposable(
DeviceManager.RegisterDevice(deviceName, device, DeviceType),
phaseOneCurrent.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT1, uAToCode(newValue))),
interPhaseCurrent.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.RESTCURR, uAToCode(newValue))),
phaseTwoCurrent.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT2, uAToCode(newValue))),
trainDelay.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.TRAINDELAY, newValue)),
phaseOneDuration.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR1, newValue)),
interPhaseInterval.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.INTERPHASEINTERVAL, newValue)),
phaseTwoDuration.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR2, newValue)),
interPulseInterval.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.INTERPULSEINTERVAL, newValue)),
interBurstInterval.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.INTERBURSTINTERVAL, newValue)),
burstPulseCount.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.BURSTCOUNT, newValue)),
trainBurstCount.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.TRAINCOUNT, newValue)),
powerEnable.Subscribe(newValue => device.WriteRegister(Headstage64ElectricalStimulator.POWERON, newValue ? 1u: 0u))
);

});
}
}

static class Headstage64ElectricalStimulator
{
public const int ID = 4;

// managed registers
public const uint NULLPARM = 0; // No command
public const uint BIPHASIC = 1; // Biphasic pulse (0 = monophasic, 1 = biphasic; NB: currently ignored)
public const uint CURRENT1 = 2; // Phase 1 current
public const uint CURRENT2 = 3; // Phase 2 current
public const uint PULSEDUR1 = 4; // Phase 1 duration, 1 microsecond steps
public const uint INTERPHASEINTERVAL = 5; // Inter-phase interval, 10 microsecond steps
public const uint PULSEDUR2 = 6; // Phase 2 duration, 1 microsecond steps
public const uint INTERPULSEINTERVAL = 7; // Inter-pulse interval, 10 microsecond steps
public const uint BURSTCOUNT = 8; // Burst duration, number of pulses in burst
public const uint INTERBURSTINTERVAL = 9; // Inter-burst interval, microseconds
public const uint TRAINCOUNT = 10; // Pulse train duration, number of bursts in train
public const uint TRAINDELAY = 11; // Pulse train delay, microseconds
public const uint TRIGGER = 12; // Trigger stimulation (1 = deliver)
public const uint POWERON = 13; // Control estim sub-circuit power (0 = off, 1 = on)
public const uint ENABLE = 14; // If 0 then stimulation triggers will be ignored, otherwise they will be applied
public const uint RESTCURR = 15; // Resting current between pulse phases
public const uint RESET = 16; // Reset all parameters to default
public const uint REZ = 17; // Internal DAC resolution in bits

internal class NameConverter : DeviceNameConverter
{
public NameConverter()
: base(typeof(Headstage64ElectricalStimulator))
{
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using Bonsai;

namespace OpenEphys.Onix
{
public class Headstage64ElectricalStimulatorTrigger: Sink<bool>
{
[TypeConverter(typeof(Headstage64ElectricalStimulator.NameConverter))]
public string DeviceName { get; set; }

public override IObservable<bool> Process(IObservable<bool> source)
{
return Observable.Using(
() => DeviceManager.ReserveDevice(DeviceName),
disposable => disposable.Subject.SelectMany(deviceInfo =>
{
var device = deviceInfo.GetDeviceContext(typeof(Headstage64ElectricalStimulator));
return source.Do(t => device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, t ? 1u : 0u));
}));
}
}
}

0 comments on commit 3efaf1b

Please sign in to comment.