# CANopen从站实现 - 项目总结 ## 📋 项目概述 本项目基于**创芯科技CAN卡底层库**实现了一个完整的**CANopen从站**,可直接与PLC等CANopen主站设备进行通信。 ### ✨ 核心特性 - ✅ **完整的NMT状态机**:支持初始化、预操作、运行、停止四种状态 - ✅ **SDO服务**:支持对象字典的读写操作 - ✅ **PDO通信**:支持4个TPDO和4个RPDO的配置和数据传输 - ✅ **心跳机制**:自动发送心跳报文告知主站从站状态 - ✅ **SYNC同步**:支持同步对象处理 - ✅ **EDS文件生成**:自动生成PLC可识别的配置文件 - ✅ **灵活的对象字典**:支持标准对象和自定义对象 ## 📁 文件结构 ``` CanTest/ ├── CanOpenSlave.cs # ⭐ CANopen从站核心类 ├── ObjectDictionary.cs # ⭐ 对象字典管理器 ├── EdsFileGenerator.cs # ⭐ EDS配置文件生成器 ├── PdoMappingExample.cs # PDO映射配置示例 ├── CANopen从站使用指南.md # 详细使用文档 ├── README_CANopen_Slave.md # 本文件 │ ├── CanOpenManager.cs # CANopen主站管理器(原有) ├── CanLibraryClass.cs # 创芯科技CAN卡底层API └── Program.cs # 主程序(包含从站示例) ``` ## 🚀 快速开始 ### 1. 基本用法 ```csharp using CCDCount.DLL.CanBus; // 创建从站(节点ID=5) using (CanOpenSlave slave = new CanOpenSlave(5)) { // 初始化 slave.Initialize(CanBaudRate.BaudRate_500K); // 配置PDO slave.ConfigureTpdo(1, true); // 启用TPDO1 slave.ConfigureRpdo(1, true); // 启用RPDO1 // 设置心跳 slave.SetHeartbeatTime(1000); // 启动 slave.Start(); // 写入数据 slave.SetObjectValue(0x6000, 0x01, (byte)0xAA); // 发送TPDO slave.SendTpdo(1); // 生成EDS文件 slave.GenerateEdsFile("Device.eds"); } ``` ### 2. 运行示例 在 `Program.cs` 中已包含完整示例,直接运行即可: ```bash cd d:\work\WindowsFormsTest\CanTest dotnet run ``` ## 🔧 技术架构 ### 核心组件 #### 1. CanOpenSlave(从站核心) **职责:** - NMT状态机管理 - CAN帧接收和分发 - SDO请求处理 - PDO数据打包/解包 - 心跳发送 **关键方法:** ```csharp Initialize() // 初始化从站 Start() // 启动接收线程 Stop() // 停止从站 SetObjectValue() // 写入对象字典 GetObjectValue() // 读取对象字典 ConfigureTpdo() // 配置TPDO ConfigureRpdo() // 配置RPDO SendTpdo() // 发送TPDO SetHeartbeatTime() // 设置心跳时间 GenerateEdsFile() // 生成EDS文件 ``` #### 2. ObjectDictionary(对象字典) **职责:** - 存储所有CANopen对象 - 提供读写接口 - 初始化标准对象 **标准对象包括:** - 设备信息(0x1000-0x1018) - PDO通信参数(0x1400-0x19FF) - PDO映射参数(0x1600-0x1BFF) - 制造商特定对象(0x6000-0x6410) #### 3. EdsFileGenerator(EDS生成器) **职责:** - 根据对象字典生成EDS文件 - 符合CiA标准格式 - 可直接导入PLC配置软件 **生成的EDS包含:** - 设备基本信息 - 支持的波特率 - 所有对象定义 - 数据类型和访问权限 - PDO配置信息 ## 📊 对象字典详解 ### 标准对象区域(0x1000-0x1FFF) | 索引 | 名称 | 类型 | 说明 | |------|------|------|------| | 0x1000:00 | Device Type | UINT32 | 设备类型代码 | | 0x1001:00 | Error Register | UINT8 | 错误寄存器 | | 0x1005:00 | COB-ID SYNC | UINT32 | SYNC COB-ID | | 0x1008:00 | Device Name | STRING | 设备名称 | | 0x1009:00 | Hardware Version | STRING | 硬件版本 | | 0x100A:00 | Software Version | STRING | 软件版本 | | 0x1017:00 | Heartbeat Time | UINT16 | 心跳时间(ms) | | 0x1018:00-04 | Identity | RECORD | 厂商ID、产品码等 | ### PDO通信参数(0x1400-0x19FF) **RPDO通信参数(0x1400-0x1403):** - 子索引01: COB-ID - 子索引02: 传输类型 **TPDO通信参数(0x1800-0x1803):** - 子索引01: COB-ID - 子索引02: 传输类型 - 子索引03: 抑制时间 - 子索引05: 事件定时器 ### PDO映射参数(0x1600-0x1BFF) **映射项格式(32位):** ``` [索引 16位][子索引 8位][位数 8位] ``` **示例:** ```csharp // 映射 0x6000:01, 8位 uint mapping = (0x6000U << 16) | (0x01U << 8) | 0x08U; ``` ### 制造商特定对象(0x6000-0x6410) | 索引 | 名称 | 类型 | 说明 | |------|------|------|------| | 0x6000:01-08 | Digital Input | UINT8[8] | 数字输入 | | 0x6200:01-08 | Digital Output | UINT8[8] | 数字输出 | | 0x6400:01-04 | Analog Input | UINT16[4] | 模拟输入 | | 0x6410:01-02 | Analog Output | UINT16[2] | 模拟输出 | ## 🔌 PLC集成指南 ### 支持的PLC品牌 - ✅ 西门子 S7-1200/1500 - ✅ 三菱 FX/Q系列 - ✅ 欧姆龙 CJ/CS系列 - ✅ 倍福 TwinCAT - ✅ 其他支持CANopen的主站 ### 配置步骤 #### 步骤1:生成EDS文件 ```csharp slave.GenerateEdsFile("MyDevice.eds"); ``` #### 步骤2:导入到PLC 以**TIA Portal**为例: 1. 打开硬件配置 2. 右键"CANopen主站" → "导入GSD/EDS" 3. 选择生成的 `.eds` 文件 4. 拖放设备到网络拓扑 5. 设置节点ID #### 步骤3:配置PDO映射 在PLC中配置映射关系,确保与从站一致: **TPDO1示例:** ``` 字节0: 0x6000:01 (Digital Input Bit 0) 字节1-2: 0x6400:01 (Analog Input Ch1) ``` **RPDO1示例:** ``` 字节0: 0x6200:01 (Digital Output Bit 0) 字节1-2: 0x6410:01 (Analog Output Ch1) ``` #### 步骤4:下载并运行 1. 编译PLC程序 2. 下载到PLC 3. 启动PLC(自动发送NMT Start命令) 4. 观察从站状态变化 ## 📡 通信协议详解 ### COB-ID分配表 | 功能 | 基础ID | 计算公式 | 节点5示例 | |------|--------|----------|-----------| | NMT | 0x000 | 固定 | 0x000 | | SYNC | 0x080 | 固定 | 0x080 | | EMCY | 0x080 | 0x080 + NodeID | 0x085 | | SDO请求 | 0x600 | 0x600 + NodeID | 0x605 | | SDO响应 | 0x580 | 0x580 + NodeID | 0x585 | | TPDO1 | 0x180 | 0x180 + NodeID | 0x185 | | TPDO2 | 0x280 | 0x280 + NodeID | 0x285 | | TPDO3 | 0x380 | 0x380 + NodeID | 0x385 | | TPDO4 | 0x480 | 0x480 + NodeID | 0x485 | | RPDO1 | 0x200 | 0x200 + NodeID | 0x205 | | RPDO2 | 0x300 | 0x300 + NodeID | 0x305 | | RPDO3 | 0x400 | 0x400 + NodeID | 0x405 | | RPDO4 | 0x500 | 0x500 + NodeID | 0x505 | | Heartbeat | 0x700 | 0x700 + NodeID | 0x705 | ### NMT状态转换 ``` Reset Node (0x81) ┌─────────────────┐ │ │ ▼ │ Initializing ──→ PreOperational ◄────┤ │ ▲ │ Start│ Reset│Comm │ (0x01)│ (0x82)│ │ ▼ │ │ Operational───┘ │ │ │ Stop│ │ (0x02) │ ▼ │ Stopped ───────────────┘ ``` ### SDO协议格式 **上传请求(读):** ``` [40h][Index_L][Index_H][SubIndex][00][00][00][00] ``` **上传响应:** ``` [4Fh][Index_L][Index_H][SubIndex][Data][00][00][00] // 1字节 [4Bh][Index_L][Index_H][SubIndex][Data_L][Data_H][00][00] // 2字节 [43h][Index_L][Index_H][SubIndex][Data0][Data1][Data2][Data3] // 4字节 ``` **下载请求(写):** ``` [23h][Index_L][Index_H][SubIndex][Data0][Data1][Data2][Data3] // 4字节 ``` **下载响应:** ``` [60h][Index_L][Index_H][SubIndex][00][00][00][00] ``` ## 🛠️ 调试技巧 ### 1. 使用CAN分析仪 推荐使用以下工具抓包分析: - CANalyzer - PCAN-View - USBCAN Tools - SocketCAN (Linux) ### 2. 验证通信流程 ``` 1. PLC发送NMT Start (COB-ID: 0x000, Data: 01 05) 2. 从站回复Heartbeat (COB-ID: 0x705, Data: 05) 3. PLC配置PDO映射(通过SDO) 4. PLC发送SYNC (COB-ID: 0x080) 5. 从站发送TPDO (COB-ID: 0x185) 6. PLC发送RPDO (COB-ID: 0x205) ``` ### 3. 常见问题排查 **问题1:从站无响应** - 检查CAN总线接线(CAN_H/CAN_L) - 验证波特率是否一致 - 确认终端电阻(120Ω)已安装 - 检查节点ID配置 **问题2:PDO无数据** - 确认从站处于Operational状态 - 验证PDO已启用 - 检查映射配置是否正确 - 查看COB-ID是否匹配 **问题3:心跳超时** - 检查心跳时间设置 - 确认从站未进入Stopped状态 - 验证CAN通信正常 ## 📚 参考资料 ### CANopen标准 - **CiA 301**: CANopen应用层协议 - **CiA 302**: CANopen行规 - **CiA 305**: CANopen分层协议 ### 相关文档 - [CANopen从站使用指南.md](./CANopen从站使用指南.md) - [CanOpenUsageExample.cs](./CanOpenUsageExample.cs) - [PdoMappingExample.cs](./PdoMappingExample.cs) ### 在线资源 - [CAN in Automation官网](https://www.can-cia.org/) - [CANopen协议介绍](https://www.can-cia.org/canopen/) ## 💡 扩展开发 ### 添加自定义对象 ```csharp // 在ObjectDictionary.cs的InitializeStandardObjects()中添加 Write(0x2000, 0x00, (byte)0x12); // 自定义参数 ``` ### 实现紧急消息 ```csharp // 在CanOpenSlave.cs中添加 public void SendEmergency(ushort errorCode) { byte[] emcyData = new byte[8]; emcyData[0] = (byte)(errorCode & 0xFF); emcyData[1] = (byte)(errorCode >> 8); emcyData[2] = 0x00; // Error Register uint emcyCobId = 0x080 + m_nodeId; m_canManager.SendCanFrame(emcyCobId, emcyData); } ``` ### 实现SYNC消费者 ```csharp // 在ProcessCanFrame()中添加SYNC处理 if (cobId == 0x080) { // 收到SYNC,触发周期性TPDO if (m_currentState == NmtState.Operational) { for (byte pdo = 1; pdo <= 4; pdo++) { if (m_tpdoConfigs[pdo-1].TransmissionType > 0 && m_tpdoConfigs[pdo-1].TransmissionType <= 240) { // 根据SYNC计数器决定是否发送 m_syncCounter++; if (m_syncCounter % m_tpdoConfigs[pdo-1].TransmissionType == 0) { SendTpdo(pdo); } } } } } ``` ## 📝 更新日志 ### v1.0.0 (2026-05-08) - ✅ 实现完整的CANopen从站功能 - ✅ 支持NMT、SDO、PDO、心跳 - ✅ 实现EDS文件生成器 - ✅ 提供完整的使用示例和文档 ## 📄 许可证 本项目仅供学习和测试使用。 ## 👥 贡献 欢迎提交Issue和Pull Request! --- **祝使用愉快!** 🎉 如有问题,请参考详细文档或查阅CANopen标准协议。