feat: 添加嵌入式逻辑分析仪
This commit is contained in:
parent
8e19587a16
commit
e3b769b24e
|
@ -111,7 +111,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
|||
new SignalTriggerConfig({
|
||||
signalIndex: index,
|
||||
operator: SignalOperator.Equal,
|
||||
value: SignalValue.Logic1,
|
||||
value: SignalValue.NotCare,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
@ -229,7 +229,7 @@ const [useProvideLogicAnalyzer, useLogicAnalyzerState] = createInjectionState(
|
|||
|
||||
signalConfigs.forEach((signal) => {
|
||||
signal.operator = SignalOperator.Equal;
|
||||
signal.value = SignalValue.Logic1;
|
||||
signal.value = SignalValue.NotCare;
|
||||
});
|
||||
|
||||
alert?.info("配置已重置", 2000);
|
||||
|
|
|
@ -0,0 +1,238 @@
|
|||
<template>
|
||||
<div
|
||||
class="w-full"
|
||||
:class="{
|
||||
'h-48': !analyzer.logicData.value,
|
||||
'h-150': analyzer.logicData.value,
|
||||
}"
|
||||
>
|
||||
<v-chart
|
||||
v-if="analyzer.logicData.value"
|
||||
class="w-full h-full"
|
||||
:option="option"
|
||||
autoresize
|
||||
:update-options="updateOptions"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
class="w-full h-full flex flex-col gap-6 items-center justify-center"
|
||||
>
|
||||
<div class="text-center">
|
||||
<h3 class="text-xl font-semibold text-slate-600 mb-2">
|
||||
暂无逻辑分析数据
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, shallowRef } from "vue";
|
||||
import VChart from "vue-echarts";
|
||||
|
||||
// Echarts
|
||||
import { use } from "echarts/core";
|
||||
import { LineChart } from "echarts/charts";
|
||||
import {
|
||||
TooltipComponent,
|
||||
GridComponent,
|
||||
DataZoomComponent,
|
||||
AxisPointerComponent,
|
||||
ToolboxComponent,
|
||||
} from "echarts/components";
|
||||
import { CanvasRenderer } from "echarts/renderers";
|
||||
import type { ComposeOption } from "echarts/core";
|
||||
import type { LineSeriesOption } from "echarts/charts";
|
||||
import type {
|
||||
AxisPointerComponentOption,
|
||||
TooltipComponentOption,
|
||||
GridComponentOption,
|
||||
DataZoomComponentOption,
|
||||
} from "echarts/components";
|
||||
import type {
|
||||
ToolboxComponentOption,
|
||||
XAXisOption,
|
||||
YAXisOption,
|
||||
} from "echarts/types/dist/shared";
|
||||
import { useRequiredInjection } from "@/utils/Common";
|
||||
import { isUndefined } from "lodash";
|
||||
import { useWaveformManager } from "./WaveformManager";
|
||||
|
||||
use([
|
||||
TooltipComponent,
|
||||
ToolboxComponent,
|
||||
GridComponent,
|
||||
AxisPointerComponent,
|
||||
DataZoomComponent,
|
||||
LineChart,
|
||||
CanvasRenderer,
|
||||
]);
|
||||
|
||||
type EChartsOption = ComposeOption<
|
||||
| AxisPointerComponentOption
|
||||
| TooltipComponentOption
|
||||
| ToolboxComponentOption
|
||||
| GridComponentOption
|
||||
| DataZoomComponentOption
|
||||
| LineSeriesOption
|
||||
>;
|
||||
|
||||
const analyzer = useRequiredInjection(useWaveformManager);
|
||||
|
||||
// 添加更新选项来减少重绘
|
||||
const updateOptions = shallowRef({
|
||||
notMerge: false,
|
||||
lazyUpdate: true,
|
||||
silent: false,
|
||||
});
|
||||
|
||||
const option = computed((): EChartsOption => {
|
||||
if (isUndefined(analyzer.logicData.value)) return {};
|
||||
|
||||
// 只获取启用的通道,使用y数据结构
|
||||
const enabledChannels = analyzer.logicData.value.y.filter(channel => channel.enabled);
|
||||
const enabledChannelIndices = analyzer.logicData.value.y
|
||||
.map((channel, index) => (channel.enabled ? index : -1))
|
||||
.filter((index) => index !== -1);
|
||||
|
||||
const channelCount = enabledChannels.length;
|
||||
const channelSpacing = 2; // 每个通道之间的间距
|
||||
|
||||
// 如果没有启用的通道,返回空配置
|
||||
if (channelCount === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// 使用单个网格
|
||||
const grids: GridComponentOption[] = [
|
||||
{
|
||||
left: "5%",
|
||||
right: "5%",
|
||||
top: "5%",
|
||||
bottom: "15%",
|
||||
},
|
||||
];
|
||||
|
||||
// 单个X轴
|
||||
const xAxis: XAXisOption[] = [
|
||||
{
|
||||
type: "category",
|
||||
boundaryGap: false,
|
||||
data: analyzer.logicData.value.x.map((x) => x.toFixed(3)),
|
||||
axisLabel: {
|
||||
formatter: (value: string) =>
|
||||
analyzer.logicData.value
|
||||
? `${value}${analyzer.logicData.value.xUnit}`
|
||||
: `${value}`,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 单个Y轴,范围根据启用通道数量调整
|
||||
const yAxis: YAXisOption[] = [
|
||||
{
|
||||
type: "value",
|
||||
min: -0.5,
|
||||
max: channelCount * channelSpacing - 0.5,
|
||||
interval: channelSpacing,
|
||||
axisLabel: {
|
||||
formatter: (value: number) => {
|
||||
const channelIndex = Math.round(value / channelSpacing);
|
||||
return channelIndex < channelCount
|
||||
? enabledChannels[channelIndex].name
|
||||
: "";
|
||||
},
|
||||
},
|
||||
splitLine: { show: false },
|
||||
},
|
||||
];
|
||||
|
||||
// 创建系列数据,只包含启用的通道
|
||||
const series: LineSeriesOption[] = enabledChannelIndices.map(
|
||||
(originalIndex: number, displayIndex: number) => ({
|
||||
name: enabledChannels[displayIndex].name,
|
||||
type: "line",
|
||||
data: analyzer.logicData.value!.y[originalIndex].value.map(
|
||||
(value: number) => value + displayIndex * channelSpacing + 0.2,
|
||||
),
|
||||
step: "end",
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: enabledChannels[displayIndex].color,
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.3,
|
||||
origin: displayIndex * channelSpacing,
|
||||
color: enabledChannels[displayIndex].color,
|
||||
},
|
||||
symbol: "none",
|
||||
// 优化性能配置
|
||||
sampling: "lttb",
|
||||
// 减少动画以避免闪烁
|
||||
animation: false,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
// 全局动画配置
|
||||
animation: false,
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
axisPointer: {
|
||||
type: "line",
|
||||
label: {
|
||||
backgroundColor: "#6a7985",
|
||||
},
|
||||
// 减少axisPointer的动画
|
||||
animation: false,
|
||||
},
|
||||
formatter: (params: any) => {
|
||||
if (Array.isArray(params) && params.length > 0) {
|
||||
const timeValue = analyzer.logicData.value!.x[params[0].dataIndex];
|
||||
const dataIndex = params[0].dataIndex;
|
||||
|
||||
let tooltip = `Time: ${timeValue.toFixed(3)}${analyzer.logicData.value!.xUnit}<br/>`;
|
||||
|
||||
// 只显示启用通道在当前时间点的原始数值(0或1)
|
||||
enabledChannelIndices.forEach(
|
||||
(originalIndex: number, displayIndex: number) => {
|
||||
const channelName = enabledChannels[displayIndex].name;
|
||||
const originalValue =
|
||||
analyzer.logicData.value!.y[originalIndex].value[dataIndex];
|
||||
tooltip += `${channelName}: ${originalValue}<br/>`;
|
||||
},
|
||||
);
|
||||
|
||||
return tooltip;
|
||||
}
|
||||
return "";
|
||||
},
|
||||
// 优化tooltip性能
|
||||
hideDelay: 100,
|
||||
},
|
||||
toolbox: {
|
||||
feature: {
|
||||
restore: {},
|
||||
},
|
||||
},
|
||||
grid: grids,
|
||||
xAxis: xAxis,
|
||||
yAxis: yAxis,
|
||||
dataZoom: [
|
||||
{
|
||||
show: true,
|
||||
realtime: true,
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
{
|
||||
type: "inside",
|
||||
realtime: true,
|
||||
start: 0,
|
||||
end: 100,
|
||||
},
|
||||
],
|
||||
series: series,
|
||||
};
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,73 @@
|
|||
import { createInjectionState } from "@vueuse/core";
|
||||
import { shallowRef } from "vue";
|
||||
|
||||
export type LogicDataType = {
|
||||
x: number[];
|
||||
y: {
|
||||
enabled: boolean;
|
||||
type: "logic" | "number";
|
||||
name: string;
|
||||
color: string;
|
||||
value: number[];
|
||||
base: "bin" | "dec" | "hex";
|
||||
}[];
|
||||
xUnit: "s" | "ms" | "us" | "ns";
|
||||
};
|
||||
|
||||
// 生成4路测试数据的函数
|
||||
export function generateTestData(): LogicDataType {
|
||||
// 生成时间轴数据 (0-100ns,每1ns一个采样点)
|
||||
const timePoints = Array.from({ length: 101 }, (_, i) => i);
|
||||
|
||||
return {
|
||||
x: timePoints,
|
||||
y: [
|
||||
{
|
||||
enabled: true,
|
||||
type: "logic",
|
||||
name: "CLK",
|
||||
color: "#ff0000",
|
||||
value: timePoints.map((t) => t % 2), // 时钟信号,每1ns翻转
|
||||
base: "bin",
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
type: "logic",
|
||||
name: "RESET",
|
||||
color: "#00ff00",
|
||||
value: timePoints.map((t) => (t < 10 ? 1 : 0)), // 复位信号,前10ns为高电平
|
||||
base: "bin",
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
type: "number",
|
||||
name: "DATA",
|
||||
color: "#0000ff",
|
||||
value: timePoints.map((t) => Math.floor(t / 4) % 16), // 计数器,每4ns递增
|
||||
base: "hex",
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
type: "logic",
|
||||
name: "ENABLE",
|
||||
color: "#ff8800",
|
||||
value: timePoints.map((t) => (t >= 20 && t < 80 ? 1 : 0)), // 使能信号,20-80ns为高电平
|
||||
base: "bin",
|
||||
},
|
||||
],
|
||||
xUnit: "ns",
|
||||
};
|
||||
}
|
||||
|
||||
const [useProvideWaveformManager, useWaveformManager] = createInjectionState(
|
||||
() => {
|
||||
const logicData = shallowRef<LogicDataType>();
|
||||
|
||||
return {
|
||||
logicData,
|
||||
generateTestData,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export { useProvideWaveformManager, useWaveformManager };
|
|
@ -0,0 +1,5 @@
|
|||
import WaveformDisplay from "./WaveformDisplay.vue";
|
||||
|
||||
export {
|
||||
WaveformDisplay
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="h-full flex flex-col gap-7">
|
||||
<div class="tabs tabs-box flex-shrink-0 shadow-xl">
|
||||
<div class="tabs tabs-box flex-shrink-0 shadow-xl mx-5">
|
||||
<label class="tab">
|
||||
<input
|
||||
type="radio"
|
||||
|
@ -42,9 +42,20 @@
|
|||
:checked="checkID === 4"
|
||||
@change="handleTabChange"
|
||||
/>
|
||||
<Zap class="icon" />
|
||||
<Binary class="icon" />
|
||||
逻辑分析仪
|
||||
</label>
|
||||
<label class="tab">
|
||||
<input
|
||||
type="radio"
|
||||
name="function-bar"
|
||||
id="5"
|
||||
:checked="checkID === 5"
|
||||
@change="handleTabChange"
|
||||
/>
|
||||
<Hand class="icon" />
|
||||
嵌入式逻辑分析仪
|
||||
</label>
|
||||
<!-- 全屏按钮 -->
|
||||
<button
|
||||
class="fullscreen-btn ml-auto btn btn-ghost btn-sm"
|
||||
|
@ -67,6 +78,9 @@
|
|||
<div v-else-if="checkID === 4" class="h-full overflow-y-auto">
|
||||
<LogicAnalyzerView />
|
||||
</div>
|
||||
<div v-else-if="checkID === 5" class="h-full overflow-y-auto">
|
||||
<Debugger />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -78,7 +92,8 @@ import {
|
|||
TerminalIcon,
|
||||
MaximizeIcon,
|
||||
MinimizeIcon,
|
||||
Zap,
|
||||
Binary,
|
||||
Hand,
|
||||
} from "lucide-vue-next";
|
||||
import { useLocalStorage } from "@vueuse/core";
|
||||
import VideoStreamView from "@/views/Project/VideoStream.vue";
|
||||
|
@ -86,8 +101,13 @@ import OscilloscopeView from "@/views/Project/Oscilloscope.vue";
|
|||
import LogicAnalyzerView from "@/views/Project/LogicAnalyzer.vue";
|
||||
import { isNull, toNumber } from "lodash";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { im } from "mathjs";
|
||||
import Debugger from "./Debugger.vue";
|
||||
import { useProvideLogicAnalyzer } from "@/components/LogicAnalyzer";
|
||||
import { useProvideWaveformManager } from "@/components/WaveformDisplay/WaveformManager";
|
||||
|
||||
const analyzer = useProvideLogicAnalyzer();
|
||||
const waveformManager = useProvideWaveformManager();
|
||||
waveformManager.logicData.value = waveformManager.generateTestData();
|
||||
|
||||
const checkID = useLocalStorage("checkID", 1);
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="card">
|
||||
<WaveformDisplay />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import WaveformDisplay from '@/components/WaveformDisplay/WaveformDisplay.vue';
|
||||
|
||||
</script>
|
|
@ -78,7 +78,7 @@
|
|||
id="splitter-group-v-panel-bar"
|
||||
:default-size="isBottomBarFullscreen ? 100 : (100 - verticalSplitterSize)"
|
||||
:min-size="isBottomBarFullscreen ? 100 : 15"
|
||||
class="w-full overflow-hidden px-5 pt-3"
|
||||
class="w-full overflow-hidden pt-3"
|
||||
>
|
||||
<BottomBar
|
||||
:isFullscreen="isBottomBarFullscreen"
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="bg-base-100 flex flex-col gap-10 mb-5">
|
||||
<!-- 逻辑信号展示 -->
|
||||
<div class="card bg-base-200 shadow-xl">
|
||||
<div class="card bg-base-200 shadow-xl mx-5">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
|
@ -19,7 +19,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 触发设置 -->
|
||||
<div class="card bg-base-200 shadow-xl">
|
||||
<div class="card bg-base-200 shadow-xl mx-5">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">
|
||||
<Settings class="w-5 h-5" />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="bg-base-100 flex flex-col">
|
||||
<!-- 波形展示 -->
|
||||
<div class="card bg-base-200 shadow-xl">
|
||||
<div class="card bg-base-200 shadow-xl mx-5">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">
|
||||
<Activity class="w-5 h-5" />
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="bg-base-100 flex flex-col gap-7">
|
||||
<!-- 控制面板 -->
|
||||
<div class="card bg-base-200 shadow-xl">
|
||||
<div class="card bg-base-200 shadow-xl mx-5">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-primary">
|
||||
<Settings class="w-6 h-6" />
|
||||
|
@ -153,7 +153,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 视频预览区域 -->
|
||||
<div class="card bg-base-200 shadow-xl">
|
||||
<div class="card bg-base-200 shadow-xl mx-5">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-primary">
|
||||
<Video class="w-6 h-6" />
|
||||
|
@ -299,7 +299,7 @@
|
|||
</div>
|
||||
|
||||
<!-- 日志区域 -->
|
||||
<div class="card bg-base-200 shadow-xl">
|
||||
<div class="card bg-base-200 shadow-xl mx-5">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title text-primary">
|
||||
<FileText class="w-6 h-6" />
|
||||
|
|
Loading…
Reference in New Issue