I’ve always been fascinated by robots that can balance on a ball.

It’s one of those builds that looks like magic, but it’s ultimately just a fast feedback loop. The robot constantly measures the tilt of the robot using the gyro sensor and adjusts the motors to keep the robot upright.

In this article, we’ll show you how you can build and program your own balancing robot using the LEGO MINDSTORMS Robot Inventor set.

You’ll use Pybricks to code it, which is much faster and stable than the standard programming environment, which is crucial for balancing robots.

Building the robot

When this set was initially launched, I was super excited to see that it included the Duplo ball and some small wheels. I immediately thought about building a balancing robot with it. After many iterations, it worked!

You can build this robot with the elements of the LEGO MINDSTORMS Robot Inventor set (51515). Click the image below to download the instructions. Just follow the steps and you’ll have your robot ready in no time.

You can also build this robot with the SPIKE Prime set. While the hub works the same, it comes with different building elements so you might want to check out these instructions instead.

Building Instructions

The balancing program

The program below balances the robot along two axes at the same time. One motor is used to control the x axis using the gyro sensor value of the x axis. The other motor is used to control the y axis.

The feedback to each motor is a weighted sum of all the signals it needs to keep at zero. This includes the gyro angle, gyro speed, motor angle, and motor speed. The weights are tuned to make the robot stable.

The ball balancing program
The ball balancing program

When you turn the hub on, leave it on the desk or the floor for a few seconds. This gives the gyro sensor a chance to calibrate. Then put the robot on top of the ball and let it stand upright between your hands. Start the program and carefully let go.

Naturally, you can keep your hands close to prevent it from falling flat, but it is generally best to not try to help the robot forcefully. It needs to find its own path!

You can see how this works in the video above.

Running the Pybricks program

This project uses Pybricks on your LEGO hub. Pybricks makes your creations come alive and helps you unlock the full potential of your LEGO Technic, City, MINDSTORMS, BOOST, or Spike sets.

If you haven’t already, install Pybricks on your hub as shown below, or check out our getting started guide for more details. You can go back to the LEGO firmware and apps at any time.

Install the Pybricks firmware.

Now import the program you downloaded earlier, as shown below. Click to connect your hub and ▶ to start!

Import a Pybricks Code project.

You can run imported block programs even if you’re not signed up. This is a great way to try out Pybricks and see how it works.

Running it as a Python program

You can also run this project as a Python (MicroPython) program. The following code was generated from the block program above. To run it, create a new empty Python program in Pybricks and copy the code into it.

from pybricks.hubs import InventorHub
from pybricks.parameters import Axis, Direction, Port
from pybricks.pupdevices import Motor
from pybricks.tools import vector, wait
from umath import copysign

# Set up all devices.
hub = InventorHub(top_side=vector(1, 1, 0), front_side=vector(-1, 1, 0))
motor_x = Motor(Port.B, Direction.COUNTERCLOCKWISE)
motor_y = Motor(Port.F, Direction.COUNTERCLOCKWISE)

# Initialize variables.
start_angle_x = hub.imu.rotation(Axis.X)
start_angle_y = hub.imu.rotation(Axis.Y)

def balance(motor, axis, start_angle, duty):
    # The feedback to the motor is a weighted sum of the gyro angle,
    # gyro angular velocity, motor angle, and motor speed.
    duty = 88 * (hub.imu.rotation(axis) - start_angle) + hub.imu.angular_velocity(axis) * 7 / 20 + motor.angle() * 18 / 25 + motor.speed(window=250) * 19 / 100
    # The feedback value is applied to the motor. We also add 10% extra
    # to account for friction. The result is scaled by the battery level so it
    # behaves the same over time.
    motor.dc(7400 * (duty + copysign(10, duty)) / hub.battery.voltage())

# The main program starts here.
# Check that the hub gyro is ready for use.
if not hub.imu.ready():
    hub.speaker.beep(500, 100)
    raise SystemExit
# Keep balancing forever.
while True:
    balance(motor_x, vector(1, 0, 0), start_angle_x, 0)
    balance(motor_y, vector(0, 1, 0), start_angle_y, 0)
Python representation of the block program.