odrive 0.6.9.dev0__py37-none-win_amd64.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.
- odrive/__init__.py +142 -0
- odrive/api_client.py +54 -0
- odrive/config.py +1296 -0
- odrive/crypto.py +369 -0
- odrive/data/brakeRs.json +18 -0
- odrive/data/drvs.json +82 -0
- odrive/data/encoders.json +86 -0
- odrive/data/motors.json +128 -0
- odrive/data/odrive-micro-x1.json +48 -0
- odrive/data/odrive-micro-x3.json +40 -0
- odrive/data/odrive-micro-x4.json +40 -0
- odrive/data/odrive-s1-x4.json +52 -0
- odrive/data/odrive-v4.4.json +58 -0
- odrive/data/schema-brakeR.json +6 -0
- odrive/data/schema-drv.json +6 -0
- odrive/data/schema-encoders.json +6 -0
- odrive/data/schema-motors.json +6 -0
- odrive/data/schema-odrive.json +5 -0
- odrive/data/schema.json +244 -0
- odrive/database.py +184 -0
- odrive/dfu.py +195 -0
- odrive/dfuse/DfuDevice.py +297 -0
- odrive/dfuse/__init__.py +1 -0
- odrive/enums.py +415 -0
- odrive/firmware.py +102 -0
- odrive/hw_version.py +67 -0
- odrive/legacy.py +73 -0
- odrive/legacy_config.py +123 -0
- odrive/legacy_dfu.py +442 -0
- odrive/lib/libodrive-windows-x64.dll +0 -0
- odrive/libodrive.py +325 -0
- odrive/pyfibre/fibre/__init__.py +4 -0
- odrive/pyfibre/fibre/libfibre-windows-amd64.dll +0 -0
- odrive/pyfibre/fibre/libfibre.py +1078 -0
- odrive/pyfibre/fibre/libwinpthread-1.dll +0 -0
- odrive/pyfibre/fibre/shell.py +162 -0
- odrive/pyfibre/fibre/utils.py +133 -0
- odrive/release_api.py +184 -0
- odrive/rich_text.py +134 -0
- odrive/shell.py +144 -0
- odrive/utils.py +881 -0
- odrive/version.py +1 -0
- odrive-0.6.9.dev0.data/scripts/odrive_demo.py +50 -0
- odrive-0.6.9.dev0.data/scripts/odrivetool +263 -0
- odrive-0.6.9.dev0.data/scripts/odrivetool.bat +2 -0
- odrive-0.6.9.dev0.dist-info/METADATA +22 -0
- odrive-0.6.9.dev0.dist-info/RECORD +49 -0
- odrive-0.6.9.dev0.dist-info/WHEEL +5 -0
- odrive-0.6.9.dev0.dist-info/top_level.txt +1 -0
odrive/utils.py
ADDED
|
@@ -0,0 +1,881 @@
|
|
|
1
|
+
|
|
2
|
+
import asyncio
|
|
3
|
+
import struct
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
import threading
|
|
7
|
+
import platform
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
import os
|
|
11
|
+
import odrive.enums
|
|
12
|
+
from odrive.enums import *
|
|
13
|
+
import odrive.database
|
|
14
|
+
import odrive.legacy
|
|
15
|
+
from odrive.rich_text import RichText, Color, Style
|
|
16
|
+
from odrive.hw_version import HwVersion
|
|
17
|
+
from typing import Callable, List
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
if platform.system() == 'Windows':
|
|
21
|
+
import win32console
|
|
22
|
+
import colorama
|
|
23
|
+
colorama.init()
|
|
24
|
+
except ImportError:
|
|
25
|
+
print("Could not init terminal features.")
|
|
26
|
+
print("Refer to install instructions at http://docs.odriverobotics.com/#downloading-and-installing-tools")
|
|
27
|
+
sys.stdout.flush()
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
_db = None
|
|
31
|
+
|
|
32
|
+
def fw_version_str_to_tuple(version_string):
|
|
33
|
+
"""
|
|
34
|
+
Converts a version string to a tuple of the form
|
|
35
|
+
(major, minor, revision, prerelease)
|
|
36
|
+
|
|
37
|
+
Example: "fw-v0.3.6-23" => (0, 3, 6, True)
|
|
38
|
+
|
|
39
|
+
If version_string does not match the pattern above, this function throws an
|
|
40
|
+
Exception.
|
|
41
|
+
"""
|
|
42
|
+
regex=r'.*v([0-9]+)\.([0-9]+)\.([0-9]+)(.*)'
|
|
43
|
+
if not re.match(regex, version_string):
|
|
44
|
+
raise Exception()
|
|
45
|
+
return (int(re.sub(regex, r"\1", version_string)),
|
|
46
|
+
int(re.sub(regex, r"\2", version_string)),
|
|
47
|
+
int(re.sub(regex, r"\3", version_string)),
|
|
48
|
+
(re.sub(regex, r"\4", version_string) != ""))
|
|
49
|
+
|
|
50
|
+
async def get_serial_number_str(device):
|
|
51
|
+
if hasattr(device, '_serial_number_property'):
|
|
52
|
+
return format(await device._serial_number_property.read(), 'x').upper()
|
|
53
|
+
else:
|
|
54
|
+
return "[unknown serial number]"
|
|
55
|
+
|
|
56
|
+
def get_serial_number_str_sync(device):
|
|
57
|
+
if hasattr(device, '_serial_number_property'):
|
|
58
|
+
return format(device._serial_number_property.read(), 'x').upper()
|
|
59
|
+
else:
|
|
60
|
+
return "[unknown serial number]"
|
|
61
|
+
|
|
62
|
+
async def attach_metadata(device):
|
|
63
|
+
global _db
|
|
64
|
+
device.__sealed__ = False
|
|
65
|
+
try:
|
|
66
|
+
if hasattr(device, '_hw_version_major_property') and hasattr(device, '_hw_version_minor_property') and hasattr(device, '_hw_version_variant_property'):
|
|
67
|
+
device._board = HwVersion(
|
|
68
|
+
await device._hw_version_major_property.read(),
|
|
69
|
+
await device._hw_version_minor_property.read(),
|
|
70
|
+
await device._hw_version_variant_property.read()
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
if _db is None:
|
|
74
|
+
_db = odrive.database.load()
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
metadata = _db.get_product(device._board)
|
|
78
|
+
except odrive.database.NotFoundError:
|
|
79
|
+
metadata = None
|
|
80
|
+
|
|
81
|
+
if not metadata is None:
|
|
82
|
+
device._metadata = metadata
|
|
83
|
+
for name in [a for a in dir(device) if a.startswith("axis")]:
|
|
84
|
+
axis = getattr(device, name)
|
|
85
|
+
axis.__sealed__ = False
|
|
86
|
+
try:
|
|
87
|
+
axis._metadata = metadata['inverters'][int(name[4:])]
|
|
88
|
+
finally:
|
|
89
|
+
axis.__sealed__ = True
|
|
90
|
+
else:
|
|
91
|
+
print('Device has no hw_version properties')
|
|
92
|
+
|
|
93
|
+
if hasattr(device, '_fw_version_major_property') and hasattr(device, '_fw_version_minor_property') and hasattr(device, '_fw_version_revision_property'):
|
|
94
|
+
device._fw_version = (
|
|
95
|
+
await device._fw_version_major_property.read(),
|
|
96
|
+
await device._fw_version_minor_property.read(),
|
|
97
|
+
await device._fw_version_revision_property.read()
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
device._fw_version = None
|
|
101
|
+
print('Device has no fw_version properties')
|
|
102
|
+
|
|
103
|
+
if hasattr(device, '_serial_number_property'):
|
|
104
|
+
device._serial_number = "{:08X}".format(await device._serial_number_property.read())
|
|
105
|
+
else:
|
|
106
|
+
print('Device has no serial number properties')
|
|
107
|
+
finally:
|
|
108
|
+
device.__sealed__ = True
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def calculate_thermistor_coeffs(degree, Rload, R_25, Beta, Tmin, Tmax, thermistor_bottom = False, plot = False):
|
|
113
|
+
import numpy as np
|
|
114
|
+
T_25 = 25 + 273.15 #Kelvin
|
|
115
|
+
temps = np.linspace(Tmin, Tmax, 1000)
|
|
116
|
+
tempsK = temps + 273.15
|
|
117
|
+
|
|
118
|
+
# https://en.wikipedia.org/wiki/Thermistor#B_or_%CE%B2_parameter_equation
|
|
119
|
+
r_inf = R_25 * np.exp(-Beta/T_25)
|
|
120
|
+
R_temps = r_inf * np.exp(Beta/tempsK)
|
|
121
|
+
if thermistor_bottom:
|
|
122
|
+
V = R_temps / (Rload + R_temps)
|
|
123
|
+
else:
|
|
124
|
+
V = Rload / (Rload + R_temps)
|
|
125
|
+
|
|
126
|
+
fit = np.polyfit(V, temps, degree)
|
|
127
|
+
p1 = np.poly1d(fit)
|
|
128
|
+
fit_temps = p1(V)
|
|
129
|
+
|
|
130
|
+
if plot:
|
|
131
|
+
import matplotlib.pyplot as plt
|
|
132
|
+
print(fit)
|
|
133
|
+
plt.plot(V, temps, label='actual')
|
|
134
|
+
plt.plot(V, fit_temps, label='fit')
|
|
135
|
+
plt.xlabel('normalized voltage')
|
|
136
|
+
plt.ylabel('Temp [C]')
|
|
137
|
+
plt.legend(loc=0)
|
|
138
|
+
plt.show()
|
|
139
|
+
|
|
140
|
+
return p1
|
|
141
|
+
|
|
142
|
+
class OperationAbortedException(Exception):
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
def set_motor_thermistor_coeffs(axis, Rload, R_25, Beta, Tmin, Tmax, thermistor_bottom = True):
|
|
146
|
+
coeffs = calculate_thermistor_coeffs(3, Rload, R_25, Beta, Tmin, Tmax, thermistor_bottom)
|
|
147
|
+
axis.motor.motor_thermistor.config.poly_coefficient_0 = float(coeffs[3])
|
|
148
|
+
axis.motor.motor_thermistor.config.poly_coefficient_1 = float(coeffs[2])
|
|
149
|
+
axis.motor.motor_thermistor.config.poly_coefficient_2 = float(coeffs[1])
|
|
150
|
+
axis.motor.motor_thermistor.config.poly_coefficient_3 = float(coeffs[0])
|
|
151
|
+
|
|
152
|
+
def decode_drv_faults(metadata, code):
|
|
153
|
+
active_flags = []
|
|
154
|
+
i = 0
|
|
155
|
+
for bit, name in metadata['faults']:
|
|
156
|
+
if code & (1 << bit):
|
|
157
|
+
active_flags.append(name)
|
|
158
|
+
code ^= (1 << bit)
|
|
159
|
+
assert(code == 0)
|
|
160
|
+
|
|
161
|
+
if len(active_flags) == len(metadata['faults']):
|
|
162
|
+
return "unpowered"
|
|
163
|
+
if any(active_flags):
|
|
164
|
+
return ", ".join(active_flags)
|
|
165
|
+
else:
|
|
166
|
+
return "None"
|
|
167
|
+
|
|
168
|
+
def get_issues(odrv):
|
|
169
|
+
return [odrv.issues.get(i) for i in range(odrv.issues.length)]
|
|
170
|
+
|
|
171
|
+
def format_errors(odrv, clear=False):
|
|
172
|
+
"""
|
|
173
|
+
Returns a summary of the error status of the device as RichText.
|
|
174
|
+
"""
|
|
175
|
+
is_legacy_firmware = (odrv.fw_version_major, odrv.fw_version_minor) < (0, 6)
|
|
176
|
+
if is_legacy_firmware:
|
|
177
|
+
return odrive.legacy.format_errors(odrv, clear)
|
|
178
|
+
|
|
179
|
+
lines = []
|
|
180
|
+
|
|
181
|
+
STYLE_GOOD = (Color.GREEN, Color.DEFAULT, Style.BOLD)
|
|
182
|
+
STYLE_WARN = (Color.YELLOW, Color.DEFAULT, Style.BOLD)
|
|
183
|
+
STYLE_BAD = (Color.RED, Color.DEFAULT, Style.BOLD)
|
|
184
|
+
|
|
185
|
+
axes = [(name, getattr(odrv, name)) for name in dir(odrv) if name.startswith('axis')]
|
|
186
|
+
axes.sort()
|
|
187
|
+
|
|
188
|
+
def decode_flags(val, enum_type):
|
|
189
|
+
errorcodes = {v.value: f"{enum_type.__name__}.{v.name}" for v in enum_type}
|
|
190
|
+
if val == 0:
|
|
191
|
+
return [RichText("no error", *STYLE_GOOD)]
|
|
192
|
+
else:
|
|
193
|
+
return [RichText("Error(s):", *STYLE_BAD)] + [
|
|
194
|
+
RichText(errorcodes.get((1 << bit), 'UNKNOWN ERROR: 0x{:08X}'.format(1 << bit)), *STYLE_BAD)
|
|
195
|
+
for bit in range(64) if val & (1 << bit) != 0]
|
|
196
|
+
|
|
197
|
+
def decode_enum(val, enum_type):
|
|
198
|
+
errorcodes = {v.value: f"{enum_type.__name__}.{v.name}" for v in enum_type}
|
|
199
|
+
return [RichText(errorcodes.get(val, 'Unknown value: ' + str(val)), *(STYLE_GOOD if (val == 0) else STYLE_BAD))]
|
|
200
|
+
|
|
201
|
+
def decode_drv_fault(axis, val):
|
|
202
|
+
if val == 0:
|
|
203
|
+
return [RichText("none", *STYLE_GOOD)]
|
|
204
|
+
elif not hasattr(axis, '_metadata'):
|
|
205
|
+
return [RichText("metadata not loaded", *STYLE_WARN)]
|
|
206
|
+
else:
|
|
207
|
+
return [RichText(odrive.utils.decode_drv_faults(axis._metadata['drv'], val), *STYLE_BAD)]
|
|
208
|
+
|
|
209
|
+
def dump_item(indent, name, obj, path, decoder):
|
|
210
|
+
prefix = indent + name.strip('0123456789') + ": "
|
|
211
|
+
for elem in path.split('.'):
|
|
212
|
+
if not hasattr(obj, elem):
|
|
213
|
+
return []
|
|
214
|
+
obj = getattr(obj, elem)
|
|
215
|
+
|
|
216
|
+
lines = decoder(obj)
|
|
217
|
+
lines = [indent + name + ": " + lines[0]] + [
|
|
218
|
+
indent + " " + line
|
|
219
|
+
for line in lines[1:]
|
|
220
|
+
]
|
|
221
|
+
return lines
|
|
222
|
+
|
|
223
|
+
lines += dump_item("", "system", odrv, 'error', lambda x: decode_flags(x, odrive.enums.ODriveError))
|
|
224
|
+
|
|
225
|
+
for name, axis in axes:
|
|
226
|
+
lines.append(name)
|
|
227
|
+
lines += dump_item(" ", 'error', axis, 'error', lambda x: decode_flags(x, odrive.enums.AxisError)) # this is 0.6.0 legacy
|
|
228
|
+
lines += dump_item(" ", 'active_errors', axis, 'active_errors', lambda x: decode_flags(x, odrive.enums.ODriveError))
|
|
229
|
+
lines += dump_item(" ", 'disarm_reason', axis, 'disarm_reason', lambda x: decode_flags(x, odrive.enums.ODriveError))
|
|
230
|
+
lines += dump_item(" ", 'procedure_result', axis, 'procedure_result', lambda x: decode_enum(x, odrive.enums.ProcedureResult))
|
|
231
|
+
lines += dump_item(" ", 'last_drv_fault', axis, 'last_drv_fault', lambda x: decode_drv_fault(axis, x))
|
|
232
|
+
|
|
233
|
+
if hasattr(odrv, 'issues') and hasattr(odrv.issues, 'length') and hasattr(odrv.issues, 'get'):
|
|
234
|
+
issues = get_issues(odrv)
|
|
235
|
+
if len(issues) == 0:
|
|
236
|
+
lines.append("internal issues: " + RichText("none", *STYLE_GOOD))
|
|
237
|
+
else:
|
|
238
|
+
lines.append("internal issues: " + RichText(str(len(issues)), *STYLE_BAD))
|
|
239
|
+
lines.append("details for bug report: " + RichText(str(issues), *STYLE_WARN))
|
|
240
|
+
|
|
241
|
+
if clear:
|
|
242
|
+
odrv.clear_errors()
|
|
243
|
+
|
|
244
|
+
if clear:
|
|
245
|
+
odrv.clear_errors()
|
|
246
|
+
|
|
247
|
+
return RichText('\n').join(lines)
|
|
248
|
+
|
|
249
|
+
def dump_errors(odrv, clear=False):
|
|
250
|
+
"""
|
|
251
|
+
Prints a summary of the error status of the device on stdout.
|
|
252
|
+
"""
|
|
253
|
+
odrive.rich_text.print_rich_text(format_errors(odrv, clear))
|
|
254
|
+
|
|
255
|
+
def oscilloscope_dump(odrv, num_vals, filename='oscilloscope.csv'):
|
|
256
|
+
with open(filename, 'w') as f:
|
|
257
|
+
for x in range(num_vals):
|
|
258
|
+
f.write(str(odrv.oscilloscope.get_val(x)))
|
|
259
|
+
f.write('\n')
|
|
260
|
+
|
|
261
|
+
data_rate = 100
|
|
262
|
+
plot_rate = 1
|
|
263
|
+
num_samples = 200
|
|
264
|
+
def start_liveplotter(get_var_callback: Callable, legend: List[str] = None):
|
|
265
|
+
"""
|
|
266
|
+
Starts a liveplotter.
|
|
267
|
+
The variable that is plotted is retrieved from get_var_callback.
|
|
268
|
+
This function returns immediately and the liveplotter quits when
|
|
269
|
+
the user closes it.
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
import numpy as np
|
|
273
|
+
import matplotlib.pyplot as plt
|
|
274
|
+
import matplotlib.animation as animation
|
|
275
|
+
|
|
276
|
+
cancellation_token = threading.Event()
|
|
277
|
+
|
|
278
|
+
global vals
|
|
279
|
+
vals = get_var_callback()
|
|
280
|
+
legend_vals = (legend + [str(n+1) for n in range(len(legend), len(vals))]) if legend else list(range(len(vals)))
|
|
281
|
+
vals = []
|
|
282
|
+
def fetch_data():
|
|
283
|
+
global vals
|
|
284
|
+
while not cancellation_token.is_set():
|
|
285
|
+
try:
|
|
286
|
+
data = get_var_callback()
|
|
287
|
+
except Exception as ex:
|
|
288
|
+
print(str(ex))
|
|
289
|
+
time.sleep(1)
|
|
290
|
+
continue
|
|
291
|
+
vals.append(data)
|
|
292
|
+
while len(vals) > num_samples:
|
|
293
|
+
vals.pop(0)
|
|
294
|
+
|
|
295
|
+
time.sleep(1/data_rate)
|
|
296
|
+
|
|
297
|
+
def plot_data():
|
|
298
|
+
global vals
|
|
299
|
+
|
|
300
|
+
def closed(evt):
|
|
301
|
+
cancellation_token.set()
|
|
302
|
+
|
|
303
|
+
plt.ion()
|
|
304
|
+
fig, ax = plt.subplots()
|
|
305
|
+
fig.canvas.mpl_connect('close_event', closed)
|
|
306
|
+
lines = ax.plot(vals)
|
|
307
|
+
ax.legend(legend_vals, loc=1)
|
|
308
|
+
|
|
309
|
+
def animate(*fargs):
|
|
310
|
+
data = np.array(vals)
|
|
311
|
+
x_data = range(len(data[:,0]))
|
|
312
|
+
ax.set_xlim(min(x_data), max(x_data))
|
|
313
|
+
if np.all(np.isnan(data)):
|
|
314
|
+
y_min, y_max = (0, 1)
|
|
315
|
+
else:
|
|
316
|
+
y_min = np.nanmin(data)
|
|
317
|
+
y_max = np.nanmax(data)
|
|
318
|
+
y_delta = y_max - y_min
|
|
319
|
+
ax.set_ylim(bottom=y_min - 0.1 * y_delta, top=y_max + 0.1 * y_delta)
|
|
320
|
+
for index, line in enumerate(lines):
|
|
321
|
+
line.set_data(x_data, data[:,index])
|
|
322
|
+
|
|
323
|
+
_ = animation.FuncAnimation(fig, animate, interval=plot_rate)
|
|
324
|
+
|
|
325
|
+
while not cancellation_token.is_set():
|
|
326
|
+
fig.canvas.draw()
|
|
327
|
+
fig.canvas.start_event_loop(1/plot_rate)
|
|
328
|
+
|
|
329
|
+
fig.canvas.stop_event_loop()
|
|
330
|
+
|
|
331
|
+
fetch_t = threading.Thread(target=fetch_data)
|
|
332
|
+
fetch_t.daemon = True
|
|
333
|
+
fetch_t.start()
|
|
334
|
+
|
|
335
|
+
plot_t = threading.Thread(target=plot_data)
|
|
336
|
+
plot_t.daemon = True
|
|
337
|
+
plot_t.start()
|
|
338
|
+
|
|
339
|
+
return cancellation_token
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class BulkCapture:
|
|
343
|
+
'''
|
|
344
|
+
Asynchronously captures a bulk set of data when instance is created.
|
|
345
|
+
|
|
346
|
+
get_var_callback: a function that returns the data you want to collect (see the example below)
|
|
347
|
+
data_rate: Rate in hz
|
|
348
|
+
length: Length of time to capture in seconds
|
|
349
|
+
|
|
350
|
+
Example Usage:
|
|
351
|
+
capture = BulkCapture(lambda :[odrv0.axis0.encoder.pos_estimate, odrv0.axis0.controller.pos_setpoint])
|
|
352
|
+
# Do stuff while capturing (like sending position commands)
|
|
353
|
+
capture.event.wait() # When you're done doing stuff, wait for the capture to be completed.
|
|
354
|
+
print(capture.data) # Do stuff with the data
|
|
355
|
+
capture.plot_data() # Helper method to plot the data
|
|
356
|
+
'''
|
|
357
|
+
|
|
358
|
+
def __init__(self,
|
|
359
|
+
get_var_callback,
|
|
360
|
+
data_rate=500.0,
|
|
361
|
+
duration=2.0):
|
|
362
|
+
import numpy as np
|
|
363
|
+
|
|
364
|
+
self.get_var_callback = get_var_callback
|
|
365
|
+
self.event = threading.Event()
|
|
366
|
+
def loop():
|
|
367
|
+
vals = []
|
|
368
|
+
start_time = time.monotonic()
|
|
369
|
+
period = 1.0 / data_rate
|
|
370
|
+
while time.monotonic() - start_time < duration:
|
|
371
|
+
try:
|
|
372
|
+
data = get_var_callback()
|
|
373
|
+
except Exception as ex:
|
|
374
|
+
print(str(ex))
|
|
375
|
+
print("Waiting 1 second before next data point")
|
|
376
|
+
time.sleep(1)
|
|
377
|
+
continue
|
|
378
|
+
relative_time = time.monotonic() - start_time
|
|
379
|
+
vals.append([relative_time] + data)
|
|
380
|
+
time.sleep(period - (relative_time % period)) # this ensures consistently timed samples
|
|
381
|
+
self.data = np.array(vals) # A lock is not really necessary due to the event
|
|
382
|
+
print("Capture complete")
|
|
383
|
+
achieved_data_rate = len(self.data) / self.data[-1, 0]
|
|
384
|
+
if achieved_data_rate < (data_rate * 0.9):
|
|
385
|
+
print("Achieved average data rate: {}Hz".format(achieved_data_rate))
|
|
386
|
+
print("If this rate is significantly lower than what you specified, consider lowering it below the achieved value for more consistent sampling.")
|
|
387
|
+
self.event.set() # tell the main thread that the bulk capture is complete
|
|
388
|
+
threading.Thread(target=loop, daemon=True).start()
|
|
389
|
+
|
|
390
|
+
def plot(self):
|
|
391
|
+
import matplotlib.pyplot as plt
|
|
392
|
+
import inspect
|
|
393
|
+
from textwrap import wrap
|
|
394
|
+
plt.plot(self.data[:,0], self.data[:,1:])
|
|
395
|
+
plt.xlabel("Time (seconds)")
|
|
396
|
+
title = (str(inspect.getsource(self.get_var_callback))
|
|
397
|
+
.strip("['\\n']")
|
|
398
|
+
.split(" = ")[1])
|
|
399
|
+
plt.title("\n".join(wrap(title, 60)))
|
|
400
|
+
plt.legend(range(self.data.shape[1]-1))
|
|
401
|
+
plt.show()
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def step_and_plot( axis,
|
|
405
|
+
step_size=100.0,
|
|
406
|
+
settle_time=0.5,
|
|
407
|
+
data_rate=500.0,
|
|
408
|
+
ctrl_mode=ControlMode.POSITION_CONTROL):
|
|
409
|
+
|
|
410
|
+
if ctrl_mode == ControlMode.POSITION_CONTROL:
|
|
411
|
+
get_var_callback = lambda :[axis.encoder.pos_estimate, axis.controller.pos_setpoint]
|
|
412
|
+
initial_setpoint = axis.encoder.pos_estimate
|
|
413
|
+
def set_setpoint(setpoint):
|
|
414
|
+
axis.controller.pos_setpoint = setpoint
|
|
415
|
+
elif ctrl_mode == ControlMode.VELOCITY_CONTROL:
|
|
416
|
+
get_var_callback = lambda :[axis.encoder.vel_estimate, axis.controller.vel_setpoint]
|
|
417
|
+
initial_setpoint = 0
|
|
418
|
+
def set_setpoint(setpoint):
|
|
419
|
+
axis.controller.vel_setpoint = setpoint
|
|
420
|
+
else:
|
|
421
|
+
print("Invalid control mode")
|
|
422
|
+
return
|
|
423
|
+
|
|
424
|
+
initial_settle_time = 0.5
|
|
425
|
+
initial_control_mode = axis.controller.config.control_mode # Set it back afterwards
|
|
426
|
+
print(initial_control_mode)
|
|
427
|
+
axis.controller.config.control_mode = ctrl_mode
|
|
428
|
+
axis.requested_state = AxisState.CLOSED_LOOP_CONTROL
|
|
429
|
+
|
|
430
|
+
capture = BulkCapture(get_var_callback,
|
|
431
|
+
data_rate=data_rate,
|
|
432
|
+
duration=initial_settle_time + settle_time)
|
|
433
|
+
|
|
434
|
+
set_setpoint(initial_setpoint)
|
|
435
|
+
time.sleep(initial_settle_time)
|
|
436
|
+
set_setpoint(initial_setpoint + step_size) # relative/incremental movement
|
|
437
|
+
|
|
438
|
+
capture.event.wait() # wait for Bulk Capture to be complete
|
|
439
|
+
|
|
440
|
+
axis.requested_state = AxisState.IDLE
|
|
441
|
+
axis.controller.config.control_mode = initial_control_mode
|
|
442
|
+
capture.plot()
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def print_drv_regs(name, motor):
|
|
446
|
+
"""
|
|
447
|
+
Dumps the current gate driver regisers for the specified motor
|
|
448
|
+
"""
|
|
449
|
+
fault = motor.gate_driver.drv_fault
|
|
450
|
+
status_reg_1 = motor.gate_driver.status_reg_1
|
|
451
|
+
status_reg_2 = motor.gate_driver.status_reg_2
|
|
452
|
+
ctrl_reg_1 = motor.gate_driver.ctrl_reg_1
|
|
453
|
+
ctrl_reg_2 = motor.gate_driver.ctrl_reg_2
|
|
454
|
+
print(name + ": " + str(fault))
|
|
455
|
+
print("DRV Fault Code: " + str(fault))
|
|
456
|
+
print("Status Reg 1: " + str(status_reg_1) + " (" + format(status_reg_1, '#010b') + ")")
|
|
457
|
+
print("Status Reg 2: " + str(status_reg_2) + " (" + format(status_reg_2, '#010b') + ")")
|
|
458
|
+
print("Control Reg 1: " + str(ctrl_reg_1) + " (" + format(ctrl_reg_1, '#013b') + ")")
|
|
459
|
+
print("Control Reg 2: " + str(ctrl_reg_2) + " (" + format(ctrl_reg_2, '#09b') + ")")
|
|
460
|
+
|
|
461
|
+
def show_oscilloscope(odrv):
|
|
462
|
+
size = 18000
|
|
463
|
+
values = []
|
|
464
|
+
for i in range(size):
|
|
465
|
+
values.append(odrv.oscilloscope.get_val(i))
|
|
466
|
+
|
|
467
|
+
import matplotlib.pyplot as plt
|
|
468
|
+
plt.plot(values)
|
|
469
|
+
plt.show()
|
|
470
|
+
|
|
471
|
+
def rate_test(device):
|
|
472
|
+
"""
|
|
473
|
+
Tests how many integers per second can be transmitted
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
# import matplotlib.pyplot as plt
|
|
477
|
+
# plt.ion()
|
|
478
|
+
|
|
479
|
+
print("reading 10000 values...")
|
|
480
|
+
numFrames = 10000
|
|
481
|
+
vals = []
|
|
482
|
+
for _ in range(numFrames):
|
|
483
|
+
vals.append(device.n_evt_control_loop)
|
|
484
|
+
|
|
485
|
+
loopsPerFrame = (vals[-1] - vals[0])/numFrames
|
|
486
|
+
loopsPerSec = (168000000/(6*3500))
|
|
487
|
+
FramePerSec = loopsPerSec/loopsPerFrame
|
|
488
|
+
print("Frames per second: " + str(FramePerSec))
|
|
489
|
+
|
|
490
|
+
# plt.plot(vals)
|
|
491
|
+
# plt.show(block=True)
|
|
492
|
+
|
|
493
|
+
def usb_burn_in_test(get_var_callback, cancellation_token):
|
|
494
|
+
"""
|
|
495
|
+
Starts background threads that read a values form the USB device in a spin-loop
|
|
496
|
+
"""
|
|
497
|
+
|
|
498
|
+
def fetch_data():
|
|
499
|
+
global vals
|
|
500
|
+
i = 0
|
|
501
|
+
while not cancellation_token.is_set():
|
|
502
|
+
try:
|
|
503
|
+
get_var_callback()
|
|
504
|
+
i += 1
|
|
505
|
+
except Exception as ex:
|
|
506
|
+
print(str(ex))
|
|
507
|
+
time.sleep(1)
|
|
508
|
+
i = 0
|
|
509
|
+
continue
|
|
510
|
+
if i % 1000 == 0:
|
|
511
|
+
print("read {} values".format(i))
|
|
512
|
+
threading.Thread(target=fetch_data, daemon=True).start()
|
|
513
|
+
|
|
514
|
+
def yes_no_prompt(question, default=None):
|
|
515
|
+
if default is None:
|
|
516
|
+
question += " [y/n] "
|
|
517
|
+
elif default == True:
|
|
518
|
+
question += " [Y/n] "
|
|
519
|
+
elif default == False:
|
|
520
|
+
question += " [y/N] "
|
|
521
|
+
|
|
522
|
+
while True:
|
|
523
|
+
print(question, end='')
|
|
524
|
+
|
|
525
|
+
choice = input().lower()
|
|
526
|
+
if choice in {'yes', 'y'}:
|
|
527
|
+
return True
|
|
528
|
+
elif choice in {'no', 'n'}:
|
|
529
|
+
return False
|
|
530
|
+
elif choice == '' and default is not None:
|
|
531
|
+
return default
|
|
532
|
+
|
|
533
|
+
def dump_interrupts(odrv):
|
|
534
|
+
interrupts = [
|
|
535
|
+
(-12, "MemoryManagement_IRQn"),
|
|
536
|
+
(-11, "BusFault_IRQn"),
|
|
537
|
+
(-10, "UsageFault_IRQn"),
|
|
538
|
+
(-5, "SVCall_IRQn"),
|
|
539
|
+
(-4, "DebugMonitor_IRQn"),
|
|
540
|
+
(-2, "PendSV_IRQn"),
|
|
541
|
+
(-1, "SysTick_IRQn"),
|
|
542
|
+
(0, "WWDG_IRQn"),
|
|
543
|
+
(1, "PVD_IRQn"),
|
|
544
|
+
(2, "TAMP_STAMP_IRQn"),
|
|
545
|
+
(3, "RTC_WKUP_IRQn"),
|
|
546
|
+
(4, "FLASH_IRQn"),
|
|
547
|
+
(5, "RCC_IRQn"),
|
|
548
|
+
(6, "EXTI0_IRQn"),
|
|
549
|
+
(7, "EXTI1_IRQn"),
|
|
550
|
+
(8, "EXTI2_IRQn"),
|
|
551
|
+
(9, "EXTI3_IRQn"),
|
|
552
|
+
(10, "EXTI4_IRQn"),
|
|
553
|
+
(11, "DMA1_Stream0_IRQn"),
|
|
554
|
+
(12, "DMA1_Stream1_IRQn"),
|
|
555
|
+
(13, "DMA1_Stream2_IRQn"),
|
|
556
|
+
(14, "DMA1_Stream3_IRQn"),
|
|
557
|
+
(15, "DMA1_Stream4_IRQn"),
|
|
558
|
+
(16, "DMA1_Stream5_IRQn"),
|
|
559
|
+
(17, "DMA1_Stream6_IRQn"),
|
|
560
|
+
(18, "ADC_IRQn"),
|
|
561
|
+
(19, "CAN1_TX_IRQn"),
|
|
562
|
+
(20, "CAN1_RX0_IRQn"),
|
|
563
|
+
(21, "CAN1_RX1_IRQn"),
|
|
564
|
+
(22, "CAN1_SCE_IRQn"),
|
|
565
|
+
(23, "EXTI9_5_IRQn"),
|
|
566
|
+
(24, "TIM1_BRK_TIM9_IRQn"),
|
|
567
|
+
(25, "TIM1_UP_TIM10_IRQn"),
|
|
568
|
+
(26, "TIM1_TRG_COM_TIM11_IRQn"),
|
|
569
|
+
(27, "TIM1_CC_IRQn"),
|
|
570
|
+
(28, "TIM2_IRQn"),
|
|
571
|
+
(29, "TIM3_IRQn"),
|
|
572
|
+
(30, "TIM4_IRQn"),
|
|
573
|
+
(31, "I2C1_EV_IRQn"),
|
|
574
|
+
(32, "I2C1_ER_IRQn"),
|
|
575
|
+
(33, "I2C2_EV_IRQn"),
|
|
576
|
+
(34, "I2C2_ER_IRQn"),
|
|
577
|
+
(35, "SPI1_IRQn"),
|
|
578
|
+
(36, "SPI2_IRQn"),
|
|
579
|
+
(37, "USART1_IRQn"),
|
|
580
|
+
(38, "USART2_IRQn"),
|
|
581
|
+
(39, "USART3_IRQn"),
|
|
582
|
+
(40, "EXTI15_10_IRQn"),
|
|
583
|
+
(41, "RTC_Alarm_IRQn"),
|
|
584
|
+
(42, "OTG_FS_WKUP_IRQn"),
|
|
585
|
+
(43, "TIM8_BRK_TIM12_IRQn"),
|
|
586
|
+
(44, "TIM8_UP_TIM13_IRQn"),
|
|
587
|
+
(45, "TIM8_TRG_COM_TIM14_IRQn"),
|
|
588
|
+
(46, "TIM8_CC_IRQn"),
|
|
589
|
+
(47, "DMA1_Stream7_IRQn"),
|
|
590
|
+
(48, "FMC_IRQn"),
|
|
591
|
+
(49, "SDMMC1_IRQn"),
|
|
592
|
+
(50, "TIM5_IRQn"),
|
|
593
|
+
(51, "SPI3_IRQn"),
|
|
594
|
+
(52, "UART4_IRQn"),
|
|
595
|
+
(53, "UART5_IRQn"),
|
|
596
|
+
(54, "TIM6_DAC_IRQn"),
|
|
597
|
+
(55, "TIM7_IRQn"),
|
|
598
|
+
(56, "DMA2_Stream0_IRQn"),
|
|
599
|
+
(57, "DMA2_Stream1_IRQn"),
|
|
600
|
+
(58, "DMA2_Stream2_IRQn"),
|
|
601
|
+
(59, "DMA2_Stream3_IRQn"),
|
|
602
|
+
(60, "DMA2_Stream4_IRQn"),
|
|
603
|
+
(61, "ETH_IRQn"),
|
|
604
|
+
(62, "ETH_WKUP_IRQn"),
|
|
605
|
+
(63, "CAN2_TX_IRQn"),
|
|
606
|
+
(64, "CAN2_RX0_IRQn"),
|
|
607
|
+
(65, "CAN2_RX1_IRQn"),
|
|
608
|
+
(66, "CAN2_SCE_IRQn"),
|
|
609
|
+
(67, "OTG_FS_IRQn"),
|
|
610
|
+
(68, "DMA2_Stream5_IRQn"),
|
|
611
|
+
(69, "DMA2_Stream6_IRQn"),
|
|
612
|
+
(70, "DMA2_Stream7_IRQn"),
|
|
613
|
+
(71, "USART6_IRQn"),
|
|
614
|
+
(72, "I2C3_EV_IRQn"),
|
|
615
|
+
(73, "I2C3_ER_IRQn"),
|
|
616
|
+
(74, "OTG_HS_EP1_OUT_IRQn"),
|
|
617
|
+
(75, "OTG_HS_EP1_IN_IRQn"),
|
|
618
|
+
(76, "OTG_HS_WKUP_IRQn"),
|
|
619
|
+
(77, "OTG_HS_IRQn"),
|
|
620
|
+
# gap
|
|
621
|
+
(80, "RNG_IRQn"),
|
|
622
|
+
(81, "FPU_IRQn"),
|
|
623
|
+
(82, "UART7_IRQn"),
|
|
624
|
+
(83, "UART8_IRQn"),
|
|
625
|
+
(84, "SPI4_IRQn"),
|
|
626
|
+
(85, "SPI5_IRQn"),
|
|
627
|
+
# gap
|
|
628
|
+
(87, "SAI1_IRQn"),
|
|
629
|
+
# gap
|
|
630
|
+
(91, "SAI2_IRQn"),
|
|
631
|
+
(92, "QUADSPI_IRQn"),
|
|
632
|
+
(93, "LPTIM1_IRQn"),
|
|
633
|
+
# gap
|
|
634
|
+
(103, "SDMMC2_IRQn"),
|
|
635
|
+
(117, "TIM16_IRQn")
|
|
636
|
+
]
|
|
637
|
+
|
|
638
|
+
print("| # | Name | Prio | En | Count |")
|
|
639
|
+
print("|-----|-------------------------|------|----|---------|")
|
|
640
|
+
for irqn, irq_name in interrupts:
|
|
641
|
+
status = odrv.get_interrupt_status(irqn)
|
|
642
|
+
if (status != 0):
|
|
643
|
+
print("| {} | {} | {} | {} | {} |".format(
|
|
644
|
+
str(irqn).rjust(3),
|
|
645
|
+
irq_name.ljust(23),
|
|
646
|
+
str(status & 0xff).rjust(4),
|
|
647
|
+
" *" if (status & 0x80000000) else " ",
|
|
648
|
+
str((status >> 8) & 0x7fffff).rjust(7)))
|
|
649
|
+
|
|
650
|
+
def dump_threads(odrv):
|
|
651
|
+
prefixes = ["max_stack_usage_", "stack_size_", "prio_"]
|
|
652
|
+
keys = [k[len(prefix):] for k in dir(odrv.system_stats) for prefix in prefixes if k.startswith(prefix)]
|
|
653
|
+
good_keys = set([k for k in set(keys) if keys.count(k) == len(prefixes)])
|
|
654
|
+
if len(good_keys) > len(set(keys)):
|
|
655
|
+
print("Warning: incomplete thread information for threads {}".format(set(keys) - good_keys))
|
|
656
|
+
|
|
657
|
+
print("| Name | Stack Size [B] | Max Ever Stack Usage [B] | Prio |")
|
|
658
|
+
print("|---------|----------------|--------------------------|------|")
|
|
659
|
+
for k in sorted(good_keys):
|
|
660
|
+
sz = getattr(odrv.system_stats, "stack_size_" + k)
|
|
661
|
+
use = getattr(odrv.system_stats, "max_stack_usage_" + k)
|
|
662
|
+
print("| {} | {} | {} | {} |".format(
|
|
663
|
+
k.ljust(7),
|
|
664
|
+
str(sz).rjust(14),
|
|
665
|
+
"{} ({:.1f}%)".format(use, use / sz * 100).rjust(24),
|
|
666
|
+
str(getattr(odrv.system_stats, "prio_" + k)).rjust(4)
|
|
667
|
+
))
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def dump_dma(odrv):
|
|
671
|
+
if odrv.hw_version_major == 3:
|
|
672
|
+
dma_functions = [[
|
|
673
|
+
# https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf Table 42
|
|
674
|
+
["SPI3_RX", "-", "SPI3_RX", "SPI2_RX", "SPI2_TX", "SPI3_TX", "-", "SPI3_TX"],
|
|
675
|
+
["I2C1_RX", "-", "TIM7_UP", "-", "TIM7_UP", "I2C1_RX", "I2C1_TX", "I2C1_TX"],
|
|
676
|
+
["TIM4_CH1", "-", "I2S3_EXT_RX", "TIM4_CH2", "I2S2_EXT_TX", "I2S3_EXT_TX", "TIM4_UP", "TIM4_CH3"],
|
|
677
|
+
["I2S3_EXT_RX", "TIM2_UP/TIM2_CH3", "I2C3_RX", "I2S2_EXT_RX", "I2C3_TX", "TIM2_CH1", "TIM2_CH2/TIM2_CH4", "TIM2_UP/TIM2_CH4"],
|
|
678
|
+
["UART5_RX", "USART3_RX", "UART4_RX", "USART3_TX", "UART4_TX", "USART2_RX", "USART2_TX", "UART5_TX"],
|
|
679
|
+
["UART8_TX", "UART7_TX", "TIM3_CH4/TIM3_UP", "UART7_RX", "TIM3_CH1/TIM3_TRIG", "TIM3_CH2", "UART8_RX", "TIM3_CH3"],
|
|
680
|
+
["TIM5_CH3/TIM5_UP", "TIM5_CH4/TIM5_TRIG", "TIM5_CH1", "TIM5_CH4/TIM5_TRIG", "TIM5_CH2", "-", "TIM5_UP", "-"],
|
|
681
|
+
["-", "TIM6_UP", "I2C2_RX", "I2C2_RX", "USART3_TX", "DAC1", "DAC2", "I2C2_TX"],
|
|
682
|
+
], [
|
|
683
|
+
# https://www.st.com/content/ccc/resource/technical/document/reference_manual/3d/6d/5a/66/b4/99/40/d4/DM00031020.pdf/files/DM00031020.pdf/jcr:content/translations/en.DM00031020.pdf Table 43
|
|
684
|
+
["ADC1", "SAI1_A", "TIM8_CH1/TIM8_CH2/TIM8_CH3", "SAI1_A", "ADC1", "SAI1_B", "TIM1_CH1/TIM1_CH2/TIM1_CH3", "-"],
|
|
685
|
+
["-", "DCMI", "ADC2", "ADC2", "SAI1_B", "SPI6_TX", "SPI6_RX", "DCMI"],
|
|
686
|
+
["ADC3", "ADC3", "-", "SPI5_RX", "SPI5_TX", "CRYP_OUT", "CRYP_IN", "HASH_IN"],
|
|
687
|
+
["SPI1_RX", "-", "SPI1_RX", "SPI1_TX", "-", "SPI1_TX", "-", "-"],
|
|
688
|
+
["SPI4_RX", "SPI4_TX", "USART1_RX", "SDIO", "-", "USART1_RX", "SDIO", "USART1_TX"],
|
|
689
|
+
["-", "USART6_RX", "USART6_RX", "SPI4_RX", "SPI4_TX", "-", "USART6_TX", "USART6_TX"],
|
|
690
|
+
["TIM1_TRIG", "TIM1_CH1", "TIM1_CH2", "TIM1_CH1", "TIM1_CH4/TIM1_TRIG/TIM1_COM", "TIM1_UP", "TIM1_CH3", "-"],
|
|
691
|
+
["-", "TIM8_UP", "TIM8_CH1", "TIM8_CH2", "TIM8_CH3", "SPI5_RX", "SPI5_TX", "TIM8_CH4/TIM8_TRIG/TIM8_COM"],
|
|
692
|
+
]]
|
|
693
|
+
elif odrv.hw_version_major == 4:
|
|
694
|
+
dma_functions = [[
|
|
695
|
+
# https://www.st.com/resource/en/reference_manual/dm00305990-stm32f72xxx-and-stm32f73xxx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf Table 26
|
|
696
|
+
["SPI3_RX", "-", "SPI3_RX", "SPI2_RX", "SPI2_TX", "SPI3_TX", "-", "SPI3_TX"],
|
|
697
|
+
["I2C1_RX", "I2C3_RX", "TIM7_UP", "-", "TIM7_UP", "I2C1_RX", "I2C1_TX", "I2C1_TX"],
|
|
698
|
+
["TIM4_CH1", "-", "-", "TIM4_CH2", "-", "-", "TIM4_UP", "TIM4_CH3"],
|
|
699
|
+
["-", "TIM2_UP/TIM2_CH3", "I2C3_RX", "-", "I2C3_TX", "TIM2_CH1", "TIM2_CH2/TIM2_CH4", "TIM2_UP/TIM2_CH4"],
|
|
700
|
+
["UART5_RX", "USART3_RX", "UART4_RX", "USART3_TX", "UART4_TX", "USART2_RX", "USART2_TX", "UART5_TX"],
|
|
701
|
+
["UART8_TX", "UART7_TX", "TIM3_CH4/TIM3_UP", "UART7_RX", "TIM3_CH1/TIM3_TRIG", "TIM3_CH2", "UART8_RX", "TIM3_CH3"],
|
|
702
|
+
["TIM5_CH3/TIM5_UP", "TIM5_CH4/TIM5_TRIG", "TIM5_CH1", "TIM5_CH4/TIM5_TRIG", "TIM5_CH2", "-", "TIM5_UP", "-"],
|
|
703
|
+
["-", "TIM6_UP", "I2C2_RX", "I2C2_RX", "USART3_TX", "DAC1", "DAC2", "I2C2_TX"],
|
|
704
|
+
], [
|
|
705
|
+
# https://www.st.com/resource/en/reference_manual/dm00305990-stm32f72xxx-and-stm32f73xxx-advanced-armbased-32bit-mcus-stmicroelectronics.pdf Table 27
|
|
706
|
+
["ADC1", "SAI1_A", "TIM8_CH1/TIM8_CH2/TIM8_CH3", "SAI1_A", "ADC1", "SAI1_B", "TIM1_CH1/TIM1_CH2/TIM1_CH3", "SAI2_B"],
|
|
707
|
+
["-", "-", "ADC2", "ADC2", "SAI1_B", "-", "-", "-"],
|
|
708
|
+
["ADC3", "ADC3", "-", "SPI5_RX", "SPI5_TX", "AES_OUT", "AES_IN", "-"],
|
|
709
|
+
["SPI1_RX", "-", "SPI1_RX", "SPI1_TX", "SAI2_A", "SPI1_TX", "SAI2_B", "QUADSPI"],
|
|
710
|
+
["SPI4_RX", "SPI4_TX", "USART1_RX", "SDMMC1", "-", "USART1_RX", "SDMMC1", "USART1_TX"],
|
|
711
|
+
["-", "USART6_RX", "USART6_RX", "SPI4_RX", "SPI4_TX", "-", "USART6_TX", "USART6_TX"],
|
|
712
|
+
["TIM1_TRIG", "TIM1_CH1", "TIM1_CH2", "TIM1_CH1", "TIM1_CH4/TIM1_TRIG/TIM1_COM", "TIM1_UP", "TIM1_CH3", "-"],
|
|
713
|
+
["-", "TIM8_UP", "TIM8_CH1", "TIM8_CH2", "TIM8_CH3", "SPI5_RX", "SPI5_TX", "TIM8_CH4/TIM8_TRIG/TIM8_COM"],
|
|
714
|
+
None,
|
|
715
|
+
None,
|
|
716
|
+
None,
|
|
717
|
+
["SDMMC2", "-", "-", "-", "-", "SDMMC2", "-", "-"],
|
|
718
|
+
]]
|
|
719
|
+
|
|
720
|
+
print("| Name | Prio | Channel | Configured |")
|
|
721
|
+
print("|--------------|------|----------------------------------|------------|")
|
|
722
|
+
for stream_num in range(16):
|
|
723
|
+
status = odrv.get_dma_status(stream_num)
|
|
724
|
+
if (status != 0):
|
|
725
|
+
channel = (status >> 2) & 0x7
|
|
726
|
+
ch_name = dma_functions[stream_num >> 3][channel][stream_num & 0x7]
|
|
727
|
+
print("| DMA{}_Stream{} | {} | {} {} | {} |".format(
|
|
728
|
+
(stream_num >> 3) + 1,
|
|
729
|
+
(stream_num & 0x7),
|
|
730
|
+
(status & 0x3),
|
|
731
|
+
channel,
|
|
732
|
+
("(" + ch_name + ")").ljust(30),
|
|
733
|
+
"*" if (status & 0x80000000) else " "))
|
|
734
|
+
|
|
735
|
+
def dump_timing(odrv, n_samples=100, path='/tmp/timings.png', reset_timings=False):
|
|
736
|
+
import matplotlib.pyplot as plt
|
|
737
|
+
import re
|
|
738
|
+
import numpy as np
|
|
739
|
+
|
|
740
|
+
timings = []
|
|
741
|
+
|
|
742
|
+
for attr in dir(odrv.task_times):
|
|
743
|
+
if not attr.startswith('_'):
|
|
744
|
+
timings.append((attr, getattr(odrv.task_times, attr), [], [])) # (name, obj, start_times, lengths)
|
|
745
|
+
for k in dir(odrv):
|
|
746
|
+
if re.match(r'axis[0-9]+', k):
|
|
747
|
+
for attr in dir(getattr(odrv, k).task_times):
|
|
748
|
+
if not attr.startswith('_'):
|
|
749
|
+
timings.append((k + '.' + attr, getattr(getattr(odrv, k).task_times, attr), [], [])) # (name, obj, start_times, lengths)
|
|
750
|
+
|
|
751
|
+
# reset max-timings
|
|
752
|
+
if reset_timings:
|
|
753
|
+
for name, obj, start_times, lengths in timings:
|
|
754
|
+
obj.max_length = 0
|
|
755
|
+
|
|
756
|
+
# Take a couple of samples
|
|
757
|
+
print("sampling...")
|
|
758
|
+
for i in range(n_samples):
|
|
759
|
+
odrv.task_timers_armed = True # Trigger sample and wait for it to finish
|
|
760
|
+
while odrv.task_timers_armed: pass
|
|
761
|
+
for name, obj, start_times, lengths in timings:
|
|
762
|
+
start_times.append(obj.start_time)
|
|
763
|
+
lengths.append(obj.length)
|
|
764
|
+
print("done")
|
|
765
|
+
|
|
766
|
+
# sort by start time
|
|
767
|
+
timings = sorted(timings, key = lambda x: np.mean(x[2]))
|
|
768
|
+
|
|
769
|
+
plt.rcParams['figure.figsize'] = 21, 9
|
|
770
|
+
plt.figure()
|
|
771
|
+
plt.grid(True)
|
|
772
|
+
plt.barh(
|
|
773
|
+
[-i for i in range(len(timings))], # y positions
|
|
774
|
+
[np.mean(lengths) for name, obj, start_times, lengths in timings], # lengths
|
|
775
|
+
left = [np.mean(start_times) for name, obj, start_times, lengths in timings], # starts
|
|
776
|
+
xerr = (
|
|
777
|
+
[np.std(lengths) for name, obj, start_times, lengths in timings], # error bars to the left side
|
|
778
|
+
[(min(obj.max_length, 20100) - np.mean(lengths)) for name, obj, start_times, lengths in timings], # error bars to the right side - TODO: remove artificial min()
|
|
779
|
+
),
|
|
780
|
+
tick_label = [name for name, obj, start_times, lengths in timings], # labels
|
|
781
|
+
)
|
|
782
|
+
plt.savefig(path, bbox_inches='tight')
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
def _ensure_debug_info(odrv):
|
|
786
|
+
try:
|
|
787
|
+
import odrive_private.more_utils
|
|
788
|
+
except ImportError:
|
|
789
|
+
raise NotImplementedError()
|
|
790
|
+
odrive_private.more_utils.ensure_debug_info(odrv)
|
|
791
|
+
|
|
792
|
+
async def ram_osci_config(odrv, expressions):
|
|
793
|
+
max_addresses = len(odrv.oscilloscope.config._inputs) - 1
|
|
794
|
+
assert len(expressions) > 0
|
|
795
|
+
assert len(expressions) <= max_addresses
|
|
796
|
+
|
|
797
|
+
addresses = [0] * len(expressions)
|
|
798
|
+
for i in range(len(expressions)):
|
|
799
|
+
if isinstance(expressions[i], int):
|
|
800
|
+
addresses[i] = expressions[i]
|
|
801
|
+
else:
|
|
802
|
+
_ensure_debug_info(odrv)
|
|
803
|
+
addresses[i], _ = await odrv._dbg_info.resolve_symbol(expressions[i])
|
|
804
|
+
|
|
805
|
+
print(f"Recording memory locations " + ", ".join(['0x{:08x}'.format(addr) for addr in addresses]) + "...")
|
|
806
|
+
|
|
807
|
+
odrv.oscilloscope.config(*addresses, *([0] * (max_addresses - len(expressions))))
|
|
808
|
+
|
|
809
|
+
def ram_osci_trigger(odrv, trigger_point: float):
|
|
810
|
+
assert trigger_point >= 0 and trigger_point <= 1
|
|
811
|
+
odrv.oscilloscope.trigger(trigger_point)
|
|
812
|
+
|
|
813
|
+
async def ram_osci_download(odrv, expressions):
|
|
814
|
+
while odrv.oscilloscope.recording:
|
|
815
|
+
await asyncio.sleep(0.1)
|
|
816
|
+
|
|
817
|
+
type_infos = [None] * len(expressions)
|
|
818
|
+
for i in range(len(expressions)):
|
|
819
|
+
if isinstance(expressions[i], int):
|
|
820
|
+
class RegTypeInfo:
|
|
821
|
+
byte_size = 4
|
|
822
|
+
def bytes_to_val(self, b):
|
|
823
|
+
return int.from_bytes(b, 'little')
|
|
824
|
+
type_infos[i] = RegTypeInfo()
|
|
825
|
+
else:
|
|
826
|
+
_ensure_debug_info(odrv)
|
|
827
|
+
_, type_infos[i] = await odrv._dbg_info.resolve_symbol(expressions[i])
|
|
828
|
+
|
|
829
|
+
print(f"Fetching buffer...")
|
|
830
|
+
|
|
831
|
+
buf = b''
|
|
832
|
+
for i in range(0, odrv.oscilloscope.size, 32):
|
|
833
|
+
buf += struct.pack('<QQQQ', *odrv.oscilloscope.get_raw(i))
|
|
834
|
+
|
|
835
|
+
outputs = []
|
|
836
|
+
offset = 0
|
|
837
|
+
sample_size = sum(4 for type_info in type_infos)
|
|
838
|
+
while True:
|
|
839
|
+
elem = ()
|
|
840
|
+
for type_info in type_infos:
|
|
841
|
+
byte_size = type_info.byte_size
|
|
842
|
+
if offset + byte_size > len(buf):
|
|
843
|
+
elem = None
|
|
844
|
+
break
|
|
845
|
+
elem += (type_info.bytes_to_val(buf[offset:(offset+byte_size)]),)
|
|
846
|
+
assert byte_size <= 4
|
|
847
|
+
offset += max(byte_size, 4)
|
|
848
|
+
if elem is None:
|
|
849
|
+
break
|
|
850
|
+
outputs.append(elem)
|
|
851
|
+
|
|
852
|
+
# Rotate
|
|
853
|
+
pos = int(odrv.oscilloscope.pos / sample_size)
|
|
854
|
+
outputs_part0 = outputs[:pos]
|
|
855
|
+
outputs_part1 = outputs[pos:]
|
|
856
|
+
if odrv.oscilloscope.rollover:
|
|
857
|
+
outputs = outputs_part1 + outputs_part0
|
|
858
|
+
else:
|
|
859
|
+
outputs = outputs_part0
|
|
860
|
+
|
|
861
|
+
sampling_freq = 8000
|
|
862
|
+
print(f"Collected {len(outputs)} samples, which corresponds to {len(outputs)/sampling_freq*1000:.2f} ms of data (assuming a sampling frequency of {sampling_freq})")
|
|
863
|
+
|
|
864
|
+
# transpose output
|
|
865
|
+
return [[outputs[i][j] for i in range(len(outputs))] for j in range(len(outputs[0]))]
|
|
866
|
+
|
|
867
|
+
async def ram_osci_run(odrv, expressions, trigger_point=0.0):
|
|
868
|
+
"""
|
|
869
|
+
Configures the RAM oscilloscope with the specified expressions, optionally
|
|
870
|
+
triggers it and waits for it to finish before downloading the data.
|
|
871
|
+
|
|
872
|
+
trigger_point: A number in [0.0, 1.0] or None.
|
|
873
|
+
If None, it is expected that a custom trigger in firmware is used after
|
|
874
|
+
configuring the osci.
|
|
875
|
+
"""
|
|
876
|
+
await ram_osci_config(odrv, expressions)
|
|
877
|
+
if not trigger_point is None:
|
|
878
|
+
ram_osci_trigger(odrv, trigger_point)
|
|
879
|
+
else:
|
|
880
|
+
print("waiting for in-firmware trigger...")
|
|
881
|
+
return await ram_osci_download(odrv, expressions)
|