robotics — DriveBase
Two-wheel differential drivebase.
Thin Python wrapper over _openbricks_native.DriveBase — the C
implementation at native/user_c_modules/openbricks/drivebase.c that
runs 2-DOF coupled control at 1 kHz. Both motors are driven by a
single forward-progress trajectory and a heading-hold trajectory; a
heading-error feedback term keeps them in sync even when one wheel has
more friction than the other.
Public API matches the M1 Python version so existing code and tests don’t need to change:
db = DriveBase(left, right, wheel_diameter_mm=56, axle_track_mm=114) db.settings(straight_speed=200, turn_rate=180) # deg/s at wheels db.straight(500) # mm, blocking db.turn(90) # deg body heading, blocking db.drive(100, 0) # non-blocking kinematic mapping
Open-loop drive() bypasses the coupled controller; it just maps
(speed_mm_s, turn_rate_dps) → (left_dps, right_dps) and hands them to
each servo’s run_speed. Useful for interactive control where
profile-based moves would feel sluggish.
- class openbricks.robotics.drivebase.DriveBase(left, right, wheel_diameter_mm, axle_track_mm, imu=None)[source]
Bases:
object- settings(straight_speed=None, turn_rate=None, acceleration=None)[source]
Tune cruise + ramp parameters for subsequent moves.
- Parameters:
straight_speed – cruise speed for
straight(), wheel-deg/s.turn_rate – cruise rate for
turn(), wheel-deg/s.acceleration – trajectory acceleration, wheel-deg/s², shared by
straight()andturn()ramps. Default 720 (2 wheel-rev/s²) — lower it if the robot pitches or lifts its rear on launch. In mm/s² that’sacceleration * wheel_circumference / 360. Applies on both paths: the native (encoder-servo) controller arms its C trajectory with it, and the serial-bus fallback shapes its per-tick speed command with the same trapezoid.
- use_gyro(enable)[source]
Switch the heading feedback source between encoder-diff (default) and the attached IMU (when True). Pybricks-style.
Requires an
imu=argument to the constructor. With the gyro, heading is slip-immune — wheel slip or wildly asymmetric friction won’t throw the robot off course, because the IMU sees actual body rotation regardless of what the wheels did.
- drive(speed_mm_s, turn_rate_dps)[source]
Start driving at a given forward speed + body turn rate.
Kinematic one-shot — no coupled feedback. Call again (or
stop()) to change. Positive turn rate = left turn.
- stop(then='coast')[source]
Halt both wheels. Also clears any pending
wait=Falsemove (new command supersedes, pybricks-style).thenselects the end-state:"coast"(default) — both motors free-wheel."brake"— both motors actively resist motion at zero velocity."hold"— both motors actively hold their current angle. Requires motors that implementhold()(e.g.ST3215Motor); open-loop drivers raiseNotImplementedError.
- done()[source]
Pybricks-style status check for in-flight
straight(wait=False)/turn(wait=False). ReturnsTrueif no move is pending or the active move has reached its target (andstop(then=…)has run). ReturnsFalsewhile the move is still progressing.On the fallback path (serial-bus servo drivebases), motors start moving on the
straight()/turn()call itself; eachdone()invocation then runs ONE tick of the heading-hold loop — read both angles, apply differential correction, check the target. You must keep polling until ``done()`` returns True or the motors will continue running at the open-loop profile speed past the target. The natural cadence istime.sleep_ms(10)between polls.On the native path, the C scheduler runs the trajectory independently at 1 kHz;
done()just checks a flag and motors stop automatically when the trajectory completes.
- straight(distance_mm, then='coast', wait=True)[source]
Drive forward by
distance_mm. 2-DOF coupled.thenis forwarded tostop()— see its docstring for coast/brake/hold semantics.wait=True(default) blocks until the move completes.wait=Falsereturns immediately after arming the move; the caller pollsdone()to advance the state machine (fallback path) or just check completion (native path), and thethen=dispatch is deferred untildone()reports the target was reached. Concurrent use with another wait=False move on a separateDriveBase(or with motorrun_angle(wait=False)calls) is the intended pattern.Any subsequent move command supersedes the previous pending wait=False move (pybricks “new command wins”).
Falls back to a pure-Python heading-hold loop for motors without a native servo (i.e. serial-bus ST-3215/ST-3032 drivebases).