emm-stepper 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emm_stepper/__init__.py +38 -0
- emm_stepper/commands.py +1284 -0
- emm_stepper/configs.py +476 -0
- emm_stepper/device.py +900 -0
- emm_stepper/parameters.py +590 -0
- emm_stepper-1.0.0.dist-info/METADATA +1240 -0
- emm_stepper-1.0.0.dist-info/RECORD +10 -0
- emm_stepper-1.0.0.dist-info/WHEEL +5 -0
- emm_stepper-1.0.0.dist-info/licenses/LICENSE +21 -0
- emm_stepper-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
"""Emm固件步进电机参数类.
|
|
2
|
+
|
|
3
|
+
基于ZDT_X42S第二代闭环步进电机用户手册V1.0.3_251224。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from serial import Serial
|
|
10
|
+
|
|
11
|
+
from .configs import (
|
|
12
|
+
Address,
|
|
13
|
+
ChecksumMode,
|
|
14
|
+
Direction,
|
|
15
|
+
SyncFlag,
|
|
16
|
+
StoreFlag,
|
|
17
|
+
MotionMode,
|
|
18
|
+
HomingMode,
|
|
19
|
+
HomingDirection,
|
|
20
|
+
ControlMode,
|
|
21
|
+
MotorType,
|
|
22
|
+
FirmwareType,
|
|
23
|
+
BaudRate,
|
|
24
|
+
CanRate,
|
|
25
|
+
ResponseMode,
|
|
26
|
+
StallProtect,
|
|
27
|
+
PulsePortMode,
|
|
28
|
+
SerialPortMode,
|
|
29
|
+
EnableLevel,
|
|
30
|
+
DirLevel,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def to_int(data: bytes) -> int:
|
|
35
|
+
"""将字节转换为整数."""
|
|
36
|
+
return int.from_bytes(data, "big")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def to_signed_int(data: bytes) -> int:
|
|
40
|
+
"""将带符号字节转换为有符号整数.
|
|
41
|
+
|
|
42
|
+
第一个字节为符号位: 00=正, 01=负
|
|
43
|
+
"""
|
|
44
|
+
if len(data) < 2:
|
|
45
|
+
return to_int(data)
|
|
46
|
+
sign = -1 if data[0] == 1 else 1
|
|
47
|
+
return sign * to_int(data[1:])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class DeviceParams:
|
|
52
|
+
"""设备参数类.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
serial_connection: 串口连接对象
|
|
56
|
+
address: 电机地址 (1-255, 0为广播地址)
|
|
57
|
+
checksum_mode: 校验模式
|
|
58
|
+
delay: 通讯延迟(秒)
|
|
59
|
+
"""
|
|
60
|
+
serial_connection: Serial
|
|
61
|
+
address: int = Address.DEFAULT
|
|
62
|
+
checksum_mode: ChecksumMode = ChecksumMode.FIXED
|
|
63
|
+
delay: Optional[float] = None
|
|
64
|
+
|
|
65
|
+
def __post_init__(self):
|
|
66
|
+
if isinstance(self.address, int) and not isinstance(self.address, Address):
|
|
67
|
+
self.address = Address(self.address)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class JogParams:
|
|
72
|
+
"""速度模式参数 (Emm固件).
|
|
73
|
+
|
|
74
|
+
对应命令: 5.3.7 速度模式控制(Emm)
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
direction: 运动方向 (CW/CCW)
|
|
78
|
+
speed: 速度 (0-3000 RPM)
|
|
79
|
+
acceleration: 加速度档位 (0-255)
|
|
80
|
+
sync_flag: 同步标志
|
|
81
|
+
"""
|
|
82
|
+
direction: Direction = Direction.CW
|
|
83
|
+
speed: int = 100 # 0-3000 RPM
|
|
84
|
+
acceleration: int = 10 # 0-255档位
|
|
85
|
+
sync_flag: SyncFlag = SyncFlag.IMMEDIATE
|
|
86
|
+
|
|
87
|
+
def __post_init__(self):
|
|
88
|
+
# 速度范围检查
|
|
89
|
+
if not 0 <= self.speed <= 3000:
|
|
90
|
+
raise ValueError("速度必须在 0-3000 RPM 之间")
|
|
91
|
+
# 加速度范围检查
|
|
92
|
+
if not 0 <= self.acceleration <= 255:
|
|
93
|
+
raise ValueError("加速度必须在 0-255 之间")
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def bytes(self) -> bytes:
|
|
97
|
+
"""返回命令字节."""
|
|
98
|
+
return bytes([
|
|
99
|
+
self.direction,
|
|
100
|
+
(self.speed >> 8) & 0xFF,
|
|
101
|
+
self.speed & 0xFF,
|
|
102
|
+
self.acceleration,
|
|
103
|
+
self.sync_flag,
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class PositionParams:
|
|
109
|
+
"""位置模式参数 (Emm固件).
|
|
110
|
+
|
|
111
|
+
对应命令: 5.3.12 位置模式控制(Emm)
|
|
112
|
+
|
|
113
|
+
Args:
|
|
114
|
+
direction: 运动方向 (CW/CCW)
|
|
115
|
+
speed: 速度 (0-3000 RPM)
|
|
116
|
+
acceleration: 加速度档位 (0-255)
|
|
117
|
+
pulse_count: 脉冲数 (默认16细分下,3200个脉冲=一圈360°)
|
|
118
|
+
motion_mode: 运动模式 (相对/绝对)
|
|
119
|
+
sync_flag: 同步标志
|
|
120
|
+
"""
|
|
121
|
+
direction: Direction = Direction.CW
|
|
122
|
+
speed: int = 100 # 0-3000 RPM
|
|
123
|
+
acceleration: int = 10 # 0-255档位
|
|
124
|
+
pulse_count: int = 3200 # 脉冲数
|
|
125
|
+
motion_mode: MotionMode = MotionMode.RELATIVE_LAST
|
|
126
|
+
sync_flag: SyncFlag = SyncFlag.IMMEDIATE
|
|
127
|
+
|
|
128
|
+
def __post_init__(self):
|
|
129
|
+
if not 0 <= self.speed <= 3000:
|
|
130
|
+
raise ValueError("速度必须在 0-3000 RPM 之间")
|
|
131
|
+
if not 0 <= self.acceleration <= 255:
|
|
132
|
+
raise ValueError("加速度必须在 0-255 之间")
|
|
133
|
+
if not 0 <= self.pulse_count <= 0xFFFFFFFF:
|
|
134
|
+
raise ValueError("脉冲数必须在 0-4294967295 之间")
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def bytes(self) -> bytes:
|
|
138
|
+
"""返回命令字节."""
|
|
139
|
+
return bytes([
|
|
140
|
+
self.direction,
|
|
141
|
+
(self.speed >> 8) & 0xFF,
|
|
142
|
+
self.speed & 0xFF,
|
|
143
|
+
self.acceleration,
|
|
144
|
+
(self.pulse_count >> 24) & 0xFF,
|
|
145
|
+
(self.pulse_count >> 16) & 0xFF,
|
|
146
|
+
(self.pulse_count >> 8) & 0xFF,
|
|
147
|
+
self.pulse_count & 0xFF,
|
|
148
|
+
self.motion_mode,
|
|
149
|
+
self.sync_flag,
|
|
150
|
+
])
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
class HomingParams:
|
|
155
|
+
"""回零参数.
|
|
156
|
+
|
|
157
|
+
对应命令: 5.4.6 修改回零参数
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
homing_mode: 回零模式
|
|
161
|
+
homing_direction: 回零方向
|
|
162
|
+
homing_speed: 回零速度 (0-3000 RPM)
|
|
163
|
+
homing_timeout: 回零超时时间 (毫秒)
|
|
164
|
+
collision_speed: 碰撞回零检测转速 (RPM)
|
|
165
|
+
collision_current: 碰撞回零检测电流 (mA)
|
|
166
|
+
collision_time: 碰撞回零检测时间 (ms)
|
|
167
|
+
auto_home: 是否使能上电自动触发回零
|
|
168
|
+
"""
|
|
169
|
+
homing_mode: HomingMode = HomingMode.NEAREST
|
|
170
|
+
homing_direction: HomingDirection = HomingDirection.CW
|
|
171
|
+
homing_speed: int = 30 # RPM
|
|
172
|
+
homing_timeout: int = 10000 # ms
|
|
173
|
+
collision_speed: int = 300 # RPM
|
|
174
|
+
collision_current: int = 800 # mA
|
|
175
|
+
collision_time: int = 60 # ms
|
|
176
|
+
auto_home: bool = False
|
|
177
|
+
|
|
178
|
+
@property
|
|
179
|
+
def bytes(self) -> bytes:
|
|
180
|
+
"""返回命令字节."""
|
|
181
|
+
return bytes([
|
|
182
|
+
self.homing_mode,
|
|
183
|
+
self.homing_direction,
|
|
184
|
+
(self.homing_speed >> 8) & 0xFF,
|
|
185
|
+
self.homing_speed & 0xFF,
|
|
186
|
+
(self.homing_timeout >> 24) & 0xFF,
|
|
187
|
+
(self.homing_timeout >> 16) & 0xFF,
|
|
188
|
+
(self.homing_timeout >> 8) & 0xFF,
|
|
189
|
+
self.homing_timeout & 0xFF,
|
|
190
|
+
(self.collision_speed >> 8) & 0xFF,
|
|
191
|
+
self.collision_speed & 0xFF,
|
|
192
|
+
(self.collision_current >> 8) & 0xFF,
|
|
193
|
+
self.collision_current & 0xFF,
|
|
194
|
+
(self.collision_time >> 8) & 0xFF,
|
|
195
|
+
self.collision_time & 0xFF,
|
|
196
|
+
1 if self.auto_home else 0,
|
|
197
|
+
])
|
|
198
|
+
|
|
199
|
+
@classmethod
|
|
200
|
+
def from_bytes(cls, data: bytes) -> "HomingParams":
|
|
201
|
+
"""从字节数据解析回零参数."""
|
|
202
|
+
return cls(
|
|
203
|
+
homing_mode=HomingMode(data[0]),
|
|
204
|
+
homing_direction=HomingDirection(data[1]),
|
|
205
|
+
homing_speed=to_int(data[2:4]),
|
|
206
|
+
homing_timeout=to_int(data[4:8]),
|
|
207
|
+
collision_speed=to_int(data[8:10]),
|
|
208
|
+
collision_current=to_int(data[10:12]),
|
|
209
|
+
collision_time=to_int(data[12:14]),
|
|
210
|
+
auto_home=bool(data[14]),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
@dataclass
|
|
215
|
+
class VersionParams:
|
|
216
|
+
"""版本参数.
|
|
217
|
+
|
|
218
|
+
对应命令: 5.5.2 读取固件版本和硬件版本
|
|
219
|
+
"""
|
|
220
|
+
firmware_version: int = 0
|
|
221
|
+
hw_series: int = 0 # 0=X系列, 1=Y系列
|
|
222
|
+
hw_type: int = 0 # 0/1/2/3/4/5/6 = 20/28/35/42/57/86
|
|
223
|
+
hw_version: int = 0
|
|
224
|
+
|
|
225
|
+
@classmethod
|
|
226
|
+
def from_bytes(cls, data: bytes) -> "VersionParams":
|
|
227
|
+
"""从字节数据解析版本参数."""
|
|
228
|
+
fw_ver = to_int(data[0:2])
|
|
229
|
+
hw_info = to_int(data[2:4])
|
|
230
|
+
return cls(
|
|
231
|
+
firmware_version=fw_ver,
|
|
232
|
+
hw_series=(hw_info >> 12) & 0x0F,
|
|
233
|
+
hw_type=(hw_info >> 8) & 0x0F,
|
|
234
|
+
hw_version=hw_info & 0xFF,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
@property
|
|
238
|
+
def firmware_version_str(self) -> str:
|
|
239
|
+
"""返回固件版本字符串."""
|
|
240
|
+
major = self.firmware_version // 100
|
|
241
|
+
minor = (self.firmware_version % 100) // 10
|
|
242
|
+
patch = self.firmware_version % 10
|
|
243
|
+
return f"V{major}.{minor}.{patch}"
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def hw_series_str(self) -> str:
|
|
247
|
+
"""返回硬件系列字符串."""
|
|
248
|
+
return "X系列" if self.hw_series == 0 else "Y系列"
|
|
249
|
+
|
|
250
|
+
@property
|
|
251
|
+
def hw_type_str(self) -> str:
|
|
252
|
+
"""返回硬件类型字符串."""
|
|
253
|
+
types = {0: "20", 1: "28", 2: "35", 3: "42", 4: "57", 5: "86"}
|
|
254
|
+
series = "X" if self.hw_series == 0 else "Y"
|
|
255
|
+
return f"{series}{types.get(self.hw_type, 'Unknown')}"
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@dataclass
|
|
259
|
+
class MotorRHParams:
|
|
260
|
+
"""电机相电阻和相电感参数.
|
|
261
|
+
|
|
262
|
+
对应命令: 5.5.3 读取相电阻和相电感
|
|
263
|
+
"""
|
|
264
|
+
phase_resistance: int = 0 # mΩ
|
|
265
|
+
phase_inductance: int = 0 # uH
|
|
266
|
+
|
|
267
|
+
@classmethod
|
|
268
|
+
def from_bytes(cls, data: bytes) -> "MotorRHParams":
|
|
269
|
+
"""从字节数据解析."""
|
|
270
|
+
return cls(
|
|
271
|
+
phase_resistance=to_int(data[0:2]),
|
|
272
|
+
phase_inductance=to_int(data[2:4]),
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@dataclass
|
|
277
|
+
class PIDParams:
|
|
278
|
+
"""PID参数 (Emm固件).
|
|
279
|
+
|
|
280
|
+
对应命令: 5.6.16 读取PID参数(Emm)
|
|
281
|
+
"""
|
|
282
|
+
kp: int = 18000 # 比例系数
|
|
283
|
+
ki: int = 10 # 积分系数
|
|
284
|
+
kd: int = 18000 # 微分系数
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def bytes(self) -> bytes:
|
|
288
|
+
"""返回命令字节."""
|
|
289
|
+
return bytes([
|
|
290
|
+
(self.kp >> 24) & 0xFF,
|
|
291
|
+
(self.kp >> 16) & 0xFF,
|
|
292
|
+
(self.kp >> 8) & 0xFF,
|
|
293
|
+
self.kp & 0xFF,
|
|
294
|
+
(self.ki >> 24) & 0xFF,
|
|
295
|
+
(self.ki >> 16) & 0xFF,
|
|
296
|
+
(self.ki >> 8) & 0xFF,
|
|
297
|
+
self.ki & 0xFF,
|
|
298
|
+
(self.kd >> 24) & 0xFF,
|
|
299
|
+
(self.kd >> 16) & 0xFF,
|
|
300
|
+
(self.kd >> 8) & 0xFF,
|
|
301
|
+
self.kd & 0xFF,
|
|
302
|
+
])
|
|
303
|
+
|
|
304
|
+
@classmethod
|
|
305
|
+
def from_bytes(cls, data: bytes) -> "PIDParams":
|
|
306
|
+
"""从字节数据解析PID参数."""
|
|
307
|
+
return cls(
|
|
308
|
+
kp=to_int(data[0:4]),
|
|
309
|
+
ki=to_int(data[4:8]),
|
|
310
|
+
kd=to_int(data[8:12]),
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@dataclass
|
|
315
|
+
class HomingStatus:
|
|
316
|
+
"""回零状态标志.
|
|
317
|
+
|
|
318
|
+
对应命令: 5.4.4 读取回零状态标志
|
|
319
|
+
"""
|
|
320
|
+
encoder_ready: bool = False # 编码器就绪
|
|
321
|
+
calibrated: bool = False # 校准表就绪
|
|
322
|
+
is_homing: bool = False # 正在回零
|
|
323
|
+
homing_failed: bool = False # 回零失败
|
|
324
|
+
over_temp: bool = False # 过热保护
|
|
325
|
+
over_current: bool = False # 过流保护
|
|
326
|
+
|
|
327
|
+
@classmethod
|
|
328
|
+
def from_byte(cls, data: int) -> "HomingStatus":
|
|
329
|
+
"""从字节数据解析回零状态."""
|
|
330
|
+
return cls(
|
|
331
|
+
encoder_ready=bool(data & 0x01),
|
|
332
|
+
calibrated=bool(data & 0x02),
|
|
333
|
+
is_homing=bool(data & 0x04),
|
|
334
|
+
homing_failed=bool(data & 0x08),
|
|
335
|
+
over_temp=bool(data & 0x10),
|
|
336
|
+
over_current=bool(data & 0x20),
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
@property
|
|
340
|
+
def homing_state(self) -> str:
|
|
341
|
+
"""返回回零状态字符串."""
|
|
342
|
+
if self.is_homing:
|
|
343
|
+
return "正在回零"
|
|
344
|
+
elif self.homing_failed:
|
|
345
|
+
return "回零失败"
|
|
346
|
+
else:
|
|
347
|
+
return "回零成功/未回零"
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@dataclass
|
|
351
|
+
class MotorStatus:
|
|
352
|
+
"""电机状态标志.
|
|
353
|
+
|
|
354
|
+
对应命令: 5.5.15 读取电机状态标志
|
|
355
|
+
"""
|
|
356
|
+
enabled: bool = False # 使能状态
|
|
357
|
+
position_reached: bool = False # 位置到达
|
|
358
|
+
stall_detected: bool = False # 堵转标志
|
|
359
|
+
stall_protected: bool = False # 堵转保护
|
|
360
|
+
left_limit: bool = False # 左限位开关状态
|
|
361
|
+
right_limit: bool = False # 右限位开关状态
|
|
362
|
+
power_off_flag: bool = True # 掉电标志
|
|
363
|
+
|
|
364
|
+
@classmethod
|
|
365
|
+
def from_byte(cls, data: int) -> "MotorStatus":
|
|
366
|
+
"""从字节数据解析电机状态."""
|
|
367
|
+
return cls(
|
|
368
|
+
enabled=bool(data & 0x01),
|
|
369
|
+
position_reached=bool(data & 0x02),
|
|
370
|
+
stall_detected=bool(data & 0x04),
|
|
371
|
+
stall_protected=bool(data & 0x08),
|
|
372
|
+
left_limit=bool(data & 0x10),
|
|
373
|
+
right_limit=bool(data & 0x20),
|
|
374
|
+
power_off_flag=bool(data & 0x80),
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
@dataclass
|
|
379
|
+
class SystemStatusParams:
|
|
380
|
+
"""系统状态参数 (Emm固件).
|
|
381
|
+
|
|
382
|
+
对应命令: 5.8.2 读取系统状态参数(Emm)
|
|
383
|
+
"""
|
|
384
|
+
bus_voltage: int = 0 # mV
|
|
385
|
+
phase_current: int = 0 # mA
|
|
386
|
+
encoder_value: int = 0 # 线性化编码器值
|
|
387
|
+
target_position: int = 0 # 电机目标位置
|
|
388
|
+
target_position_sign: int = 0 # 符号
|
|
389
|
+
realtime_speed: int = 0 # 电机实时转速 RPM
|
|
390
|
+
realtime_speed_sign: int = 0 # 符号
|
|
391
|
+
realtime_position: int = 0 # 电机实时位置
|
|
392
|
+
realtime_position_sign: int = 0 # 符号
|
|
393
|
+
position_error: int = 0 # 电机位置误差
|
|
394
|
+
position_error_sign: int = 0 # 符号
|
|
395
|
+
homing_status: HomingStatus = field(default_factory=HomingStatus)
|
|
396
|
+
motor_status: MotorStatus = field(default_factory=MotorStatus)
|
|
397
|
+
|
|
398
|
+
@classmethod
|
|
399
|
+
def from_bytes(cls, data: bytes) -> "SystemStatusParams":
|
|
400
|
+
"""从字节数据解析系统状态参数."""
|
|
401
|
+
# Emm固件返回格式: 1F 09 + 数据
|
|
402
|
+
return cls(
|
|
403
|
+
bus_voltage=to_int(data[0:2]),
|
|
404
|
+
phase_current=to_int(data[2:4]),
|
|
405
|
+
encoder_value=to_int(data[4:6]),
|
|
406
|
+
target_position_sign=data[6],
|
|
407
|
+
target_position=to_int(data[7:11]),
|
|
408
|
+
realtime_speed_sign=data[11],
|
|
409
|
+
realtime_speed=to_int(data[12:14]),
|
|
410
|
+
realtime_position_sign=data[14],
|
|
411
|
+
realtime_position=to_int(data[15:19]),
|
|
412
|
+
position_error_sign=data[19],
|
|
413
|
+
position_error=to_int(data[20:24]),
|
|
414
|
+
homing_status=HomingStatus.from_byte(data[24]),
|
|
415
|
+
motor_status=MotorStatus.from_byte(data[25]),
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def target_position_deg(self) -> float:
|
|
420
|
+
"""返回目标位置角度(度)."""
|
|
421
|
+
sign = -1 if self.target_position_sign == 1 else 1
|
|
422
|
+
return sign * (self.target_position * 360) / 65536
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def realtime_position_deg(self) -> float:
|
|
426
|
+
"""返回实时位置角度(度)."""
|
|
427
|
+
sign = -1 if self.realtime_position_sign == 1 else 1
|
|
428
|
+
return sign * (self.realtime_position * 360) / 65536
|
|
429
|
+
|
|
430
|
+
@property
|
|
431
|
+
def position_error_deg(self) -> float:
|
|
432
|
+
"""返回位置误差角度(度)."""
|
|
433
|
+
sign = -1 if self.position_error_sign == 1 else 1
|
|
434
|
+
return sign * (self.position_error * 360) / 65536
|
|
435
|
+
|
|
436
|
+
@property
|
|
437
|
+
def realtime_speed_rpm(self) -> int:
|
|
438
|
+
"""返回实时转速(RPM)."""
|
|
439
|
+
sign = -1 if self.realtime_speed_sign == 1 else 1
|
|
440
|
+
return sign * self.realtime_speed
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
@dataclass
|
|
444
|
+
class ConfigParams:
|
|
445
|
+
"""驱动配置参数 (Emm固件).
|
|
446
|
+
|
|
447
|
+
对应命令: 5.8.5 读取驱动配置参数(Emm)
|
|
448
|
+
"""
|
|
449
|
+
motor_type: MotorType = MotorType.DEGREE_18
|
|
450
|
+
pulse_port_mode: PulsePortMode = PulsePortMode.FOC
|
|
451
|
+
serial_port_mode: SerialPortMode = SerialPortMode.UART
|
|
452
|
+
enable_level: EnableLevel = EnableLevel.HOLD
|
|
453
|
+
dir_level: DirLevel = DirLevel.CW
|
|
454
|
+
microstep: int = 16
|
|
455
|
+
microstep_interp: bool = True
|
|
456
|
+
open_loop_current: int = 1200 # mA
|
|
457
|
+
closed_loop_current: int = 3000 # mA
|
|
458
|
+
max_voltage: int = 4000 # *3 mV
|
|
459
|
+
baud_rate: BaudRate = BaudRate.BAUD_115200
|
|
460
|
+
can_rate: CanRate = CanRate.CAN_500K
|
|
461
|
+
motor_id: int = 1
|
|
462
|
+
checksum_mode: ChecksumMode = ChecksumMode.FIXED
|
|
463
|
+
response_mode: ResponseMode = ResponseMode.RECEIVE
|
|
464
|
+
stall_protect: StallProtect = StallProtect.ENABLE
|
|
465
|
+
stall_speed: int = 8 # RPM
|
|
466
|
+
stall_current: int = 2200 # mA
|
|
467
|
+
stall_time: int = 2000 # ms
|
|
468
|
+
position_window: int = 8 # *0.1度
|
|
469
|
+
|
|
470
|
+
@property
|
|
471
|
+
def bytes(self) -> bytes:
|
|
472
|
+
"""返回命令字节."""
|
|
473
|
+
return bytes([
|
|
474
|
+
self.motor_type,
|
|
475
|
+
self.pulse_port_mode,
|
|
476
|
+
self.serial_port_mode,
|
|
477
|
+
self.enable_level,
|
|
478
|
+
self.dir_level,
|
|
479
|
+
self.microstep,
|
|
480
|
+
1 if self.microstep_interp else 0,
|
|
481
|
+
0, # 保留
|
|
482
|
+
(self.open_loop_current >> 8) & 0xFF,
|
|
483
|
+
self.open_loop_current & 0xFF,
|
|
484
|
+
(self.closed_loop_current >> 8) & 0xFF,
|
|
485
|
+
self.closed_loop_current & 0xFF,
|
|
486
|
+
(self.max_voltage >> 8) & 0xFF,
|
|
487
|
+
self.max_voltage & 0xFF,
|
|
488
|
+
self.baud_rate,
|
|
489
|
+
self.can_rate,
|
|
490
|
+
self.motor_id,
|
|
491
|
+
self.checksum_mode,
|
|
492
|
+
self.response_mode,
|
|
493
|
+
self.stall_protect,
|
|
494
|
+
(self.stall_speed >> 8) & 0xFF,
|
|
495
|
+
self.stall_speed & 0xFF,
|
|
496
|
+
(self.stall_current >> 8) & 0xFF,
|
|
497
|
+
self.stall_current & 0xFF,
|
|
498
|
+
(self.stall_time >> 8) & 0xFF,
|
|
499
|
+
self.stall_time & 0xFF,
|
|
500
|
+
(self.position_window >> 8) & 0xFF,
|
|
501
|
+
self.position_window & 0xFF,
|
|
502
|
+
])
|
|
503
|
+
|
|
504
|
+
@classmethod
|
|
505
|
+
def from_bytes(cls, data: bytes) -> "ConfigParams":
|
|
506
|
+
"""从字节数据解析配置参数."""
|
|
507
|
+
return cls(
|
|
508
|
+
motor_type=MotorType(data[0]),
|
|
509
|
+
pulse_port_mode=PulsePortMode(data[1]),
|
|
510
|
+
serial_port_mode=SerialPortMode(data[2]),
|
|
511
|
+
enable_level=EnableLevel(data[3]),
|
|
512
|
+
dir_level=DirLevel(data[4]),
|
|
513
|
+
microstep=data[5],
|
|
514
|
+
microstep_interp=bool(data[6]),
|
|
515
|
+
open_loop_current=to_int(data[8:10]),
|
|
516
|
+
closed_loop_current=to_int(data[10:12]),
|
|
517
|
+
max_voltage=to_int(data[12:14]),
|
|
518
|
+
baud_rate=BaudRate(data[14]),
|
|
519
|
+
can_rate=CanRate(data[15]),
|
|
520
|
+
motor_id=data[16],
|
|
521
|
+
checksum_mode=ChecksumMode(data[17]),
|
|
522
|
+
response_mode=ResponseMode(data[18]),
|
|
523
|
+
stall_protect=StallProtect(data[19]),
|
|
524
|
+
stall_speed=to_int(data[20:22]),
|
|
525
|
+
stall_current=to_int(data[22:24]),
|
|
526
|
+
stall_time=to_int(data[24:26]),
|
|
527
|
+
position_window=to_int(data[26:28]),
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
@property
|
|
531
|
+
def position_window_deg(self) -> float:
|
|
532
|
+
"""返回位置到达窗口(度)."""
|
|
533
|
+
return self.position_window * 0.1
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
@dataclass
|
|
537
|
+
class ProtectionThreshold:
|
|
538
|
+
"""过热过流保护检测阈值.
|
|
539
|
+
|
|
540
|
+
对应命令: 5.6.22 读取过热过流保护检测阈值
|
|
541
|
+
"""
|
|
542
|
+
over_temp_threshold: int = 100 # °C
|
|
543
|
+
over_current_threshold: int = 6600 # mA
|
|
544
|
+
detection_time: int = 1000 # ms
|
|
545
|
+
|
|
546
|
+
@property
|
|
547
|
+
def bytes(self) -> bytes:
|
|
548
|
+
"""返回命令字节."""
|
|
549
|
+
return bytes([
|
|
550
|
+
(self.over_temp_threshold >> 8) & 0xFF,
|
|
551
|
+
self.over_temp_threshold & 0xFF,
|
|
552
|
+
(self.over_current_threshold >> 8) & 0xFF,
|
|
553
|
+
self.over_current_threshold & 0xFF,
|
|
554
|
+
(self.detection_time >> 8) & 0xFF,
|
|
555
|
+
self.detection_time & 0xFF,
|
|
556
|
+
])
|
|
557
|
+
|
|
558
|
+
@classmethod
|
|
559
|
+
def from_bytes(cls, data: bytes) -> "ProtectionThreshold":
|
|
560
|
+
"""从字节数据解析."""
|
|
561
|
+
return cls(
|
|
562
|
+
over_temp_threshold=to_int(data[0:2]),
|
|
563
|
+
over_current_threshold=to_int(data[2:4]),
|
|
564
|
+
detection_time=to_int(data[4:6]),
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
@dataclass
|
|
569
|
+
class AutoRunParams:
|
|
570
|
+
"""上电自动运行参数 (Emm固件).
|
|
571
|
+
|
|
572
|
+
对应命令: 5.7.2 存储一组速度参数,上电自动运行(Emm)
|
|
573
|
+
"""
|
|
574
|
+
store: bool = True # 存储/清除
|
|
575
|
+
direction: Direction = Direction.CW
|
|
576
|
+
speed: int = 600 # RPM
|
|
577
|
+
acceleration: int = 100 # 档位
|
|
578
|
+
enable_en_control: bool = False # 是否使能En引脚控制启停
|
|
579
|
+
|
|
580
|
+
@property
|
|
581
|
+
def bytes(self) -> bytes:
|
|
582
|
+
"""返回命令字节."""
|
|
583
|
+
return bytes([
|
|
584
|
+
1 if self.store else 0,
|
|
585
|
+
self.direction,
|
|
586
|
+
(self.speed >> 8) & 0xFF,
|
|
587
|
+
self.speed & 0xFF,
|
|
588
|
+
self.acceleration,
|
|
589
|
+
1 if self.enable_en_control else 0,
|
|
590
|
+
])
|