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; } if (cobId != 1793) { 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); // 输出每个CAN帧的详细信息,统一格式 string dataHex = BitConverter.ToString(frame.Data, 0, frame.DataLength).Replace("-", " "); Console.WriteLine($"接收CAN帧 - COB-ID: 0x{frame.CobId:X3}, Len: {frame.DataLength}, Data: {dataHex}"); } 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 = (uint)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 = (uint)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)((ushort)0x1800 + pdoNumber - 1); SdoWrite(nodeId, index, 0x02, transmissionType); } private uint GetTpdoCobId(byte pdoNumber, byte nodeId) { switch (pdoNumber) { case 1: return (uint)TPDO1_BASE + nodeId; case 2: return (uint)TPDO2_BASE + nodeId; case 3: return (uint)TPDO3_BASE + nodeId; case 4: return (uint)TPDO4_BASE + nodeId; default: throw new ArgumentException("无效的PDO编号"); } } private uint GetRpdoCobId(byte pdoNumber, byte nodeId) { switch (pdoNumber) { case 1: return (uint)RPDO1_BASE + nodeId; case 2: return (uint)RPDO2_BASE + nodeId; case 3: return (uint)RPDO3_BASE + nodeId; case 4: return (uint)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}]"; } } }