# CANopen管理器使用指南 ## 概述 [`CanOpenManager`](d:\work\CCDCount\CCD数粒-20260427-CAN通讯稳定版\TestWork\TestWork.DLL\CanBus\CanOpenManager.cs) 是一个基于创芯科技CAN卡底层库(`controlcan.dll`)实现的CANopen协议管理器。它**直接调用** [`CanLibraryClass`](d:\work\CCDCount\CCD数粒-20260427-CAN通讯稳定版\TestWork\TestWork.DLL\CanBus\CanLibraryClass.cs) 的API,**不依赖** [`CanManagerClass`](d:\work\CCDCount\CCD数粒-20260427-CAN通讯稳定版\TestWork\TestWork.DLL\CanBus\CanManagerClass.cs)。 ## 核心特性 ✅ **独立实现** - 直接使用创芯科技CAN卡驱动 ✅ **完整CANopen支持** - NMT、SDO、PDO、EMCY、Heartbeat ✅ **灵活配置** - 支持多种波特率和设备配置 ✅ **资源管理** - 实现IDisposable,自动释放资源 ✅ **日志记录** - 完整的通信日志 ## 快速开始 ### 1. 基本初始化 ```csharp using CCDCount.DLL.CanBus; // 创建并初始化 using (var canOpen = new CanOpenManager()) { // 默认500kbps if (!canOpen.Initialize()) { Console.WriteLine("初始化失败!"); return; } Console.WriteLine("CANopen就绪!"); } ``` ### 2. 自定义配置 ```csharp // 指定设备类型、索引和波特率 var canOpen = new CanOpenManager( deviceType: 4, // USBCAN2 deviceIndex: 0, // 第1个设备 canIndex: 0 // CAN通道0 ); canOpen.Initialize(CanBaudRate.BaudRate_1M); // 1Mbps ``` ## CANopen功能详解 ### NMT - 网络管理 ```csharp // 启动节点1 canOpen.NmtStartNode(1); // 停止节点1 canOpen.NmtStopNode(1); // 进入预操作状态 canOpen.NmtEnterPreOperational(1); // 重置节点 canOpen.NmtResetNode(1); // 重置通信 canOpen.NmtResetCommunication(1); ``` **NMT命令字说明:** - `0x01` - Start (启动) - `0x02` - Stop (停止) - `0x80` - Enter Pre-Operational (预操作) - `0x81` - Reset Node (重置节点) - `0x82` - Reset Communication (重置通信) ### SDO - 服务数据对象 #### 读取参数 ```csharp // 方式1: 阻塞方式(推荐) byte[] data = canOpen.SdoReadAndWait( nodeId: 1, index: 0x1000, // 设备类型 subIndex: 0x00, timeoutMs: 1000 ); if (data != null) { uint deviceType = BitConverter.ToUInt32(data, 4); Console.WriteLine($"设备类型: 0x{deviceType:X8}"); } // 方式2: 异步方式 canOpen.SdoReadQuick(1, 0x1000, 0x00); // ... 执行其他操作 ... var response = canOpen.ReceiveSdoResponse(1, 1000); ``` #### 写入参数 ```csharp // 写入4字节数据 bool success = canOpen.SdoWriteAndWait( nodeId: 1, index: 0x6040, // 控制字 subIndex: 0x00, data: 0x0006, // 启用电压 timeoutMs: 1000 ); Console.WriteLine($"写入{(success ? "成功" : "失败")}"); ``` **常用对象字典索引:** - `0x1000` - 设备类型 - `0x1018` - 厂商信息 - `0x6040` - 控制字(Control Word) - `0x6041` - 状态字(Status Word) - `0x6060` - 模式选择 - `0x607A` - 目标位置 ### PDO - 过程数据对象 #### SYNC同步 ```csharp // 发送不带计数器的SYNC canOpen.SendSync(); // 发送带计数器的SYNC(0-255循环) for (int i = 0; i < 256; i++) { canOpen.SendSync((byte)i); Thread.Sleep(10); // 10ms周期 } ``` #### TPDO发送 ```csharp // 发送TPDO1数据到节点1 byte[] tpdoData = new byte[] { 0x01, 0x02, 0x03, 0x04 }; canOpen.SendTpdo(nodeId: 1, pdoNumber: 1, data: tpdoData); ``` #### RPDO接收 ```csharp // 接收节点1的TPDO1 byte[] receivedData = canOpen.ReceiveRpdo( nodeId: 1, pdoNumber: 1, timeoutMs: 100 ); if (receivedData != null) { Console.WriteLine($"收到{receivedData.Length}字节数据"); } ``` #### PDO配置 ```csharp // 配置TPDO1为事件驱动(传输类型254) canOpen.ConfigureTpdoTransmissionType( nodeId: 1, pdoNumber: 1, transmissionType: 254 ); // 配置为同步模式(传输类型1) canOpen.ConfigureTpdoTransmissionType(1, 1, 1); ``` **传输类型说明:** - `1` - 同步模式(每个SYNC发送) - `254` - 事件驱动(数据变化时发送) - `255` - 异步模式 ### EMCY - 紧急消息 ```csharp // 监听紧急消息 var frames = canOpen.ReceiveCanFrames(100); foreach (var frame in frames) { // 紧急消息COB-ID范围: 0x081 - 0x0FF if (frame.CobId >= 0x081 && frame.CobId <= 0x0FF) { byte nodeId = (byte)(frame.CobId - 0x080); ushort errorCode = canOpen.ParseEmergencyMessage(frame.Data); Console.WriteLine($"节点{nodeId}紧急错误: 0x{errorCode:X4}"); // 常见错误代码 switch (errorCode) { case 0x0000: Console.WriteLine("错误恢复"); break; case 0x1000: Console.WriteLine("通用错误"); break; case 0x2000: Console.WriteLine("电流错误"); break; case 0x3000: Console.WriteLine("电压错误"); break; case 0x4000: Console.WriteLine("温度错误"); break; } } } ``` ### Heartbeat - 心跳监测 ```csharp // 监听心跳消息 var frames = canOpen.ReceiveCanFrames(100); foreach (var frame in frames) { // 心跳COB-ID范围: 0x701 - 0x77F if (frame.CobId >= 0x701 && frame.CobId <= 0x77F) { byte nodeId = (byte)(frame.CobId - 0x700); byte status = canOpen.ParseHeartbeat(nodeId, frame.Data); // 状态解析 string statusStr = (status & 0x7F) switch { 0x00 => "启动中", 0x04 => "停止", 0x05 => "运行", 0x7F => "预操作", _ => "未知" }; Console.WriteLine($"节点{nodeId}: {statusStr}"); } } ``` ## 高级应用 ### 多节点网络管理 ```csharp using (var canOpen = new CanOpenManager()) { canOpen.Initialize(); byte[] nodeIds = { 1, 2, 3, 4, 5 }; // 批量启动 foreach (byte nodeId in nodeIds) { canOpen.NmtStartNode(nodeId); Thread.Sleep(50); } // 健康检查 foreach (byte nodeId in nodeIds) { var data = canOpen.SdoReadAndWait(nodeId, 0x1000, 0x00, 500); bool isOnline = data != null; Console.WriteLine($"节点{nodeId}: {(isOnline ? "在线" : "离线")}"); } } ``` ### 周期性数据采集 ```csharp using (var canOpen = new CanOpenManager()) { canOpen.Initialize(); // 启动节点 canOpen.NmtStartNode(1); // 周期性读取数据 while (true) { // 发送SYNC触发PDO canOpen.SendSync(); // 接收TPDO1 var data = canOpen.ReceiveRpdo(1, 1, 50); if (data != null && data.Length >= 4) { int position = BitConverter.ToInt32(data, 0); Console.WriteLine($"当前位置: {position}"); } Thread.Sleep(10); // 10ms采样周期 } } ``` ### 错误处理和恢复 ```csharp try { using (var canOpen = new CanOpenManager()) { if (!canOpen.Initialize()) { throw new Exception("CAN设备初始化失败"); } // 尝试通信 var data = canOpen.SdoReadAndWait(1, 0x1000, 0x00, 1000); if (data == null) { // 通信超时,尝试重置 Console.WriteLine("通信超时,尝试重置节点..."); canOpen.NmtResetNode(1); Thread.Sleep(200); canOpen.NmtStartNode(1); } } } catch (Exception ex) { Console.WriteLine($"错误: {ex.Message}"); } ``` ## 波特率配置 | 波特率 | Timing0 | Timing1 | 枚举值 | |--------|---------|---------|--------| | 1 Mbps | 0x00 | 0x14 | `CanBaudRate.BaudRate_1M` | | 500 kbps | 0x00 | 0x1C | `CanBaudRate.BaudRate_500K` | | 250 kbps | 0x01 | 0x1C | `CanBaudRate.BaudRate_250K` | | 125 kbps | 0x03 | 0x1C | `CanBaudRate.BaudRate_125K` | ## COB-ID速查表 | 功能 | COB-ID计算公式 | 示例(节点1) | |------|---------------|------------| | NMT | `0x000` | `0x000` | | SYNC | `0x080` | `0x080` | | EMCY | `0x080 + nodeId` | `0x081` | | TPDO1 | `0x180 + nodeId` | `0x181` | | TPDO2 | `0x280 + nodeId` | `0x281` | | RPDO1 | `0x200 + nodeId` | `0x201` | | RPDO2 | `0x300 + nodeId` | `0x301` | | SDO请求 | `0x600 + nodeId` | `0x601` | | SDO响应 | `0x580 + nodeId` | `0x581` | | Heartbeat | `0x700 + nodeId` | `0x701` | ## 注意事项 ⚠️ **重要提示:** 1. **资源释放** - 务必使用`using`语句或手动调用`Dispose()`关闭设备 2. **线程安全** - 当前实现不是线程安全的,多线程访问需要加锁 3. **超时设置** - SDO操作建议设置合理的超时时间(500-2000ms) 4. **波特率匹配** - 确保所有节点使用相同的波特率 5. **节点ID范围** - 有效节点ID为1-127 6. **数据长度** - CAN帧最多8字节,超过需要使用SDO分段传输 ## 与CanManagerClass的区别 | 特性 | CanOpenManager | CanManagerClass | |------|----------------|-----------------| | 依赖关系 | 直接使用CanLibraryClass | 封装了业务逻辑 | | COB-ID | 支持任意COB-ID | 固定为0 | | 数据格式 | 任意8字节数据 | 特定格式(byte0,6,7) | | CANopen支持 | ✅ 完整支持 | ❌ 不支持 | | 适用场景 | CANopen协议通信 | 自定义CAN通信 | ## 故障排查 ### 问题1: 初始化失败 ``` 可能原因: - CAN卡未连接 - 驱动未安装 - 设备索引错误 解决方法: 1. 检查CAN卡USB连接 2. 确认controlcan.dll存在 3. 尝试不同的deviceIndex ``` ### 问题2: SDO通信超时 ``` 可能原因: - 节点未启动 - 波特率不匹配 - 节点ID错误 解决方法: 1. 先发送NMT启动命令 2. 确认波特率一致 3. 检查节点ID是否正确 ``` ### 问题3: 收不到PDO数据 ``` 可能原因: - PDO未配置 - 传输类型设置错误 - COB-ID映射错误 解决方法: 1. 使用SDO配置PDO映射 2. 检查传输类型(1/254/255) 3. 确认COB-ID计算正确 ``` ## 更多信息 - 查看 [`CanOpenUsageExample.cs`](d:\work\CCDCount\CCD数粒-20260427-CAN通讯稳定版\TestWork\TestWork.DLL\CanBus\CanOpenUsageExample.cs) 获取完整示例代码 - 参考CiA 301标准了解CANopen协议细节 - 查阅设备EDS文件了解具体对象的含义