2026-06-26 00:57:59 +08:00
|
|
|
/**
|
|
|
|
|
* @file ethercat_slave.h
|
2026-06-29 01:51:02 +08:00
|
|
|
* @brief EtherCAT 从站周期处理接口与 PDO 三缓冲(triple buffer)结构。
|
2026-06-26 00:57:59 +08:00
|
|
|
* @note 周期 ISR 调用 periodicIsr() 完成输入 PDO 读取 + 输出 PDO 写入;
|
|
|
|
|
* 应用层通过 getLatestInputs()/setOutputs() 与周期路径无锁交换数据。
|
2026-06-29 01:51:02 +08:00
|
|
|
*
|
|
|
|
|
* 通知路径分离:
|
|
|
|
|
* 周期路径(periodicIsr)用 s_periodicSeq 序列号通知主循环;
|
|
|
|
|
* 事件路径(ESC IRQ)用 TASK_ETHERCAT_PROCESS 标志。
|
|
|
|
|
* 两者独立,避免 preempt=0 ISR 与 BASEPRI 临界区的 RMW 竞态。
|
2026-06-26 00:57:59 +08:00
|
|
|
*/
|
|
|
|
|
#ifndef COMM_ETHERCAT_SLAVE_H
|
|
|
|
|
#define COMM_ETHERCAT_SLAVE_H
|
|
|
|
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
#include <cstddef>
|
2026-07-16 23:00:52 +08:00
|
|
|
#include "sys/config.h" /* CONFIG_ETHERCAT_PDO_SIZE */
|
2026-06-26 00:57:59 +08:00
|
|
|
|
2026-06-29 01:51:02 +08:00
|
|
|
/** @brief EtherCAT AL 状态码(EtherCAT 协议定义,ESC AL Status 寄存器低 4 位)。 */
|
|
|
|
|
constexpr uint8_t AL_STATE_SAFEOP = 0x04U; /**< SAFEOP 状态(SYNC0 可触发下限)。 */
|
|
|
|
|
|
2026-06-26 00:57:59 +08:00
|
|
|
/**
|
|
|
|
|
* @brief PDO 数据缓冲(输入/输出共用结构)。
|
2026-06-29 01:51:02 +08:00
|
|
|
* @note 周期 ISR 与应用层通过三缓冲无锁交换。
|
2026-06-26 00:57:59 +08:00
|
|
|
*/
|
|
|
|
|
struct PdoBuffer {
|
|
|
|
|
uint8_t data[CONFIG_ETHERCAT_PDO_SIZE] = {0}; /**< 数据载荷。 */
|
|
|
|
|
size_t len = 0U; /**< 有效数据长度。 */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief EtherCAT 从站周期处理。
|
2026-06-29 01:51:02 +08:00
|
|
|
* @note 周期路径(periodicIsr)必须极简:读输入→写输出→模 3 轮转指针→递增序列号。
|
2026-06-26 00:57:59 +08:00
|
|
|
*/
|
|
|
|
|
class EthercatSlave {
|
|
|
|
|
public:
|
2026-06-29 01:51:02 +08:00
|
|
|
/** @brief 初始化 ESC 与三缓冲。 */
|
2026-06-26 00:57:59 +08:00
|
|
|
static void init();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief 周期中断处理(由 SYNC0 EXTI 触发)。
|
|
|
|
|
* @note 放入 RAM 执行段(.ramfunc)以降低 Flash 等待带来的抖动。
|
2026-06-29 01:51:02 +08:00
|
|
|
* 流程:读输入 PDO → 写输出 PDO → 模 3 轮转索引 → 递增 s_periodicSeq。
|
|
|
|
|
* 不再调用 TaskManager::set,避免 preempt=0 与 BASEPRI 临界区的 RMW 竞态。
|
2026-06-26 00:57:59 +08:00
|
|
|
*/
|
|
|
|
|
static void periodicIsr();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief 获取最新输入 PDO(主循环调用,无锁)。
|
2026-06-29 01:51:02 +08:00
|
|
|
* @return const PdoBuffer* 最新输入缓冲只读指针;返回与 ISR 当前写入位置隔一个的缓冲。
|
|
|
|
|
* @note 三缓冲容忍主循环跨 1 个周期,主循环处理必须 < 2 × 周期时间。
|
2026-06-26 00:57:59 +08:00
|
|
|
*/
|
|
|
|
|
static const PdoBuffer* getLatestInputs();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief 设置下次周期要发送的输出 PDO(主循环调用)。
|
|
|
|
|
* @param[in] outputs 输出数据指针(内容会被拷贝到内部输出缓冲)。
|
2026-06-29 01:51:02 +08:00
|
|
|
* @note 写入与 ISR 当前读取位置隔一个的缓冲,无需任何临界区。
|
2026-06-26 00:57:59 +08:00
|
|
|
*/
|
|
|
|
|
static void setOutputs(const PdoBuffer *outputs);
|
|
|
|
|
|
|
|
|
|
/**
|
2026-06-29 01:51:02 +08:00
|
|
|
* @brief 应用层 PDO 映射入口(非实时,由 TASK_ETHERCAT_PROCESS 或周期序列号触发)。
|
2026-06-26 00:57:59 +08:00
|
|
|
* @note 留接口,具体映射逻辑由协议栈后续实现。
|
|
|
|
|
*/
|
|
|
|
|
static void process();
|
|
|
|
|
|
2026-06-29 01:51:02 +08:00
|
|
|
/**
|
|
|
|
|
* @brief 获取应用层输入 PDO 快照(主循环调用)。
|
|
|
|
|
* @return const PdoBuffer* 最近一次 process() 拷贝的输入快照只读指针。
|
|
|
|
|
* @note 返回的指针在下次 process() 调用前有效;应用层应在 process() 后立即读取。
|
2026-07-15 02:49:37 +08:00
|
|
|
* @note TODO(app-layer): 应用层 PDO 字典映射 spec 启用前无外部调用方;
|
|
|
|
|
* 保留接口契约,待未来 spec 实现后由应用层调用。 */
|
2026-06-29 01:51:02 +08:00
|
|
|
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 实现后由应用层调用。 */
|
2026-06-29 01:51:02 +08:00
|
|
|
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 集成后由应用层查询。 */
|
2026-06-29 01:51:02 +08:00
|
|
|
static uint8_t getAlState() { return s_alState; }
|
|
|
|
|
|
2026-06-26 00:57:59 +08:00
|
|
|
private:
|
2026-06-29 01:51:02 +08:00
|
|
|
/**
|
|
|
|
|
* @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 守卫)。 */
|
2026-06-26 00:57:59 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#endif /* COMM_ETHERCAT_SLAVE_H */
|