CanOpenManager.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637
  1. using CanTest;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7. namespace CCDCount.DLL.CanBus
  8. {
  9. /// <summary>
  10. /// CANopen协议管理器 - 直接基于创芯科技CAN卡底层库实现
  11. /// 不依赖CanManagerClass,直接使用CanLibraryClass
  12. /// </summary>
  13. public class CanOpenManager : IDisposable
  14. {
  15. #region 私有字段
  16. private UInt32 m_deviceType = 4; // USBCAN2
  17. private UInt32 m_deviceIndex = 0;
  18. private UInt32 m_canIndex = 0;
  19. private bool m_isOpened = false;
  20. private bool m_isStarted = false;
  21. // CAN ID字节序配置
  22. private bool m_useByteSwapForId = false; // 是否对CAN ID进行字节交换
  23. // CANopen COB-ID定义
  24. private const uint NMT_COB_ID = 0x000; // 网络管理
  25. private const uint SYNC_COB_ID = 0x080; // 同步对象
  26. private const uint EMCY_COB_ID_BASE = 0x081; // 紧急对象基础ID
  27. // SDO COB-ID
  28. private const uint SDO_REQUEST_BASE = 0x600; // SDO请求基础ID
  29. private const uint SDO_RESPONSE_BASE = 0x580; // SDO响应基础ID
  30. // PDO COB-ID
  31. private const uint TPDO1_BASE = 0x180;
  32. private const uint TPDO2_BASE = 0x280;
  33. private const uint TPDO3_BASE = 0x380;
  34. private const uint TPDO4_BASE = 0x480;
  35. private const uint RPDO1_BASE = 0x200;
  36. private const uint RPDO2_BASE = 0x300;
  37. private const uint RPDO3_BASE = 0x400;
  38. private const uint RPDO4_BASE = 0x500;
  39. #endregion
  40. #region 构造函数和初始化
  41. /// <summary>
  42. /// 构造函数
  43. /// </summary>
  44. /// <param name="deviceType">设备类型(默认4=USBCAN2)</param>
  45. /// <param name="deviceIndex">设备索引</param>
  46. /// <param name="canIndex">CAN通道索引</param>
  47. public CanOpenManager(UInt32 deviceType = 4, UInt32 deviceIndex = 0, UInt32 canIndex = 0)
  48. {
  49. m_deviceType = deviceType;
  50. m_deviceIndex = deviceIndex;
  51. m_canIndex = canIndex;
  52. }
  53. /// <summary>
  54. /// 打开CAN设备并初始化
  55. /// </summary>
  56. /// <param name="baudRate">波特率(默认500kbps)</param>
  57. /// <returns>是否成功</returns>
  58. public bool Initialize(CanBaudRate baudRate = CanBaudRate.BaudRate_1M)
  59. {
  60. if (m_isOpened)
  61. {
  62. Console.WriteLine("CAN设备已打开");
  63. return true;
  64. }
  65. // 1. 打开设备
  66. UInt32 result = CanLibraryClass.VCI_OpenDevice(m_deviceType, m_deviceIndex, 0);
  67. if (result == 0)
  68. {
  69. Console.WriteLine("打开CAN设备失败");
  70. return false;
  71. }
  72. m_isOpened = true;
  73. Console.WriteLine("CAN设备打开成功");
  74. // 2. 初始化CAN通道
  75. VCI_INIT_CONFIG config = new VCI_INIT_CONFIG();
  76. config.AccCode = 0x00000000;
  77. config.AccMask = 0xFFFFFFFF;
  78. config.Filter = 1; // 接收所有帧
  79. config.Mode = 0; // 正常模式
  80. // 设置波特率参数
  81. SetBaudRate(ref config, baudRate);
  82. result = CanLibraryClass.VCI_InitCAN(m_deviceType, m_deviceIndex, m_canIndex, ref config);
  83. if (result == 0)
  84. {
  85. Console.WriteLine("初始化CAN通道失败");
  86. Close();
  87. return false;
  88. }
  89. Console.WriteLine($"CAN通道初始化成功,波特率:{baudRate}");
  90. // 3. 启动CAN通道
  91. result = CanLibraryClass.VCI_StartCAN(m_deviceType, m_deviceIndex, m_canIndex);
  92. if (result == 0)
  93. {
  94. Console.WriteLine("启动CAN通道失败");
  95. Close();
  96. return false;
  97. }
  98. m_isStarted = true;
  99. Console.WriteLine("CAN通道启动成功");
  100. return true;
  101. }
  102. private void SetBaudRate(ref VCI_INIT_CONFIG config, CanBaudRate baudRate)
  103. {
  104. switch (baudRate)
  105. {
  106. case CanBaudRate.BaudRate_1M:
  107. config.Timing0 = 0x00;
  108. config.Timing1 = 0x14;
  109. break;
  110. case CanBaudRate.BaudRate_500K:
  111. config.Timing0 = 0x00;
  112. config.Timing1 = 0x1C;
  113. break;
  114. case CanBaudRate.BaudRate_250K:
  115. config.Timing0 = 0x01;
  116. config.Timing1 = 0x1C;
  117. break;
  118. case CanBaudRate.BaudRate_125K:
  119. config.Timing0 = 0x03;
  120. config.Timing1 = 0x1C;
  121. break;
  122. default:
  123. config.Timing0 = 0x00;
  124. config.Timing1 = 0x1C;
  125. break;
  126. }
  127. }
  128. #endregion
  129. #region 底层CAN帧发送和接收
  130. /// <summary>
  131. /// 发送原始CAN帧
  132. /// </summary>
  133. /// <param name="cobId">CAN标识符</param>
  134. /// <param name="data">数据(最多8字节)</param>
  135. /// <param name="isExtended">是否为扩展帧</param>
  136. /// <param name="isRemote">是否为远程帧</param>
  137. /// <returns>是否成功</returns>
  138. public unsafe bool SendCanFrame(uint cobId, byte[] data, bool isExtended = false, bool isRemote = false)
  139. {
  140. if (!m_isStarted)
  141. {
  142. Console.WriteLine("CAN通道未启动");
  143. return false;
  144. }
  145. if (data == null || data.Length > 8)
  146. {
  147. Console.WriteLine("SendCanFrame: 数据长度无效(必须0-8字节)");
  148. return false;
  149. }
  150. VCI_CAN_OBJ sendObj = new VCI_CAN_OBJ();
  151. // 根据配置决定是否进行字节序转换
  152. sendObj.ID = m_useByteSwapForId ? SwapUint32(cobId) : cobId;
  153. sendObj.ExternFlag = isExtended ? (byte)1 : (byte)0;
  154. sendObj.RemoteFlag = isRemote ? (byte)1 : (byte)0;
  155. sendObj.SendType = 0;
  156. sendObj.TimeFlag = 0;
  157. sendObj.DataLen = (byte)data.Length;
  158. // 复制数据
  159. for (int i = 0; i < data.Length; i++)
  160. {
  161. sendObj.Data[i] = data[i];
  162. }
  163. // 填充剩余字节
  164. for (int i = data.Length; i < 8; i++)
  165. {
  166. sendObj.Data[i] = 0;
  167. }
  168. UInt32 result = CanLibraryClass.VCI_Transmit(m_deviceType, m_deviceIndex, m_canIndex, ref sendObj, 1);
  169. if (result == 0)
  170. {
  171. Console.WriteLine($"发送CAN帧失败 - COB-ID: 0x{cobId:X3}");
  172. return false;
  173. }
  174. if (cobId != 1793)
  175. {
  176. string logStr = $"发送CAN帧 - COB-ID: 0x{cobId:X3}, Len: {data.Length}, Data: ";
  177. for (int i = 0; i < data.Length; i++)
  178. {
  179. logStr += $"{data[i]:X2} ";
  180. }
  181. Console.WriteLine(logStr);
  182. }
  183. return true;
  184. }
  185. /// <summary>
  186. /// 接收CAN帧
  187. /// </summary>
  188. /// <param name="timeoutMs">超时时间(毫秒)</param>
  189. /// <returns>接收到的CAN帧列表</returns>
  190. public unsafe List<CanOpenFrame> ReceiveCanFrames(int timeoutMs = 100)
  191. {
  192. if (!m_isStarted)
  193. {
  194. Console.WriteLine("CAN通道未启动");
  195. return new List<CanOpenFrame>();
  196. }
  197. List<CanOpenFrame> receivedFrames = new List<CanOpenFrame>();
  198. VCI_CAN_OBJ[] receiveBuffer = new VCI_CAN_OBJ[2500];
  199. UInt32 count = CanLibraryClass.VCI_Receive(
  200. m_deviceType,
  201. m_deviceIndex,
  202. m_canIndex,
  203. ref receiveBuffer[0],
  204. 2500,
  205. timeoutMs);
  206. if (count == 0xFFFFFFFF) count = 0;
  207. for (UInt32 i = 0; i < count; i++)
  208. {
  209. CanOpenFrame frame = new CanOpenFrame();
  210. // 根据配置决定是否进行字节序转换
  211. frame.CobId = m_useByteSwapForId ? SwapUint32(receiveBuffer[i].ID) : receiveBuffer[i].ID;
  212. frame.IsExtended = receiveBuffer[i].ExternFlag == 1;
  213. frame.IsRemote = receiveBuffer[i].RemoteFlag == 1;
  214. frame.DataLength = receiveBuffer[i].DataLen;
  215. frame.TimeStamp = receiveBuffer[i].TimeStamp;
  216. frame.Data = new byte[8];
  217. for (int j = 0; j < 8; j++)
  218. {
  219. frame.Data[j] = receiveBuffer[i].Data[j];
  220. }
  221. receivedFrames.Add(frame);
  222. // 输出每个CAN帧的详细信息,统一格式
  223. string dataHex = BitConverter.ToString(frame.Data, 0, frame.DataLength).Replace("-", " ");
  224. Console.WriteLine($"接收CAN帧 - COB-ID: 0x{frame.CobId:X3}, Len: {frame.DataLength}, Data: {dataHex}");
  225. }
  226. return receivedFrames;
  227. }
  228. #endregion
  229. #region 字节序转换辅助方法
  230. /// <summary>
  231. /// 设置CAN ID是否使用字节交换
  232. /// </summary>
  233. /// <param name="useByteSwap">true=启用字节交换, false=禁用(默认)</param>
  234. public void SetIdByteSwap(bool useByteSwap)
  235. {
  236. m_useByteSwapForId = useByteSwap;
  237. Console.WriteLine($"CAN ID字节交换: {(useByteSwap ? "启用" : "禁用")}");
  238. }
  239. /// <summary>
  240. /// 获取当前字节交换配置
  241. /// </summary>
  242. public bool GetIdByteSwap()
  243. {
  244. return m_useByteSwapForId;
  245. }
  246. /// <summary>
  247. /// 32位无符号整数字节交换 (Little-Endian <-> Big-Endian)
  248. /// </summary>
  249. /// <param name="value">需要转换的值</param>
  250. /// <returns>转换后的值</returns>
  251. private uint SwapUint32(uint value)
  252. {
  253. return ((value & 0x000000FF) << 24) |
  254. ((value & 0x0000FF00) << 8) |
  255. ((value & 0x00FF0000) >> 8) |
  256. ((value & 0xFF000000) >> 24);
  257. }
  258. #endregion
  259. #region NMT - 网络管理
  260. /// <summary>
  261. /// NMT启动节点
  262. /// </summary>
  263. public void NmtStartNode(byte nodeId)
  264. {
  265. byte[] data = new byte[] { 0x01, nodeId };
  266. SendCanFrame(NMT_COB_ID, data);
  267. Console.WriteLine($"NMT启动节点: {nodeId}");
  268. }
  269. /// <summary>
  270. /// NMT停止节点
  271. /// </summary>
  272. public void NmtStopNode(byte nodeId)
  273. {
  274. byte[] data = new byte[] { 0x02, nodeId };
  275. SendCanFrame(NMT_COB_ID, data);
  276. Console.WriteLine($"NMT停止节点: {nodeId}");
  277. }
  278. /// <summary>
  279. /// NMT进入预操作状态
  280. /// </summary>
  281. public void NmtEnterPreOperational(byte nodeId)
  282. {
  283. byte[] data = new byte[] { 0x80, nodeId };
  284. SendCanFrame(NMT_COB_ID, data);
  285. Console.WriteLine($"NMT进入预操作状态: {nodeId}");
  286. }
  287. /// <summary>
  288. /// NMT重置节点
  289. /// </summary>
  290. public void NmtResetNode(byte nodeId)
  291. {
  292. byte[] data = new byte[] { 0x81, nodeId };
  293. SendCanFrame(NMT_COB_ID, data);
  294. Console.WriteLine($"NMT重置节点: {nodeId}");
  295. }
  296. /// <summary>
  297. /// NMT重置通信
  298. /// </summary>
  299. public void NmtResetCommunication(byte nodeId)
  300. {
  301. byte[] data = new byte[] { 0x82, nodeId };
  302. SendCanFrame(NMT_COB_ID, data);
  303. Console.WriteLine($"NMT重置通信: {nodeId}");
  304. }
  305. #endregion
  306. #region SDO - 服务数据对象
  307. /// <summary>
  308. /// SDO快速读取
  309. /// </summary>
  310. public void SdoReadQuick(byte nodeId, ushort index, byte subIndex)
  311. {
  312. byte[] sdoRequest = new byte[8];
  313. sdoRequest[0] = 0x40; // SDO上传请求
  314. sdoRequest[1] = (byte)(index & 0xFF);
  315. sdoRequest[2] = (byte)(index >> 8);
  316. sdoRequest[3] = subIndex;
  317. sdoRequest[4] = 0x00;
  318. sdoRequest[5] = 0x00;
  319. sdoRequest[6] = 0x00;
  320. sdoRequest[7] = 0x00;
  321. uint cobId = (uint)SDO_REQUEST_BASE + nodeId;
  322. SendCanFrame(cobId, sdoRequest);
  323. Console.WriteLine($"SDO读取: 节点{nodeId}, 索引0x{index:X4}, 子索引{subIndex}");
  324. }
  325. /// <summary>
  326. /// SDO写入(4字节)
  327. /// </summary>
  328. public void SdoWrite(byte nodeId, ushort index, byte subIndex, uint data)
  329. {
  330. byte[] sdoRequest = new byte[8];
  331. sdoRequest[0] = 0x23; // SDO下载请求(4字节)
  332. sdoRequest[1] = (byte)(index & 0xFF);
  333. sdoRequest[2] = (byte)(index >> 8);
  334. sdoRequest[3] = subIndex;
  335. sdoRequest[4] = (byte)(data & 0xFF);
  336. sdoRequest[5] = (byte)((data >> 8) & 0xFF);
  337. sdoRequest[6] = (byte)((data >> 16) & 0xFF);
  338. sdoRequest[7] = (byte)((data >> 24) & 0xFF);
  339. uint cobId = SDO_REQUEST_BASE + nodeId;
  340. SendCanFrame(cobId, sdoRequest);
  341. Console.WriteLine($"SDO写入: 节点{nodeId}, 索引0x{index:X4}, 子索引{subIndex}, 值0x{data:X8}");
  342. }
  343. /// <summary>
  344. /// SDO读取并等待响应
  345. /// </summary>
  346. public byte[] SdoReadAndWait(byte nodeId, ushort index, byte subIndex, int timeoutMs = 1000)
  347. {
  348. SdoReadQuick(nodeId, index, subIndex);
  349. return ReceiveSdoResponse(nodeId, timeoutMs);
  350. }
  351. /// <summary>
  352. /// SDO写入并等待确认
  353. /// </summary>
  354. public bool SdoWriteAndWait(byte nodeId, ushort index, byte subIndex, uint data, int timeoutMs = 1000)
  355. {
  356. SdoWrite(nodeId, index, subIndex, data);
  357. var response = ReceiveSdoResponse(nodeId, timeoutMs);
  358. if (response != null && response.Length >= 1)
  359. {
  360. return (response[0] & 0xE0) == 0x60;
  361. }
  362. return false;
  363. }
  364. /// <summary>
  365. /// 接收SDO响应
  366. /// </summary>
  367. public byte[] ReceiveSdoResponse(byte nodeId, int timeoutMs = 1000)
  368. {
  369. uint responseCobId = (uint)SDO_RESPONSE_BASE + nodeId;
  370. var frames = ReceiveCanFrames(timeoutMs);
  371. foreach (var frame in frames)
  372. {
  373. if (frame.CobId == responseCobId && !frame.IsRemote)
  374. {
  375. Console.WriteLine($"收到SDO响应: {frame}");
  376. return frame.Data;
  377. }
  378. }
  379. Console.WriteLine($"SDO响应超时: 节点{nodeId}");
  380. return null;
  381. }
  382. #endregion
  383. #region PDO - 过程数据对象
  384. /// <summary>
  385. /// 发送SYNC帧
  386. /// </summary>
  387. public void SendSync(byte counter = 0)
  388. {
  389. byte[] syncData = counter > 0 ? new byte[] { counter } : new byte[0];
  390. SendCanFrame(SYNC_COB_ID, syncData);
  391. Console.WriteLine($"发送SYNC帧{(counter > 0 ? $" (计数器:{counter})" : "")}");
  392. }
  393. /// <summary>
  394. /// 发送TPDO
  395. /// </summary>
  396. public void SendTpdo(byte nodeId, byte pdoNumber, byte[] data)
  397. {
  398. if (pdoNumber < 1 || pdoNumber > 4)
  399. throw new ArgumentException("PDO编号必须在1-4之间");
  400. uint cobId = GetTpdoCobId(pdoNumber, nodeId);
  401. SendCanFrame(cobId, data);
  402. Console.WriteLine($"发送TPDO{pdoNumber}: 节点{nodeId}, COB-ID: 0x{cobId:X3}");
  403. }
  404. /// <summary>
  405. /// 接收RPDO
  406. /// </summary>
  407. public byte[] ReceiveRpdo(byte nodeId, byte pdoNumber, int timeoutMs = 100)
  408. {
  409. if (pdoNumber < 1 || pdoNumber > 4)
  410. throw new ArgumentException("PDO编号必须在1-4之间");
  411. uint cobId = GetRpdoCobId(pdoNumber, nodeId);
  412. var frames = ReceiveCanFrames(timeoutMs);
  413. foreach (var frame in frames)
  414. {
  415. if (frame.CobId == cobId && !frame.IsRemote)
  416. {
  417. Console.WriteLine($"收到RPDO{pdoNumber}: {frame}");
  418. byte[] validData = new byte[frame.DataLength];
  419. Array.Copy(frame.Data, validData, frame.DataLength);
  420. return validData;
  421. }
  422. }
  423. return null;
  424. }
  425. /// <summary>
  426. /// 配置PDO传输类型
  427. /// </summary>
  428. public void ConfigureTpdoTransmissionType(byte nodeId, byte pdoNumber, byte transmissionType)
  429. {
  430. ushort index = (ushort)((ushort)0x1800 + pdoNumber - 1);
  431. SdoWrite(nodeId, index, 0x02, transmissionType);
  432. }
  433. private uint GetTpdoCobId(byte pdoNumber, byte nodeId)
  434. {
  435. switch (pdoNumber)
  436. {
  437. case 1: return (uint)TPDO1_BASE + nodeId;
  438. case 2: return (uint)TPDO2_BASE + nodeId;
  439. case 3: return (uint)TPDO3_BASE + nodeId;
  440. case 4: return (uint)TPDO4_BASE + nodeId;
  441. default: throw new ArgumentException("无效的PDO编号");
  442. }
  443. }
  444. private uint GetRpdoCobId(byte pdoNumber, byte nodeId)
  445. {
  446. switch (pdoNumber)
  447. {
  448. case 1: return (uint)RPDO1_BASE + nodeId;
  449. case 2: return (uint)RPDO2_BASE + nodeId;
  450. case 3: return (uint)RPDO3_BASE + nodeId;
  451. case 4: return (uint)RPDO4_BASE + nodeId;
  452. default: throw new ArgumentException("无效的PDO编号");
  453. }
  454. }
  455. #endregion
  456. #region EMCY - 紧急消息
  457. /// <summary>
  458. /// 解析紧急消息
  459. /// </summary>
  460. public ushort ParseEmergencyMessage(byte[] data)
  461. {
  462. if (data == null || data.Length < 2)
  463. return 0;
  464. ushort errorCode = (ushort)(data[0] | (data[1] << 8));
  465. byte errorRegister = data[2];
  466. Console.WriteLine($"紧急消息: 错误代码0x{errorCode:X4}, 错误寄存器0x{errorRegister:X2}");
  467. return errorCode;
  468. }
  469. #endregion
  470. #region 心跳监测
  471. /// <summary>
  472. /// 解析心跳消息
  473. /// </summary>
  474. public byte ParseHeartbeat(byte nodeId, byte[] data)
  475. {
  476. if (data == null || data.Length < 1)
  477. return 0xFF;
  478. byte status = data[0];
  479. string statusStr = GetNodeStatusString(status);
  480. Console.WriteLine($"节点{nodeId}心跳: 状态0x{status:X2} ({statusStr})");
  481. return status;
  482. }
  483. private string GetNodeStatusString(byte status)
  484. {
  485. switch (status & 0x7F)
  486. {
  487. case 0x00: return "启动中";
  488. case 0x04: return "停止";
  489. case 0x05: return "运行";
  490. case 0x7F: return "预操作";
  491. default: return "未知";
  492. }
  493. }
  494. #endregion
  495. #region 资源管理
  496. /// <summary>
  497. /// 关闭CAN设备
  498. /// </summary>
  499. public void Close()
  500. {
  501. if (m_isStarted)
  502. {
  503. CanLibraryClass.VCI_ResetCAN(m_deviceType, m_deviceIndex, m_canIndex);
  504. m_isStarted = false;
  505. }
  506. if (m_isOpened)
  507. {
  508. CanLibraryClass.VCI_CloseDevice(m_deviceType, m_deviceIndex);
  509. m_isOpened = false;
  510. Console.WriteLine("CAN设备已关闭");
  511. }
  512. }
  513. /// <summary>
  514. /// 释放资源
  515. /// </summary>
  516. public void Dispose()
  517. {
  518. Close();
  519. }
  520. #endregion
  521. }
  522. /// <summary>
  523. /// CANopen帧数据结构
  524. /// </summary>
  525. public class CanOpenFrame
  526. {
  527. public uint CobId { get; set; }
  528. public bool IsExtended { get; set; }
  529. public bool IsRemote { get; set; }
  530. public byte DataLength { get; set; }
  531. public byte[] Data { get; set; }
  532. public uint TimeStamp { get; set; }
  533. public override string ToString()
  534. {
  535. string dataStr = "";
  536. for (int i = 0; i < DataLength; i++)
  537. {
  538. dataStr += $"{Data[i]:X2} ";
  539. }
  540. return $"COB-ID: 0x{CobId:X3}, Len: {DataLength}, Data: [{dataStr}]";
  541. }
  542. }
  543. }