diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Controls/ChannelMeasures.axaml b/Software/LogicAnalyzer/LogicAnalyzer/Controls/ChannelMeasures.axaml new file mode 100644 index 0000000..36e93fa --- /dev/null +++ b/Software/LogicAnalyzer/LogicAnalyzer/Controls/ChannelMeasures.axaml @@ -0,0 +1,30 @@ + + + Channel 0 + + Total positive pulses: + Average positive pulse duration: + Predominant positive pulse duration: + Total negative pulses: + Average negative pulse duration: + Predominant negative pulse duration: + Average frequency: + Predominant frequency: + + + 0 + 0ns + 0ns + 0 + 0ns + 0ns + 0Hz + 0hz + + + diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Controls/ChannelMeasures.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/Controls/ChannelMeasures.axaml.cs new file mode 100644 index 0000000..1dc1efb --- /dev/null +++ b/Software/LogicAnalyzer/LogicAnalyzer/Controls/ChannelMeasures.axaml.cs @@ -0,0 +1,122 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; +using LogicAnalyzer.Classes; +using LogicAnalyzer.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace LogicAnalyzer.Controls +{ + public partial class ChannelMeasures : UserControl + { + public static readonly StyledProperty ForegroundProperty = AvaloniaProperty.Register(nameof(Foreground)); + + public static readonly StyledProperty BackgroundProperty = AvaloniaProperty.Register(nameof(Background)); + + public new IBrush Foreground + { + get { return GetValue(ForegroundProperty); } + set { SetValue(ForegroundProperty, value); base.Foreground = value; UpdateTextColors(); InvalidateVisual(); } + } + + public new IBrush Background + { + get { return GetValue(BackgroundProperty); } + set { SetValue(BackgroundProperty, value); base.Background = value; UpdateTextColors(); InvalidateVisual(); } + } + + void UpdateTextColors() + { + lblFreq.Foreground= Foreground; + lblFreq.Background= Background; + lblNegPulses.Foreground= Foreground; + lblNegPulses.Background= Background; + lblNegPulsesDuration.Foreground= Foreground; + lblNegPulsesDuration.Background= Background; + lblPosPulses.Foreground= Foreground; + lblPosPulses.Background = Background; + lblPosPulsesDuration.Foreground = Foreground; + lblPosPulsesDuration.Background = Background; + } + + public ChannelMeasures() + { + InitializeComponent(); + } + + public void SetData(string channelName, byte[] channelSamples, int frequency) + { + int currentPulse = -1; + int currentCount = 0; + + List posLengths= new List(); + List negLengths = new List(); + + for (int buc = 0; buc < channelSamples.Length; buc++) + { + if (channelSamples[buc] != currentPulse) + { + if (currentPulse == 1) + posLengths.Add(currentCount); + else if (currentPulse == 0) + negLengths.Add(currentCount); + + currentPulse = channelSamples[buc]; + currentCount = 1; + } + else + currentCount++; + } + + if (currentPulse == 1) + posLengths.Add(currentCount); + else if (currentPulse == 0) + negLengths.Add(currentCount); + + var posGrouped = posLengths.GroupBy(p => p).OrderBy(g => g.Count()).ToArray(); + var posOrderedByCount = posGrouped.SelectMany(g => g.ToArray()).ToArray(); + + int fivePercent = (int)(posOrderedByCount.Length * 0.95); + + var finalPosSamples = posOrderedByCount.Skip(fivePercent).ToArray(); + + var negGrouped = negLengths.GroupBy(p => p).OrderBy(g => g.Count()).ToArray(); + var negOrderedByCount = negGrouped.SelectMany(g => g.ToArray()).ToArray(); + + fivePercent = (int)(negOrderedByCount.Length * 0.95); + + var finalNegSamples = negOrderedByCount.Skip(fivePercent).ToArray(); + + int minPulses = Math.Min(finalPosSamples.Length, finalNegSamples.Length); + + var matchedPos = finalPosSamples.Skip(finalPosSamples.Length - minPulses).ToArray(); + var matchedNeg = finalNegSamples.Skip(finalNegSamples.Length - minPulses).ToArray(); + + int totalSamples = matchedPos.Sum() + matchedNeg.Sum(); + double period = totalSamples * (1.0 / frequency); + + double predPosPeriod = finalPosSamples.Length == 0 ? 0 : (finalPosSamples.Average() * (1.0 / frequency)); + double predNegPeriod = finalNegSamples.Length == 0 ? 0 : (finalNegSamples.Average() * (1.0 / frequency)); + + double predFreq = period == 0 ? 0 : (1.0 / (predPosPeriod + predNegPeriod)); + + double avgPosPeriod = posLengths.Count == 0 ? 0 : (posLengths.Average() * (1.0 / frequency)); + double avgNegPeriod = negLengths.Count == 0 ? 0 : (negLengths.Average() * (1.0 / frequency)); + + double avgFreq = period == 0 ? 0 : (1.0 / (avgPosPeriod + avgNegPeriod)); + + lblPosPulses.Text = posLengths.Count.ToString(); + lblNegPulses.Text = negLengths.Count.ToString(); + lblPosPredPulsesDuration.Text = predPosPeriod.ToSmallTime(); + lblPosPulsesDuration.Text = avgPosPeriod.ToSmallTime(); + lblNegPredPulsesDuration.Text = predNegPeriod.ToSmallTime(); + lblNegPulsesDuration.Text = avgNegPeriod.ToSmallTime(); + lblPredFreq.Text = predFreq.ToLargeFrequency(); + lblFreq.Text = avgFreq.ToLargeFrequency(); + lblName.Text = $"Channel {channelName}"; + } + + } +} diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml index d8f389b..271d83a 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml +++ b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml @@ -11,9 +11,10 @@ - - - + + + + diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml.cs index ba6d78b..e1d8740 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml.cs @@ -52,6 +52,7 @@ namespace LogicAnalyzer.Controls public event EventHandler SamplesDeleted; public event EventHandler UserMarkerSelected; + public event EventHandler MeasureSamples; List regions = new List(); @@ -66,6 +67,13 @@ namespace LogicAnalyzer.Controls InitializeComponent(); mnuDeleteRegions.Click += MnuDeleteRegions_Click; mnuDeleteRegionsSamples.Click += MnuDeleteRegionsSamples_Click; + mnuMeasure.Click += MnuMeasure_Click; + } + + private void MnuMeasure_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + if (MeasureSamples != null) + MeasureSamples(this, new SamplesEventArgs { FirstSample = regionsToDelete[0].FirstSample, SampleCount = regionsToDelete[0].SampleCount }); } private void MnuDeleteRegionsSamples_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/MeasureDialog.axaml b/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/MeasureDialog.axaml new file mode 100644 index 0000000..13a891a --- /dev/null +++ b/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/MeasureDialog.axaml @@ -0,0 +1,31 @@ + + + + Total samples: + 0 + Total period: + 0ns + Sample period: + 0ns + + + + + + + + + + + + + diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/MeasureDialog.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/MeasureDialog.axaml.cs new file mode 100644 index 0000000..b8c93d3 --- /dev/null +++ b/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/MeasureDialog.axaml.cs @@ -0,0 +1,42 @@ +using Avalonia.Controls; +using LogicAnalyzer.Controls; +using LogicAnalyzer.Extensions; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; + +namespace LogicAnalyzer.Dialogs +{ + public partial class MeasureDialog : Window + { + public MeasureDialog() + { + InitializeComponent(); + btnAccept.Click += BtnAccept_Click; + } + + private void BtnAccept_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + this.Close(); + } + + public void SetData(string[] ChannelNames, IEnumerable Samples, int SamplingFrequency) + { + int samples = Samples.First().Length; + lblSamples.Text = samples.ToString(); + double period = (1.0 / (double)SamplingFrequency) * samples; + lblPeriod.Text = period.ToSmallTime(); + + period = 1.0 / (double)SamplingFrequency; + lblSamplePeriod.Text = period.ToSmallTime(); + + for (int buc = 0; buc < ChannelNames.Length; buc++) + { + ChannelMeasures cm = new ChannelMeasures(); + cm.SetData(ChannelNames[buc], Samples.Skip(buc).First(), SamplingFrequency); + pnlControls.Children.Add(cm); + } + } + } +} diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Extensions/DoubleExtensions.cs b/Software/LogicAnalyzer/LogicAnalyzer/Extensions/DoubleExtensions.cs new file mode 100644 index 0000000..cb1627d --- /dev/null +++ b/Software/LogicAnalyzer/LogicAnalyzer/Extensions/DoubleExtensions.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LogicAnalyzer.Extensions +{ + public static class DoubleExtensions + { + public static string ToSmallTime(this double Time) + { + if (Time < 0.000001) + return $"{Math.Round(Time * 1000000000, 2)}ns"; + else if (Time < 0.001) + return $"{Math.Round(Time * 1000000, 2)}us"; + else if (Time < 1) + return $"{Math.Round(Time * 1000, 2)}ms"; + else + return $"{Math.Round(Time, 2)}s"; + } + + public static string ToLargeFrequency(this double Frequency) + { + if (Frequency > 999999) + return $"{Math.Round(Frequency / 1000000, 2)}Mhz"; + else if (Frequency > 999) + return $"{Math.Round(Frequency / 1000, 2)}Khz"; + else + return $"{Math.Round(Frequency, 2)}Hz"; + } + } +} diff --git a/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs index 4065091..2b26f02 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs @@ -42,6 +42,7 @@ namespace LogicAnalyzer sampleMarker.RegionDeleted += sampleMarker_RegionDeleted; sampleMarker.UserMarkerSelected += sampleMarker_UserMarkerSelected; sampleMarker.SamplesDeleted += SampleMarker_SamplesDeleted; + sampleMarker.MeasureSamples += SampleMarker_MeasureSamples; tkInScreen.PropertyChanged += tkInScreen_ValueChanged; scrSamplePos.Scroll += scrSamplePos_ValueChanged; mnuOpen.Click += mnuOpen_Click; @@ -53,6 +54,25 @@ namespace LogicAnalyzer RefreshPorts(); } + private async void SampleMarker_MeasureSamples(object? sender, SamplesEventArgs e) + { + List samples = new List(); + + for (int buc = 0; buc < sampleViewer.ChannelCount; buc++) + samples.Add(ExtractSamples(buc, sampleViewer.Samples, e.FirstSample, e.SampleCount)); + + var names = channelViewer.ChannelsText.ToArray(); + + for (int buc = 0; buc < names.Length; buc++) + if (string.IsNullOrWhiteSpace(names[buc])) + names[buc] = (buc + 1).ToString(); + + MeasureDialog dlg = new MeasureDialog(); + dlg.SetData(names, samples, settings.Frequency); + await dlg.ShowDialog(this); + + } + private async void MnuNetSettings_Click(object? sender, RoutedEventArgs e) { var dlg = new NetworkSettingsDialog(); @@ -370,6 +390,12 @@ namespace LogicAnalyzer channel.Samples = samples.Select(s => (s & mask) != 0 ? (byte)1 : (byte)0).ToArray(); } + private byte[] ExtractSamples(int channel, uint[] samples, int firstSample, int count) + { + int mask = 1 << channel; + return samples.Skip(firstSample).Take(count).Select(s => (s & mask) != 0 ? (byte)1 : (byte)0).ToArray(); + } + private async void btnOpenClose_Click(object? sender, EventArgs e) { if (driver == null)