/** * @file ethercat_slave.h * @brief EtherCAT 从站周期处理接口与 PDO 三缓冲(triple buffer)结构。 * @note 周期 ISR 调用 periodicIsr() 完成输入 PDO 读取 + 输出 PDO 写入; * 应用层通过 getLatestInputs()/setOutputs() 与周期路径无锁交换数据。 * * 通知路径分离: * 周期路径(periodicIsr)用 s_periodicSeq 序列号通知主循环; * 事件路径(ESC IRQ)用 TASK_ETHERCAT_PROCESS 标志。 * 两者独立,避免 preempt=0 ISR 与 BASEPRI 临界区的 RMW 竞态。 */ #ifndef COMM_ETHERCAT_SLAVE_H #define COMM_ETHERCAT_SLAVE_H #include #include #include "sys/config.h" /* CONFIG_ETHERCAT_PDO_SIZE */ /** @brief EtherCAT AL 状态码(EtherCAT 协议定义,ESC AL Status 寄存器低 4 位)。 */ constexpr uint8_t AL_STATE_SAFEOP = 0x04U; /**< SAFEOP 状态(SYNC0 可触发下限)。 */ /** * @brief PDO 数据缓冲(输入/输出共用结构)。 * @note 周期 ISR 与应用层通过三缓冲无锁交换。 */ struct PdoBuffer { uint8_t data[CONFIG_ETHERCAT_PDO_SIZE] = {0}; /**< 数据载荷。 */ size_t len = 0U; /**< 有效数据长度。 */ }; /** * @brief EtherCAT 从站周期处理。 * @note 周期路径(periodicIsr)必须极简:读输入→写输出→模 3 轮转指针→递增序列号。 */ class EthercatSlave { public: /** @brief 初始化 ESC 与三缓冲。 */ static void init(); /** * @brief 周期中断处理(由 SYNC0 EXTI 触发)。 * @note 放入 RAM 执行段(.ramfunc)以降低 Flash 等待带来的抖动。 * 流程:读输入 PDO → 写输出 PDO → 模 3 轮转索引 → 递增 s_periodicSeq。 * 不再调用 TaskManager::set,避免 preempt=0 与 BASEPRI 临界区的 RMW 竞态。 */ static void periodicIsr(); /** * @brief 获取最新输入 PDO(主循环调用,无锁)。 * @return const PdoBuffer* 最新输入缓冲只读指针;返回与 ISR 当前写入位置隔一个的缓冲。 * @note 三缓冲容忍主循环跨 1 个周期,主循环处理必须 < 2 × 周期时间。 */ static const PdoBuffer* getLatestInputs(); /** * @brief 设置下次周期要发送的输出 PDO(主循环调用)。 * @param[in] outputs 输出数据指针(内容会被拷贝到内部输出缓冲)。 * @note 写入与 ISR 当前读取位置隔一个的缓冲,无需任何临界区。 */ static void setOutputs(const PdoBuffer *outputs); /** * @brief 应用层 PDO 映射入口(非实时,由 TASK_ETHERCAT_PROCESS 或周期序列号触发)。 * @note 留接口,具体映射逻辑由协议栈后续实现。 */ static void process(); /** * @brief 获取应用层输入 PDO 快照(主循环调用)。 * @return const PdoBuffer* 最近一次 process() 拷贝的输入快照只读指针。 * @note 返回的指针在下次 process() 调用前有效;应用层应在 process() 后立即读取。 * @note TODO(app-layer): 应用层 PDO 字典映射 spec 启用前无外部调用方; * 保留接口契约,待未来 spec 实现后由应用层调用。 */ static const PdoBuffer* getApplicationInputs(); /** * @brief 设置应用层输出 PDO 快照(主循环调用)。 * @param[in] outputs 输出数据指针(内容会被拷贝到内部快照)。 * @note 下次 process() 调用时,内部快照通过 setOutputs() 推入三缓冲。 * @note TODO(app-layer): 应用层 PDO 字典映射 spec 启用前无外部调用方; * 保留接口契约,待未来 spec 实现后由应用层调用。 */ static void setApplicationOutputs(const PdoBuffer *outputs); /** * @brief 检查是否有新的周期数据待主循环处理。 * @return bool true 表示 s_periodicSeq 与已处理序号不同。 * @note 主循环 runPending() 优先调用此接口,替代原 TASK_ETHERCAT_PROCESS 周期路径。 */ static bool hasPendingPeriodic(); /** * @brief 标记当前周期数据已被主循环处理。 * @note 调用此接口前应已调用 taskEthercatProcess() 完成本次 PDO 映射。 */ static void markPeriodicProcessed(); /** @brief 读取 ISR 执行时间诊断(max/avg 单位:CPU 周期数,除以 180 得 µs)。 */ static void getIsrStats(uint32_t *maxCycles, uint32_t *avgCycles, uint32_t *count); /** @brief ESC 自检是否通过(init 后可查询,用于决定是否使能同步中断)。 */ static bool isEscReady() { return s_escReady; } /** @brief 读取 ESC 芯片 ID(供诊断打印;未就绪时为上次读到的值)。 */ static uint32_t getChipId() { return s_chipId; } /** @brief 读取 ESC AL 状态码(供诊断打印与 SSC 集成;未就绪时为上次读到的值)。 * @note TODO(app-layer): 当前仅 init 内部用作 SAFEOP 守卫,外部诊断未启用; * SSC 集成后由应用层查询。 */ static uint8_t getAlState() { return s_alState; } private: /** * @brief 输入三缓冲。 * ISR 按 s_inputWriteIdx 模 3 轮转写入;getLatestInputs() 读取 (writeIdx + 2) % 3。 */ static PdoBuffer s_inputs[3]; /** * @brief 输出三缓冲。 * ISR 按 s_outputReadIdx 模 3 轮转读取;setOutputs() 写入 (readIdx + 2) % 3, * 与 ISR 当前读取位置隔 2 个缓冲,提供 2 周期宽限消除拷贝竞态。 */ static PdoBuffer s_outputs[3]; /** @brief 应用层输入快照(主循环访问,process() 从三缓冲拷贝;无需三缓冲)。 */ static PdoBuffer s_appInputs; /** @brief 应用层输出快照(主循环访问,process() 推入三缓冲;无需三缓冲)。 */ static PdoBuffer s_appOutputs; static volatile uint8_t s_inputWriteIdx; /**< ISR 当前写入索引;模 3 轮转。 */ static volatile uint8_t s_outputReadIdx; /**< ISR 当前读取索引;模 3 轮转。 */ /** @brief 周期路径通知序列号(ISR 单字原子递增,主循环比对;uint16_t 提供 6.55s 回绕周期)。 */ static volatile uint16_t s_periodicSeq; /** @brief 主循环已处理序号(由 hasPendingPeriodic/markPeriodicProcessed 维护)。 */ static uint16_t s_processedSeq; static volatile uint32_t s_isrMaxCycles; /**< ISR 最大执行周期数(180MHz 基准)。 */ static volatile uint32_t s_isrAvgCycles; /**< ISR 平均执行周期数(滑动累计)。 */ static volatile uint32_t s_isrCount; /**< ISR 执行次数计数。 */ static bool s_escReady; /**< ESC 自检结果(init 后置位)。 */ static uint32_t s_chipId; /**< ESC 芯片 ID(诊断用)。 */ static uint8_t s_alState; /**< ESC AL 状态码(init 后可查询,供 isEscReady 守卫)。 */ }; #endif /* COMM_ETHERCAT_SLAVE_H */