CanOpenManager.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  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. sendObj.ID = cobId;
  150. sendObj.ExternFlag = isExtended ? (byte)1 : (byte)0;
  151. sendObj.RemoteFlag = isRemote ? (byte)1 : (byte)0;
  152. sendObj.SendType = 0;
  153. sendObj.TimeFlag = 0;
  154. sendObj.DataLen = (byte)data.Length;
  155. // 复制数据
  156. for (int i = 0; i < data.Length; i++)
  157. {
  158. sendObj.Data[i] = data[i];
  159. }
  160. // 填充剩余字节
  161. for (int i = data.Length; i < 8; i++)
  162. {
  163. sendObj.Data[i] = 0;
  164. }
  165. UInt32 result = CanLibraryClass.VCI_Transmit(m_deviceType, m_deviceIndex, m_canIndex, ref sendObj, 1);
  166. if (result == 0)
  167. {
  168. Console.WriteLine($"发送CAN帧失败 - COB-ID: 0x{cobId:X3}");
  169. return false;
  170. }
  171. string logStr = $"发送CAN帧 - COB-ID: 0x{cobId:X3}, Len: {data.Length}, Data: ";
  172. for (int i = 0; i < data.Length; i++)
  173. {
  174. logStr += $"{data[i]:X2} ";
  175. }
  176. Console.WriteLine(logStr);
  177. return true;
  178. }
  179. /// <summary>
  180. /// 接收CAN帧
  181. /// </summary>
  182. /// <param name="timeoutMs">超时时间(毫秒)</param>
  183. /// <returns>接收到的CAN帧列表</returns>
  184. public unsafe List<CanOpenFrame> ReceiveCanFrames(int timeoutMs = 100)
  185. {
  186. if (!m_isStarted)
  187. {
  188. Console.WriteLine("CAN通道未启动");
  189. return new List<CanOpenFrame>();
  190. }
  191. List<CanOpenFrame> receivedFrames = new List<CanOpenFrame>();
  192. VCI_CAN_OBJ[] receiveBuffer = new VCI_CAN_OBJ[2500];
  193. UInt32 count = CanLibraryClass.VCI_Receive(
  194. m_deviceType,
  195. m_deviceIndex,
  196. m_canIndex,
  197. ref receiveBuffer[0],
  198. 2500,
  199. timeoutMs);
  200. if (count == 0xFFFFFFFF) count = 0;
  201. for (UInt32 i = 0; i < count; i++)
  202. {
  203. CanOpenFrame frame = new CanOpenFrame();
  204. frame.CobId = receiveBuffer[i].ID;
  205. frame.IsExtended = receiveBuffer[i].ExternFlag == 1;
  206. frame.IsRemote = receiveBuffer[i].RemoteFlag == 1;
  207. frame.DataLength = receiveBuffer[i].DataLen;
  208. frame.TimeStamp = receiveBuffer[i].TimeStamp;
  209. frame.Data = new byte[8];
  210. for (int j = 0; j < 8; j++)
  211. {
  212. frame.Data[j] = receiveBuffer[i].Data[j];
  213. }
  214. receivedFrames.Add(frame);
  215. }
  216. return receivedFrames;
  217. }
  218. #endregion
  219. #region NMT - 网络管理
  220. /// <summary>
  221. /// NMT启动节点
  222. /// </summary>
  223. public void NmtStartNode(byte nodeId)
  224. {
  225. byte[] data = new byte[] { 0x01, nodeId };
  226. SendCanFrame(NMT_COB_ID, data);
  227. Console.WriteLine($"NMT启动节点: {nodeId}");
  228. }
  229. /// <summary>
  230. /// NMT停止节点
  231. /// </summary>
  232. public void NmtStopNode(byte nodeId)
  233. {
  234. byte[] data = new byte[] { 0x02, nodeId };
  235. SendCanFrame(NMT_COB_ID, data);
  236. Console.WriteLine($"NMT停止节点: {nodeId}");
  237. }
  238. /// <summary>
  239. /// NMT进入预操作状态
  240. /// </summary>
  241. public void NmtEnterPreOperational(byte nodeId)
  242. {
  243. byte[] data = new byte[] { 0x80, nodeId };
  244. SendCanFrame(NMT_COB_ID, data);
  245. Console.WriteLine($"NMT进入预操作状态: {nodeId}");
  246. }
  247. /// <summary>
  248. /// NMT重置节点
  249. /// </summary>
  250. public void NmtResetNode(byte nodeId)
  251. {
  252. byte[] data = new byte[] { 0x81, nodeId };
  253. SendCanFrame(NMT_COB_ID, data);
  254. Console.WriteLine($"NMT重置节点: {nodeId}");
  255. }
  256. /// <summary>
  257. /// NMT重置通信
  258. /// </summary>
  259. public void NmtResetCommunication(byte nodeId)
  260. {
  261. byte[] data = new byte[] { 0x82, nodeId };
  262. SendCanFrame(NMT_COB_ID, data);
  263. Console.WriteLine($"NMT重置通信: {nodeId}");
  264. }
  265. #endregion
  266. #region SDO - 服务数据对象
  267. /// <summary>
  268. /// SDO快速读取
  269. /// </summary>
  270. public void SdoReadQuick(byte nodeId, ushort index, byte subIndex)
  271. {
  272. byte[] sdoRequest = new byte[8];
  273. sdoRequest[0] = 0x40; // SDO上传请求
  274. sdoRequest[1] = (byte)(index & 0xFF);
  275. sdoRequest[2] = (byte)(index >> 8);
  276. sdoRequest[3] = subIndex;
  277. sdoRequest[4] = 0x00;
  278. sdoRequest[5] = 0x00;
  279. sdoRequest[6] = 0x00;
  280. sdoRequest[7] = 0x00;
  281. uint cobId = SDO_REQUEST_BASE + nodeId;
  282. SendCanFrame(cobId, sdoRequest);
  283. Console.WriteLine($"SDO读取: 节点{nodeId}, 索引0x{index:X4}, 子索引{subIndex}");
  284. }
  285. /// <summary>
  286. /// SDO写入(4字节)
  287. /// </summary>
  288. public void SdoWrite(byte nodeId, ushort index, byte subIndex, uint data)
  289. {
  290. byte[] sdoRequest = new byte[8];
  291. sdoRequest[0] = 0x23; // SDO下载请求(4字节)
  292. sdoRequest[1] = (byte)(index & 0xFF);
  293. sdoRequest[2] = (byte)(index >> 8);
  294. sdoRequest[3] = subIndex;
  295. sdoRequest[4] = (byte)(data & 0xFF);
  296. sdoRequest[5] = (byte)((data >> 8) & 0xFF);
  297. sdoRequest[6] = (byte)((data >> 16) & 0xFF);
  298. sdoRequest[7] = (byte)((data >> 24) & 0xFF);
  299. uint cobId = SDO_REQUEST_BASE + nodeId;
  300. SendCanFrame(cobId, sdoRequest);
  301. Console.WriteLine($"SDO写入: 节点{nodeId}, 索引0x{index:X4}, 子索引{subIndex}, 值0x{data:X8}");
  302. }
  303. /// <summary>
  304. /// SDO读取并等待响应
  305. /// </summary>
  306. public byte[] SdoReadAndWait(byte nodeId, ushort index, byte subIndex, int timeoutMs = 1000)
  307. {
  308. SdoReadQuick(nodeId, index, subIndex);
  309. return ReceiveSdoResponse(nodeId, timeoutMs);
  310. }
  311. /// <summary>
  312. /// SDO写入并等待确认
  313. /// </summary>
  314. public bool SdoWriteAndWait(byte nodeId, ushort index, byte subIndex, uint data, int timeoutMs = 1000)
  315. {
  316. SdoWrite(nodeId, index, subIndex, data);
  317. var response = ReceiveSdoResponse(nodeId, timeoutMs);
  318. if (response != null && response.Length >= 1)
  319. {
  320. return (response[0] & 0xE0) == 0x60;
  321. }
  322. return false;
  323. }
  324. /// <summary>
  325. /// 接收SDO响应
  326. /// </summary>
  327. public byte[] ReceiveSdoResponse(byte nodeId, int timeoutMs = 1000)
  328. {
  329. uint responseCobId = SDO_RESPONSE_BASE + nodeId;
  330. var frames = ReceiveCanFrames(timeoutMs);
  331. foreach (var frame in frames)
  332. {
  333. if (frame.CobId == responseCobId && !frame.IsRemote)
  334. {
  335. Console.WriteLine($"收到SDO响应: {frame}");
  336. return frame.Data;
  337. }
  338. }
  339. Console.WriteLine($"SDO响应超时: 节点{nodeId}");
  340. return null;
  341. }
  342. #endregion
  343. #region PDO - 过程数据对象
  344. /// <summary>
  345. /// 发送SYNC帧
  346. /// </summary>
  347. public void SendSync(byte counter = 0)
  348. {
  349. byte[] syncData = counter > 0 ? new byte[] { counter } : new byte[0];
  350. SendCanFrame(SYNC_COB_ID, syncData);
  351. Console.WriteLine($"发送SYNC帧{(counter > 0 ? $" (计数器:{counter})" : "")}");
  352. }
  353. /// <summary>
  354. /// 发送TPDO
  355. /// </summary>
  356. public void SendTpdo(byte nodeId, byte pdoNumber, byte[] data)
  357. {
  358. if (pdoNumber < 1 || pdoNumber > 4)
  359. throw new ArgumentException("PDO编号必须在1-4之间");
  360. uint cobId = GetTpdoCobId(pdoNumber, nodeId);
  361. SendCanFrame(cobId, data);
  362. Console.WriteLine($"发送TPDO{pdoNumber}: 节点{nodeId}, COB-ID: 0x{cobId:X3}");
  363. }
  364. /// <summary>
  365. /// 接收RPDO
  366. /// </summary>
  367. public byte[] ReceiveRpdo(byte nodeId, byte pdoNumber, int timeoutMs = 100)
  368. {
  369. if (pdoNumber < 1 || pdoNumber > 4)
  370. throw new ArgumentException("PDO编号必须在1-4之间");
  371. uint cobId = GetRpdoCobId(pdoNumber, nodeId);
  372. var frames = ReceiveCanFrames(timeoutMs);
  373. foreach (var frame in frames)
  374. {
  375. if (frame.CobId == cobId && !frame.IsRemote)
  376. {
  377. Console.WriteLine($"收到RPDO{pdoNumber}: {frame}");
  378. byte[] validData = new byte[frame.DataLength];
  379. Array.Copy(frame.Data, validData, frame.DataLength);
  380. return validData;
  381. }
  382. }
  383. return null;
  384. }
  385. /// <summary>
  386. /// 配置PDO传输类型
  387. /// </summary>
  388. public void ConfigureTpdoTransmissionType(byte nodeId, byte pdoNumber, byte transmissionType)
  389. {
  390. ushort index = (ushort)(0x1800 + pdoNumber - 1);
  391. SdoWrite(nodeId, index, 0x02, transmissionType);
  392. }
  393. private uint GetTpdoCobId(byte pdoNumber, byte nodeId)
  394. {
  395. switch (pdoNumber)
  396. {
  397. case 1: return TPDO1_BASE + nodeId;
  398. case 2: return TPDO2_BASE + nodeId;
  399. case 3: return TPDO3_BASE + nodeId;
  400. case 4: return TPDO4_BASE + nodeId;
  401. default: throw new ArgumentException("无效的PDO编号");
  402. }
  403. }
  404. private uint GetRpdoCobId(byte pdoNumber, byte nodeId)
  405. {
  406. switch (pdoNumber)
  407. {
  408. case 1: return RPDO1_BASE + nodeId;
  409. case 2: return RPDO2_BASE + nodeId;
  410. case 3: return RPDO3_BASE + nodeId;
  411. case 4: return RPDO4_BASE + nodeId;
  412. default: throw new ArgumentException("无效的PDO编号");
  413. }
  414. }
  415. #endregion
  416. #region EMCY - 紧急消息
  417. /// <summary>
  418. /// 解析紧急消息
  419. /// </summary>
  420. public ushort ParseEmergencyMessage(byte[] data)
  421. {
  422. if (data == null || data.Length < 2)
  423. return 0;
  424. ushort errorCode = (ushort)(data[0] | (data[1] << 8));
  425. byte errorRegister = data[2];
  426. Console.WriteLine($"紧急消息: 错误代码0x{errorCode:X4}, 错误寄存器0x{errorRegister:X2}");
  427. return errorCode;
  428. }
  429. #endregion
  430. #region 心跳监测
  431. /// <summary>
  432. /// 解析心跳消息
  433. /// </summary>
  434. public byte ParseHeartbeat(byte nodeId, byte[] data)
  435. {
  436. if (data == null || data.Length < 1)
  437. return 0xFF;
  438. byte status = data[0];
  439. string statusStr = GetNodeStatusString(status);
  440. Console.WriteLine($"节点{nodeId}心跳: 状态0x{status:X2} ({statusStr})");
  441. return status;
  442. }
  443. private string GetNodeStatusString(byte status)
  444. {
  445. switch (status & 0x7F)
  446. {
  447. case 0x00: return "启动中";
  448. case 0x04: return "停止";
  449. case 0x05: return "运行";
  450. case 0x7F: return "预操作";
  451. default: return "未知";
  452. }
  453. }
  454. #endregion
  455. #region 资源管理
  456. /// <summary>
  457. /// 关闭CAN设备
  458. /// </summary>
  459. public void Close()
  460. {
  461. if (m_isStarted)
  462. {
  463. CanLibraryClass.VCI_ResetCAN(m_deviceType, m_deviceIndex, m_canIndex);
  464. m_isStarted = false;
  465. }
  466. if (m_isOpened)
  467. {
  468. CanLibraryClass.VCI_CloseDevice(m_deviceType, m_deviceIndex);
  469. m_isOpened = false;
  470. Console.WriteLine("CAN设备已关闭");
  471. }
  472. }
  473. /// <summary>
  474. /// 释放资源
  475. /// </summary>
  476. public void Dispose()
  477. {
  478. Close();
  479. }
  480. #endregion
  481. }
  482. /// <summary>
  483. /// CANopen帧数据结构
  484. /// </summary>
  485. public class CanOpenFrame
  486. {
  487. public uint CobId { get; set; }
  488. public bool IsExtended { get; set; }
  489. public bool IsRemote { get; set; }
  490. public byte DataLength { get; set; }
  491. public byte[] Data { get; set; }
  492. public uint TimeStamp { get; set; }
  493. public override string ToString()
  494. {
  495. string dataStr = "";
  496. for (int i = 0; i < DataLength; i++)
  497. {
  498. dataStr += $"{Data[i]:X2} ";
  499. }
  500. return $"COB-ID: 0x{CobId:X3}, Len: {DataLength}, Data: [{dataStr}]";
  501. }
  502. }
  503. /// <summary>
  504. /// CAN波特率枚举
  505. /// </summary>
  506. public enum CanBaudRate
  507. {
  508. BaudRate_1M = 1000000,
  509. BaudRate_500K = 500000,
  510. BaudRate_250K = 250000,
  511. BaudRate_125K = 125000
  512. }
  513. }