M3 Servomotor
M3 Servomotor hardware V11
This product is a Servomotor with 4-in-1 integrated motor + motor driver + motion controller + encoder in a NEMA 17 form factor
Other hardware versions: V5V71.21.3 — the version is printed on the board next to the QR code.
Start here
Let your coding agent write it
Almost everyone writes code with an AI agent now, and we think that is the future. So we made it the easy path: copy the prompt below into Claude Code, Codex, Cursor, Gemini CLI or whatever you use, then describe what you want to build. Your agent will know exactly where every command, unit, wiring detail and error code for this motor lives, so it looks them up instead of guessing. Happy vibe coding.
I am writing code to control a Gearotons M3 Servomotor (product code M3, hardware version V11).
This product is a Servomotor with 4-in-1 integrated motor + motor driver + motion controller + encoder in a NEMA 17 form factor.
The latest firmware for it is 0.11.0.0.
All of the official documentation is machine-readable. Read what you need from these before writing any code:
- https://9o.at/products/m3/v11.md
This product: what it is, the datasheet, the schematic, every firmware release and how to flash one.
- https://9o.at/docs/python.md
The complete Python API: every command with its arguments and units, wiring, working examples, and every error code with its causes and fixes.
- https://9o.at/docs/arduino.md
The same reference for the Arduino library.
- https://9o.at/llms.txt
An index of everything else, including the other Gearotons products.
Use only commands, units and error codes that appear in those documents. If something I ask for is not covered there, tell me instead of guessing.
Here is what I want to build:Nothing to install and no account: the documentation is plain Markdown at a stable address. Point an agent straight at it if you prefer — this product, Python, Arduino, everything. Want the agent to drive a real motor while it works? Add the MCP server →
Schematic
Open the PDF in a new tab ↗ · Download · Click the drawing, then scroll to zoom and drag to pan. Full screen for reading part numbers.
Loading the schematic viewer…
Firmware
Latest for hardware V11
0.11.0.0
2025-06-12
The browser tool updates a motor over a USB to RS-485 adapter with nothing to install (Chrome, Edge or another Chromium browser; Safari and Firefox cannot talk to serial ports). Or do it from a terminal with the official Python tool, below — it flashes this same file. Either way, check the SHA-256 if you are scripting the download yourself.
Update from the command line
The official Python tool does the same job as the browser button, on any operating system.
Once per computer
Install the tools.
pip3 install --upgrade servomotorChoose your serial port. Run this one on its own. It lists the serial ports on your computer; type the number of your USB-to-RS-485 adapter and press Enter. The choice is remembered, so none of the commands below needs a port, and nothing is sent to the motor. It waits for your answer, which is why it cannot go in the block below. Moved the adapter to a different USB socket? Run it again.
servomotor_command -P
Then, for each upgrade
Paste the whole block into a terminal.
# Download the firmware file into the current folder.
curl -LO https://cdn.jsdelivr.net/gh/tomrodinger/servomotor@main/firmware/firmware_releases/servomotor_M3_fw0.11.0.0_scc3_hwV11.firmware
# Update every motor on the bus that this file is for. Motors of another
# model or compatibility code ignore it and keep their firmware.
upgrade_firmware servomotor_M3_fw0.11.0.0_scc3_hwV11.firmware
# Check the result: the Firmware Version column should show the new version
# for every motor. The upgrade itself cannot tell you whether it worked.
show_device_information_for_all_devices- This updates every motor on the bus that the file is for. Motors of another model or firmware compatibility code ignore it and keep their firmware, so a mixed bus is safe. This file, servomotor_M3_fw0.11.0.0_scc3_hwV11.firmware, is for model M3 with compatibility code 3.
- To update just one motor, the easiest way is the browser maintenance tool: press Firmware on that motor’s row. It flashes only that motor, leaves the rest of the bus alone and reads the version back. From the command line, add the motor’s alias instead: upgrade_firmware -a <ALIAS> servomotor_M3_fw0.11.0.0_scc3_hwV11.firmware. The aliases are listed by show_device_information_for_all_devices.
- Always run the check at the end. No motor answers during a broadcast transfer, so upgrade_firmware reports every page as written whether or not it was. The Firmware Version column in the check is the real result. If the check stops with “Communication error” instead of printing its table, a motor is probably still in its bootloader (its green LED blinks fast): run the upgrade again.
- The command-line tool reboots the whole bus into its bootloader for the duration of the transfer, even with -a, because its first action is a broadcast reset. Never upgrade a bus that is moving.
- An interrupted upgrade cannot brick a motor. The bootloader is never overwritten and checks the application by CRC on every boot, so a half-written motor stays in the bootloader and still answers. Run the same command again. Your alias and calibration survive an upgrade.
Scripting a production line? Name the port instead of relying on the saved choice: add -p /dev/tty.usbserial-110 (or -p COM5 on Windows) to each command, or set the SERVOMOTOR_PORT environment variable. Either one overrides the remembered port. Working from a clone of the repository rather than the pip package, the same program is python3 python_programs/upgrade_firmware.py, with identical arguments. Full procedure, including recovery →
| Version | Built for | Date | Size | SHA-256 | |
|---|---|---|---|---|---|
| 0.11.0.0latest | V11 | 2025-06-12 | 34 KB | Download | |
| 0.10.0.0 | V11 | 2025-06-12 | 34 KB | Download | |
| 0.9.1.0 | V11RC4 | 2025-04-10 | 32 KB | Download | |
| 0.9.1.0legacy | January 3, 2025 | Download |
Filenames encode the model, firmware version, compatibility code and hardware version: servomotor_M3_fw<version>_scc<code>_hwV11.firmware. Every release listed here has firmware compatibility code 3, which is the one this hardware needs.
Get coding
Python
pip install servomotor#!/usr/bin/env python3
"""
Minimal trapezoid move: rotate 1 turn in 1 second.
Edit ALIAS below if needed. Uses rotations and seconds.
"""
import time, servomotor
from servomotor import communication
# Hard-coded settings for a minimal demo
ALIAS = 'X' # Device alias, change if needed
SERIAL_PORT = "/dev/tty.usbserial-110" # Serial device path; change if needed (e.g., "COM3" on
# Windows)
DISPLACEMENT_ROTATIONS = 1.0 # 1 rotation
DURATION_SECONDS = 1.0 # 1 second
DELAY_MARGIN = 0.10 # +10% wait margin because the motor's clock is not
# perfectly accurate
communication.serial_port = SERIAL_PORT # if you comment this out then the program
# should prompt you for the serial port or it will use
# the last used port from a file
servomotor.open_serial_port()
m = servomotor.M3(ALIAS, time_unit="seconds", position_unit="shaft_rotations", verbose=0)
m.enable_mosfets()
m.trapezoid_move(DISPLACEMENT_ROTATIONS, DURATION_SECONDS)
time.sleep(DURATION_SECONDS * (1.0 + DELAY_MARGIN))
m.disable_mosfets()
servomotor.close_serial_port()Arduino
Library Manager → search Servomotor → Install.
// Minimal Arduino example: Trapezoid move using built-in unit conversions
// Goal: spin the motor exactly 1 rotation in 1 second, then stop.
// Sequence:
// system reset -> enable MOSFETs -> trapezoidMove(1.0 rotations, 1.0 seconds)
// -> wait 1.1s -> disable MOSFETs.
//
// Notes:
// - This uses the library's unit conversion (no raw counts/timesteps).
// - Configure Serial1 pins for your board (ESP32 example pins below).
// - Motor is created AFTER Serial1.begin(...) so hardware UART pins are set first.
// - Every command is checked with getError(). No command method returns a success
// flag, and a motor that has latched a fatal error silently ignores everything
// afterwards, so an unchecked sketch just stops moving with no symptom at all.
// See "Checking for errors" in the API documentation.
#include <Servomotor.h>
#define ALIAS 'X' // Device alias
#define BAUD 230400 // RS485 UART baud rate
#define DISPLACEMENT_ROTATIONS 1.0f // 1 rotation
#define DURATION_SECONDS 1.0f // 1 second
#define TOLERANCE_PERCENT 10 // +10% wait margin because the motor's clock is not
// perfectly accurate
#define WAIT_MS ((unsigned long)(DURATION_SECONDS * 1000.0f * (100 + TOLERANCE_PERCENT) / 100))
#define POST_RESET_WAIT_MS 1500 // Keep the bus SILENT this long after a reset: the motor
// boots through a bootloader window, and any packet that
// arrives during it pins the motor in the bootloader.
// Example RS485 pins for ESP32 DevKit (change as needed for your board)
#if defined(ESP32)
#define RS485_TXD 4 // TX pin to RS485 transceiver
#define RS485_RXD 5 // RX pin from RS485 transceiver
#endif
// Returns true (and explains itself) if the previous command did not succeed.
// getError() is 0 on success, positive for a fatal error reported by the motor
// (look the number up in the Error Codes section of the documentation), and
// negative for a communication failure such as -1 = no reply within 1 second.
bool failed(Servomotor &motor, const char *what) {
int e = motor.getError();
if (e == 0) return false;
Serial.print("[FAIL] ");
Serial.print(what);
Serial.print(" -> getError() = ");
Serial.print(e);
Serial.println(e > 0 ? " (motor fatal error - see the Error Codes section)"
: " (communication failure - check wiring, alias and power)");
return true;
}
void setup() {
Serial.begin(115200); // Console serial for debugging
// On ESP32-S3, set Tools > USB CDC On Boot > Enabled or this
// output never reaches the USB serial monitor.
// Create the motor; serial port opens on first instantiation.
#if defined(ESP32)
Servomotor motor(ALIAS, Serial1, RS485_RXD, RS485_TXD);
#else
Servomotor motor(ALIAS, Serial1);
#endif
// Use units: rotations for position, seconds for time.
// These are host-side only - they send nothing to the motor.
motor.setPositionUnit(PositionUnit::SHAFT_ROTATIONS);
motor.setTimeUnit(TimeUnit::SECONDS);
// Start from a known-clean state: this also clears any fatal error left over
// from a previous run, which is the usual reason a motor "stops working".
motor.systemReset();
if (failed(motor, "systemReset")) return;
delay(POST_RESET_WAIT_MS);
motor.enableMosfets();
if (failed(motor, "enableMosfets")) return;
motor.trapezoidMove(DISPLACEMENT_ROTATIONS, DURATION_SECONDS);
if (failed(motor, "trapezoidMove")) return;
delay(WAIT_MS);
// A successful move command only means the move was accepted and queued. Faults
// such as a stall or a position-deviation trip happen later, while it executes,
// so check the motor's own status once the motion should be finished.
getStatusResponse status = motor.getStatus();
if (failed(motor, "getStatus")) return;
if (status.fatalErrorCode != 0) {
Serial.print("[FAIL] motor faulted during the move, error code ");
Serial.println(status.fatalErrorCode);
return;
}
motor.disableMosfets();
if (failed(motor, "disableMosfets")) return;
Serial.println("Move completed successfully.");
}
void loop() {
}Getting started · Full Arduino reference · Library on GitHub ↗
Or just ask Claude to move it
The MCP server exposes every command to Claude Desktop, Claude Code or any MCP client, finds your serial ports and the motors on the bus, and ships with a simulator.
uvx --from servomotor-mcp servomotor-mcpSomething missing or wrong on this page? We read every message. Send feedback ↗