M0601 Hub Moto Interfacing RS485

In this comprehensive tutorial, you will learn how to interface the DFRobot M0601 Motor (Direct-Drive Hub Motor for AGV) using the RS485 communication protocol. We will guide you step-by-step through testing the motor with raw Hex commands, automating movements with Python scripts, and finally controlling it via MotorLink โ€”an intuitive, browser-based digital twin application.

ย 

MotorLink Web App

Demo

MotorLink is a free, open browser-based control application designed to simplify interfacing with the M0601 Motor. It handles all RS485 protocol complexity automatically using the Web Serial API, requiring no software installation or backend.

ย 

Key Features:

  • Digital Twin Visualization: Real-time on-screen sync of the motor's movement.
  • Full Motor Control: Intuitive sliders and presets for Velocity, Current, and Position loop modes.
  • Live Telemetry & Graphs: Monitor speed, current, position, and temperature in real time.
  • Advanced Tools: Integrated ID manager, raw hex editor, and communication packet log.
    Demo

ย 

HARDWARE LIST
1 M0601 Direct-Drive Hub Motor
1 RainbowLink 4 Channel USB to Serial Converter
S1
S2

1. Motor Overview

MO1

The M0601 is a high-performance direct-drive hub motor from DFRobot's FIT1042 series. Based on an integrated FOC (Field-Oriented Control) servo architecture, it combines an outer-rotor brushless motor, encoder, and servo driver into a single compact unit โ€” eliminating the need for external reducers and delivering smooth, low-noise torque directly to the wheel.

Unlike conventional geared motors, the M0601 uses direct-drive technology, meaning the wheel is the rotor. This gives it:

  • Zero backlash and near-zero mechanical losses
  • Ultra-low noise (suitable for indoor environments)
  • Smooth torque at low RPM without cogging
  • Precise closed-loop control at up to 500Hz command rate

Communication is entirely over RS485 using a compact 10-byte frame protocol, making it simple to integrate with any microcontroller, single-board computer, or PC via a USB-to-RS485 adapter.

ย 

2. Versions โ€” Left & Right Orientation

The M0601 is available in two mirror-image variants to suit different chassis and robot designs:

VersionSKUMountingTire Pattern DirectionTypical Use
Left OrientationFIT1042-LLeft side of chassisCounter-clockwise treadLeft wheel of differential drive
Right OrientationFIT1042-RRight side of chassisClockwise treadRight wheel of differential drive

Note: The rubber tires have a directional tread pattern. If you need to change the tire angle, remove the 3ร— M2.5ร—8 hex screws on the front black cover, detach the cover, remove the tire, rotate it to the correct orientation, and reinstall.

The two versions are mechanically and electrically identical โ€” only the tire mounting direction differs. Either motor can be commanded to spin in either direction via the sign of the velocity/current value.

ย 

3. Specifications

ParameterValue
Rated Voltage18V DC
Power Supply Range12V minimum / 18V rated
No-load Speed200 ยฑ 10 RPM
No-load Currentโ‰ค 0.2A
Velocity Loop Rangeโˆ’330 to +330 RPM
Current Loop Rangeโˆ’32767 to +32767 (= โˆ’8A to +8A)
Position Loop Range0 to 32767 (= 0ยฐ to 360ยฐ)
CommunicationRS485, 115200 baud, 8N1
Command RateUp to 500Hz
Frame Length10 bytes
CRC AlgorithmCRC-8/MAXIM (poly xโธ + xโต + xโด + 1)
Over-temperature Protection80ยฐC (releases at 75ยฐC)
Bus Overcurrent Protection3A (releases after 5s)
Phase Current Protection4.6A (releases after 5s)
Stall ProtectionTriggers after 5s, releases after 5s
Mounting ThreadM2.5, 6mm deep
Positioning15.2mm diameter outer circle, 8mm flat

ย 

4. Applications

The M0601's combination of direct drive, RS485 multi-drop, and three control modes makes it ideal for:

  • Mobile Robots โ€” AGV drive wheels, differential-drive and holonomic platforms
  • Balance Robots โ€” Precise velocity control for self-balancing systems
  • Robot Joints โ€” Torque-controlled arms and legs using current loop mode
  • Service Robots โ€” Indoor delivery, hospitality, security patrol robots
  • Fitness Equipment โ€” Smart resistance wheels with torque feedback
  • Camera Sliders / Gimbals โ€” Position loop for repeatable angular control
  • Industrial Automation โ€” Conveyor drive wheels on a shared RS485 bus


5. Pinout & Wiring

Connection

The motor has two cable connectors:

ย 
Signal Cable (4-pin JST connector)
Wire ColorSignalDescription
โฌ› BlackGNDSignal ground reference
๐ŸŸ  OrangeB (โˆ’)RS485 differential line B
โฌœ WhiteA (+)RS485 differential line A
๐ŸŸค BrownRESVShield / reserved โ€” connect to GND
ย 
Power Cable (2-pin connector)
Wire ColorSignalDescription
โฌ› BlackGNDPower ground
๐Ÿ”ด Red18VDC power supply positive (18V rated)

ย 

6. Interfacing via RS485

ย 

RS485 is a differential 2-wire bus. Multiple motors can share the same A/B pair, each with a unique address (ID 0x01โ€“0xFE).

ย 
Connection Diagram
ย 

PC / Microcontroller
ย  ย  ย  ย โ”‚
ย  USB-to-RS485 Adapter
ย  ย  ย  ย โ”‚
ย  โ”Œโ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”
ย  A(+) ย  B(-) ย  GND
ย  ย โ”‚ ย  ย  ย  โ”‚ ย  ย  ย โ”‚
ย  White ย Orange ย Black โ”€โ”€โ–บ Motor Signal Connector (4-pin)
ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  โ”€โ”€โ–บ Brown wire also to GND

ย  18V DC power supply โ”€โ”€โ–บ Red/Black Power Connector (2-pin)

ย 
Key Rules
  1. A/B polarity matters. If the motor doesn't respond, swap Orange and White.
  2. Brown wire must connect to GND โ€” it is the RS485 signal ground reference. Leaving it floating causes communication errors, especially on longer cables.
  3. 18V power is separate from the RS485 logic. The motor will not spin without power even if RS485 communication is working.
  4. Only one motor on the bus when setting or querying IDs โ€” address conflicts cause garbled responses.
  5. For cable runs longer than 1 metre, add a 120ฮฉ termination resistor across A and B at the far end of the bus.

ย 

7. Interfacing with RainbowLink (TEL0185)

IM0.JPG

The DFRobot RainbowLink (TEL0185) is a 4-channel USB-to-multi-protocol converter supporting RS485, RS232, and TTL on a single USB connection. It is the recommended PC interface for the M0601.

ย 
Step-by-step Setup

Step 1 โ€” Install driver
The RainbowLink uses a CH343 chipset. Install the driver from the DFRobot wiki or directly from WCH: Driver Download

Step 2 โ€” Connect wires

Motor Signal WireConnect To
Orange (Bโˆ’)B terminal on RS485 channel
White (A+)A terminal on RS485 channel
Black (GND)GND terminal
Brown (RESV)GND terminal (recommended)

If the motor doesn't respond, swap Orange โ†” White โ€” the A/B labelling on the motor is inverted relative to some converters.

ย 

Step 3 โ€” Identify the COM port
After plugging in the RainbowLink via USB:

  • Windows: Open Device Manager โ†’ Ports (COM & LPT) โ†’ note the COM number (e.g. COM13)
  • Linux/Mac: Run ls /dev/tty* and look for /dev/ttyUSB0 or similar

ย 

Step 4 โ€” Serial settings

SettingValue
Baud Rate115200
Data Bits8
ParityNone
Stop Bits1
Flow ControlNone / OFF

Step 5 โ€” Quick test with Hercules
Open Hercules SETUP utility โ†’ Serial tab โ†’ configure settings above โ†’ Open.
In the Send box, tick HEX, type the ID query frame, click Send:

ย 

C8 64 00 00 00 00 00 00 00 DE

ย 

A response in the Received window confirms the motor is wired and powered correctly.

ย 

8. Communication Protocol

All motor communication uses 10-byte frames at 115200 baud, 8N1, half-duplex RS485.

ย 
Send Frame Structure (Drive Command)
ByteFieldDescription
DATA[0]Motor IDTarget motor address (0x01โ€“0xFE)
DATA[1]Command type0x64 = drive, 0x74 = feedback query, 0xA0 = mode switch
DATA[2]Value HIGHHigh byte of INT16 payload
DATA[3]Value LOWLow byte of INT16 payload
DATA[4]ReservedAlways 0x00
DATA[5]ReservedAlways 0x00
DATA[6]AccelerationValid in velocity mode; 0 = default (1 = 0.1ms/rpm)
DATA[7]Brake0xFF = electric brake (velocity mode only); else 0x00
DATA[8]ReservedAlways 0x00
DATA[9]CRC8CRC-8/MAXIM of bytes DATA[0]โ€“DATA[8]
CRC-8/MAXIM Algorithm

Polynomial: xโธ + xโต + xโด + 1 (reflected: 0x8C)

You can also verify frames at https://crccalc.com/ โ€” select CRC-8/MAXIM.

ย 
Control Modes
ModeMode ByteValue RangeUnit
Current Loop0x01โˆ’32767 to +32767โ‰ˆ โˆ’8A to +8A
Velocity Loop0x02โˆ’330 to +330RPM
Position Loop0x030 to 327670ยฐ to 360ยฐ

Motor speed must be below 10 RPM before switching to Position Loop mode.

Polling Requirement

The motor uses a polling protocol. A single command does not sustain motion โ€” the host must repeatedly send the drive command at โ‰ฅ20ms intervals (50Hz recommended). Sending 0 RPM or the brake frame will halt the motor.

ย 

9. Verified Command Reference

All frames below are CRC-8/MAXIM verified.

ย 

Mode Switch
CommandFrame (10 bytes)Notes
Switch to Velocity Loop01 A0 00 00 00 00 00 00 00 02Send 3โ€“5 times. No feedback returned.
Switch to Current Loop01 A0 00 00 00 00 00 00 00 01Send 3โ€“5 times. No feedback returned.
Switch to Position Loop01 A0 00 00 00 00 00 00 00 03Speed must be <10 RPM first.

ย 

Utility Commands
CommandFrame (10 bytes)Notes
Feedback Query01 74 00 00 00 00 00 00 00 04Returns 10-byte status frame
Brake (Velocity Mode)01 64 00 00 00 00 00 FF 00 D1Electric brake
ID Query (Broadcast)C8 64 00 00 00 00 00 00 00 DESingle motor on bus only
Set Motor ID to 0x01AA 55 53 01 00 00 00 00 00 CBSend exactly 5 times. One motor on bus.

ย 

Velocity Loop (โˆ’330 to +330 RPM)
SpeedFrame
โˆ’100 RPM01 64 FF 9C 00 00 00 00 00 9A
โˆ’50 RPM01 64 FF CE 00 00 00 00 00 DA
0 RPM01 64 00 00 00 00 00 00 00 50
+50 RPM01 64 00 32 00 00 00 00 00 D3
+100 RPM01 64 00 64 00 00 00 00 00 4F
ย 
Current Loop (โˆ’32767 to +32767)
ValueApprox. CurrentFrame
โˆ’10000~โˆ’2.44A01 64 D8 F0 00 00 00 00 00 78
โˆ’5000~โˆ’1.22A01 64 EC 78 00 00 00 00 00 D3
โˆ’2000~โˆ’0.49A01 64 F8 30 00 00 00 00 00 08
00A01 64 00 00 00 00 00 00 00 50
+2000~+0.49A01 64 07 D0 00 00 00 00 00 27
+5000~+1.22A01 64 13 88 00 00 00 00 00 A7
+10000~+2.44A01 64 27 10 00 00 00 00 00 57

ย 

Position Loop (0โ€“32767 = 0ยฐโ€“360ยฐ)
Raw ValueAngleFrame
00ยฐ01 64 00 00 00 00 00 00 00 50
10000~109.8ยฐ01 64 27 10 00 00 00 00 00 57
20000~219.7ยฐ01 64 4E 20 00 00 00 00 00 5E
30000~329.5ยฐ01 64 75 30 00 00 00 00 00 A7

10. Feedback Frame Decoding

The motor responds to drive commands and feedback queries with a 10-byte frame:

ย 

Protocol 1 โ€” Drive Command Response
ByteFieldDecode
DATA[0]Motor IDResponding motor's address
DATA[1]Mode0x01=Current, 0x02=Velocity, 0x03=Position
DATA[2โ€“3]Torque Current`int16 = (D[2]<<8)
DATA[4โ€“5]Velocity`int16 = (D[4]<<8)
DATA[6โ€“7]Position`int16 = (D[6]<<8)
DATA[8]Error CodeBitmask (see below)
DATA[9]CRC8CRC-8/MAXIM of DATA[0]โ€“DATA[8]

ย 

Protocol 2 โ€” Feedback Query Response
ByteFieldDecode
DATA[6]Winding TemperatureยฐC (uint8, direct)
DATA[7]U8 Position0โ€“255 โ†’ 0ยฐโ€“360ยฐ (degrees = value ร— 360 / 255)

ย 

Error Code Bitmask
BitMeaning
BIT0Sensor error
BIT1Overcurrent error
BIT2Phase overcurrent error
BIT3Stall / locked rotor error
BIT4Troubleshooting flag
BIT5โ€“7Reserved

Example: 0x22 = 0b00100010 โ†’ Phase overcurrent + Overcurrent active simultaneously.

ย 

11. Protection Rules
ProtectionThresholdReset
Bus Overcurrent3AAuto after 5 seconds
Over-temperature80ยฐCAuto when winding temp drops below 75ยฐC
Phase Overcurrent4.6AAuto after 5 seconds
Stall (Locked Rotor)Blocked > 5sAuto after 5 seconds

During a protection event the motor stops responding to drive commands. The error code byte in the feedback frame indicates which protection is active.

ย 

12. Python Code Examples

Requirement: Install pyserial before running any of these scripts.

pip install pyserial

ย 

Edit the COM_PORT variable at the top of each script to match your system.

ย 

12.1 Motor Scan

This script performs a two-stage scan to discover all M0601 motors on the RS485 bus.

How it works:

  1. Stage 1 โ€” Broadcast query: Sends the special ID query frame C8 64 00 ... DE which all motors respond to simultaneously. This gives an instant result if only one motor is connected.
  2. Stage 2 โ€” Full poll: Iterates through every possible address from 0x01 to 0xFE, sending a feedback query to each and listening for a reply. This catches motors whose IDs were previously changed from the default.

The script prints a clean summary at the end listing all detected IDs, and tells you exactly what value to set MOTOR_ID to in the other scripts.

CODE
"""
m0601_scan.py โ€” Discover all M0601 motors on the RS485 bus.

Stage 1: Broadcast ID query (fast, ~0.3s)
Stage 2: Full poll of all IDs 0x01-0xFE (thorough, ~40s at 0.15s timeout)

Usage:
    pip install pyserial
    python m0601_scan.py
"""

import serial
import time
import sys

COM_PORT  = "COM13"       # โ† Change to your port (e.g. /dev/ttyUSB0 on Linux)
BAUD_RATE = 115200
TIMEOUT   = 0.15          # Seconds to wait per ID. Increase to 0.25 on noisy buses.


def crc8_maxim(data):
    """CRC-8/MAXIM: polynomial x^8 + x^5 + x^4 + 1, reflected 0x8C."""
    crc = 0
    for byte in data:
        crc ^= byte
        for _ in range(8):
            crc = (crc >> 1) ^ 0x8C if (crc & 0x01) else (crc >> 1)
    return crc


# --- Verified frames ---
FRAME_ID_QUERY = bytes([0xC8, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDE])

def frame_feedback_request(motor_id):
    """Build a feedback query frame for a specific motor ID."""
    f = [motor_id, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
    f.append(crc8_maxim(f))
    return bytes(f)


def stage1_broadcast(ser):
    """
    Send the broadcast ID query and parse the response.
    The motor replies with its ID in DATA[0] of the response frame.
    Returns detected ID (int) or None.
    """
    print("\n[Stage 1] Broadcast ID query...")
    ser.reset_input_buffer()
    ser.write(FRAME_ID_QUERY)
    time.sleep(0.3)

    resp = ser.read_all()
    if not resp:
        print("  No response.")
        return None

    print(f"  Raw response: {resp.hex(' ').upper()}")

    # If response is identical to our query, it's just the RS485 echo โ€” not a motor reply.
    if bytes(resp) == FRAME_ID_QUERY:
        print("  (Echo only โ€” motor did not respond separately)")
        return None

    # Motor reply starts with its own ID byte (0x01โ€“0xFE)
    for byte in resp:
        if 0x01 <= byte <= 0xFE:
            print(f"  โœ“ Motor found via broadcast โ€” ID: 0x{byte:02X} ({byte})")
            return byte

    return None


def stage2_full_poll(ser):
    """
    Poll each ID from 0x01 to 0xFE individually.
    Returns a list of all responding IDs.
    """
    print("\n[Stage 2] Full ID poll (0x01 โ†’ 0xFE)...")
    print(f"  Estimated time: {254 * TIMEOUT:.0f}s โ€” please wait.\n")
    found = []

    for motor_id in range(0x01, 0xFF):
        # Print progress bar
        pct   = motor_id / 254
        bar   = "โ–ˆ" * int(30 * pct) + "โ–‘" * (30 - int(30 * pct))
        print(f"\r  [{bar}] 0x{motor_id:02X} ({motor_id}/254)", end="", flush=True)

        ser.reset_input_buffer()
        ser.write(frame_feedback_request(motor_id))
        time.sleep(TIMEOUT)

        resp = ser.read_all()
        # A valid reply is 10 bytes starting with the motor's own ID
        if resp and len(resp) >= 3 and resp[0] == motor_id:
            found.append(motor_id)
            print(f"\n  โœ“ Motor at ID 0x{motor_id:02X} ({motor_id}) replied!")

    print(f"\r  [{'โ–ˆ' * 30}] Done!                                  ")
    return found


def main():
    print("=" * 52)
    print("  M0601 Motor Scanner")
    print(f"  Port: {COM_PORT}  |  Baud: {BAUD_RATE}")
    print("=" * 52)
    print("\n  โš   For Stage 1, ensure only ONE motor is on the bus.")
    print("  Press Enter to start scan, Ctrl+C to cancel...")
    input()

    try:
        ser = serial.Serial(
            port=COM_PORT, baudrate=BAUD_RATE,
            bytesize=8, parity='N', stopbits=1,
            timeout=TIMEOUT
        )
        print(f"[โœ“] Opened {COM_PORT}")
    except serial.SerialException as e:
        print(f"[โœ—] Cannot open port: {e}")
        sys.exit(1)

    try:
        broadcast_id = stage1_broadcast(ser)
        polled_ids   = stage2_full_poll(ser)

        # Combine results
        all_ids = sorted(set(polled_ids) | ({broadcast_id} if broadcast_id else set()))

        print("\n" + "=" * 52)
        print("  SCAN COMPLETE")
        print("=" * 52)
        if all_ids:
            print(f"\n  {len(all_ids)} motor(s) found:")
            for mid in all_ids:
                print(f"    โ€ข ID 0x{mid:02X}  (decimal {mid})")
            if len(all_ids) == 1:
                print(f"\n  โ†’ In other scripts, set:  MOTOR_ID = 0x{all_ids[0]:02X}")
        else:
            print("\n  โœ— No motors detected.")
            print("  Checklist:")
            print("    1. Is the 18V power adapter ON?")
            print("    2. Is the Brown wire connected to GND?")
            print("    3. Try swapping Orange โ†” White (A/B polarity)")
            print(f"    4. Try increasing TIMEOUT (currently {TIMEOUT}s)")
        print("=" * 52)

    except KeyboardInterrupt:
        print("\n[!] Scan cancelled.")
    finally:
        ser.close()
        print("[โœ“] Port closed.")


if __name__ == "__main__":
    main()
12.2 Motor Operations

This script demonstrates all three control modes interactively from the keyboard โ€” the most complete starting point for testing motor behaviour.

How it works:

  • On startup it opens the serial port and sends the ID query to confirm the motor is online.
  • It switches to Velocity Loop mode by default (the motor's factory default).
  • A polling loop runs in the background at 50Hz, continuously sending the current command frame. This is required because the M0601 uses a polling protocol โ€” it will not sustain motion from a single command.
  • Keyboard input changes the active command, mode, or stops the motor. The polling loop picks up the new command on the very next iteration without interruption.
CODE
"""
m0601_operations.py โ€” Interactive motor control in all three modes.

Controls:
  F / B  โ€” Forward / Backward at SPIN_RPM (velocity mode)
  1..5   โ€” Velocity presets: 50, 100, 150, 200, 250 RPM
  S      โ€” Stop (velocity = 0)
  K      โ€” Brake (electric brake)
  V      โ€” Switch to Velocity Loop mode
  C      โ€” Switch to Current Loop mode
  P      โ€” Switch to Position Loop mode
  I      โ€” Query feedback (print once)
  Q      โ€” Quit

Usage:
    pip install pyserial
    python m0601_operations.py
"""

import serial
import time
import sys
import os
import threading

COM_PORT   = "COM13"
BAUD_RATE  = 115200
MOTOR_ID   = 0x01
SPIN_RPM   = 100        # Default speed for F/B keys
POLL_HZ    = 50         # Polling frequency in Hz


def crc8_maxim(data):
    crc = 0
    for byte in data:
        crc ^= byte
        for _ in range(8):
            crc = (crc >> 1) ^ 0x8C if (crc & 0x01) else (crc >> 1)
    return crc


def build(motor_id, b1, data7):
    """Build a standard 10-byte frame: [ID, B1, 7 data bytes, CRC]."""
    f = [motor_id, b1] + list(data7)
    f.append(crc8_maxim(f))
    return bytes(f)


# โ”€โ”€ Verified frames โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

def frame_velocity_mode(motor_id):
    """Switch to velocity loop (mode 0x02). No CRC on last byte โ€” use exact bytes."""
    return bytes([motor_id, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02])

def frame_current_mode(motor_id):
    """Switch to current loop (mode 0x01)."""
    return bytes([motor_id, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01])

def frame_position_mode(motor_id):
    """Switch to position loop (mode 0x03). Motor must be <10 RPM first."""
    return bytes([motor_id, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03])

def frame_velocity(motor_id, rpm, accel=1):
    """Velocity command. rpm: -330 to +330."""
    rpm  = max(-330, min(330, rpm))
    v    = rpm.to_bytes(2, 'big', signed=True)
    return build(motor_id, 0x64, [v[0], v[1], accel, 0x00, 0x00, 0x00, 0x00])

def frame_current(motor_id, value):
    """Current command. value: -32767 to +32767 (= -8A to +8A)."""
    value = max(-32767, min(32767, value))
    v     = value.to_bytes(2, 'big', signed=True)
    return build(motor_id, 0x64, [v[0], v[1], 0x00, 0x00, 0x00, 0x00, 0x00])

def frame_position(motor_id, pos):
    """Position command. pos: 0 to 32767 (= 0ยฐ to 360ยฐ)."""
    pos = max(0, min(32767, pos))
    v   = pos.to_bytes(2, 'big', signed=False)
    return build(motor_id, 0x64, [v[0], v[1], 0x00, 0x00, 0x00, 0x00, 0x00])

def frame_brake(motor_id):
    """Electric brake. Valid in velocity mode only."""
    return build(motor_id, 0x64, [0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00])

def frame_feedback(motor_id):
    """Request full feedback (speed, current, position, temp, error)."""
    return build(motor_id, 0x74, [0x00] * 7)

def frame_id_query():
    return bytes([0xC8, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDE])


def decode_feedback(data):
    """Parse a 10-byte Protocol-2 feedback frame into a readable dict."""
    if len(data) < 10:
        return None
    mode_map = {0x01: "Current", 0x02: "Velocity", 0x03: "Position"}
    raw_cur  = int.from_bytes(data[2:4], 'big', signed=True)
    raw_vel  = int.from_bytes(data[4:6], 'big', signed=True)
    temp_c   = data[6]
    u8_pos   = data[7]
    err      = data[8]
    return {
        "id":       data[0],
        "mode":     mode_map.get(data[1], f"0x{data[1]:02X}"),
        "current_a": round(raw_cur * 8.0 / 32767, 3),
        "speed_rpm": raw_vel,
        "temp_c":    temp_c,
        "position_deg": round(u8_pos * 360.0 / 255, 1),
        "error":    f"0x{err:02X}" + (" (OK)" if err == 0 else " โš  FAULT"),
    }


def getch():
    """Read a single keypress without pressing Enter (cross-platform)."""
    if os.name == 'nt':
        import msvcrt
        return msvcrt.getch().decode('utf-8', errors='ignore').upper()
    else:
        import tty, termios
        fd  = sys.stdin.fileno()
        old = termios.tcgetattr(fd)
        try:
            tty.setraw(fd)
            return sys.stdin.read(1).upper()
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old)


def main():
    print("=" * 54)
    print("  M0601 Motor Operations โ€” Interactive Control")
    print(f"  Port: {COM_PORT}  ID: 0x{MOTOR_ID:02X}  Default: {SPIN_RPM} RPM")
    print("=" * 54)

    try:
        ser = serial.Serial(
            port=COM_PORT, baudrate=BAUD_RATE,
            bytesize=8, parity='N', stopbits=1, timeout=0.3
        )
        print(f"[โœ“] Opened {COM_PORT}")
    except serial.SerialException as e:
        print(f"[โœ—] {e}")
        sys.exit(1)

    # Confirm motor is online
    print("[*] Checking for motor...")
    ser.reset_input_buffer()
    ser.write(frame_id_query())
    time.sleep(0.3)
    resp = ser.read_all()
    if resp:
        print(f"[โœ“] Motor responded: {resp.hex(' ').upper()}")
    else:
        print("[!] No motor response โ€” check wiring and 18V power.")

    # Switch to velocity mode and initialise polling state
    for _ in range(5):
        ser.write(frame_velocity_mode(MOTOR_ID))
        time.sleep(0.02)
    print("[โœ“] Velocity loop mode set.\n")

    # Shared state between main thread (keyboard) and poll thread (serial writes)
    state = {
        "frame":   frame_velocity(MOTOR_ID, 0),   # Current frame to poll
        "polling": True,                            # Polling active flag
        "running": True,                            # Application running flag
        "mode":    "velocity",
    }
    lock = threading.Lock()

    def poll_loop():
        """Background thread: sends state['frame'] at POLL_HZ."""
        interval = 1.0 / POLL_HZ
        while state["running"]:
            with lock:
                if state["polling"]:
                    try:
                        ser.write(state["frame"])
                    except Exception:
                        pass
            time.sleep(interval)

    poll_thread = threading.Thread(target=poll_loop, daemon=True)
    poll_thread.start()

    print("Controls:")
    print("  F/B     Forward / Backward")
    print("  1-5     Speed presets (50/100/150/200/250 RPM)")
    print("  S       Stop   K  Brake")
    print("  V/C/P   Switch mode: Velocity / Current / Position")
    print("  I       Print live feedback")
    print("  Q       Quit\n")

    try:
        while True:
            print("Key: ", end="", flush=True)
            key = getch()
            print(key)

            with lock:
                if key == 'F':
                    state["frame"]   = frame_velocity(MOTOR_ID, SPIN_RPM)
                    state["polling"] = True
                    print(f"  โ–บ Forward {SPIN_RPM} RPM")

                elif key == 'B':
                    state["frame"]   = frame_velocity(MOTOR_ID, -SPIN_RPM)
                    state["polling"] = True
                    print(f"  โ—„ Backward {SPIN_RPM} RPM")

                elif key in ('1', '2', '3', '4', '5'):
                    rpm = int(key) * 50
                    state["frame"]   = frame_velocity(MOTOR_ID, rpm)
                    state["polling"] = True
                    print(f"  โ–บ {rpm} RPM")

                elif key == 'S':
                    state["frame"]   = frame_velocity(MOTOR_ID, 0)
                    state["polling"] = True
                    print("  โ–  Stopped (velocity = 0)")

                elif key == 'K':
                    state["frame"]   = frame_brake(MOTOR_ID)
                    state["polling"] = True
                    print("  โ–  Brake applied")

                elif key == 'V':
                    state["mode"] = "velocity"
                    state["polling"] = False
                for _ in range(5):
                    ser.write(frame_velocity_mode(MOTOR_ID))
                    time.sleep(0.02)
                print("  โœ“ Velocity Loop mode")

                elif key == 'C':
                    state["mode"] = "current"
                    state["polling"] = False
                    for _ in range(5):
                        ser.write(frame_current_mode(MOTOR_ID))
                        time.sleep(0.02)
                    state["frame"]   = frame_current(MOTOR_ID, 0)
                    state["polling"] = True
                    print("  โœ“ Current Loop mode โ€” sending 0A")

                elif key == 'P':
                    state["mode"] = "position"
                    state["polling"] = False
                    for _ in range(5):
                        ser.write(frame_position_mode(MOTOR_ID))
                        time.sleep(0.02)
                    pos = int(input("  Enter position (0โ€“32767): "))
                    state["frame"]   = frame_position(MOTOR_ID, pos)
                    state["polling"] = True
                    print(f"  โœ“ Position Loop โ€” targeting {pos} ({pos*360/32767:.1f}ยฐ)")

                elif key == 'I':
                    state["polling"] = False
                    time.sleep(0.05)
                    ser.reset_input_buffer()
                    ser.write(frame_feedback(MOTOR_ID))
                    time.sleep(0.2)
                    resp = ser.read_all()
                    if resp and len(resp) >= 10:
                        fb = decode_feedback(resp[:10])
                        if fb:
                            print(f"  Mode:     {fb['mode']}")
                            print(f"  Speed:    {fb['speed_rpm']} RPM")
                            print(f"  Current:  {fb['current_a']} A")
                            print(f"  Position: {fb['position_deg']}ยฐ")
                            print(f"  Temp:     {fb['temp_c']} ยฐC")
                            print(f"  Error:    {fb['error']}")
                    else:
                        print("  โœ— No feedback received")
                    state["polling"] = True

                elif key == 'Q':
                    state["frame"]   = frame_velocity(MOTOR_ID, 0)
                    time.sleep(0.1)
                    for _ in range(5):
                        ser.write(frame_brake(MOTOR_ID))
                        time.sleep(0.02)
                    state["running"] = False
                    break

    except KeyboardInterrupt:
        print("\n[!] Interrupted")
    finally:
        state["running"] = False
        time.sleep(0.1)
        ser.close()
        print("[โœ“] Port closed. Goodbye!")


if __name__ == "__main__":
    main()
12.3 Motor ID Change

This script safely changes a motor's ID address on the RS485 bus.

How it works:

  • It first queries the bus to confirm exactly one motor is present and reads its current ID.
  • It then sends the ID set frame (AA 55 53 [NEW_ID] 00 00 00 00 00 00) exactly 5 consecutive times โ€” the motor requires 5 consecutive identical frames to commit the new ID to non-volatile memory.
  • After setting, it immediately sends an ID query to verify the new ID is active.

โš ๏ธ Only one motor must be connected to the bus during this operation. If multiple motors are present, they will all receive the same new ID and conflict on the bus.

CODE
"""
m0601_set_id.py โ€” Change the RS485 ID of one M0601 motor.

โš   Connect only ONE motor to the bus before running.
โš   The motor saves the ID when powered off (non-volatile).

Usage:
    pip install pyserial
    python m0601_set_id.py
"""

import serial
import time
import sys

COM_PORT     = "COM13"       # โ† Change to your port
BAUD_RATE    = 115200
NEW_MOTOR_ID = 0x01          # โ† Change to the ID you want to assign (0x01โ€“0xFE)


def crc8_maxim(data):
    crc = 0
    for byte in data:
        crc ^= byte
        for _ in range(8):
            crc = (crc >> 1) ^ 0x8C if (crc & 0x01) else (crc >> 1)
    return crc


# Verified broadcast ID query frame
FRAME_ID_QUERY = bytes([0xC8, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDE])


def build_id_set_frame(new_id):
    """
    ID set frame: AA 55 53 [ID] 00 00 00 00 00 00
    Note: This frame has NO CRC โ€” the last byte is always 0x00.
    Must be sent exactly 5 times in a row.
    """
    assert 0x01 <= new_id <= 0xFE, "ID must be 0x01 to 0xFE"
    return bytes([0xAA, 0x55, 0x53, new_id, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])


def query_current_id(ser):
    """Send broadcast query and return the detected motor ID, or None."""
    ser.reset_input_buffer()
    ser.write(FRAME_ID_QUERY)
    time.sleep(0.3)
    resp = ser.read_all()
    if not resp or bytes(resp) == FRAME_ID_QUERY:
        return None
    # Motor ID is the first non-echo byte in the 0x01โ€“0xFE range
    for byte in resp:
        if 0x01 <= byte <= 0xFE:
            return byte
    return None


def main():
    print("=" * 52)
    print("  M0601 Motor ID Changer")
    print(f"  Port: {COM_PORT}  โ†’  New ID: 0x{NEW_MOTOR_ID:02X} ({NEW_MOTOR_ID})")
    print("=" * 52)
    print()
    print("  โš   ONLY ONE MOTOR MUST BE CONNECTED TO THE BUS.")
    print("  โš   The new ID is saved permanently (survives power-off).")
    print()

    try:
        ser = serial.Serial(
            port=COM_PORT, baudrate=BAUD_RATE,
            bytesize=8, parity='N', stopbits=1, timeout=0.3
        )
        print(f"[โœ“] Opened {COM_PORT}")
    except serial.SerialException as e:
        print(f"[โœ—] Cannot open port: {e}")
        sys.exit(1)

    # Step 1: Confirm one motor is present and read current ID
    print("\n[Step 1] Scanning for motor...")
    current_id = query_current_id(ser)
    if current_id is None:
        print("[โœ—] No motor detected.")
        print("    Check: 18V power ON? Wiring correct? Brown wire to GND?")
        ser.close()
        sys.exit(1)

    print(f"[โœ“] Motor detected โ€” current ID: 0x{current_id:02X} ({current_id})")

    if current_id == NEW_MOTOR_ID:
        print(f"[!] Motor already has ID 0x{NEW_MOTOR_ID:02X}. Nothing to do.")
        ser.close()
        sys.exit(0)

    # Step 2: Confirm with user
    print(f"\n[Step 2] Ready to change ID: 0x{current_id:02X} โ†’ 0x{NEW_MOTOR_ID:02X}")
    confirm = input("  Type 'yes' to confirm: ").strip().lower()
    if confirm != 'yes':
        print("[!] Cancelled.")
        ser.close()
        sys.exit(0)

    # Step 3: Send ID set frame exactly 5 times
    print("\n[Step 3] Sending ID set frame ร—5...")
    id_frame = build_id_set_frame(NEW_MOTOR_ID)
    print(f"  Frame: {id_frame.hex(' ').upper()}")

    for i in range(1, 6):
        ser.write(id_frame)
        print(f"  Sent {i}/5")
        time.sleep(0.05)   # Small gap between frames

    print("\n[Step 4] Waiting for motor to save ID (power-cycle not needed)...")
    time.sleep(0.5)

    # Step 4: Verify new ID
    print("[Step 4] Verifying new ID...")
    verified_id = query_current_id(ser)

    if verified_id == NEW_MOTOR_ID:
        print(f"\n[โœ“] SUCCESS โ€” Motor ID is now 0x{NEW_MOTOR_ID:02X} ({NEW_MOTOR_ID})")
        print(f"    Update MOTOR_ID = 0x{NEW_MOTOR_ID:02X} in your other scripts.")
    elif verified_id is not None:
        print(f"\n[โœ—] Motor responded with ID 0x{verified_id:02X} โ€” ID change may have failed.")
        print("    Try power-cycling the motor and running this script again.")
    else:
        print("\n[?] No response after ID change.")
        print("    Try power-cycling the motor โ€” the new ID may have been saved.")
        print(f"    Run m0601_scan.py after power-cycling to confirm.")

    ser.close()
    print("[โœ“] Port closed.")


if __name__ == "__main__":
    main()
12.4 Motor Parameter Monitoring

This script continuously polls the motor's feedback frame and prints a live terminal dashboard showing all real-time parameters: speed, current, position, winding temperature, and fault status.

How it works:

  • Sends the Protocol-2 feedback query frame (01 74 00 ... 04) at a configurable interval (default 200ms).
  • Parses each 10-byte response to extract: mode, torque current (INT16 โ†’ Amps), velocity (INT16 โ†’ RPM), winding temperature (ยฐC), angular position (U8 โ†’ degrees), and the error code bitmask.
  • Decodes the error bitmask into named fault descriptions.
  • Prints an updating single-line dashboard that overwrites itself in the terminal.
  • Logs all readings to a CSV file for later analysis.
CODE
"""
m0601_monitor.py โ€” Live parameter monitoring with CSV logging.

Polls the motor feedback at configurable interval and displays:
  Mode | Speed (RPM) | Current (A) | Position (ยฐ) | Temp (ยฐC) | Errors

Also writes all readings to motor_log.csv for offline analysis.

Usage:
    pip install pyserial
    python m0601_monitor.py
    Press Ctrl+C to stop.
"""

import serial
import time
import sys
import csv
import os
from datetime import datetime

COM_PORT      = "COM13"     # โ† Change to your port
BAUD_RATE     = 115200
MOTOR_ID      = 0x01
POLL_INTERVAL = 0.2         # Seconds between feedback queries (5Hz default)
LOG_FILE      = "motor_log.csv"


def crc8_maxim(data):
    crc = 0
    for byte in data:
        crc ^= byte
        for _ in range(8):
            crc = (crc >> 1) ^ 0x8C if (crc & 0x01) else (crc >> 1)
    return crc


def frame_feedback_query(motor_id):
    """Protocol 2 feedback request: returns speed, current, temp, position, error."""
    f = [motor_id, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
    f.append(crc8_maxim(f))
    return bytes(f)


def decode_error(code):
    """Decode error bitmask into a human-readable string."""
    if code == 0:
        return "OK"
    errors = []
    if code & 0x01: errors.append("SensorErr")
    if code & 0x02: errors.append("Overcurrent")
    if code & 0x04: errors.append("PhaseOvercurrent")
    if code & 0x08: errors.append("Stall")
    if code & 0x10: errors.append("Troubleshoot")
    return " | ".join(errors)


def parse_feedback(data):
    """
    Parse a 10-byte Protocol-2 feedback frame.
    Returns a dict with all motor parameters, or None on invalid data.
    """
    if len(data) < 10:
        return None

    motor_id = data[0]
    mode_raw = data[1]
    mode_map = {0x01: "Current ", 0x02: "Velocity", 0x03: "Position"}
    mode_str = mode_map.get(mode_raw, f"Unk(0x{mode_raw:02X})")

    # Torque current: signed INT16, big-endian, bytes 2-3
    raw_current = int.from_bytes(data[2:4], byteorder='big', signed=True)
    current_a   = round(raw_current * 8.0 / 32767.0, 3)

    # Velocity: signed INT16, big-endian, bytes 4-5
    speed_rpm = int.from_bytes(data[4:6], byteorder='big', signed=True)

    # Winding temperature: uint8, byte 6
    temp_c = data[6]

    # U8 position: uint8, byte 7 โ€” 0-255 maps to 0ยฐ-360ยฐ
    u8_pos       = data[7]
    position_deg = round(u8_pos * 360.0 / 255.0, 1)

    # Error code bitmask: byte 8
    error_raw = data[8]
    error_str = decode_error(error_raw)

    return {
        "timestamp":    datetime.now().strftime("%H:%M:%S.%f")[:-3],
        "motor_id":     motor_id,
        "mode":         mode_str,
        "speed_rpm":    speed_rpm,
        "current_a":    current_a,
        "temp_c":       temp_c,
        "position_deg": position_deg,
        "error_code":   f"0x{error_raw:02X}",
        "error_str":    error_str,
        "raw_hex":      data[:10].hex(' ').upper(),
    }


def print_dashboard(fb, count):
    """Print a compact, self-overwriting dashboard line."""
    fault_indicator = "๐Ÿ”ด FAULT" if fb['error_str'] != "OK" else "๐ŸŸข OK   "
    line = (
        f"\r[{fb['timestamp']}] #{count:5d} | "
        f"Mode: {fb['mode']} | "
        f"Speed: {fb['speed_rpm']:+5d} RPM | "
        f"Current: {fb['current_a']:+6.3f} A | "
        f"Pos: {fb['position_deg']:6.1f}ยฐ | "
        f"Temp: {fb['temp_c']:3d}ยฐC | "
        f"{fault_indicator}"
        + (f" ({fb['error_str']})" if fb['error_str'] != "OK" else "")
    )
    print(line, end="", flush=True)


def main():
    print("=" * 70)
    print("  M0601 Motor Parameter Monitor")
    print(f"  Port: {COM_PORT}  |  Motor ID: 0x{MOTOR_ID:02X}  |  Poll: {POLL_INTERVAL*1000:.0f}ms")
    print(f"  Logging to: {os.path.abspath(LOG_FILE)}")
    print("=" * 70)
    print("  Press Ctrl+C to stop.\n")

    try:
        ser = serial.Serial(
            port=COM_PORT, baudrate=BAUD_RATE,
            bytesize=8, parity='N', stopbits=1, timeout=0.3
        )
        print(f"[โœ“] Opened {COM_PORT}")
    except serial.SerialException as e:
        print(f"[โœ—] Cannot open port: {e}")
        sys.exit(1)

    query_frame = frame_feedback_query(MOTOR_ID)
    count        = 0
    no_resp_count = 0

    # Open CSV log file
    csv_fields = [
        "timestamp", "motor_id", "mode", "speed_rpm",
        "current_a", "temp_c", "position_deg",
        "error_code", "error_str", "raw_hex"
    ]
    log_file = open(LOG_FILE, 'w', newline='')
    writer   = csv.DictWriter(log_file, fieldnames=csv_fields)
    writer.writeheader()

    print(f"\n{'Timestamp':14s} {'#':>6s}  Mode      Speed    Current  Position  Temp  Status")
    print("-" * 80)

    try:
        while True:
            ser.reset_input_buffer()
            ser.write(query_frame)
            time.sleep(POLL_INTERVAL)

            resp = ser.read_all()
            if not resp or len(resp) < 10:
                no_resp_count += 1
                if no_resp_count >= 5:
                    print(f"\r[!] No response for {no_resp_count} consecutive polls โ€” check motor power.", end="")
                continue

            no_resp_count = 0
            count += 1
            fb = parse_feedback(resp[:10])
            if not fb:
                continue

            print_dashboard(fb, count)
            writer.writerow({k: fb[k] for k in csv_fields})
            log_file.flush()

    except KeyboardInterrupt:
        print("\n\n[โœ“] Monitoring stopped.")
    finally:
        log_file.close()
        ser.close()
        print(f"[โœ“] Log saved to {LOG_FILE}")
        print(f"[โœ“] {count} readings recorded.")
        print("[โœ“] Port closed.")


if __name__ == "__main__":
    main()

Sample terminal output:

[13:15:36.962] # ย 288 ย Mode: Velocity | Speed: ย +100 RPM | Current: ย +0.070 A | Pos: ย 71.6ยฐ | Temp: ย 33ยฐC | ๐ŸŸข OK

ย 

Sample CSV output:

timestamp,motor_id,mode,speed_rpm,current_a,temp_c,position_deg,error_code,error_str,raw_hex 13:15:36.962,1,Velocity,100,0.07,33,71.6,0x00,OK,01 02 00 0A 00 64 21 59 00 F5

ย 

13. MotorLink โ€” Web Control Application

Working with raw RS485 hex frames manually is error-prone and slow. To solve this, MotorLink was built as a free, open browser-based application that handles all the protocol complexity automatically.

๐Ÿ”— Live App: https://mukeshsankhla.github.io/MotorLink/

ย 

Demo
IM1
IM2
Compatible Motors
MotorManufacturerProtocol
M0601 (FIT1042)DFRobotSupported โœ…
DDSM115WaveshareSupported โœ…
How It Works

MotorLink runs entirely in the browser using the Web Serial API โ€” no installation, no backend, no drivers beyond the USB-RS485 adapter driver.

Browser (MotorLink) โ”€โ”€โ–บ Web Serial API โ”€โ”€โ–บ COM Port โ”€โ”€โ–บ RainbowLink TEL0185 โ”€โ”€โ–บ RS485 โ”€โ”€โ–บ M0601 Motor

ย 

Getting Started
  1. Connect the M0601 to your RainbowLink as described in Section 7
  2. Power the motor from an 18V DC supply
  3. Open https://mukeshsankhla.github.io/MotorLink/ in Chrome or Edge
  4. Click Connect, select your COM port from the browser dialog, click Open
  5. MotorLink automatically sends an ID query and reads the motor's current parameters โ€” the UI adjusts itself to match the motor's active mode

โš ๏ธ Web Serial API is supported in Chrome 89+ and Edge 89+ only. Firefox and Safari are not supported.

Features

๐Ÿ”Œ Auto-initialization

On connect, MotorLink automatically sends the broadcast ID query and reads the motor's current operating mode. The UI sets itself to match โ€” no manual configuration needed.

ย 

๐ŸŒ€ Digital Twin Motor Visualization

The centre panel displays a real-time digital twin of the physical motor โ€” a rendered hub wheel that rotates in sync with the actual motor. The rotation speed, direction (CW/CCW), and status (STATIONARY / SPINNING / BRAKING) all reflect the live feedback from the motor. When the motor spins at 100 RPM counter-clockwise, the on-screen wheel spins at the same rate and direction, giving you an immediate visual confirmation of what the physical motor is doing without looking away from the interface.

ย 

โšก Velocity Loop Control

  • RPM slider: โˆ’330 to +330
  • Quick action buttons: โˆ’100, โˆ’50, STOP, START, BRAKE, +50, +100
  • Configurable acceleration parameter
  • Polls at 50Hz while active
  • Electric brake with a single click
  • ย 

๐Ÿ”‹ Current Loop Control

  • Current slider: โˆ’32767 to +32767 (displayed as Amps)
  • Preset buttons for common torque values
  • Ideal for torque-controlled applications like robot joints and exoskeletons

ย 

๐ŸŽฏ Position Loop Control

  • Target position input with raw value and live degree display
  • Commands the motor to rotate to an exact angular position
  • Motor speed must be below 10 RPM before switching to this mode

ย 

๐Ÿชช ID Manager

  • Scan โ€” broadcasts ID query and reports detected motor with its current ID
  • Full scan โ€” polls all addresses 0x01โ€“0xFE to find all motors on the bus
  • Set ID โ€” change motor address; sends the command 5ร— automatically and shows a single-motor warning before proceeding

ย 

๐Ÿ“ก Raw Hex Editor

  • Type any 9-byte command frame in hex
  • CRC-8/MAXIM automatically computed and appended, showing the full 10-byte frame
  • Send once or in a configurable repeating loop

ย 

๐Ÿ“Š Live Telemetry Metrics

  • Speed โ€” real-time RPM value + mini line chart
  • Current โ€” real-time Amps value + mini line chart
  • Position โ€” raw value + degrees + circular dial gauge that mirrors the motor's actual angle
  • Temperature โ€” winding temperature in ยฐC with a gauge arc
  • Error Code โ€” displayed as hex with decoded fault names (Overcurrent, Stall, Phase Overcurrent, Sensor Error)

ย 

๐Ÿ“ˆ Real-time Performance Graph

Combined speed and current telemetry plotted on a shared time axis, updating at 200ms intervals. The graph retains the last 60 seconds of data, making it easy to observe acceleration ramps, load changes, and thermal step responses.

ย 

๐Ÿ“‹ Communication Packet Log
  • Every sent and received frame logged with millisecond-precision timestamps
  • Colour-coded entries: TX (blue) / RX (green) / System (grey) / Error (red)
  • Collapse status poll checkbox โ€” suppresses high-frequency polling entries so you can read meaningful events without scrolling through thousands of identical lines
  • Auto-scroll checkbox โ€” keeps the log pinned to the latest entry
  • Export button โ€” download the full log as a CSV file for offline analysis

ย 

14. Safety Notice

โš ๏ธ Read before operating the motor.

  • The motor will start rotating after certain commands are sent. Do not touch any rotating parts.
  • If the motor is not secured to a frame or chassis, always be ready to cut power immediately.
  • Start with low speeds (50โ€“100 RPM) when testing for the first time.
  • The motor generates significant torque. Ensure it is firmly mounted before commanding movement.
  • In case of unexpected behaviour, use the red STOP button in MotorLink, or disconnect the 18V power supply.
  • Never exceed 18V on the power supply.
  • Allow the motor to cool if the winding temperature exceeds 70ยฐC โ€” protection triggers at 80ยฐC.
  • The motor must be stationary (< 10 RPM) before switching to Position Loop mode.
License
All Rights
Reserved
licensBg
0