Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Motors

nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;

let motor = brick.motor(A)?;
motor.run(power!(75)).degrees(degrees!(360)).wait().await?;
Ok(()) });

A Motor<P, T> controls the motor on output P. Its methods take &self, so a motor can be shared between tasks, for example behind an Arc.

Running

motor.run(power) starts a run command. Configure it with builder methods, then .await it (or .block() it):

MethodEffect
(none)Run until told otherwise
.degrees(d)Stop after turning by d
.wait()Wait until the movement is finished (only after .degrees)
.brake()Brake mode: stop firmly; with .wait(), hold the position afterwards
.regulated()Keep the speed constant under load
.ramp_up() / .ramp_down()Ramp the power smoothly
nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;
let motor = brick.motor(A)?;

motor.run(power!(50)).await?;                                          // forward, forever
motor.run(power!(-30)).regulated().await?;                             // slow and steady backwards
motor.run(power!(100)).degrees(degrees!(90)).brake().wait().await?;    // quarter turn, then hold
motor.run(power!(60)).degrees(Degrees::from_rotations(3)?).wait().await?;
Ok(()) });

Negative power runs the motor backwards. .degrees always counts the rotation as a positive number.

Why .wait() needs .degrees()

Without a limit the motor never stops by itself, so waiting would hang forever. The run command carries a type parameter that records whether a limit was set, and .wait() only exists on limited runs:

async fn f(brick: nxt_rs::Brick<nxt_rs::transport::Mock>) -> nxt_rs::Result<()> {
use nxt_rs::prelude::*;
let motor = brick.motor(A)?;
motor.run(power!(50)).wait().await?;    // error: no method named `wait` found
Ok(()) }

Stopping

nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;
let motor = brick.motor(A)?;
motor.coast().await?;   // power off, spin freely
motor.brake().await?;   // stop and actively hold the position
Ok(()) });

Holding the position draws battery power, so coast when you’re done.

Position and state

The firmware keeps three counters per motor, all in degrees:

CounterMeaning
tacho_countThe firmware’s internal counter for the current movement and its limit
block_tacho_countRelative to the last programmed movement
rotation_countSince the last absolute reset; use this for “where is the motor?”
nxt_rs::doc_example(|brick| async move {
use nxt_rs::{prelude::*, motor::Reset};
let motor = brick.motor(A)?;

motor.reset_position(Reset::Absolute).await?;
motor.run(power!(40)).degrees(degrees!(180)).wait().await?;
let tacho = motor.tacho().await?;
println!("now at {}°", tacho.rotation_count);

let running = motor.is_running().await?;
let status = motor.status().await?;         // the full OutputStatus record
Ok(()) });

motor.wait_idle() waits until the firmware reports the motor idle. That’s useful after starting a limited run without .wait().

Driving with two motors

Two motors that drive a robot should turn in step, even if one wheel meets more resistance. A SyncedPair asks the firmware to keep them synchronised:

nxt_rs::doc_example(|brick| async move {
use nxt_rs::{prelude::*, turn_ratio};

let (left, right) = brick.take((B.motor(), C.motor())).await?;
let mut drive = SyncedPair::new(left, right);

drive.forward(power!(70)).degrees(degrees!(720)).wait().await?;              // straight
drive.steer(power!(50), turn_ratio!(25)).degrees(degrees!(360)).wait().await?; // gentle right
drive.steer(power!(50), turn_ratio!(-100)).degrees(degrees!(230)).wait().await?; // spin left
drive.brake().await?;
Ok(()) });

The turn ratio goes from −100 to 100:

RatioMotion
0Straight
25Gentle curve right (right wheel slower)
50Right wheel stopped: pivot around it
100Wheels in opposite directions: spin in place
negativeThe same, to the left

steer and forward support .degrees, .wait and .brake like run.

When you’re done, into_inner() stops the synchronisation and gives the motors back. The firmware keeps trying to sync motors that were synced before, so always use into_inner before driving them separately:

nxt_rs::doc_example(|brick| async move {
use nxt_rs::prelude::*;
let (left, right) = brick.take((B.motor(), C.motor())).await?;
let drive = SyncedPair::new(left, right);
let (left, right) = drive.into_inner().await?;
Ok(()) });

Accuracy

  • Rotation limits are approximate. The firmware switches the motor off when the limit is reached, and the motor overshoots by a few degrees, more at high power. .brake() reduces the overshoot.
  • Each command is a round trip: a few milliseconds over USB, about 60 ms over Bluetooth. For fast closed-loop control (balancing, precise positioning), run the control loop in an on-brick program and talk to it with messages.

Releasing the port

Dropping a motor releases output P for others. motor.release() coasts the motor and gives back the claimed Port, so you can reuse it without letting go. See Ports and devices.