High speed balancing robot: introduction

By | November 10, 2018

For quite some time, I’ve been developing a two wheeled balancing robot. You know, like a Segway. But in this case it is somewhat smaller, as to minimise damage when hitting something at high speed. That happens rather often, due to the very high maximum speed of this robot.

This post will cover the architecture of the robot, explaining all ins and outs. Many balancing robots have been made before, but this one has some nice features that make it very well behaved. Also, everything has been designed to make the robot easy to replicate. As one of the goals of this project is to get other people to work on the robot, all source files will be made open source. Also, in a future post I hope to document how to build one.

Just to be clear: the purpose of me making this project open source, is not for people to get such a cute, fun robot, that they’ll play with and then never use anymore. The purpose is to educate other people, to share knowledge, and to create and extremely good performing robot together. Just because. Because developing technological things without purpose is fun.


In order to balance an unstable system, negative feedback is required. The diagram below illustrates the approach. Besides a feedback loop, a sensor to measure an error (or rather the sytem response), and an actuator to minimise the error, are required.

Sensing and actuation

Getting a sensor with sufficient accuracy and bandwidth is relatively easy; in this case the well-known (and mostly cheap) MPU6050 IMU is used. The accelerometer provides a long term stable, but noisy signal, especially if you accelerate fast. The gyroscope on the other hand gives an accurate, high speed signal, with drift however. Combining the two using a simple complementary filter gives a stable signal, representing the angle of the robot.

Next, an actuator needs to be selected. A common choise is a DC motor, for example for its low cost or simple control. However, a DC motor has in general a too low torque to directly drive the wheels of the robot. A gearbox is used to increase torque, but several disadvantages are introduced as well. Cheap gearboxes have severe backlash and friction, which make the control loop non-linear, and therebey making everything a lot more complicated.

This is where the stepper motor comes in. This idea isn’t new, serveral other examples of balancing robots with stepper motors are available. For some examples, see herehere or here. Steppers are quite suitable for the job: direct drive, relatively high torque, low inertia (which isn’t true for a DC motor with high gear ratio), and no backlash. Stepper motors are commonly used in for example 3D printers, and can hence be found for a low price (about €10 each). Driving the steppers is also done with 3D printer related parts. A so called step stick, containing a DRV8825 IC, arranges all the power electronics, accepting a logic step and direction input.


Then comes the software. In my opinion, this is the part where the most difference can be made. The above description holds for a lot of balancing robots. With suitable software / control implementation, a lot of differentation can be made. The software of this robot runs on an ESP32, a dual core microcontroller with integrated WiFi and Bluetooth. Using an ESP32 allows for several nice tricks, such as control with a smartphone via WiFi / Bluetooh (not implemented yet), OTA, wireless data logging, etc.

Currently, the software runs two control loops at 200 Hz. See below for a schematic view of the two (cascased) controllers. The first control loop tries to minimise angle error, i.e. keeping the robot upright (in case of a zero reference). The second control loop either controls position or speed, depending on user input. If no input is present, position is regulated to be zero. Otherwise, the outer controller manipulates robot angle to obtain a speed equal to the speed setpoint. To give an example: if the robot is standing still, and a positive (forward) speed setpoint is given, the robot tilts forward until it reaches the desired velocity. When standing upright, the inner control loop moves the wheels backwards, such that the robot will tilt forward.


For powering the robot, I currently use a 6S1P (24 V) 2650 mAh LiPo battery. That’s quite a big one: it allows for up to 5 hours of driving time. So, such a big battery is not strictly required. Besides this battery being relatively cheap (in Wh/€), a big mass on top of the robot makes the robot fall “slower”. This makes stabilising the control loop easier, and, it also looks more natural in my opinion. A sturdy case has been 3D printed to fit tightly around the battery, to allow for protection. The high voltage of the battery allows the steppers to be run at extremely high speeds. Note that 6S is over the top, and 3S will give sufficiently high speeds.

The last part to add is a PCB. For the first robot versions, I developed some experimental PCBs. For the current version, I’ve drawn a simple PCB, based on commonly available modules (IMU / step-stick / ESP32 dev board), which allows for easy assembly. The PCB is drawn in KiCad, which is open source software. I have some PCBs in stock, so feel free to ask for one. In case you want to make something yourself (on an experimental PCB), see the PDF of the schematic in the Git repo.

Some PCB history

The total cost of the robot is quite limited due to use of standard components. Electronics are about €10, steppers €10 a piece, battery between €5 and €30. The frame can be printed cheaply (see Git repo for source files), or simply zip tie everything to a piece of wood. Currently I use an RC transmitter / receiver, which drives the cost up, but as soon as WiFi/Bluetooth control is implemented, it isn’t required anymore.

To end this post, some more pictures of the complete robot(s). Yes, it has a small brother :).

More info

Source files can be found in the Git repo. It is highly appreciated if you contribute to the project!

The subolders in the repo also contain readme files, explaining how to assemble the electronics, and how to setup the software environment.

13 thoughts on “High speed balancing robot: introduction

  1. Hank, LEE


    I saw your high speed balancing robot in youtube.
    it is interesting for me and I want to do this project if you let me. ^^

    Could you send me a PCB ( current version )?

    Now i am buying resource motor and etc.
    Thanks for your video.


    Hi ! Congratulations!!!!
    I really like your project! And I want to do the same.
    What is the model of the motor stepper that you’re using ?

    Thank you.

    1. Wouter Post author

      Thanks for your reponse! I use Nema17 stepper motors (the ones typically found in a 3D printer). I’ve used motors with a length of 34, 40 and 48mm, all work ok. I think 40mm length is a nice balance between weight and torque required for this application.

    1. Leonardo

      Friend excellent project. I’m mont one together for my son. I installed Atom but in the compilation it presents errors. Did you need to install any external libraries?

      1. Wouter Post author

        Hi Leonardo, thanks for your response! I think your error is caused by me not having included the Streaming library; I installed this library globally (which is bad practice). See the latest commit in the dev branch: https://gitlab.com/kloppertje/balancingrobot/tree/devel

        If it still doesn’t work, please post (or better, mail) the specific errors you get. Also, I can recommend VSCode, it works a bit better compared to Atom with PlatformIO, in my opinion.

        1. Leonardo

          lib\AsyncTCP\src\AsyncTCP.cpp:259:27: error: field ‘call’ has incomplete type ‘tcpip_api_call’

          1. Leonardo

            I solved the error above. I upgraded the AsyncTCP library to 1.0.3 now I’m having this error. lib\I2Cdev\I2Cdev.cpp:276:75: error: no matching function for call to ‘min(uint8_t&, int)’

  3. Robert Schossleitner

    Hello Wouter,

    thanks for the PCB. Now i am fighting a bit with the Stepper Code in the Loop i found this lines.

    // updateStepper(&motLeft);
    // updateStepper(&motRight);

    In my case it seems that ust the motLeft produces an appropriate stepper clock on the gpio pin the motRight gpio is quiet. However the direction pin of the motRight is working. If i flip the update-Statements (motRight first), in all cases the first is working. It seems to me that something with the timer/ISR is not working. Do you have any hints regarding that problem?

    BR Robert

    1. Wouter Post author

      Hi Robert,

      I haven’t experienced these issues before. Are you using the devel branch (which I recommend)? And, have you changed any of the pins used for step generation? Maybe something goes wrong in this function (lib/fastStepper.cpp):
      void IRAM_ATTR fastStepper::timerFunction() {
      // portENTER_CRITICAL_ISR(&timerMux);
      if (!pinState) {
      _step += dir; // Step is made on rising edge
      GPIO.out_w1ts = 1<<_stepPin; pinState = 1; } else { GPIO.out_w1tc = 1<<_stepPin; pinState = 0; } // portEXIT_CRITICAL_ISR(&timerMux); } You could for example try using digitalWrite (which is probably a bit slower). KR Wouter

  4. Robert Schossleitner

    Hello Wouter,
    yes i am on the devel branch. I have written an alternative loop-function with just a simple test code in it. And i am counting how often the IRAM_ATTR motXXXTimerFunction() are called.


    The counter of the left motor interrupt is counting up the other stays 0. If i flip the first




    the right motor output is working. The Timer and portMUX stuff is a bit suspect to me. It seems the first timer functioncall is locking something with the timer of the other motor

    BR Robert

  5. Robert Schossleitner


    in the meantime i found out that if I disable the timer before enable it, it seems to work. So the second timerAlarmEnable without timerAlarmDisable before does not work. I have no idea why.

    timerAlarmDisable(_timer); //just for testing todo: cleanup
    timerEnable = 0;
    if (!timerEnable) {
    timerAlarmEnable(_timer); // Re-enable timer
    timerEnable = 1;
    } else {
    timerAlarmWrite(_timer, 100000, true);
    timerEnable = 0;

    BR Robert


Leave a Reply

Your email address will not be published. Required fields are marked *