docs: add HWLAB first-principles showcase
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
const STAGES = [
|
||||
{
|
||||
title: "分析工程",
|
||||
summary: "识别 TIM2 输入捕获路径与 APB 时钟倍频条件。",
|
||||
log: "scan TIM2 capture path · APB1 prescaler = 2",
|
||||
},
|
||||
{
|
||||
title: "修改代码",
|
||||
summary: "修正定时器时钟计算,并注入实板标定偏移。",
|
||||
log: "patch Core/Src/frequency.c · +5 -3",
|
||||
},
|
||||
{
|
||||
title: "Keil 编译",
|
||||
summary: "使用受控工程配置生成可追溯固件产物。",
|
||||
log: "build 0 error(s), 0 warning(s) · freq.axf",
|
||||
},
|
||||
{
|
||||
title: "下载固件",
|
||||
summary: "通过 CMSIS-DAP 将本次构建产物写入目标板。",
|
||||
log: "flash 32.18 KiB · verify OK · reset target",
|
||||
},
|
||||
{
|
||||
title: "UART 采集",
|
||||
summary: "读取固件运行状态,确认输入捕获链路稳定。",
|
||||
log: "uart capture stable · sample_count = 128",
|
||||
},
|
||||
{
|
||||
title: "物理测量",
|
||||
summary: "由 ioProbe 独立测量 FREQ_OUT,形成物理事实。",
|
||||
log: "probe CH1 frequency · 999.8 Hz · error -0.02%",
|
||||
},
|
||||
];
|
||||
|
||||
const EVIDENCE = [
|
||||
{
|
||||
idle: "任务尚未进入受控执行。",
|
||||
passed: "任务 freq-1khz-accuracy 已绑定 Workspace、Board、Probe 与目标指标。",
|
||||
},
|
||||
{
|
||||
idle: "尚无 Agent 执行 trace。",
|
||||
active: "Agent 正在受控工程上下文中分析、修改与执行。",
|
||||
passed: "Agent trace 已封存:工程分析与代码变更均可回放。",
|
||||
},
|
||||
{
|
||||
idle: "尚无本次构建产物。",
|
||||
passed: "Keil 构建成功:0 error(s),0 warning(s),产物摘要已记录。",
|
||||
},
|
||||
{
|
||||
idle: "目标板尚未写入本次固件。",
|
||||
passed: "CMSIS-DAP 下载与校验通过,固件摘要与构建产物一致。",
|
||||
},
|
||||
{
|
||||
idle: "UART 运行事实尚未采集。",
|
||||
passed: "UART 连续采样 128 次,输入捕获状态稳定,无溢出。",
|
||||
},
|
||||
{
|
||||
idle: "ioProbe 物理测量尚未开始。",
|
||||
active: "ioProbe 正在独立采集 FREQ_OUT 波形。",
|
||||
passed: "ioProbe 实测 999.8 Hz,误差 −0.02%,满足 ±0.10% 阈值。",
|
||||
failed: "ioProbe 实测 972.4 Hz,误差 −2.76%,超出 ±0.10% 阈值。",
|
||||
},
|
||||
{
|
||||
idle: "Aggregate 尚未汇总,不能保存为回归 Case。",
|
||||
passed: "代码、构建、下载、运行与物理测量证据完整:Aggregate PASS。",
|
||||
blocked: "物理测量未通过:Aggregate BLOCKED,阻止错误结果进入回归集。",
|
||||
},
|
||||
];
|
||||
|
||||
const stageButtons = [...document.querySelectorAll("[data-stage]")];
|
||||
const evidenceButtons = [...document.querySelectorAll("[data-evidence]")];
|
||||
const app = document.querySelector("#app");
|
||||
const runButton = document.querySelector("#runButton");
|
||||
const resetButton = document.querySelector("#resetButton");
|
||||
const failureMode = document.querySelector("#failureMode");
|
||||
const saveCaseButton = document.querySelector("#saveCaseButton");
|
||||
const runState = document.querySelector("#runState");
|
||||
const stageDetail = document.querySelector("#stageDetail");
|
||||
const runLog = document.querySelector("#runLog");
|
||||
const traceCounter = document.querySelector("#traceCounter");
|
||||
const evidenceDetail = document.querySelector("#evidenceDetail");
|
||||
const actualFrequency = document.querySelector("#actualFrequency");
|
||||
const frequencyError = document.querySelector("#frequencyError");
|
||||
const scopeStatus = document.querySelector("#scopeStatus");
|
||||
const benchState = document.querySelector("#benchState");
|
||||
const uartOutput = document.querySelector("#uartOutput");
|
||||
const uartIndicator = document.querySelector("#uartIndicator");
|
||||
const toast = document.querySelector("#toast");
|
||||
const scopeCanvas = document.querySelector("#scopeCanvas");
|
||||
const scopeContext = scopeCanvas.getContext("2d");
|
||||
|
||||
const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)");
|
||||
let runToken = 0;
|
||||
let currentState = "idle";
|
||||
let selectedEvidence = -1;
|
||||
let selectedStage = -1;
|
||||
let scopeAnimationFrame = 0;
|
||||
let toastTimer = 0;
|
||||
|
||||
function delay(milliseconds, token) {
|
||||
const duration = reducedMotion.matches ? Math.min(milliseconds, 120) : milliseconds;
|
||||
return new Promise((resolve) => {
|
||||
window.setTimeout(() => resolve(token === runToken), duration);
|
||||
});
|
||||
}
|
||||
|
||||
function setRunState(state, label) {
|
||||
currentState = state;
|
||||
app.dataset.runState = state;
|
||||
runState.textContent = label;
|
||||
runButton.disabled = state === "running";
|
||||
failureMode.disabled = state === "running";
|
||||
saveCaseButton.disabled = state !== "passed";
|
||||
|
||||
if (state === "running" && !scopeAnimationFrame) {
|
||||
animateScope();
|
||||
}
|
||||
}
|
||||
|
||||
function setStageStatus(index, status) {
|
||||
const button = stageButtons[index];
|
||||
if (!button) return;
|
||||
if (status === "idle") {
|
||||
button.removeAttribute("data-status");
|
||||
return;
|
||||
}
|
||||
button.dataset.status = status;
|
||||
}
|
||||
|
||||
function setEvidenceStatus(index, status, label) {
|
||||
const button = evidenceButtons[index];
|
||||
if (!button) return;
|
||||
if (status === "idle") {
|
||||
button.removeAttribute("data-status");
|
||||
} else {
|
||||
button.dataset.status = status;
|
||||
}
|
||||
if (label) {
|
||||
button.querySelector("span").textContent = label;
|
||||
}
|
||||
if (selectedEvidence === index) {
|
||||
showEvidence(index);
|
||||
}
|
||||
}
|
||||
|
||||
function selectStage(index) {
|
||||
selectedStage = index;
|
||||
stageButtons.forEach((button, buttonIndex) => {
|
||||
button.classList.toggle("is-selected", buttonIndex === index);
|
||||
});
|
||||
|
||||
const stage = STAGES[index];
|
||||
if (!stage) {
|
||||
stageDetail.innerHTML = `
|
||||
<span class="detail-index">READY</span>
|
||||
<h3>等待开始工程闭环</h3>
|
||||
<p>执行过程将绑定代码、构建、下载、串口与物理测量事实。</p>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
const status = stageButtons[index].dataset.status || "idle";
|
||||
const statusLabel = {
|
||||
idle: "PENDING",
|
||||
active: "RUNNING",
|
||||
passed: "VERIFIED",
|
||||
failed: "FAILED",
|
||||
}[status];
|
||||
|
||||
stageDetail.innerHTML = `
|
||||
<span class="detail-index">${String(index + 1).padStart(2, "0")} · ${statusLabel}</span>
|
||||
<h3>${stage.title}</h3>
|
||||
<p>${stage.summary}</p>
|
||||
`;
|
||||
}
|
||||
|
||||
function showEvidence(index) {
|
||||
selectedEvidence = index;
|
||||
evidenceButtons.forEach((button, buttonIndex) => {
|
||||
button.classList.toggle("is-selected", buttonIndex === index);
|
||||
});
|
||||
|
||||
const status = evidenceButtons[index]?.dataset.status || "idle";
|
||||
const detail = EVIDENCE[index]?.[status] || EVIDENCE[index]?.idle;
|
||||
evidenceDetail.textContent = detail || "暂无证据详情。";
|
||||
}
|
||||
|
||||
function appendLog(message, tone = "") {
|
||||
const line = document.createElement("span");
|
||||
line.className = tone ? `log-${tone}` : "";
|
||||
line.textContent = `${runLog.textContent.trim() ? "\n" : ""}${message}`;
|
||||
runLog.append(line);
|
||||
runLog.scrollTop = runLog.scrollHeight;
|
||||
}
|
||||
|
||||
function resetDemo({ keepFailure = true } = {}) {
|
||||
runToken += 1;
|
||||
window.cancelAnimationFrame(scopeAnimationFrame);
|
||||
scopeAnimationFrame = 0;
|
||||
if (!keepFailure) failureMode.checked = false;
|
||||
|
||||
stageButtons.forEach((button) => {
|
||||
button.removeAttribute("data-status");
|
||||
button.classList.remove("is-selected");
|
||||
});
|
||||
evidenceButtons.forEach((button, index) => {
|
||||
button.removeAttribute("data-status");
|
||||
button.classList.remove("is-selected");
|
||||
if (index === 5) button.querySelector("span").textContent = "ioProbe 待测";
|
||||
if (index === 6) button.querySelector("span").textContent = "Aggregate 待定";
|
||||
});
|
||||
|
||||
selectedStage = -1;
|
||||
selectedEvidence = -1;
|
||||
traceCounter.textContent = "0 / 6";
|
||||
runLog.innerHTML = '<span class="log-muted">$ hwlab case run freq-1khz-accuracy</span>\n<span class="log-muted"> 尚未执行</span>';
|
||||
evidenceDetail.textContent = "选择证据节点查看边界事实。";
|
||||
actualFrequency.innerHTML = "— <small>Hz</small>";
|
||||
frequencyError.textContent = "—";
|
||||
scopeStatus.textContent = "等待采集";
|
||||
benchState.textContent = "设备待命";
|
||||
uartOutput.textContent = "[idle] waiting for firmware\n[idle] measurement channel ready";
|
||||
uartIndicator.classList.remove("is-active");
|
||||
setRunState("idle", "等待指令");
|
||||
selectStage(-1);
|
||||
drawScope(0);
|
||||
}
|
||||
|
||||
async function runDemo() {
|
||||
if (currentState === "running") return;
|
||||
|
||||
resetDemo();
|
||||
const token = runToken;
|
||||
const shouldFail = failureMode.checked;
|
||||
setRunState("running", "闭环执行中");
|
||||
benchState.textContent = "HWPOD 已锁定";
|
||||
scopeStatus.textContent = "同步设备";
|
||||
runLog.innerHTML = "";
|
||||
appendLog("$ hwlab case run freq-1khz-accuracy", "muted");
|
||||
setEvidenceStatus(0, "passed");
|
||||
setEvidenceStatus(1, "active");
|
||||
|
||||
for (let index = 0; index < STAGES.length; index += 1) {
|
||||
if (token !== runToken) return;
|
||||
|
||||
setStageStatus(index, "active");
|
||||
selectStage(index);
|
||||
traceCounter.textContent = `${index} / 6`;
|
||||
appendLog(`→ ${STAGES[index].title}`, "active");
|
||||
|
||||
if (index === 3) benchState.textContent = "正在写入固件";
|
||||
if (index === 4) {
|
||||
benchState.textContent = "固件运行中";
|
||||
uartIndicator.classList.add("is-active");
|
||||
uartOutput.textContent = "[boot] 71-FREQ rev.C\n[capture] timer input ready";
|
||||
}
|
||||
if (index === 5) {
|
||||
benchState.textContent = "ioProbe 采集中";
|
||||
scopeStatus.textContent = "采集中";
|
||||
setEvidenceStatus(5, "active", "ioProbe 采集中");
|
||||
}
|
||||
|
||||
const shouldContinue = await delay(index === 5 ? 850 : 650, token);
|
||||
if (!shouldContinue) return;
|
||||
|
||||
if (index === 5 && shouldFail) {
|
||||
setStageStatus(index, "failed");
|
||||
appendLog("probe CH1 frequency · 972.4 Hz · outside tolerance", "fail");
|
||||
traceCounter.textContent = "5 / 6";
|
||||
setEvidenceStatus(5, "failed", "ioProbe 972.4 Hz");
|
||||
setEvidenceStatus(6, "blocked", "Aggregate BLOCKED");
|
||||
actualFrequency.innerHTML = "972.4 <small>Hz</small>";
|
||||
frequencyError.textContent = "−2.76%";
|
||||
scopeStatus.textContent = "阈值超限";
|
||||
benchState.textContent = "物理验证未通过";
|
||||
uartOutput.textContent = "[capture] stable, samples=128\n[result] freq=972.4Hz, tolerance=FAIL";
|
||||
setRunState("failed", "证据阻断");
|
||||
selectStage(index);
|
||||
showEvidence(6);
|
||||
drawScope(0);
|
||||
return;
|
||||
}
|
||||
|
||||
setStageStatus(index, "passed");
|
||||
appendLog(STAGES[index].log, "pass");
|
||||
traceCounter.textContent = `${index + 1} / 6`;
|
||||
selectStage(index);
|
||||
|
||||
if (index === 1) setEvidenceStatus(1, "passed");
|
||||
if (index === 2) setEvidenceStatus(2, "passed");
|
||||
if (index === 3) setEvidenceStatus(3, "passed");
|
||||
if (index === 4) {
|
||||
setEvidenceStatus(4, "passed");
|
||||
uartOutput.textContent = "[capture] stable, samples=128\n[timer] clock=72000000Hz, overflow=0";
|
||||
}
|
||||
}
|
||||
|
||||
setEvidenceStatus(5, "passed", "ioProbe 999.8 Hz");
|
||||
setEvidenceStatus(6, "passed", "Aggregate PASS");
|
||||
actualFrequency.innerHTML = "999.8 <small>Hz</small>";
|
||||
frequencyError.textContent = "−0.02%";
|
||||
scopeStatus.textContent = "测量通过";
|
||||
benchState.textContent = "验证完成 · PASS";
|
||||
uartOutput.textContent = "[capture] stable, samples=128\n[result] freq=999.8Hz, tolerance=PASS";
|
||||
appendLog("verdict Aggregate PASS · evidence sealed", "pass");
|
||||
setRunState("passed", "Aggregate PASS");
|
||||
showEvidence(6);
|
||||
drawScope(0);
|
||||
}
|
||||
|
||||
function showToast(message) {
|
||||
window.clearTimeout(toastTimer);
|
||||
toast.textContent = message;
|
||||
toast.classList.add("is-visible");
|
||||
toastTimer = window.setTimeout(() => toast.classList.remove("is-visible"), 2600);
|
||||
}
|
||||
|
||||
function drawScope(phase = 0) {
|
||||
const bounds = scopeCanvas.getBoundingClientRect();
|
||||
const ratio = Math.min(window.devicePixelRatio || 1, 2);
|
||||
const width = Math.max(1, Math.round(bounds.width * ratio));
|
||||
const height = Math.max(1, Math.round(bounds.height * ratio));
|
||||
|
||||
if (scopeCanvas.width !== width || scopeCanvas.height !== height) {
|
||||
scopeCanvas.width = width;
|
||||
scopeCanvas.height = height;
|
||||
}
|
||||
|
||||
scopeContext.setTransform(ratio, 0, 0, ratio, 0, 0);
|
||||
const cssWidth = width / ratio;
|
||||
const cssHeight = height / ratio;
|
||||
scopeContext.clearRect(0, 0, cssWidth, cssHeight);
|
||||
scopeContext.fillStyle = "#0c1418";
|
||||
scopeContext.fillRect(0, 0, cssWidth, cssHeight);
|
||||
|
||||
scopeContext.strokeStyle = "rgba(94, 132, 143, 0.18)";
|
||||
scopeContext.lineWidth = 1;
|
||||
for (let x = 0; x <= cssWidth; x += cssWidth / 10) {
|
||||
scopeContext.beginPath();
|
||||
scopeContext.moveTo(x, 0);
|
||||
scopeContext.lineTo(x, cssHeight);
|
||||
scopeContext.stroke();
|
||||
}
|
||||
for (let y = 0; y <= cssHeight; y += cssHeight / 4) {
|
||||
scopeContext.beginPath();
|
||||
scopeContext.moveTo(0, y);
|
||||
scopeContext.lineTo(cssWidth, y);
|
||||
scopeContext.stroke();
|
||||
}
|
||||
|
||||
const amplitude = cssHeight * 0.27;
|
||||
const center = cssHeight * 0.5;
|
||||
const period = currentState === "failed" ? 51 : 43;
|
||||
const waveColor = currentState === "failed" ? "#ff766e" : "#53dce1";
|
||||
const muted = currentState === "idle";
|
||||
|
||||
scopeContext.beginPath();
|
||||
for (let x = 0; x <= cssWidth; x += 2) {
|
||||
const signal = muted
|
||||
? Math.sin(x / 25) * 1.2
|
||||
: Math.sin((x + phase) * ((Math.PI * 2) / period)) * amplitude;
|
||||
const noise = currentState === "running" ? Math.sin((x + phase * 1.9) / 5) * 0.8 : 0;
|
||||
const y = center + signal + noise;
|
||||
if (x === 0) scopeContext.moveTo(x, y);
|
||||
else scopeContext.lineTo(x, y);
|
||||
}
|
||||
scopeContext.strokeStyle = muted ? "#496169" : waveColor;
|
||||
scopeContext.lineWidth = 1.4;
|
||||
scopeContext.shadowColor = muted ? "transparent" : waveColor;
|
||||
scopeContext.shadowBlur = muted ? 0 : 5;
|
||||
scopeContext.stroke();
|
||||
scopeContext.shadowBlur = 0;
|
||||
}
|
||||
|
||||
function animateScope(timestamp = 0) {
|
||||
if (currentState !== "running") {
|
||||
scopeAnimationFrame = 0;
|
||||
return;
|
||||
}
|
||||
drawScope(timestamp * 0.08);
|
||||
scopeAnimationFrame = window.requestAnimationFrame(animateScope);
|
||||
}
|
||||
|
||||
stageButtons.forEach((button, index) => {
|
||||
button.addEventListener("click", () => selectStage(index));
|
||||
});
|
||||
|
||||
evidenceButtons.forEach((button, index) => {
|
||||
button.addEventListener("click", () => showEvidence(index));
|
||||
});
|
||||
|
||||
runButton.addEventListener("click", runDemo);
|
||||
resetButton.addEventListener("click", () => resetDemo({ keepFailure: true }));
|
||||
failureMode.addEventListener("change", () => {
|
||||
if (currentState !== "idle") resetDemo({ keepFailure: true });
|
||||
showToast(failureMode.checked ? "失败演示已开启:物理测量将阻断聚合结果" : "已恢复通过路径");
|
||||
});
|
||||
saveCaseButton.addEventListener("click", () => {
|
||||
if (currentState !== "passed") return;
|
||||
showToast("回归 Case 已保存:freq-1khz-accuracy / evidence sealed");
|
||||
});
|
||||
|
||||
window.addEventListener("resize", () => drawScope(0));
|
||||
window.addEventListener("load", () => drawScope(0));
|
||||
resetDemo({ keepFailure: false });
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 MiB |
@@ -0,0 +1,251 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta
|
||||
name="description"
|
||||
content="HWLAB 第一性原理概念展示:从工程任务到真实硬件证据闭环。"
|
||||
/>
|
||||
<title>HWLAB · 真实硬件研发闭环</title>
|
||||
<link rel="stylesheet" href="./styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-shell" id="app" data-run-state="idle">
|
||||
<header class="topbar">
|
||||
<div class="brand-block" aria-label="HWLAB">
|
||||
<span class="brand-mark" aria-hidden="true">HW</span>
|
||||
<span class="brand-name">HWLAB</span>
|
||||
<span class="brand-divider" aria-hidden="true"></span>
|
||||
<span class="brand-tagline">云端真实硬件研发闭环</span>
|
||||
</div>
|
||||
|
||||
<div class="target-context" aria-label="当前目标">
|
||||
<span class="context-label">TARGET</span>
|
||||
<strong>71-FREQ / STM32F103</strong>
|
||||
</div>
|
||||
|
||||
<div class="system-status" aria-label="系统状态">
|
||||
<span class="status-pill"><i class="status-dot"></i>NC01 · v03</span>
|
||||
<span class="status-pill status-pill--hardware"><i class="status-dot"></i>HWPOD 已占用</span>
|
||||
<span class="status-pill"><i class="status-dot"></i>Session 就绪</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="workspace">
|
||||
<aside class="task-panel" aria-label="项目与任务上下文">
|
||||
<div class="task-panel-heading">
|
||||
<span class="eyebrow">PROJECT</span>
|
||||
<div class="project-copy">
|
||||
<h1>71-FREQ</h1>
|
||||
<p>信号调理模块</p>
|
||||
</div>
|
||||
<span class="project-code" aria-hidden="true">71</span>
|
||||
</div>
|
||||
|
||||
<nav class="task-list" aria-label="研发任务">
|
||||
<button class="task-item task-item--active" type="button" title="修复 1 kHz 输入频率误差">
|
||||
<span class="task-marker">01</span>
|
||||
<span class="task-text">
|
||||
<strong>修复 1 kHz 输入频率误差</strong>
|
||||
<small><i class="state-dot state-dot--active"></i>等待验证</small>
|
||||
</span>
|
||||
</button>
|
||||
<button class="task-item" type="button" title="校准输入增益曲线">
|
||||
<span class="task-marker">02</span>
|
||||
<span class="task-text">
|
||||
<strong>校准输入增益曲线</strong>
|
||||
<small><i class="state-dot state-dot--done"></i>已完成</small>
|
||||
</span>
|
||||
</button>
|
||||
<button class="task-item" type="button" title="验证低频抗抖动策略">
|
||||
<span class="task-marker">03</span>
|
||||
<span class="task-text">
|
||||
<strong>验证低频抗抖动策略</strong>
|
||||
<small><i class="state-dot"></i>待排期</small>
|
||||
</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="context-facts">
|
||||
<span class="eyebrow">BOUND CONTEXT</span>
|
||||
<dl>
|
||||
<div>
|
||||
<dt>Workspace</dt>
|
||||
<dd>CONSTAR_workspace</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Board</dt>
|
||||
<dd>STM32F103</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Probe</dt>
|
||||
<dd>CMSIS-DAP</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Case</dt>
|
||||
<dd>freq-1khz-accuracy</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section class="agent-panel" aria-labelledby="agent-title">
|
||||
<div class="panel-title-row">
|
||||
<div>
|
||||
<span class="eyebrow">ENGINEERING AGENT</span>
|
||||
<h2 id="agent-title">受控工程工作台</h2>
|
||||
</div>
|
||||
<span class="run-state" id="runState" aria-live="polite">等待指令</span>
|
||||
</div>
|
||||
|
||||
<div class="command-bar" aria-label="工程指令">
|
||||
<span class="command-symbol" aria-hidden="true">›_</span>
|
||||
<p>修复 1 kHz 输入下的频率误差,并在真实板上完成验证</p>
|
||||
<label class="failure-toggle" title="演示物理测量未通过时的阻断状态">
|
||||
<input id="failureMode" type="checkbox" />
|
||||
<span class="toggle-track" aria-hidden="true"><i></i></span>
|
||||
<span>失败演示</span>
|
||||
</label>
|
||||
<button class="icon-button" id="resetButton" type="button" title="重置演示" aria-label="重置演示">
|
||||
↻
|
||||
</button>
|
||||
<button class="run-button" id="runButton" type="button">
|
||||
<span aria-hidden="true">▶</span>
|
||||
运行验证
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<ol class="stage-strip" aria-label="执行阶段">
|
||||
<li><button type="button" data-stage="0"><span>01</span><strong>分析工程</strong><i></i></button></li>
|
||||
<li><button type="button" data-stage="1"><span>02</span><strong>修改代码</strong><i></i></button></li>
|
||||
<li><button type="button" data-stage="2"><span>03</span><strong>Keil 编译</strong><i></i></button></li>
|
||||
<li><button type="button" data-stage="3"><span>04</span><strong>下载固件</strong><i></i></button></li>
|
||||
<li><button type="button" data-stage="4"><span>05</span><strong>UART 采集</strong><i></i></button></li>
|
||||
<li><button type="button" data-stage="5"><span>06</span><strong>物理测量</strong><i></i></button></li>
|
||||
</ol>
|
||||
|
||||
<div class="work-surface">
|
||||
<section class="editor-pane" aria-label="代码变更">
|
||||
<header class="surface-header">
|
||||
<div class="file-tabs">
|
||||
<button class="file-tab file-tab--active" type="button">frequency.c <i></i></button>
|
||||
<button class="file-tab" type="button">timer_capture.h</button>
|
||||
</div>
|
||||
<span class="change-summary"><b>+5</b> <em>−3</em></span>
|
||||
</header>
|
||||
<div class="code-view" role="region" aria-label="frequency.c 代码差异" tabindex="0">
|
||||
<div class="code-line"><span>118</span><code>static float capture_to_hz(uint32_t ticks)</code></div>
|
||||
<div class="code-line"><span>119</span><code>{</code></div>
|
||||
<div class="code-line code-line--removed"><span>120</span><code>- return TIMER_CLOCK_HZ / ticks;</code></div>
|
||||
<div class="code-line code-line--removed"><span>121</span><code>- // APB prescaler assumed to be 1</code></div>
|
||||
<div class="code-line code-line--added"><span>120</span><code>+ const uint32_t timer_clock = get_timer_clock_hz();</code></div>
|
||||
<div class="code-line code-line--added"><span>121</span><code>+ const float calibrated_ticks =</code></div>
|
||||
<div class="code-line code-line--added"><span>122</span><code>+ (float)ticks - capture_offset_ticks;</code></div>
|
||||
<div class="code-line code-line--added"><span>123</span><code>+ return timer_clock / calibrated_ticks;</code></div>
|
||||
<div class="code-line"><span>124</span><code>}</code></div>
|
||||
<div class="code-line"><span>125</span><code></code></div>
|
||||
<div class="code-line code-line--context"><span>126</span><code>float frequency_read(void)</code></div>
|
||||
<div class="code-line code-line--context"><span>127</span><code>{</code></div>
|
||||
<div class="code-line code-line--added"><span>128</span><code>+ capture_offset_ticks = calibration.capture_offset;</code></div>
|
||||
<div class="code-line code-line--context"><span>129</span><code> return capture_to_hz(last_capture_ticks);</code></div>
|
||||
<div class="code-line code-line--context"><span>130</span><code>}</code></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="trace-pane" aria-label="Agent 执行记录">
|
||||
<header class="surface-header">
|
||||
<strong>执行记录</strong>
|
||||
<span id="traceCounter">0 / 6</span>
|
||||
</header>
|
||||
<div class="stage-detail" id="stageDetail">
|
||||
<span class="detail-index">READY</span>
|
||||
<h3>等待开始工程闭环</h3>
|
||||
<p>执行过程将绑定代码、构建、下载、串口与物理测量事实。</p>
|
||||
</div>
|
||||
<pre class="run-log" id="runLog" aria-live="polite"><span class="log-muted">$ hwlab case run freq-1khz-accuracy</span>
|
||||
<span class="log-muted"> 尚未执行</span></pre>
|
||||
</aside>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="hwpod-panel" aria-labelledby="hwpod-title">
|
||||
<div class="hwpod-heading">
|
||||
<div>
|
||||
<span class="eyebrow">REAL HARDWARE</span>
|
||||
<h2 id="hwpod-title">HWPOD 现场</h2>
|
||||
</div>
|
||||
<span class="live-indicator"><i></i>LIVE</span>
|
||||
</div>
|
||||
|
||||
<figure class="bench-view">
|
||||
<img
|
||||
src="./assets/hwpod-bench.png"
|
||||
alt="STM32 开发板、调试探针与 ioProbe 测量设备组成的真实硬件台架"
|
||||
/>
|
||||
<figcaption class="sr-only">当前任务绑定的真实 HWPOD 硬件现场</figcaption>
|
||||
<span class="hardware-label hardware-label--board"><i></i>STM32F103</span>
|
||||
<span class="hardware-label hardware-label--probe"><i></i>CMSIS-DAP</span>
|
||||
<span class="hardware-label hardware-label--ioprobe"><i></i>ioProbe</span>
|
||||
<span class="bench-state" id="benchState">设备待命</span>
|
||||
</figure>
|
||||
|
||||
<section class="measurement-panel" aria-label="物理测量结果">
|
||||
<div class="scope-header">
|
||||
<span>CH1 · FREQ_OUT</span>
|
||||
<strong id="scopeStatus">等待采集</strong>
|
||||
</div>
|
||||
<canvas id="scopeCanvas" aria-label="ioProbe 输出波形"></canvas>
|
||||
<dl class="measurement-grid">
|
||||
<div>
|
||||
<dt>目标频率</dt>
|
||||
<dd>1000.0 <small>Hz</small></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>实测频率</dt>
|
||||
<dd id="actualFrequency">— <small>Hz</small></dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>测量误差</dt>
|
||||
<dd id="frequencyError">—</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section class="uart-panel" aria-label="UART 现场输出">
|
||||
<header>
|
||||
<span>UART2 · 115200</span>
|
||||
<i id="uartIndicator"></i>
|
||||
</header>
|
||||
<pre id="uartOutput">[idle] waiting for firmware
|
||||
[idle] measurement channel ready</pre>
|
||||
</section>
|
||||
</aside>
|
||||
</main>
|
||||
|
||||
<footer class="evidence-band" aria-labelledby="evidence-title">
|
||||
<div class="evidence-heading">
|
||||
<div>
|
||||
<span class="eyebrow">HARNESSRL EVIDENCE</span>
|
||||
<h2 id="evidence-title">可审计证据链</h2>
|
||||
</div>
|
||||
<p id="evidenceDetail">选择证据节点查看边界事实。</p>
|
||||
<button id="saveCaseButton" type="button" disabled>保存为回归 Case</button>
|
||||
</div>
|
||||
|
||||
<ol class="evidence-chain">
|
||||
<li><button type="button" data-evidence="0"><i></i><span>任务接收</span><small>Task</small></button></li>
|
||||
<li><button type="button" data-evidence="1"><i></i><span>Agent 执行</span><small>Trace</small></button></li>
|
||||
<li><button type="button" data-evidence="2"><i></i><span>Build 通过</span><small>Artifact</small></button></li>
|
||||
<li><button type="button" data-evidence="3"><i></i><span>Flash 通过</span><small>Probe</small></button></li>
|
||||
<li><button type="button" data-evidence="4"><i></i><span>UART 正常</span><small>Runtime</small></button></li>
|
||||
<li><button type="button" data-evidence="5"><i></i><span>ioProbe 待测</span><small>Physical</small></button></li>
|
||||
<li class="evidence-final"><button type="button" data-evidence="6"><i></i><span>Aggregate 待定</span><small>Verdict</small></button></li>
|
||||
</ol>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div class="toast" id="toast" role="status" aria-live="polite"></div>
|
||||
<script src="./app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user