# 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读写能力了! 🎉