Motor Steps

Concept: Two Motor Worlds

Raccoon has two completely separate worlds for motor control:

  1. Drive-system motors — the chassis motors (front-left, front-right, etc.). You never command these directly. The kinematic model (DifferentialKinematics / MecanumKinematics) translates a body velocity (drive_forward, turn_left) into per-wheel commands. You control the robot’s motion, not the individual motors.

  2. Mechanism motors — arm lifts, drum collectors, conveyors, indexers. These are commanded directly by port using the motor-step API on this page. No kinematic layer sits between your code and the motor.

flowchart TD
    subgraph "Drive system"
        DS["drive_forward(cm)\nturn_left(deg)\nstrafe_right(cm)"] --> KM["Kinematic model\nDifferential / Mecanum"]
        KM --> WHL["Per-wheel velocity commands\n(chassis motors only)"]
    end
    subgraph "Mechanism motors"
        MS["move_motor_to(motor, pos)\nset_motor_dps(motor, dps)\nset_motor_velocity(motor, v)"] --> MW["Direct motor command\n(mechanism motor only)"]
    end
    style DS fill:#4CAF50,color:#fff
    style MS fill:#42A5F5,color:#fff

Key rule: Never mix. Don’t command a chassis motor with set_motor_velocity — you’ll fight the drive controller. Don’t try to use mechanism motors with drive_forward — they aren’t in the kinematic model.

Motor steps give you direct control over individual motors — independent of the drive system. Use them for arm lifts, conveyors, drum collectors, indexers, or any mechanism driven by a motor that is not part of the chassis.

The drive system (drive_forward, turn_left, etc.) controls all chassis motors together through a kinematic model. Motor steps bypass that and let you command one specific motor at a time, by port number.

Control Modes

ModeAPIClosed loop?When to use
Position (absolute)move_motor_to()Yes — BEMF position controllerArms, lifts with absolute encoder reference
Position (relative)move_motor_relative()YesMove by a fixed delta from current position
Velocity (human units)set_motor_dps()Yes — BEMF velocity controllerDrums, conveyors when calibration is available
Velocity (raw BEMF)set_motor_velocity()Yes — BEMF velocity controllerWhen you know the raw ticks target
Power (open loop)set_motor_power()No — raw PWMSimple actuators where speed variation is acceptable

Imports

from raccoon.step.motor import (
    move_motor_to,
    move_motor_relative,
    set_motor_dps,
    set_motor_velocity,
    set_motor_power,
    motor_off,
    motor_passive_brake,
    motor_brake,
    StopMode,
)

All of these are also re-exported from the top-level raccoon package.

The StopMode Enum

Every motor has three stopping behaviours. Understanding the difference is important for mechanism design:

from raccoon.step.motor import StopMode
ValueWhat happensWhen to use
StopMode.OFFPower removed; motor coasts freely under frictionLet an arm fall, release a conveyor belt
StopMode.PASSIVE_BRAKEH-bridge shorts motor leads; electrical braking decelerates faster than coasting but no active holdStop a spinning drum that does not need to hold position
StopMode.ACTIVE_BRAKEFirmware stop latch: actively resists external forces and holds the current shaft positionHold an arm up against gravity after a move_motor_to

The convenience steps motor_off(), motor_passive_brake(), and motor_brake() wrap these modes so you do not need to use StopMode directly in most code.

Position Steps (Blocking)

move_motor_to() — Move to Absolute Position

Commands the motor’s firmware position controller to drive to an absolute encoder tick count, then blocks until the firmware reports the move is complete (or until an optional timeout fires).

from raccoon.step.motor import move_motor_to, motor_brake

seq([
    # Raise arm to encoder position 500 at speed 800 ticks/s
    move_motor_to(Defs.arm_motor, position=500, velocity=800, timeout=3.0),
    motor_brake(Defs.arm_motor),   # Lock in place after reaching target
])

Parameters:

ParameterTypeDefaultDescription
motorIMotorrequiredMotor from the hardware map (robot.motor(N) or Defs.arm_motor)
positionintrequiredTarget position in absolute encoder ticks
velocityint1000Movement speed in firmware ticks/s. Must be > 0.
timeoutfloat | NoneNoneMaximum seconds to wait. None = wait indefinitely. If the timeout fires, a warning is logged and the step returns — the motor is not stopped automatically.

Tip: Always follow move_motor_to() with motor_brake() if the mechanism must hold position (e.g. an arm against gravity). The position controller stops holding once the step completes.

move_motor_relative() — Move by Encoder Delta

Same as move_motor_to() but accepts a relative tick count instead of an absolute position:

from raccoon.step.motor import move_motor_relative, motor_brake

seq([
    # Extend arm 200 ticks forward
    move_motor_relative(Defs.arm_motor, delta=200, velocity=600),
    # Retract 200 ticks back
    move_motor_relative(Defs.arm_motor, delta=-200, velocity=400),
    motor_brake(Defs.arm_motor),
])

Parameters:

ParameterTypeDefaultDescription
motorIMotorrequiredMotor to control
deltaintrequiredEncoder ticks to travel. Positive = forward, negative = reverse.
velocityint1000Movement speed in firmware ticks/s. Must be > 0.
timeoutfloat | NoneNoneMaximum seconds to wait before giving up.

Velocity Steps (Non-Blocking)

Velocity steps complete immediately — the motor continues spinning until another command (or a stop step) is issued. Use these inside parallel() or combined with do_while_active() / loop_for().

set_motor_dps() — Human-Friendly Degrees Per Second

The most readable way to set a continuous motor speed. Converts degrees per second to firmware BEMF units using the motor’s stored calibration (ticks_to_rad).

from raccoon.step.motor import set_motor_dps, motor_brake

seq([
    set_motor_dps(Defs.drum_motor, dps=360.0),   # One full rotation per second
    wait(2.0),
    motor_brake(Defs.drum_motor),
])

Prerequisite: The motor must have valid calibration data (ticks_to_rad set by calibrate() or auto_tune_bemf_velocity()). Without calibration, the conversion factor will be wrong and the actual speed will differ from the requested value.

Parameters:

ParameterTypeDefaultDescription
motorIMotorrequiredMotor to control
dpsfloatrequiredAngular velocity in degrees per second. Positive = forward, negative = reverse.

set_motor_velocity() — Raw BEMF Units

Sets the motor’s firmware closed-loop velocity controller to a raw BEMF tick target. Useful when you know the firmware units directly (e.g. after reading them from a characterization run), but set_motor_dps() is usually more readable.

from raccoon.step.motor import set_motor_velocity, motor_brake

seq([
    set_motor_velocity(Defs.drum_motor, velocity=800),
    wait(3.0),
    motor_brake(Defs.drum_motor),
])

Parameters:

ParameterTypeDefaultDescription
motorIMotorrequiredMotor to control
velocityintrequiredFirmware BEMF tick velocity. Positive = forward, negative = reverse.

set_motor_power() — Open-Loop Percent Power

Sets a raw duty-cycle power percentage with no feedback control. The motor runs at whatever speed corresponds to that PWM duty cycle under current load — which varies with battery voltage and mechanical load. Use this for simple actuators where precision does not matter.

from raccoon.step.motor import set_motor_power, motor_off

seq([
    set_motor_power(Defs.conveyor_motor, percent=70),
    wait(1.5),
    motor_off(Defs.conveyor_motor),
])

Parameters:

ParameterTypeDefaultDescription
motorIMotorrequiredMotor to control
percentintrequiredDuty-cycle power from -100 (full reverse) to 100 (full forward). Values outside this range raise ValueError.

Stop Steps

All three stop steps complete immediately and do not block.

motor_off() — Coast

Removes power. The motor spins down under friction alone.

motor_off(Defs.conveyor_motor)

motor_passive_brake() — Electrical Brake

Commands zero power, causing the H-bridge to short the motor leads. Decelerates faster than coasting but does not hold position once the motor stops.

motor_passive_brake(Defs.drum_motor)

motor_brake() — Active Hold

Engages the firmware’s stop latch: the firmware actively resists external forces and maintains the current shaft position. Use this after move_motor_to() when the mechanism must not move (e.g. holding an arm up against gravity).

motor_brake(Defs.arm_motor)

Builder Pattern

Like all step factories, motor steps return a builder that lets you chain modifiers:

# Add anomaly detection — called if the step takes unusually long
move_motor_to(Defs.arm_motor, position=400).on_anomaly(
    lambda step, duration: print(f"Move slow: {duration:.2f}s")
)

# Skip timing instrumentation for this step
set_motor_dps(Defs.drum_motor, dps=180.0).skip_timing()

Real-World Patterns

Arm: Raise to Target, Hold, Then Release

seq([
    move_motor_to(Defs.arm_motor, position=500, velocity=600, timeout=3.0),
    motor_brake(Defs.arm_motor),    # Hold up against gravity

    # ... perform task while arm is raised ...
    drive_forward(30),

    move_motor_to(Defs.arm_motor, position=0, velocity=400, timeout=3.0),
    motor_off(Defs.arm_motor),      # Release at bottom — no need to hold
])

Drum: Spin in Background While Driving

parallel(
    drive_forward(50),              # Drive forward 50 cm
    seq([
        set_motor_dps(Defs.drum_motor, dps=720.0),   # Start spinning at 2 rev/s
        wait(4.0),
        motor_passive_brake(Defs.drum_motor),
    ]),
)

Indexer: Relative Move for Each Step

def advance_one_pocket():
    """Advance drum indexer by one pocket (350 encoder ticks)."""
    return seq([
        move_motor_relative(Defs.indexer_motor, delta=350, velocity=500, timeout=2.0),
        motor_brake(Defs.indexer_motor),
    ])

seq([
    advance_one_pocket(),
    # ... release object ...
    advance_one_pocket(),
    # ... release object ...
])

Timed Motor Control Without Limit Switches

When a mechanism has no limit switches, the practical alternative is time-based gating: set_motor_velocity + wait_for_seconds + a stop step. The entire sequence is wrapped in a named function for readability.

This pattern is used in competition code (adapted from the conebot’s cone container mechanism):

# src/steps/cone_container_steps.py
from raccoon import *
from src.hardware.defs import Defs

def down_cone_container():
    """Lower the cone container and re-lift slightly to release the cone."""
    return seq([
        set_motor_velocity(Defs.cone_container_motor, velocity=-1700),
        wait_for_seconds(0.4),                                        # time-gate
        motor_passive_brake(Defs.cone_container_motor),
        drive_forward(cm=10),
        set_motor_velocity(Defs.cone_container_motor, velocity=1700), # re-lift
        wait_for_seconds(0.2),
        motor_passive_brake(Defs.cone_container_motor),
    ])

The raw velocity value (-1700) is in BEMF firmware ticks. If your motor is calibrated you can use the equivalent set_motor_dps() call instead — but set_motor_velocity() is fine once you’ve characterized the ticks on a specific motor.

Getting a Motor by Port

Motor objects come from the hardware map defined in defs.py. You can also look one up by port number directly from the robot:

# In a custom step's _execute_step:
motor = robot.motor(2)     # Get the motor on port 2

In practice, always use named motors from Defs — port numbers are error-prone:

# Preferred
Defs.arm_motor

# Works, but fragile — the number must match the YAML definition
robot.motor(2)
  • Servos — position-controlled actuators for arms and claws
  • Calibration — required before set_motor_dps() gives accurate speeds
  • Custom Steps — writing MotionStep classes that call robot.drive.set_velocity() directly