Skip to content

Commit 3db3c43

Browse files
committed
Add configurable faults for position or velocity tracking error
1 parent 99880ec commit 3db3c43

File tree

7 files changed

+119
-0
lines changed

7 files changed

+119
-0
lines changed

docs/protocol/registers.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,10 @@ A fault code which will be set if the primary mode is 1 (Fault).
231231
* 48 - *invalid limits* - `servopos.position_min` or
232232
`servopos.position_max` are finite and outside the available
233233
position range
234+
* 49 - *position control error* - the position control error exceeded
235+
the threshold configured in `servo.fault_position_error`
236+
* 50 - *velocity control error* - the velocity control error exceeded
237+
the threshold configured in `servo.fault_velocity_error`
234238

235239
Some non-zero codes can be presented during valid control modes
236240
without a fault. These indicate which, if any, function is limiting

docs/reference/configuration.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,20 @@ Torque begins to be limited when the motor temperature reaches this value.
201201
If the motor temperature reaches this value, a fault is triggered and
202202
all torque is stopped.
203203

204+
## `servo.fault_position_error`
205+
206+
If finite, a fault is triggered when the absolute position control
207+
error exceeds this value. The position control error is the
208+
difference between the commanded and measured position in revolutions.
209+
If set to `nan` (the default), no fault is triggered.
210+
211+
## `servo.fault_velocity_error`
212+
213+
If finite, a fault is triggered when the absolute velocity control
214+
error exceeds this value. The velocity control error is the
215+
difference between the commanded and measured velocity in revolutions
216+
per second. If set to `nan` (the default), no fault is triggered.
217+
204218
## `servo.flux_brake_margin_voltage`
205219

206220
Selects the flux braking point relative to the currently configured `servo.max_voltage`. `flux braking point = max_voltage - flux_brake_margin_voltage`.

fw/bldc_servo_control.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,21 @@ class BldcServoControl {
889889
feedforward_Nm +
890890
inertia_feedforward_Nm);
891891

892+
if (std::isfinite(self().config_.fault_position_error) &&
893+
std::abs(self().status_.pid_position.error) >
894+
self().config_.fault_position_error) {
895+
self().status_.mode = kFault;
896+
self().status_.fault = errc::kPositionControlError;
897+
return;
898+
}
899+
if (std::isfinite(self().config_.fault_velocity_error) &&
900+
std::abs(self().status_.pid_position.error_rate) >
901+
self().config_.fault_velocity_error) {
902+
self().status_.mode = kFault;
903+
self().status_.fault = errc::kVelocityControlError;
904+
return;
905+
}
906+
892907
const auto limited_torque_Nm_pair =
893908
LimitCode(unlimited_torque_Nm, -max_torque_Nm, max_torque_Nm,
894909
errc::kLimitMaxTorque, errc::kSuccess);

fw/bldc_servo_structs.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,9 @@ struct BldcServoConfig {
479479
float motor_temperature_margin = 20.0f;
480480
float motor_fault_temperature = std::numeric_limits<float>::quiet_NaN();
481481

482+
float fault_position_error = std::numeric_limits<float>::quiet_NaN();
483+
float fault_velocity_error = std::numeric_limits<float>::quiet_NaN();
484+
482485
float velocity_threshold = 0.0f;
483486
float position_derate = 0.02f;
484487

@@ -615,6 +618,8 @@ struct BldcServoConfig {
615618
a->Visit(MJ_NVP(motor_thermistor_ohm));
616619
a->Visit(MJ_NVP(motor_temperature_margin));
617620
a->Visit(MJ_NVP(motor_fault_temperature));
621+
a->Visit(MJ_NVP(fault_position_error));
622+
a->Visit(MJ_NVP(fault_velocity_error));
618623
a->Visit(MJ_NVP(velocity_threshold));
619624
a->Visit(MJ_NVP(position_derate));
620625
a->Visit(MJ_NVP(adc_cur_cycles));

fw/error.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ enum class errc {
5050
kTimingViolation = 46,
5151
kBemfFeedforwardNoAccelLimit = 47,
5252
kInvalidLimits = 48,
53+
kPositionControlError = 49,
54+
kVelocityControlError = 50,
5355

5456
// The following are not hard faults, but instead codes that are
5557
// presented while in a control mode if some factor is limiting the

fw/test/simulation_test.cc

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,4 +371,81 @@ BOOST_AUTO_TEST_CASE(SimVelocityAcrossBusVoltages) {
371371
}
372372
}
373373

374+
// Test that position control error fault triggers when error exceeds threshold
375+
BOOST_AUTO_TEST_CASE(SimPositionControlErrorFault) {
376+
ctx.config_.fault_position_error = 0.02f;
377+
378+
auto cmd = MakePositionCommand(0.0f, 0.0f, 1.0f);
379+
ctx.Command(&cmd);
380+
381+
// Let it settle at position 0
382+
ctx.RunSimulation(&cmd, 0.2f);
383+
BOOST_TEST(ctx.status_.mode == kPosition);
384+
385+
// Apply a large external torque to push motor away from target
386+
ctx.external_torque_ = 0.5f;
387+
388+
// Run until fault triggers
389+
ctx.RunSimulation(&cmd, 1.0f);
390+
391+
BOOST_CHECK(ctx.status_.mode == kFault);
392+
BOOST_CHECK(ctx.status_.fault == errc::kPositionControlError);
393+
}
394+
395+
// Test that NaN position error threshold (default) does not trigger fault
396+
BOOST_AUTO_TEST_CASE(SimPositionControlErrorFaultDisabled) {
397+
// fault_position_error defaults to NaN
398+
399+
auto cmd = MakePositionCommand(0.0f, 0.0f, 1.0f);
400+
ctx.Command(&cmd);
401+
ctx.RunSimulation(&cmd, 0.2f);
402+
403+
// Apply same external torque
404+
ctx.external_torque_ = 0.5f;
405+
ctx.RunSimulation(&cmd, 0.5f);
406+
407+
// Should NOT have faulted
408+
BOOST_CHECK(ctx.status_.mode != kFault);
409+
}
410+
411+
// Test that velocity control error fault triggers when error rate exceeds threshold
412+
BOOST_AUTO_TEST_CASE(SimVelocityControlErrorFault) {
413+
ctx.config_.fault_velocity_error = 0.5f;
414+
415+
// Command constant velocity
416+
auto cmd = MakePositionCommand(kNaN, 2.0f, 1.0f);
417+
cmd.accel_limit = 10.0f;
418+
ctx.Command(&cmd);
419+
420+
// Let it get up to speed
421+
ctx.RunSimulation(&cmd, 0.5f);
422+
BOOST_TEST(ctx.status_.mode == kPosition);
423+
424+
// Apply a large opposing torque to create velocity error
425+
ctx.external_torque_ = -1.0f;
426+
427+
// Run until fault triggers
428+
ctx.RunSimulation(&cmd, 1.0f);
429+
430+
BOOST_CHECK(ctx.status_.mode == kFault);
431+
BOOST_CHECK(ctx.status_.fault == errc::kVelocityControlError);
432+
}
433+
434+
// Test that NaN velocity error threshold (default) does not trigger fault
435+
BOOST_AUTO_TEST_CASE(SimVelocityControlErrorFaultDisabled) {
436+
// fault_velocity_error defaults to NaN
437+
438+
auto cmd = MakePositionCommand(kNaN, 2.0f, 1.0f);
439+
cmd.accel_limit = 10.0f;
440+
ctx.Command(&cmd);
441+
ctx.RunSimulation(&cmd, 0.5f);
442+
443+
// Apply opposing torque
444+
ctx.external_torque_ = -1.0f;
445+
ctx.RunSimulation(&cmd, 0.5f);
446+
447+
// Should NOT have faulted
448+
BOOST_CHECK(ctx.status_.mode != kFault);
449+
}
450+
374451
BOOST_AUTO_TEST_SUITE_END()

utils/gui/moteus_gui/tview.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@
107107
46: "timing violation",
108108
47: "bemf feedforward without accel",
109109
48: "invalid limits",
110+
49: "position control error",
111+
50: "velocity control error",
110112
96: "limit: servo.max_velocity",
111113
97: "limit: servo.max_power_W",
112114
98: "limit: max system voltage",

0 commit comments

Comments
 (0)