/** * @file ethercat_pdo.h * @brief EtherCAT 过程数据(PDO)收发模块(1.5.54 由 dc_app.c 的散装 C 代码类化)。 * * 一个 EtherCAT 周期在 SSC 里被拆成三个回调,本类按这三步各提供一个入口: * 1. APPL_OutputMapping() → readOutputs() 从 ESC 取走主站下发的输出数据 * 2. APPL_Application() → process() 跑应用逻辑(当前是 PDO↔CAN 转发) * 3. APPL_InputMapping() → writeInputs() 把输入数据交回 ESC(从站→主站) * * 缓冲区归本类所有,dc_app.c 只保留 SSC 对象字典(0x7000/0x6000 的 packed 结构体 * 是 SDO 存储,必须留在 C 侧)与三行 extern "C" 转发。这样"收发"这件事只有一个 * 归属:想加新的过程数据语义只改本类,不必再动 SSC 移植层。 * * @note 1.5.55 起输出/输入**长度不再相等**(64B / 384B),为电机控制台腾出 * 状态窗口空间。长度常量统一由 ethercat_can_bridge.h 提供,避免三处各写 * 一个魔数后失配。 * * @note 上下文:free-run 时三个回调都在主循环(ECAT_Main / ECAT_Application); * 启用 DC 后 readOutputs/writeInputs 改由 Sync0_Isr(preempt=0) 调用,而 * process() 仍在主循环。因此 s_out/s_in 是跨上下文共享的——两者都只在 * 单一写者下被整块读写,且 CM4 上 memcpy 的撕裂最坏只会让某个周期读到 * 上/下半帧混合。对 seq 沿触发语义无害(seq 是单字节,要么旧要么新, * 不会产生第三个值),故不加临界区,避免在 ISR 里关中断拖累抖动。 */ #ifndef COMM_ETHERCAT_PDO_H #define COMM_ETHERCAT_PDO_H #include #include "comm/ethercat_can_bridge.h" /** * @brief EtherCAT 过程数据收发。 * @note 全静态类,与 EthercatSlave / EthercatSync 同风格。 */ class EthercatPdo { public: /** @brief 输出过程数据长度(主站→从站)。 */ static constexpr uint16_t kPdoOutBytes = kEcatPdoOutBytes; /** @brief 输入过程数据长度(从站→主站)。 */ static constexpr uint16_t kPdoInBytes = kEcatPdoInBytes; /** * @brief 取走主站下发的输出过程数据(主站→从站)。 * @param[in] esc 指向 ESC 输出缓冲的 kPdoOutBytes 字节。 * @note 由 APPL_OutputMapping() 每周期调用;只做搬运不做业务, * 业务在 process() 里跑,保证 ISR 上下文(DC 模式)足够短。 */ static void readOutputs(const uint8_t *esc); /** * @brief 跑一个应用周期:消费输出、生成输入。 * @note 由 APPL_Application() 在主循环调用。 */ static void process(); /** * @brief 交回输入过程数据(从站→主站)。 * @param[out] esc 指向 ESC 输入缓冲的 kPdoInBytes 字节。 * @note 由 APPL_InputMapping() 每周期调用。 */ static void writeInputs(uint8_t *esc); /** @brief 累计取走输出的周期数(主站在发过程数据的证据)。 */ static uint32_t outputCycles() { return s_outputCycles; } /** @brief 累计交回输入的周期数。 */ static uint32_t inputCycles() { return s_inputCycles; } /** @brief 是否已开始过程数据交换(收到过至少一个输出周期)。 */ static bool isExchanging() { return s_outputCycles != 0U; } /** * @brief 拷出最近一份输出过程数据快照(诊断用)。 * @param[out] dst 至少 kPdoOutBytes 字节。 */ static void snapshotOutput(uint8_t *dst); private: /** * @brief 填充输入 PDO 段 5 设备诊断区(尾部 8 字节,1.5.61)。 * @param[out] in 全长输入过程数据缓冲。 * @note 布局常量 ECAT_DIAG_OFF_* 见 ethercat_can_bridge.h;当前携带 * CPU 负载率(0.1% 单位) + 窗口序号,取自 sys/cpu_load.h。 */ static void fillDiagTail(uint8_t *in); /** @brief 最近一份主站下发的输出数据。 */ static uint8_t s_out[kPdoOutBytes]; /** @brief 待交回主站的输入数据。 */ static uint8_t s_in[kPdoInBytes]; static volatile uint32_t s_outputCycles; static volatile uint32_t s_inputCycles; }; extern "C" { /** @brief 供 dc_app.c(C 文件)调用:取走输出过程数据。 */ void EcatPdo_ReadOutputs(const uint8_t *esc); /** @brief 供 dc_app.c 调用:跑一个应用周期。 */ void EcatPdo_Process(void); /** @brief 供 dc_app.c 调用:交回输入过程数据。 */ void EcatPdo_WriteInputs(uint8_t *esc); } #endif /* COMM_ETHERCAT_PDO_H */