mirror of
https://github.com/odriverobotics/ODrive.git
synced 2026-09-21 07:14:22 +08:00
make tests more flexible
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
# Automated Testing
|
||||
|
||||
This section describes how to use the automated testing facilities.
|
||||
You don't have to do this as an end user.
|
||||
|
||||
They test the following aspects:
|
||||
- System functions (communication interfaces, configuration storage)
|
||||
- Functionality of the motor controller and state machine
|
||||
- High speed and high load conditions
|
||||
|
||||
The testing facility consists of the following components:
|
||||
* **Test rig:** In the simplest case this can be a single ODrive with a single motor and encoder pair. Can also be multiple ODrives with multiple axes, some of which may be mechanically coupled.
|
||||
* **Test host:** The PC on which the test script runs. All ODrives must be connected to the test host via USB.
|
||||
* **test-rig.yaml:** Describes your test rig. Make sure all values are correct. Incorrect values may physically break or fry your test setup.
|
||||
* **run_tests.py:** This is the main script that runs all the tests.
|
||||
|
||||
## How to run
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
./run_tests.py --skip-boring-tests --ignore top-odrive.yellow bottom-odrive.yellow
|
||||
```
|
||||
+13
-4
@@ -26,7 +26,7 @@ class ODriveTestContext():
|
||||
self.name = name
|
||||
self.axes = []
|
||||
for axis_idx, axis_yaml in enumerate(yaml['axes']):
|
||||
axis_name = axis_yaml['name'] if 'name' in axis_yaml else '{}.axis{}'.format(name, axis_idx)
|
||||
axis_name = (name + "." + axis_yaml['name']) if 'name' in axis_yaml else '{}.axis{}'.format(name, axis_idx)
|
||||
self.axes.append(AxisTestContext(axis_name, axis_yaml, self))
|
||||
|
||||
def rediscover(self):
|
||||
@@ -388,7 +388,6 @@ class TestHighVelocity(AxisTest):
|
||||
# V_bus and motor KV rating
|
||||
max_rpm = axis_ctx.odrv_ctx.yaml['vbus-voltage'] * axis_ctx.yaml['motor-kv']
|
||||
rated_limit = max_rpm / 60 * axis_ctx.yaml['encoder-cpr']
|
||||
rated_limit *= 0.5 # TODO: remove this later, but for now we want to stay away from the modulation depth limit
|
||||
expected_limit = rated_limit
|
||||
|
||||
# The KV-rating assumes square-waves on the motor phases (hexagonal space vector trajectory)
|
||||
@@ -399,6 +398,10 @@ class TestHighVelocity(AxisTest):
|
||||
# The ODrive only goes to 80% modulation depth in order to save some time for the ADC measurements.
|
||||
# See FOC_current in motor.cpp.
|
||||
expected_limit *= 0.8
|
||||
|
||||
# TODO: remove the following two lines, but for now we want to stay away from the modulation depth limit
|
||||
expected_limit *= 0.8
|
||||
rated_limit = expected_limit
|
||||
|
||||
# Add a 10% margin to account for
|
||||
expected_limit *= 0.9
|
||||
@@ -465,6 +468,10 @@ class TestHighVelocityInViscousFluid(DualAxisTest):
|
||||
Runs TestHighVelocity on one motor while using the other motor as a load.
|
||||
The load is created by running velocity control with setpoint 0.
|
||||
"""
|
||||
def __init__(self, load_current=10, driver_current=20):
|
||||
self._load_current = load_current
|
||||
self._driver_current = driver_current
|
||||
|
||||
def run_test(self, axis0_ctx: AxisTestContext, axis1_ctx: AxisTestContext, logger):
|
||||
load_ctx = axis0_ctx
|
||||
driver_ctx = axis1_ctx
|
||||
@@ -474,12 +481,14 @@ class TestHighVelocityInViscousFluid(DualAxisTest):
|
||||
load_ctx.handle.controller.config.vel_integrator_gain = 0
|
||||
load_ctx.handle.controller.vel_integrator_current = 0
|
||||
load_ctx.handle.controller.config.vel_limit = 20000 # this is not really relevant
|
||||
load_ctx.handle.motor.config.current_lim = 20
|
||||
load_ctx.handle.motor.config.current_lim = self._load_current
|
||||
load_ctx.odrv_ctx.handle.config.brake_resistance = 0 # disable brake resistance, the power will go into the bus
|
||||
load_ctx.handle.controller.set_vel_setpoint(0, 0)
|
||||
request_state(load_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL)
|
||||
|
||||
driver_test = TestHighVelocity(override_current_limit=40, load_current=20, brake=False)
|
||||
driver_test = TestHighVelocity(
|
||||
override_current_limit=self._driver_current,
|
||||
load_current=self._load_current, brake=False)
|
||||
driver_test.check_preconditions(driver_ctx, logger)
|
||||
driver_test.run_test(driver_ctx, logger)
|
||||
|
||||
|
||||
+44
-27
@@ -12,52 +12,67 @@ import os
|
||||
import sys
|
||||
import threading
|
||||
import traceback
|
||||
import argparse
|
||||
from odrive.tests import *
|
||||
from odrive.utils import Logger, for_all_parallel, Event
|
||||
|
||||
script_path=os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
all_tests = [
|
||||
# TestFlashAndErase(),
|
||||
# TestSetup(),
|
||||
# TestMotorCalibration(),
|
||||
# # TODO: test encoder index search
|
||||
# TestEncoderOffsetCalibration(),
|
||||
# # TODO: hold down one motor while the other one does an index search (should fail)
|
||||
# TestClosedLoopControl(),
|
||||
# TestStoreAndReboot(),
|
||||
# TestEncoderOffsetCalibration(), # need to find offset _or_ index after reboot
|
||||
# TestClosedLoopControl(),
|
||||
TestDiscoverAndGotoIdle(), # for testing
|
||||
TestEncoderOffsetCalibration(pass_if_ready=True),
|
||||
# TestHighVelocity(),
|
||||
TestHighVelocityInViscousFluid(),
|
||||
# TestVelCtrlVsPosCtrl()
|
||||
# TODO: test step/dir
|
||||
# TODO: test sensorless
|
||||
# TODO: test ASCII protocol
|
||||
# TODO: test protocol over UART
|
||||
]
|
||||
parser = argparse.ArgumentParser(description='ODrive automated test tool\n')
|
||||
parser.add_argument("--skip-boring-tests", action="store_true",
|
||||
help="Skip the boring tests and go right to the high power tests")
|
||||
parser.add_argument("--ignore", metavar='DEVICE', action='store', nargs='+',
|
||||
help="Ignore one or more ODrives or axes")
|
||||
parser.add_argument("--test-rig-yaml", type=argparse.FileType('r'),
|
||||
help="test rig YAML file")
|
||||
parser.set_defaults(test_rig_yaml=script_path + '/test-rig.yaml')
|
||||
parser.set_defaults(ignore=[])
|
||||
args = parser.parse_args()
|
||||
|
||||
all_tests = []
|
||||
if not args.skip_boring_tests:
|
||||
all_tests.append(TestFlashAndErase())
|
||||
all_tests.append(TestSetup())
|
||||
all_tests.append(TestMotorCalibration())
|
||||
# # TODO: test encoder index search
|
||||
all_tests.append(TestEncoderOffsetCalibration())
|
||||
# # TODO: hold down one motor while the other one does an index search (should fail)
|
||||
all_tests.append(TestClosedLoopControl())
|
||||
all_tests.append(TestStoreAndReboot())
|
||||
all_tests.append(TestEncoderOffsetCalibration()) # need to find offset _or_ index after reboot
|
||||
all_tests.append(TestClosedLoopControl())
|
||||
else:
|
||||
all_tests.append(TestDiscoverAndGotoIdle())
|
||||
all_tests.append(TestEncoderOffsetCalibration(pass_if_ready=True))
|
||||
|
||||
#all_tests.append(TestHighVelocity())
|
||||
all_tests.append(TestHighVelocityInViscousFluid(load_current=20, driver_current=40))
|
||||
#all_tests.append(TestVelCtrlVsPosCtrl())
|
||||
# TODO: test step/dir
|
||||
# TODO: test sensorless
|
||||
# TODO: test ASCII protocol
|
||||
# TODO: test protocol over UART
|
||||
|
||||
print(str(args.ignore))
|
||||
logger = Logger()
|
||||
|
||||
script_path=os.path.dirname(os.path.realpath(__file__))
|
||||
with open(script_path + '/test-rig.yaml', 'r') as file_stream:
|
||||
test_rig_yaml = yaml.load(file_stream)
|
||||
|
||||
test_rig_yaml = yaml.load(args.test_rig_yaml)
|
||||
os.chdir(script_path + '/../Firmware')
|
||||
|
||||
# Build a dictionary of odrive test contexts by name
|
||||
odrives_by_name = {}
|
||||
for odrv_idx, odrv_yaml in enumerate(test_rig_yaml['odrives']):
|
||||
name = odrv_yaml['name'] if 'name' in odrv_yaml else 'odrive{}'.format(odrv_idx)
|
||||
odrives_by_name[name] = ODriveTestContext(name, odrv_yaml)
|
||||
if not name in args.ignore:
|
||||
odrives_by_name[name] = ODriveTestContext(name, odrv_yaml)
|
||||
|
||||
# Build a dictionary of axis test contexts by name (e.g. odrive0.axis0)
|
||||
axes_by_name = {}
|
||||
for odrv_ctx in odrives_by_name.values():
|
||||
for axis_idx, axis_ctx in enumerate(odrv_ctx.axes):
|
||||
axes_by_name[axis_ctx.name] = axis_ctx
|
||||
if not axis_ctx.name in args.ignore:
|
||||
axes_by_name[axis_ctx.name] = axis_ctx
|
||||
|
||||
# Ensure mechanical couplings are valid
|
||||
couplings = []
|
||||
@@ -65,7 +80,9 @@ if test_rig_yaml['couplings'] is None:
|
||||
test_rig_yaml['couplings'] = {}
|
||||
else:
|
||||
for coupling in test_rig_yaml['couplings']:
|
||||
couplings.append([axes_by_name[axis_name] for axis_name in coupling])
|
||||
c = [axes_by_name[axis_name] for axis_name in coupling if (axis_name in axes_by_name)]
|
||||
if len(c) > 1:
|
||||
couplings.append(c)
|
||||
|
||||
app_shutdown_token = Event()
|
||||
|
||||
|
||||
+10
-6
@@ -11,14 +11,16 @@ odrives:
|
||||
vbus-voltage: 12 # [V]
|
||||
max-brake-power: 150 # [W]
|
||||
axes:
|
||||
- motor-phase-resistance: 0.0245
|
||||
- name: 'yellow'
|
||||
motor-phase-resistance: 0.0245
|
||||
motor-phase-inductance: 2.03e-05
|
||||
motor-pole-pairs: 7
|
||||
motor-direction: -1
|
||||
motor-kv: 190
|
||||
motor-max-current: 50
|
||||
encoder-cpr: 8192
|
||||
- motor-phase-resistance: 0.028
|
||||
- name: 'black'
|
||||
motor-phase-resistance: 0.028
|
||||
motor-phase-inductance: 1.6e-05
|
||||
motor-pole-pairs: 7
|
||||
motor-direction: -1
|
||||
@@ -35,14 +37,16 @@ odrives:
|
||||
vbus-voltage: 12 # [V]
|
||||
max-brake-power: 150 # [W]
|
||||
axes:
|
||||
- motor-phase-resistance: 0.0253
|
||||
- name: 'black'
|
||||
motor-phase-resistance: 0.0253
|
||||
motor-phase-inductance: 1.6e-05
|
||||
motor-pole-pairs: 7
|
||||
motor-direction: 1
|
||||
motor-kv: 270
|
||||
motor-max-current: 50
|
||||
encoder-cpr: 8192
|
||||
- motor-phase-resistance: 0.0245
|
||||
- name: 'yellow'
|
||||
motor-phase-resistance: 0.0245
|
||||
motor-phase-inductance: 2.03e-05
|
||||
motor-pole-pairs: 7
|
||||
motor-direction: -1
|
||||
@@ -52,5 +56,5 @@ odrives:
|
||||
|
||||
# Mechanical couplings
|
||||
couplings:
|
||||
- [ top-odrive.axis0, bottom-odrive.axis1 ]
|
||||
- [ top-odrive.axis1, bottom-odrive.axis0 ]
|
||||
- [ top-odrive.yellow, bottom-odrive.yellow ]
|
||||
- [ top-odrive.black, bottom-odrive.black ]
|
||||
|
||||
Reference in New Issue
Block a user