From 3765865c508f1bb3e866fa56c63e324fa89498af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Gimenez?= Date: Sun, 12 May 2024 11:39:45 +0200 Subject: [PATCH] Updated analyzers --- Firmware/LogicAnalyzer/CMakeLists.txt | 8 +- .../LogicAnalyzer_Build_Settings.h | 4 +- Software/LogicAnalyzer/LogicAnalyzer.sln | 8 +- .../Controls/SampleMarker.axaml.cs | 157 ++++-- .../ProtocolAnalyzerSettingsDialog.axaml.cs | 94 +++- .../LogicAnalyzer/MainWindow.axaml.cs | 18 + .../LogicAnalyzer/Protocols/DataClasses.cs | 6 + .../Protocols/ProtocolAnalyzerBase.cs | 11 +- .../Protocols/SimpleSegmentRenderer.cs | 11 +- .../ParallelAnalyzer.cs | 475 ++++++++++++++++++ .../ParallelProtocolAnalyzer.csproj | 13 + 11 files changed, 729 insertions(+), 76 deletions(-) create mode 100644 Software/LogicAnalyzer/ParallelProtocolAnalyzer/ParallelAnalyzer.cs create mode 100644 Software/LogicAnalyzer/ParallelProtocolAnalyzer/ParallelProtocolAnalyzer.csproj diff --git a/Firmware/LogicAnalyzer/CMakeLists.txt b/Firmware/LogicAnalyzer/CMakeLists.txt index caa1a0a..08fea21 100644 --- a/Firmware/LogicAnalyzer/CMakeLists.txt +++ b/Firmware/LogicAnalyzer/CMakeLists.txt @@ -5,12 +5,6 @@ cmake_minimum_required(VERSION 3.13) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) -# set(PICO_BOARD pico_w) - -# Initialise pico_sdk from installed location -# (note this can come from environment, CMake cache etc) -set(PICO_SDK_PATH "F:/PicoSDK/Pico/pico-sdk") - # Pull in Raspberry Pi Pico SDK (must be before project) include(pico_sdk_import.cmake) @@ -42,7 +36,7 @@ pico_enable_stdio_usb(LogicAnalyzer 1) # Regular pico: empty # Pico W without WiFi support: pico_cyw43_arch_none # Pico W with WiFi support: pico_cyw43_arch_lwip_poll -# set (CYW_LIB pico_cyw43_arch_lwip_poll) +set (CYW_LIB pico_cyw43_arch_lwip_poll) # set (CYW_LIB pico_cyw43_arch_none) # Add any user requested libraries diff --git a/Firmware/LogicAnalyzer/LogicAnalyzer_Build_Settings.h b/Firmware/LogicAnalyzer/LogicAnalyzer_Build_Settings.h index 18b410f..5287522 100644 --- a/Firmware/LogicAnalyzer/LogicAnalyzer_Build_Settings.h +++ b/Firmware/LogicAnalyzer/LogicAnalyzer_Build_Settings.h @@ -4,9 +4,9 @@ #define FIRMWARE_VERSION "V5_1" //Select the board type to build the firmware for - #define BUILD_PICO + //#define BUILD_PICO //#define BUILD_PICO_W - //#define BUILD_PICO_W_WIFI + #define BUILD_PICO_W_WIFI //#define BUILD_ZERO #endif diff --git a/Software/LogicAnalyzer/LogicAnalyzer.sln b/Software/LogicAnalyzer/LogicAnalyzer.sln index eeda00f..43a4f4d 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer.sln +++ b/Software/LogicAnalyzer/LogicAnalyzer.sln @@ -15,7 +15,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SerialProtocolAnalyzer", "S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "I2CProtocolAnalyzer", "I2CProtocolAnalyzer\I2CProtocolAnalyzer.csproj", "{74E41F50-E208-4690-84C6-51012C2F26E2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SignalDescriptionLanguage", "SignalDescriptionLanguage\SignalDescriptionLanguage.csproj", "{BD4731B1-B7CE-49C9-9982-77FA9B95C19F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SignalDescriptionLanguage", "SignalDescriptionLanguage\SignalDescriptionLanguage.csproj", "{BD4731B1-B7CE-49C9-9982-77FA9B95C19F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParallelProtocolAnalyzer", "ParallelProtocolAnalyzer\ParallelProtocolAnalyzer.csproj", "{952FD20C-7C12-407B-A264-BA5F429BDD3C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -51,6 +53,10 @@ Global {BD4731B1-B7CE-49C9-9982-77FA9B95C19F}.Debug|Any CPU.Build.0 = Debug|Any CPU {BD4731B1-B7CE-49C9-9982-77FA9B95C19F}.Release|Any CPU.ActiveCfg = Release|Any CPU {BD4731B1-B7CE-49C9-9982-77FA9B95C19F}.Release|Any CPU.Build.0 = Release|Any CPU + {952FD20C-7C12-407B-A264-BA5F429BDD3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {952FD20C-7C12-407B-A264-BA5F429BDD3C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {952FD20C-7C12-407B-A264-BA5F429BDD3C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {952FD20C-7C12-407B-A264-BA5F429BDD3C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml.cs index eeb29f6..0bcdaa8 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleMarker.axaml.cs @@ -6,6 +6,7 @@ using Avalonia.Media; using Avalonia.Threading; using LogicAnalyzer.Classes; using LogicAnalyzer.Dialogs; +using LogicAnalyzer.Protocols; using System; using System.Collections.Generic; using System.Globalization; @@ -62,6 +63,7 @@ namespace LogicAnalyzer.Controls List regions = new List(); + List analysisData = new List(); SelectedSamples? selectedSamples = null; SampleRegion? selectedRegion = null; @@ -69,6 +71,7 @@ namespace LogicAnalyzer.Controls int? userMarker = null; int mnuSample = 0; bool samplesCopied = false; + bool updating = false; public SampleMarker() { @@ -163,6 +166,37 @@ namespace LogicAnalyzer.Controls } } + public void BeginUpdate() + { + updating = true; + } + + public void EndUpdate() + { + updating = false; + InvalidateVisual(); + } + public void AddAnalyzedChannel(ProtocolAnalyzedChannel Data) + { + analysisData.Add(Data); + } + public void AddAnalyzedChannels(IEnumerable Data) + { + analysisData.AddRange(Data); + } + public bool RemoveAnalyzedChannel(ProtocolAnalyzedChannel Data) + { + return analysisData.Remove(Data); + } + + public void ClearAnalyzedChannels() + { + foreach (var data in analysisData) + data.Dispose(); + + analysisData.Clear(); + } + public void AddRegion(SampleRegion Region) { regions.Add(Region); @@ -202,70 +236,97 @@ namespace LogicAnalyzer.Controls public override void Render(DrawingContext context) { - context.FillRectangle(Background, new Rect(0, 0, Bounds.Width, Bounds.Height)); - if (VisibleSamples == 0) - return; + var bounds = new Rect(0, 0, Bounds.Width, Bounds.Height); - double sampleWidth = this.Bounds.Width / VisibleSamples; - double halfWidth = sampleWidth / 2; - double halfHeight = this.Bounds.Height / 2f; - - - if (selectedSamples != null) + using (context.PushClip(bounds)) { - int first = selectedSamples.Start; - double start = (first - FirstSample) * sampleWidth; - double end = sampleWidth * selectedSamples.SampleCount; - context.FillRectangle(GraphicObjectsCache.GetBrush(Color.FromArgb(128, 255, 255, 255)), new Rect(start, 0, end, this.Bounds.Height)); - } + if(updating) + return; - if (regions.Count > 0) - { - foreach (var region in regions) + context.FillRectangle(Background, bounds); + + if (VisibleSamples == 0) + return; + + double sampleWidth = this.Bounds.Width / VisibleSamples; + double halfWidth = sampleWidth / 2; + double halfHeight = this.Bounds.Height / 2f; + + + if (selectedSamples != null) { - int first = region.FirstSample; + int first = selectedSamples.Start; double start = (first - FirstSample) * sampleWidth; - double end = sampleWidth * region.SampleCount; - context.FillRectangle(GraphicObjectsCache.GetBrush(region.RegionColor), new Rect(start, 0, end, this.Bounds.Height)); - FormattedText text = new FormattedText(region.RegionName, Typeface.Default, 12, TextAlignment.Left, TextWrapping.NoWrap, Size.Infinity); - context.DrawText(GraphicObjectsCache.GetBrush(Colors.White), new Point(start + (end / 2) - (text.Bounds.Width / 2), 5), text); + double end = sampleWidth * selectedSamples.SampleCount; + context.FillRectangle(GraphicObjectsCache.GetBrush(Color.FromArgb(128, 255, 255, 255)), new Rect(start, 0, end, this.Bounds.Height)); + } - } - int increment; - - if (VisibleSamples < 101) - increment = 1; - else if (VisibleSamples < 501) - increment = 5; - else if (VisibleSamples < 1001) - increment = 10; - else - increment = 20; - - //Draw ticks - for (int buc = 0; buc < VisibleSamples; buc += increment) - { - double x = buc * sampleWidth; - double y1 = halfHeight * 1.5f; - double y2 = this.Bounds.Height; - - context.DrawLine(GraphicObjectsCache.GetPen(Foreground, 1), new Point(x, y1), new Point(x, y2)); - - if (increment == 1) + if (regions.Count > 0) { - x = buc * sampleWidth +halfWidth; - y1 = halfHeight * 1.75f; + foreach (var region in regions) + { + int first = region.FirstSample; + double start = (first - FirstSample) * sampleWidth; + double end = sampleWidth * region.SampleCount; + context.FillRectangle(GraphicObjectsCache.GetBrush(region.RegionColor), new Rect(start, 0, end, this.Bounds.Height)); + FormattedText text = new FormattedText(region.RegionName, Typeface.Default, 12, TextAlignment.Left, TextWrapping.NoWrap, Size.Infinity); + context.DrawText(GraphicObjectsCache.GetBrush(Colors.White), new Point(start + (end / 2) - (text.Bounds.Width / 2), 5), text); + } + } + + int increment; + + if (VisibleSamples < 101) + increment = 1; + else if (VisibleSamples < 501) + increment = 5; + else if (VisibleSamples < 1001) + increment = 10; + else + increment = 20; + + //Draw ticks + for (int buc = 0; buc < VisibleSamples; buc += increment) + { + double x = buc * sampleWidth; + double y1 = halfHeight * 1.5f; + double y2 = this.Bounds.Height; context.DrawLine(GraphicObjectsCache.GetPen(Foreground, 1), new Point(x, y1), new Point(x, y2)); + if (increment == 1) + { + x = buc * sampleWidth + halfWidth; + y1 = halfHeight * 1.75f; + + context.DrawLine(GraphicObjectsCache.GetPen(Foreground, 1), new Point(x, y1), new Point(x, y2)); + + } + } - } + if (analysisData.Count > 0) + { + foreach (var chan in analysisData) + { + foreach (var evt in chan.Segments) + { + double x1 = (evt.FirstSample - FirstSample) * sampleWidth; + double x2 = (evt.LastSample + 1 - FirstSample) * sampleWidth; + double y1 = 0; + double y2 = this.Bounds.Height; - base.Render(context); + Rect r = new Rect(new Point(x1, y1), new Point(x2, y2)); + context.FillRectangle(GraphicObjectsCache.GetBrush(chan.BackColor), r); + } + } + } + + base.Render(context); + } } protected override void OnPointerMoved(PointerEventArgs e) diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/ProtocolAnalyzerSettingsDialog.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/ProtocolAnalyzerSettingsDialog.axaml.cs index 90b67e0..4fcaf47 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/ProtocolAnalyzerSettingsDialog.axaml.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/ProtocolAnalyzerSettingsDialog.axaml.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.Media; +using LogicAnalyzer.Classes; using LogicAnalyzer.Extensions; using LogicAnalyzer.Protocols; using System; @@ -19,6 +20,9 @@ namespace LogicAnalyzer.Dialogs ProtocolAnalyzerBase analyzer; public ProtocolAnalyzerBase Analyzer { get { return analyzer; } set { analyzer = value; LoadControls(); } } + AnalysisSettings? initialSettings; + public AnalysisSettings? InitialSettings { get { return initialSettings; } set { initialSettings = value; LoadControls(); } } + Channel[] channels; public Channel[] Channels { get { return channels; } set { channels = value; LoadControls(); } } @@ -60,10 +64,18 @@ namespace LogicAnalyzer.Dialogs { var signal = signals[buc]; - pnlControls.Children.Add(new TextBlock{ IsVisible = true, Name = $"Label_Signal{buc}", Text = $"Channel for signal { signal.SignalName }:" }); + pnlControls.Children.Add(new TextBlock{ IsVisible = true, Name = $"Label_Signal{buc}", Text = signal.IsBus ? $"First channel of bus {signal.SignalName}" : $"Channel for signal { signal.SignalName }:" }); var list = new ComboBox { IsVisible = true, Name = $"List_Signal{buc}", Items = channelsSource.ToArray(), HorizontalAlignment=Avalonia.Layout.HorizontalAlignment.Stretch, Margin= new Thickness(0,10,20,0) }; + if (initialSettings != null) + { + var chan = initialSettings.Channels?.FirstOrDefault(c => c.SignalName == signal.SignalName && c.BusIndex == 0); + + if(chan != null) + list.SelectedIndex = chan.ChannelIndex + 1; + } + pnlControls.Children.Add(list); list.SelectionChanged += SignalChannel_SelectedIndexChanged; @@ -87,13 +99,32 @@ namespace LogicAnalyzer.Dialogs case ProtocolAnalyzerSetting.ProtocolAnalyzerSettingType.Boolean: var ck = new CheckBox { IsVisible = true, Name = $"Check_Index{buc}", Content = set.CheckCaption, Margin = new Thickness(0, 10, 20, 0) }; + + if (initialSettings != null) + { + var setV = initialSettings.Settings?.FirstOrDefault(s => s.SettingIndex == buc); + + if(setV != null) + ck.IsChecked = (bool)(setV.Value ?? false); + } + pnlControls.Children.Add(ck); + ck.Checked += BooleanSetting_CheckedChanged; break; case ProtocolAnalyzerSetting.ProtocolAnalyzerSettingType.Integer: - var nud = new NumericUpDown { IsVisible = true, Name = $"Numeric_Index{buc}", Minimum = set.IntegerMinimumValue, Maximum = set.IntegerMaximumValue, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch, Margin = new Thickness(0, 10, 20, 0) }; + var nud = new NumericUpDown { IsVisible = true, Name = $"Numeric_Index{buc}", Minimum = set.IntegerMinimumValue, Maximum = set.IntegerMaximumValue, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch, Margin = new Thickness(0, 10, 20, 0), Value = set.IntegerMinimumValue }; + + if (initialSettings != null) + { + var setV = initialSettings.Settings?.FirstOrDefault(s => s.SettingIndex == buc); + + if (setV != null) + nud.Value = (int)(setV.Value ?? 0); + } + pnlControls.Children.Add(nud); nud.ValueChanged += IntegerSetting_ValueChanged; break; @@ -101,6 +132,15 @@ namespace LogicAnalyzer.Dialogs case ProtocolAnalyzerSetting.ProtocolAnalyzerSettingType.List: var list = new ComboBox { IsVisible = true, Name = $"List_Index{buc}", Items = set.ListValues, HorizontalAlignment = Avalonia.Layout.HorizontalAlignment.Stretch, Margin = new Thickness(0, 10, 20, 0) }; + + if (initialSettings != null) + { + var setV = initialSettings.Settings?.FirstOrDefault(s => s.SettingIndex == buc); + + if (setV != null) + list.SelectedIndex = Array.IndexOf(set.ListValues, setV.Value); + } + pnlControls.Children.Add(list); list.SelectionChanged += ListSetting_SelectedIndexChanged; break; @@ -112,6 +152,7 @@ namespace LogicAnalyzer.Dialogs } } + ValidateSettings(); } private void ListSetting_SelectedIndexChanged(object? sender, RoutedEventArgs e) @@ -137,9 +178,16 @@ namespace LogicAnalyzer.Dialogs void ValidateSettings() { var st = ComposeSettings(); - var ch = ComposeChannels(); - if (st == null || ch == null) + if (st == null) + { + btnAccept.IsEnabled = false; + return; + } + + var ch = ComposeChannels(st); + + if (ch == null) { btnAccept.IsEnabled = false; return; @@ -221,7 +269,7 @@ namespace LogicAnalyzer.Dialogs return settingsValues.ToArray(); } - ProtocolAnalyzerSelectedChannel[]? ComposeChannels() + ProtocolAnalyzerSelectedChannel[]? ComposeChannels(ProtocolAnalyzerSettingValue[] values) { if (analyzer == null || channels == null) return null; @@ -240,11 +288,37 @@ namespace LogicAnalyzer.Dialogs if (list.SelectedIndex == -1) continue; - selectedChannels.Add(new ProtocolAnalyzerSelectedChannel + var size = signal.IsBus ? analyzer.GetBusWidth(signal, values) : 1; + + if (size == 0) + continue; + + int idx = list.SelectedIndex - 1; + + if (idx + size > channels.Length) + return null; + + if (size == 1) { - ChannelIndex = list.SelectedIndex - 1, - SignalName = signal.SignalName - }); + + selectedChannels.Add(new ProtocolAnalyzerSelectedChannel + { + ChannelIndex = idx, + SignalName = signal.SignalName + }); + } + else + { + for (int bucS = 0; bucS < size; bucS++) + { + selectedChannels.Add(new ProtocolAnalyzerSelectedChannel + { + ChannelIndex = idx + bucS, + SignalName = signal.SignalName, + BusIndex = bucS + }); + } + } } return selectedChannels.ToArray(); @@ -258,7 +332,7 @@ namespace LogicAnalyzer.Dialogs private void btnAccept_Click(object? sender, RoutedEventArgs e) { SelectedSettings = ComposeSettings(); - SelectedChannels = ComposeChannels(); + SelectedChannels = ComposeChannels(SelectedSettings); this.Close(true); } diff --git a/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs index 87614e8..3cb94d6 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs @@ -366,6 +366,7 @@ namespace LogicAnalyzer sampleMarker.VisibleSamples = sampleViewer.SamplesInScreen; sampleMarker.FirstSample = sampleViewer.FirstSample; sampleMarker.ClearRegions(); + sampleMarker.ClearAnalyzedChannels(); scrSamplePos.Maximum = samples.Length - 1; scrSamplePos.Value = sampleViewer.FirstSample; @@ -592,6 +593,7 @@ namespace LogicAnalyzer sampleMarker.VisibleSamples = sampleViewer.SamplesInScreen; sampleMarker.FirstSample = sampleViewer.FirstSample; sampleMarker.ClearRegions(); + sampleMarker.ClearAnalyzedChannels(); if (finalRegions.Count > 0) sampleMarker.AddRegions(finalRegions); @@ -766,6 +768,7 @@ namespace LogicAnalyzer sampleMarker.VisibleSamples = sampleViewer.SamplesInScreen; sampleMarker.FirstSample = sampleViewer.FirstSample; sampleMarker.ClearRegions(); + sampleMarker.ClearAnalyzedChannels(); btnCapture.IsEnabled = true; btnRepeat.IsEnabled = true; @@ -852,6 +855,9 @@ namespace LogicAnalyzer sampleViewer.BeginUpdate(); sampleViewer.AddAnalyzedChannels(analysisResult); sampleViewer.EndUpdate(); + sampleMarker.BeginUpdate(); + sampleMarker.AddAnalyzedChannels(analysisResult); + sampleMarker.EndUpdate(); } } @@ -860,6 +866,9 @@ namespace LogicAnalyzer sampleViewer.BeginUpdate(); sampleViewer.ClearAnalyzedChannels(); sampleViewer.EndUpdate(); + sampleMarker.BeginUpdate(); + sampleMarker.ClearAnalyzedChannels(); + sampleMarker.EndUpdate(); } private async void ProtocolAnalyzer_Click(object? sender, RoutedEventArgs e) @@ -873,6 +882,10 @@ namespace LogicAnalyzer var dlg = new ProtocolAnalyzerSettingsDialog(); { + + if (analysisSettings != null && analysisSettings.Analyzer == analyzer) + dlg.InitialSettings = analysisSettings; + dlg.Analyzer = analyzer; dlg.Channels = channelViewer.Channels.Select(c => { @@ -907,6 +920,10 @@ namespace LogicAnalyzer sampleViewer.BeginUpdate(); sampleViewer.AddAnalyzedChannels(analysisResult); sampleViewer.EndUpdate(); + + sampleMarker.BeginUpdate(); + sampleMarker.AddAnalyzedChannels(analysisResult); + sampleMarker.EndUpdate(); } analysisSettings = new AnalysisSettings { Analyzer = analyzer, Channels = channels, Settings = dlg.SelectedSettings }; @@ -1296,6 +1313,7 @@ namespace LogicAnalyzer sampleMarker.VisibleSamples = sampleViewer.SamplesInScreen; sampleMarker.FirstSample = sampleViewer.FirstSample; sampleMarker.ClearRegions(); + sampleMarker.ClearAnalyzedChannels(); if (ex.SelectedRegions != null) sampleMarker.AddRegions(ex.SelectedRegions); diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Protocols/DataClasses.cs b/Software/LogicAnalyzer/LogicAnalyzer/Protocols/DataClasses.cs index 4aff5a2..bc99de8 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Protocols/DataClasses.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/Protocols/DataClasses.cs @@ -73,6 +73,10 @@ namespace LogicAnalyzer.Protocols public string SignalName { get; set; } //If true the signal must be provided, else the signal is optional public bool Required { get; set; } + /// + /// If true the signal is a bus and the size will be requested to the protocol analyzer + /// + public bool IsBus { get; set; } } /// @@ -82,6 +86,8 @@ namespace LogicAnalyzer.Protocols { //Signal name for which the channel was selected public string SignalName { get; set; } + //Index (for buses) + public int BusIndex { get; set; } //Channel index in the channel viewer public int ChannelIndex { get; set; } //List of samples diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Protocols/ProtocolAnalyzerBase.cs b/Software/LogicAnalyzer/LogicAnalyzer/Protocols/ProtocolAnalyzerBase.cs index e4360d0..2b5a76a 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Protocols/ProtocolAnalyzerBase.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/Protocols/ProtocolAnalyzerBase.cs @@ -35,6 +35,15 @@ namespace LogicAnalyzer.Protocols /// An array of analyzed channels public abstract ProtocolAnalyzedChannel[] Analyze(int SamplingRate, int TriggerSample, ProtocolAnalyzerSettingValue[] SelectedSettings, ProtocolAnalyzerSelectedChannel[] SelectedChannels); - + /// + /// Informs the analyzer of the size of a bus. By default returns 0 (bus not used). + /// + /// The bus to get the size for + /// The settings the user selected + /// The size of the bus + public virtual int GetBusWidth(ProtocolAnalyzerSignal Signal, ProtocolAnalyzerSettingValue[] SelectedSettings) + { + return 0; + } } } diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Protocols/SimpleSegmentRenderer.cs b/Software/LogicAnalyzer/LogicAnalyzer/Protocols/SimpleSegmentRenderer.cs index d3948d2..86049dc 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Protocols/SimpleSegmentRenderer.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/Protocols/SimpleSegmentRenderer.cs @@ -18,7 +18,7 @@ namespace LogicAnalyzer.Protocols public override void RenderSegment(ProtocolAnalyzedChannel Channel, ProtocolAnalyzerDataSegment Segment, DrawingContext G, Rect RenderArea) { - FormattedText text = new FormattedText(Segment.Value, segmentFont, 12, TextAlignment.Center, TextWrapping.NoWrap, Size.Infinity); + FormattedText text = new FormattedText(Segment.Value, segmentFont, 12, TextAlignment.Left, TextWrapping.NoWrap, Size.Infinity); double midY = RenderArea.Y + (RenderArea.Height / 2.0); double rectHeight = text.Bounds.Height + 10; @@ -26,9 +26,6 @@ namespace LogicAnalyzer.Protocols double bottomY = midY + (rectHeight / 2.0); double minWidth = text.Bounds.Width + 10.0; - if (RenderArea.Width < minWidth) - return; - PathFigure container = new PathFigure(); container.StartPoint = new Point(RenderArea.X, midY); container.Segments.Add(new LineSegment { Point = new Point(RenderArea.X + 5, topY) }); @@ -42,11 +39,11 @@ namespace LogicAnalyzer.Protocols PathGeometry gContainer = new PathGeometry(); gContainer.Figures.Add(container); - //G.FillRectangle(GraphicObjectsCache.GetBrush(Channel.BackColor), RenderArea); - //G.DrawRectangle(GraphicObjectsCache.GetPen(Channel.ForeColor, 1), RenderArea); - G.DrawGeometry(GraphicObjectsCache.GetBrush(Channel.BackColor), GraphicObjectsCache.GetPen(Channel.ForeColor, 1), gContainer); + if (RenderArea.Width < minWidth) + return; + G.DrawText(GraphicObjectsCache.GetBrush(Channel.ForeColor), new Point(RenderArea.X + (RenderArea.Width / 2 - text.Bounds.Width / 2), RenderArea.Y + (RenderArea.Height / 2 - text.Bounds.Height / 2)), text); } } diff --git a/Software/LogicAnalyzer/ParallelProtocolAnalyzer/ParallelAnalyzer.cs b/Software/LogicAnalyzer/ParallelProtocolAnalyzer/ParallelAnalyzer.cs new file mode 100644 index 0000000..5d94ad7 --- /dev/null +++ b/Software/LogicAnalyzer/ParallelProtocolAnalyzer/ParallelAnalyzer.cs @@ -0,0 +1,475 @@ +using Avalonia.Media; +using AvaloniaEdit.Document; +using LogicAnalyzer.Controls; +using LogicAnalyzer.Protocols; +using System.Reflection; + +namespace ParallelProtocolAnalyzer +{ + public class ParallelAnalyzer : ProtocolAnalyzerBase + { + private SimpleSegmentRenderer renderer = new SimpleSegmentRenderer(); + + const string CS_SIGNAL_NAME = "CS"; + const string RD_SIGNAL_NAME = "RD"; + const string WR_SIGNAL_NAME = "WR"; + const string DATA_SIGNAL_NAME = "Data"; + const string ADDR_SIGNAL_NAME = "Address"; + const string RISING_EDGE = "Rising"; + const string FALLING_EDGE = "Falling"; + const string READ_OP = "Read"; + const string WRITE_OP = "Write"; + public override string ProtocolName + { + get + { + return "Parallel"; + } + } + + static ProtocolAnalyzerSetting[] settings = new ProtocolAnalyzerSetting[] + { + new ProtocolAnalyzerSetting + { + SettingType = ProtocolAnalyzerSetting.ProtocolAnalyzerSettingType.List, + Caption = "CS edge", + ListValues = new string[]{ RISING_EDGE, FALLING_EDGE } + }, + new ProtocolAnalyzerSetting + { + SettingType = ProtocolAnalyzerSetting.ProtocolAnalyzerSettingType.List, + Caption = "RD edge", + ListValues = new string[]{ RISING_EDGE, FALLING_EDGE } + }, + new ProtocolAnalyzerSetting + { + SettingType = ProtocolAnalyzerSetting.ProtocolAnalyzerSettingType.List, + Caption = "WR edge", + ListValues = new string[]{ RISING_EDGE, FALLING_EDGE } + }, + new ProtocolAnalyzerSetting + { + SettingType = ProtocolAnalyzerSetting.ProtocolAnalyzerSettingType.Integer, + Caption = "Read offset (in samples)", + IntegerMinimumValue = 0, + IntegerMaximumValue = 32 + }, + new ProtocolAnalyzerSetting + { + SettingType = ProtocolAnalyzerSetting.ProtocolAnalyzerSettingType.Integer, + Caption = "Write offset (in samples)", + IntegerMinimumValue = 0, + IntegerMaximumValue = 32 + }, + new ProtocolAnalyzerSetting + { + SettingType = ProtocolAnalyzerSetting.ProtocolAnalyzerSettingType.Integer, + Caption = "Data width", + IntegerMinimumValue = 4, + IntegerMaximumValue = 32 + }, + new ProtocolAnalyzerSetting + { + SettingType = ProtocolAnalyzerSetting.ProtocolAnalyzerSettingType.Integer, + Caption = "Address width", + IntegerMinimumValue = 0, + IntegerMaximumValue = 32 + } + }; + + public override ProtocolAnalyzerSetting[] Settings + { + get + { + return settings; + } + } + + ProtocolAnalyzerSignal[] signals = new ProtocolAnalyzerSignal[] + { + new ProtocolAnalyzerSignal + { + SignalName = CS_SIGNAL_NAME, + Required = false + }, + new ProtocolAnalyzerSignal + { + SignalName = RD_SIGNAL_NAME, + Required = false + }, + new ProtocolAnalyzerSignal + { + SignalName = WR_SIGNAL_NAME, + Required = false + }, + new ProtocolAnalyzerSignal + { + SignalName = DATA_SIGNAL_NAME, + Required = true, + IsBus = true + }, + new ProtocolAnalyzerSignal + { + SignalName = ADDR_SIGNAL_NAME, + Required = false, + IsBus = true + }, + }; + + public override ProtocolAnalyzerSignal[] Signals + { + get + { + return signals; + } + } + + public override int GetBusWidth(ProtocolAnalyzerSignal Signal, ProtocolAnalyzerSettingValue[] SelectedSettings) + { + if (Signal.SignalName == DATA_SIGNAL_NAME) + return (int)SelectedSettings[5].Value; + + if(Signal.SignalName == ADDR_SIGNAL_NAME) + return (int)SelectedSettings[6].Value; + + return 0; + } + + public override ProtocolAnalyzedChannel[] Analyze(int SamplingRate, int TriggerSample, ProtocolAnalyzerSettingValue[] SelectedSettings, ProtocolAnalyzerSelectedChannel[] SelectedChannels) + { + + /* + * The protocol analysis can work in two ways: + * 1-The CS signal is provided, this is used as the sampling trigger, RD/WR signals will determine + * if the OP is read or write, if none of these are provided then the operation is undetermined. + * + * 2-The CS signal is not provided, then the RD and/or WR signals will be used as trigger + */ + + bool csTrigger = false; + bool useRD = false; + bool useWR = false; + + //Check which trigger signals are used + csTrigger = SelectedChannels.Any(c => c.SignalName == CS_SIGNAL_NAME); + useRD = SelectedChannels.Any(c => c.SignalName == RD_SIGNAL_NAME); + useWR = SelectedChannels.Any(c => c.SignalName == WR_SIGNAL_NAME); + + int csEdge = 0; + int rdEdge = 0; + int wrEdge = 0; + + //Obtain the edges + if (csTrigger && SelectedSettings[0].Value?.ToString() == RISING_EDGE) + csEdge = 1; + + if(useRD && SelectedSettings[1].Value?.ToString() == RISING_EDGE) + rdEdge = 1; + + if(useWR && SelectedSettings[2].Value?.ToString() == RISING_EDGE) + wrEdge = 1; + + //Get the sampling offsets + int readOffset = (int)SelectedSettings[3].Value; + int writeOffset = (int)SelectedSettings[4].Value; + + //Compose the control channels + List controls = new List(); + + if (csTrigger) + controls.Add(new ControlChannel { Edge = (byte)csEdge, Samples = SelectedChannels.First(c => c.SignalName == CS_SIGNAL_NAME).Samples, SignalName = CS_SIGNAL_NAME }); + + if(useRD) + controls.Add(new ControlChannel { Edge = (byte)rdEdge, Samples = SelectedChannels.First(c => c.SignalName == RD_SIGNAL_NAME).Samples, SignalName = RD_SIGNAL_NAME }); + + if(useWR) + controls.Add(new ControlChannel { Edge = (byte)wrEdge, Samples = SelectedChannels.First(c => c.SignalName == WR_SIGNAL_NAME).Samples, SignalName = WR_SIGNAL_NAME }); + + //Compose the data channels + byte[][] dataBits = SelectedChannels.Where(c => c.SignalName == DATA_SIGNAL_NAME).OrderBy(c => c.BusIndex).Select(c => c.Samples).ToArray(); + + //Compose the address channels + byte[][] addressBits = SelectedChannels.Where(c => c.SignalName == ADDR_SIGNAL_NAME).OrderBy(c => c.BusIndex).Select(c => c.Samples).ToArray(); + + //Find all ops + List foundOps = new List(); + int offset = 0; + + OpData? next = FindNextOp(controls, dataBits, addressBits, readOffset, writeOffset, ref offset); + + while (next != null) + { + foundOps.Add(next); + next = FindNextOp(controls, dataBits, addressBits, readOffset, writeOffset, ref offset); + } + + //Compose all the data segments + var segments = foundOps.Select(o => ComposeSegment(o, addressBits.Length, dataBits.Length)); + + List results = new List(); + + //If we are using a CS trigger... + if (csTrigger) + { + var csChannel = SelectedChannels.First(c => c.SignalName == CS_SIGNAL_NAME); + + //All the operations are shown in the CS channel + ProtocolAnalyzedChannel csChannelData = new ProtocolAnalyzedChannel(csChannel.SignalName, csChannel.ChannelIndex, renderer, segments.ToArray(), Colors.White, Color.FromArgb(100, Colors.Blue.R, Colors.Blue.G, Colors.Blue.B)); + + results.Add(csChannelData); + } + else + { + if (useRD) + { + var rdChannel = SelectedChannels.First(c => c.SignalName == RD_SIGNAL_NAME); + + //Show read ops in RD channel + ProtocolAnalyzedChannel csChannelData = new ProtocolAnalyzedChannel(rdChannel.SignalName, rdChannel.ChannelIndex, renderer, segments.Where(s => s.Value.Contains(READ_OP)).ToArray(), Colors.White, Color.FromArgb(100, Colors.Green.R, Colors.Green.G, Colors.Green.B)); + + results.Add(csChannelData); + } + + if (useWR) + { + var wrChannel = SelectedChannels.First(c => c.SignalName == WR_SIGNAL_NAME); + + //Show read ops in RD channel + ProtocolAnalyzedChannel csChannelData = new ProtocolAnalyzedChannel(wrChannel.SignalName, wrChannel.ChannelIndex, renderer, segments.Where(s => s.Value.Contains(WRITE_OP)).ToArray(), Colors.White, Color.FromArgb(100, Colors.Red.R, Colors.Red.G, Colors.Red.B)); + + results.Add(csChannelData); + } + } + + return results.ToArray(); + + } + + private ProtocolAnalyzerDataSegment ComposeSegment(OpData o, int AddressBits, int DataBits) + { + int addressNibbles = (AddressBits / 4) + ((AddressBits % 4) == 0 ? 0 : 1); + int dataNibbles = (DataBits / 4) + ((DataBits % 4) == 0 ? 0 : 1); + + ProtocolAnalyzerDataSegment segment = new ProtocolAnalyzerDataSegment(); + segment.FirstSample = o.Start; + segment.LastSample = o.End - 1; + string value = ""; + + if (addressNibbles != 0 && o.Address != null) + value += $"A: 0x{o.Address.Value.ToString($"X{addressNibbles}")}\r\n"; + + if (o.Operation != null) + value += $"{o.Operation}: 0x{o.Value.ToString($"X{dataNibbles}")}"; + else + value += $"V: 0x{o.Value.ToString($"X{dataNibbles}")}"; + + segment.Value = value; + + return segment; + } + + private OpData? FindNextOp(IEnumerable controls, byte[][] dataBits, byte[][] addressBits, int readOffset, int writeOffset, ref int offset) + { + int max = dataBits[0].Length; + + int start = -1; + int end = max; + + //Find the trigger and range + if (controls.Any(c => c.SignalName == CS_SIGNAL_NAME)) + { + var cs = controls.First(c => c.SignalName == CS_SIGNAL_NAME); + + for (int buc = offset; buc < max; buc++) + { + if (cs.Samples[buc] == cs.Edge) + { + start = buc; + break; + } + } + + if (start == -1) + return null; + + for (int buc = start; buc < max; buc++) + { + if (cs.Samples[buc] != cs.Edge) + { + end = buc; + break; + } + } + } + else + { + ControlChannel? trigger = null; + + for (int buc = offset; buc < max; buc++) + { + foreach(var control in controls) + { + if (control.Samples[buc] == control.Edge) + { + trigger = control; + start = buc; + break; + } + } + + if (trigger != null) + break; + } + + if (trigger == null) + return null; + + for (int buc = start; buc < max; buc++) + { + if (trigger.Samples[buc] != trigger.Edge) + { + end = buc; + break; + } + } + } + + OpData op = new OpData { Start = start, End = end }; + + int samplePos = start; + + op.Operation = GetOp(controls, samplePos); + op.Address = GetAddress(addressBits, samplePos); + + if (op.Operation == READ_OP) + { + if (start + readOffset < end) + samplePos = start + readOffset; + else + samplePos = end - 1; + } + else if (op.Operation == WRITE_OP) + { + if (start + writeOffset < end) + samplePos = start + writeOffset; + else + samplePos = end - 1; + } + else + { + int maxOffset = readOffset > writeOffset ? readOffset : writeOffset; + + if (start + maxOffset < end) + samplePos = start + maxOffset; + else + samplePos = end - 1; + } + + op.Value = GetData(dataBits, samplePos); + + offset = end; + + return op; + } + + private string? GetOp(IEnumerable controls, int samplePos) + { + var rdChannel = controls.FirstOrDefault(c => c.SignalName == RD_SIGNAL_NAME); + var wrChannel = controls.FirstOrDefault(c => c.SignalName == WR_SIGNAL_NAME); + + if(rdChannel == null && wrChannel == null) + return null; + + if (rdChannel != null && rdChannel.Samples[samplePos] == rdChannel.Edge) + return READ_OP; + + if (wrChannel != null && wrChannel.Samples[samplePos] == wrChannel.Edge) + return WRITE_OP; + + return null; + } + + private uint? GetAddress(byte[][] addressBits, int samplePos) + { + if(addressBits == null || addressBits.Length == 0) + return null; + + uint address = 0; + + for (int buc = 0; buc < addressBits.Length; buc++) + address |= (uint)(addressBits[buc][samplePos] << buc); + + return address; + } + + private uint GetData(byte[][] dataBits, int samplePos) + { + uint value = 0; + + for(int buc = 0; buc < dataBits.Length; buc++) + value |= (uint)(dataBits[buc][samplePos] << buc); + + return value; + + } + + public override bool ValidateSettings(ProtocolAnalyzerSettingValue[] SelectedSettings, ProtocolAnalyzerSelectedChannel[] SelectedChannels) + { + bool csTrigger = false; + bool useRD = false; + bool useWR = false; + + //Check which trigger signals are used + csTrigger = SelectedChannels.Any(c => c.SignalName == CS_SIGNAL_NAME); + useRD = SelectedChannels.Any(c => c.SignalName == RD_SIGNAL_NAME); + useWR = SelectedChannels.Any(c => c.SignalName == WR_SIGNAL_NAME); + + //If no control signal selected then configuration is invalid + if (!csTrigger && !useRD && !useWR) + return false; + + //If CS signal is selected, check for the edge selection + if(csTrigger && SelectedSettings[0].Value == null) + return false; + + //If RD signal is selected, check for the edge selection + if (useRD && SelectedSettings[1].Value == null) + return false; + + //If WR signal is selected, check for the edge selection + if (useWR && SelectedSettings[2].Value == null) + return false; + + int addressSize = 0; + + if (SelectedSettings[5].Value != null) + addressSize = (int)SelectedSettings[5].Value; + + //If an address size is provided check for its channels + if (addressSize > 0 && !SelectedChannels.Any(c => c.SignalName == ADDR_SIGNAL_NAME)) + return false; + + //Check for the data channels + if (!SelectedChannels.Any(c => c.SignalName == DATA_SIGNAL_NAME)) + return false; + + return true; + } + + private class ControlChannel + { + public string SignalName { get; set; } + public byte[] Samples { get; set; } + public byte Edge { get; set; } + } + + private class OpData + { + public int Start { get; set; } + public int End { get; set; } + public uint? Address { get; set; } + public uint Value { get; set; } + public string? Operation { get; set; } + } + } +} diff --git a/Software/LogicAnalyzer/ParallelProtocolAnalyzer/ParallelProtocolAnalyzer.csproj b/Software/LogicAnalyzer/ParallelProtocolAnalyzer/ParallelProtocolAnalyzer.csproj new file mode 100644 index 0000000..b769c30 --- /dev/null +++ b/Software/LogicAnalyzer/ParallelProtocolAnalyzer/ParallelProtocolAnalyzer.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + enable + enable + + + + + + +