Files
ESP32_COMM/tools/spi_test.html
T
小龙虾 04a118219f feat(motor-ota): 改进电机OTA中继的握手确认和重试机制
- 添加握手确认逻辑,避免在握手阶段就标记TRANSFER状态
- 实现GD32 auto-retry检测机制,当连续WAIT超限时查询真实状态
- 修复预填充缓冲区数据重复问题,移除旧的offset重置逻辑
- 优化SPI响应等待机制,允许在WAIT后重发DATA以恢复丢失的ACK
- 添加连续SPI失败和WAIT计数监控,超过阈值时主动查询GD32状态

perf(can): 提升CAN批量发送性能和可靠性

- 将CAN帧间延迟从5ms减少到1ms以提升传输速度
- 将单批最大帧数从32增加到64,并将批次间隔从200ms减少到100ms
- 添加手动超时控制器防止fetch请求永久挂起
- 实现批量发送完成后的兜底渲染机制确保结果正确显示

refactor(spi): 优化SPI通信的事件驱动机制

- 实现基于信号量的事件驱动等待机制,替换原有的轮询方式
- 将SPI时钟频率从4MHz提升至8MHz以提高传输速度
- 优化OTA发送后的静默期处理,不同类型的命令采用不同的延迟策略
- 添加临时SPI测速端点用于性能测试

chore(version): 更新固件版本至0.3.74

- 版本号从0.3.72提升到0.3.74
- 更新前端资源版本号以确保缓存刷新
2026-08-01 04:14:51 +08:00

176 lines
6.6 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<!--
【临时功能 SPI-SPEEDTEST】SPI 链路测速独立页面
─────────────────────────────────────────────
完全自包含(无外部 css/js 依赖),不被任何其他页面引用。
删除方法:直接删掉本文件即可(固件端点删除方法见
main/monitor/spi_speed_test.cpp 文件头)。
用法:浏览器直接打开本文件,或经 serve.bat 访问
http://localhost:8080/spi_test.html
-->
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>SPI 测速(临时工具)</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Segoe UI", "Microsoft YaHei", sans-serif;
background: #14171c; color: #d8dee6;
max-width: 760px; margin: 0 auto; padding: 24px 16px;
}
h1 { font-size: 20px; margin-bottom: 4px; }
.sub { color: #7a8494; font-size: 12px; margin-bottom: 20px; }
.card {
background: #1c2129; border: 1px solid #2a313c; border-radius: 8px;
padding: 16px; margin-bottom: 16px;
}
label { font-size: 13px; color: #9aa5b3; margin-right: 6px; }
input {
background: #12151a; color: #d8dee6; border: 1px solid #2a313c;
border-radius: 4px; padding: 6px 8px; font-size: 14px; width: 130px;
}
input.narrow { width: 70px; }
.row { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; margin-bottom: 12px; }
button {
background: #2563eb; color: #fff; border: 0; border-radius: 6px;
padding: 8px 18px; font-size: 14px; cursor: pointer;
}
button:hover { background: #1d4ed8; }
button:disabled { background: #3a4250; cursor: wait; }
button.alt { background: #374151; }
button.alt:hover { background: #4b5563; }
pre {
background: #12151a; border: 1px solid #2a313c; border-radius: 6px;
padding: 12px; font-size: 14px; line-height: 1.7; white-space: pre-wrap;
min-height: 120px;
}
pre.fail { border-color: #b91c1c; color: #fca5a5; }
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 5px 8px; border-bottom: 1px solid #2a313c; }
th { color: #7a8494; font-weight: normal; }
td.bad { color: #f87171; }
.hint { color: #7a8494; font-size: 12px; line-height: 1.7; margin-top: 8px; }
</style>
</head>
<body>
<h1>SPI 链路测速</h1>
<div class="sub">临时诊断工具 [SPI-SPEEDTEST] — 探针 0xA1 APP PING,删除时直接删掉本文件</div>
<div class="card">
<div class="row">
<label>设备 IP</label><input id="ip" value="192.168.31.127">
<label>帧数</label><input id="frames" class="narrow" value="200">
<label>载荷 B</label><input id="payload" class="narrow" value="236">
</div>
<div class="row">
<button id="btnRun">开始测速</button>
<button id="btnRtt" class="alt">空载 RTT(500 帧)</button>
<button id="btnStress" class="alt">压力测试(2000 帧满载)</button>
</div>
<div class="hint">
满帧 236B = 等效电机 OTA 0xD1 数据节奏;载荷 0 = 纯握手往返。<br>
判读:失败必须为 0;「最快」是链路物理极限周期;「平均-最快」差值是软件调度开销。
</div>
</div>
<div class="card">
<pre id="out">等待测试…</pre>
</div>
<div class="card">
<table>
<thead><tr><th>时间</th><th>帧数</th><th>载荷</th><th>失败</th><th>平均</th><th>最快</th><th>吞吐</th></tr></thead>
<tbody id="hist"></tbody>
</table>
<div class="hint">本页历史仅存内存,刷新即清空——方便改参数前后对比。</div>
</div>
<script>
"use strict";
(function () {
var out = document.getElementById("out");
var hist = document.getElementById("hist");
var btns = [
document.getElementById("btnRun"),
document.getElementById("btnRtt"),
document.getElementById("btnStress")
];
var ipInput = document.getElementById("ip");
var savedIp = null;
try { savedIp = window.localStorage.getItem("spi_test_ip"); } catch (e) {}
if (savedIp) { ipInput.value = savedIp; }
function setBusy(busy) {
for (var i = 0; i < btns.length; i++) { btns[i].disabled = busy; }
}
function pad2(n) { return (n < 10 ? "0" : "") + n; }
function addHistory(frames, payload, text) {
/* 从设备中文文本里提取关键数字做历史行 */
var fail = (text.match(/失败:\s*(\d+)/) || [])[1] || "?";
var avg = (text.match(/平均\s*([\d.]+)\s*ms/) || [])[1] || "?";
var min = (text.match(/最快\s*([\d.]+)\s*ms/) || [])[1] || "?";
var tput = (text.match(/等效吞吐:\s*([\d.]+)\s*KB\/s/) || [])[1] || "-";
var d = new Date();
var tr = document.createElement("tr");
var cells = [
pad2(d.getHours()) + ":" + pad2(d.getMinutes()) + ":" + pad2(d.getSeconds()),
String(frames), String(payload) + "B", fail,
avg + "ms", min + "ms", tput === "-" ? "-" : tput + "KB/s"
];
for (var i = 0; i < cells.length; i++) {
var td = document.createElement("td");
td.textContent = cells[i];
if (i === 3 && fail !== "0") { td.className = "bad"; }
tr.appendChild(td);
}
hist.insertBefore(tr, hist.firstChild);
}
function run(frames, payload) {
var ip = ipInput.value.trim();
try { window.localStorage.setItem("spi_test_ip", ip); } catch (e) {}
var url = "http://" + ip + "/spi/speedtest?frames=" + frames + "&payload=" + payload;
setBusy(true);
out.className = "";
out.textContent = "测试中… (" + frames + " 帧 × " + payload + "B)";
fetch(url)
.then(function (r) {
if (!r.ok) { throw new Error("HTTP " + r.status); }
return r.text();
})
.then(function (text) {
var failed = /失败:\s*[1-9]/.test(text);
out.className = failed ? "fail" : "";
out.textContent = text;
addHistory(frames, payload, text);
})
.catch(function (err) {
out.className = "fail";
out.textContent = "请求失败: " + err.message + "\n请检查设备 IP 与网络连通性。";
})
.then(function () { setBusy(false); });
}
function clampInt(v, lo, hi, dflt) {
var n = parseInt(v, 10);
if (isNaN(n)) { return dflt; }
return Math.min(hi, Math.max(lo, n));
}
btns[0].onclick = function () {
run(clampInt(document.getElementById("frames").value, 1, 2000, 200),
clampInt(document.getElementById("payload").value, 0, 236, 236));
};
btns[1].onclick = function () { run(500, 0); };
btns[2].onclick = function () { run(2000, 236); };
})();
</script>
</body>
</html>