Updated analyzers

This commit is contained in:
Agustín Gimenez 2024-05-12 11:39:45 +02:00
parent 40a9b788da
commit 3765865c50
11 changed files with 729 additions and 76 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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<SampleRegion> regions = new List<SampleRegion>();
List<ProtocolAnalyzedChannel> analysisData = new List<ProtocolAnalyzedChannel>();
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<ProtocolAnalyzedChannel> 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)

View File

@ -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);
}

View File

@ -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);

View File

@ -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; }
/// <summary>
/// If true the signal is a bus and the size will be requested to the protocol analyzer
/// </summary>
public bool IsBus { get; set; }
}
/// <summary>
@ -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

View File

@ -35,6 +35,15 @@ namespace LogicAnalyzer.Protocols
/// <returns>An array of analyzed channels</returns>
public abstract ProtocolAnalyzedChannel[] Analyze(int SamplingRate, int TriggerSample, ProtocolAnalyzerSettingValue[] SelectedSettings, ProtocolAnalyzerSelectedChannel[] SelectedChannels);
/// <summary>
/// Informs the analyzer of the size of a bus. By default returns 0 (bus not used).
/// </summary>
/// <param name="Signal">The bus to get the size for</param>
/// <param name="SelectedSettings">The settings the user selected</param>
/// <returns>The size of the bus</returns>
public virtual int GetBusWidth(ProtocolAnalyzerSignal Signal, ProtocolAnalyzerSettingValue[] SelectedSettings)
{
return 0;
}
}
}

View File

@ -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);
}
}

View File

@ -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<ControlChannel> controls = new List<ControlChannel>();
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<OpData> foundOps = new List<OpData>();
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<ProtocolAnalyzedChannel> results = new List<ProtocolAnalyzedChannel>();
//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<ControlChannel> 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<ControlChannel> 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; }
}
}
}

View File

@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LogicAnalyzer\LogicAnalyzer.csproj" />
</ItemGroup>
</Project>