1.6.14 CAN-FD 数据段 2Mbps→5Mbps:CONFIG_CAN_DATA_PRESCALER 9→3、BS1 3→4TQ (90MHz/(3×6TQ),采样点 80%→83.3%),仲裁段仍 1M。TDC filter/offset 维持 4 不做 等比缩放(TDCMOD=CALC_AND_OFFSET 下硬件已自动测回路延时;放到 12 会让本节点回采 错开约 6 个数据位,自发热帧被判错误帧)。配套电机 APP/BOOT data_prescaler=2。 1.6.15 段4 批量接收由"环形队列 + 变长紧排排空"改为 5×44B 定长覆盖窗口,输入 PDO 496→600(75×64bit),段5 诊断区 484→584。动机两条:现场 4000 帧/s 超旧容量 3 帧/拍 恒丢 1 帧;更关键是排空赤字把队列长期压在饱和态,而 1.6.9~1.6.13 追的错标/拼接竞态 正是饱和态喂出来的(判满用的 tail 滞后→生产者写进消费者刚读过的槽)。定长消灭 tail/判满/pending,边界越界写在结构上不可能再犯。每槽带 u32 seqlock:buildInput 在 PDI_Isr(preempt=0) 抢占主循环写侧,无它则主站会拿到"新 ID + 旧数据"拼出的假帧。 out+in=984 ≤ 1024 硬上限,DPRAM 留 120B。语义变更:批量区从"不丢的事件流"变"最近 N 帧窗口",去重与丢帧判据移交主站按 seq 归一。三端同步 size.h/bridge/dc_app/ gen_eeprom/DCU_ESI.xml,必须重烧固件 + 烧 EEPROM + 断电冷启动,只烧一边 SAFEOP 报 AL 0x001A/0x0011。 1.6.16 段1/段3 快照 drop 计数口径修复(纯判据,PDO 布局逐字节不动):旧判据 s_rxValid 置位后永不清零 ⇒ 开机首帧后每帧都算丢弃,drop ≡ rx−1,是零灵敏度假仪表 且把网页状态栏刷成 ERR。新增 s_rxPublished,判据改 s_rxValid && !s_rxPublished。 1.6.17~19 [DIFF-STATS] 差分写取证仪表,零行为变更,只加 0x2000 sub13..23(11 个只读 UINT32) ⇒ 不动 PDO 长度/映射,无需重生成 SII。实测:每拍 27.4% 字节真变化(164B)、 run 峰值 12~13、段2 占变化量 ~53%、段4 ~25%。 1.6.19 推翻 1.6.18 的自我诊断:比对开销虚报不是 -Oz 把 memcpy 编成调用(手拼移位 反而 21.3µs→41µs),真根因是 alignas(4) 到 const uint8_t* 形参处丢失,优化器无法 证明 4 字节对齐。__builtin_assume_aligned + memcpy 后循环体缩成 ldr/ldr/cmp/bne、 bl 归零。教训:测量工具自身的开销会直接变成结论。 1.6.20 基线对照版(当前提交状态 CONFIG_ECAT_DIFF_STATS=0):同工况(电机在线无指令) 实测 load 49.1% vs 仪表开的 51.6% ⇒ 仪表自身成本 2.4 个点,差分改造收益 = 0。 按实测变化量推算 A1(段粒度条件写) 只值约 1 点(段2/段4 每拍都变,必然整块重写), 仅 A2(8B 块粒度 + run 上限退化为整写) 有空间 —— 推算值,未做 A/B 实测不作为结论。
129 lines
6.2 KiB
C++
129 lines
6.2 KiB
C++
/**
|
||
* @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 <cstdint>
|
||
|
||
#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 输出探针(1.6.0 输出按需全量读,配合 SSC PDO_OutputMapping)。
|
||
* @param[in] head 输出 PDO 头 12B(覆盖 SEQ@0 / WIN@8 / FLAGS@3)。
|
||
* @return true=需要全量读(seq 沿或清统计请求);false=本周期跳过全量读。
|
||
* @note 每周期由 PDI_Isr 在读 ESC 前调用:内部刷新桥的 s_winBase 并做 seq
|
||
* 基准 priming;s_lastSeq 的推进仍留在全量路径(processOutput)。
|
||
*/
|
||
static bool outputProbe(const uint8_t *head);
|
||
|
||
/**
|
||
* @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 process() 被重入拦下的次数。持续增长 = 跨上下文抢占确实在发生
|
||
* (PDI_Isr / Sync0_Isr / Sync1_Isr / MainLoop 互相打断),也就证明了
|
||
* 批量区跨轮次拼接的成因;恒为 0 则说明该机制推断有误。 */
|
||
static uint32_t reentryCount() { return s_reentryCount; }
|
||
|
||
/** @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 最近一份主站下发的输出数据。
|
||
* @note alignas(4):让整块 memcpy(writeInputs / [DIFF-STATS] 的快照拷贝)按
|
||
* 字宽走而非逐字节。比对主循环本身不依赖它——已改成手拼 4 字节移位,
|
||
* 因为 -Oz 下 memcpy(&a,p,4) 会编成函数调用,见 ethercat_diff_stats.cpp。 */
|
||
alignas(4) static uint8_t s_out[kPdoOutBytes];
|
||
/** @brief 待交回主站的输入数据(已发布的完整镜像,writeInputs 只读它)。 */
|
||
alignas(4) static uint8_t s_in[kPdoInBytes];
|
||
/** @brief 输入 PDO 工作缓冲:buildInput 填这里,填完整块发布到 s_in。
|
||
* ECAT_Application() 会在 PDI_Isr / Sync0_Isr / Sync1_Isr / MainLoop 四个
|
||
* 上下文被调用并互相抢占,直接写 s_in 会让读侧取到跨轮次的半成品。 */
|
||
alignas(4) static uint8_t s_build[kPdoInBytes];
|
||
/** @brief process() 被重入拦下的次数(诊断用,见 reentryCount())。 */
|
||
static volatile uint32_t s_reentryCount;
|
||
|
||
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 */
|