This is the minimal Arduino example of the Simple FOC arduino library intended for mostly for easier experiemtation and modification!
├───arduino_foc_minimal_encoder # Arduino minimal code for running a motor with Encoder
│
└───arduino_foc_minimal_magnetic # Arduino minimal code for running a motor with magentic sensor
# AS5048/47
Each of the examples will give you the opportunity to change the PI velocity parameters P
and I
, Low pass filter time constant Tf
, change the control loop in real time and check the average loop execution time, all from the serial terminal.
PI controller parameters change:
- P value : Prefix P (ex. P0.1)
- I value : Prefix I (ex. I0.1)
Velocity filter:
- Tf value : Prefix F (ex. F0.001)
Average loop execution time:
- Type T
Control loop type:
- C0 - angle control
- C1 - velocity control
- C2 - voltage control
Initial parameters:
PI velocity P: 0.20, I: 20.00, Low passs filter Tf: 0.0000
Branch | Description | Status |
---|---|---|
master | Stable and tested library version | |
dev | Developement library version | |
minimal | Minimal Arduino example with integrated library |
- Installation
- Electrical connecitons and schematic
- Code explanation and examples
- Future work and work in progress
- Contact
Depending on if you want to use this library as the plug and play Arduino library or you want to get insight in the algorithm and make changes simply there are two ways to install this code.
The simplest way to get hold of the library is direclty by using Arduino IDE and its integrated Library Manager. Just serarch for Simple FOC
library and install the lates version.
If you don't want to use the Arduino IDE and Library manager you can direclty download the library from this website.
-
Simplest way to do it is to download the
zip
aerchieve directly on the top of this webiste. Click first onClone or Download
and then onDownload ZIP
. Once you have the zip ardhieve downloaded, unzip it and place it in your Arduino Libraries forlder. On Windows it is usually inDocuments > Arduino > libraries
.
Now reopen your Arduino IDE and you should have the library examples inFile > Examples > Simple FOC
. -
If you are more experienced with the terminal you can open your terminal in the Arduino libraries folder direclty and clone the Arduino FOC git repsitory:
git clone https://github.com/askuric/Arduino-FOC.git
Now reopen your Arduino IDE and you should have the library examples in
File > Examples > Simple FOC
.
To download the minmial verison of Simple FOC intended for those willing to experiment and extend the code I suggest using this version over the full library. This code is completely indepenedet and you can run it as any other Arduino Schetch without the need for any libraries. The code is place in the minimal branch.
-
You can download it directly form the minimal branch by clicking on the
Clone or Download > Download ZIP
. Then you just unzip it and open the schetch in Arduino IDE. -
You can also clone it using the terminal:
git clone -b minimal https://github.com/askuric/Arduino-FOC.git
Then you just open it with the Arduino IDE and run it.
All you need for this project is:
- Brushless DC (BLDC) motor
- BLDC driver
- Position sensor
- Arduino
This library is compatible with any 3 phase BLDC motor out there. Feel free to choose anything that suites your applications. The most tests have been done using gimbal morots (up to 2A). Examples:
This library will be compatible with the most of the 3 phase bldc motor dirvers. Such as L6234, DRV8305 or L293. Examples:
- L6234 driver Drotek, Ebay
- Alternatively the library supports the arduino based gimbal controllers such as: HMBGC V2.2 (Ebay)
- Arduino Simple FOC shield
This library supports two types of position sensors: Encoder and Magnetic sensor.
Encoders are by far most popular position sensors, both in industry and in hobby community. The main benefits are the precision, standardisation and very low noise level. The main problem with encoders is the efficiency.
Examples:
- Optical Encoder
- Magnetic Encoders
Magentic position sensor has many benefits over the encoders:
- Very efficient position calculation ( no counting )
- Time of execution doesn't depend on velocity or number of sensors
- No need for interrupt hardware
- Absolute position value
- Very low price
- Very simple to mount
Examples:
- AS5048 | 16384cpr | ~15$ Aliexpress
- AS5047 | 16384cpr | ~15$ Mouser Youtube
At this moment we are developing an open source version of Arduin shiled specifically for FOC motor control. We already have prototypes of the board and we are in the testing phase. We will be coming out with the details very soon!
- Plug and play capability with the Arduino Simple FOC library
- Price in the range of $20-$40
- Gerber files and BOM available Open Source
- Stackable: running at least 2 motors in the same time
Let me know if you are interested! [email protected] You can explore the 3D model of the board in the PDF form.
The code is simple enough to be run on Arudino Uno board.
- Encoder channels
A
andB
are connected to the Arduino's external intrrupt pins2
and3
. - Optionally if your encoder has
index
signal you can connect it to any available pin, figure shows pin4
.- For Arudino UNO and similar broads which dont have 3 hardware interrupts, if you can choose, preferably connect index pin to pins
A0-A5
due to the interrupt rutine, it will have better performance (but any other pin will work as well). - Othervise if you are using different board and have 3 hardware interrupt pins connect the index pin to one of them.
- For Arudino UNO and similar broads which dont have 3 hardware interrupts, if you can choose, preferably connect index pin to pins
- Connected to the arduino pins
9
,10
and11
(you can use also pins5
and6
). - Additionally you can connect the
enable
pin to the any digital pin of the arduino the picture shows pin8
but this is optional. You can connect the driver enable directly to 5v. - Make sure you connect the common ground of the power supply and your Arduino
- Motor phases
a
,b
andc
are connected directly to the driver outputs - Motor phases
a
,b
,c
and encoder channelsA
andB
have to be oriented right for the algorightm to work. But don't worry about it too much. Connect it in initialy as you wish and then if it doesnt move reverse pahsea
andb
of the motor, that should be enogh.
- Magnetic sensors SPI interface singals
SCK
,MISO
andMOSI
are connected to the Arduino'sSPI
pins (Arduino UNO13
,12
and11
).- If the application requires more than one sensor all of them are connected to the same pins of theArudino.
- The
chip select
pin is connected to the desired pin. Each sensor connected to the same Arduino has to have unique chip select pin.
- Connected to the arduino pins
3
,5
and6
(you can use also pin9
and10
, pin11
is taken by the SPI interface). - Additionally you can connect the
enable
pin to the any digital pin of the arduino the picture shows pin2
but this is optional. You can connect the driver enable directly to 5v. - Make sure you connect the common ground of the power supply and your Arduino
- Motor phases
a
,b
andc
are connected directly to the driver outputs - Motor phases
a
,b
,c
and the magnetic sensor counting direciton have to be oriented right for the algorightm to work. But don't worry about it too much. Connect it initialy as you wish and then if the motor locks in place inversea
andb
line of the motor.
To use HMBGC controller for vector control (FOC) you need to connect motor to one of the motor terminals and connect the Encoder. The shema of connection is shown on the figures above, I also took a (very bad) picture of my setup.
Since HMBGC doesn't have acces to the arduinos external interrupt pins 2
and 3
and additionally we only have acces to the analog pins, we need to read the encoder using the software interrupt. To show the functionallity we provide one example of the HMBGC code (HMBGC_example.ino
) using the PciManager library.
- Encoder channels
A
andB
are connected to the pinsA0
andA1
. - Optionally if your encoder has
index
signal you can connect it to any available pin, figure shows pinA2
.
- Motor phases
a
,b
andc
are connected directly to the driver outputs - Motor phases
a
,b
,c
and encoder channelsA
andB
have to be oriented right for the algorightm to work. But don't worry about it too much. Connect it in initialy as you wish and then if it doesnt move reverse pahsea
andb
of the motor, that should be enogh.
HMBGC board doesn't support magnetic sensors because it doesn't have necessary SPI infrastructure.
The code is organised into a library. The library contains main BLDC motor class BLDCmotor
and two sensor classes Endcoder
and MagneticSensor
. BLDCmotor
contains all the necessary FOC algorithm funcitons as well as PI controllers for the velocity and angle control. Encoder
deals with the encoder interupt funcitons, calcualtes motor angle and velocity ( using the Mixed Time Frequency Method). The Encoder
class will support any type of otpical and magnetic encoder. MagneticEncoder
class deals with all the necessary communication and calculation infrastructure to handle the magnetic position sensors such as AS5048 and similar.
To initialise the encoder you need to provide the encoder A
and B
channel pins, encoder PPR
and optionally index
pin.
// Encoder(int encA, int encB , int cpr, int index)
// - encA, encB - encoder A and B pins
// - ppr - impulses per rotation (cpr=ppr*4)
// - index pin - (optional input)
Encoder encoder = Encoder(2, 3, 8192, A0);
Next important feature of the encoder is enabling or disabling the Quadrature
more. If the Encoder is run in the quadratue more its number of impulses per rotation(PPR
) is quadrupled by detecting each CHANGE
of the signals A
and B
- CPR = 4xPPR
. In some applicaitons, when the encoder PPR
is high it can be too much for the Arudino to handle so it is preferable not to use Quadrature
mode. By default all the encoders use Quadrature
mode. If you would like to enable or disable this paramter do it in the Arduino setup funciton by running:
// check if you need internal pullups
// Quadrature::ENABLE - CPR = 4xPPR - default
// Quadrature::DISABLE - CPR = PPR
encoder.quadrature = Quadrature::ENABLE;
Additionally the encoder has one more important parameters which is whether you want to use Arduino's internal pullup or you have external one. That is set by changing the value of the encoder.pullup
variuable. The default value is set to Pullup::EXTERN
// check if you need internal pullups
// Pullup::EXTERN - external pullup added - dafault
// Pullup::INTERN - needs internal arduino pullup
encoder.pullup = Pullup::EXTERN;
There are two ways you can run encoders with Simple FOC libtrary.
- Using Arduino hardware external interrupt - for Arduino UNO pins
2
and3
- Using software pin chnage interrupt by using a library such as PciManager library
Using the hardware external interrupts usualy results in a bit better and more realible performance but software interrupts will work very good as well.
Arduino UNO has two hadrware external interrupt pins, pin 2
and 3
. And in order to use its functionallities the encoder channels A
and B
will have to be connected exacly on these pins.
Simple FOC Encoder
class already has implemented initialisation and encoder A
and B
channel callbacks.
All you need to do is define two funcitons doA()
and doB()
, the buffering functions of encoder callback funcitons encoder.handleA()
and encoder.handleB()
.
// interrupt ruotine intialisation
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
And supply those functions to the encoder interrupt init fucntion encoder.enableInterrupts()
// enable encoder hardware interrupts
encoder.enableInterrupts(doA, doB)
You can name the buffering funcitons as you wish. It is just important to supply them to the encoder.init()
funciton. This procedure is a tradeoff in between scalability and simplicity. This allows you to have more than one encoder connected to the same arduino. All you need to do is to instantiate new Encoder
class and create new buffer functions. For example:
// encoder 1
Encoder enc1 = Encoder(...);
void doA1(){enc1.handleA();}
void doB1(){enc1.handleB();}
// encoder 2
Encoder enc2 = Encoder(...);
void doA2(){enc2.handleA();}
void doB2(){enc2.handleB();}
void setup(){
...
enc1.init();
enc1.enableInterrupts(doA1,doB1);
enc2.init();
enc2.enableInterrupts(doA2,doB2);
...
}
In order to read index pin efficienlty Simple FOC algorithm enables you to use the same approach as for the channels A
and B
. First you need to provide the Encoder
class the index pin number:
Encoder encoder = Encoder(pinA, pinB, cpr, index_pin);
If you are using Arduino board such as Arduino Mega and simialar and if you have more tha 2 hadrware interrupts you can connect your index pin to the hadrware interrupt pin (example Arduino Mega pin 21
). Your code will look like:
Encoder encoder = Encoder(2,3,600,A0);
// A and B interrupt rutine
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
void doIndex(){encoder.handleIndex();}
void setup(){
...
encoder.enableInterrupts(doA,doB,doIndex);
...
}
The function enableInterrupts
will handle all the intialisation for you.
If yo are using Arduino UNO to run this algorithm and you do not have enough hardware interrupt pins you will need to use software interrupt library such as PciManager library. Arduino UNO code for using an encoder with index can be:
Encoder encoder = Encoder(2,3,600,A0);
// A and B interrupt rutine
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
void doIndex(){encoder.handleIndex();}
// softaware interrupt listener for index pin
PciListenerImp listenerIndex(encoder.index_pin, doIndex);
void setup(){
...
// hardware interrupts for A and B
encoder.enableInterrupts(doA,doB);
// software interrupt for index
PciManager.registerListener(&listenerIndex);
...
}
The same procedure can be done for pins A
and B
if your application makes you run out of the hardware interrupt pins. Software interrupts are very powerfull and produce very comparable results to the hardware interupts, but in general they tend to have a bit worse performance. index
pin produces an interrupt once per rotation, therefore it is not critical, so software or hardware interrupt doesn't change too much in terms of performance.
To explore better the encoder algorithm an example is provided encoder_example.ino
.
If you are not able to access your pins 2
and 3
of your Arduino UNO or if you want to use more than none encoder you will have to use the software interrupt approach.
I suggest using the PciManager library.
The stps of using this library in code are very similar to harware interrupt.
The SimpleFOC Encoder
class still provides you with all the callbacks A
, B
and Index
channels but the Simple FOC library will not initialise the interrupts for you.
In order to use the PCIManager
library you will need to include it in your code:
#include <PciManager.h>
#include <PciListenerImp.h>
Next step is the same as before, you will just intialise the new Encoder
instance.
Encoder encoder = Encoder(10, 11, 8192);
// A and B interrupt callback buffers
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
Then you declare listeners PciListenerImp
:
// encoder interrupt init
PciListenerImp listenerA(encoder.pinA, doA);
PciListenerImp listenerB(encoder.pinB, doB);
Finally, after running encoder.init()
you skip the call of the encoder.enableInterrupts()
and call the PCIManager
library to register the interrupts for all the encoder channels.
// initialise encoder hardware
encoder.init();
// interrupt intitialisation
PciManager.registerListener(&listenerA);
PciManager.registerListener(&listenerB);
And that is it, it is very simple. It if you wnat more than one encoder, you just initialise the new class instance, create the new A
and B
callbacks, intialise the new listeners. Here is a quick example:
// encoder 1
Encoder enc1 = Encoder(9, 10, 8192);
void doA1(){enc1.handleA();}
void doB1(){enc1.handleB();}
PciListenerImp listA1(enc1.pinA, doA1);
PciListenerImp listB1(enc1.pinB, doB1);
// encoder 2
Encoder enc2 = Encoder(13, 12, 8192);
void doA2(){enc2.handleA();}
void doB2(){enc2.handleB();}
PciListenerImp listA2(enc2.pinA, doA2);
PciListenerImp listB2(enc2.pinB, doB2);
void setup(){
...
// encoder 1
enc1.init();
PciManager.registerListener(&listA1);
PciManager.registerListener(&listB1);
// encoder 2
enc2.init();
PciManager.registerListener(&listA2);
PciManager.registerListener(&listB2);
...
}
You can look into the HMBGC_example.ino
ecxample to see this code in action.
Enabling index pin in the case of the software interrupt is very simple. You just need to provide it to the Encoder
class intialisation as additional parameter.
Encoder encoder = Encoder(pinA, pinB, cpr, index_pin);
Afterward you create the same type of callback buffering function as for A
and B
channels and using the PCIManager
tools initialise and register the listener for the index
channel as for the A
and B
. Here is a quick example:
xample:
// class init
Encoder encoder = Encoder(9, 10, 8192,11);
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
void doIndex(){encoder.handleIndex();}
// listeners init
PciListenerImp listenerA(encoder.pinA, doA);
PciListenerImp listenerB(encoder.pinB, doB);
PciListenerImp listenerIndex(encoder.index_pin, doIndex);
void setup(){
...
// enable the hardware
enc1.init();
// enable interrupt
PciManager.registerListener(&listenerA);
PciManager.registerListener(&listenerB);
PciManager.registerListener(&listenerIndex);
...
}
In order to use your magnetic position sensor with Simple FOC library first create an instance of the MagneticSensor
class:
// MagneticSensor(int cs, float _cpr, int _angle_register)
// cs - SPI chip select pin
// _cpr - counts per revolution
// _angle_register - (optional) angle read register - default 0x3FFF
MagneticSensor sensor = MagneticSensor(10, 16384, 0x3FFF);
The parameters of the class is the chip select
pin number you connected your sensor to, the range of your sensor (counter value for full rotation) and your angle register
number telling the library which register value should it ask the sensor for in order to retrieve the angle value. The default angle_register
number is set to 0x3FFF
as it is the angle register for most of the low cost AS5x4x sensors.
Finally after the inialisalisation the only thing you need to do afterwards is to call the init()
function. This function prepares the SPI interface and initialises the sensor hardware. So your magnetic sensor intialisation code will look like:
MagneticSensor sensor = MagneticSensor(10, 16384, 0x3FFF);
void loop(){
...
sensor.init();
...
}
If you wish to use more than one magnetic sensor, make sure you connect their chip select
pins to different arduino pins and follow the same idea as above, here is a simple example:
MagneticSensor sensor1 = MagneticSensor(10, 16384, 0x3FFF);
MagneticSensor sensor1 = MagneticSensor(9, 16384, 0x3FFF);
void loop(){
...
sensor1.init();
sensor2.init();
...
}
Please check the magnetic_sensor_only_example.ino
example to see more about it.
To intialise the motor you need to input the pwm
pins, number of pole pairs
and optionally driver enable
pin.
// BLDCMotor( int phA, int phB, int phC, int pp, int en)
// - phA, phB, phC - motor A,B,C phase pwm pins
// - pp - pole pair number
// - enable pin - (optional input)
BLDCMotor motor = BLDCMotor(9, 10, 11, 11, 8);
If you are not sure what your pole_paris
number is I included an example code to estimate your pole_paris
number in the examples find_pole_pairs_number.ino
. I hope it helps.
To finalise the motor setup the sensor is added to the motor and the init
function is called.
// link the motor to the sensor
// either Encoder class or MagenticSensor class
motor.linkSensor(&sensor);
// intialise motor
motor.init();
The default power_supply_voltage
value is set to 12V
. If you set your power supply to some other vlaue, chnage it here for the control loops to adapt.
// power supply voltage
motor.power_supply_voltage = 12;
The power_supply_voltage
value tells the FOC algorithm what is the maximum voltage it can output. Additioanlly since the FOC algotihm implemented in the Simple FOC library uses sinusoidal voltages the magnitudes of the sine waves exiting the Drvier circuit is going to be [-power_supply_voltage/2, power_supply_voltage/2]
.
The SimpleFOC library gives you the choice of using 4 different plug and play control loops:
- voltage control loop
- velocity control loop
- angle control loop
You set it by changing the motor.controller
variable. If you want to control the motor angle you will set the controller
to ControlType::angle
, if you seek the DC motor behavior behaviour by controlling the voltage use ControlType::voltage
, if you wish to control motor angular velocity ControlType::velocity
.
// set FOC loop to be used
// ControlType::voltage
// ControlType::velocity
// ControlType::angle
motor.controller = ControlType::angle;
This control loop allows you to run the BLDC motor as it is simple DC motor using Park transformation. This mode is enabled by:
// voltage control loop
motor.controller = ControlType::voltage;
You rcan test this algoithm by running the example voltage_control.ino
.
The FOC algorithm reads the angle a from the motor and sets appropriate ua, ub and uc voltages such to always have 90 degree angle in between the magnetic fields of the permanent magents in rotor and the stator. What is exaclty the principle of the DC motor.
This control loop will give you the motor which spins freely with the velocity depending on the voltage
$U_q$ you set and the disturbance it is facing. It will turn slower if you try to hold it.
This control loop allows you to spin your BLDC motor with desired velocity. This mode is enabled by:
// velocity control loop
motor.controller = ControlType::velocity;
You can test this algorithm by running the example velocity_control.ino
.
The velocity control is created by adding a PI velocity controller. This controller reads the motor velocity v, filteres it to vf and sets the uq voltage to the motor in a such maner that it reaches and maintains the target velocity vd, set by the user.
To change the parameters of your PI controller to reach desired behaiour you can change motor.PI_velocity
structure:
// contoller configuration based on the controll type
// velocity PI controller parameters
// default P=0.5 I = 10
motor.PI_velocity.P = 0.2;
motor.PI_velocity.I = 20;
//defualt voltage_power_supply/2
motor.PI_velocity.voltage_limit = 6;
// jerk control using voltage voltage ramp
// default value is 300 volts per sec ~ 0.3V per millisecond
motor.PI_velocity.voltage_ramp = 1000;
// velocity low pass filtering
// default 5ms - try different values to see what is the best.
// the lower the less filtered
motor.LPF_velocity.Tf = 0.01;
The parameters of the PI controller are proportional gain P
, integral gain I
, voltage limit voltage_limit
and voltage_ramp
.
- The
voltage_limit
parameter is intended if, for some reason, you wish to limit the voltage that can be sent to your motor. - In general by raising the proportional gain
P
your motor controller will be more reactive, but too much will make it unstable. Setting it to0
will disable the proportional part of the controller. - The same goes for integral gain
I
the higher it is the faster motors reaction to disturbance will be, but too large value will make it unstable. Setting it to0
will disable the integral part of the controller. - The
voltage_ramp
value it intended to reduce the maximal change of the voltage value which is sent to the motor. The higher the value the PI controller will be able to change faster the Uq value. The lower the value the smaller the possible change and the less responsive your controller becomes. The value of this parameter is set to beVolts per second[V/s
or in other words how many volts can your controller raise the voltage in one time unit. If you set yourvoltage_ramp
value to10 V/s
, and on average your contol loop will run each1ms
. Your controller will be able to chnage the Uq value each time10[V/s]*0.001[s] = 0.01V
waht is not a lot.
Additionally, in order to smooth out the velocity measuement Simple FOC library has implemented the velocity low pass filter. Low pass filters are standard form of signal smoothing, and it only has one parameter - filtering time constant Tf
.
- The lower the value the less influence the filter has. If you put
Tf
to0
you basically remove the filter completely. The exactTf
value for specific implementation is hard guess in advance, but in general the range of values ofTf
will be somewhere form0
to0.5
seconds.
In order to get optimal performance you will have to fiddle a bit with with the parameters. :)
Transfer funciton of the PI contorller this library implements is:
Continiuos PI is discretized using Tustin transform. The final discrete equation becomes:
Where the u(k) is the control signal (voltage Uq in our case) in moment k, e(k),e(k-1) is the tracking error in current moment k and previous step k-1. Tracking error presents the difference in between the target velocity value vd and measured velocity v.
Transfer funciton of the Low pass filter is contorller is:
In it discrete form it becomes:where vf(k) is filtered velocity value in moment k, v(k) is the measured velocity in the moment k, Tf is the filter time constant and Ts is the sampling time (or time in between executions of the equation). This low pass filter can be also written in the form:
where:
This makes it a bit more clear what the time constat Tf
of the Low pass filter stands for. If your sample time is around 1millisecond (for arduino UNO this can be taken as an average) then setting the
Tf
value to Tf = 0.01
will result in:
alpha = 0.01/(0.01 + 0.001) = 0.91
Which means that your actual velocity measurement v will influence the filtered value vf with the coeficient 1-alpha = 0.09
which is going to smooth the velocity values considerably (maybe even too muuch, depends of the application).
This control loop allows you to move your BLDC motor to the desired angle in real time. This mode is enabled by:
// angle control loop
motor.controller = ControlType::angle;

You can test this algorithm by running the example angle_control.ino
.
The angle control loop is done by adding one more control loop in cascade on the velocity control loop like showed on the figure above. The loop is closed by using simple P controller. The controller reads the angle a from the motor and determins which velocity vd the motor should move to reach desire angle ad set by the user. And then the velocity controller reads the current filtered velocity from the motor vf and sets the voltage uq that is neaded to reach the velocity vd, set by the angle loop.
To tune this control loop you can set the parameters to both angle P controller and velocity PI controller.
// contoller configuration based on the controll type
// velocity PI controller parameters
// default P=0.5 I = 10
motor.PI_velocity.P = 0.2;
motor.PI_velocity.I = 20;
//defualt voltage_power_supply/2
motor.PI_velocity.voltage_limit = 6;
// jerk control using voltage voltage ramp
// default value is 300 volts per sec ~ 0.3V per millisecond
motor.PI_velocity.voltage_ramp = 1000;
// velocity low pass filtering
// default 5ms - try different values to see what is the best.
// the lower the less filtered
motor.LPF_velocity.Tf = 0.01;
// angle P controller
// default P=20
motor.P_angle.P = 20;
// maximal velocity of the poisiiton control
// default 20
motor.P_angle.velocity_limit = 4;
It is important to paramter both velocity PI and angle P controller to have the optimal performance.
The velocity PI controller is parametrisized by updating the motor.PI_velcity
structure as expalined before.
- Rough rule should be to lower the proportional gain
P
in order to achieve less vibrations. - You probably wont have to touch the
I
value.
The angle P controller can be updated by changign the motor.P_angle
structure.
- Roughly proportional gain
P
will make it more responsive, but too high value will make it unstable.
For the angle control you will be able to see the influence of the velocity LPF filter as well. But the Tf
value should not change much form the velocity control. So once you have it tuned for the velocity loop you can leave it as is.
Additionally you can configure the velocity_limit
value of the controller. This value prevents the contorller to set too high velocities
- If you make your
velocity_limit
very low your motor will be moving in between desired positions with exactly this velocity. If you keep it high, you will not notice that this variable even exists. :D
Finally, each application is a bit different and the chances are you will have to tune the controller values a bit to reach desired behaviour.
Finding the encoder index is performed only if the constructor of the Encoder
class has been provided with the index
pin. The search is performed by setting a constant velocity of the motor until it reaches the index pin. To set the desired searching velocity alter the paramterer:
// index search velocity - default 1rad/s
motor.index_search_velocity = 2;
This velocity control loop is implemented exaclty the same as velocity control loop but it has different contorller paramters which can be set by:
// index search PI contoller parameters
// default K=0.5 Ti = 0.01
motor.PI_velocity_index_search.P = 0.1;
motor.PI_velocity_index_search.I = 0.01;
motor.PI_velocity_index_search.voltage_limit = 3;
// jerk control using voltage voltage ramp
// default value is 100 volts per sec ~ 0.1V per millisecond
motor.PI_velocity_index_search.voltage_ramp = 300;
If you are having problems during the finding procedure, try tuning the PI controller constants. The same parameters as the PI_velocity
should work well, but you can put it a bit more conservative to avoid high jumps.
After the motor and encoder are intialised and the driver and control loops are configured you intialise the FOC algorithm.
// align encoder and start FOC
motor.initFOC();
This function aligns encoder and motor zero positions and intialises FOC variables. It is intended to be run in the setup
function of the Arudino. After the call of this funciton FOC is ready to start following your instructions.
The real time execution of the Arduino Simple FOC library is govenred by two funcitons motor.loopFOC()
and motor.move(float target)
.
// iterative setting FOC pahse voltage
// the faster you run this funciton the better
// in arduino loop it should have ~1kHz
// the best would be to be in ~10kHz range
motor.loopFOC();
The funciton loopFOC()
gets the current motor angle from the encoder, turns in into the electrical angle and computes Clarke transfrom to set the desired Uq voltage to the motor. Basically it implements the funcitonality of the voltage control loop.
- The faster you can run this funciton the better
- In the empty arduino loop it runs at ~1kHz but idealy it would be around ~10kHz
// iterative function setting the outter loop target
// velocity, position or voltage
// this funciton can be run at much lower frequency than loopFOC funciton
// it can go as low as ~50Hz
motor.move(target);
The move()
method executes the control loops of the algorihtm. If is governed by the motor.controller
variable. It executes eigther pure voltage loop, velocity loop or angle loop.
It receives one parameter BLDCMotor::move(float target)
which is current user define target value.
- If the user runs velocity loop,
move
funciton will interprettarget
as the target velocity vd. - If the user runs angle loop,
move
will interprettarget
parameter as the target anglead. - If the user runs the voltage loop,
move
funciton will interpret thetarget
parameter as voltage ud.
At this point because we are oriented to simplicity we did not implement synchornious version of this code. Uing timer interrupt. The main reason for the moment is that Arduino UNO doesn't have enough timers to run it. But in future we are planning to include this functionality.
Examples folder structure
│ examples
│ ├─── encoder examples # EXMAPLES OF ENCODER APPLICATIONS
│ │ ├───angle_control # example of angle control loop with configuraiton
│ │ ├───change_direction # simple motor changing velocity direction in real time
│ │ ├───encoder_example # simple example of encoder usage
│ │ ├───find_pole_pairs_number # simple code example estimating pole pair number of the motor
│ │ ├───HMBGC_example # example of code to be used with HMBGC controller with
│ │ ├───velocity_control # example of velocity control loop with configuraiton
│ │ ├───voltage_control # example of the voltage control loop with configuraiton
│ │ └───velocity_PI_tuning # code example of tuning velcity controller by using serial terminal
│ │
│ └─── magnetic sensor examples # EXMAPLES OF MAGENETIC SENSOR APPLICATIONS AS5047/48
│ ├───angle_control # example of angle control loop with configuraiton
│ ├───change_direction # simple motor changing velocity direction in real time
│ ├───find_pole_pairs_number # simple code example estimating pole pair number of the motor
│ ├───magnetic_sensor_only_example # simple example of magnetic sensor usage
│ ├───velocity_control # example of velocity control loop with configuraiton
│ ├───voltage_control # example of the voltage control loop with configuraiton
│ └───velocity_PI_tuning # code example of tuning velcity controller by using serial terminal
│ src
│ │
│ ├─ SimpleFOC.h # SimpleFOC library include file
│ │
│ ├─ BLDCMotor.cpp/h # BLDCMotor class implementing all the FOC operations
│ │
│ ├─ Sensor.h # Abstract Sensor class that all the sensors implement
│ ├─ Encoder.cpp/h # Enocder class implementing the Quadrature encoder operations
│ ├─ MagneticSensor.cpp/h # class implementing SPI angle read and interface for AS5047/8 type sensors
│ │
│ └─ FOCutils.cpp/h # Utility functions
BLDCMotor
clsss supports debugging using Serial
port which is enabled by:
motor.useDebugging(Serial);
before running motor.init()
.
The class will output its status during the intialisation of the motor and the FOC. Enabling tyhe debugger will not directly influence the real-time performance. By default the class will stop its debugging output once it goes to the main loop.
To debug control loop exection in the examples we added a funciton motor.monitor()
which log the motor variables to the serial port. The funciton logs different variables based for differenc control loops.
// utility function intended to be used with serial plotter to monitor motor variables
// significantly slowing the execution down!!!!
void BLDCMotor::monitor() {
if(!debugger) return;
switch (controller) {
case ControlType::velocity:
debugger->print(voltage_q);
debugger->print("\t");
debugger->print(shaft_velocity_sp);
debugger->print("\t");
debugger->println(shaft_velocity);
break;
case ControlType::angle:
debugger->print(voltage_q);
debugger->print("\t");
debugger->print(shaft_angle_sp);
debugger->print("\t");
debugger->println(shaft_angle);
break;
case ControlType::voltage:
debugger->print(voltage_q);
debugger->print("\t");
debugger->print(shaft_angle);
debugger->print("\t");
debugger->println(shaft_velocity);
break;
}
}
The intention of this method is to be called in main loop funciton along the loopFOC()
and move()
funciton. Thsi funciton is going to impaire the execution perfomance and reduce the sampling frequency so therefore take it in consideration when running the code.
If you wish to implement you own debugging functions or just output the motor variables to the Serial
terminal here are the public varaibles of the BLDCMotor
class.
class BLDCMotor
{
public:
...
// current motor angle
float shaft_angle;
// current motor velocity
float shaft_velocity;
// current target velocity
float shaft_velocity_sp;
// current target angle
float shaft_angle_sp;
// current voltage u_q set
float voltage_q;
...
}
Additionally it is possible to use encoder api directly to get the encoder angle and velocity.
class Encoder{
public:
// shaft velocity getter
float getVelocity();
// shaft angle getter
float getAngle();
}
As well as magnetic sensor's api, to get the sensor's angle and velocity.
class MagneticSensor{
public:
// shaft velocity getter
float getVelocity();
// shaft angle getter
float getAngle();
}
- Proper introduction of the Arudino FOC Shield V1.2
- Publish a video tutorial fir using the library and the samples
- Initial video with simple demonstration
- Coding setup and procedure video
- Two motors running on HMBGC example
- ....
- Implement Space Vector Modulation method
- Pure SVM
- PWM SVM
- Implement support for MOSFET control low and high pairs
- Make the library accesible in the Arduino Library Manager
- Make minimal version of the arduino code - all in one arduino file
- Encoder index proper implementation
- Enable more dirver types
- Make support for magnetic encoder AS5048 ABI
- Make support for magnetic encoder AS5048 SPI
- Add support for acceleration ramping
- Velocity Low pass filter
- Timer interrupt execution rather than in the
loop()
- FAIL: Perfromance not improved
- Sine wave lookup table implementation
Please do not hesitate to leave an issue or contact me direclty by email. I will be very happy to hear your experiences. [email protected]