| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>MicroLink Web Serial Terminal</title>
- <link rel="stylesheet" href="assets/style.css">
- <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
- <!-- 代码预览区美化和复制按钮 -->
- <style>
- .code-block { background: #1e1e1e; color: #f8f8f2; font-family: 'Consolas', 'Menlo', 'Monaco', 'monospace'; font-size: 14px; line-height: 1.6; border-radius: 8px; padding: 18px 18px 18px 24px; overflow-x: auto; min-height: 120px; white-space: pre; position: relative; }
- .code-copy-btn { position: absolute; top: 12px; right: 18px; background: #444; color: #fff; border: none; border-radius: 4px; padding: 4px 12px; font-size: 13px; cursor: pointer; opacity: 0.8; transition: opacity 0.2s; z-index: 2; }
- .code-copy-btn:hover { opacity: 1; background: #3498db; }
- /* 右侧面板整体美化 */
- .right-panel {
- background: #f7fafd;
- border-radius: 14px;
- box-shadow: 0 4px 24px rgba(52,152,219,0.08);
- padding: 28px 24px 24px 24px;
- margin: 0 0 0 18px;
- min-width: 420px;
- }
- /* FLM ELF解析器美化 */
- .flm-parser-panel {
- background: #fff;
- border-radius: 12px;
- box-shadow: 0 2px 12px rgba(52,152,219,0.07);
- padding: 28px 28px 18px 28px;
- margin-bottom: 18px;
- border: 1.5px solid #e3eaf2;
- }
- .flm-parser-panel h2 {
- color: #2980b9;
- font-size: 1.6rem;
- margin-bottom: 8px;
- }
- .flm-parser-panel p {
- color: #666;
- font-size: 1.05rem;
- margin-bottom: 18px;
- }
- .flm-parser-panel input[type="file"] {
- margin: 10px 0 18px 0;
- font-size: 1rem;
- border: 1px solid #d0d7de;
- border-radius: 6px;
- padding: 8px 12px;
- background: #f8fafc;
- }
- .flm-parser-panel button, .flm-parser-panel a.btn {
- margin: 8px 8px 8px 0;
- width: 160px;
- font-size: 1rem;
- border-radius: 6px;
- box-shadow: 0 2px 8px rgba(52,152,219,0.06);
- text-align: center;
- padding: 10px 16px;
- }
- .flm-parser-panel a.btn {
- padding: 10px 16px;
- display: inline-block;
- text-decoration: none;
- }
- .flm-parser-panel pre#log {
- background: #23272e;
- color: #e0e0e0;
- border-radius: 7px;
- padding: 14px 18px;
- font-size: 14px;
- min-height: 80px;
- margin-top: 12px;
- margin-bottom: 0;
- font-family: 'Consolas', 'Menlo', 'Monaco', 'monospace';
- box-shadow: 0 1px 4px rgba(52,152,219,0.04);
- }
- #flmYmodemSendBtn {
- width: 160px;
- font-size: 1rem;
- border-radius: 6px;
- }
- #flmYmodemProgress {
- width: 180px;
- height: 16px;
- vertical-align: middle;
- margin-left: 16px;
- border-radius: 8px;
- background: #eaf6fb;
- box-shadow: 0 1px 4px rgba(52,152,219,0.04);
- }
- #flmYmodemLog {
- background: #23272e;
- color: #0f0;
- padding: 8px 10px;
- border-radius: 6px;
- min-height: 36px;
- max-height: 90px;
- overflow: auto;
- font-size: 13px;
- margin-top: 8px;
- font-family: 'Consolas', 'Menlo', 'Monaco', 'monospace';
- }
- /* Python脚本生成器美化 */
- .config-panel {
- background: #fff;
- border-radius: 12px;
- box-shadow: 0 2px 12px rgba(52,152,219,0.07);
- padding: 24px 24px 18px 24px;
- border: 1.5px solid #e3eaf2;
- }
- .config-group {
- margin-bottom: 22px;
- padding-bottom: 10px;
- border-bottom: 1px solid #e9e9e9;
- }
- .config-group:last-child {
- border-bottom: none;
- }
- .config-panel h2 {
- color: #2980b9;
- font-size: 1.18rem;
- margin-bottom: 8px;
- }
- .config-panel label {
- font-weight: 500;
- color: #444;
- margin-bottom: 4px;
- }
- .config-panel input, .config-panel select {
- width: 100%;
- padding: 10px 12px;
- border: 1.5px solid #d0d7de;
- border-radius: 6px;
- font-size: 1rem;
- margin-bottom: 8px;
- background: #f8fafc;
- transition: border-color 0.2s;
- }
- .config-panel input:focus, .config-panel select:focus {
- border-color: #3498db;
- outline: none;
- }
- .btn-download {
- width: 100%;
- margin-top: 18px;
- font-size: 1.08rem;
- border-radius: 6px;
- padding: 12px 0;
- box-shadow: 0 2px 8px rgba(52,152,219,0.06);
- }
- .preview-panel {
- background: #23272e;
- border-radius: 12px;
- padding: 24px 18px 18px 18px;
- color: #f8f8f2;
- box-shadow: 0 2px 12px rgba(52,152,219,0.07);
- min-width: 320px;
- margin-left: 18px;
- }
- .code-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- }
- .code-header h2 {
- color: #fff;
- font-size: 1.18rem;
- margin: 0;
- }
- .script-tabs {
- display: flex;
- gap: 10px;
- }
- .script-tab {
- padding: 7px 18px;
- background: #444;
- color: #ccc;
- border: none;
- border-radius: 4px;
- cursor: pointer;
- font-size: 14px;
- transition: all 0.3s;
- }
- .script-tab.active {
- background: #3498db;
- color: #fff;
- }
- .script-tab:hover {
- background: #555;
- }
- .script-tab.active:hover {
- background: #2980b9;
- }
- .script-content {
- display: none;
- }
- .script-content.active {
- display: block;
- }
- .code-block {
- background: #181c23;
- color: #f8f8f2;
- font-family: 'Consolas', 'Menlo', 'Monaco', 'monospace';
- font-size: 15px;
- line-height: 1.7;
- border-radius: 8px;
- padding: 18px 18px 18px 24px;
- overflow-x: auto;
- min-height: 120px;
- white-space: pre;
- position: relative;
- box-shadow: 0 1px 4px rgba(52,152,219,0.04);
- }
- .code-copy-btn {
- position: absolute;
- top: 12px;
- right: 18px;
- background: #3498db;
- color: #fff;
- border: none;
- border-radius: 4px;
- padding: 4px 12px;
- font-size: 13px;
- cursor: pointer;
- opacity: 0.85;
- transition: opacity 0.2s, background 0.2s;
- z-index: 2;
- }
- .code-copy-btn:hover {
- opacity: 1;
- background: #217dbb;
- }
- .highlight {
- background-color: #ffe082;
- color: #222;
- border-radius: 3px;
- padding: 1px 4px;
- }
- @media (max-width: 1200px) {
- .right-panel { min-width: 320px; padding: 18px 6px 18px 6px; }
- .preview-panel { min-width: 0; margin-left: 0; }
- }
- @media (max-width: 900px) {
- .right-panel { min-width: 0; padding: 8px 2px 8px 2px; }
- .preview-panel { padding: 12px 4px 12px 4px; }
- }
- </style>
- <style>
- #pyYmodemLog, #flmYmodemLog, #log, #pyYmodemProgress { display: none !important; }
- </style>
- </head>
- <body>
- <div class="container">
- <!-- 头部控制栏 -->
- <header class="header">
- <div class="header-left">
- <h1><i class="fas fa-microchip"></i> MicroLink Web Serial Terminal</h1>
- </div>
- <div class="header-right">
- <!-- 移动过来的四个按键 -->
- <div class="header-controls">
- <button id="connectBtn" class="btn btn-primary"><i class="fas fa-plug"></i> 连接串口</button>
- <button id="disconnectBtn" class="btn btn-secondary" disabled><i class="fas fa-times"></i> 断开连接</button>
- <button id="clearBtn" class="btn btn-warning"><i class="fas fa-trash"></i> 清空</button>
- <button id="saveLogBtn" class="btn btn-info"><i class="fas fa-save"></i> 保存日志</button>
- </div>
- <div class="connection-status">
- <span id="connectionStatus" class="status-disconnected">
- <i class="fas fa-circle"></i> 未连接
- </span>
- </div>
- </div>
- </header>
- <!-- 主要内容区域 -->
- <main class="main-content">
- <!-- 左侧功能块选择栏 -->
- <div class="sidebar">
- <button class="sidebar-btn active" data-panel="serialPanel">串口调试</button>
- <button class="sidebar-btn" data-panel="flmPanel">FLM配置</button>
- <button class="sidebar-btn" data-panel="scriptPanel">Python脚本配置</button>
- <button class="sidebar-btn" data-panel="varPanel">变量分析</button>
- </div>
- <!-- 中间功能内容区 -->
- <div class="center-panel">
- <!-- 串口调试内容(API控制区,默认显示) -->
- <div id="serialPanel" class="center-content" style="display: block;">
- <div class="control-panel">
- <h3><i class="fas fa-cogs"></i> MicroLink API 控制</h3>
- <!-- 参数管理 -->
- <div class="api-section">
- <h4><i class="fas fa-save"></i> 参数管理</h4>
- <div class="button-group">
- <button id="saveParams" class="btn btn-success">保存到本地</button>
- <button id="saveToFile" class="btn btn-primary">保存到文件</button>
- <button id="loadParams" class="btn btn-info">加载参数</button>
- <button id="resetParams" class="btn btn-warning">重置参数</button>
- <button id="loadConfigFile" class="btn btn-secondary">重新加载HTML配置</button>
- </div>
- <div class="param-group">
- <label>手动选择配置文件 (可选):</label>
- <input type="file" id="configFileInput" accept=".txt" class="param-input" style="font-size: 12px;">
- </div>
- </div>
- <!-- 自定义命令 -->
- <div class="api-section">
- <h4><i class="fas fa-terminal"></i> 自定义命令</h4>
- <div class="param-group">
- <textarea id="customCommand" placeholder="输入自定义命令..." class="custom-command-input"></textarea>
- </div>
- <div class="button-group">
- <button id="sendCustom" class="btn btn-primary">发送命令</button>
- <button id="addCustom" class="btn btn-secondary">添加到列表</button>
- </div>
- <div id="customCommandsList" class="custom-commands-list"></div>
- </div>
- <!-- RTT控制 -->
- <div class="api-section">
- <h4><i class="fas fa-satellite-dish"></i> RTT 控制</h4>
- <div class="param-group">
- <label>RTT地址:</label>
- <input type="text" id="rttAddr" value="0x20000000" class="param-input">
- </div>
- <div class="param-group">
- <label>搜索大小:</label>
- <input type="text" id="rttSize" value="0x4000" class="param-input">
- </div>
- <div class="param-group">
- <label>通道:</label>
- <input type="number" id="rttChannel" value="0" class="param-input">
- </div>
- <div class="button-group">
- <button id="startRTT" class="btn btn-primary">启动RTT</button>
- <button id="stopRTT" class="btn btn-secondary">停止RTT</button>
- </div>
- </div>
- <!-- SystemView控制 -->
- <div class="api-section">
- <h4><i class="fas fa-chart-line"></i> SystemView 控制</h4>
- <div class="param-group">
- <label>RTT地址:</label>
- <input type="text" id="svAddr" value="0x20000000" class="param-input">
- </div>
- <div class="param-group">
- <label>搜索大小:</label>
- <input type="text" id="svSize" value="0x4000" class="param-input">
- </div>
- <div class="param-group">
- <label>通道:</label>
- <input type="number" id="svChannel" value="1" class="param-input">
- </div>
- <div class="button-group">
- <button id="startSystemView" class="btn btn-primary">启动SystemView</button>
- </div>
- </div>
- <!-- FLM下载算法 -->
- <div class="api-section">
- <h4><i class="fas fa-download"></i> FLM 下载算法</h4>
- <div class="param-group">
- <label>FLM路径:</label>
- <input type="text" id="flmPath" value="STM32/STM32F4xx_1024.FLM.o" class="param-input">
- </div>
- <div class="param-group">
- <label>Flash基地址:</label>
- <input type="text" id="baseAddr" value="0x08000000" class="param-input">
- </div>
- <div class="param-group">
- <label>RAM地址:</label>
- <input type="text" id="ramAddr" value="0x20000000" class="param-input">
- </div>
- <div class="button-group">
- <button id="loadFLM" class="btn btn-primary">加载FLM</button>
- </div>
- </div>
- <!-- 固件下载 -->
- <div class="api-section">
- <h4><i class="fas fa-upload"></i> 固件下载</h4>
- <div class="param-group">
- <label>BIN文件路径:</label>
- <input type="text" id="binPath" value="firmware.bin" class="param-input">
- </div>
- <div class="param-group">
- <label>下载地址:</label>
- <input type="text" id="binAddr" value="0x08000000" class="param-input">
- </div>
- <div class="button-group">
- <button id="loadBin" class="btn btn-primary">下载固件</button>
- <button id="offlineDownload" class="btn btn-warning">脱机下载</button>
- </div>
- </div>
- <!-- Ymodem传输 -->
- <div class="api-section">
- <h4><i class="fas fa-exchange-alt"></i> Ymodem 传输</h4>
- <div class="param-group">
- <label>文件路径:</label>
- <input type="text" id="ymodemFile" value="update.bin" class="param-input">
- </div>
- <div class="button-group">
- <button id="ymodemSend" class="btn btn-primary">发送文件</button>
- </div>
- </div>
- </div>
- </div>
- <!-- FLM配置内容 -->
- <div id="flmPanel" class="center-content" style="display: none;">
- <div class="flm-parser-panel">
- <h2>FLM ELF解析器</h2>
- <p>将FLM ELF文件转换为.o格式,用于串口设备烧写</p>
- <input type="file" id="flmFile" accept=".FLM">
- <button id="convertBtn" disabled class="btn btn-warning">转换为 .o 文件</button>
- <a id="downloadLink" href="#" style="display: none;" class="btn btn-success">下载 .o 文件</a>
- <pre id="log"></pre>
- <!-- YMODEM发送区 -->
- <button id="flmYmodemSendBtn" class="btn btn-primary" disabled>发送FLM文件</button>
- <progress id="flmYmodemProgress" value="0" max="100" style="width:180px;vertical-align:middle;display:none;margin:8px 0 0 0;"></progress>
- </div>
- </div>
- <!-- Python脚本配置内容 -->
- <div id="scriptPanel" class="center-content" style="display: none;">
- <div class="content">
- <div class="config-panel">
- <div class="config-group">
- <h2>拖拽下载FLM文件配置</h2>
- <div class="form-group">
- <label for="customFlm">FLM文件名:</label>
- <input type="text" id="customFlm" placeholder="例如: custom_flm.FLM.o">
- </div>
- </div>
- <div class="config-group">
- <h2>内存地址配置</h2>
- <div class="address-inputs">
- <div class="form-group">
- <label for="address1">FLASH基地址 (十六进制):</label>
- <input type="text" id="address1" value="0X08000000" placeholder="例如: 0X08000000">
- </div>
- <div class="form-group">
- <label for="address2">RAM基地址 (十六进制):</label>
- <input type="text" id="address2" value="0x20000000" placeholder="例如: 0x20000000">
- </div>
- </div>
- </div>
- <div class="config-group">
- <h2>多文件离线烧录配置</h2>
- <div class="multi-file-config">
- <div class="file-table-header">
- <span class="file-name-col">文件名</span>
- <span class="file-address-col">地址</span>
- <span class="file-algorithm-col">下载算法</span>
- <span class="file-action-col">操作</span>
- </div>
- <div id="fileTableBody" class="file-table-body">
- <!-- 动态生成的文件行将在这里 -->
- </div>
- <div class="file-table-actions">
- <button type="button" id="addFileBtn" class="btn btn-success">
- <i class="fas fa-plus"></i> 添加文件
- </button>
- <button type="button" id="clearFilesBtn" class="btn btn-warning">
- <i class="fas fa-trash"></i> 清空所有
- </button>
- </div>
- </div>
- </div>
- <div class="config-group">
- <h2>SWD下载速度配置</h2>
- <div class="form-group">
- <label for="swdClockSpeed">SWD时钟速度:</label>
- <select id="swdClockSpeed">
- <option value="10M">10M (最快)</option>
- <option value="5M">5M</option>
- <option value="2M">2M</option>
- <option value="1M">1M</option>
- <option value="500K">500K</option>
- <option value="200K">200K</option>
- <option value="100K">100K</option>
- <option value="50K">50K</option>
- <option value="20K">20K</option>
- <option value="10K">10K</option>
- <option value="5K">5K</option>
- </select>
- </div>
- </div>
- <div class="config-group">
- <button class="download-btn send-offline" id="pyYmodemSendOfflineBtn">发送离线下载python文件</button>
- <button class="download-btn send-drag" id="pyYmodemSendDragBtn">发送拖拽下载python文件</button>
- <progress id="pyYmodemProgress" value="0" max="100" style="width:180px;vertical-align:middle;display:none;margin-left:16px;"></progress>
- <pre id="pyYmodemLog" style="background:#23272e;color:#0f0;padding:8px 10px;border-radius:6px;min-height:36px;max-height:90px;overflow:auto;font-size:13px;margin-top:8px;font-family:'Consolas','Menlo','Monaco','monospace';"></pre>
- </div>
- </div>
- <!--
- <div class="preview-panel">
- <div class="code-header">
- <h2>生成的Python代码</h2>
- <div class="script-tabs">
- <button class="script-tab active" onclick="switchScriptTab('offline')">离线下载脚本</button>
- <button class="script-tab" onclick="switchScriptTab('drag')">拖拽下载脚本</button>
- </div>
- </div>
- <div id="offlineScript" class="script-content active">
- <div style="position:relative;">
- <button class="code-copy-btn" id="copyOfflineBtn">复制</button>
- <pre class="code-block"><code id="codePreview"></code></pre>
- </div>
- </div>
- <div id="dragScript" class="script-content">
- <div style="position:relative;">
- <button class="code-copy-btn" id="copyDragBtn">复制</button>
- <pre class="code-block"><code id="dragCodePreview"></code></pre>
- </div>
- </div>
- </div>
- -->
-
- </div>
- </div>
- <!-- 变量分析内容 -->
- <div id="varPanel" class="center-content" style="display: none;">
- <style>
- /* 变量分析tab美化,统一主站风格 */
- #varPanel .container {
- background: #fff;
- border-radius: 12px;
- box-shadow: 0 2px 12px rgba(52,152,219,0.07);
- padding: 24px 24px 18px 24px;
- border: 1.5px solid #e3eaf2;
- margin-top: 18px;
- width: 100%;
- max-width: none;
- }
- #varPanel .header {
- background: linear-gradient(135deg, #3498db, #764ba2);
- color: #fff;
- padding: 18px 20px;
- border-radius: 10px;
- margin-bottom: 20px;
- box-shadow: 0 2px 8px rgba(52,152,219,0.08);
- }
- #varPanel .card {
- border-radius: 10px;
- box-shadow: 0 2px 8px rgba(52,152,219,0.06);
- margin-bottom: 20px;
- border: none;
- }
- #varPanel .card-header {
- background-color: #f0f3fa;
- border-bottom: 1px solid #e3eaf2;
- font-weight: 600;
- padding: 13px 18px;
- border-radius: 10px 10px 0 0 !important;
- color: #2980b9;
- }
- #varPanel .table-container {
- max-height: 400px;
- overflow-y: auto;
- }
- #varPanel .variable-table {
- width: 100%;
- font-size: 14px;
- }
- #varPanel .variable-table th {
- background-color: #eaf6fb;
- position: sticky;
- top: 0;
- z-index: 10;
- }
- #varPanel .memory-map {
- height: 300px;
- background-color: #f8fafc;
- border-radius: 8px;
- padding: 15px;
- margin-bottom: 20px;
- position: relative;
- }
- #varPanel .legend {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- margin-top: 15px;
- }
- #varPanel .legend-item {
- display: flex;
- align-items: center;
- font-size: 13px;
- }
- #varPanel .legend-color {
- width: 15px;
- height: 15px;
- border-radius: 3px;
- margin-right: 5px;
- border: 1px solid rgba(0,0,0,0.1);
- }
- #varPanel .status-bar {
- background-color: #eaf6fb;
- padding: 10px 15px;
- border-radius: 5px;
- margin-top: 20px;
- color: #2980b9;
- }
- #varPanel .detail-card {
- background-color: #f8f9fa;
- border-radius: 8px;
- padding: 15px;
- font-size: 14px;
- font-family: monospace;
- white-space: pre-wrap;
- max-height: 200px;
- overflow-y: auto;
- }
- #varPanel .file-info {
- background-color: #e7f4e4;
- padding: 10px;
- border-radius: 5px;
- margin-bottom: 15px;
- font-size: 13px;
- }
- #varPanel .btn-primary {
- background: #3498db;
- color: #fff;
- border: none;
- border-radius: 6px;
- font-weight: 600;
- font-size: 15px;
- padding: 8px 0;
- transition: background 0.2s;
- }
- #varPanel .btn-primary:disabled {
- background: #b3d3f7;
- color: #fff;
- }
- #varPanel .btn-primary:hover:not(:disabled) {
- background: #2980b9;
- }
- #varPanel .form-label {
- font-weight: 500;
- color: #2980b9;
- }
- #varPanel .progress-bar {
- background: linear-gradient(90deg, #3498db, #764ba2);
- }
- /* 变量分析页面新布局样式 */
- #varPanel .var-analysis-layout {
- display: grid;
- grid-template-columns: 1fr 1.5fr;
- gap: 25px;
- height: calc(100vh - 180px);
- width: 100%;
- max-width: none;
- margin: 0;
- padding: 0;
- }
- #varPanel .left-panel {
- display: flex;
- flex-direction: column;
- gap: 20px;
- min-width: 320px;
- flex: 1;
- }
- #varPanel .right-panel {
- display: flex;
- flex-direction: column;
- gap: 20px;
- min-width: 400px;
- flex: 1.5;
- align-items: stretch;
- }
- #varPanel .filter-section {
- background: #fff;
- border-radius: 12px;
- padding: 20px;
- box-shadow: 0 2px 8px rgba(52,152,219,0.06);
- border: 1.5px solid #e3eaf2;
- }
- #varPanel .filter-controls {
- display: flex;
- gap: 15px;
- align-items: center;
- margin-bottom: 15px;
- flex-wrap: wrap;
- }
- #varPanel .filter-controls select {
- padding: 8px 12px;
- border: 1px solid #d0d7de;
- border-radius: 6px;
- font-size: 14px;
- background: #f8fafc;
- }
- #varPanel .filter-controls input {
- padding: 8px 12px;
- border: 1px solid #d0d7de;
- border-radius: 6px;
- font-size: 14px;
- background: #f8fafc;
- width: 100%;
- min-width: 150px;
- }
- #varPanel .variable-table-container {
- background: #fff;
- border-radius: 12px;
- padding: 20px;
- box-shadow: 0 2px 8px rgba(52,152,219,0.06);
- border: 1.5px solid #e3eaf2;
- flex: 1;
- overflow: hidden;
- min-height: 350px;
- min-width: 0;
- }
- #varPanel .variable-table {
- width: 100%;
- border-collapse: collapse;
- }
- #varPanel .variable-table th,
- #varPanel .variable-table td {
- padding: 10px 12px;
- text-align: left;
- border-bottom: 1px solid #e3eaf2;
- }
- #varPanel .variable-table th:first-child,
- #varPanel .variable-table td:first-child {
- width: 60px;
- text-align: center;
- }
- #varPanel .variable-table th:nth-child(2),
- #varPanel .variable-table td:nth-child(2) {
- white-space: normal;
- word-break: break-word;
- max-width: 0;
- }
- #varPanel .variable-table th:nth-child(3),
- #varPanel .variable-table td:nth-child(3) {
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
- #varPanel .variable-table th:nth-child(4),
- #varPanel .variable-table td:nth-child(4) {
- white-space: nowrap;
- text-align: right;
- }
- #varPanel .variable-table th {
- background: linear-gradient(135deg, #eaf6fb 0%, #f0f8ff 100%);
- color: #2980b9;
- font-weight: 600;
- position: sticky;
- top: 0;
- z-index: 10;
- }
- #varPanel .variable-table tbody tr:hover {
- background: #f8fafc;
- }
- #varPanel .variable-table tbody tr.selected {
- background: #e3f2fd !important;
- border-left: 4px solid #2196f3;
- }
- #varPanel .variable-checkbox {
- margin-right: 8px;
- }
- /* 多变量图表容器样式 */
- .multi-chart-container {
- display: flex;
- flex-direction: column;
- gap: 20px;
- width: 100%;
- transition: min-height 0.3s ease-in-out;
- }
-
- .variable-chart {
- background: #fff;
- border-radius: 8px;
- box-shadow: 0 2px 8px rgba(52,152,219,0.06);
- padding: 20px;
- border: 1.5px solid #e3eaf2;
- }
-
- .variable-chart-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 15px;
- padding-bottom: 10px;
- border-bottom: 1px solid #e3eaf2;
- }
-
- .variable-chart-title {
- font-size: 16px;
- font-weight: 600;
- color: #2980b9;
- }
-
- .variable-chart-controls {
- display: flex;
- gap: 10px;
- }
-
- .variable-chart-controls button {
- padding: 4px 8px;
- font-size: 12px;
- border-radius: 4px;
- }
-
- #varPanel .hex-send-panel {
- background: #fff;
- border-radius: 12px;
- padding: 20px;
- box-shadow: 0 2px 8px rgba(52,152,219,0.06);
- border: 1.5px solid #e3eaf2;
- flex: 1;
- min-height: 250px;
- transition: min-height 0.3s ease-in-out;
- }
-
- /* 串口发送命令容器的特殊样式 */
- #varPanel .right-panel .hex-send-panel:first-child {
- flex-shrink: 0 !important;
- height: auto !important;
- min-height: 350px !important;
- max-height: none !important;
- }
-
- /* 确保HEX曲线图容器也有过渡效果 */
- .hex-send-panel {
- transition: min-height 0.3s ease-in-out;
- }
- #varPanel .hex-send-controls {
- display: flex;
- flex-wrap: wrap;
- gap: 20px;
- align-items: flex-start;
- margin-bottom: 15px;
- height: 100%;
- width: 100%;
- }
- #varPanel .hex-send-controls input {
- padding: 8px 12px;
- border: 1px solid #d0d7de;
- border-radius: 6px;
- font-size: 14px;
- background: #f8fafc;
- }
- #varPanel .hex-send-controls button {
- padding: 8px 16px;
- border: none;
- border-radius: 6px;
- font-size: 14px;
- cursor: pointer;
- transition: all 0.2s;
- }
- #varPanel .hex-send-controls .btn-success {
- background: #2ecc71;
- color: white;
- }
- #varPanel .hex-send-controls .btn-danger {
- background: #e74c3c;
- color: white;
- }
- #varPanel .hex-send-controls .btn-primary {
- background: #3498db;
- color: white;
- }
- #varPanel .selected-variables-info {
- background: #e8f5e8;
- border: 1px solid #c3e6c3;
- border-radius: 8px;
- padding: 15px;
- margin-bottom: 15px;
- }
- #varPanel .selected-variables-info h5 {
- margin: 0 0 10px 0;
- color: #2d5a2d;
- font-size: 16px;
- }
- #varPanel .selected-variables-list {
- display: flex;
- flex-wrap: wrap;
- gap: 8px;
- margin-bottom: 10px;
- }
- #varPanel .selected-variable-tag {
- background: #d4edda;
- color: #155724;
- padding: 4px 8px;
- border-radius: 4px;
- font-size: 12px;
- display: flex;
- align-items: center;
- gap: 5px;
- }
- #varPanel .selected-variable-tag .remove-btn {
- background: none;
- border: none;
- color: #155724;
- cursor: pointer;
- font-size: 14px;
- padding: 0;
- margin: 0;
- }
- #varPanel .total-size-info {
- background: #d1ecf1;
- border: 1px solid #bee5eb;
- border-radius: 6px;
- padding: 10px;
- color: #0c5460;
- font-weight: 500;
- }
- </style>
-
- <!-- 变量分析页面新布局 -->
- <div class="var-analysis-layout">
- <!-- 左侧面板:文件操作和变量列表 -->
- <div class="left-panel">
- <!-- 文件操作区域 -->
- <div class="filter-section">
- <h4 style="margin:0 0 15px 0;color:#2980b9;font-size:18px;">
- <span style="margin-right:8px;">📁</span>文件操作
- </h4>
- <div style="margin-bottom:15px;">
- <label for="axfFile" class="form-label" style="font-size:15px;margin-bottom:8px;">
- <span style="margin-right:6px;">📂</span>选择 .axf 文件
- </label>
- <input class="form-control" type="file" id="axfFile" accept=".axf"
- style="padding:12px;border-radius:8px;border:2px solid #e3eaf2;font-size:14px;">
- <div style="margin-top:6px;font-size:13px;color:#6c757d;">
- <span id="fileStatus">未选择文件</span>
- </div>
- </div>
- <button id="analyzeBtn" class="btn btn-primary" disabled
- style="padding:12px;font-size:15px;font-weight:600;border-radius:8px;width:100%;">
- <span style="margin-right:6px;">🔬</span>分析文件
- </button>
- <div id="fileInfo" class="file-info d-none" style="margin-top:15px;">
- <div style="margin-bottom:8px;"><strong>📄 文件信息:</strong> <span id="fileName">-</span></div>
- <div style="margin-bottom:8px;"><strong>📊 文件大小:</strong> <span id="fileSize">-</span></div>
- <div style="margin-bottom:8px;"><strong>🏷️ ELF 类型:</strong> <span id="elfType">-</span></div>
- <div><strong>🎯 入口点:</strong> <span id="entryPoint">-</span></div>
- </div>
- </div>
- <!-- 变量筛选区域 -->
- <div class="filter-section">
- <h4 style="margin:0 0 15px 0;color:#2980b9;font-size:18px;">
- <span style="margin-right:8px;">🔍</span>变量搜索
- </h4>
- <div class="filter-controls">
- <input type="text" id="searchFilter" placeholder="搜索变量名..." style="width:100%;" />
- </div>
- </div>
- <!-- 变量列表区域 -->
- <div class="variable-table-container">
- <h4 style="margin:0 0 15px 0;color:#2980b9;font-size:18px;">
- <span style="margin-right:8px;">📊</span>变量列表
- <span style="margin-left:12px;font-size:13px;font-weight:normal;opacity:0.8;">
- (勾选变量添加到串口发送区,最多9个)
- </span>
- </h4>
- <div class="progress-container mb-3">
- <div id="progressBar" class="progress d-none" style="height:8px;border-radius:6px;background:#f0f0f0;">
- <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
- style="width: 0%;background:linear-gradient(90deg, #3498db, #764ba2);border-radius:6px;"></div>
- </div>
- </div>
- <div class="table-container" id="varTableContainer" style="max-height:500px;overflow-y:auto;">
- <table class="variable-table">
- <thead>
- <tr>
- <th style="width:60px;"><input type="checkbox" id="selectAllVars" /></th>
- <th style="width:40%;">变量名</th>
- <th style="width:35%;">地址</th>
- <th style="width:25%;">大小</th>
- </tr>
- </thead>
- <tbody id="variableTableBody">
- <tr>
- <td colspan="4" class="text-center text-muted" style="padding:40px 16px;font-size:15px;">
- <div style="margin-bottom:8px;">📁</div>
- 请上传.axf文件并点击"分析文件"
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
- </div>
- <!-- 右侧面板:串口发送和实时图表 -->
- <div class="right-panel">
- <!-- 串口发送区域 -->
- <div class="hex-send-panel" style="min-height: 350px !important; flex-shrink: 0 !important; height: auto !important; max-height: none !important;">
- <h4 style="margin:0 0 15px 0;color:#2980b9;font-size:18px;">
- <span style="margin-right:8px;">📡</span>串口发送命令
- </h4>
-
- <!-- 已选择变量信息 -->
- <div id="selectedVariablesInfo" class="selected-variables-info" style="display:none;">
- <h5>已选择的变量</h5>
- <div id="selectedVariablesList" class="selected-variables-list"></div>
- <div id="totalSizeInfo" class="total-size-info">
- 总大小: <span id="selectedTotalSize">0</span> 字节
- </div>
- </div>
- <!-- 串口发送控制 -->
- <div class="hex-send-controls">
- <div style="display:flex;flex-direction:column;gap:8px;align-items:stretch;min-width:120px;">
- <div style="display:flex;align-items:center;gap:8px;">
- <span style="color:#495057;font-size:13px;">采样率 (1-200Hz):</span>
- <input id="hexSampleRateInput" type="number" min="1" max="200" value="100" style="width:80px;padding:4px 8px;border-radius:4px;border:1px solid #d0d7de;font-size:12px;">
- <span style="color:#495057;font-size:13px;">Hz</span>
- </div>
- <div style="display:flex;align-items:center;gap:8px;">
- <span style="color:#495057;font-size:13px;">显示范围 (1-30秒):</span>
- <input id="timeDisplayRangeInput" type="number" min="1" max="30" value="10" style="width:80px;padding:4px 8px;border-radius:4px;border:1px solid #d0d7de;font-size:12px;">
- <span style="color:#495057;font-size:13px;">秒</span>
- </div>
- <button id="sendHexCmdBtn" class="btn btn-primary" style="width:100%;padding:10px;">
- <span style="margin-right:4px;">🚀</span>发送命令
- </button>
- </div>
- </div>
- </div>
- <!-- 实时HEX曲线图 -->
- <div class="hex-send-panel" style="flex:3;min-height:500px;flex-grow: 1;">
- <h4 style="margin:0 0 15px 0;color:#2980b9;font-size:18px;">
- <span style="margin-right:8px;">📈</span>实时HEX曲线图
- </h4>
- <!-- 多变量图表容器 -->
- <div id="multiChartContainer" class="multi-chart-container">
- <!-- 默认图表(当没有选择变量时显示) -->
- <div id="realtimeChart" style="width:100%;height:600px;background:#fff;border-radius:8px;box-shadow:0 2px 8px rgba(52,152,219,0.06);position:relative;margin-bottom:15px;"></div>
- </div>
- <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
- <button id="startChartBtn" class="btn btn-success">开始绘制</button>
- <button id="clearChartBtn" class="btn btn-secondary">清空曲线</button>
- <button id="stopChartBtn" class="btn btn-danger">终止绘制</button>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 右侧常驻监控栏 -->
- <div class="monitor-panel">
- <!-- 顶部连接控制区域 - 按键已移动到头部 -->
- <!-- <div class="top-connection-controls">
- <div class="connection-button-group">
- <button id="connectBtn" class="btn btn-primary"><i class="fas fa-plug"></i> 连接串口</button>
- <button id="disconnectBtn" class="btn btn-secondary" disabled><i class="fas fa-times"></i> 断开连接</button>
- </div>
- <div class="utility-button-group">
- <button id="clearBtn" class="btn btn-warning"><i class="fas fa-trash"></i> 清空</button>
- <button id="saveLogBtn" class="btn btn-info"><i class="fas fa-save"></i> 保存日志</button>
- </div>
- </div> -->
-
- <!-- 隐藏的串口配置(保留功能但不在页面上显示) -->
- <div class="hidden-config" style="display: none;">
- <div class="config-row">
- <label for="baudRate">波特率:</label>
- <select id="baudRate">
- <option value="9600">9600</option>
- <option value="115200" selected>115200</option>
- <option value="1000000">1000000</option>
- <option value="custom">自定义</option>
- </select>
- <input type="number" id="customBaudRate" placeholder="自定义波特率" style="display:none;">
- </div>
- <div class="config-row">
- <label for="dataBits">数据位:</label>
- <select id="dataBits">
- <option value="7">7</option>
- <option value="8" selected>8</option>
- </select>
- <label for="parity">校验位:</label>
- <select id="parity">
- <option value="none" selected>无</option>
- <option value="even">偶</option>
- <option value="odd">奇</option>
- </select>
- <label for="stopBits">停止位:</label>
- <select id="stopBits">
- <option value="1" selected>1</option>
- <option value="2">2</option>
- </select>
- </div>
- <div class="display-options">
- <label class="checkbox-label"><input type="checkbox" id="hexMode"> HEX模式</label>
- <label class="checkbox-label"><input type="checkbox" id="showTimestamp" checked> 显示时间戳</label>
- <label class="checkbox-label"><input type="checkbox" id="autoScroll" checked> 自动滚动</label>
- <label class="checkbox-label"><input type="checkbox" id="enableBuffer" checked> 启用数据缓冲</label>
- <label class="checkbox-label"><input type="checkbox" id="virtualTerminalMode" checked> 命令发送模式</label>
- <label class="checkbox-label"><input type="checkbox" id="processAnsiSequences" checked> 处理ANSI序列</label>
- <label class="checkbox-label"><input type="checkbox" id="debugMode"> 调试模式</label>
- <label class="checkbox-label"><input type="checkbox" id="integrityCheck" checked> 数据完整性检查</label>
- <label class="checkbox-label"><input type="checkbox" id="enableDataValidation" checked> 启用数据验证</label>
- <div class="line-timeout-config">
- <label for="lineTimeout">分行超时:</label>
- <input type="number" id="lineTimeout" value="50" min="10" max="1000" step="10" class="timeout-input">
- <span>ms</span>
- </div>
- </div>
- </div>
- <div class="monitor-log" style="margin-top:18px;">
- <div id="terminal" class="terminal">
- <div id="terminalOutput" class="terminal-output"></div>
- <div id="terminalInput" class="terminal-input">
- <span class="terminal-prompt">$ </span>
- <input type="text" id="terminalInputField" class="terminal-input-field" placeholder="输入命令后点击发送或按Enter键发送..." disabled>
- <button id="terminalSendBtn" class="btn btn-primary" style="margin-left: 8px; padding: 6px 12px; font-size: 12px;" disabled>发送</button>
- </div>
- </div>
- </div>
- </div>
- </main>
- </div>
- <!-- 嵌入配置文件内容 -->
- <script type="text/plain" id="embedded-config">
- # MicroLink Web Serial Configuration Parameters
- # 串口配置
- port=COM3
- baudrate=115200
- databits=8
- parity=N
- stopbits=1
- # RTT配置
- rtt_addr=0x20000000
- rtt_size=0x4000
- rtt_channel=0
- # SystemView配置
- systemview_addr=0x20000000
- systemview_size=0x4000
- systemview_channel=1
- # FLM配置
- flm_path=STM32/STM32F4xx_1024.FLM.o
- base_addr=0x08000000
- ram_addr=0x20000000
- # 下载配置
- bin_file_path=firmware.bin
- bin_addr=0x08000000
- # 自定义命令
- custom_commands=RTTView.start(0x20000000,1024,0);SystemView.start(0x20000000,1024,1);load.offline()
- </script>
- <script src="assets/script.js"></script>
- <script>
- document.addEventListener('DOMContentLoaded', function() {
- // 右侧功能区切换逻辑(如有)
- const panelSelect = document.getElementById('panelSelect');
- const apiPanel = document.getElementById('apiPanel');
- const flmPanel = document.getElementById('flmPanel');
- const scriptPanel = document.getElementById('scriptPanel');
- const varPanel = document.getElementById('varPanel');
- if (panelSelect && apiPanel && flmPanel && scriptPanel && varPanel) {
- function updatePanelDisplay() {
- apiPanel.style.display = panelSelect.value === 'apiPanel' ? '' : 'none';
- flmPanel.style.display = panelSelect.value === 'flmPanel' ? '' : 'none';
- scriptPanel.style.display = panelSelect.value === 'scriptPanel' ? '' : 'none';
- varPanel.style.display = panelSelect.value === 'varPanel' ? '' : 'none';
- }
- panelSelect.addEventListener('change', updatePanelDisplay);
- updatePanelDisplay();
- }
- // ========== FLM ELF解析器核心功能 ==========
- const flmFileInput = document.getElementById('flmFile');
- const convertBtn = document.getElementById('convertBtn');
- const log = document.getElementById('log');
- const downloadLink = document.getElementById('downloadLink');
- let flmArrayBuffer = null;
- let flmFileName = '';
- let convertedBlob = null;
- function logMsg(msg) {
- // 输出到主终端
- appendToTerminalOutput(`<div class='log-prefix-flm'>[FLM] ${msg}</div>`);
- // 同时输出到本地log元素(如果存在)
- if (log) {
- log.textContent += msg + '\n';
- log.scrollTop = log.scrollHeight;
- }
- }
- function parseELFHeader(buffer) {
- const dv = new DataView(buffer);
- if (dv.getUint8(0) !== 0x7f || dv.getUint8(1) !== 0x45 || dv.getUint8(2) !== 0x4c || dv.getUint8(3) !== 0x46) {
- logMsg('不是有效的ELF文件!');
- return null;
- }
- logMsg('ELF魔数校验通过');
- const e_shoff = dv.getUint32(32, true);
- const e_shentsize = dv.getUint16(46, true);
- const e_shnum = dv.getUint16(48, true);
- const e_shstrndx = dv.getUint16(50, true);
- logMsg(`节头表偏移: 0x${e_shoff.toString(16)}, 节数量: ${e_shnum}, 节头大小: ${e_shentsize}, 节名字符串表索引: ${e_shstrndx}`);
- return { e_shoff, e_shentsize, e_shnum, e_shstrndx };
- }
- function parseSectionHeaders(buffer, elfHeader) {
- const dv = new DataView(buffer);
- const sections = [];
- for (let i = 0; i < elfHeader.e_shnum; i++) {
- const offset = elfHeader.e_shoff + i * elfHeader.e_shentsize;
- sections.push({
- sh_name: dv.getUint32(offset + 0, true),
- sh_type: dv.getUint32(offset + 4, true),
- sh_flags: dv.getUint32(offset + 8, true),
- sh_addr: dv.getUint32(offset + 12, true),
- sh_offset: dv.getUint32(offset + 16, true),
- sh_size: dv.getUint32(offset + 20, true),
- sh_link: dv.getUint32(offset + 24, true),
- sh_info: dv.getUint32(offset + 28, true),
- sh_addralign: dv.getUint32(offset + 32, true),
- sh_entsize: dv.getUint32(offset + 36, true)
- });
- }
- return sections;
- }
- function getSectionNames(buffer, sections, shstrndx) {
- const dv = new DataView(buffer);
- const shstrtab = sections[shstrndx];
- const names = [];
- for (let i = 0; i < sections.length; i++) {
- const nameOffset = shstrtab.sh_offset + sections[i].sh_name;
- let name = '';
- for (let j = 0; j < 64; j++) {
- const c = dv.getUint8(nameOffset + j);
- if (c === 0) break;
- name += String.fromCharCode(c);
- }
- names.push(name);
- }
- return names;
- }
- function findSectionByName(sectionNames, name) {
- for (let i = 0; i < sectionNames.length; i++) {
- if (sectionNames[i] === name) return i;
- }
- return -1;
- }
- function extractSectionData(buffer, section) {
- return new Uint8Array(buffer, section.sh_offset, section.sh_size);
- }
- function findSymbolAddresses(buffer, sections, sectionNames, symbolNames) {
- const symtabIdx = findSectionByName(sectionNames, '.symtab');
- const strtabIdx = findSectionByName(sectionNames, '.strtab');
- if (symtabIdx === -1 || strtabIdx === -1) {
- logMsg('未找到 .symtab 或 .strtab 节!');
- return null;
- }
- const symtab = sections[symtabIdx];
- const strtab = sections[strtabIdx];
- const dv = new DataView(buffer);
- const symbolCount = symtab.sh_size / symtab.sh_entsize;
- const addresses = {};
- for (let i = 0; i < symbolCount; i++) {
- const symOffset = symtab.sh_offset + i * symtab.sh_entsize;
- const st_name = dv.getUint32(symOffset + 0, true);
- const st_value = dv.getUint32(symOffset + 4, true);
- let name = '';
- for (let j = 0; j < 64; j++) {
- const c = dv.getUint8(strtab.sh_offset + st_name + j);
- if (c === 0) break;
- name += String.fromCharCode(c);
- }
- if (symbolNames.includes(name)) {
- addresses[name] = st_value;
- }
- }
- return addresses;
- }
- function extractFlashInfoFromELF(buffer, sections, sectionNames) {
- logMsg('查找DevDscr节:');
- for (let i = 0; i < sections.length; i++) {
- const section = sections[i];
- const sectionName = sectionNames[i];
- logMsg(` 节${i}: ${sectionName} (偏移: 0x${section.sh_offset.toString(16)}, 大小: ${section.sh_size})`);
- if (sectionName === 'DevDscr') {
- logMsg(`找到DevDscr节: 偏移 0x${section.sh_offset.toString(16)}, 大小 ${section.sh_size}`);
- const devDscrData = extractSectionData(buffer, section);
- const flashInfoSize = 288;
- if (section.sh_size >= flashInfoSize) {
- const flashInfo = new ArrayBuffer(flashInfoSize);
- const flashDv = new DataView(flashInfo);
- const devDv = new DataView(devDscrData.buffer, devDscrData.byteOffset, devDscrData.byteLength);
- for (let i = 0; i < flashInfoSize; i++) {
- flashDv.setUint8(i, devDv.getUint8(i));
- }
- const vers = flashDv.getUint16(0, true);
- let devName = '';
- for (let i = 0; i < 128; i++) {
- const c = flashDv.getUint8(2 + i);
- if (c === 0) break;
- devName += String.fromCharCode(c);
- }
- if (vers !== 0 && devName.length > 0) {
- logMsg('Flash信息验证通过');
- logMsg('成功从DevDscr节提取Flash信息');
- return new Uint8Array(flashInfo);
- } else {
- logMsg('DevDscr节中的数据验证失败');
- }
- } else {
- logMsg(`DevDscr节大小不足: ${section.sh_size} < ${flashInfoSize}`);
- }
- }
- }
- logMsg('未找到有效的Flash信息数据');
- return null;
- }
- function createAlgorithmBlob(buffer, sections, sectionNames) {
- logMsg('创建算法程序blob:');
- logMsg('查找PrgCode节:');
- for (let i = 0; i < sections.length; i++) {
- const section = sections[i];
- const sectionName = sectionNames[i];
- logMsg(` 节${i}: ${sectionName} (偏移: 0x${section.sh_offset.toString(16)}, 大小: ${section.sh_size})`);
- if (sectionName === 'PrgCode') {
- logMsg(`找到PrgCode节: 偏移 0x${section.sh_offset.toString(16)}, 大小 ${section.sh_size}`);
- const prgCodeData = extractSectionData(buffer, section);
- logMsg(`PrgCode节内容已提取,大小: ${prgCodeData.length} 字节`);
- let hexStr = '';
- for (let j = 0; j < 32 && j < prgCodeData.length; j++) {
- hexStr += prgCodeData[j].toString(16).padStart(2, '0');
- }
- logMsg(`PrgCode节前32字节: ${hexStr}`);
- return prgCodeData;
- }
- }
- logMsg('未找到PrgCode节');
- return null;
- }
- function createFinalBlobWithFlashInfo(programTarget, algorithmBlob, algorithmBlobSize, flashInfo) {
- const ramSize = 8 * 4;
- const finalBlobSize = 64 + ramSize + algorithmBlobSize + flashInfo.length;
- logMsg(`最终blob创建成功,总大小: ${finalBlobSize} 字节 (结构体: 64 + RAM数组: ${ramSize} + 算法: ${algorithmBlobSize} + Flash信息: ${flashInfo.length})`);
- const finalBlob = new Uint8Array(finalBlobSize);
- let offset = 0;
- finalBlob.set(programTarget, offset); offset += programTarget.length;
- const ramArray = new Uint8Array(32);
- const ramDv = new DataView(ramArray.buffer);
- ramDv.setUint32(0, 0xE00ABE00, true);
- ramDv.setUint32(4, 0x062D780D, true);
- ramDv.setUint32(8, 0x24084068, true);
- ramDv.setUint32(12, 0xD3000040, true);
- ramDv.setUint32(16, 0x1E644058, true);
- ramDv.setUint32(20, 0x1C49D1FA, true);
- ramDv.setUint32(24, 0x2A001E52, true);
- ramDv.setUint32(28, 0x4770D1F2, true);
- finalBlob.set(ramArray, offset); offset += ramArray.length;
- if (algorithmBlob && algorithmBlobSize > 0) {
- finalBlob.set(algorithmBlob, offset); offset += algorithmBlobSize;
- }
- finalBlob.set(flashInfo, offset);
- return finalBlob;
- }
- flmFileInput.addEventListener('change', (e) => {
- const file = e.target.files[0];
- if (!file) {
- convertBtn.disabled = true;
- logMsg('未选择文件');
- return;
- }
- flmFileName = file.name.replace(/\.[^/.]+$/, "");
- const reader = new FileReader();
- reader.onload = function(evt) {
- flmArrayBuffer = evt.target.result;
- convertBtn.disabled = false;
- log.textContent = '';
- logMsg(`已加载文件: ${file.name} (大小: ${file.size} 字节)`);
- };
- reader.onerror = function() {
- logMsg('文件读取失败');
- convertBtn.disabled = true;
- };
- reader.readAsArrayBuffer(file);
- });
- convertBtn.addEventListener('click', async () => {
- if (!flmArrayBuffer) {
- logMsg('请先选择FLM文件');
- return;
- }
- logMsg('开始解析FLM文件...');
- const elfHeader = parseELFHeader(flmArrayBuffer);
- if (!elfHeader) return;
- const sections = parseSectionHeaders(flmArrayBuffer, elfHeader);
- const sectionNames = getSectionNames(flmArrayBuffer, sections, elfHeader.e_shstrndx);
- logMsg('查找算法函数符号:');
- const symbolNames = ['Init','UnInit','EraseChip','EraseSector','ProgramPage','Read','Verify'];
- const symbolAddrs = findSymbolAddresses(flmArrayBuffer, sections, sectionNames, symbolNames);
- if (!symbolAddrs) return;
- for (const name of symbolNames) {
- const addr = symbolAddrs[name] || 0;
- if (addr !== 0) {
- logMsg(` ${name}: 0x${addr.toString(16).padStart(8,'0')}`);
- } else {
- logMsg(` ${name}: 未找到`);
- }
- }
- logMsg('从ELF文件中提取Flash信息:');
- const flashInfo = extractFlashInfoFromELF(flmArrayBuffer, sections, sectionNames);
- let flash_info;
- if (!flashInfo) {
- logMsg('警告: 无法从ELF文件中提取Flash信息,使用默认值');
- const defaultFlashInfo = new ArrayBuffer(288);
- const dv = new DataView(defaultFlashInfo);
- dv.setUint16(0, 0x0101, true);
- const devName = "STM32F7xx 1024KB Flash (默认)";
- for (let i = 0; i < 128; i++) {
- dv.setUint8(2 + i, i < devName.length ? devName.charCodeAt(i) : 0);
- }
- dv.setUint16(130, 1, true);
- dv.setUint32(132, 0x08000000, true);
- dv.setUint32(136, 1024 * 1024, true);
- dv.setUint32(140, 256, true);
- dv.setUint32(144, 0, true);
- dv.setUint8(148, 0xFF);
- dv.setUint32(152, 1000, true);
- dv.setUint32(156, 5000, true);
- flash_info = new Uint8Array(defaultFlashInfo);
- } else {
- flash_info = flashInfo;
- }
- const algorithmBlob = createAlgorithmBlob(flmArrayBuffer, sections, sectionNames);
- if (!algorithmBlob) {
- logMsg('算法程序blob创建失败');
- return;
- }
- const algorithm_blob = algorithmBlob;
- const algorithm_blob_size = algorithmBlob.length;
- logMsg(`算法程序blob创建成功,大小: ${algorithm_blob_size} 字节`);
- logMsg('初始化program_target_t结构体:');
- const program_target = {};
- program_target.init = (symbolAddrs['Init'] || 0) + 0x20;
- program_target.uninit = (symbolAddrs['UnInit'] || 0) + 0x20;
- program_target.erase_chip = (symbolAddrs['EraseChip'] || 0) + 0x20;
- program_target.erase_sector = (symbolAddrs['EraseSector'] || 0) + 0x20;
- program_target.program_page = (symbolAddrs['ProgramPage'] || 0) + 0x20;
- program_target.read = (symbolAddrs['Read'] !== undefined) ? symbolAddrs['Read'] + 0x20 : 0;
- program_target.verify = (symbolAddrs['Verify'] !== undefined) ? symbolAddrs['Verify'] + 0x20 : 0;
- program_target.breakpoint = 1;
- program_target.static_base = algorithm_blob_size + 0x20 - 4;
- program_target.stack_pointer = algorithm_blob_size + 0x20 + 0x1000;
- program_target.program_buffer = algorithm_blob_size + 0x20 + 0x1800;
- program_target.algo_start = 0;
- program_target.algo_size = algorithm_blob_size + 0x20;
- program_target.algo_blob = 0;
- program_target.program_buffer_size = 0x1000;
- program_target.algo_flags = 0;
- logMsg(` init: 0x${program_target.init.toString(16).padStart(8,'0')}`);
- logMsg(` uninit: 0x${program_target.uninit.toString(16).padStart(8,'0')}`);
- logMsg(` erase_chip: 0x${program_target.erase_chip.toString(16).padStart(8,'0')}`);
- logMsg(` erase_sector: 0x${program_target.erase_sector.toString(16).padStart(8,'0')}`);
- logMsg(` program_page: 0x${program_target.program_page.toString(16).padStart(8,'0')}`);
- logMsg(` read: 0x${program_target.read.toString(16).padStart(8,'0')}`);
- logMsg(` verify: 0x${program_target.verify.toString(16).padStart(8,'0')}`);
- logMsg(` sys_call_s.breakpoint: 0x${program_target.breakpoint.toString(16).padStart(8,'0')}`);
- logMsg(` sys_call_s.static_base: 0x${program_target.static_base.toString(16).padStart(8,'0')}`);
- logMsg(` sys_call_s.stack_pointer: 0x${program_target.stack_pointer.toString(16).padStart(8,'0')}`);
- logMsg(` program_buffer: 0x${program_target.program_buffer.toString(16).padStart(8,'0')}`);
- logMsg(` algo_start: 0x${program_target.algo_start.toString(16).padStart(8,'0')}`);
- logMsg(` algo_size: 0x${program_target.algo_size.toString(16).padStart(8,'0')}`);
- logMsg(` program_buffer_size: 0x${program_target.program_buffer_size.toString(16).padStart(8,'0')}`);
- logMsg(` algo_flags: 0x${program_target.algo_flags.toString(16).padStart(8,'0')}`);
- const programTargetBuffer = new ArrayBuffer(64);
- const ptDv = new DataView(programTargetBuffer);
- let ptOffset = 0;
- ptDv.setUint32(ptOffset, program_target.init, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.uninit, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.erase_chip, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.erase_sector, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.program_page, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.read, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.verify, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.breakpoint, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.static_base, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.stack_pointer, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.program_buffer, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.algo_start, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.algo_size, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.algo_blob, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.program_buffer_size, true); ptOffset += 4;
- ptDv.setUint32(ptOffset, program_target.algo_flags, true); ptOffset += 4;
- const programTarget = new Uint8Array(programTargetBuffer);
- const finalBlob = createFinalBlobWithFlashInfo(programTarget, algorithm_blob, algorithm_blob_size, flash_info);
- const blob = new Blob([finalBlob], {type: 'application/octet-stream'});
- convertedBlob = blob;
- const fileName = `${flmFileName}.FLM.o`;
- downloadLink.href = URL.createObjectURL(blob);
- downloadLink.download = fileName;
- downloadLink.style.display = '';
- downloadLink.textContent = '下载 .o 文件';
-
-
- // 自动复制文件名到剪贴板
- try {
- await navigator.clipboard.writeText(fileName);
- logMsg(`✅ 文件名 "${fileName}" 已自动复制到剪贴板`);
- } catch (err) {
- logMsg(`⚠️ 文件名复制失败: ${err.message}`);
- }
-
- logMsg(`最终blob已保存到 ${fileName} (包含program_target_t结构体和Flash信息)`);
- logMsg('ELF文件解析完成');
- });
- downloadLink.addEventListener('click', () => {
- setTimeout(() => {
- URL.revokeObjectURL(downloadLink.href);
- }, 1000);
- });
- // ========== FLM ELF解析器 YMODEM发送功能 ==========
- const flmYmodemSendBtn = document.getElementById('flmYmodemSendBtn');
- const flmYmodemProgress = document.getElementById('flmYmodemProgress');
- // const flmPanel = document.getElementById('flmPanel'); // 移除重复声明
- function flmYlog(msg, color) {
- // flmYmodemLog.innerHTML += `<div style='color:${color||'#0f0'}'>${msg}</div>`; // 移除此行
- // flmYmodemLog.scrollTop = flmYmodemLog.scrollHeight; // 移除此行
- logMsg(`[YMODEM] ${msg}`); // 同时输出到主日志区
- }
- function flmYlogClear() {
- // flmYmodemLog.innerHTML = ''; // 移除此行
- }
- function setFlmYmodemUIBusy(busy) {
- flmYmodemSendBtn.disabled = busy || !convertedBlob || !isSerialConnected();
- flmYmodemProgress.style.display = busy ? '' : 'none';
- }
- function updateFlmYmodemBtn() {
- flmYmodemSendBtn.disabled = !convertedBlob || !isSerialConnected();
- }
- function isSerialConnected() {
- return window.microLinkTerminal && window.microLinkTerminal.isConnected && window.microLinkTerminal.port;
- }
- // 生成.o文件后使发送按钮可用
- convertBtn.addEventListener('click', function() {
- updateFlmYmodemBtn();
- });
- // 发送按钮事件
- flmYmodemSendBtn.addEventListener('click', async () => {
- if (!convertedBlob) { flmYlog('请先生成.o文件', '#ff0'); return; }
- if (!isSerialConnected()) {
- flmYlog('请先连接串口', '#f66'); return;
- }
-
- const ymodemMode = 'simple'; // 固定为简化模式
- flmYlogClear();
- setFlmYmodemUIBusy(true);
- flmYmodemProgress.value = 0;
- flmYmodemProgress.max = 100;
- const port = window.microLinkTerminal.port;
-
- try {
- if (window.microLinkTerminal.reader) {
- try { window.microLinkTerminal.reader.cancel(); } catch(e){}
- try { window.microLinkTerminal.reader.releaseLock(); } catch(e){}
- window.microLinkTerminal.reader = null;
- flmYlog('已暂停主串口监听,准备YMODEM发送...', '#0ff');
- }
-
- // 只在最开始发送一次ym.receive()
- const writer0 = port.writable.getWriter();
- await writer0.write(new TextEncoder().encode('ym.receive()\r\n'));
- writer0.releaseLock();
- flmYlog('已发送 ym.receive(),正在等待设备C信号(最多10秒)...', '#0ff');
-
- // 强制监听串口满10秒,收到C或Ready立即进入下一步
- let started = false;
- let received = '';
- const startTime = Date.now();
- while (!started && (Date.now() - startTime) < 10000) {
- let reader = port.readable.getReader();
- try {
- const { value, done } = await Promise.race([
- reader.read(),
- new Promise(resolve => setTimeout(() => resolve({value: null, done: false}), 200))
- ]);
- if (value) {
- const text = new TextDecoder().decode(value);
- flmYlog('收到内容: ' + text, '#888');
- received += text;
- if (text.includes('C') || text.includes('Ready')) {
- started = true;
- flmYlog('收到设备C信号,准备发送YMODEM文件...', '#0f0');
- break;
- }
- }
- } finally {
- reader.releaseLock();
- }
- await new Promise(r => setTimeout(r, 100));
- }
- if (!started) {
- flmYlog('10秒内未收到设备C信号,YMODEM发送中止', '#f66');
- return;
- }
-
- // 根据选择的模式使用不同的YMODEM函数
- const arrayBuffer = await convertedBlob.arrayBuffer();
- const uint8Array = new Uint8Array(arrayBuffer);
- const fileName = flmFileName ? (flmFileName + '.FLM.o') : 'firmware.FLM.o';
-
- // ======= ELF tab YMODEM发送按钮事件,完全镜像PYTHON tab逻辑 =======
- flmYlog('使用简化YMODEM模式(极保守配置)', '#0ff');
- const simpleOptions = {
- retryDelay: 1000, // 1秒包间延时
- maxRetries: 30, // 30次重试
- packetTimeout: 30000, // 30秒包超时
- restartDelay: 5000 // 5秒重启延时
- };
-
- await window.ymodemSendFileViaSerialSimple(
- uint8Array,
- fileName,
- 120000,
- progress => { flmYmodemProgress.value = progress; },
- msg => flmYlog(msg),
- {
- ...simpleOptions,
- buildHeaderPacket: window.buildHeaderPacketFlmYmodem
- }
- );
- // ======= 屏蔽原有ELF tab YMODEM流程 =======
- /*
- if (ymodemMode === 'simple') {
- // 使用简化模式
- flmYlog('使用简化YMODEM模式(极保守配置)', '#0ff');
- const simpleOptions = {
- retryDelay: 1000, // 1秒包间延时
- maxRetries: 30, // 30次重试
- packetTimeout: 30000, // 30秒包超时
- restartDelay: 5000 // 5秒重启延时
- };
-
- await window.ymodemSendFileViaSerialSimple(
- uint8Array,
- fileName,
- 120000,
- progress => { flmYmodemProgress.value = progress; },
- msg => flmYlog(msg),
- {
- ...simpleOptions,
- buildHeaderPacket: window.buildHeaderPacketFlmYmodem
- }
- );
- } else {
- // 使用标准模式
- flmYlog('使用标准YMODEM模式', '#0ff');
- const ymodemOptions = {
- retryDelay: 800, // 大幅增加包间延时,给设备端更多处理时间
- maxRetries: 25, // 增加重试次数
- packetTimeout: 20000, // 增加包超时时间到20秒
- restartDelay: 3000 // 重启传输前的延时
- };
-
- flmYlog(`使用YMODEM配置: 数据包大小128字节(SOH), 延时${ymodemOptions.retryDelay}ms, 重试${ymodemOptions.maxRetries}次, 超时${ymodemOptions.packetTimeout}ms`, '#0ff');
-
- await window.ymodemSendFileViaSerial(
- uint8Array,
- fileName,
- 120000,
- progress => { flmYmodemProgress.value = progress; },
- msg => flmYlog(msg),
- ymodemOptions
- );
- }
-
- flmYlog('✅ 文件发送完成', '#0f0');
- */
- } catch (e) {
- // 静默失败,不输出任何报错日志
- return;
- } finally {
- flmYlog('congratulations!', '#888');
- setFlmYmodemUIBusy(false);
- }
- });
- // .o文件生成后自动使按钮可用
- if (typeof updateFlmYmodemBtn === 'function') updateFlmYmodemBtn();
- // 串口连接状态变化时自动刷新按钮
- // 页面切换到FLM解析器时也刷新一次
- if (panelSelect) {
- panelSelect.addEventListener('change', function() {
- if (panelSelect.value === 'flmPanel') {
- updateFlmYmodemBtn();
- }
- });
- }
- // ========== Python脚本生成器核心功能 ==========
- const swdClockSpeedMap = { '10M': '10000000', '5M': '5000000', '2M': '2000000', '1M': '1000000', '500K': '500000', '200K': '200000', '100K': '100000', '50K': '50000', '20K': '20000', '10K': '10000', '5K': '5000' };
- const customFlmInput = document.getElementById('customFlm');
- const address1Input = document.getElementById('address1');
- const address2Input = document.getElementById('address2');
- const swdClockSpeedSelect = document.getElementById('swdClockSpeed');
- const codePreview = document.getElementById('codePreview');
- const dragCodePreview = document.getElementById('dragCodePreview');
- // 删除下载按钮相关代码
- // 全局config对象,供script.js使用
- window.config = {
- flmFile: 'custom_flm.FLM.o',
- address1: '0X08000000',
- address2: '0x20000000',
- swdClockSpeed: '10M',
- files: [
- {
- fileName: 'boot.bin',
- address: '0x08000000',
- algorithm: 'STM32F7x_1024.FLM.o'
- },
- {
- fileName: 'rtthread.bin',
- address: '0x08020000',
- algorithm: 'STM32F7x_1024.FLM.o'
- },
- {
- fileName: 'HZK.bin',
- address: '0x90000000',
- algorithm: 'STM32F767_W25QXX.FLM.o'
- }
- ]
- };
- const config = window.config;
- function switchScriptTab(scriptType) {
- const tabs = document.querySelectorAll('.script-tab');
- tabs.forEach(tab => tab.classList.remove('active'));
- const contents = document.querySelectorAll('.script-content');
- contents.forEach(content => content.classList.remove('active'));
- if (scriptType === 'offline') {
- document.querySelector('.script-tab:first-child').classList.add('active');
- document.getElementById('offlineScript').classList.add('active');
- } else {
- document.querySelector('.script-tab:last-child').classList.add('active');
- document.getElementById('dragScript').classList.add('active');
- }
- }
- function updateCodePreview() {
- const flmFile = config.flmFile;
- const pythonSwdSpeed = swdClockSpeedMap[config.swdClockSpeed] || '10000000';
-
- // 生成多文件烧录代码
- let offlineCode = `import FLMConfig\nimport PikaStdLib\nimport PikaStdDevice\nimport time\n\ntime = PikaStdDevice.Time()\nbuzzer = PikaStdDevice.GPIO()\nbuzzer.setPin('PA4')\nbuzzer.setMode('out')\n\n# 设置SWD下载速度\ncmd.set_swd_clock(${pythonSwdSpeed})\n\nReadFlm = FLMConfig.ReadFlm()`;
-
- // 按算法分组文件
- const algorithmGroups = {};
- config.files.forEach(file => {
- if (file.algorithm && file.fileName && file.address) {
- if (!algorithmGroups[file.algorithm]) {
- algorithmGroups[file.algorithm] = [];
- }
- algorithmGroups[file.algorithm].push(file);
- }
- });
-
- // 为每个算法生成加载和烧录代码
- Object.keys(algorithmGroups).forEach((algorithm, index) => {
- const files = algorithmGroups[algorithm];
- if (files.length > 0) {
- // 加载算法
- offlineCode += `\n# 加载 ${algorithm} 下载算法文件\nresult = ReadFlm.load("FLM/${algorithm}", ${config.address1}, ${config.address2})\nif result != 0:\n return`;
-
- // 烧录该算法下的所有文件
- files.forEach(file => {
- offlineCode += `\n\n# 烧写 ${file.fileName}\nresult = load.bin("${file.fileName}", ${file.address})\nif result != 0:\n return`;
- });
- }
- });
-
- offlineCode += `\n\n# 蜂鸣器响一声,表示烧写完成\nbuzzer.enable()\nbuzzer.high()\ntime.sleep_ms(500)\nbuzzer.low()\ntime.sleep_ms(500)`;
-
- const dragCode = `import FLMConfig\ncmd.set_swd_clock(${pythonSwdSpeed})\nReadFlm = FLMConfig.ReadFlm()\nres1 = ReadFlm.load(\"FLM/${flmFile}\",${config.address1},${config.address2})`;
-
- if (codePreview) {
- codePreview.textContent = offlineCode;
- }
- if (dragCodePreview) {
- dragCodePreview.textContent = dragCode;
- }
-
- // 高亮显示
- let highlightedCode = offlineCode;
- config.files.forEach(file => {
- if (file.fileName) {
- highlightedCode = highlightedCode.replace(
- new RegExp(`"${file.fileName}"`, 'g'),
- `<span class="highlight">"${file.fileName}"</span>`
- );
- }
- if (file.address) {
- highlightedCode = highlightedCode.replace(
- new RegExp(file.address.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'),
- `<span class="highlight">${file.address}</span>`
- );
- }
- if (file.algorithm) {
- highlightedCode = highlightedCode.replace(
- new RegExp(`"FLM/${file.algorithm}"`, 'g'),
- `<span class="highlight">"FLM/${file.algorithm}"</span>`
- );
- }
- });
-
- if (codePreview) {
- codePreview.innerHTML = highlightedCode
- .replace(new RegExp(config.address1.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'), `<span class="highlight">${config.address1}</span>`)
- .replace(new RegExp(config.address2.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'), `<span class="highlight">${config.address2}</span>`)
- .replace(new RegExp(pythonSwdSpeed.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'), `<span class="highlight">${pythonSwdSpeed}</span>`);
- }
-
- if (dragCodePreview) {
- dragCodePreview.innerHTML = dragCode.replace(`\"FLM/${flmFile}\"`, `<span class=\"highlight\">\"FLM/${flmFile}\"</span>`).replace(new RegExp(config.address1.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'), `<span class=\"highlight\">${config.address1}</span>`).replace(new RegExp(config.address2.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'), `<span class=\"highlight\">${config.address2}</span>`).replace(new RegExp(pythonSwdSpeed.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'), `<span class=\"highlight\">${pythonSwdSpeed}</span>`);
- }
- }
- // 复制按钮逻辑
- function copyToClipboard(text) {
- if (navigator.clipboard) {
- navigator.clipboard.writeText(text);
- } else {
- const textarea = document.createElement('textarea');
- textarea.value = text;
- document.body.appendChild(textarea);
- textarea.select();
- document.execCommand('copy');
- document.body.removeChild(textarea);
- }
- }
- if (customFlmInput) {
- customFlmInput.addEventListener('input', function() { config.flmFile = this.value || 'custom_flm.FLM.o'; updateCodePreview(); });
- }
- if (address1Input) {
- address1Input.addEventListener('input', function() { config.address1 = this.value || '0X08000000'; updateCodePreview(); });
- }
- if (address2Input) {
- address2Input.addEventListener('input', function() { config.address2 = this.value || '0x20000000'; updateCodePreview(); });
- }
- if (swdClockSpeedSelect) {
- swdClockSpeedSelect.addEventListener('change', function() { config.swdClockSpeed = this.value; updateCodePreview(); });
- }
-
- // 多文件配置功能已移至script.js中处理
-
-
-
-
- // 删除下载按钮事件监听器
- updateCodePreview();
- // 只允许发送"离线下载脚本"和"拖拽下载脚本",其它tab禁用发送按钮,不允许发送本地.py文件
- // 1. 移除本地.py文件相关UI
- // 2. 发送按钮只在"离线下载脚本"或"拖拽下载脚本"tab下可用,文件名分别为offline_download.py和drag_download.py
- // 3. 其它tab禁用发送按钮
- const pyYmodemSendBtn = document.getElementById('pyYmodemSendBtn');
- const pyYmodemProgress = document.getElementById('pyYmodemProgress');
- const pyYmodemLog = document.getElementById('pyYmodemLog');
- function pyYlog(msg, color) {
- appendToTerminalOutput(`<div class='log-prefix-python'>[PYTHON] ${msg}</div>`);
- }
- function pyYlogClear() {
- // 不清空主终端
- }
- function updatePyYmodemSendBtnState() {
- // 只允许离线下载脚本和拖拽下载脚本tab可用
- const tab = document.querySelector('.script-tab.active');
- if (tab && tab.textContent.includes('离线')) {
- pyYmodemSendBtn.disabled = false;
- pyYmodemSendBtn.setAttribute('data-pytype', 'offline');
- } else if (tab && tab.textContent.includes('拖拽')) {
- pyYmodemSendBtn.disabled = false;
- pyYmodemSendBtn.setAttribute('data-pytype', 'drag');
- } else {
- pyYmodemSendBtn.disabled = true;
- pyYmodemSendBtn.removeAttribute('data-pytype');
- }
- }
- // 监听tab切换
- const scriptTabs = document.querySelectorAll('.script-tab');
- if (scriptTabs.length > 0) {
- scriptTabs.forEach(tab => {
- tab.addEventListener('click', updatePyYmodemSendBtnState);
- });
- updatePyYmodemSendBtnState();
- }
- // 以Python tab为例,统一YMODEM发送按钮事件
- if (pyYmodemSendBtn) {
- pyYmodemSendBtn.addEventListener('click', async () => {
- if (pyYmodemSendBtn.disabled) return;
- if (!window.microLinkTerminal || !window.microLinkTerminal.isConnected || !window.microLinkTerminal.port) {
- pyYlog('请先连接串口', '#f66'); return;
- }
- pyYmodemSendBtn.disabled = true;
- pyYmodemProgress.value = 0;
- pyYmodemProgress.style.display = '';
- pyYlogClear();
- pyYlog('准备发送...', '#0ff');
- // === 加锁管理和监听控制 ===
- let wasConnected = false;
- if (window.microLinkTerminal) {
- if (window.microLinkTerminal.reader) {
- try { window.microLinkTerminal.reader.cancel(); } catch(e){}
- try { window.microLinkTerminal.reader.releaseLock(); } catch(e){}
- window.microLinkTerminal.reader = null;
- }
- wasConnected = window.microLinkTerminal.isConnected;
- window.microLinkTerminal.isConnected = false;
- await new Promise(r => setTimeout(r, 300));
- }
- try {
- let code = '';
- let fileName = '';
- const tabType = pyYmodemSendBtn.getAttribute('data-pytype');
- if (tabType === 'offline') {
- code = getOfflineCode();
- fileName = 'Python/offline_download.py';
- } else if (tabType === 'drag') {
- code = getDragCode();
- fileName = 'Python/drag_download.py';
- } else {
- pyYlog('只允许发送离线下载脚本或拖拽下载脚本', '#f66');
- return;
- }
- const uint8Array = new TextEncoder().encode(code);
- const port = window.microLinkTerminal && window.microLinkTerminal.port;
- if (!port) throw new Error('串口未连接');
- let ok = await window.sendFileViaYmodem(
- port,
- uint8Array,
- fileName,
- uint8Array.length,
- msg => pyYlog(msg)
- );
- if (ok) {
- pyYlog('✅ 发送完成', '#0f0');
- } else {
- pyYlog('❌ 发送失败', '#f66');
- }
- } catch (e) {
- pyYlog('❌ 发送失败: ' + e.message, '#f66');
- if (e && e.stack) pyYlog('错误堆栈: ' + e.stack, '#f66');
- } finally {
- // === 恢复主终端监听 ===
- if (window.microLinkTerminal) {
- window.microLinkTerminal.isConnected = wasConnected;
- if (wasConnected && typeof window.microLinkTerminal.startReading === 'function') {
- window.microLinkTerminal.startReading();
- }
- }
- pyYmodemSendBtn.disabled = false;
- pyYmodemProgress.style.display = 'none';
- }
- });
- }
- // 以ELF tab为例,统一YMODEM发送按钮事件
- if (flmYmodemSendBtn) {
- flmYmodemSendBtn.addEventListener('click', async () => {
- if (!convertedBlob) { flmYlog('请先生成.o文件', '#ff0'); return; }
- if (!isSerialConnected()) {
- flmYlog('请先连接串口', '#f66'); return;
- }
- flmYlogClear();
- setFlmYmodemUIBusy(true);
- flmYmodemProgress.value = 0;
- flmYmodemProgress.max = 100;
- // === 加锁管理和监听控制 ===
- let wasConnected = false;
- if (window.microLinkTerminal) {
- if (window.microLinkTerminal.reader) {
- try { window.microLinkTerminal.reader.cancel(); } catch(e){}
- try { window.microLinkTerminal.reader.releaseLock(); } catch(e){}
- window.microLinkTerminal.reader = null;
- }
- wasConnected = window.microLinkTerminal.isConnected;
- window.microLinkTerminal.isConnected = false;
- await new Promise(r => setTimeout(r, 300));
- }
- try {
- const arrayBuffer = await convertedBlob.arrayBuffer();
- const uint8Array = new Uint8Array(arrayBuffer);
- const fileName = flmFileName ? (flmFileName + '.FLM.o') : 'firmware.FLM.o';
- const port = window.microLinkTerminal && window.microLinkTerminal.port;
- if (!port) throw new Error('串口未连接');
- let ok = await window.sendFileViaYmodem(
- port,
- uint8Array,
- fileName,
- uint8Array.length,
- msg => flmYlog(msg)
- );
- if (ok) {
- flmYlog('✅ 文件发送完成', '#0f0');
- } else {
- flmYlog('❌ 发送失败', '#f66');
- }
- } catch (e) {
- // 静默失败,不输出任何报错日志
- return;
- } finally {
- // === 恢复主终端监听 ===
- if (window.microLinkTerminal) {
- window.microLinkTerminal.isConnected = wasConnected;
- if (wasConnected && typeof window.microLinkTerminal.startReading === 'function') {
- window.microLinkTerminal.startReading();
- }
- }
- flmYlog('YMODEM流程已结束', '#888');
- setFlmYmodemUIBusy(false);
- }
- });
- }
- // 在tab切换到flmPanel时调用updateFlmYmodemBtn
- if (panelSelect) {
- panelSelect.addEventListener('change', function() {
- if (panelSelect.value === 'flmPanel') {
- updateFlmYmodemBtn();
- }
- });
- }
- // 在convertBtn点击和串口连接事件后也调用updateFlmYmodemBtn
- if (convertBtn) {
- convertBtn.addEventListener('click', function() {
- updateFlmYmodemBtn();
- });
- }
- document.addEventListener('serialConnected', function() {
- updateFlmYmodemBtn();
- });
- document.addEventListener('serialDisconnected', function() {
- updateFlmYmodemBtn();
- });
- });
- </script>
- <!-- deepseek变量分析器脚本自动集成(完整JS) -->
- <script>
- // 内置 ELF 解析器
- class ELFParser {
- static parse(arrayBuffer) {
- const dataView = new DataView(arrayBuffer);
- // 检查 ELF 魔数
- if (dataView.getUint32(0) !== 0x7F454C46) {
- throw new Error("不是有效的 ELF 文件");
- }
- const is32Bit = dataView.getUint8(4) === 1; // ELFCLASS32
- const isLE = dataView.getUint8(5) === 1; // ELFDATA2LSB
- const header = {
- e_type: dataView.getUint16(16, isLE),
- e_machine: dataView.getUint16(18, isLE),
- e_version: dataView.getUint32(20, isLE),
- e_entry: dataView.getUint32(24, isLE),
- e_phoff: is32Bit ? dataView.getUint32(28, isLE) : Number(dataView.getBigUint64(32, isLE)),
- e_shoff: is32Bit ? dataView.getUint32(32, isLE) : Number(dataView.getBigUint64(40, isLE)),
- e_flags: dataView.getUint32(36, isLE),
- e_ehsize: dataView.getUint16(40, isLE),
- e_phentsize: dataView.getUint16(42, isLE),
- e_phnum: dataView.getUint16(44, isLE),
- e_shentsize: dataView.getUint16(46, isLE),
- e_shnum: dataView.getUint16(48, isLE),
- e_shstrndx: dataView.getUint16(50, isLE)
- };
- // 解析节头
- const sections = [];
- const shoff = Number(header.e_shoff);
- const shentsize = header.e_shentsize;
- const shnum = header.e_shnum;
- for (let i = 0; i < shnum; i++) {
- const offset = shoff + i * shentsize;
- const section = {
- sh_name: dataView.getUint32(offset, isLE),
- sh_type: dataView.getUint32(offset + 4, isLE),
- sh_flags: is32Bit ? dataView.getUint32(offset + 8, isLE) : Number(dataView.getBigUint64(offset + 8, isLE)),
- sh_addr: is32Bit ? dataView.getUint32(offset + 12, isLE) : Number(dataView.getBigUint64(offset + 16, isLE)),
- sh_offset: is32Bit ? dataView.getUint32(offset + 16, isLE) : Number(dataView.getBigUint64(offset + 24, isLE)),
- sh_size: is32Bit ? dataView.getUint32(offset + 20, isLE) : Number(dataView.getBigUint64(offset + 32, isLE)),
- sh_link: dataView.getUint32(offset + (is32Bit ? 24 : 40), isLE),
- sh_info: dataView.getUint32(offset + (is32Bit ? 28 : 44), isLE),
- sh_addralign: is32Bit ? dataView.getUint32(offset + 32, isLE) : Number(dataView.getBigUint64(offset + 48, isLE)),
- sh_entsize: is32Bit ? dataView.getUint32(offset + 36, isLE) : Number(dataView.getBigUint64(offset + 56, isLE))
- };
- sections.push(section);
- }
- // 获取节名称
- const shstrtab = sections[header.e_shstrndx];
- const shstrtabData = new Uint8Array(arrayBuffer, shstrtab.sh_offset, shstrtab.sh_size);
- sections.forEach(section => {
- section.name = this.readNullTerminatedString(shstrtabData, section.sh_name);
- });
- // 解析符号表
- sections.forEach(section => {
- if (section.sh_type === 2) { // SHT_SYMTAB
- const symbols = [];
- const strtab = sections[section.sh_link];
- const strtabData = new Uint8Array(arrayBuffer, strtab.sh_offset, strtab.sh_size);
- const count = section.sh_size / section.sh_entsize;
- for (let i = 0; i < count; i++) {
- const offset = section.sh_offset + i * section.sh_entsize;
- const st_name = dataView.getUint32(offset, isLE);
- const st_value = is32Bit ? dataView.getUint32(offset + 4, isLE) : Number(dataView.getBigUint64(offset + 8, isLE));
- const st_size = is32Bit ? dataView.getUint32(offset + 8, isLE) : Number(dataView.getBigUint64(offset + 16, isLE));
- const st_info = dataView.getUint8(offset + (is32Bit ? 12 : 4));
- const st_shndx = dataView.getUint16(offset + (is32Bit ? 14 : 6), isLE);
- symbols.push({
- name: this.readNullTerminatedString(strtabData, st_name),
- value: st_value,
- size: st_size,
- type: st_info & 0x0F,
- bind: st_info >> 4,
- sectionIndex: st_shndx
- });
- }
- section.symbols = symbols;
- }
- });
- return {
- header: header,
- sections: sections
- };
- }
- static readNullTerminatedString(data, offset) {
- let str = "";
- let i = offset;
- while (i < data.length && data[i] !== 0) {
- str += String.fromCharCode(data[i]);
- i++;
- }
- return str;
- }
- }
-
- // 内存区域定义
- const memoryRegions = [
- { name: "Flash", start: 0x08000000, end: 0x08100000, color: "#4e9a06" },
- { name: "RAM", start: 0x20000000, end: 0x20020000, color: "#3465a4" },
- { name: "Peripherals", start: 0x40000000, end: 0x60000000, color: "#cc0000" }
- ];
-
- // 段类型颜色映射
- const sectionColors = {
- '.bss': '#ffd700',
- '.data': '#ff7f50',
- '.rodata': '#ad7fa8',
- '.heap': '#73d216',
- '.stack': '#f57900',
- 'default': '#555555'
- };
-
- // 当前分析的变量列表
- let variables = [];
- let elfFile = null;
- let selectedVariables = new Set(); // 存储选中的变量
- // 将selectedVariables挂载到全局,供数据处理函数使用
- window.selectedVariables = selectedVariables;
- let filteredVariables = []; // 存储筛选后的变量
-
- // DOM元素
- const fileInput = document.getElementById('axfFile');
- const analyzeBtn = document.getElementById('analyzeBtn');
- const variableTableBody = document.getElementById('variableTableBody');
- const memoryMap = document.getElementById('memoryMap');
- const variableDetail = document.getElementById('variableDetail');
- const fileInfo = document.getElementById('fileInfo');
- const progressBar = document.getElementById('progressBar');
- const searchFilter = document.getElementById('searchFilter');
- const selectAllVars = document.getElementById('selectAllVars');
- const selectedVariablesInfo = document.getElementById('selectedVariablesInfo');
- const selectedVariablesList = document.getElementById('selectedVariablesList');
- const selectedTotalSize = document.getElementById('selectedTotalSize');
-
- // 文件选择事件
- if (fileInput) {
- fileInput.addEventListener('change', function(e) {
- if (this.files.length > 0) {
- analyzeBtn.disabled = false;
- fileInfo.classList.add('d-none');
- // 更新文件状态显示
- const fileStatus = document.getElementById('fileStatus');
- if (fileStatus) {
- fileStatus.textContent = `已选择: ${this.files[0].name}`;
- fileStatus.style.color = '#28a745';
- }
- } else {
- analyzeBtn.disabled = true;
- // 重置文件状态显示
- const fileStatus = document.getElementById('fileStatus');
- if (fileStatus) {
- fileStatus.textContent = '未选择文件';
- fileStatus.style.color = '#6c757d';
- }
- }
- });
- }
-
- // 筛选器事件监听
- if (searchFilter) {
- searchFilter.addEventListener('input', filterVariables);
- }
-
- // 全选/取消全选
- if (selectAllVars) {
- selectAllVars.addEventListener('change', function() {
- const checkboxes = variableTableBody.querySelectorAll('input[type="checkbox"]:not([id="selectAllVars"])');
-
- if (this.checked) {
- // 全选(考虑9个变量限制)
- let selectedCount = 0;
- checkboxes.forEach(checkbox => {
- if (selectedCount < 9) {
- checkbox.checked = true;
- selectedVariables.add(checkbox.value);
-
- // 设置变量信息到全局对象
- const variable = variables.find(v => v.name === checkbox.value);
- if (variable) {
- if (!window.variableInfo) {
- window.variableInfo = {};
- }
- window.variableInfo[checkbox.value] = {
- size: variable.size,
- addr: variable.addr,
- type: variable.type,
- section: variable.section
- };
- console.log(`[全选] 设置变量 ${checkbox.value} 信息:`, window.variableInfo[checkbox.value]);
- }
-
- selectedCount++;
- }
- });
-
- // 如果变量总数超过9个,显示提示
- if (selectedVariables.size > 9) {
- alert('最多只能选择9个变量,已自动限制选择数量');
- // 移除超出限制的变量
- const selectedArray = Array.from(selectedVariables);
- for (let i = 9; i < selectedArray.length; i++) {
- selectedVariables.delete(selectedArray[i]);
- const checkbox = variableTableBody.querySelector(`input[value="${selectedArray[i]}"]`);
- if (checkbox) {
- checkbox.checked = false;
- }
- }
- }
- } else {
- // 取消全选
- checkboxes.forEach(checkbox => {
- checkbox.checked = false;
- selectedVariables.delete(checkbox.value);
-
- // 从全局对象中移除变量信息
- if (window.variableInfo && window.variableInfo[checkbox.value]) {
- delete window.variableInfo[checkbox.value];
- console.log(`[取消全选] 移除变量 ${checkbox.value} 信息`);
- }
- });
- }
-
- // 更新显示
- updateSelectedVariablesDisplay();
- updateHexSendPanel();
- });
- }
-
- // 分析按钮事件
- if (analyzeBtn) {
- analyzeBtn.addEventListener('click', function() {
- const file = fileInput.files[0];
- if (!file) return;
-
- // 重置UI
- variables = [];
- selectedVariables.clear();
- window.variableInfo = {}; // 清空变量信息
- filteredVariables = [];
- variableTableBody.innerHTML = '<tr><td colspan="6" class="text-center">分析中...</td></tr>';
- if (memoryMap) memoryMap.innerHTML = '<div class="text-center text-muted mt-5">分析中...</div>';
- if (variableDetail) variableDetail.innerHTML = '分析中...';
-
- if (progressBar) progressBar.classList.remove('d-none');
-
- // 显示进度条
- const progress = progressBar ? progressBar.querySelector('.progress-bar') : null;
- if (progress) progress.style.width = '0%';
-
- // 读取文件
- const reader = new FileReader();
- reader.onload = function(e) {
- try {
- console.log('开始解析ELF文件...');
- // 解析ELF文件
- const arrayBuffer = e.target.result;
- elfFile = ELFParser.parse(arrayBuffer);
- console.log('ELF文件解析成功:', elfFile);
-
- // 显示文件信息
- displayFileInfo(file, elfFile);
- console.log('文件信息显示完成');
-
- // 提取变量
- extractVariables(elfFile);
- console.log('变量提取完成,找到变量数量:', variables.length);
-
- // 更新进度条
- if (progress) progress.style.width = '100%';
- setTimeout(() => {
- if (progressBar) progressBar.classList.add('d-none');
- }, 500);
-
- // 显示结果
- displayVariables();
- drawMemoryMap();
-
- console.log('变量分析完成');
- } catch (error) {
- console.error('ELF解析错误:', error);
- variableTableBody.innerHTML = `<tr><td colspan="6" class="text-center text-danger">解析错误: ${error.message}</td></tr>`;
-
- if (progressBar) progressBar.classList.add('d-none');
- }
- };
- reader.onprogress = function(e) {
- if (e.lengthComputable && progress) {
- const percent = (e.loaded / e.total) * 100;
- progress.style.width = `${percent}%`;
-
- }
- };
- reader.readAsArrayBuffer(file);
- });
- }
-
- // 显示文件信息
- function displayFileInfo(file, elf) {
- if (fileInfo) {
- fileInfo.classList.remove('d-none');
- const fileName = document.getElementById('fileName');
- const fileSize = document.getElementById('fileSize');
- const elfType = document.getElementById('elfType');
- const entryPoint = document.getElementById('entryPoint');
-
- if (fileName) fileName.textContent = file.name;
- if (fileSize) fileSize.textContent = formatFileSize(file.size);
- if (elfType) elfType.textContent = elf.header.e_type;
- if (entryPoint) entryPoint.textContent = `0x${elf.header.e_entry.toString(16).toUpperCase()}`;
- }
- }
-
- // 格式化文件大小
- function formatFileSize(bytes) {
- if (bytes < 1024) return bytes + ' bytes';
- else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
- else return (bytes / 1048576).toFixed(1) + ' MB';
- }
-
- // 提取变量
- function extractVariables(elf) {
- variables = [];
- console.log('开始提取变量,节数量:', elf.sections.length);
-
- // 遍历所有节
- const sections = elf.sections;
- for (let i = 0; i < sections.length; i++) {
- const section = sections[i];
- console.log(`节 ${i}: 名称=${section.name}, 类型=${section.sh_type}, 符号数量=${section.symbols ? section.symbols.length : 0}`);
-
- // 只处理符号表
- if (section.symbols) {
- // 遍历符号表
- const symbols = section.symbols;
- for (let j = 0; j < symbols.length; j++) {
- const symbol = symbols[j];
- // 只关注变量(类型为STT_OBJECT)
- if (symbol.type === 1) { // STT_OBJECT
- const addr = symbol.value;
- const size = symbol.size;
- const name = symbol.name;
- const sectionIndex = symbol.sectionIndex;
- let sectionName = "Undefined";
- // 获取段名称
- if (sectionIndex > 0 && sectionIndex < sections.length) {
- sectionName = sections[sectionIndex].name;
- }
- // 确定变量类型
- let varType = "其他";
- if (sectionName === '.bss') varType = "未初始化";
- else if (sectionName === '.data') varType = "已初始化";
- else if (sectionName === '.rodata') varType = "只读";
- else if (sectionName === '.heap') varType = "堆";
- else if (sectionName === '.stack') varType = "栈";
- // 只添加段为RW_IRAM1和RW_IRAM2的变量
- if (sectionName === 'RW_IRAM1' || sectionName === 'RW_IRAM2') {
- variables.push({
- name: name,
- addr: addr,
- size: size,
- type: varType,
- section: sectionName
- });
- console.log(`找到RW_IRAM1段变量: ${name}, 地址: 0x${addr.toString(16)}, 大小: ${size}, 类型: ${varType}, 段: ${sectionName}`);
- }
- }
- }
- }
- }
-
- console.log(`变量提取完成,总共找到 ${variables.length} 个变量`);
-
- // 按地址排序
- variables.sort((a, b) => a.addr - b.addr);
- // 初始化筛选后的变量列表
- filteredVariables = [...variables];
- }
-
- // 筛选变量
- function filterVariables() {
- const searchValue = searchFilter ? searchFilter.value.toLowerCase() : '';
-
- filteredVariables = variables.filter(variable => {
- // 名称搜索
- if (searchValue && !variable.name.toLowerCase().includes(searchValue)) {
- return false;
- }
- return true;
- });
-
- displayVariables();
- updateSelectedVariablesDisplay();
- }
-
- // 显示变量表格
- function displayVariables() {
- if (filteredVariables.length === 0) {
- variableTableBody.innerHTML = `
- <tr>
- <td colspan="4" class="text-center text-muted" style="padding:40px 16px;font-size:15px;">
- <div style="margin-bottom:8px;">🔍</div>
- 未找到符合条件的变量
- </td>
- </tr>
- `;
- return;
- }
-
- let html = '';
- filteredVariables.forEach((variable, index) => {
- const isSelected = selectedVariables.has(variable.name);
- const rowClass = isSelected ? 'selected' : '';
- html += `
- <tr class="${rowClass}" data-variable="${variable.name}">
- <td>
- <input type="checkbox" class="variable-checkbox" value="${variable.name}"
- ${isSelected ? 'checked' : ''} onchange="toggleVariableSelection(this)">
- </td>
- <td>${variable.name}</td>
- <td>0x${variable.addr.toString(16).toUpperCase()}</td>
- <td>${variable.size} 字节</td>
- </tr>
- `;
- });
-
- variableTableBody.innerHTML = html;
-
- // 更新统计信息
- updateVariableStats();
- }
-
- // 切换变量选择状态
- function toggleVariableSelection(checkbox) {
- if (checkbox.checked) {
- // 检查变量数量限制(最多9个)
- if (selectedVariables.size >= 9) {
- alert('最多只能选择9个变量');
- checkbox.checked = false;
- return;
- }
-
- selectedVariables.add(checkbox.value);
-
- // 设置变量信息到全局对象,供数据处理函数使用
- const variable = variables.find(v => v.name === checkbox.value);
- if (variable) {
- if (!window.variableInfo) {
- window.variableInfo = {};
- }
- window.variableInfo[checkbox.value] = {
- size: variable.size,
- addr: variable.addr,
- type: variable.type,
- section: variable.section
- };
- console.log(`[变量选择] 设置变量 ${checkbox.value} 信息:`, window.variableInfo[checkbox.value]);
- }
-
- // 为选中的变量创建图表
- if (window.multiChartManager) {
- window.multiChartManager.createChart(checkbox.value);
- }
- } else {
- selectedVariables.delete(checkbox.value);
-
- // 从全局对象中移除变量信息
- if (window.variableInfo && window.variableInfo[checkbox.value]) {
- delete window.variableInfo[checkbox.value];
- console.log(`[变量选择] 移除变量 ${checkbox.value} 信息`);
- }
-
- // 删除对应的图表
- if (window.multiChartManager) {
- window.multiChartManager.removeChart(checkbox.value);
- }
- }
-
- // 更新全选状态
- updateSelectAllState();
- // 更新选中变量显示
- updateSelectedVariablesDisplay();
- // 更新串口发送区域
- updateHexSendPanel();
- }
-
- // 更新全选状态
- function updateSelectAllState() {
- if (selectAllVars) {
- const checkboxes = variableTableBody.querySelectorAll('input[type="checkbox"]:not([id="selectAllVars"])');
- const checkedCount = Array.from(checkboxes).filter(cb => cb.checked).length;
- selectAllVars.checked = checkedCount === checkboxes.length;
- selectAllVars.indeterminate = checkedCount > 0 && checkedCount < checkboxes.length;
- }
- }
-
- // 更新变量统计信息
- function updateVariableStats() {
- // 统计信息已移除,此函数保留以维持代码结构
- }
-
- // 更新选中变量显示
- function updateSelectedVariablesDisplay() {
- if (selectedVariables.size === 0) {
- if (selectedVariablesInfo) selectedVariablesInfo.style.display = 'none';
- // 重置HEX曲线图容器高度
- adjustHexChartContainerHeight(0);
- return;
- }
-
- if (selectedVariablesInfo) selectedVariablesInfo.style.display = 'block';
- let html = '';
- let totalSize = 0;
-
- selectedVariables.forEach(varName => {
- const variable = variables.find(v => v.name === varName);
- if (variable) {
- totalSize += variable.size;
- html += `
- <div class="selected-variable-tag">
- ${variable.name} (${variable.size}字节)
- <button class="remove-btn" onclick="removeVariableSelection('${varName}')">×</button>
- </div>
- `;
- }
- });
-
- if (selectedVariablesList) selectedVariablesList.innerHTML = html;
- if (selectedTotalSize) selectedTotalSize.textContent = totalSize;
-
- // 动态调整HEX曲线图容器高度
- adjustHexChartContainerHeight(selectedVariables.size);
- }
-
- // 动态调整HEX曲线图容器高度
- function adjustHexChartContainerHeight(selectedCount) {
- // 找到包含"实时HEX曲线图"标题的容器
- const hexSendPanels = document.querySelectorAll('.hex-send-panel');
- let hexChartPanel = null;
-
- for (let panel of hexSendPanels) {
- const title = panel.querySelector('h4');
- if (title && title.textContent.includes('实时HEX曲线图')) {
- hexChartPanel = panel;
- break;
- }
- }
-
- const multiChartContainer = document.getElementById('multiChartContainer');
-
- if (!hexChartPanel || !multiChartContainer) return;
-
- // 基础高度:标题栏 + 按钮栏 + 边距
- const baseHeight = 120;
-
- // 每个变量图表的高度(包括间距)
- const chartHeight = 500;
- const chartMargin = 20;
-
- // 计算总高度
- let totalHeight;
- if (selectedCount === 0) {
- // 没有选中变量时,使用默认高度
- totalHeight = 600;
- } else if (selectedCount === 1) {
- // 单个变量时,使用适中的高度
- totalHeight = baseHeight + chartHeight;
- } else {
- // 多个变量时,根据数量动态计算
- totalHeight = baseHeight + (selectedCount * (chartHeight + chartMargin));
- }
-
- // 设置最小和最大高度限制
- const minHeight = 800;
- const maxHeight = 3600;
- totalHeight = Math.max(minHeight, Math.min(maxHeight, totalHeight));
-
- // 应用高度调整 - 修改正确的容器
- hexChartPanel.style.minHeight = totalHeight + 'px';
- multiChartContainer.style.minHeight = (totalHeight - baseHeight) + 'px';
-
- console.log(`[HEX曲线图容器调整] 选中变量数量: ${selectedCount}, 容器高度: ${totalHeight}px`);
- }
-
- // 移除变量选择
- function removeVariableSelection(varName) {
- selectedVariables.delete(varName);
- // 删除对应的图表
- if (window.multiChartManager) {
- window.multiChartManager.removeChart(varName);
- }
- // 更新表格中的复选框
- const checkbox = variableTableBody.querySelector(`input[value="${varName}"]`);
- if (checkbox) {
- checkbox.checked = false;
- }
- // 更新显示
- updateSelectAllState();
- updateSelectedVariablesDisplay();
- updateHexSendPanel();
- }
-
- // 更新串口发送面板
- function updateHexSendPanel() {
- // 不再需要生成地址输入框,保持面板简洁
- // 用户可以通过采样率设置来控制发送频率
- }
-
- // 将函数挂载到全局作用域
- window.displayVariables = displayVariables;
- window.toggleVariableSelection = toggleVariableSelection;
- window.removeVariableSelection = removeVariableSelection;
-
- // 显示变量详情
- window.showVariableDetail = function(index) {
- const varItem = variables[index];
-
- // 创建模态框显示详情
- const modalHTML = `
- <div id="variableDetailModal" style="
- position: fixed; top: 0; left: 0; width: 100%; height: 100%;
- background: rgba(0,0,0,0.5); z-index: 9999; display: flex;
- align-items: center; justify-content: center;">
- <div style="
- background: white; border-radius: 12px; padding: 24px;
- max-width: 500px; width: 90%; max-height: 80%; overflow-y: auto;
- box-shadow: 0 10px 30px rgba(0,0,0,0.3);">
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
- <h4 style="margin: 0; color: #2980b9; font-size: 18px;">
- <span style="margin-right:8px;">🔍</span>变量详细信息
- </h4>
- <button onclick="closeVariableDetail()" style="
- background: none; border: none; font-size: 24px;
- cursor: pointer; color: #6c757d;">×</button>
- </div>
- <div style="background: #f8f9fa; border-radius: 8px; padding: 16px; font-family: monospace; font-size: 14px; line-height: 1.6;">
- <div style="margin-bottom: 8px;"><strong>📝 变量名:</strong> ${varItem.name}</div>
- <div style="margin-bottom: 8px;"><strong>📍 地址:</strong> 0x${varItem.addr.toString(16).toUpperCase().padStart(8, '0')}</div>
- <div style="margin-bottom: 8px;"><strong>📊 大小:</strong> ${varItem.size} 字节`;
-
- // 添加大小描述
- if (varItem.size > 1024 * 1024) {
- modalHTML += ` (${(varItem.size/(1024*1024)).toFixed(2)} MB)`;
- } else if (varItem.size > 1024) {
- modalHTML += ` (${(varItem.size/1024).toFixed(2)} KB)`;
- }
-
- modalHTML += `</div>
- <div style="margin-bottom: 8px;"><strong>🏷️ 类型:</strong> ${varItem.type}</div>
- <div style="margin-bottom: 8px;"><strong>📂 段:</strong> ${varItem.section}</div>`;
-
- // 添加内存位置描述
- if (varItem.addr >= 0x08000000 && varItem.addr < 0x09000000) {
- modalHTML += `<div style="margin-bottom: 8px;"><strong>💾 位置:</strong> Flash 存储器</div>`;
- } else if (varItem.addr >= 0x20000000 && varItem.addr < 0x30000000) {
- modalHTML += `<div style="margin-bottom: 8px;"><strong>💾 位置:</strong> RAM</div>`;
- } else if (varItem.addr >= 0x40000000 && varItem.addr < 0x60000000) {
- modalHTML += `<div style="margin-bottom: 8px;"><strong>💾 位置:</strong> 外设寄存器区域</div>`;
- } else {
- modalHTML += `<div style="margin-bottom: 8px;"><strong>💾 位置:</strong> 未知内存区域</div>`;
- }
-
- modalHTML += `
- </div>
- <div style="margin-top: 20px; text-align: center;">
- <button onclick="closeVariableDetail()" style="
- background: #3498db; color: white; border: none;
- padding: 10px 20px; border-radius: 6px; cursor: pointer;
- font-size: 14px;">关闭</button>
- </div>
- </div>
- </div>
- `;
-
- // 移除已存在的模态框
- const existingModal = document.getElementById('variableDetailModal');
- if (existingModal) {
- existingModal.remove();
- }
-
- // 添加新模态框
- document.body.insertAdjacentHTML('beforeend', modalHTML);
- };
-
- // 关闭变量详情模态框
- window.closeVariableDetail = function() {
- const modal = document.getElementById('variableDetailModal');
- if (modal) {
- modal.remove();
- }
- };
-
- // 绘制内存映射
- function drawMemoryMap() {
- if (!memoryMap) return;
-
- const canvas = document.createElement('canvas');
- canvas.width = 800;
- canvas.height = 400;
- canvas.style.width = '100%';
- canvas.style.height = 'auto';
-
- const ctx = canvas.getContext('2d');
-
- // 清空画布
- ctx.clearRect(0, 0, canvas.width, canvas.height);
-
- // 绘制内存区域
- memoryRegions.forEach(region => {
- const y = (region.start - 0x08000000) / (0x60000000 - 0x08000000) * canvas.height;
- const height = (region.end - region.start) / (0x60000000 - 0x08000000) * canvas.height;
-
- ctx.fillStyle = region.color;
- ctx.fillRect(0, y, canvas.width, height);
-
- // 添加标签
- ctx.fillStyle = 'white';
- ctx.font = '12px Arial';
- ctx.fillText(region.name, 10, y + height / 2 + 4);
- });
-
- // 绘制变量位置
- variables.forEach(variable => {
- const y = (variable.addr - 0x08000000) / (0x60000000 - 0x08000000) * canvas.height;
- const height = Math.max(2, variable.size / (0x60000000 - 0x08000000) * canvas.height);
-
- ctx.fillStyle = sectionColors[variable.section] || sectionColors.default;
- ctx.fillRect(100, y, 20, height);
- });
-
- // 添加图例
- let legendY = 20;
- Object.entries(sectionColors).forEach(([section, color]) => {
- ctx.fillStyle = color;
- ctx.fillRect(canvas.width - 150, legendY, 15, 15);
- ctx.fillStyle = 'black';
- ctx.font = '10px Arial';
- ctx.fillText(section, canvas.width - 130, legendY + 12);
- legendY += 20;
- });
-
- memoryMap.innerHTML = '';
- memoryMap.appendChild(canvas);
- }
-
- // 初始化页面
- document.addEventListener('DOMContentLoaded', function() {
- // 重置UI状态
- if (variableTableBody) {
- variableTableBody.innerHTML = `
- <tr>
- <td colspan="6" class="text-center text-muted" style="padding:40px 16px;font-size:15px;">
- <div style="margin-bottom:8px;">📁</div>
- 请上传.axf文件并点击"分析文件"
- </td>
- </tr>
- `;
- }
-
-
- // 隐藏选中变量信息
- if (selectedVariablesInfo) {
- selectedVariablesInfo.style.display = 'none';
- }
-
- // 初始化HEX曲线图容器高度
- adjustHexChartContainerHeight(0);
-
- // 初始化采样率输入框
- const hexSampleRateInput = document.getElementById('hexSampleRateInput');
- if (hexSampleRateInput) {
- // 设置默认采样率
- const defaultSampleRate = parseInt(hexSampleRateInput.value) || 100;
- if (window.multiChartManager) {
- window.multiChartManager.setSamplingRate(defaultSampleRate);
- }
-
- hexSampleRateInput.addEventListener('input', function() {
- const sampleRate = parseInt(this.value) || 500;
- const period = Math.round(1000 / sampleRate);
- console.log(`采样率: ${sampleRate} Hz, 对应周期: ${period} ms`);
-
- // 设置多变量图表的采样率
- if (window.multiChartManager) {
- window.multiChartManager.setSamplingRate(sampleRate);
- }
- });
- }
-
- // 初始化时间显示范围输入框
- const timeDisplayRangeInput = document.getElementById('timeDisplayRangeInput');
- if (timeDisplayRangeInput) {
- // 设置默认时间显示范围
- const defaultTimeDisplayRange = parseInt(timeDisplayRangeInput.value) || 10;
- if (window.multiChartManager) {
- window.multiChartManager.setTimeDisplayRange(defaultTimeDisplayRange);
- }
-
- timeDisplayRangeInput.addEventListener('input', function() {
- const timeDisplayRange = parseInt(this.value) || 10;
- console.log(`时间显示范围: ${timeDisplayRange} 秒`);
-
- // 设置多变量图表的时间显示范围
- if (window.multiChartManager) {
- window.multiChartManager.setTimeDisplayRange(timeDisplayRange);
- }
- });
- }
-
- // 初始化串口发送功能的事件监听器
- const sendHexCmdBtn = document.getElementById('sendHexCmdBtn');
-
- if (sendHexCmdBtn) {
- sendHexCmdBtn.addEventListener('click', function() {
- const hexSampleRateInput = document.getElementById('hexSampleRateInput');
-
- if (!hexSampleRateInput) {
- alert('采样率输入框未找到');
- return;
- }
-
- // 检查是否有选中的变量
- if (selectedVariables.size === 0) {
- alert('请先选择要监控的变量');
- return;
- }
-
- const sampleRate = parseInt(hexSampleRateInput.value) || 100;
- const period = Math.round(1000 / sampleRate); // 将采样率转换为周期(毫秒)
-
- // 根据选中的变量动态生成读取RAM的命令
- let command = 'cmd.read_ram(';
- let first = true;
-
- selectedVariables.forEach(varName => {
- const variable = variables.find(v => v.name === varName);
- if (variable) {
- if (!first) {
- command += ',';
- }
- command += `0x${variable.addr.toString(16).toUpperCase()},${variable.size}`;
- first = false;
- }
- });
-
- command += `,${period})`;
-
- // 发送命令到串口
- if (window.microLinkTerminal && window.microLinkTerminal.isConnected) {
- window.microLinkTerminal.sendCommand(command);
- console.log('发送命令:', command);
- console.log('采样率:', sampleRate, 'Hz, 周期:', period, 'ms');
- console.log('选中的变量数量:', selectedVariables.size);
- } else {
- alert('请先连接串口');
- }
- });
- }
- });
- </script>
- <!-- ECharts - 使用本地文件 -->
- <script src="assets/echarts.min.js"></script>
- <!-- 备用CDN -->
- <script>
- if (typeof echarts === 'undefined') {
- document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"><\/script>');
- }
- </script>
- </body>
- </html>
|