add: 为逻辑分析仪添加了深度、预存储深度、通道组设置
This commit is contained in:
parent
3257a68407
commit
6b701658d1
|
@ -45,7 +45,18 @@ public class LogicAnalyzerController : ControllerBase
|
|||
/// 全局触发模式
|
||||
/// </summary>
|
||||
public GlobalCaptureMode GlobalMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 捕获深度
|
||||
/// </summary>
|
||||
public int CaptureLength { get; set; } = 2048 * 32;
|
||||
/// <summary>
|
||||
/// 预采样深度
|
||||
/// </summary>
|
||||
public int PreCaptureLength { get; set; } = 2048;
|
||||
/// <summary>
|
||||
/// 有效通道
|
||||
/// </summary>
|
||||
public AnalyzerChannelDiv ChannelDiv { get; set; } = AnalyzerChannelDiv.EIGHT;
|
||||
/// <summary>
|
||||
/// 信号触发配置列表
|
||||
/// </summary>
|
||||
|
@ -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
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置深度、预采样深度、有效通道
|
||||
/// </summary>
|
||||
/// <param name="capture_length">深度</param>
|
||||
/// <param name="pre_capture_length">预采样深度</param>
|
||||
/// <param name="channel_div">有效通道(0-[1],1-[2],2-[4],3-[8],4-[16],5-[32])</param>
|
||||
/// <returns>操作结果</returns>
|
||||
[HttpPost("SetCaptureParams")]
|
||||
[EnableCors("Users")]
|
||||
[ProducesResponseType(typeof(bool), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
|
||||
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
|
||||
public async Task<IActionResult> 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, "操作失败,请稍后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量配置捕获参数
|
||||
/// </summary>
|
||||
|
@ -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<IActionResult> GetCaptureData()
|
||||
public async Task<IActionResult> 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}");
|
||||
|
|
|
@ -46,17 +46,26 @@ static class AnalyzerAddr
|
|||
/// 111 SOME NUMBER <br/>
|
||||
/// </summary>
|
||||
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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 逻辑分析仪有效通道数
|
||||
/// </summary>
|
||||
public enum AnalyzerChannelDiv
|
||||
{
|
||||
/// <summary>
|
||||
/// 1路
|
||||
/// </summary>
|
||||
ONE = 0x0000_0000,
|
||||
/// <summary>
|
||||
/// 2路
|
||||
/// </summary>
|
||||
TWO = 0x0000_0001,
|
||||
/// <summary>
|
||||
/// 4路
|
||||
/// </summary>
|
||||
FOUR = 0x0000_0002,
|
||||
/// <summary>
|
||||
/// 8路
|
||||
/// </summary>
|
||||
EIGHT = 0x0000_0003,
|
||||
/// <summary>
|
||||
/// 16路
|
||||
/// </summary>
|
||||
XVI = 0x0000_0004,
|
||||
/// <summary>
|
||||
/// 32路
|
||||
/// </summary>
|
||||
XXXII = 0x0000_0005
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FPGA逻辑分析仪客户端,用于控制FPGA上的逻辑分析仪模块进行信号捕获和分析
|
||||
/// </summary>
|
||||
|
@ -239,58 +279,6 @@ public class Analyzer
|
|||
/// <returns>操作结果,成功返回true,否则返回异常信息</returns>
|
||||
public async ValueTask<Result<bool>> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置逻辑分析仪的深度、预采样深度、有效通道
|
||||
/// </summary>
|
||||
/// <param name="capture_length">深度</param>
|
||||
/// <param name="pre_capture_length">预采样深度</param>
|
||||
/// <param name="channel_div">有效通道(0-[1],1-[2],2-[4],3-[8],4-[16],5-[32])</param>
|
||||
/// <returns>操作结果,成功返回true,否则返回异常信息</returns>
|
||||
public async ValueTask<Result<bool>> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 读取捕获的波形数据
|
||||
/// </summary>
|
||||
/// <returns>操作结果,成功返回byte[],否则返回异常信息</returns>
|
||||
public async ValueTask<Result<byte[]>> ReadCaptureData()
|
||||
public async ValueTask<Result<byte[]>> 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"));
|
||||
|
|
123
src/APIClient.ts
123
src/APIClient.ts
|
@ -3678,6 +3678,92 @@ export class LogicAnalyzerClient {
|
|||
return Promise.resolve<boolean>(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<boolean> {
|
||||
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<boolean> {
|
||||
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 : <any>null;
|
||||
|
||||
return Promise.resolve<boolean>(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<boolean>(null as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量配置捕获参数
|
||||
* @param config 捕获配置
|
||||
|
@ -3827,10 +3913,15 @@ export class LogicAnalyzerClient {
|
|||
|
||||
/**
|
||||
* 读取捕获数据
|
||||
* @param capture_length (optional)
|
||||
* @return 捕获的波形数据(Base64编码)
|
||||
*/
|
||||
getCaptureData( cancelToken?: CancelToken): Promise<string> {
|
||||
let url_ = this.baseUrl + "/api/LogicAnalyzer/GetCaptureData";
|
||||
getCaptureData(capture_length: number | undefined, cancelToken?: CancelToken): Promise<string> {
|
||||
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[];
|
||||
}
|
||||
|
|
|
@ -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>(GlobalCaptureMode.AND);
|
||||
const currentChannelDiv = ref<number>(8); // 默认启用8个通道
|
||||
const captureLength = ref<number>(1024); // 捕获深度,默认1024
|
||||
const preCaptureLength = ref<number>(0); // 预捕获深度,默认0
|
||||
const isApplying = ref(false);
|
||||
const isCapturing = ref(false); // 添加捕获状态标识
|
||||
|
||||
// 通道配置
|
||||
const channels = reactive<Channel[]>(
|
||||
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<SignalTriggerConfig[]>(
|
||||
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,
|
||||
|
|
|
@ -21,60 +21,7 @@
|
|||
<h3 class="text-xl font-semibold text-slate-600 mb-2">
|
||||
暂无逻辑分析数据
|
||||
</h3>
|
||||
<p class="text-sm text-slate-500">点击下方按钮开始捕获</p>
|
||||
</div>
|
||||
|
||||
<!-- <button
|
||||
class="group relative px-8 py-3 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 focus:ring-blue-300 active:scale-95"
|
||||
@click="analyzer.generateTestData"
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<RefreshCcw
|
||||
class="w-5 h-5 group-hover:rotate-180 transition-transform duration-300"
|
||||
/>
|
||||
生成测试数据
|
||||
</span>
|
||||
<div
|
||||
class="absolute inset-0 bg-white opacity-0 group-hover:opacity-20 rounded-lg transition-opacity duration-200"
|
||||
></div>
|
||||
</button> -->
|
||||
<button
|
||||
class="group relative px-8 py-3 bg-gradient-to-r text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 active:scale-95"
|
||||
:class="{
|
||||
'from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 focus:ring-blue-300':
|
||||
!analyzer.isCapturing.value,
|
||||
'from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 focus:ring-red-300':
|
||||
analyzer.isCapturing.value,
|
||||
}"
|
||||
@click="
|
||||
analyzer.isCapturing.value
|
||||
? analyzer.stopCapture()
|
||||
: analyzer.startCapture()
|
||||
"
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<template v-if="analyzer.isCapturing.value">
|
||||
<Square class="w-5 h-5" />
|
||||
停止捕获
|
||||
</template>
|
||||
<template v-else>
|
||||
<Play class="w-5 h-5" />
|
||||
开始捕获
|
||||
</template>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- 强制捕获按钮 - 只在正在捕获时显示 -->
|
||||
<button
|
||||
v-if="analyzer.isCapturing.value"
|
||||
class="group relative px-8 py-3 bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white font-medium rounded-lg shadow-lg hover:shadow-xl transform hover:scale-105 transition-all duration-200 ease-in-out focus:outline-none focus:ring-4 focus:ring-orange-300 active:scale-95"
|
||||
@click="analyzer.forceCapture()"
|
||||
>
|
||||
<span class="flex items-center gap-2">
|
||||
<Square class="w-5 h-5" />
|
||||
强制捕获
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -82,7 +29,6 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, shallowRef } from "vue";
|
||||
import VChart from "vue-echarts";
|
||||
import { RefreshCcw, Play, Square } from "lucide-vue-next";
|
||||
|
||||
// Echarts
|
||||
import { use } from "echarts/core";
|
||||
|
|
|
@ -1,345 +1,175 @@
|
|||
<template>
|
||||
<div class="space-y-6">
|
||||
<!-- 通道状态概览 -->
|
||||
<div class="stats stats-horizontal bg-base-100 shadow flex justify-between">
|
||||
<div class="stat">
|
||||
<div class="stat-title">总通道数</div>
|
||||
<div class="stat-value text-primary">8</div>
|
||||
<div class="stat-desc">逻辑分析仪通道</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">启用通道</div>
|
||||
<div class="stat-value text-success">{{ enabledChannelCount }}</div>
|
||||
<div class="stat-desc">当前激活通道</div>
|
||||
</div>
|
||||
|
||||
<div class="stat">
|
||||
<div class="stat-title">采样率</div>
|
||||
<div class="stat-value text-info">5MHz</div>
|
||||
<div class="stat-desc">最大采样频率</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通道配置 -->
|
||||
<div class="form-control">
|
||||
<!-- 全局触发模式选择 -->
|
||||
<div class="flex flex-row justify-between my-4 mx-2">
|
||||
<div class="flex flex-row gap-4">
|
||||
<label class="label">
|
||||
<span class="label-text text-sm">全局触发逻辑</span>
|
||||
</label>
|
||||
<select
|
||||
v-model="currentGlobalMode"
|
||||
@change="setGlobalMode(currentGlobalMode)"
|
||||
class="select select-sm select-bordered w-full"
|
||||
>
|
||||
<option
|
||||
v-for="mode in globalModes"
|
||||
:key="mode.value"
|
||||
:value="mode.value"
|
||||
<!-- 全局触发模式选择和通道组配置 -->
|
||||
<div class="flex flex-col lg:flex-row justify-between gap-4 my-4 mx-2">
|
||||
<!-- 左侧:全局触发模式和通道组选择 -->
|
||||
<div class="flex flex-col lg:flex-row gap-4">
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<label class="label">
|
||||
<span class="label-text text-sm">全局触发逻辑</span>
|
||||
</label>
|
||||
<select
|
||||
v-model="currentGlobalMode"
|
||||
@change="setGlobalMode(currentGlobalMode)"
|
||||
class="select select-sm select-bordered"
|
||||
>
|
||||
{{ mode.label }} - {{ mode.description }}
|
||||
</option>
|
||||
</select>
|
||||
<option
|
||||
v-for="mode in globalModes"
|
||||
:key="mode.value"
|
||||
:value="mode.value"
|
||||
>
|
||||
{{ mode.label }} - {{ mode.description }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<label class="label">
|
||||
<span class="label-text text-sm">通道组</span>
|
||||
</label>
|
||||
<select
|
||||
v-model="currentChannelDiv"
|
||||
@change="setChannelDiv(currentChannelDiv)"
|
||||
class="select select-sm select-bordered"
|
||||
>
|
||||
<option
|
||||
v-for="option in channelDivOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<label class="label">
|
||||
<span class="label-text text-sm">捕获深度</span>
|
||||
</label>
|
||||
<select
|
||||
v-model="captureLength"
|
||||
class="select select-sm select-bordered"
|
||||
>
|
||||
<option
|
||||
v-for="option in captureLengthOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 items-center">
|
||||
<label class="label">
|
||||
<span class="label-text text-sm">预捕获深度</span>
|
||||
</label>
|
||||
<select
|
||||
v-model="preCaptureLength"
|
||||
class="select select-sm select-bordered"
|
||||
>
|
||||
<option
|
||||
v-for="option in preCaptureLengthOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-4">
|
||||
<button @click="toggleAllChannels" class="btn btn-primary btn-sm">
|
||||
{{ enabledChannelCount > 0 ? "全部禁用" : "全部启用" }}
|
||||
</button>
|
||||
<button
|
||||
@click="applyConfiguration"
|
||||
:disabled="isApplying"
|
||||
class="btn btn-primary btn-sm"
|
||||
>
|
||||
<span
|
||||
v-if="isApplying"
|
||||
class="loading loading-spinner loading-sm"
|
||||
></span>
|
||||
应用配置
|
||||
</button>
|
||||
|
||||
<!-- 右侧:操作按钮 -->
|
||||
<div class="flex flex-row gap-2">
|
||||
<button @click="resetConfiguration" class="btn btn-outline btn-sm">
|
||||
重置
|
||||
重置配置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 通道列表 -->
|
||||
<div class="space-y-2">
|
||||
<!-- 表头 - 小屏幕单列时显示 -->
|
||||
<!-- 表头 -->
|
||||
<div
|
||||
class="flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium lg:hidden"
|
||||
class="flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
|
||||
>
|
||||
<span class="w-16">通道</span>
|
||||
<span class="w-20">启用/触发</span>
|
||||
<span class="w-32">标签</span>
|
||||
<span class="w-16">颜色</span>
|
||||
<span class="w-32">触发操作</span>
|
||||
<span class="w-32">触发值</span>
|
||||
</div>
|
||||
|
||||
<!-- 通道配置网格 -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<!-- 左列 (CH0-CH3) -->
|
||||
<div class="space-y-2">
|
||||
<!-- 左列表头 - 大屏幕时显示 -->
|
||||
<div
|
||||
class="hidden lg:flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
|
||||
>
|
||||
<span class="w-16">通道</span>
|
||||
<span class="w-20">启用</span>
|
||||
<span class="w-32">标签</span>
|
||||
<span class="w-16">颜色</span>
|
||||
<span class="w-32">触发操作</span>
|
||||
<span class="w-32">触发值</span>
|
||||
<!-- 通道配置网格 - 根据当前通道组动态显示 -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-4">
|
||||
<div
|
||||
v-for="(channel, index) in channels.filter(ch => ch.enabled)"
|
||||
:key="index"
|
||||
class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
|
||||
>
|
||||
<!-- 通道编号和颜色指示 -->
|
||||
<div class="flex items-center gap-2 w-16">
|
||||
<span class="font-mono font-medium">CH{{ channels.indexOf(channel) }}</span>
|
||||
<div
|
||||
class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
|
||||
:style="{ backgroundColor: channel.color }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- 左列通道 (0-3) -->
|
||||
<div
|
||||
v-for="(channel, index) in channels.slice(0, 4)"
|
||||
:key="index"
|
||||
class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
|
||||
>
|
||||
<!-- 通道编号和颜色指示 -->
|
||||
<div class="flex items-center gap-2 w-16">
|
||||
<span class="font-mono font-medium">CH{{ index }}</span>
|
||||
<div
|
||||
class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
|
||||
:style="{ backgroundColor: channel.color }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- 通道启用开关(同时控制触发) -->
|
||||
<div class="form-control w-20">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="channel.enabled"
|
||||
class="toggle toggle-sm toggle-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 通道标签 -->
|
||||
<div class="form-control w-32">
|
||||
<input
|
||||
type="text"
|
||||
v-model="channel.label"
|
||||
:placeholder="`通道 ${index}`"
|
||||
class="input input-sm input-bordered w-full"
|
||||
:disabled="!channel.enabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 颜色选择 -->
|
||||
<div class="form-control w-16">
|
||||
<input
|
||||
type="color"
|
||||
v-model="channel.color"
|
||||
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
|
||||
:disabled="!channel.enabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 触发操作符选择 -->
|
||||
<select
|
||||
v-model="signalConfigs[index].operator"
|
||||
class="select select-sm select-bordered w-32"
|
||||
:disabled="!channel.enabled"
|
||||
>
|
||||
<option
|
||||
v-for="op in operators"
|
||||
:key="op.value"
|
||||
:value="op.value"
|
||||
>
|
||||
{{ op.label }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<!-- 触发信号值选择 -->
|
||||
<select
|
||||
v-model="signalConfigs[index].value"
|
||||
class="select select-sm select-bordered w-32"
|
||||
:disabled="!channel.enabled"
|
||||
>
|
||||
<option
|
||||
v-for="val in signalValues"
|
||||
:key="val.value"
|
||||
:value="val.value"
|
||||
>
|
||||
{{ val.label }}
|
||||
</option>
|
||||
</select>
|
||||
<!-- 通道标签 -->
|
||||
<div class="form-control w-32">
|
||||
<input
|
||||
type="text"
|
||||
v-model="channel.label"
|
||||
:placeholder="`通道 ${channels.indexOf(channel)}`"
|
||||
class="input input-sm input-bordered w-full"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 颜色选择 -->
|
||||
<div class="form-control w-16">
|
||||
<input
|
||||
type="color"
|
||||
v-model="channel.color"
|
||||
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 触发操作符选择 -->
|
||||
<select
|
||||
v-model="signalConfigs[channels.indexOf(channel)].operator"
|
||||
class="select select-sm select-bordered w-32"
|
||||
>
|
||||
<option
|
||||
v-for="op in operators"
|
||||
:key="op.value"
|
||||
:value="op.value"
|
||||
>
|
||||
{{ op.label }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<!-- 触发信号值选择 -->
|
||||
<select
|
||||
v-model="signalConfigs[channels.indexOf(channel)].value"
|
||||
class="select select-sm select-bordered w-32"
|
||||
>
|
||||
<option
|
||||
v-for="val in signalValues"
|
||||
:key="val.value"
|
||||
:value="val.value"
|
||||
>
|
||||
{{ val.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右列 (CH4-CH7) - 仅在大屏幕显示 -->
|
||||
<div class="hidden lg:block space-y-2">
|
||||
<!-- 右列表头 -->
|
||||
<div
|
||||
class="flex items-center gap-2 p-2 bg-base-300 rounded-lg text-sm font-medium"
|
||||
>
|
||||
<span class="w-16">通道</span>
|
||||
<span class="w-20">启用/触发</span>
|
||||
<span class="w-32">标签</span>
|
||||
<span class="w-16">颜色</span>
|
||||
<span class="w-32">触发操作</span>
|
||||
<span class="w-32">触发值</span>
|
||||
</div>
|
||||
|
||||
<!-- 右列通道 (4-7) -->
|
||||
<div
|
||||
v-for="(channel, index) in channels.slice(4, 8)"
|
||||
:key="index + 4"
|
||||
class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
|
||||
>
|
||||
<!-- 通道编号和颜色指示 -->
|
||||
<div class="flex items-center gap-2 w-16">
|
||||
<span class="font-mono font-medium">CH{{ index + 4 }}</span>
|
||||
<div
|
||||
class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
|
||||
:style="{ backgroundColor: channel.color }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- 通道启用开关(同时控制触发) -->
|
||||
<div class="form-control w-20">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="channel.enabled"
|
||||
class="toggle toggle-sm toggle-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 通道标签 -->
|
||||
<div class="form-control w-32">
|
||||
<input
|
||||
type="text"
|
||||
v-model="channel.label"
|
||||
:placeholder="`通道 ${index + 4}`"
|
||||
class="input input-sm input-bordered w-full"
|
||||
:disabled="!channel.enabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 颜色选择 -->
|
||||
<div class="form-control w-16">
|
||||
<input
|
||||
type="color"
|
||||
v-model="channel.color"
|
||||
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
|
||||
:disabled="!channel.enabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 触发操作符选择 -->
|
||||
<select
|
||||
v-model="signalConfigs[index + 4].operator"
|
||||
class="select select-sm select-bordered w-32"
|
||||
:disabled="!channel.enabled"
|
||||
>
|
||||
<option
|
||||
v-for="op in operators"
|
||||
:key="op.value"
|
||||
:value="op.value"
|
||||
>
|
||||
{{ op.label }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<!-- 触发信号值选择 -->
|
||||
<select
|
||||
v-model="signalConfigs[index + 4].value"
|
||||
class="select select-sm select-bordered w-32"
|
||||
:disabled="!channel.enabled"
|
||||
>
|
||||
<option
|
||||
v-for="val in signalValues"
|
||||
:key="val.value"
|
||||
:value="val.value"
|
||||
>
|
||||
{{ val.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 小屏幕时继续显示 CH4-CH7 -->
|
||||
<div class="lg:hidden space-y-2">
|
||||
<div
|
||||
v-for="(channel, index) in channels.slice(4, 8)"
|
||||
:key="index + 4"
|
||||
class="flex items-center gap-2 p-3 bg-base-200 rounded-lg hover:bg-base-300 transition-colors"
|
||||
>
|
||||
<!-- 通道编号和颜色指示 -->
|
||||
<div class="flex items-center gap-2 w-16">
|
||||
<span class="font-mono font-medium">CH{{ index + 4 }}</span>
|
||||
<div
|
||||
class="w-3 h-3 rounded-full border-2 border-white shadow-sm"
|
||||
:style="{ backgroundColor: channel.color }"
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<!-- 通道启用开关(同时控制触发) -->
|
||||
<div class="form-control w-20">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="channel.enabled"
|
||||
class="toggle toggle-sm toggle-primary"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 通道标签 -->
|
||||
<div class="form-control w-32">
|
||||
<input
|
||||
type="text"
|
||||
v-model="channel.label"
|
||||
:placeholder="`通道 ${index + 4}`"
|
||||
class="input input-sm input-bordered w-full"
|
||||
:disabled="!channel.enabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 颜色选择 -->
|
||||
<div class="form-control w-16">
|
||||
<input
|
||||
type="color"
|
||||
v-model="channel.color"
|
||||
class="w-8 h-8 rounded border-2 border-base-300 cursor-pointer"
|
||||
:disabled="!channel.enabled"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 触发操作符选择 -->
|
||||
<select
|
||||
v-model="signalConfigs[index + 4].operator"
|
||||
class="select select-sm select-bordered w-32"
|
||||
:disabled="!channel.enabled"
|
||||
>
|
||||
<option
|
||||
v-for="op in operators"
|
||||
:key="op.value"
|
||||
:value="op.value"
|
||||
>
|
||||
{{ op.label }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<!-- 触发信号值选择 -->
|
||||
<select
|
||||
v-model="signalConfigs[index + 4].value"
|
||||
class="select select-sm select-bordered w-32"
|
||||
:disabled="!channel.enabled"
|
||||
>
|
||||
<option
|
||||
v-for="val in signalValues"
|
||||
:key="val.value"
|
||||
:value="val.value"
|
||||
>
|
||||
{{ val.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 当没有启用通道时的提示 -->
|
||||
<div v-if="enabledChannelCount === 0" class="text-center py-8 text-base-content/60">
|
||||
<p class="text-lg font-medium">未启用任何通道</p>
|
||||
<p class="text-sm">请选择通道组来配置逻辑分析仪</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -352,6 +182,9 @@ import { useLogicAnalyzerState } from "./LogicAnalyzerManager";
|
|||
|
||||
const {
|
||||
currentGlobalMode,
|
||||
currentChannelDiv,
|
||||
captureLength,
|
||||
preCaptureLength,
|
||||
isApplying,
|
||||
channels,
|
||||
signalConfigs,
|
||||
|
@ -359,18 +192,11 @@ const {
|
|||
globalModes,
|
||||
operators,
|
||||
signalValues,
|
||||
enableAllChannels,
|
||||
disableAllChannels,
|
||||
channelDivOptions,
|
||||
captureLengthOptions,
|
||||
preCaptureLengthOptions,
|
||||
setChannelDiv,
|
||||
setGlobalMode,
|
||||
applyConfiguration,
|
||||
resetConfiguration,
|
||||
} = useRequiredInjection(useLogicAnalyzerState);
|
||||
|
||||
const toggleAllChannels = () => {
|
||||
if (enabledChannelCount.value > 0) {
|
||||
disableAllChannels();
|
||||
} else {
|
||||
enableAllChannels();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,135 +1,135 @@
|
|||
<template>
|
||||
<div class="flex flex-col bg-base-100 justify-center items-center">
|
||||
<!-- Title -->
|
||||
<h1 class="font-bold text-2xl">上传比特流文件</h1>
|
||||
|
||||
<!-- Input File -->
|
||||
<fieldset class="fieldset w-full">
|
||||
<legend class="fieldset-legend text-sm">选择或拖拽上传文件</legend>
|
||||
<input type="file" ref="fileInput" class="file-input w-full" @change="handleFileChange" />
|
||||
<label class="fieldset-label">文件最大容量: {{ maxMemory }}MB</label>
|
||||
</fieldset>
|
||||
|
||||
<!-- Upload Button -->
|
||||
<div class="card-actions w-full">
|
||||
<button @click="handleClick" class="btn btn-primary grow" :disabled="isUploading">
|
||||
<div v-if="isUploading">
|
||||
<span class="loading loading-spinner"></span>
|
||||
下载中...
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ buttonText }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useTemplateRef, onMounted } from "vue";
|
||||
import { useDialogStore } from "@/stores/dialog";
|
||||
import { isNull, isUndefined } from "lodash";
|
||||
|
||||
interface Props {
|
||||
uploadEvent?: (file: File) => Promise<boolean>;
|
||||
downloadEvent?: () => Promise<boolean>;
|
||||
maxMemory?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
maxMemory: 4,
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
finishedUpload: [file: File];
|
||||
}>();
|
||||
|
||||
const dialog = useDialogStore();
|
||||
|
||||
const isUploading = ref(false);
|
||||
const buttonText = computed(() => {
|
||||
return isUndefined(props.downloadEvent) ? "上传" : "上传并下载";
|
||||
});
|
||||
|
||||
const fileInput = useTemplateRef("fileInput");
|
||||
const bitstream = defineModel("bitstreamFile", {
|
||||
type: File,
|
||||
default: undefined,
|
||||
});
|
||||
onMounted(() => {
|
||||
if (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
|
||||
let fileList = new DataTransfer();
|
||||
fileList.items.add(bitstream.value);
|
||||
fileInput.value.files = fileList.files;
|
||||
}
|
||||
});
|
||||
|
||||
function handleFileChange(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const file = target.files?.[0]; // 获取选中的第一个文件
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
bitstream.value = file;
|
||||
}
|
||||
|
||||
function checkFile(file: File): boolean {
|
||||
const maxBytes = props.maxMemory! * 1024 * 1024; // 将最大容量从 MB 转换为字节
|
||||
if (file.size > maxBytes) {
|
||||
dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleClick(event: Event): Promise<void> {
|
||||
if (isNull(bitstream.value) || isUndefined(bitstream.value)) {
|
||||
dialog.error(`未选择文件`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkFile(bitstream.value)) return;
|
||||
if (isUndefined(props.uploadEvent)) {
|
||||
dialog.error("无法上传");
|
||||
return;
|
||||
}
|
||||
|
||||
isUploading.value = true;
|
||||
try {
|
||||
const ret = await props.uploadEvent(bitstream.value);
|
||||
if (isUndefined(props.downloadEvent)) {
|
||||
if (ret) {
|
||||
dialog.info("上传成功");
|
||||
emits("finishedUpload", bitstream.value);
|
||||
} else dialog.error("上传失败");
|
||||
return;
|
||||
}
|
||||
if (!ret) {
|
||||
isUploading.value = false;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
dialog.error("上传失败");
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Download
|
||||
try {
|
||||
const ret = await props.downloadEvent();
|
||||
if (ret) dialog.info("下载成功");
|
||||
else dialog.error("下载失败");
|
||||
} catch (e) {
|
||||
dialog.error("下载失败");
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
isUploading.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
@import "../assets/main.css";
|
||||
</style>
|
||||
<template>
|
||||
<div class="flex flex-col bg-base-100 justify-center items-center">
|
||||
<!-- Title -->
|
||||
<h1 class="font-bold text-2xl">上传比特流文件</h1>
|
||||
|
||||
<!-- Input File -->
|
||||
<fieldset class="fieldset w-full">
|
||||
<legend class="fieldset-legend text-sm">选择或拖拽上传文件</legend>
|
||||
<input type="file" ref="fileInput" class="file-input w-full" @change="handleFileChange" />
|
||||
<label class="fieldset-label">文件最大容量: {{ maxMemory }}MB</label>
|
||||
</fieldset>
|
||||
|
||||
<!-- Upload Button -->
|
||||
<div class="card-actions w-full">
|
||||
<button @click="handleClick" class="btn btn-primary grow" :disabled="isUploading">
|
||||
<div v-if="isUploading">
|
||||
<span class="loading loading-spinner"></span>
|
||||
下载中...
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ buttonText }}
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, useTemplateRef, onMounted } from "vue";
|
||||
import { useDialogStore } from "@/stores/dialog";
|
||||
import { isNull, isUndefined } from "lodash";
|
||||
|
||||
interface Props {
|
||||
uploadEvent?: (file: File) => Promise<boolean>;
|
||||
downloadEvent?: () => Promise<boolean>;
|
||||
maxMemory?: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
maxMemory: 4,
|
||||
});
|
||||
|
||||
const emits = defineEmits<{
|
||||
finishedUpload: [file: File];
|
||||
}>();
|
||||
|
||||
const dialog = useDialogStore();
|
||||
|
||||
const isUploading = ref(false);
|
||||
const buttonText = computed(() => {
|
||||
return isUndefined(props.downloadEvent) ? "上传" : "上传并下载";
|
||||
});
|
||||
|
||||
const fileInput = useTemplateRef("fileInput");
|
||||
const bitstream = defineModel("bitstreamFile", {
|
||||
type: File,
|
||||
default: undefined,
|
||||
});
|
||||
onMounted(() => {
|
||||
if (!isUndefined(bitstream.value) && !isNull(fileInput.value)) {
|
||||
let fileList = new DataTransfer();
|
||||
fileList.items.add(bitstream.value);
|
||||
fileInput.value.files = fileList.files;
|
||||
}
|
||||
});
|
||||
|
||||
function handleFileChange(event: Event): void {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const file = target.files?.[0]; // 获取选中的第一个文件
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
bitstream.value = file;
|
||||
}
|
||||
|
||||
function checkFile(file: File): boolean {
|
||||
const maxBytes = props.maxMemory! * 1024 * 1024; // 将最大容量从 MB 转换为字节
|
||||
if (file.size > maxBytes) {
|
||||
dialog.error(`文件大小超过最大限制: ${props.maxMemory}MB`);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function handleClick(event: Event): Promise<void> {
|
||||
if (isNull(bitstream.value) || isUndefined(bitstream.value)) {
|
||||
dialog.error(`未选择文件`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkFile(bitstream.value)) return;
|
||||
if (isUndefined(props.uploadEvent)) {
|
||||
dialog.error("无法上传");
|
||||
return;
|
||||
}
|
||||
|
||||
isUploading.value = true;
|
||||
try {
|
||||
const ret = await props.uploadEvent(bitstream.value);
|
||||
if (isUndefined(props.downloadEvent)) {
|
||||
if (ret) {
|
||||
dialog.info("上传成功");
|
||||
emits("finishedUpload", bitstream.value);
|
||||
} else dialog.error("上传失败");
|
||||
return;
|
||||
}
|
||||
if (!ret) {
|
||||
isUploading.value = false;
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
dialog.error("上传失败");
|
||||
console.error(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Download
|
||||
try {
|
||||
const ret = await props.downloadEvent();
|
||||
if (ret) dialog.info("下载成功");
|
||||
else dialog.error("下载失败");
|
||||
} catch (e) {
|
||||
dialog.error("下载失败");
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
isUploading.value = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="postcss">
|
||||
@import "../assets/main.css";
|
||||
</style>
|
||||
|
|
|
@ -9,8 +9,42 @@
|
|||
逻辑信号分析
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- 空闲状态:只显示开始捕获按钮 -->
|
||||
<button
|
||||
v-if="!analyzer.isCapturing.value"
|
||||
@click="analyzer.startCapture"
|
||||
:disabled="analyzer.isApplying.value"
|
||||
class="btn btn-sm btn-primary"
|
||||
>
|
||||
开始捕获
|
||||
</button>
|
||||
|
||||
<!-- 捕获状态:显示停止捕获和强制捕获按钮 -->
|
||||
<button
|
||||
v-if="analyzer.isCapturing.value"
|
||||
@click="analyzer.stopCapture"
|
||||
class="btn btn-sm btn-warning"
|
||||
>
|
||||
<span class="loading loading-spinner loading-sm"></span>
|
||||
停止捕获
|
||||
</button>
|
||||
<button
|
||||
v-if="analyzer.isCapturing.value"
|
||||
@click="analyzer.forceCapture"
|
||||
class="btn btn-sm btn-secondary"
|
||||
>
|
||||
强制捕获
|
||||
</button>
|
||||
|
||||
<!-- 其他按钮保持不变 -->
|
||||
<button
|
||||
@click="analyzer.generateTestData"
|
||||
class="btn btn-sm btn-info"
|
||||
>
|
||||
测试数据
|
||||
</button>
|
||||
<button class="btn btn-sm btn-error" @click="handleDeleteData">
|
||||
清空
|
||||
清空数据
|
||||
</button>
|
||||
</div>
|
||||
</h2>
|
||||
|
@ -21,9 +55,45 @@
|
|||
<!-- 触发设置 -->
|
||||
<div class="card bg-base-200 shadow-xl mx-5">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">
|
||||
<Settings class="w-5 h-5" />
|
||||
触发设置
|
||||
<h2 class="card-title flex justify-between items-center">
|
||||
<div class="flex gap-8">
|
||||
<div class="flex items-center gap-2">
|
||||
<Settings class="w-5 h-5" />
|
||||
触发设置
|
||||
</div>
|
||||
<!-- 配置摘要 -->
|
||||
<div class="flex items-center gap-4 text-sm text-gray-500">
|
||||
<span>{{ analyzer.enabledChannelCount.value }}/32 通道</span>
|
||||
<span>捕获: {{ analyzer.captureLength.value }}</span>
|
||||
<span>预捕获: {{ analyzer.preCaptureLength.value }}</span>
|
||||
<span>{{ analyzer.globalModes.find(m => m.value === analyzer.currentGlobalMode.value)?.label || '未知' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- 状态指示 -->
|
||||
<div class="flex items-center gap-2 text-sm">
|
||||
<span
|
||||
v-if="analyzer.isCapturing.value"
|
||||
class="flex items-center gap-1 text-warning"
|
||||
>
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
捕获中
|
||||
</span>
|
||||
<span
|
||||
v-else-if="analyzer.isApplying.value"
|
||||
class="flex items-center gap-1 text-info"
|
||||
>
|
||||
<span class="loading loading-spinner loading-xs"></span>
|
||||
配置中
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
class="text-success"
|
||||
>
|
||||
就绪
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</h2>
|
||||
<TriggerSettings />
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue