PDO (Process Data Object) - 过程数据对象,用于高速实时数据传输。
| 类型 | 方向 | 用途 | COB-ID范围 |
|---|---|---|---|
| TPDO | 从站→主站 | 从站发送状态/数据 | 0x180-0x57F |
| RPDO | 主站→从站 | 主站发送控制命令 | 0x200-0x5FF |
/// <summary>
/// 主站向从站发送RPDO数据(写入PDO)
/// </summary>
public bool SendRpdo(byte nodeId, byte pdoNumber, byte[] data)
参数:
nodeId: 目标节点ID (1-127)pdoNumber: RPDO编号 (1-4)data: 要发送的数据 (最多8字节)返回值: 是否成功
示例:
// 发送数字输出控制
byte[] rpdoData = new byte[2];
rpdoData[0] = 0xFF; // DO1全部ON
rpdoData[1] = 0x00; // DO2全部OFF
master.SendRpdo(1, 1, rpdoData); // 发送到节点1的RPDO1
public bool SendRpdoAndWait(byte nodeId, byte pdoNumber, byte[] data, int timeoutMs = 100)
特点: 发送后等待从站处理,并通过心跳确认从站在线
示例:
bool success = master.SendRpdoAndWait(1, 1, rpdoData, 100);
if (success)
{
Console.WriteLine("从站已接收并处理");
}
public bool ConfigureRpdoMapping(byte nodeId, byte pdoNumber, List<MappedObject> mappedObjects)
功能: 通过SDO修改从站的对象字典,配置RPDO映射关系
示例:
var rpdoMapping = new List<MappedObject>
{
new MappedObject { Index = 0x6200, SubIndex = 0x01, BitLength = 8 }, // DO1
new MappedObject { Index = 0x6200, SubIndex = 0x02, BitLength = 8 } // DO2
};
master.ConfigureRpdoMapping(1, 1, rpdoMapping);
public bool ConfigureTpdoMapping(byte nodeId, byte pdoNumber, List<MappedObject> mappedObjects)
功能: 配置从站发送TPDO时的数据映射
示例:
var tpdoMapping = new List<MappedObject>
{
new MappedObject { Index = 0x6400, SubIndex = 0x01, BitLength = 16 }, // AI1
new MappedObject { Index = 0x6400, SubIndex = 0x02, BitLength = 16 } // AI2
};
master.ConfigureTpdoMapping(1, 1, tpdoMapping);
using (CanOpenMaster master = new CanOpenMaster(4, 0, 0))
{
master.Initialize(CanBaudRate.BaudRate_500K);
master.AddNode(1, "IO_Module");
// 启动节点
master.NmtStartNode(1);
// 准备控制数据
byte[] controlData = new byte[2];
controlData[0] = 0x01; // DO1 = ON
controlData[1] = 0x00; // DO2 = OFF
// 发送RPDO
master.SendRpdo(1, 1, controlData);
// 更新控制
controlData[0] = 0x00; // DO1 = OFF
controlData[1] = 0xFF; // DO2 = ON
master.SendRpdo(1, 1, controlData);
}
// 在主循环中周期性发送
int loopCount = 0;
while (running)
{
loopCount++;
// 每10ms发送一次RPDO (100Hz控制频率)
if (loopCount % 1 == 0)
{
byte[] servoCommand = new byte[4];
// 位置控制模式
servoCommand[0] = 0x01; // 控制字低字节
servoCommand[1] = 0x00; // 控制字高字节
servoCommand[2] = (byte)(targetPosition & 0xFF); // 目标位置低字节
servoCommand[3] = (byte)((targetPosition >> 8) & 0xFF); // 目标位置高字节
master.SendRpdo(1, 1, servoCommand);
}
Thread.Sleep(10); // 10ms周期
}
// 配置自定义PDO映射
var node = master.GetNode(1);
// RPDO1: 控制字 + 目标位置
var rpdo1Mapping = new List<MappedObject>
{
new MappedObject { Index = 0x6040, SubIndex = 0x00, BitLength = 16 }, // 控制字
new MappedObject { Index = 0x607A, SubIndex = 0x00, BitLength = 32 } // 目标位置
};
master.ConfigureRpdoMapping(1, 1, rpdo1Mapping);
// TPDO1: 状态字 + 实际位置
var tpdo1Mapping = new List<MappedObject>
{
new MappedObject { Index = 0x6041, SubIndex = 0x00, BitLength = 16 }, // 状态字
new MappedObject { Index = 0x6064, SubIndex = 0x00, BitLength = 32 } // 实际位置
};
master.ConfigureTpdoMapping(1, 1, tpdo1Mapping);
// 配置传输类型为SYNC同步
master.ConfigureTpdoTransmissionType(1, 1, 0x01);
// 启用SYNC
master.EnableSync(1); // 1ms周期
// SDO: 配置参数(低速,可靠)
master.SdoWriteAndWait(1, 0x6098, 0x00, 6); // 设置回零方法
// PDO: 实时控制(高速)
byte[] homingCommand = new byte[2];
homingCommand[0] = 0x0F; // 启动回零
homingCommand[1] = 0x00;
master.SendRpdo(1, 1, homingCommand);
// 等待回零完成(通过TPDO监控)
master.OnTpdoReceived += (nodeId, data) =>
{
if (nodeId == 1 && data.Length >= 2)
{
ushort statusWord = BitConverter.ToUInt16(data, 0);
if ((statusWord & 0x1000) != 0) // 回零完成标志
{
Console.WriteLine("回零完成!");
}
}
};
| 特性 | PDO | SDO |
|---|---|---|
| 速度 | ⚡⚡⚡⚡⚡ 极快(微秒级) | ⚡⚡ 较慢(毫秒级) |
| 可靠性 | ⚡⚡⚡ 无确认机制 | ⚡⚡⚡⚡⚡ 有确认 |
| 数据大小 | 最大8字节/帧 | 不限(分段传输) |
| 适用场景 | 实时控制、高速数据采集 | 参数配置、诊断 |
| 总线负载 | 低(周期性) | 高(每次请求-响应) |
| 典型应用 | 伺服控制、IO监控 | 设备配置、故障查询 |
实时控制 (伺服、电机) → 使用 PDO
参数配置 (增益、限值) → 使用 SDO
状态监控 (位置、速度) → 使用 PDO (TPDO)
故障诊断 (错误代码) → 使用 SDO
高速数据采集 (传感器) → 使用 PDO (TPDO)
TPDO1: 0x180 + nodeId (例如: 节点5 → 0x185)
TPDO2: 0x280 + nodeId (例如: 节点5 → 0x285)
TPDO3: 0x380 + nodeId
TPDO4: 0x480 + nodeId
RPDO1: 0x200 + nodeId (例如: 节点5 → 0x205)
RPDO2: 0x300 + nodeId (例如: 节点5 → 0x305)
RPDO3: 0x400 + nodeId
RPDO4: 0x500 + nodeId
0xIIIISSLLIIII: 对象索引 (16位)SS: 子索引 (8位)LL: 数据长度 (8位,单位:位)// 0x60000108
// = 索引0x6000, 子索引0x01, 长度8位
new MappedObject { Index = 0x6000, SubIndex = 0x01, BitLength = 8 }
// 0x64000110
// = 索引0x6400, 子索引0x01, 长度16位
new MappedObject { Index = 0x6400, SubIndex = 0x01, BitLength = 16 }
// 0x60400020
// = 索引0x6040, 子索引0x00, 长度32位
new MappedObject { Index = 0x6040, SubIndex = 0x00, BitLength = 32 }
根据记忆中的配置,项目已有以下默认映射:
RPDO1 (0x1600):
RPDO2 (0x1601):
TPDO1 (0x1A00):
TPDO2 (0x1A01):
// 接收从站的数字输入(TPDO)
master.OnTpdoReceived += (nodeId, data) =>
{
if (nodeId == 1 && data.Length >= 2)
{
byte di1 = data[0];
byte di2 = data[1];
Console.WriteLine($"DI1={di1}, DI2={di2}");
// 根据输入控制输出
if (di1 == 0x01)
{
byte[] rpdoData = new byte[] { 0xFF, 0x00 };
master.SendRpdo(1, 2, rpdoData); // 开启DO1
}
}
};
// 三轴联动控制
while (running)
{
// X轴
byte[] xAxisCmd = CreateServoCommand(xTargetPos, xControlWord);
master.SendRpdo(1, 1, xAxisCmd);
// Y轴
byte[] yAxisCmd = CreateServoCommand(yTargetPos, yControlWord);
master.SendRpdo(2, 1, yAxisCmd);
// Z轴
byte[] zAxisCmd = CreateServoCommand(zTargetPos, zControlWord);
master.SendRpdo(3, 1, zAxisCmd);
Thread.Sleep(1); // 1ms控制周期 (1kHz)
}
// 采集多个传感器的数据
List<SensorData> sensorHistory = new List<SensorData>();
master.OnTpdoReceived += (nodeId, data) =>
{
if (data.Length >= 4)
{
var sensorData = new SensorData
{
Timestamp = DateTime.Now,
NodeId = nodeId,
Value1 = BitConverter.ToUInt16(data, 0),
Value2 = BitConverter.ToUInt16(data, 2)
};
sensorHistory.Add(sensorData);
// 每1000条保存一次
if (sensorHistory.Count >= 1000)
{
SaveToDatabase(sensorHistory);
sensorHistory.Clear();
}
}
};
// 启用高速SYNC触发TPDO
master.EnableSync(1); // 1ms周期
// ❌ 不好 - 频繁SDO读写
while (true)
{
master.SdoReadAndWait(1, 0x6064, 0x00); // 读取位置
Thread.Sleep(1);
}
// ✅ 好 - 使用TPDO
master.EnableSync(1); // TPDO自动发送
// 使用队列缓冲PDO数据
ConcurrentQueue<PdoData> pdoQueue = new ConcurrentQueue<PdoData>();
master.OnTpdoReceived += (nodeId, data) =>
{
pdoQueue.Enqueue(new PdoData { NodeId = nodeId, Data = data });
};
// 后台线程处理
Task.Run(() =>
{
while (running)
{
if (pdoQueue.TryDequeue(out var pdoData))
{
ProcessPdoData(pdoData);
}
Thread.Sleep(1);
}
});
// 根据应用需求选择
master.EnableSync(1); // 高速伺服控制 (1kHz)
master.EnableSync(10); // 一般IO控制 (100Hz)
master.EnableSync(100); // 慢速监控 (10Hz)
检查:
检查:
解决:
✅ SendRpdo() - 主站发送RPDO数据
✅ SendRpdoAndWait() - 发送并等待确认
✅ ConfigureRpdoMapping() - 配置RPDO映射
✅ ConfigureTpdoMapping() - 配置TPDO映射
✅ GetRpdoCobId() - 计算RPDO COB-ID
现在你的CANopen主站已经具备完整的PDO读写能力了! 🎉