Skip to content

Commit f077e81

Browse files
committed
Refactor Reverse Stop
Replaces the ERPM-based implementation with a distance-based one, implemented in a separate module. Feature: New and more consistent Reverse Stop implementation
1 parent 1f32d45 commit f077e81

4 files changed

Lines changed: 207 additions & 52 deletions

File tree

src/data.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "motor_data.h"
3737
#include "pid.h"
3838
#include "remote.h"
39+
#include "reverse_stop.h"
3940
#include "state.h"
4041
#include "time.h"
4142
#include "torque_tilt.h"
@@ -72,6 +73,7 @@ typedef struct {
7273
FootpadSensor footpad;
7374
HapticFeedback haptic_feedback;
7475
AlertTracker alert_tracker;
76+
ReverseStop reverse_stop;
7577

7678
Leds leds;
7779
LcmData lcm;
@@ -121,10 +123,6 @@ typedef struct {
121123
// Feature: Flywheel
122124
bool flywheel_abort;
123125

124-
// Feature: Reverse Stop
125-
float reverse_tolerance, reverse_total_erpm;
126-
time_t reverse_timer;
127-
128126
// Feature: Soft Start
129127
float softstart_pid_limit;
130128

src/main.c

Lines changed: 26 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ static void cmd_flywheel_toggle(Data *d, unsigned char *cfg, int len);
9797

9898
const VESC_PIN beeper_pin = VESC_PIN_PPM;
9999

100-
#define REVSTOP_ERPM_INCR 0.00008
101100
#define EXT_BEEPER_ON() VESC_IF->io_write(beeper_pin, 1)
102101
#define EXT_BEEPER_OFF() VESC_IF->io_write(beeper_pin, 0)
103102

@@ -161,6 +160,8 @@ static void main_freq_update_reconfigure(float frequency) {
161160
booster_configure(&d->booster, frequency);
162161

163162
ema_configure(&d->balance_current, 25.0f, frequency);
163+
164+
reverse_stop_configure(&d->reverse_stop, frequency);
164165
}
165166

166167
// Reconfigures all components dependent on the IMU loop frequency.
@@ -208,9 +209,6 @@ static void configure(Data *d) {
208209
VESC_IF->set_cfg_float(CFG_PARAM_IMU_accel_confidence_decay, 0.1);
209210
}
210211

211-
// Feature: Reverse Stop
212-
d->reverse_tolerance = 20000;
213-
214212
// Speed above which to warn users about an impending full switch fault
215213
d->switch_warn_beep_erpm = d->float_conf.is_footbeep_enabled ? 2000 : 100000;
216214

@@ -242,6 +240,7 @@ static void reset_runtime_vars(Data *d) {
242240
turn_tilt_reset(&d->turn_tilt);
243241
remote_reset(&d->remote);
244242
booster_reset(&d->booster);
243+
reverse_stop_reset(&d->reverse_stop, d->motor.distance);
245244

246245
ema_reset(&d->balance_current, 0.0f);
247246

@@ -424,34 +423,17 @@ static bool check_faults(Data *d) {
424423
timer_refresh(&d->time, &d->fault_switch_timer);
425424
}
426425

427-
// Feature: Reverse-Stop
428426
if (d->state.sat == SAT_REVERSESTOP) {
429-
// Taking your foot off entirely while reversing? Ignore delays
427+
// ignore delays if sensor is completely disengaged while reversing
430428
if (d->footpad.state == FS_NONE) {
431429
state_stop(&d->state, STOP_SWITCH_FULL);
432430
return true;
433431
}
434-
if (fabsf(d->imu.pitch) > 18) {
435-
state_stop(&d->state, STOP_REVERSE_STOP);
436-
return true;
437-
}
438-
// Above 10 degrees for a full second? Switch it off
439-
if (fabsf(d->imu.pitch) > 10 && timer_older(&d->time, d->reverse_timer, 1)) {
440-
state_stop(&d->state, STOP_REVERSE_STOP);
441-
return true;
442-
}
443-
// Above 5 degrees for 2 seconds? Switch it off
444-
if (fabsf(d->imu.pitch) > 5 && timer_older(&d->time, d->reverse_timer, 2)) {
445-
state_stop(&d->state, STOP_REVERSE_STOP);
446-
return true;
447-
}
448-
if (fabsf(d->reverse_total_erpm) > d->reverse_tolerance * 10) {
432+
433+
if (reverse_stop_stop(&d->reverse_stop, &d->time)) {
449434
state_stop(&d->state, STOP_REVERSE_STOP);
450435
return true;
451436
}
452-
if (fabsf(d->imu.pitch) < 5) {
453-
timer_refresh(&d->time, &d->reverse_timer);
454-
}
455437
}
456438

457439
// Switch partially open and stopped
@@ -518,34 +500,15 @@ static void calculate_setpoint_target(Data *d) {
518500
d->state.sat = SAT_NONE;
519501
}
520502
} else if (d->state.sat == SAT_REVERSESTOP) {
521-
// accumalete erpms:
522-
d->reverse_total_erpm += d->motor.erpm;
523-
if (fabsf(d->reverse_total_erpm) > d->reverse_tolerance) {
524-
// tilt down by 10 degrees after exceeding aggregate erpm
525-
d->setpoint_target =
526-
(fabsf(d->reverse_total_erpm) - d->reverse_tolerance) * REVSTOP_ERPM_INCR;
503+
if (reverse_stop_active(&d->reverse_stop)) {
504+
d->setpoint_target = reverse_stop_setpoint(&d->reverse_stop);
527505
} else {
528-
if (fabsf(d->reverse_total_erpm) <= d->reverse_tolerance * 0.5) {
529-
if (d->motor.erpm >= 0) {
530-
d->state.sat = SAT_NONE;
531-
d->reverse_total_erpm = 0;
532-
d->setpoint_target = 0;
533-
}
534-
}
506+
d->state.sat = SAT_NONE;
535507
}
536-
} else if (d->float_conf.fault_reversestop_enabled && d->motor.erpm < -200 &&
508+
} else if (d->float_conf.fault_reversestop_enabled && reverse_stop_active(&d->reverse_stop) &&
537509
!d->state.darkride) {
538-
// Detecting reverse stop takes priority over any error condition SAT
539-
if (d->state.sat >= SAT_PB_HIGH_VOLTAGE) {
540-
// If this happens while in Error-Tiltback (LV/HV/TEMP) then we need to
541-
// take the already existing setpoint into account
542-
d->reverse_total_erpm =
543-
-(d->reverse_tolerance + d->setpoint_target_interpolated / REVSTOP_ERPM_INCR);
544-
} else {
545-
d->reverse_total_erpm = 0;
546-
}
510+
d->setpoint_target = reverse_stop_setpoint(&d->reverse_stop);
547511
d->state.sat = SAT_REVERSESTOP;
548-
timer_refresh(&d->time, &d->reverse_timer);
549512
} else if (d->state.mode != MODE_FLYWHEEL &&
550513
// not normal, either wheelslip or wheel getting stuck
551514
fabsf(d->motor.acceleration.value) > 10000 &&
@@ -906,6 +869,20 @@ static void refloat_thd(void *arg) {
906869
break;
907870

908871
case (STATE_RUNNING):
872+
// Only update reverse stop after centering, as the centering angle
873+
// change registers as negative distance, which progresses Reverse
874+
// Stop and causes a jolt right after centering is finished.
875+
if (d->state.sat != SAT_CENTERING) {
876+
reverse_stop_update(
877+
&d->reverse_stop,
878+
d->motor.distance,
879+
d->motor.erpm,
880+
d->setpoint_target_interpolated,
881+
&d->time,
882+
d->float_conf.fault_reversestop_enabled
883+
);
884+
}
885+
909886
// Check for faults
910887
if (check_faults(d)) {
911888
if (d->state.stop_condition == STOP_SWITCH_FULL && !d->state.darkride) {
@@ -1255,6 +1232,7 @@ static void data_init(Data *d) {
12551232
footpad_sensor_init(&d->footpad);
12561233
haptic_feedback_init(&d->haptic_feedback);
12571234
alert_tracker_init(&d->alert_tracker);
1235+
reverse_stop_init(&d->reverse_stop);
12581236

12591237
leds_init(&d->leds);
12601238
lcm_init(&d->lcm, &d->float_conf.hardware.leds);

src/reverse_stop.c

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
// Copyright 2025 Lukas Hrazky
2+
//
3+
// This file is part of the Refloat VESC package.
4+
//
5+
// Refloat VESC package is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by the
7+
// Free Software Foundation, either version 3 of the License, or (at your
8+
// option) any later version.
9+
//
10+
// Refloat VESC package is distributed in the hope that it will be useful, but
11+
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12+
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13+
// more details.
14+
//
15+
// You should have received a copy of the GNU General Public License along with
16+
// this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
#include "reverse_stop.h"
19+
20+
#include "lib/transitions.h"
21+
#include "lib/utils.h"
22+
#include <math.h>
23+
24+
#define REVERSE_STOP_DISTANCE 0.25f
25+
#define TARGET_STOP_ANGLE 17.0f
26+
#define DISTANCE_PER_DEGREE (REVERSE_STOP_DISTANCE / TARGET_STOP_ANGLE)
27+
#define TIMER_ANGLE_THRESHOLD (TARGET_STOP_ANGLE / 2)
28+
29+
// Distance of travel for one motor step, could be calculated, for the purposes
30+
// of Reverse Stop an approximate value is enough. Calculated as: wheel_dia *
31+
// pi / (3 * motor_poles)
32+
#define MOTOR_STEP_DIST 0.01f
33+
34+
void reverse_stop_init(ReverseStop *rs) {
35+
ema_init(&rs->progress);
36+
reverse_stop_reset(rs, 0.0f);
37+
}
38+
39+
void reverse_stop_reset(ReverseStop *rs, float distance) {
40+
rs->start_setpoint = 0.0f;
41+
rs->target_setpoint = 0.0f;
42+
rs->start_distance = distance;
43+
rs->current_distance = 0.0f;
44+
rs->target_distance = 0.0f;
45+
ema_reset(&rs->progress, 1.0f);
46+
}
47+
48+
void reverse_stop_configure(ReverseStop *rs, float frequency) {
49+
ema_configure(&rs->progress, 1.0f, frequency);
50+
}
51+
52+
void reverse_stop_update(
53+
ReverseStop *rs, float distance, float erpm, float setpoint, const Time *time, bool enabled
54+
) {
55+
if ((!enabled || erpm > -200) && rs->progress.value >= 1.0f) {
56+
rs->start_distance = distance;
57+
return;
58+
}
59+
60+
float new_distance = distance - rs->start_distance;
61+
float distance_diff = (new_distance - rs->current_distance) * sign(rs->target_distance);
62+
63+
if (distance_diff < -2 * MOTOR_STEP_DIST) {
64+
rs->target_setpoint = rs->target_setpoint > 0.0f ? 0.0f : TARGET_STOP_ANGLE;
65+
rs->start_setpoint = setpoint;
66+
rs->start_distance = distance;
67+
rs->current_distance = 0.0f;
68+
rs->target_distance = fabsf(rs->start_setpoint - rs->target_setpoint) * DISTANCE_PER_DEGREE;
69+
// determine sign according to target_setpoint for the case we somehow go into
70+
// a reverse stop from a negative setpoint and then start going back out of it
71+
if (rs->target_setpoint > 0.0f) {
72+
rs->target_distance *= -1;
73+
}
74+
75+
float new_progress = 0.0f;
76+
if (fabsf(rs->target_distance) < MOTOR_STEP_DIST) {
77+
new_progress = 1.0f;
78+
rs->target_distance = 0.0f;
79+
}
80+
ema_reset(&rs->progress, new_progress);
81+
82+
return;
83+
}
84+
85+
if (rs->progress.value >= 1.0f) {
86+
if (distance_diff > 0.0f) {
87+
rs->start_distance = distance;
88+
}
89+
return;
90+
}
91+
92+
if (distance_diff > 0.0f) {
93+
rs->current_distance = new_distance;
94+
}
95+
96+
ema_update(&rs->progress, rs->current_distance / rs->target_distance);
97+
98+
if (rs->progress.value >= 1.0f) {
99+
rs->target_distance = 0.0f;
100+
rs->current_distance = 0.0f;
101+
}
102+
103+
// update the timer if returning from reverse or the angle is above threshold
104+
if (rs->target_setpoint == 0.0f || setpoint < TIMER_ANGLE_THRESHOLD) {
105+
timer_refresh(time, &rs->timer);
106+
}
107+
}
108+
109+
float reverse_stop_setpoint(ReverseStop *rs) {
110+
float prog = smoothstep(rs->progress.value);
111+
return rs->start_setpoint + prog * (rs->target_setpoint - rs->start_setpoint);
112+
}
113+
114+
bool reverse_stop_active(ReverseStop *rs) {
115+
return rs->target_setpoint > 0.0f || rs->progress.value < 1.0f;
116+
}
117+
118+
bool reverse_stop_stop(ReverseStop *rs, const Time *time) {
119+
float progress = rs->progress.value;
120+
121+
// the timer only starts aging below a certain setpoint angle threshold
122+
// the further we get progress-wise, the sooner we disengage: 3s at 0%, 1s at 100%
123+
float time_threshold = 3.0f - 2 * progress;
124+
if (timer_older(time, rs->timer, time_threshold)) {
125+
return true;
126+
}
127+
128+
return rs->target_setpoint > 0.0f && progress >= 1.0f;
129+
}

src/reverse_stop.h

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Copyright 2025 Lukas Hrazky
2+
//
3+
// This file is part of the Refloat VESC package.
4+
//
5+
// Refloat VESC package is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by the
7+
// Free Software Foundation, either version 3 of the License, or (at your
8+
// option) any later version.
9+
//
10+
// Refloat VESC package is distributed in the hope that it will be useful, but
11+
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12+
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13+
// more details.
14+
//
15+
// You should have received a copy of the GNU General Public License along with
16+
// this program. If not, see <http://www.gnu.org/licenses/>.
17+
18+
#pragma once
19+
20+
#include "filters/ema.h"
21+
#include "time.h"
22+
23+
#include <stdbool.h>
24+
25+
typedef struct {
26+
float start_setpoint; // absolute setpoint from which we started
27+
float target_setpoint; // absolute target setpoint, either 0 or TARGET_STOP_ANGLE
28+
float start_distance; // absolute distance from which we started
29+
float target_distance; // relative target distance to go, negative when going into reverse stop
30+
float current_distance; // relative distance we're at right now
31+
EMA progress;
32+
33+
time_t timer;
34+
} ReverseStop;
35+
36+
void reverse_stop_init(ReverseStop *rs);
37+
38+
void reverse_stop_reset(ReverseStop *rs, float distance);
39+
40+
void reverse_stop_configure(ReverseStop *rs, float frequency);
41+
42+
void reverse_stop_update(
43+
ReverseStop *rs, float distance, float erpm, float setpoint, const Time *time, bool enabled
44+
);
45+
46+
float reverse_stop_setpoint(ReverseStop *rs);
47+
48+
bool reverse_stop_active(ReverseStop *rs);
49+
50+
bool reverse_stop_stop(ReverseStop *rs, const Time *time);

0 commit comments

Comments
 (0)