Skip to content

Commit 151fc10

Browse files
authored
Merge pull request #32 from akruszew/voltage_control
Comm. Timing + PWM mode + examples
2 parents d9ba28f + cd19bf9 commit 151fc10

7 files changed

Lines changed: 626 additions & 11 deletions

File tree

emioapi/_emiomotorsparameters.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@
3434
VELOCITY_MODE = 1
3535
POSITION_MODE = 3
3636
EXT_POSITION_MODE = 4
37+
PWM_MODE = 16
3738
if MY_DXL == 'X_SERIES' or MY_DXL == 'MX_SERIES': #from https://emanual.robotis.com/docs/en/dxl/x/xm430-w210/
3839
ADDR_TORQUE_ENABLE = 64
3940
ADDR_GOAL_POSITION = 116
4041
LEN_GOAL_POSITION = 4 # Data Byte Length
4142
ADDR_GOAL_VELOCITY = 104
4243
LEN_GOAL_VELOCITY = 4 # Data Byte Length
44+
ADDR_GOAL_PWM = 100
45+
LEN_GOAL_PWM = 2
4346
ADDR_PRESENT_POSITION = 132
4447
LEN_PRESENT_POSITION = 4 # Data Byte Length
4548
ADDR_PRESENT_VELOCITY = 128

emioapi/_motorgroup.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,9 @@ def __init__(self, parameters: MotorsParametersTemplate) -> None:
145145
self.groupWriters["goal_velocity"] = GroupSyncWrite(self.portHandler, self.packetHandler,
146146
self.parameters.ADDR_GOAL_VELOCITY,
147147
self.parameters.LEN_GOAL_POSITION)
148+
self.groupWriters["goal_pwm"] = GroupSyncWrite(self.portHandler, self.packetHandler,
149+
self.parameters.ADDR_GOAL_PWM,
150+
self.parameters.LEN_GOAL_PWM)
148151
self.groupWriters["velocity_profile"] = GroupSyncWrite(self.portHandler, self.packetHandler,
149152
self.parameters.ADDR_VELOCITY_PROFILE,
150153
self.parameters.LEN_GOAL_POSITION)
@@ -222,8 +225,6 @@ def _readSyncMotorsData(self, groupSyncRead:GroupSyncRead):
222225
Raises:
223226
Exception: If the motor group is not connected or if the read operation fails.
224227
"""
225-
if not self.isConnected:
226-
raise DisconnectedException()
227228

228229
dxl_comm_result = groupSyncRead.txRxPacket()
229230
if dxl_comm_result != COMM_SUCCESS:
@@ -246,9 +247,7 @@ def __writeSyncMotorsData(self, group: GroupSyncWrite, values):
246247
group (GroupSyncWrite): The group sync write object.
247248
values (list of numbers): The values to write to the motors.
248249
"""
249-
if not self.isConnected:
250-
raise DisconnectedException()
251-
250+
252251
group.clearParam()
253252
for index, DXL_ID in enumerate(self.parameters.DXL_IDs):
254253
if group.data_length == 2:
@@ -314,6 +313,15 @@ def enableExtendedPositionMode(self):
314313
if any(t==1 for t in torques):
315314
self.enableTorque()
316315

316+
def enablePWMMode(self):
317+
torques = self.isTorqueEnable()
318+
319+
if any(t==1 for t in torques):
320+
self.disableTorque()
321+
self.__setOperatingMode(self.parameters.PWM_MODE)
322+
if any(t==1 for t in torques):
323+
self.enableTorque()
324+
317325

318326
def enablePositionMode(self):
319327
torques = self.isTorqueEnable()
@@ -342,6 +350,13 @@ def setGoalPosition(self, positions):
342350
"""
343351
self.__writeSyncMotorsData(self.groupWriters["goal_position"], positions)
344352

353+
def setGoalPWM(self, pwms):
354+
"""Set the goal velocity
355+
356+
Args:
357+
speeds (list of numbers): unit depends on motor type
358+
"""
359+
self.__writeSyncMotorsData(self.groupWriters["goal_pwm"] , pwms)
345360

346361
def setVelocityProfile(self, max_vel):
347362
"""Set the maximum velocities in position mode

emioapi/emiomotors.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ class EmioMotors:
4646
_max_vel: float = 1000 # *0.01 rev/min
4747
_goal_velocity: list = field(default_factory=lambda: [0] * len(emioparameters.DXL_IDs))
4848
_goal_position: list = field(default_factory=lambda: [0] * len(emioparameters.DXL_IDs))
49+
_goal_pwm: list = field(default_factory=lambda: [0] * len(emioparameters.DXL_IDs))
4950
_mg: motorgroup.MotorGroup = None
5051
_device_index: int = None
5152

@@ -222,6 +223,8 @@ def enablePositionMode(self):
222223
def enableExtendedPositionMode(self):
223224
self._mg.enableExtendedPositionMode()
224225

226+
def enablePWMMode(self):
227+
self._mg.enablePWMMode()
225228

226229
####################
227230
#### PROPERTIES ####
@@ -275,6 +278,18 @@ def goal_velocity(self, velocities: list):
275278
self._mg.setGoalVelocity(velocities)
276279

277280

281+
@property
282+
def goal_pwm(self) -> list:
283+
"""Get the current velocity (rev/min) of the motors."""
284+
return self._goal_pwm
285+
286+
@goal_pwm.setter
287+
def goal_pwm(self, pwms: list):
288+
"""Set the goal velocity (rev/min) of the motors."""
289+
self._goal_pwm = pwms
290+
with self._lock:
291+
self._mg.setGoalPWM(pwms)
292+
278293
@property
279294
def max_velocity(self)-> list:
280295
"""Get the current velocity (rev/min) profile of the motors."""

examples/motors_pid_position.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import numpy as np
2+
import matplotlib
3+
matplotlib.use("TkAgg")
24
import matplotlib.pyplot as plt
35
import time
4-
6+
import sys
7+
import os
8+
sys.path.append(os.path.dirname(os.path.realpath(__file__))+'/..')
59
from emioapi import EmioMotors
610

711

@@ -15,8 +19,8 @@ def main():
1519
print("Motors opened successfully.")
1620

1721
# initial and target angles
18-
init_angles = np.array([0.5, 0, 0.5, 0])
19-
target_angles = init_angles + np.array([0.4, 0, 0, 0])
22+
init_angles = np.array([0.0, 0, 0.0, 0])
23+
target_angles = init_angles + np.array([0.1, 0, 0, 0])
2024
motors.angles = init_angles
2125
time.sleep(1)
2226

@@ -42,7 +46,6 @@ def main():
4246
while time.time() - t0 < 0.3:
4347
measures.append(motors.angles)
4448
times.append(time.time())
45-
time.sleep(0.01)
4649
time.sleep(1)
4750
nb_steps1 = len(measures)
4851

@@ -51,7 +54,7 @@ def main():
5154

5255
motors.position_p_gain = [15800, 800, 15800, 800]
5356
motors.position_i_gain = [0, 0, 0, 0]
54-
motors.position_d_gain = [600, 0, 600, 0]
57+
motors.position_d_gain = [0, 0, 0, 0]
5558
print("Set second PID gains.")
5659
time.sleep(1)
5760

@@ -70,7 +73,6 @@ def main():
7073
while time.time() - t0 < 0.3:
7174
measures.append(motors.angles)
7275
times.append(time.time())
73-
time.sleep(0.01)
7476
time.sleep(1)
7577

7678
motors.close()

examples/motors_pwm.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
#!/usr/bin/env -S uv run --script
2+
3+
import time
4+
import logging
5+
import os
6+
import sys
7+
import matplotlib
8+
matplotlib.use("TkAgg")
9+
import matplotlib.pyplot as plt
10+
import numpy as np
11+
sys.path.append(os.path.dirname(os.path.realpath(__file__))+'/..')
12+
from emioapi import EmioMotors
13+
from emioapi._logging_config import logger
14+
15+
'''
16+
This example demonstrates how to use the EMIO API to control DYNAMIXEL motors in PWM mode.
17+
'''
18+
19+
def main(emio: EmioMotors, loops=500, motor_id=1):
20+
'''
21+
Main function to run the PWM test.
22+
Parameters:
23+
- emio: An instance of the EmioMotors class.
24+
- loops: Number of iterations to run the test.
25+
- motor_id: ID of the motor to test (0-3).
26+
'''
27+
# Enable PWM mode
28+
emio.enablePWMMode()
29+
time.sleep(1)
30+
emio.printStatus()
31+
32+
# Intialize lists to store data for plotting
33+
start = time.perf_counter()
34+
last = start
35+
dt = []
36+
speed = []
37+
pwm = []
38+
t = []
39+
pwm_value = [0]*4
40+
41+
# Loop to set PWM values and read velocity
42+
for i in range(loops):
43+
# Record loop time and elapsed time
44+
new = time.perf_counter()
45+
dt.append(new-last)
46+
t.append(new-start)
47+
last = new
48+
49+
# Alternate PWM values for the first half and second half of the loops
50+
if i<loops/2:
51+
pwm_value[motor_id] = 200
52+
else:
53+
pwm_value[motor_id] = 400
54+
55+
emio.goal_pwm = pwm_value
56+
pwm.append(pwm_value[motor_id])
57+
58+
velocity = emio.velocity
59+
speed.append(velocity[motor_id])
60+
61+
62+
emio.goal_pwm=[0]*4
63+
logger.info("PWM test completed. Plotting results...")
64+
logger.info(f"Average loop time: {np.mean(dt):.4f} seconds")
65+
logger.info("Plotting PWM and velocity over time...")
66+
67+
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)
68+
ax1.plot(t, pwm, '-+')
69+
ax1.legend(["pwm"])
70+
ax2.plot(t, speed, '-+')
71+
ax2.legend(["velocity"])
72+
plt.show()
73+
74+
if __name__ == "__main__":
75+
emio_motors = None
76+
try:
77+
logger.info("Starting EMIO API test...")
78+
logger.info("Opening and configuring EMIO API...")
79+
emio_motors = EmioMotors()
80+
81+
if emio_motors.open():
82+
83+
emio_motors.printStatus()
84+
85+
logger.info("Emio motors opened and configured.")
86+
logger.info("Running main function...")
87+
main(emio_motors, 500,1)
88+
89+
logger.info("Main function completed.")
90+
logger.info("Closing Emio motor connection...")
91+
92+
emio_motors.close()
93+
logger.info("Emio connection closed.")
94+
except KeyboardInterrupt:
95+
if emio_motors:
96+
emio_motors.goal_pwm=[0]*4
97+
plt.close("all")
98+
emio_motors.close()
99+
except Exception as e:
100+
logger.exception(f"An error occurred: {e}")
101+
if emio_motors:
102+
emio_motors.close()

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,8 @@ Class = 2
7272
Method = 3
7373
Function = 3
7474
Data = 3
75+
76+
[dependency-groups]
77+
dev = [
78+
"matplotlib>=3.10.8",
79+
]

0 commit comments

Comments
 (0)