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):
| Method | Effect |
|---|---|
| (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:
| Counter | Meaning |
|---|---|
tacho_count | The firmware’s internal counter for the current movement and its limit |
block_tacho_count | Relative to the last programmed movement |
rotation_count | Since 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:
| Ratio | Motion |
|---|---|
0 | Straight |
25 | Gentle curve right (right wheel slower) |
50 | Right wheel stopped: pivot around it |
100 | Wheels in opposite directions: spin in place |
| negative | The 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.