From 3499815f52d5516a9bd83161928bbee7e2b3e6a4 Mon Sep 17 00:00:00 2001 From: jonnew Date: Thu, 9 Oct 2025 15:28:00 -0400 Subject: [PATCH 1/7] Implement support for headstage-64 firmware 0.4 - Remove explcity ostim and estim trigger delay registers in favor of dynamic delay via trigger register - Add the ability to turn off indication LED - Fix register values and frame defintions - Add persistant heartbeat device to Headstage64 device aggregate --- ...e64ElectricalStimulatorOptions.Designer.cs | 133 +++++++++--------- .../Headstage64ElectricalStimulatorOptions.cs | 1 - ...age64ElectricalStimulatorSequenceDialog.cs | 7 +- ...tage64OpticalStimulatorOptions.Designer.cs | 116 ++++++++------- .../Headstage64OpticalStimulatorOptions.cs | 1 - ...dstage64OpticalStimulatorSequenceDialog.cs | 8 +- OpenEphys.Onix1/ConfigureHeadstage64.cs | 13 ++ ...onfigureHeadstage64ElectricalStimulator.cs | 78 +++------- .../ConfigureHeadstage64OpticalStimulator.cs | 80 ++++++----- OpenEphys.Onix1/ConfigurePortController.cs | 26 +++- .../Headstage64ElectricalStimulatorTrigger.cs | 19 +-- .../Headstage64OpticalStimulatorDataFrame.cs | 13 -- .../Headstage64OpticalStimulatorTrigger.cs | 19 +-- OpenEphys.Onix1/Rhs2116StimulusTrigger.cs | 6 +- 14 files changed, 250 insertions(+), 270 deletions(-) diff --git a/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorOptions.Designer.cs b/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorOptions.Designer.cs index 9abba431..8e3d42ff 100644 --- a/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorOptions.Designer.cs +++ b/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorOptions.Designer.cs @@ -48,8 +48,6 @@ private void InitializeComponent() this.labelInterBurstInterval = new System.Windows.Forms.Label(); this.textBoxTrainBurstCount = new System.Windows.Forms.TextBox(); this.labelTrainBurstCount = new System.Windows.Forms.Label(); - this.textBoxTrainDelay = new System.Windows.Forms.TextBox(); - this.labelTriggerDelay = new System.Windows.Forms.Label(); this.tableLayoutPanel1.SuspendLayout(); this.SuspendLayout(); // @@ -57,9 +55,10 @@ private void InitializeComponent() // this.labelPulseCurrent.AutoSize = true; this.labelPulseCurrent.Dock = System.Windows.Forms.DockStyle.Fill; - this.labelPulseCurrent.Location = new System.Drawing.Point(3, 31); + this.labelPulseCurrent.Location = new System.Drawing.Point(2, 25); + this.labelPulseCurrent.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelPulseCurrent.Name = "labelPulseCurrent"; - this.labelPulseCurrent.Size = new System.Drawing.Size(99, 31); + this.labelPulseCurrent.Size = new System.Drawing.Size(74, 25); this.labelPulseCurrent.TabIndex = 0; this.labelPulseCurrent.Text = "Current [µA]"; this.labelPulseCurrent.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; @@ -68,9 +67,10 @@ private void InitializeComponent() // this.labelPhaseOne.AutoSize = true; this.labelPhaseOne.Dock = System.Windows.Forms.DockStyle.Fill; - this.labelPhaseOne.Location = new System.Drawing.Point(108, 0); + this.labelPhaseOne.Location = new System.Drawing.Point(80, 0); + this.labelPhaseOne.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelPhaseOne.Name = "labelPhaseOne"; - this.labelPhaseOne.Size = new System.Drawing.Size(99, 31); + this.labelPhaseOne.Size = new System.Drawing.Size(74, 25); this.labelPhaseOne.TabIndex = 2; this.labelPhaseOne.Text = "Phase One"; this.labelPhaseOne.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; @@ -93,22 +93,24 @@ private void InitializeComponent() this.tableLayoutPanel1.Controls.Add(this.textBoxPhaseOneDuration, 1, 2); this.tableLayoutPanel1.Controls.Add(this.textBoxInterPhaseDuration, 2, 2); this.tableLayoutPanel1.Controls.Add(this.textBoxPhaseTwoDuration, 3, 2); - this.tableLayoutPanel1.Location = new System.Drawing.Point(12, 31); + this.tableLayoutPanel1.Location = new System.Drawing.Point(9, 25); + this.tableLayoutPanel1.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.tableLayoutPanel1.Name = "tableLayoutPanel1"; this.tableLayoutPanel1.RowCount = 3; this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333F)); - this.tableLayoutPanel1.Size = new System.Drawing.Size(420, 95); + this.tableLayoutPanel1.Size = new System.Drawing.Size(315, 77); this.tableLayoutPanel1.TabIndex = 0; // // labelPulseDuration // this.labelPulseDuration.AutoSize = true; this.labelPulseDuration.Dock = System.Windows.Forms.DockStyle.Fill; - this.labelPulseDuration.Location = new System.Drawing.Point(3, 62); + this.labelPulseDuration.Location = new System.Drawing.Point(2, 50); + this.labelPulseDuration.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelPulseDuration.Name = "labelPulseDuration"; - this.labelPulseDuration.Size = new System.Drawing.Size(99, 33); + this.labelPulseDuration.Size = new System.Drawing.Size(74, 27); this.labelPulseDuration.TabIndex = 1; this.labelPulseDuration.Text = "Duration [µs]"; this.labelPulseDuration.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; @@ -117,9 +119,10 @@ private void InitializeComponent() // this.labelInterPhase.AutoSize = true; this.labelInterPhase.Dock = System.Windows.Forms.DockStyle.Fill; - this.labelInterPhase.Location = new System.Drawing.Point(213, 0); + this.labelInterPhase.Location = new System.Drawing.Point(158, 0); + this.labelInterPhase.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelInterPhase.Name = "labelInterPhase"; - this.labelInterPhase.Size = new System.Drawing.Size(99, 31); + this.labelInterPhase.Size = new System.Drawing.Size(74, 25); this.labelInterPhase.TabIndex = 3; this.labelInterPhase.Text = "Inter-Phase"; this.labelInterPhase.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; @@ -128,9 +131,10 @@ private void InitializeComponent() // this.labelPhaseTwo.AutoSize = true; this.labelPhaseTwo.Dock = System.Windows.Forms.DockStyle.Fill; - this.labelPhaseTwo.Location = new System.Drawing.Point(318, 0); + this.labelPhaseTwo.Location = new System.Drawing.Point(236, 0); + this.labelPhaseTwo.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelPhaseTwo.Name = "labelPhaseTwo"; - this.labelPhaseTwo.Size = new System.Drawing.Size(99, 31); + this.labelPhaseTwo.Size = new System.Drawing.Size(77, 25); this.labelPhaseTwo.TabIndex = 4; this.labelPhaseTwo.Text = "Phase Two"; this.labelPhaseTwo.TextAlign = System.Drawing.ContentAlignment.MiddleCenter; @@ -138,146 +142,136 @@ private void InitializeComponent() // textBoxPhaseOneCurrent // this.textBoxPhaseOneCurrent.Dock = System.Windows.Forms.DockStyle.Fill; - this.textBoxPhaseOneCurrent.Location = new System.Drawing.Point(120, 34); - this.textBoxPhaseOneCurrent.Margin = new System.Windows.Forms.Padding(15, 3, 15, 3); + this.textBoxPhaseOneCurrent.Location = new System.Drawing.Point(89, 27); + this.textBoxPhaseOneCurrent.Margin = new System.Windows.Forms.Padding(11, 2, 11, 2); this.textBoxPhaseOneCurrent.Name = "textBoxPhaseOneCurrent"; - this.textBoxPhaseOneCurrent.Size = new System.Drawing.Size(75, 22); + this.textBoxPhaseOneCurrent.Size = new System.Drawing.Size(56, 20); this.textBoxPhaseOneCurrent.TabIndex = 0; // // textBoxInterPhaseCurrent // this.textBoxInterPhaseCurrent.Dock = System.Windows.Forms.DockStyle.Fill; - this.textBoxInterPhaseCurrent.Location = new System.Drawing.Point(225, 34); - this.textBoxInterPhaseCurrent.Margin = new System.Windows.Forms.Padding(15, 3, 15, 3); + this.textBoxInterPhaseCurrent.Location = new System.Drawing.Point(167, 27); + this.textBoxInterPhaseCurrent.Margin = new System.Windows.Forms.Padding(11, 2, 11, 2); this.textBoxInterPhaseCurrent.Name = "textBoxInterPhaseCurrent"; - this.textBoxInterPhaseCurrent.Size = new System.Drawing.Size(75, 22); + this.textBoxInterPhaseCurrent.Size = new System.Drawing.Size(56, 20); this.textBoxInterPhaseCurrent.TabIndex = 1; // // textBoxPhaseTwoCurrent // this.textBoxPhaseTwoCurrent.Dock = System.Windows.Forms.DockStyle.Fill; - this.textBoxPhaseTwoCurrent.Location = new System.Drawing.Point(330, 34); - this.textBoxPhaseTwoCurrent.Margin = new System.Windows.Forms.Padding(15, 3, 15, 3); + this.textBoxPhaseTwoCurrent.Location = new System.Drawing.Point(245, 27); + this.textBoxPhaseTwoCurrent.Margin = new System.Windows.Forms.Padding(11, 2, 11, 2); this.textBoxPhaseTwoCurrent.Name = "textBoxPhaseTwoCurrent"; - this.textBoxPhaseTwoCurrent.Size = new System.Drawing.Size(75, 22); + this.textBoxPhaseTwoCurrent.Size = new System.Drawing.Size(59, 20); this.textBoxPhaseTwoCurrent.TabIndex = 2; // // textBoxPhaseOneDuration // this.textBoxPhaseOneDuration.Dock = System.Windows.Forms.DockStyle.Fill; - this.textBoxPhaseOneDuration.Location = new System.Drawing.Point(120, 65); - this.textBoxPhaseOneDuration.Margin = new System.Windows.Forms.Padding(15, 3, 15, 3); + this.textBoxPhaseOneDuration.Location = new System.Drawing.Point(89, 52); + this.textBoxPhaseOneDuration.Margin = new System.Windows.Forms.Padding(11, 2, 11, 2); this.textBoxPhaseOneDuration.Name = "textBoxPhaseOneDuration"; - this.textBoxPhaseOneDuration.Size = new System.Drawing.Size(75, 22); + this.textBoxPhaseOneDuration.Size = new System.Drawing.Size(56, 20); this.textBoxPhaseOneDuration.TabIndex = 3; // // textBoxInterPhaseDuration // this.textBoxInterPhaseDuration.Dock = System.Windows.Forms.DockStyle.Fill; - this.textBoxInterPhaseDuration.Location = new System.Drawing.Point(225, 65); - this.textBoxInterPhaseDuration.Margin = new System.Windows.Forms.Padding(15, 3, 15, 3); + this.textBoxInterPhaseDuration.Location = new System.Drawing.Point(167, 52); + this.textBoxInterPhaseDuration.Margin = new System.Windows.Forms.Padding(11, 2, 11, 2); this.textBoxInterPhaseDuration.Name = "textBoxInterPhaseDuration"; - this.textBoxInterPhaseDuration.Size = new System.Drawing.Size(75, 22); + this.textBoxInterPhaseDuration.Size = new System.Drawing.Size(56, 20); this.textBoxInterPhaseDuration.TabIndex = 4; // // textBoxPhaseTwoDuration // this.textBoxPhaseTwoDuration.Dock = System.Windows.Forms.DockStyle.Fill; - this.textBoxPhaseTwoDuration.Location = new System.Drawing.Point(330, 65); - this.textBoxPhaseTwoDuration.Margin = new System.Windows.Forms.Padding(15, 3, 15, 3); + this.textBoxPhaseTwoDuration.Location = new System.Drawing.Point(245, 52); + this.textBoxPhaseTwoDuration.Margin = new System.Windows.Forms.Padding(11, 2, 11, 2); this.textBoxPhaseTwoDuration.Name = "textBoxPhaseTwoDuration"; - this.textBoxPhaseTwoDuration.Size = new System.Drawing.Size(75, 22); + this.textBoxPhaseTwoDuration.Size = new System.Drawing.Size(59, 20); this.textBoxPhaseTwoDuration.TabIndex = 5; // // labelPulsePeriod // this.labelPulsePeriod.AutoSize = true; - this.labelPulsePeriod.Location = new System.Drawing.Point(106, 146); + this.labelPulsePeriod.Location = new System.Drawing.Point(80, 119); + this.labelPulsePeriod.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelPulsePeriod.Name = "labelPulsePeriod"; - this.labelPulsePeriod.Size = new System.Drawing.Size(109, 16); + this.labelPulsePeriod.Size = new System.Drawing.Size(86, 13); this.labelPulsePeriod.TabIndex = 4; this.labelPulsePeriod.Text = "Pulse Period [µs]"; // // textBoxBurstPulseCount // - this.textBoxBurstPulseCount.Location = new System.Drawing.Point(132, 200); + this.textBoxBurstPulseCount.Location = new System.Drawing.Point(99, 162); + this.textBoxBurstPulseCount.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxBurstPulseCount.Name = "textBoxBurstPulseCount"; - this.textBoxBurstPulseCount.Size = new System.Drawing.Size(75, 22); + this.textBoxBurstPulseCount.Size = new System.Drawing.Size(57, 20); this.textBoxBurstPulseCount.TabIndex = 2; this.textBoxBurstPulseCount.TextChanged += new System.EventHandler(this.BurstPulseCountChanged); // // labelBurstPulseCount // this.labelBurstPulseCount.AutoSize = true; - this.labelBurstPulseCount.Location = new System.Drawing.Point(15, 203); + this.labelBurstPulseCount.Location = new System.Drawing.Point(11, 165); + this.labelBurstPulseCount.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelBurstPulseCount.Name = "labelBurstPulseCount"; - this.labelBurstPulseCount.Size = new System.Drawing.Size(111, 16); + this.labelBurstPulseCount.Size = new System.Drawing.Size(91, 13); this.labelBurstPulseCount.TabIndex = 6; this.labelBurstPulseCount.Text = "Burst Pulse Count"; // // textBoxPulsePeriod // - this.textBoxPulsePeriod.Location = new System.Drawing.Point(237, 146); + this.textBoxPulsePeriod.Location = new System.Drawing.Point(178, 119); + this.textBoxPulsePeriod.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxPulsePeriod.Name = "textBoxPulsePeriod"; - this.textBoxPulsePeriod.Size = new System.Drawing.Size(75, 22); + this.textBoxPulsePeriod.Size = new System.Drawing.Size(57, 20); this.textBoxPulsePeriod.TabIndex = 1; // // textBoxInterBurstInterval // - this.textBoxInterBurstInterval.Location = new System.Drawing.Point(342, 203); + this.textBoxInterBurstInterval.Location = new System.Drawing.Point(256, 165); + this.textBoxInterBurstInterval.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxInterBurstInterval.Name = "textBoxInterBurstInterval"; - this.textBoxInterBurstInterval.Size = new System.Drawing.Size(75, 22); + this.textBoxInterBurstInterval.Size = new System.Drawing.Size(57, 20); this.textBoxInterBurstInterval.TabIndex = 3; // // labelInterBurstInterval // this.labelInterBurstInterval.AutoSize = true; - this.labelInterBurstInterval.Location = new System.Drawing.Point(245, 200); + this.labelInterBurstInterval.Location = new System.Drawing.Point(184, 162); + this.labelInterBurstInterval.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelInterBurstInterval.Name = "labelInterBurstInterval"; - this.labelInterBurstInterval.Size = new System.Drawing.Size(75, 32); + this.labelInterBurstInterval.Size = new System.Drawing.Size(62, 26); this.labelInterBurstInterval.TabIndex = 8; this.labelInterBurstInterval.Text = "Inter-Burst \r\nInterval [µs]"; // // textBoxTrainBurstCount // - this.textBoxTrainBurstCount.Location = new System.Drawing.Point(132, 251); + this.textBoxTrainBurstCount.Location = new System.Drawing.Point(99, 204); + this.textBoxTrainBurstCount.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxTrainBurstCount.Name = "textBoxTrainBurstCount"; - this.textBoxTrainBurstCount.Size = new System.Drawing.Size(75, 22); + this.textBoxTrainBurstCount.Size = new System.Drawing.Size(57, 20); this.textBoxTrainBurstCount.TabIndex = 4; this.textBoxTrainBurstCount.TextChanged += new System.EventHandler(this.TrainBurstCountChanged); // // labelTrainBurstCount // this.labelTrainBurstCount.AutoSize = true; - this.labelTrainBurstCount.Location = new System.Drawing.Point(15, 254); + this.labelTrainBurstCount.Location = new System.Drawing.Point(11, 206); + this.labelTrainBurstCount.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelTrainBurstCount.Name = "labelTrainBurstCount"; - this.labelTrainBurstCount.Size = new System.Drawing.Size(108, 16); + this.labelTrainBurstCount.Size = new System.Drawing.Size(89, 13); this.labelTrainBurstCount.TabIndex = 10; this.labelTrainBurstCount.Text = "Train Burst Count"; // - // textBoxTrainDelay - // - this.textBoxTrainDelay.Location = new System.Drawing.Point(342, 251); - this.textBoxTrainDelay.Name = "textBoxTrainDelay"; - this.textBoxTrainDelay.Size = new System.Drawing.Size(75, 22); - this.textBoxTrainDelay.TabIndex = 5; - // - // labelTriggerDelay - // - this.labelTriggerDelay.AutoSize = true; - this.labelTriggerDelay.Location = new System.Drawing.Point(225, 254); - this.labelTriggerDelay.Name = "labelTriggerDelay"; - this.labelTriggerDelay.Size = new System.Drawing.Size(102, 16); - this.labelTriggerDelay.TabIndex = 12; - this.labelTriggerDelay.Text = "Train Delay [µs]"; - // // Headstage64ElectricalStimulatorOptions // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(438, 289); - this.Controls.Add(this.textBoxTrainDelay); - this.Controls.Add(this.labelTriggerDelay); + this.ClientSize = new System.Drawing.Size(328, 235); this.Controls.Add(this.textBoxTrainBurstCount); this.Controls.Add(this.labelTrainBurstCount); this.Controls.Add(this.textBoxInterBurstInterval); @@ -287,6 +281,7 @@ private void InitializeComponent() this.Controls.Add(this.textBoxPulsePeriod); this.Controls.Add(this.labelPulsePeriod); this.Controls.Add(this.tableLayoutPanel1); + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.Name = "Headstage64ElectricalStimulatorOptions"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Headstage64ElectricalStimulatorOptions"; @@ -319,7 +314,5 @@ private void InitializeComponent() private System.Windows.Forms.Label labelInterBurstInterval; internal System.Windows.Forms.TextBox textBoxTrainBurstCount; private System.Windows.Forms.Label labelTrainBurstCount; - internal System.Windows.Forms.TextBox textBoxTrainDelay; - private System.Windows.Forms.Label labelTriggerDelay; } } diff --git a/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorOptions.cs b/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorOptions.cs index d5968551..4438cf10 100644 --- a/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorOptions.cs +++ b/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorOptions.cs @@ -35,7 +35,6 @@ public Headstage64ElectricalStimulatorOptions(ConfigureHeadstage64ElectricalStim textBoxBurstPulseCount.Text = electricalStimulator.BurstPulseCount.ToString(); textBoxInterBurstInterval.Text = electricalStimulator.InterBurstInterval.ToString(); textBoxTrainBurstCount.Text = electricalStimulator.TrainBurstCount.ToString(); - textBoxTrainDelay.Text = electricalStimulator.TriggerDelay.ToString(); } void BurstPulseCountChanged(object sender, System.EventArgs e) diff --git a/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorSequenceDialog.cs b/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorSequenceDialog.cs index 0be9ad77..b9158aa0 100644 --- a/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorSequenceDialog.cs +++ b/OpenEphys.Onix1.Design/Headstage64ElectricalStimulatorSequenceDialog.cs @@ -83,11 +83,6 @@ public Headstage64ElectricalStimulatorSequenceDialog(ConfigureHeadstage64Electri new TextBoxBinding( StimulusSequenceOptions.textBoxPulsePeriod, value => { ElectricalStimulator.InterPulseInterval = value; return ElectricalStimulator.InterPulseInterval; }, - uint.Parse) }, - { StimulusSequenceOptions.textBoxTrainDelay, - new TextBoxBinding( - StimulusSequenceOptions.textBoxTrainDelay, - value => { ElectricalStimulator.TriggerDelay = value; return ElectricalStimulator.TriggerDelay; }, uint.Parse) } }; @@ -303,7 +298,7 @@ internal override PointPairList[] CreateStimulusWaveforms() { for (int channel = 0; channel < NumberOfChannels; channel++) { - waveforms[channel] = new PointPairList { new PointPair(0, 0), new PointPair(ElectricalStimulator.TriggerDelay, 0) }; + waveforms[channel] = new PointPairList { new PointPair(0, 0), new PointPair(0, 0) }; for (int i = 0; i < ElectricalStimulator.TrainBurstCount; i++) { diff --git a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.Designer.cs b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.Designer.cs index 9c3c103b..4eda2caf 100644 --- a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.Designer.cs +++ b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.Designer.cs @@ -28,8 +28,6 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.textBoxDelay = new System.Windows.Forms.TextBox(); - this.labelDelay = new System.Windows.Forms.Label(); this.textBoxBurstsPerTrain = new System.Windows.Forms.TextBox(); this.labelBurstsPerTrain = new System.Windows.Forms.Label(); this.textBoxInterBurstInterval = new System.Windows.Forms.TextBox(); @@ -52,111 +50,105 @@ private void InitializeComponent() ((System.ComponentModel.ISupportInitialize)(this.trackBarChannelTwoPercent)).BeginInit(); this.SuspendLayout(); // - // textBoxDelay - // - this.textBoxDelay.Location = new System.Drawing.Point(356, 244); - this.textBoxDelay.Name = "textBoxDelay"; - this.textBoxDelay.Size = new System.Drawing.Size(75, 22); - this.textBoxDelay.TabIndex = 19; - // - // labelDelay - // - this.labelDelay.AutoSize = true; - this.labelDelay.Location = new System.Drawing.Point(218, 247); - this.labelDelay.Name = "labelDelay"; - this.labelDelay.Size = new System.Drawing.Size(72, 16); - this.labelDelay.TabIndex = 18; - this.labelDelay.Text = "Delay [ms]"; - // // textBoxBurstsPerTrain // - this.textBoxBurstsPerTrain.Location = new System.Drawing.Point(133, 244); + this.textBoxBurstsPerTrain.Location = new System.Drawing.Point(100, 198); + this.textBoxBurstsPerTrain.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxBurstsPerTrain.Name = "textBoxBurstsPerTrain"; - this.textBoxBurstsPerTrain.Size = new System.Drawing.Size(75, 22); + this.textBoxBurstsPerTrain.Size = new System.Drawing.Size(57, 20); this.textBoxBurstsPerTrain.TabIndex = 17; this.textBoxBurstsPerTrain.TextChanged += new System.EventHandler(this.BurstsPerTrainChanged); // // labelBurstsPerTrain // this.labelBurstsPerTrain.AutoSize = true; - this.labelBurstsPerTrain.Location = new System.Drawing.Point(6, 247); + this.labelBurstsPerTrain.Location = new System.Drawing.Point(4, 201); + this.labelBurstsPerTrain.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelBurstsPerTrain.Name = "labelBurstsPerTrain"; - this.labelBurstsPerTrain.Size = new System.Drawing.Size(102, 16); + this.labelBurstsPerTrain.Size = new System.Drawing.Size(82, 13); this.labelBurstsPerTrain.TabIndex = 16; this.labelBurstsPerTrain.Text = "Bursts Per Train"; // // textBoxInterBurstInterval // - this.textBoxInterBurstInterval.Location = new System.Drawing.Point(356, 195); + this.textBoxInterBurstInterval.Location = new System.Drawing.Point(267, 158); + this.textBoxInterBurstInterval.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxInterBurstInterval.Name = "textBoxInterBurstInterval"; - this.textBoxInterBurstInterval.Size = new System.Drawing.Size(75, 22); + this.textBoxInterBurstInterval.Size = new System.Drawing.Size(57, 20); this.textBoxInterBurstInterval.TabIndex = 15; // // labelInterBurstInterval // - this.labelInterBurstInterval.Location = new System.Drawing.Point(216, 198); + this.labelInterBurstInterval.Location = new System.Drawing.Point(162, 161); + this.labelInterBurstInterval.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelInterBurstInterval.Name = "labelInterBurstInterval"; - this.labelInterBurstInterval.Size = new System.Drawing.Size(146, 19); + this.labelInterBurstInterval.Size = new System.Drawing.Size(110, 15); this.labelInterBurstInterval.TabIndex = 14; this.labelInterBurstInterval.Text = "Inter-Burst Interval [ms]"; // // textBoxPulsesPerBurst // - this.textBoxPulsesPerBurst.Location = new System.Drawing.Point(133, 195); + this.textBoxPulsesPerBurst.Location = new System.Drawing.Point(100, 158); + this.textBoxPulsesPerBurst.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxPulsesPerBurst.Name = "textBoxPulsesPerBurst"; - this.textBoxPulsesPerBurst.Size = new System.Drawing.Size(75, 22); + this.textBoxPulsesPerBurst.Size = new System.Drawing.Size(57, 20); this.textBoxPulsesPerBurst.TabIndex = 13; this.textBoxPulsesPerBurst.TextChanged += new System.EventHandler(this.PulsesPerBurstChanged); // // labelPulsesPerBurst // this.labelPulsesPerBurst.AutoSize = true; - this.labelPulsesPerBurst.Location = new System.Drawing.Point(6, 198); + this.labelPulsesPerBurst.Location = new System.Drawing.Point(4, 161); + this.labelPulsesPerBurst.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelPulsesPerBurst.Name = "labelPulsesPerBurst"; - this.labelPulsesPerBurst.Size = new System.Drawing.Size(105, 16); + this.labelPulsesPerBurst.Size = new System.Drawing.Size(84, 13); this.labelPulsesPerBurst.TabIndex = 12; this.labelPulsesPerBurst.Text = "Pulses Per Burst"; // // labelMaxCurrent // this.labelMaxCurrent.AutoSize = true; - this.labelMaxCurrent.Location = new System.Drawing.Point(6, 20); + this.labelMaxCurrent.Location = new System.Drawing.Point(4, 16); + this.labelMaxCurrent.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelMaxCurrent.Name = "labelMaxCurrent"; - this.labelMaxCurrent.Size = new System.Drawing.Size(108, 16); + this.labelMaxCurrent.Size = new System.Drawing.Size(88, 13); this.labelMaxCurrent.TabIndex = 0; this.labelMaxCurrent.Text = "Max Current [mA]"; // // textBoxMaxCurrent // - this.textBoxMaxCurrent.Location = new System.Drawing.Point(133, 17); + this.textBoxMaxCurrent.Location = new System.Drawing.Point(100, 14); + this.textBoxMaxCurrent.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxMaxCurrent.Name = "textBoxMaxCurrent"; - this.textBoxMaxCurrent.Size = new System.Drawing.Size(75, 22); + this.textBoxMaxCurrent.Size = new System.Drawing.Size(57, 20); this.textBoxMaxCurrent.TabIndex = 1; // // textBoxChannelOnePercent // - this.textBoxChannelOnePercent.Location = new System.Drawing.Point(133, 58); + this.textBoxChannelOnePercent.Location = new System.Drawing.Point(100, 47); + this.textBoxChannelOnePercent.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxChannelOnePercent.Name = "textBoxChannelOnePercent"; - this.textBoxChannelOnePercent.Size = new System.Drawing.Size(75, 22); + this.textBoxChannelOnePercent.Size = new System.Drawing.Size(57, 20); this.textBoxChannelOnePercent.TabIndex = 3; // // labelChannelOnePercent // this.labelChannelOnePercent.AutoSize = true; - this.labelChannelOnePercent.Location = new System.Drawing.Point(6, 61); + this.labelChannelOnePercent.Location = new System.Drawing.Point(4, 50); + this.labelChannelOnePercent.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelChannelOnePercent.Name = "labelChannelOnePercent"; - this.labelChannelOnePercent.Size = new System.Drawing.Size(107, 16); + this.labelChannelOnePercent.Size = new System.Drawing.Size(86, 13); this.labelChannelOnePercent.TabIndex = 2; this.labelChannelOnePercent.Text = "Channel One [%]"; // // trackBarChannelOnePercent // this.trackBarChannelOnePercent.LargeChange = 125; - this.trackBarChannelOnePercent.Location = new System.Drawing.Point(11, 83); + this.trackBarChannelOnePercent.Location = new System.Drawing.Point(8, 67); this.trackBarChannelOnePercent.Margin = new System.Windows.Forms.Padding(0); this.trackBarChannelOnePercent.Maximum = 1000; this.trackBarChannelOnePercent.Name = "trackBarChannelOnePercent"; - this.trackBarChannelOnePercent.Size = new System.Drawing.Size(197, 45); + this.trackBarChannelOnePercent.Size = new System.Drawing.Size(148, 45); this.trackBarChannelOnePercent.SmallChange = 125; this.trackBarChannelOnePercent.TabIndex = 6; this.trackBarChannelOnePercent.TickFrequency = 125; @@ -165,28 +157,30 @@ private void InitializeComponent() // // textBoxPulseDuration // - this.textBoxPulseDuration.Location = new System.Drawing.Point(133, 146); + this.textBoxPulseDuration.Location = new System.Drawing.Point(100, 119); + this.textBoxPulseDuration.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxPulseDuration.Name = "textBoxPulseDuration"; - this.textBoxPulseDuration.Size = new System.Drawing.Size(75, 22); + this.textBoxPulseDuration.Size = new System.Drawing.Size(57, 20); this.textBoxPulseDuration.TabIndex = 9; // // labelPulseDuration // this.labelPulseDuration.AutoSize = true; - this.labelPulseDuration.Location = new System.Drawing.Point(6, 149); + this.labelPulseDuration.Location = new System.Drawing.Point(4, 121); + this.labelPulseDuration.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelPulseDuration.Name = "labelPulseDuration"; - this.labelPulseDuration.Size = new System.Drawing.Size(123, 16); + this.labelPulseDuration.Size = new System.Drawing.Size(98, 13); this.labelPulseDuration.TabIndex = 8; this.labelPulseDuration.Text = "Pulse Duration [ms]"; // // trackBarChannelTwoPercent // this.trackBarChannelTwoPercent.LargeChange = 125; - this.trackBarChannelTwoPercent.Location = new System.Drawing.Point(219, 83); + this.trackBarChannelTwoPercent.Location = new System.Drawing.Point(164, 67); this.trackBarChannelTwoPercent.Margin = new System.Windows.Forms.Padding(0); this.trackBarChannelTwoPercent.Maximum = 1000; this.trackBarChannelTwoPercent.Name = "trackBarChannelTwoPercent"; - this.trackBarChannelTwoPercent.Size = new System.Drawing.Size(212, 45); + this.trackBarChannelTwoPercent.Size = new System.Drawing.Size(159, 45); this.trackBarChannelTwoPercent.SmallChange = 125; this.trackBarChannelTwoPercent.TabIndex = 7; this.trackBarChannelTwoPercent.TickFrequency = 125; @@ -194,41 +188,45 @@ private void InitializeComponent() // // textBoxChannelTwoPercent // - this.textBoxChannelTwoPercent.Location = new System.Drawing.Point(356, 58); + this.textBoxChannelTwoPercent.Location = new System.Drawing.Point(267, 47); + this.textBoxChannelTwoPercent.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxChannelTwoPercent.Name = "textBoxChannelTwoPercent"; - this.textBoxChannelTwoPercent.Size = new System.Drawing.Size(75, 22); + this.textBoxChannelTwoPercent.Size = new System.Drawing.Size(57, 20); this.textBoxChannelTwoPercent.TabIndex = 5; // // labelChannelTwoPercent // this.labelChannelTwoPercent.AutoSize = true; - this.labelChannelTwoPercent.Location = new System.Drawing.Point(216, 61); + this.labelChannelTwoPercent.Location = new System.Drawing.Point(162, 50); + this.labelChannelTwoPercent.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelChannelTwoPercent.Name = "labelChannelTwoPercent"; - this.labelChannelTwoPercent.Size = new System.Drawing.Size(108, 16); + this.labelChannelTwoPercent.Size = new System.Drawing.Size(87, 13); this.labelChannelTwoPercent.TabIndex = 4; this.labelChannelTwoPercent.Text = "Channel Two [%]"; // // textBoxPulsePeriod // - this.textBoxPulsePeriod.Location = new System.Drawing.Point(356, 146); + this.textBoxPulsePeriod.Location = new System.Drawing.Point(267, 119); + this.textBoxPulsePeriod.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.textBoxPulsePeriod.Name = "textBoxPulsePeriod"; - this.textBoxPulsePeriod.Size = new System.Drawing.Size(75, 22); + this.textBoxPulsePeriod.Size = new System.Drawing.Size(57, 20); this.textBoxPulsePeriod.TabIndex = 11; // // labelPulsePeriod // this.labelPulsePeriod.AutoSize = true; - this.labelPulsePeriod.Location = new System.Drawing.Point(216, 149); + this.labelPulsePeriod.Location = new System.Drawing.Point(162, 121); + this.labelPulsePeriod.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); this.labelPulsePeriod.Name = "labelPulsePeriod"; - this.labelPulsePeriod.Size = new System.Drawing.Size(113, 16); + this.labelPulsePeriod.Size = new System.Drawing.Size(88, 13); this.labelPulsePeriod.TabIndex = 10; this.labelPulsePeriod.Text = "Pulse Period [ms]"; // // Headstage64OpticalStimulatorOptions // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(438, 289); + this.ClientSize = new System.Drawing.Size(328, 235); this.Controls.Add(this.textBoxPulsePeriod); this.Controls.Add(this.labelPulsePeriod); this.Controls.Add(this.trackBarChannelTwoPercent); @@ -241,14 +239,13 @@ private void InitializeComponent() this.Controls.Add(this.labelChannelOnePercent); this.Controls.Add(this.textBoxMaxCurrent); this.Controls.Add(this.labelMaxCurrent); - this.Controls.Add(this.textBoxDelay); - this.Controls.Add(this.labelDelay); this.Controls.Add(this.textBoxBurstsPerTrain); this.Controls.Add(this.labelBurstsPerTrain); this.Controls.Add(this.textBoxInterBurstInterval); this.Controls.Add(this.labelInterBurstInterval); this.Controls.Add(this.textBoxPulsesPerBurst); this.Controls.Add(this.labelPulsesPerBurst); + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); this.Name = "Headstage64OpticalStimulatorOptions"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Headstage64OpticalStimulatorOptions"; @@ -260,9 +257,6 @@ private void InitializeComponent() } #endregion - - internal System.Windows.Forms.TextBox textBoxDelay; - private System.Windows.Forms.Label labelDelay; internal System.Windows.Forms.TextBox textBoxBurstsPerTrain; private System.Windows.Forms.Label labelBurstsPerTrain; internal System.Windows.Forms.TextBox textBoxInterBurstInterval; diff --git a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.cs b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.cs index 83ff20df..ad7367c5 100644 --- a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.cs +++ b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.cs @@ -44,7 +44,6 @@ internal void UpdateSequenceParameters(ConfigureHeadstage64OpticalStimulator opt textBoxInterBurstInterval.Text = opticalStimulator.InterBurstInterval.ToString(); textBoxInterBurstInterval.Enabled = opticalStimulator.BurstsPerTrain > 1; textBoxBurstsPerTrain.Text = opticalStimulator.BurstsPerTrain.ToString(); - textBoxDelay.Text = opticalStimulator.Delay.ToString(); } internal readonly double channelOneScalingFactor; diff --git a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorSequenceDialog.cs b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorSequenceDialog.cs index 222d2f0d..35d243e5 100644 --- a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorSequenceDialog.cs +++ b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorSequenceDialog.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.IO; using System.Linq; using System.Windows.Forms; using ZedGraph; @@ -78,11 +77,6 @@ public Headstage64OpticalStimulatorSequenceDialog(ConfigureHeadstage64OpticalSti StimulusSequenceOptions.textBoxInterBurstInterval, value => { OpticalStimulator.InterBurstInterval = value; return OpticalStimulator.InterBurstInterval; }, double.Parse) }, - { StimulusSequenceOptions.textBoxDelay, - new TextBoxBinding( - StimulusSequenceOptions.textBoxDelay, - value => { OpticalStimulator.Delay = value; return OpticalStimulator.Delay; }, - double.Parse) }, { StimulusSequenceOptions.textBoxPulseDuration, new TextBoxBinding( StimulusSequenceOptions.textBoxPulseDuration, @@ -225,7 +219,7 @@ internal override PointPairList[] CreateStimulusWaveforms() waveforms[channel] = new PointPairList { - new PointPairList { new PointPair(0, offset), new PointPair(OpticalStimulator.Delay, offset) } + new PointPairList { new PointPair(0, offset), new PointPair(0, offset) } }; var stimulusCurrent = offset + GetChannelCurrentScaled(OpticalStimulator.MaxCurrent, diff --git a/OpenEphys.Onix1/ConfigureHeadstage64.cs b/OpenEphys.Onix1/ConfigureHeadstage64.cs index 321ed807..a8c073d3 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64.cs @@ -88,6 +88,17 @@ public ConfigureHeadstage64() [Editor("OpenEphys.Onix1.Design.Headstage64OpticalStimulatorUITypeEditor, OpenEphys.Onix1.Design", typeof(UITypeEditor))] public ConfigureHeadstage64OpticalStimulator OpticalStimulator { get; set; } = new(); + /// + /// Gets or sets the heartbeat configuration. + /// + /// + /// This heartbeat is always enabled and beats at a minimum of 10 Hz. + /// + [Category(DevicesCategory)] + [TypeConverter(typeof(SingleDeviceFactoryConverter))] + [Description("Specifies the configuration for the heartbeat device in the headstage-64.")] + public ConfigurePersistentHeartbeat Heartbeat { get; set; } = new ConfigurePersistentHeartbeat { BeatsPerSecond = 10 }; + /// /// Gets or sets the port. /// @@ -110,6 +121,7 @@ public PortName Port TS4231.DeviceAddress = offset + 2; ElectricalStimulator.DeviceAddress = offset + 3; OpticalStimulator.DeviceAddress = offset + 4; + Heartbeat.DeviceAddress = offset + 5; } } @@ -148,6 +160,7 @@ internal override IEnumerable GetDevices() yield return TS4231; yield return ElectricalStimulator; yield return OpticalStimulator; + yield return Heartbeat; } class ConfigureHeadstage64PortController : ConfigurePortController diff --git a/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs index 6f5caae3..a2987e9e 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs @@ -30,8 +30,6 @@ public class ConfigureHeadstage64ElectricalStimulator : SingleDeviceFactory readonly BehaviorSubject burstPulseCount = new(0); readonly BehaviorSubject interBurstInterval = new(0); readonly BehaviorSubject trainBurstCount = new(0); - readonly BehaviorSubject triggerDelay = new(0); - readonly BehaviorSubject powerEnable = new(false); /// /// Initializes a new instance of the class. @@ -51,8 +49,6 @@ public ConfigureHeadstage64ElectricalStimulator(ConfigureHeadstage64ElectricalSt DeviceAddress = electricalStimulator.DeviceAddress; Enable = electricalStimulator.Enable; StimEnable = electricalStimulator.StimEnable; - PowerEnable = electricalStimulator.PowerEnable; - TriggerDelay = electricalStimulator.TriggerDelay; PhaseOneCurrent = electricalStimulator.PhaseOneCurrent; InterPhaseCurrent = electricalStimulator.InterPhaseCurrent; PhaseTwoCurrent = electricalStimulator.PhaseTwoCurrent; @@ -80,39 +76,19 @@ public ConfigureHeadstage64ElectricalStimulator(ConfigureHeadstage64ElectricalSt /// Gets or sets the device enable state. /// /// - /// If set to true, then the electrical stimulator circuit will respect triggers. If set to false, triggers will be ignored. + /// If set to true, then the electrical stimulator's ±15V power supplies will be turned on and the + /// electrical stimulator circuit will respect triggers. If set to false, the power supplies will be + /// shut down and triggers will be ignored.It may be desirable to power down the electrical + /// stimulator's power supplies outside of stimulation windows to reduce power consumption and + /// electrical noise. This property must be set to true in order for electrical stimuli to be + /// delivered properly. It takes ~10 milliseconds for these supplies to stabilize. /// [Description("Specifies whether the electrical stimulator will respect triggers.")] [Category(AcquisitionCategory)] - public bool StimEnable { get; set; } = true; - - /// - /// Gets or sets the electrical stimulator's power state. - /// - /// - /// If set to true, then the electrical stimulator's ±15V power supplies will be turned on. If set to false, - /// they will be turned off. It may be desirable to power down the electrical stimulator's power supplies outside - /// of stimulation windows to reduce power consumption and electrical noise. This property must be set to true - /// in order for electrical stimuli to be delivered properly. It takes ~10 milliseconds for these supplies to stabilize. - /// - [Description("Stimulator power on/off.")] - [Category(AcquisitionCategory)] - public bool PowerEnable - { - get => powerEnable.Value; - set => powerEnable.OnNext(value); - } - - /// - /// Gets or sets a delay from receiving a trigger to the start of stimulus sequence application in μsec. - /// - [Description("A delay from receiving a trigger to the start of stimulus sequence application (uSec).")] - [Range(0, uint.MaxValue)] - [Category(AcquisitionCategory)] - public uint TriggerDelay + public bool StimEnable { - get => triggerDelay.Value; - set => triggerDelay.OnNext(value); + get => stimEnable.Value; + set => stimEnable.OnNext(value); } static double ClampCurrent(double value) @@ -275,14 +251,11 @@ public override IObservable Process(IObservable source return new CompositeDisposable( stimEnable.SubscribeSafe(observer, value => - device.WriteRegister(Headstage64ElectricalStimulator.STIMENABLE, value ? 1u : 0u)), + device.WriteRegister(Headstage64ElectricalStimulator.STIMENABLE, value ? 3u : 0u)), phaseOneCurrent.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT1, Headstage64ElectricalStimulator.MicroampsToCode(value))), - interPhaseCurrent.SubscribeSafe(observer, value => - device.WriteRegister(Headstage64ElectricalStimulator.RESTCURR, Headstage64ElectricalStimulator.MicroampsToCode(value))), phaseTwoCurrent.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT2, Headstage64ElectricalStimulator.MicroampsToCode(value))), - triggerDelay.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.TRAINDELAY, value)), phaseOneDuration.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR1, value)), interPhaseInterval.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.INTERPHASEINTERVAL, value)), phaseTwoDuration.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR2, value)), @@ -290,7 +263,6 @@ public override IObservable Process(IObservable source interBurstInterval.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.INTERBURSTINTERVAL, value)), burstPulseCount.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.BURSTCOUNT, value)), trainBurstCount.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.TRAINCOUNT, value)), - powerEnable.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.POWERON, value ? 1u : 0u)), DeviceManager.RegisterDevice(deviceName, device, DeviceType)); }); } @@ -307,23 +279,19 @@ static class Headstage64ElectricalStimulator // managed registers public const uint ENABLE = 0; // Enable stimulus report stream - 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 STIMENABLE = 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 + public const uint CURRENT1 = 1; // Phase 1 current + public const uint CURRENT2 = 2; // Phase 2 current + public const uint PULSEDUR1 = 3; // Phase 1 duration, 1 microsecond steps + public const uint INTERPHASEINTERVAL = 4; // Inter-phase interval, 10 microsecond steps + public const uint PULSEDUR2 = 5; // Phase 2 duration, 1 microsecond steps + public const uint INTERPULSEINTERVAL = 6; // Inter-pulse interval, 10 microsecond steps + public const uint BURSTCOUNT = 7; // Burst duration, number of pulses in burst + public const uint INTERBURSTINTERVAL = 8; // Inter-burst interval, microseconds + public const uint TRAINCOUNT = 9; // Pulse train duration, number of bursts in train + public const uint TRIGGER = 10; // Trigger stimulation (1 = deliver) + public const uint STIMENABLE = 11; // If 0 then stimulation triggers will be ignored, otherwise they will be applied + public const uint RESTCURRENT = 12; // Resting current between pulse phases + public const uint REZ = 13; // Internal DAC resolution in bits internal static uint MicroampsToCode(double currentuA) { diff --git a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs index 0bd34413..c8472434 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs @@ -19,16 +19,16 @@ namespace OpenEphys.Onix1 [Editor("OpenEphys.Onix1.Design.Headstage64OpticalStimulatorComponentEditor, OpenEphys.Onix1.Design", typeof(ComponentEditor))] public class ConfigureHeadstage64OpticalStimulator : SingleDeviceFactory { - readonly BehaviorSubject stimEnable = new(true); - readonly BehaviorSubject maxCurrent = new(100); - readonly BehaviorSubject channelOneCurrent = new(100); + readonly BehaviorSubject stimEnable = new(false); + readonly BehaviorSubject enableIndicationLed = new(false); + readonly BehaviorSubject maxCurrent = new(0); + readonly BehaviorSubject channelOneCurrent = new(0); readonly BehaviorSubject channelTwoCurrent = new(0); - readonly BehaviorSubject pulseDuration = new(5); - readonly BehaviorSubject pulsesPerSecond = new(50); - readonly BehaviorSubject pulsesPerBurst = new(20); + readonly BehaviorSubject pulseDuration = new(0); + readonly BehaviorSubject pulsesPerSecond = new(0); + readonly BehaviorSubject pulsesPerBurst = new(0); readonly BehaviorSubject interBurstInterval = new(0); - readonly BehaviorSubject burstsPerTrain = new(1); - readonly BehaviorSubject delay = new(0); + readonly BehaviorSubject burstsPerTrain = new(0); /// /// Initializes a new instance of the class. @@ -48,7 +48,6 @@ public ConfigureHeadstage64OpticalStimulator(ConfigureHeadstage64OpticalStimulat DeviceAddress = opticalStimulator.DeviceAddress; Enable = opticalStimulator.Enable; StimEnable = opticalStimulator.StimEnable; - Delay = opticalStimulator.Delay; MaxCurrent = opticalStimulator.MaxCurrent; ChannelOneCurrent = opticalStimulator.ChannelOneCurrent; ChannelTwoCurrent = opticalStimulator.ChannelTwoCurrent; @@ -70,6 +69,20 @@ public ConfigureHeadstage64OpticalStimulator(ConfigureHeadstage64OpticalStimulat [Description("Specifies whether the headstage-64 optical stimulator will produce stimulus reports.")] public bool Enable { get; set; } + /// + /// Gets or sets the indication LED enable state. + /// + /// + /// If set to true, the headstage's indication LED will turn on. When set to false, it will turn off. + /// + [Description("Specifies the state of the headstage indication LED")] + [Category(AcquisitionCategory)] + public bool EnableIndicationLed + { + get => enableIndicationLed.Value; + set => enableIndicationLed.OnNext(value); + } + /// /// Gets or sets the device enable state. /// @@ -84,19 +97,6 @@ public bool StimEnable set => stimEnable.OnNext(value); } - /// - /// Gets or sets a delay from receiving a trigger to the start of stimulus sequence application in msec. - /// - [Description("A delay from receiving a trigger to the start of stimulus sequence application (msec).")] - [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] - [Range(Headstage64OpticalStimulator.MinDelay, Headstage64OpticalStimulator.MaxDelay)] - [Precision(3, 1)] - [Category(AcquisitionCategory)] - public double Delay - { - get => delay.Value; - set => delay.OnNext(Clamp(value, Headstage64OpticalStimulator.MinDelay, Headstage64OpticalStimulator.MaxDelay)); - } /// /// Gets or sets the Maximum current per channel per pulse in mA. @@ -295,9 +295,25 @@ static uint pulseFrequencyToRegister(double pulseHz, double pulseDuration) return pulsePeriod > pulseDuration ? (uint)(1000 * pulsePeriod) : (uint)(1000 * pulseDuration + 1); } + uint stimEnableValue = 0; + return new CompositeDisposable( + enableIndicationLed.SubscribeSafe(observer, value => + { + if (value) + stimEnableValue |= (1u << 8); + else + stimEnableValue &= ~(1u << 8); + device.WriteRegister(Headstage64OpticalStimulator.STIMENABLE, stimEnableValue); + }), stimEnable.SubscribeSafe(observer, value => - device.WriteRegister(Headstage64OpticalStimulator.STIMENABLE, value ? 1u : 0u)), + { + if (value) + stimEnableValue |= 1u; + else + stimEnableValue &= ~1u; + device.WriteRegister(Headstage64OpticalStimulator.STIMENABLE, stimEnableValue); + }), maxCurrent.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.MAXCURRENT, Headstage64OpticalStimulator.MilliampsToPotSetting(value))), channelOneCurrent.SubscribeSafe(observer, value => @@ -320,9 +336,7 @@ static uint pulseFrequencyToRegister(double pulseHz, double pulseDuration) device.WriteRegister(Headstage64OpticalStimulator.IBI, (uint)(1000 * value))), burstsPerTrain.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.TRAINCOUNT, value)), - delay.SubscribeSafe(observer, value => - device.WriteRegister(Headstage64OpticalStimulator.TRAINDELAY, (uint)(1000 * value))), - DeviceManager.RegisterDevice(deviceName, device, DeviceType)); + DeviceManager.RegisterDevice(deviceName, device, DeviceType));;; }); } } @@ -336,9 +350,6 @@ static class Headstage64OpticalStimulator public const uint MinRheostatResistanceOhms = 590; public const uint PotResistanceOhms = 100_000; - public const double MinDelay = 0.0; - public const double MaxDelay = 1000.0; - public const double MinCurrent = 0.0; public const double MaxCurrent = 300.0; @@ -364,13 +375,10 @@ static class Headstage64OpticalStimulator public const uint BURSTCOUNT = 5; // Number of pulses in burst public const uint IBI = 6; // Inter-burst interval, microseconds public const uint TRAINCOUNT = 7; // Number of bursts in train - public const uint TRAINDELAY = 8; // Stimulus start delay, microseconds - public const uint TRIGGER = 9; // Trigger stimulation (0 = off, 1 = deliver) - public const uint STIMENABLE = 10; // 1: enables the stimulator, 0: stimulator ignores triggers (so that a common trigger can be used) - public const uint RESTMASK = 11; // Bitmask determining the off state of the up to 32 current channels - public const uint RESET = 12; // None If 1, Reset all parameters to default (not implemented) - public const uint MINRHEOR = 13; // The series resistor between the potentiometer (rheostat) and RSET bin on the CAT4016 - public const uint POTRES = 14; // The resistance value of the potentiometer connected in rheostat config to RSET on CAT4016 + public const uint TRIGGER = 8; // Trigger stimulation (0 = off, 1 = deliver) + public const uint STIMENABLE = 9; // 1: enables the stimulator, 0: stimulator ignores triggers (so that a common trigger can be used) + public const uint MINRHEOR = 10; // The series resistor between the potentiometer (rheostat) and RSET bin on the CAT4016 + public const uint POTRES = 11; // The resistance value of the potentiometer connected in rheostat config to RSET on CAT4016 // NB: fit from Fig. 10 of CAT4016 datasheet // x = (y/a)^(1/b) diff --git a/OpenEphys.Onix1/ConfigurePortController.cs b/OpenEphys.Onix1/ConfigurePortController.cs index 0bad98f8..3652709d 100644 --- a/OpenEphys.Onix1/ConfigurePortController.cs +++ b/OpenEphys.Onix1/ConfigurePortController.cs @@ -107,7 +107,7 @@ internal static class PortController public const uint MinimumVersion = 2; public const uint ENABLE = 0; // The LSB is used to enable or disable the device data stream - public const uint GPOSTATE = 1; // GPO output state (bits 31 downto 3: ignore. bits 2 downto 0: ‘1’ = high, ‘0’ = low) + public const uint GPOSTATE = 1; // Controls the GPO output state 32 bits: [X, X, ..., X, GPO 3, GPO 2, GPO 1] public const uint DESPWR = 2; // Set link deserializer PDB state, 0 = deserializer power off else on. Does not affect port voltage. public const uint PORTVOLTAGE = 3; // 10 * link voltage public const uint SAVEVOLTAGE = 4; // Save link voltage to non-volatile EEPROM if greater than 0. This voltage will be applied after POR. @@ -193,4 +193,28 @@ public enum PortName [Description("Port B")] PortB = 2 } + + /// + /// Specifies the state of a port controller's GPIO pins. + /// + /// + /// Pin 0 is inaccessible because it is used for issuing hardware resets. + /// + [Flags] + public enum PortControllerGpioState : byte + { + /// + /// Specifies that pin 1 is high. + /// + Pin1 = 0x1, + /// + /// Specifies that pin 2 is high. + /// + Pin2 = 0x2, + /// + /// Specifies that pin 3 is high. + /// + Pin3 = 0x4, + } + } diff --git a/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs b/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs index 7cf0f497..817f6d8b 100644 --- a/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs +++ b/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs @@ -18,7 +18,7 @@ namespace OpenEphys.Onix1 /// corresponding contact on a compatible electrode interface board. /// [Description("Controls a headstage-64 onboard electrical stimulus sequencer.")] - public class Headstage64ElectricalStimulatorTrigger : Sink + public class Headstage64ElectricalStimulatorTrigger : Sink { /// [TypeConverter(typeof(Headstage64ElectricalStimulator.NameConverter))] @@ -27,20 +27,23 @@ public class Headstage64ElectricalStimulatorTrigger : Sink public string DeviceName { get; set; } /// - /// Start an electrical stimulus sequence. + /// Start an electrical stimulus sequence with an optional hardware delay. /// - /// A sequence of boolean values indicating the start of a stimulus sequence when true. - /// A sequence of boolean values that is identical to - public override IObservable Process(IObservable source) + /// A sequence of double values that serve as a combined stimulus trigger and + /// delay in microseconds. A value of 0 results in immediate stimulus delivery. A value of 100 results in + /// stimulus delivery following a 100 microsecond delay. Delays are implemented in hardware and are + /// exact. + /// A sequence of double values that is identical to + public override IObservable Process(IObservable source) { return DeviceManager.GetDevice(DeviceName).SelectMany( - deviceInfo => Observable.Create(observer => + deviceInfo => Observable.Create(observer => { var device = deviceInfo.GetDeviceContext(typeof(Headstage64ElectricalStimulator)); - var triggerObserver = Observer.Create( + var triggerObserver = Observer.Create( value => { - device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, value ? 1u : 0u); + device.WriteRegister(Headstage64ElectricalStimulator.TRIGGER, (uint)value << 8 | 0x1); observer.OnNext(value); }, observer.OnError, diff --git a/OpenEphys.Onix1/Headstage64OpticalStimulatorDataFrame.cs b/OpenEphys.Onix1/Headstage64OpticalStimulatorDataFrame.cs index 69b20f3b..2be79667 100644 --- a/OpenEphys.Onix1/Headstage64OpticalStimulatorDataFrame.cs +++ b/OpenEphys.Onix1/Headstage64OpticalStimulatorDataFrame.cs @@ -22,8 +22,6 @@ public unsafe Headstage64OpticalStimulatorDataFrame(oni.Frame frame) HubClock = payload->HubClock; Origin = (Headstage64StimulatorTriggerOrigin)(payload->DelayAndOrigin & 0x000F); Delay = (payload->DelayAndOrigin & 0xFFF0) >> 8; - ChannelOneRestCurrent = CodeToMilliamps(payload->MaxCurrent, (byte)(payload->RestMask & 0x00FF)); - ChannelTwoRestCurrent = CodeToMilliamps(payload->MaxCurrent, (byte)((payload->RestMask & 0xFF00) >> 8)); ChannelOneCurrent = CodeToMilliamps(payload->MaxCurrent, (byte)(payload->PulseMask & 0x00FF)); ChannelTwoCurrent = CodeToMilliamps(payload->MaxCurrent, (byte)((payload->PulseMask & 0xFF00) >> 8)); PulseDuration = payload->PulseDuration / 1e3; @@ -44,16 +42,6 @@ public unsafe Headstage64OpticalStimulatorDataFrame(oni.Frame frame) /// public uint Delay { get; } - /// - /// Gets the channel one rest current in milliamps. - /// - public double ChannelOneRestCurrent { get; } - - /// - /// Gets the channel two rest current in milliamps. - /// - public double ChannelTwoRestCurrent { get; } - /// /// Gets the channel one pulse current in milliamps. /// @@ -114,7 +102,6 @@ unsafe struct Headstage64OpticalStimulatorPayload { public ulong HubClock; public uint DelayAndOrigin; - public uint RestMask; public uint MaxCurrent; public uint PulseMask; public uint PulseDuration; diff --git a/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs b/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs index 6af266bb..3470a6b5 100644 --- a/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs +++ b/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs @@ -18,7 +18,7 @@ namespace OpenEphys.Onix1 /// contacts on a compatible electrode interface board. /// [Description("Controls a headstage-64 onboard optical stimulus sequencer.")] - public class Headstage64OpticalStimulatorTrigger : Sink + public class Headstage64OpticalStimulatorTrigger : Sink { /// [TypeConverter(typeof(Headstage64OpticalStimulator.NameConverter))] @@ -26,20 +26,23 @@ public class Headstage64OpticalStimulatorTrigger : Sink public string DeviceName { get; set; } /// - /// Start an optical stimulus sequence. + /// Start an optical stimulus sequence with an optional hardware delay. /// - /// A sequence of boolean values indicating the start of a stimulus sequence when true. - /// A sequence of boolean values that is identical to - public override IObservable Process(IObservable source) + /// A sequence of double values that serve as a combined stimulus trigger and + /// delay in microseconds. For instance, a value of 0 results in immediate stimulus delivery, a value + /// of 100 results in stimulus delivery following a 100 microsecond delay, etc. Delays are implemented in + /// hardware and are exact. + /// A sequence of double values that is identical to + public override IObservable Process(IObservable source) { return DeviceManager.GetDevice(DeviceName).SelectMany( - deviceInfo => Observable.Create(observer => + deviceInfo => Observable.Create(observer => { var device = deviceInfo.GetDeviceContext(typeof(Headstage64OpticalStimulator)); - var triggerObserver = Observer.Create( + var triggerObserver = Observer.Create( value => { - device.WriteRegister(Headstage64OpticalStimulator.TRIGGER, value ? 1u : 0u); + device.WriteRegister(Headstage64OpticalStimulator.TRIGGER, (uint)value << 8 | 0x1); observer.OnNext(value); }, observer.OnError, diff --git a/OpenEphys.Onix1/Rhs2116StimulusTrigger.cs b/OpenEphys.Onix1/Rhs2116StimulusTrigger.cs index 4d7293a9..89d7c4ed 100644 --- a/OpenEphys.Onix1/Rhs2116StimulusTrigger.cs +++ b/OpenEphys.Onix1/Rhs2116StimulusTrigger.cs @@ -23,9 +23,9 @@ public class Rhs2116StimulusTrigger : Sink /// Start an electrical stimulus sequence with an optional hardware delay. /// /// A sequence of double values that serve as a combined stimulus trigger and - /// delay in microseconds. A value of 0 results in immediate stimulus delivery. A value of 100 results in - /// stimulus delivery following a 100 microsecond delay. Delays are implemented in hardware and are - /// exact. + /// delay in microseconds. For instance, a value of 0 results in immediate stimulus delivery, a value + /// of 100 results in stimulus delivery following a 100 microsecond delay, etc. Delays are implemented in + /// hardware and are exact. /// A sequence of double values that is identical to public override IObservable Process(IObservable source) { From 1abc29871513c21a351d920db9352a2223197704 Mon Sep 17 00:00:00 2001 From: jonnew Date: Fri, 24 Oct 2025 15:01:46 -0400 Subject: [PATCH 2/7] Add Headstage64GpoTrigger - This is a sink operator that takes booleans and can issue stimulus triggers to the headstage-64 optical and electrical stimulators using the port controller's GPO - To implement this functionality, derivatives of ConfigurePortController now must explicitly specify their DeviceType. This allows the creation of specialized versions of the PortController for particular implementations of MultiDeviceFactory. In most cases, PortController will be used. - In the case of ConfigureHeadstage64, a type called Headstate64PortController was created and is used by ConfigureHeadstage64's specialization of ConfigurePortController. This is also the device filter used by Headstage64GpoTrigger. - This allows headstage-64 stimulus triggers using GPO, but will only work if a ConfigureHeadstage64 operator is in the configuration chain and does not permit general access to GPO since the meaning of each line depends on the headstage that is attached to the port. --- OpenEphys.Onix1/ConfigureHeadstage64.cs | 19 +++++++ ...onfigureHeadstage64ElectricalStimulator.cs | 12 ++--- .../ConfigureHeadstage64OpticalStimulator.cs | 13 +++-- .../ConfigureHeadstageNeuropixelsV1e.cs | 6 +++ .../ConfigureHeadstageNeuropixelsV1f.cs | 6 +++ OpenEphys.Onix1/ConfigureHeadstageNric1384.cs | 6 +++ OpenEphys.Onix1/ConfigureHeadstageRhs2116.cs | 6 +++ .../ConfigureNeuropixelsV2ePortController.cs | 6 +++ OpenEphys.Onix1/ConfigurePortController.cs | 4 +- OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs | 5 ++ .../Headstage64ElectricalStimulatorTrigger.cs | 2 +- OpenEphys.Onix1/Headstage64GpoTrigger.cs | 51 +++++++++++++++++++ .../Headstage64OpticalStimulatorTrigger.cs | 2 +- 13 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 OpenEphys.Onix1/Headstage64GpoTrigger.cs diff --git a/OpenEphys.Onix1/ConfigureHeadstage64.cs b/OpenEphys.Onix1/ConfigureHeadstage64.cs index a8c073d3..6d7e32c0 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64.cs @@ -165,6 +165,11 @@ internal override IEnumerable GetDevices() class ConfigureHeadstage64PortController : ConfigurePortController { + public ConfigureHeadstage64PortController() + : base(typeof(Headstage64PortController)) + { + } + protected override bool ConfigurePortVoltageOverride(DeviceContext device, double voltage) { // NB: Wait for 1 second to discharge the headstage in the case that they have e.g. just @@ -215,5 +220,19 @@ protected override bool ConfigurePortVoltage(DeviceContext device, out double vo return CheckLinkState(device); } } + + internal static class Headstage64PortController + { + public const int ID = PortController.ID; + public const uint MinimumVersion = PortController.MinimumVersion; + + internal class NameConverter : DeviceNameConverter + { + public NameConverter() + : base(typeof(Headstage64PortController)) + { + } + } + } } } diff --git a/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs index a2987e9e..e57fd74d 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs @@ -48,7 +48,7 @@ public ConfigureHeadstage64ElectricalStimulator(ConfigureHeadstage64ElectricalSt DeviceName = electricalStimulator.DeviceName; DeviceAddress = electricalStimulator.DeviceAddress; Enable = electricalStimulator.Enable; - StimEnable = electricalStimulator.StimEnable; + Arm = electricalStimulator.Arm; PhaseOneCurrent = electricalStimulator.PhaseOneCurrent; InterPhaseCurrent = electricalStimulator.InterPhaseCurrent; PhaseTwoCurrent = electricalStimulator.PhaseTwoCurrent; @@ -73,19 +73,17 @@ public ConfigureHeadstage64ElectricalStimulator(ConfigureHeadstage64ElectricalSt public bool Enable { get; set; } /// - /// Gets or sets the device enable state. + /// Gets or sets the device arm state. /// /// /// If set to true, then the electrical stimulator's ±15V power supplies will be turned on and the /// electrical stimulator circuit will respect triggers. If set to false, the power supplies will be - /// shut down and triggers will be ignored.It may be desirable to power down the electrical - /// stimulator's power supplies outside of stimulation windows to reduce power consumption and - /// electrical noise. This property must be set to true in order for electrical stimuli to be - /// delivered properly. It takes ~10 milliseconds for these supplies to stabilize. + /// shut down and triggers will be ignored. It takes ~10 milliseconds for the power supplies to to + /// stabilize. /// [Description("Specifies whether the electrical stimulator will respect triggers.")] [Category(AcquisitionCategory)] - public bool StimEnable + public bool Arm { get => stimEnable.Value; set => stimEnable.OnNext(value); diff --git a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs index c8472434..15666e05 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs @@ -47,7 +47,7 @@ public ConfigureHeadstage64OpticalStimulator(ConfigureHeadstage64OpticalStimulat DeviceName = opticalStimulator.DeviceName; DeviceAddress = opticalStimulator.DeviceAddress; Enable = opticalStimulator.Enable; - StimEnable = opticalStimulator.StimEnable; + Arm = opticalStimulator.Arm; MaxCurrent = opticalStimulator.MaxCurrent; ChannelOneCurrent = opticalStimulator.ChannelOneCurrent; ChannelTwoCurrent = opticalStimulator.ChannelTwoCurrent; @@ -84,20 +84,19 @@ public bool EnableIndicationLed } /// - /// Gets or sets the device enable state. + /// Gets or sets the device arm state. /// /// /// If set to true, then the optical stimulator circuit will respect triggers. If set to false, triggers will be ignored. /// [Description("Specifies whether the optical stimulator will respect triggers.")] [Category(AcquisitionCategory)] - public bool StimEnable + public bool Arm { get => stimEnable.Value; set => stimEnable.OnNext(value); } - /// /// Gets or sets the Maximum current per channel per pulse in mA. /// @@ -350,8 +349,8 @@ static class Headstage64OpticalStimulator public const uint MinRheostatResistanceOhms = 590; public const uint PotResistanceOhms = 100_000; - public const double MinCurrent = 0.0; - public const double MaxCurrent = 300.0; + public const double MinCurrent = 5.822; // NB: This is the lowest allowable maximum current + public const double MaxCurrent = 150.0; // NB: this is not the physical limit, but its a reasonable practical upper boundary public const double MinChannelPercentage = 0.0; public const double MaxChannelPercentage = 100.0; @@ -393,7 +392,7 @@ internal static uint MilliampsToPotSetting(double currentMa) internal static double PotSettingToMilliamps(uint potSetting) { - var R = MinRheostatResistanceOhms + PotResistanceOhms * potSetting / 256; + var R = MinRheostatResistanceOhms + PotResistanceOhms * potSetting / 255; return 3.833e+05 * Math.Pow(R, -0.9632); } diff --git a/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1e.cs b/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1e.cs index 768c9d84..e5b839a0 100644 --- a/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1e.cs +++ b/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1e.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Threading; +using static OpenEphys.Onix1.ConfigureHeadstage64; namespace OpenEphys.Onix1 { @@ -105,6 +106,11 @@ internal override IEnumerable GetDevices() class ConfigureNeuropixelsV1ePortController : ConfigurePortController { + public ConfigureNeuropixelsV1ePortController() + : base(typeof(PortController)) + { + } + protected override bool ConfigurePortVoltage(DeviceContext device, out double voltage) { const double MinVoltage = 3.3; diff --git a/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1f.cs b/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1f.cs index 9fde2bc8..36623e29 100644 --- a/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1f.cs +++ b/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1f.cs @@ -135,6 +135,12 @@ internal override IEnumerable GetDevices() class ConfigureNeuropixels1fHeadstageLinkController : ConfigurePortController { + + public ConfigureNeuropixels1fHeadstageLinkController() + : base(typeof(PortController)) + { + } + protected override bool ConfigurePortVoltage(DeviceContext device, out double voltage) { const double MinVoltage = 5.0; diff --git a/OpenEphys.Onix1/ConfigureHeadstageNric1384.cs b/OpenEphys.Onix1/ConfigureHeadstageNric1384.cs index 85b7d838..10704f37 100644 --- a/OpenEphys.Onix1/ConfigureHeadstageNric1384.cs +++ b/OpenEphys.Onix1/ConfigureHeadstageNric1384.cs @@ -111,6 +111,12 @@ internal override IEnumerable GetDevices() class ConfigureHeadstageNric1384PortController : ConfigurePortController { + + public ConfigureHeadstageNric1384PortController() + : base(typeof(PortController)) + { + } + protected override bool ConfigurePortVoltage(DeviceContext device, out double voltage) { const double MinVoltage = 3.8; diff --git a/OpenEphys.Onix1/ConfigureHeadstageRhs2116.cs b/OpenEphys.Onix1/ConfigureHeadstageRhs2116.cs index 25c1df9d..48c892d1 100644 --- a/OpenEphys.Onix1/ConfigureHeadstageRhs2116.cs +++ b/OpenEphys.Onix1/ConfigureHeadstageRhs2116.cs @@ -116,6 +116,12 @@ internal override IEnumerable GetDevices() class ConfigureHeadstageRhs2116LinkController : ConfigurePortController { + + public ConfigureHeadstageRhs2116LinkController() + : base(typeof(PortController)) + { + } + protected override bool ConfigurePortVoltage(DeviceContext device, out double voltage) { const double MinVoltage = 3.3; diff --git a/OpenEphys.Onix1/ConfigureNeuropixelsV2ePortController.cs b/OpenEphys.Onix1/ConfigureNeuropixelsV2ePortController.cs index 96f2433c..9bc111cd 100644 --- a/OpenEphys.Onix1/ConfigureNeuropixelsV2ePortController.cs +++ b/OpenEphys.Onix1/ConfigureNeuropixelsV2ePortController.cs @@ -4,6 +4,12 @@ namespace OpenEphys.Onix1 { class ConfigureNeuropixelsV2ePortController : ConfigurePortController { + + public ConfigureNeuropixelsV2ePortController() + : base(typeof(PortController)) + { + } + protected override bool ConfigurePortVoltage(DeviceContext device, out double voltage) { const double MinVoltage = 3.3; diff --git a/OpenEphys.Onix1/ConfigurePortController.cs b/OpenEphys.Onix1/ConfigurePortController.cs index 3652709d..97989289 100644 --- a/OpenEphys.Onix1/ConfigurePortController.cs +++ b/OpenEphys.Onix1/ConfigurePortController.cs @@ -8,8 +8,8 @@ namespace OpenEphys.Onix1 { internal abstract class ConfigurePortController : SingleDeviceFactory { - public ConfigurePortController() - : base(typeof(PortController)) + public ConfigurePortController(Type deviceType) + : base(deviceType) { } diff --git a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs index b2df4ec6..82ce4db8 100644 --- a/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs +++ b/OpenEphys.Onix1/ConfigureUclaMiniscopeV4.cs @@ -117,6 +117,11 @@ class ConfigureUclaMiniscopeV4PortController : ConfigurePortController { internal ConfigureUclaMiniscopeV4Camera Camera; + public ConfigureUclaMiniscopeV4PortController() + : base(typeof(PortController)) + { + } + protected override bool ConfigurePortVoltage(DeviceContext device, out double voltage) { const double MinVoltage = 5.2; diff --git a/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs b/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs index 817f6d8b..f89fe072 100644 --- a/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs +++ b/OpenEphys.Onix1/Headstage64ElectricalStimulatorTrigger.cs @@ -8,7 +8,7 @@ namespace OpenEphys.Onix1 { /// - /// Controls a headstage-64 onboard electrical stimulus sequencer. + /// Trigger a headstage-64 onboard electrical stimulus sequencer. /// /// /// This data IO operator must be linked to an appropriate configuration, such as a + /// Trigger a headstage-64 stimulator using the port controller's general purpose output (GPO). + /// + /// + /// This data IO operator must be linked to an appropriate configuration, such as a , using a shared DeviceName. This + /// operator can be used to deliver stimuli with lower latencies than or + /// since it uses a dedicated GPO line rather than a register write to trigger stimulation. However, the + /// trigger will be delivered to both of the stimulators so care must be taken to ensure only the + /// appropriate stimulator is armed when the trigger is sent. + /// + [Description("Controls a headstage-64 onboard optical stimulus sequencer.")] + public class Headstage64GpoTrigger : Sink + { + /// + [TypeConverter(typeof(ConfigureHeadstage64.Headstage64PortController.NameConverter))] + [Description(SingleDeviceFactory.DeviceNameDescription)] + [Category(DeviceFactory.ConfigurationCategory)] + public string DeviceName { get; set; } + + /// + /// Issue a stimulus trigger using the port controller's GPO. + /// + /// A sequence of boolean values indicating if a stimulus trigger should be + /// issued. + /// A sequence that is identical to . + public override IObservable Process(IObservable source) + { + return DeviceManager.GetDevice(DeviceName).SelectMany(deviceInfo => + { + var device = deviceInfo.GetDeviceContext(typeof(ConfigureHeadstage64.Headstage64PortController)); + return source.Do(value => { + if (value) + { + device.WriteRegister(PortController.GPOSTATE, (byte)PortControllerGpioState.Pin1); + device.WriteRegister(PortController.GPOSTATE, 0); + } + }); + }); + } + } +} diff --git a/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs b/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs index 3470a6b5..cc2f5d55 100644 --- a/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs +++ b/OpenEphys.Onix1/Headstage64OpticalStimulatorTrigger.cs @@ -8,7 +8,7 @@ namespace OpenEphys.Onix1 { /// - /// Controls a headstage-64 onboard optical stimulus sequencer. + /// Trigger a headstage-64 onboard optical stimulus sequencer. /// /// /// This data IO operator must be linked to an appropriate configuration, such as a Date: Fri, 24 Oct 2025 16:06:53 -0400 Subject: [PATCH 3/7] Add Heartbeat tab for Headstage64 dialog --- .../Headstage64Dialog.Designer.cs | 13 +++++++++++++ OpenEphys.Onix1.Design/Headstage64Dialog.cs | 4 ++++ OpenEphys.Onix1.Design/Headstage64Editor.cs | 1 + OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs | 13 ++++++++++++- 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/OpenEphys.Onix1.Design/Headstage64Dialog.Designer.cs b/OpenEphys.Onix1.Design/Headstage64Dialog.Designer.cs index c0c5a58c..265b7505 100644 --- a/OpenEphys.Onix1.Design/Headstage64Dialog.Designer.cs +++ b/OpenEphys.Onix1.Design/Headstage64Dialog.Designer.cs @@ -41,6 +41,7 @@ private void InitializeComponent() this.tabPageTS4231 = new System.Windows.Forms.TabPage(); this.menuStrip1 = new System.Windows.Forms.MenuStrip(); this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.tabPageHeartbeat = new System.Windows.Forms.TabPage(); this.tableLayoutPanel1.SuspendLayout(); this.flowLayoutPanel1.SuspendLayout(); this.tabControl.SuspendLayout(); @@ -104,6 +105,7 @@ private void InitializeComponent() this.tabControl.Controls.Add(this.tabPageRhd2164); this.tabControl.Controls.Add(this.tabPageBno055); this.tabControl.Controls.Add(this.tabPageTS4231); + this.tabControl.Controls.Add(this.tabPageHeartbeat); this.tabControl.Dock = System.Windows.Forms.DockStyle.Fill; this.tabControl.Location = new System.Drawing.Point(3, 2); this.tabControl.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2); @@ -178,6 +180,16 @@ private void InitializeComponent() this.fileToolStripMenuItem.Size = new System.Drawing.Size(37, 20); this.fileToolStripMenuItem.Text = "File"; // + // tabPageHeartbeat + // + this.tabPageHeartbeat.Location = new System.Drawing.Point(4, 25); + this.tabPageHeartbeat.Name = "tabPageHeartbeat"; + this.tabPageHeartbeat.Padding = new System.Windows.Forms.Padding(3); + this.tabPageHeartbeat.Size = new System.Drawing.Size(1170, 512); + this.tabPageHeartbeat.TabIndex = 6; + this.tabPageHeartbeat.Text = "Heartbeat"; + this.tabPageHeartbeat.UseVisualStyleBackColor = true; + // // Headstage64Dialog // this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); @@ -214,5 +226,6 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; private System.Windows.Forms.TabPage tabPageElectricalStimulator; private System.Windows.Forms.TabPage tabPageOpticalStimulator; + private System.Windows.Forms.TabPage tabPageHeartbeat; } } \ No newline at end of file diff --git a/OpenEphys.Onix1.Design/Headstage64Dialog.cs b/OpenEphys.Onix1.Design/Headstage64Dialog.cs index 217111fe..8a75a314 100644 --- a/OpenEphys.Onix1.Design/Headstage64Dialog.cs +++ b/OpenEphys.Onix1.Design/Headstage64Dialog.cs @@ -10,6 +10,7 @@ public partial class Headstage64Dialog : Form internal readonly GenericDeviceDialog Rhd2164Dialog; internal readonly GenericDeviceDialog Bno055Dialog; internal readonly GenericDeviceDialog TS4231V1Dialog; + internal readonly GenericDeviceDialog HeartbeatDialog; internal readonly Headstage64ElectricalStimulatorSequenceDialog ElectricalStimulatorSequenceDialog; internal readonly Headstage64OpticalStimulatorSequenceDialog OpticalStimulatorSequenceDialog; @@ -30,6 +31,9 @@ public Headstage64Dialog(ConfigureHeadstage64 configureNode) TS4231V1Dialog = new(new ConfigureTS4231V1(configureNode.TS4231)); TS4231V1Dialog.SetChildFormProperties(this).AddDialogToTab(tabPageTS4231); + HeartbeatDialog = new(new ConfigurePersistentHeartbeat(configureNode.Heartbeat)); + HeartbeatDialog.SetChildFormProperties(this).AddDialogToTab(tabPageHeartbeat); + ElectricalStimulatorSequenceDialog = new(configureNode.ElectricalStimulator); ElectricalStimulatorSequenceDialog.SetChildFormProperties(this).AddDialogToTab(tabPageElectricalStimulator); diff --git a/OpenEphys.Onix1.Design/Headstage64Editor.cs b/OpenEphys.Onix1.Design/Headstage64Editor.cs index 7efaf263..621aec6d 100644 --- a/OpenEphys.Onix1.Design/Headstage64Editor.cs +++ b/OpenEphys.Onix1.Design/Headstage64Editor.cs @@ -25,6 +25,7 @@ public override bool EditComponent(ITypeDescriptorContext context, object compon DesignHelper.CopyProperties((ConfigureRhd2164)editorDialog.Rhd2164Dialog.Device, configureNode.Rhd2164, DesignHelper.PropertiesToIgnore); DesignHelper.CopyProperties((ConfigureBno055)editorDialog.Bno055Dialog.Device, configureNode.Bno055, DesignHelper.PropertiesToIgnore); DesignHelper.CopyProperties((ConfigureTS4231V1)editorDialog.TS4231V1Dialog.Device, configureNode.TS4231, DesignHelper.PropertiesToIgnore); + DesignHelper.CopyProperties((ConfigurePersistentHeartbeat)editorDialog.HeartbeatDialog.Device, configureNode.Heartbeat, DesignHelper.PropertiesToIgnore); configureNode.ElectricalStimulator = editorDialog.ElectricalStimulatorSequenceDialog.ElectricalStimulator; configureNode.OpticalStimulator = editorDialog.OpticalStimulatorSequenceDialog.OpticalStimulator; diff --git a/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs b/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs index 9f6f4388..59f4c0c6 100644 --- a/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs +++ b/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs @@ -25,6 +25,18 @@ public ConfigurePersistentHeartbeat() { } + /// + /// Initializes a copy instance of the class with the given values. + /// + /// Existing configuration settings. + public ConfigurePersistentHeartbeat(ConfigurePersistentHeartbeat configureHeartbeat) + : this() + { + DeviceAddress = configureHeartbeat.DeviceAddress; + DeviceName = configureHeartbeat.DeviceName; + BeatsPerSecond = configureHeartbeat.BeatsPerSecond; + } + /// /// Gets or sets the rate at which beats are produced in Hz. /// @@ -50,7 +62,6 @@ public uint BeatsPerSecond /// configure a persistent heartbeat device./> public override IObservable Process(IObservable source) { - //var enable = Enable; var deviceName = DeviceName; var deviceAddress = DeviceAddress; return source.ConfigureDevice((context, observer) => From 8ab286b343e2a74888f3d9f8d8a8e0cd6233b046 Mon Sep 17 00:00:00 2001 From: jonnew Date: Sat, 25 Oct 2025 17:08:06 -0400 Subject: [PATCH 4/7] Address code review - Readd inter phase current property to ConfigureHeadstage64ElectricalStimulator - Improve code comments --- .../ConfigureHeadstage64ElectricalStimulator.cs | 2 ++ .../ConfigureHeadstageNeuropixelsV1e.cs | 1 - OpenEphys.Onix1/Headstage64GpoTrigger.cs | 15 ++++++++------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs index e57fd74d..8ac4a9b0 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64ElectricalStimulator.cs @@ -252,6 +252,8 @@ public override IObservable Process(IObservable source device.WriteRegister(Headstage64ElectricalStimulator.STIMENABLE, value ? 3u : 0u)), phaseOneCurrent.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT1, Headstage64ElectricalStimulator.MicroampsToCode(value))), + interPhaseCurrent.SubscribeSafe(observer, value => + device.WriteRegister(Headstage64ElectricalStimulator.RESTCURRENT, Headstage64ElectricalStimulator.MicroampsToCode(value))), phaseTwoCurrent.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.CURRENT2, Headstage64ElectricalStimulator.MicroampsToCode(value))), phaseOneDuration.SubscribeSafe(observer, value => device.WriteRegister(Headstage64ElectricalStimulator.PULSEDUR1, value)), diff --git a/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1e.cs b/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1e.cs index e5b839a0..217a461d 100644 --- a/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1e.cs +++ b/OpenEphys.Onix1/ConfigureHeadstageNeuropixelsV1e.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.Threading; -using static OpenEphys.Onix1.ConfigureHeadstage64; namespace OpenEphys.Onix1 { diff --git a/OpenEphys.Onix1/Headstage64GpoTrigger.cs b/OpenEphys.Onix1/Headstage64GpoTrigger.cs index 287c6087..69c3efdc 100644 --- a/OpenEphys.Onix1/Headstage64GpoTrigger.cs +++ b/OpenEphys.Onix1/Headstage64GpoTrigger.cs @@ -11,14 +11,15 @@ namespace OpenEphys.Onix1 /// /// /// This data IO operator must be linked to an appropriate configuration, such as a , using a shared DeviceName. This - /// operator can be used to deliver stimuli with lower latencies than or - /// since it uses a dedicated GPO line rather than a register write to trigger stimulation. However, the - /// trigger will be delivered to both of the stimulators so care must be taken to ensure only the - /// appropriate stimulator is armed when the trigger is sent. + /// cref="ConfigureHeadstage64.Headstage64PortController"/>, using a shared DeviceName. Each + /// headstage port has a GPO interface for sending digital signals to the headstage with low latency. This + /// operator uses a dedicated GPO line rather than a register write to trigger stimulation and therefore + /// has lower latencies than or . However, the trigger will be delivered to both of the + /// stimulators so care must be taken to ensure only the appropriate stimulator is armed when the trigger + /// is sent. /// - [Description("Controls a headstage-64 onboard optical stimulus sequencer.")] + [Description("Triggers a headstage-64 stimulator using the port controller's general purpose output (GPO)")] public class Headstage64GpoTrigger : Sink { /// From 6787b83b410ebf3e3467666f4ee8026cc84f5e54 Mon Sep 17 00:00:00 2001 From: jonnew Date: Mon, 27 Oct 2025 12:29:50 -0400 Subject: [PATCH 5/7] Remove Range attribute for ConfigurePersistentHeartbeat.BeatsPerSecond - The range is a readonly register value that is device specific and not know at compile time --- OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs | 2 +- OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs index 15666e05..01255720 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs @@ -335,7 +335,7 @@ static uint pulseFrequencyToRegister(double pulseHz, double pulseDuration) device.WriteRegister(Headstage64OpticalStimulator.IBI, (uint)(1000 * value))), burstsPerTrain.SubscribeSafe(observer, value => device.WriteRegister(Headstage64OpticalStimulator.TRAINCOUNT, value)), - DeviceManager.RegisterDevice(deviceName, device, DeviceType));;; + DeviceManager.RegisterDevice(deviceName, device, DeviceType)); }); } } diff --git a/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs b/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs index 59f4c0c6..94f192aa 100644 --- a/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs +++ b/OpenEphys.Onix1/ConfigurePersistentHeartbeat.cs @@ -40,7 +40,6 @@ public ConfigurePersistentHeartbeat(ConfigurePersistentHeartbeat configureHeartb /// /// Gets or sets the rate at which beats are produced in Hz. /// - [Range(100, 10e6)] [Category(AcquisitionCategory)] [Description("Rate at which beats are produced (Hz).")] public uint BeatsPerSecond From b84dcd2edd80e929a1fc9e3de6d2115afe98627f Mon Sep 17 00:00:00 2001 From: jonnew Date: Fri, 31 Oct 2025 10:49:27 -0400 Subject: [PATCH 6/7] Allow Headstage64PortController to be used by PortStatus --- OpenEphys.Onix1/ConfigureHeadstage64.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenEphys.Onix1/ConfigureHeadstage64.cs b/OpenEphys.Onix1/ConfigureHeadstage64.cs index 6d7e32c0..d231f110 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64.cs @@ -221,6 +221,7 @@ protected override bool ConfigurePortVoltage(DeviceContext device, out double vo } } + [EquivalentDataSource(typeof(PortController))] internal static class Headstage64PortController { public const int ID = PortController.ID; From 64d302e24e5135a73e2c44bb6a43ef939ac0f17f Mon Sep 17 00:00:00 2001 From: jonnew Date: Thu, 6 Nov 2025 15:27:12 -0500 Subject: [PATCH 7/7] Fixed ConfigureHeadstage64OpticalStimulator properties - The pulse frequency was sometimes indicated as pulse period and other times as frequency with bad results --- ...tage64OpticalStimulatorOptions.Designer.cs | 56 +++++++++---------- .../Headstage64OpticalStimulatorOptions.cs | 6 +- ...dstage64OpticalStimulatorSequenceDialog.cs | 8 +-- .../ConfigureHeadstage64OpticalStimulator.cs | 28 +++++----- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.Designer.cs b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.Designer.cs index 4eda2caf..0c3cc3de 100644 --- a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.Designer.cs +++ b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.Designer.cs @@ -44,8 +44,8 @@ private void InitializeComponent() this.trackBarChannelTwoPercent = new System.Windows.Forms.TrackBar(); this.textBoxChannelTwoPercent = new System.Windows.Forms.TextBox(); this.labelChannelTwoPercent = new System.Windows.Forms.Label(); - this.textBoxPulsePeriod = new System.Windows.Forms.TextBox(); - this.labelPulsePeriod = new System.Windows.Forms.Label(); + this.textBoxPulseFrequencyHz = new System.Windows.Forms.TextBox(); + this.labelPulseFrequencyHz = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.trackBarChannelOnePercent)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBarChannelTwoPercent)).BeginInit(); this.SuspendLayout(); @@ -53,7 +53,7 @@ private void InitializeComponent() // textBoxBurstsPerTrain // this.textBoxBurstsPerTrain.Location = new System.Drawing.Point(100, 198); - this.textBoxBurstsPerTrain.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.textBoxBurstsPerTrain.Margin = new System.Windows.Forms.Padding(2); this.textBoxBurstsPerTrain.Name = "textBoxBurstsPerTrain"; this.textBoxBurstsPerTrain.Size = new System.Drawing.Size(57, 20); this.textBoxBurstsPerTrain.TabIndex = 17; @@ -72,7 +72,7 @@ private void InitializeComponent() // textBoxInterBurstInterval // this.textBoxInterBurstInterval.Location = new System.Drawing.Point(267, 158); - this.textBoxInterBurstInterval.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.textBoxInterBurstInterval.Margin = new System.Windows.Forms.Padding(2); this.textBoxInterBurstInterval.Name = "textBoxInterBurstInterval"; this.textBoxInterBurstInterval.Size = new System.Drawing.Size(57, 20); this.textBoxInterBurstInterval.TabIndex = 15; @@ -89,7 +89,7 @@ private void InitializeComponent() // textBoxPulsesPerBurst // this.textBoxPulsesPerBurst.Location = new System.Drawing.Point(100, 158); - this.textBoxPulsesPerBurst.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.textBoxPulsesPerBurst.Margin = new System.Windows.Forms.Padding(2); this.textBoxPulsesPerBurst.Name = "textBoxPulsesPerBurst"; this.textBoxPulsesPerBurst.Size = new System.Drawing.Size(57, 20); this.textBoxPulsesPerBurst.TabIndex = 13; @@ -118,7 +118,7 @@ private void InitializeComponent() // textBoxMaxCurrent // this.textBoxMaxCurrent.Location = new System.Drawing.Point(100, 14); - this.textBoxMaxCurrent.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.textBoxMaxCurrent.Margin = new System.Windows.Forms.Padding(2); this.textBoxMaxCurrent.Name = "textBoxMaxCurrent"; this.textBoxMaxCurrent.Size = new System.Drawing.Size(57, 20); this.textBoxMaxCurrent.TabIndex = 1; @@ -126,7 +126,7 @@ private void InitializeComponent() // textBoxChannelOnePercent // this.textBoxChannelOnePercent.Location = new System.Drawing.Point(100, 47); - this.textBoxChannelOnePercent.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.textBoxChannelOnePercent.Margin = new System.Windows.Forms.Padding(2); this.textBoxChannelOnePercent.Name = "textBoxChannelOnePercent"; this.textBoxChannelOnePercent.Size = new System.Drawing.Size(57, 20); this.textBoxChannelOnePercent.TabIndex = 3; @@ -158,7 +158,7 @@ private void InitializeComponent() // textBoxPulseDuration // this.textBoxPulseDuration.Location = new System.Drawing.Point(100, 119); - this.textBoxPulseDuration.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.textBoxPulseDuration.Margin = new System.Windows.Forms.Padding(2); this.textBoxPulseDuration.Name = "textBoxPulseDuration"; this.textBoxPulseDuration.Size = new System.Drawing.Size(57, 20); this.textBoxPulseDuration.TabIndex = 9; @@ -189,7 +189,7 @@ private void InitializeComponent() // textBoxChannelTwoPercent // this.textBoxChannelTwoPercent.Location = new System.Drawing.Point(267, 47); - this.textBoxChannelTwoPercent.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.textBoxChannelTwoPercent.Margin = new System.Windows.Forms.Padding(2); this.textBoxChannelTwoPercent.Name = "textBoxChannelTwoPercent"; this.textBoxChannelTwoPercent.Size = new System.Drawing.Size(57, 20); this.textBoxChannelTwoPercent.TabIndex = 5; @@ -204,31 +204,31 @@ private void InitializeComponent() this.labelChannelTwoPercent.TabIndex = 4; this.labelChannelTwoPercent.Text = "Channel Two [%]"; // - // textBoxPulsePeriod + // textBoxPulseFrequencyHz // - this.textBoxPulsePeriod.Location = new System.Drawing.Point(267, 119); - this.textBoxPulsePeriod.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); - this.textBoxPulsePeriod.Name = "textBoxPulsePeriod"; - this.textBoxPulsePeriod.Size = new System.Drawing.Size(57, 20); - this.textBoxPulsePeriod.TabIndex = 11; + this.textBoxPulseFrequencyHz.Location = new System.Drawing.Point(267, 119); + this.textBoxPulseFrequencyHz.Margin = new System.Windows.Forms.Padding(2); + this.textBoxPulseFrequencyHz.Name = "textBoxPulseFrequencyHz"; + this.textBoxPulseFrequencyHz.Size = new System.Drawing.Size(57, 20); + this.textBoxPulseFrequencyHz.TabIndex = 11; // - // labelPulsePeriod + // labelPulseFrequencyHz // - this.labelPulsePeriod.AutoSize = true; - this.labelPulsePeriod.Location = new System.Drawing.Point(162, 121); - this.labelPulsePeriod.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); - this.labelPulsePeriod.Name = "labelPulsePeriod"; - this.labelPulsePeriod.Size = new System.Drawing.Size(88, 13); - this.labelPulsePeriod.TabIndex = 10; - this.labelPulsePeriod.Text = "Pulse Period [ms]"; + this.labelPulseFrequencyHz.AutoSize = true; + this.labelPulseFrequencyHz.Location = new System.Drawing.Point(162, 121); + this.labelPulseFrequencyHz.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.labelPulseFrequencyHz.Name = "labelPulseFrequencyHz"; + this.labelPulseFrequencyHz.Size = new System.Drawing.Size(82, 13); + this.labelPulseFrequencyHz.TabIndex = 10; + this.labelPulseFrequencyHz.Text = "Pulse Freq. [Hz]"; // // Headstage64OpticalStimulatorOptions // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(328, 235); - this.Controls.Add(this.textBoxPulsePeriod); - this.Controls.Add(this.labelPulsePeriod); + this.Controls.Add(this.textBoxPulseFrequencyHz); + this.Controls.Add(this.labelPulseFrequencyHz); this.Controls.Add(this.trackBarChannelTwoPercent); this.Controls.Add(this.textBoxChannelTwoPercent); this.Controls.Add(this.labelChannelTwoPercent); @@ -245,7 +245,7 @@ private void InitializeComponent() this.Controls.Add(this.labelInterBurstInterval); this.Controls.Add(this.textBoxPulsesPerBurst); this.Controls.Add(this.labelPulsesPerBurst); - this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.Margin = new System.Windows.Forms.Padding(2); this.Name = "Headstage64OpticalStimulatorOptions"; this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; this.Text = "Headstage64OpticalStimulatorOptions"; @@ -273,7 +273,7 @@ private void InitializeComponent() private System.Windows.Forms.Label labelChannelTwoPercent; internal System.Windows.Forms.TrackBar trackBarChannelOnePercent; internal System.Windows.Forms.TrackBar trackBarChannelTwoPercent; - internal System.Windows.Forms.TextBox textBoxPulsePeriod; - private System.Windows.Forms.Label labelPulsePeriod; + internal System.Windows.Forms.TextBox textBoxPulseFrequencyHz; + private System.Windows.Forms.Label labelPulseFrequencyHz; } } diff --git a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.cs b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.cs index ad7367c5..00d64e16 100644 --- a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.cs +++ b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorOptions.cs @@ -32,8 +32,8 @@ internal void UpdateSequenceParameters(ConfigureHeadstage64OpticalStimulator opt { textBoxMaxCurrent.Text = opticalStimulator.MaxCurrent.ToString(); textBoxPulseDuration.Text = opticalStimulator.PulseDuration.ToString(); - textBoxPulsePeriod.Text = opticalStimulator.PulsesPerSecond.ToString(); - textBoxPulsePeriod.Enabled = opticalStimulator.PulsesPerBurst > 1; + textBoxPulseFrequencyHz.Text = opticalStimulator.PulsesPerSecond.ToString(); + textBoxPulseFrequencyHz.Enabled = opticalStimulator.PulsesPerBurst > 1; textBoxChannelOnePercent.Text = opticalStimulator.ChannelOneCurrent.ToString(); trackBarChannelOnePercent.Value = (int)(opticalStimulator.ChannelOneCurrent * channelOneScalingFactor); @@ -53,7 +53,7 @@ void PulsesPerBurstChanged(object sender, System.EventArgs e) { if (int.TryParse(textBoxPulsesPerBurst.Text, out int result)) { - textBoxPulsePeriod.Enabled = result > 1; + textBoxPulseFrequencyHz.Enabled = result > 1; } } diff --git a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorSequenceDialog.cs b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorSequenceDialog.cs index 35d243e5..b114f12e 100644 --- a/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorSequenceDialog.cs +++ b/OpenEphys.Onix1.Design/Headstage64OpticalStimulatorSequenceDialog.cs @@ -82,9 +82,9 @@ public Headstage64OpticalStimulatorSequenceDialog(ConfigureHeadstage64OpticalSti StimulusSequenceOptions.textBoxPulseDuration, value => { OpticalStimulator.PulseDuration = value; return OpticalStimulator.PulseDuration; }, double.Parse) }, - { StimulusSequenceOptions.textBoxPulsePeriod, + { StimulusSequenceOptions.textBoxPulseFrequencyHz, new TextBoxBinding( - StimulusSequenceOptions.textBoxPulsePeriod, + StimulusSequenceOptions.textBoxPulseFrequencyHz, value => { OpticalStimulator.PulsesPerSecond = value; return OpticalStimulator.PulsesPerSecond; }, double.Parse) }, }; @@ -236,7 +236,7 @@ internal override PointPairList[] CreateStimulusWaveforms() if (j != OpticalStimulator.PulsesPerBurst - 1) { - waveforms[channel].Add(new PointPair(waveforms[channel].Last().X + OpticalStimulator.PulsesPerSecond - OpticalStimulator.PulseDuration, offset)); + waveforms[channel].Add(new PointPair(waveforms[channel].Last().X + 1000.0 / OpticalStimulator.PulsesPerSecond - OpticalStimulator.PulseDuration, offset)); } } @@ -275,7 +275,7 @@ static bool IsSequenceValid(ConfigureHeadstage64OpticalStimulator sequence, out reason = "Maximum current is invalid."; return false; } - else if (sequence.PulsesPerBurst > 1 && sequence.PulsesPerSecond <= sequence.PulseDuration) + else if (sequence.PulsesPerBurst > 1 && 1000.0 / sequence.PulsesPerSecond <= sequence.PulseDuration) { reason = "Pulse period is too short compared to the pulse duration."; return false; diff --git a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs index 01255720..2b2723df 100644 --- a/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs +++ b/OpenEphys.Onix1/ConfigureHeadstage64OpticalStimulator.cs @@ -164,27 +164,27 @@ public double ChannelTwoCurrent /// [Description("The duration of each pulse (msec).")] [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] - [Range(Headstage64OpticalStimulator.MinPulseDuration, Headstage64OpticalStimulator.MaxPulseDuration)] + [Range(Headstage64OpticalStimulator.MinPulseDurationMilliseconds, Headstage64OpticalStimulator.MaxPulseDurationMilliseconds)] [Precision(3, 1)] [Category(AcquisitionCategory)] public double PulseDuration { get => pulseDuration.Value; - set => pulseDuration.OnNext(Clamp(value, Headstage64OpticalStimulator.MinPulseDuration, Headstage64OpticalStimulator.MaxPulseDuration)); + set => pulseDuration.OnNext(Clamp(value, Headstage64OpticalStimulator.MinPulseDurationMilliseconds, Headstage64OpticalStimulator.MaxPulseDurationMilliseconds)); } /// - /// Gets or sets the pulse period within a burst in msec. + /// Gets or sets the pulse frequency within a burst in Hz. /// - [Description("The pulse period within a burst (msec).")] + [Description("The pulse frequency within a burst (Hz).")] [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] - [Range(Headstage64OpticalStimulator.MinPulsePeriod, Headstage64OpticalStimulator.MaxPulsePeriod)] + [Range(Headstage64OpticalStimulator.MinPulseFrequencyHz, Headstage64OpticalStimulator.MaxPulseFrequencyHz)] [Precision(3, 1)] [Category(AcquisitionCategory)] public double PulsesPerSecond { get => pulsesPerSecond.Value; - set => pulsesPerSecond.OnNext(Clamp(value, Headstage64OpticalStimulator.MinPulsePeriod, Headstage64OpticalStimulator.MaxPulsePeriod)); + set => pulsesPerSecond.OnNext(Clamp(value, Headstage64OpticalStimulator.MinPulseFrequencyHz, Headstage64OpticalStimulator.MaxPulseFrequencyHz)); } /// @@ -206,13 +206,13 @@ public uint PulsesPerBurst /// [Description("The duration of the inter-burst interval within a stimulus train (msec).")] [Editor(DesignTypes.NumericUpDownEditor, DesignTypes.UITypeEditor)] - [Range(Headstage64OpticalStimulator.MinInterBurstInterval, Headstage64OpticalStimulator.MaxInterBurstInterval)] + [Range(Headstage64OpticalStimulator.MinInterBurstIntervalMilliseconds, Headstage64OpticalStimulator.MaxInterBurstIntervalMilliseconds)] [Precision(3, 1)] [Category(AcquisitionCategory)] public double InterBurstInterval { get => interBurstInterval.Value; - set => interBurstInterval.OnNext(Clamp(value, Headstage64OpticalStimulator.MinInterBurstInterval, Headstage64OpticalStimulator.MaxInterBurstInterval)); + set => interBurstInterval.OnNext(Clamp(value, Headstage64OpticalStimulator.MinInterBurstIntervalMilliseconds, Headstage64OpticalStimulator.MaxInterBurstIntervalMilliseconds)); } /// @@ -356,14 +356,14 @@ static class Headstage64OpticalStimulator public const double MaxChannelPercentage = 100.0; public const double ChannelPercentageStep = 12.5; - public const double MinPulseDuration = 0.001; - public const double MaxPulseDuration = 1000.0; + public const double MinPulseDurationMilliseconds = 0.001; + public const double MaxPulseDurationMilliseconds = 500.0; - public const double MinPulsePeriod = 0.01; - public const double MaxPulsePeriod = 10000.0; + public const double MinPulseFrequencyHz = 0.1; + public const double MaxPulseFrequencyHz = 10000.0; - public const double MinInterBurstInterval = 0.0; - public const double MaxInterBurstInterval = 10000.0; + public const double MinInterBurstIntervalMilliseconds = 0.0; + public const double MaxInterBurstIntervalMilliseconds = 10000.0; // managed registers public const uint ENABLE = 0; // Enable stimulus report stream