This document captures the design of the debug-mode firmware and host UI
ahead of coding. It is the working notebook for the debug branch.
原顺序是「CAUTION 致命高压 → 镜像声明」。改为镜像声明在前:立创页面的读者第一件要知道的事是「这页可能不是最新的、以 GitHub 为准」,这决定他后面读到的每一条参数是否可信;安全警告紧随其后,仍在首屏内,且封面之前。
原镜像声明是琥珀色左竖线提示块,与红色 CAUTION 是同一套「彩色左竖线 + 小号大写标签 + 正文」的版式,只差色相。两块紧邻时形状雷同,弱化了 CAUTION 的警示力,也让读者要靠颜色而不是形状去区分二者。
改为另一种形状:白底卡片 + 顶部 #0f2b46 深色标题条(沿用表头结构色)+ 内容行,右侧放深色按钮。结构与 CAUTION 完全不同,一眼可辨;同时把琥珀色让回给「会损坏设备或买错」的语义(4.3 BOM 等),不再被文档元信息占用。
- 正文末尾的内联链接
github.com/AzidoPP/HVCCPS-V1.4删除,改由右侧「前往 GitHub 主仓库 →」按钮承担跳转,避免同一目标出现两次。 - 按钮沿用 Star 说明卡原有的按钮样式(
#0f2b46底 + 1 px 同色边 + 13.5 px / 700),保持整页只有一种「主操作按钮」外观。
- 文案「欢迎在 GitHub 点亮 Star ⭐」→「欢迎收藏和点赞本工程 ⭐」。立创页面上没有 Star 这个动作,收藏与点赞才是读者手边能点的按钮;让号召对应当前页面实际存在的交互。
- 删除该卡片右侧的「前往 GitHub 主仓库 ⭐」按钮:跳转已由顶部镜像声明卡承担,此处保留会形成第二个同目标按钮,且与新文案(在本页收藏点赞)指向相反的动作。卡片相应由 flex 行简化为单栏文本块,边框、内边距、外边距不变。
此前 readme_lc.md 的前 11 行(# HVCCPS V1.4 标题、4 个 markdown 徽章、📺 视频链接、QQ 群)留在版式容器之外,落在立创页面的裸底色上,字号、间距、颜色都由平台默认样式决定:整页最重要的身份信息反而是唯一没有设计的部分,且与下方容器之间有一道明显的断层。全部移入容器并重排:
- 报头块(容器最顶部):等宽小号 overline
DIGITAL HIGH VOLTAGE DC-DC POWER SUPPLY+ 31 px#0f2b46标题 + 一行项目定义,右侧底对齐放 3 个徽章(B 站粉丝 / YouTube / GitHub last-commit),下方 2 px#0f2b46底线——与 h2 的底线同一语汇,标明这是文档的最高层级标题。 - markdown 徽章语法改写为
<a><img style="height: 20px">:容器内是 HTML 块,markdown 图片语法不保证被解析;URL 中的&一并转义为&。 - 视频与 QQ 群做成报头下方的一行行内条(徽章之后、镜像声明之前):白底、1 px 边框、7 px 上下内边距,「小号灰标签 ┃ 竖分隔 ┃ 值」横向排布,宽度由内容决定。刻意不做成卡片:页面上已经有「深色标题条卡片」(镜像声明)、「左竖线提示块」(CAUTION / NOTE)、「标签在上值在下的数据格」(2.3 连接器)三种块,再加一种同构的只会让首屏四块长得一样;行内条高度只有约 34 px、不占满宽,形状上一眼可分。
- 封面卡相应只剩图片:原脚注条里的 overline 与项目定义已上移到报头,视频与 QQ 群前移到报头下,脚注条不再有内容。
- Star History 图移到文末致谢块,加分隔线与「GitHub Star 增长」小标题,限宽 460 px。它是 GitHub 侧的增长曲线,属于「回看」而非「决策」信息,放首屏会把目录挤到折叠线以下。
按要求把 CAUTION / NOTE / IMPORTANT / WARNING 四类提示块的外观回退到 2026-08-02 版式重做之前(commit 85523d1)的样子,共 13 处:
- 几何:
border-left: 5px solid+border-radius: 0 10px 10px 0+ 无外框、无阴影,取代重做后的「1 px 外框 + 4 px 左竖线 + 1 px 阴影」。 - 标签:由 11.5 px 大写字距标签(
<div>)改回正常字号的加粗彩色词(<strong>+<br>),顶部致命高压那条恢复 16 px 与⚠️ 前缀。 - 配色回到旧版:CAUTION
#d73a49/#fff5f5,NOTE#2563eb竖线 +#1d4ed8标签 /#eff6ff,IMPORTANT 与 WARNING 用旧版 WARNING 的#f97316/#c2410c/#fff7ed(旧版没有 IMPORTANT 类)。4.3 IMPORTANT 正文内的链接色同步由#8a5a00改为#c2410c,与所在块一致;BOM 表内标注最易买错项的琥珀色#8a5a00未动,正文「表中琥珀色标注」的说法仍成立。 - 其余区块(镜像声明卡、目录、数据格、图例、灰底降权块、表格)一律未动。
判断标准统一为「复刻的人看到这一段要拿它做什么」:能直接决定动作的留下,只是把画面复述一遍的删掉,只有一两个数字却要单独立框的收回正文。
删除:
- 文末的「GitHub Star 增长」小标题与 Star History 图(立创页读者不需要 GitHub 侧增长曲线,且该服务经常限流返回坏图)。
- 两条复述画面的图注:「充电进行到第 6 秒的画面。」「恒压运行到第 9 秒的画面。」(对应
alt也一并去掉秒数)。其余图注保留——「底层 · 驱动、控制与采样电路」「示波器探头连接方式。」「端子先焊后拧,避免直接在输出端子上施焊。」这类是在告诉读者看什么或怎么做。 - 第 8 章「项目不保存 MCU 厂商手册副本;使用的官方资料入口见 MCU 参考资料」整段。
- 2.3 表下脚注「USART3 引脚分配为 PB10(TX)/ PB11(RX)。」——同一行信息在上方「串口通信」表里已有
USART3 · PB10 / PB11。
数据格收回正文(原本是把段落里的数字提出来做成白底方块,数字只有两三个时纯属加戏):
- 4.5 输出电容:耐压 / 容量两格 → 正文「规格为 4000 V / 0.2 µF」。
- 7.6:充电耗时 / 等效容量 / 峰值输出功率 / 平均充电功率四格 → 正文一句带过。
- 7.7:设定输出电压 / 负载两格 → 正文「将 5 kΩ 铝壳电阻连接至输出端,设定输出电压 800 V」。
- 第 7 章测试仪器两格 → 两条 bullet(示波器 SDS804X、万用表 VC980D)。
- 7.6 / 7.7 同时由「图文并排」改为纵向堆叠:文字缩短后,右侧 195 px 竖构图会在左栏留下约 290 px 空白;并排本来就是为了填那段被删掉的数据格高度。4.5 的并排保留(左侧仍有两段文字,高度撑得住)。
其它:
- 5.3 板上接口:8 个各自独立的引脚标签 → 一条连排(外框 + 1 px 分隔线,无间隙),并在正文点明是 SH1.0-8P 端子、从 1 脚起数。原来的分离小方块看着像 8 个独立接口,与实际的联排端子不符。
- 2.3「高压输出连接器 / M3」→「高压输出 / M3 螺丝孔」:高压输出侧本就不是连接器。
Docs/BOM.xlsx、Docs/test-arc-10s.jpg两处链接文字去掉目录前缀,只留文件名(链接本身已指向具体文件)。- 波形图例改用示波器实际线色:红
#F4459C、黄#F4F445、蓝#45F4F4,共 12 个色块;色块描边统一为中性#9aa7b4,避免描边把纯色改观。原先用的是文档配色里的红/黄/蓝,与截图里的实际线色对不上,图例就失去了对照作用。
入口图形化:
- 视频与 QQ 群两条行内条前各加一个 24 px 色块图标:哔哩哔哩用用户提供的白色 SVG(原样 base64,放在
#00A1D6底色块上,白色描边在白底上看不见,所以给它配底色块),QQ 群用官方企鹅剪影(#12B7F5底)。有了图标后去掉原来的 📺 emoji。 - 「前往 GitHub 主仓库」按钮加 GitHub Octocat 白色图标。
- QQ 与 GitHub 两个图标取自 Simple Icons(CC0)的
icons/qq.svg与icons/github.svg,raw.githubusercontent / jsDelivr / unpkg 三个源取回的字节一致;path 提取后重新包成fill="#fff"的 24×24 SVG,再转 base64 内联,不引外链资源。 - QQ 群那条整条改为
<a target="_blank">,指向群主生成的官方加群链接qm.qq.com/cgi-bin/qm/qr?k=…&authKey=…(桌面点开中转页唤起 QQ,手机直接拉起 App;URL 中的&转义为&)。文案补「点击加群」蓝字,与哔哩哔哩那条的「在哔哩哔哩观看」对称,让两条行内条的可点位置一致;群号仍以等宽显示,万一立创清洗外链,读者还能手动搜群号。加群链接含群专属k/authKey,只能由群主在 qun.qq.com 或手机 QQ 群设置里生成,无法拼造。
提示块(承接上一轮的旧版式恢复):
- 标签只留
CAUTION/NOTE/IMPORTANT/WARNING,去掉「· 致命高压」「· 采购前必读」这类后缀和⚠️ 前缀——正文第一句就是说明,标签再复述一遍是冗余。 - 旧版式只有左竖线和浅底色,浅底色(
#fff5f5/#eff6ff/#fff7ed)落在页面渐变底色上时右侧和下边界会糊在一起。补 1 px 同色系描边(#f0c4c8/#c5d9f7/#f6d4b4)+0 1px 3px rgba(15, 43, 70, 0.10)阴影,四边都有明确边界,左竖线仍是主识别特征。
立创页面不接受 base64 内联图片,所以第三轮内联的三个 SVG 图标必须落到平台图床上。约束是「不允许外链图片,外链 href 允许」,因此顶部三个 img.shields.io 徽章同样属于要处理的对象。
图床接口(POST https://oshwhub.com/api/common/pullImage,用户授权使用其 cookie)实测行为:
- 表单字段名是
image,不是file——其它字段名一律返回{"success":false,"msg":""},只有image会给出带 code 的具体错误,可据此判断字段名正确。 - 需要带
cookie和x-csrf-token(值为oshwhub_csrf的 URL 解码结果)。 - SVG 被拒:
code 101012。 - 96×96 PNG 被拒:
code 101019(图片上传异常),同一张图放大到 256×256 即成功,推测有最小边长限制。 - 成功返回
{"success":true,"code":0,"result":{"url":"https://image.lceda.cn/oshwhub/pullImage/<uuid>.png"}};回读校验:CDN 上的文件与上传字节数一致,PNG 头显示徽章为 colortype 6(保留 alpha)、图标块为 colortype 2(不透明),说明平台不会重新编码或拍平透明通道。
据此把图标改为光栅化 PNG 自托管:
-
三个图标先合成为 24 单位画布的方块(哔哩哔哩
#00A1D6底、QQ#12B7F5底、GitHub 用按钮同色#0f2b46底),再渲染成 256×256 PNG 上传。GitHub 那个原本是白色透明底,一旦平台把 alpha 拍到白底就会消失,改成与按钮同色的底块后无论如何都可见,且贴在按钮上看不出边界。 -
图标块自带底色后,行内条外层
<span>的background去掉,<img>尺寸由 15 px 改为 24 px 填满原来的色块位置;GitHub 按钮内图标改为 19 px,使字形高度与原来的 15 px 白色字形接近。 -
徽章一度也改为自托管静态 PNG(静态 shields SVG → 8 倍渲染 → 上传),随后实测立创允许
img.shields.io外链图片,已回退为原来的动态徽章:粉丝数与 last-commit 恢复实时。自托管的三张静态徽章图留在图床里未被引用。更准确的结论(第五轮实测):平台放行的是它自己解析 markdown 生成的
<img>,手写的外链<img src="https://img.shields.io/…">会被清洗掉;base64 内联图片同样不行。所以外链图必须写成 markdown 图片语法,平台侧图片(image.lceda.cn)用 HTML<img>正常。
其它改动:
- 2.2 表删除「固定占空比模式」整行——发布版文档不列调试用的开环模式。(6.1 上位机功能列表里的「固定占空比调试 · 发送开环指令前会弹出显式确认对话框」保留,它描述的是公开上位机确实存在的交互;如需一并删除再处理。)
- 5.3 的 SH1.0-8P 连排:每格补
min-width: 46px; text-align: center;,内边距由6px 12px改为6px 10px。原来格宽随标签字数变化(SWCLK 与 TX 差一倍),与实际端子等距排布不符。 - 报头标题写作
HVCCPS+ 非加粗灰色的/ 1.4(font-weight: 400、#5d6b7a,去掉 V 前缀):产品名与版本号是两级信息,靠字重和颜色分层比靠字号更省地方,读者一眼能看出斜杠后面是版本而不是名字的一部分。 - 大章节编号由
01–08改为「一、」–「八、」(正文 h2 与目录共 16 处),小节仍用阿拉伯数字(1.1 / 2.3 …)。编号后用顿号而非空格:中文序号靠标点断句,纯空格在等宽字体下会被读成两个独立词。顿号自带右侧留白,原 9 / 11 px 的margin-right相应收到 2 px。
说明:探测字段名时对 QQ 图标重复上传了 3 次(成功 3 个 URL,实际只用第 1 个),账号图床里会多出 2 张同样的孤儿图片,不影响页面。本轮保留了用户自己在文件里做的修改(标题、目录条目描述等),未回退。
手写 <img> 引外链图会被立创清洗,markdown 图片语法则能渲染。要在不动现有背景与版式的前提下换成 markdown,用的是 CommonMark 的块级规则:
- HTML 块遇空行结束,空行之后的内容重新按 markdown 解析,再空一行即可继续写 HTML。于是在报头右侧那个 flex 单元格的开标签之后插入空行,写三行 markdown 徽章,再空行接
</div>。 - 空行之后的行缩进必须 ≤3 空格,否则会被当成缩进代码块。因此 markdown 徽章顶格写,其后的
</div>用 2 空格缩进(缩进只是排版,不影响 DOM 嵌套)。 - markdown 生成的图片被包在
<p>里,而平台样式是.markdown-body p{line-height:inherit;margin-top:22px;margin-bottom:22px}(从static.oshwhub.com的 CSS bundle 里查到的实际规则)。直接放进 flex 行会把徽章顶偏 22 px。解决办法是把徽章单元格写成display: flex; align-items: center; height: 20px;:<p>作为 flex item 被按外边距框居中,上下 22 px 对称正好抵消,单元格对外仍然只有 20 px 高,因此报头的对齐关系完全不变。这个写法对margin: 0(编辑器预览用的.tinymce-content p)同样成立,不依赖具体数值。
验证手段相应升级:原来的预览脚本是直接把 .md 当 HTML 塞进浏览器,现在文件里含 markdown 语法,必须先过解析器。新增 platform.js——markdown-it(html: true)渲染 → 套上从平台 CSS bundle 里抓到的 .markdown-body 规则 → Chrome 载入测量,即模拟立创的实际渲染链路。
第一版的错:把徽章塞进报头 flex 行、用 height: 20px 的格子去抵消 <p> 的外边距,实际页面上徽章压住了标题、也压住了下面的行内条。原因在平台 CSS 里:.bytemd-preview .markdown-body{max-width:800px}——编辑器预览只有 800 px 宽,报头「标题 420 px + 徽章约 330 px」放不下就换行;换行后那个 20 px 高的格子里装着 22+27+22 = 71 px 的 <p>,上下各溢出约 25 px,正好盖住上方标题与下方行内条。教训是不要用固定高度去藏一个会溢出的段落:只要容器宽度变化就会翻车。
改成不依赖溢出的写法:徽章单独成块、放在标题下方,用 text-align: right 让它靠右(text-align 会继承进生成的 <p>,而 border/margin 不会),块本身用 margin: -14px 0 -8px 把 22 px 的段落外边距收紧到 8 px 和 14 px。即使外边距为 0 也只是贴紧,不会重叠。
platform.js 扩成宽度扫描,7 个断点各跑一遍并自动判定:横向滚动、徽章与标题 / 行内条 / 按钮的矩形相交、超出视口的元素、误判代码块、坏图。同时把平台对表格和链接的规则也纳入模拟(此前只模拟了 p 和 img):
.markdown-body table{display:inline-block!important;max-width:100%;overflow:auto;font-size:12px}、td{min-width:120px}、td,th{padding:12px 7px!important}——平台会接管我们所有的<table>,窄屏下表格内部横向滚动而不是撑破页面,这是平台的既定行为,不需要也无法用内联样式对抗(!important声明胜过内联非 important)。.markdown-body a{border-bottom:1px solid #d1e9ff}——平台给所有链接加浅蓝下划线。行内条和按钮因为内联写了border简写不受影响;目录 8 个条目原本没写下边框,会被平台画成浅蓝线,与设计里的#eef2f6分隔线不一致,补border-bottom: none覆盖。- 手机宽度下行内条会逐字断行(平台
word-break: break-word叠加窄容器,出现「项目说明视 频」)。改为每段文字white-space: nowrap、行内条自身flex-wrap: wrap:整段要么在同一行,要么整段折到下一行,不再从词中间断开。
- 平台链路模拟(markdown-it +
.markdown-bodyCSS + Chrome)扫描 7 个宽度:375 / 414 / 560(iframe 窄)/ 768 / 820(ByteMD 预览)/ 1024 / 1280。全部:横向滚动 = 否,徽章与标题 / 行内条 / 按钮相交 = 0,<pre>误判 = 0,坏图 = 0,三个徽章均加载成功。375 px 下徽章自动折成两行、行内条折成两行,均无重叠。 - 逐宽度截图核对报头、行内条、目录、镜像卡在各尺寸下的表现。
- 结构校验:716 个
style=全部闭合;标签配对无错配、无未闭合(提示块批量改写后专门复查过每块的闭合</div>);文件内已无残留 markdown 语法;13 处提示块全部带border-radius: 0 10px 10px 0。 - Playwright + 系统 Chrome 渲染
file://预览(视口 1100 px):0 个请求失败,0 个横向溢出元素,0 张坏图,全页高度 20125 px。 - 图片来源审计:全文 37 个
<img>,data:/base64出现次数为 0;src主机只有image.lceda.cn(34 张,自托管)和img.shields.io(3 个动态徽章,实测允许)。外链href(GitHub、哔哩哔哩、淘宝、加群链接等)保持不变。 - 徽章与图标实测渲染尺寸:徽章 61×20 / 75×20 / 65×20,行内条图标 24×24,按钮图标 19×19。
- 二至三倍缩放截图逐个核对:动态徽章(粉丝数、last-commit)正常拉取;哔哩哔哩电视头、QQ 企鹅、GitHub Octocat 均正确渲染;SH1.0-8P 八格等宽;目录与正文章节号为「一、」–「八、」,小节仍为 1.1 / 1.2。
- 逐屏截图核对:报头(overline / 标题 / 定义行 / 徽章底对齐)、带图标的行内条、镜像卡深色标题条 + 带图标按钮、CAUTION / NOTE / WARNING(描边 + 阴影与底色分界清晰)、SH1.0-8P 连排、测试仪器 bullet、7.6 / 7.7 纵向堆叠、图例色块与截图线色一致,均符合预期。
- 文件保持 CRLF 行尾(批量改写脚本一度写成 LF,已改回,避免整文件行尾变动混进 diff)。
仅改动 readme_lc.md 的版式与文案,未涉及固件、上位机与任何技术论断,README.md / README_zh.md 无需同步(GitHub 页面本就没有镜像声明块,其 Star 请求也仍然成立;GitHub 侧的标题与徽章保持 markdown 原样)。
readme_lc.md(立创开源社区镜像版)派生自一个较早的中文 README,README_zh.md 也落后于英文 README.md。逐节比对后确认参数表、烧录流程、校准步骤、测试章节三份文件内容一致,差异集中在 4.3 BOM 的采购避坑提示和 6.1 上位机的新功能说明。
- 开头段补
file://打开时浏览器无法持久保存串口授权、需重选串口;如需保留授权用http://127.0.0.1提供目录。 - 功能列表由 6 条补到 9 条:固定命令栏(定时运行中改目标不重置计时)、固定占空比调试的显式确认对话框、中英文切换不移动命令栏与导航控件、Tools 区域(运行摘要 / 帧检查 / 声明式自动测试序列)、校准表离线编辑与预览。
- 末尾补上位机模块化架构段:
AppHostUI/src/下core/protocol/device/ui/plugins分层,loader.js的HV.define()/HV.require()注册表使模块化源码仍可从file://由普通 script 标签加载,插件宿主与window.HVCCPSAPI。 - 顺带统一单位排版:
10uH→10 µH、300uH→300 µH、注意B值→注意核对 B 值、1:200/1:100与20A/30A补空格。
补齐相对 README.md 缺失的全部条目:4.3 的 IMPORTANT(BOM 以 Docs/BOM.xlsx 为准,勿用立创 EDA 工程导出的 BOM)、U6 ACS712-20A 整行、U9 的「1:200 不是 1:100」、R8 的「注意核对 B 值」、6.1 的九条功能与串口授权说明与模块化架构段、以及 Star 请求(并入文末致谢块,镜像页顶部不放)。
原版式的问题是颜色被用作装饰——顶部深色渐变横幅、14 px 圆角、0 18px 44px 大阴影、彩色药丸标签。一旦渐变和彩色块用于装饰,读者就无法把颜色当信号读,真正的 CAUTION 也就失效了。
先确定读者的决策链:封面 →「这是什么、能到多少」→「危不危险、我能不能做」→ 参数够不够 → 制板/买料 → 焊接 → 烧录 → 上位机 → 有没有实测证据。读者是目标驱动跳读,不是线性阅读。由此定下的原则:
- 颜色即状态:红
#b3261e= 致命/不可逆,琥珀#c98a00= 会损坏设备或买错,蓝#2a6db5= 补充说明,绿#16713a= 完成判据。其余全部中性(#0f2b46结构色 /#d5dde6边框 /#f6f9fc次级底)。 - 警告即时性:警告放在「即将动手」那一步,而非堆在开头。CAUTION 出现在开篇、4.4 上电前检查、6.2 校准、7.8 拉弧共四处。
- 数据用等宽字体:电压、引脚、地址、位号、
min(CV, CC, CP)一律 monospace,复现仪表的视觉惯例。 - 图是证据不是插图:按宽高比分档限宽,每张配图注说明「看什么」。
- 表格只放参数,长说明降级为表下脚注;流程给出完成判据。
- 克制:2 px 圆角、1 px 边框、零阴影、单一结构色。
具体改动:
- 顶部:删除深色渐变横幅。改为「致命高压 CAUTION」在前(危险优先于文档元信息)、镜像声明紧随其后的单行琥珀条。
- 封面下新增 2×4 关键指标条(输出电压 / 电流 / 功率 / 峰值效率 / 输入电压 / 开关频率 / 功率密度 / 整机重量),等宽大号数字,取代原来的彩色药丸。
- 导航药丸改为 4×2 目录,每项含章节号、标题和一行内容提要。
- 章节标题:等宽灰色编号 + 深色标题 + 2 px 底线;小节用 3 px 左竖线。
- 表格:深色表头 + 斑马纹 + 数值列右对齐 + 单位列居中;2.2 的锁存说明移到表下作为脚注。
- 2.3 由一张长短不齐的表拆为「控制器 / 串口通信」两张三行表并排 + 连接器数据条,左右等高。
- 3.4 控制策略:立创不渲染 mermaid,改为 5 步竖向信号链(编号列 + 标题 + 说明),末尾用左竖线块给出「三环并联的占空比限制器」结论。
- 4.1 制板参数、7 章测试仪器改为数据条。
- 4.3:IMPORTANT 提示置于小节最前;表中最易买错的五项(R8 B 值、Q1–Q4 来源、U7/U8 假货、U9 匝比、U6 量程)注意事项用琥珀色 700 字重标出,一眼可辨。
- 5.2 / 4.2 / 4.5 / 7.6 / 7.7 采用图文并排。
- 5.3 与 6.2 的步骤改为带编号列的表格式流程;5.3 第 3、7 步的「
LED_A点亮 / 持续闪烁」用绿色标为完成判据。 - 6.1 功能列表改为带分隔线的九行卡片,每行「加粗要点 · 说明」;模块化架构段用灰底降权(次要受众给次要视觉权重)。
- 7.x 波形图下方新增示波器通道图例:色块 + 颜色名 + 信号名。色块脱落时文字仍可读,故保留颜色名。此处颜色是对示波器实际线色的复现(数据色),与状态色不冲突。
- 7.8 保留「拉弧画面可能触发图片审核,请前往 GitHub 查看」的提示块,并把危险说明单独提为 CAUTION。
用 Playwright 载入渲染页读 naturalWidth/naturalHeight,得出实际宽高比后再定每张图的 max-width,而不是凭感觉给 width: 100%:
- 7 张竖构图(AR 0.56–0.75)此前被
width: 100%撑到 683–1093 px 高,芯片方向图最糟(820×1093)。按 AR 分档限宽后全部落到 356–479 px。 - 架构图(AR 1.83 / 2.08)限到 940 px 居中;ST-LINK 截图(AR 1.43)由 700 收到 560;上位机截图(AR 2.2)保留全宽,因其需要看清界面细节。
- 竖构图旁的短文字撑不住高度会留白。7.6 / 7.7 把段落里的关键数字(5.6 s / 550 µF / 390 W / 195 W,800 V / 5 kΩ)和 4.5 的电容规格(4000 V / 0.2 µF)提为数据格,既填充高度又强化「一目了然」——是内容重排,未新增任何技术论断。
- 标签配对与
style引号闭合脚本校验:792 个style=全部闭合,无未闭合标签。 - 31 张
image.lceda.cn图片 URL 与全部外链与改版前逐一比对,无丢失、无新增。 - Playwright + 系统 Chrome 渲染
file://预览(视口 1100 px):0 个请求失败,无横向溢出元素,全页高度由 23361 px 降至 20514 px(信息密度提高的同时更短)。 - 逐屏截图核对:指标条 2×4、目录 4×2、2.3 左右等高、BOM 琥珀标注、控制策略信号链、烧录/校准流程、通道图例、文末镜像提醒均符合预期。
背景与层次:
- 最外层容器加
linear-gradient(180deg, #f8fafc, #eef2f7 55%, #e9eef4)页面底色 +1px #dbe2ea边框 + 26 px 内边距。正文直接落在这层底色上,数据与证据类内容做成白底卡片浮在其上,形成「纸面 / 卡片」两级层次。 - 白底卡片、表格、数据格统一
box-shadow: 0 1px 2px rgba(15, 43, 70, 0.06);图片0 1px 4px rgba(15, 43, 70, 0.10);提示块0 1px 2px rgba(15, 43, 70, 0.05)。阴影只用于把元素从底色上「抬起 1 px」,不做景深。 - 灰底降权块(开发者说明、通道图例、拉弧提示、文末)刻意不加阴影,保持在底色平面上,与白色卡片形成对比——降权语义靠平面高度而非仅靠字号。
- 2.3 与 4.2 的并排列把阴影加在 flex item 上而非表格本身,避免深色表头与表体之间出现接缝。
删除与重排:
- 删掉 banner 下方的 2×4 关键指标条——8 项数据与 2.1 电气参数表完全重复,属于同一信息的两次呈现。
- 原位置改放 Star 说明卡(一行说明 + 深色「前往 GitHub 主仓库」按钮);文末致谢块相应去掉重复的 Star 句,只留 QQ 群。
- 5.2 烧录准备改为图左、硬件清单表右,横向更紧凑;设备照片限宽 300 px。
去掉「本地工程」式表述(立创页面的读者手上没有工程目录):
- 1.1「仓库提供」→「GitHub 仓库提供」;1.2「公开仓库」→「GitHub 公开仓库」,「本仓库公开内容」→「本项目公开内容」。
- 4.1「使用仓库中的 Gerber 制板文件下单」→「从 GitHub 仓库下载 Gerber 制板文件后下单」。
- 4.3「BOM 以本文档提供的 Docs/BOM.xlsx …为准」→「以 GitHub 仓库中的 Docs/BOM.xlsx …为准」;正文同步。
- 5.2 删除「建议先下载当前工程(ZIP)」,改为「IAP 烧录页面和上位机无需安装,可直接在浏览器中打开:在线烧录器、在线上位机;也可以从 GitHub 仓库下载后离线使用」。
- 5.3 第 5 步「推荐在已下载的工程中打开 BootLoaderHostUI/index.html」→「打开在线 IAP 烧录页面;也可以从 GitHub 仓库下载后离线打开」。
- 6.1「推荐下载工程后…打开 AppHostUI/index.html」→「用 Chrome 或 Edge 打开在线电源控制上位机即可使用;也可以从 GitHub 仓库下载后离线打开」。
- 6.3「本地页面 BootLoaderHostUI/index.html 和在线 IAP 烧录页面」→「在线 IAP 烧录页面(同样可从 GitHub 仓库下载后离线使用)」。
- 第 8 章「本仓库采用 / 本仓库不保存」→「本项目采用 / 项目不保存」,log.md 与参考资料标注为 GitHub 仓库中的文件。
- 6.1 末尾的模块化架构段保留原样:它描述的是源码分层结构,不是「你下载的工程」。
固件表:
- 5.1 表格中
Bootloader HEX/App HEX的直链删除,改为加粗纯文本——上一句已给出「可从 GitHub Releases 下载」,直链既重复又会随版本失效。起始地址与作用列不变。
验证(第二轮):
- 标签配对、
style引号闭合全部通过;31 张 CDN 图片零丢失。 - 残留关键词扫描:
当前工程/本工程/已下载的工程/下载工程后/本地页面/本仓库全部为 0;HEX 直链releases/download/v0.0.1与v0.0.2均为 0。 - Playwright 渲染:1140 / 900 / 700 / 480 / 390 px 五种宽度均无横向溢出,0 请求失败;桌面全页高度 20249 px。
- 截图核对:顶部(警告 → 镜像声明 → 封面 → Star 卡 → 目录)、3.2/3.3 白卡浮于底色、5.1 无链接表、5.2 图左表右均符合预期。
问题现象:
- 曲线明显在变化,但纵轴刻度(如
0/0.5/1.0)看上去"更新不及时"甚至完全不动;实际次级电压 1000 V 时,照刻度读出来只有 0.8 左右。
根因(Chrome + Playwright 实测确认,非推测):
- 刻度本身并没有失效。在 60 fps 下连续对比
axis_vSecV.min/max与落在 X 窗口内的数据极值,经历暂停/继续、语言切换、信号增删后,偏差次数为 0。 - 真正的问题是读错了轴。
buildScales()为每个信号建一条独立自动缩放的 Y 轴,按 index 左右交替排布;默认 3 个信号时左侧会叠放两条轴,而 Chart.js 把第 3 个信号的轴排在最外侧——视线从左边缘扫过去先碰到的就是它。 - 输出关闭时 IPRI AC 恒为 0,Chart.js 对
min === max的轴扩展为 ±1,于是这条轴长期显示-1 / -0.5 / 0 / 0.5 / 1,且因为信号本身不变而永远不变,看起来就是"刻度压根不更新"。 - 实测:VSEC 曲线 1000 V 的顶点画在像素 10.2,而该像素在最外侧那条轴上读数为 1.25。只有 index 0 的轴画网格线,其余曲线相对于网格没有任何可读的对应关系。
offset: true是误用。Chart.js 的scale.offset是把取值范围向两端各扩半格,并非把坐标轴位置错开;叠放轴因此多出半格留白,刻度与图区上下边缘、与网格线都对不齐。
改法:全图只画一条 Y 轴——基准信号那条。
AppHostUI/src/ui/chart-selection.js- 新增
reference(纵轴基准信号)状态,持久化到chart.reference.v1;resolveReference()保证它始终是已绘制信号之一,被移除时回落到第一条。 setReference()/referencegetter;restoreDefaults()一并复位,isDefault()计入基准信号(基准被改过时"默认"按钮可用)。
- 新增
AppHostUI/src/ui/views/telemetry-panel.jsbuildScales():去掉左右交替与offset,所有 Y 轴统一position: "left",仅基准轴display: true并画网格线;其余轴保持隐藏但照常自动缩放,曲线波形不受影响,只是不再产生任何刻度。隐藏轴仍保留position,否则 Chart.js 无法从axis_vSecV这样的 id 推断轴向。- 基准轴
maxTicksLimit由 5 提到 7,刻度更密(0/200/400/600/800/1000 而非 0/500/1000)。 buildDatasets():基准曲线线宽 2.4,其余 1.3,刻度归属一眼可辨。refreshChart():每帧不再重建整个scales对象,改为只写chart.options.scales.x的min/max。注意必须经由 Chart.js 的 option 代理赋值——直接改我们自己持有的原始 options 对象无效(已用最小复现验证:原地改写得到[0,100],经代理赋值得到[500,600]),因为 Chart.js 的 resolver 按对象标识缓存已解析选项。- 图例:点击 chip 即把纵轴切到该信号,
is-reference高亮;chip 保持<span>以免吃掉拖拽排序的dragstart,改用role="button"+tabindex+ Enter/Space;删除按钮stopPropagation。 - chip 上不放数值:右侧状态面板已经逐项列出全部读数,图例再放一份只是重复;需要某一时刻的值就悬停曲线,Chart.js tooltip 会给出该点的数值,暂停时同样可用(读的是冻结窗口里的数据),比"暂停瞬间的单个值"更有用。
AppHostUI/styles/views/telemetry.css.legend-chip.is-reference用信号色描边;.legend-name溢出省略。
AppHostUI/src/i18n/locales/{zh-CN,en}.js- 新增
plot.useAsAxis/plot.isAxisReference。
- 新增
README.md/README_zh.md- 上位机功能条目补充纵轴基准与暂停语义。
附带修复:图例 hover 提示被裁掉看不见
- 现象:图例 chip 的 hover 提示完全看不到。
- 根因(实测):
.chart-legend为了多信号横向滚动设了overflow-x: auto,按 CSS 规范另一轴的visible会被强制计算为auto,于是这一行同时成了纵向裁剪盒;图例行只有 42 px 高,提示按top: calc(100% + 6px)下坠后整体落在盒外被裁掉。裁剪发生在层叠之前,z-index: 80根本没有机会生效——不是被图表盖住。 AppHostUI/styles/base.css[data-tip]增加@supports (anchor-name) and (anchor-scope)分支:提示改为position: fixed+position-anchor锚定,包含块变为视口,任何祖先的overflow都裁不到它;position-area: bottom保持原来的居中下挂外观,position-try-fallbacks: flip-block在下方空间不足时自动翻到上方。anchor-scope把锚名限制在各自子树内,保证每个提示锚到自己的控件。- 原有
position: absolute规则保留为不支持锚定定位时的回退,无@supports的浏览器行为不变。
- 该分支对全站
[data-tip]生效,抽屉等滚动容器内未来新增的提示同样不会被裁。
同轮修复三项曲线交互问题:
1)横轴时间刻度首尾闪烁
- 现象:滚动时最左(
-16s-20s)和最右(-1s-4s)附近的竖向刻度会闪。 - 根因:X 轴范围每帧右移,而 Chart.js 自动刻度落在绝对时间的整数位置上,于是首尾刻度不断跨越窗口边界、反复进出;
toFixed(0)又让贴边刻度的标签在相邻整秒之间来回跳。实测同一页面不同帧的标签分别为-20s -13s -8s 0s/-20s -14s -9s 0s/-20s -12s -7s 0s——刻度集合逐帧在变。 AppHostUI/src/ui/views/telemetry-panel.js- X 轴改用
afterBuildTicks固定生成相对窗口的刻度:chartNow - k * X_TICK_STEP_MS(步长 4 s,窗口 20 s 正好 6 格)。网格线像素位置恒定、标签恒为-20s -16s -12s -8s -4s 0s,数据在其下滑动,闪烁从原理上不再可能。
- X 轴改用
2)暂停后新增信号会跳到最新时刻
- 根因:
refreshChart()无条件chartNow = performance.now()。暂停时数据是冻结的,但增删信号 / 切换基准轴 / 切换语言都会经syncChartConfig()调到它,把窗口推到当前时刻,冻结的数据整体被甩到左边界之外,看起来就是"强制同步到最新然后丢弃"。 AppHostUI/src/ui/views/telemetry-panel.jsrefreshChart()仅在未暂停时推进chartNow;暂停期间任何重绘都渲染同一段区间。
AppHostUI/src/device/telemetry.jssetPaused()只切显示状态,latest始终保持实时,状态面板 / 保护 / 插件不会跟着停在过去。冻结窗口内某一时刻的值通过悬停曲线读取。
3)暂停再恢复后中间的包没有渲染
- 根因:
sample()里paused会直接 return,暂停期间根本不采样,恢复后曲线断了一截。 AppHostUI/src/device/telemetry.js- 暂停只冻结显示,不再中断采样。
sample()去掉paused早退;保留窗口按暂停状态切换:未暂停按CHART_WINDOW_MS(20 s)裁剪,暂停时按PAUSE_RETENTION_MS(120 s)裁剪,既让冻结窗口在暂停期间不被裁掉(支撑第 2 点"当时的值"),又给内存一个上界。
- 暂停只冻结显示,不再中断采样。
README.md/README_zh.md:补充暂停只冻结显示、采样不中断。
4)悬停浮窗:标题是无意义的大数字,且贴在图区下边框上
- 现象:浮窗第一行的数字跑一会就变得很大、读不懂;曲线位置偏低时浮窗压在时间轴和下边框上。
- 根因:浮窗标题默认取 X 值,而 X 值就是
performance.now()毫秒——单调增长且与任何物理量无关(实测显示为3,605.6)。位置方面 Chart.js 只保证浮窗不超出 canvas,不保证不超出 绘图区;实测各视口高度下浮窗底边都落在chartArea.bottom之下、压住时间轴刻度。 AppHostUI/src/ui/views/telemetry-panel.jscallbacks.title返回[],去掉标题行;时间本来就在横轴上。- 注册自定义定位器
insidePlot:在nearest的基础上把锚点 y 按浮窗半高夹在chartArea内,任何面板高度下浮窗都完整位于绘图区内(面板比浮窗还矮时退化为居中)。 - 浮窗配色改为与全站
[data-tip]一致的白底 + 1 px 边框 + 等宽字体(--font-mono从 CSS 变量读取,不再复制一份字体栈),替换 Chart.js 的深色默认样式。
验证:
node --check通过:telemetry-panel.js、chart-selection.js、i18n/locales/zh-CN.js、i18n/locales/en.js。- 提示可见性验证:目标 Chrome 150 支持
anchor-name/anchor-scope/position-area/position-try-fallbacks;图例 chip 到<html>的祖先链上没有transform/filter/contain等会把 fixed 包含块夺走的属性。悬停后实测提示框position: fixed、尺寸正常(如 179×16、193×16),截图确认完整浮在图区之上;顶栏撤销按钮提示位置与改动前一致,无回归。 - 曲线交互三项验证(先跑满 22 s 填满整个 20 s 窗口再测)全部通过:连续 90 帧采集横轴刻度标签,只有 1 种取值
-20s,-16s,-12s,-8s,-4s,0s,刻度个数恒为 6;暂停后经真实信号选择器加入"占空比",窗口漂移 0 ms、该信号在冻结窗口内有 998 个点、其轴按这段历史缩放到[0,25],窗口右边界仍停在暂停时刻(该处 225.6 V,此时实时值已到 273 V);恢复后窗口内最大采样间隔 48 ms(约等于 20 ms 采样周期,而非 4.6 s 的暂停时长),窗口填充 19980 / 20000 ms;全程控制台零错误。 - 悬停浮窗验证:950 / 800 / 700 / 620 / 560 / 500 / 460 七种视口高度下,各自悬停当前窗口内最低的数据点,浮窗底边均在
chartArea.bottom之上 6 px(改动前为压在轴上),title恒为[],正文只剩信号名: 数值一行。 - 全部用例复跑无回归:纵轴基准 11 项、边界场景 10 项、曲线交互 10 项全部通过,控制台零错误。
- Chrome(Playwright,
file://直开)功能验证 11 项全通过:全图恰好一条 Y 轴;1000 V 顶点在该轴上读数为 1000(修复前为 1.25);隐藏轴仍自动缩放;X 窗口持续滚动;chip 不重复显示数值且恰有一个基准;点击 chip 切换纵轴;刷新后基准信号保持。 - 边界场景验证 11 项全通过:删除基准信号后纵轴回落到下一条;清空全部信号显示空态且不报错;恢复默认;暂停 + 中英文切换后提示文案正确;一次选中 14 个信号仍只有一条轴;全程控制台零错误。
- 从 release 同步新的常驻命令栏、按 key 的中英文 i18n、分层模块结构、插件系统、实时周期波形修复、固定占空比启动确认与离线校准编辑。
- 保留公开 main/release 的运行限制:CV 0..2200 V、CC 0..200 mA、CP 0..400 W;校准表规格仍为 0..2200 V / 0..200 mA。
AppHostUI/继续保持无构建步骤、可直接从file://打开;模块由普通 script 标签和src/loader.js的HV.define()/HV.require()注册表加载。- 验证:全部 56 个
AppHostUI/srcJavaScript 文件通过node --check;Chrome 从file://完成启动、语言切换与公开上限检查,控制台零错误。
更正背景:
- 此前把自写引导程序称作 "ISP"。实际上 ISP(In-System Programming,在系统编程)通常特指经 ST 出厂 System Memory Bootloader / 内置协议对芯片在系统内编程;而本工程是常驻 Flash 的用户自写引导程序,通过自定义 UART 协议改写 App 区,属于 IAP(In-Application Programming,在应用编程)。故统一更名为 IAP。
- 纯命名更正,不改任何逻辑 / 协议 / 时序 / Flash 布局 / LED 行为。
代码(git mv 保留历史):
BootLoader/Core/Src/boot_isp.c→boot_iap.c;BootLoader/Core/Inc/boot_isp.h→boot_iap.h。- 标识符:
BootIsp_Run→BootIap_Run;isp_loop()→iap_loop();枚举BL_LED_ISP→BL_LED_IAP;头文件保护宏BOOT_ISP_H→BOOT_IAP_H;boot_iap.c与main.c的#include "boot_isp.h"→"boot_iap.h",main.c调用改为BootIap_Run()。
工程 / 上位机 / 文档:
- Keil:
BootLoader/MDK-ARM/G474_BL.uvprojx的<FileName>/<FilePath>指向boot_iap.c。 - 上位机:
BootLoaderHostUI/index.html的<title>/<h1>由 "HVCCPS ISP" 改为 "HVCCPS IAP"(app.js、styles.css无 ISP 字样,无需改)。 README.md:「Bootloader(自写 UART IAP 引导程序)」章节及「IAP 协议」「IAP 等待握手」等术语统一为 IAP。
说明:
- 本条目以上的历史记录(2026-06-08 / 06-09 各条)仍按当时写法保留 "ISP" 字样,作为变更留痕;自本次起该功能一律称 IAP。
- 供应商文件(CMSIS / HAL)内的
ISP子串(如SCB->ISPR、DISP*等寄存器 / 宏)与本引导程序无关,未改动。
验证:
- 待 Keil 重新编译确认(纯重命名,预期
0 Error(s),Program Size 不变)。 - 上位机仅改
index.html文本,无 JS 改动。
背景 / 隐患:
- 擦除的唯一触发点是
receive_begin()收到参数合法的BEGIN(boot_isp.c):调用metadata_begin_update()(擦 metadata 页 page 7)+erase_app_area()(擦 App 区)。 isp_loop()失败后会led_error_blink()再回到receive_begin()无限等待下一个BEGIN。若上位机卡 bug / 被替换 / 脚本死循环,反复发合法BEGIN,则每轮(约 1~2 s)就擦 1 次 metadata + App 区;按 ~10,000 次/页寿命估算约 4 小时即可擦穿,代码层面原本无任何防护。- 当前
app.js每次点击只发 1 次BEGIN,不触发;噪声因帧 CRC32 + 参数校验也无法构成合法BEGIN。这是“异常上位机”风险,非噪声风险。
策略(按用户要求):
- 每次进入 ISP 最多允许擦除 1 次。该次擦除一经消耗(无论后续烧录成功或失败),后续任何
BEGIN一律拒绝;用户须按 RST 重新进入 ISP 才能再次烧录。成功烧录本就以NVIC_SystemReset()收尾 → 复位后预算自动刷新,正常流程不受影响。
代码修改:
BootLoader/Core/Src/boot_isp.c- 新增
BL_STATUS_ERASE_LIMIT = 12U、BL_MAX_ERASES_PER_ENTRY = 1U、文件级static uint32_t bl_erase_count;。 isp_loop()入口处bl_erase_count = 0U;——isp_loop每次开机/复位只进入一次,等价于“每次进入 ISP 刷新一次擦除预算”。receive_begin()参数合法分支前加预算判断:bl_erase_count >= BL_MAX_ERASES_PER_ENTRY时回STATUS=ERASE_LIMIT(detail=已用次数)且不擦除;否则bl_erase_count++后再metadata_begin_update+erase_app_area。参数非法仍走BAD_LENGTH,不消耗预算。
- 新增
BootLoaderHostUI/app.jsSTATUS_TEXT增加[12, "ERASE_LIMIT"],烧录被拒时上位机显示BEGIN failed: ERASE_LIMIT。
README.md- ISP 协议“烧录”条目补充“每次进入 ISP 仅允许一次擦除 / 失败重试须按 RST /
STATUS=ERASE_LIMIT”的当前行为。
- ISP 协议“烧录”条目补充“每次进入 ISP 仅允许一次擦除 / 失败重试须按 RST /
验证:
- Bootloader Keil 编译:
0 Error(s), 0 Warning(s);Program Size: Code=13034 RO-data=570 RW-data=52 ZI-data=2972,ROM ≈ 13.29 KiB,仍在 14 KiB(0x3800)代码区内(本次 guard 约 +150 B)。 node --check BootLoaderHostUI/app.js通过。- 行为需实机复测:正常烧录 1 次成功并复位;故意失败后不按 RST 再发
BEGIN应收到ERASE_LIMIT,按 RST 后可再次烧录。
问题现象:
- 连接串口后立即出现
Read loop stopped: Buffer overrun;随后点击烧录,等待约 15 s 报Flash failed: Bootloader did not respond.,按下位机 RST 也无反应。
根因:
BootLoaderHostUI/app.js的readLoop()用单层try/catch包住reader.read(),遇到第一个错误就log后退出循环,整个会话不再接收任何字节。- Web Serial 对可恢复传输错误(buffer overrun / framing / parity / break)的处理方式是把
ReadableStream置为 errored;正确做法是释放旧 reader、从port.readable重新getReader()续读,而不是当成致命错误退出。 Buffer overrun本身说明“有数据正在到达”——即 Bootloader 一直在正常周期发送READY(无有效 App 时常驻isp_loop,每 100 ms 一帧)。读循环一死,enqueueBytes不再被调用,所有readByte()超时 →readFrame()收不到READY→ 15 s 后判“无响应”。按 RST 也没用,因为上位机已不再监听。
代码修改(BootLoaderHostUI/app.js):
readLoop()改为while (port && port.readable && keepReading)外层循环:每轮getReader(),内层读到done或抛错后在finally里releaseLock()并置reader=null;可恢复错误只记日志(Serial read error (recovering): …)后重新取 reader 续读。- 新增模块级
keepReading标志;connect()置true且不再预先getReader()(交给readLoop),清空rxQueue/pendingReadResolvers,port.open()增加bufferSize: 4096降低突发溢出概率。 disconnect()改为置keepReading=false后reader.cancel()唤醒挂起的read(),锁由readLoop的finally释放,避免重复releaseLock。flashFirmware()开始处清空rxQueue,避免上一段READY流的残留字节打乱首帧解析。
核查(未改但确认正确):
- 帧 CRC 两端均为软件 zlib CRC-32(
crc32_sw_update_raw↔crc32AccumulateRaw),握手不依赖硬件 CRC,本修复后HELLO/READY/BEGIN/...可正常收发。 - Bootloader 数据块/镜像 CRC 用硬件 CRC(
crc.c:默认多项式 + 输入按字节位反转 + 输出位反转 + init0xFFFFFFFF,crc32_bytes末尾再^0xFFFFFFFF),等价于 host 端软件 zlib CRC-32,握手后不会再出现二次BAD_CRC。
验证:
node --check BootLoaderHostUI/app.js通过(语法正确)。- Web Serial 行为需浏览器 + 实机,无法在此环境运行;待连接 Bootloader 实机复测整套烧录流程。
目标:
- Bootloader 烧录 App 时,metadata 页每次更新只擦除 1 次,避免 BEGIN 写 UPDATING、结束写 VALID 各擦一次导致页寿命减半。
- 按实际 Bootloader 体积缩小常驻区,把 App 起始地址前移,释放给 App 使用的 flash。
- 修正新增
BootLoader/工程后.gitignore对 MDK 输出目录的路径假设。 - 加快 Bootloader 错误 LED 闪烁,避免肉眼看起来像常亮。
实际尺寸与分区:
- 先编译现有 Bootloader:
Total ROM Size = 13292 B (12.98 KiB),Total RO Size = 13244 B (12.93 KiB)。 - 代码区按 2 KiB page 对齐保留 7 页:
0x08000000-0x080037FF,共 14 KiB。 - metadata 页放在第 7 页:
0x08003800-0x08003FFF,共 2 KiB。 - App 起始地址前移到
0x08004000,App 区为0x08004000-0x0801F7FF,共 110 KiB;末尾0x0801F800-0x0801FFFF仍保留给 App 配置日志。
代码修改:
BootLoader/Core/Src/boot_isp.c- 更新 flash 布局常量:
BL_CODE_SIZE_BYTES = 0x3800、BL_META_PAGE_ADDR = 0x08003800、BL_META_PAGE_INDEX = 7、BL_APP_BASE_ADDR = 0x08004000。 - metadata 页拆成两个 8 字节标志窗口和一个记录区:
0x08003800:UPDATING标志窗口,写0x5A5A5A5A与反码。0x08003808:VALID标志窗口,写0xA5A5A5A5与反码。0x08003810:定长 metadata 记录区。
metadata_write(state, ...)拆为:metadata_begin_update(app_size, app_crc):擦 metadata 页 1 次,写 UPDATING 窗口,再写记录。metadata_mark_valid(app_size, app_crc):不擦页,只追加写 VALID 窗口。
- 记录本体
state字段固定为 erased/invalid 参与 record CRC,实际状态由两个标志窗口推导;这样 VALID 追加写不会改变已写 doubleword,也不会破坏记录 CRC。 - 镜像 CRC 或向量表校验失败时不再额外写 INVALID;保持只有 UPDATING 标志,下次复位不会跳 App。
- ISP 等待
BEGIN不设置超时;App 无效进入 ISP 时只持续等待上位机并周期发送READY,不把空等烧录请求判为故障。 - 非
BEGIN帧返回BAD_SEQUENCE后继续等待;ABORT返回ABORTED后继续等待; malformedBEGIN/ 擦写失败 / 后续传输或校验失败才进入故障提示。 - 真实故障调用
led_error_blink():LED_C 从灭态开始每 100 ms 翻转一次,形成 200 ms 周期快闪,共 8 次翻转后回到 ISP 等待态。
- 更新 flash 布局常量:
BootLoader/Core/Src/main.cError_Handler()全灯翻转延时从900000空循环降到250000,早期初始化失败也能明显闪烁。
BootLoader/MDK-ARM/stm32g474xx_flash.sct- Bootloader IROM 从整片 flash 缩到
0x08000000+0x00003800。
- Bootloader IROM 从整片 flash 缩到
BootLoader/MDK-ARM/G474_BL.uvprojx- IROM size 同步为
0x3800;IRAM 保持0x20000。
- IROM size 同步为
App/MDK-ARM/stm32g474xx_flash.sct- App IROM 改为
0x08004000+0x0001B800。
- App IROM 改为
App/MDK-ARM/G474_HVCCPS.uvprojx- App IROM start/size 同步为
0x8004000/0x1B800。
- App IROM start/size 同步为
App/Core/Src/system_stm32g4xx.cVECT_TAB_OFFSET = 0x00004000。
BootLoaderHostUI/app.jsAPP_BASE = 0x08004000,与 Bootloader READY/App 工程一致。
.gitignore- MDK 输出目录改为
**/MDK-ARM/...形式,覆盖App/MDK-ARM与BootLoader/MDK-ARM。
- MDK 输出目录改为
README.md- 当前版本 Flash 布局、App 向量偏移、metadata 双窗口格式、BootLoaderHostUI 地址范围和编译命令同步为当前实现。
验证:
- Bootloader Keil 编译:
- 命令:
& 'D:\apps\keil_v5\uv4\uVision.com' -b G474_BL.uvprojx -j0 -o build.log - 结果:
0 Error(s), 0 Warning(s);Program Size: Code=12886 RO-data=570 RW-data=48 ZI-data=2968。 - ROM 约 13.14 KiB,低于 14 KiB 代码区上限。
- 命令:
- App Keil 编译:
- 命令:
& 'D:\apps\keil_v5\uv4\uVision.com' -b G474_HVCCPS.uvprojx -j0 -o build.log - 结果:
0 Error(s), 0 Warning(s);Program Size: Code=43796 RO-data=17304 RW-data=244 ZI-data=5740。
- 命令:
- BootLoaderHostUI 基础语法校验:
node --check BootLoaderHostUI\app.js通过。
现象:STLink 刷入 Bootloader 后,进入 Web ISP 页面、连接串口、选择固件、点击烧录并按下硬件
RST,下位机三个 LED 全部常亮且不再变化,反复按 RST 现象相同;上位机日志停在
Sending 0x45 entry stream. Press the target RST now.,随后报 Flash failed: Bootloader did not respond.。
定位:
BootLoader/Core/Src/stm32g4xx_it.c被精简成空壳,缺失全部 Cortex-M 异常处理函数, 其中关键的SysTick_Handler没有定义。- 启动文件
startup_stm32g474xx.s的向量表项SysTick_Handler为[WEAK],未被强定义覆盖时 指向Default_Handler(死循环B .)。 - 死机链路:
HAL_Init()使能 SysTick 1 ms 中断 →MX_GPIO_Init()把 LED_A/B/C 全部拉到GPIO_PIN_RESET(低有效,三灯全亮)→SystemClock_Config()启动 HSE/PLL 期间约 1 ms 后 首个 SysTick 中断触发 → 跳进Default_Handler死循环 → MCU 卡死,永远到不了BootIsp_Run()。 - 结果:三灯保持
MX_GPIO_Init点亮的全亮态、USART3 从未真正服务、每次复位都一样确定性卡死, 与现象完全吻合。READY 握手用的是软件帧 CRC(与上位机一致),故并非 CRC 或串口配置问题。
修复:
- 在
BootLoader/Core/Src/stm32g4xx_it.c中恢复标准 CubeMX 生成的 Cortex-M 异常处理函数:NMI/HardFault/MemManage/BusFault/UsageFault/SVC/DebugMon/PendSV/SysTick,其中SysTick_Handler()调用HAL_IncTick(),使HAL_GetTick()/HAL_Delay()/各超时正常工作。 Bootloader 全程用轮询方式收发 USART3、CRC、FLASH,无需任何外设 IRQ,故只补回内核异常即可。 - 在
BootLoader/Core/Inc/stm32g4xx_it.h中补回上述处理函数原型。
验证:
- Keil 命令行编译 Bootloader:
& 'D:\apps\keil_v5\uv4\uVision.com' -b BootLoader\MDK-ARM\G474_BL.uvprojx -j0 -o build.log - 结果:
0 Error(s), 0 Warning(s);Code=12674 RO-data=570,远小于0x7800代码区。
文档:
- README.md 新增「Bootloader(自写 UART ISP 引导程序)」章节,记录 Flash 布局、启动流程、
ISP 拉取协议、元数据记录、LED 指示、上位机
BootLoaderHostUI/与编译命令。
Implemented the in-project Bootloader plus browser WebSerial ISP path for HVCCPS.
- Project layout now uses
BootLoader/for the resident firmware,App/for the power-control application, andBootLoaderHostUI/for the browser ISP. - Flash layout:
- Bootloader code:
0x08000000-0x080077FF - Bootloader metadata page:
0x08007800-0x08007FFF - App image:
0x08008000-0x0801F7FF - App config log page:
0x0801F800-0x0801FFFF
- Bootloader code:
- Bootloader startup behavior:
- Initializes GPIO, USART3 at 115200 8N1, and hardware CRC.
- Waits 50 ms for entry byte
0x45; if seen, stays in ISP mode. - Without entry byte, validates metadata, App vector table, and hardware CRC32 before jumping to
0x08008000. - If App metadata/state/CRC/vector is invalid, stays in ISP mode.
- UART ISP protocol:
- Entry sync uses raw byte
0x45. - Framed protocol uses SOF
HVBL, a 16-byte header, payload, and CRC32 trailer. - Host sends
HELLO; Bootloader repliesREADYwith App base, max size, page size, chunk size, version, and pairing magic. - Host sends
BEGINwith image size, image CRC32, App base, and parsed HEX address range. - Bootloader erases the App pages, then actively requests each
(offset,length)chunk. - Host answers each request with
DATA; Bootloader checks per-chunk CRC, writes flash doublewords, skips all-0xFFdoublewords, and verifies programmed bytes. - Final image verification uses the STM32 hardware CRC peripheral and the App vector table check, then writes VALID metadata and resets.
- Failure paths send
STATUSframes for timeout, bad sequence, bad length/offset, bad CRC, flash error, image CRC error, invalid vector, and abort.
- Entry sync uses raw byte
- Robustness notes:
- Metadata is written as UPDATING before erase/program and VALID only after full CRC/vector verification.
- Interrupted or failed updates leave Bootloader resident and App invalid, so the next reset remains in ISP mode instead of jumping bad code.
- Bootloader never writes below
0x08008000or into the App config page. - Jump to App deinitializes USART3/CRC/HAL/RCC, clears SysTick and NVIC pending/enables, sets VTOR/MSP to the App vector table, restores privileged MSP thread mode, and re-enables global IRQ before entering the App reset handler.
- LED behavior is active-low: idle C on, ISP A on, receiving A blink plus B on, error B on plus C blink, valid all on briefly.
- App relocation:
- App scatter file and Keil IROM start at
0x08008000with size0x17800. - App
system_stm32g4xx.cenables vector relocation withVECT_TAB_OFFSET = 0x00008000.
- App scatter file and Keil IROM start at
- Web ISP:
BootLoaderHostUI/index.html,styles.css, andapp.jsimplement a pure browser WebSerial tool.- Supports
.hexand.bin; HEX records are checksum-verified and must fit inside the App region. - The Flash action continuously sends
0x45while waiting for the user to press target RST, then performs HELLO/BEGIN/REQUEST/DATA/STATUS. - The progress bar is driven by Bootloader REQUEST offsets, so it reflects actual requested/programmed image progress.
- The UI checks Bootloader App base, App limit, and pairing magic before erasing.
- Verification:
node --check BootLoaderHostUI/app.jspassed.git diff --checkpassed for edited Bootloader/App/WebISP files.- Bootloader Keil build passed:
0 Error(s), 0 Warning(s),Program Size: Code=12634 RO-data=570 RW-data=48 ZI-data=2968. - App Keil build passed:
0 Error(s), 0 Warning(s).
- Moved Bootloader GPIO initialization before PLL/system-clock setup and made
Error_Handler()blink all three LEDs, so early clock/init failures are visible instead of appearing as all LEDs off. - Changed ISP mode to send periodic unsolicited
READYframes every 100 ms while waiting forBEGIN;HELLOstill gets aREADYresponse, but the host no longer needs to landHELLOin the first 50 ms entry window. - Reduced Web ISP entry-byte traffic from 64 bytes every 4 ms to one
0x45every 10 ms, avoiding a saturated 115200-baud TX queue that could delay the first protocol frame. - Web ISP now deasserts DTR/RTS after opening WebSerial when supported, reducing the chance that USB-UART auto-reset wiring holds the target in reset.
让设备脱离上位机也能用:为前面板两个按键各定义一组闭环运行预设(CC/CV/CP 目标 + 运行时间),保存到 flash,上电后无需连接上位机,按一下按键即可按预设参数运行。
- 之前
Core/Src/app_core.c的read_key_flags()只把 KEY_A(PB0)/KEY_B(PA7) 的原始电平打进心跳,不触发任何动作;本次为按键加上动作逻辑。 - 抽出两个公共函数,让“上位机命令”和“按键”走完全相同的启停路径:
app_start_run(cv_mv, cc_ma, cp_mw, duration_s, fixed_active, fixed_value):原APP_CMD_BIT_ENABLE分支的全部动作(夹紧目标、清 OCP/OTP、设占空模式、装运行计时、set_psfb_outputs(1)、变频锁定)。目标在内部夹紧,调用方可传原始值;duration_s == 0表示无限运行。app_stop_output():原APP_CMD_BIT_DISABLE分支的全部动作,并清 OCP/OTP 锁存,使下一次启动是干净的上电。parse_command()改为调用这两个函数;update_run_timer()定时到期也改走app_stop_output(),去重。
- 新增
process_keys()(在APP_Task()中每轮调用,位于process_rx()之后):基于HAL_GetTick()的 25 ms 防抖 + 上升沿检测,统一规则:- 输出已在运行(无论上位机还是按键启动)→ 任意按键按下都直接停机(面板安全急停,上位机 RUN 后断连也能在下位机关断);
- 输出关闭时,按下的那个键若其预设
enable==1→ 按预设闭环启动(A 优先); - 预设未启用 → 忽略。
- 预设从
ConfigManager_Active()的btn_a_*/btn_b_*读取;LED_B 仍由set_psfb_outputs()表示“输出运行中”,按键启动时同样点亮,提供脱机反馈。
Core/Inc/config_manager.h:HV_CONFIG_SCHEMA_VERSION1→2;HV_ConfigFieldId增加 40–49(A/B 各 enable/cc_ma/cv_mv/cp_mw/time_s);HVCCPS_Config末尾追加 10 个uint32_t字段(单位对齐命令路径:CV mV、CC mA、CP mW、time s,0=无限,enable∈{0,1})。Core/Src/config_manager.c:load_defaults()全部置 0(预设默认禁用,未配置时按键按下为安全空操作);value_type_for_field()/ConfigManager_GetField()/set_field()各加 10 个分支;validate_config()加上预设范围校验(新增HV_CONFIG_BTN_MAX_*上限)。- 因
sizeof(HVCCPS_Config)由 100→140 字节,旧 flash 记录 length 不匹配会失效,刷入本固件后首次上电回退到内置默认值一次(默认值即当前已调好的值),如有额外微调在上位机重新 Save 一次即可。
Core/Inc/app_protocol.h:APP_CONFIG_RESPONSE_MAX_LEN260→320(GET_SNAPSHOT 最大 = 25 头 + 2×140 + 2 = 307 B)。Core/Src/app_protocol.c:APP_Protocol_WriteConfig()末尾追加 10 个WriteLe32(与结构体/JS 同序);BuildConfigResponse()的2U*100U→2U*140U。
protocol.js:CONFIG_FIELDS末尾追加 10 个Buttons组 U32 字段(顺序严格匹配WriteConfig:A enable/cc/cv/cp/time,再 B);DEFAULT_CONFIG补 10 个 0;CONFIG_RECORD_SIZE100→140;配置响应解码上限 260→320(CONFIG_RESPONSE_MAX_SIZE)。- 新增独立 Presets 抽屉(与“设备常量”Configure 面板分离):顶栏 Presets 按钮 +
presetsDrawer,A/B 两列各含 Enable、CV(V)/CC(mA)/CP(W)、Run Time(s)+Continuous,底部 “Save Presets to Flash”,每列另有 “Run now (test)” 直接用表单值发命令测试。 app.js:bindConfigInputs()跳过group==="Buttons",预设字段绝不出现在 Configure→Defaults 网格;readOnePreset/readPresetForm/writePresetForm/updatePresetFormVisibility/refreshPresetValidation/sendSavePresets/sendRunPreset;sendSavePresets只对 10 个预设字段做 SET_FIELD(V→mV、W→mW、s、enable/continuous→0/1)再 APPLY_DRAFT+SAVE_DRAFT;applyConfigSnapshot()调writePresetForm()回填;updateSystemUi()在运行/未连接/配置忙时锁定保存与测试按钮。styles.css:新增.preset-columns两列布局、.presets-intro,沿用现有浅色工业风样式。
- Keil 全量重编译通过:
0 Error(s), 0 Warning(s);Program Size:Code=43780 RO-data=17304 RW-data=244 ZI-data=5740。 node --check HostUI2.0/protocol.js、node --check HostUI2.0/app.js均通过;预设元素 id 在 index.html 与 app.js 间一一对应。
- 检查 CubeMX 对 HRTIM Timer A/B 的更新源调整:
Core/Src/hrtim.c中 Timer A/B 已改为UpdateTrigger = HRTIM_TIMUPDATETRIGGER_MASTER,且RepetitionUpdate = HRTIM_UPDATEONREPETITION_DISABLED、ResetUpdate = HRTIM_TIMUPDATEONRESET_DISABLED,避免 A/B 自身 repetition 或 reset 绕过 master update 提前搬运 preload。 G474_HVCCPS.ioc同步体现 A/BNumberUpdateTrigger=1、UpdateTrigger1=HRTIM_TIMUPDATETRIGGER_MASTER,并关闭 A/B repetition/reset update。C/D/E 继续沿用 master update,HRTIM 六个定时器的 preload 搬运时机一致。- 应用层
hrtim_load_periods()/set_duty()不需要逻辑改动:周期和 compare 仍只写 preload,运行中变频由 HRTIM master ISR 在周期边界写入,下一次 master update 统一生效;软起动前馈覆盖逻辑仍成立。 - 修复 CubeMX/Keil 伴随改动:
MDK-ARM/stm32g474xx_flash.sct一度恢复为完整0x00020000IROM,会覆盖config_manager.c使用的最后 2 KB 配置日志页0x0801F800,已改回0x0001F800保留最后 2 KB。
- 修复闭环自动变频后再次启动偶尔沿用上次运行频率的问题。
Core/Src/app_core.c新增restore_base_frequency_when_output_off():- 当输出已经 disabled,且
g_current_freq_hz != active.base_freq_hz或仍有 pending 变频计划时,调用apply_active_config()。 apply_active_config()重新装载 active 配置中的base_freq_hz到 HRTIM 周期寄存器,并清 duty、PI 和变频 pending。
- 当输出已经 disabled,且
- 手动 Disable 分支已经会回到 active base frequency;本次补齐其他停机路径:
- 定时运行到期
update_run_timer()停输出后立即apply_active_config()。 - OCP/OTP 或其他使
power_enable_latched变 0 的保护停机,在控制 ISR 的公共 disabled 分支中归一化到 active base frequency。
- 定时运行到期
- 这样闭环模式下一次 Enable 总是从配置里的基准频率(默认 35 kHz,或用户 active 配置中的
base_freq_hz)开始,而不是从上一次自动优化后的运行频率开始。 - 验证:Keil 编译通过,
0 Error(s), 0 Warning(s);Program Size:Code=42964 RO-data=17304 RW-data=228 ZI-data=5556。
HostUI2.0/app.js增加保持连接模式:- 操作员点击 Connect 后设置
autoReconnectEnabled = true。 - 串口读错误、read loop 结束、
port.open()失败都会继续重试同一个已授权SerialPort。 - 手动 Disconnect 会清除重连 timer、关闭保持连接模式,并取消当前 reader/open 流程。
- 重连间隔为
AUTO_RECONNECT_DELAY_MS = 500,避免浏览器 WebSerial open/close 被紧密循环打爆。 - UI 顶栏显示
CONNECTING/RECONNECTING,Connect drawer 显示Auto reconnecting和 attempt 计数。 - 重连失败只更新底部状态行,首次和每 10 次 attempt 弹 toast,避免连续错误刷屏。
- 操作员点击 Connect 后设置
- README 的 HostUI 连接说明同步到当前行为。
- 验证:
node --check HostUI2.0/app.js通过。
按本次配置解耦目标,把“编译期硬件常量 / 可持久化配置 / draft 配置 / active 配置 / 运行状态”拆开,并把上位机的设置包机制重构为带请求序号和响应状态的配置协议。当前 duty、MCMP3、PI 积分、目标值、保护锁存等仍留在运行状态中,不进入配置持久化结构。
- 新增
Core/Inc/config_manager.h/Core/Src/config_manager.c:HVCCPS_Config保存可持久化配置:三环 PI、base_freq_hz、freq_policy、soft_start_step、自动变频计分阈值、锁定窗口、滤波系数、步进、升降频阈值、前馈gamma。- 维护
draft_config、active_config、flash_config三份配置;ConfigManager_GetSnapshot()返回 draft/active 及 revision、flash sequence、flags。 - 字段访问统一走 field id:
ConfigManager_SetDraftField()/ResetDraftField()/GetField(),每次修改先构造 candidate 并整体校验。 ConfigManager_ApplyDraft()只把 draft 复制到 active;硬件周期重载由app_core.c的apply_active_config()执行。config_flags由状态派生:flash valid、draft 与 flash 不一致、active 与 draft 不一致、flash 页整理标志。
- flash 持久化:
- 使用最后 2 KB flash page:
0x0801F800,page index 63。 - 每次
SAVE_DRAFT追加完整记录:magic、record version、schema、sequence、length、crc32、reserved、完整HVCCPS_Config。 - 启动时扫描有效记录并加载 sequence 最大的配置到 draft/active。
- 页满或遇到无效/半写记录槽时擦除该页,再以当前 draft 写入第一条新记录,避免在非擦除 flash 槽位上覆盖。
MDK-ARM/stm32g474xx_flash.sct将 IROM 缩到0x0001F800,保留最后 2 KB 给配置日志。
- 使用最后 2 KB flash page:
- 新增
Core/Inc/app_protocol.h/Core/Src/app_protocol.c:- 统一 checksum/xor、小端读写、float 读写。
- 新配置请求
0xC5,固定 16 字节:op / target / field_id / value_type / value / sequence / reserved / sum8 / xor8。 - 新配置响应
0xC6,uint16 length,可变长度:- 普通响应 27 字节。
GET_SNAPSHOT响应 227 字节,payload 为 draft + active 两份完整配置。GET_FIELD响应 32 字节,payload 为 value type + value。
- op:
GET_SNAPSHOT、GET_FIELD、SET_FIELD、RESET_FIELD、APPLY_DRAFT、SAVE_DRAFT、LOAD_FLASH、LOAD_DEFAULTS、FACTORY_RESET。 - status:OK、BAD_FIELD、BAD_TYPE、BAD_VALUE、FLASH_ERROR、NO_FLASH、LOCKED、BAD_REQUEST、BUSY。
Core/Src/app_core.c:- 引入
ConfigManager_Active()作为控制 ISR 的 PI/频率/软起动/变频参数来源。 - 删除运行中的配置结构,把硬件配置应用收敛为
apply_active_config()。 - 输出关闭时才允许配置修改、apply/save/load/default/factory reset;查询允许输出开启时执行。
APPLY_DRAFT后重载 HRTIM 基准频率并重置控制状态;SAVE_DRAFT只写 flash,不自动应用 active。- 配置响应经独立
config_tx_frame发送;若 UART 正忙则挂起,TX complete 后优先发送。 - 心跳新增
base_freq_hz、freq_policy、配置 draft/active revision、flash sequence、config flags,帧长为 297 字节。
- 引入
HostUI2.0/protocol.js:- 增加配置字段表、默认配置、配置表单校验、
buildConfigRequest()/buildConfigSetFieldRequest()、parseConfigResponseFrame()、配置响应流式 decoder。 - 心跳解析同步 297 字节布局,增加
baseFreqHz、freqPolicy、configDraftRevision、configActiveRevision、configFlashSequence、configFlags。
- 增加配置字段表、默认配置、配置表单校验、
HostUI2.0/app.js:- 配置表单不从 localStorage 取设备配置;连接后自动
GET_SNAPSHOT,用设备 draft 填充 UI。 - Apply:读取表单,逐字段比较 device draft,变化字段逐个
SET_FIELD,再APPLY_DRAFT,最后重新 snapshot。 - Save Flash:发送
SAVE_DRAFT并重新 snapshot。 - 增加 Sync Draft、Load Flash、Load Defaults、Factory Reset 操作。
- 输出运行时锁定配置修改类按钮;查询 snapshot 保持可用。
- 图表选择和 Cycle Plot 偏好继续使用 localStorage;设备配置以连接后查询的 draft 为准。
- 配置表单不从 localStorage 取设备配置;连接后自动
HostUI2.0/index.html:- 配置页增加
softStartStepInput、freqPolicyInput。 - Defaults 页增加 Sync Draft、Load Flash、Load Defaults、Factory Reset。
- 动态高级配置区显示自动变频参数,footer 增加 Save Flash。
- 配置页增加
- 更新当前版协议:心跳 297 字节、配置请求
0xC5、配置响应0xC6。 - 增加配置层、flash 日志、
config_flags、配置字段、配置接受约束说明。 - 更新 HostUI 配置行为和代码组织,标明
config_manager/app_protocol模块职责。
- HostUI 语法检查通过:
node --check HostUI2.0/protocol.jsnode --check HostUI2.0/app.jsnode --check HostUI2.0/history.js
- Keil 编译通过:
& 'D:\apps\keil_v5\uv4\uVision.com' -b G474_HVCCPS.uvprojx -j0 -o build.log- 结果:
0 Error(s), 0 Warning(s)。 - Program Size:
Code=42868 RO-data=17304 RW-data=228 ZI-data=5556。
软起动逻辑很简单:限制占空比的快速增长。每个控制 ISR 占空比最多增加 10%(绝对值),即把仲裁改成 min(CV, CC, CP, duty_last + 10%);命中软起动的那个 ISR,CV/CC/CP 三环积分本轮全部不积分(抗积分饱和)。软起动是硬性的,所有路径都必须走:固定占空比 100% 启动也要 ≥10 个周期才到 100%;变频前馈从 40% 跳 70% 时本周期最多到 50%。只限制上升、不限制下降(一个 ISR 内 80%→55% 允许)。仅下位机改动,协议/上位机不变。
- 新增
APP_SOFT_START_MAX_STEP = 0.10f(归一化绝对步进,放在APP_PI_MIN/MAX旁)。 - 控制 ISR 增局部
float duty_cap; uint8_t soft_limited;。 - 捕获参考点:在
if (power_enable==0) return;之后、固定占空比分支与freq_ctrl_consume_pending()之前,先取duty_cap = app.duty + APP_SOFT_START_MAX_STEP。关键:必须在freq_ctrl_consume_pending()把app.duty顶到前馈值之前捕获,否则前馈跳变会绕过限速。app.duty是上一个 ISR 实际落地的占空比(set_duty()末尾按量化后的 mcmp3 反算),是天然的"上次占空比"。 - 固定占空比路径:
set_duty(app.fixed_duty_value)→ 先if (fixed_duty > duty_cap) fixed_duty = duty_cap;再set_duty(fixed_duty),使固定占空比也逐周期爬升。 - 闭环路径:
duty = min(CC,CV,CP)之后加soft_limited = (duty > duty_cap); if (soft_limited) duty = duty_cap;,作为仲裁的第 4 个 min 项。pi_commit()的 integrate 参数由winner==X改为(soft_limited==0) && (winner==X)——命中软起动则三环都传 0,本轮都不积分。 - 前馈路径自动覆盖:
freq_ctrl_consume_pending()内set_hrtim_phase_raw(前馈)的预装寄存器写入,会被本 ISR 末尾set_duty(capped)覆盖(HRTIM preload,下个 master 重复事件只latch最后一次写入);且duty_cap用的是前馈写入前捕获的旧app.duty,故前馈跳变同样限速。consume_pending本身不改(避免动到已调好的变频;软起动期间变频处于 100 ms 锁定窗内,互不干扰)。 - 优化:热路径仅新增约 1 次浮点加(
duty_cap)+ 1 次比较(soft_limited)+ 3 个布尔与(pi_commit入参);无除法、无分支预测惩罚的循环。下降天然不命中(duty < duty_cap),无需单独判断方向。 - 与 enable/停机的衔接:停机/OCP/OTP/禁用都会
set_duty(0)把app.duty归 0,故每次重新 Enable 自动从 0 开始软起动,无需额外标志位或计数器。 - 编译:Keil
0 Error(s), 0 Warning(s),Program Size: Code=37788(较过温保护版 +24 B),RO-data=17304, RW-data=168, ZI-data=5024。
- 控制环路新增"软起动(Soft-start)"小节:公式
min(CC,CV,CP,duty_last+10%)、只限上升、全路径强制、参考点捕获时机、抗积分饱和、实现与开销。 - "运行模式"两条都注明受软起动限速(含固定占空比 100% 启动也要逐周期爬升)。
- "下位机关键数据流"控制 ISR 那行补上软起动 min 项与"命中则三环本轮不积分",并标注固定占空比模式在更早处已
set_duty(min(fixed, duty_last+10%))返回。
加一个最基础的过温保护:MOS 温度或 MCU 内部温度任一超过 75 °C 即强制停止输出,并在心跳协议里置位过热标志(用掉心跳 status_flags 仅剩的 bit 7);上下位机配套改动。同时修复文档若干不一致——尤其"下位机关键数据流"缺 TIM7/TIM6 中断逻辑——并复核心跳速率确为 20 Hz。
- 过温保护(OTP,纯软件):
- 新增
APP_STATUS_BIT_OTP = 0x80(心跳状态位)、APP_OTP_TRIP_MC = 75000(75 °C,单位 m°C)、APP_ControlState.otp_latched。 - 控制 ISR 在 OCP(FLT4)检测之后、关断分支之前插入 OTP 检测:
process_slow_adc_isr()已刷新g_mos_temp_mc/g_internal_temp_mc,任一> APP_OTP_TRIP_MC则锁存过温、清power_enable_latched/fixed_duty_active/mode。与 OCP 不同,过温无硬件关管通路,故在跳闸沿(otp_latched0→1)调用set_psfb_outputs(0)主动关断四管;之后的if (power_enable==0) { set_duty(0); return; }保持 duty 0。跳闸沿守卫避免每周期重复 stop。 - 粘滞锁存:恢复靠上位机重新 Enable ——
parse_command()的 enable / disable 两个分支各在clear_ocp_fault()旁清otp_latched=0;若仍 >75 °C,下一个控制 ISR 立即再次跳闸(输出至多被点亮约一个开关周期,热量可忽略)。 build_heartbeat()在 OCP 位之后追加if (app.otp_latched) status_flags |= APP_STATUS_BIT_OTP。心跳帧长不变(273),上位机协议无需改长度。- 上电安全性:MOS NTC raw 0 → 0 °C(查表钳位)、内部温度出错/为负 → 0,滑动平均从 0 起爬升不过冲,故开机不会误触发。
- 新增
- 编译:Keil
0 Error(s), 0 Warning(s),Program Size: Code=37764, RO-data=17304, RW-data=168, ZI-data=5024。
- protocol.js:
createLatest()增otpTripped:false;parseHeartbeatFrame()增latest.otpTripped = (statusFlags & 0x80) !== 0。 - index.html:状态 chip 区在
wdgChip后增otpChip(默认 hidden)。 - app.js:缓存
ui.otpChip;updateSystemUi()在 wdg chip 之后按latest.otpTripped显示红色OVER-TEMP · OUTPUT OFF · RE-ENABLE(is-danger),与 OCP/WDG 同一套提示机制,提示用户当前过热。 node --check(protocol.js / app.js / history.js)通过。
- status_flags 表:bit 7 由
reserved改为otp_latched(MOS 或 MCU >75 °C,输出已关闭,需重新 Enable 恢复)。 - 新增"过温保护(OTP)"小节(接在"硬件过流保护(OCP)"之后):阈值、跳闸动作(软件
set_psfb_outputs(0)关管 vs OCP 硬件关管)、粘滞恢复、上电安全、上位机告警。 - "下位机关键数据流"补全:原图缺 TIM7/TIM6 两个中断。补入 TIM7 10 ms(
freq_ctrl_score_and_plan生成变频 pending)、TIM6 10 ms(看门狗活性检查 + 喂狗)两个 IRQ 块;并在控制 ISR 块补上原本也缺的wdg_activity标志、OCP(FLT4)与 OTP 检测步骤,注明 NVIC 优先级(HRTIM 0,0 / TIM7 2,0 / TIM6 3,0)与"两定时器经HAL_TIM_PeriodElapsedCallback按实例分发、均为 100 Hz/10 ms"。
- 固件
APP_HEARTBEAT_INTERVAL_MS = 50U(50 ms)→ 20 Hz;send_heartbeat_if_due()据此节流。 - README 多处("心跳包(20 Hz)"、"50 ms(约 20 Hz)"、"20 Hz 时上行带宽 ≈ 5.46 kB/s"、数据流注释)均为 20 Hz,无矛盾。本次仅把数据流注释顺带写明"每 50 ms 一帧",未改速率。
配合后续要加的变频机制,把 HRTIM 从"按频率自动分配分频"改成"每个定时器分频固定,变频只重载周期寄存器";控制 ISR 改挂 master 重复事件;额定频率改 11–45 kHz、默认 35 kHz;心跳包用真·当前频率取代一批 HRTIM 原始量。CubeMX 侧(hrtim.c/.ioc:master/A/B 固定 MUL4、C/D/E 固定 MUL32、master 中断源 MREP、全员 preload、统一 master 更新、ADC Trigger 3 由 Timer E 16× 驱动)已在前序提交完成,本次为固件逻辑层 + 上位机的配套改动。
- 控制 ISR 改挂 master 重复事件:回调
HAL_HRTIM_Compare2EventCallback→HAL_HRTIM_RepetitionEventCallback(HAL_HRTIM_IRQHandler对 master 的MREP标志调用此回调;RepetitionCounter=0故每个开关周期一次)。更新 ISR 头注释:现在在周期边界进入,刚结束周期的 24 高速 + 16 慢速样本已全部就位,全周期聚合更干净;新 duty 经 preload 在下个重复事件生效。 - 额定 11–45 kHz / 默认 35 kHz:
APP_FREQ_MIN_HZ10000→11000、APP_FREQ_MAX_HZ50000→45000、load_default_settings()的freq_hz50000→35000。 - 去掉自动分频分配:删除
select_hrtim_prescaler()、APP_HRTIM_PRESCALER_TABLE、APP_HRTIM_BASE_CLK_TABLE;新增APP_HRTIM_MASTER_BASE_CLK_HZ(680 MHz,MUL4)与master_prd_for_freq()(floor(680 MHz / freq))。validate_settings()改为只校验频率范围并显式保证 master PRD ∈[256, 0xFFDF]。 - 变频不再重配 HRTIM:
reconfigure_hrtim()→hrtim_load_periods(),仅用__HAL_HRTIM_SETPERIOD/__HAL_HRTIM_SETCOMPARE预装 master/timA/B/C/D/E 六个周期与 MCMP1/MCMP3、timA/B CMP1;删去死区重写(固定 34 ticks,与频率无关)和已无用的 master CMP2 写入。 apply_settings()精简:去掉「停输出 / 停计数 / 停 5 路 ADC DMA / 清缓冲 / 重新 arm / 重启」整套,只剩:算 4 个周期 →hrtim_load_periods()→ 更新g_derived→ 复位三环 PI → 相位归零。依据:每周期采样点数(24/16/10)与频率无关,循环 DMA 一次对齐后跨变频持续保持;且parse_settings()只在输出关断(power_enable_latched==0)时受理,ISR 此时强制set_duty(0),对g_derived的撕裂读不敏感。- 一次性启动逻辑移入
APP_Init():预装默认频率 → 先 arm 5 路 ADC DMA → 再WaveformCountStart_IT(APP_TIMERS_ALL);删去冗余的手工g_derived/app.mcmp*初始化(统一由apply_settings()设置)。 - 心跳新增真·当前频率:
APP_DerivedConfig删prescaler / timc_prd / timd_prd / time_prd / base_clk_hz,加actual_freq_hz(=(680 MHz + PRD/2) / PRD,即取整后的实际频率)。build_heartbeat()把 duty 之后的 4×uint16(mcmp1 / mcmp2 / mcmp3 / timc_prd,共 8 字节)替换为current_freq_hz(uint32,4 字节),并删掉每包 3 次__HAL_HRTIM_GETCOMPARE重读。心跳帧长 277 → 273。APP_ControlState删mcmp1_raw/mcmp2_raw(mcmp3_raw保留,控制环用)。 - 编译:Keil
0 Error(s), 0 Warning(s),Code=32424。
- protocol.js:心跳镜像同步——
HEARTBEAT_PAYLOAD_SIZE的(4*2)→4,createLatest()/parseHeartbeatFrame()把mcmp1Raw/mcmp2Raw/mcmp3Raw/timcPrd换成currentFreqHz(readLe32),帧长 273。删除HRTIM_PRESCALERS、HRTIM_TIMC_BASE_CLK_HZ、LEAD_RAW、pickHrtimPrescaler、hrtimTickNsFromPrescaler、timcPrdForFreq及其导出。validateSettingsForm()频率改 11000–45000 且去掉 prescaler 派生(返回{ errors })。DEFAULT_SETTINGS.freqHz50000 → 35000。 - app.js:导入去掉
LEAD_RAW/pickHrtimPrescaler/timcPrdForFreq;删 4 个 Raw 信号(MCMP1/2/3、TIMC PRD)及DEBUG_METRIC_KEYS、CHART_GROUP_ORDER的"Raw";新增 Control 组FREQ信号显示真·当前频率(kHz)。refreshSettingsReadouts()与 apply 路径去掉derived,频率摘要改为set X kHz · live Y kHz。单周期波形:周期由currentFreqHz反算、相位用duty/2(不再依赖 mcmp2/mcmp3/timcPrd),live 判定改用currentFreqHz。运行摘要频率优先取遥测真值。 - index.html:
ctrlFreqmin/max 10000/50000 → 11000/45000,note 改为 11–45 kHz(固定 MUL4、在线重载周期),频率标签注明set · live。 node --check通过(protocol.js / app.js / history.js)。
- 核对 Claude 修改后,发现主体功能已基本完成:ISR 已挂 master repetition,频率范围/默认值、心跳
current_freq_hz、HostUI 协议解析与显示均已同步。 - 修复开机默认频率落地问题:
APP_Init()中apply_settings(&defaults)只写 preload shadow,补充HAL_HRTIM_SoftwareUpdate(... master/A/B/C/D/E ...),在 HRTIM 计数启动前把 35 kHz 默认周期强制转入 active register,确保第一拍就是默认频率。 - 修复 HostUI 旧 localStorage 兼容:若本地保存过 10–50 kHz 旧频率(如 50000 Hz),加载设置时回退到当前默认 35000 Hz,避免页面一打开就处于非法配置。
- 修复
HostUI2.0/index.html中 3 处已损坏的分隔符显示,避免界面出现替换字符。 - 修复
README.md心跳描述:固件实际APP_HEARTBEAT_INTERVAL_MS = 50,因此当前协议说明更新为 20 Hz、50 ms、约 5.46 kB/s;数据流注释同步为 20 Hz。 - 修复
MDK-ARM/G474_HVCCPS.uvprojx根节点 schema 属性,恢复xsi:noNamespaceSchemaLocation前缀,避免 Keil 工程 XML 被格式化工具改成非预期属性名。 - 复测:
node --check HostUI2.0/protocol.js/app.js/history.js通过;Keil 编译通过,0 Error(s), 0 Warning(s),Program Size: Code=32504, RO-data=16952, RW-data=156, ZI-data=4900。
Todo 里的自动变频本次不加,仍由上位机设定频率;只完成"固定分频 + 在线重载 + ISR 挂 repetition + 真·当前频率"这套基础设施。
Debug Tools 浮窗扩展为上次 Enable 窗口运行摘要,用来承载这类临时/调试小功能。
HostUI2.0/index.html:Debug Tools 内的工具为Last Enable Summary,显示上次 Enable 窗口的 duty、efficiency、Ipri、Isec、Vpri、Vsec 结果,以及窗口 packet 数和持续时间;提供View Summary与Copy按钮。HostUI2.0/styles.css:Debug Tools 固定在左下角,默认折叠;z-index 降到抽屉/backdrop 下面,打开 Configure/Run/Connect 抽屉时不会挡住频率等设置控件。HostUI2.0/app.js:createRunSummaryStats()维护当前 Enable 窗口与上次封存窗口的统计量。parseHeartbeat()在解析前记录旧powerEnable,解析后按设备心跳里的powerEnable做边沿检测:false→true 开始新窗口;运行中每个 heartbeat packet 累计 duty、Ipri、Isec、Vpri、Vsec;true→false 封存为上次窗口。- duty、Ipri、Isec、Vpri、Vsec 都先按窗口 packet 做算术平均,efficiency 再按
avg(Isec)*avg(Vsec)/(avg(Ipri)*avg(Vpri))计算,避免逐包 efficiency 平均被低输入功率样本拉偏。 - frequency 在窗口开始时按上位机当前设置锁定一次,用于复制结果中的
f字段。 - 串口断开时若当前窗口仍在运行,则按断开时刻封存。
View Summary展示复制字符串;Copy一键复制f: {frequency}Hz, duty: {duty}%, eff: {efficiency}%, Ipri: {Ipri}A, Isec: {Isec}mA, Vsec: {Vsec}V, Vpri: {Vpri}V,并提供document.execCommand("copy")fallback 以兼容navigator.clipboard不可用的环境。
- 校验:
node --check HostUI2.0/app.js、protocol.js、history.js通过。只改上位机和文档,未重新编译固件。
根据实测结果,死区统一固定为约 200 ns,关闭并删除上位机的死区配置入口,同时删除独立 HRTIM 时序可视化编辑器。采样逻辑保持当前实现不动。Keil 编译通过,0 Error / 0 Warning,Code=33880, RO=16952, RW=156, ZI=4924。 上位机基础语法校验通过:node --check HostUI2.0/protocol.js、node --check HostUI2.0/history.js、node --check HostUI2.0/app.js。
Core/Src/app_core.c:APP_SETTINGS_LEN从 34 改为 32,设置包不再解析deadtime_ticks。APP_Settings删除deadtime_ticks字段,validate_settings()删除死区范围校验,默认设置不再保存死区值。reconfigure_hrtim()删除deadtime形参,A/B 两路HRTIM_DeadTimeConfig始终使用DEAD_TIME。Core/Inc/main.h与.ioc里DEAD_TIME=34保持一致,对应34 * 5.882 ns ~= 200 ns。
HostUI2.0/protocol.js:SETTINGS_FRAME_SIZE改为 32;DEFAULT_SETTINGS删除deadTimeNs;删除deadTimeTicksFromNs()和相关死区校验/写帧逻辑。HostUI2.0/app.js:设置表单只读取/保存 PI 参数和频率;旧 localStorage 配置经白名单加载,避免隐藏的旧deadTimeNs被继续存回;删除死区 readout 联动和 HRTIM 可视化联动。HostUI2.0/index.html:HRTIM 配置页显示固定200 ns fixed,删除死区输入框和 Open Visual Timing Editor 按钮。
- 删除
HostUI2.0/viz.js与HostUI2.0/viz.css。 index.html不再加载viz.js/viz.css,也删除了vizOverlayDOM。styles.css删除仅供 HRTIM 可视化和采样位置虚线使用的孤立样式。- 单周期波形
Cycle Plot保留,用于查看当前 24 点采样波形和 PWM 叠加。
README.md当前版本描述同步为固定 34 ticks / 约 200 ns 死区。- 设置包总览与字段表改为 32 字节,字段中删除
deadtime_ticks。 - HostUI 文件列表删除
viz.css/viz.js,配置页说明删除 HRTIM Visual Timing Editor。
新增硬件级 AC 过流保护与独立看门狗,并把过流/看门狗状态上报上位机。CubeMX 已生成 COMP1/DAC1/IWDG/TIM6 外设初始化(comp.c/dac.c/iwdg.c/tim.c),本条记录业务代码(app_core.c)、中断和上位机。Keil 编译通过,0 Error / 0 Warning,Code=33964, RO=16952, RW=156, ZI=4924。
- 链路:PA1(1:200 CT + 7.5Ω + 绝对值,同时给 ADC2 峰值遥测)→ COMP1(INP=PA1,INM=DAC1_CH1,非反相输出)→ HRTIM FAULT4(片上源=COMP1,active-high,FAULTFILTER_7 ≈ 188 ns)→ 异步把 TA/TB 四路强制 inactive(关管)。硬件配置全在
MX_HRTIM1_Init/MX_COMP1_Init/MX_DAC1_Init。 - 阈值 75 A(CSD19531)。
#define APP_OCP_TRIP_A 75、APP_OCP_TRIP_MV = 75*1000*15/400 = 2812 mV(与 IPRI_AC 标度i_ma=mv*400/15一致)。apply_ocp_threshold()把 DAC 码设为APP_OCP_TRIP_MV*4095/g_vcc_mv(用 VREFINT 反推的 VDDA,使绝对阈值不随供电漂移)。APP_Init里HAL_DAC_Start→apply_ocp_threshold→HAL_COMP_Start,在放输出前就武装好。 - 软件锁存/恢复:
app.ocp_latched新增。控制 ISR 每周期__HAL_HRTIM_GET_FLAG(FLT4),置位则锁存、清power_enable_latched/fixed_duty_active、mode=DISABLED(既有的power_enable==0分支负责set_duty(0)+return)。clear_ocp_fault()(清 FLT4 标志 + 清锁存)在parse_command的 ENABLE(先清再武装,等价常规上电)与 DISABLE 分支调用。恢复方式:上位机在 RUN 里重新 Enable;过流仍在则下个开关沿再次跳闸。 - 控制 ISR 在 fault 时仍运行(fault 只关输出不关 master 定时器),所以轮询可行,无需新开 fault 中断。
- IWDG 超时 ≈200 ms(
MX_IWDG_Init在main.c里先于APP_Init启动)。TIM6 中断每 10 ms 一次(HAL_TIM_Base_Start_IT在APP_Init末尾启动)。 - 活性标志
wdg_activity(volatile uint8):APP_Task每次|= APP_WDG_FLAG_LOOP(0x01),控制 ISR 每次|= APP_WDG_FLAG_ISR(0x02)。HAL_TIM_PeriodElapsedCallback(仅 TIM6)采样后清零,更新两个 miss 计数;两路都在最近APP_WDG_MISS_LIMIT=10拍(100 ms)内活过才喂狗,任一路连续 10 拍静默就停喂,由 IWDG 复位。RMW 竞争无害:每拍有数千次置位,且需连续 10 拍丢失才误判,方向上也只会"误判为死"不会"误判为活"。 - 复位原因:
APP_Init读__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)→wdg_reset_latched,再__HAL_RCC_CLEAR_RESET_FLAGS(),并早喂一次狗。
status_flags新增 bit5APP_STATUS_BIT_OCP(=app.ocp_latched)、bit6APP_STATUS_BIT_WDG_RESET(=wdg_reset_latched)。仅加位、不改帧长、不动协议其余部分。
protocol.js:createLatest加ocpTripped/wdgReset,解析statusFlags & 0x20 / 0x40。app.js+index.html:状态栏加两个仅在触发时显示的 chip —— OCP(红,"OCP TRIP · OUTPUT OFF · RE-ENABLE")、WDG(黄,"WDG RESET · CHECK FIRMWARE")。
- 全周期聚合后单点"采样位置"已无意义,按要求删除:
app.js/viz.js的SAMPLE_POSITIONS虚线标记渲染、CYCLE_SIGNALS的positions字段、showSamplePos开关(含index.html复选框与styles.css的.svg-sample-pos-*);protocol.js删除SAMPLE_POSITIONS常量及sampleIndexFor/interpolateSample(已全无引用)。原始 24 点波形折线保留,不改协议。 - 顺手修正
app.js单周期图 IPRI_DC 换算零点:l.aux5Mv/2→ 固定2500,与固件一致。 node --check通过:protocol.js / app.js / viz.js。
README.md:电源控制逻辑下新增「硬件过流保护(OCP)」「看门狗(IWDG)」两节;status_flags表补 bit5/bit6;PA1 引脚行补 COMP1_INP;单周期 Overlay 去掉采样位置虚线两处描述;protocol.js职责去掉"采样位置常量"。
ACS712 是霍尔原件、响应不快,故在 IPRI_DC 的每周期 24 点均值之上再叠加一层 8 周期移动平均,方法与 AUX5 等慢速量一致:rolling sum + 移位除法,且全程无循环。本环境无 ARM 工具链,未编译验证。
- 新增常量(
app_core.c,紧挨APP_IPRI_DC_ZERO_MV):APP_IPRI_DC_AVG_CYCLES=8、APP_IPRI_DC_AVG_SHIFT=3、APP_IPRI_DC_AVG_MASK=7。窗口取 2 的幂,故最终均值是>>3而非除法。 - 新增
ipri_dc_period_avg(uint16_t period_mean):8 元素静态环 + 静态rsum,每拍rsum = rsum + 新均值 - ring[idx](rolling sum,无重求和循环),ring[idx]=新均值,idx=(idx+1)&MASK,返回(rsum + 4) >> 3。环与 rsum 静态零初始化、仅此函数写入,故 rsum 恒等于环和;上电时环从 0 充满的短暂爬坡与 AUX 轨一致。 - ISR 内
i_pri_dc_raw = ipri_dc_period_avg(arithmetic_mean24_adc2(APP_ADC2_RANK_IPRI_DC)):先取本周期 24 点均值(已展开),再过 8 周期移动平均;其后的 2.5V 零点换算(adc8_to_mv、(zero-sensor)*10)作用在平滑后的 raw 上。 - 关于“展开”:8 周期平均用 rolling sum,本就没有求和循环可展开;它消费的每周期 24 点均值
arithmetic_mean24_adc2已是手工展开式。整条 IPRI_DC 路径无循环。环用的是ring[idx](与 AUX5 同构),若想把 8 个抽头写成显式移位寄存器(h0..h7)也可,说一声即可。 - 仅平滑进入控制/功率与遥测的
g_i_pri_dc_ma;心跳里逐样本回传的 24 个 IPRI_DC raw(供上位机示波)不受影响。
README.md:信号表 IPRI_DC 行改“24 点算术均值 + 8 周期移动平均”,并注明 ACS712 带宽有限、rolling sum + 右移、类似 AUX5;ISR 数据流图在arithmetic_mean24_adc2(ISEC/IPRI_DC)后加一行ipri_dc_period_avg()。- 改动文件:
Core/Src/app_core.c、README.md。上位机与协议未动。
按要求做三件事,并同步文档。本环境无 ARM 工具链,未编译验证。
- 原先在
APP_HRTIM_ControlISR()内部捕获DWT->CYCCNT起点、并在每个 return 分支调用isr_timing_end(),测得的只是控制体本身,漏掉 HAL 派发与回调开销。 - 现把计时移到真正的中断入口
HRTIM1_Master_IRQHandler(stm32g4xx_it.c,用 USER CODE 段以免 CubeMX 再生覆盖):USER CODE 0 取isr_start_cycles = DWT->CYCCNT,USER CODE 1 调APP_HRTIM_IsrTimingEnd(isr_start_cycles),包住HAL_HRTIM_IRQHandler(),测得整个 IRQ 窗口(HAL 标志派发 + Compare2 回调 + 控制体)。 isr_timing_end()(static inline)改名导出为void APP_HRTIM_IsrTimingEnd(uint32_t),在app_core.h声明;stm32g4xx_it.c的 USER CODE Includes 加#include "app_core.h"。控制体内删除起点捕获和 3 处isr_timing_end()。- 测量口径仍唯一对应控制 ISR:
hrtim.c中 master 只开了HRTIM_MASTER_IT_MCMP2,其余定时器HRTIM_TIM_IT_NONE,故 master IRQ 仅因 CMP2 触发。
- 原 VPRI 用
read_adc1()走sample_index_for()/interp_sample()的 A/B 两点相位插值(位置0.25+0.5φ、0.75+0.5φ)。VPRI 是硬件滤波后的直流母线,全周期平均更简单、SNR 更高,且与 ISEC/IPRI_DC 同法。 - 新增
arithmetic_mean24_adc1(rank),ISR 内v_pri_raw = arithmetic_mean24_adc1(APP_ADC1_RANK_VPRI),v_pri_mv = adc12_to_mv(v_pri_raw) * 17U(去掉两点求均值的/2)。 - 连带删除现已无用的插值机构(否则 static 函数未引用会报 warning,破坏 0-warning 构建):
read_adc1、sample_index_for、interp_sample、APP_POS_VPRI_A/B、APP_SamplePos结构;以及只服务相位插值的g_derived.inv_prd_f(上一轮刚加,现随之失效)和g_derived.period_ns,及 ISR 内phase_frac/period_ns局部量。set_duty()用的phase_span_f/inv_span_f仍保留。 - VSEC 仍用
anti_phase_pairwise_mean_vsec()(PSFB 反相对消,与 VPRI 无关),ISEC/IPRI_DC 不变。
- 将“涉及求平均的循环累加”改写为
buffer[0]+buffer[1]+…的无循环展开式:arithmetic_mean24_adc2、新增的arithmetic_mean24_adc1:各 24 项(k*APP_ADCx_CHANNELS+rank,k=0..23)。process_slow_adc_isr()的 VREFINT/温度(adc5,各 16 项)与 AUX12/AUX5/MOS-NTC(adc3/adc4,各 16 项)。展开后该函数不再需要循环变量k,并去掉sum_* = 0U初值(改为整式直接赋值)。
- 加编译期护栏:
#if (APP_SAMPLES_PER_PERIOD != 24U) #error …、#if (APP_VREF_SAMPLES != 16U) || (APP_SLOW_SAMPLES_PER_PERIOD != 16U) #error …,样本数一旦改动即编译报错,避免手工展开与常量脱节。 - 未展开两处(不属于“求平均的累加”):
period_max_adc2()是逐样本取最大值的比较循环(非累加);anti_phase_pairwise_mean_vsec()是带 5 路分支的配对分类器(非纯累加),展开 12 次会十几倍放大分支代码且无收益。build_heartbeat()里写 TX 帧的循环是序列化、非求平均,保持不变。如需也展开这两处可再说。
README.md:信号处理小节去掉sample_index_for/interp_sample插值描述,改为“全周期聚合 + 手工展开求和”;信号表 VPRI 行由“A/B 两点插值取均值”改“24 点算术均值”;ISR 数据流图把read_adc1(VPRI…)改arithmetic_mean24_adc1(VPRI)、并把计时画到HRTIM1_Master_IRQHandler包住HAL_HRTIM_IRQHandler的层级、末尾isr_timing_end()改APP_HRTIM_IsrTimingEnd()。- 改动文件:
Core/Src/app_core.c、Core/Inc/app_core.h、Core/Src/stm32g4xx_it.c、README.md。上位机与协议未动。
先逐项核实 Todo.md 中问题是否真实存在、给出的修法是否会变糟,确认后再改;全部为等价优化,不改变采样/控制节奏与对外协议,故 README 无需改动(其数据流/算法描述仍准确:慢速量最终仍是 >>4 求均值,移动平均语义不变)。本环境无 ARM 工具链,未编译验证。
核实结论:6 项均真实存在(反汇编层面每个控制 ISR 都有对应的系统存储读 / 浮点除 / UDIV / 全量重求和),修法均成立。唯一调整:第 3 项不采用其建议的 app.duty * 0.5f——因 prd = base_clk / freq 为整数除,可能为奇数(如 2.72e9/83000=32771),而 app.duty 基于 span = prd/2(整数下取整),*0.5f 在奇数 prd 时有 prd/(prd-1) 的相位偏差;改用缓存倒数 1/prd 直乘,与原 (mcmp3-LEAD)/prd 在 ~1 ULP 内,更准。
-
缓存工厂校准常量(第 2 项):
__LL_ADC_CALC_VREFANALOG_VOLTAGE/__LL_ADC_CALC_TEMPERATURE宏每个 ISR 都从系统存储区(0x1FFF75xx)读*VREFINT_CAL_ADDR、*TEMPSENSOR_CAL1/2_ADDR。初始化时缓存vrefint_cal_x3000 = *VREFINT_CAL_ADDR * VREFINT_CAL_VREF、ts_cal1、ts_span = *CAL2 - *CAL1、ts_cal_valid。ISR 内g_vcc_mv = vrefint_cal_x3000 / vref_avg;温度按宏公式内联(数据本就 12-bit,分辨率换算是 no-op,省略),与宏逐比特等价。两个真正的UDIV(/vref_avg、/ts_span)保留(操作数变化,必需)。 -
相位浮点除改缓存倒数(第 3 项):
g_derived新增inv_prd_f = 1/prd,ISR 内phase_frac = (mcmp3-LEAD) * inv_prd_f取代/(float)prd。phase_frac 仅用于给 VPRI 采样定位,VPRI 是硬件滤波后的直流母线,亚-ULP 误差无意义。 -
VPRI 固定采样位跳过除法(第 4 项):
sample_index_for()中delay_ns/period_ns一项,当前所有采样位(VPRI A/B)delay_ns==0,0/period_ns==0纯属浪费(每 ISR 两次VDIV)。改为if ((pos->delay_ns != 0) && (period_ns > 0.0f))跳过,位精确,且保留delay_ns!=0的通用路径。 -
set_duty 缓存相位跨度(第 5 项):
g_derived新增phase_span_f = (float)(prd/2)、inv_span_f = 1/phase_span_f。set_duty()去掉每次的整数prd/2(UDIV)和浮点/(float)span(VDIV):raw用duty * phase_span_f(与原duty*(float)span位等价),app.duty用*inv_span_f(倒数乘,~1 ULP,且 app.duty 仅遥测)。三个缓存量在apply_settings()与APP_Init默认块各算一次。 -
AUX 慢速环改滚动和(第 6 项):原先每 ISR 把 AUX12/AUX5/MOS-NTC 三个 16 元素环全量重求和(48 次加)。改为
process_slow_adc_isr()内三个静态rsum_*:写槽前rsum = rsum + 新均值 - 旧槽值(无符号回绕保证新<旧时仍正确),下游直接取rsum_*。静态零初始化 + 仅本函数写入,故rsum_*恒等于环和;变频时apply_settings不清环数组也不清rsum_*,二者始终一致——与原全量重求和逐比特相同(含预热与跨变频)。 -
LED 心跳去
% 5000U(第 7 项):hrtim_irq_count % 5000U每 ISR 一次硬件UDIV,且该计数器无其他用途。改为if (++hrtim_irq_count >= 5000U) { hrtim_irq_count = 0; toggle; },触发节拍(每第 5000 个 ISR)完全一致。
改动文件:仅 Core/Src/app_core.c(APP_DerivedConfig 增 3 个 float 字段;新增 4 个静态校准缓存量;apply_settings/APP_Init 各补缓存计算;set_duty、sample_index_for、process_slow_adc_isr、APP_HRTIM_ControlISR 改写如上)。上位机与协议未动。
附:VREFINT/temp 为何不一并改 rolling sum(评估后决定不改)。移位平均它本来就是(vref_avg/temp_avg 用 >>4,无除法)。rolling sum 不适用:第 6 项能 roll 的是 AUX 那层 CPU 每拍写一格的 ring(cyc_*,写前能读到旧值,故"减旧加新"精确)。AUX 其实还有一层"求 16 个 DMA 样本之和→本周期均值"的 stage 1(adc3/4_dma),第 6 项也没动它。VREFINT/temp 只有这层 stage 1(直接对 adc5_dma 求和),样本是 DMA 硬件异步写入,CPU 不知道本拍覆盖了哪格、旧值多少,无法减旧。强行 roll 要么维护 16 元素影子拷贝来 diff(开销 ≥ 直接重求和,不省),要么读 DMA NDTR 记账(脆弱、与 DMA 竞态),要么再加一级 ring(变成更长平均,改变温度/VREF 滤波特性,Todo 头部明确禁止)。故保持原样。
- 检查
adc12_to_mv()的raw * g_vcc_mv / 4095U编译结果:当前 Keil ARMCC v5 未自动变成乘法倒数,APP_HRTIM_ControlISR内联后仍为硬件UDIV,除数0x0FFF。已把“如需优化,必须先做有边界证明和穷举验证的等价无除法 helper;不合适则保留硬件除法”的方案写入Todo.md。 - 将不降低实时表现的 ISR 等价优化点记录到
Todo.md:缓存VREFINT_CAL/TS_CAL1/TS_CAL2派生常量、用app.duty * 0.5f或缓存值替代相位浮点除法、专门化 VPRI 固定采样位置以避开零 delay 的通用除法路径、缓存set_duty()的相位跨度/倒数、AUX 慢速 ring 改 rolling sum、LED 分频从% 5000U改倒计数。 - 明确未纳入会降低更新频率或改变实时表现的方案,例如温度/VREF/AUX 降频计算。
- 本次只做分析和文档记录,未修改下位机业务代码,未运行 Keil 编译。
本次两项修改,均在控制 ISR(APP_HRTIM_ControlISR)热路径上,并同步文档。
- 原实现
(uint32_t)(((uint64_t)mv * (uint64_t)ma) / 1000ULL):32×32→64 的乘法(UMULL)本身很便宜,但紧接着的 64 位常量除以 1000 会被编译器展开成一串 64 位 reciprocal-multiply(或退化为__aeabi_uldivmod风格的库调用),每个控制 ISR 跑两次(母线侧 + 副边侧功率),是 ISR 里唯一的 64 位运算。 - 改为全 32 位:把
mv拆成mv = 1000*vq + vr(vq整伏特、vr0..999 的亚伏 mV),则mv*ma/1000 = ma*vq + (ma*vr)/1000。两个/1000U都是 32 位、被编译为乘倒数+移位,无除法指令、无 64 位。 - 位精确:因
ma*vq为整数,floor((1000*vq+vr)*ma/1000) = ma*vq + floor(ma*vr/1000),与原 64 位结果逐比特相同。 - 不溢出:本机最坏情形副边
~3.3e6 mV × ~0.5A、母线~56e3 mV × ~25A,ma*vq与ma*vr两个分量都远在 uint32 内(各 < 3e7)。要溢出需功率 > 4.29 MW 或电流 > 4290 A,不可能。
- 实测 ACS712 在 0A 时输出为精确 2.5V,并非随 5V 轨比例变化的
AUX_5V/2。 - 新增宏
APP_IPRI_DC_ZERO_MV = 2500UL;ISR 内ipri_dc_zero_mv = g_aux_5v_mv / 2U;改为= APP_IPRI_DC_ZERO_MV;。因adc8_to_mv()已用 VREFINT 反推的g_vcc_mv做基准,输出是绝对电压,固定 2500 mV 即为正确零点。 g_aux_5v_mv仍在process_slow_adc_isr()中计算,作为心跳遥测(5V 轨电压)保留,仅不再用于 IPRI_DC 零点。- 同步修订两处过时注释:
process_slow_adc_isr()头注释与 ISR 内process_slow_adc_isr()调用前注释(原称 “g_aux_5v_mv 为 IPRI_DC 零点”)。
README.md:PA0 引脚表(无电流输出改为“精确 2.5V(实测,非 AUX_5V/2)”);安全约束 IPRI_DC 钳位阈值由AUX_5V/2改为2.5V(ACS712 精确零点)。- 本次未改上位机代码(
pi_power_mw为下位机内部定点实现,上位机功率/效率计算不受影响)。
- 待 Keil uVision 编译
G474_HVCCPS.uvprojx确认 0 Error / 0 Warning(本环境无 ARM 工具链,未在此运行)。
按本次要求将 MOS NTC 温度换算从 ISR 内 raw -> mV -> Beta/logf 改为完整 12-bit ADC raw 查表。
- 新增
Core/Inc/mos_ntc_lut.h,表长 4096,索引为 ADC4 MOS-NTC raw0..4095,表值单位为m°C。 - 表按文档参数生成:
3.3V -> NTC10K -> ADC -> 10K -> GND,R25=10k,B=3450,T25=298.15K。 raw=0和负温区间钳为0 m°C;raw=4095是分压公式奇点,饱和到raw=4094的最高表值。app_core.c删除 MOS NTC 的浮点 Beta 计算函数和APP_NTC_*宏,process_slow_adc_isr()对 16 周期平均后的 raw 直接索引MOS_NTC_TEMP_MC_BY_RAW[]。math.h保留给已有的isfinite()使用;MOS 温度路径不再调用logf()。README.md安全约束同步说明:MOS NTC 是 raw 直接查表,其余 ADC 物理量仍先用 VREFINT 反推 VCC。- 验证:Keil uVision 编译
G474_HVCCPS.uvprojx通过,0 Error / 0 Warning,Program Size: Code=28796, RO-data=16976, RW-data=120, ZI-data=4760。
按 Todo 原始算法架构修正上一版的慢速量处理频率:ADC3/4 的采样数量和滑动窗口保持 16(便于移位求均值),但 CPU 处理不再每 16 个控制 ISR 才跑一次,而是恢复为每个控制 ISR 都执行一次。
- 删除
APP_SLOW_PROC_INTERVAL和slow_decim,APP_HRTIM_ControlISR()每次进入都调用process_slow_adc_isr()。 process_slow_adc_isr()的算法保持 Todo 结构:- 读取本周期 ADC3/4 的 16 个样本,右移
>> 4得到“本周期均值”。 - 覆盖写入 16 元素环形数组。
- 再对环形数组右移
>> 4得到最终 AUX12 / AUX5 / MOS NTC raw。 - 因此 ring 现在表示最近 16 个连续开关周期,而不是每隔 16 周期取一次快照。
- 读取本周期 ADC3/4 的 16 个样本,右移
- 环形下标从
% APP_SLOW_AVG_CYCLES改为& APP_SLOW_AVG_MASK,窗口长度为 16 时避免取模除法。 - IPRI_AC 峰值也恢复为每个控制 ISR 更新一次,语义重新匹配“周期内峰值(不记录历史最大值)”。
- 注释同步为“所有物理量换算在每个控制 ISR 内完成”,去掉 1/16 慢速块描述。
README.md下位机数据流图更新为每周期调用process_slow_adc_isr(),并标明 ADC3/4 是“16 点周期均值 -> 连续 16 周期环形平均”。- 本次未修改上位机代码。
- Keil uVision 编译
G474_HVCCPS.uvprojx通过:0 Error / 0 Warning。
按本次要求把慢速辅助量链路从上一版 Timer D 10× 进一步调整为 Timer E 16×,文档与 CubeMX 配置同步到当前实现。
- ADC3/ADC4 改由 ADC Trigger 3 + Timer E 16× 驱动:
Core/Src/hrtim.c中 ADC Trigger 3 的触发源改为Master CMP1 | Timer E Period,postscaler 保持 0。- 新增 Timer E no-output 配置,默认周期
HRTIM_PRD/8+1,MUL32、CONTINUOUS、由 Master CMP1 reset。 APP_TIMERS_ALL增加HRTIM_TIMERID_TIMER_E,保证apply_settings()停止/启动 HRTIM 时 Timer E 也同步参与。
- 缓冲区与均值改为 16× / 移位:
APP_SLOW_SAMPLES_PER_PERIOD=16、APP_SLOW_AVG_CYCLES=16、APP_VREF_SAMPLES=16。- ADC3/4/5 DMA 缓冲长度变为
16 / 32 / 32。 - AUX12 / AUX5 / MOS-NTC 的单周期均值、16 周期环形均值,以及 VREFINT / 内部温度 16 样本均值均改为
>> 4,避免除以 10 这类非 2 的幂除法。
- 频率变更同步 Timer E:
- 新增
time_prd_for_freq(freq)=floor(5.44GHz/(freq×16))+1。 APP_DerivedConfig增加time_prd。reconfigure_hrtim()增加time_prd形参,并在 Timer C/D 后重新配置 Timer E 时基。apply_settings()和APP_Init()都计算并记录 Timer E 周期,确保上位机下发频率变更后 Timer E 也随主开关频率更新。
- 新增
- 控制 ISR 细节:
- 慢速辅助量物理换算(VREF/VCC、AUX、NTC、内部温度)仍在控制 ISR 内处理,主循环不做物理量换算。
IPRI_AC峰值仅作 MOS 保护/遥测,不参与控制。sample_index_for()用内联取整替代floorf(),避免 Cortex-M4 上的库函数调用。
- 注释同步:
- 清理
app_core.c中残留的10-sample、TIM-D 10x chain、slot k ~ k*10%等旧注释,改为 Timer E 16× / ADC5 16 样本跨周期分频窗描述。
- 清理
G474_HVCCPS.ioc增加 Timer E no-output 虚拟外设。ADCTrigger3_Source2从HRTIM_ADCTRIGGEREVENT13_TIMERD_PERIOD改为HRTIM_ADCTRIGGEREVENT13_TIMERE_PERIOD。- 增加
Periode_TE=HRTIM_PRD/8+1、ResetTrigger1-No_output_TE=HRTIM_TIMRESETTRIGGER_MASTER_CMP1等 Timer E 配置项。
README.md更新 HRTIM 章节:Timer D 只驱动 ADC5 的 Trigger 2 分频链,Timer E 驱动 ADC3/4 的 Trigger 3 16×采样链。- 启动顺序改为 master + timA/B/C/D/E,设置包说明改为频率变更时重算 TIM C/D/E。
- 下位机数据流图改为:ADC3/4 为每周期 16 点周期均值 + 连续 16 周期环形平均,ADC5 为 16 样本均值。
- Keil uVision 编译
G474_HVCCPS.uvprojx通过:0 Error / 0 Warning。 - 本次未修改上位机代码,按要求无需运行上位机实际测试。
发现 PSFB 存在无功电流,原边 SW 节点交流电流(IPRI_AC)的均值/插值对控制和功率计算无意义,本次将其改为纯峰值检测,只用于 MOS 过流保护显示;同时原边 AC 采样硬件改为 1:200 互感器 + 7.5Ω;闭环控制与效率/功率计算改用更可靠的量。
- IPRI_AC 改为周期内峰值(最大值):新增
period_max_adc2(rank)(遍历 24 个样本取最大)替代原来的单点相位插值read_adc2(...)。仅取当前周期峰值,不记录历史最大值。删除随之失去使用者的read_adc2()、APP_POS_IPRI_AC(read_adc1/APP_POS_VPRI_*仍保留供 VPRI 用)。 - 硬件系数 1:100/15Ω → 1:200/7.5Ω:
i_pri_ac_ma换算由* 100UL / 15UL改为* 400UL / 15UL(即CT/R = 200/7.5 = 400/15 ≈ 26.67 mA/mV,灵敏度 37.5 mV/A),行尾注释更新为1:200 CT + 7.5ohm + |.| (peak)。 - 闭环 CC 控制只用副边 DC 电流:
current_feedback_ma = i_sec_ma,删除原max3(原边AC折算, 原边DC折算, 副边)及局部变量pri_ac_sec_ma/pri_dc_sec_ma。 - CP 功率反馈改用 IPRI_DC:去掉
母线电压 × IPRI_AC这一无功项,改为max(母线电压 × IPRI_DC, 副边电压 × 副边电流)(用一个内联两值取大替代)。 - 上述改动后
max3_u32()失去使用者,删除其定义(避免 armcc 未使用静态函数告警)。APP_TRANSFORMER_RATIO宏变为未使用(保留,记录 100:1 变压比,宏未使用不告警)。 - IPRI_DC 已是周期内 24 点直接平均(
arithmetic_mean24_adc2),核对后无需改动。
app.jsefficiencyRatio():输入功率由iPriAcMa × vPriMv改为iPriDcMa × vPriMv(IPRI_AC 不再代表实功率)。app.js遥测指标iPriAcA标签由IPRI AC改为IPRI AC PK(轴标同改),明确这是峰值保护读数。app.jsIPRI_AC 波形toUnit系数由旧的100/11(此前 1:200+22Ω 残留,未随 0530 改动同步,本次一并修正)改为200/7.5,与下位机一致。protocol.jsSAMPLE_POSITIONS.ipriAc由单点探针改为空数组(峰值检测无单一采样点,波形上不再画该探针标记;viz 的位置标记循环对空数组安全)。
README.md最小化更新:PA1 引脚(1:200 CT + 7.5Ω,标注仅 MOS 保护取峰值);控制环信号处理表 IPRI_AC 行(单点插值 → 周期峰值);CC/CP 反馈描述(CC 仅副边 DC、CP 用 IPRI_DC);VSEC 全 0 返回 0 的补充;心跳字段 i_pri_ac 注明峰值;数据流图read_adc2→period_max_adc2。
- Keil 编译
G474_HVCCPS.uvprojx:0 Error / 0 Warning,Code=29444。 - 上位机
app.js/protocol.js/viz.jsnode --check全部通过。 - 心跳协议字段布局未变(仅
i_pri_ac语义由插值值变为峰值),上位机其余部分兼容。
本次把 AUX5 / AUX12 / MOS 温度 / VREFINT / 内部温度 这些慢速辅助量,从"软件触发 + 在 APP_Task while 循环里做物理量换算"改为"HRTIM 硬件触发 + 在控制 ISR 内同步处理",并补齐频率变更时 TIMER D 的重配置。硬件层(adc.c / hrtim.c / .ioc)已由 CubeMX 重新生成,本条记录业务代码(app_core.c)与文档的同步。
- ADC Trigger 1 =
Master CMP1 | TIMC Period,postscaler 0 → ADC1/ADC2,24×/周期(原有不变)。 - ADC Trigger 3 =
Master CMP1 | TIMD Period,postscaler 0 → ADC3(AUX12)+ ADC4(AUX5 rank0 / MOS-NTC rank1),10×/周期。 - ADC Trigger 2 =
Master CMP1 | TIMD Period,postscaler 10(每 11 个事件触发 1 次) → ADC5(内部温度 rank0 / VREFINT rank1)。 - TIMER D:MUL32、CONTINUOUS、周期
HRTIM_PRD/5+1、由 Master CMP1 reset 同步。 - ADC3/4/5 由软件触发改为 HRTIM 触发;时钟统一
ADC_CLOCK_SYNC_PCLK_DIV4(42.5 MHz);ADC3/4 采样时间 6.5 cycle,ADC5 保持 247.5 cycle;关闭了过采样;DMA 优先级提至 HIGH。usart.c的 RX/TX DMA 优先级降为 MEDIUM 以让位。
- 缓冲区 10× 化 + 常量/rank 宏:新增
APP_SLOW_SAMPLES_PER_PERIOD=10、APP_SLOW_AVG_CYCLES=10、APP_VREF_SAMPLES=10,以及APP_ADC3/4/5_CHANNELS、APP_ADC3/4/5_BUFFER_LEN(10/20/20)。adc3_dma[10]/adc4_dma[20]/adc5_dma[20](原为 1/2/2)。新增 rank 宏APP_ADC3_RANK_AUX12、APP_ADC4_RANK_AUX5/MOS_NTC、APP_ADC5_RANK_INT_TEMP/VREFINT。 - 🔴 修复 TIMER D 从未启动:
APP_TIMERS_ALL增加HRTIM_TIMERID_TIMER_D。该宏被apply_settings()的WaveformCountStart_IT/Stop_IT共用——此前 TIMER D 虽在hrtim.c配置却从未被启动,TRG2/TRG3 只会收到 Master CMP1 单一事件源(1 次/周期而非 10 次),缓冲区无法正确填充。这是本次发现并修复的关键缺陷。 - 频率变更同步 TIMER D(对标
timc_prd_for_freq):新增timd_prd_for_freq(freq)=5.44GHz/(freq×10)+1(复用APP_HRTIM_TIMC_BASE_CLK_HZ,TIMER D 同为 MUL32);APP_DerivedConfig增timd_prd;reconfigure_hrtim()增timd_prd形参并在 TIMER C 之后配置 TIMER D 时基;apply_settings()与APP_Init()计算并写入g_derived.timd_prd。心跳协议未改(不新增 timd_prd 上报),上位机无需改动。 - 慢速量处理移入 ISR:删除
update_slow_adc()及其在APP_Task()的调用(while 循环不再做任何物理量换算);新增static void process_slow_adc_isr(),在APP_HRTIM_ControlISR()最前面调用(先于所有adcXX_to_mv,因为g_vcc_mv被全部换算依赖、g_aux_5v_mv被 IPRI_DC 零点依赖)。滤波算法:- TRG3 组(AUX12/AUX5/MOS-NTC):每拍先对 DMA 缓冲内 10 个样本求均值得"本周期采样值",覆盖写入 3 个
cyc_*[10]环形数组的最旧槽(共享ring_idx循环移动),再对环内 10 个周期值求平均得最终 raw。等效 100 点平均,但只用两个 10 元素数组/信号(避免 10×10 大数组)。 - TRG2 组(VREFINT/内部温度):直接对
adc5_dma内 10 个样本求均值(缓冲本身即跨 11 周期的 0%..90% 历史窗)。g_vcc_mv保留vref_avg!=0防护,缓冲未满时维持 3300 mV 默认。
- TRG3 组(AUX12/AUX5/MOS-NTC):每拍先对 DMA 缓冲内 10 个样本求均值得"本周期采样值",覆盖写入 3 个
- 单周期 10 次采样与周期同步对齐:把 ADC3/4/5 的 DMA 预装从
APP_Init移入apply_settings()(与 ADC1/2 并列:先HAL_ADC_Stop_DMA→ 清零缓冲 →start_adc_dma,全部在WaveformCountStart_IT之前)。配合"缓冲恰好 = 1 次采集样本数 + TIMER D+1保证每周期恰好 10 次触发",首个 Master CMP1 落在 index 0,slot k 恒对应相位 ~k×10%;开机与每次改频都重新对齐。APP_Init仅保留calibrate_adc(3/4/5)。 - 副边电压全 0 → 输出 0(
anti_phase_pairwise_mean_vsec):原先无 valid/side 信息时一律返回 4095。改为仅当has_full!=0(真饱和)才返回 4095 强制退避;否则(24 样本皆 0)返回 0。应对低压大电流输出时副边分压被 EMI 拉到 0 的情况,避免误报满量程。saturation/side 分支语义不变。
ADC 时钟 42.5 MHz(1 cyc=23.53 ns)。
- TRG3 间隔 = T_sw/10 = 2000 ns:ADC3(1ch×19cyc=447 ns)、ADC4(2ch×19cyc=894 ns)均充足。
- TRG2 间隔 = 11×T_sw/10 = 22 µs:ADC5(2ch×260cyc=12.24 µs)充足。
- TIMER D
+1防碰撞:master 周期 = 108800 MUL32 tick,TIMD 周期 =HRTIM_PRD/5+1= 10881 → 108800/10881 = 9.999 → 9 个 period 事件 + 1 个 Master CMP1 = 恰好 10 次;若不 +1(10880)则刚好 10.000,第 10 次 overflow 与 reset 同刻碰撞。TIMER C 同理(4534 → 23.997 → 24 次)。 - 11 分频 → 0%..90% 扫描:10 与 11 互质,输出落在事件序号 0,11,…,99,跨 11 个主周期覆盖 10 个相位。
- 频率缩放:
timd_prd=floor(5.44e9/(freq×10))+1在 10–50 kHz 全程恒落于 (9,10),且 16-bit 内(50 kHz=10881,10 kHz=54401)。
README.md最小化更新:ADC 整体分配表 ADC3/4/5 触发列(软件触发→HRTIM Trigger 3/2);引脚表"慢速循环采样"→"HRTIM 10×/周期同步采样";HRTIM 配置新增 Timer D 段、新增"慢速 ADC 触发(Timer D,10×/周期)"小节、compare unit 列出 Timer D;启动顺序补全 5 路 ADC + Timer D;文件结构 hrtim.c 描述补 Timer D / Trigger 2/3;下位机数据流图把update_slow_adc从APP_Task移到 ISR 的process_slow_adc_isr。- 更新
app_core.c文件头注释,补充 TRG2/TRG3 + TIMER D 慢速链与"所有物理量换算在 ISR 内完成"的说明。
- Keil uVision 编译
G474_HVCCPS.uvprojx通过:0 Error / 0 Warning,Code=29648(较上次 29104 增 544 字节)。 - 协议未变,上位机 HostUI2.0 无需改动。
- 硬件变更:原边变压器 SW 节点交流电流采样电路,电流互感器由 1:200 改为 1:100,采样电阻由 22Ω 改为 15Ω。绝对值电路与 8 位 ADC2_IN2(PA1)通道不变。
Core/Src/app_core.c(APP_HRTIM_ControlISR,约 1269 行)更新 IPRI_AC 物理量换算系数:adc8_to_mv()返回采样电阻两端电压(mV),换算到原边电流 mA 的增益 =CT 变比 / 采样电阻=CT_ratio / R_sense。- 旧:
* 100UL / 11UL(即200/22,对应 1:200 CT + 22Ω)。 - 新:
* 100UL / 15UL(对应 1:100 CT + 15Ω),并同步更新行尾注释为/* 1:100 CT + 15ohm + |.| */。 - 换算关系:
I_pri(mA) = V_sense(mV) / 15 × 100,灵敏度由旧 0.11 V/A 提升至 0.15 V/A;在同一 ADC 满量程下测量上限相应由约 30 A 降至约 22 A,分辨率提升。
- 注意:
APP_TRANSFORMER_RATIO(55 行,值 100)是主功率变压器原副边匝比,用于将原边电流折算到副边,与本次电流采样 CT 无关,未改动(数值与新 CT 变比恰好同为 100 纯属巧合)。 - 采样时序参数
APP_POS_IPRI_AC = {0.015f, 0.42f, 0}仅影响相位插值采样点,与幅值标定无关,未改动。 README.md第 29 行(PA1 引脚说明)由1:200 CT + 22 Ω 采样电阻最小化更新为1:100 CT + 15 Ω 采样电阻,保持文档与代码一致。- 验证:Keil uVision 编译
G474_HVCCPS.uvprojx通过,0 Error(s) / 0 Warning(s),Code=29104。
- 将
Design.md和DebugDesign.md删除,所有当前设计信息迁移到README.md。README.md仅描述当前代码的最终状态,移除所有"旧版/新版/不变/已移除"等演进描述。- 历史设计演进信息继续保留在
log.md中。
- 修正
Design.md(现 README.md)中与实际代码不符的若干描述:- ADC 采样参数(采样时间、过采样)已固定为 CubeMX 初始值,不可由上位机覆盖。README 中明确标注为固定配置。
- VSEC 不再使用 A/B 位置插值,而是 12-pair 反相对消均值处理;ISEC 和 IPRI_DC 使用 24 点算术均值而非单点插值。更新了控制环路信号处理方式的完整描述。
- ADC2 regular 序列顺序更正为
(IPRI_AC, ISEC, IPRI_DC),与adc.cCubeMX 配置一致。 validateSettingsForm不再检查采样时间/过采样预算。- 设置包大小从文档中不存在的 67/79 字节更正为实际当前值 34 字节。
- 频率范围保持 10–50 kHz(非 10–100 kHz)。
- 移除"(不变)""新格式"等非当前时态的描述。
- 修复了
app_core.c文件头注释中 ADC2 序列顺述描述错误(IPRI_DC, ISEC, IPRI_AC → IPRI_AC, ISEC, IPRI_DC)。 - 修复了
index.html中两处 ADC2 rank 描述错误的顺序。 - 修复了
viz.js中ADC2_RANKS数组顺序,使其与硬件实际 rank 顺序一致。 - 上位机 4 个 JS 文件语法校验全部通过。
- 在
Core/Src/app_core.c启用 Cortex-M4 DWT CYCCNT,用于无开销地测量控制 ISR 的执行时间:APP_Init中设置CoreDebug->DEMCR |= TRCENA、清零并使能DWT->CYCCNT。- 新增静态全局
isr_cycles_last/min/max,min初始化为0xFFFFFFFF,max初始化为 0。 - 新增内联辅助函数
isr_timing_end():以 32 位无符号减法计算DWT->CYCCNT - start,更新 last 并维护本心跳窗口内的 min/max。 APP_HRTIM_ControlISR在函数入口采样isr_start_cycles,并在所有三个return路径(power 未使能直接出、fixed-duty 路径、完整 PI 仲裁路径)之前调用isr_timing_end(),保证任何分支的耗时都能被记录。
- 心跳协议在
run_seconds_remaining之后插入 3 ×uint32(last / min / max 周期数),总长度由 265 增至 277 字节:build_heartbeat用__disable_irq()/__enable_irq()的极短临界区把三个值快照出来,并同时把min复位为0xFFFFFFFF、max复位为 0,让下个心跳窗口从零开始累积。- 若该窗口内一次 ISR 都没跑过(
min仍是0xFFFFFFFF),写包时把min折回 0 以避免上位机把哨兵值当真。 - 调整
build_heartbeat内total_len计算项,加入(3U * 4U);TX bufferAPP_HEARTBEAT_TX_CAPACITY = 320仍有充足余量。
- 上位机
HostUI2.0/protocol.js:HEARTBEAT_PAYLOAD_SIZE增加(3 * 4)。createLatest()增加isrCyclesLast/Min/Max三个字段,默认 0。parseHeartbeatFrame在runSecondsRemaining之后顺序读取 3 个readLe32。
- 上位机
HostUI2.0/app.js:- 新增
CPU_CLOCK_HZ = 170_000_000和CPU_CYCLE_NS常量,新增formatCyclesUs()把 CYCCNT 转换为 μs 展示。 - 在
TELEMETRY_METRICS中新增isrLastUs / isrMinUs / isrMaxUs三个指标,归入新分组ISR,颜色分别为 sky-500、sky-600、red-600。 CHART_GROUP_ORDER在Control与Raw之间插入ISR,使新分组在右侧 "Secondary Indicators" 列表和波形信号选择器里自然出现。- 这三个指标既不属于
KEY_METRIC_KEYS也不属于DEBUG_METRIC_KEYS,因此会默认在 Power Status 面板的 Secondary Indicators 区域里以 "ISR LAST / ISR MIN / ISR MAX" 三行 μs 数值常驻显示,无需展开调试折叠区。
- 新增
- 删除
Todo.md中对应一行条目。 - 验证:
- 上位机用
node --check HostUI2.0\protocol.js与node --check HostUI2.0\app.js通过语法校验。 - 下位机未单独编译(按 AGENTS.md 约定可省略),仅人工核对:
build_heartbeat的off偏移变化与total_len计算项一致;ISR 三处 return 路径均在isr_timing_end()之后才返回;DWT 初始化只在上电时执行一次。
- 上位机用
- Added a host-side telemetry metric named
效率in the Power group.- The value is calculated directly as
(ISEC * VSEC) / (IPRI_AC * VBUS). - The UI displays the raw calculated percentage without clamping, so values above 100% or negative values caused by measurement noise are preserved.
- The metric is available in the readings panel and in the plot signal picker.
- The value is calculated directly as
- Updated HostUI2.0 ADC2 default trigger expressions:
- ADC2 regular default is now
(MCMP3*2+MIN_TICKS)/3with0 nsdelay. - ADC2 injected default is now
MCMP3*0.72+0.075*HRTIM_PRD-34with0 nsdelay. - Existing browser settings that exactly match the previous ADC2 default
MCMP3*2/3+16with0 nsdelay are migrated to the new defaults on load.
- ADC2 regular default is now
- No firmware/protocol frame change was needed for this request because both new ADC2 timings are represented by the existing HostUI expression-to-
(a*MCMP3+b)folding path. - Verification:
node --check HostUI2.0\protocol.js,HostUI2.0\app.js, andHostUI2.0\viz.jspassed.- Numeric default check passed at 50 kHz / MUL16: ADC2 regular folds to
a=2/3,b=16; ADC2 injected folds toa=0.72,b=4046, with ranges[48,18181]and[4081,23665]. git diff --check -- HostUI2.0\protocol.js HostUI2.0\app.js Design.md log.mdpassed with only Git CRLF normalization warnings.
- Reworked ADC trigger expression encoding so HostUI2.0 now folds each trigger into the wire form
(a * MCMP3 + b)in HRTIM ticks:- Tick expressions can use
MCMP3,HRTIM_PRD,MIN_TICKS, constants,+,-,*,/, and parentheses, while validation still rejects non-linear expressions such as variable-by-variable multiplication or division by a variable. MIN_TICKSis computed from the selected HRTIM multiplier as3 * multiplier(MUL16=48,MUL8=24,MUL4=12,MUL2=6).- The separate delay-time field remains in nanoseconds in the UI; HostUI converts it to the nearest tick at the selected frequency/prescaler and adds it into the final
bcoefficient before sending. - The firmware no longer receives or evaluates an HRTIM_PRD coefficient or a separate constant/offset field; it only evaluates
round(a * MCMP3 + b)at runtime.
- Tick expressions can use
- Shrunk the settings wire protocol from 79 bytes to 67 bytes:
- Removed
adc1_c,adc2_c, andadc2_inj_c. adc1_b,adc2_b, andadc2_inj_bare now already-folded tick constants, not HRTIM_PRD coefficients.- Updated HostUI frame building and firmware frame parsing together.
- Removed
- Updated trigger defaults:
- ADC1 custom still displays
HRTIM_PRD*13/16with0 ns; HostUI sends that asa=0,b=prd*13/16. - ADC1 preset still displays the dual phase expressions
HRTIM_PRD/4+MCMP3/2andHRTIM_PRD*3/4+MCMP3/2; HostUI sends only the first phase asa=0.5,b=prd/4+delay_ticks, and firmware generates CMP4 as CMP3+PRD/2. - ADC1 preset delay default remains the time equivalent of
-1728 ticksat 50 kHz / MUL16 (-635.294117647 ns), so changing frequency changes the sent tick offset while preserving the intended time delay. - ADC2 regular and ADC2 injected defaults are now
MCMP3*2/3+16plus0 ns; HostUI migrates the prior defaultMCMP3*2/3plus5.882352941 nslocalStorage value to the new default shape.
- ADC1 custom still displays
- Updated HostUI labels and visualization:
- Main editor and popovers now label the expression field as
Tick Expressionand the time field asDelay Time (ns). - Range analysis, summaries, and plotted ADC trigger rows now use the same folded trigger coefficients that will be sent to firmware.
- Main editor and popovers now label the expression field as
- Updated firmware validation/clamping:
- ADC compare minimum now follows the selected HRTIM multiplier via
3 * multiplierfor ADC trigger compares. - MCMP3 phase control still starts at the existing fixed 48-tick lead so the original 0% phase alignment with Master CMP1 is preserved.
- Removed the old
-1728tick compare defaults from CubeMX-generated HRTIM init; ADC Trigger 3 now starts as the custom single CMP3 trigger and app runtime configuration owns the dual-phase preset.
- ADC compare minimum now follows the selected HRTIM multiplier via
- Verification:
node --check HostUI2.0\protocol.js,HostUI2.0\app.js, andHostUI2.0\viz.jsall passed.- Numeric protocol check passed: settings frame length 67; default ADC1 custom sends
a=0,b=44200; ADC1 preset sendsa=0.5,b=11872at 50 kHz/MUL16 and triggers[11896,39096]atMCMP3=48; ADC2 regular/injected both senda=2/3,b=16;MIN_TICKSevaluates to 24 at MUL8. git diff --check -- Core\Src\app_core.c HostUI2.0\protocol.js HostUI2.0\app.js HostUI2.0\viz.js HostUI2.0\index.html Design.md log.mdpassed, apart from Git CRLF normalization warnings.- Keil build was attempted from
MDK-ARM, but uVision reported it could not createbuild.logandg474_hvccps\*. __icommand input files. The visiblebuild.logtimestamp stayed at 2026-05-19, so it is an old log and not a valid verification of this change. A sandbox-escalated retry was requested but rejected by the automatic approval service.
- Historical note: the older 79-byte /
adc*_centries below in this same 2026-05-25 section describe the intermediate implementation before this two-coefficient protocol cleanup. - Split fixed trigger constants out of the expression text and into explicit time-offset fields in HostUI2.0:
- Added
Trigger Offset (ns)inputs for ADC1, ADC2 regular, and ADC2 injected in the visual editor, including rank/trigger popovers. - ADC1 keeps separate stored offsets for custom single-trigger mode and preset dual-phase mode so switching modes does not overwrite the custom offset.
- ADC1 preset base expressions are now
HRTIM_PRD/4+MCMP3/2andHRTIM_PRD*3/4+MCMP3/2; the default preset offset is stored as time derived from-1728 ticksat the default 50 kHz / MUL16 HRTIM base (-635.294117647 ns). - ADC2 regular/injected default base expressions are now
MCMP3*2/3; the default offset is stored as time derived from+16 ticksat the default HRTIM base (5.882352941 ns), preserving the previous(MCMP3+24)*2/3effective trigger at the default HRTIM base. - Host validation, summaries, and visualization now convert each offset from nanoseconds to HRTIM ticks using the selected frequency/prescaler before range checks and frame encoding.
- The wire protocol length remains 79 bytes. No new frame fields were added; HostUI writes the converted tick offset into the existing
adc1_c,adc2_c, andadc2_inj_ccoefficients.
- Added
- Firmware ADC1 preset now uses the received
adc1_ctick offset for both dual-phase compare calculations instead of the old hardcoded1728tick constant. The lower computer still treats all received trigger coefficients as ticks. - Split ADC2 current sampling into mandatory regular and injected groups:
- ADC2 regular group now contains IPRI_AC and IPRI_DC, triggered by HRTIM ADC Trigger 1 from Master CMP4.
- ADC2 injected group now contains ISEC, triggered by HRTIM ADC Trigger 2 from Timer A CMP2.
- Added independent host/firmware settings for ADC2 injected trigger expression, sample time, and oversampling; regular and injected defaults are both equivalent to
(MCMP3+24)*2/3, 6.5 cycles, no oversampling. - Increased settings frames from 66 to 79 bytes; ADC2 regular still has its own trigger/sample/oversampling fields, and the old single ADC2 trigger / three-regular-rank shape is no longer supported.
- Updated the HRTIM/ADC visual editor with a separate ADC2 injected row and controls.
- Added an ADC1 sample-mode field to the settings protocol (
0custom single trigger,1preset dual-phase average), increasing settings frames from 65 to 66 bytes. - Firmware ADC1 preset mode:
- Dynamically configures HRTIM ADC Trigger 3 to aggregate Timer A CMP3 and CMP4.
- Computes CMP3 as
HRTIM_PRD/4 + MCMP3/2 + adc1_cand CMP4 asHRTIM_PRD*3/4 + MCMP3/2 + adc1_c, whereadc1_cis already a tick offset converted by HostUI. - Forces the applied ADC1 oversampling to 2x with right shift 1 for hardware averaging while preserving the custom-mode oversampling setting in the host form.
- Starts ADC1 DMA with four halfwords in preset mode and keeps the two-halfword buffer length in custom mode.
- In the 50 kHz control ISR, averages the two VSEC samples (
dma[0]anddma[2]) for secondary voltage feedback; VPRI uses the first sequence sample.
- Preserved custom ADC1 operation:
- Custom mode still uses the editable ADC1 expression routed through Timer A CMP3 only.
- ADC2 current sampling remains independent on HRTIM ADC Trigger 1 / Master CMP4.
- Updated HostUI2.0:
- Added an ADC1 sample-mode selector in the HRTIM/ADC visual editor.
- Preset mode disables the custom ADC1 expression/oversampling controls, sends OSR x2, and shows both preset trigger instants in the visualization.
- ADC1 rank sample-time selectors remain editable in preset mode; default ADC1 VSEC/VPRI sample times are now both 6.5 cycles.
- Validation and ADC budget calculations account for two ADC1 trigger sequences per period in preset mode.
- Updated
Design.mdminimally to describe the new ADC1 modes and CMP3/CMP4 timing. - Verification so far:
- Ran
node --check HostUI2.0\protocol.js; syntax check passed. - Ran
node --check HostUI2.0\app.js; syntax check passed. - Ran
node --check HostUI2.0\viz.js; syntax check passed.
- Ran
- Rebuilt the upper-computer UI in
HostUI2.0as a new monitor-first operator console:- Main screen now keeps telemetry plot and power status visible at all times.
- Serial connection, run/stop control, and configuration are separated into independent right-side drawers instead of sharing one mixed control dock.
- Top status pills expose the four operator work areas clearly: connection state, output/run state, live monitoring, and configuration.
- Reworked the interaction model for safety and usability:
- Added a single operating-state model (
disconnected,connected_idle,connected_running) that drives button enablement and configuration locking. - Configuration drawer is locked while output is live and exposes a direct disable action.
- Run drawer keeps CV/CC/CP targets available during closed-loop operation, but locks start mode and duration while running.
- Fixed-duty running disables the online target-update action because the targets do not have the same control meaning in that mode.
- Emergency disable bar appears whenever output is latched on.
- Added a single operating-state model (
- Reworked HRTIM/ADC configuration placement:
- Configuration drawer has a dedicated HRTIM/ADC tab with timing summaries and a large visual-editor entry point.
- The HRTIM visual editor remains fully interactive, but its manual input column is hidden by default and shown only through a
Manualbutton. - Frequency, dead time, ADC trigger expressions, oversampling, rank sample times, phase preview dragging, inline popovers, and undo/redo remain wired through shared state.
- Reworked the visual style:
- Replaced the old blue-grey dark console with a white, restrained industrial style.
- Used compact bordered panels, status pills, table-like reading rows, sparse status colors, and no decorative hero/marketing layout.
- Kept monospace typography and dense information layout suitable for bench/industrial use.
- Kept protocol behavior unchanged:
- Reused the existing non-module
protocol.jsframe helpers, heartbeat decoder, settings validation, andhistory.jsundo/redo stack. - Reused
viz.jsvisualization logic with new HostUI2.0 shell and styles.
- Reused the existing non-module
- Updated
Design.mdminimally to describe the HostUI2.0 monitor-first drawer layout and visual-editor-first HRTIM workflow. - Verification:
- Ran
node --check HostUI2.0\app.js; syntax check passed. - Ran
node --check HostUI2.0\protocol.js; syntax check passed. - Ran
node --check HostUI2.0\history.js; syntax check passed. - Ran
node --check HostUI2.0\viz.js; syntax check passed. - Firmware was not changed, so Keil firmware compilation was not run.
- Ran
- 问题:下位机
app_core.c中APP_ADC2_RANK_IPRI_DC和APP_ADC2_RANK_IPRI_AC的 rank 常量互换了(DC 写了 0、AC 写了 2),与 CubeMX 硬件配置不一致。实际adc.c中 ADC2 的 rank 顺序为:rank 0 = IPRI_AC(PA1/CH2)、rank 1 = ISEC(PA6/CH3)、rank 2 = IPRI_DC(PA0/CH1)。这导致心跳包build_heartbeat()去交织 DMA 缓冲区时 IPRI_AC 和 IPRI_DC 数据互换。 - 上位机
app.js的CYCLE_SIGNALS中 IPRI_AC 和 IPRI_DC 的 rank/sampleDelayNs/positions 也相应互换了(AC 标记为 rank 2、DC 标记为 rank 0),导致逐周期波形图中 X 轴时间偏移各差了约 518 ns。 - 修复:
app_core.c:APP_ADC2_RANK_IPRI_AC改为 0U,APP_ADC2_RANK_IPRI_DC改为 2U(ISEC 保持 1U 不变)。app.js:CYCLE_SIGNALS中交换 ipriAc 和 ipriDc 两个对象的位置,使 ipriAc 对应 ADC2 rank 0(~29 ns)、ipriDc 对应 ADC2 rank 2(~547 ns)。
- 修复后的正确时序:
信号 ADC Bank Rank 采样偏移 VSEC ADC1 0 ~29 ns IPRI_AC ADC2 0 ~29 ns ISEC ADC2 1 ~288 ns VPRI ADC1 1 ~382 ns IPRI_DC ADC2 2 ~547 ns - 上位机语法检查通过。
- Fixed HostUI2.0 operation and HRTIM visualization issues found after the first rewrite:
- Changed the right-side power status panel to a fixed status header plus an isolated readings scroll area so expanding secondary/debug sections cannot push metric cards upward into the state chips.
- Added internal scroll limits to expanded reading lists so long status groups stay contained.
- Removed the dark backdrop effect from operation drawers while output is live, and made
Start Outputclose the Run drawer immediately after sending the start command so the telemetry monitor is visible for startup observation. - Added a live control strip above the plot with
Update TargetsandDisablecontrols while output is latched on, keeping emergency stop available without reopening the Run drawer. - Kept the existing top emergency disable bar as a second always-visible stop path.
- Cleaned up the HRTIM / ADC timing editor:
- Removed the
Bridge Coloroption and all related UI/controller code; PWM colors are now always by bridge. - Restyled the Master timer counter lane for the white industrial theme instead of the previous dark/black-filled look.
- Added explicit white-theme SVG styles for lane backgrounds, cycle lines, sawtooth counter fill, PWM baselines, dead-time blocks, rank text, MCMP3 reference lines/handles, and ADC TRIG reference lines/arrows.
- Extended ADC TRIG reference lines upward through the timing view so they are visible as references, not just small local markers.
- Changed ADC budget bars to compact inline cards under the timing diagram so they no longer visually occupy the middle of the view.
- Removed the visible
Selectionhelp box and moved the rank-hover/click guidance into the timing editor help tooltip.
- Removed the
- Updated
Design.mdminimally to record that live output keeps immediate disable controls available. - Verification:
- Ran
node --check HostUI2.0\app.js; syntax check passed. - Ran
node --check HostUI2.0\viz.js; syntax check passed. - Checked all
getElementById()references inHostUI2.0\app.jsandHostUI2.0\viz.js; all referenced IDs exist inHostUI2.0\index.html. - Firmware was not changed, so Keil firmware compilation was not run.
- Ran
- 问题:下位机
app_core.c中APP_ADC2_RANK_IPRI_DC和APP_ADC2_RANK_IPRI_AC的 rank 常量互换了(DC 写了 0、AC 写了 2),与 CubeMX 硬件配置不一致。实际adc.c中 ADC2 的 rank 顺序为:rank 0 = IPRI_AC(PA1/CH2)、rank 1 = ISEC(PA6/CH3)、rank 2 = IPRI_DC(PA0/CH1)。这导致心跳包build_heartbeat()去交织 DMA 缓冲区时 IPRI_AC 和 IPRI_DC 数据互换。 - 上位机
app.js的CYCLE_SIGNALS中 IPRI_AC 和 IPRI_DC 的 rank/sampleDelayNs/positions 也相应互换了(AC 标记为 rank 2、DC 标记为 rank 0),导致逐周期波形图中 X 轴时间偏移各差了约 518 ns。 - 修复:
app_core.c:APP_ADC2_RANK_IPRI_AC改为 0U,APP_ADC2_RANK_IPRI_DC改为 2U(ISEC 保持 1U 不变)。app.js:CYCLE_SIGNALS中交换 ipriAc 和 ipriDc 两个对象的位置,使 ipriAc 对应 ADC2 rank 0(~29 ns)、ipriDc 对应 ADC2 rank 2(~547 ns)。
- 修复后的正确时序:
信号 ADC Bank Rank 采样偏移 VSEC ADC1 0 ~29 ns IPRI_AC ADC2 0 ~29 ns ISEC ADC2 1 ~288 ns VPRI ADC1 1 ~382 ns IPRI_DC ADC2 2 ~547 ns - 上位机语法检查通过。
Provide a developer-facing console that lets researchers freely tune the PSFB power stage:
- Three-loop PI (CV/CC/CP) Kp / Ki at runtime.
- Switching frequency from 10 kHz to 100 kHz (with safety margins on the experimenter; not interlocked).
- Dead time 50 ns – 1000 ns (raw
fHRTIMticks, no multiplier). - ADC1 / ADC2 trigger position as a parametric expression of
MCMP3andHRTIM_PRDso the trigger can track the switch midpoint or sit at any fixed location. - Per-rank ADC sample times (named for the user as "primary current", "secondary voltage", …).
- ADC oversampling / hardware averaging (must shift to match the ratio).
- Startup mode: fixed-duty (with risk warning) or regular three-loop control. With a "run N seconds" auto-cutoff (or "continuous").
- Stop button always available.
- Host can confirm settings reached firmware via a new
settings_okbit in the heartbeat (set after the next heartbeat following a valid settings packet).
Two new things on the wire plus a small heartbeat extension. All little-endian, all fixed-length, all sum8 + xor8 trailers. We keep the existing 0x55 heartbeat header and 0xAA command header; settings get 0x5A.
| offset | bytes | type | field |
|---|---|---|---|
| 0 | 1 | u8 | header 0x5A |
| 1 | 1 | u8 | length = 67 |
| 2 | 4 | f32 | kp_cv |
| 6 | 4 | f32 | ki_cv |
| 10 | 4 | f32 | kp_cc |
| 14 | 4 | f32 | ki_cc |
| 18 | 4 | f32 | kp_cp |
| 22 | 4 | f32 | ki_cp |
| 26 | 4 | u32 | freq_hz (10 000 – 100 000) |
| 30 | 2 | u16 | deadtime_ticks (9 – 170, fHRTIM) |
| 32 | 4 | f32 | adc1_a coefficient on MCMP3 |
| 36 | 4 | f32 | adc1_b folded tick constant |
| 40 | 4 | f32 | adc2_a |
| 44 | 4 | f32 | adc2_b folded tick constant |
| 48 | 4 | f32 | adc2_inj_a |
| 52 | 4 | f32 | adc2_inj_b folded tick constant |
| 56 | 1 | u8 | adc1_smp[0] sample-time index 0..7 |
| 57 | 1 | u8 | adc1_smp[1] |
| 58 | 1 | u8 | adc2_smp[0] regular IPRI_AC |
| 59 | 1 | u8 | adc2_smp[1] regular IPRI_DC |
| 60 | 1 | u8 | adc2_inj_smp injected ISEC |
| 61 | 1 | u8 | adc1_oversample_log2 (0 = off, 1 = 2x, ... 8 = 256x) |
| 62 | 1 | u8 | adc2_oversample_log2 regular group |
| 63 | 1 | u8 | adc2_inj_oversample_log2 |
| 64 | 1 | u8 | adc1_sample_mode (0 custom single, 1 preset dual-phase average) |
| 65 | 1 | u8 | sum8 |
| 66 | 1 | u8 | xor8 |
Sample-time index ↔ ADC cycles (G474):
0:2.5 1:6.5 2:12.5 3:24.5 4:47.5 5:92.5 6:247.5 7:640.5
Host trigger inputs are a free-form tick expression using MCMP3,
HRTIM_PRD, MIN_TICKS, and constants, plus an explicit delay time in
nanoseconds. The host converts HRTIM_PRD, MIN_TICKS, constants, and delay
time to ticks using the selected frequency/prescaler, then sends the linear
wire form round(a * MCMP3 + b).
With this form the host can express:
- fixed 48 ticks ->
a=0, b=48 MIN_TICKS->a=0, b=48at MUL16 orb=24at MUL8- "ADC2 current default"
MCMP3*2/3+16+0 ns->a=0.6666667, b=16 - "switch midpoint"
(MCMP3+48)/2->a=0.5, b=24 - "13/16 of period" (current ADC1 behaviour) ->
a=0, b=prd*13/16 - ADC1 preset first phase
HRTIM_PRD/4+MCMP3/2plus default delay derived from-1728 ticksat 50 kHz/MUL16 ->a=0.5, b=prd/4+delay_ticks; firmware derives the second phase as CMP4=CMP3+PRD/2.
Validation (firmware and host):
-
freq_hz ∈ [10 000, 100 000] -
deadtime_ticks ∈ [9, 170] -
adc1_oversample_log2,adc2_oversample_log2, andadc2_inj_oversample_log2 ∈ [0, 8] -
adc1_sample_mode ∈ {0, 1}; mode 1 applies ADC1 as 2× oversampling and doubles the ADC1 per-period budget. -
Sample-time indices
∈ [0, 7] -
Recompute
prdand choose smallest prescaler that givesprd ≤ 65535(see prescaler table below).prdmust end up> 96so the basic 48-tick offsets fit and MCMP3 has at least one tick of range. In practiceprd ≥ 27200for our band. -
For each of (adc1 custom mode, adc2 regular, adc2 injected): evaluate the expression at both ends of the MCMP3 range
[48, 48 + prd/2]and require the result ∈[48, prd + 48]. ADC1 preset dual-phase validates both Timer A compare values against[48, prd]. (We accept a fixed expression as long as it stays in range across the entire phase sweep.) Hint to user about the+48lead offset. -
ADC time budget per period: for ADC1, ADC2 regular, and ADC2 injected independently,
t_per_rank = (sample_cycles + 12.5) * oversample_count total_cycles = sum_over_ranks(t_per_rank)The ADC is clocked at
170 MHz / 4 = 42.5 MHz. Converted to seconds and compared to1 / freq_hz. Both must fit.
Settings packets are only accepted while the latch is OFF. If the
output is enabled the packet is silently dropped (host sees that
settings_ok does not flip).
Extends the v1 command with start-mode and run-duration fields.
| offset | bytes | type | field |
|---|---|---|---|
| 0 | 1 | u8 | header 0xAA |
| 1 | 1 | u8 | length = 23 |
| 2 | 1 | u8 | ctrl_flags |
| 3 | 4 | u32 | cv_target_mv |
| 7 | 4 | u32 | cc_target_ma |
| 11 | 4 | u32 | cp_target_mw |
| 15 | 2 | u16 | run_duration_s (0 = continuous) |
| 17 | 4 | f32 | fixed_duty (0.0 – 1.0; only honoured if bit2 of ctrl) |
| 21 | 1 | u8 | sum8 |
| 22 | 1 | u8 | xor8 |
ctrl_flags bits:
- bit 0:
enable_request - bit 1:
disable_request(wins over enable when both set) - bit 2:
fixed_duty_start(only meaningful with enable) - bit 3..7: reserved (0)
The firmware still applies the existing absolute target caps
(CV ≤ 3000 V, CC ≤ 250 mA, CP ≤ 500 W).
Identical to v1 up through the existing 24 telemetry / control fields.
After key_flags we insert a 2-byte run_seconds_remaining field
before the sum8/xor8 trailer (total grows by 2 bytes).
status_flags re-uses its reserved bits:
- bit 0:
power_enable_latched - bits 1-2: control mode (0 disabled, 1 CC, 2 CV, 3 CP)
- bit 3:
settings_ok— flipped to 1 by the next heartbeat after a valid settings packet has been applied. Cleared back to 0 only by reset (per packet handshake; the host treats it as a level signal, not an edge). - bit 4:
fixed_duty_active— high while the latch is on and the current run was started in fixed-duty mode. - bit 5..7: reserved.
key_flags unchanged.
The runtime parameters move into a g_settings struct alongside the
existing app state. HRTIM_PRD and DEAD_TIME macros stay in
main.h only as power-on defaults; the body of the application reads
the live values from g_settings.
New flow on a valid settings packet:
- Verify CRC, header, length.
- Reject if
power_enable_latched. - Validate fields (table above).
- Stage into a scratch struct; on success:
HAL_HRTIM_WaveformOutputStop(already off, defensive).HAL_HRTIM_WaveformCountStop_ITon master + Tim A + Tim B.HAL_ADC_Stop_DMAon ADC1 and ADC2, plusHAL_ADCEx_InjectedStopon ADC2.- Reinit ADC1/ADC2 regular groups and ADC2 injected group with new oversampling + sample times.
We mutate
hadcN.Init.OversamplingMode/Ratio/RightBitShiftthen callHAL_ADC_Initagain (which keeps the MSP / DMA bindings) followed byHAL_ADC_ConfigChannelfor each rank.
- Reconfigure HRTIM time base on Master / A / B (period +
prescaler), recompute structural compares (MCMP1 = 48,
MCMP2 =
prd/2 + 48, Tim A CMP1 =prd/2, Tim B CMP1 =prd/2), then evaluate ADC1/ADC2 regular/ADC2 injected trigger expressions atMCMP3 = 48. HAL_HRTIM_DeadTimeConfigwith new ticks (rising = falling).- Reset
app.mcmp3_raw / mcmp4_rawtolead = 48(0 % duty). - Restart ADC DMAs and ADC2 injected conversion, then HRTIM.
- Copy staged settings into
g_settings; recompute cached values (prd,phase_max, ADC trigger constant parts, ADC time budget,ki_*scaled per period if useful). - Set
settings_okflag (cleared on each new settings packet attempt and reasserted only on success).
ADC trigger update path (per-cycle inside APP_HRTIM_ControlISR):
whenever duty is applied and mcmp3_raw is known, ADC1, ADC2 regular,
and ADC2 injected evaluate their own user expressions against the same
MCMP3 and HRTIM_PRD. ADC1 writes Timer A CMP3/CMP4 as needed, ADC2
regular writes Master CMP4, and ADC2 injected writes Timer A CMP2. All
are clamped to [lead, prd + lead], and the firmware validator already
rejects expressions that leave this range over the whole MCMP3 sweep.
Fixed-duty start path:
parse_commandreadsfixed_duty_start. If bothenableandfixed_duty_startare set we set a newapp.fixed_duty_activeflag and stashapp.fixed_duty_value.- In the control ISR, if
fixed_duty_active, we bypass the entire PI block:duty = clamp(app.fixed_duty_value, 0, 1)and freeze all three integrals at their current values.app.modeis reported as CV (arbitrary; UI shows "FIXED" instead). We still read ADCs so telemetry stays fresh.
Run-duration path:
- On enable, latch
run_seconds_remaining = run_duration_s(or0xFFFFif continuous). - In the slow
APP_Taskloop, once per heartbeat tick decrementrun_seconds_remaining(in seconds, derived fromHAL_GetTick). When it hits zero in non-continuous mode, force a disable. - Stop button (host sends disable) zeroes the timer.
PI gains read from g_settings (no more macros). The candidate /
frozen / commit logic stays identical so the integral-bumpless
behaviour the original code goes to lengths to preserve is kept.
Layout becomes a tab strip with three tabs: Setup, Run, Plot. Status/telemetry stays visible across all tabs.
Inputs grouped into cards:
- PI gains — six float inputs (Kp/Ki × CV/CC/CP).
- Power frequency — number, 10 000 – 100 000 Hz. Live readout of
the resulting
prdand "selected prescaler". - Dead time — number, 50 – 1000 ns. Live readout of the resulting raw ticks (with the 5.88 ns/tick reminder).
- ADC trigger — for ADC1, ADC2 regular, and ADC2 injected separately, a free-form
expression input plus a small read-only "evaluated range" line showing
eval(MCMP3 = lead)andeval(MCMP3 = lead + prd/2). The host also shows the parsed linear coefficients sent on the wire. - ADC sample times — drop-downs per rank inside each ADC block with friendly names:
- ADC1 rank 1: VSEC, rank 2: VPRI
- ADC2 regular rank 1: IPRI_AC, regular rank 2: IPRI_DC
- ADC2 injected rank 1: ISEC
- Oversampling — separate ADC1, ADC2 regular, and ADC2 injected drop-downs 1× / 2× / 4× /
… / 256×. We don't expose the shift separately because it is locked
to
log2(ratio). - "Apply" button. Disabled while latched on. Disabled while the
validator finds an error; the error text shows in a banner above
the button. On click, builds the 65-byte settings packet, sends it,
and shows "WAITING …" until the heartbeat reports
settings_ok = 1(with a 1500 ms timeout that flips the banner to an error).
- Targets CV / CC / CP (existing inputs).
- Run-duration field plus a "Continuous" checkbox.
- Start-mode radio: "Regular (closed loop)" / "Fixed duty (NO LOAD = RISK)".
- When fixed-duty selected: an extra duty input (0 – 100 %). A red reminder banner appears: "FIXED DUTY: LOAD REQUIRED".
- Apply Enable sends the extended command packet. Disable
sends a disable command (
fixed_dutyandrun_durationechoed from the latest known values, but the disable bit wins). - The Run tab also surfaces:
RUN REMAININGchip (counts down from heartbeat).SETTINGS OKindicator chip.- Mode chip already shows CC/CV/CP — gain a FIXED state.
Untouched from v1 except the metric list gains:
settingsOk(boolean)fixedDutyActive(boolean)runSecondsRemaining(uint16)
These join the existing free-choice plot picker. Default plot selection unchanged (VSEC, ISEC, IPRI AC).
Centralise frame builders. buildSettingsFrame(...),
buildCommandFrameV2(...). Heartbeat parser updated for the 96-byte
layout and the new bits. The receiver remains robust: on any frame
mismatch we shift one byte and resync (no auto-disconnect).
Sampling-time table (cycles) and ADC clock division replicated on both sides. ADC time check:
adc_clock_hz = 170_000_000 / 4
sample_cycles = ADC_SAMPLE_TIME_CYCLES[idx]
conv_cycles = sample_cycles + 12.5 // 12-bit
ovs_count = max(1, 1 << adcN_oversample_log2)
rank_total = conv_cycles * ovs_count
adc_total_us = sum(rank_total) / adc_clock_hz
period_us = 1e6 / freq_hz
require adc_total_us < period_us // per ADC
The UI shows the budget consumption as xx.x % of period per ADC
and refuses to send when either exceeds 100 %.
- We don't expose mid-run settings changes. The hardware-level cost of stopping HRTIM + ADCs would create a power-stage glitch and the spec says these are pre-run knobs. Settings packets received while the latch is on are dropped.
- We don't expose Tim A CMP1 (always
prd/2) or MCMP1 (always 48) — those are structural and the design doc fixes them. - We retain the per-period 50 kHz baseline by leaving the defaults identical to the master branch: same PI gains, 50 kHz, 247 ns dead time, MCMP3-aware ADC2 trigger at switch midpoint, fixed 13/16 ADC1 trigger.
- Decoupled HostUI protocol/data handling from UI/rendering:
- Added
HostUI/protocol.jsas a plain non-module browser script that exportswindow.HvccpsProtocol. - Moved protocol constants, checksum helpers, little-endian binary read/write helpers, command/settings frame builders, heartbeat frame parsing, heartbeat stream resynchronization, default settings cloning, and settings validation/derived timing calculations into
protocol.js. - Kept
HostUI/app.jsfocused on DOM binding, status/telemetry rendering, Chart.js setup, signal selection UI, layout persistence, toast/messages, WebSerial port lifecycle, and calling protocol helpers. - Replaced the UI-owned
rxBufferwith a protocol-ownedcreateHeartbeatDecoder()instance; connect/disconnect now reset the decoder instead of directly clearing byte buffers. parseHeartbeat()inapp.jsnow delegates binary parsing toparseHeartbeatFrame()and only handles UI-facing side effects such as packet rate counting, settings apply confirmation, and telemetry refresh.- Kept the page load model as non-module scripts by loading
protocol.jsbeforeapp.jsinHostUI/index.html.
- Added
- Tightened the boundary after the split:
app.jshas its own small UI clamp helper for layout sizing instead of importing a protocol-level clamp.- Protocol internals keep their clamp helper private to frame construction.
- Updated
Design.mdminimally to record the new HostUI JS split. - Verification:
- Ran
node --check HostUI\protocol.js; syntax check passed. - Ran
node --check HostUI\app.js; syntax check passed. - Ran
git diff --checkfor the HostUI files; no whitespace errors. - Firmware was not changed, so Keil firmware compilation was not run.
- Ran
- Fixed the host UI settings-apply confirmation display:
HostUI/app.jsalready parsedsettings_okfrom heartbeatstatus_flags & 0x08and clearedpendingSettingswhen the device confirmed the settings.- The settings panel banner was not updated on that success path, so it could remain stuck at
Sent. Waiting for SETTINGS OK ...even after the device had applied the new settings. - Added success and timeout/reject banner updates in
parseHeartbeat()so the settings panel now changes to an applied message aftersettings_okis observed, or to an error message after timeout.
- Checked the firmware/host protocol path for this issue:
- Firmware accepts settings frames with header
0x5A, length65, checksum/xor validation, then runsapply_settings()and setsapp.settings_ok = 1. - Firmware reports that state in heartbeat
status_flagsbit3. - Host parses bit3 as
latest.settingsOk, so the protocol bit itself is consistent.
- Firmware accepts settings frames with header
- Updated
Design.mdminimally sostatus_flagsmatches the implemented heartbeat protocol:- bit3 is
settings_ok - bit4 is
fixed_duty_active - bit5-7 remain reserved
- bit3 is
- Reworked the HostUI layout for bench operation:
- Replaced the long single-column workflow with a two-dock workbench layout.
- Left control dock contains serial connection, run controls, and pre-run settings.
- Right monitor dock keeps the telemetry plot visible above the status metrics, so users do not need to scroll down after enabling output.
- Run options and settings groups now use collapsible
<details>panels for duration, start mode, PI gains, switching, ADC1, and ADC2.
- Added bottom-right toast notifications:
setMessage()still updates the inline message line for state history.- Every operational message now also creates a timed toast in the bottom-right corner.
- Error toasts stay visible longer and use the existing red warning styling.
- Improved plot smoothness:
- Removed the old 80 ms
setInterval(refreshChart)chart update loop. - Added a
requestAnimationFramechart loop capped at about 60 fps, while keeping telemetry samples at 20 ms. - Enlarged the chart viewport with responsive height constraints.
- Removed the old 80 ms
- Removed duplicated chart value chips:
- Deleted the
chartSelectionChipsDOM area fromindex.html. - Removed
renderChartSelectionChips()andchartChipValuesupdates fromapp.js. - The signal picker remains available, while live values stay in the main status metric cards.
- Deleted the
- Updated
Design.mdwith a minimal Host UI layout note so the document matches the new workbench/toast behavior.
- Refined HostUI interaction model after bench-use review:
- Converted desktop layout into a fixed-height workbench instead of a long page.
- Left control area and right monitor area now scroll independently with
overscroll-behavior: contain, so scrolling at the bottom of the left control area no longer drives the right/page scroll. - Added a three-tab control dock (
Link,Run,Settings) to reduce vertical stacking and make the main actions faster to reach. - Changed the plot signal selector into an absolute popover on desktop, so opening signal selection does not push the plot down.
- Kept responsive fallback for narrow screens as normal page scrolling.
- Disabled accidental numeric input stepping:
- Added keydown handling to prevent
ArrowUp/ArrowDownfrom changing anyinput[type="number"]. - Added wheel handling with
passive: falseto prevent mouse wheel changes on numeric fields.
- Added keydown handling to prevent
- Updated
Design.mdwith the minimal note that desktop HostUI uses independent scrolling, control tabs, and numeric-input step blocking.
- Reworked the HostUI layout to make the waveform area the primary workspace:
- Replaced the fixed minimum left-column layout with a remembered, draggable control dock width.
- Added a vertical resize handle between the control dock and monitor dock; dragging changes the left control width and immediately resizes the chart.
- Added keyboard support on the resize handle: left/right arrows step the width, Home/End snap to min/max, and double click restores the default width.
- Added a collapse/expand button for the control dock so the plot can use nearly the full window when tuning or inspecting long waveforms.
- Replaced the plot signal picker popover:
- Removed the absolute top-right signal picker that could cover parameters and the chart.
- Added a non-overlapping right-side signal drawer inside the chart workspace.
- Added a
Signalstoolbar button with selected-count feedback and a close button inside the drawer. - Added a signal search field that filters by group, key, label, and axis text.
- Added a selected-signal strip above the chart; clicking a signal chip removes it from the plot.
- Refined responsive behavior and styling:
- Kept narrow screens as normal single-column scrolling with the control dock visible even if a desktop collapse state was saved.
- Tightened settings grids with
auto-fitso the left dock remains usable at smaller widths. - Updated the dark industrial palette and hover/focus affordances without changing protocol behavior.
- Verification:
- Ran
node --check HostUI\app.js; syntax check passed. - Firmware was not changed, so Keil firmware compilation was not run.
- Ran
- 问题:下位机
app_core.c中APP_ADC2_RANK_IPRI_DC和APP_ADC2_RANK_IPRI_AC的 rank 常量互换了(DC 写了 0、AC 写了 2),与 CubeMX 硬件配置不一致。实际adc.c中 ADC2 的 rank 顺序为:rank 0 = IPRI_AC(PA1/CH2)、rank 1 = ISEC(PA6/CH3)、rank 2 = IPRI_DC(PA0/CH1)。这导致心跳包build_heartbeat()去交织 DMA 缓冲区时 IPRI_AC 和 IPRI_DC 数据互换。 - 上位机
app.js的CYCLE_SIGNALS中 IPRI_AC 和 IPRI_DC 的 rank/sampleDelayNs/positions 也相应互换了(AC 标记为 rank 2、DC 标记为 rank 0),导致逐周期波形图中 X 轴时间偏移各差了约 518 ns。 - 修复:
app_core.c:APP_ADC2_RANK_IPRI_AC改为 0U,APP_ADC2_RANK_IPRI_DC改为 2U(ISEC 保持 1U 不变)。app.js:CYCLE_SIGNALS中交换 ipriAc 和 ipriDc 两个对象的位置,使 ipriAc 对应 ADC2 rank 0(~29 ns)、ipriDc 对应 ADC2 rank 2(~547 ns)。
- 修复后的正确时序:
信号 ADC Bank Rank 采样偏移 VSEC ADC1 0 ~29 ns IPRI_AC ADC2 0 ~29 ns ISEC ADC2 1 ~288 ns VPRI ADC1 1 ~382 ns IPRI_DC ADC2 2 ~547 ns - 上位机语法检查通过。
- Fixed HRTIM timing visualization hover layering and tick-reference behavior:
- Moved
#vizTooltipto the document body at startup and raised its z-index above the click-to-edit popover, so the hover summary remains visible when a settings popover is open. - Kept the top timing row and cycle separator axis fixed to the Master timer frame.
- Removed the old clickable tick-reference label behavior from the Master timer row.
- Added Tab / Shift+Tab handling while a hover summary is visible to cycle only the hover summary's local tick reference between Master, TIM A, and TIM B.
- Preserved normal Tab focus movement while editing inputs/selects/textareas or working inside the settings popover.
- Removed absolute cycle labels from hover summaries, so users see local tick ranges only instead of values such as
cyc -2,cyc 0, or large panned cycle indices.
- Moved
- Updated
Design.mdminimally to record that the visualization top timeline remains Master timer while hover summaries have their own Tab-cycled local tick reference. - Verification:
- Ran
node --check HostUI\HRTIM_visualization\viz.js; syntax check passed. - Ran
git diff --check -- HostUI\HRTIM_visualization\viz.js HostUI\HRTIM_visualization\viz.css; no whitespace errors. - Firmware was not changed, so Keil firmware compilation was not run.
- Ran
- Fixed two HostUI signal-selection usability issues:
- Changed the signal picker from a chart-layout side panel to a fixed floating drawer, so it stays above the chart and remains clickable instead of being pushed below the waveform area.
- Kept the drawer full-height on narrow screens with viewport insets, so saved layout width/collapse states cannot make the picker unreachable.
- Changed selected signal chips from click-to-remove buttons into read-only labels with a small explicit
xremove button. - The chip body no longer removes a signal, reducing accidental removals while inspecting the plot.
- Verification:
- Ran
node --check HostUI\app.js; syntax check passed. - Firmware was not changed, so Keil firmware compilation was not run.
- Ran
-
在下位机
app_core.c中加入自动变频策略:- TIM7 以 10 ms 周期运行低优先级频率监督逻辑,只计算待执行计划,不直接写 HRTIM。
- HRTIM master ISR 在开关周期边界消费 pending 变频计划,执行寄存器写入与前馈 duty 生效。
- TIM7 预先计算:
- 新频率
freq_hz - master / Tim A / Tim B 周期
prd - Tim C 24x ADC 链周期
- Tim D 10x ADC5 链周期
- Tim E 16x ADC3/4 链周期
- 实际频率
actual_freq_hz phase_max_rawphase_span_finv_span_f- 前馈
feedforward_duty - 前馈
feedforward_mcmp3_raw
- 新频率
- HRTIM ISR 消费计划时只写这些已算好的值,不在高速 ISR 中调用
sinf/asinf/powf或重新计算周期寄存器。
-
变频控制策略:
- duty 以 TIM7 10 ms tick 做一阶滤波,滤波系数
0.25。 - 降频触发区:
duty > 95%,duty > 98%和duty > 99.5%时加速计分。 - 升频触发区:
duty < f(kHz) + 25。 - 升频停止/回滞线:
duty > (6/7) * f(kHz) + 36.43。 - 使用 score 计分触发,不用单点瞬时判定;回到带内时 score 快速衰减。
- 变频后、目标值变化后、刚使能后分别设置锁定窗口,避免连续抖动:
- 变频后 100 ms
- 目标变化后 100 ms
- 刚使能后 200 ms
- 步进为自适应 1–4 kHz,默认约为当前频率 6%;严重饱和降频允许更快下探。
- duty 以 TIM7 10 ms tick 做一阶滤波,滤波系数
-
duty 前馈:
- 使用基于测试数据拟合的连续模型:
s = sin(pi * duty / 2)s_new = s * (f_new / f_old)^0.924duty_ff = asin(clamp(s_new, 0..1)) * 2/pi
- TIM7 计算前馈 duty 和对应 MCMP3 raw。
- HRTIM ISR 执行变频时同步当前胜出 PI 环积分:
integral = duty_ff - kp * error- clamp 到
[0, 1]
- 这样变频后 PI 输出从前馈 duty 附近继续闭环修正,减少频率切换瞬间的功率抖动。
- 使用基于测试数据拟合的连续模型:
-
状态管理:
- 输出关闭、固定占空比、OCP 锁存、频率越界时自动变频监督复位并清 pending plan。
apply_settings()、目标值变化、disable、run duration 到期和 enable 均重置变频监督状态。- TIM7 已在
APP_Init()中通过HAL_TIM_Base_Start_IT(&htim7)启动。 APP_TIM7_FrequencyControlISR()加入app_core.h,由通用HAL_TIM_PeriodElapsedCallback()分派 TIM7 进入。
-
文档:
- README 当前版本说明已补充 TIM7 自动变频、阈值、前馈模型、步进和锁定窗口。
-
验证:
- 使用 Keil 命令编译下位机:
& 'D:\apps\keil_v5\uv4\uVision.com' -b G474_HVCCPS.uvprojx -j0 -o build.log
- 编译结果:0 Error(s), 0 Warning(s)。
- 使用 Keil 命令编译下位机:
背景:
AppHostUI的 Power Status 区只有CP TARGET(恒功率设定值)和Eff(效率)两个功率类指标,缺少一个直接显示当前实际输出功率的读数。运行时想确认真实输出功率(尤其是 CP 模式下设定值与实测的对照)只能口算 VSEC×ISEC。
改动(纯上位机派生量,不动协议 / 下位机):
AppHostUI/app.js新增outputPowerMw(latest)辅助函数:vSecMv[mV] × iSecMa[mA] / 1000 → mW(mV×mA=µW,除 1000 得 mW)。直接复用心跳帧已有的vSecMv(副边输出电压)与iSecMa(副边输出电流)字段,无需改动protocol.js或下位机帧格式。TELEMETRY_METRICS新增一条(Power 分组):key: "outputPowerW",label: "OUTPUT POWER",color: "#d97706"(与CP TARGET的#ea580c、Eff的#b91c1c区分),axisLabel: "OUTPUT POWER / W"。read: (l) => outputPowerMw(l) / 1000(图表 Y 轴用 W,与 CP TARGET 同量纲)。format: (l) => formatPowerMw(outputPowerMw(l))(< 1000 mW 显示 mW,否则自动换算到 W)。- 排在
cpTargetW之后、efficiencyPct之前,使「设定功率」与「实测功率」相邻。
KEY_METRIC_KEYS追加"outputPowerW",使其作为关键指标卡片显示在 Power Status header(紧随 CP TARGET),而非次级 reading 行。- 系统为数据驱动:
createSeriesStore()/ 图表信号选择器 /renderTelemetryCards()/updateTelemetryUi()均遍历TELEMETRY_METRICS,新增项自动获得历史曲线、可被选入实时图、并随每帧刷新。#readingsKey为 2 列自动流式网格,第 9 张卡片自然换行,无需改 CSS。
文档:
- README 右侧布局说明补充:关键指标卡片含由 VSEC×ISEC 实时计算的 OUTPUT POWER。
验证:
node --check AppHostUI/app.js通过(SYNTAX OK)。
背景:
- 准备发布面向开源仓库的 V0.0.1 固件 HEX。固件源码暂不公开,公开固件需要限制得更保守,避免误设置过高输出或保护阈值。
固件限制:
App/Core/Src/app_core.cAPP_MAX_CV_MV从3000000收紧为2200000,运行命令 CV 上限为 2200 V。APP_MAX_CC_MA从250收紧为200,运行命令 CC 上限为 200 mA。APP_MAX_CP_MW从500000收紧为400000,运行命令 CP 上限为 400 W。- 硬件 OCP 比较器阈值
APP_OCP_TRIP_A从 75 A 收紧为 60 A;对应 DAC 阈值从约 2812 mV 变为约 2250 mV。 - 软件 OTP 阈值
APP_OTP_TRIP_MC从 75000 m°C 收紧为 70000 m°C。
App/Core/Src/config_manager.c- 前面板 KEY_A / KEY_B 预设限制同步为 2200 V / 200 mA / 400 W,防止离线上电预设绕过运行命令上限。
上位机:
AppHostUI/protocol.jsMAX_CV_V/MAX_CC_MA/MAX_CP_W同步为 2200 / 200 / 400。
AppHostUI/index.html- RUN 面板和前面板预设抽屉的 number input
max同步为 2200 V / 200 mA / 400 W。
- RUN 面板和前面板预设抽屉的 number input
文档:
- README 当前版本描述同步为 2200 V / 200 mA / 400 W、OCP 60 A、OTP 70 °C。
- README 顶部和 Bootloader 小节移除本地 Keil 编译命令,改为描述发布 HEX 的 ST-Link / WebSerial IAP 烧录方式。
验证:
- 上位机基础语法校验通过:
node --check AppHostUI/app.jsnode --check AppHostUI/protocol.jsnode --check AppHostUI/history.jsnode --check BootLoaderHostUI/app.js
- Keil 编译 App:
- 工程:
App/MDK-ARM/G474_HVCCPS.uvprojx - 结果:0 Error(s), 0 Warning(s)
- HEX:
App/MDK-ARM/G474_HVCCPS/G474_HVCCPS.hex
- 工程:
- Keil 编译 Bootloader:
- 工程:
BootLoader/MDK-ARM/G474_BL.uvprojx - 结果:0 Error(s), 0 Warning(s)
- HEX:
BootLoader/MDK-ARM/G474_BL/G474_BL.hex
- 工程:
- 发布资产已复制:
release_assets/HVCCPS_App_V0.0.1.hexrelease_assets/HVCCPS_Bootloader_V0.0.1.hex