From 9ea405fd09229ba8e7c8eca4f60151011e81e575 Mon Sep 17 00:00:00 2001 From: jonnew Date: Thu, 12 Sep 2024 17:53:26 -0400 Subject: [PATCH 1/3] Implement the fmc card's output clock - The user can now create a divided copy of the acquisition clock to sychronize ONIX with external hardware - The hardware actualized parameters of this clock are made availalbe in the BreakoutBoardClockData class --- OpenEphys.Onix1/BreakoutOutputClockData.cs | 40 ++++ OpenEphys.Onix1/ConfigureBreakoutBoard.cs | 9 + .../ConfigureBreakoutOutputClock.cs | 203 ++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100644 OpenEphys.Onix1/BreakoutOutputClockData.cs create mode 100644 OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs diff --git a/OpenEphys.Onix1/BreakoutOutputClockData.cs b/OpenEphys.Onix1/BreakoutOutputClockData.cs new file mode 100644 index 0000000..4b74c99 --- /dev/null +++ b/OpenEphys.Onix1/BreakoutOutputClockData.cs @@ -0,0 +1,40 @@ +using System; +using System.ComponentModel; +using System.Linq; +using System.Reactive.Linq; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + + /// + /// Produces a sequence with a single element containing the output clock's exact hardware parameters. + /// + /// + /// This data IO operator must be linked to an appropriate configuration, such as a , using a shared DeviceName. + /// + [Description("Produces a sequence of analog input frames from an ONIX breakout board.")] + public class BreakoutOutputClockData : Source + { + /// + [TypeConverter(typeof(BreakoutOutputClock.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + [Category(DeviceFactory.ConfigurationCategory)] + public string DeviceName { get; set; } + + /// + /// Generates a sequence containing a single . + /// + /// A sequence containing a single + public unsafe override IObservable Generate() + { + return DeviceManager.GetDevice(DeviceName).SelectMany( + deviceInfo => + { + var clockOutDeviceInfo = (BreakoutOutputClockDeviceInfo)deviceInfo; + return Observable.Defer(() => Observable.Return(clockOutDeviceInfo.Parameters)); + }); + } + } +} diff --git a/OpenEphys.Onix1/ConfigureBreakoutBoard.cs b/OpenEphys.Onix1/ConfigureBreakoutBoard.cs index ff95ca1..56868f5 100644 --- a/OpenEphys.Onix1/ConfigureBreakoutBoard.cs +++ b/OpenEphys.Onix1/ConfigureBreakoutBoard.cs @@ -46,6 +46,14 @@ public class ConfigureBreakoutBoard : MultiDeviceFactory [Category(DevicesCategory)] public ConfigureBreakoutDigitalIO DigitalIO { get; set; } = new(); + /// + /// Gets or sets the breakout board's output clock configuration. + /// + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the clock output in the ONIX breakout board.")] + [Category(DevicesCategory)] + public ConfigureBreakoutOutputClock ClockOutput { get; set; } = new(); + /// /// Gets or sets the hardware memory monitor configuration. /// @@ -59,6 +67,7 @@ internal override IEnumerable GetDevices() yield return Heartbeat; yield return AnalogIO; yield return DigitalIO; + yield return ClockOutput; yield return MemoryMonitor; } } diff --git a/OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs b/OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs new file mode 100644 index 0000000..fdc98c7 --- /dev/null +++ b/OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs @@ -0,0 +1,203 @@ +using System; +using System.ComponentModel; +using System.Drawing.Design; +using System.Reactive.Disposables; +using System.Reactive.Subjects; +using Bonsai; + +namespace OpenEphys.Onix1 +{ + /// + /// Configures a the breakout board's output clock. + /// + /// + /// The output clock provides physical, 3.3V logic level, 50 Ohm output impedance, frequency divided copy + /// of the Acquisition Clock that is used to generate + /// values for all data streams within an ONIX system. This clock runs a + /// user defined rate, duty cycle, and start delay. It can be used to drive external hardware or can be + /// logged by external recording systems for post-hoc synchronization with ONIX data. + /// + [Description("Configures a heartbeat device.")] + public class ConfigureBreakoutOutputClock : SingleDeviceFactory + { + readonly BehaviorSubject gate = new(false); + + /// + /// Initializes a new instance of the class. + /// + public ConfigureBreakoutOutputClock() + : base(typeof(BreakoutOutputClock)) + { + DeviceAddress = 5; + } + + /// + /// Gets or sets a value specifying of the output clock is active. + /// + /// + /// If set to true, the clock output will connected to the clock output line. If set to false, the + /// clock output line will be held low. + /// + [Range(1, 10e6)] + [Category(AcquisitionCategory)] + [Description("Clock gate control signal.")] + public bool ClockGate + { + get => gate.Value; + set => gate.OnNext(value); + } + + /// + /// Gets or sets the output clock frequency in Hz. + /// + /// + /// The output clock high and low times must each be an integer multiple of the Acquisition Clock frequency. Therefore, the true clock + /// frequency will be set to a value that is as close as possible to the requested setting while + /// respecting this constraint. The value as actualized in hardware is available using . + /// + [Range(1, 10e6)] + [Category(ConfigurationCategory)] + [Description("Frequency of the output clock (Hz).")] + public double Frequency { get; set; } = 1e6; + + /// + /// Gets or sets the output clock duty cycle in percent. + /// + /// + /// The output clock high and low times must each be an integer multiple of the Acquisition Clock frequency. Therefore, the true duty + /// cycle will be set to a value that is as close as possible to the requested setting while + /// respecting this constraint. The value as actualized in hardware is available using . + /// + [Range(10, 90)] + [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] + [Category(ConfigurationCategory)] + [Precision(1, 1)] + [Description("Duty cycle of output clock (%).")] + public double DutyCycle { get; set; } = 50.0; + + /// + /// Gets or sets the delay following acquisition start before the clock becomes active in seconds. + /// + /// + /// + /// Setting to a value greater than 0 can be useful for ensuring data sources that are driven by the + /// System Clock start significantly after ONIX has begun aquisition for the purposes of + /// ordering acquisition start times. + /// + /// + /// The delay must each be an integer multiple of the Acquisition Clock frequency. Therefore, the true delay + /// cycle will be set to a value that is as close as possible to the requested setting while + /// respecting this constraint. The value as actualized in hardware is available using . + /// + /// + [Category(ConfigurationCategory)] + [Description("Specifies a delay following acquisition start before the clock becomes active (sec).")] + [Range(0, 3600)] + public double Delay { get; set; } = 0; + + /// + /// Configures a clock output. + /// + /// + /// This will schedule configuration actions to be applied by a + /// instance prior to data acquisition. + /// + /// A sequence of instances that holds configuration + /// actions. + /// The original sequence modified by adding additional configuration actions required to + /// configure a clock output device./> + public override IObservable Process(IObservable source) + { + var clkFreqHz = Frequency; + var dutyCycle = DutyCycle; + var delaySeconds = Delay; + var deviceName = DeviceName; + var deviceAddress = DeviceAddress; + + // TODO: Dispose action that turns clock off + return source.ConfigureDevice((context, observer) => + { + var device = context.GetDeviceContext(deviceAddress, DeviceType); + + var baseFreqHz = device.ReadRegister(BreakoutOutputClock.BASE_FREQ_HZ); + var periodTicks = (uint)(baseFreqHz / clkFreqHz); + var h = (uint)(periodTicks * (dutyCycle / 100)); + var l = periodTicks - h; + var delayTicks = (uint)(delaySeconds * baseFreqHz); + device.WriteRegister(BreakoutOutputClock.HIGH_CYCLES, h); + device.WriteRegister(BreakoutOutputClock.LOW_CYCLES, l); + device.WriteRegister(BreakoutOutputClock.DELAY_CYCLES, delayTicks); + + var deviceInfo = new BreakoutOutputClockDeviceInfo(device, DeviceType, + new BreakoutOutputClockParameters(baseFreqHz / (h + l), 100 * h / periodTicks, delaySeconds, h + l, h, delayTicks)); + + var shutdown = Disposable.Create(() => + { + device.WriteRegister(BreakoutOutputClock.CLOCK_GATE, 0u); + }); + + return new CompositeDisposable( + DeviceManager.RegisterDevice(deviceName, deviceInfo), + gate.SubscribeSafe(observer, value => device.WriteRegister(BreakoutOutputClock.CLOCK_GATE, value ? 1u : 0u)), + shutdown + ); + }); + } + } + + static class BreakoutOutputClock + { + public const int ID = 20; + + public const uint NULL = 0; // No command + public const uint CLOCK_GATE = 1; // Output enable. Bit 0 = 0 is disabled, Bit 0 = 1 is enabled. + public const uint HIGH_CYCLES = 2; // Number of input clock cycles output clock should be high. Valid values are 1 or greater. + public const uint LOW_CYCLES = 3; // Number of input clock cycles output clock should be low. Valid values are 1 or greater. + public const uint DELAY_CYCLES = 4; // Number of input clock cycles output clock should be low. Valid values are 1 or greater. + public const uint GATE_RUN = 5; // Number of input clock cycles output clock should be low. Valid values are 1 or greater. + public const uint BASE_FREQ_HZ = 6; // Number of input clock cycles output clock should be low. Valid values are 1 or greater. + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(BreakoutOutputClock)) + { + } + } + } + + /// + /// Hardware-verified output clock parameters. + /// + /// Gets the exact clock frequency as actualized by the clock synthesizer in + /// Hz. + /// Gets the exact clock duty cycle as actualized by the clock synthesizer + /// in percent. + /// Gets the exact clock delay as actualized by the clock synthesizer in + /// seconds. + /// Gets the exact clock period as actualized by the clock synthesizer in units + /// of ticks of the of the Acquisition Clock. + /// Gets the exact clock high time as actualized by the clock synthesizer in units + /// of ticks of the of the Acquisition Clock. + /// Gets the exact clock delay as actualized by the clock synthesizer in units of + /// ticks of the of the Acquisition Clock. + public readonly record struct BreakoutOutputClockParameters(double FrequencyHz, + double DutyCyclePercent, double DelaySeconds, uint PeriodTicks, uint HighTicks, uint DelayTicks); + + class BreakoutOutputClockDeviceInfo : DeviceInfo + { + public BreakoutOutputClockDeviceInfo(DeviceContext device, Type deviceType, BreakoutOutputClockParameters parameters) + : base(device, deviceType) + { + Parameters = parameters; + } + + public BreakoutOutputClockParameters Parameters { get; } + } +} From 1f4e21b62f9c7197e48ff1afc11f5c8c843d0452 Mon Sep 17 00:00:00 2001 From: jonnew Date: Fri, 13 Sep 2024 10:58:47 -0400 Subject: [PATCH 2/3] Minor fixes to breakout board clock ouput - Constrain frequency and duty cycle with exceptions - Remove unsafe designation from BreakoutOutputClockData.Generate() - Lots of XML improvments - Improve public property names --- OpenEphys.Onix1/BreakoutOutputClockData.cs | 2 +- .../ConfigureBreakoutOutputClock.cs | 87 ++++++++++++------- 2 files changed, 55 insertions(+), 34 deletions(-) diff --git a/OpenEphys.Onix1/BreakoutOutputClockData.cs b/OpenEphys.Onix1/BreakoutOutputClockData.cs index 4b74c99..50a0f2b 100644 --- a/OpenEphys.Onix1/BreakoutOutputClockData.cs +++ b/OpenEphys.Onix1/BreakoutOutputClockData.cs @@ -27,7 +27,7 @@ public class BreakoutOutputClockData : Source /// Generates a sequence containing a single . /// /// A sequence containing a single - public unsafe override IObservable Generate() + public override IObservable Generate() { return DeviceManager.GetDevice(DeviceName).SelectMany( deviceInfo => diff --git a/OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs b/OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs index fdc98c7..3d9fbd5 100644 --- a/OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs +++ b/OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs @@ -8,7 +8,7 @@ namespace OpenEphys.Onix1 { /// - /// Configures a the breakout board's output clock. + /// Configures the ONIX breakout board's output clock. /// /// /// The output clock provides physical, 3.3V logic level, 50 Ohm output impedance, frequency divided copy @@ -17,10 +17,12 @@ namespace OpenEphys.Onix1 /// user defined rate, duty cycle, and start delay. It can be used to drive external hardware or can be /// logged by external recording systems for post-hoc synchronization with ONIX data. /// - [Description("Configures a heartbeat device.")] + [Description("Configures the ONIX breakout board's output clock.")] public class ConfigureBreakoutOutputClock : SingleDeviceFactory { readonly BehaviorSubject gate = new(false); + double frequencyHz = 1e6; + double dutyCycle = 50; /// /// Initializes a new instance of the class. @@ -32,13 +34,13 @@ public ConfigureBreakoutOutputClock() } /// - /// Gets or sets a value specifying of the output clock is active. + /// Gets or sets a value specifying if the output clock is active. /// /// /// If set to true, the clock output will connected to the clock output line. If set to false, the - /// clock output line will be held low. + /// clock output line will be held low. This value can be toggled in real time to gate acquisition of + /// external hardware. /// - [Range(1, 10e6)] [Category(AcquisitionCategory)] [Description("Clock gate control signal.")] public bool ClockGate @@ -51,48 +53,63 @@ public bool ClockGate /// Gets or sets the output clock frequency in Hz. /// /// - /// The output clock high and low times must each be an integer multiple of the Acquisition Clock frequency. Therefore, the true clock - /// frequency will be set to a value that is as close as possible to the requested setting while - /// respecting this constraint. The value as actualized in hardware is available using . + /// Valid values are between 0.1 Hz and 10 MHz. The output clock high and low times must each be an + /// integer multiple of the Acquisition Clock + /// frequency. Therefore, the true clock frequency will be set to a value that is as close as possible + /// to the requested setting while respecting this constraint. The value as actualized in hardware is + /// reported by . /// - [Range(1, 10e6)] + [Range(0.1, 10e6)] [Category(ConfigurationCategory)] [Description("Frequency of the output clock (Hz).")] - public double Frequency { get; set; } = 1e6; + public double Frequency + { + get => frequencyHz; + set => frequencyHz = value >= 0.1 && value <= 10e6 + ? value + : throw new ArgumentOutOfRangeException(nameof(Frequency), value, + $"{nameof(Frequency)} must be between 0.1 Hz and 10 MHz."); + } /// /// Gets or sets the output clock duty cycle in percent. /// /// - /// The output clock high and low times must each be an integer multiple of the Acquisition Clock frequency. Therefore, the true duty - /// cycle will be set to a value that is as close as possible to the requested setting while - /// respecting this constraint. The value as actualized in hardware is available using . + /// Valid values are between 10% and 90%. The output clock high and low times must each be an integer + /// multiple of the Acquisition Clock frequency. + /// Therefore, the true duty cycle will be set to a value that is as close as possible to the + /// requested setting while respecting this constraint. The value as actualized in hardware is + /// reported by . /// [Range(10, 90)] [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] [Category(ConfigurationCategory)] [Precision(1, 1)] [Description("Duty cycle of output clock (%).")] - public double DutyCycle { get; set; } = 50.0; + public double DutyCycle + { + get => dutyCycle; + set => dutyCycle = value >= 10 && value <= 90 + ? value + : throw new ArgumentOutOfRangeException(nameof(DutyCycle), value, + $"{nameof(DutyCycle)} must be between 10% and 90%."); + } /// - /// Gets or sets the delay following acquisition start before the clock becomes active in seconds. + /// Gets or sets the delay following acquisition commencement before the clock becomes active in + /// seconds. /// /// /// - /// Setting to a value greater than 0 can be useful for ensuring data sources that are driven by the - /// System Clock start significantly after ONIX has begun aquisition for the purposes of - /// ordering acquisition start times. + /// Valid values are between 0 and and 3600 seconds. Setting to a value greater than 0 can be useful + /// for ensuring data sources that are driven by the output clock start significantly after ONIX has + /// begun aquisition for the purposes of ordering acquisition start times. /// /// /// The delay must each be an integer multiple of the Acquisition Clock frequency. Therefore, the true delay /// cycle will be set to a value that is as close as possible to the requested setting while - /// respecting this constraint. The value as actualized in hardware is available using . /// /// @@ -134,8 +151,8 @@ public override IObservable Process(IObservable source device.WriteRegister(BreakoutOutputClock.LOW_CYCLES, l); device.WriteRegister(BreakoutOutputClock.DELAY_CYCLES, delayTicks); - var deviceInfo = new BreakoutOutputClockDeviceInfo(device, DeviceType, - new BreakoutOutputClockParameters(baseFreqHz / (h + l), 100 * h / periodTicks, delaySeconds, h + l, h, delayTicks)); + var deviceInfo = new BreakoutOutputClockDeviceInfo(device, DeviceType, + new(baseFreqHz / (h + l), 100 * h / periodTicks, delaySeconds, h + l, h, l, delayTicks)); var shutdown = Disposable.Create(() => { @@ -175,20 +192,24 @@ public NameConverter() /// /// Hardware-verified output clock parameters. /// - /// Gets the exact clock frequency as actualized by the clock synthesizer in + /// Gets the exact clock frequency as actualized by the clock synthesizer in /// Hz. - /// Gets the exact clock duty cycle as actualized by the clock synthesizer + /// Gets the exact clock duty cycle as actualized by the clock synthesizer /// in percent. - /// Gets the exact clock delay as actualized by the clock synthesizer in + /// Gets the exact clock delay as actualized by the clock synthesizer in /// seconds. /// Gets the exact clock period as actualized by the clock synthesizer in units /// of ticks of the of the Acquisition Clock. - /// Gets the exact clock high time as actualized by the clock synthesizer in units - /// of ticks of the of the Acquisition Clock. + /// Gets the exact clock high time per period as actualized by the clock + /// synthesizer in units of ticks of the of the Acquisition + /// Clock. + /// Gets the exact clock low time per period as actualized by the clock synthesizer + /// in units of ticks of the of the Acquisition + /// Clock. /// Gets the exact clock delay as actualized by the clock synthesizer in units of /// ticks of the of the Acquisition Clock. - public readonly record struct BreakoutOutputClockParameters(double FrequencyHz, - double DutyCyclePercent, double DelaySeconds, uint PeriodTicks, uint HighTicks, uint DelayTicks); + public readonly record struct BreakoutOutputClockParameters(double Frequency, + double DutyCycle, double Delay, uint PeriodTicks, uint HighTicks, uint LowTicks, uint DelayTicks); class BreakoutOutputClockDeviceInfo : DeviceInfo { @@ -198,6 +219,6 @@ public BreakoutOutputClockDeviceInfo(DeviceContext device, Type deviceType, Brea Parameters = parameters; } - public BreakoutOutputClockParameters Parameters { get; } + public BreakoutOutputClockParameters Parameters { get; } } } From 729597bf71545cd270eba0afec0ad8186304f25d Mon Sep 17 00:00:00 2001 From: jonnew Date: Mon, 16 Sep 2024 12:47:31 -0400 Subject: [PATCH 3/3] Address review comments - Rmove "Breakout" prefix from class names (this information is included through the BreakoutBoard MultiDeviceFactory when used in that context) - Lots of typo fixes - Fixed the agruements to OutpuClockParameters such that floating point arthmetic is used so we dont loose precision --- OpenEphys.Onix1/ConfigureBreakoutBoard.cs | 2 +- ...OutputClock.cs => ConfigureOutputClock.cs} | 69 +++++++++---------- ...tOutputClockData.cs => OutputClockData.cs} | 19 +++-- 3 files changed, 44 insertions(+), 46 deletions(-) rename OpenEphys.Onix1/{ConfigureBreakoutOutputClock.cs => ConfigureOutputClock.cs} (74%) rename OpenEphys.Onix1/{BreakoutOutputClockData.cs => OutputClockData.cs} (58%) diff --git a/OpenEphys.Onix1/ConfigureBreakoutBoard.cs b/OpenEphys.Onix1/ConfigureBreakoutBoard.cs index 56868f5..bd31b6c 100644 --- a/OpenEphys.Onix1/ConfigureBreakoutBoard.cs +++ b/OpenEphys.Onix1/ConfigureBreakoutBoard.cs @@ -52,7 +52,7 @@ public class ConfigureBreakoutBoard : MultiDeviceFactory [TypeConverter(typeof(SingleDeviceFactoryConverter))] [Description("Specifies the configuration for the clock output in the ONIX breakout board.")] [Category(DevicesCategory)] - public ConfigureBreakoutOutputClock ClockOutput { get; set; } = new(); + public ConfigureOutputClock ClockOutput { get; set; } = new(); /// /// Gets or sets the hardware memory monitor configuration. diff --git a/OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs b/OpenEphys.Onix1/ConfigureOutputClock.cs similarity index 74% rename from OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs rename to OpenEphys.Onix1/ConfigureOutputClock.cs index 3d9fbd5..1d17d61 100644 --- a/OpenEphys.Onix1/ConfigureBreakoutOutputClock.cs +++ b/OpenEphys.Onix1/ConfigureOutputClock.cs @@ -11,24 +11,24 @@ namespace OpenEphys.Onix1 /// Configures the ONIX breakout board's output clock. /// /// - /// The output clock provides physical, 3.3V logic level, 50 Ohm output impedance, frequency divided copy + /// The output clock provides a 3.3V logic level, 50 Ohm output impedance, frequency divided copy /// of the Acquisition Clock that is used to generate - /// values for all data streams within an ONIX system. This clock runs a + /// values for all data streams within an ONIX system. This clock runs at a /// user defined rate, duty cycle, and start delay. It can be used to drive external hardware or can be /// logged by external recording systems for post-hoc synchronization with ONIX data. /// [Description("Configures the ONIX breakout board's output clock.")] - public class ConfigureBreakoutOutputClock : SingleDeviceFactory + public class ConfigureOutputClock : SingleDeviceFactory { readonly BehaviorSubject gate = new(false); double frequencyHz = 1e6; double dutyCycle = 50; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public ConfigureBreakoutOutputClock() - : base(typeof(BreakoutOutputClock)) + public ConfigureOutputClock() + : base(typeof(OutputClock)) { DeviceAddress = 5; } @@ -37,7 +37,7 @@ public ConfigureBreakoutOutputClock() /// Gets or sets a value specifying if the output clock is active. /// /// - /// If set to true, the clock output will connected to the clock output line. If set to false, the + /// If set to true, the clock output will be connected to the clock output line. If set to false, the /// clock output line will be held low. This value can be toggled in real time to gate acquisition of /// external hardware. /// @@ -57,7 +57,7 @@ public bool ClockGate /// integer multiple of the Acquisition Clock /// frequency. Therefore, the true clock frequency will be set to a value that is as close as possible /// to the requested setting while respecting this constraint. The value as actualized in hardware is - /// reported by . + /// reported by . /// [Range(0.1, 10e6)] [Category(ConfigurationCategory)] @@ -79,7 +79,7 @@ public double Frequency /// multiple of the Acquisition Clock frequency. /// Therefore, the true duty cycle will be set to a value that is as close as possible to the /// requested setting while respecting this constraint. The value as actualized in hardware is - /// reported by . + /// reported by . /// [Range(10, 90)] [Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))] @@ -103,14 +103,14 @@ public double DutyCycle /// /// Valid values are between 0 and and 3600 seconds. Setting to a value greater than 0 can be useful /// for ensuring data sources that are driven by the output clock start significantly after ONIX has - /// begun aquisition for the purposes of ordering acquisition start times. + /// begun acquisition for the purposes of ordering acquisition start times. /// /// - /// The delay must each be an integer multiple of the Acquisition Clock frequency. Therefore, the true delay /// cycle will be set to a value that is as close as possible to the requested setting while /// respecting this constraint. The value as actualized in hardware is reported by . + /// cref="OutputClockData"/>. /// /// [Category(ConfigurationCategory)] @@ -137,53 +137,52 @@ public override IObservable Process(IObservable source var deviceName = DeviceName; var deviceAddress = DeviceAddress; - // TODO: Dispose action that turns clock off return source.ConfigureDevice((context, observer) => { var device = context.GetDeviceContext(deviceAddress, DeviceType); - var baseFreqHz = device.ReadRegister(BreakoutOutputClock.BASE_FREQ_HZ); + var baseFreqHz = device.ReadRegister(OutputClock.BASE_FREQ_HZ); var periodTicks = (uint)(baseFreqHz / clkFreqHz); var h = (uint)(periodTicks * (dutyCycle / 100)); var l = periodTicks - h; var delayTicks = (uint)(delaySeconds * baseFreqHz); - device.WriteRegister(BreakoutOutputClock.HIGH_CYCLES, h); - device.WriteRegister(BreakoutOutputClock.LOW_CYCLES, l); - device.WriteRegister(BreakoutOutputClock.DELAY_CYCLES, delayTicks); + device.WriteRegister(OutputClock.HIGH_CYCLES, h); + device.WriteRegister(OutputClock.LOW_CYCLES, l); + device.WriteRegister(OutputClock.DELAY_CYCLES, delayTicks); - var deviceInfo = new BreakoutOutputClockDeviceInfo(device, DeviceType, - new(baseFreqHz / (h + l), 100 * h / periodTicks, delaySeconds, h + l, h, l, delayTicks)); + var deviceInfo = new OutputClockDeviceInfo(device, DeviceType, + new((double)baseFreqHz / periodTicks, 100.0 * h / periodTicks, delaySeconds, h + l, h, l, delayTicks)); var shutdown = Disposable.Create(() => { - device.WriteRegister(BreakoutOutputClock.CLOCK_GATE, 0u); + device.WriteRegister(OutputClock.CLOCK_GATE, 0u); }); return new CompositeDisposable( DeviceManager.RegisterDevice(deviceName, deviceInfo), - gate.SubscribeSafe(observer, value => device.WriteRegister(BreakoutOutputClock.CLOCK_GATE, value ? 1u : 0u)), + gate.SubscribeSafe(observer, value => device.WriteRegister(OutputClock.CLOCK_GATE, value ? 1u : 0u)), shutdown ); }); } } - static class BreakoutOutputClock + static class OutputClock { public const int ID = 20; public const uint NULL = 0; // No command - public const uint CLOCK_GATE = 1; // Output enable. Bit 0 = 0 is disabled, Bit 0 = 1 is enabled. + public const uint CLOCK_GATE = 1; // Output gate. Bit 0 = 0 is disabled, Bit 0 = 1 is enabled. public const uint HIGH_CYCLES = 2; // Number of input clock cycles output clock should be high. Valid values are 1 or greater. public const uint LOW_CYCLES = 3; // Number of input clock cycles output clock should be low. Valid values are 1 or greater. - public const uint DELAY_CYCLES = 4; // Number of input clock cycles output clock should be low. Valid values are 1 or greater. - public const uint GATE_RUN = 5; // Number of input clock cycles output clock should be low. Valid values are 1 or greater. - public const uint BASE_FREQ_HZ = 6; // Number of input clock cycles output clock should be low. Valid values are 1 or greater. + public const uint DELAY_CYCLES = 4; // Delay, in input clock cycles, following reset before clock becomes active. + public const uint GATE_RUN = 5; // LSB sets the gate using run status. Bit 0 = 0: Clock runs whenever CLOCK_GATE(0) is 1. Bit 0 = 1: Clock runs only when acquisition is in RUNNING state. + public const uint BASE_FREQ_HZ = 6; // Frequency of the input clock in Hz. internal class NameConverter : DeviceNameConverter { public NameConverter() - : base(typeof(BreakoutOutputClock)) + : base(typeof(OutputClock)) { } } @@ -199,26 +198,26 @@ public NameConverter() /// Gets the exact clock delay as actualized by the clock synthesizer in /// seconds. /// Gets the exact clock period as actualized by the clock synthesizer in units - /// of ticks of the of the Acquisition Clock. + /// of ticks of the Acquisition Clock. /// Gets the exact clock high time per period as actualized by the clock - /// synthesizer in units of ticks of the of the Acquisition + /// synthesizer in units of ticks of the Acquisition /// Clock. /// Gets the exact clock low time per period as actualized by the clock synthesizer - /// in units of ticks of the of the Acquisition + /// in units of ticks of the Acquisition /// Clock. /// Gets the exact clock delay as actualized by the clock synthesizer in units of - /// ticks of the of the Acquisition Clock. - public readonly record struct BreakoutOutputClockParameters(double Frequency, + /// ticks of the Acquisition Clock. + public readonly record struct OutputClockParameters(double Frequency, double DutyCycle, double Delay, uint PeriodTicks, uint HighTicks, uint LowTicks, uint DelayTicks); - class BreakoutOutputClockDeviceInfo : DeviceInfo + class OutputClockDeviceInfo : DeviceInfo { - public BreakoutOutputClockDeviceInfo(DeviceContext device, Type deviceType, BreakoutOutputClockParameters parameters) + public OutputClockDeviceInfo(DeviceContext device, Type deviceType, OutputClockParameters parameters) : base(device, deviceType) { Parameters = parameters; } - public BreakoutOutputClockParameters Parameters { get; } + public OutputClockParameters Parameters { get; } } } diff --git a/OpenEphys.Onix1/BreakoutOutputClockData.cs b/OpenEphys.Onix1/OutputClockData.cs similarity index 58% rename from OpenEphys.Onix1/BreakoutOutputClockData.cs rename to OpenEphys.Onix1/OutputClockData.cs index 50a0f2b..417823e 100644 --- a/OpenEphys.Onix1/BreakoutOutputClockData.cs +++ b/OpenEphys.Onix1/OutputClockData.cs @@ -6,33 +6,32 @@ namespace OpenEphys.Onix1 { - /// - /// Produces a sequence with a single element containing the output clock's exact hardware parameters. + /// Produces a sequence with a single element containing the output clock's exact hardware parameters for each subscription. /// /// /// This data IO operator must be linked to an appropriate configuration, such as a , using a shared DeviceName. + /// cref="ConfigureOutputClock"/>, using a shared DeviceName. /// - [Description("Produces a sequence of analog input frames from an ONIX breakout board.")] - public class BreakoutOutputClockData : Source + [Description("Produces a sequence with a single element containing the output clock's hardware parameters for each subscription.")] + public class OutputClockData : Source { /// - [TypeConverter(typeof(BreakoutOutputClock.NameConverter))] + [TypeConverter(typeof(OutputClock.NameConverter))] [Description(SingleDeviceFactory.DeviceNameDescription)] [Category(DeviceFactory.ConfigurationCategory)] public string DeviceName { get; set; } /// - /// Generates a sequence containing a single . + /// Generates a sequence containing a single structure. /// - /// A sequence containing a single - public override IObservable Generate() + /// A sequence containing a single structure. + public override IObservable Generate() { return DeviceManager.GetDevice(DeviceName).SelectMany( deviceInfo => { - var clockOutDeviceInfo = (BreakoutOutputClockDeviceInfo)deviceInfo; + var clockOutDeviceInfo = (OutputClockDeviceInfo)deviceInfo; return Observable.Defer(() => Observable.Return(clockOutDeviceInfo.Parameters)); }); }