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
emm_stepper/device.py
ADDED
|
@@ -0,0 +1,900 @@
|
|
|
1
|
+
"""Emm固件步进电机设备类.
|
|
2
|
+
|
|
3
|
+
基于ZDT_X42S第二代闭环步进电机用户手册V1.0.3_251224。
|
|
4
|
+
提供高级API接口用于控制步进电机。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from serial import Serial
|
|
11
|
+
|
|
12
|
+
from .configs import (
|
|
13
|
+
Address,
|
|
14
|
+
ChecksumMode,
|
|
15
|
+
Direction,
|
|
16
|
+
SyncFlag,
|
|
17
|
+
StoreFlag,
|
|
18
|
+
MotionMode,
|
|
19
|
+
HomingMode,
|
|
20
|
+
HomingDirection,
|
|
21
|
+
)
|
|
22
|
+
from .parameters import (
|
|
23
|
+
DeviceParams,
|
|
24
|
+
JogParams,
|
|
25
|
+
PositionParams,
|
|
26
|
+
HomingParams,
|
|
27
|
+
VersionParams,
|
|
28
|
+
MotorRHParams,
|
|
29
|
+
PIDParams,
|
|
30
|
+
HomingStatus,
|
|
31
|
+
MotorStatus,
|
|
32
|
+
SystemStatusParams,
|
|
33
|
+
ConfigParams,
|
|
34
|
+
AutoRunParams,
|
|
35
|
+
)
|
|
36
|
+
from .commands import (
|
|
37
|
+
# 触发动作命令
|
|
38
|
+
CalibrateEncoder,
|
|
39
|
+
Restart,
|
|
40
|
+
ZeroPosition,
|
|
41
|
+
ClearProtection,
|
|
42
|
+
FactoryReset,
|
|
43
|
+
# 运动控制命令
|
|
44
|
+
Enable,
|
|
45
|
+
Disable,
|
|
46
|
+
Jog,
|
|
47
|
+
Position,
|
|
48
|
+
EStop,
|
|
49
|
+
SyncMove,
|
|
50
|
+
# 原点回零命令
|
|
51
|
+
SetHomeZero,
|
|
52
|
+
Home,
|
|
53
|
+
StopHome,
|
|
54
|
+
GetHomingStatus,
|
|
55
|
+
GetHomingParams,
|
|
56
|
+
SetHomingParams,
|
|
57
|
+
# 读取系统参数命令
|
|
58
|
+
GetVersion,
|
|
59
|
+
GetMotorRH,
|
|
60
|
+
GetBusVoltage,
|
|
61
|
+
GetBusCurrent,
|
|
62
|
+
GetPhaseCurrent,
|
|
63
|
+
GetEncoder,
|
|
64
|
+
GetPulseCount,
|
|
65
|
+
GetTargetPosition,
|
|
66
|
+
GetRealtimeSpeed,
|
|
67
|
+
GetRealtimePosition,
|
|
68
|
+
GetPositionError,
|
|
69
|
+
GetTemperature,
|
|
70
|
+
GetMotorStatus,
|
|
71
|
+
GetPID,
|
|
72
|
+
GetConfig,
|
|
73
|
+
GetSystemStatus,
|
|
74
|
+
# 设置命令
|
|
75
|
+
SetID,
|
|
76
|
+
SetMicrostep,
|
|
77
|
+
SetLoopMode,
|
|
78
|
+
SetOpenLoopCurrent,
|
|
79
|
+
SetClosedLoopCurrent,
|
|
80
|
+
SetPID,
|
|
81
|
+
SetMotorDirection,
|
|
82
|
+
SetPositionWindow,
|
|
83
|
+
SetHeartbeatTime,
|
|
84
|
+
SetAutoRun,
|
|
85
|
+
SetConfig,
|
|
86
|
+
SetScaleInput,
|
|
87
|
+
SetLockButton,
|
|
88
|
+
BroadcastGetID,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
logger = logging.getLogger(__name__)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class EmmDevice:
|
|
95
|
+
"""Emm固件步进电机设备类.
|
|
96
|
+
|
|
97
|
+
提供高级API接口用于控制ZDT X42S第二代闭环步进电机(Emm固件)。
|
|
98
|
+
|
|
99
|
+
注意: 串口连接需要在外部创建,这样可以支持一个串口连接多个电机(通过不同地址)。
|
|
100
|
+
|
|
101
|
+
使用示例:
|
|
102
|
+
```python
|
|
103
|
+
from serial import Serial
|
|
104
|
+
from emm_stepper import EmmDevice, ChecksumMode
|
|
105
|
+
|
|
106
|
+
# 创建串口连接
|
|
107
|
+
ser = Serial('COM3', 115200, timeout=1)
|
|
108
|
+
|
|
109
|
+
# 创建设备实例(可以用同一个串口创建多个不同地址的电机)
|
|
110
|
+
motor1 = EmmDevice(ser, address=1)
|
|
111
|
+
motor2 = EmmDevice(ser, address=2)
|
|
112
|
+
|
|
113
|
+
# 使能电机
|
|
114
|
+
motor1.enable()
|
|
115
|
+
|
|
116
|
+
# 速度模式运动
|
|
117
|
+
motor1.jog(speed=100, direction=0)
|
|
118
|
+
|
|
119
|
+
# 位置模式运动
|
|
120
|
+
motor1.move_pulses(pulses=3200, speed=500) # 转一圈
|
|
121
|
+
|
|
122
|
+
# 停止
|
|
123
|
+
motor1.stop()
|
|
124
|
+
|
|
125
|
+
# 失能电机
|
|
126
|
+
motor1.disable()
|
|
127
|
+
|
|
128
|
+
# 关闭串口(由用户管理)
|
|
129
|
+
ser.close()
|
|
130
|
+
```
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
def __init__(
|
|
134
|
+
self,
|
|
135
|
+
serial_connection: Serial,
|
|
136
|
+
address: int = 1,
|
|
137
|
+
checksum_mode: ChecksumMode = ChecksumMode.FIXED,
|
|
138
|
+
delay: Optional[float] = None,
|
|
139
|
+
auto_test: bool = True,
|
|
140
|
+
):
|
|
141
|
+
"""初始化设备.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
serial_connection: 串口连接对象
|
|
145
|
+
address: 电机地址 (1-255)
|
|
146
|
+
checksum_mode: 校验模式
|
|
147
|
+
delay: 通讯延迟(秒)
|
|
148
|
+
auto_test: 是否自动测试连接 (默认True)
|
|
149
|
+
"""
|
|
150
|
+
self._serial = serial_connection
|
|
151
|
+
self._device_params = DeviceParams(
|
|
152
|
+
serial_connection=serial_connection,
|
|
153
|
+
address=address,
|
|
154
|
+
checksum_mode=checksum_mode,
|
|
155
|
+
delay=delay,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# 测试连接
|
|
159
|
+
if auto_test:
|
|
160
|
+
self._test_connection()
|
|
161
|
+
|
|
162
|
+
logger.info(f"已连接到电机 (地址: {address})")
|
|
163
|
+
|
|
164
|
+
def _test_connection(self) -> None:
|
|
165
|
+
"""测试与电机的连接."""
|
|
166
|
+
try:
|
|
167
|
+
version = self.get_version()
|
|
168
|
+
logger.info(f"固件版本: {version.firmware_version_str}, 硬件: {version.hw_type_str}")
|
|
169
|
+
except Exception as e:
|
|
170
|
+
raise ConnectionError(f"无法连接到电机: {e}")
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def device_params(self) -> DeviceParams:
|
|
174
|
+
"""返回设备参数."""
|
|
175
|
+
return self._device_params
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def address(self) -> int:
|
|
179
|
+
"""返回电机地址."""
|
|
180
|
+
return self._device_params.address
|
|
181
|
+
|
|
182
|
+
# ==================== 触发动作命令 ====================
|
|
183
|
+
|
|
184
|
+
def calibrate_encoder(self) -> bool:
|
|
185
|
+
"""触发编码器校准.
|
|
186
|
+
|
|
187
|
+
电机将缓慢正转一圈然后反转一圈对编码器进行线性化校准。
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
是否成功
|
|
191
|
+
"""
|
|
192
|
+
cmd = CalibrateEncoder(self._device_params)
|
|
193
|
+
return cmd.is_success
|
|
194
|
+
|
|
195
|
+
def restart(self) -> bool:
|
|
196
|
+
"""重启电机.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
是否成功
|
|
200
|
+
"""
|
|
201
|
+
cmd = Restart(self._device_params)
|
|
202
|
+
return cmd.is_success
|
|
203
|
+
|
|
204
|
+
def zero_position(self) -> bool:
|
|
205
|
+
"""将当前位置角度清零.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
是否成功
|
|
209
|
+
"""
|
|
210
|
+
cmd = ZeroPosition(self._device_params)
|
|
211
|
+
return cmd.is_success
|
|
212
|
+
|
|
213
|
+
def clear_protection(self) -> bool:
|
|
214
|
+
"""解除堵转/过热/过流保护.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
是否成功
|
|
218
|
+
"""
|
|
219
|
+
cmd = ClearProtection(self._device_params)
|
|
220
|
+
return cmd.is_success
|
|
221
|
+
|
|
222
|
+
def factory_reset(self) -> bool:
|
|
223
|
+
"""恢复出厂设置.
|
|
224
|
+
|
|
225
|
+
注意: 恢复后需要断电重新上电,再空载校准电机编码器。
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
是否成功
|
|
229
|
+
"""
|
|
230
|
+
cmd = FactoryReset(self._device_params)
|
|
231
|
+
return cmd.is_success
|
|
232
|
+
|
|
233
|
+
# ==================== 运动控制命令 ====================
|
|
234
|
+
|
|
235
|
+
def enable(self, sync: bool = False) -> bool:
|
|
236
|
+
"""使能电机(锁轴).
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
sync: 是否使用同步模式
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
是否成功
|
|
243
|
+
"""
|
|
244
|
+
sync_flag = SyncFlag.SYNC if sync else SyncFlag.IMMEDIATE
|
|
245
|
+
cmd = Enable(self._device_params, enable=True, sync_flag=sync_flag)
|
|
246
|
+
return cmd.is_success
|
|
247
|
+
|
|
248
|
+
def disable(self, sync: bool = False) -> bool:
|
|
249
|
+
"""失能电机(松轴).
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
sync: 是否使用同步模式
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
是否成功
|
|
256
|
+
"""
|
|
257
|
+
sync_flag = SyncFlag.SYNC if sync else SyncFlag.IMMEDIATE
|
|
258
|
+
cmd = Disable(self._device_params, sync_flag=sync_flag)
|
|
259
|
+
return cmd.is_success
|
|
260
|
+
|
|
261
|
+
def jog(
|
|
262
|
+
self,
|
|
263
|
+
speed: int = 100,
|
|
264
|
+
direction: Direction = Direction.CW,
|
|
265
|
+
acceleration: int = 10,
|
|
266
|
+
sync: bool = False,
|
|
267
|
+
) -> bool:
|
|
268
|
+
"""速度模式运动.
|
|
269
|
+
|
|
270
|
+
电机以指定速度持续运动,直到收到停止命令。
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
speed: 速度 (0-3000 RPM)
|
|
274
|
+
direction: 运动方向
|
|
275
|
+
acceleration: 加速度档位 (0-255)
|
|
276
|
+
sync: 是否使用同步模式
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
是否成功
|
|
280
|
+
"""
|
|
281
|
+
params = JogParams(
|
|
282
|
+
direction=direction,
|
|
283
|
+
speed=speed,
|
|
284
|
+
acceleration=acceleration,
|
|
285
|
+
sync_flag=SyncFlag.SYNC if sync else SyncFlag.IMMEDIATE,
|
|
286
|
+
)
|
|
287
|
+
cmd = Jog(self._device_params, params=params)
|
|
288
|
+
return cmd.is_success
|
|
289
|
+
|
|
290
|
+
def move_pulses(
|
|
291
|
+
self,
|
|
292
|
+
pulse_count: int,
|
|
293
|
+
speed: int = 100,
|
|
294
|
+
direction: Optional[Direction] = None,
|
|
295
|
+
acceleration: int = 10,
|
|
296
|
+
motion_mode: MotionMode = MotionMode.RELATIVE_LAST,
|
|
297
|
+
sync: bool = False,
|
|
298
|
+
) -> bool:
|
|
299
|
+
"""位置模式运动(脉冲数).
|
|
300
|
+
|
|
301
|
+
电机运动指定的脉冲数。默认16细分下,3200个脉冲=一圈360°。
|
|
302
|
+
|
|
303
|
+
Args:
|
|
304
|
+
pulse_count: 脉冲数 (正数为CW方向,负数为CCW方向)
|
|
305
|
+
speed: 速度 (0-3000 RPM)
|
|
306
|
+
direction: 运动方向 (如果为None,则根据pulse_count的正负自动判断)
|
|
307
|
+
acceleration: 加速度档位 (0-255)
|
|
308
|
+
motion_mode: 运动模式
|
|
309
|
+
sync: 是否使用同步模式
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
是否成功
|
|
313
|
+
"""
|
|
314
|
+
if direction is None:
|
|
315
|
+
if pulse_count < 0:
|
|
316
|
+
direction = Direction.CCW
|
|
317
|
+
pulse_count = abs(pulse_count)
|
|
318
|
+
else:
|
|
319
|
+
direction = Direction.CW
|
|
320
|
+
|
|
321
|
+
params = PositionParams(
|
|
322
|
+
direction=direction,
|
|
323
|
+
speed=speed,
|
|
324
|
+
acceleration=acceleration,
|
|
325
|
+
pulse_count=pulse_count,
|
|
326
|
+
motion_mode=motion_mode,
|
|
327
|
+
sync_flag=SyncFlag.SYNC if sync else SyncFlag.IMMEDIATE,
|
|
328
|
+
)
|
|
329
|
+
cmd = Position(self._device_params, params=params)
|
|
330
|
+
return cmd.is_success
|
|
331
|
+
|
|
332
|
+
def move_degrees(
|
|
333
|
+
self,
|
|
334
|
+
degrees: float,
|
|
335
|
+
speed: int = 100,
|
|
336
|
+
acceleration: int = 10,
|
|
337
|
+
motion_mode: MotionMode = MotionMode.RELATIVE_LAST,
|
|
338
|
+
microstep: int = 16,
|
|
339
|
+
sync: bool = False,
|
|
340
|
+
) -> bool:
|
|
341
|
+
"""位置模式运动(角度).
|
|
342
|
+
|
|
343
|
+
电机运动指定的角度。
|
|
344
|
+
|
|
345
|
+
Args:
|
|
346
|
+
degrees: 角度 (正数为CW方向,负数为CCW方向)
|
|
347
|
+
speed: 速度 (0-3000 RPM)
|
|
348
|
+
acceleration: 加速度档位 (0-255)
|
|
349
|
+
motion_mode: 运动模式
|
|
350
|
+
microstep: 细分值 (用于计算脉冲数)
|
|
351
|
+
sync: 是否使用同步模式
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
是否成功
|
|
355
|
+
"""
|
|
356
|
+
# 计算脉冲数: 1.8°电机,一圈200步,细分后一圈 = 200 * microstep 脉冲
|
|
357
|
+
pulses_per_revolution = 200 * microstep
|
|
358
|
+
pulse_count = int(degrees / 360 * pulses_per_revolution)
|
|
359
|
+
|
|
360
|
+
return self.move_pulses(
|
|
361
|
+
pulse_count=pulse_count,
|
|
362
|
+
speed=speed,
|
|
363
|
+
acceleration=acceleration,
|
|
364
|
+
motion_mode=motion_mode,
|
|
365
|
+
sync=sync,
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
def move_revolutions(
|
|
369
|
+
self,
|
|
370
|
+
revolutions: float,
|
|
371
|
+
speed: int = 100,
|
|
372
|
+
acceleration: int = 10,
|
|
373
|
+
motion_mode: MotionMode = MotionMode.RELATIVE_LAST,
|
|
374
|
+
microstep: int = 16,
|
|
375
|
+
sync: bool = False,
|
|
376
|
+
) -> bool:
|
|
377
|
+
"""位置模式运动(圈数).
|
|
378
|
+
|
|
379
|
+
电机运动指定的圈数。
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
revolutions: 圈数 (正数为CW方向,负数为CCW方向)
|
|
383
|
+
speed: 速度 (0-3000 RPM)
|
|
384
|
+
acceleration: 加速度档位 (0-255)
|
|
385
|
+
motion_mode: 运动模式
|
|
386
|
+
microstep: 细分值 (用于计算脉冲数)
|
|
387
|
+
sync: 是否使用同步模式
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
是否成功
|
|
391
|
+
"""
|
|
392
|
+
return self.move_degrees(
|
|
393
|
+
degrees=revolutions * 360,
|
|
394
|
+
speed=speed,
|
|
395
|
+
acceleration=acceleration,
|
|
396
|
+
motion_mode=motion_mode,
|
|
397
|
+
microstep=microstep,
|
|
398
|
+
sync=sync,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
def stop(self, sync: bool = False) -> bool:
|
|
402
|
+
"""立即停止电机.
|
|
403
|
+
|
|
404
|
+
Args:
|
|
405
|
+
sync: 是否使用同步模式
|
|
406
|
+
|
|
407
|
+
Returns:
|
|
408
|
+
是否成功
|
|
409
|
+
"""
|
|
410
|
+
sync_flag = SyncFlag.SYNC if sync else SyncFlag.IMMEDIATE
|
|
411
|
+
cmd = EStop(self._device_params, sync_flag=sync_flag)
|
|
412
|
+
return cmd.is_success
|
|
413
|
+
|
|
414
|
+
@staticmethod
|
|
415
|
+
def sync_move(device_params: DeviceParams) -> bool:
|
|
416
|
+
"""触发多机同步运动.
|
|
417
|
+
|
|
418
|
+
使用广播地址发送,使所有缓存了命令的电机同步开始执行。
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
device_params: 任意一个设备的参数(用于获取串口连接)
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
是否成功
|
|
425
|
+
"""
|
|
426
|
+
cmd = SyncMove(device_params)
|
|
427
|
+
return cmd.is_success
|
|
428
|
+
|
|
429
|
+
# ==================== 原点回零命令 ====================
|
|
430
|
+
|
|
431
|
+
def set_home_zero(self, store: bool = True) -> bool:
|
|
432
|
+
"""设置单圈回零的零点位置.
|
|
433
|
+
|
|
434
|
+
将当前位置设置为单圈回零的零点位置。
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
store: 是否存储(掉电不丢失)
|
|
438
|
+
|
|
439
|
+
Returns:
|
|
440
|
+
是否成功
|
|
441
|
+
"""
|
|
442
|
+
cmd = SetHomeZero(self._device_params, store=store)
|
|
443
|
+
return cmd.is_success
|
|
444
|
+
|
|
445
|
+
def home(
|
|
446
|
+
self,
|
|
447
|
+
mode: HomingMode = HomingMode.NEAREST,
|
|
448
|
+
sync: bool = False,
|
|
449
|
+
) -> bool:
|
|
450
|
+
"""触发回零.
|
|
451
|
+
|
|
452
|
+
Args:
|
|
453
|
+
mode: 回零模式
|
|
454
|
+
sync: 是否使用同步模式
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
是否成功
|
|
458
|
+
"""
|
|
459
|
+
sync_flag = SyncFlag.SYNC if sync else SyncFlag.IMMEDIATE
|
|
460
|
+
cmd = Home(self._device_params, mode=mode, sync_flag=sync_flag)
|
|
461
|
+
return cmd.is_success
|
|
462
|
+
|
|
463
|
+
def stop_home(self) -> bool:
|
|
464
|
+
"""强制中断并退出回零操作.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
是否成功
|
|
468
|
+
"""
|
|
469
|
+
cmd = StopHome(self._device_params)
|
|
470
|
+
return cmd.is_success
|
|
471
|
+
|
|
472
|
+
def get_homing_status(self) -> HomingStatus:
|
|
473
|
+
"""读取回零状态标志.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
回零状态
|
|
477
|
+
"""
|
|
478
|
+
cmd = GetHomingStatus(self._device_params)
|
|
479
|
+
return cmd.data
|
|
480
|
+
|
|
481
|
+
def get_homing_params(self) -> HomingParams:
|
|
482
|
+
"""读取回零参数.
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
回零参数
|
|
486
|
+
"""
|
|
487
|
+
cmd = GetHomingParams(self._device_params)
|
|
488
|
+
return cmd.data
|
|
489
|
+
|
|
490
|
+
def set_homing_params(self, params: HomingParams, store: bool = True) -> bool:
|
|
491
|
+
"""修改回零参数.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
params: 回零参数
|
|
495
|
+
store: 是否存储
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
是否成功
|
|
499
|
+
"""
|
|
500
|
+
cmd = SetHomingParams(self._device_params, params=params, store=store)
|
|
501
|
+
return cmd.is_success
|
|
502
|
+
|
|
503
|
+
# ==================== 读取系统参数命令 ====================
|
|
504
|
+
|
|
505
|
+
def get_version(self) -> VersionParams:
|
|
506
|
+
"""读取固件版本和硬件版本.
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
版本参数
|
|
510
|
+
"""
|
|
511
|
+
cmd = GetVersion(self._device_params)
|
|
512
|
+
return cmd.data
|
|
513
|
+
|
|
514
|
+
def get_motor_rh(self) -> MotorRHParams:
|
|
515
|
+
"""读取相电阻和相电感.
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
电机RH参数
|
|
519
|
+
"""
|
|
520
|
+
cmd = GetMotorRH(self._device_params)
|
|
521
|
+
return cmd.data
|
|
522
|
+
|
|
523
|
+
def get_bus_voltage(self) -> int:
|
|
524
|
+
"""读取总线电压.
|
|
525
|
+
|
|
526
|
+
Returns:
|
|
527
|
+
总线电压(mV)
|
|
528
|
+
"""
|
|
529
|
+
cmd = GetBusVoltage(self._device_params)
|
|
530
|
+
return cmd.data
|
|
531
|
+
|
|
532
|
+
def get_bus_current(self) -> int:
|
|
533
|
+
"""读取总线电流.
|
|
534
|
+
|
|
535
|
+
Returns:
|
|
536
|
+
总线电流(mA)
|
|
537
|
+
"""
|
|
538
|
+
cmd = GetBusCurrent(self._device_params)
|
|
539
|
+
return cmd.data
|
|
540
|
+
|
|
541
|
+
def get_phase_current(self) -> int:
|
|
542
|
+
"""读取相电流.
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
相电流(mA)
|
|
546
|
+
"""
|
|
547
|
+
cmd = GetPhaseCurrent(self._device_params)
|
|
548
|
+
return cmd.data
|
|
549
|
+
|
|
550
|
+
def get_encoder(self) -> int:
|
|
551
|
+
"""读取线性化编码器值.
|
|
552
|
+
|
|
553
|
+
Returns:
|
|
554
|
+
编码器值(0-65535表示0-360度)
|
|
555
|
+
"""
|
|
556
|
+
cmd = GetEncoder(self._device_params)
|
|
557
|
+
return cmd.data
|
|
558
|
+
|
|
559
|
+
def get_encoder_degrees(self) -> float:
|
|
560
|
+
"""读取编码器角度.
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
编码器角度(度)
|
|
564
|
+
"""
|
|
565
|
+
encoder = self.get_encoder()
|
|
566
|
+
return (encoder * 360) / 65536
|
|
567
|
+
|
|
568
|
+
def get_pulse_count(self) -> int:
|
|
569
|
+
"""读取输入脉冲数.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
输入脉冲数(带符号)
|
|
573
|
+
"""
|
|
574
|
+
cmd = GetPulseCount(self._device_params)
|
|
575
|
+
return cmd.data
|
|
576
|
+
|
|
577
|
+
def get_target_position(self) -> float:
|
|
578
|
+
"""读取电机目标位置.
|
|
579
|
+
|
|
580
|
+
Returns:
|
|
581
|
+
目标位置角度(度)
|
|
582
|
+
"""
|
|
583
|
+
cmd = GetTargetPosition(self._device_params)
|
|
584
|
+
return cmd.data
|
|
585
|
+
|
|
586
|
+
def get_realtime_speed(self) -> int:
|
|
587
|
+
"""读取电机实时转速.
|
|
588
|
+
|
|
589
|
+
Returns:
|
|
590
|
+
实时转速(RPM, 带符号)
|
|
591
|
+
"""
|
|
592
|
+
cmd = GetRealtimeSpeed(self._device_params)
|
|
593
|
+
return cmd.data
|
|
594
|
+
|
|
595
|
+
def get_realtime_position(self) -> float:
|
|
596
|
+
"""读取电机实时位置.
|
|
597
|
+
|
|
598
|
+
Returns:
|
|
599
|
+
实时位置角度(度)
|
|
600
|
+
"""
|
|
601
|
+
cmd = GetRealtimePosition(self._device_params)
|
|
602
|
+
return cmd.data
|
|
603
|
+
|
|
604
|
+
def get_position_error(self) -> float:
|
|
605
|
+
"""读取电机位置误差.
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
位置误差角度(度)
|
|
609
|
+
"""
|
|
610
|
+
cmd = GetPositionError(self._device_params)
|
|
611
|
+
return cmd.data
|
|
612
|
+
|
|
613
|
+
def get_temperature(self) -> int:
|
|
614
|
+
"""读取驱动温度.
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
温度(°C)
|
|
618
|
+
"""
|
|
619
|
+
cmd = GetTemperature(self._device_params)
|
|
620
|
+
return cmd.data
|
|
621
|
+
|
|
622
|
+
def get_motor_status(self) -> MotorStatus:
|
|
623
|
+
"""读取电机状态标志.
|
|
624
|
+
|
|
625
|
+
Returns:
|
|
626
|
+
电机状态
|
|
627
|
+
"""
|
|
628
|
+
cmd = GetMotorStatus(self._device_params)
|
|
629
|
+
return cmd.data
|
|
630
|
+
|
|
631
|
+
def get_pid(self) -> PIDParams:
|
|
632
|
+
"""读取PID参数.
|
|
633
|
+
|
|
634
|
+
Returns:
|
|
635
|
+
PID参数
|
|
636
|
+
"""
|
|
637
|
+
cmd = GetPID(self._device_params)
|
|
638
|
+
return cmd.data
|
|
639
|
+
|
|
640
|
+
def get_config(self) -> ConfigParams:
|
|
641
|
+
"""读取驱动配置参数.
|
|
642
|
+
|
|
643
|
+
Returns:
|
|
644
|
+
配置参数
|
|
645
|
+
"""
|
|
646
|
+
cmd = GetConfig(self._device_params)
|
|
647
|
+
return cmd.data
|
|
648
|
+
|
|
649
|
+
def get_system_status(self) -> SystemStatusParams:
|
|
650
|
+
"""读取系统状态参数.
|
|
651
|
+
|
|
652
|
+
Returns:
|
|
653
|
+
系统状态参数
|
|
654
|
+
"""
|
|
655
|
+
cmd = GetSystemStatus(self._device_params)
|
|
656
|
+
return cmd.data
|
|
657
|
+
|
|
658
|
+
# ==================== 设置命令 ====================
|
|
659
|
+
|
|
660
|
+
def set_id(self, new_id: int, store: bool = True) -> bool:
|
|
661
|
+
"""修改电机ID/地址.
|
|
662
|
+
|
|
663
|
+
Args:
|
|
664
|
+
new_id: 新ID (1-255)
|
|
665
|
+
store: 是否存储
|
|
666
|
+
|
|
667
|
+
Returns:
|
|
668
|
+
是否成功
|
|
669
|
+
"""
|
|
670
|
+
cmd = SetID(self._device_params, new_id=new_id, store=store)
|
|
671
|
+
if cmd.is_success:
|
|
672
|
+
self._device_params.address = Address(new_id)
|
|
673
|
+
return cmd.is_success
|
|
674
|
+
|
|
675
|
+
def set_microstep(self, microstep: int, store: bool = True) -> bool:
|
|
676
|
+
"""修改细分值.
|
|
677
|
+
|
|
678
|
+
Args:
|
|
679
|
+
microstep: 细分值 (1-256)
|
|
680
|
+
store: 是否存储
|
|
681
|
+
|
|
682
|
+
Returns:
|
|
683
|
+
是否成功
|
|
684
|
+
"""
|
|
685
|
+
cmd = SetMicrostep(self._device_params, microstep=microstep, store=store)
|
|
686
|
+
return cmd.is_success
|
|
687
|
+
|
|
688
|
+
def set_loop_mode(self, closed_loop: bool = True, store: bool = True) -> bool:
|
|
689
|
+
"""修改开环/闭环控制模式.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
closed_loop: True为闭环模式,False为开环模式
|
|
693
|
+
store: 是否存储
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
是否成功
|
|
697
|
+
"""
|
|
698
|
+
cmd = SetLoopMode(self._device_params, closed_loop=closed_loop, store=store)
|
|
699
|
+
return cmd.is_success
|
|
700
|
+
|
|
701
|
+
def set_open_loop_current(self, current_ma: int, store: bool = True) -> bool:
|
|
702
|
+
"""修改开环模式工作电流.
|
|
703
|
+
|
|
704
|
+
Args:
|
|
705
|
+
current_ma: 电流(mA, 0-5000)
|
|
706
|
+
store: 是否存储
|
|
707
|
+
|
|
708
|
+
Returns:
|
|
709
|
+
是否成功
|
|
710
|
+
"""
|
|
711
|
+
cmd = SetOpenLoopCurrent(self._device_params, current_ma=current_ma, store=store)
|
|
712
|
+
return cmd.is_success
|
|
713
|
+
|
|
714
|
+
def set_closed_loop_current(self, current_ma: int, store: bool = True) -> bool:
|
|
715
|
+
"""修改闭环模式最大电流.
|
|
716
|
+
|
|
717
|
+
Args:
|
|
718
|
+
current_ma: 电流(mA, 0-5000)
|
|
719
|
+
store: 是否存储
|
|
720
|
+
|
|
721
|
+
Returns:
|
|
722
|
+
是否成功
|
|
723
|
+
"""
|
|
724
|
+
cmd = SetClosedLoopCurrent(self._device_params, current_ma=current_ma, store=store)
|
|
725
|
+
return cmd.is_success
|
|
726
|
+
|
|
727
|
+
def set_pid(self, params: PIDParams, store: bool = True) -> bool:
|
|
728
|
+
"""修改PID参数.
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
params: PID参数
|
|
732
|
+
store: 是否存储
|
|
733
|
+
|
|
734
|
+
Returns:
|
|
735
|
+
是否成功
|
|
736
|
+
"""
|
|
737
|
+
cmd = SetPID(self._device_params, params=params, store=store)
|
|
738
|
+
return cmd.is_success
|
|
739
|
+
|
|
740
|
+
def set_motor_direction(self, direction: Direction = Direction.CW, store: bool = True) -> bool:
|
|
741
|
+
"""修改电机运动正方向.
|
|
742
|
+
|
|
743
|
+
Args:
|
|
744
|
+
direction: 运动方向
|
|
745
|
+
store: 是否存储
|
|
746
|
+
|
|
747
|
+
Returns:
|
|
748
|
+
是否成功
|
|
749
|
+
"""
|
|
750
|
+
cmd = SetMotorDirection(self._device_params, direction=direction, store=store)
|
|
751
|
+
return cmd.is_success
|
|
752
|
+
|
|
753
|
+
def set_position_window(self, window_deg: float = 0.8, store: bool = True) -> bool:
|
|
754
|
+
"""修改位置到达窗口.
|
|
755
|
+
|
|
756
|
+
Args:
|
|
757
|
+
window_deg: 位置到达窗口(度)
|
|
758
|
+
store: 是否存储
|
|
759
|
+
|
|
760
|
+
Returns:
|
|
761
|
+
是否成功
|
|
762
|
+
"""
|
|
763
|
+
cmd = SetPositionWindow(self._device_params, window_deg=window_deg, store=store)
|
|
764
|
+
return cmd.is_success
|
|
765
|
+
|
|
766
|
+
def set_heartbeat_time(self, time_ms: int = 0, store: bool = True) -> bool:
|
|
767
|
+
"""修改心跳保护功能时间.
|
|
768
|
+
|
|
769
|
+
Args:
|
|
770
|
+
time_ms: 心跳保护时间(毫秒), 0表示关闭
|
|
771
|
+
store: 是否存储
|
|
772
|
+
|
|
773
|
+
Returns:
|
|
774
|
+
是否成功
|
|
775
|
+
"""
|
|
776
|
+
cmd = SetHeartbeatTime(self._device_params, time_ms=time_ms, store=store)
|
|
777
|
+
return cmd.is_success
|
|
778
|
+
|
|
779
|
+
def set_auto_run(self, params: AutoRunParams) -> bool:
|
|
780
|
+
"""存储一组速度参数,上电自动运行.
|
|
781
|
+
|
|
782
|
+
Args:
|
|
783
|
+
params: 自动运行参数
|
|
784
|
+
|
|
785
|
+
Returns:
|
|
786
|
+
是否成功
|
|
787
|
+
"""
|
|
788
|
+
cmd = SetAutoRun(self._device_params, params=params)
|
|
789
|
+
return cmd.is_success
|
|
790
|
+
|
|
791
|
+
def set_config(self, params: ConfigParams, store: bool = True) -> bool:
|
|
792
|
+
"""修改驱动配置参数.
|
|
793
|
+
|
|
794
|
+
Args:
|
|
795
|
+
params: 配置参数
|
|
796
|
+
store: 是否存储
|
|
797
|
+
|
|
798
|
+
Returns:
|
|
799
|
+
是否成功
|
|
800
|
+
"""
|
|
801
|
+
cmd = SetConfig(self._device_params, params=params, store=store)
|
|
802
|
+
return cmd.is_success
|
|
803
|
+
|
|
804
|
+
def set_scale_input(self, enable: bool = False, store: bool = True) -> bool:
|
|
805
|
+
"""修改命令速度值是否缩小10倍输入.
|
|
806
|
+
|
|
807
|
+
Args:
|
|
808
|
+
enable: 是否使能(使能后输入1RPM实际为0.1RPM)
|
|
809
|
+
store: 是否存储
|
|
810
|
+
|
|
811
|
+
Returns:
|
|
812
|
+
是否成功
|
|
813
|
+
"""
|
|
814
|
+
cmd = SetScaleInput(self._device_params, enable=enable, store=store)
|
|
815
|
+
return cmd.is_success
|
|
816
|
+
|
|
817
|
+
def set_lock_button(self, lock: bool = False, store: bool = True) -> bool:
|
|
818
|
+
"""修改锁定按键功能.
|
|
819
|
+
|
|
820
|
+
Args:
|
|
821
|
+
lock: 是否锁定
|
|
822
|
+
store: 是否存储
|
|
823
|
+
|
|
824
|
+
Returns:
|
|
825
|
+
是否成功
|
|
826
|
+
"""
|
|
827
|
+
cmd = SetLockButton(self._device_params, lock=lock, store=store)
|
|
828
|
+
return cmd.is_success
|
|
829
|
+
|
|
830
|
+
@staticmethod
|
|
831
|
+
def broadcast_get_id(serial_connection: Serial) -> int:
|
|
832
|
+
"""广播读取ID地址.
|
|
833
|
+
|
|
834
|
+
当忘记电机ID地址时,可以单独接线该电机,广播读取该电机的地址。
|
|
835
|
+
|
|
836
|
+
Args:
|
|
837
|
+
serial_connection: 串口连接
|
|
838
|
+
|
|
839
|
+
Returns:
|
|
840
|
+
电机ID
|
|
841
|
+
"""
|
|
842
|
+
device_params = DeviceParams(
|
|
843
|
+
serial_connection=serial_connection,
|
|
844
|
+
address=Address.BROADCAST,
|
|
845
|
+
)
|
|
846
|
+
cmd = BroadcastGetID(device_params)
|
|
847
|
+
return cmd.data
|
|
848
|
+
|
|
849
|
+
# ==================== 便捷方法 ====================
|
|
850
|
+
|
|
851
|
+
def is_enabled(self) -> bool:
|
|
852
|
+
"""检查电机是否使能.
|
|
853
|
+
|
|
854
|
+
Returns:
|
|
855
|
+
是否使能
|
|
856
|
+
"""
|
|
857
|
+
status = self.get_motor_status()
|
|
858
|
+
return status.enabled
|
|
859
|
+
|
|
860
|
+
def is_position_reached(self) -> bool:
|
|
861
|
+
"""检查是否到达目标位置.
|
|
862
|
+
|
|
863
|
+
Returns:
|
|
864
|
+
是否到达
|
|
865
|
+
"""
|
|
866
|
+
status = self.get_motor_status()
|
|
867
|
+
return status.position_reached
|
|
868
|
+
|
|
869
|
+
def is_stalled(self) -> bool:
|
|
870
|
+
"""检查是否堵转.
|
|
871
|
+
|
|
872
|
+
Returns:
|
|
873
|
+
是否堵转
|
|
874
|
+
"""
|
|
875
|
+
status = self.get_motor_status()
|
|
876
|
+
return status.stall_detected or status.stall_protected
|
|
877
|
+
|
|
878
|
+
def wait_for_position(self, timeout: float = 10.0, poll_interval: float = 0.1) -> bool:
|
|
879
|
+
"""等待电机到达目标位置.
|
|
880
|
+
|
|
881
|
+
Args:
|
|
882
|
+
timeout: 超时时间(秒)
|
|
883
|
+
poll_interval: 轮询间隔(秒)
|
|
884
|
+
|
|
885
|
+
Returns:
|
|
886
|
+
是否到达目标位置
|
|
887
|
+
"""
|
|
888
|
+
import time
|
|
889
|
+
start_time = time.time()
|
|
890
|
+
while time.time() - start_time < timeout:
|
|
891
|
+
if self.is_position_reached():
|
|
892
|
+
return True
|
|
893
|
+
time.sleep(poll_interval)
|
|
894
|
+
return False
|
|
895
|
+
|
|
896
|
+
def __repr__(self) -> str:
|
|
897
|
+
return f"EmmDevice(address={self.address})"
|
|
898
|
+
|
|
899
|
+
def __str__(self) -> str:
|
|
900
|
+
return f"Emm步进电机 (地址: {self.address})"
|