Slave 属性与方法
通过 master.GetSlave(index) 获取 Slave& 引用。
C++ 可用 darra::sugar::idx(master, 1) 简短获取从站, 或用 if (auto opt = darra::sugar::slave_opt(master, i)) {...} std::optional 安全访问 (越界不抛异常). 详见 索引访问.
属性
| 类别 | 属性 | 类型 | 读写 | 说明 |
|---|---|---|---|---|
| 基本标识 | Index() / SlaveNum() | int | 只读 | 从站编号(1-based) |
| Name() | std::string | 只读 | 设备名称(从 EEPROM group_name 读取) | |
| DriveName() | std::string | 只读 | 驱动/设备名称(从 SDO 0x1008 读取) | |
| 设备信息 | VendorId() | uint32_t | 只读 | 制造商 ID(从 SII EEPROM 读取) |
| VendorName() | std::string | 只读 | 制造商名称(从 ESI 文件读取) | |
| ProductId() | uint32_t | 只读 | 产品 ID | |
| RevId() / Revision() | uint32_t | 只读 | 修订版本号 | |
| SerialNumber() | uint32_t | 只读 | 序列号(从 SII EEPROM 读取) | |
| HasMDP() | bool | 只读 | 是否支持模块化设备配置文件(ETG.5001) | |
| BlockLRW() | bool | 只读 | LRW 逻辑读写操作阻止标志 | |
| 地址 | ConfigAddr() | uint16_t | 只读 | 物理配置地址 |
| AliasAddress() | uint16_t | 只读 | 别名地址 | |
| 状态 | State() | EcState | 只读 | 从站当前状态 |
| ErrorCode() | EcALState | 只读 | AL Status Code 错误码 | |
| IsLost() | bool | 只读 | 从站是否丢失(断开连接) | |
| 诊断镜像 | diag_mirror::slave_wc_contributed(mi, si) | WcContribution | 只读 | 该从站本周期对工作计数器(WKC)的贡献状态(薄读零帧,自由函数),详见 WKC 贡献语义 |
| diag_mirror::slave_al_status_mirror(mi, si) | AlStatusMirror | 只读 | 内核 AL 状态镜像(解析为 state + error + code + raw,薄读零帧,自由函数) | |
| 拓扑 | Topology() | uint8_t | 只读 | 拓扑类型 (0=无链接, 1=端点, 2=中间, 3=分支, 4=交叉) |
| ParentStation() | uint16_t | 只读 | 父从站的站地址 (注: 实际是从站索引 1-based, 不是 station address; 0 表示父节点是主站) | |
| ParentPort() | uint8_t | 只读 | 父端口号 | |
| EntryPort() | uint8_t | 只读 | 入口端口号 | |
| ActivePorts() | uint8_t | 只读 | 激活端口位掩码 | |
| PhysicalType() | uint8_t | 只读 | 物理端口类型 | |
| PDO 数据 | Ibits() / Obits() | uint16_t | 只读 | 输入/输出数据位数 |
| Ibytes() / Obytes() | uint32_t | 只读 | 输入/输出数据字节数 | |
| Ioffset() / Ooffset() | uint32_t | 只读 | 输入/输出在过程数据区中的偏移 | |
| Istartbit() / Ostartbit() | uint8_t | 只读 | 输入/输出起始位 | |
| ESI/配置 | HasEsi() | bool | 只读 | 是否已加载 ESI 文件 |
| EsiName() | std::string | 只读 | ESI 名称(从 EEPROM 读取) | |
| EsiVersion() | std::string | 只读 | ESI 版本号 | |
| ConfigByEsi() | bool | 只读 | 从 ESI 文件获取的设备配置(综合自动配置) | |
| EEPROM | Eep8ByteAddressing() | bool | 只读 | EEPROM 寻址模式 (true=8字节, false=4字节) |
| EepPDI() | uint8_t | 只读 | 物理设备接口 (PDI) 类型 | |
| EbusCurrent() | int16_t | 只读 | E-bus 电流消耗 (mA) | |
| 邮箱 | MbxProto() | MailboxType | 只读 | 支持的邮箱协议类型(位掩码) |
| MbxLength() | uint16_t | 只读 | 邮箱发送缓冲区大小 | |
| MbxReadLength() | uint16_t | 只读 | 邮箱接收缓冲区大小 | |
| MbxReadOffset() / MbxWriteOffset() | uint16_t | 只读 | 邮箱读/写偏移 | |
| MbxCount() | uint8_t | 只读 | 邮箱协议计数器 | |
| 协议详情 | CoEDetails() | uint8_t | 只读 | CoE 协议功能标志 |
| EoEDetails() | uint8_t | 只读 | EoE 协议功能标志 | |
| FoEDetails() | uint8_t | 只读 | FoE 协议详情 | |
| SoEDetails() | uint8_t | 只读 | SoE 协议详情 | |
| FMMU | FMMU0Function() | uint8_t | 只读 | FMMU0 功能类型(bit 0=输出, bit 1=输入) |
| FMMU1Function() | uint8_t | 只读 | FMMU1 功能类型 | |
| FMMU2Function() | uint8_t | 只读 | FMMU2 功能类型 | |
| FMMU3Function() | uint8_t | 只读 | FMMU3 功能类型 | |
| DC | HasDC() | bool | 只读 | 是否支持 DC,详见 DC 同步 |
| DCActive() | uint16_t | 只读 | DC 激活状态(0=禁用, 非0=已激活),详见 DC 同步 | |
| DCCycle0() / DCCycle1() | int32_t | 只读 | SYNC0/SYNC1 周期(纳秒),详见 DC 同步 | |
| DCShift() | int32_t | 只读 | 相位偏移(纳秒),详见 DC 同步 | |
| PropagationDelay() / PDelay() | int32_t | 只读 | 帧从主站到达此从站的传播延迟(纳秒),详见 DC 同步 | |
| DCNext() / DCPrevious() | uint16_t | 只读 | DC 下一个/上一个从站索引 | |
| DCParentPort() | int32_t | 只读 | DC 父端口 | |
| DCReceiveTimeA/B/C/D() | int32_t | 只读 | 端口 A/B/C/D 接收时间(纳秒) | |
| 拓扑扩展 | SupportsFrameRepeat() / SupportsFrameRepeat(bool) | bool | 读写 | 是否支持帧重复功能 (ETG.1500 §5.4.3) |
| 冗余 | RedundancyActivated() | bool | 只读 | 冗余是否激活 |
| PrimaryLinkBroken() | bool | 只读 | 主链路是否断开 | |
| SecondaryLinkBroken() | bool | 只读 | 备链路是否断开 | |
| 邮箱健康 (2.5.x) | MailboxHealth() | slave_stats::MailboxHealth | 只读 | 邮箱健康度 (Unknown/Healthy/Degraded), 薄读零帧 |
| IsFreeRunDemoted() | bool | 只读 | 是否被迫从 DC 降级到 FreeRun | |
| HealthDegradedCount() | uint32_t | 只读 | 邮箱半失效连续累计计数 (≈秒数) | |
| RecoverMailboxHealth() | bool | 方法 | 手动触发邮箱半失效修复 (内核通常自动修复) | |
| 配置 | Group() / Group(uint8_t) | uint8_t | 读写 | 从站分组号(0-7,0=默认组,必须在 SAFE_OP 前设置),详见 从站分组 |
| IsOptional() / IsOptional(bool) | bool | 读写 | 可选从站标记,缺席时不影响 WKC 检查(必须在 OP 前设置) | |
| Startup | ShouldWritePDOAssignment() / ShouldWritePDOAssignment(bool) | bool | 读写 | 是否写入 PDO Assignment 配置 |
| ShouldWritePDOConfiguration() / ShouldWritePDOConfiguration(bool) | bool | 读写 | 是否写入 PDO Configuration 配置 | |
| SupportsCompleteAccess() / SupportsCompleteAccess(bool) | bool | 读写 | 是否支持 Complete Access 模式 |
WcContribution 枚举值(WKC 贡献状态)
定义于 darra::ethercat::diag_mirror 命名空间。
enum class WcContribution : uint8_t {
NotContributed = 0, // 该从站此刻未响应(掉站 / 热插拔中)— 如实暴露,非错误处理失败
Contributed = 1, // 该从站此刻正常贡献 WKC
Unknown = 0xFF // 内核缓存未知(未映射 / 未刷新)— 诚实暴露,不臆测
};
AlStatusMirror 结构(AL 状态镜像解析)
定义于 darra::ethercat::diag_mirror 命名空间,从 16bit 镜像拆出。
struct AlStatusMirror {
uint16_t raw; // 原始 16bit 镜像(0 表示未知 / 未刷新)
uint8_t state; // AL State(低 4 位,1/2/4/8 = INIT/PREOP/SAFEOP/OP)
bool error; // AL Status Error 位(bit4)
uint8_t code; // AL Status Code 高字节(0 表示无错误码)
bool known; // raw != 0 时为 true;false = 内核尚无该从站镜像
bool is_op() const; // AL State 是否处于 OP
bool is_safeop() const; // AL State 是否处于 SAFEOP
bool is_preop() const; // AL State 是否处于 PREOP
bool is_init() const; // AL State 是否处于 INIT
};
EcTopologyType / EcPortType 枚举值
enum class EcTopologyType : uint8_t {
NoLink = 0, // 无链接
EndPoint = 1, // 端点
Line = 2, // 中间节点(线性拓扑)
Fork = 3, // 分支点
Cross = 4 // 交叉点
};
enum class EcPortType : uint8_t {
NotUsed = 0, // 未使用
MII = 1, // MII
EBUS = 2, // EBUS
EBUSEnhanced = 3 // EBUS 增强型
};
MailboxType 枚举值
enum class MailboxType : uint16_t {
ErrorMailbox = 0x00, // 错误邮箱
ADSOverEtherCAT = 0x01, // AoE
EthernetOverEtherCAT = 0x02, // EoE
CANopenOverEtherCAT = 0x03, // CoE
FileOverEtherCAT = 0x04, // FoE
ServoOverEtherCAT = 0x05, // SoE
VendorOverEtherCAT = 0x0F // VoE
};
EcCoEDetails 枚举值
enum EcCoEDetails : uint8_t {
None = 0x00,
SDO = 0x01, // 支持 SDO
SDOInfo = 0x02, // 支持 SDO Info
PDOAssign = 0x04, // 支持 PDO Assign
PDOConfig = 0x08, // 支持 PDO Config
Startup = 0x10, // 支持 Startup
CompleteAccess = 0x20 // 支持 Complete Access
};
EcEoEDetails 枚举值
enum EcEoEDetails : uint8_t {
None = 0x00,
SendFrame = 0x01, // 支持发送帧
ReceiveFrame = 0x02, // 支持接收帧
SetIPParam = 0x04, // 支持设置 IP 参数
GetIPParam = 0x08 // 支持获取 IP 参数
};
子对象
| 属性 | 类型 | 说明 |
|---|---|---|
| GetCoE() | CoE& | CANopen over EtherCAT,懒初始化 |
| GetSoE(driveNo) | SoE& | Servo over EtherCAT,可选参数 driveNo |
| GetFoE() | FoE& | File over EtherCAT,懒初始化 |
| GetEoE() | EoE& | Ethernet over EtherCAT,懒初始化 |
| GetAoE() | AoE& | ADS over EtherCAT,懒初始化 |
| GetVoE() | VoE& | Vendor over EtherCAT,懒初始化 |
| GetFSoE() | FSoE& | Functional Safety over EtherCAT,懒初始化 |
| GetCiA402() | CiA402& | CiA402 驱动控制,懒初始化 |
| GetMDP() | MDP* | MDP 模块化设备,不支持时返回 nullptr |
PDO 尺寸 / 偏移等多返回值字段, 可用 auto [in_bytes, out_bytes] = darra::sugar::pdo_size(slave); structured binding 一次取出. 详见 structured binding.
枚举描述
C++ 使用 GetALStatusCodeString() 方法获取 AL Status Code 的英文描述:
std::string desc = slave.GetALStatusCodeString(); // "Sync manager watchdog"
其他枚举可通过 static_cast<int>() 转换为整数值后查表。
标志与错误确认
auto& slave = master.GetSlave(1);
// OpOnly 标志(ETG.1500)—— 从站是否仅在 OP 状态下可用
bool opOnly = slave.IsOpOnly();
// 设备仿真标志 —— 从站是否为仿真设备
bool emulated = slave.IsDeviceEmulation();
// 确认从站错误(清除 AL Status Code 错误)
slave.SetErrorAck(true);
// 热插拔重配置检测
if (slave.NeedsStartupReconfig()) {
// 从站需要重新执行启动配置
slave.ClearStartupReconfigFlag();
}
协议子对象访问
auto& slave = master.GetSlave(1);
// 获取协议实例 (懒初始化)
CoE& coe = slave.GetCoE();
SoE& soe = slave.GetSoE(); // 可选参数: driveNo
FoE& foe = slave.GetFoE();
EoE& eoe = slave.GetEoE();
AoE& aoe = slave.GetAoE();
VoE& voe = slave.GetVoE();
FSoE& fsoe = slave.GetFSoE();
CiA402& cia = slave.GetCiA402();
诊断
从站诊断信息(通信异常率、冗余状态、DC 同步)通过独立诊断接口访问。
ESI 方法
ConfigureFromEsi()
bool ConfigureFromEsi() const;
从 ESI 文件自动配置从站(SM/FMMU/PDO 映射)。
返回值:
bool— 是否成功
ConfigByEsi()
bool ConfigByEsi() const;
综合自动配置(SM + DC),对应 C# ConfigByEsi。
auto& slave = master.GetSlave(1);
// 从 ESI 文件自动配置从站
bool ok = slave.ConfigureFromEsi();
printf("ESI 自动配置: %s\n", ok ? "成功" : "失败");
// 综合自动配置 (SM + DC)
bool ok2 = slave.ConfigByEsi();
printf("ESI 综合配置: %s\n", ok2 ? "成功" : "失败");
过程数据看门狗
SetWatchdog()
bool SetWatchdog(uint32_t timeoutMs) const;
设置从站过程数据看门狗超时。从站在超时时间内未收到过程数据帧时触发看门狗错误(ALStatusCode 0x001B)。
参数:
timeoutMs(uint32_t) — 超时时间(毫秒),0 = 禁用,最大 6553ms
返回值:
bool— 是否成功
应在 SafeOp 或 OP 状态下调用。
SetPdiWatchdog()
bool SetPdiWatchdog(int timeoutMs) const;
设置从站 PDI 看门狗超时。PDI 看门狗监控从站本地应用(微控制器固件)是否正常运行。
参数:
timeoutMs(int) — 超时时间(毫秒),0 = 禁用
返回值:
bool— 是否成功
GetWatchdogConfig()
bool GetWatchdogConfig(WatchdogConfig& config) const;
读取从站看门狗当前配置。
参数:
config(WatchdogConfig&) — 输出配置结构体,字段:
| 字段 | 含义 |
|---|---|
pd_timeout_ms | 过程数据看门狗超时 (ms) |
pdi_timeout_ms | PDI 看门狗超时 (ms) |
返回值:
bool— 是否成功
GetWatchdogStatus()
bool GetWatchdogStatus(WatchdogStatus& status) const;
读取从站看门狗运行状态。
参数:
status(WatchdogStatus&) — 输出状态结构体,字段:
| 字段 | 含义 |
|---|---|
pd_watchdog_state | 过程数据看门狗状态 |
pdi_watchdog_state | PDI 看门狗状态 |
返回值:
bool— 是否成功
示例:
auto& slave = master.GetSlave(1);
// 设置过程数据看门狗超时
slave.SetWatchdog(100); // 100ms, 0=禁用
// 设置 PDI 看门狗超时
slave.SetPdiWatchdog(200);
// 获取看门狗配置
WatchdogConfig wdConfig;
if (slave.GetWatchdogConfig(wdConfig)) {
printf("看门狗配置: PD=%ums, PDI=%ums\n",
wdConfig.pd_timeout_ms, wdConfig.pdi_timeout_ms);
}
// 获取看门狗状态
WatchdogStatus wdStatus;
if (slave.GetWatchdogStatus(wdStatus)) {
printf("看门狗状态: PD=%d, PDI=%d\n",
wdStatus.pd_watchdog_state, wdStatus.pdi_watchdog_state);
}
状态切换
SetState()
bool SetState(EcState target, uint32_t timeoutMs = 3000) const;
设置从站 EtherCAT 状态(带超时)。用于手动恢复单个从站或将从站切换到指定状态。
参数:
target(EcState) — 目标状态timeoutMs(uint32_t) — 超时时间(毫秒),默认 3000ms
返回值:
bool— 是否成功
状态切换遵循 EtherCAT 标准状态机流程,协议层自动处理中间状态。例如从 INIT 切换到 OP 会自动经过 PreOp -> SafeOp -> OP。
示例:
auto& slave = master.GetSlave(1);
// 手动恢复单个从站到 OP
if (slave.State() != EcState::OP)
slave.SetState(EcState::OP);
// 将从站切换到 Init(重置)
slave.SetState(EcState::Init, 5000);
实时诊断镜像 (薄读零帧)
MailboxHealth() / IsFreeRunDemoted() / HealthDegradedCount() 是 Slave 成员方法;WcContributed / AlStatusMirror 由 darra::ethercat::diag_mirror 命名空间的自由函数提供(按 mi/si 取值,不进 Slave 成员表)。两者都是内核 per-slave 诊断缓存的薄读:每次调用直接读 native,不收发帧(零帧),不二次缓存,读到即此刻总线现实,无需刷新。
WKC 贡献语义
diag_mirror::slave_wc_contributed(mi, si) 反映此刻该从站是否真的在响应:
Contributed— 该从站正常贡献工作计数器(WKC),即聚合 WKC 完整(actual == expected,亦即缺口wc_deficit() == 0)时各从站逐字节比对都"满"。NotContributed— 该从站此刻没在贡献 WKC(疑似掉站 / 热插拔恢复中)。这不是 master 故障,从站修复后内核镜像自然回到Contributed。Unknown(0xFF)— 内核镜像尚未填充,诚实暴露,不臆造为"已贡献"。
这些薄读仅"如实反映总线现实", 不参与任何 WKC 篡改 / 迁就逻辑。逐从站贡献位是聚合 WKC 缺口(expected - actual)的逐站分解;缺口大于 0 表示有从站此刻未贡献,应报警 + 诊断,不下调 expected、不停 OP。配套主站级薄读:diag_mirror::wc_deficit(mi) / wkc_actual_mirror(mi) / wkc_expected_mirror(mi) / wkc_complete(mi)。
示例:
#include "slave/diag_mirror.hpp"
namespace dm = darra::ethercat::diag_mirror;
auto& slave = master.GetSlave(1);
uint16_t mi = 0, si = 1;
if (dm::slave_wc_contributed(mi, si) == dm::WcContribution::NotContributed) {
auto al = dm::slave_al_status_mirror(mi, si);
printf("从站 %u 此刻未贡献 WKC(疑似掉站),AL Code=0x%02X\n", si, al.code);
}
if (slave.MailboxHealth() == slave_stats::MailboxHealth::Degraded)
printf("从站 %u 在 OP 但邮箱半失效, 已持续 %us\n", si, slave.HealthDegradedCount());
Startup 配置
| 属性 | 类型 | 说明 |
|---|---|---|
| ShouldWritePDOAssignment() / ShouldWritePDOAssignment(bool) | bool | 启动时是否写入 PDO Assignment,默认 true |
| ShouldWritePDOConfiguration() / ShouldWritePDOConfiguration(bool) | bool | 启动时是否写入 PDO Configuration,默认 false |
| SupportsCompleteAccess() / SupportsCompleteAccess(bool) | bool | 从站是否支持 SDO Complete Access |
ESC 寄存器访问 (高级)
直接读写从站 ESC (EtherCAT Slave Controller) 寄存器, 用于故障诊断、自定义 ESC 操作、底层调试. 协议层走 FPRD/FPWR 数据报, 自动 primary → secondary → APWR 三级回退.
正常使用 SDK 时无需调用, 状态切换/PDO/邮箱等流程 SDK 已自动配置寄存器. 此 API 用于深度诊断和特殊场景 (例如读取错误计数器、强制端口策略、调试 ESI 烧写不生效等).
寄存器定义见 ETG.1000.4 §6 / ETG.1000.6 §5 (公开标准), 例如:
| 寄存器 | 说明 |
|---|---|
| 0x0000 | Type / Revision / Build (设备类型) |
| 0x0030 | AL Control (主站发起状态请求) |
| 0x0130 | AL Status (从站当前状态) |
| 0x0134 | AL Status Code (错误码) |
| 0x0300-0x030F | 端口 0-3 错误计数器 |
| 0x0400-0x043F | 看门狗配置/计数 |
ReadRegister()
bool ReadRegister(uint16_t addr, uint8_t* data, uint32_t len) const;
读取从站 ESC 寄存器 (FPRD).
参数:
addr(uint16_t) — 寄存器地址 (例如0x0130= AL Status)data(uint8_t*) — 接收缓冲区len(uint32_t) — 读取字节数 (1/2/4 等)
返回值:
bool— 成功返回true, 失败 (从站离线/超时) 返回false
WriteRegister()
bool WriteRegister(uint16_t addr, const uint8_t* data, uint32_t len) const;
写入从站 ESC 寄存器 (FPWR).
参数:
addr(uint16_t) — 寄存器地址data(const uint8_t*) — 写入数据len(uint32_t) — 字节数
返回值:
bool— 成功返回true
示例:
auto& slave = master.GetSlave(1);
// 读取 AL Status (0x0130, 2 字节)
uint8_t alStatus[2] = {0};
if (slave.ReadRegister(0x0130, alStatus, 2)) {
uint16_t state = alStatus[0] | (alStatus[1] << 8);
printf("AL Status = 0x%04X (state=%u, err=%s)\n",
state, state & 0x0F, (state & 0x10) ? "yes" : "no");
}
// 读取 AL Status Code (0x0134, 错误码)
uint8_t alCode[2] = {0};
slave.ReadRegister(0x0134, alCode, 2);
uint16_t code = alCode[0] | (alCode[1] << 8);
printf("AL Status Code = 0x%04X\n", code);
// 写 AL Control = 0x04 (请求 SafeOp)
uint8_t alCtrl[2] = { 0x04, 0x00 };
slave.WriteRegister(0x0030, alCtrl, 2);
EEPROM (SII) 访问
读写从站 SII EEPROM (Slave Information Interface, ETG.1000.6 §6). EEPROM 存储 VendorID / ProductCode / RevisionNo / SerialNo / SyncManager / FMMU / PDO 映射 / Strings 等设备身份与配置信息. 通常 SDK 在 config_init 阶段自动读取, 应用一般无需直接访问.
EEPROM 写入慎用 — 写错可能导致从站身份信息错乱, 严重时永久 brick 从站, 需厂家工具恢复. 仅在以下场景使用:
- 烧写 alias 地址 (Hot-Connect 别名)
- 修复出厂数据被误覆盖
- 厂商授权的固件/参数烧录
写入前必须先调用 Acquire() 接管 EEPROM, 写入完成后调用 Release() 归还给 PDI.
EEPROM 写需要从站处于 Init / PreOp 状态, OP 状态下写入会被拒绝.
EEPROM 大小通常 1 KB - 16 KB (按 word 寻址, 1 word = 2 byte). 起始 8 word 为厂商基本信息, 之后是 Category 链表 (Strings / General / FMMU / SyncM / TxPdo / RxPdo / DC / End=0xFFFF).
ReadEeprom(uint16_t byte_offset, uint16_t byte_length)
std::vector<uint8_t> ReadEeprom(uint16_t byte_offset, uint16_t byte_length) const;
读取从站 SII EEPROM 字节区域 (按 word 循环)。SDK 自动处理 BUSY 轮询、字对齐, 推荐应用层优先使用此高层 API。
参数:
byte_offset(uint16_t) — 起始字节偏移 (建议偶数对齐)byte_length(uint16_t) — 读取字节数 (建议偶数)
返回值:
std::vector<uint8_t>—byte_length字节数据; 失败或参数非法返回空 vector
WriteEeprom(uint16_t byte_offset, const std::vector<uint8_t>& data)
bool WriteEeprom(uint16_t byte_offset, const std::vector<uint8_t>& data) const;
写入从站 SII EEPROM 字节区域 (按 word 循环, 内部走 SIIWriteWord)。byte_offset 与 data.size() 都必须是偶数。
参数:
byte_offset(uint16_t) — 起始字节偏移 (必须偶数)data(std::vector<uint8_t>) — 写入字节 (长度必须偶数)
返回值:
bool— 全部 word 成功写入返回true
示例:
auto& slave = master.GetSlave(1);
// 读 vendor_id (EEPROM 字节偏移 0x10, 长度 4)
auto data = slave.ReadEeprom(0x10, 4);
if (data.size() == 4) {
uint32_t vendor = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
printf("VendorID = 0x%08X\n", vendor);
}
// 读取头部 16 字节 (含 PDIControl/StationAlias 等)
auto header = slave.ReadEeprom(0, 16);
// 写 alias address (EEPROM 字节偏移 0x08)
// 必须从站处于 Init/PreOp 状态!
if (slave.State() == EcState::Init) {
slave.WriteEeprom(0x08, std::vector<uint8_t>{0x01, 0x00}); // alias = 1
}
- 首选:
ReadEeprom / WriteEeprom(按字节, 自动字对齐) - 次选:
slave.VendorId() / ProductId() / SerialNumber() / EsiName()等已封装属性 - 高级用法 (按需): 下方
slave.GetSii().*子对象 API 提供 word 级、Category 级、控制权管理等更精细的控制, 用于枚举 Category / 读 PDO 映射原始字节 / 烧写 alias 等场景。直接用 ESC 寄存器 (0x0500-0x050F) 读写需要应用层自己处理 BUSY 轮询和时序, 不推荐。
高级用法: Sii 低层 API
通过 slave.GetSii() 获取 Sii& 引用(延迟初始化, 对齐 C# slave.Sii)。Sii 类提供 word 级、Category 级、控制权管理等更精细的 EEPROM 访问能力(ETG.1000.6 §6)。普通应用建议优先使用上方 ReadEeprom / WriteEeprom。
| 类别 | 方法 | 返回类型 | 读写 | 说明 |
|---|---|---|---|---|
| 单 word | ReadWord(uint16_t wordAddr, uint16_t& value) | bool | 读 | 读取单个 EEPROM word(wordAddr 单位 = word), 失败返回 false |
| 单 word | WriteWord(uint16_t wordAddr, uint16_t value) | bool | 写 | 写入单个 EEPROM word(带 PDI 切换保护), 失败返回 false |
| 控制权 | Acquire() | bool | 写 | 强制主站接管 EEPROM(写前需要) |
| 控制权 | Release() | bool | 写 | 归还 EEPROM 控制权给 PDI |
| 控制权 | ReadControlReg() | std::optional<uint8_t> | 读 | 读取 SII 控制寄存器原始字节, 失败返回 std::nullopt |
| 控制权 | IsHeldByPdi() | bool | 读 | EEPROM 控制权是否归 PDI(true = PDI 掌控, false = 主站掌控) |
| Category | FindCategory(SiiCategory cat) | std::optional<std::pair<uint32_t, uint32_t>> | 读 | 查找 Category 的(字节偏移, 字节大小), 不存在返回 std::nullopt |
| Category | ReadCategory(SiiCategory cat, int maxBytes = 4096) | std::optional<std::vector<uint8_t>> | 读 | 读取整个 Category 数据字节, 不存在返回 std::nullopt |
| Category | EnumerateCategories(int max = 64) | std::vector<SiiCategory> | 读 | 枚举从站所有 Category 类型, 失败返回空 vector |
| 结构化 | GetStrings(int maxBufBytes = 4096) | std::vector<std::string> | 读 | 读取 Strings Category 并解析为字符串列表(NUL 分隔) |
| 结构化 | GetStringByIndex(uint8_t stringIndex, int maxBytes = 256) | std::string | 读 | 按索引(1-based)取单个字符串(GroupName / DeviceName / ImageName 等) |
| 结构化 | GetGeneralInfo() | std::optional<SiiGeneralInfo> | 读 | 读取并解析 General Category(type = 30)为结构化信息 |
class Sii {
public:
// 单 word 读写
bool ReadWord(uint16_t wordAddr, uint16_t& value) const;
bool WriteWord(uint16_t wordAddr, uint16_t value) const;
// 控制权管理
std::optional<uint8_t> ReadControlReg() const;
bool IsHeldByPdi() const;
bool Acquire() const;
bool Release() const;
// Category 遍历
std::optional<std::pair<uint32_t, uint32_t>> FindCategory(SiiCategory cat) const;
std::optional<std::vector<uint8_t>> ReadCategory(SiiCategory cat, int maxBytes = 4096) const;
std::vector<SiiCategory> EnumerateCategories(int max = 64) const;
// 结构化读取
std::vector<std::string> GetStrings(int maxBufBytes = 4096) const;
std::string GetStringByIndex(uint8_t stringIndex, int maxBytes = 256) const;
std::optional<SiiGeneralInfo> GetGeneralInfo() const;
};
SiiCategory 枚举(ETG.1000.6 Table 17)
enum class SiiCategory : uint16_t {
Strings = 10,
DataTypes = 20,
General = 30,
FMMU = 40,
SyncM = 41,
TxPdo = 50,
RxPdo = 51,
DC = 60,
End = 0xFFFF,
};
SiiGeneralInfo 结构(ETG.1000.6 Table 18, General Category type=30)
各 *Idx 字段是 Strings Category 中的条目索引(1-based), 配合 GetStringByIndex() 取实际字符串。
struct SiiGeneralInfo {
uint8_t GroupIdx; // GroupType 条目索引 (1-based)
uint8_t ImageIdx; // ImageName 条目索引
uint8_t OrderIdx; // 订货号条目索引
uint8_t NameIdx; // DeviceName 条目索引
uint8_t CoeDetails; // CoE 详情位图
uint8_t FoeDetails; // FoE 支持 (bit0)
uint8_t EoeDetails; // EoE 支持 (bit0)
uint8_t SoeDetails; // SoE 支持 (bit0)
uint8_t Flags; // 标志位
int16_t EbusCurrentMa; // E-bus 电流 (mA)
uint16_t PhysicalPort; // 物理端口编码 (4 nibble, 每 nibble 1 port)
// CoE 详情位解析便捷方法
bool SupportsSdo() const;
bool SupportsSdoInfo() const;
bool SupportsPdoAssign() const;
bool SupportsPdoConfig() const;
bool UploadAtStartup() const;
bool SupportsCompleteAccess() const;
bool SupportsFoE() const;
bool SupportsEoE() const;
bool SupportsSoE() const;
};
Sii 低层用法示例:
auto& slave = master.GetSlave(1);
auto& sii = slave.GetSii();
// 枚举 Category
for (auto cat : sii.EnumerateCategories())
printf(" Category: %u\n", static_cast<unsigned>(cat));
// 读 General Category 结构化信息
if (auto info = sii.GetGeneralInfo()) {
printf("E-bus 电流: %d mA, CoE Complete Access: %s\n",
info->EbusCurrentMa,
info->SupportsCompleteAccess() ? "是" : "否");
// 用 NameIdx 取设备名字符串
std::string name = sii.GetStringByIndex(info->NameIdx);
printf("设备名: %s\n", name.c_str());
}
// 读 PDO 映射 Category 字节数组 (TxPdo)
if (auto txPdo = sii.ReadCategory(SiiCategory::TxPdo))
printf("TxPdo Category: %zu 字节\n", txPdo->size());
// word 级写入 (Acquire/Release 手动管理 EEPROM 接管)
// 必须从站处于 Init/PreOp 状态!
if (slave.State() == EcState::Init && sii.Acquire()) {
sii.WriteWord(0x04, 0x0001); // alias = 1
sii.Release();
}
DL Port 端口控制
直接读写 ESC 的 DL Port Control 寄存器 (0x0101),用于端口故障注入测试和冗余 / 环拓扑的手动诊断。
正常运行时无需调用。大部分用户应该通过订阅 SlavePortLinkChanged 事件和读取 端口错误计数器 来诊断端口状态。仅在需要主动模拟端口故障(测试冗余切换)或排查特定端口问题时使用。
WriteDLPort() / ReadDLPort() 在 SDK 头文件中由 #ifdef DARRA_SDK_DEBUG_EXPORTS 包裹,
仅在定义了该宏的调试构建中可用, Release / 生产构建不暴露这两个方法。
端口故障注入仅用于内部测试场景。
ESC 有 4 个物理端口 P0 / P1 / P2 / P3,DL Port Control 寄存器的位定义如下:
| DLPORT 值 | 行为 |
|---|---|
| 0x00 | Auto — 所有端口由 ESC 自动管理(默认) |
| 0x03 | 关闭 P0 |
| 0x0C | 关闭 P1 |
| 0x30 | 关闭 P2 |
| 0xC0 | 关闭 P3 |
SDK 自动采用 primary → secondary → APWR 三级回退写入路径,即使 P0 关闭后仍能通过副网口 / 广播恢复。
WriteDLPort(value)
bool WriteDLPort(uint8_t value) const;
写入从站 DL Port 控制寄存器(0x0101)。
参数:
value(uint8_t) — DLPORT 值(见上表)
返回值:
bool— 成功返回true
ReadDLPort()
uint8_t ReadDLPort() const;
读取从站 DL Port 控制寄存器的当前值。
返回值:
uint8_t— 当前 DLPORT 值(读取失败时返回0)
示例:
auto& slave = master.GetSlave(1);
// 模拟 P1 端口故障 (测试冗余切换)
bool ok = slave.WriteDLPort(0x0C);
printf("关闭 P1: %s\n", ok ? "成功" : "失败");
// 读回确认
uint8_t dlport = slave.ReadDLPort();
printf("当前 DLPORT = 0x%02X\n", dlport);
// 故障恢复后还原
slave.WriteDLPort(0x00); // 恢复 Auto
关闭一个端口后,订阅 master.Events().OnSlavePortLinkChanged(cb) 并查 master.GetDiagnostics().GetBreakPoints() 验证冗余切换是否生效。
SyncManager 控制
auto& slave = master.GetSlave(1);
// 启用输出 SyncManager
slave.EnableOutputSyncManager();
// 禁用输出 SyncManager
slave.DisableOutputSyncManager();
从站身份验证
C++ 可用 auto [vendor, product, rev] = darra::sugar::identity_tuple(slave); structured binding 一次取三件套, 写日志/格式化时不用串联多次属性调用. 详见 structured binding.
从站身份结构 SlaveIdentity 字段: vendor_id (厂商 ID) / product_code (产品码) / revision (修订号) / serial_number (序列号)。
auto& slave = master.GetSlave(1);
// 获取从站身份信息
SlaveIdentity identity;
if (slave.GetIdentity(identity)) {
printf("VID=0x%08X, PID=0x%08X\n", identity.vendor_id, identity.product_code);
}
// 验证从站身份
SlaveIdentity expected{};
expected.vendor_id = 0x00000002;
expected.product_code = 0x03F03052;
bool match = slave.VerifyIdentity(expected, true, false);
DC 配置方法
auto& slave = master.GetSlave(1);
// 配置 DC 同步
slave.ConfigureDC(1000000); // SYNC0 = 1ms
slave.ConfigureDC(1000000, 500000); // SYNC0 + SYNC1
slave.ConfigureDC(125000, 0, 100000); // SYNC0 + 偏移
// 禁用 DC
slave.DisableDC();
// 传播延迟
int delay = slave.PropagationDelay();
// 同步窗口状态
auto syncStatus = slave.GetSyncWindowStatus();
if (syncStatus) {
printf("同步差=%dns, 同步=%s\n",
syncStatus->DiffNs, syncStatus->InSync ? "是" : "否");
}
PDO 零拷贝
auto& slave = master.GetSlave(1);
// 获取零拷贝指针
void* input = slave.InputDataPointer();
void* output = slave.OutputDataPointer();
// 读取输入数据到缓冲区
uint8_t buffer[64];
int bytesRead = slave.ReadInputDirect(buffer, sizeof(buffer));
// 写入输出数据从缓冲区
uint8_t outData[] = {0x0F, 0x00, 0, 0, 0, 0};
int bytesWritten = slave.WriteOutputDirect(outData, sizeof(outData));
零拷贝结构体映射
#pragma pack(push, 1)
struct ServoInput { uint16_t sw; int32_t pos; int32_t vel; };
struct ServoOutput { uint16_t cw; int32_t tp; };
#pragma pack(pop)
auto* out = reinterpret_cast<ServoOutput*>(slave.OutputDataPointer());
auto* in = reinterpret_cast<ServoInput*>(slave.InputDataPointer());
printf("输出大小=%u, 输入大小=%u\n", slave.Obytes(), slave.Ibytes());
out->cw = 0x000F;
out->tp = in->pos + 1000;
字符串表示
auto& slave = master.GetSlave(1);
printf("%s\n", slave.ToString().c_str());
// 输出: "Slave[1] MyDevice (0x00000002:0x03F03052)"
完整示例
#include "ethercat.hpp"
using namespace darra;
int main() {
EtherCATMaster master(dll);
master.SetNetwork("\\Device\\NPF_{...}")
.SetENI("config.deni")
.Build();
auto& slave = master.GetSlave(1);
// 身份信息
printf("VID=0x%08X PID=0x%08X\n", slave.VendorId(), slave.ProductId());
printf("名称: %s\n", slave.Name().c_str());
// 看门狗
slave.SetWatchdog(100);
// 状态切换
slave.SetState(EcState::OP, 5000);
// 拓扑
printf("活动端口: 0x%02X, 父节点: %d\n",
slave.ActivePorts(), slave.ParentStation());
return 0;
}