From 203054b89fd4ad502afd0b627292d18fce4dbb7b Mon Sep 17 00:00:00 2001 From: jonnew Date: Fri, 28 Jun 2024 12:03:33 -0400 Subject: [PATCH] Bring rhs2116 up to date with main and gateware 0.3 - Stimulus trigger device accepts a delay in microsends to the applicatin of a stimulus - Mark CheckLinkStatus as virtual so headstage can be reset between power cycles (see #120) - Rebase main and take advantage of latest improvments with respect to port voltage override --- Bonsai/Bonsai.config | 24 +++++--- .../OpenEphys.Onix.Design.csproj | 2 +- .../ConfigureFmcLinkController.cs | 2 +- .../ConfigureHeadstageRhs2116.cs | 60 +++++++++++-------- .../OpenEphys.Onix/ConfigureRhs2116.cs | 1 + .../OpenEphys.Onix/OpenEphys.Onix.csproj | 2 +- OpenEphys.Onix/OpenEphys.Onix/Rhs2116Data.cs | 4 +- .../OpenEphys.Onix/Rhs2116StimulusTrigger.cs | 14 +++-- 8 files changed, 66 insertions(+), 43 deletions(-) diff --git a/Bonsai/Bonsai.config b/Bonsai/Bonsai.config index 851577f4..818e4125 100644 --- a/Bonsai/Bonsai.config +++ b/Bonsai/Bonsai.config @@ -1,23 +1,26 @@  - - - + + + - + + + + @@ -43,26 +46,31 @@ + + - - - + + + - + + + + diff --git a/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj b/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj index 2f8e34b8..f93237f0 100644 --- a/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj +++ b/OpenEphys.Onix/OpenEphys.Onix.Design/OpenEphys.Onix.Design.csproj @@ -10,7 +10,7 @@ - + diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs index 0b71b0a1..c93ffb55 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureFmcLinkController.cs @@ -21,7 +21,7 @@ public ConfigureFmcLinkController() "Consult the device datasheet and documentation for allowable voltage ranges.")] public double? PortVoltage { get; set; } = null; - protected bool CheckLinkState(DeviceContext device) + protected virtual bool CheckLinkState(DeviceContext device) { var linkState = device.ReadRegister(FmcLinkController.LINKSTATE); return (linkState & FmcLinkController.LINKSTATE_SL) != 0; diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstageRhs2116.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstageRhs2116.cs index 4b55298e..18311894 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstageRhs2116.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureHeadstageRhs2116.cs @@ -31,12 +31,12 @@ public ConfigureHeadstageRhs2116() [TypeConverter(typeof(HubDeviceConverter))] public ConfigureRhs2116Trigger StimulusTrigger { get; set; } = new(); - internal override void UpdateDeviceNames(string hubName) + internal override void UpdateDeviceNames() { - LinkController.DeviceName = $"{hubName}/{nameof(LinkController)}"; - Rhs2116A.DeviceName = $"{hubName}/{nameof(Rhs2116A)}"; - Rhs2116B.DeviceName = $"{hubName}/{nameof(Rhs2116B)}"; - StimulusTrigger.DeviceName = $"{hubName}/{nameof(StimulusTrigger)}"; + LinkController.DeviceName = GetFullDeviceName(nameof(LinkController)); + Rhs2116A.DeviceName = GetFullDeviceName(nameof(Rhs2116A)); + Rhs2116B.DeviceName = GetFullDeviceName(nameof(Rhs2116B)); + StimulusTrigger.DeviceName = GetFullDeviceName(nameof(StimulusTrigger)); } public PortName Port @@ -53,6 +53,16 @@ public PortName Port } } + + [Description("If defined, it will override automated voltage discovery and apply the specified voltage" + + "to the headstage. Warning: this device requires 3.4V to 4.4V for proper operation." + + "Supplying higher voltages may result in damage to the headstage.")] + public double? PortVoltage + { + get => LinkController.PortVoltage; + set => LinkController.PortVoltage = value; + } + internal override IEnumerable GetDevices() { yield return LinkController; @@ -65,40 +75,38 @@ class ConfigureHeadstageRhs2116LinkController : ConfigureFmcLinkController { protected override bool ConfigurePortVoltage(DeviceContext device) { - const uint MinVoltage = 33; - const uint MaxVoltage = 50; - const uint VoltageOffset = 25; - const uint VoltageIncrement = 02; + const double MinVoltage = 3.3; + const double MaxVoltage = 4.4; + const double VoltageOffset = 2.0; + const double VoltageIncrement = 0.2; - for (uint voltage = MinVoltage; voltage <= MaxVoltage; voltage += VoltageIncrement) + for (var voltage = MinVoltage; voltage <= MaxVoltage; voltage += VoltageIncrement) { SetPortVoltage(device, voltage); - if (CheckLinkState(device)) + if (base.CheckLinkState(device)) { SetPortVoltage(device, voltage + VoltageOffset); - if (CheckLinkState(device)) - { - // TODO: The RHS2116 headstage needs an additional reset after power on - // to provide its device table. - Thread.Sleep(500); - device.Context.Reset(); - return true; - } - else break; + return CheckLinkState(device); } } return false; } - private void SetPortVoltage(DeviceContext device, uint voltage) + private void SetPortVoltage(DeviceContext device, double voltage) { - const int WaitUntilVoltageOffSettles = 500; - const int WaitUntilVoltageOnSettles = 500; device.WriteRegister(FmcLinkController.PORTVOLTAGE, 0); - Thread.Sleep(WaitUntilVoltageOffSettles); - device.WriteRegister(FmcLinkController.PORTVOLTAGE, voltage); - Thread.Sleep(WaitUntilVoltageOnSettles); + Thread.Sleep(500); + device.WriteRegister(FmcLinkController.PORTVOLTAGE, (uint)(10 * voltage)); + Thread.Sleep(500); + } + + protected override bool CheckLinkState(DeviceContext device) + { + // NB: The RHS2116 headstage needs an additional reset after power on to provide its device table. + device.Context.Reset(); + var linkState = device.ReadRegister(FmcLinkController.LINKSTATE); + return (linkState & FmcLinkController.LINKSTATE_SL) != 0; } } } diff --git a/OpenEphys.Onix/OpenEphys.Onix/ConfigureRhs2116.cs b/OpenEphys.Onix/OpenEphys.Onix/ConfigureRhs2116.cs index a66e9632..c99cd62d 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/ConfigureRhs2116.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/ConfigureRhs2116.cs @@ -172,6 +172,7 @@ static class Rhs2116 // constants public const int AmplifierChannelCount = 16; public const int StimMemorySlotsAvailable = 1024; + public const double SampleFrequencyHz = 30.1932367151e3; // managed registers public const uint ENABLE = 0x8000; // Enable or disable the data output stream (32767) diff --git a/OpenEphys.Onix/OpenEphys.Onix/OpenEphys.Onix.csproj b/OpenEphys.Onix/OpenEphys.Onix/OpenEphys.Onix.csproj index 43be3221..5f7cae43 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/OpenEphys.Onix.csproj +++ b/OpenEphys.Onix/OpenEphys.Onix/OpenEphys.Onix.csproj @@ -11,7 +11,7 @@ - + diff --git a/OpenEphys.Onix/OpenEphys.Onix/Rhs2116Data.cs b/OpenEphys.Onix/OpenEphys.Onix/Rhs2116Data.cs index 8a0d6ccb..6a5533d6 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/Rhs2116Data.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/Rhs2116Data.cs @@ -41,8 +41,8 @@ public unsafe override IObservable Generate() clockBuffer[sampleIndex] = frame.Clock; if (++sampleIndex >= bufferSize) { - var amplifierData = BufferHelper.CopyBuffer(amplifierBuffer, bufferSize, Rhs2116.AmplifierChannelCount, Depth.U16); - var dcData = BufferHelper.CopyBuffer(dcBuffer, bufferSize, Rhs2116.AmplifierChannelCount, Depth.U16); + var amplifierData = BufferHelper.CopyTranspose(amplifierBuffer, bufferSize, Rhs2116.AmplifierChannelCount, Depth.U16); + var dcData = BufferHelper.CopyTranspose(dcBuffer, bufferSize, Rhs2116.AmplifierChannelCount, Depth.U16); observer.OnNext(new Rhs2116DataFrame(clockBuffer, hubClockBuffer, amplifierData, dcData)); hubClockBuffer = new ulong[bufferSize]; clockBuffer = new ulong[bufferSize]; diff --git a/OpenEphys.Onix/OpenEphys.Onix/Rhs2116StimulusTrigger.cs b/OpenEphys.Onix/OpenEphys.Onix/Rhs2116StimulusTrigger.cs index 159764d7..93dcaaad 100644 --- a/OpenEphys.Onix/OpenEphys.Onix/Rhs2116StimulusTrigger.cs +++ b/OpenEphys.Onix/OpenEphys.Onix/Rhs2116StimulusTrigger.cs @@ -6,19 +6,25 @@ namespace OpenEphys.Onix { - public class Rhs2116StimulusTrigger : Sink + public class Rhs2116StimulusTrigger : Sink { [TypeConverter(typeof(Rhs2116Trigger.NameConverter))] public string DeviceName { get; set; } - public override IObservable Process(IObservable source) + public override IObservable Process(IObservable source) { return Observable.Using( () => DeviceManager.ReserveDevice(DeviceName), disposable => disposable.Subject.SelectMany(deviceInfo => { - var device = deviceInfo.GetDeviceContext(typeof(Rhs2116Trigger)); - return source.Do(t => device.WriteRegister(Rhs2116Trigger.TRIGGER, t ? 1u : 0u)); + var device = deviceInfo.GetDeviceContext(typeof(Rhs2116Trigger)); + return source.Do(t => + { + const double SampleFrequencyMegaHz = Rhs2116.SampleFrequencyHz / 1e6; + var delaySamples = (int)(t * SampleFrequencyMegaHz); + device.WriteRegister(Rhs2116Trigger.TRIGGER, (uint)(delaySamples << 12 | 0x1)); + }); + })); } }