fix(comm): 回退LAN9252批量读写优化并修复多个CAN-EtherCAT桥竞态问题
回退1.6.0引入的LAN9252 PRAM批量读写优化,因其AVAIL_CNT计数器对短读存在语义问题, 导致邮箱短读和探针传输时出现FIFO陈旧数据损坏问题。 同时修复EtherCAT-CAN桥中的多个竞态条件: - 修复消费者在fillRxBatch中直接访问共享ring buffer导致的数据混淆问题 - 修复生产者-消费者间tail指针发布时机不当造成的槽位覆盖竞态 - 修复WIN窗口基址更新位置错误导致电机状态窗口被锁定的问题 - 修复buildInput中断上下文与主循环间的数据竞争和内存撕裂问题 - 添加STATUS帧过滤机制减少批量区压力并解决队列饱和问题 此外还增加了多个诊断计数器用于监控撕裂、溢出和拼接错误,并调整了 输入PDO段布局和大小参数以支持新的48字节STATUS格式。 这些修改解决了上位机观察到的帧标签错误、电机状态丢失和数据拼接问题。
This commit is contained in:
@@ -122,7 +122,17 @@ constexpr bool CONFIG_CAN_CH2_ENABLE = true;
|
||||
* ============================================================ */
|
||||
constexpr uint32_t APP_FW_VERSION_MAJOR = 1U;
|
||||
constexpr uint32_t APP_FW_VERSION_MINOR = 6U;
|
||||
constexpr uint32_t APP_FW_VERSION_PATCH = 1U; /* 1.6.1:输入 PDO 瘦身 640→496——批量接收条目数据 64→32B(真机 6086 帧 DLC 仅 24/32,超长帧截断打 flags bit1,环形队列仍存全量)、段5 诊断区 632→484;DWT 实测 640B PRAM 写 296µs/拍且与 SYNC0 相位无关(≈0.463µs/B 线性),预期写侧 −67µs/拍。五处一致(pdo_size/bridge/dc_app/gen_eeprom/ESI)已同步,需重烧固件+EEPROM 并断电冷启动,主站 decode_input 按 Ibytes 双布局自适应可任意先后升级。 */
|
||||
constexpr uint32_t APP_FW_VERSION_PATCH = 13U; /* 1.6.13:根治 —— 1.6.12 的 PDI 入口预抽实测(60s 正弦指令负载,链路活性已断言):overfull 50/s→0 但指纹违规仍 ~92/s、torn 在无溢出阶段 1-7/s 持续涨 —— 判决:溢出只是共犯之一,**357µs 的 PDI 阻塞窗口本身**才是病理母体(窗口内 FIFO 满溢毁窗口 + 被冻结的取帧路径留着不依赖溢出的换帧路径,MI/MP 只抓到少数)。本版釜底抽薪:CAN RX ISR 提到 preempt=0(CONFIG_IRQ_PREEMPT_CAN_RX,与 SYNC0 同级互不抢占)—— RX 不再被 PDI/SYNC1/SCHED 压住,FIFO 深度 ≤2、溢出与冻结同时消失,receiveFrameChecked 的多字取数自此真正原子;TX ISR 留在 2(PDI 上下文有 TX 提交,提 0 会引入提交/释放竞争)。代价:SYNC0/PDI 偶发 µs 级延迟(RX ISR ~3µs/帧×8kHz≈2.4% 占空),相对 1ms 周期是零头。PDI 预抽 pumpAllChannels 随之撤除(失去意义且引入 PDI/ISR 双消费者风险)。保留 1.6.12 的诚实管道(校验只计不丢)与诊断布局(overfull@490、relto@492、seq u16@488)作为验证仪表:修好 = reject/torn/overfull 恒 0。 */
|
||||
/* 1.6.12:源头修复 —— 用户裁定"过滤是遮羞布,修采集源头,前端只许诚实反馈"。1.6.11 上线后实测(repro_cmdload 正弦负载)reject@494 仍以 ~384/s 增长:RFOD=DISABLE 挡不住溢出毁窗口(本 IP 满溢时 DISCARD 语义存疑,或毁帧发生在满态下的读并发路径),内容校验丢弃只是把坏数据拦在计数前。本版两处:(1) **源头**:CanFd::pumpAllChannels() 挂进 onEscIrqExti 的 PDI_Isr 入口 —— 在 buildInput 357µs 长阻塞(CAN ISR preempt=2 全程被压)开始之前把三路 FIFO 抽干,阻塞窗口起点深度=0、窗口内到达 ≤2-3 帧,3 深度 FIFO 从"必然打满"变成"必然不满";preempt=1 + SYNC0 已关的上下文保证与 CAN ISR 取帧互斥,与主循环批量队列生产零交集。(2) **诚实管道**:内容校验改为只计不丢(reject@494 恒 0 = 源头修好;还在涨 = 源头未愈且页面如实显示),诊断区加挂判决性测量:seq 488 拆 u16、overfull(RFO0)@490、release_timeout@492(原重入计数位,恒 0 让出)。reject 与 overfull 是否同涨 = "残余拼接是否仍源自溢出"的判决。 */
|
||||
/* 1.6.11:根治残余拼接 —— 1.6.10 的 settle 延迟实测无效(正弦指令负载下拼接率不变),节点指纹实验(两电机反相正弦+速度限 200/150 打指纹)实锤拼接是**跨节点**的:node3 的 STATUS 帧头 24B = node2 的完整 CONFIG(速度指纹 200.0、角度 1142° 均属 node2 轨迹),且 CONFIG↔CONFIG 同型拼接结构校验抓不到(两边都合法)—— 电机 TX 不可能发出别的电机的字节 ⇒ 混合点在 GD32 RX 侧。真根因:can_init 的 rec_fifo_overwrite=ENABLE —— FIFO 满时覆盖最旧邮箱,而最旧邮箱正是 CPU 正在读的输出窗口;buildInput 在 PDI_Isr 占 ~357µs 压住 CAN ISR,发指令时总线 ~8kHz、阻塞窗口内 ~3 帧打满 FIFO 深度 → 覆盖进行中被读走 = 拼接(MI/MP 回读挡不住:MI 是覆盖序列最后更新的一环;settle 延迟也挡不住:非时间竞态而是溢出策略)。本版:(1) rec_fifo_overwrite=DISABLE —— 溢出丢新帧置 RFO0(rx_fifo_overfull_count 已统计),周期量丢一拍下拍即补,与软件队列"满丢新不覆旧"同则;(2) 撤 settle 延迟(无效且白烧 CPU);(3) 保留 1.6.10 的重取循环删除与边界修正,并新增入队前内容校验(STATUS 温度/电压量程+帧间斜率、CONFIG 未使用区/模式)丢结构矛盾帧、计数段5@494 —— 作为溢出之外的纵深防御(实测能挡跨型拼接,挡不住同型 CONFIG↔CONFIG,故必须配合 (1))。验证:正弦指令负载 60s,指纹违规 40→0。 */
|
||||
/* 1.6.10:根治拼接帧 —— 1.6.9 的"队列覆写"定性被自测推翻为**次要**:段 2 电机槽(MotorState 路径,与批量环形队列完全独立)出现同签名拼接(node2 槽头 = node3 CONFIG 前缀 03c12200,解出 mcuTemp=-161.25),且 rx_torn/pdo_reentry 自启动恒 0 —— 两条独立下游同时中招 ⇒ 混合点在共同上游 CAN RX ISR;校验恒不报 ⇒ 拼接发生在 MI/MP 判据盲区。真根因=1.6.7 的"校验失败原地重取":RFIFOMDATA0 读指针随读自动前进且不因换帧复位,attempt1 失败后 attempt2 从错位指针开读,MI/MP 比对的是窗口身份、管不住指针错位,于是"校验通过、内容为上一帧尾巴拼下一帧开头"。实测(自测脚本 scripts/selftest_torn.py,按拍去重+灵敏度自证):空闲 0%(35k 条),电机活跃时 9.155%(56835/620838,STATUS 错标为 CONFIG 占绝对多数)。本版:(1) 去掉重取循环,校验失败即 release+丢帧+rx_torn 计数(1.6.6 语义,STATUS/CONFIG 均 1kHz 周期量,丢一拍下拍即补);(2) fillRxBatch 边界改判段 5 起点 ECAT_DIAG_OFF_LOAD(484) 而非 PDO 全长(496)——潜伏越界写诊断区,当前容量下触不到,容量放宽即咬人;(3) releaseFifo0 自清后补 settle 延迟 —— 烧(1)(2)后实测活跃流量下仍残余 0.11% 跨节点拼接(node2 槽头=node3 CONFIG 前缀,且 rx_torn 已正常计数 146,证明窗口换帧路径已被挡住),定性为 RFD 自清只代表 dequeue 握手完成、下一帧 MDATA 还在逐字搬运(MI/MP 先落数据区后落),while 连排的第二次读紧跟 release 即拿到陈旧头+新鲜尾,MI/MP 回读对这种陈旧全盲;空闲每 ISR 单帧故 0%、活跃连排才中招,与实测分布吻合。 */
|
||||
/* 1.6.9:拿到实证并定位真根因 —— 给主站加了同拍 raw+parsed 的只读诊断路由(/api/motor/rxraw)后抓到:**坏帧在原始 PDO 字节里就已经是错的**(如 off=72 处条目声称 id=0x20101/dlc=24,数据却是合法 STATUS:mcuTemp=35.92 motTemp=33.55 pwrV=23.972),而主站 parsed 与 raw 逐字节一致 —— 坏在从站写入侧,与主站解析无关。根因:批量接收环形队列长期饱和下,生产者覆写了消费者正在读的槽。实测证据:CAN 侧约 4000 帧/s 而批量区每拍最多 3 条(=3000 帧/s),净积压使 pending 恒 8/12、lost 几乎每拍都有、count 恒取满 3;消费者 fillRxBatch 又攒到循环末才发布 s_rxRingTail,生产者判满用的正是这个滞后值,于是把新帧写进消费者刚读过但未发布的槽,拷进 PDO 的 id/dlc/data 遂来自不同帧(错标率 1.77%)。两处修复:(1) onCanFrame 入队**过滤 STATUS 帧** —— 完整 STATUS 本就由段 2 按槽上报,主站 drainRx() 拿到 STATUS 也直接 continue 丢掉,让它占队列纯属重复,正是它把队列压爆;(2) fillRxBatch 改为整条拷贝到局部副本再用,并逐条发布 tail,使生产者判满始终基于真实进度。
|
||||
1.6.8:曾判定为"输入 PDO 填充跨上下文重入"并加了双缓冲发布 + 重入计数(段5@492)。**该假说事后被证伪**:SM-Synchron 稳态下 MainLoop 分支的门控 (!bEscIntEnabled||!bEcatFirstOutputsReceived)&&!bDcSyncActive 恒为假不执行,Sync0 因 free-run 无 DC 脉冲不触发,Sync1 与 PDI_Isr 同为 preempt=1 不能抢占 —— buildInput 实际只在 PDI_Isr 单上下文跑。双缓冲发布本身是正确加固,予以保留。
|
||||
1.6.7:接着 1.6.6 治 CAN 取帧拼接。1.6.6 的校验只读 MP 的 TS/DLENC,TS 是 16 位计数器、相邻帧可能同值,DLENC 更常同为 48,于是"ID 报 STATUS、数据却是 CONFIG 帧"这种错配从校验漏了过去。本版三处改动:(1) 回读校验再加上 MI(完整 ID/帧格式),ID 变了就一定是换了帧,没有巧合空间;(2) 校验失败改为原地重取最多 3 次而不是直接丢弃 —— 失败时窗口里躺着的正是完整的新帧,重读一次通常就能拿全;(3) 整段取数放进 BASEPRI 临界区,挡住 preempt>=1 的 EtherCAT 中断。注:事后(1.6.8)证明本版方向不是主因,但这三条本身仍是正确的加固,予以保留。
|
||||
1.6.6:根治 ECAT/CAN 偶发乱码 —— 取帧不再用厂商库 can_message_receive。一条 48B CANFD 报文要对同一地址 CAN_RFIFOMDATA0 连读 12 次才取完,整个过程不原子;而厂商库置 RFD0 后既不等硬件自清也不校验,取数过程会横跨 dequeue、读出两帧拼接。现改为本地 receiveFrameChecked():取数前后各读一次 MP,比对 TS[31:16] 与 DLENC,不一致即判定邮箱窗口被换帧,整帧丢弃并计入 Diag::rx_torn_count(经输入 PDO 段 5 偏移 486 原保留位上报,PDO 长度不变、EEPROM 无需重刷);releaseFifo0() 置位后轮询等自清,且改用直接赋值 —— 原来的 |= 是读改写,会把期间硬件置起的 RFF0/RFO0(rc_w1) 顺手清掉,FIFO 溢出证据就是这么丢的。另修 ISR 里 frame.id/stats.last_rx_id 取自三路共享的 s_diag.last_rx_id(而 data 取自局部 rx_message,会串出"A 通道 id 配 B 通道数据"),一律改用本帧局部 rxId。
|
||||
1.6.5:段 2 电机槽补 seqlock(MotorSlot.wr_seq)。buildInput 跑在 PDI_Isr,会抢占主循环的 MotorState::onCanFrame,而那里是"先 seq=++global_seq、后 memcpy(slot.data,48)"——抢占落在 memcpy 中途就会发布"新 seq + 半新半旧 48B",主站只按 seq 判新鲜度分辨不出,混合边界若落在 float 的符号/指数字节(B16/B20/B24)就是网页上的偶发离谱值。现消费者见 wr_seq 为奇数即跳过本槽(该 56B 留零,主站按 node_id==0 跳过、下拍自愈),win_count 改报窗口跨度而非填充数以保持按 i 定位。CONFIG 路径不套锁:它只写 config_* 与恒等的 node_id/channel,buildInput 不同读这些。槽布局不变 ⇒ 输入 PDO 仍 496B,EEPROM/SII 无需重刷。
|
||||
1.6.4:电机槽 40B→56B(8B 头 + v0.74 的 48B STATUS),每拍槽数 7→5(段 2 的 280B 正好 5×56)。修掉两件事:buildInput 按 48B 拷贝时溢出 16B 盖住段 3 长帧快照;主站侧 STATUS 被截到 32B 使 InitStatus(B32-35) 永远拿不到、网页把"无数据"显示成 NORMAL。槽数×槽长不变 ⇒ 输入 PDO 仍 496B,EEPROM/SII 无需重刷;主站需同步 MOT_SLOT_SIZE / MOT_SLOTS_PER_PDO / MOT_STATUS_DLC。
|
||||
1.6.3:processOutput 不再从 out 回读 WIN@8——1.6.0 起 out 只在 seq 沿那拍新鲜,无沿周期里它是上一次全量读的残值,于是每拍用开机首拍的 0 盖掉 peekOutput 刚取到的新值,电机状态扫描窗口被钉死在基址 0,槽位 7..26(node3 及以上)永不上报,只有主站真发一帧才复位。WIN 的每拍刷新改由 peekOutput 独占。判据:不发任何帧,从站回显 win_base 也应等于主站请求值。 */
|
||||
constexpr uint32_t APP_FW_VERSION_BUILD = 0U;
|
||||
|
||||
|
||||
@@ -298,6 +308,17 @@ constexpr uint32_t CONFIG_NVIC_PRIORITY_GROUP = NVIC_PRIGROUP_PRE4_SUB0
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC = 0U;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_SCHED = 1U;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_CAN = 2U;
|
||||
/* 1.6.13:CAN RX 提到 preempt=0(与 EtherCAT SYNC0 同级)。
|
||||
* 1.6.12 的 PDI 入口预抽实测:溢出确实大降(overfull 50/s→0)但拼接照旧
|
||||
* (指纹违规 ~92/s,且 torn 在无溢出阶段仍 1-7/s 在涨)—— 357µs 的 PDI
|
||||
* 阻塞窗口本身就是病理:窗口内 FIFO 满溢毁窗口的同时,被冻结的取帧路径
|
||||
* 还留着不依赖溢出的换帧路径(MI/MP 只抓到少数)。preempt=0 一并掐断:
|
||||
* RX ISR 不再被 PDI/SYNC1/SCHED 压住(FIFO 深度 ≤2,溢出与冻结同时消失),
|
||||
* 且与 SYNC0 同级互不抢占 —— receiveFrameChecked 的多字取数自此真正原子。
|
||||
* TX ISR 留在 2:PDI 上下文有 TX 提交,提到 0 会引入提交/释放竞争。
|
||||
* 代价:SYNC0/PDI 偶发 µs 级延迟(RX ISR ~3µs/帧 × ~8kHz ≈ 2.4% 占空),
|
||||
* 相对 1ms 周期与 165µs 总线时间是零头。 */
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_CAN_RX = 0U;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_ESC = 1U;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_ETHERCAT_SYNC1 = 1U;
|
||||
constexpr uint32_t CONFIG_IRQ_PREEMPT_SPI_USART = 3U;
|
||||
|
||||
@@ -229,8 +229,12 @@ static void enterState(State st) {
|
||||
|| prev == State::kFinishOta);
|
||||
if (otaActive && !wasActive) {
|
||||
CanFd::setChannelAutoRetrans(s_channel, false); /* OTA 开始:单发 */
|
||||
CanFd::setChannelBrsSuppress(s_channel, true); /* OTA 开始:压 BRS——电机 BOOT 收不了
|
||||
BRS 数据段,nonce/confirm/64B 数据包
|
||||
(DLC>8) 会全部静默丢失 */
|
||||
} else if (!otaActive && wasActive) {
|
||||
CanFd::setChannelAutoRetrans(s_channel, true); /* OTA 结束:恢复重发 */
|
||||
CanFd::setChannelBrsSuppress(s_channel, false); /* OTA 结束:恢复 BRS(EtherCAT 桥接吞吐) */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user