本项目基于创芯科技CAN卡底层库实现了一个完整的CANopen从站,可直接与PLC等CANopen主站设备进行通信。
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 # 主程序(包含从站示例)
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");
}
在 Program.cs 中已包含完整示例,直接运行即可:
cd d:\work\WindowsFormsTest\CanTest
dotnet run
职责:
关键方法:
Initialize() // 初始化从站
Start() // 启动接收线程
Stop() // 停止从站
SetObjectValue() // 写入对象字典
GetObjectValue() // 读取对象字典
ConfigureTpdo() // 配置TPDO
ConfigureRpdo() // 配置RPDO
SendTpdo() // 发送TPDO
SetHeartbeatTime() // 设置心跳时间
GenerateEdsFile() // 生成EDS文件
职责:
标准对象包括:
职责:
生成的EDS包含:
| 索引 | 名称 | 类型 | 说明 |
|---|---|---|---|
| 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、产品码等 |
RPDO通信参数(0x1400-0x1403):
TPDO通信参数(0x1800-0x1803):
映射项格式(32位):
[索引 16位][子索引 8位][位数 8位]
示例:
// 映射 0x6000:01, 8位
uint mapping = (0x6000U << 16) | (0x01U << 8) | 0x08U;
| 索引 | 名称 | 类型 | 说明 |
|---|---|---|---|
| 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] | 模拟输出 |
slave.GenerateEdsFile("MyDevice.eds");
以TIA Portal为例:
.eds 文件在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)
| 功能 | 基础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 |
Reset Node (0x81)
┌─────────────────┐
│ │
▼ │
Initializing ──→ PreOperational ◄────┤
│ ▲ │
Start│ Reset│Comm │
(0x01)│ (0x82)│ │
▼ │ │
Operational───┘ │
│ │
Stop│ │
(0x02) │
▼ │
Stopped ───────────────┘
上传请求(读):
[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. 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)
问题1:从站无响应
问题2:PDO无数据
问题3:心跳超时
// 在ObjectDictionary.cs的InitializeStandardObjects()中添加
Write(0x2000, 0x00, (byte)0x12); // 自定义参数
// 在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);
}
// 在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);
}
}
}
}
}
本项目仅供学习和测试使用。
欢迎提交Issue和Pull Request!
祝使用愉快! 🎉
如有问题,请参考详细文档或查阅CANopen标准协议。