From 6b701658d16ba9a4383d9d843a0fa5dec4eda63b Mon Sep 17 00:00:00 2001 From: alivender <13898766233@163.com> Date: Thu, 31 Jul 2025 13:14:23 +0800 Subject: [PATCH] =?UTF-8?q?add:=20=E4=B8=BA=E9=80=BB=E8=BE=91=E5=88=86?= =?UTF-8?q?=E6=9E=90=E4=BB=AA=E6=B7=BB=E5=8A=A0=E4=BA=86=E6=B7=B1=E5=BA=A6?= =?UTF-8?q?=E3=80=81=E9=A2=84=E5=AD=98=E5=82=A8=E6=B7=B1=E5=BA=A6=E3=80=81?= =?UTF-8?q?=E9=80=9A=E9=81=93=E7=BB=84=E8=AE=BE=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/LogicAnalyzerController.cs | 77 ++- server/src/Peripherals/LogicAnalyzerClient.cs | 195 ++++--- src/APIClient.ts | 123 ++++- .../LogicAnalyzer/LogicAnalyzerManager.ts | 455 ++++++++++++----- .../LogicAnalyzer/LogicalWaveFormDisplay.vue | 54 -- .../LogicAnalyzer/TriggerSettings.vue | 478 ++++++------------ src/components/UploadCard.vue | 270 +++++----- src/views/Project/LogicAnalyzer.vue | 78 ++- 8 files changed, 1012 insertions(+), 718 deletions(-) diff --git a/server/src/Controllers/LogicAnalyzerController.cs b/server/src/Controllers/LogicAnalyzerController.cs index 7148252..f943700 100644 --- a/server/src/Controllers/LogicAnalyzerController.cs +++ b/server/src/Controllers/LogicAnalyzerController.cs @@ -45,7 +45,18 @@ public class LogicAnalyzerController : ControllerBase /// 全局触发模式 /// public GlobalCaptureMode GlobalMode { get; set; } - + /// + /// 捕获深度 + /// + public int CaptureLength { get; set; } = 2048 * 32; + /// + /// 预采样深度 + /// + public int PreCaptureLength { get; set; } = 2048; + /// + /// 有效通道 + /// + public AnalyzerChannelDiv ChannelDiv { get; set; } = AnalyzerChannelDiv.EIGHT; /// /// 信号触发配置列表 /// @@ -77,7 +88,7 @@ public class LogicAnalyzerController : ControllerBase return null; var board = boardRet.Value.Value; - return new Analyzer(board.IpAddr, board.Port, 2); + return new Analyzer(board.IpAddr, board.Port, 0); } catch (Exception ex) { @@ -208,8 +219,8 @@ public class LogicAnalyzerController : ControllerBase { try { - if (signalIndex < 0 || signalIndex > 7) - return BadRequest("信号索引必须在0-7之间"); + if (signalIndex < 0 || signalIndex > 31) + return BadRequest("信号索引必须在0-31之间"); var analyzer = GetAnalyzer(); if (analyzer == null) @@ -231,6 +242,48 @@ public class LogicAnalyzerController : ControllerBase } } + /// + /// 设置深度、预采样深度、有效通道 + /// + /// 深度 + /// 预采样深度 + /// 有效通道(0-[1],1-[2],2-[4],3-[8],4-[16],5-[32]) + /// 操作结果 + [HttpPost("SetCaptureParams")] + [EnableCors("Users")] + [ProducesResponseType(typeof(bool), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status500InternalServerError)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + public async Task SetCaptureParams(int capture_length, int pre_capture_length, AnalyzerChannelDiv channel_div) + { + try + { + if (capture_length < 0 || capture_length > 2048*32) + return BadRequest("采样深度设置错误"); + if (pre_capture_length < 0 || pre_capture_length >= capture_length) + return BadRequest("预采样深度必须小于捕获深度"); + + var analyzer = GetAnalyzer(); + if (analyzer == null) + return BadRequest("用户未绑定有效的实验板"); + + var result = await analyzer.SetCaptureParams(capture_length, pre_capture_length, channel_div); + if (!result.IsSuccessful) + { + logger.Error($"设置深度、预采样深度、有效通道失败: {result.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置深度、预采样深度、有效通道失败"); + } + + return Ok(result.Value); + } + catch (Exception ex) + { + logger.Error(ex, "设置深度、预采样深度、有效通道失败时发生异常"); + return StatusCode(StatusCodes.Status500InternalServerError, "操作失败,请稍后重试"); + } + } + /// /// 批量配置捕获参数 /// @@ -264,8 +317,8 @@ public class LogicAnalyzerController : ControllerBase // 设置信号触发模式 foreach (var signalConfig in config.SignalConfigs) { - if (signalConfig.SignalIndex < 0 || signalConfig.SignalIndex > 7) - return BadRequest($"信号索引{signalConfig.SignalIndex}超出范围0-7"); + if (signalConfig.SignalIndex < 0 || signalConfig.SignalIndex > 31) + return BadRequest($"信号索引{signalConfig.SignalIndex}超出范围0-31"); var signalResult = await analyzer.SetSignalTrigMode( signalConfig.SignalIndex, signalConfig.Operator, signalConfig.Value); @@ -276,6 +329,14 @@ public class LogicAnalyzerController : ControllerBase $"设置信号{signalConfig.SignalIndex}触发模式失败"); } } + // 设置深度、预采样深度、有效通道 + var paramsResult = await analyzer.SetCaptureParams( + config.CaptureLength, config.PreCaptureLength, config.ChannelDiv); + if (!paramsResult.IsSuccessful) + { + logger.Error($"设置深度、预采样深度、有效通道失败: {paramsResult.Error}"); + return StatusCode(StatusCodes.Status500InternalServerError, "设置深度、预采样深度、有效通道失败"); + } return Ok(true); } @@ -330,7 +391,7 @@ public class LogicAnalyzerController : ControllerBase [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status500InternalServerError)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] - public async Task GetCaptureData() + public async Task GetCaptureData(int capture_length = 2048 * 32) { try { @@ -338,7 +399,7 @@ public class LogicAnalyzerController : ControllerBase if (analyzer == null) return BadRequest("用户未绑定有效的实验板"); - var result = await analyzer.ReadCaptureData(); + var result = await analyzer.ReadCaptureData(capture_length); if (!result.IsSuccessful) { logger.Error($"读取捕获数据失败: {result.Error}"); diff --git a/server/src/Peripherals/LogicAnalyzerClient.cs b/server/src/Peripherals/LogicAnalyzerClient.cs index db0a43e..2913fd5 100644 --- a/server/src/Peripherals/LogicAnalyzerClient.cs +++ b/server/src/Peripherals/LogicAnalyzerClient.cs @@ -46,17 +46,26 @@ static class AnalyzerAddr /// 111 SOME NUMBER /// public static readonly UInt32[] SIGNAL_TRIG_MODE = { - BASE + 0x0000_0010, - BASE + 0x0000_0011, - BASE + 0x0000_0012, - BASE + 0x0000_0013, - BASE + 0x0000_0014, - BASE + 0x0000_0015, - BASE + 0x0000_0016, - BASE + 0x0000_0017, + BASE + 0x0000_0010, BASE + 0x0000_0011, + BASE + 0x0000_0012, BASE + 0x0000_0013, + BASE + 0x0000_0014, BASE + 0x0000_0015, + BASE + 0x0000_0016, BASE + 0x0000_0017, + BASE + 0x0000_0018, BASE + 0x0000_0019, + BASE + 0x0000_001A, BASE + 0x0000_001B, + BASE + 0x0000_001C, BASE + 0x0000_001D, + BASE + 0x0000_001E, BASE + 0x0000_001F, + BASE + 0x0000_0020, BASE + 0x0000_0021, + BASE + 0x0000_0022, BASE + 0x0000_0023, + BASE + 0x0000_0024, BASE + 0x0000_0025, + BASE + 0x0000_0026, BASE + 0x0000_0027, + BASE + 0x0000_0028, BASE + 0x0000_0029, + BASE + 0x0000_002A, BASE + 0x0000_002B, + BASE + 0x0000_002C, BASE + 0x0000_002D, + BASE + 0x0000_002E, BASE + 0x0000_002F }; public const UInt32 LOAD_NUM_ADDR = BASE + 0x0000_0002; public const UInt32 PRE_LOAD_NUM_ADDR = BASE + 0x0000_0003; + public const UInt32 CAHNNEL_DIV_ADDR = BASE + 0x0000_0004; public const UInt32 DMA1_START_WRITE_ADDR = DMA1_BASE + 0x0000_0012; public const UInt32 DMA1_END_WRITE_ADDR = DMA1_BASE + 0x0000_0013; public const UInt32 DMA1_CAPTURE_CTRL_ADDR = DMA1_BASE + 0x0000_0014; @@ -198,6 +207,37 @@ public enum SignalValue : byte SomeNumber = 0b111 // SOME NUMBER } +/// +/// 逻辑分析仪有效通道数 +/// +public enum AnalyzerChannelDiv +{ + /// + /// 1路 + /// + ONE = 0x0000_0000, + /// + /// 2路 + /// + TWO = 0x0000_0001, + /// + /// 4路 + /// + FOUR = 0x0000_0002, + /// + /// 8路 + /// + EIGHT = 0x0000_0003, + /// + /// 16路 + /// + XVI = 0x0000_0004, + /// + /// 32路 + /// + XXXII = 0x0000_0005 +} + /// /// FPGA逻辑分析仪客户端,用于控制FPGA上的逻辑分析仪模块进行信号捕获和分析 /// @@ -239,58 +279,6 @@ public class Analyzer /// 操作结果,成功返回true,否则返回异常信息 public async ValueTask> SetCaptureMode(bool captureOn, bool force) { - { - var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.LOAD_NUM_ADDR, AnalyzerAddr.CAPTURE_DATA_LENGTH - 1, this.timeout); - if (!ret.IsSuccessful) - { - logger.Error($"Failed to set LOAD_NUM_ADDR: {ret.Error}"); - return new(ret.Error); - } - if (!ret.Value) - { - logger.Error("WriteAddr to LOAD_NUM_ADDR returned false"); - return new(new Exception("Failed to set LOAD_NUM_ADDR")); - } - } - { - var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.PRE_LOAD_NUM_ADDR, AnalyzerAddr.CAPTURE_DATA_PRELOAD - 1, this.timeout); - if (!ret.IsSuccessful) - { - logger.Error($"Failed to set PRE_LOAD_NUM_ADDR: {ret.Error}"); - return new(ret.Error); - } - if (!ret.Value) - { - logger.Error("WriteAddr to PRE_LOAD_NUM_ADDR returned false"); - return new(new Exception("Failed to set PRE_LOAD_NUM_ADDR")); - } - } - { - var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_START_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR, this.timeout); - if (!ret.IsSuccessful) - { - logger.Error($"Failed to set DMA1_START_WRITE_ADDR: {ret.Error}"); - return new(ret.Error); - } - if (!ret.Value) - { - logger.Error("WriteAddr to DMA1_START_WRITE_ADDR returned false"); - return new(new Exception("Failed to set DMA1_START_WRITE_ADDR")); - } - } - { - var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_END_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR + AnalyzerAddr.CAPTURE_DATA_LENGTH - 1, this.timeout); - if (!ret.IsSuccessful) - { - logger.Error($"Failed to set DMA1_END_WRITE_ADDR: {ret.Error}"); - return new(ret.Error); - } - if (!ret.Value) - { - logger.Error("WriteAddr to DMA1_END_WRITE_ADDR returned false"); - return new(new Exception("Failed to set DMA1_END_WRITE_ADDR")); - } - } // 构造寄存器值 UInt32 value = 0; if (captureOn) value |= 1 << 0; @@ -380,7 +368,7 @@ public class Analyzer return new(new ArgumentException($"Signal index must be 0~{AnalyzerAddr.SIGNAL_TRIG_MODE.Length}")); // 计算模式值: [2:0] 信号值, [5:3] 操作符 - UInt32 mode = ((UInt32)op << 3) | (UInt32) val; + UInt32 mode = ((UInt32)op << 3) | (UInt32)val; var addr = AnalyzerAddr.SIGNAL_TRIG_MODE[signalIndex]; var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, addr, mode, this.timeout); @@ -397,17 +385,96 @@ public class Analyzer return true; } + /// + /// 设置逻辑分析仪的深度、预采样深度、有效通道 + /// + /// 深度 + /// 预采样深度 + /// 有效通道(0-[1],1-[2],2-[4],3-[8],4-[16],5-[32]) + /// 操作结果,成功返回true,否则返回异常信息 + public async ValueTask> SetCaptureParams(int capture_length, int pre_capture_length, AnalyzerChannelDiv channel_div) + { + if (capture_length == 0) capture_length = 1; + if (pre_capture_length == 0) pre_capture_length = 1; + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.LOAD_NUM_ADDR, (UInt32)(capture_length - 1), this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set LOAD_NUM_ADDR: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to LOAD_NUM_ADDR returned false"); + return new(new Exception("Failed to set LOAD_NUM_ADDR")); + } + } + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.PRE_LOAD_NUM_ADDR, (UInt32)(pre_capture_length - 1), this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set PRE_LOAD_NUM_ADDR: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to PRE_LOAD_NUM_ADDR returned false"); + return new(new Exception("Failed to set PRE_LOAD_NUM_ADDR")); + } + } + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_START_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set DMA1_START_WRITE_ADDR: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to DMA1_START_WRITE_ADDR returned false"); + return new(new Exception("Failed to set DMA1_START_WRITE_ADDR")); + } + } + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.DMA1_END_WRITE_ADDR, AnalyzerAddr.STORE_OFFSET_ADDR + (UInt32)(capture_length - 1), this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set DMA1_END_WRITE_ADDR: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to DMA1_END_WRITE_ADDR returned false"); + return new(new Exception("Failed to set DMA1_END_WRITE_ADDR")); + } + } + { + var ret = await UDPClientPool.WriteAddr(this.ep, this.taskID, AnalyzerAddr.CAHNNEL_DIV_ADDR, (UInt32)channel_div, this.timeout); + if (!ret.IsSuccessful) + { + logger.Error($"Failed to set CAHNNEL_DIV_ADDR: {ret.Error}"); + return new(ret.Error); + } + if (!ret.Value) + { + logger.Error("WriteAddr to CAHNNEL_DIV_ADDR returned false"); + return new(new Exception("Failed to set CAHNNEL_DIV_ADDR")); + } + } + return true; + } + /// /// 读取捕获的波形数据 /// /// 操作结果,成功返回byte[],否则返回异常信息 - public async ValueTask> ReadCaptureData() + public async ValueTask> ReadCaptureData(int capture_length = 2048 * 32) { var ret = await UDPClientPool.ReadAddr4BytesAsync( this.ep, this.taskID, AnalyzerAddr.STORE_OFFSET_ADDR, - AnalyzerAddr.CAPTURE_DATA_LENGTH, + capture_length, this.timeout ); if (!ret.IsSuccessful) @@ -416,7 +483,7 @@ public class Analyzer return new(ret.Error); } var data = ret.Value; - if (data == null || data.Length != AnalyzerAddr.CAPTURE_DATA_LENGTH * 4) + if (data == null || data.Length != capture_length * 4) { logger.Error($"Capture data length mismatch: {data?.Length}"); return new(new Exception("Capture data length mismatch")); diff --git a/src/APIClient.ts b/src/APIClient.ts index f712998..23f2beb 100644 --- a/src/APIClient.ts +++ b/src/APIClient.ts @@ -3678,6 +3678,92 @@ export class LogicAnalyzerClient { return Promise.resolve(null as any); } + /** + * 设置深度、预采样深度、有效通道 + * @param capture_length (optional) 深度 + * @param pre_capture_length (optional) 预采样深度 + * @param channel_div (optional) 有效通道(0-[1],1-[2],2-[4],3-[8],4-[16],5-[32]) + * @return 操作结果 + */ + setCaptureParams(capture_length: number | undefined, pre_capture_length: number | undefined, channel_div: AnalyzerChannelDiv | undefined, cancelToken?: CancelToken): Promise { + let url_ = this.baseUrl + "/api/LogicAnalyzer/SetCaptureParams?"; + if (capture_length === null) + throw new Error("The parameter 'capture_length' cannot be null."); + else if (capture_length !== undefined) + url_ += "capture_length=" + encodeURIComponent("" + capture_length) + "&"; + if (pre_capture_length === null) + throw new Error("The parameter 'pre_capture_length' cannot be null."); + else if (pre_capture_length !== undefined) + url_ += "pre_capture_length=" + encodeURIComponent("" + pre_capture_length) + "&"; + if (channel_div === null) + throw new Error("The parameter 'channel_div' cannot be null."); + else if (channel_div !== undefined) + url_ += "channel_div=" + encodeURIComponent("" + channel_div) + "&"; + url_ = url_.replace(/[?&]$/, ""); + + let options_: AxiosRequestConfig = { + method: "POST", + url: url_, + headers: { + "Accept": "application/json" + }, + cancelToken + }; + + return this.instance.request(options_).catch((_error: any) => { + if (isAxiosError(_error) && _error.response) { + return _error.response; + } else { + throw _error; + } + }).then((_response: AxiosResponse) => { + return this.processSetCaptureParams(_response); + }); + } + + protected processSetCaptureParams(response: AxiosResponse): Promise { + const status = response.status; + let _headers: any = {}; + if (response.headers && typeof response.headers === "object") { + for (const k in response.headers) { + if (response.headers.hasOwnProperty(k)) { + _headers[k] = response.headers[k]; + } + } + } + if (status === 200) { + const _responseText = response.data; + let result200: any = null; + let resultData200 = _responseText; + result200 = resultData200 !== undefined ? resultData200 : null; + + return Promise.resolve(result200); + + } else if (status === 400) { + const _responseText = response.data; + let result400: any = null; + let resultData400 = _responseText; + result400 = ProblemDetails.fromJS(resultData400); + return throwException("A server side error occurred.", status, _responseText, _headers, result400); + + } else if (status === 500) { + const _responseText = response.data; + return throwException("A server side error occurred.", status, _responseText, _headers); + + } else if (status === 401) { + const _responseText = response.data; + let result401: any = null; + let resultData401 = _responseText; + result401 = ProblemDetails.fromJS(resultData401); + return throwException("A server side error occurred.", status, _responseText, _headers, result401); + + } else if (status !== 200 && status !== 204) { + const _responseText = response.data; + return throwException("An unexpected server error occurred.", status, _responseText, _headers); + } + return Promise.resolve(null as any); + } + /** * 批量配置捕获参数 * @param config 捕获配置 @@ -3827,10 +3913,15 @@ export class LogicAnalyzerClient { /** * 读取捕获数据 + * @param capture_length (optional) * @return 捕获的波形数据(Base64编码) */ - getCaptureData( cancelToken?: CancelToken): Promise { - let url_ = this.baseUrl + "/api/LogicAnalyzer/GetCaptureData"; + getCaptureData(capture_length: number | undefined, cancelToken?: CancelToken): Promise { + let url_ = this.baseUrl + "/api/LogicAnalyzer/GetCaptureData?"; + if (capture_length === null) + throw new Error("The parameter 'capture_length' cannot be null."); + else if (capture_length !== undefined) + url_ += "capture_length=" + encodeURIComponent("" + capture_length) + "&"; url_ = url_.replace(/[?&]$/, ""); let options_: AxiosRequestConfig = { @@ -7237,10 +7328,26 @@ export enum SignalValue { SomeNumber = 7, } +/** 逻辑分析仪有效通道数 */ +export enum AnalyzerChannelDiv { + ONE = 0, + TWO = 1, + FOUR = 2, + EIGHT = 3, + XVI = 4, + XXXII = 5, +} + /** 捕获配置 */ export class CaptureConfig implements ICaptureConfig { /** 全局触发模式 */ globalMode!: GlobalCaptureMode; + /** 捕获深度 */ + captureLength!: number; + /** 预采样深度 */ + preCaptureLength!: number; + /** 有效通道 */ + channelDiv!: AnalyzerChannelDiv; /** 信号触发配置列表 */ signalConfigs!: SignalTriggerConfig[]; @@ -7259,6 +7366,9 @@ export class CaptureConfig implements ICaptureConfig { init(_data?: any) { if (_data) { this.globalMode = _data["globalMode"]; + this.captureLength = _data["captureLength"]; + this.preCaptureLength = _data["preCaptureLength"]; + this.channelDiv = _data["channelDiv"]; if (Array.isArray(_data["signalConfigs"])) { this.signalConfigs = [] as any; for (let item of _data["signalConfigs"]) @@ -7277,6 +7387,9 @@ export class CaptureConfig implements ICaptureConfig { toJSON(data?: any) { data = typeof data === 'object' ? data : {}; data["globalMode"] = this.globalMode; + data["captureLength"] = this.captureLength; + data["preCaptureLength"] = this.preCaptureLength; + data["channelDiv"] = this.channelDiv; if (Array.isArray(this.signalConfigs)) { data["signalConfigs"] = []; for (let item of this.signalConfigs) @@ -7290,6 +7403,12 @@ export class CaptureConfig implements ICaptureConfig { export interface ICaptureConfig { /** 全局触发模式 */ globalMode: GlobalCaptureMode; + /** 捕获深度 */ + captureLength: number; + /** 预采样深度 */ + preCaptureLength: number; + /** 有效通道 */ + channelDiv: AnalyzerChannelDiv; /** 信号触发配置列表 */ signalConfigs: SignalTriggerConfig[]; } diff --git a/src/components/LogicAnalyzer/LogicAnalyzerManager.ts b/src/components/LogicAnalyzer/LogicAnalyzerManager.ts index 52969a4..84b410f 100644 --- a/src/components/LogicAnalyzer/LogicAnalyzerManager.ts +++ b/src/components/LogicAnalyzer/LogicAnalyzerManager.ts @@ -9,6 +9,7 @@ import { SignalOperator, SignalTriggerConfig, SignalValue, + AnalyzerChannelDiv, } from "@/APIClient"; import { AuthManager } from "@/utils/AuthManager"; import { useAlertStore } from "@/components/Alert"; @@ -65,6 +66,39 @@ const signalValues = [ { value: SignalValue.SomeNumber, label: "#" }, ]; +// 通道组选项 +const channelDivOptions = [ + { value: 1, label: "1通道", description: "启用1个通道 (CH0)" }, + { value: 2, label: "2通道", description: "启用2个通道 (CH0-CH1)" }, + { value: 4, label: "4通道", description: "启用4个通道 (CH0-CH3)" }, + { value: 8, label: "8通道", description: "启用8个通道 (CH0-CH7)" }, + { value: 16, label: "16通道", description: "启用16个通道 (CH0-CH15)" }, + { value: 32, label: "32通道", description: "启用32个通道 (CH0-CH31)" }, +]; + +// 捕获深度选项 +const captureLengthOptions = [ + { value: 256, label: "256" }, + { value: 512, label: "512" }, + { value: 1024, label: "1K" }, + { value: 2048, label: "2K" }, + { value: 4096, label: "4K" }, + { value: 8192, label: "8K" }, + { value: 16384, label: "16K" }, + { value: 32768, label: "32K" }, +]; + +// 预捕获深度选项 +const preCaptureLengthOptions = [ + { value: 0, label: "0" }, + { value: 16, label: "16" }, + { value: 32, label: "32" }, + { value: 64, label: "64" }, + { value: 128, label: "128" }, + { value: 256, label: "256" }, + { value: 512, label: "512" }, +]; + // 默认颜色数组 const defaultColors = [ "#FF5733", @@ -78,7 +112,7 @@ const defaultColors = [ ]; // 添加逻辑分析仪频率常量 -const LOGIC_ANALYZER_FREQUENCY = 5_000_000; // 5MHz +const LOGIC_ANALYZER_FREQUENCY = 125_000_000; // 125MHz const SAMPLE_PERIOD_NS = 1_000_000_000 / LOGIC_ANALYZER_FREQUENCY; // 采样周期,单位:纳秒 const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( @@ -91,22 +125,25 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( // 触发设置相关状态 const currentGlobalMode = ref(GlobalCaptureMode.AND); + const currentChannelDiv = ref(8); // 默认启用8个通道 + const captureLength = ref(1024); // 捕获深度,默认1024 + const preCaptureLength = ref(0); // 预捕获深度,默认0 const isApplying = ref(false); const isCapturing = ref(false); // 添加捕获状态标识 // 通道配置 const channels = reactive( - Array.from({ length: 8 }, (_, index) => ({ - enabled: false, + Array.from({ length: 32 }, (_, index) => ({ + enabled: index < 8, // 默认启用前8个通道 label: `CH${index}`, - color: defaultColors[index], + color: defaultColors[index % defaultColors.length], // 使用模运算避免数组越界 })), ); - // 8个信号通道的配置 + // 32个信号通道的配置 const signalConfigs = reactive( Array.from( - { length: 8 }, + { length: 32 }, (_, index) => new SignalTriggerConfig({ signalIndex: index, @@ -131,101 +168,52 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( channels.filter((channel) => channel.enabled), ); - const enableAllChannels = () => { - channels.forEach((channel) => { - channel.enabled = true; - }); + // 转换通道数字到枚举值 + const getChannelDivEnum = (channelCount: number): AnalyzerChannelDiv => { + switch (channelCount) { + case 1: return AnalyzerChannelDiv.ONE; + case 2: return AnalyzerChannelDiv.TWO; + case 4: return AnalyzerChannelDiv.FOUR; + case 8: return AnalyzerChannelDiv.EIGHT; + case 16: return AnalyzerChannelDiv.XVI; + case 32: return AnalyzerChannelDiv.XXXII; + default: return AnalyzerChannelDiv.EIGHT; + } }; - const disableAllChannels = () => { + // 设置通道组 + const setChannelDiv = (channelCount: number) => { + // 验证通道数量是否有效 + if (!channelDivOptions.find(option => option.value === channelCount)) { + console.error(`无效的通道组设置: ${channelCount}`); + return; + } + currentChannelDiv.value = channelCount; + + // 禁用所有通道 channels.forEach((channel) => { channel.enabled = false; }); + + // 启用指定数量的通道(从CH0开始) + for (let i = 0; i < channelCount && i < channels.length; i++) { + channels[i].enabled = true; + } + + const option = channelDivOptions.find(opt => opt.value === channelCount); + alert?.success(`已设置为${option?.label}`, 2000); }; - const setGlobalMode = async (mode: GlobalCaptureMode) => { - // 检查是否有其他操作正在进行 - if (operationMutex.isLocked()) { - alert.warn("有其他操作正在进行中,请稍后再试", 3000); - return; - } - - const release = await operationMutex.acquire(); - try { - const client = AuthManager.createAuthenticatedLogicAnalyzerClient(); - const success = await client.setGlobalTrigMode(mode); - - if (success) { - currentGlobalMode.value = mode; - alert?.success( - `全局触发模式已设置为 ${globalModes.find((m) => m.value === mode)?.label}`, - 3000, - ); - } else { - throw new Error("设置失败"); - } - } catch (error) { - console.error("设置全局触发模式失败:", error); - alert?.error("设置全局触发模式失败", 3000); - } finally { - release(); - } - }; - - const applyConfiguration = async () => { - // 检查是否有其他操作正在进行 - if (operationMutex.isLocked()) { - alert.warn("有其他操作正在进行中,请稍后再试", 3000); - return; - } - - const release = await operationMutex.acquire(); - isApplying.value = true; - - try { - const client = AuthManager.createAuthenticatedLogicAnalyzerClient(); - - // 准备配置数据 - 只包含启用的通道 - const enabledSignals = signalConfigs.filter( - (signal, index) => channels[index].enabled, - ); - - const config = new CaptureConfig({ - globalMode: currentGlobalMode.value, - signalConfigs: enabledSignals, - }); - - // 发送配置 - const success = await client.configureCapture(config); - - if (success) { - const enabledChannelCount = channels.filter( - (ch) => ch.enabled, - ).length; - alert?.success( - `配置已成功应用,启用了 ${enabledChannelCount} 个通道和触发条件`, - 3000, - ); - } else { - throw new Error("应用配置失败"); - } - } catch (error) { - console.error("应用配置失败:", error); - alert?.error("应用配置失败,请检查设备连接", 3000); - } finally { - isApplying.value = false; - release(); - } + const setGlobalMode = (mode: GlobalCaptureMode) => { + currentGlobalMode.value = mode; + const modeOption = globalModes.find((m) => m.value === mode); + alert?.info(`全局触发模式已设置为 ${modeOption?.label}`, 2000); }; const resetConfiguration = () => { currentGlobalMode.value = GlobalCaptureMode.AND; - - channels.forEach((channel, index) => { - channel.enabled = false; - channel.label = `CH${index}`; - channel.color = defaultColors[index]; - }); + currentChannelDiv.value = 8; // 重置为默认的8通道 + setChannelDiv(8); // 重置为默认的8通道 signalConfigs.forEach((signal) => { signal.operator = SignalOperator.Equal; @@ -243,51 +231,223 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( const getCaptureData = async () => { try { const client = AuthManager.createAuthenticatedLogicAnalyzerClient(); - // 3. 获取捕获数据 - const base64Data = await client.getCaptureData(); + // 获取捕获数据,使用当前设置的捕获长度 + const base64Data = await client.getCaptureData(captureLength.value); - // 4. 将base64数据转换为bytes + // 将base64数据转换为bytes const binaryString = atob(base64Data); const bytes = new Uint8Array(binaryString.length); for (let i = 0; i < binaryString.length; i++) { bytes[i] = binaryString.charCodeAt(i); } - // 5. 解析数据为8个通道的数字信号 - const sampleCount = bytes.length; - const timeStepNs = SAMPLE_PERIOD_NS; // 每个采样点间隔200ns (1/5MHz) + // 根据当前通道数量解析数据 + const channelCount = currentChannelDiv.value; + const timeStepNs = SAMPLE_PERIOD_NS; + + let sampleCount: number; + let x: number[]; + let y: number[][]; - // 创建时间轴(转换为合适的单位) - const x = Array.from( - { length: sampleCount }, - (_, i) => (i * timeStepNs) / 1000, - ); // 转换为微秒 - - // 创建8个通道的数据 - const y: number[][] = Array.from( - { length: 8 }, - () => new Array(sampleCount), - ); - - // 解析每个字节的8个位到对应通道 - for (let i = 0; i < sampleCount; i++) { - const byte = bytes[i]; - for (let channel = 0; channel < 8; channel++) { - // bit0对应ch0, bit1对应ch1, ..., bit7对应ch7 - y[channel][i] = (byte >> channel) & 1; + if (channelCount === 1) { + // 1通道:每个字节包含8个时间单位的数据 + sampleCount = bytes.length * 8; + + // 创建时间轴 + x = Array.from( + { length: sampleCount }, + (_, i) => (i * timeStepNs) / 1000, + ); // 转换为微秒 + + // 创建通道数据数组 + y = Array.from( + { length: 1 }, + () => new Array(sampleCount), + ); + + // 解析数据:每个字节的8个位对应8个时间单位 + for (let byteIndex = 0; byteIndex < bytes.length; byteIndex++) { + const byte = bytes[byteIndex]; + for (let bitIndex = 0; bitIndex < 8; bitIndex++) { + const timeIndex = byteIndex * 8 + bitIndex; + y[0][timeIndex] = (byte >> bitIndex) & 1; + } } + } else if (channelCount === 2) { + // 2通道:每个字节包含4个时间单位的数据 + sampleCount = bytes.length * 4; + + // 创建时间轴 + x = Array.from( + { length: sampleCount }, + (_, i) => (i * timeStepNs) / 1000, + ); // 转换为微秒 + + // 创建通道数据数组 + y = Array.from( + { length: 2 }, + () => new Array(sampleCount), + ); + + // 解析数据:每个字节的8个位对应4个时间单位的2通道数据 + // 位分布:[T3_CH1, T3_CH0, T2_CH1, T2_CH0, T1_CH1, T1_CH0, T0_CH1, T0_CH0] + for (let byteIndex = 0; byteIndex < bytes.length; byteIndex++) { + const byte = bytes[byteIndex]; + for (let timeUnit = 0; timeUnit < 4; timeUnit++) { + const timeIndex = byteIndex * 4 + timeUnit; + const bitOffset = timeUnit * 2; + y[0][timeIndex] = (byte >> bitOffset) & 1; // CH0 + y[1][timeIndex] = (byte >> (bitOffset + 1)) & 1; // CH1 + } + } + } else if (channelCount === 4) { + // 4通道:每个字节包含2个时间单位的数据 + sampleCount = bytes.length * 2; + + // 创建时间轴 + x = Array.from( + { length: sampleCount }, + (_, i) => (i * timeStepNs) / 1000, + ); // 转换为微秒 + + // 创建通道数据数组 + y = Array.from( + { length: 4 }, + () => new Array(sampleCount), + ); + + // 解析数据:每个字节的8个位对应2个时间单位的4通道数据 + // 位分布:[T1_CH3, T1_CH2, T1_CH1, T1_CH0, T0_CH3, T0_CH2, T0_CH1, T0_CH0] + for (let byteIndex = 0; byteIndex < bytes.length; byteIndex++) { + const byte = bytes[byteIndex]; + + // 处理第一个时间单位(低4位) + const timeIndex1 = byteIndex * 2; + for (let channel = 0; channel < 4; channel++) { + y[channel][timeIndex1] = (byte >> channel) & 1; + } + + // 处理第二个时间单位(高4位) + const timeIndex2 = byteIndex * 2 + 1; + for (let channel = 0; channel < 4; channel++) { + y[channel][timeIndex2] = (byte >> (channel + 4)) & 1; + } + } + } else if (channelCount === 8) { + // 8通道:每个字节包含1个时间单位的8个通道数据 + sampleCount = bytes.length; + + // 创建时间轴 + x = Array.from( + { length: sampleCount }, + (_, i) => (i * timeStepNs) / 1000, + ); // 转换为微秒 + + // 创建8个通道的数据 + y = Array.from( + { length: 8 }, + () => new Array(sampleCount), + ); + + // 解析每个字节的8个位到对应通道 + for (let i = 0; i < sampleCount; i++) { + const byte = bytes[i]; + for (let channel = 0; channel < 8; channel++) { + // bit0对应ch0, bit1对应ch1, ..., bit7对应ch7 + y[channel][i] = (byte >> channel) & 1; + } + } + } else if (channelCount === 16) { + // 16通道:每2个字节包含1个时间单位的16个通道数据 + sampleCount = bytes.length / 2; + + // 创建时间轴 + x = Array.from( + { length: sampleCount }, + (_, i) => (i * timeStepNs) / 1000, + ); // 转换为微秒 + + // 创建16个通道的数据 + y = Array.from( + { length: 16 }, + () => new Array(sampleCount), + ); + + // 解析数据:每2个字节为一个时间单位 + for (let timeIndex = 0; timeIndex < sampleCount; timeIndex++) { + const byteIndex = timeIndex * 2; + const byte1 = bytes[byteIndex]; // [7:0] + const byte2 = bytes[byteIndex + 1]; // [15:8] + + // 处理低8位通道 [7:0] + for (let channel = 0; channel < 8; channel++) { + y[channel][timeIndex] = (byte1 >> channel) & 1; + } + + // 处理高8位通道 [15:8] + for (let channel = 0; channel < 8; channel++) { + y[channel + 8][timeIndex] = (byte2 >> channel) & 1; + } + } + } else if (channelCount === 32) { + // 32通道:每4个字节包含1个时间单位的32个通道数据 + sampleCount = bytes.length / 4; + + // 创建时间轴 + x = Array.from( + { length: sampleCount }, + (_, i) => (i * timeStepNs) / 1000, + ); // 转换为微秒 + + // 创建32个通道的数据 + y = Array.from( + { length: 32 }, + () => new Array(sampleCount), + ); + + // 解析数据:每4个字节为一个时间单位 + for (let timeIndex = 0; timeIndex < sampleCount; timeIndex++) { + const byteIndex = timeIndex * 4; + const byte1 = bytes[byteIndex]; // [7:0] + const byte2 = bytes[byteIndex + 1]; // [15:8] + const byte3 = bytes[byteIndex + 2]; // [23:16] + const byte4 = bytes[byteIndex + 3]; // [31:24] + + // 处理 [7:0] + for (let channel = 0; channel < 8; channel++) { + y[channel][timeIndex] = (byte1 >> channel) & 1; + } + + // 处理 [15:8] + for (let channel = 0; channel < 8; channel++) { + y[channel + 8][timeIndex] = (byte2 >> channel) & 1; + } + + // 处理 [23:16] + for (let channel = 0; channel < 8; channel++) { + y[channel + 16][timeIndex] = (byte3 >> channel) & 1; + } + + // 处理 [31:24] + for (let channel = 0; channel < 8; channel++) { + y[channel + 24][timeIndex] = (byte4 >> channel) & 1; + } + } + } else { + throw new Error(`不支持的通道数量: ${channelCount}`); } - // 6. 设置逻辑数据 + // 设置逻辑数据 const logicData: LogicDataType = { x, y, - xUnit: "us", // 改为微秒单位 + xUnit: "us", // 微秒单位 }; setLogicData(logicData); } catch (error) { console.error("获取捕获数据失败:", error); + alert?.error("获取捕获数据失败", 3000); } }; @@ -303,7 +463,45 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( try { const client = AuthManager.createAuthenticatedLogicAnalyzerClient(); - // 1. 设置捕获模式为开始捕获 + // 1. 先应用配置 + alert?.info("正在应用配置...", 2000); + + // 准备配置数据 - 包含所有32个通道,未启用的通道设置为默认值 + const allSignals = signalConfigs.map((signal, index) => { + if (channels[index].enabled) { + // 启用的通道使用用户配置的触发条件 + return signal; + } else { + // 未启用的通道设置为默认触发条件 + return new SignalTriggerConfig({ + signalIndex: index, + operator: SignalOperator.Equal, + value: SignalValue.NotCare, + }); + } + }); + + const config = new CaptureConfig({ + globalMode: currentGlobalMode.value, + channelDiv: getChannelDivEnum(currentChannelDiv.value), + captureLength: captureLength.value, + preCaptureLength: preCaptureLength.value, + signalConfigs: allSignals, + }); + + // 发送配置 + const configSuccess = await client.configureCapture(config); + if (!configSuccess) { + throw new Error("配置应用失败"); + } + + const enabledChannelCount = channels.filter((ch) => ch.enabled).length; + alert?.success( + `配置已应用,启用了 ${enabledChannelCount} 个通道,捕获深度: ${captureLength.value}`, + 2000, + ); + + // 2. 设置捕获模式为开始捕获 const captureStarted = await client.setCaptureMode(true, false); if (!captureStarted) { throw new Error("无法启动捕获"); @@ -311,7 +509,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( alert?.info("开始捕获信号...", 2000); - // 2. 轮询捕获状态 + // 3. 轮询捕获状态 let captureCompleted = false; while (isCapturing.value) { const status = await client.getCaptureStatus(); @@ -390,8 +588,11 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( }; const forceCapture = async () => { - // 设置捕获状态为false,这会使轮询停止 - isCapturing.value = false; + // 检查是否有其他操作正在进行 + if (operationMutex.isLocked()) { + alert.warn("有其他操作正在进行中,请稍后再试", 3000); + return; + } const release = await operationMutex.acquire(); try { @@ -404,7 +605,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( } await getCaptureData(); - alert.success(`捕获完成!`, 3000); + alert.success(`强制捕获完成!`, 3000); } catch (error) { console.error("强制捕获失败:", error); alert.error( @@ -487,7 +688,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( }); // 设置逻辑数据 - enableAllChannels(); + setChannelDiv(8); setLogicData({ x, y, xUnit: "us" }); // 改为微秒单位 alert?.success("测试数据生成成功", 2000); @@ -499,6 +700,9 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( // 触发设置状态 currentGlobalMode, + currentChannelDiv, // 导出当前通道组状态 + captureLength, // 导出捕获深度 + preCaptureLength, // 导出预捕获深度 isApplying, isCapturing, // 导出捕获状态 isOperationInProgress, // 导出操作进行状态 @@ -512,12 +716,13 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState( globalModes, operators, signalValues, + channelDivOptions, // 导出通道组选项 + captureLengthOptions, // 导出捕获深度选项 + preCaptureLengthOptions, // 导出预捕获深度选项 // 触发设置方法 - enableAllChannels, - disableAllChannels, + setChannelDiv, // 导出设置通道组方法 setGlobalMode, - applyConfiguration, resetConfiguration, setLogicData, startCapture, diff --git a/src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue b/src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue index 91a684f..b40fbe9 100644 --- a/src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue +++ b/src/components/LogicAnalyzer/LogicalWaveFormDisplay.vue @@ -21,60 +21,7 @@ 暂无逻辑分析数据 - 点击下方按钮开始捕获 - - - - - - - 停止捕获 - - - - 开始捕获 - - - - - - - - - 强制捕获 - - @@ -82,7 +29,6 @@ diff --git a/src/components/UploadCard.vue b/src/components/UploadCard.vue index 17a21e7..14058da 100644 --- a/src/components/UploadCard.vue +++ b/src/components/UploadCard.vue @@ -1,135 +1,135 @@ - - - - 上传比特流文件 - - - - 选择或拖拽上传文件 - - 文件最大容量: {{ maxMemory }}MB - - - - - - - - 下载中... - - - {{ buttonText }} - - - - - - - - - + + + + 上传比特流文件 + + + + 选择或拖拽上传文件 + + 文件最大容量: {{ maxMemory }}MB + + + + + + + + 下载中... + + + {{ buttonText }} + + + + + + + + + diff --git a/src/views/Project/LogicAnalyzer.vue b/src/views/Project/LogicAnalyzer.vue index da30927..91dd27f 100644 --- a/src/views/Project/LogicAnalyzer.vue +++ b/src/views/Project/LogicAnalyzer.vue @@ -9,8 +9,42 @@ 逻辑信号分析 + + + 开始捕获 + + + + + + 停止捕获 + + + 强制捕获 + + + + + 测试数据 + - 清空 + 清空数据 @@ -21,9 +55,45 @@ - - - 触发设置 + + + + + 触发设置 + + + + {{ analyzer.enabledChannelCount.value }}/32 通道 + 捕获: {{ analyzer.captureLength.value }} + 预捕获: {{ analyzer.preCaptureLength.value }} + {{ analyzer.globalModes.find(m => m.value === analyzer.currentGlobalMode.value)?.label || '未知' }} + + + + + + + + 捕获中 + + + + 配置中 + + + 就绪 + + +
点击下方按钮开始捕获