Files
DCU_APP/GD32E508_DCU_APP/comm/ethercat_slave.h
T

145 lines
6.7 KiB
C++
Raw Normal View History

/**
* @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 <cstdint>
#include <cstddef>
#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() 后立即读取。
2026-07-15 02:49:37 +08:00
* @note TODO(app-layer): 应用层 PDO 字典映射 spec 启用前无外部调用方;
* 保留接口契约,待未来 spec 实现后由应用层调用。 */
static const PdoBuffer* getApplicationInputs();
/**
* @brief 设置应用层输出 PDO 快照(主循环调用)。
* @param[in] outputs 输出数据指针(内容会被拷贝到内部快照)。
* @note 下次 process() 调用时,内部快照通过 setOutputs() 推入三缓冲。
2026-07-15 02:49:37 +08:00
* @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; }
2026-07-15 02:49:37 +08:00
/** @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 */