VEX V5 PYTHON | 03.2 CONTROLLED DECELERATION
Guide objective
Use separate acceleration and deceleration rates, compare a gradual slowdown with a brake stop and measure how far your robot travels after stopping begins.
01 | SLOWING DOWN IS PART OF THE ROUTE
A robot arriving at a target with too much speed may overshoot, slide or disturb the object it is approaching. Begin slowing before the destination rather than treating stopping as one final line of code.
Controlled deceleration gradually reduces a movement command. Stopping distance is the physical distance travelled from the chosen stop trigger until the robot comes to rest.
A softer slowdown may improve stability while taking more distance. A sharp brake command may stop more quickly but can produce slip or pitching. Measure the trade-off on your build.
02 | THREE DIFFERENT THINGS
| Idea | What it controls | Example |
|---|---|---|
| Deceleration ramp | How the commanded output decreases over time | 20%, 18.2%, 16.4%… |
| Stopping mode | Motor behaviour when a stop is issued | BRAKE, COAST or HOLD |
| Stopping distance | Physical travel after the stop trigger | Measured with a ruler or video |
The sample ramps to zero, then issues BRAKE. You can use the same final stopping mode for both gradual and direct stops to compare the effect of the ramp.
Setting output to zero does not prove that the robot is stationary. Momentum, traction and the motor controller affect the physical result.
03 | SEPARATE ACCELERATION AND DECELERATION
ACCEL_RATE_PPS = 60 DECEL_RATE_PPS = 90
Use separate rates because a gentle start and a responsive slowdown may suit the robot better than one shared rate. These values are command percentage points per second, not metres per second squared.
A larger deceleration rate reduces the command faster. A smaller rate produces a longer ramp and will generally require more room, depending on the physical response.
Compare absolute magnitudes to handle reverse motion correctly. Going from −30 to −10 is slowing down even though the signed number increases.
04 | CALCULATE A COMMAND RAMP
At 90 percentage points per second and a 20 ms update, the maximum change is 90 × 0.020 = 1.8 points.
| Time from ramp start | Ideal output from 30% |
|---|---|
| 0 ms | 30% |
| 100 ms | 21% |
| 200 ms | 12% |
| 300 ms | 3% |
| Approximately 333 ms | 0% |
Command ramp time is approximately initial command magnitude ÷ deceleration rate. A 30-point reduction at 90 points/s takes about 0.333 seconds; actual discrete updates finish at the next suitable loop iteration.
That is not a prediction of ground stopping time or distance. The test project measures elapsed time and caps delayed ramp updates at 50 ms, as in 03.1.
05 | A LIMITER THAT HANDLES BOTH RATES
def slew(current, target, rate_pps, dt_seconds):
max_change = rate_pps * dt_seconds
change = target - current
if abs(change) <= max_change:
return target
if change > 0:
return current + max_change
return current - max_change
def ramp_side(current, target, dt_seconds):
# Reduce to zero before commanding the opposite direction.
if current * target < 0:
target = 0
if abs(target) > abs(current):
rate = ACCEL_RATE_PPS
else:
rate = DECEL_RATE_PPS
return slew(current, target, rate, dt_seconds)
If target magnitude is larger, choose the acceleration rate. Otherwise choose the deceleration rate. For an opposite-sign target, first reduce towards zero, then begin the opposite direction on a later update.
The helper clamps the last step to the target, so it does not overshoot through zero. The two rate settings must be positive.
The stored value remains a commanded percent. Do not substitute measured RPM for it without redesigning the units and control logic.
06 | NORMAL SLOWDOWN VS IMMEDIATE STOP
Controlled deceleration is for a planned transition. A cancellation, mode change, deadline or limit condition should bypass the ramp when the required response is to stop.
The driver loop keeps the policy from 03.1: centring both driving inputs or holding B issues a brake stop and resets the stored outputs. Reducing a non-zero joystick request uses the deceleration rate.
The autonomous experiment deliberately ramps down on its planned stop trigger. Holding B, leaving autonomous or exceeding the test deadline issues the direct stop through cleanup.
B is a software control, not a hardware emergency stop. In this autonomous test it ends the current invocation; in driver control it stops while held and movement may resume after release if the sticks remain displaced.
07 | WHAT DETERMINES STOPPING DISTANCE?
- Initial physical speed: a faster robot usually needs more distance.
- Effective deceleration: how quickly the actual motion slows.
- Surface and tyres: available grip and sliding behaviour.
- Mass and load position: momentum, stability and load transfer.
- Gearing and motor behaviour: how motor torque affects the wheels.
- Command delay: time between detecting the stop condition and changing motor output.
Command percent is not a fixed ground speed. Compare measured approach speeds where possible, and keep battery, payload and surface comparable between trials.
08 | A SIMPLE PHYSICS ESTIMATE
For an ideal model with constant physical deceleration, stopping distance is:
distance = speed² / (2 × deceleration)
Use speed in m/s and deceleration magnitude in m/s² to obtain distance in metres. At 0.6 m/s and 1.2 m/s², distance is 0.36 ÷ 2.4 = 0.15 m.
At 1.2 m/s with the same deceleration, the estimate becomes 1.44 ÷ 2.4 = 0.60 m. Doubling speed gives four times the distance in this model.
If a stop response takes 0.05 seconds at 0.6 m/s, an additional delay distance is about 0.03 m. The simplified total would be 0.18 m.
This is an explanatory estimate, not a calibrated VEX controller. Do not insert percentage points per second into the physical deceleration formula. Real deceleration can vary, and wheel slip changes the result.
09 | COMPLETE STOPPING EXPERIMENT
This project accelerates to a low test command, holds it briefly, then either ramps down or issues a direct brake stop. Run separate trials by changing CONTROLLED_STOP_TEST.
Keep generated imports and Brain setup above this block. Paste below # Begin project code, outside the generated region, replacing the previous starter. Check motor ports, cartridges and reversal flags, and avoid duplicate Devices-panel definitions.
The test uses four explicitly declared motors and does not require an inertial sensor. It is a timed stopping experiment, not a distance-controlled autonomous routine.
# =============================================================================
# TEAM [MY TEAM NAME / NUMBER]
# =============================================================================
# ROBOT CONFIGURATION
# - FOUR-motor drivetrain:
# Left = Ports 3 and 4
# Right = Ports 1 and 2
# - Example: blue cartridges, left normal, right reversed.
# VERIFY your actual ports, cartridges and direction settings.
# DRIVER CONTROLS
# - Left joystick vertical (axis3): forward/back
# - Right joystick horizontal (axis1): turn
# - This program MOVES during an autonomous stopping experiment.
# - Driver control retains the established split-stick scheme.
# - Add BELOW # Begin project code, outside generated configuration.
# - Do not also define these drive motors in the Devices panel.
# - Keep generated from vex import * and brain = Brain() above.
# =============================================================================
# FOUR-MOTOR DRIVETRAIN
# =============================================================================
left_motor_1 = Motor(Ports.PORT3, GearSetting.RATIO_6_1, False)
left_motor_2 = Motor(Ports.PORT4, GearSetting.RATIO_6_1, False)
right_motor_1 = Motor(Ports.PORT1, GearSetting.RATIO_6_1, True)
right_motor_2 = Motor(Ports.PORT2, GearSetting.RATIO_6_1, True)
left_drive = MotorGroup(left_motor_1, left_motor_2)
right_drive = MotorGroup(right_motor_1, right_motor_2)
# =============================================================================
# CONTROLLER, SETTINGS AND HELPERS
# =============================================================================
controller = Controller(PRIMARY) # Do not duplicate a generated Controller
DEADBAND = 5
DRIVE_LIMIT_PERCENT = 30 # Start low; increase only after testing
TURN_GAIN = 0.7
LOOP_DELAY_MS = 20
ACCEL_RATE_PPS = 60 # Normal increases in command magnitude
DECEL_RATE_PPS = 90 # Normal decreases in command magnitude
TEST_SPEED_PERCENT = 20
CRUISE_TIME_MS = 400
TEST_LIMIT_MS = 5000
CONTROLLED_STOP_TEST = True # False: issue BRAKE at the same stop trigger
MAX_RAMP_DT_SECONDS = 0.05 # Cap a delayed update to a 50 ms ramp step
DRIVER_STOP_MODE = BRAKE
# Controller B requests an immediate software stop while held.
def apply_deadband(value):
return 0 if abs(value) <= DEADBAND else value
def drive_mix(forward, turn):
left = forward + turn
right = forward - turn
largest = max(100, abs(left), abs(right))
left = left * 100 / largest
right = right * 100 / largest
return (left * DRIVE_LIMIT_PERCENT / 100,
right * DRIVE_LIMIT_PERCENT / 100)
def set_side_output(side, output):
if abs(output) < 0.01:
side.stop(DRIVER_STOP_MODE)
elif output > 0:
side.spin(FORWARD, output, PERCENT)
else:
side.spin(REVERSE, -output, PERCENT)
def slew(current, target, rate_pps, dt_seconds):
max_change = rate_pps * dt_seconds
change = target - current
if abs(change) <= max_change:
return target
if change > 0:
return current + max_change
return current - max_change
def ramp_side(current, target, dt_seconds):
# Reduce to zero before commanding the opposite direction.
if current * target < 0:
target = 0
if abs(target) > abs(current):
rate = ACCEL_RATE_PPS
else:
rate = DECEL_RATE_PPS
return slew(current, target, rate, dt_seconds)
def show_status(message):
brain.screen.clear_screen()
brain.screen.set_cursor(1, 1)
brain.screen.print(message)
def stop_drive():
left_drive.stop(DRIVER_STOP_MODE)
right_drive.stop(DRIVER_STOP_MODE)
# =============================================================================
# PRE-AUTON: CALLED ONCE AT PROJECT STARTUP
# =============================================================================
def pre_autonomous():
stop_drive()
show_status("Setup ready")
# Later: initialise sensors and starting values here.
# Finish setup before the robot is enabled for a match.
# =============================================================================
# AUTONOMOUS: A FINITE ROUTINE
# =============================================================================
def autonomous():
output = 0
stage = "ACCELERATE"
start_ms = brain.timer.time(MSEC)
previous_ms = start_ms
cruise_start_ms = start_ms
stop_drive()
show_status(stage)
try:
while True:
# These conditions always bypass the deceleration ramp.
if not (competition.is_enabled() and competition.is_autonomous()):
show_status("MODE_CHANGED")
return
if controller.buttonB.pressing():
show_status("CANCELLED")
return
now_ms = brain.timer.time(MSEC)
if now_ms - start_ms >= TEST_LIMIT_MS:
show_status("TIMEOUT")
return
dt = max(0, (now_ms - previous_ms) / 1000)
previous_ms = now_ms
dt = min(dt, MAX_RAMP_DT_SECONDS)
if stage == "ACCELERATE":
output = ramp_side(output, TEST_SPEED_PERCENT, dt)
if output == TEST_SPEED_PERCENT:
stage = "CRUISE"
cruise_start_ms = now_ms
show_status(stage)
elif stage == "CRUISE":
if now_ms - cruise_start_ms >= CRUISE_TIME_MS:
# Use this marker to identify the start of stopping in video.
print("STOP_START_MS", now_ms)
show_status("STOP START")
if not CONTROLLED_STOP_TEST:
stop_drive()
show_status("BRAKE commanded; observe settling")
return
stage = "DECELERATE"
else:
output = ramp_side(output, 0, dt)
if output == 0:
stop_drive()
print("ZERO_COMMAND_MS", now_ms)
show_status("Zero command; observe settling")
return
set_side_output(left_drive, output)
set_side_output(right_drive, output)
wait(LOOP_DELAY_MS, MSEC)
finally:
stop_drive()
# =============================================================================
# DRIVER CONTROL: REPEATING UPDATES WHILE ENABLED
# =============================================================================
def driver_control():
left_output = 0
right_output = 0
previous_ms = brain.timer.time(MSEC)
stop_drive()
show_status("Separate ramp-rate driver control")
try:
while competition.is_enabled() and competition.is_driver_control():
now_ms = brain.timer.time(MSEC)
dt_seconds = max(0, (now_ms - previous_ms) / 1000)
previous_ms = now_ms
dt_seconds = min(dt_seconds, MAX_RAMP_DT_SECONDS)
forward = apply_deadband(controller.axis3.position())
turn = apply_deadband(controller.axis1.position()) * TURN_GAIN
left_target, right_target = drive_mix(forward, turn)
if controller.buttonB.pressing() or (forward == 0 and turn == 0):
# A deliberate stop bypasses the normal ramp.
stop_drive()
left_output = 0
right_output = 0
else:
left_output = ramp_side(left_output, left_target, dt_seconds)
right_output = ramp_side(right_output, right_target, dt_seconds)
set_side_output(left_drive, left_output)
set_side_output(right_drive, right_output)
wait(LOOP_DELAY_MS, MSEC)
finally:
stop_drive()
# =============================================================================
# PROGRAM START: SETUP, THEN CALLBACK REGISTRATION
# =============================================================================
pre_autonomous()
competition = Competition(driver_control, autonomous)
# Keep the main task alive without running a driving loop here.
while True:
wait(100, MSEC)
The STOP_START_MS message marks the planned stop trigger; ZERO_COMMAND_MS marks the end of a controlled command ramp. Neither proves physical rest. Observe the robot until it has settled.
The test has no automatic next movement. Driver controls retain left-stick forward/back and right-stick turning. Match all settings to your robot before testing.
10 | MEASURE FROM THE CORRECT POINT
- Verify wheel directions with the robot supported.
- Choose a level, clear test lane with ample run-out space. Begin with the supplied low test command.
- Record the robot and a visible stop-trigger indicator with fixed reference marks on the floor.
- Identify the stop-trigger frame using the Brain display or synchronised console timing. Record the position of one consistent chassis reference point.
- Record that same reference point once the robot is physically at rest.
- Subtract the two positions. This is post-trigger stopping distance, including travel during the ramp and subsequent settling.
Do not use total distance from the start of the trial: that includes acceleration and cruising. If the cruise interval does not give a stable approach speed, adjust it cautiously and document the change before comparing runs.
Record timing uncertainty in the trigger frame. Nobody should approach or mark the robot by hand while it is moving.
11 | COMPARE LIKE WITH LIKE
| Test command | Stop type | Trial 1 distance | Trial 2 distance | Trial 3 distance |
|---|---|---|---|---|
| 20% | Controlled ramp + BRAKE | Measure | Measure | Measure |
| 20% | Direct BRAKE | Measure | Measure | Measure |
| Higher tested setting | Controlled ramp + BRAKE | Measure | Measure | Measure |
Use the same final BRAKE mode, initial command, acceleration rate, cruise duration and load for the first comparison. Change only the planned stop policy.
Calculate the mean and range. A short average stop with large variation can be less useful for autonomous accuracy than a slightly longer but predictable stop.
After testing, explore a second deceleration rate with all other settings unchanged. Increase speed only when there is sufficient clearance and the build remains stable.
12 | BEGIN SLOWING BEFORE THE TARGET
Use your measured stopping behaviour to choose an approach zone. If repeated tests show around 160 mm of travel after a planned slowdown begins, starting it exactly 160 mm from an object leaves no allowance for variation.
Allow a margin based on the observed range, sensing delay and surface conditions, then approach at a lower final speed. A timed ramp alone cannot guarantee arrival at a position.
A distance controller or motion profile uses remaining distance to decide when to slow, and checks the final outcome. Avoid running a separate output-ramping task alongside SmartDrive commands that also own the motors.
13 | COMMON MISTAKES
- Confusing ramp time with stopping time: zero command is not verified rest.
- Measuring from the launch point: this includes the whole run, not just stopping.
- Using signed comparison for reverse: compare magnitudes to select the rate.
- Changing stop mode and ramp together: you cannot tell which caused the difference.
- Delaying cancellation: a planned slowdown must not hide an immediate stop condition.
- Trusting one trial: collect repeated measurements and inspect variation.
CHALLENGES | STOP WITH EVIDENCE
- Calculate: find command ramp time from 24% to zero at 80 points/s.
- Explain: distinguish controlled deceleration, BRAKE mode and physical stopping distance.
- Trace: identify the rate chosen for −30 to −10 and −10 to −30.
- Compare: collect three controlled-stop and three direct-brake trials.
- Analyse: calculate mean and range for each condition.
- Verify: confirm that B and competition disable bypass the planned ramp.
- Plan: propose an approach zone and justify its margin using your measurements.
- Evaluate: explain why a command-percent ramp cannot supply the deceleration value in the physics formula.
Check the calculations
24 ÷ 80 = 0.30 seconds for the ideal command ramp. −30 to −10 reduces magnitude, so it uses DECEL_RATE_PPS. −10 to −30 increases magnitude, so it uses ACCEL_RATE_PPS.
OFFICIAL REFERENCES
The ramp logic is team-written Python. This is an independent learning resource; VEX and VEXcode are trademarks of their respective owners.