通信类型
EtherCAT 提供两种主要的通信方式:过程数据通信 (PDO) 和 邮箱通信 (Mailbox)。
过程数据通信 (PDO)
PDO (Process Data Object) 是周期性实时数据,每个通信周期都会交换。
工作原理

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 特点
- 非周期性 — 按需通信
- 非实时 — 响应时间不确定
- 数据量大 — 可传输大量数据
- 协议多样 — 支持多种应用层协议
- 可用状态 — 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 协议:
| 协议 | 全称 | 用途 |
|---|---|---|
| SoE | SERCOS over EtherCAT | 用于 SERCOS 驱动器,通过 IDN 参数访问 |
| FoE | File over EtherCAT | 文件传输协议,用于固件升级、日志下载等 |
| EoE | Ethernet over EtherCAT | 以太网隧道协议,允许标准以太网通信 |
| VoE | Vendor over EtherCAT | 厂商自定义协议,用于特殊功能 |
| AoE | ADS over EtherCAT | Beckhoff ADS 协议,用于 TwinCAT 系统通信 |
PDO vs Mailbox 对比
| 对比项 | PDO | Mailbox (SDO) |
|---|---|---|
| 用途 | 实时控制数据 | 配置和诊断 |
| 周期性 | 周期性 | 非周期性 |
| 实时性 | 微秒级 | 毫秒级 |
| 数据量 | 小(几字节到几百字节) | 大(可达数 KB) |
| 可用状态 | SAFEOP/OP | PREOP/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)