private void FG_SaveWaveformParameters_Click(object sender, RoutedEventArgs e) { memoryLocationSelected = (string)WaveformList.SelectedItem; // the data stored in WaveformList will always be strings WaveformFile current = fileDataMap[memoryLocationSelected]; // get the waveformFileStruct from the selected memory location double sampleRate = double.Parse(WaveformSampleRate.Text); if (sampleRate == 0) // don't let the user set the sample rate to 0 Hz. { WaveformSampleRate.Text = current.SampleRate.ToString(); // just have it set back to what is was before if they try to put in 0 } else { current.SampleRate = sampleRate; // else set the sample rate to what they put in } double scaleFactor = double.Parse(WaveformAmplitudeScaleFactor.Text); // get the user set scale factor by parsing the textbox text if (scaleFactor != current.ScaleFactor) // if the scalefactor parsed from the text box doesn't match the one in the WaveformFile { // then the user has changed the scale factor FG_ScaleAmplitude(current, scaleFactor); current.ScaleFactor = scaleFactor; // and then update the scale factor saved in the WaveformFile } current.IsUploaded = false; ListBoxItem item = WaveformList.ItemContainerGenerator.ContainerFromItem(WaveformList.SelectedItem) as ListBoxItem; item.Background = Brushes.Gold; // set the color back to gold to signal that the waveform is saved but is not uploaded. // the user will have to click upload again to see changes. }
// Draws the waveform contained in the given WaveformFile on the app's graph canvas private void FG_DrawWaveformGraph(WaveformFile wave) { int length; FGWaveformGraphDataLine.Points.Clear(); if (wave == null || wave.FileName == null) // if there's nothing actually saved in the current waveform, just draw the reference // line for 0V and then return { FGWaveformGraphZeroLine.Points.Clear(); FGWaveformGraphZeroLine.Points.Add(new DataPoint(0, 0)); FGWaveformGraphZeroLine.Points.Add(new DataPoint(FGWaveformPlot.ActualWidth, 0)); FGWaveformPlot.Model.InvalidatePlot(true); return; } if (wave.Voltages.Length > 1000) { length = 1000; } else { length = wave.Voltages.Length; } FGWaveformGraphZeroLine.Points.Clear(); FGWaveformGraphZeroLine.Points.Add(new DataPoint(0, 0)); FGWaveformGraphZeroLine.Points.Add(new DataPoint(length, 0)); for (int i = 0; i < length; i++) { FGWaveformGraphDataLine.Points.Add(new DataPoint(i, wave.Voltages[i])); } FGWaveformPlot.Model.InvalidatePlot(true); if (openingFile) // if we're in file opening mode { WaveformList.IsEnabled = true; // it's best just to wait until the graph is drawn. } }
private void FG_LoadWaveformCallback() { loading = false; // disable the loading flag currentWaveform.ChannelsLoadedTo.Add(functionGeneratorChannelInFocus); // add the current channel to the current waveform's set // of channels it's loaded to // signal the UI thread to update the state of the elements Application.Current.Dispatcher.Invoke(() => { memoryLocationSelected = (string)WaveformList.SelectedItem; // the data stored in WaveformList will always be strings WaveformFile temp = fileDataMap[memoryLocationSelected]; // get the waveformFileStruct from the selected memory location if (temp.IsUploaded) { LoadWaveformButton.IsEnabled = true; // we ungray out the load button once complete } if (temp.ChannelsLoadedTo.Contains(functionGeneratorChannelInFocus)) { PlayWaveform.IsEnabled = true; // The loading is complete, so we ungray out the button } if (temp.FileName != null) { UploadWaveform.IsEnabled = true; // re-enable the upload button } WaveformLoadMessage.Visibility = Visibility.Hidden; // hide the "waveform loading please wait" message }); }
private void FG_WaveformUploadedCallback() { // signal the UI thread to update the following UI elements Application.Current.Dispatcher.Invoke(() => { memoryLocationSelected = (string)WaveformList.SelectedItem; // the data stored in WaveformList will always be strings WaveformFile temp = fileDataMap[memoryLocationSelected]; // get the waveformFileStruct from the selected memory location if (temp.IsUploaded) // this stops the load button from enabling if the user switched waveforms during the upload process { LoadWaveformButton.IsEnabled = true; // only once the uploading is complete do } // we re-enable the button to load the waveform UploadWaveform.IsEnabled = true; // and the button to upload another one WaveformUploadMessage.Visibility = Visibility.Hidden; // hide the "uploading waveform please wait" message EditWaveformParameterCheckbox.IsEnabled = true; // enable the edit waveform parameter checkbox }); uploading = false; // turn off the uploading flag }
private void FG_EditWaveformParameterCheckbox_UnChecked(object sender, RoutedEventArgs e) { WaveformSampleRate.IsReadOnly = true; SaveWaveformParameters.IsEnabled = false; // disable the save waveform parameters button WaveformSampleRate.IsEnabled = false; // disable the input WaveformAmplitudeScaleFactor.IsEnabled = false; WaveformAmplitudeScaleFactor.IsReadOnly = true; // make the scale factor textbox read only memoryLocationSelected = (string)WaveformList.SelectedItem; // the data stored in WaveformList will always be strings WaveformFile temp = fileDataMap[memoryLocationSelected]; // get the waveformFileStruct from the selected memory location WaveformSampleRate.Text = temp.SampleRate.ToString(); WaveformAmplitudeScaleFactor.Text = temp.ScaleFactor.ToString(); if (ScalingErrorLabel.Visibility == Visibility.Visible) // if there was an error when the user unchecks the box { WaveformAmplitudeScaleFactor.Text = "1"; // just set everything back to 1 temp.ScaleFactor = 1; } ScalingErrorLabel.Visibility = Visibility.Hidden; // then hide the error }
private void FG_UploadWaveform_Click(object sender, RoutedEventArgs e) { uploading = true; // we can block other UI things from happening. i.e. switching to channel 2 and then back could in some // cases re-activate disabled buttons, and then allow the user to do strange things. WaveformUploadMessage.Visibility = Visibility.Visible; // show the "uploading waveform please wait" message // UPLOADING WILL BE DONE IN A SEPERATE THREAD WaveformFile data = currentWaveform; EditWaveformParameterCheckbox.IsEnabled = false; // disable the edit waveform parameter checkbox currentWaveform.ChannelsLoadedTo.Clear(); // when a new waveform is uploaded, it won't be loaded to any channel PlayWaveform.IsEnabled = false; // we know that this is false because we've cleared everything. This just saves us the trouble // of forcing an update. LoadWaveformButton.IsEnabled = false; // these are all non-channel specific, however should probably // stop the user from loading a waveform while the waveform is uploading // that could result in the user loading the previous waveform stored in that location, and then overwriting it with the // parameters of the new one, while keeping the waveform the same. This could result in a DC offset and H2 production. UploadWaveform.IsEnabled = false; // disable the upload waveform button. If the user spam clicks it, then we have a problem double[] voltages = data.Voltages; double SampleRate = data.SampleRate; string memLocation = string.Copy((string)WaveformList.SelectedItem); // on button click, upload the waveform to the function generator //ThreadPool.QueueUserWorkItem(ThreadProc); ThreadPool.QueueUserWorkItem(lamdaThing => { try { // again we can't use lowLevel and highLevel here because of the way that amplitude scaling works. // if all this parallelism causes CPU bottleneck I could store the scaled min and max as well, or just // have it multiply the original high/low levels by the scaling factor. // actually that's not a bad plan. fg.UploadWaveformData(voltages, SampleRate, (data.ScaleFactor * (data.HighLevel + data.LowLevel)) / 2, 0, memLocation); } finally { FG_WaveformUploadedCallback(); } }); ListBoxItem item = WaveformList.ItemContainerGenerator.ContainerFromItem(WaveformList.SelectedItem) as ListBoxItem; item.Background = Brushes.Green; // set the background so the user knows that the waveform has been uploaded currentWaveform.IsUploaded = true; // stuff uses this flag }
private void FG_ScaleAmplitude(WaveformFile fileToScale, double scaleFactor) { double[] originalVoltages = fileToScale.OriginalVoltages; // get the unscaled voltage array if (scaleFactor.Equals(1)) // for sanity, using the .Equals() method { fileToScale.Voltages = fileToScale.OriginalVoltages; // set the voltage reference in the WaveformFile to be the original // voltages if the user sets the scale factor to 1. FG_DrawWaveformGraph(); // draw the graph, can't forget that ScalingErrorLabel.Visibility = Visibility.Hidden; return; // then we're done. } // better to do this checking before we start processing rather than having the API throw an error after we've done all of the work. if (fileToScale.HighLevel * scaleFactor > maximumAllowedAmplitude / 2) { ScalingErrorLabel.Visibility = Visibility.Visible; // show the scaling error label return; } if (fileToScale.LowLevel * scaleFactor < -1 * (maximumAllowedAmplitude / 2)) { // DO UI SIGNALING THAT SOMETHING IS WRONG ScalingErrorLabel.Visibility = Visibility.Visible; // show the scaling error labelG return; } double[] scaledVoltages = fileToScale.ScaledVoltages; // get a reference to the scaled voltages in the WaveformFile ScalingErrorLabel.Visibility = Visibility.Hidden; if (scaledVoltages == null) // we only init the scaled voltages array when needed. { // dealing with the shifting around of references to counteract stuff is easier than trying to get the array inside that // WaveformFile to be passed by reference of reference. scaledVoltages = new double[originalVoltages.Length]; // it will (and must) always be // the same length as the original Voltage array fileToScale.ScaledVoltages = scaledVoltages; // reference weirdness, pay no mind } Parallel.For(0, originalVoltages.Length, (i, state) => { scaledVoltages[i] = originalVoltages[i] * scaleFactor; // using a parallel for loop, iterate through each of the voltages and // multiply it by the scale factor before placing it in the scaled voltage array. }); FG_RemoveDCOffset(scaledVoltages); // remove any DC offset this process created. fileToScale.Voltages = scaledVoltages; // change the reference to scaled voltages FG_DrawWaveformGraph(); // and then we redraw the graph so that the changes to the waveform show up immediately }
public MainWindow() { calibration = false; uploading = false; loading = false; openingFile = false; // set the uploading flag functionGeneratorChannelInFocus = 1; // start with channel 1 in focus fileDataMap = new Dictionary <string, WaveformFile>(); currentWaveform = new WaveformFile(); channelsPlaying = new HashSet <int>(); cancelToken = new CancellationTokenSource(); AutoResetEvent autoEvent = new AutoResetEvent(false); // Generate configuration file if one does not exist: (replace this in a future update to be more stable and make more sense) if (!File.Exists("config.cfg")) { File.WriteAllText("config.cfg", "#Replace USB with ENET to use ethernet connectivity, do not include spaces and do not remove this line!\nINTERFACE=USB"); // a bit sketchy looking, but it works! } string[] configInput = File.ReadAllLines("config.cfg"); string interfaceConfig; if (configInput.Length > 0) // if the file is just empty for some reason, use USB { interfaceConfig = configInput[1]; if (interfaceConfig.Equals("INTERFACE=ENET")) // don't crash on invalid config files (in the future alert the user), just use USB { interfaceConfig = "ENET"; } else { interfaceConfig = "USB"; } } else { interfaceConfig = "USB"; } if (interfaceConfig.Equals("ENET")) { Thread.Sleep(1000); // it seems that in some VISA implementations that repeatedly spam-opening the application will cause timeout errors ENET_Constructor(); // this is a temporary fix to that. Also don't spam-open the application, wait a second or two before reopening. Thread.Sleep(1000); } else { USB_Constructor(); } // at the end of both options, scope and fg are set and initialized for I/O operations // now we will read in that config file to see whether to use USB connection or over Ethernet, which means bringing up a seperate window idealNumScreenPoints = scope.GetNumPointsPerScreenCapture(); // for graphing purposes oscilloscopeNumHorizDiv = scope.GetNumHorizontalDivisions(); oscilloscopeNumVertDiv = scope.GetNumVerticalDivisions(); scope.Run(); // start the scope fg.SetAllOutputsOff(); // turn off all the outputs of the function generator scopeChannelInFocus = 1; // start with channel 1 in focus for the scope InitializeComponent(); LogoImage.Source = new BitmapImage(new Uri("logo.png", UriKind.Relative)); WaveformLoadMessage.Visibility = Visibility.Hidden; // hide the "waveform loading please wait" message WaveformUploadMessage.Visibility = Visibility.Hidden; // hide the "uploading waveform please wait" message // waveformGraph.Background = Brushes.Black; // set the canvas background to black WaveformSampleRate.IsReadOnly = true; // make the sample rate textbox read only by default WaveformAmplitudeScaleFactor.IsReadOnly = true; // make the scale factor textbox read only cancelFileOpen.Visibility = Visibility.Hidden; // hide the button for canceling file upload UploadWaveform.IsEnabled = false; // disable the button for uploading a waveform EditWaveformParameterCheckbox.IsEnabled = false; // disable the edit waveform parameter checkbox LoadWaveformButton.IsEnabled = false; // disable the button for loading a waveform into active memory WaveformSampleRate.IsEnabled = false; WaveformAmplitudeScaleFactor.IsEnabled = false; OffsetErrorLabel.Visibility = Visibility.Hidden; // hide the error label AmplitudeErrorLabel.Visibility = Visibility.Hidden; // hide the amplitude error label OffsetErrorLabel.Foreground = Brushes.Red; // make the error text red AmplitudeErrorLabel.Foreground = Brushes.Red; ScalingErrorLabel.Visibility = Visibility.Hidden; ScalingErrorLabel.Foreground = Brushes.Red; // make the error text red PlayWaveform.IsEnabled = false; // disable the button for playing a waveform SaveWaveformParameters.IsEnabled = false; // disable the save waveform parameters button WaveformSaveInstructionLabel.Visibility = Visibility.Hidden; // hide the instruction label for saving waveform voltageOffsetScaleConstant = scope.GetYAxisOffsetScaleConstant(); triggerPositionScaleConstant = scope.GetTriggerPositionScaleConstant(); // get the graph constants from the scope timeOffsetScaleConstant = scope.GetXAxisOffsetScaleConstant(); SavingWaveformCaptureLabel.Visibility = Visibility.Hidden; // hide the "saving waveform please wait" label showTriggerLine = false; // start by not showing the dashed line for the trigger double tempYScale = scope.GetYScale(); VoltageOffsetSlider.Maximum = voltageOffsetScaleConstant * tempYScale; // set the voltage slider max and min to whatever the max and min VoltageOffsetSlider.Minimum = -1 * VoltageOffsetSlider.Maximum; // are when we boot up the scope TriggerSlider.Maximum = triggerPositionScaleConstant * tempYScale; // trigger slider needs the same range as the offset slider TriggerSlider.Minimum = -1 * TriggerSlider.Maximum; numOscilloscopeChannels = scope.GetNumChannels(); channelsToDisable = new HashSet <int>(); channelsToDraw = new HashSet <int>(); // if a channel number is contained within this list, draw it channelsToDraw.Add(1); // we enable graphing channel 1 by default channelGraphs = new LineSeries[numOscilloscopeChannels]; // init the channelGraphs arrray voltageAxes = new LinearAxis[numOscilloscopeChannels]; // get an axis for each input channel of the scope FGWaveformPlot.Model = new PlotModel(); FGWaveformGraphDataLine = new LineSeries() { Color = OxyColor.FromRgb(34, 139, 34) }; FGWaveformGraphZeroLine = new LineSeries() { Color = OxyColor.FromRgb(0, 0, 0) }; // zero line is black FGWaveformPlot.Model.Series.Add(FGWaveformGraphZeroLine); FGWaveformPlot.Model.Series.Add(FGWaveformGraphDataLine); channelColors = new System.Drawing.Color[numOscilloscopeChannels]; mappedVoltageScales = scope.GetVoltageScalePresets(); mappedTimeScales = scope.GetTimeScalePresets(); for (int i = 0; i < numOscilloscopeChannels; i++) // this application does not respond to runtime changes in channel color // I have never heard of a scope that does that but just for reference. { channelColors[i] = scope.GetChannelColor(i + 1); // we save the channel colors so we don't need to access them again } foreach (string s in scope.GetVoltageScalePresetStrings()) // add all supported voltage scale presets to the combobox of voltage scales { VoltageScalePresetComboBox.Items.Add(s); } foreach (string s in scope.GetTimeScalePresetStrings()) // add all supported time scale presets to the combobox of time scales { TimeScalePresetComboBox.Items.Add(s); } MemoryDepthComboBox.Items.Add("AUTO"); // add the auto option first int[] tempAllowedMemDepths = scope.GetAllowedMemDepths(); foreach (int i in tempAllowedMemDepths) // add all supported memory depths to the combobox of memory depths { MemoryDepthComboBox.Items.Add(i); } // on startup set the displayed memory depth to be the current selected mem depth for the scope for (int i = 1; i <= numOscilloscopeChannels; i++) // add the everything for the different channels into the different stackpanels { scope.DisableChannel(i); // just make sure everything is set to off before we start Label channelLabel = new Label() { Content = i + "=" + scope.GetYScale(i) + "V", Visibility = Visibility.Hidden }; // get the scale labels for each channel, and then hide them CheckBox cb = new CheckBox() { Content = "Channel " + i, IsChecked = false }; RadioButton rb = new RadioButton() { Content = "Channel " + i + " ", IsChecked = false, FlowDirection = FlowDirection.RightToLeft, }; rb.Checked += (sender, args) => // on channel change, switch the displayed scale to the scale for that channel { int checkedChannel = (int)(sender as RadioButton).Tag; scopeChannelInFocus = checkedChannel; double offset = scope.GetYAxisOffset(scopeChannelInFocus); // and set the displayed offset to the offset of that channel VoltageOffsetSlider.Value = offset; previousYScaleFactor = scope.GetYScale(scopeChannelInFocus); int channelChangedVoltageScaleCheck = Array.IndexOf(mappedVoltageScales, previousYScaleFactor); if (channelChangedVoltageScaleCheck >= 0) { // use the mapping between names and actual scales to set the initial selected scale to the one // the scope is presently showing for channel 1. VoltageScalePresetComboBox.SelectedIndex = channelChangedVoltageScaleCheck; } }; rb.Unchecked += (sender, args) => { }; // currently no events need to be triggered on an un-check rb.Tag = i; OscilloscopeRadioButtonStackPanel.Children.Add(rb); cb.Checked += (sender, args) => { int checkedChannel = (int)(sender as CheckBox).Tag; channelsToDraw.Add(checkedChannel); // enable drawing of the checked channel scope.EnableChannel(checkedChannel); channelsToDisable.Remove(checkedChannel); (OscilloscopeChannelScaleStackPanel.Children[checkedChannel - 1] as Label).Visibility = Visibility.Visible; // when checked/enabled, show the scale for the channel // when a channel is enabled or disabled, we'll need to check if we need to adjust the possible memory depth values MemoryDepthComboBox.Items.Clear(); // first clear out the current iteams MemoryDepthComboBox.Items.Add("AUTO"); int[] updatedMemDepths = scope.GetAllowedMemDepths(); foreach (int memDepth in updatedMemDepths) { MemoryDepthComboBox.Items.Add(memDepth); // then add back in the new ones } OScope_CheckMemoryDepth(updatedMemDepths); }; cb.Unchecked += (sender, args) => { int uncheckedChannel = (int)(sender as CheckBox).Tag; channelsToDraw.Remove(uncheckedChannel); // disable drawing of the unchecked channel scope.DisableChannel(uncheckedChannel); WaveformPlot.Model.Series.Remove(channelGraphs[uncheckedChannel - 1]); // when the user disables the waveform, clear what remains WaveformPlot.Model.InvalidatePlot(true); channelsToDisable.Add(uncheckedChannel); (OscilloscopeChannelScaleStackPanel.Children[uncheckedChannel - 1] as Label).Visibility = Visibility.Hidden; // when unchecked/disabled, hide the scale for the channel // when a channel is enabled or disabled, we'll need to check if we need to adjust the possible memory depth values MemoryDepthComboBox.Items.Clear(); // first clear out the current iteams MemoryDepthComboBox.Items.Add("AUTO"); int[] updatedMemDepths = scope.GetAllowedMemDepths(); foreach (int memDepth in updatedMemDepths) { MemoryDepthComboBox.Items.Add(memDepth); // then add back in the new ones } OScope_CheckMemoryDepth(updatedMemDepths); }; cb.Tag = i; OscilloscopeChannelButtonStackPanel.Children.Add(cb); OscilloscopeChannelScaleStackPanel.Children.Add(channelLabel); // add the channel label to the stackpanel } (OscilloscopeChannelButtonStackPanel.Children[0] as CheckBox).IsChecked = true; // set channel 1 to be checked by default on startup (OscilloscopeRadioButtonStackPanel.Children[0] as RadioButton).IsChecked = true; // set channel 1 to be checked by default on startup (OscilloscopeChannelScaleStackPanel.Children[0] as Label).Visibility = Visibility.Visible; // show channel 1's scale as well. scope.EnableChannel(1); double currentYScale = scope.GetYScale(1); // we have tempYScale and currentYScale hmmmm previousYScaleFactor = currentYScale; int currentVoltageScaleCheck = Array.IndexOf(mappedVoltageScales, currentYScale); if (currentVoltageScaleCheck >= 0) { // use the mapping between names and actual scales to set the initial selected scale to the one // the scope is presently showing for channel 1. VoltageScalePresetComboBox.SelectedIndex = currentVoltageScaleCheck; } int currentTimeScaleCheck = Array.IndexOf(mappedTimeScales, scope.GetXAxisScale()); if (currentTimeScaleCheck >= 0) { // use the mapping between names and actual scales to set the initial selected scale to the one // the scope is presently showing for channel 1. TimeScalePresetComboBox.SelectedIndex = currentTimeScaleCheck; } OScope_CheckMemoryDepth(tempAllowedMemDepths); WaveformPlot.Model = new PlotModel { Title = "Oscilloscope Capture" }; foreach (string memoryLocation in fg.GetValidMemoryLocations()) { fileDataMap.Add(memoryLocation, new WaveformFile()); // put placeholder blank waveforms in the fileDataMap WaveformList.Items.Add(memoryLocation); // add each valid memory location to the list box } for (int i = 1; i <= fg.GetNumChannels(); i++) { RadioButton rb = new RadioButton() { Content = "Channel " + i, IsChecked = i == 0 }; rb.Checked += (sender, args) => { int checkedChannel = (int)(sender as RadioButton).Tag; functionGeneratorChannelInFocus = checkedChannel; if (channelsPlaying.Contains(checkedChannel)) { PlayWaveform.Content = "Restart Waveform"; } else { PlayWaveform.Content = "Play Waveform"; } FG_ChannelChanged(); }; rb.Unchecked += (sender, args) => { }; // currently no events need to be triggered on an un-check rb.Tag = i; FunctionGenChannelButtonStackPanel.Children.Add(rb); } (FunctionGenChannelButtonStackPanel.Children[0] as RadioButton).IsChecked = true; // set channel 1 to be checked by default if (maximumAllowedAmplitude <= 0 || maximumAllowedAmplitude > fg.GetMaxSupportedVoltage() - fg.GetMinSupportedVoltage()) // if the maximumAllowedAmplitude setting is below (or equals for sanity checking) 0, OR it's set to something too large then // we just set it to be the maximum allowed amplitude of the function generator itself. { maximumAllowedAmplitude = fg.GetMaxSupportedVoltage() - fg.GetMinSupportedVoltage(); } // these get forced into being symmetrical. That is okay in my opinion. I don't think there are any function generators with // non-symmetrical voltage limits Color backgroundColor = (Color)Background.GetValue(SolidColorBrush.ColorProperty); OxyColor hiddenColor = OxyColor.FromArgb(0, backgroundColor.R, backgroundColor.G, backgroundColor.B); // This axis is the voltage axis for the FG waveform display LinearAxis FGWaveformVoltageAxis = new LinearAxis { Minimum = -1 * maximumAllowedAmplitude / 2, // set the graph's max and min labels to be what the used function generator's max and min supported voltages actually are. Maximum = maximumAllowedAmplitude / 2, IsPanEnabled = false, IsZoomEnabled = false, Position = AxisPosition.Left, Title = "Voltage" }; // This axis is just here to make sure that the bottom axis has no ticks or number labels LinearAxis FGWaveformBottomAxis = new LinearAxis { IsPanEnabled = false, IsZoomEnabled = false, IsAxisVisible = false, TickStyle = TickStyle.None, TextColor = hiddenColor, // makes it look rectangular at startup while still having no visible numbers on the bottom. Position = AxisPosition.Bottom }; FGWaveformPlot.Model.Axes.Add(FGWaveformVoltageAxis); FGWaveformPlot.Model.Axes.Add(FGWaveformBottomAxis); // These Axes form the background grid of the oscope display. LinearAxis horizGridAxis1 = new LinearAxis // make horizontal grid lines { Position = AxisPosition.Left, // radiate out from center bar to the left MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot, IsPanEnabled = false, MajorStep = idealNumScreenPoints / oscilloscopeNumVertDiv, IsZoomEnabled = false, Minimum = -1 * (idealNumScreenPoints / 2), // point 0 is centered, so we have a range of -600 to 600 or similar Maximum = (idealNumScreenPoints / 2), AbsoluteMinimum = -1 * (idealNumScreenPoints / 2), AbsoluteMaximum = (idealNumScreenPoints / 2), MinimumPadding = 0, MaximumPadding = 0, TicklineColor = OxyColor.FromRgb(0, 0, 0), PositionAtZeroCrossing = true, TextColor = hiddenColor, }; LinearAxis horizGridAxis2 = new LinearAxis // make horizontal grid lines { Position = AxisPosition.Right, // radiate out of center bar to the right MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot, IsPanEnabled = false, MajorStep = idealNumScreenPoints / oscilloscopeNumVertDiv, IsZoomEnabled = false, Minimum = -1 * (idealNumScreenPoints / 2), // point 0 is centered, so we have a range of -600 to 600 or similar Maximum = (idealNumScreenPoints / 2), AbsoluteMinimum = -1 * (idealNumScreenPoints / 2), AbsoluteMaximum = (idealNumScreenPoints / 2), IntervalLength = idealNumScreenPoints / oscilloscopeNumHorizDiv, MinimumPadding = 0, MaximumPadding = 0, TicklineColor = OxyColor.FromRgb(0, 0, 0), PositionAtZeroCrossing = true, TextColor = hiddenColor, ExtraGridlines = new double[] { 0 } }; LinearAxis vertGridAxis1 = new LinearAxis { Position = AxisPosition.Top, MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot, MajorStep = Width / oscilloscopeNumHorizDiv, TicklineColor = OxyColor.FromRgb(0, 0, 0), PositionAtZeroCrossing = true, MinimumPadding = 0, MaximumPadding = 0, IsZoomEnabled = false, IsPanEnabled = false, TextColor = hiddenColor, ExtraGridlines = new double[] { 0 } }; LinearAxis vertGridAxis2 = new LinearAxis { Position = AxisPosition.Bottom, MajorGridlineStyle = LineStyle.Solid, MinorGridlineStyle = LineStyle.Dot, MajorStep = Width / oscilloscopeNumHorizDiv, TicklineColor = OxyColor.FromRgb(0, 0, 0), PositionAtZeroCrossing = true, MinimumPadding = 0, MaximumPadding = 0, IsZoomEnabled = false, IsPanEnabled = false, TextColor = hiddenColor, }; for (int i = 0; i < channelGraphs.Length; i++) { channelGraphs[i] = new LineSeries(); // then init the objects in it voltageAxes[i] = new LinearAxis { // just like on the actual scope display, we need to hide our axes Position = AxisPosition.Left, TicklineColor = hiddenColor, TextColor = hiddenColor, IsZoomEnabled = false, IsPanEnabled = false, Key = (i + 1).ToString(), // make the key the channel number (it's fine the text is clear) }; WaveformPlot.Model.Axes.Add(voltageAxes[i]); // a lot of array accesses here, possibly redo with temp variables? channelGraphs[i].YAxisKey = voltageAxes[i].Key; // then use that to associate the axis with the channel graph } WaveformPlot.Model.Axes.Add(horizGridAxis1); // add the left to right grid line axis to the display WaveformPlot.Model.Axes.Add(vertGridAxis1); WaveformPlot.Model.Axes.Add(horizGridAxis2); WaveformPlot.Model.Axes.Add(vertGridAxis2); triggerLine = new LineSeries(); // the orange trigger set line must be drawn on the graph double triggerLineScaled = (scope.GetTriggerLevel() * currentYScale) - (currentYScale / 2); WaveformPlot.Model.Series.Add(triggerLine); WaveformPlot.Model.InvalidatePlot(true); }
// a single click on one of the memory locations in the list private void FG_WaveformList_SelectionChanged(object sender, SelectionChangedEventArgs e) { OffsetErrorLabel.Visibility = Visibility.Hidden; // hide the offset error label if it's showing AmplitudeErrorLabel.Visibility = Visibility.Hidden; // hide the amplitude one too memoryLocationSelected = (string)WaveformList.SelectedItem; // the data stored in WaveformList will always be strings WaveformFile current = fileDataMap[memoryLocationSelected]; // get the waveformFileStruct from the selected memory location if (current.FileName == null) // if the waveform selected still has its default values, just let the user know and then return { WaveformName.Content = "No data in requested location"; WaveformMemoryLocation.Content = memoryLocationSelected; // show the memory location WaveformSampleRate.Text = "No data in requested location"; WaveformAmplitudeScaleFactor.Text = "No data in requested location"; // show the scaling factor FG_DrawWaveformGraph(current); // draw an empty graph, with just the 0V reference line UploadWaveform.IsEnabled = false; // try just disabling the button instead of hiding it EditWaveformParameterCheckbox.IsEnabled = false; // disable the edit waveform parameter checkbox LoadWaveformButton.IsEnabled = false; // if there's nothing in the memory location, we definitely can't load it PlayWaveform.IsEnabled = false; // we definitely can't play it if there's nothing there return; } else { if (!openingFile) { currentWaveform = current; } // UploadWaveform.IsEnabled = true; // try just enabling the button instead of showing it EditWaveformParameterCheckbox.IsEnabled = true; // enable the edit waveform parameter checkbox if (currentWaveform.IsUploaded) // if the waveform in the memory location is uploaded to the generator { if (!(uploading || loading)) // as long as no operation is in progress { LoadWaveformButton.IsEnabled = true; // we can enable the button to load the waveform into active memory UploadWaveform.IsEnabled = true; } if (currentWaveform.ChannelsLoadedTo.Contains(functionGeneratorChannelInFocus)) // if the waveform is loaded into the //selected channel's active memory. { if (!loading) // if we're not currently loading a waveform { PlayWaveform.IsEnabled = true; // enable the playwaveform button } } else { PlayWaveform.IsEnabled = false; // just in case } } else { if (!(uploading || loading)) { UploadWaveform.IsEnabled = true; } LoadWaveformButton.IsEnabled = false; // just in case PlayWaveform.IsEnabled = false; } // if the waveform selected has set values, display them for the user. WaveformName.Content = current.FileName; // show the displayed name WaveformMemoryLocation.Content = memoryLocationSelected; // show the memory location WaveformSampleRate.Text = current.SampleRate.ToString(); // show the sample rate WaveformAmplitudeScaleFactor.Text = current.ScaleFactor.ToString(); // show the scaling factor FG_DrawWaveformGraph(current); // draw the waveform on the app's graph } }
// a double click on one of the memory locations in the list private void FG_WaveformList_MouseDoubleClick(object sender, MouseButtonEventArgs e) { memoryLocationSelected = (string)WaveformList.SelectedItem; // the data stored in WaveformList will always be strings WaveformFile current = fileDataMap[memoryLocationSelected]; // get the waveformFileStruct from the selected memory location OffsetErrorLabel.Visibility = Visibility.Hidden; // hide the offset error label if it's showing AmplitudeErrorLabel.Visibility = Visibility.Hidden; // hide the amplitude error one too if (openingFile) { // if we are currently in opening file mode if (current.FileName == null) // if the memory location that was clicked on is empty, just save the data there. { fileDataMap[memoryLocationSelected] = currentWaveform; // save the data openingFile = false; // set opening file to false WaveformSaveInstructionLabel.Visibility = Visibility.Hidden; // hide the instruction label cancelFileOpen.Visibility = Visibility.Hidden; // hide the cancel button again. WaveformName.Content = currentWaveform.FileName; // show the displayed name WaveformMemoryLocation.Content = memoryLocationSelected; // show the memory location WaveformSampleRate.Text = currentWaveform.SampleRate.ToString(); // show the sample rate WaveformAmplitudeScaleFactor.Text = currentWaveform.ScaleFactor.ToString(); // show the scaling factor EditWaveformParameterCheckbox.IsEnabled = true; // enable the edit waveform parameter checkbox ListBoxItem item = WaveformList.ItemContainerGenerator.ContainerFromItem(WaveformList.SelectedItem) as ListBoxItem; item.Background = Brushes.Gold; // set the color to gold to signal that the waveform is saved but // not uploaded if (!(uploading || loading)) { UploadWaveform.IsEnabled = true; // enable the upload button } FG_DrawWaveformGraph(); // draw the graph return; // then just return. } else // if the memory location that was clicked on isn't empty, ask the user if they are okay with overwriting the data that // is already there { if (MessageBox.Show("Overwrite Waveform in " + memoryLocationSelected, "Overwrite Waveform?", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes) { fileDataMap[memoryLocationSelected] = currentWaveform; // they said it was okay to overwrite openingFile = false; // set opening file to false cancelFileOpen.Visibility = Visibility.Hidden; // hide the cancel button again. WaveformSaveInstructionLabel.Visibility = Visibility.Hidden; // hide the instruction label WaveformName.Content = currentWaveform.FileName; // show the displayed name WaveformMemoryLocation.Content = memoryLocationSelected; // show the memory location WaveformSampleRate.Text = currentWaveform.SampleRate.ToString(); // show the sample rate WaveformAmplitudeScaleFactor.Text = currentWaveform.ScaleFactor.ToString(); // show the scaling factor FG_DrawWaveformGraph(); // draw the graph if (!(uploading || loading)) { UploadWaveform.IsEnabled = true; // enable the upload button } ListBoxItem item = WaveformList.ItemContainerGenerator.ContainerFromItem(WaveformList.SelectedItem) as ListBoxItem; EditWaveformParameterCheckbox.IsEnabled = true; // enable the edit waveform parameter checkbox item.Background = Brushes.Gold; // set the color to gold to signal that the waveform is saved but // not uploaded return; // then just return. } else { // we don't set opening file to false because the user may have clicked on the wrong waveform or something // the empty else branch is left here as a placeholder. return; // just return } } } else // if the user is not opening a file, and just double clicks on the memory location, load the waveform there into current { current = fileDataMap[memoryLocationSelected]; // reload current value // if the waveform selected has set values, display them for the user. // (Then also like load the file into the graph but that's for later) currentWaveform = current; if (current.FileName == null) // if there isn't any data saved there, keep the original "no data" messages { WaveformName.Content = "No data in requested location"; WaveformMemoryLocation.Content = memoryLocationSelected; // show the memory location WaveformSampleRate.Text = "No data in requested location"; WaveformAmplitudeScaleFactor.Text = "No data in requested location"; // show the scaling factor EditWaveformParameterCheckbox.IsEnabled = false; // disable the edit waveform parameter checkbox FG_DrawWaveformGraph(); // draw an empty graph UploadWaveform.IsEnabled = false; // disable the upload button return; } // if it's not null, just show it's data WaveformName.Content = current.FileName; // show the displayed name WaveformMemoryLocation.Content = memoryLocationSelected; // show the memory location WaveformSampleRate.Text = current.SampleRate.ToString(); // show the sample rate WaveformAmplitudeScaleFactor.Text = currentWaveform.ScaleFactor.ToString(); // show the scaling factor EditWaveformParameterCheckbox.IsEnabled = true; // enable the edit waveform parameter checkbox if (!(uploading || loading)) { UploadWaveform.IsEnabled = true; // enable the upload button } FG_DrawWaveformGraph(); // draw the graph with the data saved in the memory location } }
private void FG_ParseFile(string filePath) { double sampleRate = 844; // default samplerate string fileName = System.IO.Path.GetFileName(filePath); IEnumerable <string> fileLines = File.ReadLines(filePath); if (fileLines.First().StartsWith("samplerate=")) { sampleRate = double.Parse(fileLines.First().Substring(11)); // parse the sampleRate fileLines = fileLines.Skip(1); // and then we have to skip the first one. } double[] voltageArray = fileLines.AsParallel().AsOrdered().Select(line => double.Parse(line)).ToArray(); // parse to an IEnumerable of doubles, using Parallel processing, but preserving the order if (voltageArray.AsParallel().Max() - voltageArray.AsParallel().Min() > maximumAllowedAmplitude) // amplitude is too high { // signal the UI thread to display the error/warning Application.Current.Dispatcher.Invoke(() => { AmplitudeErrorLabel.Visibility = Visibility.Visible; }); double absMax = Math.Max(Math.Abs(voltageArray.AsParallel().Max()), Math.Abs(voltageArray.AsParallel().Min())); // get the maximum absolute value from the waveform. // now we can't just use the ScaleWaveform() function for this because then it would set the original // values to ones that were beyond the max/min. double scalingFactor = absMax / (maximumAllowedAmplitude / 2); // get the scaling factor Parallel.For(0, voltageArray.Length, (i, state) => { voltageArray[i] = voltageArray[i] / scalingFactor; // then we multiply every value in the waveform by the scaling factor. }); // and then we just move on } int returnedValue = FG_RemoveDCOffset(voltageArray); // remove the DC offset from the waveform if there is one. if (returnedValue == -1) // then there was an error removing the DC offset { // signal the UI thread to show the DC offset removal error and clean up the opening file process Application.Current.Dispatcher.Invoke(() => { OffsetErrorLabel.Visibility = Visibility.Visible; cancelFileOpen.Visibility = Visibility.Hidden; // hide the cancel file open button WaveformSaveInstructionLabel.Visibility = Visibility.Hidden; // hide the save file instructions WaveformList.IsEnabled = true; // also reenable the list }); openingFile = false; // set opening file to false return; // then just give up without actually saving the waveform or anything. } // for safety reasons, we will only be putting in a maximum amplitude of 1Vpp into our bucket. // However, checking for this does make the code less flexible, so here are the lines of code that cause this. // 1Vpp checking code: // should we check this before or after DC offset removal. Before is faster, but after is better. So after. // okay so we need to automatically scale the amplitude down to fit the maximum allowed amplitude. WaveformFile fileStruct = new WaveformFile(sampleRate, voltageArray.Length, fileName, voltageArray, filePath); // create a new WaveformFileStruct to contain the info about this waveform file currentWaveform = fileStruct; // then set the waveform data for the currently selected memory address Application.Current.Dispatcher.Invoke(FG_DrawWaveformGraph); // attempt to signal the UI thread to update the graph as soon // as we are done doing the parsing }