# CANopen主站使用指南 ## 概述 本指南介绍如何使用 `CanOpenMaster` 类创建CANopen主站程序,管理多个从站节点,实现NMT控制、SDO读写、PDO通信等功能。 ## 核心组件 ### 1. CanOpenMaster - 主站管理器 主要功能: - 管理多个从站节点 - NMT网络管理(启动/停止/重置节点) - SDO服务数据对象通信 - PDO过程数据对象通信 - SYNC同步对象 - 心跳监测 - 紧急消息处理 ### 2. CanOpenNode - 从站节点信息 记录每个从站节点的状态和统计信息: - 节点ID和名称 - 当前状态(初始化/预操作/运行/停止) - 心跳统计 - SDO通信统计 - TPDO数据统计 - 紧急消息统计 ## 快速开始 ### 基本使用流程 ```csharp using CCDCount.DLL.CanBus; // 1. 创建主站 using (CanOpenMaster master = new CanOpenMaster(4, 0, 0)) { // 2. 注册事件回调 master.OnTpdoReceived += Master_OnTpdoReceived; master.OnHeartbeatReceived += Master_OnHeartbeatReceived; master.OnEmergencyReceived += Master_OnEmergencyReceived; // 3. 初始化主站 if (!master.Initialize(CanBaudRate.BaudRate_500K)) { Console.WriteLine("初始化失败"); return; } // 4. 添加从站节点 master.AddNode(1, "IO_Module_1"); master.AddNode(2, "Servo_Drive"); // 5. 启动所有节点 master.StartAllNodes(); // 6. 启用SYNC(可选) master.EnableSync(10); // 10ms周期 // 7. 主循环 - 处理数据 while (running) { // PDO数据通过事件自动接收 Thread.Sleep(10); } // 8. 清理资源 master.DisableSync(); master.StopAllNodes(); } ``` ## 详细功能说明 ### 1. 节点管理 #### 添加节点 ```csharp // 添加单个节点 master.AddNode(1, "IO_Module_1"); // 批量添加节点 byte[] nodeIds = { 1, 2, 3, 4, 5 }; master.AddNodes(nodeIds); ``` #### 获取节点信息 ```csharp var node = master.GetNode(1); if (node != null) { Console.WriteLine($"节点状态: {node.GetStateString()}"); Console.WriteLine($"是否在线: {node.IsOnline(1000)}"); Console.WriteLine($"心跳计数: {node.HeartbeatCount}"); } ``` ### 2. NMT网络管理 #### 控制节点状态 ```csharp // 启动节点 master.NmtStartNode(1); // 停止节点 master.NmtStopNode(1); // 进入预操作状态 master.NmtEnterPreOperational(1); // 重置节点 master.NmtResetNode(1); // 重置通信 master.NmtResetCommunication(1); // 批量控制 master.StartAllNodes(); master.StopAllNodes(); ``` ### 3. SDO通信 #### 读取数据 ```csharp // 快速读取(不等待响应) master.SdoReadQuick(1, 0x1000, 0x00); // 读取并等待响应 byte[] response = master.SdoReadAndWait(1, 0x1000, 0x00, 1000); if (response != null && response.Length >= 5) { uint value = BitConverter.ToUInt32(response, 4); Console.WriteLine($"读取值: 0x{value:X8}"); } ``` #### 写入数据 ```csharp // 快速写入(不等待确认) master.SdoWriteQuick(1, 0x6200, 0x01, 0x00000001); // 写入并等待确认 bool success = master.SdoWriteAndWait(1, 0x6200, 0x01, 0x00000001, 1000); if (success) { Console.WriteLine("写入成功"); } ``` #### 读取设备信息 ```csharp // 读取设备类型 uint deviceType = master.ReadDeviceType(1); // 读取厂商ID uint vendorId = master.ReadVendorId(1); // 读取产品代码 uint productCode = master.ReadProductCode(1); // 读取序列号 uint serialNumber = master.ReadSerialNumber(1); ``` ### 4. PDO通信 #### SYNC同步 ```csharp // 发送单次SYNC master.SendSync(); // 发送带计数器的SYNC master.SendSync(1); // 启用周期性SYNC master.EnableSync(10); // 10ms周期 // 禁用SYNC master.DisableSync(); ``` #### 配置TPDO ```csharp // 配置TPDO传输类型 // transmissionType: // 0 = 禁止 // 1 = 同步(周期) // 254 = 远程请求 // 255 = 事件驱动 master.ConfigureTpdoTransmissionType(1, 1, 0xFF); // 触发节点发送TPDO master.TriggerTpdo(1, 1); ``` #### 接收TPDO数据 TPDO数据通过事件自动接收: ```csharp private static void Master_OnTpdoReceived(byte nodeId, byte[] data) { string dataStr = BitConverter.ToString(data).Replace("-", " "); Console.WriteLine($"节点{nodeId} TPDO数据: [{dataStr}]"); // 解析数据 if (data.Length >= 2) { ushort value = BitConverter.ToUInt16(data, 0); Console.WriteLine($"解析值: {value}"); } } ``` ### 5. 心跳监测 心跳通过事件自动接收: ```csharp private static void Master_OnHeartbeatReceived(byte nodeId, byte status) { string statusStr = GetNodeStatusString(status); Console.WriteLine($"节点{nodeId}: {statusStr}"); // 检查节点是否在线 var node = master.GetNode(nodeId); if (node != null && node.IsOnline(1000)) { Console.WriteLine("节点在线"); } } ``` ### 6. 紧急消息处理 紧急消息通过事件自动接收: ```csharp private static void Master_OnEmergencyReceived(byte nodeId, ushort errorCode) { Console.WriteLine($"节点{nodeId} 紧急错误: 0x{errorCode:X4}"); // 根据错误代码采取相应措施 switch (errorCode) { case 0x0000: Console.WriteLine("错误恢复"); break; case 0x1000: Console.WriteLine("通用错误"); break; case 0x2000: Console.WriteLine("电流错误"); break; // ... 更多错误代码 } } ``` ## 完整示例 参考 `Program.cs` 中的 `RunCanOpenMaster()` 方法,包含: - 主站初始化和配置 - 节点管理和扫描 - NMT控制 - SDO读写 - PDO数据处理 - SYNC同步 - 状态监控 ## 注意事项 ### 1. 线程安全 - `CanOpenMaster` 内部使用独立线程接收CAN帧 - 事件回调在接收线程中执行,注意线程安全 - 如需更新UI,请使用Invoke/BeginInvoke ### 2. 超时处理 - SDO读写都有超时机制(默认1000ms) - 可以通过参数调整超时时间 - 超时后返回null或false ### 3. 总线负载 - 避免频繁发送SDO请求 - PDO适合高速数据传输 - SYNC周期不宜过小(建议>=1ms) - 多个节点间添加适当延时 ### 4. 资源释放 - 使用 `using` 语句确保资源正确释放 - 或手动调用 `Dispose()` 方法 - 关闭前应先停止SYNC和节点 ### 5. 错误处理 - 检查所有方法的返回值 - 捕获可能的异常 - 记录日志便于调试 ## API参考 ### CanOpenMaster 主要方法 | 方法 | 说明 | |------|------| | `Initialize(baudRate)` | 初始化主站 | | `AddNode(nodeId, nodeName)` | 添加从站节点 | | `RemoveNode(nodeId)` | 移除从站节点 | | `GetNode(nodeId)` | 获取节点对象 | | `NmtStartNode(nodeId)` | 启动节点 | | `NmtStopNode(nodeId)` | 停止节点 | | `SdoReadAndWait(nodeId, index, subIndex, timeout)` | SDO读取 | | `SdoWriteAndWait(nodeId, index, subIndex, data, timeout)` | SDO写入 | | `SendSync(counter)` | 发送SYNC | | `EnableSync(intervalMs)` | 启用周期性SYNC | | `DisableSync()` | 禁用SYNC | | `Close()` | 关闭主站 | ### CanOpenNode 主要属性 | 属性 | 说明 | |------|------| | `NodeId` | 节点ID | | `NodeName` | 节点名称 | | `CurrentState` | 当前状态 | | `LastHeartbeatTime` | 最后心跳时间 | | `HeartbeatCount` | 心跳计数 | | `IsOnline(timeoutMs)` | 是否在线 | | `GetStateString()` | 获取状态字符串 | ## 常见问题 ### Q1: 如何判断节点是否在线? ```csharp var node = master.GetNode(1); bool online = node.IsOnline(1000); // 1秒内有心跳则认为在线 ``` ### Q2: 如何处理大量PDO数据? 使用事件异步处理,避免阻塞主线程: ```csharp master.OnTpdoReceived += (nodeId, data) => { Task.Run(() => ProcessTpdoData(nodeId, data)); }; ``` ### Q3: SDO读写失败怎么办? - 检查节点是否处于预操作或运行状态 - 检查索引和子索引是否正确 - 增加超时时间 - 查看日志输出 ### Q4: 如何配置PDO映射? PDO映射需要在从站的EDS文件中配置,或通过SDO修改从站的对象字典。 ## 技术支持 如有问题,请参考: - CANopen协议规范 CiA DS 301 - 创芯科技CAN卡开发文档 - 项目中的其他示例代码