This software forms a framework for a Radio Controlled Self Balancing Robot. It is designed to be configurable and to support a variety of motor types, with and without encoders.
The radio control uses the ESP-NOW protocol.
Additionally there is a backchannel - this supports telemetry and also has a command interface to allow remote PID tuning.
There are four currently implementations:
- one for the M5Stack Bala2Fire,
- one for the M5Stack BalaC,
- Lego version using 2 Power Functions XL Motors (8882) and the M5Stack GoPlus2 Module,
- 3D printed version using JGA25_370 Metal Geared Motors with encoders and the M5Stack 4EncoderMotor Module.
PID tuning for the Lego version is currently stalled - I burnt out one of the motor drivers on the GoPlus2 module during PID tuning.
PID tuning for the JGA25_370 is currently stalled - I ordered the wrong the JGA25_370 motors for the 3D printed version, the ones I ordered don't have enough torque, so I've ordered new motors (same motors with lower gearing).
These are controlled with the M5Stack AtomJoyStick.
Telemetry and PID tuning is using the M5Stack Cardputer
The Self Balancing Robot Framework is implemented in a modular way, with the different components split off into separate libraries. These libraries are designed so they may be used in other projects.
| Library | Function | Location |
|---|---|---|
| VectorQuaternionMatrix | General purpose 3D vector, quaternion, and 3x3 matrix classes | https://github.com/martinbudden/Library-VectorQuaternionMatrix |
| PIDF | PID controller with feed-forward | https://github.com/martinbudden/Library-PIDF |
| Filters | Collection of general purpose filters | https://github.com/martinbudden/Library-Filters |
| SensorFusion | Sensor Fusion including Complementary Filter, Mahony Filter, Madgwick Filter, VQF | https://github.com/martinbudden/Library-SensorFusion |
| IMU | Inertial Measurement Unit - gyroscopes and accelerometers. | https://github.com/martinbudden/Library-IMU |
| Stabilized Vehicle | AHRS (Attitude and Heading Reference System) | https://github.com/martinbudden/Library-StabilizedVehicle.git |
| Receiver | Receiver base class and implementations, including implementation using ESP-NOW | https://github.com/martinbudden/Library-Receiver |
| Backchannel | Backchannel over ESP-NOW for telemetry, PID tuning, and benchmarking | https://github.com/martinbudden/Library-Backchannel |
Simplified outline of main classes.
Classes with <eg> suffix are specific instances for Bala2 self balancing robot.
All objects are statically allocated in Main::setup().
classDiagram
class SensorFusionFilterBase {
<<abstract>>
update() Quaternion *
getOrientation() Quaternion const
}
link SensorFusionFilterBase "https://github.com/martinbudden/Library-SensorFusion/blob/main/src/SensorFusion.h"
class IMU_Base {
virtual readAccGyroRPS() accGyroRPS_t
}
link IMU_Base "https://github.com/martinbudden/Library-IMU/blob/main/src/IMU_Base.h"
class IMU_FiltersBase {
<<abstract>>
setFilters() *
filter() *
}
link IMU_FiltersBase "https://github.com/martinbudden/Library-StabilizedVehicle/blob/main/src/IMU_FiltersBase.h"
IMU_FiltersBase <|-- IMU_Filters
class VehicleControllerBase {
<<abstract>>
loop() *
updateOutputsUsingPIDs() *
}
link VehicleControllerBase "https://github.com/martinbudden/Library-StabilizedVehicle/blob/main/src/VehicleControllerBase.h"
VehicleControllerBase <|-- MotorPairController
class MotorPairController {
array~PIDF~ _pids
updateSetpoints();
updateMotorSpeedEstimates();
}
link MotorPairController "https://github.com/martinbudden/SelfBalancingRobot/blob/main/lib/SelfBalancingRobot/src/MotorPairController.h"
class MotorPairMixer {
}
link MotorPairMixer "https://github.com/martinbudden/SelfBalancingRobot/blob/main/lib/SelfBalancingRobot/src/MotorPairMixer.h"
class MotorPairBase {
<<abstract>>
readEncoder() *
setPower() *
}
link MotorPairBase "https://github.com/martinbudden/SelfBalancingRobot/blob/main/lib/SelfBalancingRobot/src/MotorPairBase.h"
MotorPairController *-- MotorPairBase
MotorPairController *-- MotorPairMixer
MotorPairController o-- CockpitBase
MotorPairBase <|-- MotorsBala2
class MotorsBala2["MotorsBala2(eg)"]
link MotorsBala2 "https://github.com/martinbudden/SelfBalancingRobot/blob/main/lib/MotorPairs/src/MotorsBala2.h"
class AHRS {
bool readIMUandUpdateOrientation()
}
link AHRS "https://github.com/martinbudden/Library-StabilizedVehicle/blob/main/src/AHRS.h"
AHRS *-- IMU_Base
AHRS *-- SensorFusionFilterBase
AHRS *-- IMU_FiltersBase
AHRS o-- VehicleControllerBase
VehicleControllerBase o-- AHRS
class ReceiverBase {
<<abstract>>
WAIT_FOR_DATA_RECEIVED() int32_t *
update() bool *
getStickValues() *
getAuxiliaryChannel() uint32_t *
}
link ReceiverBase "https://github.com/martinbudden/Library-Receiver/blob/main/src/ReceiverBase.h"
class CockpitBase {
<<abstract>>
updateControls() *
checkFailsafe() *
}
link CockpitBase "https://github.com/martinbudden/Library-Receiver/blob/main/src/CockpitBase.h"
Cockpit o-- MotorPairController
CockpitBase o--ReceiverBase
CockpitBase <|-- Cockpit
class Cockpit {
updateControls() override
checkFailsafe() override
getFailsafePhase() uint32_t const
}
link Cockpit "https://github.com/martinbudden/SelfBalancingRobot/blob/main/lib/SelfBalancingRobot/src/Cockpit.h"
IMU_Base <|-- IMU_MPU6886
class IMU_MPU6886["IMU_MPU6886(eg)"]
link IMU_MPU6886 "https://github.com/martinbudden/Library-IMU/blob/main/src/IMU_MPU6886.h"
SensorFusionFilterBase <|-- MadgwickFilter
class MadgwickFilter["MadgwickFilter(eg)"] {
update() Quaternion override
}
ReceiverBase <|-- ReceiverAtomJoyStick
class ReceiverAtomJoyStick["ReceiverAtomJoyStick(eg)"]
link ReceiverAtomJoyStick "https://github.com/martinbudden/Library-Receiver/blob/main/src/ReceiverAtomJoyStick.h"
ReceiverAtomJoyStick *-- ESPNOW_Transceiver
class ESPNOW_Transceiver
link ESPNOW_Transceiver "https://github.com/martinbudden/Library-Receiver/blob/main/src/ESPNOW_Transceiver.h"
Simplified outline of tasks.
On a dual-core processor AHRS_Task has the second core all to itself.
The AHRS_Task and the ReceiverTask may be either interrupt driven or timer driven.
All other tasks are timer driven.
Tasks are statically (build-time) polymorphic, not dynamically (run-time) polymorphic.
They all have task and loop functions, but these functions are not virtual.
This is deliberate.
BackchannelTask is optional tasks and are not required for motion.
classDiagram
class TaskBase {
uint32_t _taskIntervalMicroseconds
}
link TaskBase "https://github.com/martinbudden/Library-TaskBase/blob/main/src/TaskBase.h"
TaskBase <|-- MainTask
class MainTask {
loop()
}
MainTask o-- ButtonsBase : calls update
MainTask o-- ScreenBase : calls update
class CockpitBase {
<<abstract>>
updateControls() *
checkFailsafe() *
}
class ReceiverBase {
<<abstract>>
WAIT_FOR_DATA_RECEIVED() int32_t *
update() bool *
getStickValues() *
getAuxiliaryChannel() uint32_t *
}
CockpitBase o--ReceiverBase
CockpitBase <|-- Cockpit
Cockpit o-- MotorPairController : calls updateSetpoints
TaskBase <|-- ReceiverTask
class ReceiverTask {
loop()
-task() [[noreturn]]
}
link ReceiverTask "https://github.com/martinbudden/Library-Receiver/blob/main/src/ReceiverTask.h"
class ReceiverWatcher {
<<abstract>>
newReceiverPacketAvailable() *
}
ReceiverTask o-- ReceiverWatcher : calls newReceiverPacketAvailable
ReceiverTask o-- ReceiverBase : calls WAIT_FOR_DATA_RECEIVED update getStickValues
ReceiverWatcher <|-- ScreenBase
ReceiverTask o-- CockpitBase : calls updateControls checkFailsafe
class VehicleControllerBase {
<<abstract>>
loop() *
updateOutputsUsingPIDs() *
}
TaskBase <|-- VehicleControllerTask
class VehicleControllerTask {
loop()
-task() [[noreturn]]
}
link VehicleControllerTask "https://github.com/martinbudden/Library-StabilizedVehicle/blob/main/src/VehicleControllerTask.h"
VehicleControllerTask o-- VehicleControllerBase : calls loop
VehicleControllerBase <|-- MotorPairController
TaskBase <|-- AHRS_Task
class AHRS_Task {
loop()
-task() [[noreturn]]
}
link AHRS_Task "https://github.com/martinbudden/Library-StabilizedVehicle/blob/main/src/AHRS_Task.h"
AHRS_Task o-- AHRS : calls readIMUandUpdateOrientation
class AHRS {
bool readIMUandUpdateOrientation()
}
AHRS o-- VehicleControllerBase : calls updateOutputsUsingPIDs
VehicleControllerBase o-- AHRS
class Backchannel {
processedReceivedPacket() bool
}
TaskBase <|-- BackchannelTask
class BackchannelTask {
loop()
-task() [[noreturn]]
}
link BackchannelTask "https://github.com/martinbudden/Library-Backchannel/blob/main/src/BackchannelTask.h"
BackchannelTask o-- Backchannel : calls processedReceivedPacket
This is more a list of ideas for possible future implementations rather than a plan to make those implementations. I might undertake some of these depending on availability of time and hardware:
- Hoverboard version using ODrive, ODesc, or Moteus drivers and probably the M5 Stack PwrCAN Module.
- 3D printed version using NEMA17 stepper motors.
- 3D printed version using M5Stack RollerCAN Units and M5 Stack PwrCAN Module.
- Tiny version using Carl Bugeja's Microbots CodeCell and two Microbots DriveCell - how small can I make it?
- Raspberry Pi Pico version. In particular I'd like to try and port the D-Shot ESC Protocol to the Raspberry Pi Pico Programmable I/O and use the Pico to control the motors. Since D-Shot is a bidirectional protocol it will be possible to obtain the motors' RPM and use that as part of the motor control loop. As I understand it, D-Shot only provides reliable motor RPM values for high RPM, but it might still be possible to use the values provided instead of using encoders. Something at least worth investigating.
- Port to Raspberry Pi and use Raspberry Pi Lego hat to make a Lego version using PoweredUp motors (which have encoders built in).
- Addition of wheels to the feet of Dan's Modular Biped Robot. The idea is they would be a bit like Heelys and allow the robot to lift itself onto it's heels and then propel itself.
How to visually tune PID Control Loops - good explanation of how to do PID tuning.
Building an Arduino-based self-balancing robot. Good explanation of all the ins and outs of self balancing robots, by Mike Jacobs.
J Pieper's Hoverbot uses hoverboard motors and moteus-c1 BLDC motor controllers. Code runs on a Raspberry Pi. Has an explanation of the build and the PID control system.
Stijns Projects self balancing robot uses hoverboard motors and ODrive/ODESC BLDC motor controllers. Code runs on two Arduinos. Has a good explanation of how to to set up ODrive.
Lukas Kaul's version of the HoverBot uses hoverboard motors and ODrive BLDC motor controllers Code runs on an Arduino. Has a good explanation of the build.
James Bruton's TallBalancer youtube video uses Turnigy 6374 148 kV motors and ODrive BLDC motor controllers. Code runs on a Teensy 3.6. The code is here
Noah Zipin's self balancing robot uses Pololu 156:1 Metal Gearmotors (20Dx44L mm 12V CB with Extended Motor Shaft and Hall effect encoders) and L298N (H-bridge) motor controllers. The code runs on an Arduino. The code is here
Your Arduino Balancing Robot uses NEMA-17 stepper motors. The code runs on an Arduino. Good overall explanation.
Arduino SimpleFOCBalancer - Modular Arduino two wheel balancing robot. Uses 4108 gimbal BLDC motors with AMT103 CUI encoders. Motors controlled by an Arduino SimpleFOCShield and the SimpleFOClibrary. Good explanation of the build and overview of the control algorithm.



