跳到主要内容

Slave 属性与方法

通过 master.GetSlave(index) 获取 Slave& 引用。

提示

属性

类别属性类型读写说明
基本标识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 读取)
Dtype()uint16_t只读设备类型标识
HasMDP()bool只读是否支持模块化设备配置文件(ETG.5001)
BlockLRW()bool只读LRW 逻辑读写操作阻止标志
地址ConfigAddr()uint16_t只读物理配置地址
AliasAddress()uint16_t只读别名地址
状态State()EcState只读从站当前状态
ErrorCode()EcALState只读AL Status Code 错误码
IsLost()bool只读从站是否丢失(断开连接)
拓扑Topology()uint8_t只读拓扑类型 (0=无链接, 1=端点, 2=中间, 3=分支, 4=交叉)
ParentStation()uint16_t只读父从站的站地址
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 文件获取的设备配置(综合自动配置)
EEPROMEep8ByteAddressing()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 协议详情
FMMUFMMU0Function()uint8_t只读FMMU0 功能类型(bit 0=输出, bit 1=输入)
FMMU1Function()uint8_t只读FMMU1 功能类型
FMMU2Function()uint8_t只读FMMU2 功能类型
FMMU3Function()uint8_t只读FMMU3 功能类型
DCHasDC()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只读备链路是否断开
配置Group() / Group(uint8_t)uint8_t读写从站分组号(0-7,0=默认组,必须在 SAFE_OP 前设置),详见 从站分组
IsOptional() / IsOptional(bool)bool读写可选从站标记,缺席时不影响 WKC 检查(必须在 OP 前设置)
StartupShouldWritePDOAssignment() / ShouldWritePDOAssignment(bool)bool读写是否写入 PDO Assignment 配置
ShouldWritePDOConfiguration() / ShouldWritePDOConfiguration(bool)bool读写是否写入 PDO Configuration 配置
SupportsCompleteAccess() / SupportsCompleteAccess(bool)bool读写是否支持 Complete Access 模式
EcDeviceType 枚举值
enum class EcDeviceType : uint16_t {
Undefined = 0, // 未定义
Static = 1, // 静态设备,无IO映射,如EK1100耦合器
InputNoMailbox = 2, // 输入设备(无邮箱)
OutputNoMailbox = 3, // 输出设备(无邮箱)
InputWithMailbox = 4, // 输入设备(有邮箱)
OutputWithMailbox = 5, // 输出设备(有邮箱)
IONoMailbox = 6, // 输入输出设备(无邮箱)
IOWithMailbox = 7 // 输入输出设备(有邮箱)
};
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

枚举描述

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(ec_watchdog_config_t& config) const;

读取从站看门狗当前配置。

参数:

  • config (ec_watchdog_config_t&) — 输出配置结构体

返回值:

  • bool — 是否成功

GetWatchdogStatus()

bool GetWatchdogStatus(ec_watchdog_status_t& status) const;

读取从站看门狗运行状态。

参数:

  • status (ec_watchdog_status_t&) — 输出状态结构体

返回值:

  • bool — 是否成功

示例:

auto& slave = master.GetSlave(1);

// 设置过程数据看门狗超时
slave.SetWatchdog(100); // 100ms, 0=禁用

// 设置 PDI 看门狗超时
slave.SetPdiWatchdog(200);

// 获取看门狗配置
ec_watchdog_config_t wdConfig;
if (slave.GetWatchdogConfig(wdConfig)) {
printf("看门狗配置: PD=%ums, PDI=%ums\n",
wdConfig.pd_timeout_ms, wdConfig.pdi_timeout_ms);
}

// 获取看门狗状态
ec_watchdog_status_t 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);

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 三级回退.

高级 API

正常使用 SDK 时无需调用, 状态切换/PDO/邮箱等流程 SDK 已自动配置寄存器. 此 API 用于深度诊断特殊场景 (例如读取错误计数器、强制端口策略、调试 ESI 烧写不生效等).

寄存器定义见 ETG.1000.4 §6 / ETG.1000.6 §5 (公开标准), 例如:

寄存器说明
0x0000Type / Revision / Build (设备类型)
0x0030AL Control (主站发起状态请求)
0x0130AL Status (从站当前状态)
0x0134AL 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 循环, 内部走 SIIReadWord ordinal)。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_offsetdata.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
}
高层 API vs 低层 SII API
  • 首选: ReadEeprom / WriteEeprom (按字节, 自动字对齐)
  • 次选: slave.VendorId() / ProductId() / SerialNumber() / EsiName() 等已封装属性
  • 高级用法 (按需): 直接通过 C SDK 的 SIIReadWord / SIIWriteWord / SIIAcquire / SIIRelease / SIIReadCategory / SIIEnumerateCategories / SIIGetGeneralInfo 等 ordinal 函数, 用于枚举 Category / 读 PDO 映射原始字节 / 烧写 alias 等场景。直接用 ESC 寄存器 (0x0500-0x050F) 读写需要应用层自己处理 BUSY 轮询和时序, 不推荐。

DL Port 端口控制

直接读写 ESC 的 DL Port Control 寄存器 (0x0101),用于端口故障注入测试冗余 / 环拓扑的手动诊断

高级 API

正常运行时无需调用。大部分用户应该通过订阅 SlavePortLinkChanged 事件和读取 端口错误计数器 来诊断端口状态。仅在需要主动模拟端口故障(测试冗余切换)或排查特定端口问题时使用。

ESC 有 4 个物理端口 P0 / P1 / P2 / P3DL Port Control 寄存器的位定义如下:

DLPORT 值行为
0x00Auto — 所有端口由 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().SlavePortLinkChangedmaster.GetDiagnostics().BreakPoint() 验证冗余切换是否生效。

SyncManager 控制

auto& slave = master.GetSlave(1);

// 启用输出 SyncManager
slave.EnableOutputSyncManager();

// 禁用输出 SyncManager
slave.DisableOutputSyncManager();

从站身份验证

auto& slave = master.GetSlave(1);

// 获取从站身份信息
ec_slave_identity_t identity;
if (slave.GetIdentity(identity)) {
printf("VID=0x%08X, PID=0x%08X\n", identity.vendor_id, identity.product_code);
}

// 验证从站身份
ec_slave_identity_t 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;
}