using CanTest;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CCDCount.DLL.CanBus
{
///
/// CANopen协议管理器 - 直接基于创芯科技CAN卡底层库实现
/// 不依赖CanManagerClass,直接使用CanLibraryClass
///
public class CanOpenManager : IDisposable
{
#region 私有字段
private UInt32 m_deviceType = 4; // USBCAN2
private UInt32 m_deviceIndex = 0;
private UInt32 m_canIndex = 0;
private bool m_isOpened = false;
private bool m_isStarted = false;
// CANopen COB-ID定义
private const uint NMT_COB_ID = 0x000; // 网络管理
private const uint SYNC_COB_ID = 0x080; // 同步对象
private const uint EMCY_COB_ID_BASE = 0x081; // 紧急对象基础ID
// SDO COB-ID
private const uint SDO_REQUEST_BASE = 0x600; // SDO请求基础ID
private const uint SDO_RESPONSE_BASE = 0x580; // SDO响应基础ID
// PDO COB-ID
private const uint TPDO1_BASE = 0x180;
private const uint TPDO2_BASE = 0x280;
private const uint TPDO3_BASE = 0x380;
private const uint TPDO4_BASE = 0x480;
private const uint RPDO1_BASE = 0x200;
private const uint RPDO2_BASE = 0x300;
private const uint RPDO3_BASE = 0x400;
private const uint RPDO4_BASE = 0x500;
#endregion
#region 构造函数和初始化
///
/// 构造函数
///
/// 设备类型(默认4=USBCAN2)
/// 设备索引
/// CAN通道索引
public CanOpenManager(UInt32 deviceType = 4, UInt32 deviceIndex = 0, UInt32 canIndex = 0)
{
m_deviceType = deviceType;
m_deviceIndex = deviceIndex;
m_canIndex = canIndex;
}
///
/// 打开CAN设备并初始化
///
/// 波特率(默认500kbps)
/// 是否成功
public bool Initialize(CanBaudRate baudRate = CanBaudRate.BaudRate_1M)
{
if (m_isOpened)
{
Console.WriteLine("CAN设备已打开");
return true;
}
// 1. 打开设备
UInt32 result = CanLibraryClass.VCI_OpenDevice(m_deviceType, m_deviceIndex, 0);
if (result == 0)
{
Console.WriteLine("打开CAN设备失败");
return false;
}
m_isOpened = true;
Console.WriteLine("CAN设备打开成功");
// 2. 初始化CAN通道
VCI_INIT_CONFIG config = new VCI_INIT_CONFIG();
config.AccCode = 0x00000000;
config.AccMask = 0xFFFFFFFF;
config.Filter = 1; // 接收所有帧
config.Mode = 0; // 正常模式
// 设置波特率参数
SetBaudRate(ref config, baudRate);
result = CanLibraryClass.VCI_InitCAN(m_deviceType, m_deviceIndex, m_canIndex, ref config);
if (result == 0)
{
Console.WriteLine("初始化CAN通道失败");
Close();
return false;
}
Console.WriteLine($"CAN通道初始化成功,波特率:{baudRate}");
// 3. 启动CAN通道
result = CanLibraryClass.VCI_StartCAN(m_deviceType, m_deviceIndex, m_canIndex);
if (result == 0)
{
Console.WriteLine("启动CAN通道失败");
Close();
return false;
}
m_isStarted = true;
Console.WriteLine("CAN通道启动成功");
return true;
}
private void SetBaudRate(ref VCI_INIT_CONFIG config, CanBaudRate baudRate)
{
switch (baudRate)
{
case CanBaudRate.BaudRate_1M:
config.Timing0 = 0x00;
config.Timing1 = 0x14;
break;
case CanBaudRate.BaudRate_500K:
config.Timing0 = 0x00;
config.Timing1 = 0x1C;
break;
case CanBaudRate.BaudRate_250K:
config.Timing0 = 0x01;
config.Timing1 = 0x1C;
break;
case CanBaudRate.BaudRate_125K:
config.Timing0 = 0x03;
config.Timing1 = 0x1C;
break;
default:
config.Timing0 = 0x00;
config.Timing1 = 0x1C;
break;
}
}
#endregion
#region 底层CAN帧发送和接收
///
/// 发送原始CAN帧
///
/// CAN标识符
/// 数据(最多8字节)
/// 是否为扩展帧
/// 是否为远程帧
/// 是否成功
public unsafe bool SendCanFrame(uint cobId, byte[] data, bool isExtended = false, bool isRemote = false)
{
if (!m_isStarted)
{
Console.WriteLine("CAN通道未启动");
return false;
}
if (data == null || data.Length > 8)
{
Console.WriteLine("SendCanFrame: 数据长度无效(必须0-8字节)");
return false;
}
VCI_CAN_OBJ sendObj = new VCI_CAN_OBJ();
sendObj.ID = cobId;
sendObj.ExternFlag = isExtended ? (byte)1 : (byte)0;
sendObj.RemoteFlag = isRemote ? (byte)1 : (byte)0;
sendObj.SendType = 0;
sendObj.TimeFlag = 0;
sendObj.DataLen = (byte)data.Length;
// 复制数据
for (int i = 0; i < data.Length; i++)
{
sendObj.Data[i] = data[i];
}
// 填充剩余字节
for (int i = data.Length; i < 8; i++)
{
sendObj.Data[i] = 0;
}
UInt32 result = CanLibraryClass.VCI_Transmit(m_deviceType, m_deviceIndex, m_canIndex, ref sendObj, 1);
if (result == 0)
{
Console.WriteLine($"发送CAN帧失败 - COB-ID: 0x{cobId:X3}");
return false;
}
string logStr = $"发送CAN帧 - COB-ID: 0x{cobId:X3}, Len: {data.Length}, Data: ";
for (int i = 0; i < data.Length; i++)
{
logStr += $"{data[i]:X2} ";
}
Console.WriteLine(logStr);
return true;
}
///
/// 接收CAN帧
///
/// 超时时间(毫秒)
/// 接收到的CAN帧列表
public unsafe List ReceiveCanFrames(int timeoutMs = 100)
{
if (!m_isStarted)
{
Console.WriteLine("CAN通道未启动");
return new List();
}
List receivedFrames = new List();
VCI_CAN_OBJ[] receiveBuffer = new VCI_CAN_OBJ[2500];
UInt32 count = CanLibraryClass.VCI_Receive(
m_deviceType,
m_deviceIndex,
m_canIndex,
ref receiveBuffer[0],
2500,
timeoutMs);
if (count == 0xFFFFFFFF) count = 0;
for (UInt32 i = 0; i < count; i++)
{
CanOpenFrame frame = new CanOpenFrame();
frame.CobId = receiveBuffer[i].ID;
frame.IsExtended = receiveBuffer[i].ExternFlag == 1;
frame.IsRemote = receiveBuffer[i].RemoteFlag == 1;
frame.DataLength = receiveBuffer[i].DataLen;
frame.TimeStamp = receiveBuffer[i].TimeStamp;
frame.Data = new byte[8];
for (int j = 0; j < 8; j++)
{
frame.Data[j] = receiveBuffer[i].Data[j];
}
receivedFrames.Add(frame);
}
return receivedFrames;
}
#endregion
#region NMT - 网络管理
///
/// NMT启动节点
///
public void NmtStartNode(byte nodeId)
{
byte[] data = new byte[] { 0x01, nodeId };
SendCanFrame(NMT_COB_ID, data);
Console.WriteLine($"NMT启动节点: {nodeId}");
}
///
/// NMT停止节点
///
public void NmtStopNode(byte nodeId)
{
byte[] data = new byte[] { 0x02, nodeId };
SendCanFrame(NMT_COB_ID, data);
Console.WriteLine($"NMT停止节点: {nodeId}");
}
///
/// NMT进入预操作状态
///
public void NmtEnterPreOperational(byte nodeId)
{
byte[] data = new byte[] { 0x80, nodeId };
SendCanFrame(NMT_COB_ID, data);
Console.WriteLine($"NMT进入预操作状态: {nodeId}");
}
///
/// NMT重置节点
///
public void NmtResetNode(byte nodeId)
{
byte[] data = new byte[] { 0x81, nodeId };
SendCanFrame(NMT_COB_ID, data);
Console.WriteLine($"NMT重置节点: {nodeId}");
}
///
/// NMT重置通信
///
public void NmtResetCommunication(byte nodeId)
{
byte[] data = new byte[] { 0x82, nodeId };
SendCanFrame(NMT_COB_ID, data);
Console.WriteLine($"NMT重置通信: {nodeId}");
}
#endregion
#region SDO - 服务数据对象
///
/// SDO快速读取
///
public void SdoReadQuick(byte nodeId, ushort index, byte subIndex)
{
byte[] sdoRequest = new byte[8];
sdoRequest[0] = 0x40; // SDO上传请求
sdoRequest[1] = (byte)(index & 0xFF);
sdoRequest[2] = (byte)(index >> 8);
sdoRequest[3] = subIndex;
sdoRequest[4] = 0x00;
sdoRequest[5] = 0x00;
sdoRequest[6] = 0x00;
sdoRequest[7] = 0x00;
uint cobId = SDO_REQUEST_BASE + nodeId;
SendCanFrame(cobId, sdoRequest);
Console.WriteLine($"SDO读取: 节点{nodeId}, 索引0x{index:X4}, 子索引{subIndex}");
}
///
/// SDO写入(4字节)
///
public void SdoWrite(byte nodeId, ushort index, byte subIndex, uint data)
{
byte[] sdoRequest = new byte[8];
sdoRequest[0] = 0x23; // SDO下载请求(4字节)
sdoRequest[1] = (byte)(index & 0xFF);
sdoRequest[2] = (byte)(index >> 8);
sdoRequest[3] = subIndex;
sdoRequest[4] = (byte)(data & 0xFF);
sdoRequest[5] = (byte)((data >> 8) & 0xFF);
sdoRequest[6] = (byte)((data >> 16) & 0xFF);
sdoRequest[7] = (byte)((data >> 24) & 0xFF);
uint cobId = SDO_REQUEST_BASE + nodeId;
SendCanFrame(cobId, sdoRequest);
Console.WriteLine($"SDO写入: 节点{nodeId}, 索引0x{index:X4}, 子索引{subIndex}, 值0x{data:X8}");
}
///
/// SDO读取并等待响应
///
public byte[] SdoReadAndWait(byte nodeId, ushort index, byte subIndex, int timeoutMs = 1000)
{
SdoReadQuick(nodeId, index, subIndex);
return ReceiveSdoResponse(nodeId, timeoutMs);
}
///
/// SDO写入并等待确认
///
public bool SdoWriteAndWait(byte nodeId, ushort index, byte subIndex, uint data, int timeoutMs = 1000)
{
SdoWrite(nodeId, index, subIndex, data);
var response = ReceiveSdoResponse(nodeId, timeoutMs);
if (response != null && response.Length >= 1)
{
return (response[0] & 0xE0) == 0x60;
}
return false;
}
///
/// 接收SDO响应
///
public byte[] ReceiveSdoResponse(byte nodeId, int timeoutMs = 1000)
{
uint responseCobId = SDO_RESPONSE_BASE + nodeId;
var frames = ReceiveCanFrames(timeoutMs);
foreach (var frame in frames)
{
if (frame.CobId == responseCobId && !frame.IsRemote)
{
Console.WriteLine($"收到SDO响应: {frame}");
return frame.Data;
}
}
Console.WriteLine($"SDO响应超时: 节点{nodeId}");
return null;
}
#endregion
#region PDO - 过程数据对象
///
/// 发送SYNC帧
///
public void SendSync(byte counter = 0)
{
byte[] syncData = counter > 0 ? new byte[] { counter } : new byte[0];
SendCanFrame(SYNC_COB_ID, syncData);
Console.WriteLine($"发送SYNC帧{(counter > 0 ? $" (计数器:{counter})" : "")}");
}
///
/// 发送TPDO
///
public void SendTpdo(byte nodeId, byte pdoNumber, byte[] data)
{
if (pdoNumber < 1 || pdoNumber > 4)
throw new ArgumentException("PDO编号必须在1-4之间");
uint cobId = GetTpdoCobId(pdoNumber, nodeId);
SendCanFrame(cobId, data);
Console.WriteLine($"发送TPDO{pdoNumber}: 节点{nodeId}, COB-ID: 0x{cobId:X3}");
}
///
/// 接收RPDO
///
public byte[] ReceiveRpdo(byte nodeId, byte pdoNumber, int timeoutMs = 100)
{
if (pdoNumber < 1 || pdoNumber > 4)
throw new ArgumentException("PDO编号必须在1-4之间");
uint cobId = GetRpdoCobId(pdoNumber, nodeId);
var frames = ReceiveCanFrames(timeoutMs);
foreach (var frame in frames)
{
if (frame.CobId == cobId && !frame.IsRemote)
{
Console.WriteLine($"收到RPDO{pdoNumber}: {frame}");
byte[] validData = new byte[frame.DataLength];
Array.Copy(frame.Data, validData, frame.DataLength);
return validData;
}
}
return null;
}
///
/// 配置PDO传输类型
///
public void ConfigureTpdoTransmissionType(byte nodeId, byte pdoNumber, byte transmissionType)
{
ushort index = (ushort)(0x1800 + pdoNumber - 1);
SdoWrite(nodeId, index, 0x02, transmissionType);
}
private uint GetTpdoCobId(byte pdoNumber, byte nodeId)
{
switch (pdoNumber)
{
case 1: return TPDO1_BASE + nodeId;
case 2: return TPDO2_BASE + nodeId;
case 3: return TPDO3_BASE + nodeId;
case 4: return TPDO4_BASE + nodeId;
default: throw new ArgumentException("无效的PDO编号");
}
}
private uint GetRpdoCobId(byte pdoNumber, byte nodeId)
{
switch (pdoNumber)
{
case 1: return RPDO1_BASE + nodeId;
case 2: return RPDO2_BASE + nodeId;
case 3: return RPDO3_BASE + nodeId;
case 4: return RPDO4_BASE + nodeId;
default: throw new ArgumentException("无效的PDO编号");
}
}
#endregion
#region EMCY - 紧急消息
///
/// 解析紧急消息
///
public ushort ParseEmergencyMessage(byte[] data)
{
if (data == null || data.Length < 2)
return 0;
ushort errorCode = (ushort)(data[0] | (data[1] << 8));
byte errorRegister = data[2];
Console.WriteLine($"紧急消息: 错误代码0x{errorCode:X4}, 错误寄存器0x{errorRegister:X2}");
return errorCode;
}
#endregion
#region 心跳监测
///
/// 解析心跳消息
///
public byte ParseHeartbeat(byte nodeId, byte[] data)
{
if (data == null || data.Length < 1)
return 0xFF;
byte status = data[0];
string statusStr = GetNodeStatusString(status);
Console.WriteLine($"节点{nodeId}心跳: 状态0x{status:X2} ({statusStr})");
return status;
}
private string GetNodeStatusString(byte status)
{
switch (status & 0x7F)
{
case 0x00: return "启动中";
case 0x04: return "停止";
case 0x05: return "运行";
case 0x7F: return "预操作";
default: return "未知";
}
}
#endregion
#region 资源管理
///
/// 关闭CAN设备
///
public void Close()
{
if (m_isStarted)
{
CanLibraryClass.VCI_ResetCAN(m_deviceType, m_deviceIndex, m_canIndex);
m_isStarted = false;
}
if (m_isOpened)
{
CanLibraryClass.VCI_CloseDevice(m_deviceType, m_deviceIndex);
m_isOpened = false;
Console.WriteLine("CAN设备已关闭");
}
}
///
/// 释放资源
///
public void Dispose()
{
Close();
}
#endregion
}
///
/// CANopen帧数据结构
///
public class CanOpenFrame
{
public uint CobId { get; set; }
public bool IsExtended { get; set; }
public bool IsRemote { get; set; }
public byte DataLength { get; set; }
public byte[] Data { get; set; }
public uint TimeStamp { get; set; }
public override string ToString()
{
string dataStr = "";
for (int i = 0; i < DataLength; i++)
{
dataStr += $"{Data[i]:X2} ";
}
return $"COB-ID: 0x{CobId:X3}, Len: {DataLength}, Data: [{dataStr}]";
}
}
///
/// CAN波特率枚举
///
public enum CanBaudRate
{
BaudRate_1M = 1000000,
BaudRate_500K = 500000,
BaudRate_250K = 250000,
BaudRate_125K = 125000
}
}