25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

372 lines
11 KiB

  1. """PLSR 参数表-2026 的 Modbus RTU 地址、枚举和编解码工具。"""
  2. from enum import IntEnum
  3. from typing import Dict, List, Sequence, Tuple
  4. DEFAULT_SLAVE_ADDRESS = 1
  5. DEFAULT_BAUD_RATE = 115200
  6. MIN_FREQUENCY_HZ = 1
  7. MAX_FREQUENCY_HZ = 100000
  8. MAX_RAMP_TIME_MS = 0xFFFF
  9. class Register(IntEnum):
  10. PULSE_POINT = 0x1000
  11. DIRECTION_POINT = 0x1001
  12. WAIT_SIGNAL = 0x1002
  13. EXT_SIGNAL = 0x1003
  14. SEND_MODE = 0x1004
  15. DIRECTION_DELAY_MS = 0x1005
  16. DIRECTION_LOGIC = 0x1006
  17. PROFILE_TYPE = 0x1007
  18. RUN_MODE = 0x1008
  19. TOTAL_SEGMENTS = 0x1009
  20. START_SEGMENT = 0x100A
  21. TARGET_FREQ_LO = 0x100B
  22. TARGET_FREQ_HI = 0x100C
  23. START_FREQ_LO = 0x100D
  24. START_FREQ_HI = 0x100E
  25. OUTPUT_MODE = 0x100F
  26. END_FREQ_LO = 0x1010
  27. END_FREQ_HI = 0x1011
  28. ACCEL_TIME = 0x1012
  29. DECEL_TIME = 0x1013
  30. MAX_FREQ_LO = 0x1014
  31. MAX_FREQ_HI = 0x1015
  32. # 瞬时运行参数,不属于公共参数,也不保存到Flash。
  33. RUNTIME_FREQ_LO = 0x1016
  34. RUNTIME_FREQ_HI = 0x1017
  35. SEGMENT_TABLE_BASE = 0x1100
  36. CUMULATIVE_POSITION_LO = 0x2000
  37. CUMULATIVE_POSITION_HI = 0x2001
  38. CURRENT_FREQ_LO = 0x2002
  39. CURRENT_FREQ_HI = 0x2003
  40. STATE = 0x2004
  41. CURRENT_SEGMENT = 0x2005
  42. ERROR = 0x2006
  43. CURRENT_PULSES_LO = 0x2007
  44. CURRENT_PULSES_HI = 0x2008
  45. TOTAL_PULSES_LO = 0x2009
  46. TOTAL_PULSES_HI = 0x200A
  47. DIAG_STATUS = 0x200B
  48. DIAG_PROFILE_TYPE = 0x200C
  49. DIAG_PHASE = 0x200D
  50. DIAG_EXPECTED_PULSES_LO = 0x200E
  51. DIAG_EXPECTED_PULSES_HI = 0x200F
  52. DIAG_OBSERVED_PULSES_LO = 0x2010
  53. DIAG_OBSERVED_PULSES_HI = 0x2011
  54. DIAG_EXPECTED_FREQ_LO = 0x2012
  55. DIAG_EXPECTED_FREQ_HI = 0x2013
  56. DIAG_ACTUAL_FREQ_LO = 0x2014
  57. DIAG_ACTUAL_FREQ_HI = 0x2015
  58. DIAG_MAX_FREQ_ERROR_LO = 0x2016
  59. DIAG_MAX_FREQ_ERROR_HI = 0x2017
  60. DIAG_COUNT_ERRORS = 0x2018
  61. DIAG_FREQ_ERRORS = 0x2019
  62. DIAG_PROFILE_ERRORS = 0x201A
  63. CONTROL = 0x3000
  64. # 旧界面源码的兼容别名;实际通讯流程只使用上面的参数表地址。
  65. COMMAND = CONTROL
  66. DIRECTION = DIRECTION_LOGIC
  67. SEGMENT_META_BASE = SEGMENT_TABLE_BASE
  68. SEGMENT_PULSES_DONE_HI = CURRENT_FREQ_LO
  69. SEGMENT_PULSES_DONE_LO = CURRENT_FREQ_HI
  70. ACTIVE_WAIT = STATE
  71. WAIT_ELAPSED_HI = CURRENT_SEGMENT
  72. WAIT_ELAPSED_LO = ERROR
  73. SEGMENT_STRIDE = 0x10
  74. SEGMENT_USED_REGISTERS = 8
  75. def segment_base(index: int) -> int:
  76. if not 0 <= index < 10:
  77. raise ValueError("段索引必须为0~9")
  78. return int(Register.SEGMENT_TABLE_BASE) + index * SEGMENT_STRIDE
  79. def register_read_count(_first: Register = Register.PULSE_POINT) -> int:
  80. """全部公共参数连续块0x1000~0x1015的长度。"""
  81. return int(Register.MAX_FREQ_HI) - int(Register.PULSE_POINT) + 1
  82. class Command(IntEnum):
  83. START = 0x0001
  84. STOP = 0x0002
  85. CLEAR_POSITION = 0x0004
  86. RESUME = 0x0001 # 暂停状态下再次置START即继续
  87. # 兼容旧界面分支;新参数表没有这些独立命令码。
  88. CLEAR_TOTAL = 0x0008
  89. SAVE_PUBLIC = 0x0010
  90. SAVE_OUTPUTS = SAVE_PUBLIC # 兼容旧名称;设备端现在保存全部公共参数
  91. CLEAR_DIAGNOSTIC = 0x0020
  92. UPDATE_FREQUENCY = 0x0040
  93. START_SEGMENTS = 0x0001
  94. STATUS_REGISTER_COUNT = (
  95. int(Register.DIAG_PROFILE_ERRORS) - int(Register.CUMULATIVE_POSITION_LO) + 1
  96. )
  97. class DiagnosticStatus(IntEnum):
  98. ACTIVE = 0x0001
  99. COMPLETE = 0x0002
  100. COUNT_PASS = 0x0004
  101. FREQUENCY_PASS = 0x0008
  102. PROFILE_PASS = 0x0010
  103. COUNT_FAIL = 0x0100
  104. FREQUENCY_FAIL = 0x0200
  105. PROFILE_FAIL = 0x0400
  106. DIAGNOSTIC_PHASE_TEXT = {
  107. 0: "空闲",
  108. 1: "加速段",
  109. 2: "匀速段",
  110. 3: "减速段",
  111. }
  112. class State(IntEnum):
  113. IDLE = 0
  114. RUNNING = 1
  115. COMPLETE = 2
  116. ERROR = 3
  117. PAUSED = 4
  118. WAITING = 5
  119. class WaitCondition(IntEnum):
  120. WAIT_TIME = 0
  121. WAIT_SIGNAL = 1
  122. ACT_TIME = 2
  123. EXT_SIGNAL = 3
  124. EXT_OR_COMPLETE = 4
  125. class RunMode(IntEnum):
  126. RELATIVE = 0
  127. ABSOLUTE = 1
  128. class SendMode(IntEnum):
  129. COMPLETE = 0
  130. FOLLOW = 1
  131. class DirectionLogic(IntEnum):
  132. POSITIVE = 0
  133. NEGATIVE = 1
  134. class ProfileType(IntEnum):
  135. LINEAR = 0
  136. S_CURVE = 1
  137. SINE = 2
  138. class OutputMode(IntEnum):
  139. PULSE_DIRECTION = 0
  140. AB_QUADRATURE = 1
  141. class Error(IntEnum):
  142. NONE = 0
  143. BUSY = 1
  144. INVALID_PULSE_COUNT = 2
  145. INVALID_FREQUENCY = 3
  146. INVALID_DIRECTION = 4
  147. TIMER_RANGE = 5
  148. INVALID_COMMAND = 6
  149. NOT_PAUSED = 7
  150. NOT_ACTIVE = 8
  151. INVALID_PULSE_POINT = 9
  152. INVALID_DIRECTION_POINT = 10
  153. SAME_OUTPUT_POINT = 11
  154. FLASH_SAVE = 12
  155. INVALID_MODE = 13
  156. INVALID_PROFILE = 14
  157. INVALID_RAMP_TIME = 15
  158. INVALID_SPEED = 16
  159. INVALID_SEGMENT_COUNT = 17
  160. INVALID_START_SEGMENT = 18
  161. INVALID_SEGMENT_PARAM = 19
  162. SEGMENT_TOTAL_OVERFLOW = 20
  163. INVALID_WAIT_CONDITION = 21
  164. INVALID_INPUT_POINT = 22
  165. STATE_TEXT = {
  166. State.IDLE: "空闲", State.RUNNING: "运行中", State.COMPLETE: "完成",
  167. State.ERROR: "错误", State.PAUSED: "已暂停", State.WAITING: "条件等待中",
  168. }
  169. ERROR_TEXT = {item: item.name for item in Error}
  170. ERROR_TEXT[Error.NONE] = "无错误"
  171. WAIT_CONDITION_TEXT = {
  172. WaitCondition.WAIT_TIME: "Wait时间",
  173. WaitCondition.WAIT_SIGNAL: "Wait信号(全局X4/X5)",
  174. WaitCondition.ACT_TIME: "ACT时间",
  175. WaitCondition.EXT_SIGNAL: "EXT信号(全局X4/X5)",
  176. WaitCondition.EXT_OR_COMPLETE: "EXT信号或脉冲完成",
  177. }
  178. MODE_TEXT = {RunMode.RELATIVE: "相对模式", RunMode.ABSOLUTE: "绝对模式"}
  179. SEND_MODE_TEXT = {
  180. SendMode.COMPLETE: "完成模式(段间停止)",
  181. SendMode.FOLLOW: "后续模式(满足条件时连续衔接)",
  182. }
  183. DIRECTION_LOGIC_TEXT = {
  184. DirectionLogic.POSITIVE: "正逻辑(正向ON,反向OFF)",
  185. DirectionLogic.NEGATIVE: "负逻辑(正向OFF,反向ON)",
  186. }
  187. PROFILE_TEXT = {
  188. ProfileType.LINEAR: "直线加减速",
  189. ProfileType.S_CURVE: "S曲线加减速",
  190. ProfileType.SINE: "正弦曲线加减速",
  191. }
  192. OUTPUT_MODE_TEXT = {
  193. OutputMode.PULSE_DIRECTION: "PUL/DIR(脉冲+方向)",
  194. OutputMode.AB_QUADRATURE: "AB正交脉冲",
  195. }
  196. INPUT_POINTS = (4, 5)
  197. PULSE_POINTS = {
  198. 0: "Y0 / PF6 / TIM10_CH1", 1: "Y1 / PF8 / TIM13_CH1",
  199. 2: "Y2 / PF7 / TIM11_CH1", 3: "Y3 / PF9 / TIM14_CH1",
  200. }
  201. SECONDARY_POINTS = {
  202. 0: "Y12 / PH9", 1: "Y13 / PH8", 2: "Y14 / PH7", 3: "Y15 / PH6",
  203. }
  204. def point_id_from_label(options: Dict[int, str], label: str) -> int:
  205. for point, text in options.items():
  206. if text == label:
  207. return point
  208. raise ValueError(f"未知输出点:{label}")
  209. def calculate_execution_total(
  210. segments: Sequence[Sequence[int]], start_segment: int
  211. ) -> Tuple[int, bool]:
  212. """按跳转段实际路径累计脉冲;检测到循环时返回(cycle前累计值, True)。"""
  213. if not segments or not 1 <= start_segment <= len(segments):
  214. raise ValueError("起始执行段超出有效段范围")
  215. current = start_segment
  216. visited = set()
  217. total = 0
  218. while current not in visited:
  219. visited.add(current)
  220. segment = segments[current - 1]
  221. total += abs(int(segment[0]))
  222. jump = int(segment[2])
  223. if jump == 0:
  224. if current >= len(segments):
  225. return total, False
  226. current += 1
  227. else:
  228. if not 1 <= jump <= len(segments):
  229. raise ValueError("跳转段超出有效段范围")
  230. current = jump
  231. return total, True
  232. PRESETS = [
  233. {"name": "LED可视测试:10脉冲 / 1Hz", "pulses": 10, "frequency": 1,
  234. "acceleration_ms": 0, "deceleration_ms": 0,
  235. "start_frequency": 1, "end_frequency": 1},
  236. {"name": "基础测试:1000脉冲 / 1000Hz", "pulses": 1000, "frequency": 1000,
  237. "acceleration_ms": 1000, "deceleration_ms": 1000,
  238. "start_frequency": 100, "end_frequency": 100},
  239. {"name": "长行程测试:100000脉冲 / 5000Hz", "pulses": 100000, "frequency": 5000,
  240. "acceleration_ms": 1000, "deceleration_ms": 1000,
  241. "start_frequency": 100, "end_frequency": 100},
  242. {"name": "最高频率测试:100000脉冲 / 100000Hz", "pulses": 100000,
  243. "frequency": MAX_FREQUENCY_HZ, "acceleration_ms": 500,
  244. "deceleration_ms": 500, "start_frequency": 100, "end_frequency": 100},
  245. ]
  246. def split_u32(value: int) -> List[int]:
  247. """参数表规定:低地址存低16位。"""
  248. if not 0 <= value <= 0xFFFFFFFF:
  249. raise ValueError("32位参数超出范围")
  250. return [value & 0xFFFF, (value >> 16) & 0xFFFF]
  251. def signed_to_u32(value: int) -> int:
  252. if not -0x80000000 <= value <= 0x7FFFFFFF:
  253. raise ValueError("有符号32位参数超出范围")
  254. return value & 0xFFFFFFFF
  255. def join_u32(low: int, high: int) -> int:
  256. return (low & 0xFFFF) | ((high & 0xFFFF) << 16)
  257. def join_i32(low: int, high: int) -> int:
  258. value = join_u32(low, high)
  259. return value - 0x100000000 if value >= 0x80000000 else value
  260. def state_text(value: int) -> str:
  261. try:
  262. return STATE_TEXT[State(value)]
  263. except (ValueError, KeyError):
  264. return f"未知状态 {value}"
  265. def error_text(value: int) -> str:
  266. try:
  267. return ERROR_TEXT[Error(value)]
  268. except (ValueError, KeyError):
  269. return f"未知错误 {value}"
  270. def parse_status(registers: List[int]) -> Dict[str, int]:
  271. """解析0x2000开始的状态区;兼容旧固件的11寄存器响应。"""
  272. if len(registers) < 11:
  273. raise ValueError("状态寄存器数量不足11个")
  274. result = {
  275. "cumulative_position": join_i32(registers[0], registers[1]),
  276. "current_frequency": join_u32(registers[2], registers[3]),
  277. "state": registers[4],
  278. "current_segment": registers[5],
  279. "error": registers[6],
  280. "current_pulses": join_u32(registers[7], registers[8]),
  281. "total_pulses": join_u32(registers[9], registers[10]),
  282. }
  283. if len(registers) >= STATUS_REGISTER_COUNT:
  284. result.update({
  285. "diagnostic_status": registers[11],
  286. "diagnostic_profile_type": registers[12],
  287. "diagnostic_phase": registers[13],
  288. "diagnostic_expected_pulses": join_u32(registers[14], registers[15]),
  289. "diagnostic_observed_pulses": join_u32(registers[16], registers[17]),
  290. "diagnostic_expected_frequency": join_u32(registers[18], registers[19]),
  291. "diagnostic_actual_frequency": join_u32(registers[20], registers[21]),
  292. "diagnostic_max_frequency_error": join_u32(registers[22], registers[23]),
  293. "diagnostic_count_errors": registers[24],
  294. "diagnostic_frequency_errors": registers[25],
  295. "diagnostic_profile_errors": registers[26],
  296. })
  297. else:
  298. result.update({
  299. "diagnostic_status": 0,
  300. "diagnostic_profile_type": 0,
  301. "diagnostic_phase": 0,
  302. "diagnostic_expected_pulses": 0,
  303. "diagnostic_observed_pulses": 0,
  304. "diagnostic_expected_frequency": 0,
  305. "diagnostic_actual_frequency": 0,
  306. "diagnostic_max_frequency_error": 0,
  307. "diagnostic_count_errors": 0,
  308. "diagnostic_frequency_errors": 0,
  309. "diagnostic_profile_errors": 0,
  310. })
  311. return result