simplefoc / Arduino-FOC-reaction-wheel-inverted-pendulum

Reaction wheel inverted pendulum project based on the Arduino Simple FOC library and SimpleFOC shied.
MIT License
121 stars 34 forks source link
arduino bldc-motors field-oriented-control foc foc-algorithm inverted-pendulum simple user-friendly

Arduino field oriented control (FOC) reaction wheel inverted pendulum

Test Compile

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:

YouTube demonstration video :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:

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.

Readme structure

What are the necessary components?

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.

3D printed parts

This pendulum project has three 3d printed parts. You can find them in the CAD files > STL directory. They are:

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.

Hardware parts

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$

Electrical components

BLDC motor

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$

Position sensors

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:

BLDC driver

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$

Table mount

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

Connecting all the components

In this project all the electronics components are:

Arduino UNO Arduino SimpleFOCShield 2x AMT 103 encoder iPower GM4108-120T

Encoder 1 (motor)

Encoder 2 (pendulum)

Pinout restriction
Arduino UNO doesn't have enough hardware interrupt pins for two encoders therefore we need to use the software interrupt library.

Motor

Alignment
Motor phases a,b,c and encoder channels A and B 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 phase a and b of the motor, that should be enough.

Arduino code

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>

Encoder 1 (motor) code

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.

Encoder 2 (pendulum) code

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.

Motor code

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.

Control algorithm code

The control algorithm is divided in two stages. Stabilization and swing-up.

Stabilization

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:

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 algorithm motor.loopFOC() will run ~1ms but the control algorithm and the function motor.move() will be downsampled to ~25ms.

Swing-up

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.

The integration

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!

Full Arduino 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!

Contact

Feel free to leave the issue if you experience any problems setting up this project!