SlaveTestForm.cs 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  1. using System;
  2. using System.Windows.Forms;
  3. using System.Drawing;
  4. using System.Diagnostics;
  5. using System.Threading;
  6. using System.Threading.Tasks;
  7. using CCDCount.DLL.CanBus;
  8. using CanTest;
  9. namespace CanOpenSlaveTest
  10. {
  11. /// <summary>
  12. /// CANopen从站设备测试界面
  13. /// </summary>
  14. public class SlaveTestForm : Form
  15. {
  16. private CanOpenSlaveDevice m_slaveDevice;
  17. // UI控件
  18. private GroupBox grpDeviceInfo;
  19. private Label lblNodeId;
  20. private Label lblState;
  21. private Label lblStatus;
  22. private GroupBox grpControl;
  23. private Button btnStart;
  24. private Button btnStop;
  25. private GroupBox grpPdo1Send;
  26. private Label lblPdo1Data0;
  27. private Label lblPdo1Data1;
  28. private Label lblPdo1Data2;
  29. private Label lblPdo1Data3;
  30. private Label lblPdo1Data4;
  31. private Label lblPdo1Data5;
  32. private Label lblPdo1Data6;
  33. private Label lblPdo1Data7;
  34. private TextBox txtPdo1Data0;
  35. private TextBox txtPdo1Data1;
  36. private TextBox txtPdo1Data2;
  37. private TextBox txtPdo1Data3;
  38. private TextBox txtPdo1Data4;
  39. private TextBox txtPdo1Data5;
  40. private TextBox txtPdo1Data6;
  41. private TextBox txtPdo1Data7;
  42. private Button btnSendTpdo1;
  43. private Button btnRuleSend;
  44. private Button btnContinuousSend;
  45. // 连续发送相关控件
  46. private Label lblPeriodTime;
  47. private TextBox txtPeriodTime;
  48. private Label lblPeriodUnit;
  49. // 规则发送运行时长相关控件
  50. private Label lblRuleDuration;
  51. private TextBox txtRuleDuration;
  52. private Label lblRuleDurationUnit;
  53. private TextBox txtLog;
  54. private System.ComponentModel.IContainer components;
  55. private System.Windows.Forms.Timer m_updateTimer;
  56. // 连续发送状态
  57. private bool m_isContinuousSending = false;
  58. private CancellationTokenSource m_continuousSendCts;
  59. private long m_continuousSendCounter = 0;
  60. // 规则发送状态
  61. private bool m_isRuleSending = false;
  62. private long m_ruleSendCounter = 0;
  63. // 规则发送运行时长相关
  64. private DateTime m_ruleSendStartTime;
  65. private int m_ruleSendDurationMs = 0;
  66. private CancellationTokenSource m_ruleSendCts;
  67. public SlaveTestForm()
  68. {
  69. InitializeComponent();
  70. // 在构造函数中创建从站设备(避免设计器错误)
  71. if (!DesignMode)
  72. {
  73. m_slaveDevice = new CanOpenSlaveDevice(nodeId: 1);
  74. // 注册事件
  75. m_slaveDevice.OnRpdoReceived += SlaveDevice_OnRpdoReceived;
  76. m_slaveDevice.OnNmtStateChanged += SlaveDevice_OnNmtStateChanged;
  77. m_slaveDevice.OnSdoReadRequest += SlaveDevice_OnSdoReadRequest;
  78. m_slaveDevice.OnSdoWriteRequest += SlaveDevice_OnSdoWriteRequest;
  79. // 初始化日志
  80. AppendLog("CANopen从站测试程序已启动");
  81. AppendLog("节点ID: " + m_slaveDevice.NodeId.ToString());
  82. AppendLog("心跳周期: 1000ms (固定值)");
  83. AppendLog("点击'启动从站'按钮开始测试\n");
  84. }
  85. }
  86. private void InitializeComponent()
  87. {
  88. this.components = new System.ComponentModel.Container();
  89. this.grpDeviceInfo = new System.Windows.Forms.GroupBox();
  90. this.lblNodeId = new System.Windows.Forms.Label();
  91. this.lblState = new System.Windows.Forms.Label();
  92. this.lblStatus = new System.Windows.Forms.Label();
  93. this.grpControl = new System.Windows.Forms.GroupBox();
  94. this.btnStart = new System.Windows.Forms.Button();
  95. this.btnStop = new System.Windows.Forms.Button();
  96. this.grpPdo1Send = new System.Windows.Forms.GroupBox();
  97. this.lblPdo1Data0 = new System.Windows.Forms.Label();
  98. this.lblPdo1Data1 = new System.Windows.Forms.Label();
  99. this.lblPdo1Data2 = new System.Windows.Forms.Label();
  100. this.lblPdo1Data3 = new System.Windows.Forms.Label();
  101. this.lblPdo1Data4 = new System.Windows.Forms.Label();
  102. this.lblPdo1Data5 = new System.Windows.Forms.Label();
  103. this.lblPdo1Data6 = new System.Windows.Forms.Label();
  104. this.lblPdo1Data7 = new System.Windows.Forms.Label();
  105. this.txtPdo1Data0 = new System.Windows.Forms.TextBox();
  106. this.txtPdo1Data1 = new System.Windows.Forms.TextBox();
  107. this.txtPdo1Data2 = new System.Windows.Forms.TextBox();
  108. this.txtPdo1Data3 = new System.Windows.Forms.TextBox();
  109. this.txtPdo1Data4 = new System.Windows.Forms.TextBox();
  110. this.txtPdo1Data5 = new System.Windows.Forms.TextBox();
  111. this.txtPdo1Data6 = new System.Windows.Forms.TextBox();
  112. this.txtPdo1Data7 = new System.Windows.Forms.TextBox();
  113. this.btnSendTpdo1 = new System.Windows.Forms.Button();
  114. this.btnRuleSend = new System.Windows.Forms.Button();
  115. this.btnContinuousSend = new System.Windows.Forms.Button();
  116. this.lblPeriodTime = new System.Windows.Forms.Label();
  117. this.txtPeriodTime = new System.Windows.Forms.TextBox();
  118. this.lblPeriodUnit = new System.Windows.Forms.Label();
  119. // 添加规则发送时长相关控件
  120. this.lblRuleDuration = new System.Windows.Forms.Label();
  121. this.txtRuleDuration = new System.Windows.Forms.TextBox();
  122. this.lblRuleDurationUnit = new System.Windows.Forms.Label();
  123. this.txtLog = new System.Windows.Forms.TextBox();
  124. this.m_updateTimer = new System.Windows.Forms.Timer(this.components);
  125. this.grpDeviceInfo.SuspendLayout();
  126. this.grpControl.SuspendLayout();
  127. this.grpPdo1Send.SuspendLayout();
  128. this.SuspendLayout();
  129. //
  130. // grpDeviceInfo
  131. //
  132. this.grpDeviceInfo.Controls.Add(this.lblNodeId);
  133. this.grpDeviceInfo.Controls.Add(this.lblState);
  134. this.grpDeviceInfo.Controls.Add(this.lblStatus);
  135. this.grpDeviceInfo.Location = new System.Drawing.Point(10, 10);
  136. this.grpDeviceInfo.Name = "grpDeviceInfo";
  137. this.grpDeviceInfo.Size = new System.Drawing.Size(380, 100);
  138. this.grpDeviceInfo.TabIndex = 0;
  139. this.grpDeviceInfo.TabStop = false;
  140. this.grpDeviceInfo.Text = "设备信息";
  141. //
  142. // lblNodeId
  143. //
  144. this.lblNodeId.AutoSize = true;
  145. this.lblNodeId.Location = new System.Drawing.Point(10, 25);
  146. this.lblNodeId.Name = "lblNodeId";
  147. this.lblNodeId.Size = new System.Drawing.Size(77, 15);
  148. this.lblNodeId.TabIndex = 0;
  149. this.lblNodeId.Text = "节点ID: -";
  150. //
  151. // lblState
  152. //
  153. this.lblState.AutoSize = true;
  154. this.lblState.Location = new System.Drawing.Point(10, 50);
  155. this.lblState.Name = "lblState";
  156. this.lblState.Size = new System.Drawing.Size(61, 15);
  157. this.lblState.TabIndex = 1;
  158. this.lblState.Text = "状态: -";
  159. //
  160. // lblStatus
  161. //
  162. this.lblStatus.AutoSize = true;
  163. this.lblStatus.Location = new System.Drawing.Point(10, 75);
  164. this.lblStatus.Name = "lblStatus";
  165. this.lblStatus.Size = new System.Drawing.Size(128, 15);
  166. this.lblStatus.TabIndex = 2;
  167. this.lblStatus.Text = "运行状态: 未启动";
  168. //
  169. // grpControl
  170. //
  171. this.grpControl.Controls.Add(this.btnStart);
  172. this.grpControl.Controls.Add(this.btnStop);
  173. this.grpControl.Location = new System.Drawing.Point(400, 10);
  174. this.grpControl.Name = "grpControl";
  175. this.grpControl.Size = new System.Drawing.Size(380, 100);
  176. this.grpControl.TabIndex = 1;
  177. this.grpControl.TabStop = false;
  178. this.grpControl.Text = "控制";
  179. //
  180. // btnStart
  181. //
  182. this.btnStart.Location = new System.Drawing.Point(10, 25);
  183. this.btnStart.Name = "btnStart";
  184. this.btnStart.Size = new System.Drawing.Size(100, 30);
  185. this.btnStart.TabIndex = 0;
  186. this.btnStart.Text = "启动从站";
  187. this.btnStart.Click += new System.EventHandler(this.BtnStart_Click);
  188. //
  189. // btnStop
  190. //
  191. this.btnStop.Enabled = false;
  192. this.btnStop.Location = new System.Drawing.Point(120, 25);
  193. this.btnStop.Name = "btnStop";
  194. this.btnStop.Size = new System.Drawing.Size(100, 30);
  195. this.btnStop.TabIndex = 1;
  196. this.btnStop.Text = "停止从站";
  197. this.btnStop.Click += new System.EventHandler(this.BtnStop_Click);
  198. //
  199. // grpPdo1Send
  200. //
  201. this.grpPdo1Send.Controls.Add(this.lblPdo1Data0);
  202. this.grpPdo1Send.Controls.Add(this.lblPdo1Data1);
  203. this.grpPdo1Send.Controls.Add(this.lblPdo1Data2);
  204. this.grpPdo1Send.Controls.Add(this.lblPdo1Data3);
  205. this.grpPdo1Send.Controls.Add(this.lblPdo1Data4);
  206. this.grpPdo1Send.Controls.Add(this.lblPdo1Data5);
  207. this.grpPdo1Send.Controls.Add(this.lblPdo1Data6);
  208. this.grpPdo1Send.Controls.Add(this.lblPdo1Data7);
  209. this.grpPdo1Send.Controls.Add(this.txtPdo1Data0);
  210. this.grpPdo1Send.Controls.Add(this.txtPdo1Data1);
  211. this.grpPdo1Send.Controls.Add(this.txtPdo1Data2);
  212. this.grpPdo1Send.Controls.Add(this.txtPdo1Data3);
  213. this.grpPdo1Send.Controls.Add(this.txtPdo1Data4);
  214. this.grpPdo1Send.Controls.Add(this.txtPdo1Data5);
  215. this.grpPdo1Send.Controls.Add(this.txtPdo1Data6);
  216. this.grpPdo1Send.Controls.Add(this.txtPdo1Data7);
  217. this.grpPdo1Send.Controls.Add(this.btnSendTpdo1);
  218. this.grpPdo1Send.Controls.Add(this.btnRuleSend);
  219. this.grpPdo1Send.Controls.Add(this.btnContinuousSend);
  220. this.grpPdo1Send.Controls.Add(this.lblPeriodTime);
  221. this.grpPdo1Send.Controls.Add(this.txtPeriodTime);
  222. this.grpPdo1Send.Controls.Add(this.lblPeriodUnit);
  223. // 添加规则发送时长相关控件到界面
  224. this.grpPdo1Send.Controls.Add(this.lblRuleDuration);
  225. this.grpPdo1Send.Controls.Add(this.txtRuleDuration);
  226. this.grpPdo1Send.Controls.Add(this.lblRuleDurationUnit);
  227. this.grpPdo1Send.Location = new System.Drawing.Point(10, 120);
  228. this.grpPdo1Send.Name = "grpPdo1Send";
  229. this.grpPdo1Send.Size = new System.Drawing.Size(770, 160); // 增加高度以容纳新控件
  230. this.grpPdo1Send.TabIndex = 2;
  231. this.grpPdo1Send.TabStop = false;
  232. this.grpPdo1Send.Text = "TPDO1发送配置";
  233. //
  234. // lblPdo1Data0
  235. //
  236. this.lblPdo1Data0.AutoSize = true;
  237. this.lblPdo1Data0.Location = new System.Drawing.Point(10, 25);
  238. this.lblPdo1Data0.Name = "lblPdo1Data0";
  239. this.lblPdo1Data0.Size = new System.Drawing.Size(47, 15);
  240. this.lblPdo1Data0.TabIndex = 0;
  241. this.lblPdo1Data0.Text = "Byte0:";
  242. //
  243. // lblPdo1Data1
  244. //
  245. this.lblPdo1Data1.AutoSize = true;
  246. this.lblPdo1Data1.Location = new System.Drawing.Point(90, 25);
  247. this.lblPdo1Data1.Name = "lblPdo1Data1";
  248. this.lblPdo1Data1.Size = new System.Drawing.Size(47, 15);
  249. this.lblPdo1Data1.TabIndex = 1;
  250. this.lblPdo1Data1.Text = "Byte1:";
  251. //
  252. // lblPdo1Data2
  253. //
  254. this.lblPdo1Data2.AutoSize = true;
  255. this.lblPdo1Data2.Location = new System.Drawing.Point(170, 25);
  256. this.lblPdo1Data2.Name = "lblPdo1Data2";
  257. this.lblPdo1Data2.Size = new System.Drawing.Size(47, 15);
  258. this.lblPdo1Data2.TabIndex = 2;
  259. this.lblPdo1Data2.Text = "Byte2:";
  260. //
  261. // lblPdo1Data3
  262. //
  263. this.lblPdo1Data3.AutoSize = true;
  264. this.lblPdo1Data3.Location = new System.Drawing.Point(250, 25);
  265. this.lblPdo1Data3.Name = "lblPdo1Data3";
  266. this.lblPdo1Data3.Size = new System.Drawing.Size(47, 15);
  267. this.lblPdo1Data3.TabIndex = 3;
  268. this.lblPdo1Data3.Text = "Byte3:";
  269. //
  270. // lblPdo1Data4
  271. //
  272. this.lblPdo1Data4.AutoSize = true;
  273. this.lblPdo1Data4.Location = new System.Drawing.Point(330, 25);
  274. this.lblPdo1Data4.Name = "lblPdo1Data4";
  275. this.lblPdo1Data4.Size = new System.Drawing.Size(47, 15);
  276. this.lblPdo1Data4.TabIndex = 4;
  277. this.lblPdo1Data4.Text = "Byte4:";
  278. //
  279. // lblPdo1Data5
  280. //
  281. this.lblPdo1Data5.AutoSize = true;
  282. this.lblPdo1Data5.Location = new System.Drawing.Point(410, 25);
  283. this.lblPdo1Data5.Name = "lblPdo1Data5";
  284. this.lblPdo1Data5.Size = new System.Drawing.Size(47, 15);
  285. this.lblPdo1Data5.TabIndex = 5;
  286. this.lblPdo1Data5.Text = "Byte5:";
  287. //
  288. // lblPdo1Data6
  289. //
  290. this.lblPdo1Data6.AutoSize = true;
  291. this.lblPdo1Data6.Location = new System.Drawing.Point(490, 25);
  292. this.lblPdo1Data6.Name = "lblPdo1Data6";
  293. this.lblPdo1Data6.Size = new System.Drawing.Size(47, 15);
  294. this.lblPdo1Data6.TabIndex = 6;
  295. this.lblPdo1Data6.Text = "Byte6:";
  296. //
  297. // lblPdo1Data7
  298. //
  299. this.lblPdo1Data7.AutoSize = true;
  300. this.lblPdo1Data7.Location = new System.Drawing.Point(570, 25);
  301. this.lblPdo1Data7.Name = "lblPdo1Data7";
  302. this.lblPdo1Data7.Size = new System.Drawing.Size(47, 15);
  303. this.lblPdo1Data7.TabIndex = 7;
  304. this.lblPdo1Data7.Text = "Byte7:";
  305. //
  306. // txtPdo1Data0
  307. //
  308. this.txtPdo1Data0.Location = new System.Drawing.Point(10, 45);
  309. this.txtPdo1Data0.MaxLength = 3;
  310. this.txtPdo1Data0.Name = "txtPdo1Data0";
  311. this.txtPdo1Data0.Size = new System.Drawing.Size(60, 25);
  312. this.txtPdo1Data0.TabIndex = 8;
  313. this.txtPdo1Data0.Text = "0";
  314. this.txtPdo1Data0.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  315. //
  316. // txtPdo1Data1
  317. //
  318. this.txtPdo1Data1.Location = new System.Drawing.Point(90, 45);
  319. this.txtPdo1Data1.MaxLength = 3;
  320. this.txtPdo1Data1.Name = "txtPdo1Data1";
  321. this.txtPdo1Data1.Size = new System.Drawing.Size(60, 25);
  322. this.txtPdo1Data1.TabIndex = 9;
  323. this.txtPdo1Data1.Text = "255";
  324. this.txtPdo1Data1.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  325. //
  326. // txtPdo1Data2
  327. //
  328. this.txtPdo1Data2.Location = new System.Drawing.Point(170, 45);
  329. this.txtPdo1Data2.MaxLength = 3;
  330. this.txtPdo1Data2.Name = "txtPdo1Data2";
  331. this.txtPdo1Data2.Size = new System.Drawing.Size(60, 25);
  332. this.txtPdo1Data2.TabIndex = 10;
  333. this.txtPdo1Data2.Text = "255";
  334. this.txtPdo1Data2.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  335. //
  336. // txtPdo1Data3
  337. //
  338. this.txtPdo1Data3.Location = new System.Drawing.Point(250, 45);
  339. this.txtPdo1Data3.MaxLength = 3;
  340. this.txtPdo1Data3.Name = "txtPdo1Data3";
  341. this.txtPdo1Data3.Size = new System.Drawing.Size(60, 25);
  342. this.txtPdo1Data3.TabIndex = 11;
  343. this.txtPdo1Data3.Text = "255";
  344. this.txtPdo1Data3.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  345. //
  346. // txtPdo1Data4
  347. //
  348. this.txtPdo1Data4.Location = new System.Drawing.Point(330, 45);
  349. this.txtPdo1Data4.MaxLength = 3;
  350. this.txtPdo1Data4.Name = "txtPdo1Data4";
  351. this.txtPdo1Data4.Size = new System.Drawing.Size(60, 25);
  352. this.txtPdo1Data4.TabIndex = 12;
  353. this.txtPdo1Data4.Text = "255";
  354. this.txtPdo1Data4.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  355. //
  356. // txtPdo1Data5
  357. //
  358. this.txtPdo1Data5.Location = new System.Drawing.Point(410, 45);
  359. this.txtPdo1Data5.MaxLength = 3;
  360. this.txtPdo1Data5.Name = "txtPdo1Data5";
  361. this.txtPdo1Data5.Size = new System.Drawing.Size(60, 25);
  362. this.txtPdo1Data5.TabIndex = 13;
  363. this.txtPdo1Data5.Text = "255";
  364. this.txtPdo1Data5.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  365. //
  366. // txtPdo1Data6
  367. //
  368. this.txtPdo1Data6.Location = new System.Drawing.Point(490, 45);
  369. this.txtPdo1Data6.MaxLength = 3;
  370. this.txtPdo1Data6.Name = "txtPdo1Data6";
  371. this.txtPdo1Data6.Size = new System.Drawing.Size(60, 25);
  372. this.txtPdo1Data6.TabIndex = 14;
  373. this.txtPdo1Data6.Text = "255";
  374. this.txtPdo1Data6.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  375. //
  376. // txtPdo1Data7
  377. //
  378. this.txtPdo1Data7.Location = new System.Drawing.Point(570, 45);
  379. this.txtPdo1Data7.MaxLength = 3;
  380. this.txtPdo1Data7.Name = "txtPdo1Data7";
  381. this.txtPdo1Data7.Size = new System.Drawing.Size(60, 25);
  382. this.txtPdo1Data7.TabIndex = 15;
  383. this.txtPdo1Data7.Text = "255";
  384. this.txtPdo1Data7.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  385. //
  386. // btnSendTpdo1
  387. //
  388. this.btnSendTpdo1.Enabled = false;
  389. this.btnSendTpdo1.Location = new System.Drawing.Point(10, 95);
  390. this.btnSendTpdo1.Name = "btnSendTpdo1";
  391. this.btnSendTpdo1.Size = new System.Drawing.Size(100, 25);
  392. this.btnSendTpdo1.TabIndex = 16;
  393. this.btnSendTpdo1.Text = "立即发送";
  394. this.btnSendTpdo1.Click += new System.EventHandler(this.BtnSendTpdo1_Click);
  395. //
  396. // btnRuleSend
  397. //
  398. this.btnRuleSend.Enabled = false;
  399. this.btnRuleSend.Location = new System.Drawing.Point(120, 95);
  400. this.btnRuleSend.Name = "btnRuleSend";
  401. this.btnRuleSend.Size = new System.Drawing.Size(100, 25);
  402. this.btnRuleSend.TabIndex = 17;
  403. this.btnRuleSend.Text = "规则发送";
  404. this.btnRuleSend.Click += new System.EventHandler(this.BtnRuleSend_Click);
  405. //
  406. // btnContinuousSend
  407. //
  408. this.btnContinuousSend.Enabled = false;
  409. this.btnContinuousSend.Location = new System.Drawing.Point(230, 95);
  410. this.btnContinuousSend.Name = "btnContinuousSend";
  411. this.btnContinuousSend.Size = new System.Drawing.Size(100, 25);
  412. this.btnContinuousSend.TabIndex = 18;
  413. this.btnContinuousSend.Text = "连续发送";
  414. this.btnContinuousSend.Click += new System.EventHandler(this.BtnContinuousSend_Click);
  415. //
  416. // lblPeriodTime
  417. //
  418. this.lblPeriodTime.AutoSize = true;
  419. this.lblPeriodTime.Location = new System.Drawing.Point(350, 98);
  420. this.lblPeriodTime.Name = "lblPeriodTime";
  421. this.lblPeriodTime.Size = new System.Drawing.Size(77, 15);
  422. this.lblPeriodTime.TabIndex = 19;
  423. this.lblPeriodTime.Text = "周期时间:";
  424. //
  425. // txtPeriodTime
  426. //
  427. this.txtPeriodTime.Location = new System.Drawing.Point(430, 95);
  428. this.txtPeriodTime.Name = "txtPeriodTime";
  429. this.txtPeriodTime.Size = new System.Drawing.Size(80, 25);
  430. this.txtPeriodTime.TabIndex = 20;
  431. this.txtPeriodTime.Text = "1000";
  432. this.txtPeriodTime.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  433. //
  434. // lblPeriodUnit
  435. //
  436. this.lblPeriodUnit.AutoSize = true;
  437. this.lblPeriodUnit.Location = new System.Drawing.Point(520, 98);
  438. this.lblPeriodUnit.Name = "lblPeriodUnit";
  439. this.lblPeriodUnit.Size = new System.Drawing.Size(23, 15);
  440. this.lblPeriodUnit.TabIndex = 21;
  441. this.lblPeriodUnit.Text = "us";
  442. //
  443. // lblRuleDuration
  444. //
  445. this.lblRuleDuration.AutoSize = true;
  446. this.lblRuleDuration.Location = new System.Drawing.Point(350, 128);
  447. this.lblRuleDuration.Name = "lblRuleDuration";
  448. this.lblRuleDuration.Size = new System.Drawing.Size(77, 15);
  449. this.lblRuleDuration.TabIndex = 22;
  450. this.lblRuleDuration.Text = "持续时间:";
  451. //
  452. // txtRuleDuration
  453. //
  454. this.txtRuleDuration.Location = new System.Drawing.Point(430, 125);
  455. this.txtRuleDuration.Name = "txtRuleDuration";
  456. this.txtRuleDuration.Size = new System.Drawing.Size(80, 25);
  457. this.txtRuleDuration.TabIndex = 23;
  458. this.txtRuleDuration.Text = "10";
  459. this.txtRuleDuration.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
  460. //
  461. // lblRuleDurationUnit
  462. //
  463. this.lblRuleDurationUnit.AutoSize = true;
  464. this.lblRuleDurationUnit.Location = new System.Drawing.Point(520, 128);
  465. this.lblRuleDurationUnit.Name = "lblRuleDurationUnit";
  466. this.lblRuleDurationUnit.Size = new System.Drawing.Size(23, 15);
  467. this.lblRuleDurationUnit.TabIndex = 24;
  468. this.lblRuleDurationUnit.Text = "秒";
  469. //
  470. // txtLog
  471. //
  472. this.txtLog.BackColor = System.Drawing.Color.White;
  473. this.txtLog.Font = new System.Drawing.Font("微软雅黑", 9F);
  474. this.txtLog.ForeColor = System.Drawing.Color.Black;
  475. this.txtLog.Location = new System.Drawing.Point(10, 260);
  476. this.txtLog.Multiline = true;
  477. this.txtLog.Name = "txtLog";
  478. this.txtLog.ReadOnly = true;
  479. this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Vertical;
  480. this.txtLog.Size = new System.Drawing.Size(770, 270);
  481. this.txtLog.TabIndex = 3;
  482. //
  483. // m_updateTimer
  484. //
  485. this.m_updateTimer.Enabled = true;
  486. this.m_updateTimer.Interval = 500;
  487. //
  488. // SlaveTestForm
  489. //
  490. this.ClientSize = new System.Drawing.Size(782, 543);
  491. this.Controls.Add(this.grpDeviceInfo);
  492. this.Controls.Add(this.grpControl);
  493. this.Controls.Add(this.grpPdo1Send);
  494. this.Controls.Add(this.txtLog);
  495. this.Name = "SlaveTestForm";
  496. this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
  497. this.Text = "CANopen从站设备测试";
  498. this.grpDeviceInfo.ResumeLayout(false);
  499. this.grpDeviceInfo.PerformLayout();
  500. this.grpControl.ResumeLayout(false);
  501. this.grpPdo1Send.ResumeLayout(false);
  502. this.grpPdo1Send.PerformLayout();
  503. this.ResumeLayout(false);
  504. this.PerformLayout();
  505. }
  506. private void BtnStart_Click(object sender, EventArgs e)
  507. {
  508. try
  509. {
  510. AppendLog("正在启动从站...");
  511. if (m_slaveDevice.Start(CanBaudRate.BaudRate_1M))
  512. {
  513. AppendLog("✓ 从站启动成功");
  514. btnStart.Enabled = false;
  515. btnStop.Enabled = true;
  516. btnSendTpdo1.Enabled = true;
  517. btnRuleSend.Enabled = true;
  518. btnContinuousSend.Enabled = true;
  519. UpdateDeviceInfo();
  520. }
  521. else
  522. {
  523. AppendLog("✗ 从站启动失败");
  524. MessageBox.Show("启动失败!请检查CAN设备连接。", "错误",
  525. MessageBoxButtons.OK, MessageBoxIcon.Error);
  526. }
  527. }
  528. catch (Exception ex)
  529. {
  530. AppendLog("✗ 启动错误: " + ex.Message);
  531. MessageBox.Show(ex.Message, "错误",
  532. MessageBoxButtons.OK, MessageBoxIcon.Error);
  533. }
  534. }
  535. private void BtnStop_Click(object sender, EventArgs e)
  536. {
  537. try
  538. {
  539. // 如果正在连续发送,先停止
  540. if (m_isContinuousSending)
  541. {
  542. StopContinuousSend();
  543. }
  544. // 如果正在规则发送,先停止
  545. if (m_isRuleSending)
  546. {
  547. StopRuleSend();
  548. }
  549. AppendLog("正在停止从站...");
  550. m_slaveDevice.Stop();
  551. AppendLog("✓ 从站已停止");
  552. btnStart.Enabled = true;
  553. btnStop.Enabled = false;
  554. btnSendTpdo1.Enabled = false;
  555. btnRuleSend.Enabled = false;
  556. btnContinuousSend.Enabled = false;
  557. btnContinuousSend.Text = "连续发送";
  558. btnRuleSend.Text = "规则发送";
  559. UpdateDeviceInfo();
  560. }
  561. catch (Exception ex)
  562. {
  563. AppendLog("✗ 停止错误: " + ex.Message);
  564. }
  565. }
  566. private void BtnSendTpdo1_Click(object sender, EventArgs e)
  567. {
  568. try
  569. {
  570. // 从界面读取PDO1数据
  571. byte[] pdoData = new byte[8];
  572. pdoData[0] = ParseByteValue(txtPdo1Data0.Text, 0);
  573. pdoData[1] = ParseByteValue(txtPdo1Data1.Text, 1);
  574. pdoData[2] = ParseByteValue(txtPdo1Data2.Text, 2);
  575. pdoData[3] = ParseByteValue(txtPdo1Data3.Text, 3);
  576. pdoData[4] = ParseByteValue(txtPdo1Data4.Text, 4);
  577. pdoData[5] = ParseByteValue(txtPdo1Data5.Text, 5);
  578. pdoData[6] = ParseByteValue(txtPdo1Data6.Text, 6);
  579. pdoData[7] = ParseByteValue(txtPdo1Data7.Text, 7);
  580. // 构建数据字符串用于日志
  581. string dataStr = BitConverter.ToString(pdoData).Replace("-", " ");
  582. AppendLog("→ 发送TPDO1: [" + dataStr + "]");
  583. // TODO: 这里需要修改CanOpenSlaveDevice以支持自定义数据发送
  584. // 暂时使用默认发送方法
  585. m_slaveDevice.SendTpdo(1, pdoData);
  586. }
  587. catch (Exception ex)
  588. {
  589. AppendLog("✗ 发送TPDO1失败: " + ex.Message);
  590. }
  591. }
  592. private void BtnContinuousSend_Click(object sender, EventArgs e)
  593. {
  594. if (m_isContinuousSending)
  595. {
  596. // 停止连续发送
  597. StopContinuousSend();
  598. }
  599. else
  600. {
  601. // 启动连续发送
  602. StartContinuousSend();
  603. }
  604. }
  605. /// <summary>
  606. /// 规则发送按钮点击事件
  607. /// </summary>
  608. private void BtnRuleSend_Click(object sender, EventArgs e)
  609. {
  610. if (m_isRuleSending)
  611. {
  612. // 停止规则发送
  613. StopRuleSend();
  614. }
  615. else
  616. {
  617. // 启动规则发送
  618. StartRuleSend();
  619. }
  620. }
  621. /// <summary>
  622. /// 启动连续发送
  623. /// </summary>
  624. private void StartContinuousSend()
  625. {
  626. try
  627. {
  628. // 解析周期时间(微秒)
  629. if (!double.TryParse(txtPeriodTime.Text.Trim(), out double periodUs) || periodUs <= 0)
  630. {
  631. AppendLog("✗ 周期时间无效,请输入大于0的数值(单位:微秒)");
  632. return;
  633. }
  634. // 重置计数器
  635. m_continuousSendCounter = 0;
  636. // 创建取消令牌
  637. m_continuousSendCts = new CancellationTokenSource();
  638. m_isContinuousSending = true;
  639. // 更新按钮文本
  640. btnContinuousSend.Text = "停止发送";
  641. AppendLog("▶ 开始连续发送 - 周期: " + periodUs.ToString("F0") + "us");
  642. // 启动异步发送任务
  643. Task.Run(() => ContinuousSendLoop(periodUs, m_continuousSendCts.Token));
  644. }
  645. catch (Exception ex)
  646. {
  647. AppendLog("✗ 启动连续发送失败: " + ex.Message);
  648. m_isContinuousSending = false;
  649. btnContinuousSend.Text = "连续发送";
  650. }
  651. }
  652. /// <summary>
  653. /// 停止连续发送
  654. /// </summary>
  655. private void StopContinuousSend()
  656. {
  657. try
  658. {
  659. m_isContinuousSending = false;
  660. m_continuousSendCts?.Cancel();
  661. // 等待任务结束
  662. Thread.Sleep(100);
  663. btnContinuousSend.Text = "连续发送";
  664. AppendLog("⏹ 连续发送已停止 - 共发送 " + m_continuousSendCounter.ToString() + " 次");
  665. }
  666. catch (Exception ex)
  667. {
  668. AppendLog("✗ 停止连续发送失败: " + ex.Message);
  669. }
  670. }
  671. /// <summary>
  672. /// 连续发送循环(高精度)
  673. /// </summary>
  674. private async Task ContinuousSendLoop(double periodUs, CancellationToken cancellationToken)
  675. {
  676. Stopwatch stopwatch = new Stopwatch();
  677. long sendCount = 0;
  678. // 计算每次循环的目标时间间隔(纳秒)
  679. long periodNs = (long)(periodUs * 1000.0);
  680. AppendLog("[调试] 目标周期: " + periodUs.ToString("F0") + "us = " + periodNs.ToString() + "ns");
  681. while (!cancellationToken.IsCancellationRequested)
  682. {
  683. try
  684. {
  685. // 记录开始时间
  686. stopwatch.Restart();
  687. // 构建PDO数据 - 第一个字节自增
  688. byte[] pdoData = new byte[8];
  689. pdoData[0] = (byte)(m_continuousSendCounter % 256); // 第一个字节自增
  690. pdoData[1] = ParseByteValue(txtPdo1Data1.Text, 1);
  691. pdoData[2] = ParseByteValue(txtPdo1Data2.Text, 2);
  692. pdoData[3] = ParseByteValue(txtPdo1Data3.Text, 3);
  693. pdoData[4] = ParseByteValue(txtPdo1Data4.Text, 4);
  694. pdoData[5] = ParseByteValue(txtPdo1Data5.Text, 5);
  695. pdoData[6] = ParseByteValue(txtPdo1Data6.Text, 6);
  696. pdoData[7] = ParseByteValue(txtPdo1Data7.Text, 7);
  697. // 发送PDO(注意:这里需要修改CanOpenSlaveDevice支持自定义数据)
  698. // 暂时使用默认方法,实际应该传入pdoData
  699. m_slaveDevice.SendTpdo(1, pdoData);
  700. sendCount++;
  701. m_continuousSendCounter++; // 计数器自增
  702. // 每100次输出一次日志,避免日志过多
  703. if (sendCount % 100 == 0)
  704. {
  705. AppendLog("→ 连续发送 #" + sendCount.ToString() + ", Byte0=" + m_continuousSendCounter.ToString());
  706. }
  707. // 计算已经消耗的时间
  708. long elapsedNs = stopwatch.ElapsedTicks * 1000000000L / Stopwatch.Frequency;
  709. long remainingNs = periodNs - elapsedNs;
  710. // 如果还有剩余时间,进行精确延迟
  711. if (remainingNs > 0)
  712. {
  713. // 对于较大的延迟,使用Task.Delay
  714. if (remainingNs > 100000) // 大于100微秒
  715. {
  716. int delayMs = (int)(remainingNs / 1000000);
  717. await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
  718. }
  719. else
  720. {
  721. // 对于较小的延迟,使用SpinWait
  722. SpinWait.SpinUntil(() => cancellationToken.IsCancellationRequested,
  723. TimeSpan.FromTicks(remainingNs * Stopwatch.Frequency / 1000000000L));
  724. }
  725. }
  726. }
  727. catch (OperationCanceledException)
  728. {
  729. // 正常取消
  730. break;
  731. }
  732. catch (Exception ex)
  733. {
  734. AppendLog("✗ 连续发送错误: " + ex.Message);
  735. break;
  736. }
  737. }
  738. }
  739. private byte ParseByteValue(string text, int index)
  740. {
  741. if (string.IsNullOrWhiteSpace(text))
  742. return 0;
  743. if (byte.TryParse(text.Trim(), out byte value))
  744. return value;
  745. AppendLog("⚠ Byte" + index.ToString() + " 值无效: '" + text + "',使用默认值0");
  746. return 0;
  747. }
  748. /// <summary>
  749. /// 根据规则发送TPDO1(第一字节自增)
  750. /// </summary>
  751. private void SendRuleBasedTpdo1()
  752. {
  753. // 检查是否达到运行时长限制
  754. if (m_ruleSendDurationMs > 0)
  755. {
  756. double elapsedMs = (DateTime.Now - m_ruleSendStartTime).TotalMilliseconds;
  757. if (elapsedMs >= m_ruleSendDurationMs)
  758. {
  759. StopRuleSend();
  760. return;
  761. }
  762. }
  763. try
  764. {
  765. // 构建PDO数据 - 第一个字节自增
  766. byte[] pdoData = new byte[8];
  767. pdoData[0] = (byte)(m_ruleSendCounter % 256); // 第一个字节自增
  768. pdoData[1] = ParseByteValue(txtPdo1Data1.Text, 1);
  769. pdoData[2] = ParseByteValue(txtPdo1Data2.Text, 2);
  770. pdoData[3] = ParseByteValue(txtPdo1Data3.Text, 3);
  771. pdoData[4] = ParseByteValue(txtPdo1Data4.Text, 4);
  772. pdoData[5] = ParseByteValue(txtPdo1Data5.Text, 5);
  773. pdoData[6] = ParseByteValue(txtPdo1Data6.Text, 6);
  774. pdoData[7] = ParseByteValue(txtPdo1Data7.Text, 7);
  775. // 发送PDO
  776. m_slaveDevice.SendTpdo(1, pdoData);
  777. // 计数器自增
  778. m_ruleSendCounter++;
  779. // 输出日志
  780. string dataStr = BitConverter.ToString(pdoData).Replace("-", " ");
  781. AppendLog("→ 规则发送 #" + m_ruleSendCounter.ToString() + ", Byte0=" + ((m_ruleSendCounter - 1) % 256).ToString() + ", Data: [" + dataStr + "]");
  782. }
  783. catch (Exception ex)
  784. {
  785. AppendLog("✗ 规则发送失败: " + ex.Message);
  786. }
  787. }
  788. private void SlaveDevice_OnRpdoReceived(byte nodeId, byte pdoNumber, byte[] data)
  789. {
  790. if (this.InvokeRequired)
  791. {
  792. this.Invoke(new Action(() =>
  793. SlaveDevice_OnRpdoReceived(nodeId, pdoNumber, data)));
  794. return;
  795. }
  796. string dataStr = BitConverter.ToString(data).Replace("-", " ");
  797. AppendLog("← 收到RPDO" + pdoNumber.ToString() + " [节点" + nodeId.ToString() + "]: " + dataStr);
  798. // 如果启用了规则发送,则发送TPDO1
  799. if (m_isRuleSending)
  800. {
  801. SendRuleBasedTpdo1();
  802. }
  803. }
  804. private void SlaveDevice_OnNmtStateChanged(NmtState oldState, NmtState newState)
  805. {
  806. if (this.InvokeRequired)
  807. {
  808. this.Invoke(new Action(() =>
  809. SlaveDevice_OnNmtStateChanged(oldState, newState)));
  810. return;
  811. }
  812. AppendLog("⟳ NMT状态改变: " + oldState.ToString() + " → " + newState.ToString());
  813. UpdateDeviceInfo();
  814. }
  815. private void SlaveDevice_OnSdoReadRequest(byte nodeId, ushort index, byte subIndex)
  816. {
  817. if (this.InvokeRequired)
  818. {
  819. this.Invoke(new Action(() =>
  820. SlaveDevice_OnSdoReadRequest(nodeId, index, subIndex)));
  821. return;
  822. }
  823. AppendLog("? SDO读取请求: 索引0x" + index.ToString("X4") + ", 子索引" + subIndex.ToString());
  824. }
  825. private void SlaveDevice_OnSdoWriteRequest(byte nodeId, ushort index, byte subIndex, uint value)
  826. {
  827. if (this.InvokeRequired)
  828. {
  829. this.Invoke(new Action(() =>
  830. SlaveDevice_OnSdoWriteRequest(nodeId, index, subIndex, value)));
  831. return;
  832. }
  833. AppendLog("↓ SDO写入请求: 索引0x" + index.ToString("X4") + ", 子索引" + subIndex.ToString() + ", 值0x" + value.ToString("X8"));
  834. }
  835. private void UpdateTimer_Tick(object sender, EventArgs e)
  836. {
  837. UpdateDeviceInfo();
  838. }
  839. private void UpdateDeviceInfo()
  840. {
  841. if (this.InvokeRequired)
  842. {
  843. this.Invoke(new Action(UpdateDeviceInfo));
  844. return;
  845. }
  846. lblNodeId.Text = "节点ID: " + m_slaveDevice.NodeId.ToString();
  847. lblState.Text = "状态: " + m_slaveDevice.CurrentState.ToString();
  848. lblStatus.Text = "运行状态: " + (m_slaveDevice.IsRunning ? "运行中" : "已停止");
  849. }
  850. private void AppendLog(string message)
  851. {
  852. if (this.InvokeRequired)
  853. {
  854. this.Invoke(new Action<string>(AppendLog), message);
  855. return;
  856. }
  857. string timestamp = DateTime.Now.ToString("HH:mm:ss.fff");
  858. // 使用字符串拼接而非插值,确保兼容性
  859. txtLog.AppendText("[" + timestamp + "] " + message + "\r\n");
  860. // 自动滚动到底部
  861. txtLog.SelectionStart = txtLog.Text.Length;
  862. txtLog.ScrollToCaret();
  863. }
  864. protected override void OnFormClosing(FormClosingEventArgs e)
  865. {
  866. // 如果正在连续发送,先停止
  867. if (m_isContinuousSending)
  868. {
  869. StopContinuousSend();
  870. }
  871. // 如果正在规则发送,先停止
  872. if (m_isRuleSending)
  873. {
  874. StopRuleSend();
  875. }
  876. if (m_slaveDevice != null)
  877. {
  878. m_slaveDevice.Stop();
  879. m_slaveDevice.Dispose();
  880. }
  881. m_continuousSendCts?.Dispose();
  882. m_updateTimer?.Stop();
  883. m_updateTimer?.Dispose();
  884. base.OnFormClosing(e);
  885. }
  886. /// <summary>
  887. /// 启动规则发送
  888. /// </summary>
  889. private void StartRuleSend()
  890. {
  891. try
  892. {
  893. // 解析运行时长(秒转毫秒),如果输入为空或无效,默认为0(无限制)
  894. int durationSeconds = 0;
  895. if (int.TryParse(txtRuleDuration.Text.Trim(), out int parsedSeconds))
  896. {
  897. durationSeconds = Math.Max(0, parsedSeconds); // 确保非负数
  898. }
  899. m_ruleSendDurationMs = durationSeconds * 1000; // 转换为毫秒
  900. // 重置计数器
  901. m_ruleSendCounter = 0;
  902. m_ruleSendStartTime = DateTime.Now;
  903. m_isRuleSending = true;
  904. m_ruleSendCts = new CancellationTokenSource();
  905. // 更新按钮文本
  906. btnRuleSend.Text = "停止规则发送";
  907. string durationText = durationSeconds > 0 ? durationSeconds.ToString() + "秒" : "无限制";
  908. AppendLog("▶ 开始规则发送 - 时长: " + durationText);
  909. // 如果设置了运行时长,则启动定时器来检查时间
  910. if (m_ruleSendDurationMs > 0)
  911. {
  912. Task.Delay(m_ruleSendDurationMs, m_ruleSendCts.Token)
  913. .ContinueWith(t =>
  914. {
  915. if (!t.IsCanceled)
  916. {
  917. // 时间到了,自动停止规则发送
  918. if (this.InvokeRequired)
  919. {
  920. this.Invoke(new Action(StopRuleSend));
  921. }
  922. else
  923. {
  924. StopRuleSend();
  925. }
  926. }
  927. });
  928. }
  929. }
  930. catch (Exception ex)
  931. {
  932. AppendLog("✗ 启动规则发送失败: " + ex.Message);
  933. m_isRuleSending = false;
  934. btnRuleSend.Text = "规则发送";
  935. }
  936. }
  937. /// <summary>
  938. /// 停止规则发送
  939. /// </summary>
  940. private void StopRuleSend()
  941. {
  942. try
  943. {
  944. m_isRuleSending = false;
  945. m_ruleSendCts?.Cancel();
  946. btnRuleSend.Text = "规则发送";
  947. AppendLog("⏹ 规则发送已停止 - 共发送 " + m_ruleSendCounter.ToString() + " 次");
  948. }
  949. catch (Exception ex)
  950. {
  951. AppendLog("✗ 停止规则发送失败: " + ex.Message);
  952. }
  953. }
  954. }
  955. }