Files
cbb b171767023 feat(ethercat): 添加EtherCAT从站功能支持并优化同步机制
- 添加CAN反馈和ID快照SPI命令文件
- 移除EXMC组件依赖,精简项目配置
- 添加RAM函数段链接脚本,优化Flash等待周期抖动
- 实现EscExmcBus类,提供16位并行总线访问LAN9252
- 实现EscLan9252驱动,支持寄存器和PDI内存访问
- 实现EthercatSlave类,采用三缓冲机制进行PDO数据交换
- 实现EthercatSync同步管理,支持SYNC0/SYNC1中断和定时器模式
- 添加ISR执行时间统计和诊断功能
- 分离周期路径和事件路径通知机制,避免RMW竞态
2026-06-29 01:51:02 +08:00

7.8 KiB

Tasks

  • Task 1: 阶段 A — EtherCAT 周期任务标志脱离 TaskManager(修复严重风险 #1)

    • SubTask 1.1: 在 comm/ethercat_slave.h 中声明 static volatile uint8_t s_periodicSeq 与 static uint8_t s_processedSeq 私有静态成员,并增加 static bool hasPendingPeriodic() / static void markPeriodicProcessed() 公有接口
    • SubTask 1.2: 在 comm/ethercat_slave.cpp 的 periodicIsr() 末尾移除 TaskManager::set(1UL << TASK_ETHERCAT_PROCESS),改为 s_periodicSeq++(uint8 自然回绕,原子单字节写)
    • SubTask 1.3: 实现 hasPendingPeriodic() 返回 s_periodicSeq != s_processedSeq,markPeriodicProcessed() 执行 s_processedSeq = s_periodicSeq
    • SubTask 1.4: 在 sys/task_runner.cpp 的 runPending() 开头(TaskManager::takePending 之前或之后)增加:if (EthercatSlave::hasPendingPeriodic()) { EthercatSlave::markPeriodicProcessed(); taskEthercatProcess(); },该分支用 #if CONFIG_ETHERCAT_ENABLE 保护
    • SubTask 1.5: 确认 ESC IRQ 路径(comm/ethercat_sync.cpp::onEscIrqExti())仍调用 TaskManager::set(1UL << TASK_ETHERCAT_PROCESS),BASEPRI 保护该路径,无改动
    • SubTask 1.6: 在 comm/ethercat_slave.h 头注释中说明"周期路径用 s_periodicSeq,事件路径用 TASK_ETHERCAT_PROCESS,两者独立"
  • Task 2: 阶段 B — EXMC 总线访问权分离(修复严重风险 #2)

    • SubTask 2.1: 在 comm/esc_lan9252.h 中声明 static uint16_t safeReadReg(uint16_t addr) / static void safeWriteReg(uint16_t addr, uint16_t val) / static void safeReadPdi(uint16_t offset, uint8_t *buf, size_t len) / static void safeWritePdi(uint16_t offset, const uint8_t *buf, size_t len) 公有接口
    • SubTask 2.2: 在 comm/esc_lan9252.cpp 实现 safe 接口,内部用 __disable_irq() / __set_PRIMASK() 包裹(注意:这里用 PRIMASK 而非 BASEPRI,因为要阻塞 preempt=0 的 SYNC0),调用对应的无锁版本后恢复
    • SubTask 2.3: 在 comm/esc_lan9252.h / comm/esc_exmc_bus.h 头文件注释中明确标注:readReg/writeReg/readPdi/writePdi/read16/write16/readBlock/writeBlock 为 "ISR-only,仅供 preempt=0 调用,无锁";safeReadReg/safeWriteReg/safeReadPdi/safeWritePdi 为 "Thread-safe,主循环与 init 调用,PRIMASK 保护"
    • SubTask 2.4: 审查 comm/ethercat_slave.cpp 的 init() 与 process(),所有 EXMC 访问改用 safe 接口(init 阶段虽中断未使能但保持一致性;process 若有 EXMC 访问必须用 safe)
    • SubTask 2.5: 在 comm/ethercat_slave.cpp 的 periodicIsr() 中确认仍调用无锁的 readPdi/writePdi(因 ISR 为 preempt=0,天然原子)
    • SubTask 2.6: 代码审查确认无主循环路径误调用 ISR-only 接口
  • Task 3: 阶段 C — PDO 三缓冲替换双缓冲(修复严重风险 #3 与 #4)

    • SubTask 3.1: 在 comm/ethercat_slave.h 中将 s_inputs[2] / s_outputs[2] 改为 s_inputs[3] / s_outputs[3],成员名改为 s_inputWriteIdx / s_outputReadIdx(替换原 s_inputReadIndex / s_outputWriteIndex),类型 volatile uint8_t
    • SubTask 3.2: 在 comm/ethercat_slave.h 头注释中更新双缓冲说明为三缓冲说明,描述模 3 轮转规则
    • SubTask 3.3: 在 comm/ethercat_slave.cpp 的 init() 中初始化 s_inputWriteIdx = 0 / s_outputReadIdx = 0
    • SubTask 3.4: 重写 periodicIsr() 的缓冲管理:写入 s_inputs[s_inputWriteIdx] → 递增 s_inputWriteIdx = (s_inputWriteIdx + 1) % 3;读取 s_outputs[s_outputReadIdx] → 递增 s_outputReadIdx = (s_outputReadIdx + 1) % 3
    • SubTask 3.5: 重写 getLatestInputs() 返回 &s_inputs[(s_inputWriteIdx + 2) % 3](最新完成且与 ISR 当前写入隔一个)
    • SubTask 3.6: 重写 setOutputs() 写入 s_outputs[(s_outputReadIdx + 1) % 3](与 ISR 当前读取隔一个),无需临界区
    • SubTask 3.7: 移除原 XOR 翻转逻辑(^1U),确认无残留引用
    • SubTask 3.8: 在 comm/ethercat_slave.h 注释中明确"三缓冲容忍主循环跨 1 个周期,主循环处理必须 < 2 × 周期时间"
  • Task 4: 阶段 D — periodicIsr 执行时间测量与评估(修复高风险 #5)

    • SubTask 4.1: 在 comm/ethercat_slave.h 增加 static volatile uint32_t s_isrMaxCycles / static volatile uint32_t s_isrAvgCycles / static volatile uint32_t s_isrCount 诊断成员
    • SubTask 4.2: 在 comm/ethercat_slave.cpp 的 periodicIsr() 入口读 DWT->CYCCNT 存局部变量,出口再读,差值更新 max(原子比较)与 avg(滑动平均或累加)
    • SubTask 4.3: 在 sys/initial.cpp 或 comm/esc_lan9252.cpp::init() 中使能 DWT:CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk; DWT->CYCCNT = 0; DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
    • SubTask 4.4: 在 sys/task_runner.cpp 的 taskDiagnosticPrint() 中增加 EtherCAT ISR 执行时间输出(受 #if CONFIG_ETHERCAT_ENABLE 保护),格式 periodicIsr max=Xus avg=Yus(cycles / 180 转 µs)
    • SubTask 4.5: 在 sys/config.h 增加 CONFIG_ETHERCAT_ISR_MAX_US_THRESHOLD 5U 宏,作为评估阈值
    • SubTask 4.6: 在 spec.md 与代码注释中记录:若实测 > 5µs 需评估减小 CONFIG_ETHERCAT_PDO_SIZE 或优化 EXMC 时序;若 ≤ 5µs 则接受现状并在注释中说明"preempt=0 阻塞窗口 ≤ 5µs,CAN/SPI 需容忍"
  • Task 5: 阶段 E — .ramfunc 段落地与调用链 RAM 化(修复高风险 #6)

    • SubTask 5.1: 定位项目链接脚本文件(查找 *.sct 或 RTE 配置中的 linker script)
    • SubTask 5.2: 在链接脚本中新增 .ramfunc 输出段,执行区(VMA)位于 SRAM,加载区(LMA)位于 Flash(参考 .data 段的 scatter 语法)
    • SubTask 5.3: 确认启动代码(startup_gd32e50x_cl.s 或 system_gd32e50x.c)是否自动拷贝 .ramfunc 段;若否,增加拷贝逻辑(类似 .data 拷贝循环)
    • SubTask 5.4: 在 comm/ethercat_slave.cpp 的 periodicIsr() 已有 __attribute__((section(".ramfunc"))) 基础上增加 noinline 属性
    • SubTask 5.5: 在 comm/esc_lan9252.cpp 的 readPdi() / writePdi() 增加 __attribute__((section(".ramfunc"), noinline))
    • SubTask 5.6: 在 comm/esc_exmc_bus.cpp 的 read16() / write16() 增加 __attribute__((section(".ramfunc"), noinline))(注意:readBlock/writeBlock 不在 ISR 调用链,不标记)
    • SubTask 5.7: 编译后查看 map 文件,确认 .ramfunc 段的 VMA 在 SRAM 区域、LMA 在 Flash 区域
    • SubTask 5.8: 若 DWT 测量(阶段 D)显示 Flash 执行已满足 <1µs 抖动,可在 spec 中记录降级方案(仅 periodicIsr 本体 RAM 化),但默认按完整 RAM 化实施
  • Task 6: 编译验证与回退(⚠️ 受阻于预存 EXMC 组件解析问题)

    • SubTask 6.1: 临时将 sys/config.h 中 CONFIG_ETHERCAT_ENABLE 改为 1,全量编译通过,无 error
    • SubTask 6.2: 检查无新增 warning(特别注意 .ramfunc 段未定义或未拷贝的链接警告)
    • SubTask 6.3: 查看 map 文件确认 .ramfunc 段在 SRAM、s_inputs[3]/s_outputs[3] 在 SRAM、s_periodicSeq 在 SRAM
    • SubTask 6.4: 编译验证完成后将 CONFIG_ETHERCAT_ENABLE 恢复为 0
    • SubTask 6.5: 确认 CONFIG_ETHERCAT_ENABLE=0 时全量编译仍通过,功能与改造前等价
    • SubTask 6.6: ⚠️ 阻塞项:GD32E508_DCU_APP.cproject.yml 第 68 行无条件引用 Device:GD32E50x_StdPeripherals:EXMC 组件,但已安装的 GD32E50x_DFP pack(1.9.0/1.3.2)pdsc 未注册 EXMC 组件,导致 csolution 组件解析失败,ENABLE=0 与 ENABLE=1 均无法构建。EXMC 驱动源文件(gd32e50x_exmc.h/.c)实际存在于 pack 中。需单独修复 cproject.yml(改为直接引用源文件)或更新 pack 后重新执行本任务

Task Dependencies

  • Task 1, 2, 3 相互独立,可并行实施(均集中在 comm/ 目录,但修改不同函数与成员)
  • Task 4 依赖 Task 1(periodicIsr 改造完成才能测量)
  • Task 5 依赖 Task 1, 2, 3(调用链 RAM 化需在接口稳定后)
  • Task 6 依赖 Task 1-5 全部完成