Skip to content

Commit

Permalink
Merge pull request #66 from neurogears/issue-14
Browse files Browse the repository at this point in the history
Add HS64 electrical stim and trigger nodes
  • Loading branch information
glopesdev authored Apr 3, 2024
2 parents 99b42ae + 6e72d09 commit 830f5bd
Show file tree
Hide file tree
Showing 3 changed files with 245 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 @@ -31,6 +31,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();

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

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

class ConfigureHeadstage64LinkController : ConfigureFmcLinkController
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;

namespace OpenEphys.Onix
{
public class ConfigureHeadstage64ElectricalStimulator : SingleDeviceFactory
{
public ConfigureHeadstage64ElectricalStimulator()
: base(typeof(Headstage64ElectricalStimulator))
{
}

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, 0);
return DeviceManager.RegisterDevice(deviceName, device, DeviceType);
});
}
}

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,182 @@
using System;
using System.ComponentModel;
using System.Drawing.Design;
using System.Linq;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using Bonsai;

namespace OpenEphys.Onix
{
public class Headstage64ElectricalStimulatorTrigger: Sink<bool>
{
readonly BehaviorSubject<bool> enable = new(true);
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;

[TypeConverter(typeof(Headstage64ElectricalStimulator.NameConverter))]
public string DeviceName { get; set; }

[Description("Specifies whether the electrical stimulation subcircuit will respect triggers.")]
public bool Enable
{
get => enable.Value;
set => enable.OnNext(value);
}

[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);
}

[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);
}

[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);
}

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

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

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

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

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

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

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

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

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

public override IObservable<bool> Process(IObservable<bool> source)
{
return Observable.Using(
() => DeviceManager.ReserveDevice(DeviceName),
disposable => disposable.Subject.SelectMany(deviceInfo =>
Observable.Create<bool>(observer =>
{
var device = deviceInfo.GetDeviceContext(typeof(Headstage64ElectricalStimulator));
var triggerObserver = Observer.Create<bool>(
value => device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, value ? 1u : 0u),
observer.OnError,
observer.OnCompleted);

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

return new CompositeDisposable(
enable.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.ENABLE, value ? 1u : 0u)),
phaseOneCurrent.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT1, uAToCode(value))),
interPhaseCurrent.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.RESTCURR, uAToCode(value))),
phaseTwoCurrent.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT2, uAToCode(value))),
trainDelay.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.TRAINDELAY, value)),
phaseOneDuration.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR1, value)),
interPhaseInterval.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.INTERPHASEINTERVAL, value)),
phaseTwoDuration.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR2, value)),
interPulseInterval.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.INTERPULSEINTERVAL, value)),
interBurstInterval.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.INTERBURSTINTERVAL, value)),
burstPulseCount.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.BURSTCOUNT, value)),
trainBurstCount.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.TRAINCOUNT, value)),
powerEnable.Subscribe(value => device.WriteRegister(Headstage64ElectricalStimulator.POWERON, value ? 1u : 0u)),
source.SubscribeSafe(triggerObserver)
);
})));
}
}
}

0 comments on commit 830f5bd

Please sign in to comment.