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() and turn() ramps. Default 720 (2 wheel-rev/s²) — lower it if the robot pitches or lifts its rear on launch. In mm/s² that’s acceleration * 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=False move (new command supersedes, pybricks-style). then selects 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 implement hold() (e.g. ST3215Motor); open-loop drivers raise NotImplementedError.

done()[source]

Pybricks-style status check for in-flight straight(wait=False) / turn(wait=False). Returns True if no move is pending or the active move has reached its target (and stop(then=…) has run). Returns False while the move is still progressing.

On the fallback path (serial-bus servo drivebases), motors start moving on the straight() / turn() call itself; each done() 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 is time.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.

then is forwarded to stop() — see its docstring for coast/brake/hold semantics.

wait=True (default) blocks until the move completes. wait=False returns immediately after arming the move; the caller polls done() to advance the state machine (fallback path) or just check completion (native path), and the then= dispatch is deferred until done() reports the target was reached. Concurrent use with another wait=False move on a separate DriveBase (or with motor run_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).

turn(angle_deg, then='coast', wait=True)[source]

Turn in place by angle_deg body heading (positive = left).

Same then / wait semantics as straight() — see its docstring.