Skip to content

Commit 0e50f1b

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 f242139 commit 0e50f1b

4 files changed

Lines changed: 201 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: 20 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

@@ -172,6 +171,8 @@ static void main_freq_update_reconfigure(float frequency) {
172171

173172
ema_configure(&d->balance_current, 25.0f, frequency);
174173

174+
reverse_stop_configure(&d->reverse_stop, frequency);
175+
175176
log_msg(
176177
"Main freq reconfgure old: %dHz new: %dHz",
177178
(int32_t) d->main_t.filter_frequency,
@@ -227,9 +228,6 @@ static void configure(Data *d) {
227228
VESC_IF->set_cfg_float(CFG_PARAM_IMU_accel_confidence_decay, 0.1);
228229
}
229230

230-
// Feature: Reverse Stop
231-
d->reverse_tolerance = 20000;
232-
233231
// Speed above which to warn users about an impending full switch fault
234232
d->switch_warn_beep_erpm = d->float_conf.is_footbeep_enabled ? 2000 : 100000;
235233

@@ -259,6 +257,7 @@ static void reset_runtime_vars(Data *d) {
259257
turn_tilt_reset(&d->turn_tilt);
260258
remote_reset(&d->remote);
261259
booster_reset(&d->booster);
260+
reverse_stop_reset(&d->reverse_stop, d->motor.distance);
262261

263262
ema_reset(&d->balance_current, 0.0f);
264263

@@ -441,34 +440,17 @@ static bool check_faults(Data *d) {
441440
timer_refresh(&d->time, &d->fault_switch_timer);
442441
}
443442

444-
// Feature: Reverse-Stop
445443
if (d->state.sat == SAT_REVERSESTOP) {
446-
// Taking your foot off entirely while reversing? Ignore delays
444+
// ignore delays if sensor is completely disengaged while reversing
447445
if (d->footpad.state == FS_NONE) {
448446
state_stop(&d->state, STOP_SWITCH_FULL);
449447
return true;
450448
}
451-
if (fabsf(d->imu.pitch) > 18) {
452-
state_stop(&d->state, STOP_REVERSE_STOP);
453-
return true;
454-
}
455-
// Above 10 degrees for a full second? Switch it off
456-
if (fabsf(d->imu.pitch) > 10 && timer_older(&d->time, d->reverse_timer, 1)) {
457-
state_stop(&d->state, STOP_REVERSE_STOP);
458-
return true;
459-
}
460-
// Above 5 degrees for 2 seconds? Switch it off
461-
if (fabsf(d->imu.pitch) > 5 && timer_older(&d->time, d->reverse_timer, 2)) {
462-
state_stop(&d->state, STOP_REVERSE_STOP);
463-
return true;
464-
}
465-
if (fabsf(d->reverse_total_erpm) > d->reverse_tolerance * 10) {
449+
450+
if (reverse_stop_stop(&d->reverse_stop, &d->time)) {
466451
state_stop(&d->state, STOP_REVERSE_STOP);
467452
return true;
468453
}
469-
if (fabsf(d->imu.pitch) < 5) {
470-
timer_refresh(&d->time, &d->reverse_timer);
471-
}
472454
}
473455

474456
// Switch partially open and stopped
@@ -535,34 +517,15 @@ static void calculate_setpoint_target(Data *d) {
535517
d->state.sat = SAT_NONE;
536518
}
537519
} else if (d->state.sat == SAT_REVERSESTOP) {
538-
// accumalete erpms:
539-
d->reverse_total_erpm += d->motor.erpm;
540-
if (fabsf(d->reverse_total_erpm) > d->reverse_tolerance) {
541-
// tilt down by 10 degrees after exceeding aggregate erpm
542-
d->setpoint_target =
543-
(fabsf(d->reverse_total_erpm) - d->reverse_tolerance) * REVSTOP_ERPM_INCR;
520+
if (reverse_stop_active(&d->reverse_stop)) {
521+
d->setpoint_target = reverse_stop_setpoint(&d->reverse_stop);
544522
} else {
545-
if (fabsf(d->reverse_total_erpm) <= d->reverse_tolerance * 0.5) {
546-
if (d->motor.erpm >= 0) {
547-
d->state.sat = SAT_NONE;
548-
d->reverse_total_erpm = 0;
549-
d->setpoint_target = 0;
550-
}
551-
}
523+
d->state.sat = SAT_NONE;
552524
}
553-
} else if (d->float_conf.fault_reversestop_enabled && d->motor.erpm < -200 &&
525+
} else if (d->float_conf.fault_reversestop_enabled && reverse_stop_active(&d->reverse_stop) &&
554526
!d->state.darkride) {
555-
// Detecting reverse stop takes priority over any error condition SAT
556-
if (d->state.sat >= SAT_PB_HIGH_VOLTAGE) {
557-
// If this happens while in Error-Tiltback (LV/HV/TEMP) then we need to
558-
// take the already existing setpoint into account
559-
d->reverse_total_erpm =
560-
-(d->reverse_tolerance + d->setpoint_target_interpolated / REVSTOP_ERPM_INCR);
561-
} else {
562-
d->reverse_total_erpm = 0;
563-
}
527+
d->setpoint_target = reverse_stop_setpoint(&d->reverse_stop);
564528
d->state.sat = SAT_REVERSESTOP;
565-
timer_refresh(&d->time, &d->reverse_timer);
566529
} else if (d->state.mode != MODE_FLYWHEEL &&
567530
// not normal, either wheelslip or wheel getting stuck
568531
fabsf(d->motor.acceleration.value) > 10000 &&
@@ -923,6 +886,14 @@ static void refloat_thd(void *arg) {
923886
break;
924887

925888
case (STATE_RUNNING):
889+
reverse_stop_update(
890+
&d->reverse_stop,
891+
d->motor.distance,
892+
d->setpoint_target_interpolated,
893+
&d->time,
894+
d->float_conf.fault_reversestop_enabled
895+
);
896+
926897
// Check for faults
927898
if (check_faults(d)) {
928899
if (d->state.stop_condition == STOP_SWITCH_FULL && !d->state.darkride) {
@@ -1262,6 +1233,7 @@ static void data_init(Data *d) {
12621233
footpad_sensor_init(&d->footpad);
12631234
haptic_feedback_init(&d->haptic_feedback);
12641235
alert_tracker_init(&d->alert_tracker);
1236+
reverse_stop_init(&d->reverse_stop);
12651237

12661238
leds_init(&d->leds);
12671239
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.5f
25+
#define TARGET_STOP_ANGLE 15.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 setpoint, const Time *time, bool enabled
54+
) {
55+
if (!enabled && 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 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)