CanOpenManager 是一个基于创芯科技CAN卡底层库(controlcan.dll)实现的CANopen协议管理器。它直接调用 CanLibraryClass 的API,不依赖 CanManagerClass。
✅ 独立实现 - 直接使用创芯科技CAN卡驱动
✅ 完整CANopen支持 - NMT、SDO、PDO、EMCY、Heartbeat
✅ 灵活配置 - 支持多种波特率和设备配置
✅ 资源管理 - 实现IDisposable,自动释放资源
✅ 日志记录 - 完整的通信日志
using CCDCount.DLL.CanBus;
// 创建并初始化
using (var canOpen = new CanOpenManager())
{
// 默认500kbps
if (!canOpen.Initialize())
{
Console.WriteLine("初始化失败!");
return;
}
Console.WriteLine("CANopen就绪!");
}
// 指定设备类型、索引和波特率
var canOpen = new CanOpenManager(
deviceType: 4, // USBCAN2
deviceIndex: 0, // 第1个设备
canIndex: 0 // CAN通道0
);
canOpen.Initialize(CanBaudRate.BaudRate_1M); // 1Mbps
// 启动节点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 (重置通信)// 方式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);
// 写入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 - 目标位置// 发送不带计数器的SYNC
canOpen.SendSync();
// 发送带计数器的SYNC(0-255循环)
for (int i = 0; i < 256; i++)
{
canOpen.SendSync((byte)i);
Thread.Sleep(10); // 10ms周期
}
// 发送TPDO1数据到节点1
byte[] tpdoData = new byte[] { 0x01, 0x02, 0x03, 0x04 };
canOpen.SendTpdo(nodeId: 1, pdoNumber: 1, data: tpdoData);
// 接收节点1的TPDO1
byte[] receivedData = canOpen.ReceiveRpdo(
nodeId: 1,
pdoNumber: 1,
timeoutMs: 100
);
if (receivedData != null)
{
Console.WriteLine($"收到{receivedData.Length}字节数据");
}
// 配置TPDO1为事件驱动(传输类型254)
canOpen.ConfigureTpdoTransmissionType(
nodeId: 1,
pdoNumber: 1,
transmissionType: 254
);
// 配置为同步模式(传输类型1)
canOpen.ConfigureTpdoTransmissionType(1, 1, 1);
传输类型说明:
1 - 同步模式(每个SYNC发送)254 - 事件驱动(数据变化时发送)255 - 异步模式// 监听紧急消息
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;
}
}
}
// 监听心跳消息
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}");
}
}
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 ? "在线" : "离线")}");
}
}
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采样周期
}
}
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计算公式 | 示例(节点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 |
⚠️ 重要提示:
using语句或手动调用Dispose()关闭设备| 特性 | CanOpenManager | CanManagerClass |
|---|---|---|
| 依赖关系 | 直接使用CanLibraryClass | 封装了业务逻辑 |
| COB-ID | 支持任意COB-ID | 固定为0 |
| 数据格式 | 任意8字节数据 | 特定格式(byte0,6,7) |
| CANopen支持 | ✅ 完整支持 | ❌ 不支持 |
| 适用场景 | CANopen协议通信 | 自定义CAN通信 |
可能原因:
- CAN卡未连接
- 驱动未安装
- 设备索引错误
解决方法:
1. 检查CAN卡USB连接
2. 确认controlcan.dll存在
3. 尝试不同的deviceIndex
可能原因:
- 节点未启动
- 波特率不匹配
- 节点ID错误
解决方法:
1. 先发送NMT启动命令
2. 确认波特率一致
3. 检查节点ID是否正确
可能原因:
- PDO未配置
- 传输类型设置错误
- COB-ID映射错误
解决方法:
1. 使用SDO配置PDO映射
2. 检查传输类型(1/254/255)
3. 确认COB-ID计算正确
CanOpenUsageExample.cs 获取完整示例代码