纯前端上位机界面 + esp32_server.py 静态服务/桌面窗口外壳, 不含固件、 不直接访问硬件; 业务 API 由 ESP32_COMM 固件 80 端口跨域提供。 - tabs.html 单窗口多标签外壳, iframe 承载各功能页 - index.html OTA 中继(ESP32 WiFi / GD32 relay / BLE), js/ota + js/ble - can.html / can_monitor.html CAN 通信与接收监控, js/can - motor.html 电机 CAN FD 控制台, js/motor - continuity_test.html LAN9252 通断测试, spi_test.html SPI 测速(临时, 自包含) - 三入口: esp32_app.vbs 桌面窗口 / esp32_web.vbs 浏览器 / serve.bat 控制台调试 - 补 README(页面与目录约定、?v= 缓存版本等改动须知)、.gitignore、 .gitattributes(* -text 保持 Windows 换行原样)
755 lines
33 KiB
HTML
755 lines
33 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>LAN9252 通断测试</title>
|
|
<style>
|
|
/* === WebGL 液态玻璃 (参考 yzrt/html-liquid-glass-effect-webgl) ===
|
|
参数:IOR=1.5 / Thickness=50 / Normal=10 / Blur=0 / Highlight=4 / Radius=26
|
|
实现:SVG feDisplacementMap 做真实背景像素折射位移 + saturate/contrast 色散
|
|
+ 边缘 4px 白色高光带 (Highlight Width=4)
|
|
+ 边缘 1px 暗描边 (Normal Strength=10)
|
|
所有组件统一用此风格,旧 yzrt .box 多层 inset 阴影已舍弃
|
|
*/
|
|
:root{
|
|
--grid:#E1E1E1;
|
|
--text:#f0f0f5;
|
|
--text-2:#c8c8d0;
|
|
--text-3:#8a8a95;
|
|
--separator:rgba(255,255,255,.08);
|
|
--blue:#5b9eff;
|
|
--green:#30d158;
|
|
--red:#ff453a;
|
|
--orange:#ff9f0a;
|
|
--purple:#bf5af2;
|
|
}
|
|
*{box-sizing:border-box;margin:0;padding:0;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transition:.25s;}
|
|
html,body{height:100%;}
|
|
body{
|
|
font-family:"Microsoft YaHei","SF Pro Display","Segoe UI",sans-serif;
|
|
color:var(--text);font-size:15px;line-height:1.5;
|
|
min-height:100vh;padding:32px 20px 80px;
|
|
position:relative;overflow-x:hidden;
|
|
/* 彩色 blur 球 + 细点阵 — 让液态玻璃折射位移清晰可见
|
|
色球提供颜色参照(折射色散),点阵提供位置参照(位移扭曲) */
|
|
background:
|
|
/* 细点阵:让折射位移有参照物 */
|
|
radial-gradient(circle at 50% 50%, rgba(255,255,255,.12) 1px, transparent 1.5px) 0 0/24px 24px,
|
|
/* 彩色 blur 球:中饱和度,不要太鲜艳以免干扰文字 */
|
|
radial-gradient(circle at 15% 20%, #4a90e2 0%, transparent 35%),
|
|
radial-gradient(circle at 85% 15%, #b06ab3 0%, transparent 38%),
|
|
radial-gradient(circle at 75% 80%, #e27d60 0%, transparent 40%),
|
|
radial-gradient(circle at 20% 75%, #41d3a3 0%, transparent 38%),
|
|
radial-gradient(circle at 50% 50%, #f0a500 0%, transparent 45%),
|
|
/* 底色渐变 */
|
|
linear-gradient(135deg, #2d3561 0%, #2d4a6b 50%, #1a2a45 100%);
|
|
background-attachment:fixed;
|
|
}
|
|
/* 大色球:增强折射参照(放固定层,不会被玻璃遮挡) */
|
|
body::before{
|
|
content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;
|
|
background:
|
|
radial-gradient(circle at 30% 40%, rgba(255,200,100,.18) 0%, transparent 20%),
|
|
radial-gradient(circle at 70% 60%, rgba(100,200,255,.15) 0%, transparent 25%);
|
|
}
|
|
.wrap{max-width:1100px;margin:0 auto;position:relative;z-index:1;}
|
|
|
|
h1{
|
|
font-size:36px;font-weight:700;letter-spacing:-.03em;margin-bottom:6px;color:#fff;
|
|
text-shadow:0 2px 8px rgba(0,0,0,.4);
|
|
}
|
|
.sub{color:rgba(255,255,255,.85);font-size:15px;margin-bottom:24px;font-weight:500;text-shadow:0 1px 4px rgba(0,0,0,.3);}
|
|
|
|
/* === 玻璃基础类 — 纯 WebGL 液态玻璃(无辅助效果) ===
|
|
IOR1.5/Thick50 → SVG feDisplacementMap 背景折射位移 + saturate/contrast 色散
|
|
Blur0 → 不模糊
|
|
Highlight4 → 边缘 8px 白色高光带(略扩散)
|
|
Normal10 → 边缘 2px 暗影
|
|
Radius26 → 圆角 */
|
|
.glass,.glass-strong{
|
|
position:relative;z-index:0;isolation:isolate;
|
|
border-radius:26px;
|
|
border:none;
|
|
background:transparent;
|
|
backdrop-filter:url(#glassRefract) saturate(1.4) contrast(1.15);
|
|
-webkit-backdrop-filter:url(#glassRefract) saturate(1.4) contrast(1.15);
|
|
box-shadow:
|
|
inset 0 0 8px rgba(255,255,255,.6), /* Highlight Width=4 → 略向中间扩散 */
|
|
inset 0 0 2px rgba(0,0,0,.6), /* Normal Strength=10 */
|
|
0 4px 12px rgba(0,0,0,.18);
|
|
}
|
|
.glass-strong{
|
|
box-shadow:
|
|
inset 0 0 10px rgba(255,255,255,.7),
|
|
inset 0 0 2px rgba(0,0,0,.6),
|
|
0 6px 16px rgba(0,0,0,.2);
|
|
}
|
|
.glass-subtle{
|
|
position:relative;z-index:0;isolation:isolate;
|
|
border-radius:18px;
|
|
border:none;
|
|
background:transparent;
|
|
backdrop-filter:url(#glassRefract) saturate(1.3) contrast(1.1);
|
|
-webkit-backdrop-filter:url(#glassRefract) saturate(1.3) contrast(1.1);
|
|
box-shadow:
|
|
inset 0 0 6px rgba(255,255,255,.5),
|
|
inset 0 0 2px rgba(0,0,0,.5),
|
|
0 2px 8px rgba(0,0,0,.14);
|
|
}
|
|
.card{padding:24px;margin-bottom:16px;}
|
|
.card > *{position:relative;z-index:1;}
|
|
.card h2{
|
|
font-size:22px;font-weight:700;letter-spacing:-.02em;margin-bottom:6px;color:#fff;
|
|
text-shadow:0 1px 6px rgba(0,0,0,.3);
|
|
}
|
|
.card-desc{font-size:13px;color:var(--text-2);margin-bottom:16px;}
|
|
|
|
/* 警告条 — 纯 WebGL 玻璃 + 橙红染色 */
|
|
.warn{
|
|
color:#fff;font-size:13px;margin-bottom:24px;padding:14px 18px;
|
|
position:relative;z-index:0;isolation:isolate;
|
|
background:linear-gradient(135deg,rgba(255,159,10,.35),rgba(255,69,58,.3));
|
|
backdrop-filter:url(#glassRefract) saturate(1.4) contrast(1.15);
|
|
-webkit-backdrop-filter:url(#glassRefract) saturate(1.4) contrast(1.15);
|
|
border:none;
|
|
border-radius:26px;
|
|
box-shadow:
|
|
inset 0 0 8px rgba(255,255,255,.5),
|
|
inset 0 0 2px rgba(0,0,0,.5),
|
|
0 4px 12px rgba(0,0,0,.16);
|
|
display:flex;align-items:flex-start;gap:10px;font-weight:500;
|
|
text-shadow:0 1px 3px rgba(0,0,0,.4);
|
|
}
|
|
.warn > *{position:relative;z-index:1;}
|
|
.warn-icon{font-size:16px;flex-shrink:0;}
|
|
|
|
/* === 按钮 — 纯 WebGL 液态玻璃(无辅助效果) === */
|
|
button{
|
|
--btn-glow:rgba(0,0,0,.3);
|
|
position:relative;z-index:0;isolation:isolate;
|
|
background:transparent;
|
|
backdrop-filter:url(#glassRefract) saturate(1.4) contrast(1.15);
|
|
-webkit-backdrop-filter:url(#glassRefract) saturate(1.4) contrast(1.15);
|
|
color:var(--text);
|
|
border:none;
|
|
border-radius:26px;
|
|
padding:11px 22px;
|
|
font-size:14px;font-weight:600;font-family:inherit;
|
|
cursor:pointer;letter-spacing:-.01em;
|
|
box-shadow:
|
|
inset 0 0 8px rgba(255,255,255,.6),
|
|
inset 0 0 2px rgba(0,0,0,.6),
|
|
0 2px 6px rgba(0,0,0,.16);
|
|
}
|
|
button:hover{
|
|
transform:translateY(-1px);
|
|
backdrop-filter:url(#glassRefract) saturate(1.5) contrast(1.2);
|
|
-webkit-backdrop-filter:url(#glassRefract) saturate(1.5) contrast(1.2);
|
|
box-shadow:
|
|
inset 0 0 10px rgba(255,255,255,.7),
|
|
inset 0 0 2px rgba(0,0,0,.6),
|
|
0 4px 10px rgba(0,0,0,.2);
|
|
}
|
|
button:active{transform:translateY(0);}
|
|
/* 液体弹动:点击时像液态玻璃被按压后弹性回弹
|
|
注:用 transform-origin:center 保证对称变形;
|
|
background 用 background-size:200% 避免渐变被拉伸变形;
|
|
box-shadow 跟随收缩,保持形状一致 */
|
|
button.pressed{
|
|
animation:liquidPress .6s cubic-bezier(.25,1.5,.5,1) 1;
|
|
background-size:200% 200% !important;
|
|
background-position:center !important;
|
|
}
|
|
@keyframes liquidPress{
|
|
0%{transform:scale(1,1);box-shadow:inset 0 0 8px rgba(255,255,255,.5),inset 0 0 2px rgba(0,0,0,.6),0 3px 10px var(--btn-glow,rgba(0,0,0,.3));}
|
|
25%{transform:scale(.90,.86);border-radius:32px;box-shadow:inset 0 0 6px rgba(255,255,255,.4),inset 0 0 2px rgba(0,0,0,.6),0 1px 4px var(--btn-glow,rgba(0,0,0,.3));}
|
|
45%{transform:scale(1.06,.94);border-radius:22px;box-shadow:inset 0 0 10px rgba(255,255,255,.6),inset 0 0 2px rgba(0,0,0,.6),0 4px 12px var(--btn-glow,rgba(0,0,0,.3));}
|
|
65%{transform:scale(.97,1.03);border-radius:28px;box-shadow:inset 0 0 9px rgba(255,255,255,.55),inset 0 0 2px rgba(0,0,0,.6),0 3px 9px var(--btn-glow,rgba(0,0,0,.3));}
|
|
82%{transform:scale(1.01,.99);border-radius:25px;box-shadow:inset 0 0 8px rgba(255,255,255,.5),inset 0 0 2px rgba(0,0,0,.6),0 3px 10px var(--btn-glow,rgba(0,0,0,.3));}
|
|
100%{transform:scale(1,1);border-radius:26px;box-shadow:inset 0 0 8px rgba(255,255,255,.5),inset 0 0 2px rgba(0,0,0,.6),0 3px 10px var(--btn-glow,rgba(0,0,0,.3));}
|
|
}
|
|
|
|
button.selected{
|
|
--btn-glow:rgba(37,99,235,.45);
|
|
background:linear-gradient(135deg,rgba(37,99,235,.82),rgba(29,78,216,.82));
|
|
background-size:200% 200%;
|
|
color:#fff;
|
|
box-shadow:
|
|
inset 0 0 8px rgba(255,255,255,.5),
|
|
inset 0 0 2px rgba(0,0,0,.6),
|
|
0 3px 10px rgba(37,99,235,.45);
|
|
}
|
|
button.selected:hover{background:linear-gradient(135deg,rgba(29,78,216,.88),rgba(37,99,235,.88));}
|
|
|
|
button.confirm-btn{
|
|
--btn-glow:rgba(22,163,74,.4);
|
|
background:linear-gradient(135deg,rgba(22,163,74,.8),rgba(21,128,61,.8));
|
|
background-size:200% 200%;
|
|
color:#fff;
|
|
box-shadow:
|
|
inset 0 0 8px rgba(255,255,255,.5),
|
|
inset 0 0 2px rgba(0,0,0,.6),
|
|
0 3px 10px rgba(22,163,74,.4);
|
|
}
|
|
button.confirm-btn:hover{background:linear-gradient(135deg,rgba(21,128,61,.88),rgba(22,163,74,.88));}
|
|
button.confirm-btn:disabled{
|
|
--btn-glow:rgba(0,0,0,.2);
|
|
background:rgba(140,140,145,.3);color:rgba(255,255,255,.8);
|
|
cursor:not-allowed;filter:brightness(.85);
|
|
box-shadow:inset 0 0 2px rgba(0,0,0,.4);transform:none;
|
|
}
|
|
|
|
button.loop-btn{
|
|
--btn-glow:rgba(124,58,237,.4);
|
|
background:linear-gradient(135deg,rgba(124,58,237,.8),rgba(109,40,217,.8));
|
|
background-size:200% 200%;
|
|
color:#fff;
|
|
box-shadow:
|
|
inset 0 0 8px rgba(255,255,255,.5),
|
|
inset 0 0 2px rgba(0,0,0,.6),
|
|
0 3px 10px rgba(124,58,237,.4);
|
|
}
|
|
button.loop-btn:hover{background:linear-gradient(135deg,rgba(109,40,217,.92),rgba(124,58,237,.92));}
|
|
button.loop-btn.active{
|
|
--btn-glow:rgba(234,88,12,.5);
|
|
background:linear-gradient(135deg,rgba(234,88,12,.92),rgba(194,65,12,.92));
|
|
background-size:200% 200%;
|
|
animation:pulse 2s infinite;
|
|
}
|
|
|
|
button.stop-btn{
|
|
--btn-glow:rgba(220,38,38,.4);
|
|
background:linear-gradient(135deg,rgba(220,38,38,.8),rgba(185,28,28,.8));
|
|
background-size:200% 200%;
|
|
color:#fff;
|
|
box-shadow:
|
|
inset 0 0 8px rgba(255,255,255,.5),
|
|
inset 0 0 2px rgba(0,0,0,.6),
|
|
0 3px 10px rgba(220,38,38,.4);
|
|
}
|
|
button.stop-btn:hover{background:linear-gradient(135deg,rgba(185,28,28,.88),rgba(220,38,38,.88));}
|
|
|
|
@keyframes pulse{
|
|
0%,100%{box-shadow:inset 0 0 2px rgba(0,0,0,.7),0 0 0 0 rgba(234,88,12,.5);}
|
|
50%{box-shadow:inset 0 0 2px rgba(0,0,0,.7),0 0 0 12px rgba(234,88,12,0);}
|
|
}
|
|
|
|
.btn-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(170px,1fr));gap:10px;}
|
|
.ctrl-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:10px;}
|
|
.section-label{
|
|
font-size:11px;color:var(--text-3);text-transform:uppercase;letter-spacing:.08em;
|
|
margin:18px 0 10px;font-weight:700;
|
|
}
|
|
.section-label:first-child{margin-top:0;}
|
|
|
|
/* SVG 引脚图容器 — 纯 WebGL 玻璃 */
|
|
.pin-svg-wrap{
|
|
display:flex;justify-content:center;margin:12px 0;padding:20px;
|
|
position:relative;z-index:0;isolation:isolate;
|
|
background:transparent;
|
|
backdrop-filter:url(#glassRefract) saturate(1.4) contrast(1.15);
|
|
-webkit-backdrop-filter:url(#glassRefract) saturate(1.4) contrast(1.15);
|
|
border-radius:18px;
|
|
border:none;
|
|
box-shadow:
|
|
inset 0 0 6px rgba(255,255,255,.5),
|
|
inset 0 0 2px rgba(0,0,0,.5),
|
|
0 2px 8px rgba(0,0,0,.14);
|
|
}
|
|
.pin-svg-wrap svg{max-width:100%;height:auto;}
|
|
.pin-rect{stroke:rgba(255,255,255,.3);stroke-width:.5;transition:fill .3s ease;}
|
|
.pin-num{fill:rgba(255,255,255,.7);font-size:12px;font-family:ui-monospace,"SF Mono",monospace;}
|
|
.pin-name{fill:rgba(255,255,255,.9);font-size:11px;font-family:ui-monospace,"SF Mono",monospace;}
|
|
.pin-name-data{fill:#7fd3ff;font-weight:600;}
|
|
.pin-name-ctrl{fill:#ffb74d;font-weight:600;}
|
|
.chip-body{fill:url(#chipGrad);stroke:rgba(255,255,255,.25);stroke-width:1;}
|
|
.chip-label{fill:#fff;font-size:22px;font-weight:700;text-anchor:middle;}
|
|
.chip-sub{fill:rgba(255,255,255,.7);font-size:10px;text-anchor:middle;}
|
|
.pin1-dot{fill:var(--blue);}
|
|
.pin-legend{
|
|
display:flex;gap:18px;justify-content:center;flex-wrap:wrap;margin-top:16px;
|
|
font-size:12px;color:var(--text-2);padding-top:16px;
|
|
border-top:1px solid var(--separator);font-weight:500;
|
|
position:relative;z-index:1;
|
|
}
|
|
.pin-legend span{display:flex;align-items:center;gap:6px;}
|
|
.pin-legend .swatch{
|
|
width:14px;height:14px;border-radius:4px;display:inline-block;
|
|
box-shadow:0 1px 2px rgba(0,0,0,.2),inset 0 1px 0 rgba(255,255,255,.5);
|
|
}
|
|
|
|
/* 连接 / 操作 */
|
|
.connect-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;font-size:14px;position:relative;z-index:1;}
|
|
.connect-label{color:var(--text-2);font-weight:500;}
|
|
.connect-ip{
|
|
color:var(--blue);font-weight:700;
|
|
font-family:ui-monospace,"SF Mono",monospace;font-size:15px;
|
|
background:rgba(37,99,235,.15);padding:5px 14px;border-radius:26px;
|
|
border:none;
|
|
box-shadow:inset 0 0 6px rgba(255,255,255,.4),inset 0 0 2px rgba(0,0,0,.3);
|
|
}
|
|
.connect-hint{color:var(--text-3);font-size:12px;}
|
|
.action-row{display:flex;gap:10px;flex-wrap:wrap;position:relative;z-index:1;}
|
|
.action-desc{
|
|
font-size:13px;color:var(--text-2);margin-top:16px;padding-top:14px;
|
|
border-top:1px solid var(--separator);line-height:1.7;position:relative;z-index:1;
|
|
}
|
|
.action-desc strong{color:var(--text);font-weight:600;}
|
|
|
|
/* 日志面板 — 纯 WebGL 玻璃 */
|
|
.log-panel{padding:0;overflow:hidden;}
|
|
.log-panel-header{
|
|
padding:18px 24px 14px;border-bottom:1px solid var(--separator);
|
|
display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;
|
|
background:transparent;position:relative;z-index:1;
|
|
}
|
|
.log-panel-title{font-size:17px;font-weight:700;letter-spacing:-.01em;}
|
|
.log-filters{display:flex;gap:6px;}
|
|
.log-filter{
|
|
position:relative;z-index:0;isolation:isolate;
|
|
padding:5px 14px;border-radius:26px;font-size:12px;font-weight:600;
|
|
border:none;background:transparent;color:var(--text-2);
|
|
cursor:pointer;font-family:inherit;
|
|
backdrop-filter:url(#glassRefract) saturate(1.3) contrast(1.1);
|
|
-webkit-backdrop-filter:url(#glassRefract) saturate(1.3) contrast(1.1);
|
|
box-shadow:
|
|
inset 0 0 5px rgba(255,255,255,.4),
|
|
inset 0 0 2px rgba(0,0,0,.4),
|
|
0 1px 3px rgba(0,0,0,.1);
|
|
}
|
|
.log-filter.active{
|
|
background:linear-gradient(135deg,rgba(37,99,235,.82),rgba(29,78,216,.82));
|
|
color:#fff;
|
|
box-shadow:
|
|
inset 0 0 8px rgba(255,255,255,.5),
|
|
inset 0 0 2px rgba(0,0,0,.6),
|
|
0 2px 6px rgba(37,99,235,.35);
|
|
}
|
|
.log-filter:hover:not(.active){color:var(--text);}
|
|
.log-scroll{
|
|
padding:14px 24px;font-family:ui-monospace,"SF Mono","Cascadia Code",monospace;
|
|
font-size:12px;line-height:1.8;max-height:380px;overflow-y:auto;
|
|
background:transparent;position:relative;z-index:1;
|
|
}
|
|
.log-scroll::-webkit-scrollbar{width:6px;}
|
|
.log-scroll::-webkit-scrollbar-track{background:transparent;}
|
|
.log-scroll::-webkit-scrollbar-thumb{background:rgba(0,0,0,.2);border-radius:3px;}
|
|
.log-scroll::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.35);}
|
|
.log-line{display:flex;gap:8px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.05);}
|
|
.log-line .lt{color:var(--text-3);flex-shrink:0;}
|
|
.log-line .lv{font-weight:700;min-width:18px;flex-shrink:0;}
|
|
.log-line .lv.E{color:var(--red);}
|
|
.log-line .lv.I{color:var(--blue);}
|
|
.log-line .lg{color:var(--purple);flex-shrink:0;}
|
|
.log-line .lm{color:#fff;word-break:break-all;}
|
|
.log-foot{
|
|
display:flex;justify-content:space-between;align-items:center;
|
|
padding:12px 24px;border-top:1px solid var(--separator);
|
|
font-size:12px;color:var(--text-3);font-weight:500;
|
|
background:transparent;position:relative;z-index:1;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- SVG 折射滤镜:模拟 WebGL IOR=1.5 Thickness=50 的背景位移折射 -->
|
|
<svg style="position:absolute;width:0;height:0;overflow:hidden" aria-hidden="true">
|
|
<defs>
|
|
<filter id="glassRefract" x="-20%" y="-20%" width="140%" height="140%">
|
|
<feTurbulence type="fractalNoise" baseFrequency="0.012 0.012" numOctaves="2" seed="7" result="noise"/>
|
|
<feGaussianBlur in="noise" stdDeviation="1.5" result="softNoise"/>
|
|
<feDisplacementMap in="SourceGraphic" in2="softNoise" scale="14" xChannelSelector="R" yChannelSelector="G"/>
|
|
</filter>
|
|
</defs>
|
|
</svg>
|
|
<div class="wrap">
|
|
|
|
<h1>LAN9252 通断测试</h1>
|
|
<div class="sub">通过 ESP32 /spi/send → SPI 0xA2 → GD32 驱动 EXMC 引脚,万用表测 LAN9252 端电压</div>
|
|
|
|
<div class="warn">
|
|
<span class="warn-icon">⚠️</span>
|
|
<span>不点按钮时 GD32 引脚为高阻输入态(安全)。点击模式后先预览引脚图,点"确认应用"才实际驱动引脚。测量完毕请点"停止/恢复"释放。</span>
|
|
</div>
|
|
|
|
<!-- 连接 -->
|
|
<div class="glass card">
|
|
<div class="connect-row">
|
|
<span class="connect-label">设备 IP</span>
|
|
<span id="ipDisplay" class="connect-ip">读取中...</span>
|
|
<span class="connect-hint">共用主页"设备连接"配置的 IP</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 引脚图 -->
|
|
<div class="glass-strong card">
|
|
<h2>LAN9252 64-QFN 引脚图</h2>
|
|
<p class="card-desc">俯视,Pin1 右上角。点击下方模式按钮预览引脚电压,点"确认应用"发送命令。绿色=3.3V,红色=0V,橙色=控制线测试中。</p>
|
|
<div class="pin-svg-wrap">
|
|
<svg id="pinSvg" width="720" height="720" viewBox="0 0 720 720">
|
|
<defs>
|
|
<linearGradient id="chipGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" stop-color="rgba(60,60,80,.6)"/>
|
|
<stop offset="100%" stop-color="rgba(40,40,60,.5)"/>
|
|
</linearGradient>
|
|
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
|
|
<feGaussianBlur stdDeviation="3.5" result="blur"/>
|
|
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
|
</filter>
|
|
</defs>
|
|
</svg>
|
|
</div>
|
|
<div class="pin-legend">
|
|
<span><span class="swatch" style="background:#16a34a;"></span> HIGH 3.3V</span>
|
|
<span><span class="swatch" style="background:#dc2626;"></span> LOW 0V</span>
|
|
<span><span class="swatch" style="background:#ea580c;"></span> 控制线测试</span>
|
|
<span><span class="swatch" style="background:rgba(232,232,237,.8);"></span> 数据线空闲</span>
|
|
<span><span class="swatch" style="background:rgba(255,245,230,.8);"></span> 控制线空闲</span>
|
|
<span><span class="swatch" style="background:rgba(240,240,243,.8);"></span> 电源/其他</span>
|
|
<span><span class="swatch" style="background:#2563eb;border-radius:50%;"></span> Pin1</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 模式选择 -->
|
|
<div class="glass card">
|
|
<h2>模式选择</h2>
|
|
<p class="card-desc">点击预览引脚图,再点"确认应用"发送命令</p>
|
|
|
|
<div class="section-label">数据线 pattern · D0-D15</div>
|
|
<div class="btn-grid">
|
|
<button class="mode-btn" data-action="01">0xFFFF 全高</button>
|
|
<button class="mode-btn" data-action="02">0x0000 全低</button>
|
|
<button class="mode-btn" data-action="03">0xAAAA 奇数高</button>
|
|
<button class="mode-btn" data-action="04">0x5555 偶数高</button>
|
|
</div>
|
|
|
|
<div class="section-label">控制线测试</div>
|
|
<div class="ctrl-grid">
|
|
<button class="mode-btn" data-action="05">CS 拉低 · PD7→Pin28</button>
|
|
<button class="mode-btn" data-action="06">RD 拉低 · PD4→Pin31</button>
|
|
<button class="mode-btn" data-action="07">WR 拉低 · PD5→Pin30</button>
|
|
<button class="mode-btn" data-action="08">A1 拉高 · PD11→Pin25</button>
|
|
<button class="mode-btn" data-action="09">A2 拉高 · PD12→Pin29</button>
|
|
</div>
|
|
|
|
<div class="section-label">读周期保持 · 判定芯片是否驱动总线</div>
|
|
<div class="ctrl-grid">
|
|
<button class="mode-btn" data-action="10">读保持 · CS#+RD# 持续低</button>
|
|
</div>
|
|
<p class="card-desc" style="margin-top:10px;">
|
|
数据线切为高阻输入、CS#+RD# 持续拉低(读 IDX_0)。用万用表在 <strong>LAN9252 芯片引脚</strong> 上量数据线:
|
|
芯片活着 → D2(Pin12)、D4(Pin49)、D5(Pin50)、D9(Pin19)、D12(Pin21) ≈ 3.3V,其余数据脚 ≈ 0V(全部确定电平,即 0x1234);
|
|
全部中电平/漂移 → 芯片未驱动总线。图上预览色 = 芯片应答的期望值。
|
|
</p>
|
|
</div>
|
|
|
|
<!-- 操作 -->
|
|
<div class="glass card">
|
|
<h2>操作</h2>
|
|
<div class="action-row">
|
|
<button class="confirm-btn" id="confirmBtn" disabled>✓ 确认应用</button>
|
|
<button class="loop-btn" id="loopBtn">🔄 循环切换</button>
|
|
<button class="stop-btn" id="stopBtn">⏹ 停止 / 恢复</button>
|
|
</div>
|
|
<p class="action-desc">
|
|
<strong>确认应用</strong> · 发送当前选中的模式到 GD32<br>
|
|
<strong>循环切换</strong> · 每5s在 0x0000 ↔ 当前选中模式 之间切换(需先选数据线 pattern)<br>
|
|
<strong>停止 / 恢复</strong> · 恢复 EXMC 正常模式
|
|
</p>
|
|
</div>
|
|
|
|
<!-- 日志 -->
|
|
<div class="glass log-panel">
|
|
<div class="log-panel-header">
|
|
<span class="log-panel-title">操作日志</span>
|
|
<div class="log-filters">
|
|
<button class="log-filter active" onclick="setLogFilter('all',this)">全部</button>
|
|
<button class="log-filter" onclick="setLogFilter('E',this)">错误</button>
|
|
<button class="log-filter" onclick="setLogFilter('I',this)">信息</button>
|
|
<button class="log-filter" onclick="clearLogs()">清除</button>
|
|
</div>
|
|
</div>
|
|
<div class="log-scroll" id="logScroll"></div>
|
|
<div class="log-foot">
|
|
<span>共 <span id="logCount">0</span> 条</span>
|
|
<span style="font-family:ui-monospace,monospace;">SPI 0xA2 · UDP 6666</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div><!-- /.wrap -->
|
|
|
|
<script>
|
|
/* ===== LAN9252 64-QFN 引脚表 ===== */
|
|
var pinData=[
|
|
{n:1,name:"OSCI",type:"other"},{n:2,name:"OSCO",type:"other"},
|
|
{n:3,name:"VDD12",type:"power"},{n:4,name:"VSS",type:"gnd"},
|
|
{n:5,name:"VDD33",type:"power"},{n:6,name:"VDDCR",type:"power"},
|
|
{n:7,name:"REG_EN",type:"other"},{n:8,name:"FXLOSEN",type:"other"},
|
|
{n:9,name:"FXSDA",type:"other"},{n:10,name:"FXSDB",type:"other"},
|
|
{n:11,name:"RST#",type:"other"},{n:12,name:"D2",type:"data",d:2},
|
|
{n:13,name:"D1",type:"data",d:1},{n:14,name:"VDDIO",type:"power"},
|
|
{n:15,name:"D14",type:"data",d:14},{n:16,name:"D13",type:"data",d:13},
|
|
{n:17,name:"D0",type:"data",d:0},{n:18,name:"SYNC1",type:"other"},
|
|
{n:19,name:"D9",type:"data",d:9},{n:20,name:"VDDIO",type:"power"},
|
|
{n:21,name:"D12",type:"data",d:12},{n:22,name:"D11",type:"data",d:11},
|
|
{n:23,name:"D10",type:"data",d:10},{n:24,name:"VDDCR",type:"power"},
|
|
{n:25,name:"A1",type:"ctrl",ctrl:"A1"},{n:26,name:"A3",type:"other"},
|
|
{n:27,name:"A4",type:"other"},{n:28,name:"CS",type:"ctrl",ctrl:"CS"},
|
|
{n:29,name:"A2",type:"ctrl",ctrl:"A2"},{n:30,name:"WR",type:"ctrl",ctrl:"WR"},
|
|
{n:31,name:"RD",type:"ctrl",ctrl:"RD"},{n:32,name:"VDDIO",type:"power"},
|
|
{n:33,name:"D15",type:"data",d:15},{n:34,name:"SYNC0",type:"other"},
|
|
{n:35,name:"D3",type:"data",d:3},{n:36,name:"D6",type:"data",d:6},
|
|
{n:37,name:"VDDIO",type:"power"},{n:38,name:"VDDCR",type:"power"},
|
|
{n:39,name:"D7",type:"data",d:7},{n:40,name:"D8",type:"data",d:8},
|
|
{n:41,name:"TESTMODE",type:"other"},{n:42,name:"EESDA",type:"other"},
|
|
{n:43,name:"EESCL",type:"other"},{n:44,name:"IRQ",type:"other"},
|
|
{n:45,name:"RUNLED",type:"other"},{n:46,name:"MODE1",type:"other"},
|
|
{n:47,name:"VDDIO",type:"power"},{n:48,name:"MODE0",type:"other"},
|
|
{n:49,name:"D4",type:"data",d:4},{n:50,name:"D5",type:"data",d:5},
|
|
{n:51,name:"TXRX1",type:"power"},{n:52,name:"TXNA",type:"other"},
|
|
{n:53,name:"TXPA",type:"other"},{n:54,name:"RXNA",type:"other"},
|
|
{n:55,name:"RXPA",type:"other"},{n:56,name:"VDD12",type:"power"},
|
|
{n:57,name:"RBIAS",type:"other"},{n:58,name:"VDD33B",type:"power"},
|
|
{n:59,name:"VDD12",type:"power"},{n:60,name:"RXPB",type:"other"},
|
|
{n:61,name:"RXNB",type:"other"},{n:62,name:"TXPB",type:"other"},
|
|
{n:63,name:"TXNB",type:"other"},{n:64,name:"TXRX2",type:"power"}
|
|
];
|
|
|
|
var actionMap={
|
|
"01":{t:"data",val:0xFFFF,label:"0xFFFF 全高"},"02":{t:"data",val:0x0000,label:"0x0000 全低"},
|
|
"03":{t:"data",val:0xAAAA,label:"0xAAAA 奇数高"},"04":{t:"data",val:0x5555,label:"0x5555 偶数高"},
|
|
"05":{t:"ctrl",val:"CS",label:"CS 拉低"},"06":{t:"ctrl",val:"RD",label:"RD 拉低"},
|
|
"07":{t:"ctrl",val:"WR",label:"WR 拉低"},"08":{t:"ctrl",val:"A1",label:"A1 拉高"},
|
|
"09":{t:"ctrl",val:"A2",label:"A2 拉高"},
|
|
"10":{t:"readhold",val:0x1234,label:"读周期保持(CS#+RD# 持续低)"},
|
|
"00":{t:"stop",label:"停止/恢复"}
|
|
};
|
|
|
|
/* ===== SVG ===== */
|
|
var SVG_NS="http://www.w3.org/2000/svg";
|
|
var CHIP_X1=120,CHIP_Y1=120,CHIP_X2=600,CHIP_Y2=600;
|
|
var CHIP_W=CHIP_X2-CHIP_X1,PIN_GAP=CHIP_W/16,PIN_OFF=PIN_GAP/2;
|
|
var DEFAULT_COLORS={data:"rgba(200,200,210,.5)",ctrl:"rgba(255,200,150,.5)",power:"rgba(220,220,230,.5)",gnd:"rgba(180,180,190,.5)",other:"rgba(210,210,220,.5)"};
|
|
|
|
function el(tag,attrs,text){
|
|
var e=document.createElementNS(SVG_NS,tag);
|
|
for(var k in attrs)e.setAttribute(k,attrs[k]);
|
|
if(text!==undefined)e.textContent=text;
|
|
return e;
|
|
}
|
|
function getPinPos(n){
|
|
if(n>=1&&n<=16){
|
|
/* 顶部:引脚上方,名称在上、编号在下 */
|
|
var x=CHIP_X2-PIN_OFF-(n-1)*PIN_GAP;
|
|
return{rx:x-7,ry:CHIP_Y1-14,rw:14,rh:14,tx:x,ty:CHIP_Y1-16,anchor:"middle",ny:CHIP_Y1-32};
|
|
}else if(n>=17&&n<=32){
|
|
/* 左侧:引脚左方,编号在上、名称在下(竖向堆叠) */
|
|
var y=CHIP_Y1+PIN_OFF+(n-17)*PIN_GAP;
|
|
return{rx:CHIP_X1-14,ry:y-7,rw:14,rh:14,tx:CHIP_X1-18,ty:y-1,anchor:"end",ny:y+13};
|
|
}else if(n>=33&&n<=48){
|
|
/* 底部:引脚下方,编号在上、名称在下 */
|
|
var x=CHIP_X1+PIN_OFF+(n-33)*PIN_GAP;
|
|
return{rx:x-7,ry:CHIP_Y2,rw:14,rh:14,tx:x,ty:CHIP_Y2+22,anchor:"middle",ny:CHIP_Y2+38};
|
|
}else{
|
|
/* 右侧:引脚右方,编号在上、名称在下(竖向堆叠) */
|
|
var y=CHIP_Y2-PIN_OFF-(n-49)*PIN_GAP;
|
|
return{rx:CHIP_X2,ry:y-7,rw:14,rh:14,tx:CHIP_X2+18,ty:y-1,anchor:"start",ny:y+13};
|
|
}
|
|
}
|
|
var pinRects={};
|
|
function buildSvg(){
|
|
var svg=document.getElementById("pinSvg");
|
|
svg.appendChild(el("rect",{class:"chip-body",x:CHIP_X1,y:CHIP_Y1,width:CHIP_W,height:CHIP_W,rx:14}));
|
|
svg.appendChild(el("text",{class:"chip-label",x:360,y:345},"LAN9252"));
|
|
svg.appendChild(el("text",{class:"chip-sub",x:360,y:368},"64-QFN"));
|
|
svg.appendChild(el("text",{class:"chip-sub",x:360,y:386},"Pin1 → 右上角"));
|
|
svg.appendChild(el("circle",{class:"pin1-dot",cx:CHIP_X2-14,cy:CHIP_Y1+14,r:5}));
|
|
for(var i=0;i<pinData.length;i++){
|
|
var p=pinData[i],pos=getPinPos(p.n);
|
|
var rect=el("rect",{class:"pin-rect",x:pos.rx,y:pos.ry,width:pos.rw,height:pos.rh,rx:3,id:"pin-"+p.n});
|
|
rect.setAttribute("fill",DEFAULT_COLORS[p.type]||DEFAULT_COLORS.other);
|
|
svg.appendChild(rect);
|
|
pinRects[p.n]=rect;
|
|
svg.appendChild(el("text",{class:"pin-num",x:pos.tx,y:pos.ty,"text-anchor":pos.anchor},String(p.n)));
|
|
var nc="pin-name";
|
|
if(p.type==="data")nc="pin-name pin-name-data";
|
|
else if(p.type==="ctrl")nc="pin-name pin-name-ctrl";
|
|
svg.appendChild(el("text",{class:nc,x:pos.tx,y:pos.ny||pos.ty,"text-anchor":pos.anchor},p.name));
|
|
}
|
|
}
|
|
function updatePinColors(actionHex){
|
|
var act=actionMap[actionHex];if(!act)return;
|
|
for(var i=0;i<pinData.length;i++){
|
|
var p=pinData[i],color=DEFAULT_COLORS[p.type]||DEFAULT_COLORS.other;
|
|
var glow=false;
|
|
if(act.t==="data"&&p.type==="data"){
|
|
color=(act.val&(1<<p.d))?"#30d158":"#ff453a";glow=true;
|
|
}else if(act.t==="ctrl"&&p.type==="ctrl"&&p.ctrl===act.val){
|
|
color="#ff9f0a";glow=true;
|
|
}else if(act.t==="readhold"){
|
|
/* CS/RD 由 GD32 拉低(橙), 数据线 = 芯片应答期望值 0x1234 */
|
|
if(p.type==="ctrl"&&(p.ctrl==="CS"||p.ctrl==="RD")){color="#ff9f0a";glow=true;}
|
|
else if(p.type==="data"){color=(act.val&(1<<p.d))?"#30d158":"#ff453a";glow=true;}
|
|
}
|
|
pinRects[p.n].setAttribute("fill",color);
|
|
if(glow)pinRects[p.n].setAttribute("filter","url(#glow)");
|
|
else pinRects[p.n].removeAttribute("filter");
|
|
}
|
|
}
|
|
|
|
function resetPinColors(){
|
|
for(var i=0;i<pinData.length;i++){
|
|
var p=pinData[i];
|
|
pinRects[p.n].setAttribute("fill",DEFAULT_COLORS[p.type]||DEFAULT_COLORS.other);
|
|
pinRects[p.n].removeAttribute("filter");
|
|
}
|
|
}
|
|
|
|
/* ===== IP ===== */
|
|
var LS_ESP_IP="dcu_esp_ip",DEFAULT_IP="192.168.31.127";
|
|
function getIp(){try{var s=localStorage.getItem(LS_ESP_IP);if(s&&s.trim())return s.trim();}catch(e){}return DEFAULT_IP;}
|
|
document.getElementById("ipDisplay").textContent=getIp();
|
|
window.addEventListener("storage",function(e){if(e.key===LS_ESP_IP)document.getElementById("ipDisplay").textContent=getIp();});
|
|
|
|
/* ===== 日志 ===== */
|
|
var logScroll=document.getElementById("logScroll"),logCountEl=document.getElementById("logCount"),logCount=0,logFilter="all";
|
|
function esc(s){return String(s==null?"":s).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");}
|
|
function addLog(level,tag,msg){
|
|
var line=document.createElement("div");
|
|
line.className="log-line";
|
|
if(logFilter!=="all"&&level!==logFilter){line.style.display="none";}
|
|
/* msg/tag 含设备响应内容(如 /spi/send JSON),拼 innerHTML 前必须转义 */
|
|
line.innerHTML='<span class="lt">['+new Date().toLocaleTimeString()+']</span>'+
|
|
'<span class="lv '+esc(level)+'">'+esc(level)+'</span>'+
|
|
'<span class="lg">['+esc(tag)+']</span>'+
|
|
'<span class="lm">'+esc(msg)+'</span>';
|
|
logScroll.insertBefore(line,logScroll.firstChild);
|
|
logCount++;logCountEl.textContent=logCount;
|
|
while(logScroll.children.length>200)logScroll.removeChild(logScroll.lastChild);
|
|
}
|
|
function setLogFilter(f,btn){
|
|
logFilter=f;
|
|
document.querySelectorAll(".log-filter").forEach(function(b){b.classList.remove("active");});
|
|
btn.classList.add("active");
|
|
logScroll.querySelectorAll(".log-line").forEach(function(l){
|
|
var lv=l.querySelector(".lv");
|
|
if(f==="all"||(lv&&lv.classList.contains(f)))l.style.display="";
|
|
else l.style.display="none";
|
|
});
|
|
}
|
|
function clearLogs(){logScroll.innerHTML="";logCount=0;logCountEl.textContent=0;}
|
|
|
|
/* ===== 按钮:液体弹动效果 ===== */
|
|
document.addEventListener("click",function(e){
|
|
var btn=e.target.closest("button");
|
|
if(!btn)return;
|
|
btn.classList.remove("pressed");
|
|
void btn.offsetWidth; /* 强制 reflow,允许重复触发 */
|
|
btn.classList.add("pressed");
|
|
var done=function(){
|
|
btn.classList.remove("pressed");
|
|
btn.removeEventListener("animationend",done);
|
|
};
|
|
btn.addEventListener("animationend",done);
|
|
},true);
|
|
|
|
/* ===== 模式选择 ===== */
|
|
var selectedAction=null,lastModeBtn=null;
|
|
document.querySelectorAll(".mode-btn").forEach(function(btn){
|
|
btn.addEventListener("click",function(){
|
|
var action=btn.getAttribute("data-action");
|
|
if(lastModeBtn)lastModeBtn.classList.remove("selected");
|
|
btn.classList.add("selected");
|
|
lastModeBtn=btn;
|
|
selectedAction=action;
|
|
updatePinColors(action);
|
|
document.getElementById("confirmBtn").disabled=false;
|
|
addLog("I","预览","选中: "+actionMap[action].label);
|
|
});
|
|
});
|
|
|
|
/* ===== 发送 ===== */
|
|
var busy=false;
|
|
function sendAction(actionHex){
|
|
if(busy)return;
|
|
var ip=getIp();if(!ip){addLog("E","错误","请先在主页配置 ESP32 IP");return;}
|
|
busy=true;
|
|
var label=actionMap[actionHex]?actionMap[actionHex].label:actionHex;
|
|
addLog("I","发送","action=0x"+actionHex+" ("+label+")");
|
|
fetch("http://"+ip+"/spi/send",{
|
|
method:"POST",headers:{"Content-Type":"application/json"},
|
|
body:JSON.stringify({type:"A2",data:actionHex,timeout_ms:2000}),
|
|
signal:AbortSignal.timeout(3000)
|
|
})
|
|
.then(function(r){if(!r.ok)throw new Error("HTTP "+r.status);return r.json();})
|
|
.then(function(j){
|
|
if(j.status==="ok"&&j.resp_type==="80")
|
|
addLog("I","成功",label+" → ACK"+(j.recv?" 0x"+j.recv:""));
|
|
else if(j.status==="ok"&&j.resp_type==="82")
|
|
addLog("E","失败",label+" → GD32 ERROR");
|
|
else
|
|
addLog("E","异常",label+" → "+JSON.stringify(j));
|
|
})
|
|
.catch(function(err){addLog("E","网络",label+" → "+err.message);})
|
|
.then(function(){busy=false;});
|
|
}
|
|
|
|
document.getElementById("confirmBtn").addEventListener("click",function(){
|
|
if(selectedAction)sendAction(selectedAction);
|
|
});
|
|
|
|
document.getElementById("stopBtn").addEventListener("click",function(){
|
|
stopLoop();
|
|
sendAction("00");
|
|
resetPinColors();
|
|
if(lastModeBtn)lastModeBtn.classList.remove("selected");
|
|
lastModeBtn=null;selectedAction=null;
|
|
document.getElementById("confirmBtn").disabled=true;
|
|
addLog("I","恢复","EXMC 恢复正常模式");
|
|
});
|
|
|
|
/* ===== 循环: 0x0000 ↔ 当前模式 ===== */
|
|
var loopTimer=null,loopToggle=false;
|
|
var loopBtn=document.getElementById("loopBtn");
|
|
loopBtn.addEventListener("click",function(){
|
|
if(loopTimer)stopLoop();else startLoop();
|
|
});
|
|
function startLoop(){
|
|
if(!selectedAction||actionMap[selectedAction].t!=="data"){
|
|
addLog("E","循环","请先选择一个数据线 pattern 模式");
|
|
return;
|
|
}
|
|
stopLoop();
|
|
var baseAction=selectedAction;
|
|
loopBtn.textContent="⏸ 停止循环";
|
|
loopBtn.classList.add("active");
|
|
addLog("I","循环","开始:每5s在 0x0000 ↔ "+actionMap[baseAction].label+" 切换");
|
|
loopToggle=false;
|
|
loopBtn.setAttribute("data-base",baseAction);
|
|
doLoopStep();
|
|
loopTimer=setInterval(doLoopStep,5000);
|
|
}
|
|
function stopLoop(){
|
|
if(loopTimer){clearInterval(loopTimer);loopTimer=null;}
|
|
loopBtn.textContent="🔄 循环切换";
|
|
loopBtn.classList.remove("active");
|
|
loopBtn.removeAttribute("data-base");
|
|
}
|
|
function doLoopStep(){
|
|
var baseAction=loopBtn.getAttribute("data-base");if(!baseAction)return;
|
|
loopToggle=!loopToggle;
|
|
var action=loopToggle?baseAction:"02";
|
|
if(lastModeBtn)lastModeBtn.classList.remove("selected");
|
|
var btn=document.querySelector('[data-action="'+action+'"]');
|
|
if(btn){btn.classList.add("selected");lastModeBtn=btn;}
|
|
selectedAction=action;
|
|
updatePinColors(action);
|
|
sendAction(action);
|
|
}
|
|
|
|
buildSvg();
|
|
addLog("I","系统","通断测试工具就绪");
|
|
</script>
|
|
</body>
|
|
</html>
|