| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637 |
- using CanTest;
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading.Tasks;
- namespace CCDCount.DLL.CanBus
- {
- /// <summary>
- /// CANopen协议管理器 - 直接基于创芯科技CAN卡底层库实现
- /// 不依赖CanManagerClass,直接使用CanLibraryClass
- /// </summary>
- 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;
-
- // CAN ID字节序配置
- private bool m_useByteSwapForId = false; // 是否对CAN ID进行字节交换
- // 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 构造函数和初始化
- /// <summary>
- /// 构造函数
- /// </summary>
- /// <param name="deviceType">设备类型(默认4=USBCAN2)</param>
- /// <param name="deviceIndex">设备索引</param>
- /// <param name="canIndex">CAN通道索引</param>
- public CanOpenManager(UInt32 deviceType = 4, UInt32 deviceIndex = 0, UInt32 canIndex = 0)
- {
- m_deviceType = deviceType;
- m_deviceIndex = deviceIndex;
- m_canIndex = canIndex;
- }
- /// <summary>
- /// 打开CAN设备并初始化
- /// </summary>
- /// <param name="baudRate">波特率(默认500kbps)</param>
- /// <returns>是否成功</returns>
- 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帧发送和接收
- /// <summary>
- /// 发送原始CAN帧
- /// </summary>
- /// <param name="cobId">CAN标识符</param>
- /// <param name="data">数据(最多8字节)</param>
- /// <param name="isExtended">是否为扩展帧</param>
- /// <param name="isRemote">是否为远程帧</param>
- /// <returns>是否成功</returns>
- 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 = m_useByteSwapForId ? SwapUint32(cobId) : 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;
- }
- /// <summary>
- /// 接收CAN帧
- /// </summary>
- /// <param name="timeoutMs">超时时间(毫秒)</param>
- /// <returns>接收到的CAN帧列表</returns>
- public unsafe List<CanOpenFrame> ReceiveCanFrames(int timeoutMs = 100)
- {
- if (!m_isStarted)
- {
- Console.WriteLine("CAN通道未启动");
- return new List<CanOpenFrame>();
- }
- List<CanOpenFrame> receivedFrames = new List<CanOpenFrame>();
- 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 = m_useByteSwapForId ? SwapUint32(receiveBuffer[i].ID) : 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 字节序转换辅助方法
- /// <summary>
- /// 设置CAN ID是否使用字节交换
- /// </summary>
- /// <param name="useByteSwap">true=启用字节交换, false=禁用(默认)</param>
- public void SetIdByteSwap(bool useByteSwap)
- {
- m_useByteSwapForId = useByteSwap;
- Console.WriteLine($"CAN ID字节交换: {(useByteSwap ? "启用" : "禁用")}");
- }
- /// <summary>
- /// 获取当前字节交换配置
- /// </summary>
- public bool GetIdByteSwap()
- {
- return m_useByteSwapForId;
- }
- /// <summary>
- /// 32位无符号整数字节交换 (Little-Endian <-> Big-Endian)
- /// </summary>
- /// <param name="value">需要转换的值</param>
- /// <returns>转换后的值</returns>
- private uint SwapUint32(uint value)
- {
- return ((value & 0x000000FF) << 24) |
- ((value & 0x0000FF00) << 8) |
- ((value & 0x00FF0000) >> 8) |
- ((value & 0xFF000000) >> 24);
- }
- #endregion
- #region NMT - 网络管理
- /// <summary>
- /// NMT启动节点
- /// </summary>
- public void NmtStartNode(byte nodeId)
- {
- byte[] data = new byte[] { 0x01, nodeId };
- SendCanFrame(NMT_COB_ID, data);
- Console.WriteLine($"NMT启动节点: {nodeId}");
- }
- /// <summary>
- /// NMT停止节点
- /// </summary>
- public void NmtStopNode(byte nodeId)
- {
- byte[] data = new byte[] { 0x02, nodeId };
- SendCanFrame(NMT_COB_ID, data);
- Console.WriteLine($"NMT停止节点: {nodeId}");
- }
- /// <summary>
- /// NMT进入预操作状态
- /// </summary>
- public void NmtEnterPreOperational(byte nodeId)
- {
- byte[] data = new byte[] { 0x80, nodeId };
- SendCanFrame(NMT_COB_ID, data);
- Console.WriteLine($"NMT进入预操作状态: {nodeId}");
- }
- /// <summary>
- /// NMT重置节点
- /// </summary>
- public void NmtResetNode(byte nodeId)
- {
- byte[] data = new byte[] { 0x81, nodeId };
- SendCanFrame(NMT_COB_ID, data);
- Console.WriteLine($"NMT重置节点: {nodeId}");
- }
- /// <summary>
- /// NMT重置通信
- /// </summary>
- public void NmtResetCommunication(byte nodeId)
- {
- byte[] data = new byte[] { 0x82, nodeId };
- SendCanFrame(NMT_COB_ID, data);
- Console.WriteLine($"NMT重置通信: {nodeId}");
- }
- #endregion
- #region SDO - 服务数据对象
- /// <summary>
- /// SDO快速读取
- /// </summary>
- 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}");
- }
- /// <summary>
- /// SDO写入(4字节)
- /// </summary>
- 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}");
- }
- /// <summary>
- /// SDO读取并等待响应
- /// </summary>
- public byte[] SdoReadAndWait(byte nodeId, ushort index, byte subIndex, int timeoutMs = 1000)
- {
- SdoReadQuick(nodeId, index, subIndex);
- return ReceiveSdoResponse(nodeId, timeoutMs);
- }
- /// <summary>
- /// SDO写入并等待确认
- /// </summary>
- 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;
- }
- /// <summary>
- /// 接收SDO响应
- /// </summary>
- 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 - 过程数据对象
- /// <summary>
- /// 发送SYNC帧
- /// </summary>
- 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})" : "")}");
- }
- /// <summary>
- /// 发送TPDO
- /// </summary>
- 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}");
- }
- /// <summary>
- /// 接收RPDO
- /// </summary>
- 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;
- }
- /// <summary>
- /// 配置PDO传输类型
- /// </summary>
- 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 - 紧急消息
- /// <summary>
- /// 解析紧急消息
- /// </summary>
- 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 心跳监测
- /// <summary>
- /// 解析心跳消息
- /// </summary>
- 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 资源管理
- /// <summary>
- /// 关闭CAN设备
- /// </summary>
- 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设备已关闭");
- }
- }
- /// <summary>
- /// 释放资源
- /// </summary>
- public void Dispose()
- {
- Close();
- }
- #endregion
- }
- /// <summary>
- /// CANopen帧数据结构
- /// </summary>
- 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}]";
- }
- }
- }
|