make tests more flexible

This commit is contained in:
Samuel Sadok
2018-04-10 15:41:34 -07:00
parent d316badc2b
commit d202491e9a
4 changed files with 90 additions and 37 deletions
+23
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 ]