跳到主要内容

通信类型

EtherCAT 提供两种主要的通信方式:过程数据通信 (PDO)邮箱通信 (Mailbox)

过程数据通信 (PDO)

PDO (Process Data Object)周期性实时数据,每个通信周期都会交换。

工作原理

PDO 通信示意图

RxPDO: 主站→从站 (控制命令: 目标位置、速度等) TxPDO: 从站→主站 (反馈数据: 实际位置、状态等)

PDO 特点

  • 实时性 — 微秒级延迟
  • 周期性 — 固定周期交换
  • 确定性 — 通信时间可预测
  • 高效 — 单帧可包含多个从站数据
  • 数据量 — 小(几字节到几百字节)

PDO 映射配置

在访问 PDO 数据之前,需要先配置 PDO 映射,指定哪些对象字典条目被映射到过程数据中。

手动配置 PDO 映射

通过 SDO 配置 RxPDO 映射(主站→从站):

var slave = master.Slaves[0];

// 1. 禁用 RxPDO 映射(0x1600)
slave.CoE.SDOWrite<byte>(0x1600, 0x00, 0);

// 2. 配置要映射的对象
slave.CoE.SDOWrite<uint>(0x1600, 0x01, 0x60400010); // 控制字(0x6040,16位)
slave.CoE.SDOWrite<uint>(0x1600, 0x02, 0x607A0020); // 目标位置(0x607A,32位)
slave.CoE.SDOWrite<uint>(0x1600, 0x03, 0x60FF0020); // 目标速度(0x60FF,32位)

// 3. 启用 RxPDO 映射(写入映射对象数量)
slave.CoE.SDOWrite<byte>(0x1600, 0x00, 3);

// 4. 将 PDO 映射分配到同步管理器 SM2
slave.CoE.SDOWrite<ushort>(0x1C12, 0x00, 0); // 禁用 SM2 PDO 分配
slave.CoE.SDOWrite<ushort>(0x1C12, 0x01, 0x1600); // 分配 RxPDO 0x1600 到 SM2
slave.CoE.SDOWrite<ushort>(0x1C12, 0x00, 1); // 启用(1个PDO)
工具推荐

建议使用 DARRA EtherCAT Master Tools 进行配置:

  • 一键配置同步管理器 - 自动识别设备并配置 SM
  • 一键导出配置文件 - 导出 XML 配置文件用于生产环境部署
  • 可视化 PDO 映射编辑器 - 图形化界面一键配置 PDO
  • 自动校验 - 检查配置有效性,避免配置错误

手动配置适合学习和调试,生产环境强烈建议使用工具配置以提高效率和可靠性。

PDO 数据访问方式

方式一:结构体映射(推荐)

使用结构体直接映射 PDO 内存,性能最高(5-10纳秒)。

定义 PDO 结构体

using System.Runtime.InteropServices;

// 输出 PDO 结构体(主站 → 从站)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ServoDriveOutputPdo
{
public ushort ControlWord; // 0x6040 控制字
public int TargetPosition; // 0x607A 目标位置
public int TargetVelocity; // 0x60FF 目标速度
}

// 输入 PDO 结构体(从站 → 主站)
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct ServoDriveInputPdo
{
public ushort StatusWord; // 0x6041 状态字
public int ActualPosition; // 0x6064 实际位置
public int ActualVelocity; // 0x606C 实际速度
}

使用结构体映射

// 获取从站
var slave = master.Slaves[0];

// 在循环外获取结构体映射引用(零拷贝,指向 IOmap 内存)
ref var inputs = ref slave.PDO.InputsMapping<ServoDriveInputPdo>();
ref var outputs = ref slave.PDO.OutputsMapping<ServoDriveOutputPdo>();

// 周期性控制循环
while (running)
{
// 读取输入 PDO
var currentPosition = inputs.ActualPosition;
var statusWord = inputs.StatusWord;

// 写入输出 PDO
outputs.ControlWord = 0x000F; // 使能
outputs.TargetPosition = 100000; // 设置目标位置
outputs.TargetVelocity = 3000; // 设置目标速度

// 发送和接收数据
master.ProcessData();

Thread.Sleep(0);
}

方式二:字节数组访问

直接访问原始字节数据。

// 读取输入 PDO 字节数组
byte[] inputs = slave.PDO.Inputs;
int position = BitConverter.ToInt32(inputs, 2); // 偏移 2 字节

// 写入输出 PDO 字节数组
byte[] outputs = new byte[slave.PDO.Outputs.Length];
Buffer.BlockCopy(BitConverter.GetBytes((ushort)0x0F), 0, outputs, 0, 2); // 控制字
Buffer.BlockCopy(BitConverter.GetBytes(10000), 0, outputs, 2, 4); // 目标位置
slave.PDO.Outputs = outputs;

邮箱通信 (Mailbox)

Mailbox非周期性通信,用于配置和诊断。

Mailbox 通信示意图

Mailbox 特点

  • 非周期性 — 按需通信
  • 非实时 — 响应时间不确定
  • 数据量大 — 可传输大量数据
  • 协议多样 — 支持多种应用层协议
  • 可用状态 — PREOP/SAFEOP/OP

SDO 读写(CoE 协议)

SDO 写入方式一:泛型写入(推荐)

自动类型转换,代码简洁。

// 写入不同类型的参数
slave.CoE.SDOWrite<byte>(0x6060, 0x00, 8); // 设置工作模式(位置模式)
slave.CoE.SDOWrite<ushort>(0x6040, 0x00, 0x0006); // 写入控制字
slave.CoE.SDOWrite<int>(0x6081, 0x00, 3000); // 设置速度
slave.CoE.SDOWrite<uint>(0x6083, 0x00, 1000000); // 设置加速度
slave.CoE.SDOWrite<string>(0x1008, 0x00, "MyDevice"); // 写入字符串

SDO 写入方式二:自由索引写入

手动转换字节数组,完全控制。

// 写入 ushort 值
ushort controlWord = 0x06;
slave.CoE.SDOWrite(0x6040, 0, BitConverter.GetBytes(controlWord));

// 写入 int 值
int velocity = 3000;
slave.CoE.SDOWrite(0x6081, 0, BitConverter.GetBytes(velocity));

// 写入字节数组
byte[] customData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
slave.CoE.SDOWrite(0x2000, 0, customData);

SDO 读取示例

// 泛型读取(推荐)
byte mode = slave.CoE.SDORead<byte>(0x6061, 0x00); // 读取当前模式
ushort statusWord = slave.CoE.SDORead<ushort>(0x6041, 0x00); // 读取状态字
int position = slave.CoE.SDORead<int>(0x6064, 0x00); // 读取实际位置
string deviceName = slave.CoE.SDORead<string>(0x1008, 0x00); // 读取设备名称

Console.WriteLine($"工作模式: {mode}");
Console.WriteLine($"状态字: 0x{statusWord:X4}");
Console.WriteLine($"实际位置: {position}");
Console.WriteLine($"设备名称: {deviceName}");

Complete Access 批量读写

一次读写整个对象的所有子索引。

// 批量读取 RxPDO 映射对象 (0x1600)
byte[] pdoMapping = slave.CoE.SDORead(0x1600, 0, completeAccess: true);

// 解析数据
byte subindexCount = pdoMapping[0];
Console.WriteLine($"PDO 映射项数量: {subindexCount}");

其他 Mailbox 协议

除了 CoE(CANopen over EtherCAT)外,EtherCAT 还支持以下 Mailbox 协议:

协议全称用途
SoESERCOS over EtherCAT用于 SERCOS 驱动器,通过 IDN 参数访问
FoEFile over EtherCAT文件传输协议,用于固件升级、日志下载等
EoEEthernet over EtherCAT以太网隧道协议,允许标准以太网通信
VoEVendor over EtherCAT厂商自定义协议,用于特殊功能
AoEADS over EtherCATBeckhoff ADS 协议,用于 TwinCAT 系统通信

PDO vs Mailbox 对比

对比项PDOMailbox (SDO)
用途实时控制数据配置和诊断
周期性周期性非周期性
实时性微秒级毫秒级
数据量小(几字节到几百字节)大(可达数 KB)
可用状态SAFEOP/OPPREOP/SAFEOP/OP
典型应用位置控制、I/O 读写参数设置、固件升级
访问方式结构体映射、按名称索引访问、泛型读写

混合使用场景

实际应用中,通常同时使用 PDO 和 Mailbox:

// ===== 1. 初始化阶段 - 使用 Mailbox 配置 =====
master.State = EcState.PreOp;

// 通过 SDO 配置驱动器参数
slave.CoE.SDOWrite<byte>(0x6060, 0x00, 8); // 位置模式
slave.CoE.SDOWrite<uint>(0x6081, 0x00, 3000); // 速度
slave.CoE.SDOWrite<uint>(0x6083, 0x00, 1000000); // 加速度
slave.CoE.SDOWrite<uint>(0x6084, 0x00, 1000000); // 减速度

// ===== 2. 运行阶段 - 使用 PDO 实时控制 =====
master.State = EcState.OP;

// 定义 PDO 结构体
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct DriveOutput
{
public ushort ControlWord;
public int TargetPosition;
}

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct DriveInput
{
public ushort StatusWord;
public int ActualPosition;
}

// 在循环外获取结构体映射引用(零拷贝,指向 IOmap 内存)
ref var outputs = ref slave.PDO.OutputsMapping<DriveOutput>();
ref var inputs = ref slave.PDO.InputsMapping<DriveInput>();

// 周期性控制循环
while (running)
{
// PDO: 实时数据交换(结构体映射,零拷贝高性能)
outputs.ControlWord = 0x000F;
outputs.TargetPosition = targetPosition;

master.ProcessData();

var position = inputs.ActualPosition;
var status = inputs.StatusWord;

// Mailbox: 偶尔读取诊断信息(非实时)
if (needDiagnostics && DateTime.Now - lastDiagTime > TimeSpan.FromSeconds(1))
{
var temp = slave.CoE.SDORead<short>(0x2205, 0x00);
var voltage = slave.CoE.SDORead<ushort>(0x2206, 0x00);
Console.WriteLine($"温度: {temp}°C, 电压: {voltage}V");
lastDiagTime = DateTime.Now;
}

Thread.Sleep(0);
}

性能对比与选择

PDO 访问性能

  • 结构体映射 — 性能: 5-10纳秒,适用场景: 高性能实时控制
  • 按名称访问 — 性能: 微秒级,适用场景: 动态访问、调试
  • 字节数组 — 性能: 几十纳秒,适用场景: 底层控制

SDO 访问性能

  • 泛型写入 — 编码复杂度: 低,类型安全: 高
  • 自由索引 — 编码复杂度: 中,类型安全: 中
  • 按名称 — 编码复杂度: 低,类型安全: 高

最佳实践

PDO 使用建议

// ✅ 推荐:使用结构体映射进行实时控制
ref var pdo = ref slave.PDO.OutputsMapping<MyPdoStruct>();
pdo.Value = 100;

// ❌ 避免:在实时循环中使用字节数组拷贝
var outputs = slave.PDO.Outputs; // 会发生内存拷贝
outputs[0] = 100;
slave.PDO.Outputs = outputs; // 又一次内存拷贝

SDO 使用建议

// ✅ 推荐:使用泛型方法,类型安全
slave.CoE.SDOWrite<int>(0x6081, 0x00, 3000);

// ❌ 避免:在循环中频繁调用 SDO
while (running)
{
var param = slave.CoE.SDORead<int>(0x6081, 0x00); // ❌ 很慢!
// ...
}

// ✅ 正确:初始化时读取一次
var param = slave.CoE.SDORead<int>(0x6081, 0x00);
while (running)
{
// 使用缓存的 param 值
}

结构体定义注意事项

// ✅ 正确:使用 Pack = 1 确保无填充字节
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MyPdo
{
public ushort Value1; // 2 bytes
public int Value2; // 4 bytes
} // 总大小:6 bytes

// ❌ 错误:默认对齐会添加填充字节
[StructLayout(LayoutKind.Sequential)] // 默认 Pack = 8
public struct BadPdo
{
public ushort Value1; // 2 bytes + 2 bytes 填充
public int Value2; // 4 bytes
} // 总大小:8 bytes(不匹配 PDO)