index.html 138 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>MicroLink Web Serial Terminal</title>
  7. <link rel="stylesheet" href="assets/style.css">
  8. <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
  9. <!-- 代码预览区美化和复制按钮 -->
  10. <style>
  11. .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; }
  12. .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; }
  13. .code-copy-btn:hover { opacity: 1; background: #3498db; }
  14. /* 右侧面板整体美化 */
  15. .right-panel {
  16. background: #f7fafd;
  17. border-radius: 14px;
  18. box-shadow: 0 4px 24px rgba(52,152,219,0.08);
  19. padding: 28px 24px 24px 24px;
  20. margin: 0 0 0 18px;
  21. min-width: 420px;
  22. }
  23. /* FLM ELF解析器美化 */
  24. .flm-parser-panel {
  25. background: #fff;
  26. border-radius: 12px;
  27. box-shadow: 0 2px 12px rgba(52,152,219,0.07);
  28. padding: 28px 28px 18px 28px;
  29. margin-bottom: 18px;
  30. border: 1.5px solid #e3eaf2;
  31. }
  32. .flm-parser-panel h2 {
  33. color: #2980b9;
  34. font-size: 1.6rem;
  35. margin-bottom: 8px;
  36. }
  37. .flm-parser-panel p {
  38. color: #666;
  39. font-size: 1.05rem;
  40. margin-bottom: 18px;
  41. }
  42. .flm-parser-panel input[type="file"] {
  43. margin: 10px 0 18px 0;
  44. font-size: 1rem;
  45. border: 1px solid #d0d7de;
  46. border-radius: 6px;
  47. padding: 8px 12px;
  48. background: #f8fafc;
  49. }
  50. .flm-parser-panel button, .flm-parser-panel a.btn {
  51. margin: 8px 8px 8px 0;
  52. width: 160px;
  53. font-size: 1rem;
  54. border-radius: 6px;
  55. box-shadow: 0 2px 8px rgba(52,152,219,0.06);
  56. text-align: center;
  57. padding: 10px 16px;
  58. }
  59. .flm-parser-panel a.btn {
  60. padding: 10px 16px;
  61. display: inline-block;
  62. text-decoration: none;
  63. }
  64. .flm-parser-panel pre#log {
  65. background: #23272e;
  66. color: #e0e0e0;
  67. border-radius: 7px;
  68. padding: 14px 18px;
  69. font-size: 14px;
  70. min-height: 80px;
  71. margin-top: 12px;
  72. margin-bottom: 0;
  73. font-family: 'Consolas', 'Menlo', 'Monaco', 'monospace';
  74. box-shadow: 0 1px 4px rgba(52,152,219,0.04);
  75. }
  76. #flmYmodemSendBtn {
  77. width: 160px;
  78. font-size: 1rem;
  79. border-radius: 6px;
  80. }
  81. #flmYmodemProgress {
  82. width: 180px;
  83. height: 16px;
  84. vertical-align: middle;
  85. margin-left: 16px;
  86. border-radius: 8px;
  87. background: #eaf6fb;
  88. box-shadow: 0 1px 4px rgba(52,152,219,0.04);
  89. }
  90. #flmYmodemLog {
  91. background: #23272e;
  92. color: #0f0;
  93. padding: 8px 10px;
  94. border-radius: 6px;
  95. min-height: 36px;
  96. max-height: 90px;
  97. overflow: auto;
  98. font-size: 13px;
  99. margin-top: 8px;
  100. font-family: 'Consolas', 'Menlo', 'Monaco', 'monospace';
  101. }
  102. /* Python脚本生成器美化 */
  103. .config-panel {
  104. background: #fff;
  105. border-radius: 12px;
  106. box-shadow: 0 2px 12px rgba(52,152,219,0.07);
  107. padding: 24px 24px 18px 24px;
  108. border: 1.5px solid #e3eaf2;
  109. }
  110. .config-group {
  111. margin-bottom: 22px;
  112. padding-bottom: 10px;
  113. border-bottom: 1px solid #e9e9e9;
  114. }
  115. .config-group:last-child {
  116. border-bottom: none;
  117. }
  118. .config-panel h2 {
  119. color: #2980b9;
  120. font-size: 1.18rem;
  121. margin-bottom: 8px;
  122. }
  123. .config-panel label {
  124. font-weight: 500;
  125. color: #444;
  126. margin-bottom: 4px;
  127. }
  128. .config-panel input, .config-panel select {
  129. width: 100%;
  130. padding: 10px 12px;
  131. border: 1.5px solid #d0d7de;
  132. border-radius: 6px;
  133. font-size: 1rem;
  134. margin-bottom: 8px;
  135. background: #f8fafc;
  136. transition: border-color 0.2s;
  137. }
  138. .config-panel input:focus, .config-panel select:focus {
  139. border-color: #3498db;
  140. outline: none;
  141. }
  142. .btn-download {
  143. width: 100%;
  144. margin-top: 18px;
  145. font-size: 1.08rem;
  146. border-radius: 6px;
  147. padding: 12px 0;
  148. box-shadow: 0 2px 8px rgba(52,152,219,0.06);
  149. }
  150. .preview-panel {
  151. background: #23272e;
  152. border-radius: 12px;
  153. padding: 24px 18px 18px 18px;
  154. color: #f8f8f2;
  155. box-shadow: 0 2px 12px rgba(52,152,219,0.07);
  156. min-width: 320px;
  157. margin-left: 18px;
  158. }
  159. .code-header {
  160. display: flex;
  161. justify-content: space-between;
  162. align-items: center;
  163. margin-bottom: 15px;
  164. }
  165. .code-header h2 {
  166. color: #fff;
  167. font-size: 1.18rem;
  168. margin: 0;
  169. }
  170. .script-tabs {
  171. display: flex;
  172. gap: 10px;
  173. }
  174. .script-tab {
  175. padding: 7px 18px;
  176. background: #444;
  177. color: #ccc;
  178. border: none;
  179. border-radius: 4px;
  180. cursor: pointer;
  181. font-size: 14px;
  182. transition: all 0.3s;
  183. }
  184. .script-tab.active {
  185. background: #3498db;
  186. color: #fff;
  187. }
  188. .script-tab:hover {
  189. background: #555;
  190. }
  191. .script-tab.active:hover {
  192. background: #2980b9;
  193. }
  194. .script-content {
  195. display: none;
  196. }
  197. .script-content.active {
  198. display: block;
  199. }
  200. .code-block {
  201. background: #181c23;
  202. color: #f8f8f2;
  203. font-family: 'Consolas', 'Menlo', 'Monaco', 'monospace';
  204. font-size: 15px;
  205. line-height: 1.7;
  206. border-radius: 8px;
  207. padding: 18px 18px 18px 24px;
  208. overflow-x: auto;
  209. min-height: 120px;
  210. white-space: pre;
  211. position: relative;
  212. box-shadow: 0 1px 4px rgba(52,152,219,0.04);
  213. }
  214. .code-copy-btn {
  215. position: absolute;
  216. top: 12px;
  217. right: 18px;
  218. background: #3498db;
  219. color: #fff;
  220. border: none;
  221. border-radius: 4px;
  222. padding: 4px 12px;
  223. font-size: 13px;
  224. cursor: pointer;
  225. opacity: 0.85;
  226. transition: opacity 0.2s, background 0.2s;
  227. z-index: 2;
  228. }
  229. .code-copy-btn:hover {
  230. opacity: 1;
  231. background: #217dbb;
  232. }
  233. .highlight {
  234. background-color: #ffe082;
  235. color: #222;
  236. border-radius: 3px;
  237. padding: 1px 4px;
  238. }
  239. @media (max-width: 1200px) {
  240. .right-panel { min-width: 320px; padding: 18px 6px 18px 6px; }
  241. .preview-panel { min-width: 0; margin-left: 0; }
  242. }
  243. @media (max-width: 900px) {
  244. .right-panel { min-width: 0; padding: 8px 2px 8px 2px; }
  245. .preview-panel { padding: 12px 4px 12px 4px; }
  246. }
  247. </style>
  248. <style>
  249. #pyYmodemLog, #flmYmodemLog, #log, #pyYmodemProgress { display: none !important; }
  250. </style>
  251. </head>
  252. <body>
  253. <div class="container">
  254. <!-- 头部控制栏 -->
  255. <header class="header">
  256. <div class="header-left">
  257. <h1><i class="fas fa-microchip"></i> MicroLink Web Serial Terminal</h1>
  258. </div>
  259. <div class="header-right">
  260. <!-- 移动过来的四个按键 -->
  261. <div class="header-controls">
  262. <button id="connectBtn" class="btn btn-primary"><i class="fas fa-plug"></i> 连接串口</button>
  263. <button id="disconnectBtn" class="btn btn-secondary" disabled><i class="fas fa-times"></i> 断开连接</button>
  264. <button id="clearBtn" class="btn btn-warning"><i class="fas fa-trash"></i> 清空</button>
  265. <button id="saveLogBtn" class="btn btn-info"><i class="fas fa-save"></i> 保存日志</button>
  266. </div>
  267. <div class="connection-status">
  268. <span id="connectionStatus" class="status-disconnected">
  269. <i class="fas fa-circle"></i> 未连接
  270. </span>
  271. </div>
  272. </div>
  273. </header>
  274. <!-- 主要内容区域 -->
  275. <main class="main-content">
  276. <!-- 左侧功能块选择栏 -->
  277. <div class="sidebar">
  278. <button class="sidebar-btn active" data-panel="serialPanel">串口调试</button>
  279. <button class="sidebar-btn" data-panel="flmPanel">FLM配置</button>
  280. <button class="sidebar-btn" data-panel="scriptPanel">Python脚本配置</button>
  281. <button class="sidebar-btn" data-panel="varPanel">变量分析</button>
  282. </div>
  283. <!-- 中间功能内容区 -->
  284. <div class="center-panel">
  285. <!-- 串口调试内容(API控制区,默认显示) -->
  286. <div id="serialPanel" class="center-content" style="display: block;">
  287. <div class="control-panel">
  288. <h3><i class="fas fa-cogs"></i> MicroLink API 控制</h3>
  289. <!-- 参数管理 -->
  290. <div class="api-section">
  291. <h4><i class="fas fa-save"></i> 参数管理</h4>
  292. <div class="button-group">
  293. <button id="saveParams" class="btn btn-success">保存到本地</button>
  294. <button id="saveToFile" class="btn btn-primary">保存到文件</button>
  295. <button id="loadParams" class="btn btn-info">加载参数</button>
  296. <button id="resetParams" class="btn btn-warning">重置参数</button>
  297. <button id="loadConfigFile" class="btn btn-secondary">重新加载HTML配置</button>
  298. </div>
  299. <div class="param-group">
  300. <label>手动选择配置文件 (可选):</label>
  301. <input type="file" id="configFileInput" accept=".txt" class="param-input" style="font-size: 12px;">
  302. </div>
  303. </div>
  304. <!-- 自定义命令 -->
  305. <div class="api-section">
  306. <h4><i class="fas fa-terminal"></i> 自定义命令</h4>
  307. <div class="param-group">
  308. <textarea id="customCommand" placeholder="输入自定义命令..." class="custom-command-input"></textarea>
  309. </div>
  310. <div class="button-group">
  311. <button id="sendCustom" class="btn btn-primary">发送命令</button>
  312. <button id="addCustom" class="btn btn-secondary">添加到列表</button>
  313. </div>
  314. <div id="customCommandsList" class="custom-commands-list"></div>
  315. </div>
  316. <!-- RTT控制 -->
  317. <div class="api-section">
  318. <h4><i class="fas fa-satellite-dish"></i> RTT 控制</h4>
  319. <div class="param-group">
  320. <label>RTT地址:</label>
  321. <input type="text" id="rttAddr" value="0x20000000" class="param-input">
  322. </div>
  323. <div class="param-group">
  324. <label>搜索大小:</label>
  325. <input type="text" id="rttSize" value="0x4000" class="param-input">
  326. </div>
  327. <div class="param-group">
  328. <label>通道:</label>
  329. <input type="number" id="rttChannel" value="0" class="param-input">
  330. </div>
  331. <div class="button-group">
  332. <button id="startRTT" class="btn btn-primary">启动RTT</button>
  333. <button id="stopRTT" class="btn btn-secondary">停止RTT</button>
  334. </div>
  335. </div>
  336. <!-- SystemView控制 -->
  337. <div class="api-section">
  338. <h4><i class="fas fa-chart-line"></i> SystemView 控制</h4>
  339. <div class="param-group">
  340. <label>RTT地址:</label>
  341. <input type="text" id="svAddr" value="0x20000000" class="param-input">
  342. </div>
  343. <div class="param-group">
  344. <label>搜索大小:</label>
  345. <input type="text" id="svSize" value="0x4000" class="param-input">
  346. </div>
  347. <div class="param-group">
  348. <label>通道:</label>
  349. <input type="number" id="svChannel" value="1" class="param-input">
  350. </div>
  351. <div class="button-group">
  352. <button id="startSystemView" class="btn btn-primary">启动SystemView</button>
  353. </div>
  354. </div>
  355. <!-- FLM下载算法 -->
  356. <div class="api-section">
  357. <h4><i class="fas fa-download"></i> FLM 下载算法</h4>
  358. <div class="param-group">
  359. <label>FLM路径:</label>
  360. <input type="text" id="flmPath" value="STM32/STM32F4xx_1024.FLM.o" class="param-input">
  361. </div>
  362. <div class="param-group">
  363. <label>Flash基地址:</label>
  364. <input type="text" id="baseAddr" value="0x08000000" class="param-input">
  365. </div>
  366. <div class="param-group">
  367. <label>RAM地址:</label>
  368. <input type="text" id="ramAddr" value="0x20000000" class="param-input">
  369. </div>
  370. <div class="button-group">
  371. <button id="loadFLM" class="btn btn-primary">加载FLM</button>
  372. </div>
  373. </div>
  374. <!-- 固件下载 -->
  375. <div class="api-section">
  376. <h4><i class="fas fa-upload"></i> 固件下载</h4>
  377. <div class="param-group">
  378. <label>BIN文件路径:</label>
  379. <input type="text" id="binPath" value="firmware.bin" class="param-input">
  380. </div>
  381. <div class="param-group">
  382. <label>下载地址:</label>
  383. <input type="text" id="binAddr" value="0x08000000" class="param-input">
  384. </div>
  385. <div class="button-group">
  386. <button id="loadBin" class="btn btn-primary">下载固件</button>
  387. <button id="offlineDownload" class="btn btn-warning">脱机下载</button>
  388. </div>
  389. </div>
  390. <!-- Ymodem传输 -->
  391. <div class="api-section">
  392. <h4><i class="fas fa-exchange-alt"></i> Ymodem 传输</h4>
  393. <div class="param-group">
  394. <label>文件路径:</label>
  395. <input type="text" id="ymodemFile" value="update.bin" class="param-input">
  396. </div>
  397. <div class="button-group">
  398. <button id="ymodemSend" class="btn btn-primary">发送文件</button>
  399. </div>
  400. </div>
  401. </div>
  402. </div>
  403. <!-- FLM配置内容 -->
  404. <div id="flmPanel" class="center-content" style="display: none;">
  405. <div class="flm-parser-panel">
  406. <h2>FLM ELF解析器</h2>
  407. <p>将FLM ELF文件转换为.o格式,用于串口设备烧写</p>
  408. <input type="file" id="flmFile" accept=".FLM">
  409. <button id="convertBtn" disabled class="btn btn-warning">转换为 .o 文件</button>
  410. <a id="downloadLink" href="#" style="display: none;" class="btn btn-success">下载 .o 文件</a>
  411. <pre id="log"></pre>
  412. <!-- YMODEM发送区 -->
  413. <button id="flmYmodemSendBtn" class="btn btn-primary" disabled>发送FLM文件</button>
  414. <progress id="flmYmodemProgress" value="0" max="100" style="width:180px;vertical-align:middle;display:none;margin:8px 0 0 0;"></progress>
  415. </div>
  416. </div>
  417. <!-- Python脚本配置内容 -->
  418. <div id="scriptPanel" class="center-content" style="display: none;">
  419. <div class="content">
  420. <div class="config-panel">
  421. <div class="config-group">
  422. <h2>拖拽下载FLM文件配置</h2>
  423. <div class="form-group">
  424. <label for="customFlm">FLM文件名:</label>
  425. <input type="text" id="customFlm" placeholder="例如: custom_flm.FLM.o">
  426. </div>
  427. </div>
  428. <div class="config-group">
  429. <h2>内存地址配置</h2>
  430. <div class="address-inputs">
  431. <div class="form-group">
  432. <label for="address1">FLASH基地址 (十六进制):</label>
  433. <input type="text" id="address1" value="0X08000000" placeholder="例如: 0X08000000">
  434. </div>
  435. <div class="form-group">
  436. <label for="address2">RAM基地址 (十六进制):</label>
  437. <input type="text" id="address2" value="0x20000000" placeholder="例如: 0x20000000">
  438. </div>
  439. </div>
  440. </div>
  441. <div class="config-group">
  442. <h2>多文件离线烧录配置</h2>
  443. <div class="multi-file-config">
  444. <div class="file-table-header">
  445. <span class="file-name-col">文件名</span>
  446. <span class="file-address-col">地址</span>
  447. <span class="file-algorithm-col">下载算法</span>
  448. <span class="file-action-col">操作</span>
  449. </div>
  450. <div id="fileTableBody" class="file-table-body">
  451. <!-- 动态生成的文件行将在这里 -->
  452. </div>
  453. <div class="file-table-actions">
  454. <button type="button" id="addFileBtn" class="btn btn-success">
  455. <i class="fas fa-plus"></i> 添加文件
  456. </button>
  457. <button type="button" id="clearFilesBtn" class="btn btn-warning">
  458. <i class="fas fa-trash"></i> 清空所有
  459. </button>
  460. </div>
  461. </div>
  462. </div>
  463. <div class="config-group">
  464. <h2>SWD下载速度配置</h2>
  465. <div class="form-group">
  466. <label for="swdClockSpeed">SWD时钟速度:</label>
  467. <select id="swdClockSpeed">
  468. <option value="10M">10M (最快)</option>
  469. <option value="5M">5M</option>
  470. <option value="2M">2M</option>
  471. <option value="1M">1M</option>
  472. <option value="500K">500K</option>
  473. <option value="200K">200K</option>
  474. <option value="100K">100K</option>
  475. <option value="50K">50K</option>
  476. <option value="20K">20K</option>
  477. <option value="10K">10K</option>
  478. <option value="5K">5K</option>
  479. </select>
  480. </div>
  481. </div>
  482. <div class="config-group">
  483. <button class="download-btn send-offline" id="pyYmodemSendOfflineBtn">发送离线下载python文件</button>
  484. <button class="download-btn send-drag" id="pyYmodemSendDragBtn">发送拖拽下载python文件</button>
  485. <progress id="pyYmodemProgress" value="0" max="100" style="width:180px;vertical-align:middle;display:none;margin-left:16px;"></progress>
  486. <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>
  487. </div>
  488. </div>
  489. <!--
  490. <div class="preview-panel">
  491. <div class="code-header">
  492. <h2>生成的Python代码</h2>
  493. <div class="script-tabs">
  494. <button class="script-tab active" onclick="switchScriptTab('offline')">离线下载脚本</button>
  495. <button class="script-tab" onclick="switchScriptTab('drag')">拖拽下载脚本</button>
  496. </div>
  497. </div>
  498. <div id="offlineScript" class="script-content active">
  499. <div style="position:relative;">
  500. <button class="code-copy-btn" id="copyOfflineBtn">复制</button>
  501. <pre class="code-block"><code id="codePreview"></code></pre>
  502. </div>
  503. </div>
  504. <div id="dragScript" class="script-content">
  505. <div style="position:relative;">
  506. <button class="code-copy-btn" id="copyDragBtn">复制</button>
  507. <pre class="code-block"><code id="dragCodePreview"></code></pre>
  508. </div>
  509. </div>
  510. </div>
  511. -->
  512. </div>
  513. </div>
  514. <!-- 变量分析内容 -->
  515. <div id="varPanel" class="center-content" style="display: none;">
  516. <style>
  517. /* 变量分析tab美化,统一主站风格 */
  518. #varPanel .container {
  519. background: #fff;
  520. border-radius: 12px;
  521. box-shadow: 0 2px 12px rgba(52,152,219,0.07);
  522. padding: 24px 24px 18px 24px;
  523. border: 1.5px solid #e3eaf2;
  524. margin-top: 18px;
  525. width: 100%;
  526. max-width: none;
  527. }
  528. #varPanel .header {
  529. background: linear-gradient(135deg, #3498db, #764ba2);
  530. color: #fff;
  531. padding: 18px 20px;
  532. border-radius: 10px;
  533. margin-bottom: 20px;
  534. box-shadow: 0 2px 8px rgba(52,152,219,0.08);
  535. }
  536. #varPanel .card {
  537. border-radius: 10px;
  538. box-shadow: 0 2px 8px rgba(52,152,219,0.06);
  539. margin-bottom: 20px;
  540. border: none;
  541. }
  542. #varPanel .card-header {
  543. background-color: #f0f3fa;
  544. border-bottom: 1px solid #e3eaf2;
  545. font-weight: 600;
  546. padding: 13px 18px;
  547. border-radius: 10px 10px 0 0 !important;
  548. color: #2980b9;
  549. }
  550. #varPanel .table-container {
  551. max-height: 400px;
  552. overflow-y: auto;
  553. }
  554. #varPanel .variable-table {
  555. width: 100%;
  556. font-size: 14px;
  557. }
  558. #varPanel .variable-table th {
  559. background-color: #eaf6fb;
  560. position: sticky;
  561. top: 0;
  562. z-index: 10;
  563. }
  564. #varPanel .memory-map {
  565. height: 300px;
  566. background-color: #f8fafc;
  567. border-radius: 8px;
  568. padding: 15px;
  569. margin-bottom: 20px;
  570. position: relative;
  571. }
  572. #varPanel .legend {
  573. display: flex;
  574. flex-wrap: wrap;
  575. gap: 10px;
  576. margin-top: 15px;
  577. }
  578. #varPanel .legend-item {
  579. display: flex;
  580. align-items: center;
  581. font-size: 13px;
  582. }
  583. #varPanel .legend-color {
  584. width: 15px;
  585. height: 15px;
  586. border-radius: 3px;
  587. margin-right: 5px;
  588. border: 1px solid rgba(0,0,0,0.1);
  589. }
  590. #varPanel .status-bar {
  591. background-color: #eaf6fb;
  592. padding: 10px 15px;
  593. border-radius: 5px;
  594. margin-top: 20px;
  595. color: #2980b9;
  596. }
  597. #varPanel .detail-card {
  598. background-color: #f8f9fa;
  599. border-radius: 8px;
  600. padding: 15px;
  601. font-size: 14px;
  602. font-family: monospace;
  603. white-space: pre-wrap;
  604. max-height: 200px;
  605. overflow-y: auto;
  606. }
  607. #varPanel .file-info {
  608. background-color: #e7f4e4;
  609. padding: 10px;
  610. border-radius: 5px;
  611. margin-bottom: 15px;
  612. font-size: 13px;
  613. }
  614. #varPanel .btn-primary {
  615. background: #3498db;
  616. color: #fff;
  617. border: none;
  618. border-radius: 6px;
  619. font-weight: 600;
  620. font-size: 15px;
  621. padding: 8px 0;
  622. transition: background 0.2s;
  623. }
  624. #varPanel .btn-primary:disabled {
  625. background: #b3d3f7;
  626. color: #fff;
  627. }
  628. #varPanel .btn-primary:hover:not(:disabled) {
  629. background: #2980b9;
  630. }
  631. #varPanel .form-label {
  632. font-weight: 500;
  633. color: #2980b9;
  634. }
  635. #varPanel .progress-bar {
  636. background: linear-gradient(90deg, #3498db, #764ba2);
  637. }
  638. /* 变量分析页面新布局样式 */
  639. #varPanel .var-analysis-layout {
  640. display: grid;
  641. grid-template-columns: 1fr 1.5fr;
  642. gap: 25px;
  643. height: calc(100vh - 180px);
  644. width: 100%;
  645. max-width: none;
  646. margin: 0;
  647. padding: 0;
  648. }
  649. #varPanel .left-panel {
  650. display: flex;
  651. flex-direction: column;
  652. gap: 20px;
  653. min-width: 320px;
  654. flex: 1;
  655. }
  656. #varPanel .right-panel {
  657. display: flex;
  658. flex-direction: column;
  659. gap: 20px;
  660. min-width: 400px;
  661. flex: 1.5;
  662. align-items: stretch;
  663. }
  664. #varPanel .filter-section {
  665. background: #fff;
  666. border-radius: 12px;
  667. padding: 20px;
  668. box-shadow: 0 2px 8px rgba(52,152,219,0.06);
  669. border: 1.5px solid #e3eaf2;
  670. }
  671. #varPanel .filter-controls {
  672. display: flex;
  673. gap: 15px;
  674. align-items: center;
  675. margin-bottom: 15px;
  676. flex-wrap: wrap;
  677. }
  678. #varPanel .filter-controls select {
  679. padding: 8px 12px;
  680. border: 1px solid #d0d7de;
  681. border-radius: 6px;
  682. font-size: 14px;
  683. background: #f8fafc;
  684. }
  685. #varPanel .filter-controls input {
  686. padding: 8px 12px;
  687. border: 1px solid #d0d7de;
  688. border-radius: 6px;
  689. font-size: 14px;
  690. background: #f8fafc;
  691. width: 100%;
  692. min-width: 150px;
  693. }
  694. #varPanel .variable-table-container {
  695. background: #fff;
  696. border-radius: 12px;
  697. padding: 20px;
  698. box-shadow: 0 2px 8px rgba(52,152,219,0.06);
  699. border: 1.5px solid #e3eaf2;
  700. flex: 1;
  701. overflow: hidden;
  702. min-height: 350px;
  703. min-width: 0;
  704. }
  705. #varPanel .variable-table {
  706. width: 100%;
  707. border-collapse: collapse;
  708. }
  709. #varPanel .variable-table th,
  710. #varPanel .variable-table td {
  711. padding: 10px 12px;
  712. text-align: left;
  713. border-bottom: 1px solid #e3eaf2;
  714. }
  715. #varPanel .variable-table th:first-child,
  716. #varPanel .variable-table td:first-child {
  717. width: 60px;
  718. text-align: center;
  719. }
  720. #varPanel .variable-table th:nth-child(2),
  721. #varPanel .variable-table td:nth-child(2) {
  722. white-space: normal;
  723. word-break: break-word;
  724. max-width: 0;
  725. }
  726. #varPanel .variable-table th:nth-child(3),
  727. #varPanel .variable-table td:nth-child(3) {
  728. white-space: nowrap;
  729. overflow: hidden;
  730. text-overflow: ellipsis;
  731. }
  732. #varPanel .variable-table th:nth-child(4),
  733. #varPanel .variable-table td:nth-child(4) {
  734. white-space: nowrap;
  735. text-align: right;
  736. }
  737. #varPanel .variable-table th {
  738. background: linear-gradient(135deg, #eaf6fb 0%, #f0f8ff 100%);
  739. color: #2980b9;
  740. font-weight: 600;
  741. position: sticky;
  742. top: 0;
  743. z-index: 10;
  744. }
  745. #varPanel .variable-table tbody tr:hover {
  746. background: #f8fafc;
  747. }
  748. #varPanel .variable-table tbody tr.selected {
  749. background: #e3f2fd !important;
  750. border-left: 4px solid #2196f3;
  751. }
  752. #varPanel .variable-checkbox {
  753. margin-right: 8px;
  754. }
  755. /* 多变量图表容器样式 */
  756. .multi-chart-container {
  757. display: flex;
  758. flex-direction: column;
  759. gap: 20px;
  760. width: 100%;
  761. transition: min-height 0.3s ease-in-out;
  762. }
  763. .variable-chart {
  764. background: #fff;
  765. border-radius: 8px;
  766. box-shadow: 0 2px 8px rgba(52,152,219,0.06);
  767. padding: 20px;
  768. border: 1.5px solid #e3eaf2;
  769. }
  770. .variable-chart-header {
  771. display: flex;
  772. justify-content: space-between;
  773. align-items: center;
  774. margin-bottom: 15px;
  775. padding-bottom: 10px;
  776. border-bottom: 1px solid #e3eaf2;
  777. }
  778. .variable-chart-title {
  779. font-size: 16px;
  780. font-weight: 600;
  781. color: #2980b9;
  782. }
  783. .variable-chart-controls {
  784. display: flex;
  785. gap: 10px;
  786. }
  787. .variable-chart-controls button {
  788. padding: 4px 8px;
  789. font-size: 12px;
  790. border-radius: 4px;
  791. }
  792. #varPanel .hex-send-panel {
  793. background: #fff;
  794. border-radius: 12px;
  795. padding: 20px;
  796. box-shadow: 0 2px 8px rgba(52,152,219,0.06);
  797. border: 1.5px solid #e3eaf2;
  798. flex: 1;
  799. min-height: 250px;
  800. transition: min-height 0.3s ease-in-out;
  801. }
  802. /* 串口发送命令容器的特殊样式 */
  803. #varPanel .right-panel .hex-send-panel:first-child {
  804. flex-shrink: 0 !important;
  805. height: auto !important;
  806. min-height: 350px !important;
  807. max-height: none !important;
  808. }
  809. /* 确保HEX曲线图容器也有过渡效果 */
  810. .hex-send-panel {
  811. transition: min-height 0.3s ease-in-out;
  812. }
  813. #varPanel .hex-send-controls {
  814. display: flex;
  815. flex-wrap: wrap;
  816. gap: 20px;
  817. align-items: flex-start;
  818. margin-bottom: 15px;
  819. height: 100%;
  820. width: 100%;
  821. }
  822. #varPanel .hex-send-controls input {
  823. padding: 8px 12px;
  824. border: 1px solid #d0d7de;
  825. border-radius: 6px;
  826. font-size: 14px;
  827. background: #f8fafc;
  828. }
  829. #varPanel .hex-send-controls button {
  830. padding: 8px 16px;
  831. border: none;
  832. border-radius: 6px;
  833. font-size: 14px;
  834. cursor: pointer;
  835. transition: all 0.2s;
  836. }
  837. #varPanel .hex-send-controls .btn-success {
  838. background: #2ecc71;
  839. color: white;
  840. }
  841. #varPanel .hex-send-controls .btn-danger {
  842. background: #e74c3c;
  843. color: white;
  844. }
  845. #varPanel .hex-send-controls .btn-primary {
  846. background: #3498db;
  847. color: white;
  848. }
  849. #varPanel .selected-variables-info {
  850. background: #e8f5e8;
  851. border: 1px solid #c3e6c3;
  852. border-radius: 8px;
  853. padding: 15px;
  854. margin-bottom: 15px;
  855. }
  856. #varPanel .selected-variables-info h5 {
  857. margin: 0 0 10px 0;
  858. color: #2d5a2d;
  859. font-size: 16px;
  860. }
  861. #varPanel .selected-variables-list {
  862. display: flex;
  863. flex-wrap: wrap;
  864. gap: 8px;
  865. margin-bottom: 10px;
  866. }
  867. #varPanel .selected-variable-tag {
  868. background: #d4edda;
  869. color: #155724;
  870. padding: 4px 8px;
  871. border-radius: 4px;
  872. font-size: 12px;
  873. display: flex;
  874. align-items: center;
  875. gap: 5px;
  876. }
  877. #varPanel .selected-variable-tag .remove-btn {
  878. background: none;
  879. border: none;
  880. color: #155724;
  881. cursor: pointer;
  882. font-size: 14px;
  883. padding: 0;
  884. margin: 0;
  885. }
  886. #varPanel .total-size-info {
  887. background: #d1ecf1;
  888. border: 1px solid #bee5eb;
  889. border-radius: 6px;
  890. padding: 10px;
  891. color: #0c5460;
  892. font-weight: 500;
  893. }
  894. </style>
  895. <!-- 变量分析页面新布局 -->
  896. <div class="var-analysis-layout">
  897. <!-- 左侧面板:文件操作和变量列表 -->
  898. <div class="left-panel">
  899. <!-- 文件操作区域 -->
  900. <div class="filter-section">
  901. <h4 style="margin:0 0 15px 0;color:#2980b9;font-size:18px;">
  902. <span style="margin-right:8px;">📁</span>文件操作
  903. </h4>
  904. <div style="margin-bottom:15px;">
  905. <label for="axfFile" class="form-label" style="font-size:15px;margin-bottom:8px;">
  906. <span style="margin-right:6px;">📂</span>选择 .axf 文件
  907. </label>
  908. <input class="form-control" type="file" id="axfFile" accept=".axf"
  909. style="padding:12px;border-radius:8px;border:2px solid #e3eaf2;font-size:14px;">
  910. <div style="margin-top:6px;font-size:13px;color:#6c757d;">
  911. <span id="fileStatus">未选择文件</span>
  912. </div>
  913. </div>
  914. <button id="analyzeBtn" class="btn btn-primary" disabled
  915. style="padding:12px;font-size:15px;font-weight:600;border-radius:8px;width:100%;">
  916. <span style="margin-right:6px;">🔬</span>分析文件
  917. </button>
  918. <div id="fileInfo" class="file-info d-none" style="margin-top:15px;">
  919. <div style="margin-bottom:8px;"><strong>📄 文件信息:</strong> <span id="fileName">-</span></div>
  920. <div style="margin-bottom:8px;"><strong>📊 文件大小:</strong> <span id="fileSize">-</span></div>
  921. <div style="margin-bottom:8px;"><strong>🏷️ ELF 类型:</strong> <span id="elfType">-</span></div>
  922. <div><strong>🎯 入口点:</strong> <span id="entryPoint">-</span></div>
  923. </div>
  924. </div>
  925. <!-- 变量筛选区域 -->
  926. <div class="filter-section">
  927. <h4 style="margin:0 0 15px 0;color:#2980b9;font-size:18px;">
  928. <span style="margin-right:8px;">🔍</span>变量搜索
  929. </h4>
  930. <div class="filter-controls">
  931. <input type="text" id="searchFilter" placeholder="搜索变量名..." style="width:100%;" />
  932. </div>
  933. </div>
  934. <!-- 变量列表区域 -->
  935. <div class="variable-table-container">
  936. <h4 style="margin:0 0 15px 0;color:#2980b9;font-size:18px;">
  937. <span style="margin-right:8px;">📊</span>变量列表
  938. <span style="margin-left:12px;font-size:13px;font-weight:normal;opacity:0.8;">
  939. (勾选变量添加到串口发送区,最多9个)
  940. </span>
  941. </h4>
  942. <div class="progress-container mb-3">
  943. <div id="progressBar" class="progress d-none" style="height:8px;border-radius:6px;background:#f0f0f0;">
  944. <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
  945. style="width: 0%;background:linear-gradient(90deg, #3498db, #764ba2);border-radius:6px;"></div>
  946. </div>
  947. </div>
  948. <div class="table-container" id="varTableContainer" style="max-height:500px;overflow-y:auto;">
  949. <table class="variable-table">
  950. <thead>
  951. <tr>
  952. <th style="width:60px;"><input type="checkbox" id="selectAllVars" /></th>
  953. <th style="width:40%;">变量名</th>
  954. <th style="width:35%;">地址</th>
  955. <th style="width:25%;">大小</th>
  956. </tr>
  957. </thead>
  958. <tbody id="variableTableBody">
  959. <tr>
  960. <td colspan="4" class="text-center text-muted" style="padding:40px 16px;font-size:15px;">
  961. <div style="margin-bottom:8px;">📁</div>
  962. 请上传.axf文件并点击"分析文件"
  963. </td>
  964. </tr>
  965. </tbody>
  966. </table>
  967. </div>
  968. </div>
  969. </div>
  970. <!-- 右侧面板:串口发送和实时图表 -->
  971. <div class="right-panel">
  972. <!-- 串口发送区域 -->
  973. <div class="hex-send-panel" style="min-height: 350px !important; flex-shrink: 0 !important; height: auto !important; max-height: none !important;">
  974. <h4 style="margin:0 0 15px 0;color:#2980b9;font-size:18px;">
  975. <span style="margin-right:8px;">📡</span>串口发送命令
  976. </h4>
  977. <!-- 已选择变量信息 -->
  978. <div id="selectedVariablesInfo" class="selected-variables-info" style="display:none;">
  979. <h5>已选择的变量</h5>
  980. <div id="selectedVariablesList" class="selected-variables-list"></div>
  981. <div id="totalSizeInfo" class="total-size-info">
  982. 总大小: <span id="selectedTotalSize">0</span> 字节
  983. </div>
  984. </div>
  985. <!-- 串口发送控制 -->
  986. <div class="hex-send-controls">
  987. <div style="display:flex;flex-direction:column;gap:8px;align-items:stretch;min-width:120px;">
  988. <div style="display:flex;align-items:center;gap:8px;">
  989. <span style="color:#495057;font-size:13px;">采样率 (1-200Hz):</span>
  990. <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;">
  991. <span style="color:#495057;font-size:13px;">Hz</span>
  992. </div>
  993. <div style="display:flex;align-items:center;gap:8px;">
  994. <span style="color:#495057;font-size:13px;">显示范围 (1-30秒):</span>
  995. <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;">
  996. <span style="color:#495057;font-size:13px;">秒</span>
  997. </div>
  998. <button id="sendHexCmdBtn" class="btn btn-primary" style="width:100%;padding:10px;">
  999. <span style="margin-right:4px;">🚀</span>发送命令
  1000. </button>
  1001. </div>
  1002. </div>
  1003. </div>
  1004. <!-- 实时HEX曲线图 -->
  1005. <div class="hex-send-panel" style="flex:3;min-height:500px;flex-grow: 1;">
  1006. <h4 style="margin:0 0 15px 0;color:#2980b9;font-size:18px;">
  1007. <span style="margin-right:8px;">📈</span>实时HEX曲线图
  1008. </h4>
  1009. <!-- 多变量图表容器 -->
  1010. <div id="multiChartContainer" class="multi-chart-container">
  1011. <!-- 默认图表(当没有选择变量时显示) -->
  1012. <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>
  1013. </div>
  1014. <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;">
  1015. <button id="startChartBtn" class="btn btn-success">开始绘制</button>
  1016. <button id="clearChartBtn" class="btn btn-secondary">清空曲线</button>
  1017. <button id="stopChartBtn" class="btn btn-danger">终止绘制</button>
  1018. </div>
  1019. </div>
  1020. </div>
  1021. </div>
  1022. </div>
  1023. </div>
  1024. <!-- 右侧常驻监控栏 -->
  1025. <div class="monitor-panel">
  1026. <!-- 顶部连接控制区域 - 按键已移动到头部 -->
  1027. <!-- <div class="top-connection-controls">
  1028. <div class="connection-button-group">
  1029. <button id="connectBtn" class="btn btn-primary"><i class="fas fa-plug"></i> 连接串口</button>
  1030. <button id="disconnectBtn" class="btn btn-secondary" disabled><i class="fas fa-times"></i> 断开连接</button>
  1031. </div>
  1032. <div class="utility-button-group">
  1033. <button id="clearBtn" class="btn btn-warning"><i class="fas fa-trash"></i> 清空</button>
  1034. <button id="saveLogBtn" class="btn btn-info"><i class="fas fa-save"></i> 保存日志</button>
  1035. </div>
  1036. </div> -->
  1037. <!-- 隐藏的串口配置(保留功能但不在页面上显示) -->
  1038. <div class="hidden-config" style="display: none;">
  1039. <div class="config-row">
  1040. <label for="baudRate">波特率:</label>
  1041. <select id="baudRate">
  1042. <option value="9600">9600</option>
  1043. <option value="115200" selected>115200</option>
  1044. <option value="1000000">1000000</option>
  1045. <option value="custom">自定义</option>
  1046. </select>
  1047. <input type="number" id="customBaudRate" placeholder="自定义波特率" style="display:none;">
  1048. </div>
  1049. <div class="config-row">
  1050. <label for="dataBits">数据位:</label>
  1051. <select id="dataBits">
  1052. <option value="7">7</option>
  1053. <option value="8" selected>8</option>
  1054. </select>
  1055. <label for="parity">校验位:</label>
  1056. <select id="parity">
  1057. <option value="none" selected>无</option>
  1058. <option value="even">偶</option>
  1059. <option value="odd">奇</option>
  1060. </select>
  1061. <label for="stopBits">停止位:</label>
  1062. <select id="stopBits">
  1063. <option value="1" selected>1</option>
  1064. <option value="2">2</option>
  1065. </select>
  1066. </div>
  1067. <div class="display-options">
  1068. <label class="checkbox-label"><input type="checkbox" id="hexMode"> HEX模式</label>
  1069. <label class="checkbox-label"><input type="checkbox" id="showTimestamp" checked> 显示时间戳</label>
  1070. <label class="checkbox-label"><input type="checkbox" id="autoScroll" checked> 自动滚动</label>
  1071. <label class="checkbox-label"><input type="checkbox" id="enableBuffer" checked> 启用数据缓冲</label>
  1072. <label class="checkbox-label"><input type="checkbox" id="virtualTerminalMode" checked> 命令发送模式</label>
  1073. <label class="checkbox-label"><input type="checkbox" id="processAnsiSequences" checked> 处理ANSI序列</label>
  1074. <label class="checkbox-label"><input type="checkbox" id="debugMode"> 调试模式</label>
  1075. <label class="checkbox-label"><input type="checkbox" id="integrityCheck" checked> 数据完整性检查</label>
  1076. <label class="checkbox-label"><input type="checkbox" id="enableDataValidation" checked> 启用数据验证</label>
  1077. <div class="line-timeout-config">
  1078. <label for="lineTimeout">分行超时:</label>
  1079. <input type="number" id="lineTimeout" value="50" min="10" max="1000" step="10" class="timeout-input">
  1080. <span>ms</span>
  1081. </div>
  1082. </div>
  1083. </div>
  1084. <div class="monitor-log" style="margin-top:18px;">
  1085. <div id="terminal" class="terminal">
  1086. <div id="terminalOutput" class="terminal-output"></div>
  1087. <div id="terminalInput" class="terminal-input">
  1088. <span class="terminal-prompt">$ </span>
  1089. <input type="text" id="terminalInputField" class="terminal-input-field" placeholder="输入命令后点击发送或按Enter键发送..." disabled>
  1090. <button id="terminalSendBtn" class="btn btn-primary" style="margin-left: 8px; padding: 6px 12px; font-size: 12px;" disabled>发送</button>
  1091. </div>
  1092. </div>
  1093. </div>
  1094. </div>
  1095. </main>
  1096. </div>
  1097. <!-- 嵌入配置文件内容 -->
  1098. <script type="text/plain" id="embedded-config">
  1099. # MicroLink Web Serial Configuration Parameters
  1100. # 串口配置
  1101. port=COM3
  1102. baudrate=115200
  1103. databits=8
  1104. parity=N
  1105. stopbits=1
  1106. # RTT配置
  1107. rtt_addr=0x20000000
  1108. rtt_size=0x4000
  1109. rtt_channel=0
  1110. # SystemView配置
  1111. systemview_addr=0x20000000
  1112. systemview_size=0x4000
  1113. systemview_channel=1
  1114. # FLM配置
  1115. flm_path=STM32/STM32F4xx_1024.FLM.o
  1116. base_addr=0x08000000
  1117. ram_addr=0x20000000
  1118. # 下载配置
  1119. bin_file_path=firmware.bin
  1120. bin_addr=0x08000000
  1121. # 自定义命令
  1122. custom_commands=RTTView.start(0x20000000,1024,0);SystemView.start(0x20000000,1024,1);load.offline()
  1123. </script>
  1124. <script src="assets/script.js"></script>
  1125. <script>
  1126. document.addEventListener('DOMContentLoaded', function() {
  1127. // 右侧功能区切换逻辑(如有)
  1128. const panelSelect = document.getElementById('panelSelect');
  1129. const apiPanel = document.getElementById('apiPanel');
  1130. const flmPanel = document.getElementById('flmPanel');
  1131. const scriptPanel = document.getElementById('scriptPanel');
  1132. const varPanel = document.getElementById('varPanel');
  1133. if (panelSelect && apiPanel && flmPanel && scriptPanel && varPanel) {
  1134. function updatePanelDisplay() {
  1135. apiPanel.style.display = panelSelect.value === 'apiPanel' ? '' : 'none';
  1136. flmPanel.style.display = panelSelect.value === 'flmPanel' ? '' : 'none';
  1137. scriptPanel.style.display = panelSelect.value === 'scriptPanel' ? '' : 'none';
  1138. varPanel.style.display = panelSelect.value === 'varPanel' ? '' : 'none';
  1139. }
  1140. panelSelect.addEventListener('change', updatePanelDisplay);
  1141. updatePanelDisplay();
  1142. }
  1143. // ========== FLM ELF解析器核心功能 ==========
  1144. const flmFileInput = document.getElementById('flmFile');
  1145. const convertBtn = document.getElementById('convertBtn');
  1146. const log = document.getElementById('log');
  1147. const downloadLink = document.getElementById('downloadLink');
  1148. let flmArrayBuffer = null;
  1149. let flmFileName = '';
  1150. let convertedBlob = null;
  1151. function logMsg(msg) {
  1152. // 输出到主终端
  1153. appendToTerminalOutput(`<div class='log-prefix-flm'>[FLM] ${msg}</div>`);
  1154. // 同时输出到本地log元素(如果存在)
  1155. if (log) {
  1156. log.textContent += msg + '\n';
  1157. log.scrollTop = log.scrollHeight;
  1158. }
  1159. }
  1160. function parseELFHeader(buffer) {
  1161. const dv = new DataView(buffer);
  1162. if (dv.getUint8(0) !== 0x7f || dv.getUint8(1) !== 0x45 || dv.getUint8(2) !== 0x4c || dv.getUint8(3) !== 0x46) {
  1163. logMsg('不是有效的ELF文件!');
  1164. return null;
  1165. }
  1166. logMsg('ELF魔数校验通过');
  1167. const e_shoff = dv.getUint32(32, true);
  1168. const e_shentsize = dv.getUint16(46, true);
  1169. const e_shnum = dv.getUint16(48, true);
  1170. const e_shstrndx = dv.getUint16(50, true);
  1171. logMsg(`节头表偏移: 0x${e_shoff.toString(16)}, 节数量: ${e_shnum}, 节头大小: ${e_shentsize}, 节名字符串表索引: ${e_shstrndx}`);
  1172. return { e_shoff, e_shentsize, e_shnum, e_shstrndx };
  1173. }
  1174. function parseSectionHeaders(buffer, elfHeader) {
  1175. const dv = new DataView(buffer);
  1176. const sections = [];
  1177. for (let i = 0; i < elfHeader.e_shnum; i++) {
  1178. const offset = elfHeader.e_shoff + i * elfHeader.e_shentsize;
  1179. sections.push({
  1180. sh_name: dv.getUint32(offset + 0, true),
  1181. sh_type: dv.getUint32(offset + 4, true),
  1182. sh_flags: dv.getUint32(offset + 8, true),
  1183. sh_addr: dv.getUint32(offset + 12, true),
  1184. sh_offset: dv.getUint32(offset + 16, true),
  1185. sh_size: dv.getUint32(offset + 20, true),
  1186. sh_link: dv.getUint32(offset + 24, true),
  1187. sh_info: dv.getUint32(offset + 28, true),
  1188. sh_addralign: dv.getUint32(offset + 32, true),
  1189. sh_entsize: dv.getUint32(offset + 36, true)
  1190. });
  1191. }
  1192. return sections;
  1193. }
  1194. function getSectionNames(buffer, sections, shstrndx) {
  1195. const dv = new DataView(buffer);
  1196. const shstrtab = sections[shstrndx];
  1197. const names = [];
  1198. for (let i = 0; i < sections.length; i++) {
  1199. const nameOffset = shstrtab.sh_offset + sections[i].sh_name;
  1200. let name = '';
  1201. for (let j = 0; j < 64; j++) {
  1202. const c = dv.getUint8(nameOffset + j);
  1203. if (c === 0) break;
  1204. name += String.fromCharCode(c);
  1205. }
  1206. names.push(name);
  1207. }
  1208. return names;
  1209. }
  1210. function findSectionByName(sectionNames, name) {
  1211. for (let i = 0; i < sectionNames.length; i++) {
  1212. if (sectionNames[i] === name) return i;
  1213. }
  1214. return -1;
  1215. }
  1216. function extractSectionData(buffer, section) {
  1217. return new Uint8Array(buffer, section.sh_offset, section.sh_size);
  1218. }
  1219. function findSymbolAddresses(buffer, sections, sectionNames, symbolNames) {
  1220. const symtabIdx = findSectionByName(sectionNames, '.symtab');
  1221. const strtabIdx = findSectionByName(sectionNames, '.strtab');
  1222. if (symtabIdx === -1 || strtabIdx === -1) {
  1223. logMsg('未找到 .symtab 或 .strtab 节!');
  1224. return null;
  1225. }
  1226. const symtab = sections[symtabIdx];
  1227. const strtab = sections[strtabIdx];
  1228. const dv = new DataView(buffer);
  1229. const symbolCount = symtab.sh_size / symtab.sh_entsize;
  1230. const addresses = {};
  1231. for (let i = 0; i < symbolCount; i++) {
  1232. const symOffset = symtab.sh_offset + i * symtab.sh_entsize;
  1233. const st_name = dv.getUint32(symOffset + 0, true);
  1234. const st_value = dv.getUint32(symOffset + 4, true);
  1235. let name = '';
  1236. for (let j = 0; j < 64; j++) {
  1237. const c = dv.getUint8(strtab.sh_offset + st_name + j);
  1238. if (c === 0) break;
  1239. name += String.fromCharCode(c);
  1240. }
  1241. if (symbolNames.includes(name)) {
  1242. addresses[name] = st_value;
  1243. }
  1244. }
  1245. return addresses;
  1246. }
  1247. function extractFlashInfoFromELF(buffer, sections, sectionNames) {
  1248. logMsg('查找DevDscr节:');
  1249. for (let i = 0; i < sections.length; i++) {
  1250. const section = sections[i];
  1251. const sectionName = sectionNames[i];
  1252. logMsg(` 节${i}: ${sectionName} (偏移: 0x${section.sh_offset.toString(16)}, 大小: ${section.sh_size})`);
  1253. if (sectionName === 'DevDscr') {
  1254. logMsg(`找到DevDscr节: 偏移 0x${section.sh_offset.toString(16)}, 大小 ${section.sh_size}`);
  1255. const devDscrData = extractSectionData(buffer, section);
  1256. const flashInfoSize = 288;
  1257. if (section.sh_size >= flashInfoSize) {
  1258. const flashInfo = new ArrayBuffer(flashInfoSize);
  1259. const flashDv = new DataView(flashInfo);
  1260. const devDv = new DataView(devDscrData.buffer, devDscrData.byteOffset, devDscrData.byteLength);
  1261. for (let i = 0; i < flashInfoSize; i++) {
  1262. flashDv.setUint8(i, devDv.getUint8(i));
  1263. }
  1264. const vers = flashDv.getUint16(0, true);
  1265. let devName = '';
  1266. for (let i = 0; i < 128; i++) {
  1267. const c = flashDv.getUint8(2 + i);
  1268. if (c === 0) break;
  1269. devName += String.fromCharCode(c);
  1270. }
  1271. if (vers !== 0 && devName.length > 0) {
  1272. logMsg('Flash信息验证通过');
  1273. logMsg('成功从DevDscr节提取Flash信息');
  1274. return new Uint8Array(flashInfo);
  1275. } else {
  1276. logMsg('DevDscr节中的数据验证失败');
  1277. }
  1278. } else {
  1279. logMsg(`DevDscr节大小不足: ${section.sh_size} < ${flashInfoSize}`);
  1280. }
  1281. }
  1282. }
  1283. logMsg('未找到有效的Flash信息数据');
  1284. return null;
  1285. }
  1286. function createAlgorithmBlob(buffer, sections, sectionNames) {
  1287. logMsg('创建算法程序blob:');
  1288. logMsg('查找PrgCode节:');
  1289. for (let i = 0; i < sections.length; i++) {
  1290. const section = sections[i];
  1291. const sectionName = sectionNames[i];
  1292. logMsg(` 节${i}: ${sectionName} (偏移: 0x${section.sh_offset.toString(16)}, 大小: ${section.sh_size})`);
  1293. if (sectionName === 'PrgCode') {
  1294. logMsg(`找到PrgCode节: 偏移 0x${section.sh_offset.toString(16)}, 大小 ${section.sh_size}`);
  1295. const prgCodeData = extractSectionData(buffer, section);
  1296. logMsg(`PrgCode节内容已提取,大小: ${prgCodeData.length} 字节`);
  1297. let hexStr = '';
  1298. for (let j = 0; j < 32 && j < prgCodeData.length; j++) {
  1299. hexStr += prgCodeData[j].toString(16).padStart(2, '0');
  1300. }
  1301. logMsg(`PrgCode节前32字节: ${hexStr}`);
  1302. return prgCodeData;
  1303. }
  1304. }
  1305. logMsg('未找到PrgCode节');
  1306. return null;
  1307. }
  1308. function createFinalBlobWithFlashInfo(programTarget, algorithmBlob, algorithmBlobSize, flashInfo) {
  1309. const ramSize = 8 * 4;
  1310. const finalBlobSize = 64 + ramSize + algorithmBlobSize + flashInfo.length;
  1311. logMsg(`最终blob创建成功,总大小: ${finalBlobSize} 字节 (结构体: 64 + RAM数组: ${ramSize} + 算法: ${algorithmBlobSize} + Flash信息: ${flashInfo.length})`);
  1312. const finalBlob = new Uint8Array(finalBlobSize);
  1313. let offset = 0;
  1314. finalBlob.set(programTarget, offset); offset += programTarget.length;
  1315. const ramArray = new Uint8Array(32);
  1316. const ramDv = new DataView(ramArray.buffer);
  1317. ramDv.setUint32(0, 0xE00ABE00, true);
  1318. ramDv.setUint32(4, 0x062D780D, true);
  1319. ramDv.setUint32(8, 0x24084068, true);
  1320. ramDv.setUint32(12, 0xD3000040, true);
  1321. ramDv.setUint32(16, 0x1E644058, true);
  1322. ramDv.setUint32(20, 0x1C49D1FA, true);
  1323. ramDv.setUint32(24, 0x2A001E52, true);
  1324. ramDv.setUint32(28, 0x4770D1F2, true);
  1325. finalBlob.set(ramArray, offset); offset += ramArray.length;
  1326. if (algorithmBlob && algorithmBlobSize > 0) {
  1327. finalBlob.set(algorithmBlob, offset); offset += algorithmBlobSize;
  1328. }
  1329. finalBlob.set(flashInfo, offset);
  1330. return finalBlob;
  1331. }
  1332. flmFileInput.addEventListener('change', (e) => {
  1333. const file = e.target.files[0];
  1334. if (!file) {
  1335. convertBtn.disabled = true;
  1336. logMsg('未选择文件');
  1337. return;
  1338. }
  1339. flmFileName = file.name.replace(/\.[^/.]+$/, "");
  1340. const reader = new FileReader();
  1341. reader.onload = function(evt) {
  1342. flmArrayBuffer = evt.target.result;
  1343. convertBtn.disabled = false;
  1344. log.textContent = '';
  1345. logMsg(`已加载文件: ${file.name} (大小: ${file.size} 字节)`);
  1346. };
  1347. reader.onerror = function() {
  1348. logMsg('文件读取失败');
  1349. convertBtn.disabled = true;
  1350. };
  1351. reader.readAsArrayBuffer(file);
  1352. });
  1353. convertBtn.addEventListener('click', async () => {
  1354. if (!flmArrayBuffer) {
  1355. logMsg('请先选择FLM文件');
  1356. return;
  1357. }
  1358. logMsg('开始解析FLM文件...');
  1359. const elfHeader = parseELFHeader(flmArrayBuffer);
  1360. if (!elfHeader) return;
  1361. const sections = parseSectionHeaders(flmArrayBuffer, elfHeader);
  1362. const sectionNames = getSectionNames(flmArrayBuffer, sections, elfHeader.e_shstrndx);
  1363. logMsg('查找算法函数符号:');
  1364. const symbolNames = ['Init','UnInit','EraseChip','EraseSector','ProgramPage','Read','Verify'];
  1365. const symbolAddrs = findSymbolAddresses(flmArrayBuffer, sections, sectionNames, symbolNames);
  1366. if (!symbolAddrs) return;
  1367. for (const name of symbolNames) {
  1368. const addr = symbolAddrs[name] || 0;
  1369. if (addr !== 0) {
  1370. logMsg(` ${name}: 0x${addr.toString(16).padStart(8,'0')}`);
  1371. } else {
  1372. logMsg(` ${name}: 未找到`);
  1373. }
  1374. }
  1375. logMsg('从ELF文件中提取Flash信息:');
  1376. const flashInfo = extractFlashInfoFromELF(flmArrayBuffer, sections, sectionNames);
  1377. let flash_info;
  1378. if (!flashInfo) {
  1379. logMsg('警告: 无法从ELF文件中提取Flash信息,使用默认值');
  1380. const defaultFlashInfo = new ArrayBuffer(288);
  1381. const dv = new DataView(defaultFlashInfo);
  1382. dv.setUint16(0, 0x0101, true);
  1383. const devName = "STM32F7xx 1024KB Flash (默认)";
  1384. for (let i = 0; i < 128; i++) {
  1385. dv.setUint8(2 + i, i < devName.length ? devName.charCodeAt(i) : 0);
  1386. }
  1387. dv.setUint16(130, 1, true);
  1388. dv.setUint32(132, 0x08000000, true);
  1389. dv.setUint32(136, 1024 * 1024, true);
  1390. dv.setUint32(140, 256, true);
  1391. dv.setUint32(144, 0, true);
  1392. dv.setUint8(148, 0xFF);
  1393. dv.setUint32(152, 1000, true);
  1394. dv.setUint32(156, 5000, true);
  1395. flash_info = new Uint8Array(defaultFlashInfo);
  1396. } else {
  1397. flash_info = flashInfo;
  1398. }
  1399. const algorithmBlob = createAlgorithmBlob(flmArrayBuffer, sections, sectionNames);
  1400. if (!algorithmBlob) {
  1401. logMsg('算法程序blob创建失败');
  1402. return;
  1403. }
  1404. const algorithm_blob = algorithmBlob;
  1405. const algorithm_blob_size = algorithmBlob.length;
  1406. logMsg(`算法程序blob创建成功,大小: ${algorithm_blob_size} 字节`);
  1407. logMsg('初始化program_target_t结构体:');
  1408. const program_target = {};
  1409. program_target.init = (symbolAddrs['Init'] || 0) + 0x20;
  1410. program_target.uninit = (symbolAddrs['UnInit'] || 0) + 0x20;
  1411. program_target.erase_chip = (symbolAddrs['EraseChip'] || 0) + 0x20;
  1412. program_target.erase_sector = (symbolAddrs['EraseSector'] || 0) + 0x20;
  1413. program_target.program_page = (symbolAddrs['ProgramPage'] || 0) + 0x20;
  1414. program_target.read = (symbolAddrs['Read'] !== undefined) ? symbolAddrs['Read'] + 0x20 : 0;
  1415. program_target.verify = (symbolAddrs['Verify'] !== undefined) ? symbolAddrs['Verify'] + 0x20 : 0;
  1416. program_target.breakpoint = 1;
  1417. program_target.static_base = algorithm_blob_size + 0x20 - 4;
  1418. program_target.stack_pointer = algorithm_blob_size + 0x20 + 0x1000;
  1419. program_target.program_buffer = algorithm_blob_size + 0x20 + 0x1800;
  1420. program_target.algo_start = 0;
  1421. program_target.algo_size = algorithm_blob_size + 0x20;
  1422. program_target.algo_blob = 0;
  1423. program_target.program_buffer_size = 0x1000;
  1424. program_target.algo_flags = 0;
  1425. logMsg(` init: 0x${program_target.init.toString(16).padStart(8,'0')}`);
  1426. logMsg(` uninit: 0x${program_target.uninit.toString(16).padStart(8,'0')}`);
  1427. logMsg(` erase_chip: 0x${program_target.erase_chip.toString(16).padStart(8,'0')}`);
  1428. logMsg(` erase_sector: 0x${program_target.erase_sector.toString(16).padStart(8,'0')}`);
  1429. logMsg(` program_page: 0x${program_target.program_page.toString(16).padStart(8,'0')}`);
  1430. logMsg(` read: 0x${program_target.read.toString(16).padStart(8,'0')}`);
  1431. logMsg(` verify: 0x${program_target.verify.toString(16).padStart(8,'0')}`);
  1432. logMsg(` sys_call_s.breakpoint: 0x${program_target.breakpoint.toString(16).padStart(8,'0')}`);
  1433. logMsg(` sys_call_s.static_base: 0x${program_target.static_base.toString(16).padStart(8,'0')}`);
  1434. logMsg(` sys_call_s.stack_pointer: 0x${program_target.stack_pointer.toString(16).padStart(8,'0')}`);
  1435. logMsg(` program_buffer: 0x${program_target.program_buffer.toString(16).padStart(8,'0')}`);
  1436. logMsg(` algo_start: 0x${program_target.algo_start.toString(16).padStart(8,'0')}`);
  1437. logMsg(` algo_size: 0x${program_target.algo_size.toString(16).padStart(8,'0')}`);
  1438. logMsg(` program_buffer_size: 0x${program_target.program_buffer_size.toString(16).padStart(8,'0')}`);
  1439. logMsg(` algo_flags: 0x${program_target.algo_flags.toString(16).padStart(8,'0')}`);
  1440. const programTargetBuffer = new ArrayBuffer(64);
  1441. const ptDv = new DataView(programTargetBuffer);
  1442. let ptOffset = 0;
  1443. ptDv.setUint32(ptOffset, program_target.init, true); ptOffset += 4;
  1444. ptDv.setUint32(ptOffset, program_target.uninit, true); ptOffset += 4;
  1445. ptDv.setUint32(ptOffset, program_target.erase_chip, true); ptOffset += 4;
  1446. ptDv.setUint32(ptOffset, program_target.erase_sector, true); ptOffset += 4;
  1447. ptDv.setUint32(ptOffset, program_target.program_page, true); ptOffset += 4;
  1448. ptDv.setUint32(ptOffset, program_target.read, true); ptOffset += 4;
  1449. ptDv.setUint32(ptOffset, program_target.verify, true); ptOffset += 4;
  1450. ptDv.setUint32(ptOffset, program_target.breakpoint, true); ptOffset += 4;
  1451. ptDv.setUint32(ptOffset, program_target.static_base, true); ptOffset += 4;
  1452. ptDv.setUint32(ptOffset, program_target.stack_pointer, true); ptOffset += 4;
  1453. ptDv.setUint32(ptOffset, program_target.program_buffer, true); ptOffset += 4;
  1454. ptDv.setUint32(ptOffset, program_target.algo_start, true); ptOffset += 4;
  1455. ptDv.setUint32(ptOffset, program_target.algo_size, true); ptOffset += 4;
  1456. ptDv.setUint32(ptOffset, program_target.algo_blob, true); ptOffset += 4;
  1457. ptDv.setUint32(ptOffset, program_target.program_buffer_size, true); ptOffset += 4;
  1458. ptDv.setUint32(ptOffset, program_target.algo_flags, true); ptOffset += 4;
  1459. const programTarget = new Uint8Array(programTargetBuffer);
  1460. const finalBlob = createFinalBlobWithFlashInfo(programTarget, algorithm_blob, algorithm_blob_size, flash_info);
  1461. const blob = new Blob([finalBlob], {type: 'application/octet-stream'});
  1462. convertedBlob = blob;
  1463. const fileName = `${flmFileName}.FLM.o`;
  1464. downloadLink.href = URL.createObjectURL(blob);
  1465. downloadLink.download = fileName;
  1466. downloadLink.style.display = '';
  1467. downloadLink.textContent = '下载 .o 文件';
  1468. // 自动复制文件名到剪贴板
  1469. try {
  1470. await navigator.clipboard.writeText(fileName);
  1471. logMsg(`✅ 文件名 "${fileName}" 已自动复制到剪贴板`);
  1472. } catch (err) {
  1473. logMsg(`⚠️ 文件名复制失败: ${err.message}`);
  1474. }
  1475. logMsg(`最终blob已保存到 ${fileName} (包含program_target_t结构体和Flash信息)`);
  1476. logMsg('ELF文件解析完成');
  1477. });
  1478. downloadLink.addEventListener('click', () => {
  1479. setTimeout(() => {
  1480. URL.revokeObjectURL(downloadLink.href);
  1481. }, 1000);
  1482. });
  1483. // ========== FLM ELF解析器 YMODEM发送功能 ==========
  1484. const flmYmodemSendBtn = document.getElementById('flmYmodemSendBtn');
  1485. const flmYmodemProgress = document.getElementById('flmYmodemProgress');
  1486. // const flmPanel = document.getElementById('flmPanel'); // 移除重复声明
  1487. function flmYlog(msg, color) {
  1488. // flmYmodemLog.innerHTML += `<div style='color:${color||'#0f0'}'>${msg}</div>`; // 移除此行
  1489. // flmYmodemLog.scrollTop = flmYmodemLog.scrollHeight; // 移除此行
  1490. logMsg(`[YMODEM] ${msg}`); // 同时输出到主日志区
  1491. }
  1492. function flmYlogClear() {
  1493. // flmYmodemLog.innerHTML = ''; // 移除此行
  1494. }
  1495. function setFlmYmodemUIBusy(busy) {
  1496. flmYmodemSendBtn.disabled = busy || !convertedBlob || !isSerialConnected();
  1497. flmYmodemProgress.style.display = busy ? '' : 'none';
  1498. }
  1499. function updateFlmYmodemBtn() {
  1500. flmYmodemSendBtn.disabled = !convertedBlob || !isSerialConnected();
  1501. }
  1502. function isSerialConnected() {
  1503. return window.microLinkTerminal && window.microLinkTerminal.isConnected && window.microLinkTerminal.port;
  1504. }
  1505. // 生成.o文件后使发送按钮可用
  1506. convertBtn.addEventListener('click', function() {
  1507. updateFlmYmodemBtn();
  1508. });
  1509. // 发送按钮事件
  1510. flmYmodemSendBtn.addEventListener('click', async () => {
  1511. if (!convertedBlob) { flmYlog('请先生成.o文件', '#ff0'); return; }
  1512. if (!isSerialConnected()) {
  1513. flmYlog('请先连接串口', '#f66'); return;
  1514. }
  1515. const ymodemMode = 'simple'; // 固定为简化模式
  1516. flmYlogClear();
  1517. setFlmYmodemUIBusy(true);
  1518. flmYmodemProgress.value = 0;
  1519. flmYmodemProgress.max = 100;
  1520. const port = window.microLinkTerminal.port;
  1521. try {
  1522. if (window.microLinkTerminal.reader) {
  1523. try { window.microLinkTerminal.reader.cancel(); } catch(e){}
  1524. try { window.microLinkTerminal.reader.releaseLock(); } catch(e){}
  1525. window.microLinkTerminal.reader = null;
  1526. flmYlog('已暂停主串口监听,准备YMODEM发送...', '#0ff');
  1527. }
  1528. // 只在最开始发送一次ym.receive()
  1529. const writer0 = port.writable.getWriter();
  1530. await writer0.write(new TextEncoder().encode('ym.receive()\r\n'));
  1531. writer0.releaseLock();
  1532. flmYlog('已发送 ym.receive(),正在等待设备C信号(最多10秒)...', '#0ff');
  1533. // 强制监听串口满10秒,收到C或Ready立即进入下一步
  1534. let started = false;
  1535. let received = '';
  1536. const startTime = Date.now();
  1537. while (!started && (Date.now() - startTime) < 10000) {
  1538. let reader = port.readable.getReader();
  1539. try {
  1540. const { value, done } = await Promise.race([
  1541. reader.read(),
  1542. new Promise(resolve => setTimeout(() => resolve({value: null, done: false}), 200))
  1543. ]);
  1544. if (value) {
  1545. const text = new TextDecoder().decode(value);
  1546. flmYlog('收到内容: ' + text, '#888');
  1547. received += text;
  1548. if (text.includes('C') || text.includes('Ready')) {
  1549. started = true;
  1550. flmYlog('收到设备C信号,准备发送YMODEM文件...', '#0f0');
  1551. break;
  1552. }
  1553. }
  1554. } finally {
  1555. reader.releaseLock();
  1556. }
  1557. await new Promise(r => setTimeout(r, 100));
  1558. }
  1559. if (!started) {
  1560. flmYlog('10秒内未收到设备C信号,YMODEM发送中止', '#f66');
  1561. return;
  1562. }
  1563. // 根据选择的模式使用不同的YMODEM函数
  1564. const arrayBuffer = await convertedBlob.arrayBuffer();
  1565. const uint8Array = new Uint8Array(arrayBuffer);
  1566. const fileName = flmFileName ? (flmFileName + '.FLM.o') : 'firmware.FLM.o';
  1567. // ======= ELF tab YMODEM发送按钮事件,完全镜像PYTHON tab逻辑 =======
  1568. flmYlog('使用简化YMODEM模式(极保守配置)', '#0ff');
  1569. const simpleOptions = {
  1570. retryDelay: 1000, // 1秒包间延时
  1571. maxRetries: 30, // 30次重试
  1572. packetTimeout: 30000, // 30秒包超时
  1573. restartDelay: 5000 // 5秒重启延时
  1574. };
  1575. await window.ymodemSendFileViaSerialSimple(
  1576. uint8Array,
  1577. fileName,
  1578. 120000,
  1579. progress => { flmYmodemProgress.value = progress; },
  1580. msg => flmYlog(msg),
  1581. {
  1582. ...simpleOptions,
  1583. buildHeaderPacket: window.buildHeaderPacketFlmYmodem
  1584. }
  1585. );
  1586. // ======= 屏蔽原有ELF tab YMODEM流程 =======
  1587. /*
  1588. if (ymodemMode === 'simple') {
  1589. // 使用简化模式
  1590. flmYlog('使用简化YMODEM模式(极保守配置)', '#0ff');
  1591. const simpleOptions = {
  1592. retryDelay: 1000, // 1秒包间延时
  1593. maxRetries: 30, // 30次重试
  1594. packetTimeout: 30000, // 30秒包超时
  1595. restartDelay: 5000 // 5秒重启延时
  1596. };
  1597. await window.ymodemSendFileViaSerialSimple(
  1598. uint8Array,
  1599. fileName,
  1600. 120000,
  1601. progress => { flmYmodemProgress.value = progress; },
  1602. msg => flmYlog(msg),
  1603. {
  1604. ...simpleOptions,
  1605. buildHeaderPacket: window.buildHeaderPacketFlmYmodem
  1606. }
  1607. );
  1608. } else {
  1609. // 使用标准模式
  1610. flmYlog('使用标准YMODEM模式', '#0ff');
  1611. const ymodemOptions = {
  1612. retryDelay: 800, // 大幅增加包间延时,给设备端更多处理时间
  1613. maxRetries: 25, // 增加重试次数
  1614. packetTimeout: 20000, // 增加包超时时间到20秒
  1615. restartDelay: 3000 // 重启传输前的延时
  1616. };
  1617. flmYlog(`使用YMODEM配置: 数据包大小128字节(SOH), 延时${ymodemOptions.retryDelay}ms, 重试${ymodemOptions.maxRetries}次, 超时${ymodemOptions.packetTimeout}ms`, '#0ff');
  1618. await window.ymodemSendFileViaSerial(
  1619. uint8Array,
  1620. fileName,
  1621. 120000,
  1622. progress => { flmYmodemProgress.value = progress; },
  1623. msg => flmYlog(msg),
  1624. ymodemOptions
  1625. );
  1626. }
  1627. flmYlog('✅ 文件发送完成', '#0f0');
  1628. */
  1629. } catch (e) {
  1630. // 静默失败,不输出任何报错日志
  1631. return;
  1632. } finally {
  1633. flmYlog('congratulations!', '#888');
  1634. setFlmYmodemUIBusy(false);
  1635. }
  1636. });
  1637. // .o文件生成后自动使按钮可用
  1638. if (typeof updateFlmYmodemBtn === 'function') updateFlmYmodemBtn();
  1639. // 串口连接状态变化时自动刷新按钮
  1640. // 页面切换到FLM解析器时也刷新一次
  1641. if (panelSelect) {
  1642. panelSelect.addEventListener('change', function() {
  1643. if (panelSelect.value === 'flmPanel') {
  1644. updateFlmYmodemBtn();
  1645. }
  1646. });
  1647. }
  1648. // ========== Python脚本生成器核心功能 ==========
  1649. const swdClockSpeedMap = { '10M': '10000000', '5M': '5000000', '2M': '2000000', '1M': '1000000', '500K': '500000', '200K': '200000', '100K': '100000', '50K': '50000', '20K': '20000', '10K': '10000', '5K': '5000' };
  1650. const customFlmInput = document.getElementById('customFlm');
  1651. const address1Input = document.getElementById('address1');
  1652. const address2Input = document.getElementById('address2');
  1653. const swdClockSpeedSelect = document.getElementById('swdClockSpeed');
  1654. const codePreview = document.getElementById('codePreview');
  1655. const dragCodePreview = document.getElementById('dragCodePreview');
  1656. // 删除下载按钮相关代码
  1657. // 全局config对象,供script.js使用
  1658. window.config = {
  1659. flmFile: 'custom_flm.FLM.o',
  1660. address1: '0X08000000',
  1661. address2: '0x20000000',
  1662. swdClockSpeed: '10M',
  1663. files: [
  1664. {
  1665. fileName: 'boot.bin',
  1666. address: '0x08000000',
  1667. algorithm: 'STM32F7x_1024.FLM.o'
  1668. },
  1669. {
  1670. fileName: 'rtthread.bin',
  1671. address: '0x08020000',
  1672. algorithm: 'STM32F7x_1024.FLM.o'
  1673. },
  1674. {
  1675. fileName: 'HZK.bin',
  1676. address: '0x90000000',
  1677. algorithm: 'STM32F767_W25QXX.FLM.o'
  1678. }
  1679. ]
  1680. };
  1681. const config = window.config;
  1682. function switchScriptTab(scriptType) {
  1683. const tabs = document.querySelectorAll('.script-tab');
  1684. tabs.forEach(tab => tab.classList.remove('active'));
  1685. const contents = document.querySelectorAll('.script-content');
  1686. contents.forEach(content => content.classList.remove('active'));
  1687. if (scriptType === 'offline') {
  1688. document.querySelector('.script-tab:first-child').classList.add('active');
  1689. document.getElementById('offlineScript').classList.add('active');
  1690. } else {
  1691. document.querySelector('.script-tab:last-child').classList.add('active');
  1692. document.getElementById('dragScript').classList.add('active');
  1693. }
  1694. }
  1695. function updateCodePreview() {
  1696. const flmFile = config.flmFile;
  1697. const pythonSwdSpeed = swdClockSpeedMap[config.swdClockSpeed] || '10000000';
  1698. // 生成多文件烧录代码
  1699. 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()`;
  1700. // 按算法分组文件
  1701. const algorithmGroups = {};
  1702. config.files.forEach(file => {
  1703. if (file.algorithm && file.fileName && file.address) {
  1704. if (!algorithmGroups[file.algorithm]) {
  1705. algorithmGroups[file.algorithm] = [];
  1706. }
  1707. algorithmGroups[file.algorithm].push(file);
  1708. }
  1709. });
  1710. // 为每个算法生成加载和烧录代码
  1711. Object.keys(algorithmGroups).forEach((algorithm, index) => {
  1712. const files = algorithmGroups[algorithm];
  1713. if (files.length > 0) {
  1714. // 加载算法
  1715. offlineCode += `\n# 加载 ${algorithm} 下载算法文件\nresult = ReadFlm.load("FLM/${algorithm}", ${config.address1}, ${config.address2})\nif result != 0:\n return`;
  1716. // 烧录该算法下的所有文件
  1717. files.forEach(file => {
  1718. offlineCode += `\n\n# 烧写 ${file.fileName}\nresult = load.bin("${file.fileName}", ${file.address})\nif result != 0:\n return`;
  1719. });
  1720. }
  1721. });
  1722. offlineCode += `\n\n# 蜂鸣器响一声,表示烧写完成\nbuzzer.enable()\nbuzzer.high()\ntime.sleep_ms(500)\nbuzzer.low()\ntime.sleep_ms(500)`;
  1723. const dragCode = `import FLMConfig\ncmd.set_swd_clock(${pythonSwdSpeed})\nReadFlm = FLMConfig.ReadFlm()\nres1 = ReadFlm.load(\"FLM/${flmFile}\",${config.address1},${config.address2})`;
  1724. if (codePreview) {
  1725. codePreview.textContent = offlineCode;
  1726. }
  1727. if (dragCodePreview) {
  1728. dragCodePreview.textContent = dragCode;
  1729. }
  1730. // 高亮显示
  1731. let highlightedCode = offlineCode;
  1732. config.files.forEach(file => {
  1733. if (file.fileName) {
  1734. highlightedCode = highlightedCode.replace(
  1735. new RegExp(`"${file.fileName}"`, 'g'),
  1736. `<span class="highlight">"${file.fileName}"</span>`
  1737. );
  1738. }
  1739. if (file.address) {
  1740. highlightedCode = highlightedCode.replace(
  1741. new RegExp(file.address.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'),
  1742. `<span class="highlight">${file.address}</span>`
  1743. );
  1744. }
  1745. if (file.algorithm) {
  1746. highlightedCode = highlightedCode.replace(
  1747. new RegExp(`"FLM/${file.algorithm}"`, 'g'),
  1748. `<span class="highlight">"FLM/${file.algorithm}"</span>`
  1749. );
  1750. }
  1751. });
  1752. if (codePreview) {
  1753. codePreview.innerHTML = highlightedCode
  1754. .replace(new RegExp(config.address1.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'), `<span class="highlight">${config.address1}</span>`)
  1755. .replace(new RegExp(config.address2.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'), `<span class="highlight">${config.address2}</span>`)
  1756. .replace(new RegExp(pythonSwdSpeed.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'), 'g'), `<span class="highlight">${pythonSwdSpeed}</span>`);
  1757. }
  1758. if (dragCodePreview) {
  1759. 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>`);
  1760. }
  1761. }
  1762. // 复制按钮逻辑
  1763. function copyToClipboard(text) {
  1764. if (navigator.clipboard) {
  1765. navigator.clipboard.writeText(text);
  1766. } else {
  1767. const textarea = document.createElement('textarea');
  1768. textarea.value = text;
  1769. document.body.appendChild(textarea);
  1770. textarea.select();
  1771. document.execCommand('copy');
  1772. document.body.removeChild(textarea);
  1773. }
  1774. }
  1775. if (customFlmInput) {
  1776. customFlmInput.addEventListener('input', function() { config.flmFile = this.value || 'custom_flm.FLM.o'; updateCodePreview(); });
  1777. }
  1778. if (address1Input) {
  1779. address1Input.addEventListener('input', function() { config.address1 = this.value || '0X08000000'; updateCodePreview(); });
  1780. }
  1781. if (address2Input) {
  1782. address2Input.addEventListener('input', function() { config.address2 = this.value || '0x20000000'; updateCodePreview(); });
  1783. }
  1784. if (swdClockSpeedSelect) {
  1785. swdClockSpeedSelect.addEventListener('change', function() { config.swdClockSpeed = this.value; updateCodePreview(); });
  1786. }
  1787. // 多文件配置功能已移至script.js中处理
  1788. // 删除下载按钮事件监听器
  1789. updateCodePreview();
  1790. // 只允许发送"离线下载脚本"和"拖拽下载脚本",其它tab禁用发送按钮,不允许发送本地.py文件
  1791. // 1. 移除本地.py文件相关UI
  1792. // 2. 发送按钮只在"离线下载脚本"或"拖拽下载脚本"tab下可用,文件名分别为offline_download.py和drag_download.py
  1793. // 3. 其它tab禁用发送按钮
  1794. const pyYmodemSendBtn = document.getElementById('pyYmodemSendBtn');
  1795. const pyYmodemProgress = document.getElementById('pyYmodemProgress');
  1796. const pyYmodemLog = document.getElementById('pyYmodemLog');
  1797. function pyYlog(msg, color) {
  1798. appendToTerminalOutput(`<div class='log-prefix-python'>[PYTHON] ${msg}</div>`);
  1799. }
  1800. function pyYlogClear() {
  1801. // 不清空主终端
  1802. }
  1803. function updatePyYmodemSendBtnState() {
  1804. // 只允许离线下载脚本和拖拽下载脚本tab可用
  1805. const tab = document.querySelector('.script-tab.active');
  1806. if (tab && tab.textContent.includes('离线')) {
  1807. pyYmodemSendBtn.disabled = false;
  1808. pyYmodemSendBtn.setAttribute('data-pytype', 'offline');
  1809. } else if (tab && tab.textContent.includes('拖拽')) {
  1810. pyYmodemSendBtn.disabled = false;
  1811. pyYmodemSendBtn.setAttribute('data-pytype', 'drag');
  1812. } else {
  1813. pyYmodemSendBtn.disabled = true;
  1814. pyYmodemSendBtn.removeAttribute('data-pytype');
  1815. }
  1816. }
  1817. // 监听tab切换
  1818. const scriptTabs = document.querySelectorAll('.script-tab');
  1819. if (scriptTabs.length > 0) {
  1820. scriptTabs.forEach(tab => {
  1821. tab.addEventListener('click', updatePyYmodemSendBtnState);
  1822. });
  1823. updatePyYmodemSendBtnState();
  1824. }
  1825. // 以Python tab为例,统一YMODEM发送按钮事件
  1826. if (pyYmodemSendBtn) {
  1827. pyYmodemSendBtn.addEventListener('click', async () => {
  1828. if (pyYmodemSendBtn.disabled) return;
  1829. if (!window.microLinkTerminal || !window.microLinkTerminal.isConnected || !window.microLinkTerminal.port) {
  1830. pyYlog('请先连接串口', '#f66'); return;
  1831. }
  1832. pyYmodemSendBtn.disabled = true;
  1833. pyYmodemProgress.value = 0;
  1834. pyYmodemProgress.style.display = '';
  1835. pyYlogClear();
  1836. pyYlog('准备发送...', '#0ff');
  1837. // === 加锁管理和监听控制 ===
  1838. let wasConnected = false;
  1839. if (window.microLinkTerminal) {
  1840. if (window.microLinkTerminal.reader) {
  1841. try { window.microLinkTerminal.reader.cancel(); } catch(e){}
  1842. try { window.microLinkTerminal.reader.releaseLock(); } catch(e){}
  1843. window.microLinkTerminal.reader = null;
  1844. }
  1845. wasConnected = window.microLinkTerminal.isConnected;
  1846. window.microLinkTerminal.isConnected = false;
  1847. await new Promise(r => setTimeout(r, 300));
  1848. }
  1849. try {
  1850. let code = '';
  1851. let fileName = '';
  1852. const tabType = pyYmodemSendBtn.getAttribute('data-pytype');
  1853. if (tabType === 'offline') {
  1854. code = getOfflineCode();
  1855. fileName = 'Python/offline_download.py';
  1856. } else if (tabType === 'drag') {
  1857. code = getDragCode();
  1858. fileName = 'Python/drag_download.py';
  1859. } else {
  1860. pyYlog('只允许发送离线下载脚本或拖拽下载脚本', '#f66');
  1861. return;
  1862. }
  1863. const uint8Array = new TextEncoder().encode(code);
  1864. const port = window.microLinkTerminal && window.microLinkTerminal.port;
  1865. if (!port) throw new Error('串口未连接');
  1866. let ok = await window.sendFileViaYmodem(
  1867. port,
  1868. uint8Array,
  1869. fileName,
  1870. uint8Array.length,
  1871. msg => pyYlog(msg)
  1872. );
  1873. if (ok) {
  1874. pyYlog('✅ 发送完成', '#0f0');
  1875. } else {
  1876. pyYlog('❌ 发送失败', '#f66');
  1877. }
  1878. } catch (e) {
  1879. pyYlog('❌ 发送失败: ' + e.message, '#f66');
  1880. if (e && e.stack) pyYlog('错误堆栈: ' + e.stack, '#f66');
  1881. } finally {
  1882. // === 恢复主终端监听 ===
  1883. if (window.microLinkTerminal) {
  1884. window.microLinkTerminal.isConnected = wasConnected;
  1885. if (wasConnected && typeof window.microLinkTerminal.startReading === 'function') {
  1886. window.microLinkTerminal.startReading();
  1887. }
  1888. }
  1889. pyYmodemSendBtn.disabled = false;
  1890. pyYmodemProgress.style.display = 'none';
  1891. }
  1892. });
  1893. }
  1894. // 以ELF tab为例,统一YMODEM发送按钮事件
  1895. if (flmYmodemSendBtn) {
  1896. flmYmodemSendBtn.addEventListener('click', async () => {
  1897. if (!convertedBlob) { flmYlog('请先生成.o文件', '#ff0'); return; }
  1898. if (!isSerialConnected()) {
  1899. flmYlog('请先连接串口', '#f66'); return;
  1900. }
  1901. flmYlogClear();
  1902. setFlmYmodemUIBusy(true);
  1903. flmYmodemProgress.value = 0;
  1904. flmYmodemProgress.max = 100;
  1905. // === 加锁管理和监听控制 ===
  1906. let wasConnected = false;
  1907. if (window.microLinkTerminal) {
  1908. if (window.microLinkTerminal.reader) {
  1909. try { window.microLinkTerminal.reader.cancel(); } catch(e){}
  1910. try { window.microLinkTerminal.reader.releaseLock(); } catch(e){}
  1911. window.microLinkTerminal.reader = null;
  1912. }
  1913. wasConnected = window.microLinkTerminal.isConnected;
  1914. window.microLinkTerminal.isConnected = false;
  1915. await new Promise(r => setTimeout(r, 300));
  1916. }
  1917. try {
  1918. const arrayBuffer = await convertedBlob.arrayBuffer();
  1919. const uint8Array = new Uint8Array(arrayBuffer);
  1920. const fileName = flmFileName ? (flmFileName + '.FLM.o') : 'firmware.FLM.o';
  1921. const port = window.microLinkTerminal && window.microLinkTerminal.port;
  1922. if (!port) throw new Error('串口未连接');
  1923. let ok = await window.sendFileViaYmodem(
  1924. port,
  1925. uint8Array,
  1926. fileName,
  1927. uint8Array.length,
  1928. msg => flmYlog(msg)
  1929. );
  1930. if (ok) {
  1931. flmYlog('✅ 文件发送完成', '#0f0');
  1932. } else {
  1933. flmYlog('❌ 发送失败', '#f66');
  1934. }
  1935. } catch (e) {
  1936. // 静默失败,不输出任何报错日志
  1937. return;
  1938. } finally {
  1939. // === 恢复主终端监听 ===
  1940. if (window.microLinkTerminal) {
  1941. window.microLinkTerminal.isConnected = wasConnected;
  1942. if (wasConnected && typeof window.microLinkTerminal.startReading === 'function') {
  1943. window.microLinkTerminal.startReading();
  1944. }
  1945. }
  1946. flmYlog('YMODEM流程已结束', '#888');
  1947. setFlmYmodemUIBusy(false);
  1948. }
  1949. });
  1950. }
  1951. // 在tab切换到flmPanel时调用updateFlmYmodemBtn
  1952. if (panelSelect) {
  1953. panelSelect.addEventListener('change', function() {
  1954. if (panelSelect.value === 'flmPanel') {
  1955. updateFlmYmodemBtn();
  1956. }
  1957. });
  1958. }
  1959. // 在convertBtn点击和串口连接事件后也调用updateFlmYmodemBtn
  1960. if (convertBtn) {
  1961. convertBtn.addEventListener('click', function() {
  1962. updateFlmYmodemBtn();
  1963. });
  1964. }
  1965. document.addEventListener('serialConnected', function() {
  1966. updateFlmYmodemBtn();
  1967. });
  1968. document.addEventListener('serialDisconnected', function() {
  1969. updateFlmYmodemBtn();
  1970. });
  1971. });
  1972. </script>
  1973. <!-- deepseek变量分析器脚本自动集成(完整JS) -->
  1974. <script>
  1975. // 内置 ELF 解析器
  1976. class ELFParser {
  1977. static parse(arrayBuffer) {
  1978. const dataView = new DataView(arrayBuffer);
  1979. // 检查 ELF 魔数
  1980. if (dataView.getUint32(0) !== 0x7F454C46) {
  1981. throw new Error("不是有效的 ELF 文件");
  1982. }
  1983. const is32Bit = dataView.getUint8(4) === 1; // ELFCLASS32
  1984. const isLE = dataView.getUint8(5) === 1; // ELFDATA2LSB
  1985. const header = {
  1986. e_type: dataView.getUint16(16, isLE),
  1987. e_machine: dataView.getUint16(18, isLE),
  1988. e_version: dataView.getUint32(20, isLE),
  1989. e_entry: dataView.getUint32(24, isLE),
  1990. e_phoff: is32Bit ? dataView.getUint32(28, isLE) : Number(dataView.getBigUint64(32, isLE)),
  1991. e_shoff: is32Bit ? dataView.getUint32(32, isLE) : Number(dataView.getBigUint64(40, isLE)),
  1992. e_flags: dataView.getUint32(36, isLE),
  1993. e_ehsize: dataView.getUint16(40, isLE),
  1994. e_phentsize: dataView.getUint16(42, isLE),
  1995. e_phnum: dataView.getUint16(44, isLE),
  1996. e_shentsize: dataView.getUint16(46, isLE),
  1997. e_shnum: dataView.getUint16(48, isLE),
  1998. e_shstrndx: dataView.getUint16(50, isLE)
  1999. };
  2000. // 解析节头
  2001. const sections = [];
  2002. const shoff = Number(header.e_shoff);
  2003. const shentsize = header.e_shentsize;
  2004. const shnum = header.e_shnum;
  2005. for (let i = 0; i < shnum; i++) {
  2006. const offset = shoff + i * shentsize;
  2007. const section = {
  2008. sh_name: dataView.getUint32(offset, isLE),
  2009. sh_type: dataView.getUint32(offset + 4, isLE),
  2010. sh_flags: is32Bit ? dataView.getUint32(offset + 8, isLE) : Number(dataView.getBigUint64(offset + 8, isLE)),
  2011. sh_addr: is32Bit ? dataView.getUint32(offset + 12, isLE) : Number(dataView.getBigUint64(offset + 16, isLE)),
  2012. sh_offset: is32Bit ? dataView.getUint32(offset + 16, isLE) : Number(dataView.getBigUint64(offset + 24, isLE)),
  2013. sh_size: is32Bit ? dataView.getUint32(offset + 20, isLE) : Number(dataView.getBigUint64(offset + 32, isLE)),
  2014. sh_link: dataView.getUint32(offset + (is32Bit ? 24 : 40), isLE),
  2015. sh_info: dataView.getUint32(offset + (is32Bit ? 28 : 44), isLE),
  2016. sh_addralign: is32Bit ? dataView.getUint32(offset + 32, isLE) : Number(dataView.getBigUint64(offset + 48, isLE)),
  2017. sh_entsize: is32Bit ? dataView.getUint32(offset + 36, isLE) : Number(dataView.getBigUint64(offset + 56, isLE))
  2018. };
  2019. sections.push(section);
  2020. }
  2021. // 获取节名称
  2022. const shstrtab = sections[header.e_shstrndx];
  2023. const shstrtabData = new Uint8Array(arrayBuffer, shstrtab.sh_offset, shstrtab.sh_size);
  2024. sections.forEach(section => {
  2025. section.name = this.readNullTerminatedString(shstrtabData, section.sh_name);
  2026. });
  2027. // 解析符号表
  2028. sections.forEach(section => {
  2029. if (section.sh_type === 2) { // SHT_SYMTAB
  2030. const symbols = [];
  2031. const strtab = sections[section.sh_link];
  2032. const strtabData = new Uint8Array(arrayBuffer, strtab.sh_offset, strtab.sh_size);
  2033. const count = section.sh_size / section.sh_entsize;
  2034. for (let i = 0; i < count; i++) {
  2035. const offset = section.sh_offset + i * section.sh_entsize;
  2036. const st_name = dataView.getUint32(offset, isLE);
  2037. const st_value = is32Bit ? dataView.getUint32(offset + 4, isLE) : Number(dataView.getBigUint64(offset + 8, isLE));
  2038. const st_size = is32Bit ? dataView.getUint32(offset + 8, isLE) : Number(dataView.getBigUint64(offset + 16, isLE));
  2039. const st_info = dataView.getUint8(offset + (is32Bit ? 12 : 4));
  2040. const st_shndx = dataView.getUint16(offset + (is32Bit ? 14 : 6), isLE);
  2041. symbols.push({
  2042. name: this.readNullTerminatedString(strtabData, st_name),
  2043. value: st_value,
  2044. size: st_size,
  2045. type: st_info & 0x0F,
  2046. bind: st_info >> 4,
  2047. sectionIndex: st_shndx
  2048. });
  2049. }
  2050. section.symbols = symbols;
  2051. }
  2052. });
  2053. return {
  2054. header: header,
  2055. sections: sections
  2056. };
  2057. }
  2058. static readNullTerminatedString(data, offset) {
  2059. let str = "";
  2060. let i = offset;
  2061. while (i < data.length && data[i] !== 0) {
  2062. str += String.fromCharCode(data[i]);
  2063. i++;
  2064. }
  2065. return str;
  2066. }
  2067. }
  2068. // 内存区域定义
  2069. const memoryRegions = [
  2070. { name: "Flash", start: 0x08000000, end: 0x08100000, color: "#4e9a06" },
  2071. { name: "RAM", start: 0x20000000, end: 0x20020000, color: "#3465a4" },
  2072. { name: "Peripherals", start: 0x40000000, end: 0x60000000, color: "#cc0000" }
  2073. ];
  2074. // 段类型颜色映射
  2075. const sectionColors = {
  2076. '.bss': '#ffd700',
  2077. '.data': '#ff7f50',
  2078. '.rodata': '#ad7fa8',
  2079. '.heap': '#73d216',
  2080. '.stack': '#f57900',
  2081. 'default': '#555555'
  2082. };
  2083. // 当前分析的变量列表
  2084. let variables = [];
  2085. let elfFile = null;
  2086. let selectedVariables = new Set(); // 存储选中的变量
  2087. // 将selectedVariables挂载到全局,供数据处理函数使用
  2088. window.selectedVariables = selectedVariables;
  2089. let filteredVariables = []; // 存储筛选后的变量
  2090. // DOM元素
  2091. const fileInput = document.getElementById('axfFile');
  2092. const analyzeBtn = document.getElementById('analyzeBtn');
  2093. const variableTableBody = document.getElementById('variableTableBody');
  2094. const memoryMap = document.getElementById('memoryMap');
  2095. const variableDetail = document.getElementById('variableDetail');
  2096. const fileInfo = document.getElementById('fileInfo');
  2097. const progressBar = document.getElementById('progressBar');
  2098. const searchFilter = document.getElementById('searchFilter');
  2099. const selectAllVars = document.getElementById('selectAllVars');
  2100. const selectedVariablesInfo = document.getElementById('selectedVariablesInfo');
  2101. const selectedVariablesList = document.getElementById('selectedVariablesList');
  2102. const selectedTotalSize = document.getElementById('selectedTotalSize');
  2103. // 文件选择事件
  2104. if (fileInput) {
  2105. fileInput.addEventListener('change', function(e) {
  2106. if (this.files.length > 0) {
  2107. analyzeBtn.disabled = false;
  2108. fileInfo.classList.add('d-none');
  2109. // 更新文件状态显示
  2110. const fileStatus = document.getElementById('fileStatus');
  2111. if (fileStatus) {
  2112. fileStatus.textContent = `已选择: ${this.files[0].name}`;
  2113. fileStatus.style.color = '#28a745';
  2114. }
  2115. } else {
  2116. analyzeBtn.disabled = true;
  2117. // 重置文件状态显示
  2118. const fileStatus = document.getElementById('fileStatus');
  2119. if (fileStatus) {
  2120. fileStatus.textContent = '未选择文件';
  2121. fileStatus.style.color = '#6c757d';
  2122. }
  2123. }
  2124. });
  2125. }
  2126. // 筛选器事件监听
  2127. if (searchFilter) {
  2128. searchFilter.addEventListener('input', filterVariables);
  2129. }
  2130. // 全选/取消全选
  2131. if (selectAllVars) {
  2132. selectAllVars.addEventListener('change', function() {
  2133. const checkboxes = variableTableBody.querySelectorAll('input[type="checkbox"]:not([id="selectAllVars"])');
  2134. if (this.checked) {
  2135. // 全选(考虑9个变量限制)
  2136. let selectedCount = 0;
  2137. checkboxes.forEach(checkbox => {
  2138. if (selectedCount < 9) {
  2139. checkbox.checked = true;
  2140. selectedVariables.add(checkbox.value);
  2141. // 设置变量信息到全局对象
  2142. const variable = variables.find(v => v.name === checkbox.value);
  2143. if (variable) {
  2144. if (!window.variableInfo) {
  2145. window.variableInfo = {};
  2146. }
  2147. window.variableInfo[checkbox.value] = {
  2148. size: variable.size,
  2149. addr: variable.addr,
  2150. type: variable.type,
  2151. section: variable.section
  2152. };
  2153. console.log(`[全选] 设置变量 ${checkbox.value} 信息:`, window.variableInfo[checkbox.value]);
  2154. }
  2155. selectedCount++;
  2156. }
  2157. });
  2158. // 如果变量总数超过9个,显示提示
  2159. if (selectedVariables.size > 9) {
  2160. alert('最多只能选择9个变量,已自动限制选择数量');
  2161. // 移除超出限制的变量
  2162. const selectedArray = Array.from(selectedVariables);
  2163. for (let i = 9; i < selectedArray.length; i++) {
  2164. selectedVariables.delete(selectedArray[i]);
  2165. const checkbox = variableTableBody.querySelector(`input[value="${selectedArray[i]}"]`);
  2166. if (checkbox) {
  2167. checkbox.checked = false;
  2168. }
  2169. }
  2170. }
  2171. } else {
  2172. // 取消全选
  2173. checkboxes.forEach(checkbox => {
  2174. checkbox.checked = false;
  2175. selectedVariables.delete(checkbox.value);
  2176. // 从全局对象中移除变量信息
  2177. if (window.variableInfo && window.variableInfo[checkbox.value]) {
  2178. delete window.variableInfo[checkbox.value];
  2179. console.log(`[取消全选] 移除变量 ${checkbox.value} 信息`);
  2180. }
  2181. });
  2182. }
  2183. // 更新显示
  2184. updateSelectedVariablesDisplay();
  2185. updateHexSendPanel();
  2186. });
  2187. }
  2188. // 分析按钮事件
  2189. if (analyzeBtn) {
  2190. analyzeBtn.addEventListener('click', function() {
  2191. const file = fileInput.files[0];
  2192. if (!file) return;
  2193. // 重置UI
  2194. variables = [];
  2195. selectedVariables.clear();
  2196. window.variableInfo = {}; // 清空变量信息
  2197. filteredVariables = [];
  2198. variableTableBody.innerHTML = '<tr><td colspan="6" class="text-center">分析中...</td></tr>';
  2199. if (memoryMap) memoryMap.innerHTML = '<div class="text-center text-muted mt-5">分析中...</div>';
  2200. if (variableDetail) variableDetail.innerHTML = '分析中...';
  2201. if (progressBar) progressBar.classList.remove('d-none');
  2202. // 显示进度条
  2203. const progress = progressBar ? progressBar.querySelector('.progress-bar') : null;
  2204. if (progress) progress.style.width = '0%';
  2205. // 读取文件
  2206. const reader = new FileReader();
  2207. reader.onload = function(e) {
  2208. try {
  2209. console.log('开始解析ELF文件...');
  2210. // 解析ELF文件
  2211. const arrayBuffer = e.target.result;
  2212. elfFile = ELFParser.parse(arrayBuffer);
  2213. console.log('ELF文件解析成功:', elfFile);
  2214. // 显示文件信息
  2215. displayFileInfo(file, elfFile);
  2216. console.log('文件信息显示完成');
  2217. // 提取变量
  2218. extractVariables(elfFile);
  2219. console.log('变量提取完成,找到变量数量:', variables.length);
  2220. // 更新进度条
  2221. if (progress) progress.style.width = '100%';
  2222. setTimeout(() => {
  2223. if (progressBar) progressBar.classList.add('d-none');
  2224. }, 500);
  2225. // 显示结果
  2226. displayVariables();
  2227. drawMemoryMap();
  2228. console.log('变量分析完成');
  2229. } catch (error) {
  2230. console.error('ELF解析错误:', error);
  2231. variableTableBody.innerHTML = `<tr><td colspan="6" class="text-center text-danger">解析错误: ${error.message}</td></tr>`;
  2232. if (progressBar) progressBar.classList.add('d-none');
  2233. }
  2234. };
  2235. reader.onprogress = function(e) {
  2236. if (e.lengthComputable && progress) {
  2237. const percent = (e.loaded / e.total) * 100;
  2238. progress.style.width = `${percent}%`;
  2239. }
  2240. };
  2241. reader.readAsArrayBuffer(file);
  2242. });
  2243. }
  2244. // 显示文件信息
  2245. function displayFileInfo(file, elf) {
  2246. if (fileInfo) {
  2247. fileInfo.classList.remove('d-none');
  2248. const fileName = document.getElementById('fileName');
  2249. const fileSize = document.getElementById('fileSize');
  2250. const elfType = document.getElementById('elfType');
  2251. const entryPoint = document.getElementById('entryPoint');
  2252. if (fileName) fileName.textContent = file.name;
  2253. if (fileSize) fileSize.textContent = formatFileSize(file.size);
  2254. if (elfType) elfType.textContent = elf.header.e_type;
  2255. if (entryPoint) entryPoint.textContent = `0x${elf.header.e_entry.toString(16).toUpperCase()}`;
  2256. }
  2257. }
  2258. // 格式化文件大小
  2259. function formatFileSize(bytes) {
  2260. if (bytes < 1024) return bytes + ' bytes';
  2261. else if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
  2262. else return (bytes / 1048576).toFixed(1) + ' MB';
  2263. }
  2264. // 提取变量
  2265. function extractVariables(elf) {
  2266. variables = [];
  2267. console.log('开始提取变量,节数量:', elf.sections.length);
  2268. // 遍历所有节
  2269. const sections = elf.sections;
  2270. for (let i = 0; i < sections.length; i++) {
  2271. const section = sections[i];
  2272. console.log(`节 ${i}: 名称=${section.name}, 类型=${section.sh_type}, 符号数量=${section.symbols ? section.symbols.length : 0}`);
  2273. // 只处理符号表
  2274. if (section.symbols) {
  2275. // 遍历符号表
  2276. const symbols = section.symbols;
  2277. for (let j = 0; j < symbols.length; j++) {
  2278. const symbol = symbols[j];
  2279. // 只关注变量(类型为STT_OBJECT)
  2280. if (symbol.type === 1) { // STT_OBJECT
  2281. const addr = symbol.value;
  2282. const size = symbol.size;
  2283. const name = symbol.name;
  2284. const sectionIndex = symbol.sectionIndex;
  2285. let sectionName = "Undefined";
  2286. // 获取段名称
  2287. if (sectionIndex > 0 && sectionIndex < sections.length) {
  2288. sectionName = sections[sectionIndex].name;
  2289. }
  2290. // 确定变量类型
  2291. let varType = "其他";
  2292. if (sectionName === '.bss') varType = "未初始化";
  2293. else if (sectionName === '.data') varType = "已初始化";
  2294. else if (sectionName === '.rodata') varType = "只读";
  2295. else if (sectionName === '.heap') varType = "堆";
  2296. else if (sectionName === '.stack') varType = "栈";
  2297. // 只添加段为RW_IRAM1和RW_IRAM2的变量
  2298. if (sectionName === 'RW_IRAM1' || sectionName === 'RW_IRAM2') {
  2299. variables.push({
  2300. name: name,
  2301. addr: addr,
  2302. size: size,
  2303. type: varType,
  2304. section: sectionName
  2305. });
  2306. console.log(`找到RW_IRAM1段变量: ${name}, 地址: 0x${addr.toString(16)}, 大小: ${size}, 类型: ${varType}, 段: ${sectionName}`);
  2307. }
  2308. }
  2309. }
  2310. }
  2311. }
  2312. console.log(`变量提取完成,总共找到 ${variables.length} 个变量`);
  2313. // 按地址排序
  2314. variables.sort((a, b) => a.addr - b.addr);
  2315. // 初始化筛选后的变量列表
  2316. filteredVariables = [...variables];
  2317. }
  2318. // 筛选变量
  2319. function filterVariables() {
  2320. const searchValue = searchFilter ? searchFilter.value.toLowerCase() : '';
  2321. filteredVariables = variables.filter(variable => {
  2322. // 名称搜索
  2323. if (searchValue && !variable.name.toLowerCase().includes(searchValue)) {
  2324. return false;
  2325. }
  2326. return true;
  2327. });
  2328. displayVariables();
  2329. updateSelectedVariablesDisplay();
  2330. }
  2331. // 显示变量表格
  2332. function displayVariables() {
  2333. if (filteredVariables.length === 0) {
  2334. variableTableBody.innerHTML = `
  2335. <tr>
  2336. <td colspan="4" class="text-center text-muted" style="padding:40px 16px;font-size:15px;">
  2337. <div style="margin-bottom:8px;">🔍</div>
  2338. 未找到符合条件的变量
  2339. </td>
  2340. </tr>
  2341. `;
  2342. return;
  2343. }
  2344. let html = '';
  2345. filteredVariables.forEach((variable, index) => {
  2346. const isSelected = selectedVariables.has(variable.name);
  2347. const rowClass = isSelected ? 'selected' : '';
  2348. html += `
  2349. <tr class="${rowClass}" data-variable="${variable.name}">
  2350. <td>
  2351. <input type="checkbox" class="variable-checkbox" value="${variable.name}"
  2352. ${isSelected ? 'checked' : ''} onchange="toggleVariableSelection(this)">
  2353. </td>
  2354. <td>${variable.name}</td>
  2355. <td>0x${variable.addr.toString(16).toUpperCase()}</td>
  2356. <td>${variable.size} 字节</td>
  2357. </tr>
  2358. `;
  2359. });
  2360. variableTableBody.innerHTML = html;
  2361. // 更新统计信息
  2362. updateVariableStats();
  2363. }
  2364. // 切换变量选择状态
  2365. function toggleVariableSelection(checkbox) {
  2366. if (checkbox.checked) {
  2367. // 检查变量数量限制(最多9个)
  2368. if (selectedVariables.size >= 9) {
  2369. alert('最多只能选择9个变量');
  2370. checkbox.checked = false;
  2371. return;
  2372. }
  2373. selectedVariables.add(checkbox.value);
  2374. // 设置变量信息到全局对象,供数据处理函数使用
  2375. const variable = variables.find(v => v.name === checkbox.value);
  2376. if (variable) {
  2377. if (!window.variableInfo) {
  2378. window.variableInfo = {};
  2379. }
  2380. window.variableInfo[checkbox.value] = {
  2381. size: variable.size,
  2382. addr: variable.addr,
  2383. type: variable.type,
  2384. section: variable.section
  2385. };
  2386. console.log(`[变量选择] 设置变量 ${checkbox.value} 信息:`, window.variableInfo[checkbox.value]);
  2387. }
  2388. // 为选中的变量创建图表
  2389. if (window.multiChartManager) {
  2390. window.multiChartManager.createChart(checkbox.value);
  2391. }
  2392. } else {
  2393. selectedVariables.delete(checkbox.value);
  2394. // 从全局对象中移除变量信息
  2395. if (window.variableInfo && window.variableInfo[checkbox.value]) {
  2396. delete window.variableInfo[checkbox.value];
  2397. console.log(`[变量选择] 移除变量 ${checkbox.value} 信息`);
  2398. }
  2399. // 删除对应的图表
  2400. if (window.multiChartManager) {
  2401. window.multiChartManager.removeChart(checkbox.value);
  2402. }
  2403. }
  2404. // 更新全选状态
  2405. updateSelectAllState();
  2406. // 更新选中变量显示
  2407. updateSelectedVariablesDisplay();
  2408. // 更新串口发送区域
  2409. updateHexSendPanel();
  2410. }
  2411. // 更新全选状态
  2412. function updateSelectAllState() {
  2413. if (selectAllVars) {
  2414. const checkboxes = variableTableBody.querySelectorAll('input[type="checkbox"]:not([id="selectAllVars"])');
  2415. const checkedCount = Array.from(checkboxes).filter(cb => cb.checked).length;
  2416. selectAllVars.checked = checkedCount === checkboxes.length;
  2417. selectAllVars.indeterminate = checkedCount > 0 && checkedCount < checkboxes.length;
  2418. }
  2419. }
  2420. // 更新变量统计信息
  2421. function updateVariableStats() {
  2422. // 统计信息已移除,此函数保留以维持代码结构
  2423. }
  2424. // 更新选中变量显示
  2425. function updateSelectedVariablesDisplay() {
  2426. if (selectedVariables.size === 0) {
  2427. if (selectedVariablesInfo) selectedVariablesInfo.style.display = 'none';
  2428. // 重置HEX曲线图容器高度
  2429. adjustHexChartContainerHeight(0);
  2430. return;
  2431. }
  2432. if (selectedVariablesInfo) selectedVariablesInfo.style.display = 'block';
  2433. let html = '';
  2434. let totalSize = 0;
  2435. selectedVariables.forEach(varName => {
  2436. const variable = variables.find(v => v.name === varName);
  2437. if (variable) {
  2438. totalSize += variable.size;
  2439. html += `
  2440. <div class="selected-variable-tag">
  2441. ${variable.name} (${variable.size}字节)
  2442. <button class="remove-btn" onclick="removeVariableSelection('${varName}')">×</button>
  2443. </div>
  2444. `;
  2445. }
  2446. });
  2447. if (selectedVariablesList) selectedVariablesList.innerHTML = html;
  2448. if (selectedTotalSize) selectedTotalSize.textContent = totalSize;
  2449. // 动态调整HEX曲线图容器高度
  2450. adjustHexChartContainerHeight(selectedVariables.size);
  2451. }
  2452. // 动态调整HEX曲线图容器高度
  2453. function adjustHexChartContainerHeight(selectedCount) {
  2454. // 找到包含"实时HEX曲线图"标题的容器
  2455. const hexSendPanels = document.querySelectorAll('.hex-send-panel');
  2456. let hexChartPanel = null;
  2457. for (let panel of hexSendPanels) {
  2458. const title = panel.querySelector('h4');
  2459. if (title && title.textContent.includes('实时HEX曲线图')) {
  2460. hexChartPanel = panel;
  2461. break;
  2462. }
  2463. }
  2464. const multiChartContainer = document.getElementById('multiChartContainer');
  2465. if (!hexChartPanel || !multiChartContainer) return;
  2466. // 基础高度:标题栏 + 按钮栏 + 边距
  2467. const baseHeight = 120;
  2468. // 每个变量图表的高度(包括间距)
  2469. const chartHeight = 500;
  2470. const chartMargin = 20;
  2471. // 计算总高度
  2472. let totalHeight;
  2473. if (selectedCount === 0) {
  2474. // 没有选中变量时,使用默认高度
  2475. totalHeight = 600;
  2476. } else if (selectedCount === 1) {
  2477. // 单个变量时,使用适中的高度
  2478. totalHeight = baseHeight + chartHeight;
  2479. } else {
  2480. // 多个变量时,根据数量动态计算
  2481. totalHeight = baseHeight + (selectedCount * (chartHeight + chartMargin));
  2482. }
  2483. // 设置最小和最大高度限制
  2484. const minHeight = 800;
  2485. const maxHeight = 3600;
  2486. totalHeight = Math.max(minHeight, Math.min(maxHeight, totalHeight));
  2487. // 应用高度调整 - 修改正确的容器
  2488. hexChartPanel.style.minHeight = totalHeight + 'px';
  2489. multiChartContainer.style.minHeight = (totalHeight - baseHeight) + 'px';
  2490. console.log(`[HEX曲线图容器调整] 选中变量数量: ${selectedCount}, 容器高度: ${totalHeight}px`);
  2491. }
  2492. // 移除变量选择
  2493. function removeVariableSelection(varName) {
  2494. selectedVariables.delete(varName);
  2495. // 删除对应的图表
  2496. if (window.multiChartManager) {
  2497. window.multiChartManager.removeChart(varName);
  2498. }
  2499. // 更新表格中的复选框
  2500. const checkbox = variableTableBody.querySelector(`input[value="${varName}"]`);
  2501. if (checkbox) {
  2502. checkbox.checked = false;
  2503. }
  2504. // 更新显示
  2505. updateSelectAllState();
  2506. updateSelectedVariablesDisplay();
  2507. updateHexSendPanel();
  2508. }
  2509. // 更新串口发送面板
  2510. function updateHexSendPanel() {
  2511. // 不再需要生成地址输入框,保持面板简洁
  2512. // 用户可以通过采样率设置来控制发送频率
  2513. }
  2514. // 将函数挂载到全局作用域
  2515. window.displayVariables = displayVariables;
  2516. window.toggleVariableSelection = toggleVariableSelection;
  2517. window.removeVariableSelection = removeVariableSelection;
  2518. // 显示变量详情
  2519. window.showVariableDetail = function(index) {
  2520. const varItem = variables[index];
  2521. // 创建模态框显示详情
  2522. const modalHTML = `
  2523. <div id="variableDetailModal" style="
  2524. position: fixed; top: 0; left: 0; width: 100%; height: 100%;
  2525. background: rgba(0,0,0,0.5); z-index: 9999; display: flex;
  2526. align-items: center; justify-content: center;">
  2527. <div style="
  2528. background: white; border-radius: 12px; padding: 24px;
  2529. max-width: 500px; width: 90%; max-height: 80%; overflow-y: auto;
  2530. box-shadow: 0 10px 30px rgba(0,0,0,0.3);">
  2531. <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
  2532. <h4 style="margin: 0; color: #2980b9; font-size: 18px;">
  2533. <span style="margin-right:8px;">🔍</span>变量详细信息
  2534. </h4>
  2535. <button onclick="closeVariableDetail()" style="
  2536. background: none; border: none; font-size: 24px;
  2537. cursor: pointer; color: #6c757d;">&times;</button>
  2538. </div>
  2539. <div style="background: #f8f9fa; border-radius: 8px; padding: 16px; font-family: monospace; font-size: 14px; line-height: 1.6;">
  2540. <div style="margin-bottom: 8px;"><strong>📝 变量名:</strong> ${varItem.name}</div>
  2541. <div style="margin-bottom: 8px;"><strong>📍 地址:</strong> 0x${varItem.addr.toString(16).toUpperCase().padStart(8, '0')}</div>
  2542. <div style="margin-bottom: 8px;"><strong>📊 大小:</strong> ${varItem.size} 字节`;
  2543. // 添加大小描述
  2544. if (varItem.size > 1024 * 1024) {
  2545. modalHTML += ` (${(varItem.size/(1024*1024)).toFixed(2)} MB)`;
  2546. } else if (varItem.size > 1024) {
  2547. modalHTML += ` (${(varItem.size/1024).toFixed(2)} KB)`;
  2548. }
  2549. modalHTML += `</div>
  2550. <div style="margin-bottom: 8px;"><strong>🏷️ 类型:</strong> ${varItem.type}</div>
  2551. <div style="margin-bottom: 8px;"><strong>📂 段:</strong> ${varItem.section}</div>`;
  2552. // 添加内存位置描述
  2553. if (varItem.addr >= 0x08000000 && varItem.addr < 0x09000000) {
  2554. modalHTML += `<div style="margin-bottom: 8px;"><strong>💾 位置:</strong> Flash 存储器</div>`;
  2555. } else if (varItem.addr >= 0x20000000 && varItem.addr < 0x30000000) {
  2556. modalHTML += `<div style="margin-bottom: 8px;"><strong>💾 位置:</strong> RAM</div>`;
  2557. } else if (varItem.addr >= 0x40000000 && varItem.addr < 0x60000000) {
  2558. modalHTML += `<div style="margin-bottom: 8px;"><strong>💾 位置:</strong> 外设寄存器区域</div>`;
  2559. } else {
  2560. modalHTML += `<div style="margin-bottom: 8px;"><strong>💾 位置:</strong> 未知内存区域</div>`;
  2561. }
  2562. modalHTML += `
  2563. </div>
  2564. <div style="margin-top: 20px; text-align: center;">
  2565. <button onclick="closeVariableDetail()" style="
  2566. background: #3498db; color: white; border: none;
  2567. padding: 10px 20px; border-radius: 6px; cursor: pointer;
  2568. font-size: 14px;">关闭</button>
  2569. </div>
  2570. </div>
  2571. </div>
  2572. `;
  2573. // 移除已存在的模态框
  2574. const existingModal = document.getElementById('variableDetailModal');
  2575. if (existingModal) {
  2576. existingModal.remove();
  2577. }
  2578. // 添加新模态框
  2579. document.body.insertAdjacentHTML('beforeend', modalHTML);
  2580. };
  2581. // 关闭变量详情模态框
  2582. window.closeVariableDetail = function() {
  2583. const modal = document.getElementById('variableDetailModal');
  2584. if (modal) {
  2585. modal.remove();
  2586. }
  2587. };
  2588. // 绘制内存映射
  2589. function drawMemoryMap() {
  2590. if (!memoryMap) return;
  2591. const canvas = document.createElement('canvas');
  2592. canvas.width = 800;
  2593. canvas.height = 400;
  2594. canvas.style.width = '100%';
  2595. canvas.style.height = 'auto';
  2596. const ctx = canvas.getContext('2d');
  2597. // 清空画布
  2598. ctx.clearRect(0, 0, canvas.width, canvas.height);
  2599. // 绘制内存区域
  2600. memoryRegions.forEach(region => {
  2601. const y = (region.start - 0x08000000) / (0x60000000 - 0x08000000) * canvas.height;
  2602. const height = (region.end - region.start) / (0x60000000 - 0x08000000) * canvas.height;
  2603. ctx.fillStyle = region.color;
  2604. ctx.fillRect(0, y, canvas.width, height);
  2605. // 添加标签
  2606. ctx.fillStyle = 'white';
  2607. ctx.font = '12px Arial';
  2608. ctx.fillText(region.name, 10, y + height / 2 + 4);
  2609. });
  2610. // 绘制变量位置
  2611. variables.forEach(variable => {
  2612. const y = (variable.addr - 0x08000000) / (0x60000000 - 0x08000000) * canvas.height;
  2613. const height = Math.max(2, variable.size / (0x60000000 - 0x08000000) * canvas.height);
  2614. ctx.fillStyle = sectionColors[variable.section] || sectionColors.default;
  2615. ctx.fillRect(100, y, 20, height);
  2616. });
  2617. // 添加图例
  2618. let legendY = 20;
  2619. Object.entries(sectionColors).forEach(([section, color]) => {
  2620. ctx.fillStyle = color;
  2621. ctx.fillRect(canvas.width - 150, legendY, 15, 15);
  2622. ctx.fillStyle = 'black';
  2623. ctx.font = '10px Arial';
  2624. ctx.fillText(section, canvas.width - 130, legendY + 12);
  2625. legendY += 20;
  2626. });
  2627. memoryMap.innerHTML = '';
  2628. memoryMap.appendChild(canvas);
  2629. }
  2630. // 初始化页面
  2631. document.addEventListener('DOMContentLoaded', function() {
  2632. // 重置UI状态
  2633. if (variableTableBody) {
  2634. variableTableBody.innerHTML = `
  2635. <tr>
  2636. <td colspan="6" class="text-center text-muted" style="padding:40px 16px;font-size:15px;">
  2637. <div style="margin-bottom:8px;">📁</div>
  2638. 请上传.axf文件并点击"分析文件"
  2639. </td>
  2640. </tr>
  2641. `;
  2642. }
  2643. // 隐藏选中变量信息
  2644. if (selectedVariablesInfo) {
  2645. selectedVariablesInfo.style.display = 'none';
  2646. }
  2647. // 初始化HEX曲线图容器高度
  2648. adjustHexChartContainerHeight(0);
  2649. // 初始化采样率输入框
  2650. const hexSampleRateInput = document.getElementById('hexSampleRateInput');
  2651. if (hexSampleRateInput) {
  2652. // 设置默认采样率
  2653. const defaultSampleRate = parseInt(hexSampleRateInput.value) || 100;
  2654. if (window.multiChartManager) {
  2655. window.multiChartManager.setSamplingRate(defaultSampleRate);
  2656. }
  2657. hexSampleRateInput.addEventListener('input', function() {
  2658. const sampleRate = parseInt(this.value) || 500;
  2659. const period = Math.round(1000 / sampleRate);
  2660. console.log(`采样率: ${sampleRate} Hz, 对应周期: ${period} ms`);
  2661. // 设置多变量图表的采样率
  2662. if (window.multiChartManager) {
  2663. window.multiChartManager.setSamplingRate(sampleRate);
  2664. }
  2665. });
  2666. }
  2667. // 初始化时间显示范围输入框
  2668. const timeDisplayRangeInput = document.getElementById('timeDisplayRangeInput');
  2669. if (timeDisplayRangeInput) {
  2670. // 设置默认时间显示范围
  2671. const defaultTimeDisplayRange = parseInt(timeDisplayRangeInput.value) || 10;
  2672. if (window.multiChartManager) {
  2673. window.multiChartManager.setTimeDisplayRange(defaultTimeDisplayRange);
  2674. }
  2675. timeDisplayRangeInput.addEventListener('input', function() {
  2676. const timeDisplayRange = parseInt(this.value) || 10;
  2677. console.log(`时间显示范围: ${timeDisplayRange} 秒`);
  2678. // 设置多变量图表的时间显示范围
  2679. if (window.multiChartManager) {
  2680. window.multiChartManager.setTimeDisplayRange(timeDisplayRange);
  2681. }
  2682. });
  2683. }
  2684. // 初始化串口发送功能的事件监听器
  2685. const sendHexCmdBtn = document.getElementById('sendHexCmdBtn');
  2686. if (sendHexCmdBtn) {
  2687. sendHexCmdBtn.addEventListener('click', function() {
  2688. const hexSampleRateInput = document.getElementById('hexSampleRateInput');
  2689. if (!hexSampleRateInput) {
  2690. alert('采样率输入框未找到');
  2691. return;
  2692. }
  2693. // 检查是否有选中的变量
  2694. if (selectedVariables.size === 0) {
  2695. alert('请先选择要监控的变量');
  2696. return;
  2697. }
  2698. const sampleRate = parseInt(hexSampleRateInput.value) || 100;
  2699. const period = Math.round(1000 / sampleRate); // 将采样率转换为周期(毫秒)
  2700. // 根据选中的变量动态生成读取RAM的命令
  2701. let command = 'cmd.read_ram(';
  2702. let first = true;
  2703. selectedVariables.forEach(varName => {
  2704. const variable = variables.find(v => v.name === varName);
  2705. if (variable) {
  2706. if (!first) {
  2707. command += ',';
  2708. }
  2709. command += `0x${variable.addr.toString(16).toUpperCase()},${variable.size}`;
  2710. first = false;
  2711. }
  2712. });
  2713. command += `,${period})`;
  2714. // 发送命令到串口
  2715. if (window.microLinkTerminal && window.microLinkTerminal.isConnected) {
  2716. window.microLinkTerminal.sendCommand(command);
  2717. console.log('发送命令:', command);
  2718. console.log('采样率:', sampleRate, 'Hz, 周期:', period, 'ms');
  2719. console.log('选中的变量数量:', selectedVariables.size);
  2720. } else {
  2721. alert('请先连接串口');
  2722. }
  2723. });
  2724. }
  2725. });
  2726. </script>
  2727. <!-- ECharts - 使用本地文件 -->
  2728. <script src="assets/echarts.min.js"></script>
  2729. <!-- 备用CDN -->
  2730. <script>
  2731. if (typeof echarts === 'undefined') {
  2732. document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"><\/script>');
  2733. }
  2734. </script>
  2735. </body>
  2736. </html>