Skip to content

Commit f8dc84d

Browse files
committed
Add hysteresis to prevent spurious acceleration sign changes
Add hysteresis to CalculateAcceleration() to prevent spurious sign changes in the middle of the deceleration phase. Once we start decelerating towards the target, continue decelerating until we either overshoot (caught by the v_frame*dx<0 check) or reach the target. This eliminates mid-deceleration jitter caused by floating-point noise in the distance-based threshold calculation. End-of-trajectory oscillations (when velocity crosses zero) are still allowed as they are part of normal overshoot correction. The test expectations have been updated to: - Distinguish mid-trajectory violations (should be 0) from end-of-trajectory oscillations (allowed up to 15) - Allow slightly wider step range for trajectory completion (440-470 vs 440-460) - Update expected durations for acceleration-only cases to account for the ~1% longer trajectory due to committing to deceleration earlier
1 parent 2db5feb commit f8dc84d

File tree

2 files changed

+51
-16
lines changed

2 files changed

+51
-16
lines changed

fw/bldc_servo_position.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ class BldcServoPosition {
8787
}
8888

8989
static float CalculateAcceleration(
90+
const BldcServoStatus* status,
9091
BldcServoCommandData* data,
9192
float a,
9293
float v0,
@@ -112,7 +113,25 @@ class BldcServoPosition {
112113
if ((v_frame * dx) >= 0.0f && dx != 0.0f) {
113114
// The target is stationary and we are moving towards it.
114115
const float decel_distance = (v_frame * v_frame) / (2.0f * a);
115-
if (std::abs(dx) >= decel_distance) {
116+
117+
// Compute what acceleration we would use based on distance.
118+
const bool should_accelerate = std::abs(dx) >= decel_distance;
119+
120+
// Use hysteresis to prevent oscillation due to floating point
121+
// precision issues. Once we've started decelerating towards
122+
// the target, continue decelerating until we either overshoot
123+
// (caught by v_frame*dx<0 above) or reach the target. This
124+
// prevents numerical oscillation in the middle of the
125+
// deceleration phase.
126+
const float prev_accel = status->control_acceleration;
127+
const bool was_decelerating_towards_target =
128+
(prev_accel != 0.0f) && (prev_accel * v_frame < 0.0f);
129+
130+
if (was_decelerating_towards_target) {
131+
return std::copysign(a, -v_frame);
132+
}
133+
134+
if (should_accelerate) {
116135
if (std::isnan(data->velocity_limit) ||
117136
std::abs(v0) < data->velocity_limit) {
118137
return std::copysign(a, dx);
@@ -160,7 +179,7 @@ class BldcServoPosition {
160179
}
161180

162181
const float acceleration = CalculateAcceleration(
163-
data, a, v0, vf, dx, dv);
182+
status, data, a, v0, vf, dx, dv);
164183

165184
status->control_acceleration = acceleration;
166185
*status->control_velocity += acceleration * period_s;

fw/test/bldc_servo_position_test.cc

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -388,12 +388,14 @@ BOOST_AUTO_TEST_CASE(AccelVelocityLimits, * boost::unit_test::tolerance(1e-3)) {
388388

389389
/////////////////////////////////
390390
// No velocity limit.
391-
{ 0.0, 0.0, 5.0, 0.0, 1.0, NaN, 40, 0.000, 4.514 },
392-
{ 0.0, 1.0, 5.0, 0.0, 1.0, NaN, 40, 0.000, 3.730 },
393-
{ 0.0, 1.0, 5.0, 1.5, 1.0, NaN, 40, 0.000, 5.025 },
394-
{ 0.0, -1.0, -5.0,-1.5, 1.0, NaN, 40, 0.000, 5.025 },
395-
{ 5.0, 0.0, 0.0, 0.0, 1.0, NaN, 40, 0.000, 4.514 },
396-
{ 5.0, 0.0, 0.0, 0.0, 2.0, NaN, 40, 0.000, 3.205 },
391+
// Note: expected durations include ~1% increase due to hysteresis
392+
// in acceleration calculations (see CalculateAcceleration).
393+
{ 0.0, 0.0, 5.0, 0.0, 1.0, NaN, 40, 0.000, 4.568 },
394+
{ 0.0, 1.0, 5.0, 0.0, 1.0, NaN, 40, 0.000, 3.791 },
395+
{ 0.0, 1.0, 5.0, 1.5, 1.0, NaN, 40, 0.000, 5.140 },
396+
{ 0.0, -1.0, -5.0,-1.5, 1.0, NaN, 40, 0.000, 5.141 },
397+
{ 5.0, 0.0, 0.0, 0.0, 1.0, NaN, 40, 0.000, 4.568 },
398+
{ 5.0, 0.0, 0.0, 0.0, 2.0, NaN, 40, 0.000, 3.212 },
397399

398400
/////////////////////////////////
399401
// Accel and velocity limits
@@ -653,6 +655,7 @@ BOOST_AUTO_TEST_CASE(ControlAccelerationConsistent) {
653655
// strictly negative, and then 0, with no other transitions.
654656

655657
int negative_violations = 0;
658+
int end_of_trajectory_violations = 0;
656659
int zero_violations = 0;
657660

658661
constexpr int kMaxSteps = 50000;
@@ -686,7 +689,17 @@ BOOST_AUTO_TEST_CASE(ControlAccelerationConsistent) {
686689
expected_accel = kZero;
687690
break;
688691
} else {
689-
negative_violations++;
692+
// Track violations separately for mid-trajectory vs end-of-trajectory.
693+
// End-of-trajectory oscillations (when velocity is small) are expected
694+
// as part of normal overshoot correction. Mid-trajectory violations
695+
// (when velocity is significant) should not occur with hysteresis.
696+
const float vel = ctx.status.control_velocity.value();
697+
const float vel_threshold = kAccel * (1.0f / ctx.rate_hz) * 50.0f;
698+
if (std::abs(vel) > vel_threshold) {
699+
negative_violations++;
700+
} else {
701+
end_of_trajectory_violations++;
702+
}
690703
}
691704
break;
692705
}
@@ -700,12 +713,15 @@ BOOST_AUTO_TEST_CASE(ControlAccelerationConsistent) {
700713
}
701714
}
702715

716+
// With hysteresis, trajectories may take slightly longer (about 1-2%)
717+
// due to committing to deceleration once started.
703718
BOOST_TEST(steps_to_complete >= 440);
704-
BOOST_TEST(steps_to_complete <= 460);
705-
706-
// Because of floating point precision issues in the current
707-
// implementation, we aren't perfect. Still, verify that it
708-
// *mostly* does what we want.
709-
BOOST_TEST(negative_violations <= 15);
710-
BOOST_TEST(zero_violations <= 2);
719+
BOOST_TEST(steps_to_complete <= 470);
720+
721+
// With hysteresis in the acceleration calculation, we should have
722+
// no spurious mid-trajectory sign changes. End-of-trajectory
723+
// oscillations are expected as part of normal overshoot correction.
724+
BOOST_TEST(negative_violations == 0);
725+
BOOST_TEST(end_of_trajectory_violations <= 15);
726+
BOOST_TEST(zero_violations == 0);
711727
}

0 commit comments

Comments
 (0)