CanOpenManager.cs 19 KB

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