Files
Motor_v2/docs/review/Motor_v2_专家评审_2026-10-19.html
T

64 lines
39 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Motor_v2 专家评审 2026-10-19</title><style>body{font-family:-apple-system,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;margin:0;background:#f5f6f8;color:#1f2328;line-height:1.6}
main{max-width:1200px;margin:0 auto;padding:24px}h1{font-size:26px;margin:0 0 4px}h2{border-left:4px solid #2f6feb;padding-left:10px;margin-top:36px}
.meta{color:#57606a;font-size:14px}.card{background:#fff;border-radius:8px;padding:16px 20px;box-shadow:0 1px 3px rgba(0,0,0,.08);margin:12px 0}
table{border-collapse:collapse;width:100%;background:#fff;font-size:13px}th,td{border:1px solid #d0d7de;padding:6px 8px;vertical-align:top;text-align:left}
th{background:#eef2f7;position:sticky;top:0}code{font-size:12px;background:#f0f2f5;padding:1px 4px;border-radius:3px;word-break:break-all}
.b{display:inline-block;padding:1px 8px;border-radius:10px;color:#fff;font-size:12px;white-space:nowrap}.b0{background:#b91c1c}.b1{background:#ea580c}.b2{background:#ca8a04}.b3{background:#2563eb}
tr.s0 td{background:#fef2f2}.kpi{display:flex;gap:12px;flex-wrap:wrap}.kpi div{flex:1;min-width:120px;background:#fff;border-radius:8px;padding:12px;text-align:center;box-shadow:0 1px 3px rgba(0,0,0,.08)}
.kpi b{font-size:28px;display:block}pre{background:#0d1117;color:#e6edf3;padding:14px;border-radius:8px;overflow:auto;font-size:13px}</style></head><body><main>
<h1>Motor_v2 电机固件专家评审</h1>
<div class="meta">仓库 cbb/Motor_v2 · 分支 master · 评审基线提交 <code>86ca766</code>(2026-10-13 “feat(motor): 引入运行禁止机制重构电机控制安全系统”)· 评审日期 2026-10-19 · 静态评审,未实编/未上机</div>
<div class="kpi"><div><b style="color:#b91c1c">2</b>致命</div><div><b style="color:#ea580c">15</b>严重</div><div><b style="color:#ca8a04">22</b>一般</div><div><b style="color:#2563eb">11</b>建议</div><div><b>5.6</b>综合评分/10</div></div>
<h2>1. 项目概述与架构</h2>
<div class="card">GD32C103CB(Cortex-M4F,120 MHz)上的无刷电机 FOC 驱动固件,配 DRV8313 门驱动、KTH7823 磁编码器,CAN-FD(仲裁 1 Mbps / 数据 5 Mbps)与上位机通信;CMSIS-Toolbox csolution + AC6(microlib) 构建;应用位于 0x08012000,配合独立 BOOT 实现 OTA。最新提交引入了 <code>RunInhibit</code> 位图式“运行禁止”闸门,作为功率级唯一执行者。</div>
<pre>┌──────────────── GD32C103CB (Cortex-M4F @120MHz, APP @0x08012000) ────────────────┐
│ 中断层 │
│ ADC0 注入组(TIMER1 CH3 TRGO 触发) ─► 写三相电流 + 置 TASK_CURRENT_UPDATE [prio 0,0] │
│ TIMER2 50µs 节拍 ─► 置任务位(角度/速度/电机控制/1ms CAN …) [prio 0,1] │
│ TIMER1 中心对齐 PWM ≈20.0067kHz (ARR=2999) [prio 0,2] │
│ SysTick 1ms(delay/CPU 统计) [0] CAN RX0/TX/DMA0-CH4 [prio 1] │
├──────────────────────────────────────────────────────────────────────────────────┤
│ 主循环 main.cpp:关中断取任务位快照 → do_task() │
│ ├ 高频:KTH7823 SPI 读角度 → α-β/卡尔曼 → run_inhibit.update() → FOC 模式分发 │
│ │ Clarke/Park → PID(电流/速度/位置/MIT) → 逆Park → SVPWM → Set_3Pwm │
│ ├ 低频:health(热模型/欠压/nFAULT) · power_manage · 喂狗 │
│ └ CAN:CAN-FD(仲裁1M/数据5M) 命令解析 · 周期反馈帧 · 参数持久化(flash.cpp EEPROM) │
├──────────────────────────────────────────────────────────────────────────────────┤
│ 存储:EEPROM 模拟 0x0801F800-0x08020000(2×1KB) · 节点ID 乒乓双备份+CRC · OTA/备份区 │
└──────────────────────────────────────────────────────────────────────────────────┘</pre>
<table><tr><th>目录</th><th>主要文件</th><th>职责</th><th>规模(行)</th></tr><tr><td><code>Algorithm/</code></td><td>FOC.cpp/h、pid、Filter(α-β/卡尔曼)</td><td>坐标变换、SVPWM、多模式控制环、零位校准</td><td>~1.8k</td></tr><tr><td><code>device/</code></td><td>Can、ADC、Timer、ang_sensor、motor</td><td>CAN-FD 协议栈、注入采样、PWM、KTH7823 SPI、DRV8313 使能</td><td>~3.6k</td></tr><tr><td><code>sys/</code></td><td>flash、health、run_inhibit、initial、systick、config</td><td>参数持久化/OTA、保护、运行禁止闸门、初始化、时基</td><td>~3.0k</td></tr><tr><td><code>顶层</code></td><td>main.cpp、tasks.cpp</td><td>调度主循环、任务拆分</td><td>~0.4k</td></tr><tr><td><code>RTE/</code></td><td>GD32C10x 标准库、启动文件、链接脚本</td><td>厂商库(含本地修改)</td><td>—</td></tr></table>
<h2>2. 评审范围与方法</h2>
<div class="card"><ul>
<li>范围:Robot_V2/ 下全部应用源码(Algorithm、device、sys、main、tasks,约 9.6k 行)、RTE 启动文件/时钟/链接区域/中断文件、csolution/cproject/cbuild-pack/vcpkg、.gitignore、IDE 配置;厂商标准库仅比对与 .base 的差异。</li>
<li>专家组:①电机控制算法/FOC ②实时性/中断/DMA/MCU 架构 ③CAN-FD 通信协议 ④功能安全与故障保护 + Flash 存储 ⑤代码质量、构建与仓库卫生。各视角独立通读后交叉核对,关键结论由主评审逐行复核(M01、M02、M04、M07、M10、M19 等)。</li>
<li>既有记录:全仓搜索 .md/.html,仅根目录 <code>STATIC_ANALYSIS_NOTES.md</code>。逐条核对见第 4 节,修订版见 <a href="STATIC_ANALYSIS_NOTES_复核_2026-10-19.md">STATIC_ANALYSIS_NOTES_复核_2026-10-19.md</a>(原文件保留未改)。</li>
<li>严重度:<b>致命</b>=可能导致失控/烧毁/伤人;<b>严重</b>=保护失效、控制失效或数据丢失;<b>一般</b>=功能缺陷/可靠性隐患;<b>建议</b>=质量与可维护性。</li>
<li>局限:未编译、未上机、未示波;行号基于 86ca766。</li></ul></div>
<h2>3. 问题清单(50 条)</h2>
<table><tr><th>编号</th><th>严重度</th><th>领域</th><th>文件:行号</th><th>标题 / 现象</th><th>风险</th><th>修改建议</th></tr><tr class="s0"><td>M01</td><td><span class="b b0">致命</span></td><td>CAN/FOC</td><td><code>device/Can.cpp:1128-1132;Algorithm/pid.cpp:50,126</code></td><td><b>控制帧目标电流/速度直接作为 PID 限幅,未校验符号与有限性</b><br>目标电流 target_current、目标速度 target_velocity 被原样传入 set_limit();pid.cpp:50 _constrain(x,-limit,limit) 在 limit&lt;0 时上下界颠倒;NaN/Inf 未过滤。</td><td>mode3/4/5 下发负值时 PID 输出被钉在 +|limit|,与误差方向无关,可导致反向满压、电机失控;电流环限幅(电压量纲)被设为电流值,单位错配。</td><td>协议层对所有浮点字段做 isfinite() 与范围夹紧;set_limit 内取 fabsf 并拒绝非正值;电流环输出限幅改用 limit_voltage/v_safe,与目标电流分离。</td></tr><tr class="s0"><td>M02</td><td><span class="b b0">致命</span></td><td>Flash/安全</td><td><code>sys/flash.cpp:304-321,393-404,887-899(format 后 save_system_config);sys/flash.cpp:77-84</code></td><td><b>EEPROM 页满重建时丢失电角度零位校准角</b><br>任一写入触发页满 → format() 擦除两页 → save_system_config(false) 只重写 6 个控制参数、CRC 与两个刷新周期,不重写 ADDR_CALIBRATION_ANGLE(0x07);若正是写校准角触发的页满,新校准角同样不会写入。上电回退默认 83°。</td><td>换向角错误 → 力矩方向/大小错误,速度环可能正反馈失控,且无任何告警(init 认为读取失败属正常首启)。</td><td>save_system_config 重建路径同时重写校准角(及将来所有独立记录);或改为双页轮转搬运最新有效记录;无有效校准角时置 INHIBIT 位禁止闭环并上报。</td></tr><tr class="s1"><td>M03</td><td><span class="b b1">严重</span></td><td>CAN/安全</td><td><code>sys/run_inhibit.h:19-29;device/Can.cpp(无命令超时逻辑)</code></td><td><b>无通信超时/心跳丢失停机</b><br>禁止来源仅 nFAULT/欠压/过热/采样失效/未使能,无 CAN 超时来源;收到最后一条控制帧后电机持续执行。</td><td>上位机死机、线缆脱落、总线 Bus-Off 时电机按最后目标一直运行(速度模式下持续转动)。</td><td>新增 INHIBIT_COMM_TIMEOUT:控制帧到达刷新时间戳,超时(可配置,如 100 ms)request(),收到新控制帧再 release;超时阈值可经 CAN 配置并持久化。</td></tr><tr class="s1"><td>M04</td><td><span class="b b1">严重</span></td><td>FOC/安全</td><td><code>Algorithm/FOC.cpp:59-63;sys/systick.cpp:164-168</code></td><td><b>零位校准在主循环阻塞约 1 s 且持续喂狗</b><br>calibrate_zero_position() 直接 motor_resume() + Set_3Pwm(ALIGN_PWM_DUTY) + delay_1ms(1000);delay_1ms 循环内 fwdgt_counter_reload()。</td><td>1 s 内欠压快检、nFAULT 轮询、热模型、CAN 处理全部停摆,看门狗也被持续喂而失去意义;对齐电流无限流、对齐后未判转子静止。</td><td>改为非阻塞状态机(每 50 µs 推进),期间 run_inhibit/保护任务照常运行;对齐时做电流闭环或占空比按 Vbus 归一;delay_1ms 内不得喂狗。</td></tr><tr class="s1"><td>M05</td><td><span class="b b1">严重</span></td><td>安全/FOC</td><td><code>Algorithm/FOC.cpp:426-431;sys/config.h(MAX_VCC 仅作读数有效判定)</code></td><td><b>无过压保护,Vbus&gt;36 V 时 SVPWM 按旧电压归一</b><br>update_power 仅接受 8–36 V,超出时保留旧 Vdc;固件无过压禁止位,回馈泵升无处理。</td><td>制动/回馈时母线抬升,实际相电压被放大,叠加功率器件超压,存在击穿 MOSFET/驱动风险。</td><td>增加 INHIBIT_OVERVOLTAGE(带回差与防抖);超限时继续用实测 Vbus 归一;必要时短路制动或限制回馈电流。</td></tr><tr class="s1"><td>M06</td><td><span class="b b1">严重</span></td><td>实时性/安全</td><td><code>RTE/Device/GD32C103CB/startup_gd32c10x.s:178-181;gd32c10x_it.c 未编入工程</code></td><td><b>HardFault 等异常直接死循环,PWM 保持最后占空比</b><br>实际生效的是启动文件弱定义 `B .`,不关 PWM、不睡眠驱动,依赖约 50 ms 看门狗复位。gd32c10x_it.c 不在 cproject 编译列表且重复定义 SysTick_Handler。</td><td>异常期间三相按最后占空比输出最长约 50 ms(IRC40K 偏差下更不确定),可能过流/冲击。</td><td>实现强定义 HardFault/MemManage/BusFault/UsageFault/NMI:首先关 TIMER1 输出(CHxEN/POEN)并拉低 nSLEEP,再保存故障现场到 BKP/保留 RAM,最后复位。</td></tr><tr class="s1"><td>M07</td><td><span class="b b1">严重</span></td><td>安全</td><td><code>device/Can.cpp:1167-1168;device/motor.cpp:243-249;sys/run_inhibit.cpp:32-45</code></td><td><b>motReset 直接唤醒驱动,绕过运行禁止闸门的睡眠状态</b><br>RESET bit0 先 release(NFAULT) 再 motor_reset()(sleep→resume)。若其他禁止位仍在,闸门 stopped 已为 true,不会再次 motor_sleep(),驱动保持唤醒。</td><td>欠压/过热/未使能期间驱动被唤醒(PWM 虽被置 0,但违背单一执行者设计,DRV8313 故障锁存也被清除)。</td><td>motReset 仅 release(NFAULT),驱动睡眠/唤醒全部交给 RunInhibit::update();或在闸门中把 stopped 置 false 强制重新施加睡眠。</td></tr><tr class="s1"><td>M08</td><td><span class="b b1">严重</span></td><td>CAN/Flash</td><td><code>device/Can.cpp:940,1048,1175;sys/flash.cpp(writeNodeId/save_system_config/format)</code></td><td><b>运行中 Flash 擦写使控制环停摆</b><br>节点 ID、刷新周期、RESET bit2 存参等命令在主循环中直接擦写 Flash,不检查电机是否静止;FOC 控制环也在主循环(tasks.cpp:96-118)。</td><td>单 bank 擦页期间 CPU 取指停顿数十 ms,PWM 保持最后占空比,控制失效;可能触发看门狗复位。</td><td>仅在 control_mode==0 且被禁止/静止时允许持久化;否则延迟到停机或回 NACK;擦写前先 request(INHIBIT_FLASH_BUSY)。</td></tr><tr class="s1"><td>M09</td><td><span class="b b1">严重</span></td><td>CAN</td><td><code>device/Can.cpp:950,967-981</code></td><td><b>旧格式 SET_NODE_ID 未拦截广播</b><br>DLC 1/2 的旧格式允许 target_node==BROADCAST;扩展格式(约 1010 行)已拦截,旧格式没有。</td><td>一条广播帧可把总线上所有节点改为同一 ID,造成 ID 冲突、命令错发至多个电机。</td><td>旧格式同样拒绝广播目标,并要求魔数/二次确认;考虑废弃旧格式。</td></tr><tr class="s1"><td>M10</td><td><span class="b b1">严重</span></td><td>Flash</td><td><code>sys/flash.cpp:444,906;sys/health.cpp:195</code></td><td><b>一次欠压保存后本次上电所有存参被跳过</b><br>欠压路径 save_system_config(true) 将 system_config_saved 置 true 且永不清零,此后 444 行直接返回 OK。</td><td>欠压恢复后的参数修改、进 BOOT 前保存、format 后的重建全部被静默跳过 → 与问题 2 叠加,页满时整页参数丢失。</td><td>save 标志只用于“本次欠压已保存”,在欠压解除时清零;format 重建路径应强制保存(忽略该标志)。</td></tr><tr class="s1"><td>M11</td><td><span class="b b1">严重</span></td><td>Flash</td><td><code>sys/flash.cpp:413-430,161-181;main.cpp:107</code></td><td><b>EEPROM 无掉电原子性,CRC 错误时参数全部归零</b><br>format() 同时擦除两页,无备份页;format 可能在欠压保存路径中被触发;CRC 错误时全部参数回 0(含多圈角度 angle_set);main 不检查 flashEEP.init() 返回值,也不进 init_error_flags。</td><td>掉电/欠压期间擦写失败即全部配置丢失,上电以 0 参数运行;多圈位置丢失。</td><td>采用双页交替(有效页标记 + 搬运后再擦旧页);欠压时禁止 format;init 失败置初始化错误位并禁止运行。</td></tr><tr class="s1"><td>M12</td><td><span class="b b1">严重</span></td><td>安全</td><td><code>sys/health.cpp:98-103,212-213</code></td><td><b>过热/欠压解除后自动恢复运行</b><br>保护解除后不需上位机确认,电机直接按原模式、原目标继续动作;过热还会在 PROTECT_TIME 到期后按时间解除。</td><td>无人值守下电机突然重新转动,存在机械伤人/撞击风险。</td><td>故障解除后保留 INHIBIT_NOT_ENABLED(需上位机重新下发使能);过热解除改为温度回差而非定时。</td></tr><tr class="s1"><td>M13</td><td><span class="b b1">严重</span></td><td>实时性</td><td><code>device/Timer.cpp:75-107,139-141;sys/config.h:376,384</code></td><td><b>控制节拍与 PWM/采样不同步,频率不一致</b><br>TIMER2 产生 50 µs(20 kHz) 任务节拍;TIMER1 中心对齐 ARR=2999 → 周期 5998 clk ≈ 20.0067 kHz。两者约 150 ms 拍频一次。</td><td>采样到控制的延时在 0~50 µs 漂移,每秒约 6.7 帧被重复/丢弃,产生约 6.7 Hz 转矩纹波与噪声。</td><td>由 ADC 注入完成中断(或 TIMER1 更新事件)驱动 FOC 计算;修正 ARR 使周期整数匹配;去掉独立的 TIMER2 控制节拍。</td></tr><tr class="s1"><td>M14</td><td><span class="b b1">严重</span></td><td>实时性</td><td><code>device/Timer.cpp:329-342</code></td><td><b>PWM 比较值写入未开影子寄存器预装载</b><br>Set_3Pwm 直接写 CHxCV,全仓无 timer_channel_output_shadow_config 调用。</td><td>周期中途更新产生窄脉冲/不对称脉冲,扰动低边电流采样窗口并增加开关损耗。</td><td>对 CH0~CH2 开启 TIMER_OC_SHADOW_ENABLE,并开启 ARR 预装载。</td></tr><tr class="s1"><td>M15</td><td><span class="b b1">严重</span></td><td>实时性</td><td><code>main.cpp:67-80;device/Timer.cpp:139</code></td><td><b>无任务超时/丢拍检测</b><br>主循环一轮超过 50 µs 时任务位被静默合并,控制直接丢拍,无计数无上报。</td><td>高负载(CAN 突发、Flash 擦写)时控制周期失真不可见,调试困难,严重时失稳。</td><td>在 TIMER2 ISR 中检测上次置位未被取走则累加 overrun 计数,通过 CAN 状态帧上报;超阈值请求禁止。</td></tr><tr class="s1"><td>M16</td><td><span class="b b1">严重</span></td><td>FOC</td><td><code>Algorithm/pid.cpp:64-68;device/ang_sensor.h:103-111;Algorithm/FOC.cpp:509-510</code></td><td><b>NaN/Inf 目标值导致积分污染与死循环</b><br>NaN 进 PID 后斜坡 fminf/fmaxf 使输出按最大速率爬升且积分永久为 NaN;Inf 进 normalizeAngle360 的 while 循环永不结束(mode6 开环角度累加路径)。</td><td>控制输出失控或主循环卡死(之后仅靠看门狗复位,期间 PWM 保持)。</td><td>协议层过滤非有限值;normalizeAngle360 改用 fmodf 并对非有限输入返回 0;PID 入口检测 isfinite 并复位。</td></tr><tr class="s1"><td>M17</td><td><span class="b b1">严重</span></td><td>FOC/安全</td><td><code>device/ang_sensor.cpp:78,160-176</code></td><td><b>角度传感器读失败/断线未升级为禁止运行</b><br>读失败返回 FAULT_RECEIVE 并保留旧角度,无错误计数、不请求 inhibit;读回 0xFFFF(MISO 浮空)被当作有效;TRANS 标志等待无超时。</td><td>编码器断线时以陈旧/错误角度换向,力矩方向错误,可能飞车。</td><td>连续 N 次读失败或校验失败 → request(INHIBIT_ENCODER_FAULT);识别 0x0000/0xFFFF 等非法值;TRANS 等待加超时。</td></tr><tr class="s2"><td>M18</td><td><span class="b b2">一般</span></td><td>CAN</td><td><code>device/Can.cpp:1117</code></td><td><b>控制帧不校验长度与模式范围</b><br>短帧缺失字段按 0 处理(P 增益等被清零);P_config、control_mode 无范围检查,tasks.cpp:113 对非法模式仅不执行。</td><td>错误/截断帧改变控制参数,行为不可预期。</td><td>按命令定义校验 DLC 与字段范围,不合法回 NACK 且不修改任何状态。</td></tr><tr class="s2"><td>M19</td><td><span class="b b2">一般</span></td><td>CAN</td><td><code>device/Can.cpp:1187</code></td><td><b>RESET 深睡眠位判断运算符优先级错误</b><br>`eigth_byte &amp; 0x80 &gt;&gt; 7` 等价于 `eigth_byte &amp; 1`,实际判断 bit0 而非 bit7。</td><td>上位机置 bit0 时意外进入深睡眠,bit7 反而无效。</td><td>改为 `(eigth_byte &gt;&gt; 7) &amp; 0x01` 或 `eigth_byte &amp; 0x80`。</td></tr><tr class="s2"><td>M20</td><td><span class="b b2">一般</span></td><td>CAN/FOC</td><td><code>device/Can.cpp:1177-1181</code></td><td><b>运行中可清零角度</b><br>RESET bit3 不检查状态即 angle_set(0) 并重置卡尔曼。</td><td>位置模式下目标与实际角度瞬间跳变,电机猛冲。</td><td>仅在 mode0/禁止状态下允许,或同时把 target_angle 置为新零点。</td></tr><tr class="s2"><td>M21</td><td><span class="b b2">一般</span></td><td>CAN</td><td><code>device/Can.cpp:1395,744</code></td><td><b>TX 路径主循环与 ISR 竞争邮箱、乱序</b><br>CANFD_AsyncSend 在主循环直接 can_message_transmit,同时 TX 中断从队列填邮箱。</td><td>选邮箱竞态、新帧绕过队列先发,反馈帧时序错乱。</td><td>统一经 TX 队列发送,仅在临界区内检测空邮箱并启动。</td></tr><tr class="s2"><td>M22</td><td><span class="b b2">一般</span></td><td>CAN</td><td><code>device/Can.cpp:896-924</code></td><td><b>BOOT 帧无魔数/校验</b><br>任何基础 ID 0x0000 的帧都可触发进 BOOT 或节点静默。</td><td>误触发导致电机离线。</td><td>增加魔数 + 目标节点 + 停机状态校验。</td></tr><tr class="s2"><td>M23</td><td><span class="b b2">一般</span></td><td>CAN</td><td><code>device/Can.cpp:575,630</code></td><td><b>远程帧未过滤</b><br>rx_ft 读出后未判断,远程帧被当作数据帧处理。</td><td>DLC 非 0 的远程帧携带随机数据进入命令解析。</td><td>丢弃 RTR 帧或单独处理。</td></tr><tr class="s2"><td>M24</td><td><span class="b b2">一般</span></td><td>CAN</td><td><code>device/Can.cpp:359,413,1241-1251</code></td><td><b>总线错误/Bus-Off 不可见,FIFO 溢出分支不可达</b><br>未开错误中断,Bus-Off 不上报也不纳入保护;只开 RFNE,FIFO 满/溢出处理永远进不去。</td><td>总线故障时静默失联(与无通信超时叠加)。</td><td>开启 EWIE/BOIE/ERRNIE,记录 TEC/REC,Bus-Off 时请求禁止并按策略恢复。</td></tr><tr class="s2"><td>M25</td><td><span class="b b2">一般</span></td><td>CAN</td><td><code>device/Can.cpp:394-407;sys/config.h:341</code></td><td><b>硬件过滤器全收,默认 STATUS 1 kHz×48 B</b><br>所有帧靠软件过滤。</td><td>多节点组网时总线与中断负载偏高。</td><td>按节点 ID 配置硬件过滤器;默认刷新率下调。</td></tr><tr class="s2"><td>M26</td><td><span class="b b2">一般</span></td><td>CAN/Flash</td><td><code>device/Can.cpp:1034</code></td><td><b>反馈刷新周期无下限且每次设置都写 Flash</b><br>可配置为极小周期;频繁设置磨损 Flash。</td><td>总线饱和、Flash 寿命下降。</td><td>设定下限(如 1 ms);值未变不写(write 已跳过同值,但应在命令层限频)。</td></tr><tr class="s2"><td>M27</td><td><span class="b b2">一般</span></td><td>Flash</td><td><code>sys/flash.cpp:230-257</code></td><td><b>readFloat 无保留字节防护,可能误读 CRC 字</b><br>read() 在 206 行用保留字节排除 CRC 字,readFloat 没有同类防护,扫描至头部 CRC 区。</td><td>记录不存在时可能把低字节为 0x07 的 CRC 字当作校准角返回,calib==0 判定拦不住。</td><td>float 记录格式加入类型标记/校验位,或扫描时跳过 CRC 区。</td></tr><tr class="s2"><td>M28</td><td><span class="b b2">一般</span></td><td>Flash/BOOT</td><td><code>sys/flash.cpp:929-1016,1077-1098</code></td><td><b>OTA 标志与备份区搬运缺乏原子性</b><br>OTA_SET 擦页后未写回目标值;前序写失败后 FMC 已锁,后续擦除失败;move_app_data 擦除备份区前未先作废 BACKUP_AVAL,48 页擦除期间关中断且不喂狗;新 APP 首启即覆盖备份区,A/B 回滚失效;地址注释过时。</td><td>升级中途掉电变砖或回滚失效。</td><td>先作废标志再擦写、分段喂狗、确认新 APP 正常运行后再覆盖备份;更新注释。</td></tr><tr class="s2"><td>M29</td><td><span class="b b2">一般</span></td><td>安全</td><td><code>Algorithm/FOC.cpp:466-468;sys/config.h:407-408</code></td><td><b>欠压快检基准只升不降,阈值注释不一致</b><br>vbus_reference 峰值保持无衰减;回馈短时抬高后恢复正常即被判跌幅&gt;5 V;高母线时恢复阈值(×0.9)低于触发阈值(−5 V)会抖动;注释默认 8 V,实际 12 V。</td><td>误跳闸、保护抖动。</td><td>基准改为慢速低通跟踪;触发/恢复阈值统一并带回差;更正注释。</td></tr><tr class="s2"><td>M30</td><td><span class="b b2">一般</span></td><td>FOC</td><td><code>Algorithm/FOC.h:47;Algorithm/FOC.cpp:429,413;device/Timer.cpp:329-342</code></td><td><b>v_safe 初值与运行值不一致,占空比无统一限幅</b><br>初值 Vdc/1.05,运行值 Vdc×1.1;首次 update_power 前 T0 可能&lt;0 → 负占空比;Set_3Pwm 不限幅,负浮点转 u32 为未定义行为。</td><td>上电首拍产生异常占空比。</td><td>Set_3Pwm 内统一夹紧 [0, Dmax];v_safe 初值与运行公式一致。</td></tr><tr class="s2"><td>M31</td><td><span class="b b2">一般</span></td><td>FOC/实时性</td><td><code>sys/config.h:395-397;Algorithm/FOC.cpp:95-98</code></td><td><b>最高占空比约 95.5% 超出采样窗口要求(约 90%)</b><br>高调制时 W 相低边导通时间不足,电流读数趋零,经共模去除把误差分摊到 Iq;注释“每周期匹配两次”与实际单沿触发不符。</td><td>高速/高负载时转矩估计失真。</td><td>限制最大占空比或做两相动态选相采样;修正注释。</td></tr><tr class="s2"><td>M32</td><td><span class="b b2">一般</span></td><td>FOC</td><td><code>Algorithm/FOC.cpp:526,546,636-640,751</code></td><td><b>多模式积分饱和与热路径改写用户限幅</b><br>526 行在热路径直接改写 limit_voltage;mode1/4/5 由 SVPWM 或外部钳位限压,但 PID 内部 limit 仍为 Vdc,积分 windup。</td><td>大扰动后超调、恢复慢。</td><td>各环 PID 的 limit 与实际执行限幅一致;不在控制路径改写用户参数。</td></tr><tr class="s2"><td>M33</td><td><span class="b b2">一般</span></td><td>实时性</td><td><code>device/ADC.cpp:62-63,467-481</code></td><td><b>电流三相读取与清 ready 无原子保护(撕裂)</b><br>ISR 可能在读取三相中途写入新帧,三相来自不同帧,或丢失一帧 ready。</td><td>偶发电流尖峰/误判采样失效。</td><td>读取侧用 LDREX/STREX 或序号双读校验(seq 前后相同才接受),无需长临界区。</td></tr><tr class="s2"><td>M34</td><td><span class="b b2">一般</span></td><td>实时性</td><td><code>main.cpp:53-65;sys/initial.cpp:255</code></td><td><b>看门狗启用晚,复位原因未读取</b><br>看门狗在 OTA 初始化与磁检之后才开启,初始化失败循环中既无看门狗也不喂狗;未读 RCU_RSTSCK。</td><td>上电阶段卡死不自动复位;现场无法区分看门狗/软件/欠压复位。</td><td>尽早启用看门狗;启动时读取并上报复位原因后清除。</td></tr><tr class="s2"><td>M35</td><td><span class="b b2">一般</span></td><td>构建</td><td><code>startup_gd32c10x.s:42,53;regions_GD32C103CB.h:102-103</code></td><td><b>栈/堆大小两处定义不一致</b><br>启动文件 Stack 0x800/Heap 0x800;regions 头文件 0x200/0xC00。缺少最坏栈深分析。</td><td>实际生效值不明,存在栈溢出隐患。</td><td>以 map 确认生效值并统一一处;用 armlink --info=stack/--callgraph 核算最坏栈深;栈底放哨兵。</td></tr><tr class="s2"><td>M36</td><td><span class="b b2">一般</span></td><td>FOC</td><td><code>device/ang_sensor.cpp(SPI 初始化 prescale)</code></td><td><b>SPI 分频与注释不符</b><br>prescale=24 实际映射为 /16,约 7.5 MHz,注释写 5 MHz。</td><td>超出传感器/走线裕量时误码。</td><td>按手册确认 KTH7823 最高时钟并使用正确枚举。</td></tr><tr class="s2"><td>M37</td><td><span class="b b2">一般</span></td><td>安全</td><td><code>sys/health.cpp:150-153,252-256;Algorithm/FOC.cpp:89-91</code></td><td><b>若干故障只上报不禁止</b><br>温度传感器失效(255)被置 0,温度保护静默失效;编码器 MagStatus 只上报;单相电流失效时用陈旧 Kalman 值继续闭环;无软件瞬时过流与堵转保护。</td><td>保护覆盖不全。</td><td>传感器失效 → 禁止运行;增加软件过流(相电流阈值+计数)与堵转(Iq 大且速度≈0 持续 T)保护。</td></tr><tr class="s2"><td>M38</td><td><span class="b b2">一般</span></td><td>构建</td><td><code>Robot_V2/Robot_V2.cproject.yml:misc;Robot_V2.csolution.yml:build-types</code></td><td><b>编译选项与 build-types 冲突,链接诊断大面积抑制</b><br>cproject 对所有构建强制 -O3,覆盖 Debug optimize:none / Release balanced;Link 抑制约 70 个 L62xx/L63xx 诊断(含 L6329 重复)。</td><td>Debug 不可调;真实链接问题被掩盖。</td><td>移除 cproject 中 -O3,由 build-types 控制;只保留确认无害的个别抑制。</td></tr><tr class="s2"><td>M39</td><td><span class="b b2">一般</span></td><td>构建</td><td><code>Robot_V2/RTE/Device/GD32C103CB/*.c 与 *.base@1.0.0</code></td><td><b>厂商库文件被本地修改(与 .base 差异)</b><br>25 个 RTE 设备文件与 base 不同(如 system_gd32c10x.c 改了 VECT_TAB_OFFSET=APP_BASE 0x08012000 并加注释,timer/can/adc 库也有改动)。</td><td>pack 更新时修改被覆盖或合并困难,应用基址隐藏在厂商文件中。</td><td>将应用基址等改动移到用户代码(如 SystemInit 后设置 SCB-&gt;VTOR)或文档化;尽量恢复厂商库原样。</td></tr><tr class="s3"><td>M40</td><td><span class="b b3">建议</span></td><td>构建</td><td><code>仓库根目录</code></td><td><b>缺少 README / 协议文档 / 构建说明 / CI</b><br>无 README,CAN 命令集只存在于代码注释。</td><td>新人与上位机开发者难以对接,回归无保障。</td><td>补 README(硬件、构建、烧录、地址布局)与 CAN 协议表;增加 cbuild CI 与主机端 PID/SVPWM 单元测试。</td></tr><tr class="s3"><td>M41</td><td><span class="b b3">建议</span></td><td>代码质量</td><td><code>Robot_V2/Algorithm/thermal.cpp,.h</code></td><td><b>空壳文件未加入工程</b><br>thermal.cpp 仅有文件头注释,未在 cproject 中。</td><td>死代码干扰阅读。</td><td>删除或实现后加入工程(热模型目前在 sys/health.cpp)。</td></tr><tr class="s3"><td>M42</td><td><span class="b b3">建议</span></td><td>代码质量</td><td><code>RTE/Device/GD32C103CB/gd32c10x_it.c</code></td><td><b>死文件且重复定义 SysTick_Handler</b><br>不在编译列表。</td><td>误加入工程会产生重复定义。</td><td>删除,或改为强定义异常处理的唯一来源。</td></tr><tr class="s3"><td>M43</td><td><span class="b b3">建议</span></td><td>构建</td><td><code>.continue/、.vscode/c_cpp_properties.json</code></td><td><b>IDE 配置入库</b><br>Continue MCP 模板(无敏感信息)与 windows-gcc-x86 IntelliSense 配置(与 AC6 目标不符)。</td><td>噪声、误导。</td><td>加入 .gitignore 或改为 arm 交叉配置。</td></tr><tr class="s3"><td>M44</td><td><span class="b b3">建议</span></td><td>代码质量</td><td><code>sys/initial.h:10-176,42;sys/initial.cpp:7,15</code></td><td><b>头文件与 C/C++ 混用问题</b><br>头文件中定义 static 变量;C++ 类包在 extern &quot;C&quot; 中;initial.h 被重复包含;fputc 使用 throw()。</td><td>每个编译单元各有副本、链接语义混乱。</td><td>static 变量移入 .cpp;extern &quot;C&quot; 只包 C 接口;去掉 throw()。</td></tr><tr class="s3"><td>M45</td><td><span class="b b3">建议</span></td><td>代码质量</td><td><code>sys/flash.h:59 等</code></td><td><b>魔数与命名不一致</b><br>ADDR_VEL_LIMIT 实际存 target_current;多处魔数。</td><td>维护误用。</td><td>重命名、集中常量。</td></tr><tr class="s3"><td>M46</td><td><span class="b b3">建议</span></td><td>FOC</td><td><code>Algorithm/FOC.cpp(Ud 恒 0);Algorithm/Filter.cpp(CurrentKalman Q=10,R=5e-5)</code></td><td><b>Id 未闭环;电流卡尔曼增益≈1 等于不滤波</b><br>Ud 恒为 0;Kalman 参数使滤波失效。</td><td>高速/弱磁场景能力受限;无效计算消耗 CPU。</td><td>增加 Id 电流环(目标 0);重新整定或移除该滤波器。</td></tr><tr class="s3"><td>M47</td><td><span class="b b3">建议</span></td><td>FOC</td><td><code>Algorithm/FOC.cpp(SVPWM)</code></td><td><b>SVPWM 量纲约定未注明</b><br>Uq 量纲为相幅值的 √3 倍(数值验证扇区与角度误差约 1e-13,变换正确)。</td><td>参数整定易误解。</td><td>在注释中写明电压归一化约定。</td></tr><tr class="s3"><td>M48</td><td><span class="b b3">建议</span></td><td>CAN</td><td><code>device/Can.cpp:366-390</code></td><td><b>CAN-FD 位时序裕量待确认</b><br>数据段 5 Mbps 仅 6 tq、SJW=1,TDCO=4 单位需对照手册。</td><td>长线/多节点时误码。</td><td>按手册核算采样点(70–80%),示波验证 TDC。</td></tr><tr class="s3"><td>M49</td><td><span class="b b3">建议</span></td><td>实时性</td><td><code>system_gd32c10x.c:857</code></td><td><b>未开启时钟监控 CKM</b><br>晶振起振失败卡在 while(1)。</td><td>板级故障时无安全输出。</td><td>开启 CKM,NMI 中安全停机。</td></tr><tr class="s3"><td>M50</td><td><span class="b b3">建议</span></td><td>CAN</td><td><code>device/Can.cpp:331,1292;Can.h:226-228</code></td><td><b>CAN RX DMA 收益有限、残留死代码</b><br>M2M 按帧启动 DMA;通道 5 中断为空;旧 rx_message_buffer 队列无人调用。</td><td>额外中断与维护负担。</td><td>评估直接读邮箱;删除死代码。</td></tr></table>
<h2>4. 既有评审(STATIC_ANALYSIS_NOTES.md)结论跟踪</h2>
<table><tr><th>条目</th><th>原结论</th><th>当前状态</th><th>证据 / 说明</th></tr><tr><td>SUP-GLB-001 编译期常量(ADC/health/config constexpr)</td><td>误报/偏离</td><td><b>仍成立</b></td><td>ADC.cpp:22-47;health.cpp:107;config.h 全部为 constexpr</td></tr><tr><td>硬件单例 FOC / sampler / CAN0_transmiter / KTH7823 / thermalController / Version</td><td>接受单例</td><td><b>仍成立</b></td><td>FOC.cpp:28;ADC.cpp:60;Can.cpp:86;ang_sensor.cpp:39;health.cpp:104;initial.cpp:218</td></tr><tr><td>TaskScheduler scheduler 短临界区</td><td>已保护,接受</td><td><b>仍成立</b></td><td>main.cpp:68-72 关中断取快照并清位;taskFlags volatile(initial.h:75);两写入方同抢占级</td></tr><tr><td>CAN RX/TX 队列临界区与 PRIMASK 恢复</td><td>已保护,接受</td><td><b>基本成立,但有遗漏</b></td><td>Can.cpp:644-731、480;遗漏:主循环 CANFD_AsyncSend 直接写邮箱与 TX ISR 竞争(Can.cpp:1395/744,见M21)</td></tr><tr><td>ADC injected 电流缓存不加临界区</td><td>保留现状</td><td><b>风险仍在</b></td><td>ADC.cpp:62-63、467-481 存在三相撕裂/丢 ready(M33);建议序号双读而非 PRIMASK</td></tr><tr><td>“加 PRIMASK 后实机连续重启”归因于时序</td><td>归因时序</td><td><b>归因不成立</b></td><td>10 周期级临界区不足以致复位;更可能是 update_current 提前返回分支(ADC.cpp:468)未恢复中断 + 与 systick 改动捆绑未隔离 + 50 ms 看门狗(IRC40K)余量小;无复位原因记录无法定位</td></tr><tr><td>systick DWT CPU 占用统计</td><td>允许小误差</td><td><b>仍成立</b></td><td>systick.cpp:75-79、193-206;仅诊断值</td></tr><tr><td>ang_sensor SPI DMA 完成标志</td><td>接受 volatile 标志</td><td><b>已过时</b></td><td>已改为寄存器轮询 spi_rw_half_word_polling(ang_sensor.cpp:57-82),标志不存在;新问题:TRANS 等待无超时(:78)</td></tr><tr><td>Timer total_us100_counter</td><td>仅 ISR 使用,接受</td><td><b>仍成立</b></td><td>Timer.cpp:19、149-153;可改为 ISR 内 static</td></tr><tr><td>health voltage_state / last_vbus_slow</td><td>模块私有,接受</td><td><b>仍成立(有残留)</b></td><td>health.cpp:112-113 文件内 static;last_vbus_slow 只写不读</td></tr><tr><td>SUP-CMP-001 normalizeAngle360 浮点比较</td><td>无需容差</td><td><b>比较结论成立,但有新缺陷</b></td><td>ang_sensor.h:103-111 输入 Inf/NaN 时死循环(M16)</td></tr><tr><td>printf:FOC 齿槽校准打印位于 FOC_DEBUG</td><td>正式版不编译</td><td><b>已过时</b></td><td>9fbd9c3 已移除齿槽补偿;FOC_DEBUG 仅剩 motor.cpp:245 一处</td></tr><tr><td>versionToString 去 snprintf</td><td>已改</td><td><b>仍成立</b></td><td>initial.cpp:302-346</td></tr><tr><td>AUTO-MET-001 calibrate_cogging_tick 拆分</td><td>已拆分</td><td><b>已过时</b></td><td>函数随齿槽功能删除;Can.cpp:1186 bit4 改为保留位</td></tr><tr><td>AUTO-MET-001 communication_check 拆分</td><td>已拆分</td><td><b>已落实</b></td><td>Can.cpp:841-1115</td></tr><tr><td>AUTO-MET-001 do_task 拆分</td><td>已拆分</td><td><b>已落实</b></td><td>tasks.cpp:296-399</td></tr><tr><td>“实编仍需在 Keil/CMSIS/cbuild 环境验证”</td><td>待验证</td><td><b>仍未验证</b></td><td>本次评审为静态评审,未实编;cproject -O3 覆盖 build-types(M38)需一并处理</td></tr></table>
<h2>5. 亮点</h2>
<div class="card"><ul>
<li><code>RunInhibit</code> 位图闸门:功率级动作收拢为单一执行者,上电默认 INHIBIT_NOT_ENABLED,闸门位于模式分发之前,非法模式也能封住输出。</li>
<li>节点 ID 双页乒乓 + 魔数 + 计数器 + CRC,写后校验重试,写成功才回 ACK。</li>
<li>ADC0 只跑注入组、VBUS/温度移到 ADC1 连续模式,消除规则/注入组互扰;电流零点配平带独立错误位上报。</li>
<li>Clarke/Park/逆 Park 与 SVPWM 数值正确;PID Tustin 积分 + 测量值微分低通 + 条件积分抗饱和设计合理。</li>
<li>CAN DLC 合法化(CANFD_NormalizeLength)、DWT 按任务统计负载、Doxygen 注释完整。</li></ul></div>
<h2>6. 优先级修复路线图</h2><div class="card"><ol>
<li><b>P0(上电机前必须修,1–2 天)</b>:M01 控制帧输入校验与 PID 限幅取正;M02/M10 EEPROM 重建重写校准角并修正 system_config_saved;M03 通信超时禁止;M06 HardFault 先关 PWM;M07 motReset 交给闸门;M16 NaN/Inf 过滤。</li>
<li><b>P1(一周内)</b>:M04 零位校准非阻塞化、delay 不喂狗;M05 过压保护;M08 运行中禁止 Flash 擦写;M09 广播改 ID 防护;M12 故障解除需重新使能;M17 编码器故障禁止;M19 深睡眠位优先级 bug。</li>
<li><b>P2(迭代内)</b>:M13/M14/M15 由 ADC 中断驱动 FOC、影子寄存器、丢拍计数;M11 EEPROM 双页原子化;M33 电流序号双读;Bus-Off/错误中断;看门狗前移与复位原因上报;补软件过流/堵转。</li>
<li><b>P3(持续改进)</b>:构建选项清理(-O3、诊断抑制)、厂商库还原、README/协议文档/CI/主机单元测试、死代码清理、Id 闭环。</li></ol></div>
<h2>7. 总结评分</h2>
<table><tr><th>维度</th><th>评分</th><th>说明</th></tr><tr><td>功能安全与故障保护</td><td><b>5.0</b> / 10</td><td>有统一运行禁止闸门与上电默认禁止,但缺通信超时/过压/过流/编码器故障禁止,故障后自动恢复</td></tr><tr><td>电机控制算法</td><td><b>6.5</b> / 10</td><td>变换与离散化正确;限幅符号、NaN、windup、采样窗口问题</td></tr><tr><td>实时性与中断</td><td><b>6.0</b> / 10</td><td>执行模型清晰、临界区合理;节拍与 PWM 不同步、无丢拍检测、异常不关 PWM</td></tr><tr><td>CAN 通信协议</td><td><b>5.5</b> / 10</td><td>DLC 合法化、ACK 回写较好;输入校验、Bus-Off、广播防护不足</td></tr><tr><td>参数存储/Flash</td><td><b>4.5</b> / 10</td><td>节点 ID 存储设计好;EEPROM 重建丢校准角、无原子性、标志位逻辑缺陷</td></tr><tr><td>代码质量与可维护性</td><td><b>6.5</b> / 10</td><td>注释充分(Doxygen)、函数已拆分;头文件/死代码/魔数</td></tr><tr><td>构建与仓库卫生</td><td><b>5.0</b> / 10</td><td>csolution 规范;-O3 覆盖、诊断抑制、厂商库改动、无 README/CI</td></tr><tr><td><b>综合</b></td><td><b>5.6</b> / 10</td><td>架构方向正确、近期在安全闸门上投入明显;但输入校验、通信超时、参数存储一致性三处缺陷可直接导致失控,修完 P0 前不建议无人值守或带负载运行。</td></tr></table>
<p class="meta">本报告由专家评审生成,仅新增 docs/ 文件,未改动任何源码。</p>
</main></body></html>