Skip to content

Commit 9ea405f

Browse files
committed
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
1 parent f48d2f2 commit 9ea405f

File tree

3 files changed

+252
-0
lines changed

3 files changed

+252
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Linq;
4+
using System.Reactive.Linq;
5+
using Bonsai;
6+
7+
namespace OpenEphys.Onix1
8+
{
9+
10+
/// <summary>
11+
/// Produces a sequence with a single element containing the output clock's exact hardware parameters.
12+
/// </summary>
13+
/// <remarks>
14+
/// This data IO operator must be linked to an appropriate configuration, such as a <see
15+
/// cref="ConfigureBreakoutOutputClock"/>, using a shared <c>DeviceName</c>.
16+
/// </remarks>
17+
[Description("Produces a sequence of analog input frames from an ONIX breakout board.")]
18+
public class BreakoutOutputClockData : Source<BreakoutOutputClockParameters>
19+
{
20+
/// <inheritdoc cref = "SingleDeviceFactory.DeviceName"/>
21+
[TypeConverter(typeof(BreakoutOutputClock.NameConverter))]
22+
[Description(SingleDeviceFactory.DeviceNameDescription)]
23+
[Category(DeviceFactory.ConfigurationCategory)]
24+
public string DeviceName { get; set; }
25+
26+
/// <summary>
27+
/// Generates a sequence containing a single <see cref="BreakoutOutputClockParameters"/>.
28+
/// </summary>
29+
/// <returns>A sequence containing a single <see cref="BreakoutOutputClockParameters"/></returns>
30+
public unsafe override IObservable<BreakoutOutputClockParameters> Generate()
31+
{
32+
return DeviceManager.GetDevice(DeviceName).SelectMany(
33+
deviceInfo =>
34+
{
35+
var clockOutDeviceInfo = (BreakoutOutputClockDeviceInfo)deviceInfo;
36+
return Observable.Defer(() => Observable.Return(clockOutDeviceInfo.Parameters));
37+
});
38+
}
39+
}
40+
}

OpenEphys.Onix1/ConfigureBreakoutBoard.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ public class ConfigureBreakoutBoard : MultiDeviceFactory
4646
[Category(DevicesCategory)]
4747
public ConfigureBreakoutDigitalIO DigitalIO { get; set; } = new();
4848

49+
/// <summary>
50+
/// Gets or sets the breakout board's output clock configuration.
51+
/// </summary>
52+
[TypeConverter(typeof(SingleDeviceFactoryConverter))]
53+
[Description("Specifies the configuration for the clock output in the ONIX breakout board.")]
54+
[Category(DevicesCategory)]
55+
public ConfigureBreakoutOutputClock ClockOutput { get; set; } = new();
56+
4957
/// <summary>
5058
/// Gets or sets the hardware memory monitor configuration.
5159
/// </summary>
@@ -59,6 +67,7 @@ internal override IEnumerable<IDeviceConfiguration> GetDevices()
5967
yield return Heartbeat;
6068
yield return AnalogIO;
6169
yield return DigitalIO;
70+
yield return ClockOutput;
6271
yield return MemoryMonitor;
6372
}
6473
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
using System;
2+
using System.ComponentModel;
3+
using System.Drawing.Design;
4+
using System.Reactive.Disposables;
5+
using System.Reactive.Subjects;
6+
using Bonsai;
7+
8+
namespace OpenEphys.Onix1
9+
{
10+
/// <summary>
11+
/// Configures a the breakout board's output clock.
12+
/// </summary>
13+
/// <remarks>
14+
/// The output clock provides physical, 3.3V logic level, 50 Ohm output impedance, frequency divided copy
15+
/// of the <see cref="ContextTask.AcquisitionClockHz">Acquisition Clock</see> that is used to generate
16+
/// <see cref="DataFrame.Clock"/> values for all data streams within an ONIX system. This clock runs a
17+
/// user defined rate, duty cycle, and start delay. It can be used to drive external hardware or can be
18+
/// logged by external recording systems for post-hoc synchronization with ONIX data.
19+
/// </remarks>
20+
[Description("Configures a heartbeat device.")]
21+
public class ConfigureBreakoutOutputClock : SingleDeviceFactory
22+
{
23+
readonly BehaviorSubject<bool> gate = new(false);
24+
25+
/// <summary>
26+
/// Initializes a new instance of the <see cref="ConfigureBreakoutOutputClock"/> class.
27+
/// </summary>
28+
public ConfigureBreakoutOutputClock()
29+
: base(typeof(BreakoutOutputClock))
30+
{
31+
DeviceAddress = 5;
32+
}
33+
34+
/// <summary>
35+
/// Gets or sets a value specifying of the output clock is active.
36+
/// </summary>
37+
/// <remarks>
38+
/// If set to true, the clock output will connected to the clock output line. If set to false, the
39+
/// clock output line will be held low.
40+
/// </remarks>
41+
[Range(1, 10e6)]
42+
[Category(AcquisitionCategory)]
43+
[Description("Clock gate control signal.")]
44+
public bool ClockGate
45+
{
46+
get => gate.Value;
47+
set => gate.OnNext(value);
48+
}
49+
50+
/// <summary>
51+
/// Gets or sets the output clock frequency in Hz.
52+
/// </summary>
53+
/// <remarks>
54+
/// The output clock high and low times must each be an integer multiple of the <see
55+
/// cref="ContextTask.AcquisitionClockHz">Acquisition Clock</see> frequency. Therefore, the true clock
56+
/// frequency will be set to a value that is as close as possible to the requested setting while
57+
/// respecting this constraint. The value as actualized in hardware is available using <see
58+
/// cref="BreakoutOutputClockData"/>.
59+
/// </remarks>
60+
[Range(1, 10e6)]
61+
[Category(ConfigurationCategory)]
62+
[Description("Frequency of the output clock (Hz).")]
63+
public double Frequency { get; set; } = 1e6;
64+
65+
/// <summary>
66+
/// Gets or sets the output clock duty cycle in percent.
67+
/// </summary>
68+
/// <remarks>
69+
/// The output clock high and low times must each be an integer multiple of the <see
70+
/// cref="ContextTask.AcquisitionClockHz">Acquisition Clock</see> frequency. Therefore, the true duty
71+
/// cycle will be set to a value that is as close as possible to the requested setting while
72+
/// respecting this constraint. The value as actualized in hardware is available using <see
73+
/// cref="BreakoutOutputClockData"/>.
74+
/// </remarks>
75+
[Range(10, 90)]
76+
[Editor(DesignTypes.SliderEditor, typeof(UITypeEditor))]
77+
[Category(ConfigurationCategory)]
78+
[Precision(1, 1)]
79+
[Description("Duty cycle of output clock (%).")]
80+
public double DutyCycle { get; set; } = 50.0;
81+
82+
/// <summary>
83+
/// Gets or sets the delay following acquisition start before the clock becomes active in seconds.
84+
/// </summary>
85+
/// <remarks>
86+
/// <para>
87+
/// Setting to a value greater than 0 can be useful for ensuring data sources that are driven by the
88+
/// <c>System Clock</c> start significantly after ONIX has begun aquisition for the purposes of
89+
/// ordering acquisition start times.
90+
/// </para>
91+
/// <para>
92+
/// The delay must each be an integer multiple of the <see
93+
/// cref="ContextTask.AcquisitionClockHz">Acquisition Clock</see> frequency. Therefore, the true delay
94+
/// cycle will be set to a value that is as close as possible to the requested setting while
95+
/// respecting this constraint. The value as actualized in hardware is available using <see
96+
/// cref="BreakoutOutputClockData"/>.
97+
/// </para>
98+
/// </remarks>
99+
[Category(ConfigurationCategory)]
100+
[Description("Specifies a delay following acquisition start before the clock becomes active (sec).")]
101+
[Range(0, 3600)]
102+
public double Delay { get; set; } = 0;
103+
104+
/// <summary>
105+
/// Configures a clock output.
106+
/// </summary>
107+
/// <remarks>
108+
/// This will schedule configuration actions to be applied by a <see cref="StartAcquisition"/>
109+
/// instance prior to data acquisition.
110+
/// </remarks>
111+
/// <param name="source">A sequence of <see cref="ContextTask"/> instances that holds configuration
112+
/// actions.</param>
113+
/// <returns>The original sequence modified by adding additional configuration actions required to
114+
/// configure a clock output device./></returns>
115+
public override IObservable<ContextTask> Process(IObservable<ContextTask> source)
116+
{
117+
var clkFreqHz = Frequency;
118+
var dutyCycle = DutyCycle;
119+
var delaySeconds = Delay;
120+
var deviceName = DeviceName;
121+
var deviceAddress = DeviceAddress;
122+
123+
// TODO: Dispose action that turns clock off
124+
return source.ConfigureDevice((context, observer) =>
125+
{
126+
var device = context.GetDeviceContext(deviceAddress, DeviceType);
127+
128+
var baseFreqHz = device.ReadRegister(BreakoutOutputClock.BASE_FREQ_HZ);
129+
var periodTicks = (uint)(baseFreqHz / clkFreqHz);
130+
var h = (uint)(periodTicks * (dutyCycle / 100));
131+
var l = periodTicks - h;
132+
var delayTicks = (uint)(delaySeconds * baseFreqHz);
133+
device.WriteRegister(BreakoutOutputClock.HIGH_CYCLES, h);
134+
device.WriteRegister(BreakoutOutputClock.LOW_CYCLES, l);
135+
device.WriteRegister(BreakoutOutputClock.DELAY_CYCLES, delayTicks);
136+
137+
var deviceInfo = new BreakoutOutputClockDeviceInfo(device, DeviceType,
138+
new BreakoutOutputClockParameters(baseFreqHz / (h + l), 100 * h / periodTicks, delaySeconds, h + l, h, delayTicks));
139+
140+
var shutdown = Disposable.Create(() =>
141+
{
142+
device.WriteRegister(BreakoutOutputClock.CLOCK_GATE, 0u);
143+
});
144+
145+
return new CompositeDisposable(
146+
DeviceManager.RegisterDevice(deviceName, deviceInfo),
147+
gate.SubscribeSafe(observer, value => device.WriteRegister(BreakoutOutputClock.CLOCK_GATE, value ? 1u : 0u)),
148+
shutdown
149+
);
150+
});
151+
}
152+
}
153+
154+
static class BreakoutOutputClock
155+
{
156+
public const int ID = 20;
157+
158+
public const uint NULL = 0; // No command
159+
public const uint CLOCK_GATE = 1; // Output enable. Bit 0 = 0 is disabled, Bit 0 = 1 is enabled.
160+
public const uint HIGH_CYCLES = 2; // Number of input clock cycles output clock should be high. Valid values are 1 or greater.
161+
public const uint LOW_CYCLES = 3; // Number of input clock cycles output clock should be low. Valid values are 1 or greater.
162+
public const uint DELAY_CYCLES = 4; // Number of input clock cycles output clock should be low. Valid values are 1 or greater.
163+
public const uint GATE_RUN = 5; // Number of input clock cycles output clock should be low. Valid values are 1 or greater.
164+
public const uint BASE_FREQ_HZ = 6; // Number of input clock cycles output clock should be low. Valid values are 1 or greater.
165+
166+
internal class NameConverter : DeviceNameConverter
167+
{
168+
public NameConverter()
169+
: base(typeof(BreakoutOutputClock))
170+
{
171+
}
172+
}
173+
}
174+
175+
/// <summary>
176+
/// Hardware-verified output clock parameters.
177+
/// </summary>
178+
/// <param name="FrequencyHz">Gets the exact clock frequency as actualized by the clock synthesizer in
179+
/// Hz.</param>
180+
/// <param name="DutyCyclePercent">Gets the exact clock duty cycle as actualized by the clock synthesizer
181+
/// in percent.</param>
182+
/// <param name="DelaySeconds">Gets the exact clock delay as actualized by the clock synthesizer in
183+
/// seconds.</param>
184+
/// <param name="PeriodTicks">Gets the exact clock period as actualized by the clock synthesizer in units
185+
/// of ticks of the of the <see cref="ContextTask.AcquisitionClockHz">Acquisition Clock</see>.</param>
186+
/// <param name="HighTicks">Gets the exact clock high time as actualized by the clock synthesizer in units
187+
/// of ticks of the of the <see cref="ContextTask.AcquisitionClockHz">Acquisition Clock</see>.</param>
188+
/// <param name="DelayTicks">Gets the exact clock delay as actualized by the clock synthesizer in units of
189+
/// ticks of the of the <see cref="ContextTask.AcquisitionClockHz">Acquisition Clock</see>.</param>
190+
public readonly record struct BreakoutOutputClockParameters(double FrequencyHz,
191+
double DutyCyclePercent, double DelaySeconds, uint PeriodTicks, uint HighTicks, uint DelayTicks);
192+
193+
class BreakoutOutputClockDeviceInfo : DeviceInfo
194+
{
195+
public BreakoutOutputClockDeviceInfo(DeviceContext device, Type deviceType, BreakoutOutputClockParameters parameters)
196+
: base(device, deviceType)
197+
{
198+
Parameters = parameters;
199+
}
200+
201+
public BreakoutOutputClockParameters Parameters { get; }
202+
}
203+
}

0 commit comments

Comments
 (0)