This is a project of designing and controlling the reaction wheel inverted pendulum based entirely on Arduino SimpleFOC library and SimpleFOC shield.
This is a very fun project in many ways, and it is intended:
- Students in search for a good testing platform for their advanced algorithms
- Everyone with a bit of free time and a motivation to create something cool :D
But for me, the most exciting part of this project was the ability to use the Field Oriented Control algorithm. The main benefits of using the BLDC motor in this project are:
- High torque to weight ratio
- The lighter the better
- Lots of torque for low angular velocities
- No need to spin the motor to very high RPM to achieve high torques
- No gearboxes and backlash
- Very smooth operation = very stable pendulum
So far, FOC has been restricted to high-end applications due to the complexity and the cost of the hardware mostly, but also due to the lack of user-friendly, well documented software. Therefore I am very happy to show you the projects like this one, which directly benefit the FOC algorithm and BLDC motors and encourage you to use these techniques in your projects as well. One of the very simple ways of starting (demonstrated in this video) is using the [SimpleFOC library and SimpleFOC shield](https://github.com/ /Arduino-FOC).
Due to the using of the brushless motor and the SimpleFOC shield, this might be one of the simplest hardware setups of the reaction wheel inverted pendulum there is.
This pendulum project has three 3d printed parts. You can find them in the CAD files > STL
directory. They are:
- inertia wheel (
reaction_wheel.stl
)- infill:
70%
- layer hight:
<0.12mm
- it should be printed as precise as possible (to reduce the vibrations)
- mass:
100g
- infill:
- pendulum arm (
pendulum_arm.stl
)- infill:
50-70%
- layer hight:
0.12-0.2mm
- no need for too much precision, only the strength
- mass:
20g
- infill:
- base (
bottpm_swingup.stl
)- infill:
70%
- layer hight:
<0.12
- due to the bearings needs to be pretty precise
- mass:
60g
- infill:
- cable holder (
cable_holder.stl
) - optional
I have printed the pendulum in the PETG filament but there should be no real difference in using PLA or ABS filaments, this is just what I had in hand at the moment.
You will also find all the CAD files used in this project in the CAD files
folder. I have included the Solidworks 2016 parts and the full assembly. Additionally all the parts are exported to STEP format for easier porting to the other CAD IDEs.
Beware: I have designed the holes on the pendulum arm and the wheel to match perfectly the motor the encoders I was using, so please check the dimensions of your motors and encoder holes before printing :D
If you would prefer some other CAD format, let me know, maybe I can export it differently.
All of the mechanical hardware is very simple and can be bought in any hardware store. If you prefer to buy it online, I have included links to the Ebay products you could use.
Component | Description | Link | Price |
---|---|---|---|
Ball bearing 8x16x5mm 3x |
Ebay | 3$ | |
Aluminum tube diameter: 8mm length: 30mm |
Ebay | 3$ | |
M3 screws 7pcs M3x10mm |
Ebay | 2$ | |
M4 screws & nuts 2pcs M4x50mm |
Ebay | 4$ |
Unfortunately the BLDC motor I was using in my video is an old version of the iPower GM4108 motor which is not available anymore to buy, in any case I was not able to find a website that would sell them. The good news is that you can use basically any other gimbal motor in that class with only minor modifications, mostly in the CAD parts - change the mounting holes positions. Some of the motors I was able to find that would be the most similar to my motor would be:
Component | Description | Link | Price |
---|---|---|---|
iPower Motor GM4108H-120T | iFlight webshop | 35$ | |
BGM 4108 120T | Ebay | 30$ | |
BGM4108-150HS | Ebay | 32$ |
In this project I am using two encoders to track the movement of the pendulum and the motor. Now, this is somewhat particular to my implementation, there are may ways to measure the angle of the pendulum. You could use the IMU
and measure the inclination by combining the accelerometer and the gyro measurement, you could also use the magnetic sensors such as AS5048
and similar absolute encoders.
The encoders I have used in this project are:
AMT103 CUI | Mouser | 20$ |
---|
The main features these encoders are:
- Configurable number of impulses per revolution (PPR)
48 - 2048
- Vey easy mounting
- click-on to any type of shaft
< 8mm
- click-on to any type of shaft
- Integrated pull-ups
- Index pin
- Low-cost
~20$
In this project I used the SimpleFOCShield board which is basically an Arduino shield which enables the usage of the FOC algorithm with the BLDC motors. It is practically plug & play in combination with the Arduino SimpleFOC library.
There are quiet a few boards out there that are in short a triple H-bridge motor drivers and the SimpleFOC library will be compatible with most of them. But for most of them the hardware setup will be a bit more complex. Here are some examples of the possible BLDC drives you could use:
Examples | Description | Link | Price |
---|---|---|---|
Arduino SimpleFOCShield | More info | 25$ | |
Dorotek L6234 breakout board | Drotek, Ebay | 30$ | |
HMBGC V2.2 | Ebay | 20$ |
There are surely many ways to mount this pendulum to the stable surface, in my case I hade on my hands a very robust vacuum suction cup holder which works perfectly for this application. It is not the cheapest solution though.
Delkin Fat Gecko | Ebay | 30$ |
---|
The only important thing is that the pendulum base is fixed, everything else is the design choice :D
In this project all the electronics components are:
Arduino UNO | Arduino SimpleFOCShield | 2x AMT 103 encoder | iPower GM4108-120T |
---|---|---|---|
- Channels
A
andB
are connected to the encoder connectorP_ENC
, terminalsA
andB
.
Pinout restriction
Arduino UNO doesn't have enough hardware interrupt pins for two encoders therefore we need to use the software interrupt library.
- Encoder channels
A
andB
are connected to the pinsA0
andA1
.
- Motor phases
a
,b
andc
are connected directly the motor terminal connectorTB_M1
Alignment
Motor phasesa
,b
,c
and encoder channelsA
andB
have to have the same orientation for the algorithm to work. But don't worry about it too much. Connect it initially as you wish and then if the motor locks in place reverse phasea
andb
of the motor, that should be enough.
Let's go through the full code for this project and write it together.
First thing you need to do is include the SimpleFOC
library:
#include <SimpleFOC.h>
Make sure you have the library installed. If you still don't have it please check the get started page.
Also in this case, we are using two encoders so we will need to have a software interrupt library.
I would suggest using PciManager
library. If you have not installed it yet, you can do it using the Arduino library manager directly. Please check the SimpeFOClibrary's Encoder
class docs for more info.
So once you have it please include it to the sketch:
// software interrupt library
#include <PciManager.h>
#include <PciListenerImp.h>
First we define the Encoder
class with the A and B channel pins and number of impulses per revolution.
// define Encoder
Encoder encoder = Encoder(2, 3, 500);
Then we define the buffering callback functions.
// channel A and B callbacks
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
In the setup()
function we initialize the encoder and enable interrupts:
// initialize encoder hardware
encoder.init();
// hardware interrupt enable
encoder.enableInterrupts(doA, doB);
And that is it, let's setup the pendulum encoder.
For more configuration parameters of the encoders please check the *SimpeFOClibrary*'s Encoder
class docs.
We define the pendulum as the Encoder
class with the A and B channel pins and number of impulses per revolution.
// define Encoder
Encoder pendulum = Encoder(A0, A1, 1000);
Then we define the buffering callback functions.
// channel A and B callbacks
void doPA(){pendulum.handleA();}
void doPB(){pendulum.handleB();}
Next we define the PciManager
pin change listeners:
// pin change listeners
PciListenerImp listenerPA(pendulum.pinA, doPA);
PciListenerImp listenerPB(pendulum.pinB, doPB);
In the setup()
function first we initialize the pendulum encoder:
// initialize encoder hardware
pendulum.init();
And then instead of calling pendulum.enableInterrupt()
function we use the PciManager
library interface to attach the interrupts.
// interrupt initialization
PciManager.registerListener(&listenerPA);
PciManager.registerListener(&listenerPB);
And that is it the pendulum is ready, let's setup the motor.
First we need to define the BLDCMotor
class with the number of pole pairs(11
) of the motor.
// define BLDC motor
BLDCMotor motor = BLDCMotor(11);
If you are not sure what your pole pairs number is please check the find_pole_pairs.ino
example.
Next we need to define the BLDCDriver3PWM
class with the PWM pin numbers and the driver enable pin.
// define BLDC driver
BLDCDriver3PWM driver = BLDCDriver3PWM(9, 10, 11, 8);
Then in the setup()
we configure first the voltage of the power supply if it is not 12
Volts and initialize and link the driver.
// power supply voltage
// default 12V
driver.voltage_power_supply = 12;
driver.init();
motor.linkDriver(&driver);
Then we tell the motor which control loop to run by specifying the motor.controller
variable.
// set control loop type to be used
// MotionControlType::torque
// MotionControlType::velocity
// MotionControlType::angle
motor.controller = MotionControlType::torque;
For more information about the voltage control loop please check the doc.
Next we connect the encoder to the motor, do the hardware init and init of the Field Oriented Control.
// link the motor to the sensor
motor.linkSensor(&encoder);
// initialize motor
motor.init();
// align encoder and start FOC
motor.initFOC();
The last peace of code important for the motor is of course the FOC routine in the loop
function.
void loop() {
// iterative FOC function
motor.loopFOC();
// iterative function setting and calculating the angle/position loop
// this function can be run at much lower frequency than loopFOC function
motor.move(target_voltage);
}
Now we are able to read the two encoders and set the voltage to the motor, now we need to write the stabilization algorithm.
For more configuration parameters and control loops please check the *SimpleFOClibrary*'s BLDCMotor
class doc.
The control algorithm is divided in two stages. Stabilization and swing-up.
In order to stabilize the pendulum we will be using a state space controller which means that it takes in consideration all three variables important for this pendulum system:
- pendulum angle -
p_angle
- pendulum velocity -
p_vel
- motor velocity -
m_vel
The controller code is very simple at the end, it just calculates the linear control rule:
target_voltage = 40*p_angle + 7*p_vel + 0.3*m_vel;
The gains 40
,7
and 0.3
you can imagine as weights, which tell how much we care about these variables. The highest weight is obviously on the pendulum angle and the smallest is on motor velocity that makes sense. Basically if we set 0
to the motor velocity weight, your pendulum will still be stable but your motor will probably never stop spinning. It will always have some velocity. On the other hand if you put it much higher, you will probably prioritize your motor movements over the stability and your pendulum will no longer be stable. So there is a tradeoff here.
This is a very simple explanation of a relatively complex topic and I would like to point you toward a nice youtube video explanation of similar approaches.
Also maybe interesting to say is that for a system like this one there is really no need to run it with the sample times less then 20ms. In my case I have run it at ~25ms, but you can go even to 50ms.
NOTE
The FOC algorithmmotor.loopFOC()
will run ~1ms but the control algorithm and the functionmotor.move()
will be downsampled to ~25ms.
The swingup implemented in this example is the simples one possible, that is always good, it means that the hardware is well designed so you dont need to make some fancy algorithm to make it work :D
This is hte code of the swing-up:
target_voltage = -_sign(pendulum.getVelocity())*motor.voltage_power_supply*0.4;
What it does really is it checks which direction the pendulum is moving sign(pendulum.getVelocity())
and sets the very high voltage value motor.voltage_power_supply*0.4
in the opposite direction (-
).
It means that the algorithm is going to try to accelerate the movement of the pendulum (because the pendulum acceleration is caused as the reaction of the motor acceleration, but inverse direction).
The voltage value you are setting is something you will tune. I have found that for my pendulum 40% of the maximum voltage was enough to make the pendulum swing up. More voltage would make it swing up too fast and the pendulum would not be able to stabilize when it reaches the top. Much less voltage would not be enough for the pendulum to swing up at all.
Now we jsut need to decide when do we do the swing up and when do we do the stabilization. Basically we need to decide the angle from which we decide that it is not possible to recover and we should proceed with the swing-up.
I my case I have decided it is 0.5 radians
, ~30degrees
.
So the full control algorithm code looks like this:
// control loop each ~25ms
if(loop_count++ > 25){
// calculate the pendulum angle
float pendulum_angle = constrainAngle(pendulum.getAngle() + M_PI);
float target_voltage;
if( abs(pendulum_angle) < 0.5 ) // if angle small enough stabilize
target_voltage = 40*pendulum_angle + 7*pendulum.getVelocity() + 0.3*motor.shaftVelocity();
else // else do swing-up
// sets 40% of the maximal voltage to the motor in order to swing up
target_voltage = -_sign(pendulum.getVelocity())*motor.voltage_power_supply*0.4;
// set the target voltage to the motor
motor.move(target_voltage);
// restart the counter
loop_count=0;
}
And that is it guys we can read our pendulum angle, we can control the motor, and we have our control algorithm. Lets write the full code!
For the full code I have just added a function constrainAngle()
to constrain the pendulum angle in between -180 and 180 degrees and moved the stabilization part of the controller to the stand-alone function controllerLQR()
.
#include <SimpleFOC.h>
// software interrupt library
#include <PciManager.h>
#include <PciListenerImp.h>
// BLDC motor init
BLDCMotor motor = BLDCMotor(11);
// driver instance
BLDCDriver3PWM driver = BLDCDriver3PWM(9, 10, 11, 8);
//Motor encoder init
Encoder encoder = Encoder(2, 3, 500);
// interrupt routine
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
// pendulum encoder init
Encoder pendulum = Encoder(A1, A2, 1000);
// interrupt routine
void doPA(){pendulum.handleA();}
void doPB(){pendulum.handleB();}
// PCI manager interrupt
PciListenerImp listenerPA(pendulum.pinA, doPA);
PciListenerImp listenerPB(pendulum.pinB, doPB);
void setup() {
// initialise motor encoder hardware
encoder.init();
encoder.enableInterrupts(doA,doB);
// init the pendulum encoder
pendulum.init();
PciManager.registerListener(&listenerPA);
PciManager.registerListener(&listenerPB);
// set control loop type to be used
motor.controller = MotionControlType::torque;
// link the motor to the encoder
motor.linkSensor(&encoder);
// driver
driver.voltage_power_supply = 12;
driver.init();
// link the driver and the motor
motor.linkDriver(&driver);
// initialize motor
motor.init();
// align encoder and start FOC
motor.initFOC();
}
// loop downsampling counter
long loop_count = 0;
void loop() {
// ~1ms
motor.loopFOC();
// control loop each ~25ms
if(loop_count++ > 25){
// updating the pendulum angle sensor
// NECESSARY for library versions > v2.2
pendulum.update();
// calculate the pendulum angle
float pendulum_angle = constrainAngle(pendulum.getAngle() + M_PI);
float target_voltage;
if( abs(pendulum_angle) < 0.5 ) // if angle small enough stabilize
target_voltage = controllerLQR(pendulum_angle, pendulum.getVelocity(), motor.shaftVelocity());
else // else do swing-up
// sets 40% of the maximal voltage to the motor in order to swing up
target_voltage = -_sign(pendulum.getVelocity())*motor.voltage_limit*0.4;
// set the target voltage to the motor
motor.move(target_voltage);
// restart the counter
loop_count=0;
}
}
// function constraining the angle in between -pi and pi, in degrees -180 and 180
float constrainAngle(float x){
x = fmod(x + M_PI, _2PI);
if (x < 0)
x += _2PI;
return x - M_PI;
}
// LQR stabilization controller functions
// calculating the voltage that needs to be set to the motor in order to stabilize the pendulum
float controllerLQR(float p_angle, float p_vel, float m_vel){
// if angle controllable
// calculate the control law
// LQR controller u = k*x
// - k = [40, 7, 0.3]
// - x = [pendulum angle, pendulum velocity, motor velocity]'
float u = 40*p_angle + 7*p_vel + 0.3*m_vel;
// limit the voltage set to the motor
if(abs(u) > motor.voltage_limit*0.7) u = sign(u)*motor.voltage_limit*0.7;
return u;
}
This is the full code guys. You can also find it in the Arduino
directory of the git repo.
I hope you found this readme (even though very long) helpful and I hope you will be successful in realizing this project as well. It is very very cool!
Feel free to leave the issue if you experience any problems setting up this project!