PDO读写完整指南.md 11 KB

CANopen PDO读写完整指南

📡 PDO数据通信详解

核心概念

PDO (Process Data Object) - 过程数据对象,用于高速实时数据传输。

类型 方向 用途 COB-ID范围
TPDO 从站→主站 从站发送状态/数据 0x180-0x57F
RPDO 主站→从站 主站发送控制命令 0x200-0x5FF

一、新增的PDO读写方法

✅ 已添加到 CanOpenMaster.cs

1. SendRpdo() - 主站向从站发送RPDO数据

/// <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

2. SendRpdoAndWait() - 发送并等待确认

public bool SendRpdoAndWait(byte nodeId, byte pdoNumber, byte[] data, int timeoutMs = 100)

特点: 发送后等待从站处理,并通过心跳确认从站在线

示例:

bool success = master.SendRpdoAndWait(1, 1, rpdoData, 100);
if (success)
{
    Console.WriteLine("从站已接收并处理");
}

3. ConfigureRpdoMapping() - 配置RPDO映射

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);

4. ConfigureTpdoMapping() - 配置TPDO映射

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);

二、完整使用示例

示例1: 基本RPDO发送

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控制(高速)

// 在主循环中周期性发送
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映射配置

// 配置自定义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周期

示例4: 混合使用SDO和PDO

// 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位,单位:位)

示例解析

// 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模块监控

// 接收从站的数字输入(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: 多轴伺服控制

// 三轴联动控制
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: 数据采集系统

// 采集多个传感器的数据
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周期

八、性能优化建议

1. 减少SDO使用频率

// ❌ 不好 - 频繁SDO读写
while (true)
{
    master.SdoReadAndWait(1, 0x6064, 0x00);  // 读取位置
    Thread.Sleep(1);
}

// ✅ 好 - 使用TPDO
master.EnableSync(1);  // TPDO自动发送

2. 批量处理PDO数据

// 使用队列缓冲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);
    }
});

3. 合理设置SYNC周期

// 根据应用需求选择
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读写能力了! 🎉