Protocol.cs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. using System;
  2. using System.Collections.Generic;
  3. namespace AutoFocusTool.Serial
  4. {
  5. /// <summary>
  6. /// 下位机串口协议帧构造 + 校验。完全依据原工程 Commander.cs 实测确证:
  7. /// 帧: [0]帧头0x5E [1]命令码 [2]序号0x00 [3]整帧长 [4..n-2]辅助码+参数 [n-1]累加和校验
  8. /// 校验 = 前 n-1 字节逐字节相加(byte溢出截断)放末字节。无独立帧尾。
  9. /// 电机绝对运动脉冲值: 32位int, 大端写入帧(高字节在前)。
  10. /// </summary>
  11. public static class Protocol
  12. {
  13. public const byte ST = 0x5E; // 帧头
  14. public const byte CMD_MOTOR = 0x05; // 电机控制
  15. public const byte CMD_IO = 0x09; // 设IO(含LED)
  16. public const byte CMD_READ_MOTOR = 0x18; // 读电机位置
  17. public const byte CMD_SHAKE = 0x01; // 握手
  18. public const byte CMD_READ_EEPROM = 0x11; // 读EEPROM(回复10字节)
  19. /// <summary>累加和校验:前 n-1 字节求和写入末字节</summary>
  20. public static byte[] WithChecksum(byte[] frame)
  21. {
  22. byte sum = 0;
  23. for (int i = 0; i < frame.Length - 1; i++) sum += frame[i];
  24. frame[frame.Length - 1] = sum;
  25. return frame;
  26. }
  27. /// <summary>校验回复帧(末字节==前面累加和)</summary>
  28. public static bool CheckChecksum(byte[] frame)
  29. {
  30. if (frame == null || frame.Length < 2) return false;
  31. byte sum = 0;
  32. for (int i = 0; i < frame.Length - 1; i++) sum += frame[i];
  33. return frame[frame.Length - 1] == sum;
  34. }
  35. /// <summary>
  36. /// 根据命令码返回下位机回复的固定字节数(依据 Commander.CustomProtocolLength)。
  37. /// 接收端按这个长度收帧,不靠帧尾。
  38. /// </summary>
  39. public static int ReplyLength(byte cmd)
  40. {
  41. switch (cmd)
  42. {
  43. case 0x01: return 6; // 握手
  44. case 0x02: return 6; // 自检
  45. case 0x04: return 7; // 设目标温度
  46. case 0x05: return 6; // 电机控制
  47. case 0x06: return 9; // 读传感器
  48. case 0x08: return 9;
  49. case 0x09: return 6; // 设IO(LED开关)
  50. case 0x10: return 7;
  51. case 0x11: return 10; // 读EEPROM
  52. case 0x12: return 6;
  53. case 0x18: return 10; // 读电机位置
  54. case 0x19: return 6;
  55. case 0x20: return 12;
  56. default: return 6;
  57. }
  58. }
  59. // ── 握手:5E 01 00 05 00 + 校验 ──
  60. public static byte[] ShakeHands()
  61. => WithChecksum(new byte[] { ST, 0x01, 0x00, 0x05, 0x00 });
  62. // ── 读舱室绑定的相机序列号 CCDSN(读EEPROM,原工程 GetModule)──
  63. // 5E 11 00 09 00 00 10 04 00 + 校验,回复10字节,[4..7]拼int32
  64. public static byte[] GetCCDSN()
  65. => WithChecksum(new byte[] { ST, 0x11, 0x00, 0x09, 0x00, 0x00, 0x10, 0x04, 0x00 });
  66. // ── 读EEPROM灯光亮度(只读,无写命令)──
  67. // 5E 11 00 09 00 05 34 04 00 + 校验
  68. public static byte[] ReadLightBrightness()
  69. => WithChecksum(new byte[] { ST, 0x11, 0x00, 0x09, 0x00, 0x05, 0x34, 0x04, 0x00 });
  70. /// <summary>
  71. /// 读第 wellNum(1-16) 个well的水平电机位置(旋转脉冲,存EEPROM)。
  72. /// 培养皿16个well圆形布局,转动培养皿让well对准相机。地址表来自原工程 Commander.cs:284。
  73. /// 命令: 5E 11 00 09 00 04 [地址低] 04 00 + 校验,回复10字节,位置在[4..7]。
  74. /// </summary>
  75. public static byte[] ReadWellHorizontalPos(int wellNum)
  76. {
  77. // wellNum → EEPROM 地址低字节(确证自源码)
  78. byte addr;
  79. switch (wellNum)
  80. {
  81. case 1: addr = 0x00; break;
  82. case 2: addr = 0x50; break;
  83. case 3: addr = 0x54; break;
  84. case 4: addr = 0x58; break;
  85. case 5: addr = 0x5C; break;
  86. case 6: addr = 0x60; break;
  87. case 7: addr = 0x64; break;
  88. case 8: addr = 0x68; break;
  89. case 9: addr = 0x6C; break;
  90. case 10: addr = 0x70; break;
  91. case 11: addr = 0x74; break;
  92. case 12: addr = 0x78; break;
  93. case 13: addr = 0x7C; break;
  94. case 14: addr = 0x80; break;
  95. case 15: addr = 0x84; break;
  96. case 16: addr = 0x04; break;
  97. default: addr = 0x00; break;
  98. }
  99. return WithChecksum(new byte[] { ST, 0x11, 0x00, 0x09, 0x00, 0x04, addr, 0x04, 0x00 });
  100. }
  101. /// <summary>
  102. /// 读第 wellNum(1-16) 个well的垂直电机焦准脉冲数(Z对焦零点,存EEPROM)。
  103. /// 地址表来自原工程 Commander.cs:530。well1=0x08, 步进4, well16=0x44。
  104. /// </summary>
  105. public static byte[] ReadWellFocusZero(int wellNum)
  106. {
  107. byte addr;
  108. switch (wellNum)
  109. {
  110. case 1: addr = 0x08; break;
  111. case 2: addr = 0x0C; break;
  112. case 3: addr = 0x10; break;
  113. case 4: addr = 0x14; break;
  114. case 5: addr = 0x18; break;
  115. case 6: addr = 0x1C; break;
  116. case 7: addr = 0x20; break;
  117. case 8: addr = 0x24; break;
  118. case 9: addr = 0x28; break;
  119. case 10: addr = 0x2C; break;
  120. case 11: addr = 0x30; break;
  121. case 12: addr = 0x34; break;
  122. case 13: addr = 0x38; break;
  123. case 14: addr = 0x3C; break;
  124. case 15: addr = 0x40; break;
  125. case 16: addr = 0x44; break;
  126. default: addr = 0x08; break;
  127. }
  128. return WithChecksum(new byte[] { ST, 0x11, 0x00, 0x09, 0x00, 0x04, addr, 0x04, 0x00 });
  129. }
  130. /// <summary>读垂直电机扫描间隔脉冲(每层Z步距,存EEPROM)。Commander.cs:668。</summary>
  131. public static byte[] ReadScanStep()
  132. => WithChecksum(new byte[] { ST, 0x11, 0x00, 0x09, 0x00, 0x04, 0x48, 0x04, 0x00 });
  133. // ── LED 开/关(固定字节,原工程实测)──
  134. public static byte[] OpenLED()
  135. => new byte[] { 0x5E, 0x09, 0x00, 0x07, 0x00, 0x01, 0x6F };
  136. public static byte[] CloseLED()
  137. => new byte[] { 0x5E, 0x09, 0x00, 0x07, 0x00, 0x00, 0x6E };
  138. // ── 读电机位置:5E 18 00 06 [轴] 00 + 校验。轴: 0x01水平 0x02垂直 ──
  139. public static byte[] ReadHorizontalMotor()
  140. => WithChecksum(new byte[] { ST, 0x18, 0x00, 0x06, 0x01, 0x00 });
  141. public static byte[] ReadVerticalMotor()
  142. => WithChecksum(new byte[] { ST, 0x18, 0x00, 0x06, 0x02, 0x00 });
  143. // ── 电机复位(固定字节,原工程实测)──
  144. // 水平复位 5E 05 00 0B 13 00 00 00 0B B8 44
  145. public static byte[] HorizontalReset()
  146. => new byte[] { 0x5E, 0x05, 0x00, 0x0B, 0x13, 0x00, 0x00, 0x00, 0x0B, 0xB8, 0x44 };
  147. // 垂直复位 5E 05 00 0B 23 00 00 00 07 D0 68
  148. public static byte[] VerticalReset()
  149. => new byte[] { 0x5E, 0x05, 0x00, 0x0B, 0x23, 0x00, 0x00, 0x00, 0x07, 0xD0, 0x68 };
  150. // ── 电机绝对运动:11字节帧,辅助码 0x14水平 0x24垂直,脉冲32位大端 ──
  151. public static byte[] HorizontalAbsolute(int pulse) => MotorAbsolute(0x14, pulse);
  152. public static byte[] VerticalAbsolute(int pulse) => MotorAbsolute(0x24, pulse);
  153. // ── 电机相对正/反转:辅助码 水平0x10/0x11,垂直0x20/0x21 ──
  154. public static byte[] HorizontalForward(int pulse) => MotorAbsolute(0x10, pulse);
  155. public static byte[] HorizontalBackward(int pulse) => MotorAbsolute(0x11, pulse);
  156. public static byte[] VerticalForward(int pulse) => MotorAbsolute(0x20, pulse);
  157. public static byte[] VerticalBackward(int pulse) => MotorAbsolute(0x21, pulse);
  158. /// <summary>
  159. /// 构造电机运动帧:5E 05 00 0B [辅助码] [p3 p2 p1 p0 大端] 00 [校验]
  160. /// </summary>
  161. private static byte[] MotorAbsolute(byte auxCode, int pulse)
  162. {
  163. byte[] p = BitConverter.GetBytes(pulse); // 小端: p[0]低..p[3]高
  164. var cmd = new List<byte>
  165. {
  166. ST, CMD_MOTOR, 0x00, 0x0B, auxCode,
  167. p[3], p[2], p[1], p[0], // 大端写入:高字节在前
  168. 0x00, // 参数5
  169. 0x00 // 校验占位
  170. };
  171. return WithChecksum(cmd.ToArray());
  172. }
  173. /// <summary>解析读电机位置回复:字节[4..7]拼 int32(帧内大端)。失败 -1。</summary>
  174. public static int ParseMotorPosition(byte[] reply)
  175. {
  176. if (reply == null || reply.Length < 8) return -1;
  177. byte[] t = { reply[7], reply[6], reply[5], reply[4] };
  178. if (!BitConverter.IsLittleEndian) Array.Reverse(t);
  179. return BitConverter.ToInt32(t, 0);
  180. }
  181. /// <summary>解析握手回复:字节[2]即模块号/houseSn。</summary>
  182. public static int ParseShakeHands(byte[] reply)
  183. {
  184. if (reply == null || reply.Length < 3) return -1;
  185. return reply[2];
  186. }
  187. /// <summary>解析读EEPROM回复:字节[4..7]拼 int32(用于 CCDSN / 亮度)。失败 -1。</summary>
  188. public static int ParseEepromInt(byte[] reply)
  189. {
  190. if (reply == null || reply.Length < 8) return -1;
  191. byte[] t = { reply[4], reply[5], reply[6], reply[7] };
  192. if (!BitConverter.IsLittleEndian) Array.Reverse(t);
  193. return BitConverter.ToInt32(t, 0);
  194. }
  195. }
  196. }