# CANopen PDO读写完整指南 ## 📡 PDO数据通信详解 ### 核心概念 **PDO (Process Data Object)** - 过程数据对象,用于高速实时数据传输。 | 类型 | 方向 | 用途 | COB-ID范围 | |------|------|------|-----------| | **TPDO** | 从站→主站 | 从站发送状态/数据 | 0x180-0x57F | | **RPDO** | 主站→从站 | 主站发送控制命令 | 0x200-0x5FF | --- ## 一、新增的PDO读写方法 ### ✅ 已添加到 CanOpenMaster.cs #### 1. **SendRpdo()** - 主站向从站发送RPDO数据 ```csharp /// /// 主站向从站发送RPDO数据(写入PDO) /// public bool SendRpdo(byte nodeId, byte pdoNumber, byte[] data) ``` **参数:** - `nodeId`: 目标节点ID (1-127) - `pdoNumber`: RPDO编号 (1-4) - `data`: 要发送的数据 (最多8字节) **返回值:** 是否成功 **示例:** ```csharp // 发送数字输出控制 byte[] rpdoData = new byte[2]; rpdoData[0] = 0xFF; // DO1全部ON rpdoData[1] = 0x00; // DO2全部OFF master.SendRpdo(1, 1, rpdoData); // 发送到节点1的RPDO1 ``` --- #### 2. **SendRpdoAndWait()** - 发送并等待确认 ```csharp public bool SendRpdoAndWait(byte nodeId, byte pdoNumber, byte[] data, int timeoutMs = 100) ``` **特点:** 发送后等待从站处理,并通过心跳确认从站在线 **示例:** ```csharp bool success = master.SendRpdoAndWait(1, 1, rpdoData, 100); if (success) { Console.WriteLine("从站已接收并处理"); } ``` --- #### 3. **ConfigureRpdoMapping()** - 配置RPDO映射 ```csharp public bool ConfigureRpdoMapping(byte nodeId, byte pdoNumber, List mappedObjects) ``` **功能:** 通过SDO修改从站的对象字典,配置RPDO映射关系 **示例:** ```csharp var rpdoMapping = new List { new MappedObject { Index = 0x6200, SubIndex = 0x01, BitLength = 8 }, // DO1 new MappedObject { Index = 0x6200, SubIndex = 0x02, BitLength = 8 } // DO2 }; master.ConfigureRpdoMapping(1, 1, rpdoMapping); ``` --- #### 4. **ConfigureTpdoMapping()** - 配置TPDO映射 ```csharp public bool ConfigureTpdoMapping(byte nodeId, byte pdoNumber, List mappedObjects) ``` **功能:** 配置从站发送TPDO时的数据映射 **示例:** ```csharp var tpdoMapping = new List { new MappedObject { Index = 0x6400, SubIndex = 0x01, BitLength = 16 }, // AI1 new MappedObject { Index = 0x6400, SubIndex = 0x02, BitLength = 16 } // AI2 }; master.ConfigureTpdoMapping(1, 1, tpdoMapping); ``` --- ## 二、完整使用示例 ### 示例1: 基本RPDO发送 ```csharp 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); } ``` --- ### 示例2: 周期性RPDO控制(高速) ```csharp // 在主循环中周期性发送 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周期 } ``` --- ### 示例3: PDO映射配置 ```csharp // 配置自定义PDO映射 var node = master.GetNode(1); // RPDO1: 控制字 + 目标位置 var rpdo1Mapping = new List { 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 { 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周期 ``` --- ### 示例4: 混合使用SDO和PDO ```csharp // 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 vs SDO 对比 | 特性 | PDO | SDO | |------|-----|-----| | **速度** | ⚡⚡⚡⚡⚡ 极快(微秒级) | ⚡⚡ 较慢(毫秒级) | | **可靠性** | ⚡⚡⚡ 无确认机制 | ⚡⚡⚡⚡⚡ 有确认 | | **数据大小** | 最大8字节/帧 | 不限(分段传输) | | **适用场景** | 实时控制、高速数据采集 | 参数配置、诊断 | | **总线负载** | 低(周期性) | 高(每次请求-响应) | | **典型应用** | 伺服控制、IO监控 | 设备配置、故障查询 | ### 选择建议 ``` 实时控制 (伺服、电机) → 使用 PDO 参数配置 (增益、限值) → 使用 SDO 状态监控 (位置、速度) → 使用 PDO (TPDO) 故障诊断 (错误代码) → 使用 SDO 高速数据采集 (传感器) → 使用 PDO (TPDO) ``` --- ## 四、COB-ID计算规则 ### TPDO COB-ID (从站→主站) ``` TPDO1: 0x180 + nodeId (例如: 节点5 → 0x185) TPDO2: 0x280 + nodeId (例如: 节点5 → 0x285) TPDO3: 0x380 + nodeId TPDO4: 0x480 + nodeId ``` ### RPDO COB-ID (主站→从站) ``` RPDO1: 0x200 + nodeId (例如: 节点5 → 0x205) RPDO2: 0x300 + nodeId (例如: 节点5 → 0x305) RPDO3: 0x400 + nodeId RPDO4: 0x500 + nodeId ``` --- ## 五、PDO映射格式 ### 映射条目格式: `0xIIIISSLL` - `IIII`: 对象索引 (16位) - `SS`: 子索引 (8位) - `LL`: 数据长度 (8位,单位:位) ### 示例解析 ```csharp // 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 } ``` --- ## 六、默认PDO映射(项目已配置) 根据记忆中的配置,项目已有以下默认映射: ### RPDO映射 (主站→从站) **RPDO1 (0x1600):** - 0x6000:01 (DI1, 8位) - 数字输入1状态 - 0x6000:02 (DI2, 8位) - 数字输入2状态 **RPDO2 (0x1601):** - 0x6200:01 (DO1, 8位) - 数字输出1控制 - 0x6200:02 (DO2, 8位) - 数字输出2控制 ### TPDO映射 (从站→主站) **TPDO1 (0x1A00):** - 0x6400:01 (AI1, 16位) - 模拟输入1值 - 0x6400:02 (AI2, 16位) - 模拟输入2值 **TPDO2 (0x1A01):** - 0x6410:01 (AO1, 16位) - 模拟输出1值 - 0x6410:02 (AO2, 16位) - 模拟输出2值 --- ## 七、实际应用案例 ### 案例1: IO模块监控 ```csharp // 接收从站的数字输入(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 } } }; ``` ### 案例2: 多轴伺服控制 ```csharp // 三轴联动控制 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) } ``` ### 案例3: 数据采集系统 ```csharp // 采集多个传感器的数据 List sensorHistory = new List(); 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周期 ``` --- ## 八、性能优化建议 ### 1. 减少SDO使用频率 ```csharp // ❌ 不好 - 频繁SDO读写 while (true) { master.SdoReadAndWait(1, 0x6064, 0x00); // 读取位置 Thread.Sleep(1); } // ✅ 好 - 使用TPDO master.EnableSync(1); // TPDO自动发送 ``` ### 2. 批量处理PDO数据 ```csharp // 使用队列缓冲PDO数据 ConcurrentQueue pdoQueue = new ConcurrentQueue(); 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); } }); ``` ### 3. 合理设置SYNC周期 ```csharp // 根据应用需求选择 master.EnableSync(1); // 高速伺服控制 (1kHz) master.EnableSync(10); // 一般IO控制 (100Hz) master.EnableSync(100); // 慢速监控 (10Hz) ``` --- ## 九、常见问题 ### Q1: RPDO发送失败? **检查:** 1. 节点是否处于Operational状态 2. RPDO是否已启用(COB-ID最高位为0) 3. 数据长度是否符合映射配置 4. COB-ID是否正确 ### Q2: TPDO没有收到数据? **检查:** 1. 从站是否已启动(NMT Operational) 2. TPDO传输类型配置是否正确 3. SYNC是否正常发送(如果是同步模式) 4. 从站的对象字典数据是否有变化 ### Q3: PDO映射配置不生效? **解决:** 1. 确保在PreOperational状态下配置 2. 配置完成后重启通信或节点 3. 验证SDO读写是否成功 4. 检查映射格式是否正确 --- ## 十、总结 ### 新增功能清单 ✅ **SendRpdo()** - 主站发送RPDO数据 ✅ **SendRpdoAndWait()** - 发送并等待确认 ✅ **ConfigureRpdoMapping()** - 配置RPDO映射 ✅ **ConfigureTpdoMapping()** - 配置TPDO映射 ✅ **GetRpdoCobId()** - 计算RPDO COB-ID ### 关键优势 - 🚀 **高速通信** - PDO比SDO快10-100倍 - 🔄 **实时控制** - 支持1kHz以上控制频率 - 📊 **灵活映射** - 可自定义PDO映射关系 - 🎯 **简单易用** - API简洁明了 现在你的CANopen主站已经具备完整的PDO读写能力了! 🎉