diff --git a/Firmware/LogicAnalyzer/DigitalSignalGenerator.pio b/Firmware/LogicAnalyzer/DigitalSignalGenerator.pio deleted file mode 100644 index e69de29..0000000 diff --git a/Firmware/LogicAnalyzer/LogicAnalyzer.c b/Firmware/LogicAnalyzer/LogicAnalyzer.c index 4760a15..e565d47 100644 --- a/Firmware/LogicAnalyzer/LogicAnalyzer.c +++ b/Firmware/LogicAnalyzer/LogicAnalyzer.c @@ -10,6 +10,7 @@ #include "pico/multicore.h" #include "LogicAnalyzer.pio.h" #include "LogicAnalyzer_Structs.h" +#include "tusb.h" #ifdef WS2812_LED #include "LogicAnalyzer_W2812.h" @@ -85,6 +86,8 @@ CAPTURE_REQUEST* req; #ifdef USE_CYGW_WIFI +/// @brief Stores a new WiFi configuration in the flash of the device +/// @param settings Settings to store void storeSettings(WIFI_SETTINGS* settings) { uint8_t buffer[FLASH_PAGE_SIZE]; @@ -129,6 +132,10 @@ void storeSettings(WIFI_SETTINGS* settings) } #endif + +/// @brief Sends a response message to the host application in string mode +/// @param response The message to be sent (null terminated) +/// @param toWiFi If true the message is sent to a WiFi endpoint, else to the USB connection through STDIO void sendResponse(const char* response, bool toWiFi) { #ifdef USE_CYGW_WIFI @@ -146,6 +153,10 @@ void sendResponse(const char* response, bool toWiFi) printf(response); } +/// @brief Processes data received from the host application +/// @param data The received data +/// @param length Length of the data +/// @param fromWiFi If true the message comes from a WiFi connection void processData(uint8_t* data, uint length, bool fromWiFi) { for(uint pos = 0; pos < length; pos++) @@ -208,7 +219,7 @@ void processData(uint8_t* data, uint length, bool fromWiFi) else if(req->triggerType == 2) //start fast trigger capture started = startCaptureFast(req->frequency, req->preSamples, req->postSamples, (uint8_t*)&req->channels, req->channelCount, req->trigger, req->count, req->triggerValue, req->captureMode); else //Start simple trigger capture - started = startCaptureSimple(req->frequency, req->preSamples, req->postSamples, (uint8_t*)&req->channels, req->channelCount, req->trigger, req->inverted, req->captureMode); + started = startCaptureSimple(req->frequency, req->preSamples, req->postSamples, req->loopCount, (uint8_t*)&req->channels, req->channelCount, req->trigger, req->inverted, req->captureMode); #else @@ -301,6 +312,44 @@ void processData(uint8_t* data, uint length, bool fromWiFi) //have any data, but the capture request has a CAPTURE_REQUEST struct as data. } +/// @brief Transfer a buffer of data through USB using the TinyUSB CDC functions +/// @param data Buffer of data to transfer +/// @param len Length of the buffer +void cdc_transfer(unsigned char* data, int len) +{ + + int left = len; + int pos = 0; + + while(left > 0) + { + int avail = (int) tud_cdc_write_available(); + + if(avail > left) + avail = left; + + if(avail) + { + int transferred = (int) tud_cdc_write(data + pos, avail); + tud_task(); + tud_cdc_write_flush(); + + pos += transferred; + left -= transferred; + } + else + { + tud_task(); + tud_cdc_write_flush(); + if (!tud_cdc_connected()) + break; + } + } +} + +/// @brief Receive and process USB data from the host application +/// @param skipProcessing If true the received data is not processed (used for cleanup) +/// @return True if anything is received, false if not bool processUSBInput(bool skipProcessing) { //Try to get char @@ -321,11 +370,14 @@ bool processUSBInput(bool skipProcessing) #ifdef USE_CYGW_WIFI +/// @brief Purges any pending data in the USB input void purgeUSBData() { while(getchar_timeout_us(0) != PICO_ERROR_TIMEOUT); } +/// @brief Callback for the WiFi event queue +/// @param event Received event void wifiEvent(void* event) { EVENT_FROM_WIFI* wEvent = (EVENT_FROM_WIFI*)event; @@ -353,6 +405,9 @@ void wifiEvent(void* event) } } +/// @brief Receives and processes input from the host application (when connected through WiFi) +/// @param skipProcessing /// @param skipProcessing If true the received data is not processed (used for cleanup) +/// @return True if anything is received, false if not bool processWiFiInput(bool skipProcessing) { bool res = event_has_events(&wifiToFrontend); @@ -372,6 +427,7 @@ bool processWiFiInput(bool skipProcessing) #endif +/// @brief Process input data from the host application if it is available void processInput() { #ifdef USE_CYGW_WIFI @@ -384,6 +440,8 @@ void processInput() #endif } +/// @brief Processes input data from the host application to check if there is any cancel capture request +/// @return True if there was input data bool processCancel() { #ifdef USE_CYGW_WIFI @@ -397,6 +455,8 @@ bool processCancel() #endif } +/// @brief Main app loop +/// @return Exit code int main() { //Overclock Powerrrr! @@ -454,18 +514,10 @@ int main() event_push(&frontendToWifi, &evt); } else - { - putchar_raw(lengthPointer[0]); - putchar_raw(lengthPointer[1]); - putchar_raw(lengthPointer[2]); - putchar_raw(lengthPointer[3]); - } + cdc_transfer(lengthPointer, 4); #else - putchar_raw(lengthPointer[0]); - putchar_raw(lengthPointer[1]); - putchar_raw(lengthPointer[2]); - putchar_raw(lengthPointer[3]); + cdc_transfer(lengthPointer, 4); #endif sleep_ms(100); @@ -513,23 +565,24 @@ int main() } else { - for(int buc = 0; buc < length; buc++) + if(first + length > CAPTURE_BUFFER_SIZE) { - putchar_raw(buffer[first++]); - - if(first >= 131072) - first = 0; + cdc_transfer(buffer + first, CAPTURE_BUFFER_SIZE - first); + cdc_transfer(buffer, (first + length) - CAPTURE_BUFFER_SIZE); } + else + cdc_transfer(buffer + first, length); } #else - //Send the samples - for(int buc = 0; buc < length; buc++) - { - putchar_raw(buffer[first++]); - if(first >= 131072) - first = 0; + if(first + length > CAPTURE_BUFFER_SIZE) + { + cdc_transfer(buffer + first, CAPTURE_BUFFER_SIZE - first); + cdc_transfer(buffer, (first + length) - CAPTURE_BUFFER_SIZE); } + else + cdc_transfer(buffer + first, length); + #endif //Done! capturing = false; diff --git a/Firmware/LogicAnalyzer/LogicAnalyzer.pio b/Firmware/LogicAnalyzer/LogicAnalyzer.pio index f7353ce..60e5e04 100644 --- a/Firmware/LogicAnalyzer/LogicAnalyzer.pio +++ b/Firmware/LogicAnalyzer/LogicAnalyzer.pio @@ -2,7 +2,9 @@ .program POSITIVE_CAPTURE pull - out x 32 ;read capture length + out y 32 ;read loop count + pull + mov x, osr ;read capture length (use MOV instead of PULL so we can MOV it again on each loop) .wrap_target @@ -16,19 +18,28 @@ POST_CAPTURE: in pins 32 ;read sample jmp x-- POST_CAPTURE ;loop if more samples needed + jmp y-- LOOP ;jump to loop control + irq 0 ;notify to the main program that we have finished capturing LOCK: jmp LOCK ;block the program +LOOP: + mov x, osr ;read loop count +INNER_LOOP: + + jmp pin POST_CAPTURE ;wait for trigger + jmp INNER_LOOP + ;-------------------------------------------------------------------------------------------- .program NEGATIVE_CAPTURE -.wrap_target - pull - out x 32 ;read capture length + out y 32 ;read loop count + pull + mov x, osr ;read capture length (use MOV instead of PULL so we can MOV it again on each loop) PRE_CAPTURE: @@ -37,15 +48,24 @@ PRE_CAPTURE: POST_CAPTURE: +.wrap_target + in pins 32 ;read sample jmp x-- POST_CAPTURE ;loop if more samples needed + jmp y-- LOOP ;jump to loop control + irq 0 ;notify to the main program that we have finished capturing LOCK: jmp LOCK ;block the program +LOOP: + mov x, osr ;read loop count +INNER_LOOP: + jmp pin INNER_LOOP ;wait for trigger + .wrap ;-------------------------------------------------------------------------------------------- @@ -131,6 +151,8 @@ LOCK: #include "string.h" #include "hardware/sync.h" +#define CAPTURE_BUFFER_SIZE (128 * 1024) + typedef enum { MODE_8_CHANNEL, @@ -161,6 +183,7 @@ static uint8_t lastCapturePinCount; //Count of captured pins static uint32_t lastTriggerCapture; //Moment where the trigger happened inside the circular pre buffer static uint32_t lastPreSize; //Pre-trigger buffer size static uint32_t lastPostSize; //Post-trigger buffer size +static uint32_t lastLoopCount; //Number of loops static bool lastTriggerInverted; //Inverted? static uint8_t lastTriggerPin; static uint32_t lastStartPosition; @@ -187,7 +210,7 @@ static bool captureProcessed; #endif //Main capture buffer, aligned at a 32k boundary, to use the maxixmum ring size supported by DMA channels -static uint8_t captureBuffer[128 * 1024] __attribute__((aligned(32768))); +static uint8_t captureBuffer[CAPTURE_BUFFER_SIZE] __attribute__((aligned(32768))); //----------------------------------------------------------------------------- //--------------Complex trigger PIO program------------------------------------ @@ -330,8 +353,8 @@ uint32_t find_capture_tail() if(busy_channel == 0xFFFFFFFF) return 0xFFFFFFFF; - //Ok, now we need to know at which transfer the DMA is. The value equals to MAX_TRANSFERS - TRANSFERS_LEFT. - int32_t transfer = transferCount - dma_channel_hw_addr(busy_channel)->transfer_count; + //Ok, now we need to know at which transfer the DMA is. The value equals to MAX_TRANSFERS - TRANSFERS_LEFT - 1 (DMA channel decrements transfer_count when it starts :/). + int32_t transfer = transferCount - dma_channel_hw_addr(busy_channel)->transfer_count - 1; //Now compute the last capture position transfer = (transfer + busy_offset) - 1; @@ -636,6 +659,7 @@ bool startCaptureFast(uint32_t freq, uint32_t preLength, uint32_t postLength, co //Store info about the capture lastPreSize = preLength; lastPostSize = postLength; + lastLoopCount = 0; lastCapturePinCount = capturePinCount; lastCaptureComplexFast = true; lastCaptureMode = captureMode; @@ -653,8 +677,8 @@ bool startCaptureFast(uint32_t freq, uint32_t preLength, uint32_t postLength, co //Store the PIO units and clear program memory capturePIO = pio1; //Cannot clear it in PIO1 because the W uses PIO1 to transfer data - triggerPIO = pio0; + pio_clear_instruction_memory(triggerPIO); //Configure 24 + 2 IO's to be used by the PIO (24 channels + 2 trigger pins) @@ -665,7 +689,7 @@ bool startCaptureFast(uint32_t freq, uint32_t preLength, uint32_t postLength, co pio_gpio_init(capturePIO, pinMap[i]); //Configure capture SM - sm_Capture = pio_claim_unused_sm(capturePIO, true); + sm_Capture = pio_claim_unused_sm(capturePIO, true); pio_sm_clear_fifos(capturePIO, sm_Capture); pio_sm_restart(capturePIO, sm_Capture); captureOffset = pio_add_program(capturePIO, &FAST_CAPTURE_program); @@ -802,6 +826,7 @@ bool startCaptureComplex(uint32_t freq, uint32_t preLength, uint32_t postLength, //Store info about the capture lastPreSize = preLength; lastPostSize = postLength; + lastLoopCount = 0; lastCapturePinCount = capturePinCount; lastCaptureComplexFast = true; lastCaptureMode = captureMode; @@ -910,7 +935,7 @@ bool startCaptureComplex(uint32_t freq, uint32_t preLength, uint32_t postLength, #endif -bool startCaptureSimple(uint32_t freq, uint32_t preLength, uint32_t postLength, const uint8_t* capturePins, uint8_t capturePinCount, uint8_t triggerPin, bool invertTrigger, CHANNEL_MODE captureMode) +bool startCaptureSimple(uint32_t freq, uint32_t preLength, uint32_t postLength, uint8_t loopCount, const uint8_t* capturePins, uint8_t capturePinCount, uint8_t triggerPin, bool invertTrigger, CHANNEL_MODE captureMode) { int maxSamples; @@ -950,6 +975,7 @@ bool startCaptureSimple(uint32_t freq, uint32_t preLength, uint32_t postLength, //Store info about the capture lastPreSize = preLength; lastPostSize = postLength; + lastLoopCount = loopCount; lastCapturePinCount = capturePinCount; lastTriggerInverted = invertTrigger; lastCaptureComplexFast = false; @@ -1028,8 +1054,10 @@ bool startCaptureSimple(uint32_t freq, uint32_t preLength, uint32_t postLength, //Enable state machine pio_sm_set_enabled(capturePIO, sm_Capture, true); - //Write capture length to post program to start the capture process + //Write loop count and capture length to post program to start the capture process + pio_sm_put_blocking(capturePIO, sm_Capture, loopCount); pio_sm_put_blocking(capturePIO, sm_Capture, postLength - 1); + //Finally clear capture status, process flags and capture type captureFinished = false; @@ -1048,6 +1076,9 @@ bool IsCapturing() uint8_t* GetBuffer(uint32_t* bufferSize, uint32_t* firstSample, CHANNEL_MODE* captureMode) { + //Compute total sample count + uint32_t totalSamples = lastPreSize + (lastPostSize * (lastLoopCount + 1)); + //If we don't have processed the buffer... if(!captureProcessed) { @@ -1066,11 +1097,13 @@ uint8_t* GetBuffer(uint32_t* bufferSize, uint32_t* firstSample, CHANNEL_MODE* ca break; } //Calculate start position - if(lastTail < lastPreSize + lastPostSize - 1) - lastStartPosition = maxSize - ((lastPreSize + lastPostSize) - (lastTail - 1)); + if(lastTail < totalSamples - 1) + lastStartPosition = (maxSize - totalSamples) + lastTail + 1; else - lastStartPosition = lastTail - (lastPreSize + lastPostSize) + 1; + lastStartPosition = lastTail - totalSamples + 1; + uint32_t currentPos = lastStartPosition; + switch(lastCaptureMode) { case MODE_24_CHANNEL: @@ -1082,7 +1115,7 @@ uint8_t* GetBuffer(uint32_t* bufferSize, uint32_t* firstSample, CHANNEL_MODE* ca //Sort channels //(reorder captured bits based on the channels requested) - for(int buc = 0; buc < lastPreSize + lastPostSize; buc++) + for(int buc = 0; buc < totalSamples; buc++) { oldValue = buffer[currentPos]; //Store current value newValue = 0; //New value @@ -1110,7 +1143,7 @@ uint8_t* GetBuffer(uint32_t* bufferSize, uint32_t* firstSample, CHANNEL_MODE* ca //Sort channels //(reorder captured bits based on the channels requested) - for(int buc = 0; buc < lastPreSize + lastPostSize; buc++) + for(int buc = 0; buc < totalSamples; buc++) { oldValue = buffer[currentPos]; //Store current value newValue = 0; //New value @@ -1138,7 +1171,7 @@ uint8_t* GetBuffer(uint32_t* bufferSize, uint32_t* firstSample, CHANNEL_MODE* ca //Sort channels //(reorder captured bits based on the channels requested) - for(int buc = 0; buc < lastPreSize + lastPostSize; buc++) + for(int buc = 0; buc < totalSamples; buc++) { oldValue = buffer[currentPos]; //Store current value newValue = 0; //New value @@ -1162,7 +1195,7 @@ uint8_t* GetBuffer(uint32_t* bufferSize, uint32_t* firstSample, CHANNEL_MODE* ca } //Return data *captureMode = lastCaptureMode; - *bufferSize = lastPreSize + lastPostSize; + *bufferSize = totalSamples; *firstSample = lastStartPosition; return captureBuffer; } diff --git a/Firmware/LogicAnalyzer/LogicAnalyzer_Build_Settings.h b/Firmware/LogicAnalyzer/LogicAnalyzer_Build_Settings.h index 5b7efe3..fa79bdb 100644 --- a/Firmware/LogicAnalyzer/LogicAnalyzer_Build_Settings.h +++ b/Firmware/LogicAnalyzer/LogicAnalyzer_Build_Settings.h @@ -1,7 +1,7 @@ #ifndef __BUILD_SETTINGS__ #define __BUILD_SETTINGS__ - #define FIRMWARE_VERSION "4_5" + #define FIRMWARE_VERSION "V5_0" //Select the board type to build the firmware for #define BUILD_PICO diff --git a/Firmware/LogicAnalyzer/LogicAnalyzer_Structs.h b/Firmware/LogicAnalyzer/LogicAnalyzer_Structs.h index e2cdf3d..572018f 100644 --- a/Firmware/LogicAnalyzer/LogicAnalyzer_Structs.h +++ b/Firmware/LogicAnalyzer/LogicAnalyzer_Structs.h @@ -33,6 +33,8 @@ uint32_t preSamples; //Number of samples stored after the trigger uint32_t postSamples; + //Number of capture loops + uint8_t loopCount; //Capture mode (0 = 8 channel, 1 = 16 channel, 2 = 24 channel) uint8_t captureMode; diff --git a/Software/LogicAnalyzer/Artwork/Logo40.png b/Software/LogicAnalyzer/Artwork/Logo40.png index 721460d..ac9dd9c 100644 Binary files a/Software/LogicAnalyzer/Artwork/Logo40.png and b/Software/LogicAnalyzer/Artwork/Logo40.png differ diff --git a/Software/LogicAnalyzer/Artwork/Logo40.psd b/Software/LogicAnalyzer/Artwork/Logo40.psd index c89a935..16ae046 100644 Binary files a/Software/LogicAnalyzer/Artwork/Logo40.psd and b/Software/LogicAnalyzer/Artwork/Logo40.psd differ diff --git a/Software/LogicAnalyzer/CLCapture/CLCapture.csproj b/Software/LogicAnalyzer/CLCapture/CLCapture.csproj index 4f6bbef..a41225b 100644 --- a/Software/LogicAnalyzer/CLCapture/CLCapture.csproj +++ b/Software/LogicAnalyzer/CLCapture/CLCapture.csproj @@ -6,7 +6,7 @@ enable enable window.ico - 4.5.1.0 + 5.0.0.0 diff --git a/Software/LogicAnalyzer/CLCapture/CLCaptureOptions.cs b/Software/LogicAnalyzer/CLCapture/CLCaptureOptions.cs index 074edd2..2526284 100644 --- a/Software/LogicAnalyzer/CLCapture/CLCaptureOptions.cs +++ b/Software/LogicAnalyzer/CLCapture/CLCaptureOptions.cs @@ -14,17 +14,25 @@ namespace CLCapture { [Value(0, Required = true, HelpText = "Device's serial port or IP address and port.")] public string? AddressPort { get; set; } + [Value(1, Required = true, HelpText = "Desired sampling frequency.")] public int SamplingFrequency { get; set; } + [Value(2, Required = true, HelpText = "List of channels to capture (channels sepparated by comma, can contain a name adding a semicolon after the channel number, no spaces allowed).")] public string? Channels { get; set; } + [Value(3, Required = true, HelpText = "Number of samples to capture before the trigger.")] public int PreSamples { get; set; } + [Value(4, Required = true, HelpText = "Number of samples to capture after the trigger.")] public int PostSamples { get; set; } - [Value(5, Required = true, HelpText = "Trigger definition in the form of \"TriggerType:(Edge, Fast or Complex),Channel:(base trigger channel),Value:(string containing 1's and 0's indicating each trigger chanel state)\".")] + + [Value(5, Required = true, HelpText = "Number of bursts to capture (0 or 1 to disable burst mode).")] + public int LoopCount { get; set; } + + [Value(6, Required = true, HelpText = "Trigger definition in the form of \"TriggerType:(Edge, Fast or Complex),Channel:(base trigger channel),Value:(string containing 1's and 0's indicating each trigger chanel state)\".")] public CLTrigger? Trigger { get; set; } - [Value(6, Required = true, HelpText = "Name of the output file.")] + [Value(7, Required = true, HelpText = "Name of the output file.")] public string? OutputFile { get; set; } } diff --git a/Software/LogicAnalyzer/CLCapture/Program.cs b/Software/LogicAnalyzer/CLCapture/Program.cs index 3ed7d9c..0b8ad7d 100644 --- a/Software/LogicAnalyzer/CLCapture/Program.cs +++ b/Software/LogicAnalyzer/CLCapture/Program.cs @@ -180,8 +180,7 @@ async Task Capture(CLCaptureOptions opts) if (opts.Trigger.TriggerType == CLTriggerType.Edge) { Console.WriteLine("Starting edge triggered capture..."); - var resStart = driver.StartCapture(opts.SamplingFrequency, opts.PreSamples, opts.PostSamples, - nChannels, opts.Trigger.Channel - 1, opts.Trigger.Value == "0", CaptureFinished); + var resStart = driver.StartCapture(opts.SamplingFrequency, opts.PreSamples, opts.PostSamples, opts.LoopCount < 2 ? 0 : opts.LoopCount - 1, nChannels, opts.Trigger.Channel - 1, opts.Trigger.Value == "0", CaptureFinished); if (resStart != CaptureError.None) { diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Assets/Logo40.png b/Software/LogicAnalyzer/LogicAnalyzer/Assets/Logo40.png index 721460d..ac9dd9c 100644 Binary files a/Software/LogicAnalyzer/LogicAnalyzer/Assets/Logo40.png and b/Software/LogicAnalyzer/LogicAnalyzer/Assets/Logo40.png differ diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Classes/CaptureSettings.cs b/Software/LogicAnalyzer/LogicAnalyzer/Classes/CaptureSettings.cs index 4ebbbd8..746db05 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Classes/CaptureSettings.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/Classes/CaptureSettings.cs @@ -11,12 +11,18 @@ namespace LogicAnalyzer.Classes public int Frequency { get; set; } public int PreTriggerSamples { get; set; } public int PostTriggerSamples { get; set; } + public int LoopCount { get; set; } public CaptureChannel[] CaptureChannels { get; set; } = new CaptureChannel[0]; public int TriggerType { get; set; } public int TriggerChannel { get; set; } public bool TriggerInverted { get; set; } public int TriggerBitCount { get; set; } public ushort TriggerPattern { get; set; } + + public CaptureSettings Clone() + { + return (CaptureSettings)MemberwiseClone(); + } } public class CaptureChannel diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Controls/ChannelViewer.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/Controls/ChannelViewer.axaml.cs index 9edde7f..fdef170 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Controls/ChannelViewer.axaml.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/Controls/ChannelViewer.axaml.cs @@ -67,7 +67,7 @@ namespace LogicAnalyzer.Controls newChannelLabel.Text = channels[buc].TextualChannelNumber; - newChannelLabel.Foreground = GraphicObjectsCache.GetBrush(AnalyzerColors.FgChannelColors[buc]); + newChannelLabel.Foreground = GraphicObjectsCache.GetBrush(AnalyzerColors.FgChannelColors[buc % 24]); newChannelGrid.Children.Add(newChannelLabel); diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Controls/SamplePreviewer.axaml b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SamplePreviewer.axaml new file mode 100644 index 0000000..a74303e --- /dev/null +++ b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SamplePreviewer.axaml @@ -0,0 +1,7 @@ + + diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Controls/SamplePreviewer.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SamplePreviewer.axaml.cs new file mode 100644 index 0000000..a3bef3a --- /dev/null +++ b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SamplePreviewer.axaml.cs @@ -0,0 +1,102 @@ +using Avalonia.Controls; +using Avalonia.Media; +using Avalonia.Media.Imaging; +using LogicAnalyzer.Classes; +using MessageBox.Avalonia.DTO; +using SkiaSharp; +using System; + +namespace LogicAnalyzer.Controls +{ + public partial class SamplePreviewer : UserControl + { + Bitmap? bmp; + int sampleCount = 0; + + int viewPosition; + public int ViewPosition { get { return viewPosition; } set { viewPosition = value; InvalidateVisual(); } } + + public SamplePreviewer() + { + InitializeComponent(); + } + + public void UpdateSamples(UInt128[] Samples, int ChannelCount) + { + if (ChannelCount > 24) + ChannelCount = 24; + + int width = Math.Max(Math.Min(Samples.Length, 4096), 1024); + + float cHeight = 144 / (float)ChannelCount; + float sWidth = (float)width / (float)Samples.Length; + float high = cHeight / 6; + float low = cHeight - high; + + using SKBitmap skb = new SKBitmap(width, 144); + + SKPaint[] colors = new SKPaint[ChannelCount]; + + for (int buc = 0; buc < ChannelCount; buc++) + { + var avColor = AnalyzerColors.FgChannelColors[buc]; + + colors[buc] = new SKPaint + { + Style = SKPaintStyle.Stroke, + StrokeWidth = 1, + Color = new SKColor(avColor.R, avColor.G, avColor.B) + }; + } + + using (var canvas = new SKCanvas(skb)) + { + for (int x = 0; x < Samples.Length; x++) + { + UInt128 sample = Samples[x]; + UInt128 prevSample = Samples[x == 0 ? x : x - 1]; + + for (int chan = 0; chan < ChannelCount; chan++) + { + UInt128 curVal = sample & ((UInt128)1 << chan); + UInt128 prevVal = prevSample & ((UInt128)1 << chan); + + float y = chan * cHeight + (curVal != 0 ? high : low); + + canvas.DrawLine(x * sWidth, y, (x + 1) * sWidth, y, colors[chan]); + + if (curVal != prevVal) + canvas.DrawLine(x * sWidth, chan * cHeight + high, x * sWidth, chan * cHeight + low, colors[chan]); + } + } + } + + using var encoded = skb.Encode(SKEncodedImageFormat.Png, 1); + using var stream = encoded.AsStream(); + + if (bmp != null) + bmp.Dispose(); + + bmp = new Bitmap(stream); + sampleCount = Samples.Length; + } + + public override void Render(DrawingContext context) + { + //base.Render(context); + var bounds = new Avalonia.Rect(0, 0, this.Bounds.Width, this.Bounds.Height); + + context.FillRectangle(GraphicObjectsCache.GetBrush(Color.Parse("#222222")), bounds); + + if (sampleCount == 0 || bmp == null) + return; + + (bmp as IImage).Draw(context, new Avalonia.Rect(bmp.Size), bounds, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode.HighQuality); + + float ratio = (float)bounds.Size.Width / (float)sampleCount; + float pos = viewPosition * ratio; + + context.DrawLine(GraphicObjectsCache.GetPen(Colors.White, 1, DashStyle.Dash), new Avalonia.Point(pos, 0), new Avalonia.Point(pos, 143)); + } + } +} diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleViewer.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleViewer.axaml.cs index c27fd51..4034d5c 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleViewer.axaml.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/Controls/SampleViewer.axaml.cs @@ -16,6 +16,7 @@ namespace LogicAnalyzer.Controls const int MIN_CHANNEL_HEIGHT = 48; public int PreSamples { get; set; } + public int[]? Bursts { get; set; } public UInt128[] Samples { get; set; } public int ChannelCount { get; set; } public int SamplesInScreen { get; set; } @@ -32,7 +33,9 @@ namespace LogicAnalyzer.Controls Color sampleLineColor = Color.FromRgb(60, 60, 60); Color sampleDashColor = Color.FromArgb(60, 60, 60, 60); Color triggerLineColor = Colors.White; + Color burstLineColor = Colors.Azure; Color userLineColor = Colors.Cyan; + public SampleViewer() { InitializeComponent(); @@ -101,7 +104,7 @@ namespace LogicAnalyzer.Controls { //context.FillRectangle(GraphicObjectsCache.GetBrush(AnalyzerColors.BgChannelColors[0]), thisBounds); - if (PreSamples == 0 || Samples == null || ChannelCount == 0 || SamplesInScreen == 0 || updating) + if (Samples == null || ChannelCount == 0 || SamplesInScreen == 0 || updating) return; double channelHeight = thisBounds.Height / (double)ChannelCount; @@ -148,6 +151,14 @@ namespace LogicAnalyzer.Controls if (buc == PreSamples) context.DrawLine(GraphicObjectsCache.GetPen(triggerLineColor, 2), new Point(lineX, 0), new Point(lineX, thisBounds.Height)); + if (Bursts != null) + { + if (Bursts.Any(b => b == buc)) + { + context.DrawLine(GraphicObjectsCache.GetPen(burstLineColor, 2, DashStyle.DashDot), new Point(lineX, 0), new Point(lineX, thisBounds.Height)); + } + } + if(UserMarker != null && UserMarker == buc) context.DrawLine(GraphicObjectsCache.GetPen(userLineColor, 2, DashStyle.DashDot), new Point(lineX, 0), new Point(lineX, thisBounds.Height)); diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/CaptureDialog.axaml b/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/CaptureDialog.axaml index 6f91c08..2b1fd3a 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/CaptureDialog.axaml +++ b/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/CaptureDialog.axaml @@ -158,7 +158,12 @@ - Negative edge + + Negative edge + Burst mode + Burst count: + + Pattern trigger diff --git a/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/CaptureDialog.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/CaptureDialog.axaml.cs index d1e0e06..6df7fce 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/CaptureDialog.axaml.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/Dialogs/CaptureDialog.axaml.cs @@ -178,6 +178,7 @@ namespace LogicAnalyzer.Dialogs Frequency = oldset.Frequency, PostTriggerSamples = oldset.PostTriggerSamples, PreTriggerSamples = oldset.PreTriggerSamples, + LoopCount = 0, TriggerBitCount = oldset.TriggerBitCount, TriggerChannel = oldset.TriggerChannel, TriggerInverted = oldset.TriggerInverted, @@ -216,6 +217,8 @@ namespace LogicAnalyzer.Dialogs triggerChannels[settings.TriggerChannel].IsChecked = true; ckNegativeTrigger.IsChecked = settings.TriggerInverted; + ckBurst.IsChecked = settings.LoopCount > 0; + nudBurstCount.Value = settings.LoopCount > 0 ? settings.LoopCount : 1; rbTriggerTypePattern.IsChecked = false; rbTriggerTypeEdge.IsChecked = true; @@ -280,7 +283,9 @@ namespace LogicAnalyzer.Dialogs int max = driver.GetLimits(channelsToCapture.Select(c => c.ChannelNumber).ToArray()).MaxTotalSamples; - if (nudPreSamples.Value + nudPostSamples.Value > max) + int loops = (int)((ckBurst.IsChecked ?? false) ? nudBurstCount.Value - 1 : 0); + + if (nudPreSamples.Value + (nudPostSamples.Value * (loops + 1)) > max) { await this.ShowError("Error", $"Total samples cannot exceed {max}."); return; @@ -385,6 +390,7 @@ namespace LogicAnalyzer.Dialogs settings.Frequency = (int)nudFrequency.Value; settings.PreTriggerSamples = (int)nudPreSamples.Value; settings.PostTriggerSamples = (int)nudPostSamples.Value; + settings.LoopCount = loops; settings.TriggerInverted = ckNegativeTrigger.IsChecked == true; settings.CaptureChannels = channelsToCapture.ToArray(); diff --git a/Software/LogicAnalyzer/LogicAnalyzer/LogicAnalyzer.csproj b/Software/LogicAnalyzer/LogicAnalyzer/LogicAnalyzer.csproj index 63a1db0..efd9494 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/LogicAnalyzer.csproj +++ b/Software/LogicAnalyzer/LogicAnalyzer/LogicAnalyzer.csproj @@ -8,7 +8,7 @@ true Assets\Ico40.ico True - 4.5.1.0 + 5.0.0.0 diff --git a/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml b/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml index 03b1e5f..247caa9 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml +++ b/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml @@ -46,8 +46,10 @@ + + diff --git a/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs b/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs index 2ed0239..fabdd77 100644 --- a/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs +++ b/Software/LogicAnalyzer/LogicAnalyzer/MainWindow.axaml.cs @@ -1,5 +1,6 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; +using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Threading; using AvaloniaEdit.Utils; @@ -64,16 +65,115 @@ namespace LogicAnalyzer tkInScreen.PropertyChanged += tkInScreen_ValueChanged; scrSamplePos.Scroll += scrSamplePos_ValueChanged; + scrSamplePos.PointerEnter += ScrSamplePos_PointerEnter; + scrSamplePos.PointerLeave += ScrSamplePos_PointerLeave; mnuNew.Click += MnuNew_Click; mnuOpen.Click += mnuOpen_Click; mnuSave.Click += mnuSave_Click; mnuExit.Click += MnuExit_Click; mnuExport.Click += MnuExport_Click; mnuNetSettings.Click += MnuNetSettings_Click; + + AddHandler(InputElement.KeyDownEvent, MainWindow_KeyDown, handledEventsToo: true); + LoadAnalyzers(); RefreshPorts(); } + private void MainWindow_KeyDown(object? sender, Avalonia.Input.KeyEventArgs e) + { + if (e.KeyModifiers == Avalonia.Input.KeyModifiers.Control) + { + switch (e.Key) + { + case Key.Left: + { + var currentVal = scrSamplePos.Value; + var maxVal = sampleViewer.SamplesInScreen; + int newVal = (int)currentVal - (maxVal / 10); + + if (newVal < 0) + newVal = 0; + + scrSamplePos.Value = newVal; + scrSamplePos_ValueChanged(scrSamplePos, null); + } + break; + case Key.Right: + { + var currentVal = scrSamplePos.Value; + var maxVal = sampleViewer.SamplesInScreen; + int newVal = (int)currentVal + (maxVal / 10); + + if (newVal > scrSamplePos.Maximum) + newVal = (int)scrSamplePos.Maximum; + + scrSamplePos.Value = newVal; + scrSamplePos_ValueChanged(scrSamplePos, null); + } + break; + case Key.Down: + { + var currentVal = scrSamplePos.Value; + var maxVal = sampleViewer.SamplesInScreen; + int newVal = (int)currentVal - maxVal; + + if (newVal < 0) + newVal = 0; + + scrSamplePos.Value = newVal; + scrSamplePos_ValueChanged(scrSamplePos, null); + } + break; + case Key.Up: + { + var currentVal = scrSamplePos.Value; + var maxVal = sampleViewer.SamplesInScreen; + int newVal = (int)currentVal + maxVal; + + if (newVal > scrSamplePos.Maximum) + newVal = (int)scrSamplePos.Maximum; + + scrSamplePos.Value = newVal; + scrSamplePos_ValueChanged(scrSamplePos, null); + } + break; + } + } + else if (e.KeyModifiers == KeyModifiers.Shift) + { + switch (e.Key) + { + case Key.Left: + { + var currentVal = scrSamplePos.Value; + int newVal = (int)currentVal - 1; + + if (newVal < 0) + newVal = 0; + + scrSamplePos.Value = newVal; + scrSamplePos_ValueChanged(scrSamplePos, null); + } + break; + case Key.Right: + { + var currentVal = scrSamplePos.Value; + int newVal = (int)currentVal + 1; + + if (newVal > scrSamplePos.Maximum) + newVal = (int)scrSamplePos.Maximum; + + scrSamplePos.Value = newVal; + scrSamplePos_ValueChanged(scrSamplePos, null); + } + break; + + } + + } + } + private async void SampleMarker_ShiftSamples(object? sender, EventArgs e) { var dlg = new ShiftChannelsDialog(); @@ -81,7 +181,7 @@ namespace LogicAnalyzer if (await dlg.ShowDialog(this)) { - var samples = sampleViewer.Samples; + var samples = sampleViewer.Samples; foreach (var channel in dlg.ShiftedChannels) { @@ -120,6 +220,8 @@ namespace LogicAnalyzer sampleViewer.BeginUpdate(); sampleViewer.Samples = samples; sampleViewer.EndUpdate(); + samplePreviewer.UpdateSamples(samples, sampleViewer.ChannelCount); + samplePreviewer.ViewPosition = sampleViewer.FirstSample; } } @@ -195,6 +297,9 @@ namespace LogicAnalyzer sampleViewer.EndUpdate(); + samplePreviewer.ViewPosition = sampleViewer.FirstSample; + samplePreviewer.UpdateSamples(samples, sampleViewer.ChannelCount); + sampleMarker.VisibleSamples = sampleViewer.SamplesInScreen; sampleMarker.FirstSample = sampleViewer.FirstSample; sampleMarker.ClearRegions(); @@ -331,14 +436,6 @@ namespace LogicAnalyzer var lastSample = e.FirstSample + e.SampleCount - 1; var triggerSample = sampleViewer.PreSamples - 1; - var containsTrigger = e.FirstSample <= triggerSample && lastSample >= triggerSample; - - if (containsTrigger) - { - await this.ShowError("Error", "Cannot delete the trigger sample."); - return; - } - var preDelete = sampleViewer.Samples.Take(e.FirstSample); var postDelete = sampleViewer.Samples.Skip(e.FirstSample + e.SampleCount + 1); @@ -412,7 +509,8 @@ namespace LogicAnalyzer { sampleViewer.BeginUpdate(); sampleViewer.Samples = finalSamples; - sampleViewer.PreSamples = finalPreSamples; + sampleViewer.PreSamples = 0; + sampleViewer.Bursts = null; if (sampleViewer.FirstSample > finalSamples.Length - 1) sampleViewer.FirstSample = finalSamples.Length - 1; @@ -425,6 +523,9 @@ namespace LogicAnalyzer sampleViewer.EndUpdate(); + samplePreviewer.UpdateSamples(finalSamples, sampleViewer.ChannelCount); + samplePreviewer.ViewPosition = sampleViewer.FirstSample; + sampleMarker.VisibleSamples = sampleViewer.SamplesInScreen; sampleMarker.FirstSample = sampleViewer.FirstSample; sampleMarker.ClearRegions(); @@ -569,10 +670,30 @@ namespace LogicAnalyzer sampleViewer.SamplesInScreen = Math.Min(100, e.Samples.Length / 10); sampleViewer.FirstSample = Math.Max(e.PreSamples - 10, 0); + + if (settings.LoopCount > 0) + { + int pos = e.PreSamples; + List bursts = new List(); + + for (int buc = 0; buc < settings.LoopCount; buc++) + { + pos = pos + settings.PostTriggerSamples; + bursts.Add(pos); + } + + sampleViewer.Bursts = bursts.ToArray(); + } + else + sampleViewer.Bursts = null; + sampleViewer.ClearRegions(); sampleViewer.ClearAnalyzedChannels(); sampleViewer.EndUpdate(); + samplePreviewer.UpdateSamples(e.Samples, sampleViewer.ChannelCount); + samplePreviewer.ViewPosition = sampleViewer.FirstSample; + scrSamplePos.Maximum = e.Samples.Length - 1; scrSamplePos.Value = sampleViewer.FirstSample; tkInScreen.Value = sampleViewer.SamplesInScreen; @@ -844,7 +965,7 @@ namespace LogicAnalyzer } else { - var error = driver.StartCapture(settings.Frequency, settings.PreTriggerSamples, settings.PostTriggerSamples, settings.CaptureChannels.Select(c => c.ChannelNumber).ToArray(), settings.TriggerChannel, settings.TriggerInverted); + var error = driver.StartCapture(settings.Frequency, settings.PreTriggerSamples, settings.PostTriggerSamples, settings.LoopCount, settings.CaptureChannels.Select(c => c.ChannelNumber).ToArray(), settings.TriggerChannel, settings.TriggerInverted); if (error != CaptureError.None) { @@ -878,7 +999,15 @@ namespace LogicAnalyzer return; } } + private void ScrSamplePos_PointerLeave(object? sender, Avalonia.Input.PointerEventArgs e) + { + samplePreviewer.IsVisible = false; + } + private void ScrSamplePos_PointerEnter(object? sender, Avalonia.Input.PointerEventArgs e) + { + samplePreviewer.IsVisible = true; + } private void scrSamplePos_ValueChanged(object? sender, ScrollEventArgs e) { if (sampleViewer.Samples != null) @@ -886,6 +1015,7 @@ namespace LogicAnalyzer sampleViewer.BeginUpdate(); sampleViewer.FirstSample = (int)scrSamplePos.Value; sampleViewer.EndUpdate(); + samplePreviewer.ViewPosition = sampleViewer.FirstSample; sampleMarker.FirstSample = sampleViewer.FirstSample; } } @@ -927,7 +1057,11 @@ namespace LogicAnalyzer if (string.IsNullOrWhiteSpace(file)) return; - ExportedCapture ex = new ExportedCapture { Settings = settings, Samples = sampleViewer.Samples, SelectedRegions = sampleViewer.SelectedRegions }; + var sets = settings.Clone(); + sets.PreTriggerSamples = sampleViewer.PreSamples; + sets.LoopCount = sampleViewer.Bursts?.Length ?? 0; + + ExportedCapture ex = new ExportedCapture { Settings = sets, Samples = sampleViewer.Samples, SelectedRegions = sampleViewer.SelectedRegions }; File.WriteAllText(file, JsonConvert.SerializeObject(ex, new JsonConverter[] { new SampleRegion.SampleRegionConverter() })); } @@ -980,6 +1114,7 @@ namespace LogicAnalyzer Frequency = oldset.Frequency, PostTriggerSamples = oldset.PostTriggerSamples, PreTriggerSamples = oldset.PreTriggerSamples, + LoopCount = 0, TriggerBitCount = oldset.TriggerBitCount, TriggerChannel = oldset.TriggerChannel, TriggerInverted = oldset.TriggerInverted, @@ -1015,6 +1150,9 @@ namespace LogicAnalyzer sampleViewer.EndUpdate(); + samplePreviewer.UpdateSamples(ex.Samples, sampleViewer.ChannelCount); + samplePreviewer.ViewPosition = sampleViewer.FirstSample; + sampleMarker.VisibleSamples = sampleViewer.SamplesInScreen; sampleMarker.FirstSample = sampleViewer.FirstSample; sampleMarker.ClearRegions(); diff --git a/Software/LogicAnalyzer/SharedDriver/EmulatedAnalizerDriver.cs b/Software/LogicAnalyzer/SharedDriver/EmulatedAnalizerDriver.cs index 49eb1d5..b774344 100644 --- a/Software/LogicAnalyzer/SharedDriver/EmulatedAnalizerDriver.cs +++ b/Software/LogicAnalyzer/SharedDriver/EmulatedAnalizerDriver.cs @@ -62,7 +62,7 @@ namespace SharedDriver throw new NotSupportedException(); } - public CaptureError StartCapture(int Frequency, int PreSamples, int PostSamples, int[] Channels, int TriggerChannel, bool TriggerInverted, Action? CaptureCompletedHandler = null) + public CaptureError StartCapture(int Frequency, int PreSamples, int PostSamples, int LoopCount, int[] Channels, int TriggerChannel, bool TriggerInverted, Action? CaptureCompletedHandler = null) { throw new NotSupportedException(); } diff --git a/Software/LogicAnalyzer/SharedDriver/IAnalizerDriver.cs b/Software/LogicAnalyzer/SharedDriver/IAnalizerDriver.cs index da04fe8..cb5dba2 100644 --- a/Software/LogicAnalyzer/SharedDriver/IAnalizerDriver.cs +++ b/Software/LogicAnalyzer/SharedDriver/IAnalizerDriver.cs @@ -13,7 +13,7 @@ namespace SharedDriver public int Channels { get; } public event EventHandler CaptureCompleted; public bool SendNetworkConfig(string AccesPointName, string Password, string IPAddress, ushort Port); - public CaptureError StartCapture(int Frequency, int PreSamples, int PostSamples, int[] Channels, int TriggerChannel, bool TriggerInverted, Action? CaptureCompletedHandler = null); + public CaptureError StartCapture(int Frequency, int PreSamples, int PostSamples, int LoopCount, int[] Channels, int TriggerChannel, bool TriggerInverted, Action? CaptureCompletedHandler = null); public CaptureError StartPatternCapture(int Frequency, int PreSamples, int PostSamples, int[] Channels, int TriggerChannel, int TriggerBitCount, UInt16 TriggerPattern, bool Fast, Action? CaptureCompletedHandler = null); public bool StopCapture(); public CaptureLimits GetLimits(int[] Channels); diff --git a/Software/LogicAnalyzer/SharedDriver/LogicAnalyzerDriver.cs b/Software/LogicAnalyzer/SharedDriver/LogicAnalyzerDriver.cs index 698d39f..b41ea40 100644 --- a/Software/LogicAnalyzer/SharedDriver/LogicAnalyzerDriver.cs +++ b/Software/LogicAnalyzer/SharedDriver/LogicAnalyzerDriver.cs @@ -10,6 +10,7 @@ namespace SharedDriver { public class LogicAnalyzerDriver : IDisposable, IAnalizerDriver { + Regex regVersion = new Regex(".*?(V([0-9]+)_([0-9]+))$"); Regex regAddressPort = new Regex("([0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+)\\:([0-9]+)"); StreamReader readResponse; BinaryReader readData; @@ -74,7 +75,26 @@ namespace SharedDriver baseStream.ReadTimeout = 10000; DeviceVersion = readResponse.ReadLine(); + + var verMatch = regVersion.Match(DeviceVersion ?? ""); + + if (verMatch == null || !verMatch.Success || !verMatch.Groups[2].Success) + { + Dispose(); + throw new DeviceConnectionException($"Invalid device version V{ (string.IsNullOrWhiteSpace(verMatch?.Value) ? "(unknown)" : verMatch?.Value) }, minimum supported version: V5_0"); + } + + int majorVer = int.Parse(verMatch.Groups[2].Value); + + if (majorVer < 5) + { + Dispose(); + throw new DeviceConnectionException($"Invalid device version V{verMatch.Value}, minimum supported version: V5_0"); + } + baseStream.ReadTimeout = Timeout.Infinite; + + } private void InitNetwork(string AddressPort) { @@ -138,7 +158,7 @@ namespace SharedDriver return false; } - public CaptureError StartCapture(int Frequency, int PreSamples, int PostSamples, int[] Channels, int TriggerChannel, bool TriggerInverted, Action? CaptureCompletedHandler = null) + public CaptureError StartCapture(int Frequency, int PreSamples, int PostSamples, int LoopCount, int[] Channels, int TriggerChannel, bool TriggerInverted, Action? CaptureCompletedHandler = null) { if (capturing) @@ -159,25 +179,27 @@ namespace SharedDriver var captureMode = GetCaptureMode(Channels); + int requestedSamples = PreSamples + (PostSamples * ((byte)LoopCount + 1)); + try { switch (captureMode) { case 0: - if (PreSamples > 98303 || PostSamples > 131069 || PreSamples + PostSamples > 131071) + if (PreSamples > 98303 || PostSamples > 131069 || requestedSamples > 131071) return CaptureError.BadParams; break; case 1: - if (PreSamples > 49151 || PostSamples > 65533 || PreSamples + PostSamples > 65535) + if (PreSamples > 49151 || PostSamples > 65533 || requestedSamples > 65535) return CaptureError.BadParams; break; case 2: - if (PreSamples > 24576 || PostSamples > 32765 || PreSamples + PostSamples > 32767) + if (PreSamples > 24576 || PostSamples > 32765 || requestedSamples > 32767) return CaptureError.BadParams; break; } @@ -197,6 +219,7 @@ namespace SharedDriver frequency = (uint)Frequency, preSamples = (uint)PreSamples, postSamples = (uint)PostSamples, + loopCount = (byte)LoopCount, captureMode = captureMode }; @@ -217,7 +240,7 @@ namespace SharedDriver if (result == "CAPTURE_STARTED") { capturing = true; - Task.Run(() => ReadCapture(PreSamples + PostSamples, captureMode)); + Task.Run(() => ReadCapture(requestedSamples, captureMode)); return CaptureError.None; } return CaptureError.HardwareError; @@ -547,6 +570,7 @@ namespace SharedDriver public UInt32 frequency; public UInt32 preSamples; public UInt32 postSamples; + public byte loopCount; public byte captureMode; } diff --git a/Software/LogicAnalyzer/SharedDriver/MultiAnalizerDriver.cs b/Software/LogicAnalyzer/SharedDriver/MultiAnalizerDriver.cs index 6c116f6..0a26393 100644 --- a/Software/LogicAnalyzer/SharedDriver/MultiAnalizerDriver.cs +++ b/Software/LogicAnalyzer/SharedDriver/MultiAnalizerDriver.cs @@ -10,7 +10,7 @@ namespace SharedDriver { public class MultiAnalizerDriver : IDisposable, IAnalizerDriver { - Regex regVersion = new Regex(".*?(V[0-9]_[0-9])$"); + Regex regVersion = new Regex(".*?(V([0-9]+)_([0-9]+))$"); LogicAnalyzerDriver[] connectedDevices; UInt128[][]? tempCapture; @@ -79,7 +79,24 @@ namespace SharedDriver } if (ver == null) + { ver = mVer.Groups[1].Value; + + if (mVer == null || !mVer.Success || !mVer.Groups[2].Success) + { + Dispose(); + throw new DeviceConnectionException($"Invalid device version V{(string.IsNullOrWhiteSpace(mVer?.Value) ? "(unknown)" : mVer?.Value)}, minimum supported version: V5_0"); + } + + int majorVer = int.Parse(mVer.Groups[2].Value); + + if (majorVer < 5) + { + Dispose(); + throw new DeviceConnectionException($"Invalid device version V{mVer.Value}, minimum supported version: V5_0"); + } + + } else { if (ver != mVer.Groups[1].Value) @@ -96,7 +113,7 @@ namespace SharedDriver { throw new NotSupportedException(); } - public CaptureError StartCapture(int Frequency, int PreSamples, int PostSamples, int[] Channels, int TriggerChannel, bool TriggerInverted, Action? CaptureCompletedHandler = null) + public CaptureError StartCapture(int Frequency, int PreSamples, int PostSamples, int LoopCount, int[] Channels, int TriggerChannel, bool TriggerInverted, Action? CaptureCompletedHandler = null) { throw new NotSupportedException(); } @@ -163,7 +180,7 @@ namespace SharedDriver continue; connectedDevices[buc].Tag = channelsCapturing; - var err = connectedDevices[buc].StartCapture(Frequency, PreSamples + offset, PostSamples - offset, chan, 24, false); + var err = connectedDevices[buc].StartCapture(Frequency, PreSamples + offset, PostSamples - offset, 0, chan, 24, false); if (err != CaptureError.None) {