跳到主要内容

通信类型

新手知识

EtherCAT 有两种通信方式:PDO(实时数据,如电机位置/速度)和 Mailbox(配置参数,如设置工作模式)。日常开发中 90% 的时间你在用 PDO。最推荐的 PDO 访问方式是结构体映射,性能最高、代码最简洁。

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

过程数据通信 (PDO)​

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

工作原理​

PDO 通信示意图

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

PDO 特点​

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

PDO 映射配置​

新手知识

PDO 映射 = 告诉从站"我需要哪些数据"。绝大多数情况下,你不需要手动配置——使用 DARRA 配置工具完成,或者 SDK 根据 ESI 文件自动配置。下面的手动配置仅供学习原理,可以跳过。

推荐方式

使用 DARRA EtherCAT Master Tools 配置 PDO 映射:

  • 可视化 PDO 映射编辑器 — 图形化界面配置
  • 导出配置文件 — 导出 DENI 配置文件用于生产环境
  • 自动校验 — 检查配置有效性,避免手动出错

绝大多数用户使用工具配置即可。只有需要运行时动态修改 PDO 映射时,才需要手动编码。

手动配置 PDO 映射(进阶)​

以下代码展示 PDO 映射的底层原理。通过 SDO 配置 RxPDO 映射(主站→从站):

var slave = master.Slaves[0];

// 1. 禁用 RxPDO 映射(0x1600)
slave.CoE.SDOWrite(0x1600, 0x00, new byte[] { 0 });

// 2. 配置要映射的对象
slave.CoE.SDOWrite(0x1600, 0x01, BitConverter.GetBytes(0x60400010u)); // 控制字(0x6040,16位)
slave.CoE.SDOWrite(0x1600, 0x02, BitConverter.GetBytes(0x607A0020u)); // 目标位置(0x607A,32位)
slave.CoE.SDOWrite(0x1600, 0x03, BitConverter.GetBytes(0x60FF0020u)); // 目标速度(0x60FF,32位)

// 3. 启用 RxPDO 映射(写入映射对象数量)
slave.CoE.SDOWrite(0x1600, 0x00, new byte[] { 3 });

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

PDO 数据访问方式​

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

使用结构体直接映射 PDO 内存,零拷贝直读直写,性能最高。

定义 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; // 设置目标速度

// PDO 数据通过事件回调自动周期发送和接收
// master.Events.ProcessDataCyclicSync += (masterIndex) => { ... };

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 写入方式一:SDOWrite 按索引写入(推荐)​

按索引 + 子索引写入字节数组,数值类型用 BitConverter 转成小端字节。

// 写入不同类型的参数
slave.CoE.SDOWrite(0x6060, 0x00, new byte[] { 8 }); // 设置工作模式(位置模式)
slave.CoE.SDOWrite(0x6040, 0x00, BitConverter.GetBytes((ushort)0x0006)); // 写入控制字
slave.CoE.SDOWrite(0x6081, 0x00, BitConverter.GetBytes(3000)); // 设置速度
slave.CoE.SDOWrite(0x6083, 0x00, BitConverter.GetBytes(1000000u)); // 设置加速度
slave.CoE.SDOWrite(0x1008, 0x00, Encoding.ASCII.GetBytes("MyDevice")); // 写入字符串

SDO 写入方式二:对象入口写入(索引器)​

通过对象入口的 Bytes 属性或 SDOWrite 方法写入,适合按对象组织代码。

// 写入 ushort 值
ushort controlWord = 0x06;
slave.CoE[0x6040][0].Bytes = BitConverter.GetBytes(controlWord);

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

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

SDO 读取示例​

// 类型化读取 GetValue<T>(推荐),转换失败返回类型默认值
byte mode = slave.CoE[0x6061][0].GetValue<byte>(); // 读取当前模式
ushort statusWord = slave.CoE[0x6041][0].GetValue<ushort>(); // 读取状态字
int position = slave.CoE[0x6064][0].GetValue<int>(); // 读取实际位置
string deviceName = slave.CoE[0x1008][0].GetValue<string>(); // 读取设备名称

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 EtherCATADS 协议,用于支持 ADS 的设备与系统通信

PDO vs Mailbox 对比​

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

混合使用场景​

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

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

// 通过 SDO 配置驱动器参数
slave.CoE.SDOWrite(0x6060, 0x00, new byte[] { 8 }); // 位置模式
slave.CoE.SDOWrite(0x6081, 0x00, BitConverter.GetBytes(3000)); // 速度
slave.CoE.SDOWrite(0x6083, 0x00, BitConverter.GetBytes(1000000)); // 加速度
slave.CoE.SDOWrite(0x6084, 0x00, BitConverter.GetBytes(1000000)); // 减速度

// ===== 2. 运行阶段 - 使用 PDO 实时控制 =====
master.SetState(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;

// PDO 数据通过事件回调自动周期发送和接收
// master.Events.ProcessDataCyclicSync += (masterIndex) => { ... };

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

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

Thread.Sleep(0);
}

性能对比与选择​

PDO 访问性能​

  • 结构体映射 — 零拷贝直读直写(最快),适用场景: 高性能实时控制
  • 按名称访问 — 需查表解析(较慢),适用场景: 动态访问、调试
  • 字节数组 — 按偏移直接读写,适用场景: 底层控制

SDO 访问性能​

  • 类型化读取 GetValue<T> — 编码复杂度: 低,类型安全: 高
  • SDOWrite 字节写入 — 编码复杂度: 中,类型安全: 中
  • 按名称 — 编码复杂度: 低,类型安全: 高

最佳实践​

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 使用建议​

// ✅ 推荐:写入用 SDOWrite 字节数组,读取用类型化 GetValue<T>
slave.CoE.SDOWrite(0x6081, 0x00, BitConverter.GetBytes(3000));

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

// ✅ 正确:初始化时读取一次
var param = slave.CoE[0x6081][0].GetValue<int>();
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)
SDK 参考