In this project, we will build a two-wheeled self-balancing robot using a Kypruino, an MPU6050 motion sensor, an L298N motor driver and a single TT gear motor.
The robot has no stand, no third wheel and nothing holding it up. It stays upright the same way you balance a broom on the palm of your hand: 200 times every second, it measures how far it is leaning and drives its wheels back underneath itself before it can fall.
This is a more advanced build than our other Kypruino projects. You should be comfortable wiring a motor driver, uploading an Arduino sketch and reading values in the Serial Monitor. In return, it covers some of the most important ideas in robotics: feedback control, sensor fusion and motor control.
Project Resources: Source Code & 3D Printing Files.

What the project does
Once it is standing, the robot keeps itself upright without any help.
- Self-balancing: it measures its tilt 200 times per second and drives its wheels to stay upright
- Holds its position: it leans gently against its own movement, so it does not simply drive away
- Finds its balance point: it fine-tunes the exact angle it balances at while it is running
- Starts by itself: hold it upright and still for a moment, and it starts balancing
- Status lights and sounds: the onboard NeoPixels act as a spirit level, and the buzzer signals when it is ready or has fallen
- Safety cut-off: if it tips past 25Β° forwards, backwards or sideways, the motor stops
The result is a robot that can stand by itself for minutes at a time, constantly making small corrections to stay upright.
Components needed
For this build, you will need:
- 1 Γ Kypruino board
- 1 Γ MPU6050 gyroscope and accelerometer module
- 1 Γ L298N dual motor controller (2A)
- 2 Γ TT motor and wheel kits (one motor drives both wheels, so the second kit provides the second wheel)
- 2 Γ 9V batteries, each with a 9V battery snap connector
- DuPont jumper wires
- 1 Γ USB-C cable for programming
- 3D-printed frame, about 27 cm tall, available in our GitHub repository
- Arduino IDE for programming
The project uses the Kypruinoβs onboard NeoPixels, buzzer and button B, so no separate LEDs, buzzer or buttons are required.
How a balancing robot works
The inverted pendulum
A balancing robot is an inverted pendulum: a weight sitting on top of a pivot. A normal pendulum always swings back to hang straight down, but an inverted pendulum always wants to fall. The only way to keep it up is to keep moving the pivot back underneath the weight.
That is exactly what the robot does. If it starts leaning forwards, the wheels drive forwards to get back under the weight. If it leans backwards, they drive backwards. Done quickly and smoothly enough, the robot stays upright.
Measuring the tilt
The MPU6050 gives us two different ways to measure tilt, and neither is good enough on its own:
- The gyroscope measures how fast the robot is rotating. Adding this up over time gives a smooth and fast tilt reading, but small errors build up, so the reading slowly drifts.
- The accelerometer measures which way gravity is pulling. It never drifts, but it is noisy, and it gets confused when the robot itself speeds up or slows down.
The program combines the two with a complementary filter: it trusts the gyroscope from moment to moment and uses the accelerometer to slowly correct the drift. This is called sensor fusion, and the same idea is used in phones, drones and aircraft.
Deciding how hard to drive
Each time the robot measures its tilt, it calculates how hard to drive the motor using a PD controller:
- P (proportional): the further it leans, the harder it drives
- D (derivative): the faster it is falling, the harder it drives, which catches a fall early and stops it from overshooting
Staying in one place
Balancing alone is not enough. If the robotβs balance angle is even slightly off, it can only hold itself up by slowly speeding up, so it drives away across the table. Our robot has no wheel sensors (rotary encoders), so it estimates how fast it is moving and how far it has travelled from the commands it has sent to the motor. When it is moving forwards, it aims to lean slightly backwards, which slows it down and brings it back.
The Kypruino
The Kypruino is the brain of the robot. 200 times every second, it reads the MPU6050, works out the tilt, calculates the motor command and sends it to the L298N.
Besides acting as the main controller, the Kypruino provides several onboard features that this project uses:
- NeoPixels on D8 for status lights and a live spirit level
- Buzzer on D9 for ready, fall and fault sounds
- Button B on D6 to start or stop the robot manually
- USB-C for programming and for reading data back from the robot
The MPU6050 motion sensor
The MPU6050 combines a 3-axis gyroscope and a 3-axis accelerometer on one small board. It communicates with the Kypruino using the I2C protocol, which needs only two signal wires.
For a balancing robot, a few sensor settings make a big difference:
- Gyroscope range: Β±500 Β°/s, enough for fast falls without losing precision
- Accelerometer range: Β±4 g, so hard jolts from the motor do not overload the sensor
- Built-in low-pass filter: about 44 Hz, which smooths out vibration from the gearbox
- Calibration: the gyroscope is calibrated every time the robot starts, so it must lie completely still for a few seconds
Mount the sensor firmly to the frame. If it wobbles on its wires, it measures its own wobble instead of the robotβs tilt.
The L298N motor driver
The Kypruino cannot power a motor directly, so the L298N dual motor controller does the heavy lifting. It is an H-bridge: a set of electronic switches that can connect the motor to the battery in either direction. It has two channels, but this robot only needs one.
- ENA sets the speed, using a PWM signal from 0 to 255. Remove the jumper on ENA so that the Kypruino can control it
- IN1 and IN2 set the direction
- 5V is an output from the boardβs built-in 5 V regulator, which we use to power the Kypruino
One detail matters a lot for balancing. A geared motor does not move at all until the PWM value passes a certain threshold, called the deadband. In the air, our wheel only started turning at around 90β110. On the ground, the robot already reacted from about 75, so we settled on 90. The program adds this amount to its corrections, so even a small correction actually turns the wheel.
Designing and 3D printing the frame
The frame is a tall 3D-printed beam, about 27 cm high, with the motor at the bottom and the heaviest parts at the top.
- The batteries and the Kypruino sit at the top. A tall robot with its weight up high falls more slowly, which gives the controller more time to react. This is why a broom is easier to balance on your hand than a pencil.
- The L298N is mounted partway up the beam, between the batteries and the motor.
- A single TT gear motor at the bottom drives both wheels through its double-ended shaft.
- The MPU6050 is fixed firmly to the frame, so it moves exactly with the robot.

Wheel spacing
The robot can only balance forwards and backwards. Sideways, it relies entirely on how far apart its wheels are. On our robot, the wheels were only 24 mm apart, so a sideways lean of just 6β8Β° was enough to tip it over. Tipping sideways is what ended our longest run, so if you design your own frame, place the wheels as far apart as you can.
Powering the robot
The robot runs on two 9V batteries wired in parallel: the two positive wires are joined together, and so are the two negative wires. This keeps the voltage at 9 V but doubles the capacity, so the robot runs for longer before the batteries need replacing. The robot also works with a single 9V battery.
- The batteries power the L298N through its 12V terminal, which drives the motor
- The L298Nβs built-in regulator turns this into 5 V, and its 5V terminal powers the Kypruino through the Kypruinoβs 5V pin
- The Kypruinoβs GND is connected to the L298Nβs GND, so the control signals share a common reference
Use two batteries of the same type and charge level, and replace them together. Otherwise, the fuller battery drains into the weaker one.
Wiring the project
Make sure the Kypruinoβs onboard NeoPixel and buzzer switches are turned on. The motor direction pins use D10 and D11, so that D8 and D9 stay free for the NeoPixels and the buzzer.
MPU6050 motion sensor
| MPU6050 Pin | Connects to Kypruino |
|---|---|
| SDA | A4 |
| SCL | A5 |
| VCC | 5V |
| GND | GND |
L298N motor driver
| L298N Pin | Connects to |
|---|---|
| ENA (jumper removed) | Kypruino D5 |
| IN1 | Kypruino D10 |
| IN2 | Kypruino D11 |
| 5V | Kypruino 5V |
| GND | Kypruino GND |
| OUT1 / OUT2 | TT gear motor |
Keep the L298Nβs 5V-enable jumper in place. It switches on the built-in regulator that powers the Kypruino.
Power
| Part | Connects to |
|---|---|
| Both 9V batteries (+) | L298N 12V |
| Both 9V batteries (β) | L298N GND |
Keep the sensor wires away from the motor wires, and make sure every connection is secure. A loose wire that briefly loses contact when the robot shakes can make the sensor drop out mid-run.

Installing the Arduino libraries
Before uploading the code, install the two libraries the sketch uses.
- Open the Arduino IDE
- Go to Sketch β Include Library β Manage Libraries...
- Search for MPU6050_light and install it
- Search for Adafruit NeoPixel and install it
Uploading the code
Connect the Kypruino to your computer using the USB-C cable, select the correct board and port, then upload the balancing sketch. Lay the robot flat and still while it starts up, because it calibrates the gyroscope as soon as it is powered on.
The motor driver, lights, buzzer and button are connected to:
const uint8_t ENA = 5; // L298N speed (PWM)
const uint8_t IN1 = D10, IN2 = D11; // L298N direction
const uint8_t PIX_PIN = 8; // onboard NeoPixels
const uint8_t BUZZER = 9; // onboard buzzer
const uint8_t BUTTON = 6; // button B: start / stop
These are the balancing settings we ended up with after testing:
float Kp = 32; // push per degree of lean
float Kd = 2.5; // push per degree/second of falling speed
float KV = 10; // lean against estimated wheel speed
float KX = 2; // lean against estimated distance travelled
int DEADBAND = 90; // PWM where the motor starts to move
You can find the complete code in our GitHub repository.
Understanding the program
1. Calibrating the gyroscope
When it is powered on, the robot measures the gyroscopeβs reading while lying still and then checks that the result is close to zero. If the robot was moved during calibration, it gives a low beep and tries again. When it is ready, it plays two short rising beeps.
Between runs, it also re-measures this zero point whenever it lies still, because the gyroscope drifts slightly as it warms up.
2. Measuring the tilt
Every 5 milliseconds, the program predicts the new angle from the gyroscope and then nudges it slightly towards the angle the accelerometer reports:
float predicted = pitch + gyroRate * dt; // where the gyro says we are
float accAngle = atan2(ay, az) * RAD_TO_DEG; // where gravity says we are
pitch = predicted + 0.01 * (accAngle - predicted); // trust the gyro, correct slowly
Accelerometer readings that make no physical sense, for example during a hard jolt from the motor, are ignored, and the gyroscope carries the angle through them.
3. Balancing
The program compares the measured tilt with the balance angle and calculates the motor command:
float push = Kp * lean + Kd * rate;
It then adds the deadband, limits the result to the 0β255 PWM range, and sets the motor direction from the sign of the result.
4. Staying in place
The robot has no wheel sensors, so it estimates its own speed and distance from the commands it has sent to the motor. These estimates shift the balance target slightly: moving forwards makes it aim a little further back, so it brakes and returns.
A slow trim also adjusts the balance angle itself while the robot is running. If the robot keeps having to push in one direction, its balance angle is slightly off, so the trim moves it until the push disappears. After a long run, the robot saves part of this correction, so it starts closer to its true balance point next time. This lets it balance even if the weight is not perfectly centred.
5. Starting and stopping
When the robot is held near upright and still for 0.4 seconds, it starts balancing by itself. To avoid a sudden jerk, the target starts at the angle where you let go and glides to the balance angle within about a second. Pressing button B also starts or stops it.
If the robot tips past 25Β° forwards, backwards or sideways, the motor switches off and the robot waits to be stood up again.
Testing and tuning
Test the robot in stages:
- Calibrate: power it on lying flat and still. The LEDs glow blue while it calibrates, and two rising beeps mean it is ready
- Check the directions in the air: hold the robot upright with its wheels off the ground. All three LEDs turn green, and a moment later it starts. Tip it slightly forwards or backwards: the LED on the side it is leaning towards should glow red, and the wheels should spin as if driving in the direction of the lean
- First balance: stand it on a flat surface, wait for it to start and let go. Keep a soft surface nearby, and only catch it once it is clearly falling
- Check the readings: connect the USB cable and open the Serial Monitor at 115200 baud. Send s to see the measured angle, the balance angle and the current settings
If the wheels drive away from the lean instead of towards it, swap the two motor wires on OUT1 and OUT2, or send f in the Serial Monitor. If the opposite LED lights up, swap the values of PIX_BACK and PIX_FWD in the code.
If the robot buzzes or shakes rapidly, the D value is too high. If it reacts too slowly and falls over, increase P. You can try new values without uploading again by sending, for example, p30 or d2 in the Serial Monitor. These are forgotten when the board restarts, so copy the values that work into the code.
Test the robot on the floor, or well away from the edge of a table. Until it is tuned, it can wander off before you have time to catch it.
Final result
The finished robot stands on its own, constantly making small corrections. In testing, it balanced for 13 minutes in a single run, and for more than 24 minutes across three runs.
It is not perfectly still. Without wheel sensors, it can only estimate where it is, so it sways slowly back and forth by up to about 18 cm, once every 4β6 seconds, and can slowly creep away over time. Its longest run ended when it tipped over sideways on its narrow wheels, not because the balancing failed.
What students can learn
This project introduces several useful STEM concepts:
- How an inverted pendulum behaves, and why it needs active control
- Reading a gyroscope and an accelerometer over I2C
- Sensor fusion with a complementary filter
- Feedback control with P and D terms
- Driving a DC motor with an H-bridge and PWM
- Motor deadband and how to compensate for it
- Wiring batteries in parallel with a common ground
- Tuning a control system by testing it on real hardware
- Designing a frame around its centre of mass
It also demonstrates an important engineering idea: a system that looks simple from the outside, a stick on two wheels, depends on many small details working together, from sensing and timing to motor control and tuning.
Possible extensions
- Move the wheels further apart to stop the robot tipping sideways
- Add a second motor on the L298Nβs second channel, so the robot can turn
- Add wheel encoders, so the robot knows exactly how far it has moved and can stay perfectly in place
- Add Bluetooth or Wi-Fi remote control to drive it around while it balances
- Compare different batteries and see how they affect motor power and run time
- Show the tilt angle live on an OLED display
Final thoughts
A self-balancing robot is one of the most satisfying projects to complete, because you can see the control loop working. A robot that falls over instantly without power stands on its own as soon as the code takes over.
Using a Kypruino, an MPU6050, an L298N and a single TT motor, this build combines sensors, motors, control theory and tuning in one project. It is a challenging build, but every step teaches something that applies to real robots, drones and vehicles.
