Skip to main content

Crate m0601

Crate m0601 

Source
Expand description

Driver for the DFRobot M0601 direct-drive hub motor over half-duplex RS485. Covers both SKUs — FIT1042 (left) and FIT1038 (right) are mirror-image builds of the same motor and speak the identical protocol; see M0601::mirrored for making “forward” mean the same thing on both sides of a chassis.

The M0601 is not Modbus. It speaks a fixed 10-byte frame protocol at 115200 8N1 with a CRC-8/MAXIM checksum, and it is a polling device: motion is sustained only while the host keeps resending drive frames. RS485 is multi-drop: several motors share one A/B pair (IDs 0x01..=0xFE) — a Bus owns the port and mints per-motor M0601 handles.

§Safety and the polling protocol

A single drive command will not keep the wheel spinning. The motor moves only while drive frames arrive at ≥DRIVE_HZ_MIN (50) Hz, up to CMD_HZ_MAX (500) Hz. If the host stops — crash, unplugged adapter, power loss — the motor coasts to a stop. That is the protocol’s built-in fail-safe; M0601::safe_stop upgrades a coast to an active braked stop for orderly shutdowns and should be called on every exit path of a control loop.

One consequence deserves stating outright: a zero setpoint does not mean “stop” except in velocity mode. The same zero-valued 0x64 frame commands a move to 0° in position mode and zero torque in current mode, which is why M0601::safe_stop establishes velocity mode before it sends anything else.

The motor also protects itself in hardware (each auto-resets after ~5 s):

ProtectionTripFault bit
Sensor errorhall/encoder fault0x01
Bus overcurrent3 A0x02
Phase overcurrent4.6 A0x04
Stalllocked > 5 s0x08
Over-temperature80 °C (releases 75 °C)0x10

§Wire format

Host → motor frames (see protocol):

Byte0123456789
IDCMDVAL_HIVAL_LO00ACCELBRAKE0CRC
  • CMD is 0x64 (drive), 0x74 (feedback query) or 0xA0 (mode switch). For 0xA0 the last byte is the mode (01/02/03), not a CRC.
  • ACCEL sets ramp steepness: larger is gentler, and 0 selects the motor default — which measures identical to 1, the fastest ramp. No vendor source states that direction; see protocol::frame_velocity for the measurement.
  • BRAKE = 0xFF engages the electric brake (velocity mode only).
  • Two special unaddressed frames exist: the broadcast ID query (C8 64 00×7 DE) and set-ID (AA 55 53 <id> 00×6, no CRC, must be sent 5×, one motor on the bus).

Motor → host telemetry replies come in two layouts, selected by the command that elicited them (ReplyKind):

Reply to a 0x74 feedback query (ReplyKind::Query):

Byte012–34–56789
IDmodecurrent (i16 BE)speed (i16 BE)temp °Cposition u8faultschk

Reply to a 0x64 drive frame or the broadcast ID query (ReplyKind::Drive) — no temperature, but a 16-bit position:

Byte012–34–56–789
IDmodecurrent (i16 BE)speed (i16 BE)position (u16 BE)faultschk

Current scales ×8/32767 to amps; the 8-bit position ×360/255 and the 16-bit position ×360/32767 to degrees. Replies carry a CRC-8/MAXIM in byte 9 (verified on hardware). By default telemetry is not rejected on it — Feedback::crc_ok is informational — but the opt-in strict mode (Bus::with_strict_crc / M0601::with_strict_crc) turns a bad checksum into Ok(None). See PROTOCOL.md.

§Multiple motors on one bus

Bus enforces a minimum idle gap between frames (Bus::with_min_gap) so no two frames — or a frame and the reply an earlier drive frame elicited — can overlap on the half-duplex pair; Bus::set_mode_all and Bus::safe_stop_all switch or stop every wheel round-major, so a vehicle stops in the same ~300 ms as one motor. Budget the wire: each motor needs its drive frame at ≥50 Hz, so N motors put ≥N×50 frames/s (plus replies, plus gaps) through one bus. bus_period computes that occupancy from frame_time and the gap; a loop’s cycle must exceed it yet stay within drive_floor. See Budgeting the wire for the worked arithmetic.

Coming from another fieldbus or motor-control ecosystem, the concepts map directly:

HereElsewhere
enforced inter-frame gap (Bus::with_min_gap)Modbus RTU’s 3.5-character silence; CANopen’s PDO inhibit time
coast when drive frames stop (the 50 Hz floor)a command watchdog / failsafe timeout, permanently enabled
Bus::set_mode_all / Bus::safe_stop_all (reply-less batching)Dynamixel’s broadcast Sync Write
automatic low-latency request (SerialTransport::low_latency)pyserial’s set_low_latency_mode(True)

§Control modes

ModeWireValue rangeMeaning
Mode::Current0x01−32767..=32767≈ −8 A..+8 A
Mode::Velocity0x02−330..=330RPM
Mode::Position0x030..=327670°..360°

Setpoints outside these ranges are clamped, never wrapped. Mode switches must be sent five times (M0601::set_mode does). Switching to position mode requires the wheel to be under 10 RPM.

§Example

Real hardware:

use std::time::Duration;
use m0601::M0601;

let mut motor = M0601::open("/dev/ttyUSB0", 0x01, Duration::from_millis(150))?;
match motor.query()? {
    // `query()` replies always carry the winding temperature.
    Some(fb) if fb.temp_c.is_some_and(|t| t < 70) => {
        println!("{:+} RPM, faults: {}", fb.speed_rpm, fb.faults);
    }
    Some(fb) => println!("running hot: {:?} °C", fb.temp_c),
    None => println!("no reply — check 18 V power, wiring (brown → GND), A/B polarity"),
}

No hardware needed — every driver behavior runs against MockTransport:

use std::time::Duration;
use m0601::{M0601, MockTransport};

let mock = MockTransport::with_replies([
    vec![0x01, 0x02, 0x00, 0x00, 0x00, 0x64, 0x28, 0x00, 0x00, 0x00],
]);
let mut motor = M0601::with_transport(mock, 0x01, Duration::from_millis(150))?;
let fb = motor.query()?.unwrap();
assert_eq!(fb.speed_rpm, 100);

§References

The repository’s protocol reference is the full protocol and hardware reference, with per-claim sourcing and the known contradictions between sources (every PROTOCOL.md mention in these docs points there — the root PROTOCOL.md is now a pointer to that page). Primary materials:

Re-exports§

pub use bus::Bus;
pub use bus::BusTiming;
pub use bus::DEFAULT_DRIVE_ACCEL;
pub use bus::DEFAULT_MIN_GAP;
pub use bus::M0601;
pub use bus::PositionMirror;
pub use bus::ScanReport;
pub use bus::bus_period;
pub use error::Error;
pub use error::Result;
pub use protocol::ReplyKind;
pub use protocol::drive_floor;
pub use protocol::frame_time;
pub use slew::SlewLimiter;
pub use transport::MockTransport;
pub use transport::SerialTransport;
pub use transport::Transport;
pub use types::Faults;
pub use types::Feedback;
pub use types::Mode;
pub use types::PositionAccumulator;
pub use types::Telemetry;

Modules§

bus
The Bus (a shared RS485 port) and M0601 (one motor on it) types.
error
Error and result types for the driver.
protocol
Pure protocol layer: frame construction and parsing. No I/O.
slew
Host-side setpoint slew limiting: SlewLimiter.
transport
Byte transport: the seam between the driver and the wire.
types
Data types: control Mode, fault flags, and parsed Feedback telemetry.