The RoboClaw Python library is now the Basicmicro Python library, installed with pip install basicmicro. Create a Basicmicro object with the serial port and baud rate, call Open() to start communication, then call motor commands like DutyM1() on that object. The library requires Python 3.6 or newer.
The Basicmicro Python library controls RoboClaw and MCP motor controllers over a serial connection using packet serial. It runs on any platform with Python 3.6 or newer and a serial port, which makes it a natural fit for the Raspberry Pi as well as Windows, Linux, and macOS desktops. This article covers installing the library, the general pattern for using it in a script, a complete working example, and a description of the most commonly used functions it contains. The library’s source code and additional examples are in the basicmicro_python repository on GitHub.
How Do You Install the Basicmicro Python Library?
The library is published on PyPI under the name basicmicro, so pip installs it and its only dependency, pyserial, in a single command. There is nothing to download and no files to copy into your project.
- Open a terminal window. On Windows this is Command Prompt or PowerShell; on Linux and macOS it is any shell.
- Install the library with pip:
pip install basicmicro - On current versions of Raspberry Pi OS and some other Linux distributions, pip refuses to install into the system Python and reports an “externally-managed-environment” error. If that happens, create and activate a virtual environment first, then install the library inside it:
python3 -m venv ~/basicmicro-env source ~/basicmicro-env/bin/activate pip install basicmicroActivate the environment again with the
sourcecommand in any new terminal session before running your scripts. - On Linux, add your user to the dialout group so Python can open the serial port, then log out and back in for the change to take effect:
sudo usermod -a -G dialout $USER
How Does the Basicmicro Python Library Work?
There is a general pattern for using the library. First a Basicmicro object is created, passing it the name of the serial port and the baud rate to use. Communication with the attached controller begins when the Open() function is called on that object. From there, the functions that control the RoboClaw are called on the Basicmicro object, and close() releases the serial port when the script is finished.
The functions in this article communicate with the RoboClaw using packet serial. Before running your Python code, connect the RoboClaw to Motion Studio and set it to Packet Serial mode, and note the address and baud rate configured there. The values used in your script must match the controller’s settings. Motion Studio is a free download from the BasicMicro downloads page, and Installing BasicMicro Motion Studio covers the install.
Which Serial Port Should You Use?
The first parameter passed to the Basicmicro object is the name of the serial port the RoboClaw is connected to, and that name is different on every platform. A USB connection to the RoboClaw is the simplest option and works on all of them.
| Platform | Typical port name | How to find it |
|---|---|---|
| Windows | COM3 |
Device Manager, under Ports (COM & LPT) |
| Linux, USB connection | /dev/ttyACM0 |
Run ls /dev/ttyACM* |
| macOS, USB connection | /dev/tty.usbserial-XXXXXXXX |
Run ls /dev/tty.usb* |
| Raspberry Pi, GPIO serial port | /dev/serial0 |
Enable the port first, see Next Steps |
How Do You Use the Library in a Script?
- Import the controller class from the library. The class is named
Basicmicroand it is the only import most scripts need.# Import the controller class from the Basicmicro library from basicmicro import Basicmicro - Create the controller object, passing it the serial port name and the baud rate. The baud rate must match the value set in Motion Studio.
from basicmicro import Basicmicro # Create the controller object, passing the serial port and baud rate controller = Basicmicro("/dev/ttyACM0", 38400) - Open the serial port by calling
Open()on the object. It takes no parameters and returns True when the port was opened successfully, so it is worth checking rather than assuming.from basicmicro import Basicmicro controller = Basicmicro("/dev/ttyACM0", 38400) # Open() returns True when the serial port was opened successfully if not controller.Open(): raise SystemExit("Could not open the serial port") - Call motor control functions on the object. Every one of them takes the controller’s address as its first parameter, which is how several controllers can share one serial bus.
from basicmicro import Basicmicro controller = Basicmicro("/dev/ttyACM0", 38400) controller.Open() # Run motor 1 forward at about 50% duty cycle controller.DutyM1(0x80, 16384) - Close the connection when the script is finished, which releases the serial port for other programs.
from basicmicro import Basicmicro controller = Basicmicro("/dev/ttyACM0", 38400) controller.Open() controller.DutyM1(0x80, 0) # Release the serial port when the script is finished controller.close()
The library also works as a context manager, which opens the port on entry and closes it automatically when the block ends, even if the script raises an error part way through. The complete example below uses this form.
from basicmicro import Basicmicro
# The port is opened on entry and closed automatically on exit
with Basicmicro("/dev/ttyACM0", 38400) as controller:
controller.DutyM1(0x80, 16384)
What Do the Library Functions Return?
Command functions, the ones that make the controller do something, return a single boolean that is True when the RoboClaw acknowledged the command. Read functions behave differently: each one returns a tuple whose first element reports whether the read succeeded, followed by the value or values requested. Check that first element before trusting the rest, because a failed read still returns placeholder values that look like real readings.
from basicmicro import Basicmicro
controller = Basicmicro("/dev/ttyACM0", 38400)
controller.Open()
# Read the main battery voltage, returned in tenths of a volt
result = controller.ReadMainBatteryVoltage(0x80)
if result[0]:
print(f"Main battery: {result[1] / 10.0}V")
Complete Example
The script below shows the whole pattern end to end: it opens the connection, confirms the controller is responding by reading its firmware version, reads the main battery voltage, then runs motor 1 forward and backward before stopping it. Save it to a file, edit the port, baud rate, and address values at the top to match your setup, and run it with python3 roboclaw_python_example.py. More example scripts are in the library’s examples folder on GitHub.
#!/usr/bin/env python3
# Demonstrates the general pattern for using the Basicmicro Python library
# with a RoboClaw motor controller.
import time
from basicmicro import Basicmicro
# Serial port the RoboClaw is connected to
# USB: /dev/ttyACM0 on Linux, COM3 on Windows
# GPIO serial port: /dev/serial0 on the Raspberry Pi
PORT = "/dev/ttyACM0"
# Baud rate and address as set in Motion Studio
BAUD = 38400
ADDRESS = 0x80
def main():
with Basicmicro(PORT, BAUD) as controller:
# Confirm the controller is responding before commanding the motors
result = controller.ReadVersion(ADDRESS)
if result[0]:
print(f"Connected to firmware version: {result[1]}")
# Read the main battery voltage, returned in tenths of a volt
result = controller.ReadMainBatteryVoltage(ADDRESS)
if result[0]:
print(f"Main battery: {result[1] / 10.0}V")
# Run motor 1 forward at about 50% duty cycle
controller.DutyM1(ADDRESS, 16384)
time.sleep(2)
# Run motor 1 backward at about 25% duty cycle
controller.DutyM1(ADDRESS, -8192)
time.sleep(2)
# Stop motor 1
controller.DutyM1(ADDRESS, 0)
if __name__ == "__main__":
main()
Basicmicro Python Library Functions
Each function in the library wraps one packet serial command that is sent to the controller. Every function takes the controller’s address as its first parameter. The most commonly used functions are grouped below; the library’s examples folder on GitHub has a working script for each group.
Duty Cycle Functions
The functions below drive the motors directly with a PWM duty cycle, with no encoders or PID tuning required. This is the simplest way to control a motor and the right starting point for most projects. Duty is a signed value from -32767 (full reverse) to +32767 (full forward), where 0 stops the motor. The DutyAccel versions add an accel parameter that ramps the duty cycle change rather than applying it instantly.
DutyM1(address, duty)
DutyM2(address, duty)
DutyM1M2(address, duty1, duty2)
DutyAccelM1(address, accel, duty)
DutyAccelM2(address, accel, duty)
DutyAccelM1M2(address, accel1, duty1, accel2, duty2)
In this example motor 1 is run forward at about 50% duty cycle:
# Run motor 1 forward at about 50% duty cycle
controller.DutyM1(0x80, 16384)
Speed Functions
The functions below run the motors at a commanded speed using encoder feedback and the controller’s velocity PID. Speed is in encoder counts per second and accel is in counts per second squared. Velocity PID parameters must be set and tuned, in Motion Studio or with SetM1VelocityPID and SetM2VelocityPID, before these commands will work correctly.
SpeedM1(address, speed)
SpeedM2(address, speed)
SpeedM1M2(address, speed1, speed2)
SpeedAccelM1(address, accel, speed)
SpeedAccelM2(address, accel, speed)
SpeedAccelM1M2(address, accel, speed1, speed2)
In this example motor 1 is run at 1000 encoder counts per second:
# Run motor 1 at 1000 encoder counts per second
controller.SpeedM1(0x80, 1000)
Distance Functions
The functions below run a motor at a commanded speed for a specific distance. Speed is the speed of the motor in quadrature pulses per second, distance is the distance in quadrature counts, and accel sets the acceleration. Buffer controls whether the command runs immediately or waits its turn: a value of 0 buffers the command so it executes in the order sent, and a value of 1 stops the running command, clears anything else in the buffer, and executes the new command immediately. Like the speed functions, these commands use encoder feedback, so velocity PID parameters must be set and tuned before they will work correctly.
A distance command is not a point-to-point move. When the commanded distance is reached the motor is still running at the commanded speed, and if no new command follows, the controller automatically stops the motors after 1 second. For a controlled stop, follow the move with a second buffered distance command with a speed of 0, or send a regular speed command when the move completes. The stop command’s accel value sets how quickly the motor decelerates to 0, and that deceleration rate dictates how far beyond the commanded distance the motor travels while stopping.
SpeedDistanceM1(address, speed, distance, buffer)
SpeedDistanceM2(address, speed, distance, buffer)
SpeedDistanceM1M2(address, speed1, distance1, speed2, distance2, buffer)
SpeedAccelDistanceM1(address, accel, speed, distance, buffer)
SpeedAccelDistanceM2(address, accel, speed, distance, buffer)
SpeedAccelDistanceM1M2(address, accel, speed1, distance1, speed2, distance2, buffer)
In this example motor 1 moves 10,000 counts at 1,000 counts per second, and a second buffered command decelerates it to a controlled stop at the end of the move:
# Move motor 1 10000 counts at 1000 counts per second, then
# decelerate to a controlled stop with a second buffered command
controller.SpeedAccelDistanceM1(0x80, 500, 1000, 10000, 0)
controller.SpeedAccelDistanceM1(0x80, 500, 0, 0, 0)
Because a buffered move takes time to finish, a script often needs to wait for it. ReadBuffers reports how many commands are still waiting on each channel: a return value of 0 means the last command is still executing, and a value of 0x80 means the buffer is empty and the last command has completed.
ReadBuffers(address)
In this example the script polls until the buffered move has finished:
# Wait for a buffered command to finish
# 0x80 means the buffer is empty and the last command has completed
while True:
result = controller.ReadBuffers(0x80)
if result[0] and result[1] == 0x80:
break
time.sleep(0.1)
Position Functions
The functions below move a motor to a target position rather than a distance. Position is the target in encoder counts, speed sets the speed in counts per second, and accel and deccel set the acceleration and deceleration. Buffer works exactly as it does for the distance functions. The PercentPosition versions take a position as a percentage of the configured range instead of a raw count. Position PID parameters must be set and tuned, in Motion Studio or with SetM1PositionPID and SetM2PositionPID, before these commands will work correctly.
PositionM1(address, position, buffer)
PositionM2(address, position, buffer)
PositionM1M2(address, position1, position2, buffer)
SpeedPositionM1(address, speed, position, buffer)
SpeedPositionM2(address, speed, position, buffer)
SpeedAccelDeccelPositionM1(address, accel, speed, deccel, position, buffer)
SpeedAccelDeccelPositionM2(address, accel, speed, deccel, position, buffer)
SpeedAccelDeccelPositionM1M2(address, accel1, speed1, deccel1, position1, accel2, speed2, deccel2, position2, buffer)
PercentPositionM1(address, position, buffer)
PercentPositionM2(address, position, buffer)
In this example motor 1 moves to encoder position 5,000, executing immediately:
# Move motor 1 to encoder position 5000, executing immediately
controller.SpeedAccelDeccelPositionM1(0x80, 10000, 2000, 10000, 5000, 1)
Encoder Functions
The functions below read and set the encoder values. The read functions return a tuple of (success, value, status), where status holds the encoder’s direction and underflow and overflow flags. GetEncoders reads both channels in a single command. Using Encoders with the Python Library covers these functions in detail.
ReadEncM1(address)
ReadEncM2(address)
SetEncM1(address, cnt)
SetEncM2(address, cnt)
ReadSpeedM1(address)
ReadSpeedM2(address)
ResetEncoders(address)
GetEncoders(address)
In this example the current encoder count on channel 1 is read and printed:
# Read the current value of the encoder on channel 1
result = controller.ReadEncM1(0x80)
if result[0]:
print(f"Encoder 1 count: {result[1]}")
Status and Diagnostic Functions
The functions below read the controller’s own condition rather than the motors. Battery voltages are returned in tenths of a volt, motor currents in hundredths of an amp, and temperatures in tenths of a degree Celsius. When a script needs several of these values at once, GetStatus returns them all in a single command: both encoder counts, both speeds, temperatures, battery voltages, PWM values, motor currents, and the speed and position error values. Using one GetStatus call instead of a series of individual reads keeps the serial traffic down, which matters in a control loop.
ReadVersion(address)
ReadMainBatteryVoltage(address)
ReadLogicBatteryVoltage(address)
ReadCurrents(address)
ReadTemp(address)
ReadError(address)
GetStatus(address)
In this example both motor currents are read and converted to amps:
# Read both motor currents, returned in hundredths of an amp
result = controller.ReadCurrents(0x80)
if result[0]:
print(f"Motor 1: {result[1] / 100.0}A, Motor 2: {result[2] / 100.0}A")
Mixed Mode Functions
The functions below are compatibility commands for using the RoboClaw when mixing is enabled. This is the type of drive setup that a tank-style robot uses, also referred to as differential drive. Unlike DutyM1M2(), which sets each motor’s duty cycle directly, the mixed mode functions assume a differential steering setup and handle the mixing between the two motors internally. The value from 0 to 127 sets motor power, not a measured speed. For ForwardMixed, BackwardMixed, TurnRightMixed, and TurnLeftMixed, 0 is 0% power and 127 is 100% power. For ForwardBackwardMixed the range is centered: 0 is full reverse, 64 is stop, and 127 is full forward. LeftRightMixed works the same way, with 0 full left, 64 straight, and 127 full right. For most projects the duty cycle functions described earlier are the simpler choice; use the mixed mode functions when you want the controller to handle the differential drive mixing for you.
ForwardMixed(address, power)
BackwardMixed(address, power)
TurnRightMixed(address, power)
TurnLeftMixed(address, power)
ForwardBackwardMixed(address, power)
LeftRightMixed(address, power)
In this example both motors are driven forward at about half power:
# Drive both motors forward at about half power
controller.ForwardMixed(0x80, 64)
Next Steps
With the library installed and the basic pattern in hand, Using Encoders with the Python Library puts the encoder and position functions to work on a real motor. If you are connecting through the Raspberry Pi’s GPIO serial port rather than USB, the port has to be enabled and configured first: see Configuring the Raspberry Pi 5 Serial Port, Configuring the Raspberry Pi 4 Serial Port, or Configuring the Raspberry Pi Zero Serial Port for your model. For the Arduino equivalent of this guide, see Using the RoboClaw Arduino Library.



